Skip to content

Commit

Permalink
Support JSON output (#225)
Browse files Browse the repository at this point in the history
  • Loading branch information
david942j authored Oct 4, 2024
1 parent 0b3a207 commit 6946c1b
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 18 deletions.
47 changes: 33 additions & 14 deletions lib/one_gadget/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ module CLI
# Help message.
USAGE = 'Usage: one_gadget <FILE|-b BuildID> [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

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 <pretty|raw|json>.', '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.',
Expand Down Expand Up @@ -176,14 +181,19 @@ def handle_script(gadgets, script)

# Writes gadgets to stdout.
# @param [Array<OneGadget::Gadget::Gadget>] 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

Expand All @@ -199,7 +209,7 @@ def error(msg)
# @param [String] libc_file
# @param [Array<OneGadget::Gadget::Gadget>] 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)
Expand All @@ -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
Expand Down
22 changes: 19 additions & 3 deletions lib/one_gadget/gadget.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
# frozen_string_literal: true

require 'json'

require 'one_gadget/abi'
require 'one_gadget/emulators/lambda'
require 'one_gadget/error'
Expand Down Expand Up @@ -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 ")
Expand All @@ -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
Expand Down
4 changes: 3 additions & 1 deletion spec/bin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 <pretty|raw|json>.
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.
Expand Down
16 changes: 16 additions & 0 deletions spec/cli_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
18 changes: 18 additions & 0 deletions spec/gadget_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 6946c1b

Please sign in to comment.