@@ -40,6 +40,14 @@ def perform_manual_getproc_loadlib_32(target, dll_name):
4040 code += x86 .Call (":FUNC_GETPROCADDRESS32" )
4141 code += x86 .Push (x86 .mem ("[ECX + 8]" ))
4242 code += x86 .Call ("EAX" ) # LoadLibrary
43+ code += x86 .Cmp ("EAX" , 0 )
44+ code += x86 .Jnz (":end" )
45+ # GetLastError()
46+ # I really don't want to resolve another function
47+ # For a field that have been the same since XP/Win2003
48+ code += x86 .Mov ('EAX' , x86 .mem ('fs:[0x34]' ))
49+ code += x86 .Add ("EAX" , 0x80000000 )
50+ code += x86 .Label (":end" )
4351 code += x86 .Pop ("ECX" )
4452 code += x86 .Pop ("ECX" )
4553 code += x86 .Ret ()
@@ -60,9 +68,18 @@ def perform_manual_getproc_loadlib_32(target, dll_name):
6068
6169 t = target .execute (RemoteManualLoadLibray .get_code (), addr4 )
6270 t .wait ()
63- if not t .exit_code :
64- raise InjectionFailedError ("Injection of <{0}> failed" .format (dll_name ))
65- return True
71+ module_baseaddr = t .exit_code
72+
73+ if module_baseaddr & 0x80000000 :
74+ # Not a possible userland addr -> its a GetLastError()
75+ error_code = module_baseaddr & 0x7fffffff
76+ module_baseaddr = None
77+ real_error = ctypes .WinError (error_code )
78+ myexc = InjectionFailedError (u"Injection of <{0}> failed due to error <{1}> in injected process" .format (dll_name , str (real_error )))
79+ myexc .__cause__ = real_error
80+ raise myexc
81+
82+ return module_baseaddr
6683
6784def perform_manual_getproc_loadlib_64 (target , dll_name ):
6885 dll = get_kernel32_dll_name ()
@@ -76,18 +93,23 @@ def perform_manual_getproc_loadlib_64(target, dll_name):
7693 code += x64 .Mov ("RDX" , x64 .mem ("[R15 + 8]" ))
7794 code += x64 .Call (":FUNC_GETPROCADDRESS64" )
7895 code += x64 .Mov ("RCX" , x64 .mem ("[R15 + 0x10]" ))
79- code += x64 .Push ("RCX" )
80- code += x64 .Push ("RCX" )
81- code += x64 .Push ("RCX" )
96+ code += (x64 .Push ("RCX" ) * 3 )
8297 code += x64 .Call ("RAX" ) # LoadLibrary
83- code += x64 .Pop ("RCX" )
84- code += x64 .Pop ("RCX" )
85- code += x64 .Pop ("RCX" )
98+ code += (x64 .Pop ("RCX" ) * 3 )
99+ code += x64 .Mov ("RCX" , x64 .mem ("[R15]" ))
100+ code += x64 .Mov (x64 .mem ("[RCX]" ), "RAX" )
101+ # GetLastError()
102+ # I really don't want to resolve another function
103+ # For a field that have been the same since XP/Win2003
104+ code += x64 .Mov ('RAX' , x64 .mem ('gs:[0x68]' ))
86105 code += x64 .Ret ()
87106
88107 RemoteManualLoadLibray += GetProcAddress64
89108
90109 with target .allocated_memory (0x1000 ) as addr :
110+ # Addr contains the name of kernel32
111+ # The data at addr are discadable after the call
112+ # So, on return it contains the return PVOID64 value of LoadLibraryA
91113 addr2 = addr + len (dll )
92114 addr3 = addr2 + len (api )
93115 addr4 = addr3 + len (dll_to_load )
@@ -101,17 +123,55 @@ def perform_manual_getproc_loadlib_64(target, dll_name):
101123
102124 t = target .execute (RemoteManualLoadLibray .get_code (), addr4 )
103125 t .wait ()
104- if not t .exit_code :
105- raise InjectionFailedError ("Injection of <{0}> failed" .format (dll_name ))
106- return True
126+ module_baseaddr = target .read_ptr (addr )
127+ if not module_baseaddr :
128+ module_baseaddr = None
129+ real_error = ctypes .WinError (t .exit_code )
130+ myexc = InjectionFailedError (u"Injection of <{0}> failed due to error <{1}> in injected process" .format (dll_name , str (real_error )))
131+ myexc .__cause__ = real_error
132+ raise myexc
133+
134+ return module_baseaddr
135+
136+ def generate_simple_LoadLibraryW_32_with_error (k32 ):
137+ """A shellcode that execute LoadLibraryW(param) and returns the value.
138+ If LoadLibraryW fails -> returns (GetLastError | 0x10000000)
107139
108- def generate_simple_LoadLibraryW_64 (load_libraryW , remote_store ):
140+ As a valid 32b modules will never be in >=0x80000000,
141+ this allow to determine if the call was successful of not"""
142+ load_libraryW = k32 .pe .exports ["LoadLibraryW" ]
143+ GetLastError = k32 .pe .exports ["GetLastError" ]
144+
145+ code = x86 .MultipleInstr ()
146+ code += x86 .Mov ("EAX" , x86 .mem ("[ESP + 4]" ))
147+ code += x86 .Push ("EAX" )
148+ code += x86 .Mov ("EAX" , load_libraryW )
149+ code += x86 .Call ("EAX" )
150+ code += x86 .Cmp ("EAX" , 0 )
151+ code += x86 .Jnz (":end" )
152+ code += x86 .Mov ("EAX" , GetLastError )
153+ code += x86 .Call ("EAX" )
154+ code += x86 .Add ("EAX" , 0x80000000 )
155+ code += x86 .Label (":end" )
156+ code += x86 .Ret ()
157+ return code .get_code ()
158+
159+ def generate_simple_LoadLibraryW_64_with_error (k32 , remote_store ):
160+ """A shellcode that execute LoadLibraryW(param) and store the value at a fixed address.
161+ This allow a 32b process to inject and retrieve a 64bit module address
162+
163+ Thread return value is the result of GetLastError()
164+ """
165+ load_libraryW = k32 .pe .exports ["LoadLibraryW" ]
166+ GetLastError = k32 .pe .exports ["GetLastError" ]
109167 code = RemoteLoadLibrayStub = x64 .MultipleInstr ()
110168 code += x64 .Mov ("RAX" , load_libraryW )
111169 code += (x64 .Push ("RDI" ) * 5 ) # Prepare stack
112170 code += x64 .Call ("RAX" )
113- code += (x64 .Pop ("RDI" ) * 5 ) # Clean stack
114171 code += x64 .Mov (x64 .deref (remote_store ), "RAX" )
172+ code += x64 .Mov ("RAX" , GetLastError ) # Add a jump ?
173+ code += x64 .Call ("RAX" )
174+ code += (x64 .Pop ("RDI" ) * 5 ) # Clean stack
115175 code += x64 .Ret ()
116176 return RemoteLoadLibrayStub .get_code ()
117177
@@ -136,17 +196,21 @@ def load_dll_in_remote_process(target, dll_path):
136196 if k32 :
137197 # We have kernel32 \o/
138198 k32 = k32 [0 ]
139- try :
140- load_libraryW = k32 .pe .exports ["LoadLibraryW" ]
141- except KeyError :
142- raise ValueError ("Kernel32 have no export <LoadLibraryA> (wtf)" )
143-
144199 with target .allocated_memory (0x1000 ) as addr :
145200 if target .bitness == 32 :
146- target .write_memory (addr , (dll_path + "\x00 " ).encode ('utf-16le' ))
147- t = target .create_thread (load_libraryW , addr )
201+ shellcode32 = generate_simple_LoadLibraryW_32_with_error (k32 )
202+ encoded_dll_name = (dll_path + "\x00 " ).encode ('utf-16le' )
203+ paramaddr = addr
204+ target .write_memory (addr , encoded_dll_name )
205+ shellcode_addr = addr + len (encoded_dll_name )
206+ target .write_memory (shellcode_addr , shellcode32 )
207+ t = target .create_thread (shellcode_addr , paramaddr )
148208 t .wait ()
149- module_baseaddr = t .exit_code
209+ exit_code = module_baseaddr = t .exit_code
210+ if module_baseaddr & 0x80000000 :
211+ # Not a possible userland addr -> its a GetLastError()
212+ module_baseaddr = None
213+ exit_code = exit_code & 0x7fffffff
150214 else :
151215 # For 64b target we need a special stub as the return value of
152216 # load_libraryW does not fit in t.exit_code (DWORD)
@@ -158,14 +222,18 @@ def load_dll_in_remote_process(target, dll_path):
158222 param_addr = addr
159223 addr += len (full_dll_name )
160224 shellcode_addr = addr
161- shellcode = generate_simple_LoadLibraryW_64 ( load_libraryW , retval_addr )
225+ shellcode = generate_simple_LoadLibraryW_64_with_error ( k32 , retval_addr )
162226 target .write_memory (shellcode_addr , shellcode )
163227 t = target .create_thread (shellcode_addr , param_addr )
164228 t .wait ()
229+ exit_code = t .exit_code
165230 module_baseaddr = target .read_ptr (retval_addr )
166231
167232 if not module_baseaddr :
168- raise InjectionFailedError (u"Injection of <{0}> failed" .format (dll_path ))
233+ real_error = ctypes .WinError (exit_code )
234+ myexc = InjectionFailedError (u"Injection of <{0}> failed due to error <{1}> in injected process" .format (dll_path , str (real_error )))
235+ myexc .__cause__ = real_error
236+ raise myexc
169237 dbgprint ("DLL Injected via LoadLibray" , "DLLINJECT" )
170238 # Cannot return the full return value of load_libraryW in 64b target.. (exit_code is a DWORD)
171239 return module_baseaddr
0 commit comments