#include "main.h" ULONG CsrAllocateMessagePointer( IN OUT PCSR_CAPTURE_BUFFER CaptureBuffer, IN ULONG MessageLength, OUT PVOID* CapturedData) { if (MessageLength == 0) { *CapturedData = NULL; CapturedData = NULL; } else { /* Set the capture data at our current available buffer */ *CapturedData = CaptureBuffer->BufferEnd; /* Align it to a 4-byte boundary */ MessageLength = (MessageLength + 3) & ~3; /* Move our available buffer beyond this space */ CaptureBuffer->BufferEnd = (PVOID)((ULONG_PTR)CaptureBuffer->BufferEnd + MessageLength); } /* Write down this pointer in the array and increase the count */ CaptureBuffer->PointerOffsetsArray[CaptureBuffer->PointerCount++] = (ULONG_PTR)CapturedData; /* Return the aligned length */ return MessageLength; } VOID CsrCaptureMessageString( IN PCSR_CAPTURE_BUFFER CaptureBuffer, IN PCSTR String, IN ULONG StringLength, IN ULONG MaximumLength, OUT PSTRING CapturedString) { /* * If we don't have a string, initialize an empty one, * otherwise capture the given string. */ if (!String) { CapturedString->Length = 0; CapturedString->MaximumLength = (USHORT)MaximumLength; /* Allocate a pointer for it */ CsrAllocateMessagePointer(CaptureBuffer, MaximumLength, (PVOID*)&CapturedString->Buffer); } else { /* Cut-off the string length if needed */ if (StringLength > MaximumLength) StringLength = MaximumLength; CapturedString->Length = (USHORT)StringLength; /* Allocate a buffer and get its size */ CapturedString->MaximumLength = (USHORT)CsrAllocateMessagePointer(CaptureBuffer, MaximumLength, (PVOID*)&CapturedString->Buffer); /* If the string has data, copy it into the buffer */ if (StringLength) memcpy(CapturedString->Buffer, String, StringLength); } /* Null-terminate the string if we don't take up the whole space */ if (CapturedString->Length < CapturedString->MaximumLength) CapturedString->Buffer[CapturedString->Length] = '\0'; } VOID CsrCaptureMessageUnicodeStringInPlace( IN OUT PCSR_CAPTURE_BUFFER CaptureBuffer, IN PUNICODE_STRING String) { if (!String) return; /* This is a way to capture the UNICODE string, since (Maximum)Length are also in bytes */ CsrCaptureMessageString(CaptureBuffer, (PCSTR)String->Buffer, String->Length, String->MaximumLength, (PSTRING)String); /* Null-terminate the string */ if (String->MaximumLength >= String->Length + sizeof(WCHAR)) { String->Buffer[String->Length / sizeof(WCHAR)] = L'\0'; } } /* * find a pattern within the code of an API, * get the address of a global variable and * return its address */ PVOID find_generic( PVOID funcAddress, PBYTE searchPattern, ULONG32 patternSize) { ULONG sizeOfRegion = 0x100; for (ULONG i = 0; i < sizeOfRegion; i++) { PVOID address = RVA2VA(PVOID, funcAddress, i); if (!memcmp(address, searchPattern, patternSize)) { ULONG32 ripOffset = *RVA2VA(PULONG32, address, patternSize); PVOID rip = RVA2VA(PVOID, address, patternSize + sizeof(ULONG32)); return RVA2VA(PVOID, rip, ripOffset); } } return NULL; } /* * We need to find the global variable CsrPortMemoryRemoteDelta * which is referenced in CsrClientCallServer. * This is a bit hacky but it works */ BOOL find_csr_port_memory_remote_delta(PULONG_PTR pCsrPortMemoryRemoteDelta) { BYTE search_pattern[] = { 0x4c, 0x8b, 0x0d }; PVOID address = NULL; PVOID addr_CsrClientCallServer = get_function_address( get_library_address(NTDLL_DLL, TRUE), CsrClientCallServer_SW2_HASH, 0); if (!addr_CsrClientCallServer) { api_not_found("CsrClientCallServer"); goto cleanup; } address = find_generic( addr_CsrClientCallServer, search_pattern, sizeof(search_pattern)); if (address) { *pCsrPortMemoryRemoteDelta = *(PULONG_PTR)address; DPRINT("[i] CsrPortMemoryRemoteDelta: 0x%p\n", (PVOID)*pCsrPortMemoryRemoteDelta); return TRUE; } cleanup: PRINT_ERR("[!] could not find the CsrPortMemoryRemoteDelta!\n"); return FALSE; } /* * We need to find the global variable CsrPortHandle * which is referenced in CsrClientCallServer. * This is a bit hacky but it works */ BOOL find_csr_port_handle(PHANDLE pCsrPortHandle) { BYTE search_pattern[] = { 0x48, 0x8b, 0x0d }; PVOID address = NULL; PVOID addr_CsrClientCallServer = get_function_address( get_library_address(NTDLL_DLL, TRUE), CsrClientCallServer_SW2_HASH, 0); if (!addr_CsrClientCallServer) { api_not_found("CsrClientCallServer"); goto cleanup; } address = find_generic( addr_CsrClientCallServer, search_pattern, sizeof(search_pattern)); if (address) { *pCsrPortHandle = *(PHANDLE)address; DPRINT("[i] CsrPortHandle: 0x%p\n", *pCsrPortHandle); return TRUE; } cleanup: PRINT_ERR("[!] could not find the CsrPortHandle!\n"); return FALSE; } /* * We need to find the global variable CsrPortBaseTag * which is referenced in CsrAllocateCaptureBuffer. * This is a bit hacky but it works */ BOOL find_csr_port_base_tag(PULONG32 pCsrPortBaseTag) { BYTE search_pattern[] = { 0x8b, 0x15 }; PVOID address = NULL; PVOID addr_CsrAllocateCaptureBuffer = get_function_address( get_library_address(NTDLL_DLL, TRUE), CsrAllocateCaptureBuffer_SW2_HASH, 0); if (!addr_CsrAllocateCaptureBuffer) { api_not_found("CsrAllocateCaptureBuffer"); goto cleanup; } address = find_generic( addr_CsrAllocateCaptureBuffer, search_pattern, sizeof(search_pattern)); if (address) { *pCsrPortBaseTag = *(PULONG32)address; DPRINT("[i] CsrPortBaseTag: 0x%x\n", *pCsrPortBaseTag); return TRUE; } cleanup: PRINT_ERR("[!] could not find the CsrPortBaseTag!\n"); return FALSE; } /* * We need to find the global variable CsrPortHeap * which is referenced in CsrAllocateCaptureBuffer. * This is a bit hacky but it works */ BOOL find_csr_port_heap(PHANDLE pCsrPortHeap) { BYTE search_pattern[] = { 0x48, 0x8b, 0x0d }; PVOID address = NULL; PVOID addr_CsrAllocateCaptureBuffer = get_function_address( get_library_address(NTDLL_DLL, TRUE), CsrAllocateCaptureBuffer_SW2_HASH, 0); if (!addr_CsrAllocateCaptureBuffer) { api_not_found("CsrAllocateCaptureBuffer"); goto cleanup; } address = find_generic( addr_CsrAllocateCaptureBuffer, search_pattern, sizeof(search_pattern)); if (address) { *pCsrPortHeap = *(PHANDLE)address; DPRINT("[i] CsrPortHeap: 0x%p\n", *pCsrPortHeap); return TRUE; } cleanup: PRINT_ERR("[!] could not find the CsrPortHeap!\n"); return FALSE; } BOOL CsrFreeCaptureBuffer(PCSR_CAPTURE_BUFFER captureBuffer) { HANDLE CsrPortHeap = NULL; BOOL success = FALSE; RtlFreeHeap_t _RtlFreeHeap = get_function_address( get_library_address(NTDLL_DLL, TRUE), RtlFreeHeap_SW2_HASH, 0); if (!_RtlFreeHeap) { api_not_found("RtlFreeHeap"); goto cleanup; } success = find_csr_port_heap(&CsrPortHeap); if (!success || !CsrPortHeap) return FALSE; success = _RtlFreeHeap(CsrPortHeap, 0, captureBuffer); cleanup: return success; } PCSR_CAPTURE_BUFFER CsrAllocateCaptureBuffer(ULONG ArgumentCount, ULONG BufferSize) { HANDLE CsrPortHeap = NULL; ULONG32 CsrPortBaseTag = 0; BOOL success = FALSE; PCSR_CAPTURE_BUFFER CaptureBuffer = NULL; ULONG OffsetsArraySize = 0; RtlAllocateHeap_t _RtlAllocateHeap = NULL; _RtlAllocateHeap = (RtlAllocateHeap_t)(ULONG_PTR)get_function_address( get_library_address(NTDLL_DLL, TRUE), RtlAllocateHeap_SW2_HASH, 0); if (!_RtlAllocateHeap) { api_not_found("RtlAllocateHeap"); goto cleanup; } success = find_csr_port_heap(&CsrPortHeap); if (!success || !CsrPortHeap) goto cleanup; success = find_csr_port_base_tag(&CsrPortBaseTag); if (!success) goto cleanup; OffsetsArraySize = ArgumentCount * sizeof(ULONG_PTR); /* Add the size of the header and of the pointer-offset array */ BufferSize += FIELD_OFFSET(CSR_CAPTURE_BUFFER, PointerOffsetsArray) + OffsetsArraySize; /* Add the size of the alignment padding for each argument */ BufferSize += ArgumentCount * 3; /* Align it to a 4-byte boundary */ BufferSize = (BufferSize + 3) & ~3; CaptureBuffer = (PCSR_CAPTURE_BUFFER)_RtlAllocateHeap(CsrPortHeap, CsrPortBaseTag, BufferSize); if (!CaptureBuffer) goto cleanup; /* Initialize the header */ CaptureBuffer->Size = BufferSize; CaptureBuffer->PointerCount = 0; /* Initialize the pointer-offset array */ memset(CaptureBuffer->PointerOffsetsArray, 0, OffsetsArraySize); /* Point to the start of the free buffer */ CaptureBuffer->BufferEnd = (PVOID)((ULONG_PTR)CaptureBuffer->PointerOffsetsArray + OffsetsArraySize); cleanup: /* Return the address of the buffer */ return CaptureBuffer; } NTSTATUS CsrCaptureMessageMultiUnicodeStringsInPlace( OUT PCSR_CAPTURE_BUFFER* pCaptureBuffer, IN ULONG StringsCount, IN PUNICODE_STRING* MessageStrings) { ULONG Count = 0; ULONG Size = 0; /* Compute the required size for the capture buffer */ Count = 0; while (Count < StringsCount) { if (MessageStrings[Count]) Size += MessageStrings[Count]->MaximumLength; ++Count; } *pCaptureBuffer = CsrAllocateCaptureBuffer(StringsCount, Size); if (!*pCaptureBuffer) return STATUS_NO_MEMORY; /* Now capture each UNICODE string */ Count = 0; while (Count < StringsCount) { if (MessageStrings[Count]) CsrCaptureMessageUnicodeStringInPlace(*pCaptureBuffer, MessageStrings[Count]); ++Count; } return STATUS_SUCCESS; } /* * Register the new process to the CSRSS using syscalls for extra sneakyness */ NTSTATUS CsrClientCallServer( IN PBASE_API_MSG ApiMessage, IN PCSR_CAPTURE_BUFFER CaptureBuffer, IN ULONG ApiNumber, IN ULONG DataLength) { HANDLE CsrPortHandle = NULL; ULONG_PTR CsrPortMemoryRemoteDelta = 0; ULONG PointerCount = 0; PULONG_PTR OffsetPointer = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; SIZE_T BufferLength = 0x3b8; BOOL success = FALSE; success = find_csr_port_handle(&CsrPortHandle); if (!success || !CsrPortHandle) goto cleanup; success = find_csr_port_memory_remote_delta(&CsrPortMemoryRemoteDelta); if (!success || !CsrPortMemoryRemoteDelta) goto cleanup; /* Fill out the Port Message Header */ ApiMessage->PortHeader.Header.u1.s1.DataLength = DataLength + 0x18; ApiMessage->PortHeader.Header.u1.s1.TotalLength = DataLength + 0x40; ApiMessage->PortHeader.ApiNumber = ApiNumber; ApiMessage->PortHeader.CaptureBuffer = NULL; if (CaptureBuffer) { /* * We have to convert from our local (client) view * to the remote (server) view. */ ApiMessage->PortHeader.CaptureBuffer = (PCSR_CAPTURE_HEADER) ((ULONG_PTR)CaptureBuffer + CsrPortMemoryRemoteDelta); /* Lock the buffer */ CaptureBuffer->BufferEnd = NULL; /* * Each client pointer inside the CSR message is converted into * a server pointer, and each pointer to these message pointers * is converted into an offset. */ PointerCount = CaptureBuffer->PointerCount; OffsetPointer = CaptureBuffer->PointerOffsetsArray; while (PointerCount--) { if (*OffsetPointer != 0) { *(PULONG_PTR)*OffsetPointer += CsrPortMemoryRemoteDelta; *OffsetPointer -= (ULONG_PTR)ApiMessage; } ++OffsetPointer; } } // trigger the syscall :) status = _NtAlpcSendWaitReceivePort( CsrPortHandle, 0x20000, ApiMessage, NULL, ApiMessage, &BufferLength, NULL, NULL); if (CaptureBuffer) { /* * We have to convert back from the remote (server) view * to our local (client) view. */ ApiMessage->PortHeader.CaptureBuffer = (PCSR_CAPTURE_HEADER) ((ULONG_PTR)ApiMessage->PortHeader.CaptureBuffer - CsrPortMemoryRemoteDelta); /* * Convert back the offsets into pointers to CSR message * pointers, and convert back these message server pointers * into client pointers. */ PointerCount = CaptureBuffer->PointerCount; OffsetPointer = CaptureBuffer->PointerOffsetsArray; while (PointerCount--) { if (*OffsetPointer != 0) { *OffsetPointer += (ULONG_PTR)ApiMessage; *(PULONG_PTR)*OffsetPointer -= CsrPortMemoryRemoteDelta; } ++OffsetPointer; } } cleanup: return status; } NTSTATUS register_with_csr( IN PPROCESS_INFORMATION pi, IN PPS_CREATE_INFO ci, IN LPCWSTR process_image, IN LPCWSTR nt_process_image) { NTSTATUS status = STATUS_UNSUCCESSFUL; PCSR_CAPTURE_BUFFER captureBuffer = NULL; BASE_API_MSG m = { 0 }; PUNICODE_STRING stringToCapture[4] = { 0 }; BYTE locale[0x14] = { 0 }; RtlInitUnicodeString_t _RtlInitUnicodeString = NULL; _RtlInitUnicodeString = (RtlInitUnicodeString_t)(ULONG_PTR)get_function_address( get_library_address(NTDLL_DLL, TRUE), RtlInitUnicodeString_SW2_HASH, 0); if (!_RtlInitUnicodeString) { api_not_found("RtlInitUnicodeString"); goto cleanup; } // build msg for csr memset(&m, 0, sizeof(BASE_API_MSG)); // basic fields m.CreateProcessMSG.ProcessHandle = (HANDLE)((ULONG_PTR)pi->hProcess | 2); m.CreateProcessMSG.ThreadHandle = pi->hThread; m.CreateProcessMSG.ClientId.UniqueProcess = (HANDLE)(ULONG_PTR)pi->dwProcessId; m.CreateProcessMSG.ClientId.UniqueThread = (HANDLE)(ULONG_PTR)pi->dwThreadId; m.CreateProcessMSG.CreationFlags = 0x0; m.CreateProcessMSG.PebAddressNative = (ULONG64)ci->SuccessState.PebAddressNative; m.CreateProcessMSG.PebAddressWow64 = 0x0; #ifdef _WIN64 m.CreateProcessMSG.ProcessorArchitecture = 9; #else m.CreateProcessMSG.ProcessorArchitecture = 0; #endif // sxs m.CreateProcessMSG.Sxs.Flags = 0x40; m.CreateProcessMSG.Sxs.ProcessParameterFlags = 0x6001; m.CreateProcessMSG.Sxs.FileHandle = ci->SuccessState.FileHandle; _RtlInitUnicodeString(&m.CreateProcessMSG.Sxs.SxsWin32ExePath, process_image); _RtlInitUnicodeString(&m.CreateProcessMSG.Sxs.SxsNtExePath, nt_process_image); m.CreateProcessMSG.Sxs.PolicyStream.ManifestAddress = ci->SuccessState.ManifestAddress; m.CreateProcessMSG.Sxs.PolicyStream.ManifestSize = ci->SuccessState.ManifestSize; m.CreateProcessMSG.Sxs.FileName3.Length = 0x14; m.CreateProcessMSG.Sxs.FileName3.MaximumLength = 0x14; m.CreateProcessMSG.Sxs.FileName3.Buffer = (PWCH)locale; memcpy(m.CreateProcessMSG.Sxs.FileName3.Buffer, "\x65\x00\x6e\x00\x2d\x00\x55\x00\x53\x00\x00\x00\x65\x00\x6e\x00\x00\x00\x00\x00", 0x14); // wtf windows _RtlInitUnicodeString(&m.CreateProcessMSG.Sxs.FileName4, L"-----------------------------------------------------------"); // notify the windows subsystem stringToCapture[0] = &m.CreateProcessMSG.Sxs.SxsWin32ExePath; stringToCapture[1] = &m.CreateProcessMSG.Sxs.SxsNtExePath; stringToCapture[2] = &m.CreateProcessMSG.Sxs.FileName3; stringToCapture[3] = &m.CreateProcessMSG.Sxs.FileName4; // here we could simply call ntdll!CsrCaptureMessageMultiUnicodeStringsInPlace status = CsrCaptureMessageMultiUnicodeStringsInPlace(&captureBuffer, 4, stringToCapture); if (!NT_SUCCESS(status)) { PRINT_ERR("[-] CsrCaptureMessageMultiUnicodeStringsInPlace failed, status: 0x%lx\n", status); goto cleanup; } DPRINT("[+] Got the capture buffer = 0x%p\n", captureBuffer); // here we could simply call ntdll!CsrClientCallServer status = CsrClientCallServer( &m, captureBuffer, CSR_MAKE_API_NUMBER(BASESRV_SERVERDLL_INDEX, BasepRegisterThread), sizeof(BASE_API_MSG) - sizeof(PORT_MESSAGE)); if (!NT_SUCCESS(status)) { PRINT_ERR("[-] CsrClientCallServer failed, status: 0x%lx\n", status); goto cleanup; } PRINT("[+] Registered new process with the CSRSS\n"); cleanup: if (captureBuffer) { // here we could simply call ntdll!CsrFreeCaptureBuffer CsrFreeCaptureBuffer(captureBuffer); } return status; } HANDLE get_parent_handle( IN DWORD ppid) { HANDLE hProcess = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; OBJECT_ATTRIBUTES obj_attributes = { 0 }; CLIENT_ID client_id = { 0 }; if (!ppid) { hProcess = NtCurrentProcess(); goto cleanup; } InitializeObjectAttributes( &obj_attributes, NULL, 0, NULL, NULL); client_id.UniqueProcess = (HANDLE)(ULONG_PTR)ppid; status = _NtOpenProcess( &hProcess, PROCESS_CREATE_PROCESS, &obj_attributes, &client_id); if (!NT_SUCCESS(status)) { PRINT_ERR("[-] NtOpenProcess failed, status: 0x%lx\n", status); hProcess = NtCurrentProcess(); goto cleanup; } PRINT("[i] Parent process\n"); PRINT("[i] PPID = %ld\n", ppid); PRINT("[i] hPprocess = 0x%p\n", hProcess); cleanup: return hProcess; } PVOID get_peb_address( IN HANDLE hProcess) { PROCESS_BASIC_INFORMATION basic_info = { 0 }; basic_info.PebBaseAddress = 0; PROCESSINFOCLASS ProcessInformationClass = 0; NTSTATUS status = _NtQueryInformationProcess( hProcess, ProcessInformationClass, &basic_info, sizeof(PROCESS_BASIC_INFORMATION), NULL); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtQueryInformationProcess, status: 0x%lx\n", status); return 0; } return basic_info.PebBaseAddress; } DWORD get_rva_entrypoint( PIMAGE_DOS_HEADER dos) { PIMAGE_NT_HEADERS nt = NULL; DWORD rva_entrypoint = 0; // check the MZ magic bytes if (dos->e_magic != 0x5A4D) { printf("[-] invalid magic bytes\n"); goto cleanup; } nt = RVA(PIMAGE_NT_HEADERS, dos, dos->e_lfanew); // check the NT_HEADER signature if (nt->Signature != IMAGE_NT_SIGNATURE) goto cleanup; rva_entrypoint = nt->OptionalHeader.AddressOfEntryPoint; cleanup: return rva_entrypoint; } PVOID get_entrypoint_address( IN HANDLE hProcess, IN PVOID image_base_address, IN PIMAGE_DOS_HEADER dos) { DWORD rva_entrypoint_addr = 0; PVOID entrypoint_addr = NULL; rva_entrypoint_addr = get_rva_entrypoint(dos); if (!rva_entrypoint_addr) { PRINT("[-] Failed to get the address of the entry point\n"); goto cleanup; } entrypoint_addr = RVA2VA(PVOID, image_base_address, rva_entrypoint_addr); cleanup: return entrypoint_addr; } PVOID get_image_base_address( IN HANDLE hProcess, IN PVOID peb_address, IN BOOL is_x64) { PVOID read_addr = NULL; SIZE_T buffer_size = 0; PVOID image_base_address = NULL; NTSTATUS status = STATUS_UNSUCCESSFUL; if (is_x64) { // is x64 read_addr = RVA2VA(PVOID, peb_address, 0x10); buffer_size = 8; } else { // is x86 read_addr = RVA2VA(PVOID, peb_address, 0x08); buffer_size = 4; } status = _NtReadVirtualMemory( hProcess, read_addr, &image_base_address, buffer_size, NULL); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtReadVirtualMemory, status: 0x%lx\n", status); return NULL; } return image_base_address; } VOID find_manifest( IN HANDLE hProcess, IN PVOID image_base_address, IN PIMAGE_DOS_HEADER dos, IN BOOL is_x64, OUT PVOID* manifest_address, OUT PDWORD manifest_size) { NTSTATUS status = STATUS_UNSUCCESSFUL; PIMAGE_NT_HEADERS nt = NULL; PIMAGE_SECTION_HEADER sec = NULL; PVOID resource_section_address = NULL; SIZE_T resource_section_size = 0; PVOID resource_section = NULL; *manifest_address = NULL; *manifest_size = 0; nt = RVA(PIMAGE_NT_HEADERS, dos, dos->e_lfanew); // check the NT_HEADER signature if (nt->Signature != IMAGE_NT_SIGNATURE) goto cleanup; if (is_x64) { sec = RVA2VA(PIMAGE_SECTION_HEADER, &nt->OptionalHeader, 0xf0); } else { sec = RVA2VA(PIMAGE_SECTION_HEADER, &nt->OptionalHeader, 0xe0); } while (TRUE) { if (!sec->VirtualAddress) { //PRINT("[-] Could not find the .rsrc section\n"); goto cleanup; } if (!strncmp((LPCSTR)sec->Name, ".rsrc", 6)) { resource_section_address = RVA2VA(PVOID, image_base_address, sec->VirtualAddress); resource_section_size = sec->SizeOfRawData; //PRINT("found the .rsrc section at: 0x%p (0x%lx)\n", resource_section_address, resource_section_size); break; } sec = RVA2VA(PIMAGE_SECTION_HEADER, sec, sizeof(IMAGE_SECTION_HEADER)); } resource_section = calloc(resource_section_size, 1); status = _NtReadVirtualMemory( hProcess, resource_section_address, resource_section, resource_section_size, NULL); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtReadVirtualMemory, status: 0x%lx\n", status); goto cleanup; } // look for the XML document inside the .rsrc section SIZE_T index = 0; PVOID ptr = NULL; while (index < resource_section_size) { ptr = RVA2VA(PVOID, resource_section, index); if (!strncmp(ptr, "EnvironmentSize + pProcParams->MaximumLength; status = _NtAllocateVirtualMemory( hProcess, ¶ms_remote_address, 0, ®ion_size, MEM_COMMIT, PAGE_READWRITE); if (!NT_SUCCESS(status)) { PRINT_ERR("[-] NtAllocateVirtualMemory failed, status: 0x%lx\n", status); goto cleanup; } // we need to adjust all the pointers so that they make sense on the remote process params_address_diff = (ULONG_PTR)params_remote_address - (ULONG_PTR)pProcParams; if (pProcParams->CurrentDirectory.DosPath.Buffer) pProcParams->CurrentDirectory.DosPath.Buffer = RVA2VA(PVOID, pProcParams->CurrentDirectory.DosPath.Buffer, params_address_diff); if (pProcParams->DllPath.Buffer) pProcParams->DllPath.Buffer = RVA2VA(PVOID, pProcParams->DllPath.Buffer, params_address_diff); if (pProcParams->ImagePathName.Buffer) pProcParams->ImagePathName.Buffer = RVA2VA(PVOID, pProcParams->ImagePathName.Buffer, params_address_diff); if (pProcParams->CommandLine.Buffer) pProcParams->CommandLine.Buffer = RVA2VA(PVOID, pProcParams->CommandLine.Buffer, params_address_diff); if (pProcParams->Environment) pProcParams->Environment = RVA2VA(PVOID, pProcParams->Environment, params_address_diff); if (pProcParams->WindowTitle.Buffer) pProcParams->WindowTitle.Buffer = RVA2VA(PVOID, pProcParams->WindowTitle.Buffer, params_address_diff); if (pProcParams->DesktopInfo.Buffer) pProcParams->DesktopInfo.Buffer = RVA2VA(PVOID, pProcParams->DesktopInfo.Buffer, params_address_diff); if (pProcParams->ShellInfo.Buffer) pProcParams->ShellInfo.Buffer = RVA2VA(PVOID, pProcParams->ShellInfo.Buffer, params_address_diff); if (pProcParams->RuntimeData.Buffer) pProcParams->RuntimeData.Buffer = RVA2VA(PVOID, pProcParams->RuntimeData.Buffer, params_address_diff); if (pProcParams->PackageDependencyData) pProcParams->PackageDependencyData = RVA2VA(PVOID, pProcParams->PackageDependencyData, params_address_diff); if (pProcParams->RedirectionDllName.Buffer) pProcParams->RedirectionDllName.Buffer = RVA2VA(PVOID, pProcParams->RedirectionDllName.Buffer, params_address_diff); if (pProcParams->HeapPartitionName.Buffer) pProcParams->HeapPartitionName.Buffer = RVA2VA(PVOID, pProcParams->HeapPartitionName.Buffer, params_address_diff); region_size = pProcParams->EnvironmentSize + pProcParams->MaximumLength; status = _NtWriteVirtualMemory( hProcess, params_remote_address, pProcParams, region_size, NULL); if (!NT_SUCCESS(status)) { PRINT_ERR("[-] NtWriteVirtualMemory failed, status: 0x%lx\n", status); goto cleanup; } if (is_x64) { ptr_to_params = RVA2VA(PVOID, peb_address, 0x20); region_size = 8; } else { ptr_to_params = RVA2VA(PVOID, peb_address, 0x10); region_size = 4; } status = _NtWriteVirtualMemory( hProcess, ptr_to_params, ¶ms_remote_address, region_size, NULL); if (!NT_SUCCESS(status)) { PRINT_ERR("[-] NtWriteVirtualMemory failed, status: 0x%lx\n", status); goto cleanup; } ret_val = TRUE; cleanup: if (pProcParams) _RtlDestroyProcessParameters((PRTL_USER_PROCESS_PARAMETERS)pProcParams); return ret_val; } DWORD get_pid( IN HANDLE hProcess) { PROCESS_BASIC_INFORMATION basic_info; basic_info.UniqueProcessId = 0; PROCESSINFOCLASS ProcessInformationClass = 0; NTSTATUS status = _NtQueryInformationProcess( hProcess, ProcessInformationClass, &basic_info, sizeof(PROCESS_BASIC_INFORMATION), NULL); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtQueryInformationProcess, status: 0x%lx\n", status); return 0; } return basic_info.UniqueProcessId; } DWORD get_tid( IN HANDLE hThread) { THREAD_BASIC_INFORMATION basic_info = { 0 }; THREADINFOCLASS ProcessInformationClass = 0; NTSTATUS status = _NtQueryInformationThread( hThread, ProcessInformationClass, &basic_info, sizeof(THREAD_BASIC_INFORMATION), NULL); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtQueryInformationThread, status: 0x%lx\n", status); return 0; } return (DWORD)(ULONG_PTR)basic_info.ClientId.UniqueThread; } BOOL get_arch( IN PIMAGE_DOS_HEADER dos, OUT PBOOL is_x64) { BOOL ret_val = FALSE; PIMAGE_NT_HEADERS nt = NULL; // check the MZ magic bytes if (dos->e_magic != 0x5A4D) { goto cleanup; } nt = RVA(PIMAGE_NT_HEADERS, dos, dos->e_lfanew); // check the NT_HEADER signature if (nt->Signature != IMAGE_NT_SIGNATURE) goto cleanup; if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR64_MAGIC) { *is_x64 = TRUE; } else if (nt->OptionalHeader.Magic == IMAGE_NT_OPTIONAL_HDR32_MAGIC) { *is_x64 = FALSE; } else { // unknown Magic goto cleanup; } ret_val = TRUE; cleanup: return ret_val; } BOOL create_process_with_ntcreateprocex( OUT PPROCESS_INFORMATION pi, OUT PPS_CREATE_INFO ci, IN LPWSTR process_image, IN LPWSTR nt_process_image, IN LPWSTR wparams, IN LPWSTR wdirectory, IN DWORD ppid, IN BOOL block_dlls) { NTSTATUS status = STATUS_UNSUCCESSFUL; BOOL success = FALSE; BOOL ret_val = FALSE; HANDLE hFile = NULL; HANDLE hSection = NULL; HANDLE hProcess = NULL; HANDLE hThread = NULL; HANDLE hPprocess = NULL; OBJECT_ATTRIBUTES obj_attributes = { 0 }; UNICODE_STRING ustr_process_path = { 0 }; IO_STATUS_BLOCK io_status_block = { 0 }; PVOID entrypoint_addr = NULL; PVOID peb_address = NULL; PVOID manifest_address = NULL; DWORD manifest_size = 0; PIMAGE_DOS_HEADER dos = NULL; PVOID image_base_address = NULL; BOOL is_x64 = TRUE; dos = calloc(DOS_HEADERS_SIZE, 1); if (!dos) { PRINT("[-] Calloc failed\n"); goto cleanup; } ustr_process_path.Buffer = nt_process_image; ustr_process_path.Length = wcsnlen(ustr_process_path.Buffer, MAX_PATH); ustr_process_path.Length *= 2; ustr_process_path.MaximumLength = ustr_process_path.Length + 2; InitializeObjectAttributes( &obj_attributes, &ustr_process_path, OBJ_CASE_INSENSITIVE, NULL, NULL); // open handle to the file status = _NtOpenFile( &hFile, FILE_READ_DATA|FILE_EXECUTE|FILE_READ_ATTRIBUTES|SYNCHRONIZE, &obj_attributes, &io_status_block, FILE_SHARE_READ|FILE_SHARE_DELETE, FILE_SYNCHRONOUS_IO_NONALERT|FILE_NON_DIRECTORY_FILE); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtOpenFile, status: 0x%lx\n", status); goto cleanup; } // read the PE headers status = _NtReadFile( hFile, NULL, NULL, NULL, &io_status_block, dos, DOS_HEADERS_SIZE, NULL, NULL); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtReadFile, status: 0x%lx\n", status); goto cleanup; } success = get_arch( dos, &is_x64); if (!success) { PRINT("[-] The file does not seem to be a PE\n"); goto cleanup; } if (!is_x64) { PRINT("[-] Only x64 is supported\n"); goto cleanup; } // create section status = _NtCreateSection( &hSection, SECTION_ALL_ACCESS, NULL, NULL, PAGE_READONLY, SEC_IMAGE, hFile); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtCreateSection, status: 0x%lx\n", status); goto cleanup; } // get a handle to the parent process hPprocess = get_parent_handle(ppid); // create the process status = _NtCreateProcessEx( &hProcess, PROCESS_ALL_ACCESS, NULL, hPprocess, CREATE_SUSPENDED, hSection, NULL, NULL, 0); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtCreateProcessEx, status: 0x%lx\n", status); goto cleanup; } peb_address = get_peb_address(hProcess); if (!peb_address) { PRINT("[-] Could not find the address of the PEB\n"); goto cleanup; } image_base_address = get_image_base_address( hProcess, peb_address, is_x64); if (!image_base_address) { PRINT("[-] Could not find the address of the image base\n"); goto cleanup; } entrypoint_addr = get_entrypoint_address( hProcess, image_base_address, dos); if (!entrypoint_addr) { PRINT("[-] Could not find the address of the entry point\n"); goto cleanup; } success = set_parameters_and_directory( hProcess, peb_address, process_image, nt_process_image, wparams, wdirectory, is_x64); if (!success) { PRINT("[-] Could not set the parameters and directory\n"); goto cleanup; } /* if (block_dlls) { DWORD64 policy = 0x100000008; status = _NtSetInformationProcess( hProcess, ProcessMitigationPolicy, &policy, 8); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtSetInformationProcess, status: 0x%lx\n", status); goto cleanup; } } */ // creating the thread will trigger the kernel-based process creation notification status = _NtCreateThreadEx( &hThread, THREAD_ALL_ACCESS, NULL, hProcess, entrypoint_addr, NULL, THREAD_CREATE_FLAGS_CREATE_SUSPENDED, 0, 0, 0, NULL); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtCreateThreadEx, status: 0x%lx\n", status); goto cleanup; } find_manifest( hProcess, image_base_address, dos, is_x64, &manifest_address, &manifest_size); pi->hProcess = hProcess; pi->hThread = hThread; pi->dwProcessId = get_pid(hProcess); pi->dwThreadId = get_tid(hThread); ci->SuccessState.PebAddressNative = (ULONG_PTR)peb_address; ci->SuccessState.FileHandle = hFile; ci->SuccessState.ManifestAddress = (ULONG_PTR)manifest_address; ci->SuccessState.ManifestSize = manifest_size; ret_val = TRUE; cleanup: if (hSection) _NtClose(hSection); if (hPprocess) _NtClose(hPprocess); if (dos) free(dos); return ret_val; } BOOL create_process_with_ntcreateuserproc( OUT PPROCESS_INFORMATION pi, OUT PPS_CREATE_INFO ci, IN LPWSTR process_image, IN LPWSTR nt_process_image, IN LPWSTR wparams, IN LPWSTR wdirectory, IN DWORD ppid, IN BOOL block_dlls) { NTSTATUS status = STATUS_UNSUCCESSFUL; PS_ATTRIBUTE_LIST attrList = { 0 }; CLIENT_ID clientId = { 0 }; BOOL ret_val = FALSE; HANDLE hProcess = NULL; HANDLE hThread = NULL; ULONG THREAD_FLAGS = 0; ULONG PROCESS_FLAGS = 0; UNICODE_STRING ustr_process_image = { 0 }; WCHAR process_parameters[MAX_PATH] = { 0 }; UNICODE_STRING ustr_process_parameters = { 0 }; UNICODE_STRING ustr_current_directory = { 0 }; RtlInitUnicodeString_t _RtlInitUnicodeString = NULL; RtlCreateProcessParametersEx_t _RtlCreateProcessParametersEx = NULL; RtlDestroyProcessParameters_t _RtlDestroyProcessParameters = NULL; PRTL_USER_PROCESS_PARAMETERS pProcParams = NULL; HANDLE hPprocess = NULL; DWORD64 policy = 0; _RtlInitUnicodeString = (RtlInitUnicodeString_t)(ULONG_PTR)get_function_address( get_library_address(NTDLL_DLL, TRUE), RtlInitUnicodeString_SW2_HASH, 0); if (!_RtlInitUnicodeString) { api_not_found("RtlInitUnicodeString"); goto cleanup; } _RtlCreateProcessParametersEx = (RtlCreateProcessParametersEx_t)(ULONG_PTR)get_function_address( get_library_address(NTDLL_DLL, TRUE), RtlCreateProcessParametersEx_SW2_HASH, 0); if (!_RtlCreateProcessParametersEx) { api_not_found("RtlCreateProcessParametersEx"); goto cleanup; } _RtlDestroyProcessParameters = (RtlDestroyProcessParameters_t)(ULONG_PTR)get_function_address( get_library_address(NTDLL_DLL, TRUE), RtlDestroyProcessParameters_SW2_HASH, 0); if (!_RtlDestroyProcessParameters) { api_not_found("RtlDestroyProcessParameters"); goto cleanup; } _RtlInitUnicodeString(&ustr_process_image, nt_process_image); if (wparams) { swprintf_s(process_parameters, MAX_PATH, L"\"%ws\" %ws", process_image, wparams); } else { swprintf_s(process_parameters, MAX_PATH, L"\"%ws\"", process_image); } _RtlInitUnicodeString(&ustr_process_parameters, process_parameters); if (wdirectory) { _RtlInitUnicodeString(&ustr_current_directory, wdirectory); } /* * the RTL_USER_PROCESS_PARAMETERS structure is quite large * and changes from version to version, * that is why I preffer using this API */ status = _RtlCreateProcessParametersEx( &pProcParams, &ustr_process_image, NULL, wdirectory ? &ustr_current_directory : NULL, &ustr_process_parameters, NULL, NULL, NULL, NULL, NULL, RTL_USER_PROC_PARAMS_NORMALIZED); if (!NT_SUCCESS(status)) { PRINT_ERR("[-] RtlCreateProcessParametersEx failed, status: 0x%lx\n", status); goto cleanup; } // set create info ci->State = PsCreateInitialState; ci->Size = sizeof(PS_CREATE_INFO); ci->InitState.InitFlags = WriteOutputOnExit | DetectManifest | 0x20000000; ci->InitState.AdditionalFileAccess = FILE_READ_ATTRIBUTES | FILE_READ_DATA; // set attribute list attrList.TotalLength = sizeof(PS_ATTRIBUTE_LIST); attrList.Attributes[0].Attribute = PsAttributeValue(PsAttributeImageName, FALSE, TRUE, FALSE); attrList.Attributes[0].Size = ustr_process_image.Length; attrList.Attributes[0].ValuePtr = ustr_process_image.Buffer; attrList.Attributes[1].Attribute = PsAttributeValue(PsAttributeClientId, TRUE, FALSE, FALSE); attrList.Attributes[1].Size = sizeof(CLIENT_ID); attrList.Attributes[1].ValuePtr = &clientId; hPprocess = get_parent_handle(ppid); attrList.Attributes[2].Attribute = PsAttributeValue(PsAttributeParentProcess, FALSE, TRUE, TRUE); attrList.Attributes[2].Size = sizeof(HANDLE); attrList.Attributes[2].Value = (ULONG_PTR)hPprocess; attrList.Attributes[3].Attribute = PsAttributeValue(PsAttributeChpe, FALSE, TRUE, TRUE); attrList.Attributes[3].Size = 1; attrList.Attributes[3].Value = 1; if (block_dlls) policy = PROCESS_CREATION_MITIGATION_POLICY_BLOCK_NON_MICROSOFT_BINARIES_ALWAYS_ON; attrList.Attributes[4].Attribute = PsAttributeValue(PsAttributeMitigationOptions, FALSE, TRUE, FALSE); attrList.Attributes[4].Size = sizeof(DWORD64); attrList.Attributes[4].Value = (ULONG_PTR)&policy; THREAD_FLAGS = THREAD_CREATE_FLAGS_CREATE_SUSPENDED; PROCESS_FLAGS = PROCESS_CREATE_FLAGS_SUSPENDED; status = _NtCreateUserProcess( &hProcess, &hThread, MAXIMUM_ALLOWED, MAXIMUM_ALLOWED, NULL, NULL, PROCESS_FLAGS, THREAD_FLAGS, pProcParams, ci, &attrList); if (!NT_SUCCESS(status)) { PRINT("[-] Failed to call NtCreateUserProcess, status: 0x%lx\n", status); goto cleanup; } pi->hProcess = hProcess; pi->hThread = hThread; pi->dwProcessId = (DWORD)(ULONG_PTR)clientId.UniqueProcess; pi->dwThreadId = (DWORD)(ULONG_PTR)clientId.UniqueThread; ret_val = TRUE; cleanup: if (pProcParams) _RtlDestroyProcessParameters(pProcParams); if (hPprocess) _NtClose(hPprocess); return ret_val; } BOOL create_process_internal( OUT PPROCESS_INFORMATION pi, IN BOOL use_ntcreateuserproc, IN LPWSTR process_image, IN LPWSTR wparams, IN LPWSTR wdirectory, IN BOOL suspended, IN DWORD ppid, IN BOOL block_dlls) { NTSTATUS status = STATUS_UNSUCCESSFUL; BOOL success = FALSE; BOOL ret_val = FALSE; WCHAR nt_process_image[MAX_PATH] = { 0 }; PS_CREATE_INFO ci = { 0 }; memset(nt_process_image, 0, sizeof(WCHAR) * MAX_PATH); swprintf_s(nt_process_image, MAX_PATH, L"\\??\\%ws", process_image); if (use_ntcreateuserproc) { // call NtCreateUserProcess success = create_process_with_ntcreateuserproc( pi, &ci, process_image, nt_process_image, wparams, wdirectory, ppid, block_dlls); if (!success) { PRINT_ERR("Failed to create the process\n"); goto cleanup; } } else { // call NtCreateProcessEx success = create_process_with_ntcreateprocex( pi, &ci, process_image, nt_process_image, wparams, wdirectory, ppid, block_dlls); if (!success) { PRINT_ERR("Failed to create the process\n"); goto cleanup; } } PRINT("[+] New process created\n"); PRINT("[i] hProcess = 0x%p\n", pi->hProcess); PRINT("[i] hThread = 0x%p\n", pi->hThread); PRINT("[i] PID = %ld\n", pi->dwProcessId); PRINT("[i] TID = %ld\n", pi->dwThreadId); PRINT("[i] PEB = 0x%p\n", (PVOID)(ULONG_PTR)ci.SuccessState.PebAddressNative); // register the new process with the CSRSS status = register_with_csr( pi, &ci, process_image, nt_process_image); if (!NT_SUCCESS(status)) { PRINT_ERR("Could not register process with the CSRSS\n"); goto cleanup; } // resume if suspended flag was not provided if (!suspended) { status = _NtResumeThread(pi->hThread, 0); if (!NT_SUCCESS(status)) { PRINT_ERR("Could resume the process, status: 0x%lx\n", status); goto cleanup; } PRINT("[+] Resumed process\n"); } ret_val = TRUE; cleanup: if (ci.SuccessState.FileHandle) _NtClose(ci.SuccessState.FileHandle); return ret_val; } BOOL is_win_6_point_0_or_grater(VOID) { PVOID pPeb; ULONG32 OSMajorVersion; pPeb = (PVOID)READ_MEMLOC(PEB_OFFSET); OSMajorVersion = *RVA2VA(PULONG32, pPeb, OSMAJORVERSION_OFFSET); if (OSMajorVersion >= 6) return TRUE; return FALSE; } VOID usage( IN LPCSTR procname) { PRINT("usage: %s --binary C:\\Windows\\notepad.exe [--syscall NtCreateUserProcess] [--params \"--some params\"] [--directory C:\\Windows\\Temp] [--suspended] [--ppid 1234] [--blockdlls] [--help]\n", procname); PRINT(" --binary BIN_PATH, -b BIN_PATH\n"); PRINT(" full path of the binary to run\n"); PRINT(" --syscall {NtCreateUserProcess,NtCreateProcessEx}\n"); PRINT(" create the process using NtCreateUserProcess (default) or NtCreateProcessEx\n"); PRINT(" --params PARAMS, -p PARAMS\n"); PRINT(" parameters of the new process\n"); PRINT(" --directory DIR, -d DIR\n"); PRINT(" the current working directory that should be assigned to the process\n"); PRINT(" --suspended, -s\n"); PRINT(" create the process in a suspended state\n"); PRINT(" --ppid NUM, -pp NUM\n"); PRINT(" the PID of the parent process\n"); PRINT(" --blockdlls, -bd\n"); PRINT(" only allow Microsoft DLLs to be loaded\n"); PRINT(" --help, -h\n"); PRINT(" print this help message and leave\n"); } int main(int argc, char* argv[]) { LPCSTR binary = NULL; BOOL use_ntcreateuserproc = TRUE; LPCSTR parameters = NULL; LPCSTR directory = NULL; BOOL suspended = FALSE; BOOL block_dlls = FALSE; DWORD ppid = 0; WCHAR wbinary[MAX_PATH] = { 0 }; WCHAR wparams[MAX_PATH] = { 0 }; WCHAR wdirectory[MAX_PATH] = { 0 }; PROCESS_INFORMATION pi = { 0 }; BOOL success = FALSE; for (int i = 1; i < argc; ++i) { if (!strncmp(argv[i], "-b", 3) || !strncmp(argv[i], "--binary", 9)) { if (i + 1 >= argc) { PRINT("missing --binary value\n"); return 0; } binary = argv[++i]; mbstowcs(wbinary, binary, MAX_PATH); } else if (!strncmp(argv[i], "--syscall", 10)) { if (i + 1 >= argc) { PRINT("missing --syscall value\n"); return 0; } i++; if (!strncmp(argv[i], "NtCreateUserProcess", 20)) { use_ntcreateuserproc = TRUE; } else if (!strncmp(argv[i], "NtCreateProcessEx", 18)) { use_ntcreateuserproc = FALSE; } else { printf("invalid --syscall value\n"); return 0; } } else if (!strncmp(argv[i], "-p", 3) || !strncmp(argv[i], "--params", 9)) { if (i + 1 >= argc) { PRINT("missing --params value\n"); return 0; } parameters = argv[++i]; mbstowcs(wparams, parameters, MAX_PATH); } else if (!strncmp(argv[i], "-d", 3) || !strncmp(argv[i], "--directory", 12)) { if (i + 1 >= argc) { PRINT("missing --directory value\n"); return 0; } directory = argv[++i]; mbstowcs(wdirectory, directory, MAX_PATH); } else if (!strncmp(argv[i], "-s", 3) || !strncmp(argv[i], "--suspended", 12)) { suspended = TRUE; } else if (!strncmp(argv[i], "-pp", 4) || !strncmp(argv[i], "--ppid", 7)) { if (i + 1 >= argc) { PRINT("missing --ppid value\n"); return 0; } ppid = atoi(argv[++i]); if (!ppid) { PRINT("invalid ppid\n") return 0; } } else if (!strncmp(argv[i], "-bd", 4) || !strncmp(argv[i], "--blockdlls", 12)) { block_dlls = TRUE; } else if (!strncmp(argv[i], "-h", 3) || !strncmp(argv[i], "--help", 7)) { usage(argv[0]); return 0; } else { PRINT("invalid argument: %s\n", argv[i]); return 0; } } if (!binary) { usage(argv[0]); return 0; } if (use_ntcreateuserproc && !is_win_6_point_0_or_grater()) { PRINT("The NtCreateUserProcess syscall is not supported on this system, use NtCreateProcessEx\n"); return 0; } if (!use_ntcreateuserproc && block_dlls) { PRINT("The --blockdlls option is not supported while using NtCreateProcessEx\n"); return 0; } success = create_process_internal( &pi, use_ntcreateuserproc, wbinary, parameters ? wparams : NULL, directory ? wdirectory : NULL, suspended, ppid, block_dlls); if (success) { if (pi.hProcess) _NtClose(pi.hProcess); if (pi.hThread) _NtClose(pi.hThread); } return 0; }