Module: PWN::Plugins::BinaryAnalysis
- Defined in:
- lib/pwn/plugins/binary_analysis.rb
Overview
Bounded, argv-only read-only backend shared by normalized analysis adapters.
Class Method Summary collapse
- .analyze(opts = {}) ⇒ Object
- .authors ⇒ Object
- .available?(opts = {}) ⇒ Boolean
- .help ⇒ Object
- .run(opts = {}) ⇒ Object
Class Method Details
.analyze(opts = {}) ⇒ Object
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
# File 'lib/pwn/plugins/binary_analysis.rb', line 43 public_class_method def self.analyze(opts = {}) path = File.realpath(File.(opts[:path].to_s)) raise ArgumentError, 'binary must be a regular file' unless File.file?(path) result = { backend: 'binutils', status: 'degraded', risk_level: 'low', path: path, sha256: Digest::SHA256.file(path).hexdigest, functions: [], strings: [], imports: [], symbols: [], types: [], warnings: ['heavy analysis backend unavailable or binutils requested; no decompiled C or complete xrefs'] } { symbols: ['nm', '-a', path], imports: ['readelf', '--dyn-syms', '--wide', path], strings: ['strings', '-a', '-t', 'x', path], disassembly: ['objdump', '-d', path] }.each do |key, command| text = run(argv: command, timeout: opts.fetch(:timeout, 60)) result[key] = case key when :symbols text.lines.filter_map do |line| m = line.match(/^([0-9a-fA-F]+)\s+(\w)\s+(.+)$/) { address: m[1].to_i(16), type: m[2], name: m[3] } if m end when :imports text.lines.filter_map { |line| { name: line.split.last } if line.include?(' UND ') && line.split.length >= 8 } when :strings text.lines.filter_map do |line| m = line.match(/^\s*([0-9a-f]+) (.*)$/) { offset: m[1].to_i(16), string: m[2] } if m end else text end rescue StandardError => e result[:warnings] << "#{command.first}: #{e.}" end result[:functions] = result.fetch(:disassembly, '').scan(/^([0-9a-fA-F]+) <([^>]+)>:/).map { |address, name| { address: address.to_i(16), name: name } } result end |
.authors ⇒ Object
73 74 75 |
# File 'lib/pwn/plugins/binary_analysis.rb', line 73 public_class_method def self. "AUTHOR(S):\n 0day Inc. <[email protected]>\n" end |
.available?(opts = {}) ⇒ Boolean
11 12 13 14 |
# File 'lib/pwn/plugins/binary_analysis.rb', line 11 public_class_method def self.available?(opts = {}) name = opts[:name] ENV.fetch('PATH', '').split(File::PATH_SEPARATOR).any? { |dir| File.executable?(File.join(dir, name)) && !File.directory?(File.join(dir, name)) } end |
.help ⇒ Object
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 |
# File 'lib/pwn/plugins/binary_analysis.rb', line 77 public_class_method def self.help puts "USAGE: # Check whether a named backend executable is present on PATH. #{self}.available?( name: 'required - backend executable filename to search on PATH' ) # Run a bounded argv subprocess without shell interpolation. #{self}.run( argv: 'required - array of executable name and separate argument strings', limit: 'optional - maximum captured subprocess output bytes before termination', timeout: 'optional - positive subprocess or HTTP deadline in seconds' ) # Read binary symbols, strings, imports and disassembly through binutils. #{self}.analyze( path: 'required - filesystem path to the local artifact or binary', timeout: 'optional - positive subprocess or HTTP deadline in seconds' ) # Display module authors. #{self}.authors " constants.sort end |
.run(opts = {}) ⇒ Object
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
# File 'lib/pwn/plugins/binary_analysis.rb', line 16 public_class_method def self.run(opts = {}) argv = opts[:argv] timeout = opts.fetch(:timeout, 60) limit = opts.fetch(:limit, 2_000_000) output = +'' Open3.popen2e(*argv, pgroup: true) do |stdin, stream, waiter| stdin.close begin Timeout.timeout(timeout) do loop do output << stream.readpartial(16_384) raise 'binary backend output limit exceeded' if output.bytesize > limit rescue EOFError break end raise "backend exited #{waiter.value.exitstatus}: #{output[-1000, 1000] || output}" unless waiter.value.success? end ensure if waiter.alive? Process.kill('KILL', -waiter.pid) waiter.join end end end output.encode('UTF-8', invalid: :replace, undef: :replace) end |