mirror of
https://github.com/klezVirus/NimlineWhispers3
synced 2026-06-08 15:18:58 +00:00
Initial Commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
[submodule "SysWhispers2"]
|
||||
path = SysWhispers3
|
||||
url = https://github.com/klezVirus/SysWhispers3
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="DuplicatedCode" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<Languages>
|
||||
<language minSize="225" name="Python" />
|
||||
</Languages>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||
<option name="ourVersions">
|
||||
<value>
|
||||
<list size="3">
|
||||
<item index="0" class="java.lang.String" itemvalue="3.8" />
|
||||
<item index="1" class="java.lang.String" itemvalue="3.9" />
|
||||
<item index="2" class="java.lang.String" itemvalue="3.10" />
|
||||
</list>
|
||||
</value>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="PyPep8NamingInspection" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredErrors">
|
||||
<list>
|
||||
<option value="N801" />
|
||||
<option value="N802" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
</profile>
|
||||
</component>
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
Generated
+4
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
Generated
+8
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/NimlineWhispers3.iml" filepath="$PROJECT_DIR$/.idea/NimlineWhispers3.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
Generated
+6
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
@@ -0,0 +1,298 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf-8
|
||||
|
||||
# Adapted from @Outflank and @_DaWouw 's InlineWhispers project. https://github.com/outflanknl/InlineWhispers
|
||||
# All credit to them for the syswhispers regexp code
|
||||
|
||||
import re, random, string, os, platform
|
||||
import argparse
|
||||
from pprint import pprint
|
||||
from SysWhispers3.syswhispers import SysWhispers
|
||||
|
||||
class NimlineWhispers:
|
||||
|
||||
def __init__(self, debug, randomise, nobanner):
|
||||
self.debug = debug
|
||||
self.randomise = randomise
|
||||
|
||||
# file containing functions we require
|
||||
self.functionsInName = "functions.txt"
|
||||
|
||||
# name for temporary files generated with syswhispers2
|
||||
self.basename = "nimlinewhispers"
|
||||
self.sw2headers = f"{self.basename}.h"
|
||||
self.sw2methods = f"{self.basename}.c"
|
||||
|
||||
# paths to SW2 functions and headers added to our eventual nim file
|
||||
self.sw2baseh = os.path.join(".", "SysWhispers2", "data", "base.h")
|
||||
self.sw2basec = os.path.join(".", "SysWhispers2", "data", "base.c")
|
||||
|
||||
self.fileInName = f"{self.basename}stubs.asm"
|
||||
self.fileOutName = "syscalls.nim"
|
||||
|
||||
self.regexFunctionStart = re.compile(r'([a-z0-9]{1,70})(\s+PROC)', re.IGNORECASE)
|
||||
self.regexFunctionEnd = re.compile(r'([a-z0-9]{1,70})(\s+ENDP)', re.IGNORECASE)
|
||||
self.regexAsmComment = re.compile(r'([^;\r\n]*)', re.IGNORECASE)
|
||||
self.regexHexNotation = re.compile(r'([^;\r\n]*[\s\+\[])([0-9a-f]{1,5})(?:h)([^;\r\n]*)', re.IGNORECASE)
|
||||
|
||||
self.functions = []
|
||||
self.filterFunctions = False
|
||||
self.functionOutputs = {}
|
||||
self.functionArgs = {}
|
||||
self.function_map = {}
|
||||
|
||||
# initialise class instance of syswhispers2 so we can generate stubs and fetch seed values later
|
||||
self.syswhispers = SysWhispers()
|
||||
|
||||
# why someone wouldn't want to print this masterpiece idk
|
||||
if not nobanner: self.printBanner()
|
||||
|
||||
self.generateSysWhispersOutput()
|
||||
self.generate_function_args_mapping()
|
||||
self.produce_randomised_function_names()
|
||||
|
||||
|
||||
def printBanner(self):
|
||||
print(r"""
|
||||
|
||||
% ..%%%%%# %/.
|
||||
/%%%%%,.%%%%%%%%%%%%%%%%%%%%%%%%%%%%.%%%%%%
|
||||
. #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%.
|
||||
%%*.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ,%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%.
|
||||
#%%%%%%%%%%%%%%. %%%%%%%%%%%%%%%%
|
||||
%%%%%%%( %%%%%%%%%
|
||||
& %%# .%% ..
|
||||
&&. . . #&
|
||||
&&&&. . %&&&&&&&&. &&&&
|
||||
&&&&&&&.. . . (&&&&&&&&&&&&&&&&&%. . .&&&&&&&
|
||||
.%&&&&&&&&&&&&&&&&&&&&& ___ &&&&&&&&&&&&&&&&&&&&&
|
||||
#&&&&&&&&&&&&&&&&&&& |__ \ &&&&&&&&&&&&&&&&&&&
|
||||
,&&&&&&&&&&&&&&&&& ) | &&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&& / / &&&&&&&&&&&&&&
|
||||
&&&&&&&&&& / /_ &&&&&&&&&&
|
||||
%&& |____| &&.
|
||||
NimlineWhispers2
|
||||
@ajpc500 2021
|
||||
""")
|
||||
|
||||
def generateSysWhispersOutput(self):
|
||||
'''
|
||||
Grab functions from the functions.txt file and
|
||||
pass them to SysWhispers2 to generate our stubs
|
||||
'''
|
||||
self.read_required_functions_from_file()
|
||||
print("\n[i] Using SysWhispers2 to generate asm stubs...")
|
||||
self.syswhispers.generate(self.functions, basename=self.basename)
|
||||
|
||||
# taken from https://github.com/FalconForceTeam/SysWhispers2BOF/blob/main/syswhispers2bof.py#L23
|
||||
def fix_asm_line(self, line):
|
||||
if ';' in line:
|
||||
line = line.split(';')[0]
|
||||
line = line.rstrip()
|
||||
line = re.sub('([0-9A-Fa-f]+)h', '0x\\1', line) # Fix f00h => 0xf00
|
||||
return line
|
||||
|
||||
def addSysWhispersFunctionBlock(self):
|
||||
'''
|
||||
Create an emit block that contains both the headers and functions from Sw2 base code.
|
||||
Also manually replace the seed value placeholder
|
||||
'''
|
||||
out = "{.emit: \"\"\"\n"
|
||||
|
||||
h = open(self.sw2baseh, mode='r').read()
|
||||
if platform.system() != "Windows": h = h.replace('Windows.h', 'windows.h')
|
||||
h = h.replace('<SEED_VALUE>', f'0x{self.syswhispers.seed:08X}')
|
||||
h += "#endif\n"
|
||||
out += h
|
||||
|
||||
c = open(self.sw2basec, mode='r').read()
|
||||
|
||||
# taken from https://github.com/FalconForceTeam/SysWhispers2BOF
|
||||
c = c.replace('#include "<BASENAME>.h"', '') # will generate a single file so no need to include other parts
|
||||
c = c.replace('SW2_SYSCALL_LIST SW2_SyscallList;', 'SW2_SYSCALL_LIST SW2_SyscallList = {0,1};') # BOF cannot deal with unitialized global variables
|
||||
out += c
|
||||
out+= "\n\"\"\".}"
|
||||
return out
|
||||
|
||||
def produce_randomised_function_names(self):
|
||||
if self.randomise: print("\n[i] Producing randomised function mapping...")
|
||||
for function in self.functions:
|
||||
rand_val = ''.join(random.choices(string.ascii_letters, k=16))
|
||||
self.function_map[function] = rand_val if self.randomise else function
|
||||
if self.randomise: print("\t{} -> {}".format(function, rand_val))
|
||||
|
||||
def strip_chars(self, str):
|
||||
return str.strip("),;")
|
||||
|
||||
def parse_function_arg(self, arg_list):
|
||||
argType = argName = ''
|
||||
argTypeIndex = argNameIndex = 0
|
||||
|
||||
arg_list = [self.strip_chars(a) for a in arg_list] # clean unneeded characters
|
||||
|
||||
if len(arg_list) > 0:
|
||||
if len(arg_list) == 2:
|
||||
# TYPE Name
|
||||
argTypeIndex = 0
|
||||
argNameIndex = 1
|
||||
elif len(arg_list) == 3:
|
||||
if arg_list[0].upper() in ['IN','OUT'] and arg_list[2].upper() != 'OPTIONAL':
|
||||
# IN TYPE Name
|
||||
argTypeIndex = 1
|
||||
argNameIndex = 2
|
||||
elif arg_list[0].upper() not in ['IN','OUT'] and arg_list[2].upper() == 'OPTIONAL':
|
||||
# TYPE Name OPTIONAL
|
||||
argTypeIndex = 0
|
||||
argNameIndex = 1
|
||||
elif arg_list[0].upper() not in ['IN','OUT'] and arg_list[1].upper() == '*':
|
||||
# TYPE * Name
|
||||
argTypeIndex = 0
|
||||
argNameIndex = 1
|
||||
|
||||
elif len(arg_list) == 4:
|
||||
if arg_list[0].upper() in ['IN','OUT'] and arg_list[1].upper() in ['IN','OUT']:
|
||||
# IN OUT TYPE Name
|
||||
argTypeIndex = 2
|
||||
argNameIndex = 3
|
||||
elif arg_list[0].upper() in ['IN','OUT'] and arg_list[1].upper() not in ['IN','OUT'] and arg_list[2].upper() == '*':
|
||||
# OUT TYPE * Name
|
||||
argTypeIndex = 1
|
||||
argNameIndex = 3
|
||||
elif arg_list[0].upper() in ['IN','OUT'] and arg_list[1].upper() not in ['IN','OUT'] and arg_list[2].upper() != '*' and arg_list[3].upper() == 'OPTIONAL':
|
||||
# OUT TYPE Name OPTIONAL
|
||||
argTypeIndex = 1
|
||||
argNameIndex = 2
|
||||
elif len(arg_list) == 5:
|
||||
if arg_list[0].upper() in ['IN','OUT'] and arg_list[1].upper() in ['IN','OUT'] and arg_list[3] == '*':
|
||||
# IN OUT TYPE * Name
|
||||
argTypeIndex = 2
|
||||
argNameIndex = 4
|
||||
elif arg_list[0].upper() in ['IN','OUT'] and arg_list[1].upper() in ['IN','OUT'] and arg_list[4].upper() == 'OPTIONAL':
|
||||
# IN OUT TYPE Name OPTIONAL
|
||||
argTypeIndex = 2
|
||||
argNameIndex = 3
|
||||
|
||||
if argNameIndex != argTypeIndex:
|
||||
return arg_list[argNameIndex], arg_list[argTypeIndex]
|
||||
else:
|
||||
print('[i] No idea what we\'re doing with function arg: {}.'.format(arg_list))
|
||||
|
||||
def get_function_return_type(self, functionName):
|
||||
if functionName in self.functionOutputs:
|
||||
return self.functionOutputs[functionName]
|
||||
else:
|
||||
print('[i] We don\'t know the return type for {}, fix this manually.'.format(functionName))
|
||||
return 'UNKNOWN'
|
||||
|
||||
def get_function_arguments(self, functionName):
|
||||
if functionName in self.functionOutputs:
|
||||
argString = ""
|
||||
for arg in self.functionArgs[functionName]:
|
||||
if argString:
|
||||
argString += ", "
|
||||
argString += "{}: {}".format(arg[1], arg[0])
|
||||
return argString
|
||||
else:
|
||||
print('[i] We don\'t know the arguments for {}, fix this manually.'.format(functionName))
|
||||
return 'UNKNOWN_ARG: UNKNOWN_TYPE'
|
||||
|
||||
def read_required_functions_from_file(self):
|
||||
try:
|
||||
with open(self.functionsInName, mode='r') as functionsIn:
|
||||
self.functions = ['Nt'+f[2:] if f[:2] == 'Zw' else f for f in [l.strip() for l in functionsIn.readlines()]]
|
||||
self.filterFunctions = len(self.functions) and "*" not in self.functions
|
||||
print('[i] Function filter file "{}" contains {} functions.'.format(self.functionsInName,len(self.functions)))
|
||||
except:
|
||||
print('[i] Function filter file "{}" not found. So not filtering functions.'.format(self.functionsInName))
|
||||
|
||||
def generate_function_args_mapping(self):
|
||||
try:
|
||||
with open(self.sw2headers, mode='r') as structsIn:
|
||||
inFunction = False
|
||||
currentFunction = ''
|
||||
currentFunctionArgs = []
|
||||
for f in [l.strip() for l in structsIn.readlines()]:
|
||||
if f.startswith("EXTERN_C"):
|
||||
functionName = currentFunction = f.split()[2].split("(")[0]
|
||||
if functionName in self.functions:
|
||||
inFunction = True
|
||||
self.functionOutputs[functionName] = f.split()[1]
|
||||
if f.endswith(");"):
|
||||
inFunction = False
|
||||
self.functionArgs[currentFunction] = []
|
||||
elif inFunction:
|
||||
arg = f.split()
|
||||
if len(arg) > 0:
|
||||
argType, argName = self.parse_function_arg(arg)
|
||||
currentFunctionArgs.append([argName, argType])
|
||||
if arg[-1].endswith(");"):
|
||||
inFunction = False
|
||||
self.functionArgs[currentFunction] = currentFunctionArgs
|
||||
currentFunctionArgs = []
|
||||
print('\n[i] Found return types for {} functions.'.format(len(self.functionOutputs)))
|
||||
|
||||
if self.debug:
|
||||
pprint(self.functionArgs)
|
||||
except:
|
||||
print('[i] Functions and Structs file "{}" not found. We need this to get return types and args. Exiting...'.format(self.sw2headers))
|
||||
exit()
|
||||
|
||||
def write_inline_assembly_to_file(self):
|
||||
filterThisFunction = False
|
||||
with open(self.fileInName, mode='r') as fileIn:
|
||||
lines = fileIn.readlines()
|
||||
lines.pop(0) #remove .code line
|
||||
|
||||
out = '{.passC:"-masm=intel".}\n\n'
|
||||
out += self.addSysWhispersFunctionBlock() + "\n\n"
|
||||
|
||||
if self.randomise:
|
||||
for function in self.functions:
|
||||
out += "# {} -> {}\n".format(function, self.function_map[function])
|
||||
|
||||
inFunction = False
|
||||
currentFunction = ""
|
||||
for line in lines:
|
||||
if inFunction:
|
||||
if self.regexFunctionEnd.match(line):
|
||||
inFunction = False
|
||||
out += '' if filterThisFunction else ' \"\"\"'+'\n'
|
||||
elif not filterThisFunction:
|
||||
mhex = self.regexHexNotation.match(line)
|
||||
if mhex:
|
||||
out += mhex[1]+'0x'+mhex[2]+mhex[3]+'\n'
|
||||
else:
|
||||
# instruction that sets SW2 hash
|
||||
if "mov ecx" in re.sub(currentFunction, self.function_map[currentFunction], self.regexAsmComment.match(line)[1]):
|
||||
out += self.fix_asm_line(re.sub(currentFunction, self.function_map[currentFunction], self.regexAsmComment.match(line)[1]))+'\n'
|
||||
else:
|
||||
out += re.sub(currentFunction, self.function_map[currentFunction], self.regexAsmComment.match(line)[1])+'\n'
|
||||
else:
|
||||
mstart = self.regexFunctionStart.match(line)
|
||||
if mstart:
|
||||
inFunction = True
|
||||
currentFunction = mstart[1]
|
||||
filterThisFunction = self.filterFunctions and not(mstart[1] in self.functions)
|
||||
out += '' if filterThisFunction else 'proc '+ self.function_map[mstart[1]] + '*(' + self.get_function_arguments(mstart[1]) + ')' +': '+ self.get_function_return_type(mstart[1]) + ' {.asmNoStackFrame.} ='+'\n'
|
||||
out += '' if filterThisFunction else ' asm \"\"\"\n'
|
||||
elif not filterThisFunction:
|
||||
out += '\n'
|
||||
|
||||
with open(self.fileOutName, mode='w') as fileOut:
|
||||
fileOut.write(out)
|
||||
fileOut.close()
|
||||
print("\n[+] Success! Outputted to {}".format(self.fileOutName))
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
parser = argparse.ArgumentParser(description="Convert SysWhispers output to Nim inline assembly.")
|
||||
parser.add_argument('--debug', action='store_true', help="Print mapped functions JSON")
|
||||
parser.add_argument('--randomise', action='store_true', help="Randomise the NT function names")
|
||||
parser.add_argument('--nobanner', action='store_true', help="Skip banner print out")
|
||||
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
nw = NimlineWhispers(args.debug, args.randomise, args.nobanner)
|
||||
nw.write_inline_assembly_to_file()
|
||||
@@ -0,0 +1,118 @@
|
||||
```
|
||||
% ..%%%%%# %/.
|
||||
/%%%%%,.%%%%%%%%%%%%%%%%%%%%%%%%%%%%.%%%%%%
|
||||
. #%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%.
|
||||
%%*.%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ,%%
|
||||
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%.
|
||||
#%%%%%%%%%%%%%%. %%%%%%%%%%%%%%%%
|
||||
%%%%%%%( %%%%%%%%%
|
||||
& %%# .%% ..
|
||||
&&. . . #&
|
||||
&&&&. . %&&&&&&&&. &&&&
|
||||
&&&&&&&.. . . (&&&&&&&&&&&&&&&&&%. . .&&&&&&&
|
||||
.%&&&&&&&&&&&&&&&&&&&&& ___ &&&&&&&&&&&&&&&&&&&&&
|
||||
#&&&&&&&&&&&&&&&&&&& |__ \ &&&&&&&&&&&&&&&&&&&
|
||||
,&&&&&&&&&&&&&&&&& ) | &&&&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&& / / &&&&&&&&&&&&&&
|
||||
&&&&&&&&&& / /_ &&&&&&&&&&
|
||||
%&& |____| &&.
|
||||
NimlineWhispers2
|
||||
@ajpc500 2021
|
||||
```
|
||||
|
||||
# NimlineWhispers2 #
|
||||
|
||||
Originally inspired by Outflank's [InlineWhispers](https://github.com/outflanknl/InlineWhispers) tool, `NimlineWhispers2` processes output from [SysWhispers2](https://github.com/jthuraisamy/SysWhispers2) to provide compatible inline assembly for use in Nim projects.
|
||||
|
||||
As with the original `NimlineWhispers`, this project also parses the `SysWhispers2` header file output to include function return types and arguments in the outputted inline assembly. Everything is then output into a single Nim file including an `emit` block with the SysWhispers2 methods, plus the defined functions.
|
||||
|
||||
> NOTE: NimlineWhispers 1 can be found [here](https://github.com/ajpc500/NimlineWhispers).
|
||||
|
||||
### How do I set this up? ###
|
||||
|
||||
* Clone this repository including the forked [SysWhispers2](https://github.com/ajpc500/SysWhispers2) sub module.
|
||||
* `git clone --recurse-submodules https://github.com/ajpc500/NimlineWhispers2.git `
|
||||
* Update which functions you required in `functions.txt`.
|
||||
* Run `python3 NimlineWhispers2.py` (additional flags listed below) to generate the inline assembly (`syscalls.nim`) file - example in the repo.
|
||||
* Add `include syscalls` to your Nim project.
|
||||
|
||||
An example of integrating NimlineWhispers2 output with your project can be seen in this [repo](https://github.com/ajpc500/NimExamples/tree/main/src/SysWhispers2).
|
||||
|
||||
### Randomised Function Names (same functionality as NW1) ###
|
||||
|
||||
To evade detection based on the presence of function names in our Nim executables (as outlined in [@ShitSecure](https://twitter.com/ShitSecure)'s blog [here](https://s3cur3th1ssh1t.github.io/A-tale-of-EDR-bypass-methods/)), NimlineWhispers2 can be run with a `--randomise` flag, as follows:
|
||||
|
||||
```
|
||||
python3 .\NimlineWhispers2.py --randomise --nobanner
|
||||
[i] Function filter file "functions.txt" contains 6 functions.
|
||||
|
||||
[i] Using SysWhispers2 to generate asm stubs...
|
||||
Complete! Files written to:
|
||||
nimlinewhispers.h
|
||||
nimlinewhispers.c
|
||||
nimlinewhispersstubs.asm
|
||||
|
||||
[i] Found return types for 6 functions.
|
||||
|
||||
[i] Producing randomised function mapping...
|
||||
NtResumeThread -> vWhUCQWffAEdMboE
|
||||
NtAllocateVirtualMemory -> SIitcDuyPGMirHPr
|
||||
NtClose -> uWZzTmdnlNmvteiL
|
||||
NtCreateThreadEx -> PDoWbNOwYbDDAcmW
|
||||
NtOpenProcess -> vWfwKlChxKZOutiX
|
||||
NtWriteVirtualMemory -> wItyVDPJWcFUqTNK
|
||||
|
||||
[+] Success! Outputted to syscalls.nim
|
||||
```
|
||||
|
||||
For ease of integration, the mapping shown in the command-line is added as a comment in the outputted `syscalls.nim` file (just above the functions and below the SW2 methods). As below (including the first function to demonstrate the output):
|
||||
|
||||
```
|
||||
...
|
||||
|
||||
# NtResumeThread -> vWhUCQWffAEdMboE
|
||||
# NtAllocateVirtualMemory -> SIitcDuyPGMirHPr
|
||||
# NtClose -> uWZzTmdnlNmvteiL
|
||||
# NtCreateThreadEx -> PDoWbNOwYbDDAcmW
|
||||
# NtOpenProcess -> vWfwKlChxKZOutiX
|
||||
# NtWriteVirtualMemory -> wItyVDPJWcFUqTNK
|
||||
|
||||
proc vWhUCQWffAEdMboE*(ThreadHandle: HANDLE, PreviousSuspendCount: PULONG): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
...
|
||||
```
|
||||
Notably your function definitions such as the below will need to be updated with the randomised names too.
|
||||
|
||||
```
|
||||
EXTERN_C NTSTATUS NtOpenProcess(
|
||||
OUT PHANDLE ProcessHandle,
|
||||
IN ACCESS_MASK DesiredAccess,
|
||||
IN POBJECT_ATTRIBUTES ObjectAttributes,
|
||||
IN PCLIENT_ID ClientId OPTIONAL);
|
||||
```
|
||||
Should become:
|
||||
|
||||
```
|
||||
EXTERN_C NTSTATUS sjGfpzWwEqIMryMW(
|
||||
OUT PHANDLE ProcessHandle,
|
||||
IN ACCESS_MASK DesiredAccess,
|
||||
IN POBJECT_ATTRIBUTES ObjectAttributes,
|
||||
IN PCLIENT_ID ClientId OPTIONAL);
|
||||
```
|
||||
|
||||
`syscalls_rand.nim` is included as an example output of this randomisation function.
|
||||
|
||||
### Limitations ###
|
||||
|
||||
* 64-bit only.
|
||||
|
||||
### Credits ###
|
||||
|
||||
* [Cas van Cooten](https://twitter.com/chvancooten) and [yamakadi](https://github.com/yamakadi) for posing and then [answering](https://gist.github.com/chvancooten/083dbdfd4a10261ee8dfecb4caf07e6c#gistcomment-3989360) how SW2 output could be used in Nim projects, which I've simply codified😁
|
||||
* This tool uses [SysWhispers2](https://github.com/jthuraisamy/SysWhispers2) to generate syscall stubs which are then processed for Nim, huge props to [@Jackson_T](https://twitter.com/Jackson_T) for `SysWhispers2`.
|
||||
* FalconForce's [SysWhispers2BOF](https://github.com/FalconForceTeam/SysWhispers2BOF) from which I borrowed several helper functions.
|
||||
* All people credited for [SysWhispers](https://github.com/jthuraisamy/SysWhispers#credits) and [SysWhispers2](https://github.com/jthuraisamy/SysWhispers2#credits)
|
||||
* @Outflank and @\_DaWouw for InlineWhispers
|
||||
* @byt3bl33d3r for his incredibly informative [OffensiveNim](https://github.com/byt3bl33d3r/OffensiveNim/) repository
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
NtResumeThread
|
||||
NtAllocateVirtualMemory
|
||||
NtClose
|
||||
NtCreateThreadEx
|
||||
NtOpenProcess
|
||||
NtWriteVirtualMemory
|
||||
+301
@@ -0,0 +1,301 @@
|
||||
{.passC:"-masm=intel".}
|
||||
|
||||
{.emit: """
|
||||
#pragma once
|
||||
|
||||
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||
|
||||
#ifndef SW2_HEADER_H_
|
||||
#define SW2_HEADER_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define SW2_SEED 0x25636360
|
||||
#define SW2_ROL8(v) (v << 8 | v >> 24)
|
||||
#define SW2_ROR8(v) (v >> 8 | v << 24)
|
||||
#define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v))
|
||||
#define SW2_MAX_ENTRIES 500
|
||||
#define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
|
||||
|
||||
// Typedefs are prefixed to avoid pollution.
|
||||
|
||||
typedef struct _SW2_SYSCALL_ENTRY
|
||||
{
|
||||
DWORD Hash;
|
||||
DWORD Address;
|
||||
} SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;
|
||||
|
||||
typedef struct _SW2_SYSCALL_LIST
|
||||
{
|
||||
DWORD Count;
|
||||
SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES];
|
||||
} SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST;
|
||||
|
||||
typedef struct _SW2_PEB_LDR_DATA {
|
||||
BYTE Reserved1[8];
|
||||
PVOID Reserved2[3];
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
} SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA;
|
||||
|
||||
typedef struct _SW2_LDR_DATA_TABLE_ENTRY {
|
||||
PVOID Reserved1[2];
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
PVOID Reserved2[2];
|
||||
PVOID DllBase;
|
||||
} SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
typedef struct _SW2_PEB {
|
||||
BYTE Reserved1[2];
|
||||
BYTE BeingDebugged;
|
||||
BYTE Reserved2[1];
|
||||
PVOID Reserved3[2];
|
||||
PSW2_PEB_LDR_DATA Ldr;
|
||||
} SW2_PEB, *PSW2_PEB;
|
||||
|
||||
DWORD SW2_HashSyscall(PCSTR FunctionName);
|
||||
BOOL SW2_PopulateSyscallList();
|
||||
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||
|
||||
SW2_SYSCALL_LIST SW2_SyscallList = {0,1};
|
||||
|
||||
DWORD SW2_HashSyscall(PCSTR FunctionName)
|
||||
{
|
||||
DWORD i = 0;
|
||||
DWORD Hash = SW2_SEED;
|
||||
|
||||
while (FunctionName[i])
|
||||
{
|
||||
WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++);
|
||||
Hash ^= PartialName + SW2_ROR8(Hash);
|
||||
}
|
||||
|
||||
return Hash;
|
||||
}
|
||||
|
||||
BOOL SW2_PopulateSyscallList()
|
||||
{
|
||||
// Return early if the list is already populated.
|
||||
if (SW2_SyscallList.Count) return TRUE;
|
||||
|
||||
PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
|
||||
PSW2_PEB_LDR_DATA Ldr = Peb->Ldr;
|
||||
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
|
||||
PVOID DllBase = NULL;
|
||||
|
||||
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
|
||||
// in the list, so it's safer to loop through the full list and find it.
|
||||
PSW2_LDR_DATA_TABLE_ENTRY LdrEntry;
|
||||
for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
|
||||
{
|
||||
DllBase = LdrEntry->DllBase;
|
||||
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
|
||||
PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
|
||||
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
|
||||
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||
if (VirtualAddress == 0) continue;
|
||||
|
||||
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
|
||||
|
||||
// If this is NTDLL.dll, exit loop.
|
||||
PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
|
||||
|
||||
if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;
|
||||
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;
|
||||
}
|
||||
|
||||
if (!ExportDirectory) return FALSE;
|
||||
|
||||
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
|
||||
PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
|
||||
PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
|
||||
PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
|
||||
|
||||
// Populate SW2_SyscallList with unsorted Zw* entries.
|
||||
DWORD i = 0;
|
||||
PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
|
||||
do
|
||||
{
|
||||
PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
|
||||
|
||||
// Is this a system call?
|
||||
if (*(USHORT*)FunctionName == 'wZ')
|
||||
{
|
||||
Entries[i].Hash = SW2_HashSyscall(FunctionName);
|
||||
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
|
||||
|
||||
i++;
|
||||
if (i == SW2_MAX_ENTRIES) break;
|
||||
}
|
||||
} while (--NumberOfNames);
|
||||
|
||||
// Save total number of system calls found.
|
||||
SW2_SyscallList.Count = i;
|
||||
|
||||
// Sort the list by address in ascending order.
|
||||
for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++)
|
||||
{
|
||||
for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++)
|
||||
{
|
||||
if (Entries[j].Address > Entries[j + 1].Address)
|
||||
{
|
||||
// Swap entries.
|
||||
SW2_SYSCALL_ENTRY TempEntry;
|
||||
|
||||
TempEntry.Hash = Entries[j].Hash;
|
||||
TempEntry.Address = Entries[j].Address;
|
||||
|
||||
Entries[j].Hash = Entries[j + 1].Hash;
|
||||
Entries[j].Address = Entries[j + 1].Address;
|
||||
|
||||
Entries[j + 1].Hash = TempEntry.Hash;
|
||||
Entries[j + 1].Address = TempEntry.Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW2_SyscallList is populated.
|
||||
if (!SW2_PopulateSyscallList()) return -1;
|
||||
|
||||
for (DWORD i = 0; i < SW2_SyscallList.Count; i++)
|
||||
{
|
||||
if (FunctionHash == SW2_SyscallList.Entries[i].Hash)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
""".}
|
||||
|
||||
|
||||
|
||||
|
||||
proc NtResumeThread*(ThreadHandle: HANDLE, PreviousSuspendCount: PULONG): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x02E8A17CF
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc NtAllocateVirtualMemory*(ProcessHandle: HANDLE, BaseAddress: PVOID, ZeroBits: ULONG, RegionSize: PSIZE_T, AllocationType: ULONG, Protect: ULONG): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x01D97191B
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc NtClose*(Handle: HANDLE): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x01494EE89
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc NtCreateThreadEx*(ThreadHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ProcessHandle: HANDLE, StartRoutine: PVOID, Argument: PVOID, CreateFlags: ULONG, ZeroBits: SIZE_T, StackSize: SIZE_T, MaximumStackSize: SIZE_T, AttributeList: PPS_ATTRIBUTE_LIST): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x052AB0070
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc NtOpenProcess*(ProcessHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ClientId: PCLIENT_ID): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x0521B7388
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc NtWriteVirtualMemory*(ProcessHandle: HANDLE, BaseAddress: PVOID, Buffer: PVOID, NumberOfBytesToWrite: SIZE_T, NumberOfBytesWritten: PSIZE_T): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x005962B01
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
|
||||
@@ -0,0 +1,305 @@
|
||||
{.passC:"-masm=intel".}
|
||||
|
||||
{.emit: """
|
||||
#pragma once
|
||||
|
||||
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||
|
||||
#ifndef SW2_HEADER_H_
|
||||
#define SW2_HEADER_H_
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#define SW2_SEED 0x4D022A31
|
||||
#define SW2_ROL8(v) (v << 8 | v >> 24)
|
||||
#define SW2_ROR8(v) (v >> 8 | v << 24)
|
||||
#define SW2_ROX8(v) ((SW2_SEED % 2) ? SW2_ROL8(v) : SW2_ROR8(v))
|
||||
#define SW2_MAX_ENTRIES 500
|
||||
#define SW2_RVA2VA(Type, DllBase, Rva) (Type)((ULONG_PTR) DllBase + Rva)
|
||||
|
||||
// Typedefs are prefixed to avoid pollution.
|
||||
|
||||
typedef struct _SW2_SYSCALL_ENTRY
|
||||
{
|
||||
DWORD Hash;
|
||||
DWORD Address;
|
||||
} SW2_SYSCALL_ENTRY, *PSW2_SYSCALL_ENTRY;
|
||||
|
||||
typedef struct _SW2_SYSCALL_LIST
|
||||
{
|
||||
DWORD Count;
|
||||
SW2_SYSCALL_ENTRY Entries[SW2_MAX_ENTRIES];
|
||||
} SW2_SYSCALL_LIST, *PSW2_SYSCALL_LIST;
|
||||
|
||||
typedef struct _SW2_PEB_LDR_DATA {
|
||||
BYTE Reserved1[8];
|
||||
PVOID Reserved2[3];
|
||||
LIST_ENTRY InMemoryOrderModuleList;
|
||||
} SW2_PEB_LDR_DATA, *PSW2_PEB_LDR_DATA;
|
||||
|
||||
typedef struct _SW2_LDR_DATA_TABLE_ENTRY {
|
||||
PVOID Reserved1[2];
|
||||
LIST_ENTRY InMemoryOrderLinks;
|
||||
PVOID Reserved2[2];
|
||||
PVOID DllBase;
|
||||
} SW2_LDR_DATA_TABLE_ENTRY, *PSW2_LDR_DATA_TABLE_ENTRY;
|
||||
|
||||
typedef struct _SW2_PEB {
|
||||
BYTE Reserved1[2];
|
||||
BYTE BeingDebugged;
|
||||
BYTE Reserved2[1];
|
||||
PVOID Reserved3[2];
|
||||
PSW2_PEB_LDR_DATA Ldr;
|
||||
} SW2_PEB, *PSW2_PEB;
|
||||
|
||||
DWORD SW2_HashSyscall(PCSTR FunctionName);
|
||||
BOOL SW2_PopulateSyscallList();
|
||||
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash);
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
// Code below is adapted from @modexpblog. Read linked article for more details.
|
||||
// https://www.mdsec.co.uk/2020/12/bypassing-user-mode-hooks-and-direct-invocation-of-system-calls-for-red-teams
|
||||
|
||||
SW2_SYSCALL_LIST SW2_SyscallList = {0,1};
|
||||
|
||||
DWORD SW2_HashSyscall(PCSTR FunctionName)
|
||||
{
|
||||
DWORD i = 0;
|
||||
DWORD Hash = SW2_SEED;
|
||||
|
||||
while (FunctionName[i])
|
||||
{
|
||||
WORD PartialName = *(WORD*)((ULONG64)FunctionName + i++);
|
||||
Hash ^= PartialName + SW2_ROR8(Hash);
|
||||
}
|
||||
|
||||
return Hash;
|
||||
}
|
||||
|
||||
BOOL SW2_PopulateSyscallList()
|
||||
{
|
||||
// Return early if the list is already populated.
|
||||
if (SW2_SyscallList.Count) return TRUE;
|
||||
|
||||
PSW2_PEB Peb = (PSW2_PEB)__readgsqword(0x60);
|
||||
PSW2_PEB_LDR_DATA Ldr = Peb->Ldr;
|
||||
PIMAGE_EXPORT_DIRECTORY ExportDirectory = NULL;
|
||||
PVOID DllBase = NULL;
|
||||
|
||||
// Get the DllBase address of NTDLL.dll. NTDLL is not guaranteed to be the second
|
||||
// in the list, so it's safer to loop through the full list and find it.
|
||||
PSW2_LDR_DATA_TABLE_ENTRY LdrEntry;
|
||||
for (LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW2_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
|
||||
{
|
||||
DllBase = LdrEntry->DllBase;
|
||||
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
|
||||
PIMAGE_NT_HEADERS NtHeaders = SW2_RVA2VA(PIMAGE_NT_HEADERS, DllBase, DosHeader->e_lfanew);
|
||||
PIMAGE_DATA_DIRECTORY DataDirectory = (PIMAGE_DATA_DIRECTORY)NtHeaders->OptionalHeader.DataDirectory;
|
||||
DWORD VirtualAddress = DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
|
||||
if (VirtualAddress == 0) continue;
|
||||
|
||||
ExportDirectory = (PIMAGE_EXPORT_DIRECTORY)SW2_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
|
||||
|
||||
// If this is NTDLL.dll, exit loop.
|
||||
PCHAR DllName = SW2_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
|
||||
|
||||
if ((*(ULONG*)DllName | 0x20202020) != 'ldtn') continue;
|
||||
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 'ld.l') break;
|
||||
}
|
||||
|
||||
if (!ExportDirectory) return FALSE;
|
||||
|
||||
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
|
||||
PDWORD Functions = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
|
||||
PDWORD Names = SW2_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
|
||||
PWORD Ordinals = SW2_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
|
||||
|
||||
// Populate SW2_SyscallList with unsorted Zw* entries.
|
||||
DWORD i = 0;
|
||||
PSW2_SYSCALL_ENTRY Entries = SW2_SyscallList.Entries;
|
||||
do
|
||||
{
|
||||
PCHAR FunctionName = SW2_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
|
||||
|
||||
// Is this a system call?
|
||||
if (*(USHORT*)FunctionName == 'wZ')
|
||||
{
|
||||
Entries[i].Hash = SW2_HashSyscall(FunctionName);
|
||||
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
|
||||
|
||||
i++;
|
||||
if (i == SW2_MAX_ENTRIES) break;
|
||||
}
|
||||
} while (--NumberOfNames);
|
||||
|
||||
// Save total number of system calls found.
|
||||
SW2_SyscallList.Count = i;
|
||||
|
||||
// Sort the list by address in ascending order.
|
||||
for (DWORD i = 0; i < SW2_SyscallList.Count - 1; i++)
|
||||
{
|
||||
for (DWORD j = 0; j < SW2_SyscallList.Count - i - 1; j++)
|
||||
{
|
||||
if (Entries[j].Address > Entries[j + 1].Address)
|
||||
{
|
||||
// Swap entries.
|
||||
SW2_SYSCALL_ENTRY TempEntry;
|
||||
|
||||
TempEntry.Hash = Entries[j].Hash;
|
||||
TempEntry.Address = Entries[j].Address;
|
||||
|
||||
Entries[j].Hash = Entries[j + 1].Hash;
|
||||
Entries[j].Address = Entries[j + 1].Address;
|
||||
|
||||
Entries[j + 1].Hash = TempEntry.Hash;
|
||||
Entries[j + 1].Address = TempEntry.Address;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
EXTERN_C DWORD SW2_GetSyscallNumber(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW2_SyscallList is populated.
|
||||
if (!SW2_PopulateSyscallList()) return -1;
|
||||
|
||||
for (DWORD i = 0; i < SW2_SyscallList.Count; i++)
|
||||
{
|
||||
if (FunctionHash == SW2_SyscallList.Entries[i].Hash)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
""".}
|
||||
|
||||
# NtResumeThread -> vWhUCQWffAEdMboE
|
||||
# NtAllocateVirtualMemory -> SIitcDuyPGMirHPr
|
||||
# NtClose -> uWZzTmdnlNmvteiL
|
||||
# NtCreateThreadEx -> PDoWbNOwYbDDAcmW
|
||||
# NtOpenProcess -> vWfwKlChxKZOutiX
|
||||
# NtWriteVirtualMemory -> wItyVDPJWcFUqTNK
|
||||
|
||||
proc vWhUCQWffAEdMboE*(ThreadHandle: HANDLE, PreviousSuspendCount: PULONG): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x0FA5D24E7
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc SIitcDuyPGMirHPr*(ProcessHandle: HANDLE, BaseAddress: PVOID, ZeroBits: ULONG, RegionSize: PSIZE_T, AllocationType: ULONG, Protect: ULONG): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x007972D29
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc uWZzTmdnlNmvteiL*(Handle: HANDLE): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x04041D569
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc PDoWbNOwYbDDAcmW*(ThreadHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ProcessHandle: HANDLE, StartRoutine: PVOID, Argument: PVOID, CreateFlags: ULONG, ZeroBits: SIZE_T, StackSize: SIZE_T, MaximumStackSize: SIZE_T, AttributeList: PPS_ATTRIBUTE_LIST): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x0142842F6
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc vWfwKlChxKZOutiX*(ProcessHandle: PHANDLE, DesiredAccess: ACCESS_MASK, ObjectAttributes: POBJECT_ATTRIBUTES, ClientId: PCLIENT_ID): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x05E2551B8
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
proc wItyVDPJWcFUqTNK*(ProcessHandle: HANDLE, BaseAddress: PVOID, Buffer: PVOID, NumberOfBytesToWrite: SIZE_T, NumberOfBytesWritten: PSIZE_T): NTSTATUS {.asmNoStackFrame.} =
|
||||
asm """
|
||||
mov [rsp +8], rcx
|
||||
mov [rsp+16], rdx
|
||||
mov [rsp+24], r8
|
||||
mov [rsp+32], r9
|
||||
sub rsp, 0x28
|
||||
mov ecx, 0x00F911503
|
||||
call SW2_GetSyscallNumber
|
||||
add rsp, 0x28
|
||||
mov rcx, [rsp +8]
|
||||
mov rdx, [rsp+16]
|
||||
mov r8, [rsp+24]
|
||||
mov r9, [rsp+32]
|
||||
mov r10, rcx
|
||||
syscall
|
||||
ret
|
||||
"""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user