class OneGadget::Fetcher::Base
Define common methods for gadget fetchers.
Attributes
file[R]
The absolute path of glibc. @return [String] The filename.
Public Class Methods
new(file)
click to toggle source
Instantiate a fetcher object. @param [String] file Absolute path of target libc.
# File lib/one_gadget/fetchers/base.rb, line 16 def initialize(file) @file = file arch = self.class.name.split('::').last.downcase.to_sym @objdump = Objdump.new(file, arch) @objdump.extra_options = objdump_options end
Public Instance Methods
candidates(&block)
click to toggle source
Fetch candidates that end with call exec*.
Give a block to filter gadget candidates. @yieldparam [String] cand
Is this candidate valid?
@yieldreturn [Boolean]
True for valid.
@return [Array<String>]
Each +String+ returned is multi-lines of assembly code.
# File lib/one_gadget/fetchers/base.rb, line 51 def candidates(&block) call_regexp = "#{call_str}.*<exec[^+]*>$" cands = [] `#{@objdump.command}|egrep '#{call_regexp}' -B 30`.split('--').each do |cand| lines = cand.lines.map(&:strip).reject(&:empty?) # split with call_regexp loop do idx = lines.index { |l| l =~ /#{call_regexp}/ } break if idx.nil? cands << lines.shift(idx + 1).join("\n") end end # remove all jmps cands = slice_prefix(cands, &method(:branch?)) cands.select!(&block) if block_given? cands end
find()
click to toggle source
Do find gadgets in glibc. @return [Array<OneGadget::Gadget::Gadget>] Gadgets found.
# File lib/one_gadget/fetchers/base.rb, line 25 def find candidates.map do |cand| lines = cand.lines # use processor to find which can lead to a valid one-gadget call. gadgets = [] (lines.size - 2).downto(0) do |i| processor = emulate(lines[i..-1]) options = resolve(processor) next if options.nil? # impossible be a gadget offset = offset_of(lines[i]) gadgets << OneGadget::Gadget::Gadget.new(offset, **options) end gadgets end.flatten end
Private Instance Methods
branch?(_str)
click to toggle source
If str contains a branch instruction.
# File lib/one_gadget/fetchers/base.rb, line 191 def branch?(_str); raise NotImplementedError end
call_str()
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 167 def call_str; raise NotImplementedError end
check_envp(processor, arg) { |cons| ... }
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 129 def check_envp(processor, arg) # if str starts with [[ and is global var, # believe it is environ # if starts with [[ but not global, drop it. return global_var?(arg) if arg.start_with?('[[') # normal cons = check_execve_arg(processor, arg) return nil if cons.nil? yield cons end
check_execve_arg(processor, arg)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 116 def check_execve_arg(processor, arg) if arg.start_with?(processor.sp) # arg = sp+<num> # in this case, the only constraint is [sp+<num>] == NULL num = Integer(arg[processor.sp.size..-1]) slot = processor.stack[num].to_s return if global_var?(slot) "#{slot} == NULL" else "[#{arg}] == NULL || #{arg} == NULL" end end
emulate(cmds)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 170 def emulate(cmds) cmds.each_with_object(emulator) { |cmd, obj| break obj unless obj.process(cmd) } end
emulator()
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 174 def emulator; raise NotImplementedError end
global_var?(_str)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 158 def global_var?(_str); raise NotImplementedError end
objdump_options()
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 177 def objdump_options [] end
offset_of(assembly)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 199 def offset_of(assembly) assembly.scan(/^([\da-f]+):/)[0][0].to_i(16) end
resolve(processor)
click to toggle source
Generating constraints to be a valid gadget. @param [OneGadget::Emulators::Processor] processor The processor after executing the gadgets. @return [Hash{Symbol => Array<String>, String}?]
The options to create a {OneGadget::Gadget::Gadget} object. Keys might be: 1. constraints: Array<String> List of constraints. 2. effect: String Result function call of this gadget. If the constraints can never be satisfied, +nil+ is returned.
# File lib/one_gadget/fetchers/base.rb, line 80 def resolve(processor) call = processor.registers[processor.pc].to_s # This costs cheaper, so check first. # check call execve / execl return unless %w[execve execl].any? { |n| call.include?(n) } # check first argument contains /bin/sh # since the logic is different between amd64 and i386, # invoke str_bin_sh? for checking return unless str_bin_sh?(processor.argument(0).to_s) if call.include?('execve') resolve_execve(processor) elsif call.include?('execl') resolve_execl(processor) end end
resolve_execl(processor)
click to toggle source
Resolve +call execl+ case.
# File lib/one_gadget/fetchers/base.rb, line 143 def resolve_execl(processor) args = [] arg = processor.argument(1).to_s if str_sh?(arg) arg = processor.argument(2).to_s args << '"sh"' end return nil if global_var?(arg) # we don't want base-related constraints args << arg cons = processor.constraints + ["#{arg} == NULL"] # now arg is the constraint. { constraints: cons, effect: %(execl("/bin/sh", #{args.join(', ')})) } end
resolve_execve(processor)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 97 def resolve_execve(processor) # arg[1] == NULL || [arg[1]] == NULL # arg[2] == NULL || [arg[2]] == NULL || arg[2] == envp arg1 = processor.argument(1).to_s arg2 = processor.argument(2).to_s cons = processor.constraints cons << check_execve_arg(processor, arg1) return nil unless cons.all? envp = 'environ' return nil unless check_envp(processor, arg2) do |c| cons << c envp = arg2 end { constraints: cons, effect: %(execve("/bin/sh", #{arg1}, #{envp})) } end
slice_prefix(cands, &block)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 181 def slice_prefix(cands, &block) cands.map do |cand| lines = cand.lines to_rm = lines[0...-1].rindex(&block) lines = lines[to_rm + 1..-1] unless to_rm.nil? lines.join end end
str_bin_sh?(_str)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 161 def str_bin_sh?(_str); raise NotImplementedError end
str_offset(str)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 194 def str_offset(str) IO.binread(file).index("#{str}\x00") || raise(Error::ArgumentError, "File #{file.inspect} doesn't contain string #{str.inspect}, not glibc?") end
str_sh?(_str)
click to toggle source
# File lib/one_gadget/fetchers/base.rb, line 164 def str_sh?(_str); raise NotImplementedError end