generate a fake PE which exports stuff found in k32/ntdll so that other lib may directly scan this PE with their own getprocaddr
# File samples/peldr.rb, line 206 def initialize(elist=nil) @exports = [] @win_version = { :major => 5, :minor => 1, :build => 2600, :sp => 'Service pack 3', :sp_major => 3, :sp_minor => 0 } # if you know the exact list you need, put it there (much faster) if not elist elist = Metasm::WindowsExports::EXPORT.map { |k, v| k if v =~ /kernel32|ntdll/ }.compact elist |= ['free', 'malloc', 'memset', '??2@YAPAXI@Z', '_initterm', '_lock', '_unlock', '_wcslwr', '_wcsdup', '__dllonexit'] end src = ".libname 'emu_winapi'\ndummy: int 3\n" + elist.map { |e| ".export #{e.inspect} dummy" }.join("\n") super(Metasm::PE.assemble(Metasm::Ia32.new, src).encode_string(:lib), :eat) # put 'nil' instead of :eat if all exports are emu @heap = {} malloc = lambda { |sz| str = 0.chr*sz ; ptr = DL.str_ptr(str) ; @heap[ptr] = str ; ptr } lasterr = 0 # kernel32 hook_export('CloseHandle', '__stdcall int f(int)') { |a| 1 } hook_export('DuplicateHandle', '__stdcall int f(int, int, int, void*, int, int, int)') { |*a| DL.memory_write_int(a[3], a[1]) ; 1 } hook_export('EnterCriticalSection', '__stdcall int f(void*)') { 1 } hook_export('GetCurrentProcess', '__stdcall int f(void)') { -1 } hook_export('GetCurrentProcessId', '__stdcall int f(void)') { Process.pid } hook_export('GetCurrentThreadId', '__stdcall int f(void)') { Process.pid } hook_export('GetEnvironmentVariableW', '__stdcall int f(void*, void*, int)') { |name, resp, sz| next 0 if name == 0 s = DL.memory_read_wstrz(name) s = s.unpack('v*').pack('C*') rescue nil puts "GetEnv #{s.inspect}" if $VERBOSE v = ENV[s].to_s if resp and v.length*2+2 <= sz DL.memory_write(resp, (v.unpack('C*') << 0).pack('v*')) v.length*2 # 0 if not found else v.length*2+2 end } hook_export('GetLastError', '__stdcall int f(void)') { lasterr } hook_export('GetProcAddress', '__stdcall int f(int, char*)') { |h, v| v = DL.memory_read_strz(v) if v >= 0x10000 puts "GetProcAddr #{'0x%x' % h}, #{v.inspect}" if $VERBOSE @eat_cb[v] or raise "unemulated getprocaddr #{v}" } hook_export('GetSystemInfo', '__stdcall void f(void*)') { |ptr| DL.memory_write(ptr, [0, 0x1000, 0x10000, 0x7ffeffff, 1, 1, 586, 0x10000, 0].pack('V*')) 1 } hook_export('GetSystemTimeAsFileTime', '__stdcall void f(void*)') { |ptr| v = ((Time.now - Time.mktime(1971, 1, 1, 0, 0, 0) + 370*365.25*24*60*60) * 1000 * 1000 * 10).to_i DL.memory_write(ptr, [v & 0xffffffff, (v >> 32 & 0xffffffff)].pack('VV')) 1 } hook_export('GetTickCount', '__stdcall int f(void)') { (Time.now.to_i * 1000) & 0xffff_ffff } hook_export('GetVersion', '__stdcall int f(void)') { (@win_version[:build] << 16) | (@win_version[:major] << 8) | @win_version[:minor] } hook_export('GetVersionExA', '__stdcall int f(void*)') { |ptr| sz = DL.memory_read(ptr, 4).unpack('V').first data = [@win_version[:major], @win_version[:minor], @win_version[:build], 2, @win_version[:sp], @win_version[:sp_major], @win_version[:sp_minor]].pack('VVVVa128VV') DL.memory_write(ptr+4, data[0, sz-4]) 1 } hook_export('HeapAlloc', '__stdcall int f(int, int, int)') { |h, f, sz| malloc[sz] } hook_export('HeapCreate', '__stdcall int f(int, int, int)') { 1 } hook_export('HeapFree', '__stdcall int f(int, int, int)') { |h, f, p| @heap.delete p ; 1 } hook_export('InterlockedCompareExchange', '__stdcall int f(int*, int, int)'+ '{ asm("mov eax, [ebp+16] mov ecx, [ebp+12] mov edx, [ebp+8] lock cmpxchg [edx], ecx"); }') hook_export('InterlockedExchange', '__stdcall int f(int*, int)'+ '{ asm("mov eax, [ebp+12] mov ecx, [ebp+8] lock xchg [ecx], eax"); }') hook_export('InitializeCriticalSectionAndSpinCount', '__stdcall int f(int, int)') { 1 } hook_export('InitializeCriticalSection', '__stdcall int f(void*)') { 1 } hook_export('LeaveCriticalSection', '__stdcall int f(void*)') { 1 } hook_export('QueryPerformanceCounter', '__stdcall int f(void*)') { |ptr| v = (Time.now.to_f * 1000 * 1000).to_i DL.memory_write(ptr, [v & 0xffffffff, (v >> 32 & 0xffffffff)].pack('VV')) 1 } hook_export('SetLastError', '__stdcall int f(int)') { |i| lasterr = i ; 1 } hook_export('TlsAlloc', '__stdcall int f(void)') { 1 } # ntdll readustring = lambda { |p| DL.memory_read(*DL.memory_read(p, 8).unpack('vvV').values_at(2, 0)) } hook_export('RtlEqualUnicodeString', '__stdcall int f(void*, void*, int)') { |s1, s2, cs| s1 = readustring[s1] s2 = readustring[s2] puts "RtlEqualUnicodeString #{s1.unpack('v*').pack('C*').inspect}, #{s2.unpack('v*').pack('C*').inspect}, #{cs}" if $VERBOSE if cs == 1 s1 = s1.downcase s2 = s2.downcase end s1 == s2 ? 1 : 0 } hook_export('MultiByteToWideChar', '__stdcall int f(int, int, void*, int, void*, int)') { |cp, fl, ip, is, op, os| is = DL.memory_read_strz(ip).length if is == 0xffff_ffff if os == 0 is elsif os >= is*2 # not sure with this.. DL.memory_write(op, DL.memory_read(ip, is).unpack('C*').pack('v*')) is else 0 end } hook_export('LdrUnloadDll', '__stdcall int f(int)') { 0 } # msvcrt hook_export('free', 'void f(int)') { |i| @heap.delete i ; 0} hook_export('malloc', 'int f(int)') { |i| malloc[i] } hook_export('memset', 'char* f(char* p, char c, int n) { while (n--) p[n] = c; return p; }') hook_export('??2@YAPAXI@Z', 'int f(int)') { |i| raise 'fuuu' if i > 0x100000 ; malloc[i] } # at some point we're called with a ptr as arg, may be a peldr bug hook_export('__dllonexit', 'int f(int, int, int)') { |i, ii, iii| i } hook_export('_initterm', 'void f(void (**p)(void), void*p2) { while(p < p2) { if (*p) (**p)(); p++; } }') hook_export('_lock', 'void f(int)') { 0 } hook_export('_unlock', 'void f(int)') { 0 } hook_export('_wcslwr', '__int16* f(__int16* p) { int i=-1; while (p[++i]) p[i] |= 0x20; return p; }') hook_export('_wcsdup', 'int f(__int16* p)') { |p| cp = DL.memory_read_wstrz(p) + "\00\\00"" p = DL.str_ptr(cp) @heap[p] = cp p } end
# File samples/peldr.rb, line 328 def hook_export(*a, &b) @exports |= [a.first] super(*a, &b) end
take another PeLdr and patch its IAT with functions from our @exports (eg our explicit export hooks)
# File samples/peldr.rb, line 334 def intercept_iat(ldr) ldr.pe.imports.to_a.each { |id| id.imports.each { |i| next if not @exports.include? i.name or not @eat_cb[i.name] ldr.hook_import(id.libname, i.name, @eat_cb[i.name]) } } end