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

Feature: Search nearest gadgets #76

Merged
merged 14 commits into from
May 4, 2019
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
6 changes: 2 additions & 4 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,8 @@ GEM
jaro_winkler (1.5.2)
json (2.1.0)
parallel (1.17.0)
parser (2.6.2.1)
parser (2.6.3.0)
ast (~> 2.4.0)
psych (3.1.0)
rainbow (3.0.0)
rake (12.3.2)
rspec (3.8.0)
Expand All @@ -34,11 +33,10 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.8.0)
rspec-support (3.8.0)
rubocop (0.67.2)
rubocop (0.68.1)
jaro_winkler (~> 1.5.1)
parallel (~> 1.10)
parser (>= 2.5, != 2.5.1.1)
psych (>= 3.1.0)
rainbow (>= 2.2.2, < 4.0)
ruby-progressbar (~> 1.7)
unicode-display_width (>= 1.4.0, < 1.6)
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ $ one_gadget
# Usage: one_gadget [file] [options]
# -b, --build-id BuildID BuildID[sha1] of libc.
# -f, --[no-]force-file Force search gadgets in file instead of build id first.
# -n, --near FUNCTIONS/FILE Order gadgets by their distance to the given functions or to the GOT functions of the given file.
# -l, --level OUTPUT_LEVEL The output level.
# OneGadget automatically selects gadgets with higher successful probability.
# Increase this level to ask OneGadget show more gadgets it found.
Expand Down
28 changes: 27 additions & 1 deletion bin/one_gadget
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ parser = OptionParser.new do |opts|
options[:force_file] = b
end

opts.on('-n', '--near FUNCTIONS/FILE', 'Order gadgets by their distance to the given functions'\
' or to the GOT functions of the given file.') do |n|
options[:near] = n
end

opts.on('-l', '--level OUTPUT_LEVEL', Integer, 'The output level.',
'OneGadget automatically selects gadgets with higher successful probability.',
'Increase this level to ask OneGadget show more gadgets it found.',
Expand Down Expand Up @@ -74,12 +79,33 @@ end

if options[:script]
gadgets.map(&:offset).each do |offset|
OneGadget::Logger.info("Trying #{OneGadget::Helper.colorize(format('0x%x', offset), sev: :integer)}...\n")
OneGadget::Logger.info("Trying #{OneGadget::Helper.colored_hex(offset)}...\n")
execute(options[:script], offset)
end
exit(0)
end

if !options[:build_id] && ARGV[0] && options[:near]
functions = if File.file?(options[:near]) && OneGadget::Helper.valid_elf_file?(options[:near])
OneGadget::Helper.got_functions(options[:near])
else
options[:near].split(',').map(&:strip)
end
function_offsets = OneGadget::Helper.function_offsets(ARGV[0], functions)
function_offsets.each do |function, offset|
colored_offset = OneGadget::Helper.colored_hex(offset)
OneGadget::Logger.warn("Gadgets near #{OneGadget::Helper.colorize(function)}(#{colored_offset}):\n")
gadgets.sort_by! { |gadget| (gadget.offset - offset).abs }
if options[:raw]
puts gadgets.map(&:offset).join(' ')
else
puts gadgets.map(&:inspect).join("\n")
end
puts "\n"
end
exit(0)
end

if options[:raw]
puts gadgets.map(&:offset).join(' ')
else
Expand Down
30 changes: 30 additions & 0 deletions lib/one_gadget/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ def colorize(str, sev: :normal_s)
"#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}"
end

def colored_hex(val)
colorize(hex(val), sev: :integer)
end

# Fetch the latest release version's tag name.
# @return [String] The tag name, in form +vX.X.X+.
def latest_tag
Expand Down Expand Up @@ -312,5 +316,31 @@ def arch_specific_objdump(arch)
i386: 'i686-linux-gnu-objdump'
}[arch]
end

# Returns the names of functions from the file's global offset table.
# @param [String] file
# @return [Array<String>]
def got_functions(file)
arch = architecture(file)
objdump_bin = find_objdump(arch)
`#{::Shellwords.join([objdump_bin, '-T', file])} | grep -iPo 'GLIBC_.+?\\s+\\K.*'`.split
end

# Returns a dictionary that maps functions to their offsets.
# @param [String] file
# @param [Array<String>] functions
# @return [Hash{String => Integer}]
def function_offsets(file, functions)
arch = architecture(file)
objdump_bin = find_objdump(arch)
objdump_cmd = ::Shellwords.join([objdump_bin, '-T', file])
functions.map! { |f| '\\b' + f + '\\b' }
ret = {}
`#{objdump_cmd} | grep -iP '(#{functions.join('|')})'`.lines.map(&:chomp).each do |line|
tokens = line.split
ret[tokens[-1]] = tokens[0].to_i(16)
end
ret
end
end
end
73 changes: 73 additions & 0 deletions spec/bin_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
Usage: one_gadget [file] [options]
-b, --build-id BuildID BuildID[sha1] of libc.
-f, --[no-]force-file Force search gadgets in file instead of build id first.
-n, --near FUNCTIONS/FILE Order gadgets by their distance to the given functions or to the GOT functions of the given file.
-l, --level OUTPUT_LEVEL The output level.
OneGadget automatically selects gadgets with higher successful probability.
Increase this level to ask OneGadget show more gadgets it found.
Expand All @@ -20,4 +21,76 @@
--version Current gem version.
EOS
end

context 'near' do
before do
skip_unless_objdump
end

it 'functions' do
file = data_path('libc-2.24-8cba3297f538691eb1875be62986993c004f3f4d.so')
expect(`env ruby -I#{@lib} #{@bin} -n system -l 1 #{file}`).to eq <<-EOS
[OneGadget] Gadgets near system(0x3f4d0):
0x3f3aa execve("/bin/sh", rsp+0x30, environ)
constraints:
[rsp+0x30] == NULL

0x3f356 execve("/bin/sh", rsp+0x30, environ)
constraints:
rax == NULL

0xb8a38 execve("/bin/sh", r13, r12)
constraints:
[r13] == NULL || r13 == NULL
[r12] == NULL || r12 == NULL

0xd67e5 execve("/bin/sh", rsp+0x70, environ)
constraints:
[rsp+0x70] == NULL

0xd67f1 execve("/bin/sh", rsi, [rax])
constraints:
[rsi] == NULL || rsi == NULL
[[rax]] == NULL || [rax] == NULL

EOS
end

it 'functions' do
file = data_path('libc-2.24-8cba3297f538691eb1875be62986993c004f3f4d.so')
expect(`env ruby -I#{@lib} #{@bin} -n wscanf,pwrite -l 1 -r #{file}`).to eq <<-EOS
[OneGadget] Gadgets near pwrite(0xd9b70):
878577 878565 756280 258986 258902

[OneGadget] Gadgets near wscanf(0x6afe0):
258986 258902 756280 878565 878577

EOS
end

it 'file' do
bin_file = data_path('test_near_file.elf')
lib_file = data_path('libc-2.24-8cba3297f538691eb1875be62986993c004f3f4d.so')
expect(`env ruby -I#{@lib} #{@bin} -n #{bin_file} -l 1 -r #{lib_file}`).to eq <<-EOS
[OneGadget] Gadgets near exit(0x359d0):
258902 258986 756280 878565 878577

[OneGadget] Gadgets near puts(0x68fe0):
258986 258902 756280 878565 878577

[OneGadget] Gadgets near printf(0x4f1e0):
258986 258902 756280 878565 878577

[OneGadget] Gadgets near strlen(0x80420):
756280 258986 258902 878565 878577

[OneGadget] Gadgets near __cxa_finalize(0x35c70):
258902 258986 756280 878565 878577

[OneGadget] Gadgets near __libc_start_main(0x201a0):
258902 258986 756280 878565 878577

EOS
end
end
end
Binary file added spec/data/test_near_file.elf
Binary file not shown.
22 changes: 22 additions & 0 deletions spec/helper_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@
expect(described_class.colorize('123', sev: :integer)).to eq "\e[38;5;189m123\e[0m"
end

it 'colorize' do
allow(described_class).to receive(:color_enabled?).and_return(true)
expect(described_class.colored_hex(-0x137)).to eq "\e[38;5;189m-0x137\e[0m"
end

it 'url_request' do
val = :val
expect { hook_logger { val = described_class.url_request('oao') } }.to output(<<-EOS.strip).to_stdout
Expand All @@ -43,4 +48,21 @@
expect(described_class.architecture(f.path)).to be :unknown
end
end

it 'got_function' do
skip_unless_objdump

file = data_path('test_near_file.elf')
expect(described_class.got_functions(file)).to eq %w[puts strlen printf __libc_start_main exit __cxa_finalize]
end

it 'function_offsets' do
skip_unless_objdump

result = {
'system' => 0x45390,
'printf' => 0x55800
}
expect(described_class.function_offsets(@libcpath, %w[system printf])).to eq result
end
end