mirror of
https://github.com/tdeerenberg/InlineWhispers3.git
synced 2025-07-15 00:44:17 +00:00
Initial Commit
This commit is contained in:
163
InlineWhispers3.py
Normal file
163
InlineWhispers3.py
Normal file
@ -0,0 +1,163 @@
|
||||
#!/usr/bin/env python
|
||||
# coding: utf=8
|
||||
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import argparse
|
||||
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
|
||||
def get_new_seed():
|
||||
'''Get new seed'''
|
||||
with open('SysWhispers3/syscalls_all.h') as file:
|
||||
for line in file:
|
||||
if 'SW3_SEED' in line:
|
||||
return line
|
||||
|
||||
def replace_seed():
|
||||
'''Replace SEED in the new file'''
|
||||
new_seed = get_new_seed()
|
||||
|
||||
replacement = ''
|
||||
with open('output/syscalls.h', 'r') as file:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
changes = line.replace('$$SEED$$', new_seed)
|
||||
replacement = replacement + changes + '\n'
|
||||
with open('output/syscalls.h', 'w') as file:
|
||||
file.write(replacement)
|
||||
print("[+] New seed added to syscalls.h")
|
||||
|
||||
def replace_extern():
|
||||
'''Replace EXTERN_C definitions in the new file'''
|
||||
replacement = ''
|
||||
actual_extern_c = ''
|
||||
is_extern_part = False
|
||||
with open('output/syscalls.h', 'r') as file:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
if 'EXTERN_C NTSTATUS' in line:
|
||||
actual_extern_c = line.replace('EXTERN_C NTSTATUS', '').replace('(', '').replace(')', '').replace(';', '').replace(' ', '')
|
||||
is_extern_part = True
|
||||
if is_extern_part and ';' in line:
|
||||
changes = line.replace(';', f' asm("{actual_extern_c}");')
|
||||
|
||||
if '#include <windows.h>' in line:
|
||||
changes = line.replace('#include <windows.h>', '#include <windows.h>\n#include "syscalls-asm.h"')
|
||||
else:
|
||||
changes = line
|
||||
replacement = replacement + changes + '\n'
|
||||
with open('output/syscalls.h', 'w') as file:
|
||||
file.write(replacement)
|
||||
|
||||
def create_asm_file():
|
||||
'''Create asm stubs file'''
|
||||
replacement = ''
|
||||
with open('SysWhispers3/syscalls_all_-asm.x64.asm', 'r') as file:
|
||||
for line in file:
|
||||
line = line.strip()
|
||||
|
||||
if '.code' in line:
|
||||
line = line.replace('.code', '#pragma once\r\n#include <windows.h>\r\n\r\n#if _WIN64')
|
||||
if 'EXTERN SW3_GetSyscallNumber: PROC' in line:
|
||||
line = line.replace('EXTERN SW3_GetSyscallNumber: PROC', '')
|
||||
if 'EXTERN SW3_GetSyscallAddress: PROC' in line:
|
||||
line = line.replace('EXTERN SW3_GetSyscallAddress: PROC', '')
|
||||
if 'PROC' in line:
|
||||
func_nt = line.split(' ', 1)[0]
|
||||
func_zw = func_nt.replace('Nt', 'Zw')
|
||||
line = line.replace(f'{func_nt} PROC', f'#define {func_zw} {func_nt}\r\n__asm__("{func_nt}: \\n\\')
|
||||
if ';' in line:
|
||||
line = line.split(';', 1)[0]
|
||||
if 'sub rsp, 28h' in line:
|
||||
line = line.replace('sub rsp, 28h', 'sub rsp, 0x28').rstrip() + ' \\n\\'
|
||||
if 'add rsp, 28h' in line:
|
||||
line = line.replace('add rsp, 28h', 'add rsp, 0x28').rstrip() + ' \\n\\'
|
||||
if 'mov ecx,' in line:
|
||||
line = line.replace('mov ecx, ', 'mov ecx, 0x').rstrip()[:-1]
|
||||
if line.startswith('call'):
|
||||
line = line.rstrip() + ' \\n\\'
|
||||
if line.startswith('mov'):
|
||||
line = line.rstrip() + ' \\n\\'
|
||||
if line.startswith('syscall'):
|
||||
line = line.rstrip() + ' \\n\\'
|
||||
if line.startswith('ret'):
|
||||
line = line.rstrip() + ' \\n\\'
|
||||
if line.startswith('jmp'):
|
||||
line = line.rstrip() + ' \\n\\'
|
||||
if 'ENDP' in line:
|
||||
line = '");'
|
||||
|
||||
if line.startswith('end'):
|
||||
line = '#endif'
|
||||
|
||||
changes = line
|
||||
replacement = replacement + changes + '\n'
|
||||
|
||||
with open('output/syscalls-asm.h', 'w') as file:
|
||||
file.write(replacement)
|
||||
|
||||
def remove_specific_includes(content):
|
||||
'''Remove #include "syscalls.h", #include "syscalls-asm.h", and #include "syscalls.c" statements from the content'''
|
||||
return re.sub(r'#(include ("syscalls(.h"|-asm.h"|.c")|<windows.h>)|pragma once)', '', content)
|
||||
|
||||
def merge_to_aio():
|
||||
'''Merge all files to one usable .h file'''
|
||||
with open('output/syscalls-aio.h', 'w') as aio:
|
||||
with open('output/syscalls.h', 'r') as sys_h, \
|
||||
open('output/syscalls.c', 'r') as sys_c, \
|
||||
open('output/syscalls-asm.h', 'r') as sys_asm:
|
||||
|
||||
aio.write("#pragma once\n#include <windows.h>\n")
|
||||
aio.write(remove_specific_includes(sys_h.read()))
|
||||
aio.write('\n')
|
||||
aio.write(remove_specific_includes(sys_c.read()))
|
||||
aio.write('\n')
|
||||
aio.write(remove_specific_includes(sys_asm.read()))
|
||||
|
||||
if __name__ == '__main__':
|
||||
'''Main'''
|
||||
print(".___ .__ .__ __ __.__ .__ ________ ")
|
||||
print("| | ____ | | |__| ____ ____/ \\ / \\ |__ |__| ____________ ___________ _____\\_____ \\ ")
|
||||
print("| |/ \\| | | |/ \\_/ __ \\ \\/\\/ / | \\| |/ ___/\\____ \\_/ __ \\_ __ \\/ ___/ _(__ < ")
|
||||
print("| | | \\ |_| | | \\ ___/\\ /| Y \\ |\\___ \\ | |_> > ___/| | \\/\\___ \\ / \\ ")
|
||||
print("|___|___| /____/__|___| /\\___ >\\__/\\ / |___| /__/____ >| __/ \\___ >__| /____ >______ /")
|
||||
print(" \\/ \\/ \\/ \\/ \\/ \\/ |__| \\/ \\/ \\/")
|
||||
print("\r\ntdeerenberg - https://github.com/tdeerenberg\r\n")
|
||||
|
||||
if not os.path.isdir('SysWhispers3') or not os.path.isfile('SysWhispers3/syscalls_all.h'):
|
||||
print('[ERROR] SysWhispers3 not present, to fix:\r\n')
|
||||
print('git clone https://github.com/klezVirus/SysWhispers3')
|
||||
print('cd SysWhispers3/ && python3 syswhispers.py -p all -a x64 -m jumper -o syscalls_all && cd ..\r\n')
|
||||
sys.exit(0)
|
||||
|
||||
# Create output directory with the new templates
|
||||
Path('output').mkdir(parents=True, exist_ok=True)
|
||||
copyfile('syscalls.c.template', 'output/syscalls.c')
|
||||
copyfile('syscalls.h.template', 'output/syscalls.h')
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument('--aio', action='store_true', help="Trigger the aio flag")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Replace EXTERN_C in syscalls.h
|
||||
print("[+] Replacing EXTERN_C in syscalls.h")
|
||||
replace_extern()
|
||||
print("[+] Replaced EXTERN_C in syscalls.h")
|
||||
|
||||
# Create asm stub with the correct syscalls format
|
||||
print("[+] Creating syscalls-asm.c with indirect syscall instructions")
|
||||
create_asm_file()
|
||||
print("[+] Done creating syscalls-asm.c\n")
|
||||
|
||||
if args.aio:
|
||||
print('[+] All in One flag detected, merging syscalls.c, syscalls.h, and syscalls-asm.h into one file')
|
||||
merge_to_aio()
|
||||
print("[+] Merged into one usable file: syscalls-aio.h\n")
|
||||
print("Import syscalls-aio.h in your project and include syscalls-aio.h to use indirect syscalls, or:")
|
||||
|
||||
print("Import syscalls.c syscalls.h, syscalls-asm.h in your project and include syscalls.c to start to use syscalls")
|
||||
|
||||
|
44
README.md
44
README.md
@ -1,2 +1,44 @@
|
||||
# InlineWhispers3
|
||||
Tool for working with Indirect System Calls in Cobalt Strike's Beacon Object Files (BOF) using SysWhispers3
|
||||
InlineWhispers3 is an updated version of [InlineWhispers2](https://github.com/Sh0ckFR/InlineWhispers2), designed to work with Indirect System Calls in Cobalt Strike's Beacon Object Files (BOFs) using [SysWhispers3](https://github.com/klezVirus/SysWhispers3). This tool helps changing SysWhispers3 generated files to be BOF compatible.
|
||||
|
||||
## How to set this up?
|
||||
|
||||
```sh
|
||||
# Clone the main repository and initialize submodules (SysWhispers3)
|
||||
git clone --recurse-submodules https://github.com/tdeerenberg/InlineWhispers3
|
||||
cd InlineWhispers3
|
||||
|
||||
# Navigate to submodule directory and run the syswhispers.py script
|
||||
cd SysWhispers3/
|
||||
python3 syswhispers.py -p all -a x64 -m jumper -o syscalls_all
|
||||
|
||||
# Navigate back to the main repository and run the InlineWhispers3.py script
|
||||
cd ..
|
||||
python3 InlineWhispers3.py --aio
|
||||
```
|
||||
|
||||
This generates the required syscalls.c/h files and then runs InlineWhispers3 to make the files compatible with BOFs.
|
||||
|
||||
> The `--aio` flag is optional and merges all output files into one `.h` file, which can also be used instead of using `syscalls.c`, `syscalls.h`, and `syscalls-asm.h`
|
||||
|
||||
## How to use indirect syscalls in your BOF
|
||||
|
||||
Import `syscalls.h`, `syscalls.c`, and `syscalls-asm.h` (or only `syscalls-aio.h`) in your project and include `syscalls.c` (or `syscalls-aio.h`) in your C code to start using syscalls.
|
||||
|
||||
An example BOF for reference (creates a new process using ` `):
|
||||
|
||||
```c
|
||||
#include <windows.h>
|
||||
#include "beacon.h"
|
||||
#include "syscalls.c"
|
||||
|
||||
void go(char* args, int length) {
|
||||
... CODE TO BE ADDED ...
|
||||
}
|
||||
```
|
||||
|
||||
## Credits
|
||||
- [@klezVirus](https://github.com/klezVirus) for SysWhispers3
|
||||
- [@Sh0ckFR](https://github.com/Sh0ckFR) for InlineWhispers2
|
||||
- [@outflanknl](https://github.com/outflanknl) for the first version of InlineWhispers and their informative blog post about it
|
||||
- The Cyber Security Community for all the articles and resources
|
1
SysWhispers3
Submodule
1
SysWhispers3
Submodule
Submodule SysWhispers3 added at 31cfc93c94
14499
output/syscalls-aio.h
Normal file
14499
output/syscalls-aio.h
Normal file
File diff suppressed because it is too large
Load Diff
10132
output/syscalls-asm.h
Normal file
10132
output/syscalls-asm.h
Normal file
File diff suppressed because it is too large
Load Diff
306
output/syscalls.c
Normal file
306
output/syscalls.c
Normal file
@ -0,0 +1,306 @@
|
||||
#include "syscalls.h"
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#define JUMPER
|
||||
|
||||
#ifdef _M_IX86
|
||||
|
||||
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) {
|
||||
return (PVOID)__readfsdword(0xC0);
|
||||
}
|
||||
|
||||
__declspec(naked) BOOL local_is_wow64(void)
|
||||
{
|
||||
__asm {
|
||||
mov eax, fs:[0xc0]
|
||||
test eax, eax
|
||||
jne wow64
|
||||
mov eax, 0
|
||||
ret
|
||||
wow64:
|
||||
mov eax, 1
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#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
|
||||
|
||||
SW3_SYSCALL_LIST SW3_SyscallList;
|
||||
|
||||
// SEARCH_AND_REPLACE
|
||||
#ifdef SEARCH_AND_REPLACE
|
||||
// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release
|
||||
EXTERN void SearchAndReplace(unsigned char[], unsigned char[]);
|
||||
#endif
|
||||
|
||||
DWORD SW3_HashSyscall(PCSTR FunctionName)
|
||||
{
|
||||
DWORD i = 0;
|
||||
DWORD Hash = SW3_SEED;
|
||||
|
||||
while (FunctionName[i])
|
||||
{
|
||||
WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++);
|
||||
Hash ^= PartialName + SW3_ROR8(Hash);
|
||||
}
|
||||
|
||||
return Hash;
|
||||
}
|
||||
|
||||
#ifndef JUMPER
|
||||
PVOID SC_Address(PVOID NtApiAddress)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
PVOID SC_Address(PVOID NtApiAddress)
|
||||
{
|
||||
DWORD searchLimit = 512;
|
||||
PVOID SyscallAddress;
|
||||
|
||||
#ifdef _WIN64
|
||||
// If the process is 64-bit on a 64-bit OS, we need to search for syscall
|
||||
BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
|
||||
ULONG distance_to_syscall = 0x12;
|
||||
#else
|
||||
// If the process is 32-bit on a 32-bit OS, we need to search for sysenter
|
||||
BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
|
||||
ULONG distance_to_syscall = 0x0f;
|
||||
#endif
|
||||
|
||||
#ifdef _M_IX86
|
||||
// If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved
|
||||
if (local_is_wow64())
|
||||
{
|
||||
#ifdef DEBUG
|
||||
// printf("[+] Running 32-bit app on x64 (WOW64)\n");
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
// we don't really care if there is a 'jmp' between
|
||||
// NtApiAddress and the 'syscall; ret' instructions
|
||||
SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall);
|
||||
|
||||
int match = 1; // Assume a match initially
|
||||
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
||||
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
||||
match = 0; // Set match to 0 if any byte doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// we can use the original code for this system call :)
|
||||
#if defined(DEBUG)
|
||||
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||
#endif
|
||||
return SyscallAddress;
|
||||
}
|
||||
|
||||
// the 'syscall; ret' intructions have not been found,
|
||||
// we will try to use one near it, similarly to HalosGate
|
||||
|
||||
for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++)
|
||||
{
|
||||
// let's try with an Nt* API below our syscall
|
||||
SyscallAddress = SW3_RVA2VA(
|
||||
PVOID,
|
||||
NtApiAddress,
|
||||
distance_to_syscall + num_jumps * 0x20);
|
||||
match = 1; // Assume a match initially
|
||||
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
||||
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
||||
match = 0; // Set match to 0 if any byte doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// we can use the original code for this system call :)
|
||||
#if defined(DEBUG)
|
||||
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||
#endif
|
||||
return SyscallAddress;
|
||||
}
|
||||
|
||||
// let's try with an Nt* API above our syscall
|
||||
SyscallAddress = SW3_RVA2VA(
|
||||
PVOID,
|
||||
NtApiAddress,
|
||||
distance_to_syscall - num_jumps * 0x20);
|
||||
|
||||
match = 1; // Assume a match initially
|
||||
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
||||
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
||||
match = 0; // Set match to 0 if any byte doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// we can use the original code for this system call :)
|
||||
#if defined(DEBUG)
|
||||
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||
#endif
|
||||
return SyscallAddress;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// printf("Syscall Opcodes not found!\n");
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
BOOL SW3_PopulateSyscallList()
|
||||
{
|
||||
// Return early if the list is already populated.
|
||||
if (SW3_SyscallList.Count) return TRUE;
|
||||
|
||||
#ifdef _WIN64
|
||||
PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60);
|
||||
#else
|
||||
PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30);
|
||||
#endif
|
||||
PSW3_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.
|
||||
PSW3_LDR_DATA_TABLE_ENTRY LdrEntry;
|
||||
for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
|
||||
{
|
||||
DllBase = LdrEntry->DllBase;
|
||||
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
|
||||
PIMAGE_NT_HEADERS NtHeaders = SW3_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)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
|
||||
|
||||
// If this is NTDLL.dll, exit loop.
|
||||
PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
|
||||
|
||||
if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue;
|
||||
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break;
|
||||
}
|
||||
|
||||
if (!ExportDirectory) return FALSE;
|
||||
|
||||
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
|
||||
PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
|
||||
PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
|
||||
PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
|
||||
|
||||
// Populate SW3_SyscallList with unsorted Zw* entries.
|
||||
DWORD i = 0;
|
||||
PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries;
|
||||
do
|
||||
{
|
||||
PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
|
||||
|
||||
// Is this a system call?
|
||||
if (*(USHORT*)FunctionName == 0x775a)
|
||||
{
|
||||
Entries[i].Hash = SW3_HashSyscall(FunctionName);
|
||||
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
|
||||
Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address));
|
||||
|
||||
i++;
|
||||
if (i == SW3_MAX_ENTRIES) break;
|
||||
}
|
||||
} while (--NumberOfNames);
|
||||
|
||||
// Save total number of system calls found.
|
||||
SW3_SyscallList.Count = i;
|
||||
|
||||
// Sort the list by address in ascending order.
|
||||
for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++)
|
||||
{
|
||||
for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++)
|
||||
{
|
||||
if (Entries[j].Address > Entries[j + 1].Address)
|
||||
{
|
||||
// Swap entries.
|
||||
SW3_SYSCALL_ENTRY TempEntry;
|
||||
|
||||
TempEntry.Hash = Entries[j].Hash;
|
||||
TempEntry.Address = Entries[j].Address;
|
||||
TempEntry.SyscallAddress = Entries[j].SyscallAddress;
|
||||
|
||||
Entries[j].Hash = Entries[j + 1].Hash;
|
||||
Entries[j].Address = Entries[j + 1].Address;
|
||||
Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress;
|
||||
|
||||
Entries[j + 1].Hash = TempEntry.Hash;
|
||||
Entries[j + 1].Address = TempEntry.Address;
|
||||
Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW3_SyscallList is populated.
|
||||
if (!SW3_PopulateSyscallList()) return -1;
|
||||
|
||||
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||
{
|
||||
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW3_SyscallList is populated.
|
||||
if (!SW3_PopulateSyscallList()) return NULL;
|
||||
|
||||
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||
{
|
||||
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||
{
|
||||
return SW3_SyscallList.Entries[i].SyscallAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DWORD LCG() {
|
||||
static DWORD seed = 123987654;
|
||||
return (seed >> 16) & 0x7FFF; // return a pseudo-random number
|
||||
}
|
||||
|
||||
EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW3_SyscallList is populated.
|
||||
if (!SW3_PopulateSyscallList()) return NULL;
|
||||
|
||||
DWORD index = LCG() % SW3_SyscallList.Count;
|
||||
|
||||
while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
|
||||
// Spoofing the syscall return address
|
||||
index = LCG() % SW3_SyscallList.Count;
|
||||
}
|
||||
return SW3_SyscallList.Entries[index].SyscallAddress;
|
||||
}
|
4057
output/syscalls.h
Normal file
4057
output/syscalls.h
Normal file
File diff suppressed because it is too large
Load Diff
0
sample_bof/Makefile
Normal file
0
sample_bof/Makefile
Normal file
370
sample_bof/beacon.h
Normal file
370
sample_bof/beacon.h
Normal file
@ -0,0 +1,370 @@
|
||||
/*
|
||||
* Beacon Object Files (BOF)
|
||||
* -------------------------
|
||||
* A Beacon Object File is a light-weight post exploitation tool that runs
|
||||
* with Beacon's inline-execute command.
|
||||
*
|
||||
* Additional BOF resources are available here:
|
||||
* - https://github.com/Cobalt-Strike/bof_template
|
||||
*
|
||||
* Cobalt Strike 4.x
|
||||
* ChangeLog:
|
||||
* 1/25/2022: updated for 4.5
|
||||
* 7/18/2023: Added BeaconInformation API for 4.9
|
||||
* 7/31/2023: Added Key/Value store APIs for 4.9
|
||||
* BeaconAddValue, BeaconGetValue, and BeaconRemoveValue
|
||||
* 8/31/2023: Added Data store APIs for 4.9
|
||||
* BeaconDataStoreGetItem, BeaconDataStoreProtectItem,
|
||||
* BeaconDataStoreUnprotectItem, and BeaconDataStoreMaxEntries
|
||||
* 9/01/2023: Added BeaconGetCustomUserData API for 4.9
|
||||
* 3/21/2024: Updated BeaconInformation API for 4.10 to return a BOOL
|
||||
* Updated the BEACON_INFO data structure to add new parameters
|
||||
* 4/19/2024: Added BeaconGetSyscallInformation API for 4.10
|
||||
* 4/25/2024: Added APIs to call Beacon's system call implementation
|
||||
*/
|
||||
#ifndef _BEACON_H_
|
||||
#define _BEACON_H_
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif // __cplusplus
|
||||
|
||||
/* data API */
|
||||
typedef struct {
|
||||
char * original; /* the original buffer [so we can free it] */
|
||||
char * buffer; /* current pointer into our buffer */
|
||||
int length; /* remaining length of data */
|
||||
int size; /* total size of this buffer */
|
||||
} datap;
|
||||
|
||||
DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size);
|
||||
DECLSPEC_IMPORT char * BeaconDataPtr(datap * parser, int size);
|
||||
DECLSPEC_IMPORT int BeaconDataInt(datap * parser);
|
||||
DECLSPEC_IMPORT short BeaconDataShort(datap * parser);
|
||||
DECLSPEC_IMPORT int BeaconDataLength(datap * parser);
|
||||
DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size);
|
||||
|
||||
/* format API */
|
||||
typedef struct {
|
||||
char * original; /* the original buffer [so we can free it] */
|
||||
char * buffer; /* current pointer into our buffer */
|
||||
int length; /* remaining length of data */
|
||||
int size; /* total size of this buffer */
|
||||
} formatp;
|
||||
|
||||
DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz);
|
||||
DECLSPEC_IMPORT void BeaconFormatReset(formatp * format);
|
||||
DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, const char * text, int len);
|
||||
DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, const char * fmt, ...);
|
||||
DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size);
|
||||
DECLSPEC_IMPORT void BeaconFormatFree(formatp * format);
|
||||
DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value);
|
||||
|
||||
/* Output Functions */
|
||||
#define CALLBACK_OUTPUT 0x0
|
||||
#define CALLBACK_OUTPUT_OEM 0x1e
|
||||
#define CALLBACK_OUTPUT_UTF8 0x20
|
||||
#define CALLBACK_ERROR 0x0d
|
||||
#define CALLBACK_CUSTOM 0x1000
|
||||
#define CALLBACK_CUSTOM_LAST 0x13ff
|
||||
|
||||
|
||||
DECLSPEC_IMPORT void BeaconOutput(int type, const char * data, int len);
|
||||
DECLSPEC_IMPORT void BeaconPrintf(int type, const char * fmt, ...);
|
||||
|
||||
|
||||
/* Token Functions */
|
||||
DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token);
|
||||
DECLSPEC_IMPORT void BeaconRevertToken();
|
||||
DECLSPEC_IMPORT BOOL BeaconIsAdmin();
|
||||
|
||||
/* Spawn+Inject Functions */
|
||||
DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length);
|
||||
DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len);
|
||||
DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len);
|
||||
DECLSPEC_IMPORT BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * si, PROCESS_INFORMATION * pInfo);
|
||||
DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo);
|
||||
|
||||
/* Utility Functions */
|
||||
DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max);
|
||||
|
||||
/* Beacon Information */
|
||||
/*
|
||||
* ptr - pointer to the base address of the allocated memory.
|
||||
* size - the number of bytes allocated for the ptr.
|
||||
*/
|
||||
typedef struct {
|
||||
char * ptr;
|
||||
size_t size;
|
||||
} HEAP_RECORD;
|
||||
#define MASK_SIZE 13
|
||||
|
||||
/* Information the user can set in the USER_DATA via a UDRL */
|
||||
typedef enum {
|
||||
PURPOSE_EMPTY,
|
||||
PURPOSE_GENERIC_BUFFER,
|
||||
PURPOSE_BEACON_MEMORY,
|
||||
PURPOSE_SLEEPMASK_MEMORY,
|
||||
PURPOSE_BOF_MEMORY,
|
||||
PURPOSE_USER_DEFINED_MEMORY = 1000
|
||||
} ALLOCATED_MEMORY_PURPOSE;
|
||||
|
||||
typedef enum {
|
||||
LABEL_EMPTY,
|
||||
LABEL_BUFFER,
|
||||
LABEL_PEHEADER,
|
||||
LABEL_TEXT,
|
||||
LABEL_RDATA,
|
||||
LABEL_DATA,
|
||||
LABEL_PDATA,
|
||||
LABEL_RELOC,
|
||||
LABEL_USER_DEFINED = 1000
|
||||
} ALLOCATED_MEMORY_LABEL;
|
||||
|
||||
typedef enum {
|
||||
METHOD_UNKNOWN,
|
||||
METHOD_VIRTUALALLOC,
|
||||
METHOD_HEAPALLOC,
|
||||
METHOD_MODULESTOMP,
|
||||
METHOD_NTMAPVIEW,
|
||||
METHOD_USER_DEFINED = 1000,
|
||||
} ALLOCATED_MEMORY_ALLOCATION_METHOD;
|
||||
|
||||
/**
|
||||
* This structure allows the user to provide additional information
|
||||
* about the allocated heap for cleanup. It is mandatory to provide
|
||||
* the HeapHandle but the DestroyHeap Boolean can be used to indicate
|
||||
* whether the clean up code should destroy the heap or simply free the pages.
|
||||
* This is useful in situations where a loader allocates memory in the
|
||||
* processes current heap.
|
||||
*/
|
||||
typedef struct _HEAPALLOC_INFO {
|
||||
PVOID HeapHandle;
|
||||
BOOL DestroyHeap;
|
||||
} HEAPALLOC_INFO, *PHEAPALLOC_INFO;
|
||||
|
||||
typedef struct _MODULESTOMP_INFO {
|
||||
HMODULE ModuleHandle;
|
||||
} MODULESTOMP_INFO, *PMODULESTOMP_INFO;
|
||||
|
||||
typedef union _ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION {
|
||||
HEAPALLOC_INFO HeapAllocInfo;
|
||||
MODULESTOMP_INFO ModuleStompInfo;
|
||||
PVOID Custom;
|
||||
} ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION, *PALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION;
|
||||
|
||||
typedef struct _ALLOCATED_MEMORY_CLEANUP_INFORMATION {
|
||||
BOOL Cleanup;
|
||||
ALLOCATED_MEMORY_ALLOCATION_METHOD AllocationMethod;
|
||||
ALLOCATED_MEMORY_ADDITIONAL_CLEANUP_INFORMATION AdditionalCleanupInformation;
|
||||
} ALLOCATED_MEMORY_CLEANUP_INFORMATION, *PALLOCATED_MEMORY_CLEANUP_INFORMATION;
|
||||
|
||||
typedef struct _ALLOCATED_MEMORY_SECTION {
|
||||
ALLOCATED_MEMORY_LABEL Label; // A label to simplify Sleepmask development
|
||||
PVOID BaseAddress; // Pointer to virtual address of section
|
||||
SIZE_T VirtualSize; // Virtual size of the section
|
||||
DWORD CurrentProtect; // Current memory protection of the section
|
||||
DWORD PreviousProtect; // The previous memory protection of the section (prior to masking/unmasking)
|
||||
BOOL MaskSection; // A boolean to indicate whether the section should be masked
|
||||
} ALLOCATED_MEMORY_SECTION, *PALLOCATED_MEMORY_SECTION;
|
||||
|
||||
typedef struct _ALLOCATED_MEMORY_REGION {
|
||||
ALLOCATED_MEMORY_PURPOSE Purpose; // A label to indicate the purpose of the allocated memory
|
||||
PVOID AllocationBase; // The base address of the allocated memory block
|
||||
SIZE_T RegionSize; // The size of the allocated memory block
|
||||
DWORD Type; // The type of memory allocated
|
||||
ALLOCATED_MEMORY_SECTION Sections[8]; // An array of section information structures
|
||||
ALLOCATED_MEMORY_CLEANUP_INFORMATION CleanupInformation; // Information required to cleanup the allocation
|
||||
} ALLOCATED_MEMORY_REGION, *PALLOCATED_MEMORY_REGION;
|
||||
|
||||
typedef struct {
|
||||
ALLOCATED_MEMORY_REGION AllocatedMemoryRegions[6];
|
||||
} ALLOCATED_MEMORY, *PALLOCATED_MEMORY;
|
||||
|
||||
/*
|
||||
* version - The version of the beacon dll was added for release 4.10
|
||||
* version format: 0xMMmmPP, where MM = Major, mm = Minor, and PP = Patch
|
||||
* e.g. 0x040900 -> CS 4.9
|
||||
* 0x041000 -> CS 4.10
|
||||
*
|
||||
* sleep_mask_ptr - pointer to the sleep mask base address
|
||||
* sleep_mask_text_size - the sleep mask text section size
|
||||
* sleep_mask_total_size - the sleep mask total memory size
|
||||
*
|
||||
* beacon_ptr - pointer to beacon's base address
|
||||
* The stage.obfuscate flag affects this value when using CS default loader.
|
||||
* true: beacon_ptr = allocated_buffer - 0x1000 (Not a valid address)
|
||||
* false: beacon_ptr = allocated_buffer (A valid address)
|
||||
* For a UDRL the beacon_ptr will be set to the 1st argument to DllMain
|
||||
* when the 2nd argument is set to DLL_PROCESS_ATTACH.
|
||||
* heap_records - list of memory addresses on the heap beacon wants to mask.
|
||||
* The list is terminated by the HEAP_RECORD.ptr set to NULL.
|
||||
* mask - the mask that beacon randomly generated to apply
|
||||
*
|
||||
* Added in version 4.10
|
||||
* allocatedMemory - An ALLOCATED_MEMORY structure that can be set in the USER_DATA
|
||||
* via a UDRL.
|
||||
*/
|
||||
typedef struct {
|
||||
unsigned int version;
|
||||
char * sleep_mask_ptr;
|
||||
DWORD sleep_mask_text_size;
|
||||
DWORD sleep_mask_total_size;
|
||||
|
||||
char * beacon_ptr;
|
||||
HEAP_RECORD * heap_records;
|
||||
char mask[MASK_SIZE];
|
||||
|
||||
ALLOCATED_MEMORY allocatedMemory;
|
||||
} BEACON_INFO, *PBEACON_INFO;
|
||||
|
||||
DECLSPEC_IMPORT BOOL BeaconInformation(PBEACON_INFO info);
|
||||
|
||||
/* Key/Value store functions
|
||||
* These functions are used to associate a key to a memory address and save
|
||||
* that information into beacon. These memory addresses can then be
|
||||
* retrieved in a subsequent execution of a BOF.
|
||||
*
|
||||
* key - the key will be converted to a hash which is used to locate the
|
||||
* memory address.
|
||||
*
|
||||
* ptr - a memory address to save.
|
||||
*
|
||||
* Considerations:
|
||||
* - The contents at the memory address is not masked by beacon.
|
||||
* - The contents at the memory address is not released by beacon.
|
||||
*
|
||||
*/
|
||||
DECLSPEC_IMPORT BOOL BeaconAddValue(const char * key, void * ptr);
|
||||
DECLSPEC_IMPORT void * BeaconGetValue(const char * key);
|
||||
DECLSPEC_IMPORT BOOL BeaconRemoveValue(const char * key);
|
||||
|
||||
/* Beacon Data Store functions
|
||||
* These functions are used to access items in Beacon's Data Store.
|
||||
* BeaconDataStoreGetItem returns NULL if the index does not exist.
|
||||
*
|
||||
* The contents are masked by default, and BOFs must unprotect the entry
|
||||
* before accessing the data buffer. BOFs must also protect the entry
|
||||
* after the data is not used anymore.
|
||||
*
|
||||
*/
|
||||
|
||||
#define DATA_STORE_TYPE_EMPTY 0
|
||||
#define DATA_STORE_TYPE_GENERAL_FILE 1
|
||||
|
||||
typedef struct {
|
||||
int type;
|
||||
DWORD64 hash;
|
||||
BOOL masked;
|
||||
char* buffer;
|
||||
size_t length;
|
||||
} DATA_STORE_OBJECT, *PDATA_STORE_OBJECT;
|
||||
|
||||
DECLSPEC_IMPORT PDATA_STORE_OBJECT BeaconDataStoreGetItem(size_t index);
|
||||
DECLSPEC_IMPORT void BeaconDataStoreProtectItem(size_t index);
|
||||
DECLSPEC_IMPORT void BeaconDataStoreUnprotectItem(size_t index);
|
||||
DECLSPEC_IMPORT size_t BeaconDataStoreMaxEntries();
|
||||
|
||||
/* Beacon User Data functions */
|
||||
DECLSPEC_IMPORT char * BeaconGetCustomUserData();
|
||||
|
||||
/* Beacon System call */
|
||||
/* Syscalls API */
|
||||
typedef struct
|
||||
{
|
||||
PVOID fnAddr;
|
||||
PVOID jmpAddr;
|
||||
DWORD sysnum;
|
||||
} SYSCALL_API_ENTRY, *PSYSCALL_API_ENTRY;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SYSCALL_API_ENTRY ntAllocateVirtualMemory;
|
||||
SYSCALL_API_ENTRY ntProtectVirtualMemory;
|
||||
SYSCALL_API_ENTRY ntFreeVirtualMemory;
|
||||
SYSCALL_API_ENTRY ntGetContextThread;
|
||||
SYSCALL_API_ENTRY ntSetContextThread;
|
||||
SYSCALL_API_ENTRY ntResumeThread;
|
||||
SYSCALL_API_ENTRY ntCreateThreadEx;
|
||||
SYSCALL_API_ENTRY ntOpenProcess;
|
||||
SYSCALL_API_ENTRY ntOpenThread;
|
||||
SYSCALL_API_ENTRY ntClose;
|
||||
SYSCALL_API_ENTRY ntCreateSection;
|
||||
SYSCALL_API_ENTRY ntMapViewOfSection;
|
||||
SYSCALL_API_ENTRY ntUnmapViewOfSection;
|
||||
SYSCALL_API_ENTRY ntQueryVirtualMemory;
|
||||
SYSCALL_API_ENTRY ntDuplicateObject;
|
||||
SYSCALL_API_ENTRY ntReadVirtualMemory;
|
||||
SYSCALL_API_ENTRY ntWriteVirtualMemory;
|
||||
SYSCALL_API_ENTRY ntReadFile;
|
||||
SYSCALL_API_ENTRY ntWriteFile;
|
||||
SYSCALL_API_ENTRY ntCreateFile;
|
||||
} SYSCALL_API, *PSYSCALL_API;
|
||||
|
||||
/* Additional Run Time Library (RTL) addresses used to support system calls.
|
||||
* If they are not set then system calls that require them will fall back
|
||||
* to the Standard Windows API.
|
||||
*
|
||||
* Required to support the following system calls:
|
||||
* ntCreateFile
|
||||
*/
|
||||
typedef struct
|
||||
{
|
||||
PVOID rtlDosPathNameToNtPathNameUWithStatusAddr;
|
||||
PVOID rtlFreeHeapAddr;
|
||||
PVOID rtlGetProcessHeapAddr;
|
||||
} RTL_API, *PRTL_API;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
PSYSCALL_API syscalls;
|
||||
PRTL_API rtls;
|
||||
} BEACON_SYSCALLS, *PBEACON_SYSCALLS;
|
||||
|
||||
DECLSPEC_IMPORT BOOL BeaconGetSyscallInformation(PBEACON_SYSCALLS info, BOOL resolveIfNotInitialized);
|
||||
|
||||
/* Beacon System call functions which will use the current system call method */
|
||||
DECLSPEC_IMPORT LPVOID BeaconVirtualAlloc(LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
|
||||
DECLSPEC_IMPORT LPVOID BeaconVirtualAllocEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
|
||||
DECLSPEC_IMPORT BOOL BeaconVirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
|
||||
DECLSPEC_IMPORT BOOL BeaconVirtualProtectEx(HANDLE processHandle, LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpflOldProtect);
|
||||
DECLSPEC_IMPORT BOOL BeaconVirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
|
||||
DECLSPEC_IMPORT BOOL BeaconGetThreadContext(HANDLE threadHandle, PCONTEXT threadContext);
|
||||
DECLSPEC_IMPORT BOOL BeaconSetThreadContext(HANDLE threadHandle, PCONTEXT threadContext);
|
||||
DECLSPEC_IMPORT DWORD BeaconResumeThread(HANDLE threadHandle);
|
||||
DECLSPEC_IMPORT HANDLE BeaconOpenProcess(DWORD desiredAccess, BOOL inheritHandle, DWORD processId);
|
||||
DECLSPEC_IMPORT HANDLE BeaconOpenThread(DWORD desiredAccess, BOOL inheritHandle, DWORD threadId);
|
||||
DECLSPEC_IMPORT BOOL BeaconCloseHandle(HANDLE object);
|
||||
DECLSPEC_IMPORT BOOL BeaconUnmapViewOfFile(LPCVOID baseAddress);
|
||||
DECLSPEC_IMPORT SIZE_T BeaconVirtualQuery(LPCVOID address, PMEMORY_BASIC_INFORMATION buffer, SIZE_T length);
|
||||
DECLSPEC_IMPORT BOOL BeaconDuplicateHandle(HANDLE hSourceProcessHandle, HANDLE hSourceHandle, HANDLE hTargetProcessHandle, LPHANDLE lpTargetHandle, DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwOptions);
|
||||
DECLSPEC_IMPORT BOOL BeaconReadProcessMemory(HANDLE hProcess, LPCVOID lpBaseAddress, LPVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesRead);
|
||||
DECLSPEC_IMPORT BOOL BeaconWriteProcessMemory(HANDLE hProcess, LPVOID lpBaseAddress, LPCVOID lpBuffer, SIZE_T nSize, SIZE_T *lpNumberOfBytesWritten);
|
||||
|
||||
/* Beacon Gate APIs */
|
||||
DECLSPEC_IMPORT VOID BeaconDisableBeaconGate();
|
||||
DECLSPEC_IMPORT VOID BeaconEnableBeaconGate();
|
||||
|
||||
/* Beacon User Data
|
||||
*
|
||||
* version format: 0xMMmmPP, where MM = Major, mm = Minor, and PP = Patch
|
||||
* e.g. 0x040900 -> CS 4.9
|
||||
* 0x041000 -> CS 4.10
|
||||
*/
|
||||
|
||||
#define DLL_BEACON_USER_DATA 0x0d
|
||||
#define BEACON_USER_DATA_CUSTOM_SIZE 32
|
||||
typedef struct
|
||||
{
|
||||
unsigned int version;
|
||||
PSYSCALL_API syscalls;
|
||||
char custom[BEACON_USER_DATA_CUSTOM_SIZE];
|
||||
PRTL_API rtls;
|
||||
PALLOCATED_MEMORY allocatedMemory;
|
||||
} USER_DATA, * PUSER_DATA;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif // __cplusplus
|
||||
#endif // _BEACON_H_
|
0
sample_bof/main.c
Normal file
0
sample_bof/main.c
Normal file
14499
sample_bof/syscalls-aio.h
Normal file
14499
sample_bof/syscalls-aio.h
Normal file
File diff suppressed because it is too large
Load Diff
306
syscalls.c.template
Normal file
306
syscalls.c.template
Normal file
@ -0,0 +1,306 @@
|
||||
#include "syscalls.h"
|
||||
|
||||
//#define DEBUG
|
||||
|
||||
#define JUMPER
|
||||
|
||||
#ifdef _M_IX86
|
||||
|
||||
EXTERN_C PVOID internal_cleancall_wow64_gate(VOID) {
|
||||
return (PVOID)__readfsdword(0xC0);
|
||||
}
|
||||
|
||||
__declspec(naked) BOOL local_is_wow64(void)
|
||||
{
|
||||
__asm {
|
||||
mov eax, fs:[0xc0]
|
||||
test eax, eax
|
||||
jne wow64
|
||||
mov eax, 0
|
||||
ret
|
||||
wow64:
|
||||
mov eax, 1
|
||||
ret
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#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
|
||||
|
||||
SW3_SYSCALL_LIST SW3_SyscallList;
|
||||
|
||||
// SEARCH_AND_REPLACE
|
||||
#ifdef SEARCH_AND_REPLACE
|
||||
// THIS IS NOT DEFINED HERE; don't know if I'll add it in a future release
|
||||
EXTERN void SearchAndReplace(unsigned char[], unsigned char[]);
|
||||
#endif
|
||||
|
||||
DWORD SW3_HashSyscall(PCSTR FunctionName)
|
||||
{
|
||||
DWORD i = 0;
|
||||
DWORD Hash = SW3_SEED;
|
||||
|
||||
while (FunctionName[i])
|
||||
{
|
||||
WORD PartialName = *(WORD*)((ULONG_PTR)FunctionName + i++);
|
||||
Hash ^= PartialName + SW3_ROR8(Hash);
|
||||
}
|
||||
|
||||
return Hash;
|
||||
}
|
||||
|
||||
#ifndef JUMPER
|
||||
PVOID SC_Address(PVOID NtApiAddress)
|
||||
{
|
||||
return NULL;
|
||||
}
|
||||
#else
|
||||
PVOID SC_Address(PVOID NtApiAddress)
|
||||
{
|
||||
DWORD searchLimit = 512;
|
||||
PVOID SyscallAddress;
|
||||
|
||||
#ifdef _WIN64
|
||||
// If the process is 64-bit on a 64-bit OS, we need to search for syscall
|
||||
BYTE syscall_code[] = { 0x0f, 0x05, 0xc3 };
|
||||
ULONG distance_to_syscall = 0x12;
|
||||
#else
|
||||
// If the process is 32-bit on a 32-bit OS, we need to search for sysenter
|
||||
BYTE syscall_code[] = { 0x0f, 0x34, 0xc3 };
|
||||
ULONG distance_to_syscall = 0x0f;
|
||||
#endif
|
||||
|
||||
#ifdef _M_IX86
|
||||
// If the process is 32-bit on a 64-bit OS, we need to jump to WOW32Reserved
|
||||
if (local_is_wow64())
|
||||
{
|
||||
#ifdef DEBUG
|
||||
// printf("[+] Running 32-bit app on x64 (WOW64)\n");
|
||||
#endif
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
// we don't really care if there is a 'jmp' between
|
||||
// NtApiAddress and the 'syscall; ret' instructions
|
||||
SyscallAddress = SW3_RVA2VA(PVOID, NtApiAddress, distance_to_syscall);
|
||||
|
||||
int match = 1; // Assume a match initially
|
||||
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
||||
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
||||
match = 0; // Set match to 0 if any byte doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// we can use the original code for this system call :)
|
||||
#if defined(DEBUG)
|
||||
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||
#endif
|
||||
return SyscallAddress;
|
||||
}
|
||||
|
||||
// the 'syscall; ret' intructions have not been found,
|
||||
// we will try to use one near it, similarly to HalosGate
|
||||
|
||||
for (ULONG32 num_jumps = 1; num_jumps < searchLimit; num_jumps++)
|
||||
{
|
||||
// let's try with an Nt* API below our syscall
|
||||
SyscallAddress = SW3_RVA2VA(
|
||||
PVOID,
|
||||
NtApiAddress,
|
||||
distance_to_syscall + num_jumps * 0x20);
|
||||
match = 1; // Assume a match initially
|
||||
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
||||
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
||||
match = 0; // Set match to 0 if any byte doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// we can use the original code for this system call :)
|
||||
#if defined(DEBUG)
|
||||
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||
#endif
|
||||
return SyscallAddress;
|
||||
}
|
||||
|
||||
// let's try with an Nt* API above our syscall
|
||||
SyscallAddress = SW3_RVA2VA(
|
||||
PVOID,
|
||||
NtApiAddress,
|
||||
distance_to_syscall - num_jumps * 0x20);
|
||||
|
||||
match = 1; // Assume a match initially
|
||||
for (size_t i = 0; i < sizeof(syscall_code); i++) {
|
||||
if (((BYTE*)syscall_code)[i] != ((BYTE*)SyscallAddress)[i]) {
|
||||
match = 0; // Set match to 0 if any byte doesn't match
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (match) {
|
||||
// we can use the original code for this system call :)
|
||||
#if defined(DEBUG)
|
||||
// printf("Found Syscall Opcodes at address 0x%p\n", SyscallAddress);
|
||||
#endif
|
||||
return SyscallAddress;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG
|
||||
// printf("Syscall Opcodes not found!\n");
|
||||
#endif
|
||||
|
||||
return NULL;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
BOOL SW3_PopulateSyscallList()
|
||||
{
|
||||
// Return early if the list is already populated.
|
||||
if (SW3_SyscallList.Count) return TRUE;
|
||||
|
||||
#ifdef _WIN64
|
||||
PSW3_PEB Peb = (PSW3_PEB)__readgsqword(0x60);
|
||||
#else
|
||||
PSW3_PEB Peb = (PSW3_PEB)__readfsdword(0x30);
|
||||
#endif
|
||||
PSW3_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.
|
||||
PSW3_LDR_DATA_TABLE_ENTRY LdrEntry;
|
||||
for (LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)Ldr->Reserved2[1]; LdrEntry->DllBase != NULL; LdrEntry = (PSW3_LDR_DATA_TABLE_ENTRY)LdrEntry->Reserved1[0])
|
||||
{
|
||||
DllBase = LdrEntry->DllBase;
|
||||
PIMAGE_DOS_HEADER DosHeader = (PIMAGE_DOS_HEADER)DllBase;
|
||||
PIMAGE_NT_HEADERS NtHeaders = SW3_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)SW3_RVA2VA(ULONG_PTR, DllBase, VirtualAddress);
|
||||
|
||||
// If this is NTDLL.dll, exit loop.
|
||||
PCHAR DllName = SW3_RVA2VA(PCHAR, DllBase, ExportDirectory->Name);
|
||||
|
||||
if ((*(ULONG*)DllName | 0x20202020) != 0x6c64746e) continue;
|
||||
if ((*(ULONG*)(DllName + 4) | 0x20202020) == 0x6c642e6c) break;
|
||||
}
|
||||
|
||||
if (!ExportDirectory) return FALSE;
|
||||
|
||||
DWORD NumberOfNames = ExportDirectory->NumberOfNames;
|
||||
PDWORD Functions = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfFunctions);
|
||||
PDWORD Names = SW3_RVA2VA(PDWORD, DllBase, ExportDirectory->AddressOfNames);
|
||||
PWORD Ordinals = SW3_RVA2VA(PWORD, DllBase, ExportDirectory->AddressOfNameOrdinals);
|
||||
|
||||
// Populate SW3_SyscallList with unsorted Zw* entries.
|
||||
DWORD i = 0;
|
||||
PSW3_SYSCALL_ENTRY Entries = SW3_SyscallList.Entries;
|
||||
do
|
||||
{
|
||||
PCHAR FunctionName = SW3_RVA2VA(PCHAR, DllBase, Names[NumberOfNames - 1]);
|
||||
|
||||
// Is this a system call?
|
||||
if (*(USHORT*)FunctionName == 0x775a)
|
||||
{
|
||||
Entries[i].Hash = SW3_HashSyscall(FunctionName);
|
||||
Entries[i].Address = Functions[Ordinals[NumberOfNames - 1]];
|
||||
Entries[i].SyscallAddress = SC_Address(SW3_RVA2VA(PVOID, DllBase, Entries[i].Address));
|
||||
|
||||
i++;
|
||||
if (i == SW3_MAX_ENTRIES) break;
|
||||
}
|
||||
} while (--NumberOfNames);
|
||||
|
||||
// Save total number of system calls found.
|
||||
SW3_SyscallList.Count = i;
|
||||
|
||||
// Sort the list by address in ascending order.
|
||||
for (DWORD i = 0; i < SW3_SyscallList.Count - 1; i++)
|
||||
{
|
||||
for (DWORD j = 0; j < SW3_SyscallList.Count - i - 1; j++)
|
||||
{
|
||||
if (Entries[j].Address > Entries[j + 1].Address)
|
||||
{
|
||||
// Swap entries.
|
||||
SW3_SYSCALL_ENTRY TempEntry;
|
||||
|
||||
TempEntry.Hash = Entries[j].Hash;
|
||||
TempEntry.Address = Entries[j].Address;
|
||||
TempEntry.SyscallAddress = Entries[j].SyscallAddress;
|
||||
|
||||
Entries[j].Hash = Entries[j + 1].Hash;
|
||||
Entries[j].Address = Entries[j + 1].Address;
|
||||
Entries[j].SyscallAddress = Entries[j + 1].SyscallAddress;
|
||||
|
||||
Entries[j + 1].Hash = TempEntry.Hash;
|
||||
Entries[j + 1].Address = TempEntry.Address;
|
||||
Entries[j + 1].SyscallAddress = TempEntry.SyscallAddress;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
EXTERN_C DWORD SW3_GetSyscallNumber(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW3_SyscallList is populated.
|
||||
if (!SW3_PopulateSyscallList()) return -1;
|
||||
|
||||
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||
{
|
||||
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
EXTERN_C PVOID SW3_GetSyscallAddress(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW3_SyscallList is populated.
|
||||
if (!SW3_PopulateSyscallList()) return NULL;
|
||||
|
||||
for (DWORD i = 0; i < SW3_SyscallList.Count; i++)
|
||||
{
|
||||
if (FunctionHash == SW3_SyscallList.Entries[i].Hash)
|
||||
{
|
||||
return SW3_SyscallList.Entries[i].SyscallAddress;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
DWORD LCG() {
|
||||
static DWORD seed = 123987654;
|
||||
return (seed >> 16) & 0x7FFF; // return a pseudo-random number
|
||||
}
|
||||
|
||||
EXTERN_C PVOID SW3_GetRandomSyscallAddress(DWORD FunctionHash)
|
||||
{
|
||||
// Ensure SW3_SyscallList is populated.
|
||||
if (!SW3_PopulateSyscallList()) return NULL;
|
||||
|
||||
DWORD index = LCG() % SW3_SyscallList.Count;
|
||||
|
||||
while (FunctionHash == SW3_SyscallList.Entries[index].Hash){
|
||||
// Spoofing the syscall return address
|
||||
index = LCG() % SW3_SyscallList.Count;
|
||||
}
|
||||
return SW3_SyscallList.Entries[index].SyscallAddress;
|
||||
}
|
4056
syscalls.h.template
Normal file
4056
syscalls.h.template
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user