class PeLdr
Constants
- DL
Attributes
Public Class Methods
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
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
# File samples/peldr.rb, line 185 def self.peb ; @@peb ; end
# 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
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
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
# File samples/peldr.rb, line 184 def self.teb ; @@teb ; end
Public Instance Methods
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
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
similar to DL.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
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 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