├── syswhispers2bof.py └── README.md /syswhispers2bof.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | import re 4 | import pipes 5 | import sys 6 | 7 | def get_used_syscalls(fn): 8 | syscall_data = open(fn).read() 9 | syscalls = re.findall('EXTERN_C NTSTATUS (.*)\(', syscall_data, re.MULTILINE) 10 | return syscalls 11 | 12 | def call_syswhispers2(syscalls): 13 | sys.stdout.flush() 14 | os.system(f'cd SysWhispers2 && python3 ./syswhispers.py -f {pipes.quote(",".join(syscalls))} -o syswhispers2bof') 15 | sys.stdout.flush() 16 | 17 | def fixup_c(input_file): 18 | r = open(input_file).read() 19 | r = r.replace('#include "syswhispers2bof.h"', '') # will generate a single file so no need to include other parts 20 | r = r.replace('SW2_SYSCALL_LIST SW2_SyscallList;', 'SW2_SYSCALL_LIST SW2_SyscallList = {0,1};') # BOF cannot deal with unitialized global variables 21 | return r 22 | 23 | def fix_asm_line(line): 24 | if ';' in line: 25 | line = line.split(';')[0] 26 | line = line.rstrip() 27 | line = line + '\\n\\' 28 | line = re.sub('([0-9A-Fa-f]+)h', '0x\\1', line) # Fix f00h => 0xf00 29 | return line 30 | 31 | # Convert the stubs in .asm file to inline C syntax 32 | # Requires a few fixes like replace f00h with 0xf00 33 | def build_stubs(input_file): 34 | out = [] 35 | r = open(input_file).read() 36 | inside_function = False 37 | for line in r.split('\n'): 38 | if re.match('^(\S+) PROC$', line): 39 | fn = line.split(' ')[0] 40 | out.append(f'#define {fn.replace("Nt", "Zw")} {fn}') 41 | out.append(f'__asm__("{fn}: \\n\\') 42 | inside_function = True 43 | elif re.match('^(\S+) ENDP$', line): 44 | inside_function = False 45 | out.append('");') 46 | elif inside_function: 47 | out.append(fix_asm_line(line)) 48 | out.append('') 49 | return "\n".join(out) 50 | 51 | # Remove a typedef declaration from the input 52 | def remove_declaration(r, duplicate): 53 | o = [] 54 | inside = False 55 | for line in r.split('\n'): 56 | if re.match(f'^typedef.*{duplicate}', line): 57 | inside = True 58 | if not inside: 59 | o.append(line) 60 | if line.startswith('}'): 61 | inside = False 62 | return '\n'.join(o) 63 | 64 | def fixup_h(input_file): 65 | r = open(input_file).read() 66 | r = r.replace('Windows.h','windows.h') # Wont compile on Linux otherwise 67 | # Remove duplicate declarations, might be specific to particular mingw versions of windows.h 68 | # You might have to update this list if compiling gives you additional duplicate symbol definitions 69 | for duplicate in ["_SYSTEM_HANDLE_INFORMATION","_UNICODE_STRING","_OBJECT_ATTRIBUTES","_CLIENT_ID","_SYSTEM_INFORMATION_CLASS"]: 70 | r = remove_declaration(r, duplicate) 71 | return r 72 | 73 | def main(): 74 | parser = argparse.ArgumentParser() 75 | parser.add_argument("--syscalls", type=str, help="list of system calls to include as a comma seperated string") 76 | parser.add_argument("--syscalls_h", type=str, help="syscalls.h file from which to get list of used system calls") 77 | parser.add_argument("--syscalls_file", type=str, help="read list of system calls to include from a file") 78 | args = parser.parse_args() 79 | syscalls = [] 80 | if not(os.path.isdir('SysWhispers2')): 81 | print("[E] SysWhispers2 directory does not exist under the current path, run the following command to download it:\ngit clone https://github.com/jthuraisamy/SysWhispers2") 82 | exit(1) 83 | if args.syscalls_h: 84 | syscall_file = args.syscalls_h 85 | print(f"[*] Extracting syscalls from {syscall_file}") 86 | syscalls = get_used_syscalls(syscall_file) 87 | elif args.syscalls: 88 | syscalls = args.syscalls.split(',') 89 | elif args.syscalls_file: 90 | for sc in open(args.syscalls_file).read().replace('\r','').split('\n'): 91 | if not sc: continue 92 | syscalls.append(sc) 93 | 94 | else: 95 | print("[E] Specify either --syscalls=comma,seperated,list or --syscalls_h=../bof/syscalls.h or syscalls_file=file.txt") 96 | exit(1) 97 | 98 | print(f"[*] Used syscalls: {syscalls}") 99 | print("[*] Calling SysWhispers2 to generate stubs for these system calls") 100 | call_syswhispers2(syscalls) 101 | h_fn = os.path.join('SysWhispers2', 'syswhispers2bof.h') 102 | print(f"[*] Fixing up H file {h_fn}") 103 | out_file = fixup_h(h_fn) 104 | c_fn = os.path.join('SysWhispers2', 'syswhispers2bof.c') 105 | print(f"[*] Fixing up C file {c_fn}") 106 | out_file += fixup_c(c_fn) 107 | stub_fn = os.path.join('SysWhispers2', 'syswhispers2bofstubs.asm') 108 | print(f"[*] Converting ASM stubs from {stub_fn}") 109 | out_file += build_stubs(stub_fn) 110 | print(f"[*] Writing combined output to syscalls.h") 111 | open("syscalls.h",'w').write(out_file) 112 | print(f"[*] Note: asm.h is no longer needed") 113 | 114 | if __name__ == '__main__': 115 | main() 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SysWhispers2BOF 2 | Script to use SysWhispers2 direct system calls from Cobalt Strike BOFs. 3 | 4 | ## Introduction 5 | 6 | This script was initially created to fix specific Cobalt Stike BOFs, such as [@rookuu's MiniDumpWriteDump](https://github.com/rookuu/BOFs/tree/main/MiniDumpWriteDump) that did not work on Windows 21H1. 7 | The reason for the BOFs breaking was that they relied on direct system calls based on a syscalls.h file generated using [@Outflank's InlineWhispers](https://github.com/outflanknl/InlineWhispers) - which generates syscall wrappers based on the original [SysWhispers](https://github.com/jthuraisamy/SysWhispers) project. 8 | The original version of SysWhispers relies on a table that maps system call names to system call numbers, which requires updating for each new Windows version to include the appropriate system call numbers for the updated Windows version. 9 | This means that a new syscalls.h file needs to be generated and BOFs using this syscalls.h file need to be recompiled each time a new Windows version is released. 10 | 11 | A new version of SysWhispers called [SysWhispers2](https://github.com/jthuraisamy/SysWhispers2) was released in March 2021 by [Jackson T.](https://twitter.com/Jackson_T). It uses a different technique and resolves the system call numbers on the target machine instead of relying on a pre-calculated list of system call numbers. This allows generating the syscalls.h and compiled BOF once and this single version should work on new Windows versions without updates. 12 | 13 | Unfortunately, the output generated by SysWhispers2 cannot be directly used inside Cobalt Strike BOFs and requires some tweaks to convert it into a format that can be used by Cobalt Strike BOFs. 14 | The script provided in this repository performs those tweaks automatically for you and can also be used to convert an existing syscalls.h file from an existing BOF to a new syscalls.h file that uses SysWhispers2. 15 | 16 | ## Installation 17 | 18 | Start by cloning this repository. Once the repository is cloned, clone the SysWhispers2 repository inside, for example: 19 | 20 | ``` 21 | $ git clone https://github.com/FalconForceTeam/SysWhispers2BOF 22 | $ cd SysWhispers2BOF 23 | $ git clone https://github.com/jthuraisamy/SysWhispers2 24 | ``` 25 | 26 | ## Usage 27 | 28 | The tool can be used to generate a syscalls.h file. To do this, the list of system calls to include in the .h file needs to be specified. This can be specified in 3 different ways: 29 | 1) On the command-line using `--syscalls=comma,separated,list`, e.g. `--syscalls=NtOpenProcess,NtQuerySystemInformation` 30 | 2) By reading the syscalls.h file from an existing BOF. This allows easy conversion of the BOF to use SysWhispers2 using `--syscalls_h=file_name.h`, e.g. `--syscalls=bof/syscalls.h` 31 | 3) By reading the functions from a text file in the same method used by InlineWhispers, using `--syscalls_file=filename`, e.g. `--syscalls_file=functions.txt`. Note: make sure to use the Nt prefix rather than the Zw prefix for the system call names. 32 | 33 | It will produce a syscalls.h file in the current directory. 34 | 35 | ## Usage Examples 36 | 37 | ### Example of using it during BOF development: 38 | ``` 39 | $ python3 syswhispers2bof.py --syscalls=NtOpenProcess,NtQuerySystemInformation 40 | [*] Used syscalls: ['NtOpenProcess', 'NtQuerySystemInformation'] 41 | [*] Calling SysWhispers2 to generate stubs for these system calls 42 | 43 | . ,--. 44 | ,-. . . ,-. . , , |-. o ,-. ,-. ,-. ,-. ,-. / 45 | `-. | | `-. |/|/ | | | `-. | | |-' | `-. ,-' 46 | `-' `-| `-' ' ' ' ' ' `-' |-' `-' ' `-' `--- 47 | /| | @Jackson_T 48 | `-' ' @modexpblog, 2021 49 | 50 | SysWhispers2: Why call the kernel when you can whisper? 51 | 52 | Complete! Files written to: 53 | syswhispers2bof.h 54 | syswhispers2bof.c 55 | syswhispers2bofstubs.asm 56 | [*] Fixing up H file SysWhispers2/syswhispers2bof.h 57 | [*] Fixing up C file SysWhispers2/syswhispers2bof.c 58 | [*] Converting ASM stubs from SysWhispers2/syswhispers2bofstubs.asm 59 | [*] Writing combined output to syscalls.h 60 | [*] Note: asm.h is no longer needed 61 | ``` 62 | 63 | This will provide a single file: `syscalls.h` that can be included in the BOF to make direct system calls. 64 | 65 | ### Example of using it to update the syscalls.h file on an existing BOF to create a version of the BOF that works on Windows 21H1 and later. 66 | 67 | ``` 68 | # Clone a BOF that is not compatible with Windows 21H1 since it uses an older version of syscalls.h 69 | $ git clone https://github.com/rookuu/BOFs 70 | Cloning into 'BOFs'... 71 | 72 | $ python3 syswhispers2bof.py --syscalls_h=BOFs/MiniDumpWriteDump/syscalls.h 73 | [*] Extracting syscalls from BOFs/MiniDumpWriteDump/syscalls.h 74 | [*] Used syscalls: ['NtReadVirtualMemory', 'NtOpenProcessToken', 'NtAdjustPrivilegesToken', 'NtOpenProcess', 'NtClose', 'NtQuerySystemInformation'] 75 | 76 | [*] Writing combined output to syscalls.h 77 | [*] Note: asm.h is no longer needed 78 | $ cp syscalls.h BOFs/MiniDumpWriteDump 79 | $ cd BOFs/MiniDumpWriteDump 80 | $ rm asm.h 81 | $ make 82 | x86_64-w64-mingw32-gcc -o minidumpwritedump.x64.o -c bof.c -masm=intel -Wno-multichar 83 | # New .o file should be usable across newer Windows versions without the need to recompile it. 84 | ``` 85 | 86 | ## Notes 87 | 88 | The tool was only tested on Mac and Linux - it might not work fully on Windows. 89 | 90 | ## Credits 91 | 92 | Note that this script is just a small wrapper around the excellent work done by [@jthuraisamy](https://github.com/jthuraisamy) and was heavily inspired by the output generated by [@Outflank's InlineWhispers](https://github.com/outflanknl/InlineWhispers). 93 | --------------------------------------------------------------------------------