class Metasm::DynLdr
Constants
- DYNLDR_ASM_IA32
ia32 asm source for the native component: handles ABI stuff
- DYNLDR_ASM_X86_64
ia32 asm source for the native component: handles ABI stuff
- DYNLDR_C
generic C source for the native component, ruby glue
- DYNLDR_C_PE_HACK
see the note in compile_bin_module this is a dynamic resolver for the ruby symbols we use
- RUBY_H
basic C defs for ruby internals - 1.8 and 1.9 compat - x86/x64
Public Class Methods
allocate a C::AllocCStruct holding an Array of typename variables if len is an int, it holds the ary length, or it can be an array of initialisers eg ::alloc_c_ary(“int”, [4, 5, 28])
# File metasm/dynldr.rb, line 1242 def self.alloc_c_ary(typename, len) cp.alloc_c_ary(typename, len) end
return an AllocCStruct holding an array of 1 element of type typename access its value with obj useful when you need a pointer to an int that will be filled by an API: use ::alloc_c_ptr('int')
# File metasm/dynldr.rb, line 1254 def self.alloc_c_ptr(typename, init=nil) cp.alloc_c_ary(typename, (init ? [init] : 1)) end
allocate a C::AllocCStruct to hold a specific struct defined in a previous ::new_api_c
# File metasm/dynldr.rb, line 1228 def self.alloc_c_struct(structname, values={}) cp.alloc_c_struct(structname, values) end
# File metasm/dynldr.rb, line 963 def self.api_not_found(lib, func) raise "could not find symbol #{func.name.inspect} in #{lib.inspect}" end
when defining ruby wrapper for C constants (numeric define/enum), the ruby const name is the string returned by this function from the C name. It should follow ruby standards (1st letter upcase)
# File metasm/dynldr.rb, line 957 def self.c_const_name_to_rb(name) n = name.to_s.gsub(/[^a-z0-9_]/i) { |c| c.unpack('H*')[0] }.upcase n = "C#{n}" if n !~ /^[A-Z]/ n end
allocate a callback for a given C prototype (string) accepts full C functions (with body) (only 1 at a time) or toplevel 'asm' statement
# File metasm/dynldr.rb, line 1086 def self.callback_alloc_c(proto, &b) proto += ';' # allow 'int foo()' parse_c(proto) v = cp.toplevel.symbol.values.find_all { |v_| v_.kind_of? C::Variable and v_.type.kind_of? C::Function }.first if (v and v.initializer) or cp.toplevel.statements.find { |st| st.kind_of? C::Asm } cp.toplevel.statements.delete_if { |st| st.kind_of? C::Asm } cp.toplevel.symbol.delete v.name if v sc = sc_map_resolve(compile_c(proto)) sc.base_x elsif not v raise 'empty prototype' else cp.toplevel.symbol.delete v.name callback_alloc_cobj(v, b) end end
allocates a callback for a given C prototype (C variable, pointer to func accepted)
# File metasm/dynldr.rb, line 1104 def self.callback_alloc_cobj(proto, b) ori = proto proto = proto.type if proto and proto.kind_of? C::Variable proto = proto.pointed while proto and proto.pointer? id = callback_find_id cb = {} cb[:id] = id cb[:proc] = b cb[:proto] = proto cb[:proto_ori] = ori cb[:abi_stackfix] = proto.args.inject(0) { |s, a| s + [cp.sizeof(a), cp.typesize[:ptr]].max } if ori and ori.has_attribute('stdcall') cb[:abi_stackfix] = proto.args[2..-1].to_a.inject(0) { |s, a| s + [cp.sizeof(a), cp.typesize[:ptr]].max } if ori and ori.has_attribute('fastcall') # supercedes stdcall @@callback_table[id] = cb id end
finds a free callback id, allocates a new page if needed
# File metasm/dynldr.rb, line 1126 def self.callback_find_id if not id = @@callback_addrs.find { |a| not @@callback_table[a] } page_size = 4096 cb_page = memory_alloc(page_size) sc = Shellcode.new(host_cpu, cb_page) case sc.cpu.shortname when 'ia32' asm = "call #{CALLBACK_TARGET}" when 'x64' if (cb_page - CALLBACK_TARGET).abs >= 0x7fff_f000 # cannot directly 'jmp CB_T' asm = "1: mov rax, #{CALLBACK_TARGET} push rax lea rax, [rip-$_+1b] ret" else asm = "1: lea rax, [rip-$_+1b] jmp #{CALLBACK_TARGET}" end else raise 'Who are you?' end # fill the page with valid callbacks loop do off = sc.encoded.length sc.assemble asm break if sc.encoded.length > page_size @@callback_addrs << (cb_page + off) end memory_write cb_page, sc.encode_string[0, page_size] memory_perm cb_page, page_size, 'rx' raise 'callback_alloc bouh' if not id = @@callback_addrs.find { |a| not @@callback_table[a] } end id end
releases a callback id, so that it may be reused by a later callback_alloc
# File metasm/dynldr.rb, line 1121 def self.callback_free(id) @@callback_table.delete id end
this method is called from the C part to run the ruby code corresponding to a given C callback allocated by ::callback_alloc_c
# File metasm/dynldr.rb, line 1029 def self.callback_run(id, args) cb = @@callback_table[id] raise "invalid callback #{'%x' % id} not in #{@@callback_table.keys.map { |c| c.to_s(16) }}" if not cb rawargs = args.dup if host_cpu.shortname == 'ia32' and (not cb[:proto_ori] or not cb[:proto_ori].has_attribute('fastcall')) rawargs.shift rawargs.shift end ra = cb[:proto] ? cb[:proto].args.map { |fa| convert_cbargs_c2rb(fa, rawargs) } : [] # run it ret = cb[:proc].call(*ra) # the C code expects to find in args[0] the amount of stack fixing needed for __stdcall callbacks args[0] = cb[:abi_stackfix] || 0 ret end
compile the dynldr binary ruby module for a specific arch/cpu/modulename
# File metasm/dynldr.rb, line 623 def self.compile_binary_module(exe, cpu, modulename) bin = exe.new(cpu) # compile the C code, but patch the Init_ export name, which must match the string used in 'require' module_c_src = DYNLDR_C.gsub('<insertfilenamehere>', File.basename(modulename, '.so')) bin.compile_c module_c_src # compile the Asm stuff according to the target architecture bin.assemble case cpu.shortname when 'ia32'; DYNLDR_ASM_IA32 when 'x64'; DYNLDR_ASM_X86_64 end # tweak the resulting binary linkage procedures if needed compile_binary_module_hack(bin) # save the shared library bin.encode_file(modulename, :lib) end
# File metasm/dynldr.rb, line 641 def self.compile_binary_module_hack(bin) # this is a hack # we need the module to use ruby symbols # but we don't know the actual ruby lib filename (depends on ruby version, # platform, ...) case bin.shortname when 'elf' # we know the lib is already loaded by the main ruby executable, no DT_NEEDED needed class << bin def automagic_symbols(*a) # do the plt generation super(*a) # but remove the specific lib names @tag.delete 'NEEDED' end end return when 'coff' # the hard part, see below else # unhandled arch, dont tweak return end # we remove the PE IAT section related to ruby symbols, and make # a manual symbol resolution on module loading. # populate the ruby import table ourselves on module loading bin.imports.delete_if { |id| id.libname =~ /ruby/ } # we generate something like: # .data # ruby_import_table: # rb_cObject dd str_rb_cObject - ruby_import_table # riat_rb_intern dd str_rb_intern - ruby_import_table # dd 0 # # .rodata # str_rb_cObject db "rb_cObject", 0 # str_rb_intern db "rb_intern", 0 # # .text # rb_intern: jmp [riat_rb_intern] # # the PE_HACK code will parse ruby_import_table and make the symbol resolution on startup # setup the string table and the thunks text = bin.sections.find { |s| s.name == '.text' }.encoded rb_syms = text.reloc_externals.grep(/^rb_/) dd = (bin.cpu.size == 64 ? 'dq' : 'dd') init_symbol = text.export.keys.grep(/^Init_/).first raise 'no Init_mname symbol found' if not init_symbol if bin.cpu.size == 32 # hax to find the base of libruby under Win98 (peb sux) text.export[init_symbol + '_real'] = text.export.delete(init_symbol) bin.unique_labels_cache.delete(init_symbol) end # the C glue: getprocaddress etc bin.compile_c DYNLDR_C_PE_HACK.gsub('Init_dynldr', init_symbol) # the IAT, initialized with relative offsets to symbol names asm_table = ['.data', '.align 8', 'ruby_import_table:'] # strings will be in .rodata bin.parse('.rodata') rb_syms.each { |sym| # raw symbol name str_label = bin.parse_new_label('str', "db #{sym.inspect}, 0") if sym !~ /^rb_[ce][A-Z]/ # if we dont reference a data import (rb_cClass / rb_eException), # then create a function thunk i = PE::ImportDirectory::Import.new i.thunk = sym sym = i.target = 'riat_' + str_label bin.arch_encode_thunk(text, i) # encode a jmp [importtable] end # update the IAT asm_table << "#{sym} #{dd} #{str_label} - ruby_import_table" } # IAT null-terminated asm_table << "#{dd} 0" # now parse & assemble the IAT in .data bin.assemble asm_table.join("\n") end
compile a C fragment into a Shellcode_RWX, honors the host ABI
# File metasm/dynldr.rb, line 801 def self.compile_c(src) # XXX could we reuse self.cp ? (for its macros etc) cp = C::Parser.new(host_exe.new(host_cpu)) cp.parse(src) sc = Shellcode_RWX.new(host_cpu) asm = host_cpu.new_ccompiler(cp, sc).compile sc.assemble(asm) end
::const_missing handler: will try to find a matching define
# File metasm/dynldr.rb, line 929 def self.const_missing(c) # infinite loop on autorequire C.. return super(c) if not defined? @cp or not @cp cs = c.to_s if @cp.lexer.definition[cs] m = cs else m = @cp.lexer.definition.keys.find { |k| c_const_name_to_rb(k) == cs } end if m and v = @cp.macro_numeric(m) const_set(c, v) v else super(c) end end
interpret a raw decoded C value to a ruby value according to the C prototype handles signedness etc XXX val is an integer, how to decode Floats etc ? raw binary ptr ?
# File metasm/dynldr.rb, line 1066 def self.convert_c2rb(formal, val) formal = formal.type if formal.kind_of? C::Variable val &= (1 << 8*cp.sizeof(formal))-1 if formal.integral? val = Expression.make_signed(val, 8*cp.sizeof(formal)) if formal.integral? and formal.signed? val = nil if formal.pointer? and val == 0 val end
C raw cb arg -> ruby object will combine 2 32bit values for 1 64bit arg
# File metasm/dynldr.rb, line 1050 def self.convert_cbargs_c2rb(formal, rawargs) val = rawargs.shift if formal.type.integral? and cp.sizeof(formal) == 8 and host_cpu.size == 32 if host.cpu.endianness == :little val |= rawargs.shift << 32 else val = (val << 32) | rawargs.shift end end convert_c2rb(formal, val) end
ruby object -> integer suitable as arg for raw_invoke
# File metasm/dynldr.rb, line 1007 def self.convert_rb2c(formal, val, opts=nil) case val when String; str_ptr(val) when Proc; cb = callback_alloc_cobj(formal, val) ; (opts[:cb_list] << cb if opts and opts[:cb_list]) ; cb when C::AllocCStruct; str_ptr(val.str) + val.stroff when Hash if not formal.type.pointed.kind_of?(C::Struct) raise "invalid argument #{val.inspect} for #{formal}, need a struct*" end buf = cp.alloc_c_struct(formal, val) val.instance_variable_set('@rb2c', buf) # GC trick: lifetime(buf) >= lifetime(hash) (XXX or until next call to convert_rb2c) str_ptr(buf.str) #when Float; val # TODO handle that in raw_invoke C code else v = val.to_i rescue 0 # NaN, Infinity, etc v = -v if v == -(1<<(cp.typesize[:ptr]*8-1)) # ruby bug... raise -0x8000_0000: out of ulong range v end end
C raw ret -> ruby obj can be overridden for system-specific calling convention (eg return 0/-1 => raise an error)
# File metasm/dynldr.rb, line 1076 def self.convert_ret_c2rb(fproto, ret) fproto = fproto.type if fproto.kind_of? C::Variable convert_c2rb(fproto.untypedef.type, ret) end
# File metasm/dynldr.rb, line 1081 def self.cp ; @cp ||= C::Parser.new(host_exe.new(host_cpu)) ; end
# File metasm/dynldr.rb, line 1082 def self.cp=(c); @cp = c ; end
return a C::AllocCStruct holding an array of type typename mapped over str
# File metasm/dynldr.rb, line 1247 def self.decode_c_ary(typename, len, str, off=0) cp.decode_c_ary(typename, len, str, off) end
return a C::AllocCStruct mapped over the string (with optionnal offset) str may be an EncodedData
# File metasm/dynldr.rb, line 1234 def self.decode_c_struct(structname, str, off=0) str = str.data if str.kind_of? EncodedData cp.decode_c_struct(structname, str, off) end
decode a C variable only integral types handled for now
# File metasm/dynldr.rb, line 1266 def self.decode_c_value(str, var, off=0) cp.decode_c_value(str, var, off) end
return the binary version of a ruby value encoded as a C variable only integral types handled for now
# File metasm/dynldr.rb, line 1260 def self.encode_c_value(var, val) cp.encode_c_value(var, val) end
find the path of the binary module if none exists, create a path writeable by the current user
# File metasm/dynldr.rb, line 732 def self.find_bin_path fname = ['dynldr', host_arch, host_cpu.shortname, RUBY_VERSION.gsub('.', '')].join('-') + '.so' dir = File.dirname(__FILE__) binmodule = File.join(dir, fname) if not File.exist? binmodule or File.stat(binmodule).mtime < File.stat(__FILE__).mtime if not dir = find_write_dir raise LoadError, "no writable dir to put the DynLdr ruby module, try to run as root" end binmodule = File.join(dir, fname) end binmodule end
find a writeable directory searches this script directory, $HOME / %APPDATA% / %USERPROFILE%, or $TMP
# File metasm/dynldr.rb, line 747 def self.find_write_dir writable = lambda { |d| begin foo = '/_test_write_' + rand(1<<32).to_s true if File.writable?(d) and File.open(d+foo, 'w') { true } and File.unlink(d+foo) rescue end } dir = File.dirname(__FILE__) return dir if writable[dir] dir = ENV['HOME'] || ENV['APPDATA'] || ENV['USERPROFILE'] if writable[dir] dir = File.join(dir, '.metasm') Dir.mkdir dir if not File.directory? dir return dir end ENV['TMP'] || ENV['TEMP'] || '.' end
returns whether we run on linux or windows
# File metasm/dynldr.rb, line 779 def self.host_arch case RUBY_PLATFORM when /linux/i; :linux when /mswin|mingw|cygwin/i; :windows else raise LoadError, "Unsupported host platform #{RUBY_PLATFORM}" end end
CPU suitable for compiling code for the current running host
# File metasm/dynldr.rb, line 769 def self.host_cpu @cpu ||= case RUBY_PLATFORM when /i[3-6]86/; Ia32.new when /x86_64|x64/i; X86_64.new else raise LoadError, "Unsupported host platform #{RUBY_PLATFORM}" end end
ExeFormat suitable as current running host native module
# File metasm/dynldr.rb, line 788 def self.host_exe case host_arch when :linux; ELF when :windows; PE end end
retrieve the library where a symbol is to be found (uses AutoImport)
# File metasm/dynldr.rb, line 868 def self.lib_from_sym(symname) case host_arch when :linux; GNUExports::EXPORT when :windows; WindowsExports::EXPORT end[symname] end
allocate some memory suitable for code allocation (ie VirtualAlloc)
# File metasm/dynldr.rb, line 1339 def self.memory_alloc(sz) virtualalloc(nil, sz, MEM_RESERVE|MEM_COMMIT, PAGE_READWRITE) end
free memory allocated through ::memory_alloc
# File metasm/dynldr.rb, line 1344 def self.memory_free(addr) virtualfree(addr, 0, MEM_RELEASE) end
change memory permissions - perm in [r rw rx rwx]
# File metasm/dynldr.rb, line 1349 def self.memory_perm(addr, len, perm) perm = { 'r' => PAGE_READONLY, 'rw' => PAGE_READWRITE, 'rx' => PAGE_EXECUTE_READ, 'rwx' => PAGE_EXECUTE_READWRITE }[perm.to_s.downcase] virtualprotect(addr, len, perm, str_ptr([0].pack('C')*8)) end
read a 0-terminated string from memory
# File metasm/dynldr.rb, line 1271 def self.memory_read_strz(ptr, szmax=4096) # read up to the end of the ptr memory page pglim = (ptr + 0x1000) & ~0xfff sz = [pglim-ptr, szmax].min data = memory_read(ptr, sz) return data[0, data.index(?\0)] if data.index(?\0) if sz < szmax data = memory_read(ptr, szmax) data = data[0, data.index(?\0)] if data.index(?\0) end data end
read a 0-terminated wide string from memory
# File metasm/dynldr.rb, line 1285 def self.memory_read_wstrz(ptr, szmax=4096) # read up to the end of the ptr memory page pglim = (ptr + 0x1000) & ~0xfff sz = [pglim-ptr, szmax].min data = memory_read(ptr, sz) if i = data.unpack('v*').index(0) return data[0, 2*i] end if sz < szmax data = memory_read(ptr, szmax) data = data[0, 2*i] if i = data.unpack('v*').index(0) end data end
reads a bunch of C code, creates binding for those according to the prototypes handles enum/defines to define constants For each toplevel method prototype, it generates a ruby method in this module, the name is lowercased For each numeric macro/enum, it also generates an uppercase named constant When such a function is called with a lambda as argument, a callback is created for the duration of the call and destroyed afterwards ; use ::callback_alloc_c to get a callback id with longer life span
# File metasm/dynldr.rb, line 881 def self.new_api_c(proto, fromlib=nil) proto += "\n;" # allow 'int foo()' and '#include <bar>' parse_c(proto) cp.toplevel.symbol.dup.each_value { |v| next if not v.kind_of? C::Variable # enums cp.toplevel.symbol.delete v.name lib = fromlib || lib_from_sym(v.name) addr = sym_addr(lib, v.name) if addr == 0 or addr == -1 or addr == 0xffff_ffff or addr == 0xffffffff_ffffffff api_not_found(lib, v) next end rbname = c_func_name_to_rb(v.name) if not v.type.kind_of? C::Function # not a function, simply return the symbol address # TODO struct/table access through hash/array ? class << self ; self ; end.send(:define_method, rbname) { addr } next end next if v.initializer # inline & stuff puts "new_api_c: load method #{rbname} from #{lib}" if $DEBUG new_caller_for(v, rbname, addr) } # predeclare constants from enums # macros are handled in const_missing (too slow to (re)do here everytime) # TODO #define FOO(v) (v<<1)|1 => create ruby counterpart cexist = constants.inject({}) { |h, c| h.update c.to_s => true } cp.toplevel.symbol.each { |k, v| if v.kind_of? ::Integer n = c_const_name_to_rb(k) const_set(n, v) if v.kind_of? Integer and not cexist[n] end } # avoid WTF rb warning: toplevel const TRUE referenced by WinAPI::TRUE cp.lexer.definition.each_key { |k| n = c_const_name_to_rb(k) if not cexist[n] and Object.const_defined?(n) and v = @cp.macro_numeric(n) const_set(n, v) end } end
define a new method 'name' in the current module to invoke the raw method at addr addr translates ruby args to raw args using the specified prototype
# File metasm/dynldr.rb, line 974 def self.new_caller_for(proto, name, addr) flags = 0 flags |= 1 if proto.has_attribute('stdcall') flags |= 2 if proto.has_attribute('fastcall') flags |= 4 if proto.type.type.integral? and cp.sizeof(nil, proto.type.type) == 8 flags |= 8 if proto.type.type.float? class << self ; self ; end.send(:define_method, name) { |*a| raise ArgumentError, "bad arg count for #{name}: #{a.length} for #{proto.type.args.to_a.length}" if a.length != proto.type.args.to_a.length and not proto.type.varargs # convert the arglist suitably for raw_invoke auto_cb = [] # list of automatic C callbacks generated from lambdas a = a.zip(proto.type.args.to_a).map { |ra, fa| aa = convert_rb2c(fa, ra, :cb_list => auto_cb) if fa and fa.type.integral? and cp.sizeof(fa) == 8 and host_cpu.size == 32 aa = [aa & 0xffff_ffff, (aa >> 32) & 0xffff_ffff] aa.reverse! if host_cpu.endianness != :little end aa }.flatten trace_invoke(name, a) # do it ret = raw_invoke(addr, a, flags) # cleanup autogenerated callbacks auto_cb.each { |cb| callback_free(cb) } # interpret return value ret = convert_ret_c2rb(proto, ret) } end
compile an asm sequence, callable with the ABI of the C prototype given function name comes from the prototype the shellcode is mapped in read-only memory unless selfmodifyingcode is true note that you can use a .data section for simple writable non-executable memory
# File metasm/dynldr.rb, line 1196 def self.new_func_asm(proto, asm, selfmodifyingcode=false) proto += "\n;" old = cp.toplevel.symbol.keys parse_c(proto) news = cp.toplevel.symbol.keys - old raise "invalid proto #{proto}" if news.length != 1 f = cp.toplevel.symbol[news.first] raise "invalid func proto #{proto}" if not f.name or not f.type.kind_of? C::Function or f.initializer cp.toplevel.symbol.delete f.name sc = Shellcode_RWX.assemble(host_cpu, asm) sc = sc_map_resolve(sc) if selfmodifyingcode memory_perm sc.base_x, sc.encoded_x.length, 'rwx' end rbname = c_func_name_to_rb(f.name) new_caller_for(f, rbname, sc.base_x) if block_given? begin yield ensure class << self ; self ; end.send(:remove_method, rbname) memory_free sc.base_r if sc.base_r memory_free sc.base_w if sc.base_w memory_free sc.base_x end else sc.base_x end end
compile a bunch of C functions, defines methods in this module to call them returns the raw pointer to the code page if given a block, run the block and then undefine all the C functions & free memory
# File metasm/dynldr.rb, line 1164 def self.new_func_c(src) sc = sc_map_resolve(compile_c(src)) parse_c(src) # XXX the Shellcode parser may have defined stuff / interpreted C another way... defs = [] cp.toplevel.symbol.dup.each_value { |v| next if not v.kind_of? C::Variable cp.toplevel.symbol.delete v.name next if not v.type.kind_of? C::Function or not v.initializer next if not off = sc.encoded_x.export[v.name] rbname = c_func_name_to_rb(v.name) new_caller_for(v, rbname, sc.base_x+off) defs << rbname } if block_given? begin yield ensure defs.each { |d| class << self ; self ; end.send(:remove_method, d) } memory_free sc.base_r if sc.base_r memory_free sc.base_w if sc.base_w memory_free sc.base_x if sc.base_x end else sc.base_x end end
parse a C string into the @cp parser, create it if needed
# File metasm/dynldr.rb, line 796 def self.parse_c(src) cp.parse(src) end
maps a Shellcode_RWX in memory, fixup stdlib relocations returns the Shellcode_RWX, with the base_r/w/x initialized to the allocated memory
# File metasm/dynldr.rb, line 812 def self.sc_map_resolve(sc) sc_map_resolve_addthunks(sc) sc.base_r = memory_alloc(sc.encoded_r.length) if sc.encoded_r.length > 0 sc.base_w = memory_alloc(sc.encoded_w.length) if sc.encoded_w.length > 0 sc.base_x = memory_alloc(sc.encoded_x.length) if sc.encoded_x.length > 0 locals = sc.encoded_r.export.keys | sc.encoded_w.export.keys | sc.encoded_x.export.keys exts = sc.encoded_r.reloc_externals(locals) | sc.encoded_w.reloc_externals(locals) | sc.encoded_x.reloc_externals(locals) bd = {} exts.uniq.each { |ext| bd[ext] = sym_addr(lib_from_sym(ext), ext) or raise rescue raise "unknown symbol #{ext.inspect}" } sc.fixup_check(bd) memory_write sc.base_r, sc.encoded_r.data if sc.encoded_r.length > 0 memory_write sc.base_w, sc.encoded_w.data if sc.encoded_w.length > 0 memory_write sc.base_x, sc.encoded_x.data if sc.encoded_x.length > 0 memory_perm sc.base_r, sc.encoded_r.length, 'r' if sc.encoded_r.length > 0 memory_perm sc.base_w, sc.encoded_w.length, 'rw' if sc.encoded_w.length > 0 memory_perm sc.base_x, sc.encoded_x.length, 'rx' if sc.encoded_x.length > 0 sc end
# File metasm/dynldr.rb, line 836 def self.sc_map_resolve_addthunks(sc) case host_cpu.shortname when 'x64' # patch 'call moo' into 'call thunk; thunk: jmp qword [moo_ptr]' # this is similar to ELF PLT section, allowing code to call # into a library mapped more than 4G away # XXX handles only 'call extern', not 'lea reg, extern' or anything else # in this case, the linker will still raise an 'immediate overflow' # during fixup_check in sc_map_resolve [sc.encoded_r, sc.encoded_w, sc.encoded_x].each { |edata| edata.reloc.dup.each { |off, rel| # target only call extern / jmp.i32 extern next if rel.type != :i32 next if rel.target.op != :- next if edata.export[rel.target.rexpr] != off+4 next if edata.export[rel.target.lexpr] opc = edata.data[off-1, 1].unpack('C')[0] next if opc != 0xe8 and opc != 0xe9 thunk_sc = Shellcode.new(host_cpu).share_namespace(sc) thunk = thunk_sc.assemble(<<EOS).encoded 1: jmp qword [rip] dq #{rel.target.lexpr} EOS edata << thunk rel.target.lexpr = thunk.inv_export[0] } } end end
initialization load (build if needed) the binary module
# File metasm/dynldr.rb, line 606 def self.start # callbacks are really just a list of asm 'call', so we share them among subclasses of DynLdr @@callback_addrs = [] # list of all allocated callback addrs (in use or not) @@callback_table = {} # addr -> cb structure (inuse only) binmodule = find_bin_path if not File.exist?(binmodule) or File.stat(binmodule).mtime < File.stat(__FILE__).mtime compile_binary_module(host_exe, host_cpu, binmodule) end require binmodule @@callback_addrs << CALLBACK_ID_0 << CALLBACK_ID_1 end
called whenever a native API is called through new_api_c/new_func_c/etc
# File metasm/dynldr.rb, line 968 def self.trace_invoke(api, args) #p api end