From bc35ada51e57935a5726c2497460c810ff141696 Mon Sep 17 00:00:00 2001 From: david942j Date: Thu, 13 Sep 2018 10:27:46 +0800 Subject: [PATCH] Upgrade rubocop to 0.59 (#44) --- Gemfile.lock | 4 ++-- lib/one_gadget.rb | 2 ++ lib/one_gadget/emulators/instruction.rb | 1 + lib/one_gadget/emulators/lambda.rb | 5 +++++ lib/one_gadget/emulators/processor.rb | 1 + lib/one_gadget/emulators/x86.rb | 11 +++++++++++ lib/one_gadget/fetcher.rb | 1 + lib/one_gadget/fetchers/amd64.rb | 2 ++ lib/one_gadget/fetchers/base.rb | 8 ++++++++ lib/one_gadget/fetchers/i386.rb | 3 +++ lib/one_gadget/gadget.rb | 4 ++++ lib/one_gadget/helper.rb | 6 ++++++ lib/one_gadget/logger.rb | 1 + lib/one_gadget/update.rb | 3 +++ one_gadget.gemspec | 2 +- tasks/builds/generate.rake | 7 +++++++ tasks/readme.rake | 2 ++ 17 files changed, 60 insertions(+), 3 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index a46cd5f..9872f0f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -34,7 +34,7 @@ GEM diff-lcs (>= 1.2.0, < 2.0) rspec-support (~> 3.8.0) rspec-support (3.8.0) - rubocop (0.58.2) + rubocop (0.59.0) jaro_winkler (~> 1.5.1) parallel (~> 1.10) parser (>= 2.5, != 2.5.1.1) @@ -58,7 +58,7 @@ DEPENDENCIES one_gadget! rake (~> 12.3) rspec (~> 3.7) - rubocop (~> 0.58) + rubocop (~> 0.59) simplecov (~> 0.16.1) yard (~> 0.9) diff --git a/lib/one_gadget.rb b/lib/one_gadget.rb index 90be154..cc51ba3 100644 --- a/lib/one_gadget.rb +++ b/lib/one_gadget.rb @@ -52,6 +52,7 @@ module OneGadget def try_from_build(file) build_id = OneGadget::Helper.build_id_of(file) return unless build_id + OneGadget::Fetcher.from_build_id(build_id, remote: false) end @@ -59,6 +60,7 @@ module OneGadget def refine_gadgets(gadgets, level) return [] if gadgets.empty? return gadgets if level > 0 # currently only supports level > 0 or not + # remain gadgets with the fewest constraints best = gadgets.map { |g| g.constraints.size }.min gadgets.select { |g| g.constraints.size == best } diff --git a/lib/one_gadget/emulators/instruction.rb b/lib/one_gadget/emulators/instruction.rb index 0ee3d44..d9e02e1 100644 --- a/lib/one_gadget/emulators/instruction.rb +++ b/lib/one_gadget/emulators/instruction.rb @@ -26,6 +26,7 @@ module OneGadget if argc >= 0 && args.size != argc raise Error::ArgumentError, "Incorrect argument number in #{cmd}, expect: #{argc}" end + args.map do |arg| arg.gsub(/XMMWORD|QWORD|DWORD|WORD|BYTE|PTR/, '').strip end diff --git a/lib/one_gadget/emulators/lambda.rb b/lib/one_gadget/emulators/lambda.rb index 7ffa117..b5d2f1a 100644 --- a/lib/one_gadget/emulators/lambda.rb +++ b/lib/one_gadget/emulators/lambda.rb @@ -25,6 +25,7 @@ module OneGadget # @return [Lambda] The result. def +(other) raise Error::ArgumentError, 'Expect other to be Numeric.' unless other.is_a?(Numeric) + if deref_count > 0 ret = Lambda.new(self) else @@ -53,6 +54,7 @@ module OneGadget # @raise [Error::ArgumentError] When this object cannot be referenced anymore. def ref! raise Error::ArgumentError, 'Cannot reference anymore!' if @deref_count <= 0 + @deref_count -= 1 end @@ -84,6 +86,7 @@ module OneGadget def evaluate(context) raise Error::ArgumentError, "Can't eval #{self}" if deref_count > 0 raise Error::ArgumentError, "Can't eval #{self}" if obj && !context.key?(obj) + context[obj] + immi end @@ -107,10 +110,12 @@ module OneGadget deref_count = 1 end return Integer(arg) if OneGadget::Helper.integer?(arg) + sign = arg =~ /[+-]/ val = 0 if sign raise Error::ArgumentError, "Not support #{arg}" unless OneGadget::Helper.integer?(arg[sign..-1]) + val = Integer(arg.slice!(sign..-1)) end obj = predefined[arg] || Lambda.new(arg) diff --git a/lib/one_gadget/emulators/processor.rb b/lib/one_gadget/emulators/processor.rb index 4b8851b..8253af8 100644 --- a/lib/one_gadget/emulators/processor.rb +++ b/lib/one_gadget/emulators/processor.rb @@ -22,6 +22,7 @@ module OneGadget def parse(cmd) inst = instructions.find { |i| i.match?(cmd) } raise Error::ArgumentError, "Not implemented instruction in #{cmd}" if inst.nil? + [inst, inst.fetch_args(cmd)] end diff --git a/lib/one_gadget/emulators/x86.rb b/lib/one_gadget/emulators/x86.rb index 148f6a0..6599432 100644 --- a/lib/one_gadget/emulators/x86.rb +++ b/lib/one_gadget/emulators/x86.rb @@ -31,6 +31,7 @@ module OneGadget inst, args = parse(cmd) # return registers[pc] = args[0] if inst.inst == 'call' return true if inst.inst == 'jmp' # believe the fetcher has handled jmp. + sym = "inst_#{inst.inst}".to_sym __send__(sym, *args) != :fail end @@ -87,8 +88,10 @@ module OneGadget else # Just ignore strange case... return unless tar.include?(sp) + tar = OneGadget::Emulators::Lambda.parse(tar, predefined: registers) return if tar.deref_count != 1 # should not happen + tar.ref! stack[tar.evaluate(eval_dict)] = src end @@ -128,9 +131,11 @@ module OneGadget # check if (tar, src) in form (xmm*, [sp+*]) def check_xmm_sp(tar, src) return yield unless tar.start_with?('xmm') && register?(tar) && src.include?(sp) + tar_lm = OneGadget::Emulators::Lambda.parse(tar, predefined: registers) src_lm = OneGadget::Emulators::Lambda.parse(src, predefined: registers) return yield if src_lm.deref_count != 1 + src_lm.ref! [tar_lm, src_lm] end @@ -146,12 +151,14 @@ module OneGadget registers[sp] -= size_t cur_top = registers[sp].evaluate(eval_dict) raise Error::ArgumentError, "Corrupted stack pointer: #{cur_top}" unless cur_top.is_a?(Integer) + stack[cur_top] = val end def inst_xor(dst, src) # only supports dst == src raise Error::ArgumentError, 'xor operator only supports dst = src' unless dst == src + dst[0] = 'r' if self.class.bits == 64 && dst.start_with?('e') registers[dst] = 0 end @@ -164,6 +171,7 @@ module OneGadget def inst_sub(tar, src) src = OneGadget::Emulators::Lambda.parse(src, predefined: registers) raise Error::ArgumentError, "Can't handle -= of type #{src.class}" unless src.is_a?(Integer) + registers[tar] -= src end @@ -183,6 +191,7 @@ module OneGadget def inst_call(addr) # This is the last call return registers[pc] = addr if %w[execve execl].any? { |n| addr.include?(n) } + # TODO: handle some registers would be fucked after call checker = { 'sigprocmask' => {}, @@ -192,6 +201,7 @@ module OneGadget } func = checker.keys.find { |n| addr.include?(n) } return if func && checker[func].all? { |idx, sym| check_argument(idx, sym) } + # unhandled case or checker's condition fails :fail end @@ -210,6 +220,7 @@ module OneGadget def to_lambda(reg) return super unless reg =~ /^xmm\d+$/ + Array.new(128 / self.class.bits) do |i| OneGadget::Emulators::Lambda.new("#{reg}__#{i}") end diff --git a/lib/one_gadget/fetcher.rb b/lib/one_gadget/fetcher.rb index 9b46c1a..1904d32 100644 --- a/lib/one_gadget/fetcher.rb +++ b/lib/one_gadget/fetcher.rb @@ -31,6 +31,7 @@ module OneGadget i386: OneGadget::Fetcher::I386 }[arch] raise Error::UnsupportedArchitectureError, arch if klass.nil? + trim_gadgets(klass.new(file).find) end diff --git a/lib/one_gadget/fetchers/amd64.rb b/lib/one_gadget/fetchers/amd64.rb index e761a7a..32903e1 100644 --- a/lib/one_gadget/fetchers/amd64.rb +++ b/lib/one_gadget/fetchers/amd64.rb @@ -16,6 +16,7 @@ module OneGadget cands = super do |candidate| next false unless candidate.include?(bin_sh_hex) # works in x86-64 next false unless candidate.lines.last.include?('execve') # only care execve + true end cands + jmp_case_candidates # + sigaction_case_candidates @@ -34,6 +35,7 @@ module OneGadget cand = cand.lines.map(&:strip).reject(&:empty?) jmp_at = cand.index { |c| c.include?('jmp') } next nil if jmp_at.nil? + cand = cand[0..jmp_at] jmp_addr = cand.last.scan(/jmp\s+([\da-f]+)\s/)[0][0].to_i(16) dump = `#{objdump_cmd(start: jmp_addr, stop: jmp_addr + 100)}|egrep '[0-9a-f]+:'` diff --git a/lib/one_gadget/fetchers/base.rb b/lib/one_gadget/fetchers/base.rb index df2b727..b8198dc 100644 --- a/lib/one_gadget/fetchers/base.rb +++ b/lib/one_gadget/fetchers/base.rb @@ -24,6 +24,7 @@ module OneGadget 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 @@ -69,6 +70,7 @@ module OneGadget # 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') @@ -84,11 +86,13 @@ module OneGadget cons = [] 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 @@ -99,6 +103,7 @@ module OneGadget 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" @@ -110,9 +115,11 @@ module OneGadget # 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 @@ -125,6 +132,7 @@ module OneGadget args << '"sh"' end return nil if global_var?(arg) # we don't want base-related constraints + args << arg # now arg is the constraint. { constraints: ["#{arg} == NULL"], effect: %(execl("/bin/sh", #{args.join(', ')})) } diff --git a/lib/one_gadget/fetchers/i386.rb b/lib/one_gadget/fetchers/i386.rb index d7fc8dc..ef588c7 100644 --- a/lib/one_gadget/fetchers/i386.rb +++ b/lib/one_gadget/fetchers/i386.rb @@ -13,6 +13,7 @@ module OneGadget rel_sh_hex = rel_sh.to_s(16) super do |candidate| next false unless candidate.include?(rel_sh_hex) + true end end @@ -26,10 +27,12 @@ module OneGadget # first check if argument 0 is '/bin/sh' to prevent error arg0 = processor.argument(0) return nil unless str_bin_sh?(arg0.to_s) + @base_reg = arg0.deref.obj.to_s # this should be esi or ebx.. # now we can let parent to invoke global_var? res = super return if res.nil? + # unshift got constraint into cons res[:constraints].unshift("#{@base_reg} is the GOT address of libc") res diff --git a/lib/one_gadget/gadget.rb b/lib/one_gadget/gadget.rb index d91b73c..ac42417 100644 --- a/lib/one_gadget/gadget.rb +++ b/lib/one_gadget/gadget.rb @@ -54,9 +54,11 @@ module OneGadget require_all if BUILDS.empty? return BUILDS[build_id] if BUILDS.key?(build_id) return build_not_found unless remote + # fetch remote builds table = OneGadget::Helper.remote_builds.find { |c| c.include?(build_id) } return build_not_found if table.nil? # remote doesn't have this one either. + # builds found in remote! Ask update gem and download remote gadgets. OneGadget::Helper.ask_update(msg: 'The desired one-gadget can be found in lastest version!') tmp_file = OneGadget::Helper.download_build(table) @@ -79,8 +81,10 @@ module OneGadget # # ... def builds_info(build_id) raise Error::ArgumentError, "Invalid BuildID #{build_id.inspect}" if build_id =~ /[^0-9a-f]/ + files = Dir.glob(File.join(BUILDS_PATH, "*-#{build_id}*.rb")).sort return OneGadget::Logger.not_found(build_id) && nil if files.empty? + if files.size > 1 OneGadget::Logger.warn("Multiple BuildIDs match /^#{build_id}/\n") show = files.map do |f| diff --git a/lib/one_gadget/helper.rb b/lib/one_gadget/helper.rb index 6d2d67a..1bd3cd6 100644 --- a/lib/one_gadget/helper.rb +++ b/lib/one_gadget/helper.rb @@ -23,6 +23,7 @@ module OneGadget # @return [void] def verify_build_id!(build_id) return if build_id =~ /\A#{OneGadget::Helper::BUILD_ID_FORMAT}\Z/ + raise OneGadget::Error::ArgumentError, format('invalid BuildID format: %p', build_id) end @@ -71,6 +72,7 @@ module OneGadget # @raise [Error::ArgumentError] Raise exception if not a valid ELF. def verify_elf_file!(path) return if valid_elf_file?(path) + raise Error::ArgumentError, 'Not an ELF file, expected glibc as input' end @@ -102,6 +104,7 @@ module OneGadget def color_enabled? # if not set, use tty to check return $stdout.tty? if @disable_color.nil? + !@disable_color end @@ -121,6 +124,7 @@ module OneGadget # @return [String] Wrapper with color codes. def colorize(str, sev: :normal_s) return str unless color_enabled? + cc = COLOR_CODE color = cc.key?(sev) ? cc[sev] : '' "#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}" @@ -173,6 +177,7 @@ module OneGadget response = http.request(request) raise ArgumentError, "Fail to get response of #{url}" unless %w(200 302).include?(response.code) + response.code == '302' ? response['location'] : response.body rescue NoMethodError, SocketError, ArgumentError => e p e @@ -219,6 +224,7 @@ module OneGadget # hex(0, psign: true) #=> +0x0 def hex(val, psign: false) return format("#{psign ? '+' : ''}0x%x", val) if val >= 0 + format('-0x%x', -val) end diff --git a/lib/one_gadget/logger.rb b/lib/one_gadget/logger.rb index fa2d6ac..32a024c 100644 --- a/lib/one_gadget/logger.rb +++ b/lib/one_gadget/logger.rb @@ -10,6 +10,7 @@ module OneGadget prep = ' ' * 12 message = msg.lines.map.with_index do |str, i| next str if i.zero? + str.strip.empty? ? str : prep + str end color = case severity diff --git a/lib/one_gadget/update.rb b/lib/one_gadget/update.rb index e51e191..a55ef20 100644 --- a/lib/one_gadget/update.rb +++ b/lib/one_gadget/update.rb @@ -18,6 +18,7 @@ module OneGadget # @return [void] def check! return unless need_check? + FileUtils.touch(cache_file) OneGadget::Logger.info("Checking for new versions of OneGadget\n" \ "To disable this functionality, do\n$ echo never > #{CACHE_FILE}\n\n") @@ -39,12 +40,14 @@ module OneGadget cache = cache_file return false if cache.nil? # cache file fails, no update check. return false if IO.binread(cache).strip == 'never' + Time.now >= last_check + FREQUENCY end def last_check cache = cache_file return Time.now if cache.nil? + File.open(cache, &:mtime) end diff --git a/one_gadget.gemspec b/one_gadget.gemspec index d131827..f4d7272 100644 --- a/one_gadget.gemspec +++ b/one_gadget.gemspec @@ -30,7 +30,7 @@ Gem::Specification.new do |s| s.add_development_dependency 'rake', '~> 12.3' s.add_development_dependency 'rspec', '~> 3.7' - s.add_development_dependency 'rubocop', '~> 0.58' + s.add_development_dependency 'rubocop', '~> 0.59' s.add_development_dependency 'simplecov', '~> 0.16.1' s.add_development_dependency 'yard', '~> 0.9' end diff --git a/tasks/builds/generate.rake b/tasks/builds/generate.rake index 6abcd7a..c221220 100644 --- a/tasks/builds/generate.rake +++ b/tasks/builds/generate.rake @@ -18,12 +18,16 @@ namespace :builds do info = libc_info(libc_file) next failed('parse info fail') if info.nil? # error when fetching info next failed('build id not found') if info[:build_id].nil? # no .note.gnu.build.id section + version = info[:info].scan(/version ([\d.]+\d)/).flatten.first next skipped('version too old') if Gem::Version.new(version) < Gem::Version.new('2.19') + filename = File.join(path, "libc-#{version}-#{info[:build_id]}.rb") next skipped('file exists') if File.file?(filename) + gadgets = OneGadget.gadgets(file: libc_file, force_file: true, details: true, level: 100) next failed('no gadgets found') if gadgets.empty? + content = template(info, gadgets) File.open(filename, 'w') { |f| f.write(content) } puts 'done' @@ -62,11 +66,14 @@ OneGadget::Gadget.add(build_id, OFFSET, return nil unless ['Advanced Micro Devices X86-64', 'Intel 80386'].include?(arch) # let's skip amd64 with 32bit, i.e. x32 return nil if arch.start_with?('Advanced') && libc.elf_class == 32 + str = file.read st = str.index('GNU C Library') return nil if st.nil? + len = str[st..-1].index("\x00") return nil if len.nil? + fname = filename.sub('../libcdb', 'https://gitlab.com/libcdb/libcdb/blob/master') { build_id: build_id, diff --git a/tasks/readme.rake b/tasks/readme.rake index 1603fba..20364cb 100644 --- a/tasks/readme.rake +++ b/tasks/readme.rake @@ -1,6 +1,7 @@ desc 'To auto generate README.md from README.tpl' task :readme do next if ENV['CI'] + @tpl = IO.binread('README.tpl') def replace(prefix) @@ -12,6 +13,7 @@ task :readme do replace('SHELL_OUTPUT_OF') do |cmd| '$ ' + cmd + "\n" + `#{cmd}`.lines.map do |c| next "#\n" if c.strip.empty? + '# ' + c end.join end