class Metasm::DL

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

alloc_c_ary(typename, len) click to toggle source

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
alloc_c_ptr(typename, init=nil) click to toggle source

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
alloc_c_struct(structname, values={}) click to toggle source

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
api_not_found(lib, func) click to toggle source
# 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
c_const_name_to_rb(name) click to toggle source

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_]/) { |c| c.unpack('H*')[0] }.upcase
        n = "C#{n}" if n !~ /^[A-Z]/
        n
end
c_func_name_to_rb(name) click to toggle source

when defining ruby wrapper for C methods, the ruby method name is the string returned by this function from the C name

# File metasm/dynldr.rb, line 949
def self.c_func_name_to_rb(name)
        n = name.to_s.gsub(/[^a-z0-9_]/) { |c| c.unpack('H*')[0] }.downcase
        n = "m#{n}" if n !~ /^[a-z]/
        n
end
callback_alloc_c(proto, &b) click to toggle source

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
callback_alloc_cobj(proto, b) click to toggle source

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
callback_find_id() click to toggle source

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
callback_free(id) click to toggle source

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
callback_run(id, args) click to toggle source

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_binary_module(exe, cpu, modulename) click to toggle source

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
compile_binary_module_hack(bin) click to toggle source
# 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_c(src) click to toggle source

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(c) click to toggle source

::const_missing handler: will try to find a matching define

Calls superclass method
# 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
convert_c2rb(formal, val) click to toggle source

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
convert_cbargs_c2rb(formal, rawargs) click to toggle source

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
convert_rb2c(formal, val, opts=nil) click to toggle source

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
convert_ret_c2rb(fproto, ret) click to toggle source

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
cp() click to toggle source
# File metasm/dynldr.rb, line 1081
def self.cp ; @cp ||= C::Parser.new(host_exe.new(host_cpu)) ; end
cp=(c) click to toggle source
# File metasm/dynldr.rb, line 1082
def self.cp=(c); @cp = c ; end
decode_c_ary(typename, len, str, off=0) click to toggle source

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
decode_c_struct(structname, str, off=0) click to toggle source

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_c_value(str, var, off=0) click to toggle source

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
encode_c_value(var, val) click to toggle source

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_bin_path() click to toggle source

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_write_dir() click to toggle source

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
host_arch() click to toggle source

returns whether we run on linux or windows

# File metasm/dynldr.rb, line 779
def self.host_arch
        case RUBY_PLATFORM
        when /linux/; :linux
        when /mswin|mingw|cygwin/; :windows
        else raise LoadError, "Unsupported host platform #{RUBY_PLATFORM}"
        end
end
host_cpu() click to toggle source

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/; X86_64.new
        else raise LoadError, "Unsupported host platform #{RUBY_PLATFORM}"
        end
end
host_exe() click to toggle source

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
lib_from_sym(symname) click to toggle source

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
memory_alloc(sz) click to toggle source

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
memory_free(addr) click to toggle source

free memory allocated through ::memory_alloc

# File metasm/dynldr.rb, line 1344
def self.memory_free(addr)
        virtualfree(addr, 0, MEM_RELEASE)
end
memory_perm(addr, len, perm) click to toggle source

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
memory_read_strz(ptr, szmax=4096) click to toggle source

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
memory_read_wstrz(ptr, szmax=4096) click to toggle source

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
new_api_c(proto, fromlib=nil) click to toggle source

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
new_caller_for(proto, name, addr) click to toggle source

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
new_func_asm(proto, asm, selfmodifyingcode=false) { || ... } click to toggle source

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
new_func_c(src) { || ... } click to toggle source

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_c(src) click to toggle source

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
sc_map_resolve(sc) click to toggle source

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
sc_map_resolve_addthunks(sc) click to toggle source
# 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("1: jmp qword [rip]
dq #{rel.target.lexpr}
").encoded
                                        edata << thunk
                                        rel.target.lexpr = thunk.inv_export[0]
                                }
                        }
                end
        end
start() click to toggle source

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
trace_invoke(api, args) click to toggle source

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