class PeLdr

Constants

DL

Attributes

load_address[RW]
pe[RW]

Public Class Methods

allocate_ldt_entry_teb() click to toggle source

allocate an LDT entry for the teb, returns a value suitable for the fs selector

# File samples/peldr.rb, line 188
def self.allocate_ldt_entry_teb
        entry = 1
        # ldt_entry base_addr size_in_pages
        # 32bits:1 type:2 (0=data) readonly:1 limit_in_pages:1 seg_not_present:1 usable:1
        struct = [entry, @@teb, 1, 0b1_0_1_0_00_1].pack('VVVV')
        Kernel.syscall(123, 1, DL.str_ptr(struct), struct.length)     # __NR_modify_ldt
        (entry << 3) | 7
end
new(file, hooktype=:iat) click to toggle source

load a PE file, setup basic IAT hooks (raises “unhandled lib!import”)

# File samples/peldr.rb, line 17
def initialize(file, hooktype=:iat)
        if file.kind_of? Metasm::PE
                @pe = file
        elsif file[0, 2] == 'MZ' and file.length > 0x3c
                @pe = Metasm::PE.decode(file)
        else  # filename
                @pe = Metasm::PE.decode_file(file)
        end
        @load_address = DL.memory_alloc(@pe.optheader.image_size)
        raise 'malloc' if @load_address == 0xffff_ffff
        
        puts "map sections" if $DEBUG
        DL.memory_write(@load_address, @pe.encoded.data[0, @pe.optheader.headers_size].to_str)
        @pe.sections.each { |s|
                DL.memory_write(@load_address+s.virtaddr, s.encoded.data.to_str)
        }
        
        puts "fixup sections" if $DEBUG
        off = @load_address - @pe.optheader.image_base
        @pe.relocations.to_a.each { |rt|
                base = rt.base_addr
                rt.relocs.each { |r|
                        if r.type == 'HIGHLOW'
                                ptr = @load_address + base + r.offset
                                old = DL.memory_read(ptr, 4).unpack('V').first
                                DL.memory_write_int(ptr, old + off)
                        end
                }
        }

        @iat_cb = {}
        @eat_cb = {}
        case hooktype
        when :iat
                puts "hook IAT" if $DEBUG
                @pe.imports.to_a.each { |id|
                        ptr = @load_address + id.iat_p
                        id.imports.each { |i|
                                n = "#{id.libname}!#{i.name}"
                                cb = DL.callback_alloc_c('void x(void)') { raise "unemulated import #{n}" }
                                DL.memory_write_int(ptr, cb)
                                @iat_cb[n] = cb
                                ptr += 4
                        }
                }
        when :eat, :exports
                puts "hook EAT" if $DEBUG
                ptr = @load_address + @pe.export.func_p
                @pe.export.exports.each { |e|
                        n = e.name || e.ordinal
                        cb = DL.callback_alloc_c('void x(void)') { raise "unemulated export #{n}" }
                        DL.memory_write_int(ptr, cb - @load_address)        # RVA
                        @eat_cb[n] = cb
                        ptr += 4
                }
        end
end
peb() click to toggle source
# File samples/peldr.rb, line 185
def self.peb ; @@peb ; end
populate_peb() click to toggle source
# File samples/peldr.rb, line 179
def self.populate_peb
        DL.memory_write(@@peb, 0.chr*4096)
        #set = lambda { |off, val| DL.memory_write_int(@@peb+off, val) }
end
populate_teb() click to toggle source

fills a fake TEB structure

# File samples/peldr.rb, line 169
def self.populate_teb
        DL.memory_write(@@teb, 0.chr*4096)
        set = lambda { |off, val| DL.memory_write_int(@@teb+off, val) }
        # the stack will probably never go higher than that whenever in the dll...
        set[0x4, DL.new_func_c('int get_sp(void) { asm("mov eax, esp  and eax, ~0xfff"); }') { DL.get_sp }]   # stack_base
        set[0x8, 0x10000]     # stack_limit
        set[0x18, @@teb]      # teb
        set[0x30, @@peb]      # peb
end
setup_teb() click to toggle source

maps a TEB/PEB in the current process, sets the fs register to point to it

# File samples/peldr.rb, line 159
def self.setup_teb
        @@teb = DL.memory_alloc(4096)
        @@peb = DL.memory_alloc(4096)
        populate_teb
        populate_peb
        fs = allocate_ldt_entry_teb
        DL.new_func_c('__fastcall void set_fs(int i) { asm("mov fs, ecx"); }') { DL.set_fs(fs) }
end
teb() click to toggle source
# File samples/peldr.rb, line 184
def self.teb ; @@teb ; end

Public Instance Methods

hook_export(name, proto, &b) click to toggle source

add a specific hook in the export table exemple: #hook_export('ExportedFunc', '__stdcall int f(int, char*)') { |i, p| blabla ; 1 }

# File samples/peldr.rb, line 112
def hook_export(name, proto, &b)
        ptr = @load_address + @pe.export.func_p
        @pe.export.exports.each { |e|
                n = e.name || e.ordinal
                if n == name
                        DL.callback_free(@eat_cb[name])
                        if proto.kind_of? Integer
                                cb = proto
                        else
                                cb = DL.callback_alloc_c(proto, &b)
                                @eat_cb[name] = cb
                        end
                        DL.memory_write_int(ptr, cb - @load_address)        # RVA
                end
                ptr += 4
        }
end
hook_import(libname, impname, proto, &b) click to toggle source

add a specific hook for an IAT function accepts a function pointer in proto exemple: #hook_import('KERNEL32.dll', 'GetProcAddress', '__stdcall int f(int, char*)') { |h, name| puts “getprocaddr #{name}” ; 0 }

# File samples/peldr.rb, line 90
def hook_import(libname, impname, proto, &b)
        @pe.imports.to_a.each { |id|
                next if id.libname != libname
                ptr = @load_address + id.iat_p
                id.imports.each { |i|
                        if i.name == impname
                                DL.callback_free(@iat_cb["#{libname}!#{impname}"])
                                if proto.kind_of? Integer
                                        cb = proto
                                else
                                        cb = DL.callback_alloc_c(proto, &b)
                                        @iat_cb["#{libname}!#{impname}"] = cb
                                end
                                DL.memory_write_int(ptr, cb)
                        end
                        ptr += 4
                }
        }
end
new_api_c(proto) click to toggle source

similar to Metasm::DynLdr.new_api_c for the mapped PE

# File samples/peldr.rb, line 140
def new_api_c(proto)
        proto += ';'    # allow 'int foo()'
        cp = DL.host_cpu.new_cparser
        cp.parse(proto)
        cp.toplevel.symbol.each_value { |v|
                next if not v.kind_of? Metasm::C::Variable      # enums
                if e = pe.export.exports.find { |e_| e_.name == v.name and e_.target }
                        DL.new_caller_for(cp, v, v.name.downcase, @load_address + pe.label_rva(e.target))
                end
        }

        cp.numeric_constants.each { |k, v, f|
                n = k.upcase
                n = "C#{n}" if n !~ /^[A-Z]/
                DL.const_set(n, v) if not DL.const_defined?(n) and v.kind_of? Integer
        }
end
reprotect_sections() click to toggle source

reset original expected memory protections for the sections the IAT may reside in a readonly section, so call this only after all hook_imports

# File samples/peldr.rb, line 77
def reprotect_sections
        @pe.sections.each { |s|
                p = ''
                p << 'r' if s.characteristics.include? 'MEM_READ'
                p << 'w' if s.characteristics.include? 'MEM_WRITE'
                p << 'x' if s.characteristics.include? 'MEM_EXECUTE'
                DL.memory_perm(@load_address + s.virtaddr, s.virtsize, p)
        }
end
run_init() click to toggle source

run the loaded PE entrypoint

# File samples/peldr.rb, line 131
def run_init
        ptr = @pe.optheader.entrypoint
        if ptr != 0
                ptr += @load_address
                DL.raw_invoke(ptr, [@load_address, 1, 1], 1)
        end
end