diff --git a/lib/one_gadget/cli.rb b/lib/one_gadget/cli.rb index d4ae7c2..72f4c61 100644 --- a/lib/one_gadget/cli.rb +++ b/lib/one_gadget/cli.rb @@ -12,7 +12,7 @@ module CLI # Help message. USAGE = 'Usage: one_gadget [options]' # Default options. - DEFAULT_OPTIONS = { raw: false, force_file: false, level: 0, base: 0 }.freeze + DEFAULT_OPTIONS = { format: :pretty, force_file: false, level: 0, base: 0 }.freeze module_function @@ -66,7 +66,7 @@ def handle_gadgets(gadgets, libc_file) return handle_script(gadgets, @options[:script]) if @options[:script] return handle_near(libc_file, gadgets, @options[:near]) if @options[:near] - display_gadgets(gadgets, @options[:raw]) + display_gadgets(gadgets, @options[:format]) end # Displays libc information given BuildID. @@ -124,8 +124,13 @@ def parser @options[:near] = n end - opts.on('-r', '--[no-]raw', 'Output gadgets offset only, split with one space.') do |v| - @options[:raw] = v + opts.on('-o FORMAT', '--output-format FORMAT', %i[pretty raw json], + 'Output format. FORMAT should be one of .', 'Default: pretty') do |o| + @options[:format] = o + end + + opts.on('-r', '--raw', 'Alias of -o raw. Output gadgets offset only, split with one space.') do |_| + @options[:format] = :raw end opts.on('-s', '--script exploit-script', 'Run exploit script with all possible gadgets.', @@ -176,14 +181,19 @@ def handle_script(gadgets, script) # Writes gadgets to stdout. # @param [Array] gadgets - # @param [Boolean] raw - # In raw mode, only the offset of gadgets are printed. + # @param [Symbol] format + # :raw - Only the offset of gadgets are printed. + # :pretty - Colorful and human-readable format. + # :json - In JSON format. # @return [true] - def display_gadgets(gadgets, raw) - if raw + def display_gadgets(gadgets, format) + case format + when :raw show(gadgets.map(&:value).join(' ')) - else + when :pretty show(gadgets.map(&:inspect).join("\n")) + when :json + show(gadgets.to_json) end end @@ -199,7 +209,7 @@ def error(msg) # @param [String] libc_file # @param [Array] gadgets # @param [String] near - # This can be name of functions or an ELF file. + # Either name of functions or path to an ELF file. # - Use one comma without spaces to specify a list of functions: +printf,scanf,free+. # - Path to an ELF file and take its GOT functions to process: +/bin/ls+ def handle_near(libc_file, gadgets, near) @@ -213,10 +223,19 @@ def handle_near(libc_file, gadgets, near) function_offsets = OneGadget::Helper.function_offsets(libc_file, functions) return error('No functions for processing') if function_offsets.empty? - function_offsets.each do |function, offset| - colored_offset = OneGadget::Helper.colored_hex(offset) - OneGadget::Logger.warn("Gadgets near #{OneGadget::Helper.colorize(function)}(#{colored_offset}):") - display_gadgets(gadgets.sort_by { |gadget| (gadget.offset - offset).abs }, @options[:raw]) + collection = function_offsets.map do |function, offset| + { + near: function, + near_offset: offset, + gadgets: gadgets.sort_by { |gadget| (gadget.offset - offset).abs } + } + end + return show(collection.to_json) if @options[:format] == :json + + collection.each do |c| + colored_offset = OneGadget::Helper.colored_hex(c[:near_offset]) + OneGadget::Logger.warn("Gadgets near #{OneGadget::Helper.colorize(c[:near])}(#{colored_offset}):") + display_gadgets(c[:gadgets], @options[:format]) show("\n") end true diff --git a/lib/one_gadget/gadget.rb b/lib/one_gadget/gadget.rb index 3b41635..3426a26 100644 --- a/lib/one_gadget/gadget.rb +++ b/lib/one_gadget/gadget.rb @@ -1,5 +1,7 @@ # frozen_string_literal: true +require 'json' + require 'one_gadget/abi' require 'one_gadget/emulators/lambda' require 'one_gadget/error' @@ -28,13 +30,12 @@ def initialize(offset, **options) @base = 0 @offset = offset @constraints = options[:constraints] || [] - @effect = options[:effect] + @effect = options[:effect] || '' end # Show gadget in a pretty way. def inspect - str = OneGadget::Helper.hex(value) - str += effect ? " #{effect}\n" : "\n" + str = "#{OneGadget::Helper.hex(value)} #{effect}\n" unless constraints.empty? str += "#{OneGadget::Helper.colorize('constraints')}:\n " str += merge_constraints.join("\n ") @@ -46,6 +47,21 @@ def inspect "#{str}\n" end + # @return [Hash] + def to_obj + { + value:, + effect:, + constraints: + } + end + + # To have this class can be serialized in JSON. + # @return [String] + def to_json(*) + to_obj.to_json + end + # @return [Integer] # Returns +base+ plus +offset+. def value diff --git a/spec/bin_spec.rb b/spec/bin_spec.rb index 14a7714..d34b106 100644 --- a/spec/bin_spec.rb +++ b/spec/bin_spec.rb @@ -16,7 +16,9 @@ Increase this level to ask OneGadget show more gadgets it found. Default: 0 -n, --near FUNCTIONS/FILE Order gadgets by their distance to the given functions or to the GOT functions of the given file. - -r, --[no-]raw Output gadgets offset only, split with one space. + -o, --output-format FORMAT Output format. FORMAT should be one of . + Default: pretty + -r, --raw Alias of -o raw. Output gadgets offset only, split with one space. -s, --script exploit-script Run exploit script with all possible gadgets. The script will be run as 'exploit-script $offset'. --info BuildID Show version information given BuildID. diff --git a/spec/cli_spec.rb b/spec/cli_spec.rb index 3ca89da..3e1c2e3 100644 --- a/spec/cli_spec.rb +++ b/spec/cli_spec.rb @@ -201,4 +201,20 @@ EOS end end + + context 'json' do + it 'normal case' do + argv = b_param + %w[--output-format json] + expect { hook_logger { described_class.work(argv) } }.to output(<<-EOS).to_stdout +[{"value":324286,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["rsp & 0xf == 0","writable: rsp+0x50","rcx == NULL || {rcx, \\"-c\\", r12, NULL} is a valid argv"]},{"value":324293,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["rsp & 0xf == 0","writable: rsp+0x50","rcx == NULL || {rcx, rax, r12, NULL} is a valid argv"]},{"value":324386,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["[rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv"]},{"value":1090444,"effect":"execve(\\"/bin/sh\\", rsp+0x70, environ)","constraints":["[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv"]}] + EOS + end + + it 'with near' do + argv = [libc_file] + %w[-o json --near exit,mkdir] + expect { hook_logger { described_class.work(argv) } }.to output(<<-EOS).to_stdout +[{"near":"exit","near_offset":274720,"gadgets":[{"value":324286,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["rsp & 0xf == 0","writable: rsp+0x50","rcx == NULL || {rcx, \\"-c\\", r12, NULL} is a valid argv"]},{"value":324293,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["rsp & 0xf == 0","writable: rsp+0x50","rcx == NULL || {rcx, rax, r12, NULL} is a valid argv"]},{"value":324386,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["[rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv"]},{"value":1090444,"effect":"execve(\\"/bin/sh\\", rsp+0x70, environ)","constraints":["[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv"]}]},{"near":"mkdir","near_offset":1113008,"gadgets":[{"value":1090444,"effect":"execve(\\"/bin/sh\\", rsp+0x70, environ)","constraints":["[rsp+0x70] == NULL || {[rsp+0x70], [rsp+0x78], [rsp+0x80], [rsp+0x88], ...} is a valid argv"]},{"value":324386,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["[rsp+0x40] == NULL || {[rsp+0x40], [rsp+0x48], [rsp+0x50], [rsp+0x58], ...} is a valid argv"]},{"value":324293,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["rsp & 0xf == 0","writable: rsp+0x50","rcx == NULL || {rcx, rax, r12, NULL} is a valid argv"]},{"value":324286,"effect":"execve(\\"/bin/sh\\", rsp+0x40, environ)","constraints":["rsp & 0xf == 0","writable: rsp+0x50","rcx == NULL || {rcx, \\"-c\\", r12, NULL} is a valid argv"]}]}] + EOS + end + end end diff --git a/spec/gadget_spec.rb b/spec/gadget_spec.rb index b4989a2..2185fc4 100644 --- a/spec/gadget_spec.rb +++ b/spec/gadget_spec.rb @@ -38,6 +38,24 @@ end end + context 'to_obj' do + it 'simple' do + gadget = OneGadget::Gadget::Gadget.new(0x1234, constraints: ['[rsp+0x30] == NULL', 'rax == 0'], + effect: 'execve("/bin/sh", rsp+0x30, rax)') + expect(gadget.to_obj).to eq({ + value: 0x1234, + effect: 'execve("/bin/sh", rsp+0x30, rax)', + constraints: ['[rsp+0x30] == NULL', 'rax == 0'] + }) + end + + it 'to_json' do + gadget = OneGadget::Gadget::Gadget.new(0x1234, constraints: ['everything is fine'], + effect: 'side') + expect(gadget.to_json).to eq('{"value":4660,"effect":"side","constraints":["everything is fine"]}') + end + end + context 'score' do def new(cons) OneGadget::Gadget::Gadget.new(0, constraints: cons)