Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support JSON output #225

Merged
merged 5 commits into from
Oct 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading