class FakeWinAPI

generate a fake PE which exports stuff found in k32/ntdll so that other lib may directly scan this PE with their own getprocaddr

Attributes

exports[RW]
win_version[RW]

Public Class Methods

new(elist=nil) click to toggle source
Calls superclass method PeLdr.new
# 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/i }.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) + "\0\0"
                        p = DL.str_ptr(cp)
                        @heap[p] = cp
                        p
                }
        end

Public Instance Methods

hook_export(*a, &b) click to toggle source
Calls superclass method PeLdr#hook_export
# File samples/peldr.rb, line 328
def hook_export(*a, &b)
        @exports |= [a.first]
        super(*a, &b)
end
intercept_iat(ldr) click to toggle source

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