Skip to content

Commit

Permalink
Merge pull request #9 from bugproof/main
Browse files Browse the repository at this point in the history
Enhancements - x86 support (WoW64 + native), ability to change function prefix
  • Loading branch information
odzhan committed Mar 13, 2022
2 parents 82c9e3b + 997e1df commit 90dbc1e
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 7 deletions.
2 changes: 0 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,9 @@ Using the `--preset common` switch will create a header/ASM pair with the follow
2. In Visual Studio, go to *Project**Build Customizations...* and enable MASM.
3. In the *Solution Explorer*, add the .h and .c/.asm files to the project as header and source files, respectively.
4. Go to the properties of the ASM file, and set the *Item Type* to *Microsoft Macro Assembler*.
5. Ensure that the project platform is set to x64. 32-bit projects are not supported at this time.

## Caveats and Limitations

- Only 64-bit Windows is supported at this time.
- System calls from the graphical subsystem (`win32k.sys`) are not supported.
- Tested on Visual Studio 2019 (v142) with Windows 10 SDK.

Expand Down
4 changes: 4 additions & 0 deletions data/base.c
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@ BOOL SW2_PopulateSyscallList(void)
// Return early if the list is already populated.
if (SW2_SyscallList.Count) return TRUE;

#if defined(_WIN64)
PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
#else
PSW2_PEB Peb = (PSW2_PEB)__readfsdword(0x30);
#endif
PSW2_PEB_LDR_DATA Ldr = Peb->Ldr;
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
PVOID DllBase = NULL;
Expand Down
48 changes: 43 additions & 5 deletions syswhispers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@


class SysWhispers(object):
def __init__(self):
def __init__(self, function_prefix):
self.__function_prefix = function_prefix

self.seed = random.randint(2 ** 28, 2 ** 32 - 1)
self.typedefs: list = json.load(open(os.path.join(os.path.dirname(__file__), "data", "typedefs.json")))
self.prototypes: dict = json.load(open(os.path.join(os.path.dirname(__file__), "data", "prototypes.json")))
Expand All @@ -19,6 +21,18 @@ def generate(self, function_names: list = (), basename: str = 'syscalls'):
elif any([f not in self.prototypes.keys() for f in function_names]):
raise ValueError('Prototypes are not available for one or more of the requested functions.')

# Change default function prefix.
if self.__function_prefix != 'Nt':
new_function_names = []
for function_name in function_names:
new_function_name = function_name.replace('Nt', self.__function_prefix, 1)
if new_function_name != function_name:
self.prototypes[new_function_name] = self.prototypes[function_name]
del self.prototypes[function_name]
new_function_names.append(new_function_name)

function_names = new_function_names

# Write C file.
with open (os.path.join(os.path.dirname(__file__), "data", "base.c"), 'rb') as base_source:
with open(f'{basename}.c', 'wb') as output_source:
Expand All @@ -31,7 +45,8 @@ def generate(self, function_names: list = (), basename: str = 'syscalls'):
basename_suffix = basename_suffix.capitalize() if os.path.basename(basename).istitle() else basename_suffix
basename_suffix = f'_{basename_suffix}' if '_' in basename else basename_suffix
with open(f'{basename}{basename_suffix}.asm', 'wb') as output_asm:
output_asm.write(b'.code\n\nEXTERN SW2_GetSyscallNumber: PROC\n\n')
output_asm.write(b'IFDEF RAX\n\n.CODE\n\nELSE\n\n.MODEL FLAT, C\n.CODE\n\nASSUME FS:NOTHING\n\nENDIF\n\n'
b'EXTERN SW2_GetSyscallNumber: PROC\n\n')
for function_name in function_names:
output_asm.write((self._get_function_asm_code(function_name) + '\n').encode())
output_asm.write(b'end')
Expand Down Expand Up @@ -124,7 +139,7 @@ def _get_function_prototype(self, function_name: str) -> str:

def _get_function_hash(self, function_name: str):
hash = self.seed
name = function_name.replace('Nt', 'Zw', 1) + '\0'
name = function_name.replace(self.__function_prefix, 'Zw', 1) + '\0'
ror8 = lambda v: ((v >> 8) & (2 ** 32 - 1)) | ((v << 24) & (2 ** 32 - 1))

for segment in [s for s in [name[i:i + 2] for i in range(len(name))] if len(s) == 2]:
Expand All @@ -137,7 +152,8 @@ def _get_function_asm_code(self, function_name: str) -> str:
function_hash = self._get_function_hash(function_name)

# Generate 64-bit ASM code.
code = ''
code = 'IFDEF RAX\n\n'

code += f'{function_name} PROC\n'
code += '\tmov [rsp +8], rcx ; Save registers.\n'
code += '\tmov [rsp+16], rdx\n'
Expand All @@ -155,6 +171,27 @@ def _get_function_asm_code(self, function_name: str) -> str:
code += '\tsyscall ; Invoke system call.\n'
code += '\tret\n'
code += f'{function_name} ENDP\n'

# Generate 32-bit ASM code
code += '\nELSE\n\n'

code += f'{function_name} PROC\n'
code += f'\tpush 0{function_hash:08X}h\n'
code += '\tcall SW2_GetSyscallNumber ; Resolve function hash into syscall number.\n'
code += '\tadd esp, 4\n'
code += '\tmov ecx, fs:[0c0h]\n'
code += '\ttest ecx, ecx\n'
code += '\tjne _wow64\n'
code += '\tlea edx, [esp+4h]\n'
code += '\tINT 02eh\n'
code += '\tret\n'
code += '\t_wow64:\n'
code += '\txor ecx, ecx\n'
code += '\tlea edx, [esp+4h]\n'
code += '\tcall dword ptr fs:[0c0h]\n'
code += '\tret\n'
code += f'{function_name} ENDP\n'
code += '\nENDIF\n'

return code

Expand All @@ -175,9 +212,10 @@ def _get_function_asm_code(self, function_name: str) -> str:
parser.add_argument('-p', '--preset', help='Preset ("all", "common")', required=False)
parser.add_argument('-f', '--functions', help='Comma-separated functions', required=False)
parser.add_argument('-o', '--out-file', help='Output basename (w/o extension)', required=True)
parser.add_argument('--function-prefix', default='Nt', help='Function prefix', required=False)
args = parser.parse_args()

sw = SysWhispers()
sw = SysWhispers(args.function_prefix)

if args.preset == 'all':
print('All functions selected.\n')
Expand Down

0 comments on commit 90dbc1e

Please sign in to comment.