Consider posix_spawn as one-gadgets (#183)
* emulator: implement Processor#arg_to_lambda * refine descriptions * emulator: x86: enhance movq instruction * emulator: x86: add punpcklqdq support * enhance nested Lambda.parse * emulators: support more amd64 arguments * emulators: display XMM's lambda in a programmic way * support posix_spawn * Add spec for libc-2.31
This commit is contained in:
parent
8cc0b042f1
commit
f3d63ab670
|
@ -1,7 +1,7 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module OneGadget
|
||||
# Defines the abi of different architectures.
|
||||
# Defines the ABI of different architectures.
|
||||
module ABI
|
||||
# Registers of i386.
|
||||
X86_32 = %w[eax ebx ecx edx edi esi ebp esp] + 0.upto(7).map { |i| "xmm#{i}" }
|
||||
|
@ -19,7 +19,7 @@ module OneGadget
|
|||
# Registers' name of amd64.
|
||||
# @return [Array<String>] List of registers.
|
||||
def amd64
|
||||
X86_64.uniq
|
||||
X86_64
|
||||
end
|
||||
|
||||
# Registers' name of i386.
|
||||
|
|
|
@ -49,8 +49,8 @@ module OneGadget
|
|||
def inst_add(dst, src, op2, mode = 'sxtw')
|
||||
check_register!(dst)
|
||||
|
||||
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
op2 = OneGadget::Emulators::Lambda.parse(op2, predefined: registers)
|
||||
src = arg_to_lambda(src)
|
||||
op2 = arg_to_lambda(op2)
|
||||
raise_unsupported('add', dst, src, op2) unless op2.is_a?(Integer) && mode == 'sxtw'
|
||||
|
||||
registers[dst] = src + op2
|
||||
|
@ -84,7 +84,7 @@ module OneGadget
|
|||
def inst_ldr(dst, src, index = 0)
|
||||
check_register!(dst)
|
||||
|
||||
src_l = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
src_l = arg_to_lambda(src)
|
||||
registers[dst] = src_l
|
||||
raise_unsupported('ldr', dst, src, index) unless OneGadget::Helper.integer?(index)
|
||||
|
||||
|
@ -101,28 +101,27 @@ module OneGadget
|
|||
def inst_mov(dst, src)
|
||||
check_register!(dst)
|
||||
|
||||
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
registers[dst] = src
|
||||
registers[dst] = arg_to_lambda(src)
|
||||
end
|
||||
|
||||
def inst_stp(reg1, reg2, dst)
|
||||
raise_unsupported('stp', reg1, reg2, dst) unless reg64?(reg1) && reg64?(reg2)
|
||||
|
||||
dst_l = OneGadget::Emulators::Lambda.parse(dst, predefined: registers).ref!
|
||||
dst_l = arg_to_lambda(dst).ref!
|
||||
raise_unsupported('stp', reg1, reg2, dst) unless dst_l.obj == sp && dst_l.deref_count.zero?
|
||||
|
||||
cur_top = dst_l.evaluate(eval_dict)
|
||||
stack[cur_top] = registers[reg1]
|
||||
stack[cur_top + size_t] = registers[reg2]
|
||||
|
||||
registers[sp] += OneGadget::Emulators::Lambda.parse(dst).immi if dst.end_with?('!')
|
||||
registers[sp] += arg_to_lambda(dst).immi if dst.end_with?('!')
|
||||
end
|
||||
|
||||
def inst_str(src, dst, index = 0)
|
||||
check_register!(src)
|
||||
raise_unsupported('str', src, dst, index) unless OneGadget::Helper.integer?(index)
|
||||
|
||||
dst_l = OneGadget::Emulators::Lambda.parse(dst, predefined: registers).ref!
|
||||
dst_l = arg_to_lambda(dst).ref!
|
||||
# Only stores on stack.
|
||||
if dst_l.obj == sp && dst_l.deref_count.zero?
|
||||
cur_top = dst_l.evaluate(eval_dict)
|
||||
|
|
|
@ -27,6 +27,9 @@ module OneGadget
|
|||
when 0 then registers['rdi']
|
||||
when 1 then registers['rsi']
|
||||
when 2 then registers['rdx']
|
||||
when 3 then registers['rcx']
|
||||
when 4 then registers['r8']
|
||||
when 5 then registers['r9']
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -46,13 +46,13 @@ module OneGadget
|
|||
self.+(-other)
|
||||
end
|
||||
|
||||
# Increase dereference count with 1.
|
||||
# Increase dereference count by 1.
|
||||
# @return [void]
|
||||
def deref!
|
||||
@deref_count += 1
|
||||
end
|
||||
|
||||
# Decrease dereference count with 1.
|
||||
# Decrease dereference count by 1.
|
||||
# @return [self]
|
||||
# @raise [Error::InstrutionArgumentError] When this object cannot be referenced anymore.
|
||||
def ref!
|
||||
|
@ -105,7 +105,7 @@ module OneGadget
|
|||
# @param [Hash{String => Lambda}] predefined
|
||||
# Predefined values.
|
||||
# @return [OneGadget::Emulators::Lambda, Integer]
|
||||
# If +argument+ contains number only, returns the value.
|
||||
# If +argument+ contains numbers only, returns the value.
|
||||
# Otherwise, returns a {Lambda} object.
|
||||
# @example
|
||||
# obj = Lambda.parse('[rsp+0x50]')
|
||||
|
@ -117,9 +117,17 @@ module OneGadget
|
|||
# #=> #<Lambda @obj='x0', @immi=-104, @deref_count=1>
|
||||
def parse(argument, predefined: {})
|
||||
arg = argument.dup
|
||||
return 0 if arg.empty? || arg == '!'
|
||||
return Integer(arg) if OneGadget::Helper.integer?(arg)
|
||||
|
||||
# nested []
|
||||
return parse(arg[1...arg.rindex(']')], predefined: predefined).deref if arg[0] == '['
|
||||
if arg[0] == '['
|
||||
ridx = arg.rindex(']')
|
||||
immi = parse(arg[(ridx + 1)..-1])
|
||||
lm = parse(arg[1...ridx], predefined: predefined).deref
|
||||
lm += immi unless immi.zero?
|
||||
return lm
|
||||
end
|
||||
|
||||
base, disp = mem_obj(arg)
|
||||
obj = predefined[base] || Lambda.new(base)
|
||||
|
|
|
@ -47,7 +47,7 @@ module OneGadget
|
|||
# @return [Boolean]
|
||||
def process(cmd)
|
||||
process!(cmd)
|
||||
# rescue OneGadget::Error::UnsupportedError # for debugging
|
||||
# rescue OneGadget::Error::UnsupportedError => e; p e # for debugging
|
||||
rescue OneGadget::Error::Error
|
||||
false
|
||||
end
|
||||
|
@ -115,6 +115,14 @@ module OneGadget
|
|||
OneGadget::Emulators::Lambda.new(reg)
|
||||
end
|
||||
|
||||
# Fetch the corresponding lambda value of instruction arguments from the current register sets.
|
||||
#
|
||||
# @param [String] arg The instruction argument passed to inst_* functions.
|
||||
# @return [Lambda]
|
||||
def arg_to_lambda(arg)
|
||||
OneGadget::Emulators::Lambda.parse(arg, predefined: registers)
|
||||
end
|
||||
|
||||
def raise_unsupported(inst, *args)
|
||||
raise OneGadget::Error::UnsupportedInstructionArgumentError, "#{inst} #{args.join(', ')}"
|
||||
end
|
||||
|
|
|
@ -45,14 +45,15 @@ module OneGadget
|
|||
Instruction.new('xor', 2),
|
||||
Instruction.new('movq', 2),
|
||||
Instruction.new('movaps', 2),
|
||||
Instruction.new('movhps', 2)
|
||||
Instruction.new('movhps', 2),
|
||||
Instruction.new('punpcklqdq', 2)
|
||||
]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def inst_mov(dst, src)
|
||||
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
src = arg_to_lambda(src)
|
||||
if register?(dst)
|
||||
registers[dst] = src
|
||||
else
|
||||
|
@ -60,7 +61,7 @@ module OneGadget
|
|||
# TODO(david942j): #120
|
||||
return add_writable(dst) unless dst.include?(sp)
|
||||
|
||||
dst = OneGadget::Emulators::Lambda.parse(dst, predefined: registers)
|
||||
dst = arg_to_lambda(dst)
|
||||
return if dst.deref_count != 1 # should not happen
|
||||
|
||||
dst.ref!
|
||||
|
@ -79,9 +80,17 @@ module OneGadget
|
|||
end
|
||||
end
|
||||
|
||||
# Move *src to dst[:64]
|
||||
# Move src to dst[:64]
|
||||
# Supported forms:
|
||||
# movq xmm*, [sp+*]
|
||||
# movq xmm*, reg64
|
||||
def inst_movq(dst, src)
|
||||
# XXX: here we only support `movq xmm*, [sp+*]`
|
||||
if self.class.bits == 64 && xmm_reg?(dst) && src.start_with?('r') && register?(src)
|
||||
dst = arg_to_lambda(dst)
|
||||
src = arg_to_lambda(src)
|
||||
dst[0] = src
|
||||
return
|
||||
end
|
||||
dst, src = check_xmm_sp(dst, src) { raise_unsupported('movq', dst, src) }
|
||||
off = src.evaluate(eval_dict)
|
||||
(64 / self.class.bits).times do |i|
|
||||
|
@ -89,7 +98,7 @@ module OneGadget
|
|||
end
|
||||
end
|
||||
|
||||
# Move *src to dst[64:128]
|
||||
# Move src to dst[64:128]
|
||||
def inst_movhps(dst, src)
|
||||
# XXX: here we only support `movhps xmm*, [sp+*]`
|
||||
dst, src = check_xmm_sp(dst, src) { raise_unsupported('movhps', dst, src) }
|
||||
|
@ -99,28 +108,41 @@ module OneGadget
|
|||
end
|
||||
end
|
||||
|
||||
# check if (dst, src) in form (xmm*, [sp+*])
|
||||
# check whether (dst, src) is in form (xmm*, [sp+*])
|
||||
def check_xmm_sp(dst, src)
|
||||
return yield unless dst.start_with?('xmm') && register?(dst) && src.include?(sp)
|
||||
return yield unless xmm_reg?(dst) && src.include?(sp)
|
||||
|
||||
dst_lm = OneGadget::Emulators::Lambda.parse(dst, predefined: registers)
|
||||
src_lm = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
dst_lm = arg_to_lambda(dst)
|
||||
src_lm = arg_to_lambda(src)
|
||||
return yield if src_lm.deref_count != 1
|
||||
|
||||
src_lm.ref!
|
||||
[dst_lm, src_lm]
|
||||
end
|
||||
|
||||
def xmm_reg?(reg)
|
||||
reg.start_with?('xmm') && register?(reg)
|
||||
end
|
||||
|
||||
# dst[64:128] = src[0:64]
|
||||
def inst_punpcklqdq(dst, src)
|
||||
raise_unsupported('punpcklqdq', dst, src) unless xmm_reg?(dst) && xmm_reg?(src)
|
||||
|
||||
dst = arg_to_lambda(dst)
|
||||
src = arg_to_lambda(src)
|
||||
(64 / self.class.bits).times do |i|
|
||||
dst[i + 64 / self.class.bits] = src[i]
|
||||
end
|
||||
end
|
||||
|
||||
def inst_lea(dst, src)
|
||||
check_register!(dst)
|
||||
|
||||
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
src.ref!
|
||||
registers[dst] = src
|
||||
registers[dst] = arg_to_lambda(src).ref!
|
||||
end
|
||||
|
||||
def inst_push(val)
|
||||
val = OneGadget::Emulators::Lambda.parse(val, predefined: registers)
|
||||
val = arg_to_lambda(val)
|
||||
registers[sp] -= size_t
|
||||
cur_top = registers[sp].evaluate(eval_dict)
|
||||
raise Error::InstructionArgumentError, "Corrupted stack pointer: #{cur_top}" unless cur_top.is_a?(Integer)
|
||||
|
@ -141,12 +163,12 @@ module OneGadget
|
|||
def inst_add(dst, src)
|
||||
check_register!(dst)
|
||||
|
||||
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
src = arg_to_lambda(src)
|
||||
registers[dst] += src
|
||||
end
|
||||
|
||||
def inst_sub(dst, src)
|
||||
src = OneGadget::Emulators::Lambda.parse(src, predefined: registers)
|
||||
src = arg_to_lambda(src)
|
||||
raise Error::UnsupportedInstructionArgumentError, "Unhandled -= of type #{src.class}" unless src.is_a?(Integer)
|
||||
|
||||
registers[dst] -= src
|
||||
|
@ -160,7 +182,7 @@ module OneGadget
|
|||
# because it just invokes syscall.
|
||||
def inst_call(addr)
|
||||
# This is the last call
|
||||
return registers[pc] = addr if %w[execve execl].any? { |n| addr.include?(n) }
|
||||
return registers[pc] = addr if %w[execve execl posix_spawn].any? { |n| addr.include?(n) }
|
||||
|
||||
# TODO: handle some registers would be fucked after call
|
||||
checker = {
|
||||
|
@ -177,7 +199,7 @@ module OneGadget
|
|||
end
|
||||
|
||||
def add_writable(dst)
|
||||
lmda = OneGadget::Emulators::Lambda.parse(dst, predefined: registers).ref!
|
||||
lmda = arg_to_lambda(dst).ref!
|
||||
# pc-relative addresses should be writable
|
||||
return if lmda.obj == pc
|
||||
|
||||
|
@ -188,7 +210,8 @@ module OneGadget
|
|||
return super unless reg =~ /^xmm\d+$/
|
||||
|
||||
Array.new(128 / self.class.bits) do |i|
|
||||
OneGadget::Emulators::Lambda.new("#{reg}__#{i}")
|
||||
cast = "(u#{self.class.bits})"
|
||||
OneGadget::Emulators::Lambda.new(i.zero? ? "#{cast}#{reg}" : "#{cast}(#{reg} >> #{self.class.bits * i})")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -16,6 +16,7 @@ module OneGadget
|
|||
def candidates
|
||||
# one basic block case
|
||||
cands = super do |candidate|
|
||||
next true if candidate.include?('posix_spawn@')
|
||||
next false unless candidate.include?(bin_sh_hex) # works in x86-64
|
||||
next false unless candidate.lines.last.include?('execve') # only care execve
|
||||
|
||||
|
|
|
@ -7,12 +7,12 @@ module OneGadget
|
|||
module Fetcher
|
||||
# Define common methods for gadget fetchers.
|
||||
class Base
|
||||
# The absolute path of glibc.
|
||||
# The absolute path to glibc.
|
||||
# @return [String] The filename.
|
||||
attr_reader :file
|
||||
|
||||
# Instantiate a fetcher object.
|
||||
# @param [String] file Absolute path of target libc.
|
||||
# @param [String] file Absolute path to the target libc.
|
||||
def initialize(file)
|
||||
@file = file
|
||||
arch = self.class.name.split('::').last.downcase.to_sym
|
||||
|
@ -30,7 +30,7 @@ module OneGadget
|
|||
(lines.size - 2).downto(0) do |i|
|
||||
processor = emulate(lines[i..-1])
|
||||
options = resolve(processor)
|
||||
next if options.nil? # impossible be a gadget
|
||||
next if options.nil? # impossible to be a gadget
|
||||
|
||||
offset = offset_of(lines[i])
|
||||
gadgets << OneGadget::Gadget::Gadget.new(offset, **options)
|
||||
|
@ -41,7 +41,7 @@ module OneGadget
|
|||
|
||||
# Fetch candidates that end with call exec*.
|
||||
#
|
||||
# Give a block to filter gadget candidates.
|
||||
# Provide a block to filter gadget candidates.
|
||||
# @yieldparam [String] cand
|
||||
# Is this candidate valid?
|
||||
# @yieldreturn [Boolean]
|
||||
|
@ -49,7 +49,7 @@ module OneGadget
|
|||
# @return [Array<String>]
|
||||
# Each +String+ returned is multi-lines of assembly code.
|
||||
def candidates(&block)
|
||||
call_regexp = "#{call_str}.*<exec[^+]*>$"
|
||||
call_regexp = "#{call_str}.*<(exec[^+]*|posix_spawn[^+]*)>$"
|
||||
cands = []
|
||||
`#{@objdump.command}|egrep '#{call_regexp}' -B 30`.split('--').each do |cand|
|
||||
lines = cand.lines.map(&:strip).reject(&:empty?)
|
||||
|
@ -69,7 +69,7 @@ module OneGadget
|
|||
|
||||
private
|
||||
|
||||
# Generating constraints to be a valid gadget.
|
||||
# Generating constraints for being 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.
|
||||
|
@ -79,28 +79,28 @@ module OneGadget
|
|||
# If the constraints can never be satisfied, +nil+ is returned.
|
||||
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
|
||||
return resolve_posix_spawn(processor) if call.include?('posix_spawn')
|
||||
return resolve_execve(processor) if call.include?('execve')
|
||||
return resolve_execl(processor) if call.include?('execl')
|
||||
end
|
||||
|
||||
def resolve_execve(processor)
|
||||
# arg[1] == NULL || [arg[1]] == NULL
|
||||
# arg[2] == NULL || [arg[2]] == NULL || arg[2] == envp
|
||||
arg0 = processor.argument(0).to_s
|
||||
arg1 = processor.argument(1).to_s
|
||||
arg2 = processor.argument(2).to_s
|
||||
res = resolve_execve_args(processor, arg0, arg1, arg2)
|
||||
return nil if res.nil?
|
||||
|
||||
{ constraints: res[:constraints], effect: %(execve("/bin/sh", #{arg1}, #{res[:envp]})) }
|
||||
end
|
||||
|
||||
def resolve_execve_args(processor, arg0, arg1, arg2, allow_null_argv: true)
|
||||
return unless str_bin_sh?(arg0)
|
||||
|
||||
# arg1 == NULL || [arg1] == NULL
|
||||
# arg2 == NULL || [arg2] == NULL || arg[2] == envp
|
||||
cons = processor.constraints
|
||||
cons << check_execve_arg(processor, arg1)
|
||||
cons << check_execve_arg(processor, arg1, allow_null_argv)
|
||||
return nil unless cons.all?
|
||||
|
||||
envp = 'environ'
|
||||
|
@ -109,38 +109,42 @@ module OneGadget
|
|||
envp = arg2
|
||||
end
|
||||
|
||||
{ constraints: cons, effect: %(execve("/bin/sh", #{arg1}, #{envp})) }
|
||||
{ constraints: cons, envp: envp }
|
||||
end
|
||||
|
||||
# arg[1] == NULL || [arg[1]] == NULL
|
||||
def check_execve_arg(processor, arg)
|
||||
# arg == NULL || [arg] == NULL
|
||||
def check_execve_arg(processor, arg, allow_null)
|
||||
if arg.start_with?(processor.sp) # arg = sp+<num>
|
||||
# in this case, the only constraint is [sp+<num>] == NULL
|
||||
# in this case, the only chance 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
|
||||
elsif allow_null
|
||||
"[#{arg}] == NULL || #{arg} == NULL"
|
||||
else
|
||||
"[#{arg}] == NULL"
|
||||
end
|
||||
end
|
||||
|
||||
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.
|
||||
# If str starts with [[ and is a global variable,
|
||||
# believe it is environ.
|
||||
# If it starts with [[ but not a global var, drop it.
|
||||
return global_var?(arg) if arg.start_with?('[[')
|
||||
|
||||
# normal
|
||||
cons = check_execve_arg(processor, arg)
|
||||
cons = check_execve_arg(processor, arg, true)
|
||||
return nil if cons.nil?
|
||||
|
||||
yield cons
|
||||
end
|
||||
|
||||
# Resolve +call execl+ case.
|
||||
# Resolve +call execl+ cases.
|
||||
def resolve_execl(processor)
|
||||
return unless str_bin_sh?(processor.argument(0).to_s)
|
||||
|
||||
args = []
|
||||
arg = processor.argument(1).to_s
|
||||
if str_sh?(arg)
|
||||
|
@ -151,10 +155,37 @@ module OneGadget
|
|||
|
||||
args << arg
|
||||
cons = processor.constraints + ["#{arg} == NULL"]
|
||||
# now arg is the constraint.
|
||||
{ constraints: cons, effect: %(execl("/bin/sh", #{args.join(', ')})) }
|
||||
end
|
||||
|
||||
# posix_spawn (*pid, *path, *file_actions, *attrp, argv[], envp[])
|
||||
# Constraints are
|
||||
# * pid == NULL || *pid is writable
|
||||
# * file_actions == NULL || (int) (file_actions->__used) <= 0
|
||||
# * attrp == NULL || attrp->flags == 0
|
||||
# Meet all constraints then posix_spawn eventually calls execve(path, argv, envp)
|
||||
def resolve_posix_spawn(processor)
|
||||
args = Array.new(6) { |i| processor.argument(i) }
|
||||
res = resolve_execve_args(processor, args[1].to_s, args[4].to_s, args[5].to_s, allow_null_argv: false)
|
||||
return nil if res.nil?
|
||||
|
||||
cons = res[:constraints]
|
||||
arg0 = args[0]
|
||||
if arg0.to_s != '0'
|
||||
if arg0.deref_count.zero? && arg0.to_s.include?(processor.sp)
|
||||
# Assume stack is always writable, no additional constraints.
|
||||
else
|
||||
cons << "#{arg0} == NULL || writable: #{arg0}"
|
||||
end
|
||||
end
|
||||
arg2 = args[2]
|
||||
cons << "#{arg2} == NULL || (s32)#{(arg2 + 4).deref} <= 0" if arg2.to_s != '0'
|
||||
arg3 = args[3]
|
||||
cons << "#{arg3} == NULL || (u16)#{arg3.deref} == NULL" if arg3.to_s != '0'
|
||||
|
||||
{ constraints: cons, effect: %(posix_spawn(#{arg0}, "/bin/sh", #{arg2}, #{arg3}, #{args[4]}, #{res[:envp]})) }
|
||||
end
|
||||
|
||||
def global_var?(_str); raise NotImplementedError
|
||||
end
|
||||
|
||||
|
|
|
@ -62,12 +62,15 @@ module OneGadget
|
|||
|
||||
# REG: OneGadget::ABI.all
|
||||
# IMM: [+-]0x[\da-f]+
|
||||
# BITS: 8, 16, 32, 64
|
||||
# CAST: (<s|u><BITS>)
|
||||
# Identity: <REG><IMM>?
|
||||
# Identity: [<Identity>]
|
||||
# Expr: <REG> is the GOT address of libc
|
||||
# Expr: writable: <Identity>
|
||||
# Expr: <Identity> == NULL
|
||||
# Expr: <CAST>?<Identity> == NULL
|
||||
# Expr: <REG> & 0xf == <IMM>
|
||||
# Expr: (s32)[<Identity>] <= 0
|
||||
# Expr: <Expr> || <Expr>
|
||||
def calculate_score(expr)
|
||||
return expr.split(' || ').map(&method(:calculate_score)).max if expr.include?(' || ')
|
||||
|
@ -76,15 +79,16 @@ module OneGadget
|
|||
when / & 0xf/ then 0.95
|
||||
when /GOT address/ then 0.9
|
||||
when /^writable/ then 0.81
|
||||
when / == NULL$/ then calculate_null_score(expr)
|
||||
when / == NULL$/ then calculate_null_score(expr.slice(0...expr.rindex(' == NULL')))
|
||||
when / <= 0$/ then calculate_null_score(expr.slice(0...expr.rindex(' <= ')))
|
||||
end
|
||||
end
|
||||
|
||||
def calculate_null_score(expr)
|
||||
identity = expr.slice(0...expr.rindex(' == NULL'))
|
||||
def calculate_null_score(identity)
|
||||
# remove <CAST>
|
||||
identity.sub!(/^\([s|u]\d+\)/, '')
|
||||
# Thank God we are already able to parse this
|
||||
lmda = OneGadget::Emulators::Lambda.parse(identity)
|
||||
# raise Error::ArgumentError, expr unless OneGadget::ABI.all.include?(lmda.obj)
|
||||
# rax == 0 is easy; rax + 0x10 == 0 is damn hard.
|
||||
return lmda.immi.zero? ? 0.9 : 0.1 if lmda.deref_count.zero?
|
||||
|
||||
|
|
|
@ -10,7 +10,6 @@ require 'one_gadget/version'
|
|||
Gem::Specification.new do |s|
|
||||
s.name = 'one_gadget'
|
||||
s.version = ::OneGadget::VERSION
|
||||
s.date = Date.today.to_s
|
||||
s.summary = 'one_gadget'
|
||||
s.description = <<-EOS
|
||||
When playing ctf pwn challenges we usually needs the one-gadget of execve('/bin/sh', NULL, NULL).
|
||||
|
|
Binary file not shown.
|
@ -47,6 +47,18 @@ describe OneGadget::Emulators::Amd64 do
|
|||
expect(@processor.stack[0x48].to_s).to eq 'rax'
|
||||
end
|
||||
|
||||
it 'punpcklqdq' do
|
||||
gadget = <<-EOS
|
||||
movq xmm1,rax
|
||||
movq xmm0,rcx
|
||||
punpcklqdq xmm0,xmm1
|
||||
movaps XMMWORD PTR [rsp+0x50],xmm0
|
||||
EOS
|
||||
gadget.each_line { |s| @processor.process(s) }
|
||||
expect(@processor.stack[0x50].to_s).to eq 'rcx'
|
||||
expect(@processor.stack[0x58].to_s).to eq 'rax'
|
||||
end
|
||||
|
||||
it 'unsupported form' do
|
||||
expect { @processor.process!('movaps xmm0, [rsp+0x40]') }
|
||||
.to raise_error(OneGadget::Error::UnsupportedInstructionArgumentError)
|
||||
|
|
|
@ -38,10 +38,10 @@ describe OneGadget::Emulators::Lambda do
|
|||
|
||||
it 'parse' do
|
||||
expect(described_class.parse('[rsp+0x50]').to_s).to eq '[rsp+0x50]'
|
||||
# ARM form
|
||||
# ARM forms
|
||||
expect(described_class.parse('[x0, 1160]').to_s).to eq '[x0+0x488]'
|
||||
expect(described_class.parse('[x22, -104]').to_s).to eq '[x22-0x68]'
|
||||
# test if OK with bang
|
||||
# test argument with bang
|
||||
expect(described_class.parse('[x2, -8]!').to_s).to eq '[x2-0x8]'
|
||||
expect(described_class.parse('[rsp+80]').to_s).to eq '[rsp+0x50]'
|
||||
expect(described_class.parse('esp').to_s).to eq 'esp'
|
||||
|
@ -54,6 +54,7 @@ describe OneGadget::Emulators::Lambda do
|
|||
|
||||
# Nested []
|
||||
expect(described_class.parse('[[rsp+0x33]]').to_s).to eq '[[rsp+0x33]]'
|
||||
expect(described_class.parse('[[rdx+0x33]+0x4154]').to_s).to eq '[[rdx+0x33]+0x4154]'
|
||||
end
|
||||
|
||||
it 'evaluate' do
|
||||
|
|
|
@ -33,6 +33,15 @@ describe 'one_gadget_amd64' do
|
|||
expect(one_gadget(path)).to eq OneGadget.gadgets(file: path)
|
||||
end
|
||||
|
||||
it 'libc-2.31' do
|
||||
path = data_path('libc-2.31-9fdb74e7b217d06c93172a8243f8547f947ee6d1.so')
|
||||
expect(OneGadget.gadgets(file: path, force_file: true,
|
||||
level: 1)).to eq [0x51e39, 0x51e45, 0x51e5a, 0x51e62, 0x84173, 0x84180, 0x8418c,
|
||||
0x84199, 0xe3b2e, 0xe3b31, 0xe3b34, 0xe3d23, 0xe3d26, 0xe3d99,
|
||||
0xe3da0, 0xe3de5, 0xe3ded, 0x1075da, 0x1075e2, 0x1075e7, 0x1075f1]
|
||||
expect(OneGadget.gadgets(file: path)).to eq [0xe3b2e, 0xe3b31, 0xe3b34]
|
||||
end
|
||||
|
||||
it 'not ELF' do
|
||||
expect { hook_logger { OneGadget.gadgets(file: __FILE__) } }.to output(<<-EOS).to_stdout
|
||||
[OneGadget] ArgumentError: Not an ELF file, expected glibc as input
|
||||
|
|
Loading…
Reference in New Issue