direct syscalls independent of the underlying bit width
This commit is contained in:
516
get_syscall64_ids.cpp
Normal file
516
get_syscall64_ids.cpp
Normal file
@@ -0,0 +1,516 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <ntdll.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
#include "structs.h"
|
||||||
|
#include "wow64ext.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "syscall64.h"
|
||||||
|
#include "get_syscall64_ids.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
\file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief All functions doing direct syscalls are saved with a hash of their name
|
||||||
|
and the accompying syscall ID in the table (API_TO_INDEX*) ID_table.
|
||||||
|
To get the ID for a given hash the table can be searched.
|
||||||
|
(To speed up the search the table is sorted and a binary search is used)
|
||||||
|
*/
|
||||||
|
struct API_TO_INDEX
|
||||||
|
{
|
||||||
|
DWORD hash;
|
||||||
|
DWORD id;
|
||||||
|
} *ID_table = NULL;
|
||||||
|
DWORD ID_table_count = 0; //!< Count of entries in ID_table
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Parses the ID out of a x64 function.
|
||||||
|
\return Returns the ID or INVALID_SYSCALL_ID on err
|
||||||
|
*/
|
||||||
|
static DWORD get_syscall_id(LPBYTE function)
|
||||||
|
{
|
||||||
|
if(!function)
|
||||||
|
return INVALID_SYSCALL_ID;
|
||||||
|
|
||||||
|
/*
|
||||||
|
00000000`77b61800 4c8bd1 mov r10,rcx
|
||||||
|
00000000`77b61803 b852000000 mov eax,52h
|
||||||
|
00000000`77b61808 0f05 syscall
|
||||||
|
|
||||||
|
On Windows 10 it's:
|
||||||
|
0:000> u NtOpenFile
|
||||||
|
ntdll!NtOpenFile:
|
||||||
|
00007ffe`62bf6720 4c8bd1 mov r10,rcx
|
||||||
|
00007ffe`62bf6723 b833000000 mov eax,33h
|
||||||
|
00007ffe`62bf6728 f604250803fe7f01 test byte ptr [SharedUserData+0x308 (00000000`7ffe0308)],1
|
||||||
|
00007ffe`62bf6730 7503 jne ntdll!NtOpenFile+0x15 (00007ffe`62bf6735)
|
||||||
|
00007ffe`62bf6732 0f05 syscall
|
||||||
|
00007ffe`62bf6734 c3 ret
|
||||||
|
00007ffe`62bf6735 cd2e int 2Eh
|
||||||
|
00007ffe`62bf6737 c3 ret
|
||||||
|
*/
|
||||||
|
|
||||||
|
const unsigned char MOV_R10_RCX_OPCODE[] = {0x4c, 0x8b, 0xd1};
|
||||||
|
if(memcmp(function, MOV_R10_RCX_OPCODE, sizeof(MOV_R10_RCX_OPCODE)))
|
||||||
|
return INVALID_SYSCALL_ID;
|
||||||
|
|
||||||
|
const unsigned char SYSCALL_OPCODE[] = {0x0f, 0x05};
|
||||||
|
const size_t SYSCALL_OFFSET_OLD = 8,
|
||||||
|
SYSCALL_OFFSET_10 = 0x12;
|
||||||
|
uint8_t* old = function + SYSCALL_OFFSET_OLD;
|
||||||
|
uint8_t* win10 = function + SYSCALL_OFFSET_10;
|
||||||
|
if (memcmp(old, SYSCALL_OPCODE, sizeof(SYSCALL_OPCODE)) &&
|
||||||
|
memcmp(win10, SYSCALL_OPCODE, sizeof(SYSCALL_OPCODE)))
|
||||||
|
{
|
||||||
|
return INVALID_SYSCALL_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
const unsigned char MOV_EAX_OPCODE = 0xB8;
|
||||||
|
const size_t MOV_EAX_ID_OFFSET = 3;
|
||||||
|
if(MOV_EAX_OPCODE != *(function + MOV_EAX_ID_OFFSET))
|
||||||
|
return INVALID_SYSCALL_ID;
|
||||||
|
|
||||||
|
return *(PDWORD)(function + MOV_EAX_ID_OFFSET + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Parses the given image to get the ID or just count them.
|
||||||
|
\return Number of entries found / populated
|
||||||
|
\param imageBase Image base of the DLL the info will be taken from
|
||||||
|
\param ids The struct the info is put into. If NULL - won't be touched
|
||||||
|
*/
|
||||||
|
static DWORD get_syscall_ids(LPVOID imageBase, API_TO_INDEX* ids)
|
||||||
|
{
|
||||||
|
if(!imageBase)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
|
||||||
|
// not a valid DOS header
|
||||||
|
if(IMAGE_DOS_SIGNATURE != dos->e_magic)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)((LPBYTE)imageBase + dos->e_lfanew);
|
||||||
|
// not a valid PE or not the correct architecture
|
||||||
|
if(IMAGE_NT_SIGNATURE != nt->Signature || IMAGE_FILE_MACHINE_AMD64 != nt->FileHeader.Machine)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No exports?
|
||||||
|
if(!nt->OptionalHeader.DataDirectory->Size)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
DWORD count = 0; //! Number of functions who do direct syscalls
|
||||||
|
IMAGE_EXPORT_DIRECTORY *exportDir = (IMAGE_EXPORT_DIRECTORY *)((LPBYTE)imageBase + nt->OptionalHeader.DataDirectory->VirtualAddress);
|
||||||
|
PDWORD nameRef = (DWORD *)((LPBYTE)imageBase + exportDir->AddressOfNames);
|
||||||
|
WORD* ordinal = (WORD *)((LPBYTE)imageBase + exportDir->AddressOfNameOrdinals);
|
||||||
|
DWORD* addressOfFunctions = (DWORD*)((LPBYTE)imageBase + exportDir->AddressOfFunctions);
|
||||||
|
|
||||||
|
printf("Total functions: %d\n", exportDir->NumberOfNames);
|
||||||
|
for(DWORD i = 0; i < exportDir->NumberOfNames; i++, nameRef++)
|
||||||
|
{
|
||||||
|
const char* name = (const char*)((LPBYTE)imageBase + (*nameRef));
|
||||||
|
LPBYTE address = (LPBYTE)imageBase + addressOfFunctions[ordinal[i]];
|
||||||
|
|
||||||
|
DWORD id = get_syscall_id(address);
|
||||||
|
if(INVALID_SYSCALL_ID == id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Put it into the table
|
||||||
|
if(ids)
|
||||||
|
{
|
||||||
|
ids[count].hash = hash(name);
|
||||||
|
ids[count].id = id;
|
||||||
|
}
|
||||||
|
// Print info
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("%s (%X) = %X\n", name, hash(name), id);
|
||||||
|
}
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief (Bubble)Sorts the ID_table so it can be searched via binary search
|
||||||
|
*/
|
||||||
|
static void sort_ID_table()
|
||||||
|
{
|
||||||
|
DWORD n = ID_table_count;
|
||||||
|
|
||||||
|
bool swapped = false;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
for(unsigned int i = 0; i < n - 1; ++i)
|
||||||
|
{
|
||||||
|
if(ID_table[i].hash > ID_table[i + 1].hash)
|
||||||
|
{
|
||||||
|
API_TO_INDEX tmp = {ID_table[i].hash, ID_table[i].id};
|
||||||
|
|
||||||
|
ID_table[i].hash = ID_table[i + 1].hash;
|
||||||
|
ID_table[i].id = ID_table[i + 1].id;
|
||||||
|
|
||||||
|
ID_table[i + 1].hash = tmp.hash;
|
||||||
|
ID_table[i + 1].id = tmp.id;
|
||||||
|
|
||||||
|
swapped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
n = n - 1;
|
||||||
|
} while(swapped == true && n);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Gets the path to the x64 ntdll in native format
|
||||||
|
\param path Output buffer
|
||||||
|
*/
|
||||||
|
static void get_ntdll_path(wchar_t* path)
|
||||||
|
{
|
||||||
|
wchar_t systemDirectory[MAX_PATH];
|
||||||
|
|
||||||
|
if(!path)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GetSystemDirectory(systemDirectory, _countof(systemDirectory));
|
||||||
|
|
||||||
|
wsprintfW(path, L"\\??\\%s\\ntdll.dll", systemDirectory);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
static BOOL read_ntdll(LPBYTE& content, DWORD& size)
|
||||||
|
{
|
||||||
|
wchar_t ntdllPath[MAX_PATH]; //! Path in the native format
|
||||||
|
get_ntdll_path(ntdllPath);
|
||||||
|
printf("%ls\n", ntdllPath);
|
||||||
|
|
||||||
|
_UNICODE_STRING_T<DWORD64> filename = {lstrlenW(ntdllPath) * sizeof(WCHAR), MAX_PATH * sizeof(WCHAR), (DWORD64)ntdllPath};
|
||||||
|
_OBJECT_ATTRIBUTES_T<DWORD64> obja = {sizeof(_OBJECT_ATTRIBUTES_T<DWORD64>), NULL, (DWORD64)&filename, OBJ_CASE_INSENSITIVE, NULL, NULL};
|
||||||
|
_IO_STATUS_BLOCK_T<DWORD64> iostatusblock = {0};
|
||||||
|
|
||||||
|
_HANDLE_T<DWORD64> fileHandle = {(DWORD64)INVALID_HANDLE_VALUE};
|
||||||
|
NTSTATUS stat = DO_SYSCALL(get_basic_syscall_ID(NTOPENFILE), &fileHandle,
|
||||||
|
FILE_READ_DATA | SYNCHRONIZE, &obja, &iostatusblock, FILE_SHARE_READ,
|
||||||
|
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT);
|
||||||
|
if(STATUS_SUCCESS != stat)
|
||||||
|
{
|
||||||
|
printf("NTSTATUS: %X\tHandle: %llX\n", stat, fileHandle.h);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
printf("Handle: %llX\n", fileHandle.h);
|
||||||
|
|
||||||
|
if(!(size = GetFileSize((HANDLE)fileHandle.h, NULL)))
|
||||||
|
{
|
||||||
|
DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
printf("Size of ntdll: %dkb\n", size / 1024);
|
||||||
|
|
||||||
|
/* As no code is actually executed in the new ntdll - but only
|
||||||
|
a few bytes are read from it - rw is enough */
|
||||||
|
if(!(content = (LPBYTE)VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
|
||||||
|
PAGE_READWRITE)))
|
||||||
|
{
|
||||||
|
DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
printf("content %p\n", content);
|
||||||
|
|
||||||
|
printf("ios ptr %X", &iostatusblock);
|
||||||
|
LARGE_INTEGER offset = {0};
|
||||||
|
stat = DO_SYSCALL(get_basic_syscall_ID(NTREADFILE),
|
||||||
|
(HANDLE)fileHandle.h,
|
||||||
|
NULL, // event
|
||||||
|
NULL, // ApcRoutine
|
||||||
|
NULL, // ApcContext
|
||||||
|
&iostatusblock,
|
||||||
|
content,
|
||||||
|
size,
|
||||||
|
&offset,
|
||||||
|
NULL); // key
|
||||||
|
if(STATUS_SUCCESS != stat)
|
||||||
|
{
|
||||||
|
VirtualFree(content, 0, MEM_RELEASE);
|
||||||
|
content = NULL;
|
||||||
|
DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
|
||||||
|
printf("Reading failed: %X\t%X != %X\n", stat, size, offset.LowPart);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
printf("ios: %X\toffset: %X\n", iostatusblock.Status, offset.LowPart);
|
||||||
|
|
||||||
|
DO_SYSCALL(get_basic_syscall_ID(NTCLOSE), fileHandle.h);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief (Partly) maps the image.
|
||||||
|
|
||||||
|
This does NOT fix relocations, imports, TLS, sets section protections yadda yadda.
|
||||||
|
As this image is only used to parse out the IDs that is no problem.
|
||||||
|
\param content Raw data of the file to be mapped
|
||||||
|
\param mappedImage The finished image
|
||||||
|
*/
|
||||||
|
static BOOL map_PE(LPBYTE content, LPBYTE* mappedImage)
|
||||||
|
{
|
||||||
|
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)content;
|
||||||
|
if(IMAGE_DOS_SIGNATURE != dos->e_magic)
|
||||||
|
{
|
||||||
|
printf("Not a valid DOS header: %s\n", content);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS64 nt = (PIMAGE_NT_HEADERS64)(content + dos->e_lfanew);
|
||||||
|
if(IMAGE_NT_SIGNATURE != nt->Signature)
|
||||||
|
{
|
||||||
|
printf("Not a valid PE header\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Must be mapped properly (Actually almost -
|
||||||
|
only the code section would be needed) so the IDs can be be
|
||||||
|
parsed properly */
|
||||||
|
if(!(*mappedImage = (LPBYTE)VirtualAlloc(NULL, nt->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE,
|
||||||
|
PAGE_READWRITE)))
|
||||||
|
{
|
||||||
|
printf("Can't alloc\n");
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("Let's map that shit @ %p. %X sections. %X imageSize\n", *mappedImage,
|
||||||
|
nt->FileHeader.NumberOfSections, nt->OptionalHeader.SizeOfImage);
|
||||||
|
|
||||||
|
// Copy up to the first section (Size of optional header + delta optional header to start of image)
|
||||||
|
memcpy(*mappedImage, content, nt->FileHeader.SizeOfOptionalHeader + ((LPBYTE)&nt->OptionalHeader - (LPBYTE)content));
|
||||||
|
|
||||||
|
// Copy sections
|
||||||
|
#define IMAGE_FIRST_SECTION64( ntheader ) ((PIMAGE_SECTION_HEADER) \
|
||||||
|
((ULONG_PTR)(ntheader)+\
|
||||||
|
FIELD_OFFSET(IMAGE_NT_HEADERS64, OptionalHeader) + \
|
||||||
|
((ntheader))->FileHeader.SizeOfOptionalHeader \
|
||||||
|
))
|
||||||
|
PIMAGE_SECTION_HEADER pish = IMAGE_FIRST_SECTION64(nt);
|
||||||
|
for(unsigned int i = 0; i < nt->FileHeader.NumberOfSections; i++)
|
||||||
|
{
|
||||||
|
printf("Copy %Xbytes @ RVA %X From %X\n", pish->SizeOfRawData, pish->VirtualAddress, pish->PointerToRawData);
|
||||||
|
memcpy((*mappedImage + pish->VirtualAddress), (content + pish->PointerToRawData),
|
||||||
|
pish->SizeOfRawData);
|
||||||
|
pish++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Initalize the ID_table.
|
||||||
|
*/
|
||||||
|
BOOL initalize_ID_table()
|
||||||
|
{
|
||||||
|
LPBYTE ntdll = NULL, imageBase = NULL;
|
||||||
|
DWORD ntdllSize = 0;
|
||||||
|
if(!read_ntdll(ntdll, ntdllSize))
|
||||||
|
return FALSE;
|
||||||
|
if(!map_PE(ntdll, &imageBase))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
ID_table_count = get_syscall_ids(imageBase, NULL);
|
||||||
|
printf("%i functions who do direct syscalls\n", ID_table_count);
|
||||||
|
if(!ID_table_count)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if(!(ID_table = (API_TO_INDEX*)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ID_table_count * sizeof(API_TO_INDEX))))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
if(!get_syscall_ids(imageBase, ID_table))
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
VirtualFree(ntdll, 0, MEM_RELEASE);
|
||||||
|
VirtualFree(imageBase, 0, MEM_RELEASE);
|
||||||
|
|
||||||
|
sort_ID_table();
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Has a few hardcoded IDs and returns the correct one for the current OS
|
||||||
|
|
||||||
|
Which IDs exactly are hardcoded can be seen in the enum.
|
||||||
|
Table made mostly with data from:
|
||||||
|
x86: http://j00ru.vexillium.org/ntapi/
|
||||||
|
WOW64: http://j00ru.vexillium.org/ntapi_64/
|
||||||
|
|
||||||
|
\fixme >=Win8 Support?
|
||||||
|
*/
|
||||||
|
DWORD get_basic_syscall_ID(SYSCALL_IDS func)
|
||||||
|
{
|
||||||
|
_KUSER_SHARED_DATA* _kuser_s_d = GET_KUSER_SHARED_DATA();
|
||||||
|
ULONG majorVersion = _kuser_s_d->NtMajorVersion;
|
||||||
|
ULONG minorVersion = _kuser_s_d->NtMinorVersion;
|
||||||
|
NT_PRODUCT_TYPE productType = _kuser_s_d->NtProductType;
|
||||||
|
_PEB* p = (_PEB*)__readfsdword(0x30);
|
||||||
|
ULONG buildID = p->NtBuildNumber;
|
||||||
|
|
||||||
|
switch(majorVersion)
|
||||||
|
{
|
||||||
|
case 5:
|
||||||
|
// XP
|
||||||
|
if(1 == minorVersion ||
|
||||||
|
(2 == minorVersion && VER_NT_WORKSTATION == productType))
|
||||||
|
{
|
||||||
|
if(NTOPENFILE == func)
|
||||||
|
return 0x30;
|
||||||
|
else if(NTCREATEFILE == func)
|
||||||
|
return 0x52;
|
||||||
|
else if(NTREADFILE == func)
|
||||||
|
return 0x03;
|
||||||
|
else if(NTCLOSE == func)
|
||||||
|
return 0x0C;
|
||||||
|
}
|
||||||
|
// Server 2003
|
||||||
|
else
|
||||||
|
{
|
||||||
|
printf("SRV03 unsupported\n"); // fixme
|
||||||
|
if(NTOPENFILE == func)
|
||||||
|
return 0x7a;
|
||||||
|
else if(NTCREATEFILE == func)
|
||||||
|
return 0x26;
|
||||||
|
else if(NTREADFILE == func)
|
||||||
|
return 0xbf;
|
||||||
|
else if(NTCLOSE == func)
|
||||||
|
return 0x1b;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
switch(minorVersion)
|
||||||
|
{
|
||||||
|
// Vista SP0-2 & Server 2008
|
||||||
|
case 0:
|
||||||
|
printf("Vista unsupported\n"); // fixme
|
||||||
|
if(NTOPENFILE == func)
|
||||||
|
return 0xba;
|
||||||
|
else if(NTCREATEFILE == func)
|
||||||
|
return 0x3c;
|
||||||
|
else if(NTREADFILE == func)
|
||||||
|
return 0x102;
|
||||||
|
else if(NTCLOSE == func)
|
||||||
|
return 0x30;
|
||||||
|
break;
|
||||||
|
// Win7
|
||||||
|
case 1:
|
||||||
|
if(NTOPENFILE == func)
|
||||||
|
return 0x30;
|
||||||
|
else if(NTCREATEFILE == func)
|
||||||
|
return 0x52;
|
||||||
|
else if(NTREADFILE == func)
|
||||||
|
return 0x03;
|
||||||
|
else if(NTCLOSE == func)
|
||||||
|
return 0x0C;
|
||||||
|
// Win 8
|
||||||
|
case 2:
|
||||||
|
printf("Win8 unsupported\n"); // fixme
|
||||||
|
if(NTOPENFILE == func)
|
||||||
|
return 0xe7;
|
||||||
|
else if(NTCREATEFILE == func)
|
||||||
|
return 0x15f;
|
||||||
|
else if(NTREADFILE == func)
|
||||||
|
return 0x86;
|
||||||
|
else if(NTCLOSE == func)
|
||||||
|
return 0x0171;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
// Win 10
|
||||||
|
case 10:
|
||||||
|
if (NTOPENFILE == func)
|
||||||
|
return 0x0033;
|
||||||
|
else if (NTCREATEFILE == func)
|
||||||
|
return 0x55;
|
||||||
|
else if (NTREADFILE == func)
|
||||||
|
return 0x06;
|
||||||
|
else if (NTCLOSE == func)
|
||||||
|
return 0x0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return INVALID_SYSCALL_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD get_syscall_ID(DWORD func)
|
||||||
|
{
|
||||||
|
DWORD left = 0, right = ID_table_count;
|
||||||
|
|
||||||
|
while(left != right)
|
||||||
|
{
|
||||||
|
DWORD middle = (left + right) / 2;
|
||||||
|
|
||||||
|
if(func == ID_table[middle].hash)
|
||||||
|
return ID_table[middle].id;
|
||||||
|
if(func > ID_table[middle].hash)
|
||||||
|
left = middle + 1;
|
||||||
|
else
|
||||||
|
right = middle - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(func == ID_table[left].hash)
|
||||||
|
{
|
||||||
|
return ID_table[left].id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return INVALID_SYSCALL_ID;
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID destroy_ID_table()
|
||||||
|
{
|
||||||
|
HeapFree(GetProcessHeap(), 0, ID_table);
|
||||||
|
}
|
||||||
|
|
||||||
|
#if 0
|
||||||
|
BOOL test_id_table()
|
||||||
|
{
|
||||||
|
LPVOID imageBase = GetModuleHandle(TEXT("ntdll.dll"));
|
||||||
|
|
||||||
|
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)imageBase;
|
||||||
|
// not a valid DOS header
|
||||||
|
if(IMAGE_DOS_SIGNATURE != dos->e_magic)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((LPBYTE)imageBase + dos->e_lfanew);
|
||||||
|
// not a valid PE or not the correct architecture
|
||||||
|
if(IMAGE_NT_SIGNATURE != nt->Signature || IMAGE_FILE_MACHINE_I386 != nt->FileHeader.Machine)
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No exports?
|
||||||
|
if(!nt->OptionalHeader.DataDirectory->Size)
|
||||||
|
return FALSE;
|
||||||
|
|
||||||
|
IMAGE_EXPORT_DIRECTORY *exportDir = (IMAGE_EXPORT_DIRECTORY *)((LPBYTE)imageBase + nt->OptionalHeader.DataDirectory->VirtualAddress);
|
||||||
|
PDWORD nameRef = (DWORD *)((LPBYTE)imageBase + exportDir->AddressOfNames);
|
||||||
|
WORD* ordinal = (WORD *)((LPBYTE)imageBase + exportDir->AddressOfNameOrdinals);
|
||||||
|
DWORD* addressOfFunctions = (DWORD*)((LPBYTE)imageBase + exportDir->AddressOfFunctions);
|
||||||
|
|
||||||
|
for(DWORD i = 0; i < exportDir->NumberOfNames; i++, nameRef++)
|
||||||
|
{
|
||||||
|
const char* name = (const char*)((LPBYTE)imageBase + (*nameRef));
|
||||||
|
LPBYTE address = (LPBYTE)imageBase + addressOfFunctions[ordinal[i]];
|
||||||
|
|
||||||
|
DWORD id = get_syscall_id(address);
|
||||||
|
if(INVALID_SYSCALL_ID == id)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// At address is a function doing direct syscalls - check if can be found
|
||||||
|
if(INVALID_SYSCALL_ID == get_syscall_ID(hash(name)))
|
||||||
|
{
|
||||||
|
printf("%s not found", name);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
71
get_syscall64_ids.h
Normal file
71
get_syscall64_ids.h
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
#ifndef GET_SYSCALL64_IDS_H
|
||||||
|
#define GET_SYSCALL64_IDS_H
|
||||||
|
/**
|
||||||
|
\file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Definition of the hashs of APIs and the error value INVALID_SYSCALL_ID
|
||||||
|
*/
|
||||||
|
enum SYSCALL_IDS
|
||||||
|
{
|
||||||
|
// Files
|
||||||
|
NTOPENFILE = 0xC29C5019, //! Supported by get_basic_syscall_ID
|
||||||
|
NTCREATEFILE = 0x15A5ECDB, //! Supported by get_basic_syscall_ID
|
||||||
|
NTREADFILE = 0x2E979AE3, //! Supported by get_basic_syscall_ID
|
||||||
|
NTCLOSE = 0x8B8E133D, //! Supported by get_basic_syscall_ID
|
||||||
|
NTWRITEFILE = 0xD69326B2,
|
||||||
|
|
||||||
|
// Mutexes
|
||||||
|
NTCREATEMUTANT = 0x280632B4,
|
||||||
|
NTOPENMUTANT = 0xEC225D72,
|
||||||
|
NTRELEASEMUTANT = 0x29567961,
|
||||||
|
|
||||||
|
// Registry
|
||||||
|
NTOPENKEY = 0x4BB73E02,
|
||||||
|
NTQUERYVALUEKEY = 0xB4C18A83,
|
||||||
|
|
||||||
|
// Process
|
||||||
|
NTQUERYSYSTEMINFORMATION = 0xEE4F73A8,
|
||||||
|
|
||||||
|
INVALID_SYSCALL_ID = 0xFFFFFFFF, //! Used to signify errors
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Gets the basic ID for the hash given.
|
||||||
|
|
||||||
|
This function does not dependent on the ID table but instead has
|
||||||
|
hardcoded definitions for a FEW Apis (these are marked in the
|
||||||
|
SYSCALL_IDS enum)
|
||||||
|
\param func The hash of the API that the ID is searched for
|
||||||
|
\return Returns the ID or INVALID_SYSCALL_ID
|
||||||
|
\sa get_syscall_ID()
|
||||||
|
*/
|
||||||
|
DWORD get_basic_syscall_ID(SYSCALL_IDS func);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Initalizes the ID table.
|
||||||
|
\return If FALSE no direct syscalls can be made.
|
||||||
|
\sa free_ID_table()
|
||||||
|
*/
|
||||||
|
BOOL initalize_ID_table();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Frees the ID table. After this is done
|
||||||
|
no direct syscalls can be made anymore
|
||||||
|
\sa initalize_ID_table()
|
||||||
|
*/
|
||||||
|
VOID destroy_ID_table();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Gets the ID for the hash given.
|
||||||
|
\pre This function does dependent on the ID table so make sure
|
||||||
|
to initalize_ID_table() first.
|
||||||
|
\param func The hash of the API that the ID is searched for
|
||||||
|
\return Returns the ID or INVALID_SYSCALL_ID
|
||||||
|
\sa initalize_ID_table()
|
||||||
|
*/
|
||||||
|
DWORD get_syscall_ID(DWORD func);
|
||||||
|
|
||||||
|
#endif // GET_SYSCALL64_IDS_H
|
||||||
|
|
||||||
35
main.cpp
Normal file
35
main.cpp
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/*
|
||||||
|
there was a call to openfile that failed with STATUS_DATATYPE_MISALIGNMENT
|
||||||
|
*/
|
||||||
|
#include <cstdio>
|
||||||
|
#include <iostream>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <malloc.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#include <Shlwapi.h>
|
||||||
|
#include <ntdll.h>
|
||||||
|
|
||||||
|
#include "structs.h"
|
||||||
|
#include "wow64ext.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "syscall64.h"
|
||||||
|
#include "get_syscall64_ids.h"
|
||||||
|
|
||||||
|
|
||||||
|
BOOL file_test();
|
||||||
|
void WINAPI RtlInitUnicodeString(unsigned char* target, PCWSTR source);
|
||||||
|
|
||||||
|
#define PRINTF_PP(string, ...) printf(string, NARG(__VA_ARGS__), __VA_ARGS__)
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
print_os_info();
|
||||||
|
|
||||||
|
if(!initalize_ID_table())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
file_test();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
56
misc.cpp
Normal file
56
misc.cpp
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
#include <cstdio>
|
||||||
|
#include <ntdll.h>
|
||||||
|
#include "structs.h"
|
||||||
|
#include "misc.h"
|
||||||
|
|
||||||
|
BOOL is_WOW64()
|
||||||
|
{
|
||||||
|
return NULL != __readfsdword(0xC0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VOID print_os_info()
|
||||||
|
{
|
||||||
|
_KUSER_SHARED_DATA* _kuser_s_d = GET_KUSER_SHARED_DATA();
|
||||||
|
ULONG majorVersion = _kuser_s_d->NtMajorVersion;
|
||||||
|
ULONG minorVersion = _kuser_s_d->NtMinorVersion;
|
||||||
|
NT_PRODUCT_TYPE productType = _kuser_s_d->NtProductType;
|
||||||
|
_PEB* p = (_PEB*)__readfsdword(0x30);
|
||||||
|
ULONG buildID = p->NtBuildNumber;
|
||||||
|
|
||||||
|
printf("Running on %i.%i %i %X (x%s)\n", majorVersion, minorVersion, buildID, productType, (is_WOW64() ? "64" : "86"));
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD hash(const char* str)
|
||||||
|
{
|
||||||
|
return hash((const unsigned char*)str, strlen(str));
|
||||||
|
}
|
||||||
|
|
||||||
|
DWORD hash(const unsigned char* buf, const size_t sz)
|
||||||
|
{
|
||||||
|
unsigned int hash = 5381;
|
||||||
|
|
||||||
|
for(unsigned int i = 0; i < sz; i++)
|
||||||
|
hash = ((hash << 5) + hash) + (unsigned int)buf[i];
|
||||||
|
|
||||||
|
return hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BOOL is_executable(LPVOID addr)
|
||||||
|
{
|
||||||
|
MEMORY_BASIC_INFORMATION mbi = {0};
|
||||||
|
if(!VirtualQuery(addr, &mbi, sizeof(MEMORY_BASIC_INFORMATION)))
|
||||||
|
{
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
return (mbi.Protect & PAGE_EXECUTE);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL is_Win8()
|
||||||
|
{
|
||||||
|
KUSER_SHARED_DATA* _kuser_s_d = GET_KUSER_SHARED_DATA();
|
||||||
|
ULONG majorVersion = _kuser_s_d->NtMajorVersion;
|
||||||
|
ULONG minorVersion = _kuser_s_d->NtMinorVersion;
|
||||||
|
|
||||||
|
return 6 == majorVersion && 2 == minorVersion;
|
||||||
|
}
|
||||||
38
misc.h
Normal file
38
misc.h
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
#ifndef MISC_H
|
||||||
|
#define MISC_H
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Checks if the page at addr is executable
|
||||||
|
*/
|
||||||
|
BOOL is_executable(LPVOID addr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Checks if the process is running on WOW64 by examing Heaven's Gate
|
||||||
|
*/
|
||||||
|
BOOL is_WOW64();
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Computes the djb2 hash of the input string
|
||||||
|
\param str The data to be hashed
|
||||||
|
\return The value.
|
||||||
|
*/
|
||||||
|
DWORD hash(const char* str);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Computes the djb2 hash of the input
|
||||||
|
\param buf The data to be hashed
|
||||||
|
\param sz The size of the data
|
||||||
|
\return The value.
|
||||||
|
*/
|
||||||
|
DWORD hash(const unsigned char* buf, const size_t sz);
|
||||||
|
|
||||||
|
/**
|
||||||
|
\brief Prints a small summarization of the current OS
|
||||||
|
*/
|
||||||
|
VOID print_os_info();
|
||||||
|
|
||||||
|
BOOL is_Win8();
|
||||||
|
|
||||||
|
#define GET_KUSER_SHARED_DATA() ((_KUSER_SHARED_DATA*)0x7FFE0000)
|
||||||
|
|
||||||
|
#endif
|
||||||
85
structs.h
Normal file
85
structs.h
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
#ifndef STRUCTS_INCLUDE_HEADER
|
||||||
|
#define STRUCTS_INCLUDE_HEADER
|
||||||
|
|
||||||
|
typedef struct _KSYSTEM_TIME {
|
||||||
|
UINT32 LowPart;
|
||||||
|
INT32 High1Time;
|
||||||
|
INT32 High2Time;
|
||||||
|
} KSYSTEM_TIME, *PKSYSTEM_TIME;
|
||||||
|
typedef enum _NT_PRODUCT_TYPE
|
||||||
|
{
|
||||||
|
NtProductWinNt = 1,
|
||||||
|
NtProductLanManNt = 2,
|
||||||
|
NtProductServer = 3
|
||||||
|
} NT_PRODUCT_TYPE;
|
||||||
|
typedef enum _ALTERNATIVE_ARCHITECTURE_TYPE
|
||||||
|
{
|
||||||
|
StandardDesign = 0,
|
||||||
|
NEC98x86 = 1,
|
||||||
|
EndAlternatives = 2
|
||||||
|
} ALTERNATIVE_ARCHITECTURE_TYPE;
|
||||||
|
typedef struct _KUSER_SHARED_DATA
|
||||||
|
{
|
||||||
|
ULONG TickCountLowDeprecated;
|
||||||
|
ULONG TickCountMultiplier;
|
||||||
|
KSYSTEM_TIME InterruptTime;
|
||||||
|
KSYSTEM_TIME SystemTime;
|
||||||
|
KSYSTEM_TIME TimeZoneBias;
|
||||||
|
WORD ImageNumberLow;
|
||||||
|
WORD ImageNumberHigh;
|
||||||
|
WCHAR NtSystemRoot[260];
|
||||||
|
ULONG MaxStackTraceDepth;
|
||||||
|
ULONG CryptoExponent;
|
||||||
|
ULONG TimeZoneId;
|
||||||
|
ULONG LargePageMinimum;
|
||||||
|
ULONG Reserved2[7];
|
||||||
|
NT_PRODUCT_TYPE NtProductType;
|
||||||
|
UCHAR ProductTypeIsValid;
|
||||||
|
ULONG NtMajorVersion;
|
||||||
|
ULONG NtMinorVersion;
|
||||||
|
UCHAR ProcessorFeatures[64];
|
||||||
|
ULONG Reserved1;
|
||||||
|
ULONG Reserved3;
|
||||||
|
ULONG TimeSlip;
|
||||||
|
ALTERNATIVE_ARCHITECTURE_TYPE AlternativeArchitecture;
|
||||||
|
LARGE_INTEGER SystemExpirationDate;
|
||||||
|
ULONG SuiteMask;
|
||||||
|
UCHAR KdDebuggerEnabled;
|
||||||
|
UCHAR NXSupportPolicy;
|
||||||
|
ULONG ActiveConsoleId;
|
||||||
|
ULONG DismountCount;
|
||||||
|
ULONG ComPlusPackage;
|
||||||
|
ULONG LastSystemRITEventTickCount;
|
||||||
|
ULONG NumberOfPhysicalPages;
|
||||||
|
UCHAR SafeBootMode;
|
||||||
|
ULONG SharedDataFlags;
|
||||||
|
ULONG DbgErrorPortPresent : 1;
|
||||||
|
ULONG DbgElevationEnabled : 1;
|
||||||
|
ULONG DbgVirtEnabled : 1;
|
||||||
|
ULONG DbgInstallerDetectEnabled : 1;
|
||||||
|
ULONG SystemDllRelocated : 1;
|
||||||
|
ULONG SpareBits : 27;
|
||||||
|
UINT64 TestRetInstruction;
|
||||||
|
ULONG SystemCall;
|
||||||
|
ULONG SystemCallReturn;
|
||||||
|
UINT64 SystemCallPad[3];
|
||||||
|
union
|
||||||
|
{
|
||||||
|
KSYSTEM_TIME TickCount;
|
||||||
|
UINT64 TickCountQuad;
|
||||||
|
};
|
||||||
|
ULONG Cookie;
|
||||||
|
INT64 ConsoleSessionForegroundProcessId;
|
||||||
|
ULONG Wow64SharedInformation[16];
|
||||||
|
WORD UserModeGlobalLogger[8];
|
||||||
|
ULONG HeapTracingPid[2];
|
||||||
|
ULONG CritSecTracingPid[2];
|
||||||
|
ULONG ImageFileExecutionOptions;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
UINT64 AffinityPad;
|
||||||
|
ULONG ActiveProcessorAffinity;
|
||||||
|
};
|
||||||
|
UINT64 InterruptTimeBias;
|
||||||
|
} KUSER_SHARED_DATA, *PKUSER_SHARED_DATA;
|
||||||
|
#endif // STRUCTS_INCLUDE_HEADER
|
||||||
43
syscall64.h
Normal file
43
syscall64.h
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
#ifndef SYSCALL64_H
|
||||||
|
#define SYSCALL64_H
|
||||||
|
#include <malloc.h> // eww include in headers
|
||||||
|
|
||||||
|
extern "C" DWORD _cdecl syscall64(size_t cnt, DWORD id, ...);
|
||||||
|
|
||||||
|
#define NARG(...) Y_TUPLE_SIZE_II((Y_TUPLE_SIZE_PREFIX_ ## __VA_ARGS__ ## _Y_TUPLE_SIZE_POSTFIX,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0))
|
||||||
|
#define Y_TUPLE_SIZE_II(__args) Y_TUPLE_SIZE_I __args
|
||||||
|
|
||||||
|
#define Y_TUPLE_SIZE_PREFIX__Y_TUPLE_SIZE_POSTFIX ,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,0
|
||||||
|
|
||||||
|
#define Y_TUPLE_SIZE_I(__p0,__p1,__p2,__p3,__p4,__p5,__p6,__p7,__p8,__p9,__p10,__p11,__p12,__p13,__p14,__p15,__p16,__p17,__p18,__p19,__p20,__p21,__p22,__p23,__p24,__p25,__p26,__p27,__p28,__p29,__p30,__p31,__n,...) __n
|
||||||
|
|
||||||
|
|
||||||
|
#define DO_SYSCALL(id, ...) syscall64(NARG(__VA_ARGS__), id, __VA_ARGS__)
|
||||||
|
|
||||||
|
#define STR_VALUE(arg) #arg
|
||||||
|
|
||||||
|
#define ALLOC_STRUCTURE(type, name) uint8_t* name = (unsigned char*)_aligned_malloc(is_WOW64() ? sizeof(type<DWORD64>) : sizeof(type<DWORD>), 16); \
|
||||||
|
memset(name, 0, is_WOW64() ? sizeof(type<DWORD64>) : sizeof(type<DWORD>)); \
|
||||||
|
printf("%s %s @ %p\n", STR_VALUE(type), STR_VALUE(name), name);
|
||||||
|
#define FREE_STRUCTURE(type, name) _aligned_free(name); name = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
#define GET_MEMBER_OFFSET64(type, member) offsetof(type<DWORD64>, member)
|
||||||
|
#define GET_MEMBER_OFFSET86(type, member) offsetof(type<DWORD>, member)
|
||||||
|
#define GET_MEMBER_OFFSET(type, member) (is_WOW64() ? GET_MEMBER_OFFSET64(type, member) : GET_MEMBER_OFFSET86(type, member))
|
||||||
|
|
||||||
|
#define GET_MEMBER(type, member, object) (object + GET_MEMBER_OFFSET(type, member))
|
||||||
|
#define SET_MEMBER(type, member, object, val) { \
|
||||||
|
decltype(val)* tmpDst = (decltype(val)*)GET_MEMBER(type, member, object); \
|
||||||
|
decltype(val) tmpSrc = val; \
|
||||||
|
printf("cpy %X(%s) from/value of %X to %X\n", sizeof(tmpDst), STR_VALUE(member), val, tmpDst); \
|
||||||
|
memcpy((void*)tmpDst, (void*)&tmpSrc, sizeof(tmpDst)); \
|
||||||
|
}
|
||||||
|
#define SET_MEMBER_PTR(type, member, object, src) { \
|
||||||
|
void* dstPtr = (void*)GET_MEMBER(type, member, object); \
|
||||||
|
void* ptrToSrc = (void*)src; \
|
||||||
|
printf("cpy ptr(%s) from %X to %X\n", STR_VALUE(member), src, dstPtr); \
|
||||||
|
memcpy((void*)dstPtr, &ptrToSrc, 4); \
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
100
tests.cpp
Normal file
100
tests.cpp
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
#include <array>
|
||||||
|
#include <ntdll.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <shlobj.h>
|
||||||
|
#include <Shlwapi.h>
|
||||||
|
#include "structs.h"
|
||||||
|
#include "misc.h"
|
||||||
|
#include "wow64ext.h"
|
||||||
|
#include "syscall64.h"
|
||||||
|
#include "get_syscall64_ids.h"
|
||||||
|
|
||||||
|
void WINAPI RtlInitUnicodeString(
|
||||||
|
unsigned char* target,
|
||||||
|
PCWSTR source)
|
||||||
|
{
|
||||||
|
SET_MEMBER_PTR(_UNICODE_STRING_T, Buffer, target, source);
|
||||||
|
if(source)
|
||||||
|
{
|
||||||
|
unsigned int length = lstrlenW(source) * sizeof(WCHAR);
|
||||||
|
if(length > 0xfffc)
|
||||||
|
length = 0xfffc;
|
||||||
|
SET_MEMBER(_UNICODE_STRING_T, Length, target, length);
|
||||||
|
SET_MEMBER(_UNICODE_STRING_T, MaximumLength, target, length + sizeof(WCHAR));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SET_MEMBER(_UNICODE_STRING_T, Length, target, 0);
|
||||||
|
SET_MEMBER(_UNICODE_STRING_T, MaximumLength, target, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
VOID initialize_object_attributes(unsigned char* p, LPVOID n, ULONG a)
|
||||||
|
{
|
||||||
|
SET_MEMBER(_OBJECT_ATTRIBUTES_T, uLength, p, is_WOW64() ? sizeof(_OBJECT_ATTRIBUTES_T<DWORD64>) : sizeof(_OBJECT_ATTRIBUTES_T<DWORD>));
|
||||||
|
SET_MEMBER(_OBJECT_ATTRIBUTES_T, hRootDirectory, p, NULL);
|
||||||
|
SET_MEMBER(_OBJECT_ATTRIBUTES_T, uAttributes, p, a);
|
||||||
|
SET_MEMBER_PTR(_OBJECT_ATTRIBUTES_T, pObjectName, p, n);
|
||||||
|
SET_MEMBER(_OBJECT_ATTRIBUTES_T, pSecurityDescriptor, p, NULL); // Actually a ptr but we set a NULL
|
||||||
|
SET_MEMBER(_OBJECT_ATTRIBUTES_T, pSecurityQualityOfService, p, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL file_test()
|
||||||
|
{
|
||||||
|
ALLOC_STRUCTURE(_UNICODE_STRING_T, filename);
|
||||||
|
wchar_t desktopPath[MAX_PATH];
|
||||||
|
lstrcpyW(desktopPath, L"\\??\\");
|
||||||
|
if(!SHGetSpecialFolderPathW(HWND_DESKTOP, &desktopPath[lstrlenW(desktopPath)], CSIDL_DESKTOPDIRECTORY, FALSE))
|
||||||
|
return FALSE;
|
||||||
|
PathAppendW(desktopPath, L"WaitAMinute.HowDidThisGetHere.txt");
|
||||||
|
RtlInitUnicodeString(filename, desktopPath);
|
||||||
|
printf("Desktop: %ws\n", desktopPath);
|
||||||
|
|
||||||
|
ALLOC_STRUCTURE(_OBJECT_ATTRIBUTES_T, obja);
|
||||||
|
initialize_object_attributes(obja, (void*)filename, OBJ_CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
ALLOC_STRUCTURE(_HANDLE_T, fileHandle);
|
||||||
|
ALLOC_STRUCTURE(_IO_STATUS_BLOCK_T, iostatusblock);
|
||||||
|
|
||||||
|
NTSTATUS stat = DO_SYSCALL(get_syscall_ID(NTCREATEFILE), fileHandle, FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
|
||||||
|
obja, iostatusblock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OVERWRITE_IF,
|
||||||
|
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
|
||||||
|
if(STATUS_SUCCESS != stat)
|
||||||
|
{
|
||||||
|
printf("NTSTATUS: %X\n", stat);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
printf("Handle: %X\tIO.uInformation:%X\n", *(PHANDLE)GET_MEMBER(_HANDLE_T, h, fileHandle), *(PDWORD)GET_MEMBER(_IO_STATUS_BLOCK_T, uInformation, iostatusblock));
|
||||||
|
|
||||||
|
FREE_STRUCTURE(_UNICODE_STRING_T, filename);
|
||||||
|
FREE_STRUCTURE(_OBJECT_ATTRIBUTES_T, obja);
|
||||||
|
|
||||||
|
const char TEST_STRING[] = {"0123456789ABCDEF"};
|
||||||
|
if(STATUS_SUCCESS != (stat = DO_SYSCALL(get_syscall_ID(NTWRITEFILE), *(PHANDLE)GET_MEMBER(_HANDLE_T, h, fileHandle),
|
||||||
|
NULL, NULL, NULL, iostatusblock, TEST_STRING, lstrlenA(TEST_STRING), NULL, NULL)))
|
||||||
|
{
|
||||||
|
printf("Write: %X\t\n", stat);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
|
||||||
|
FlushFileBuffers(*(PHANDLE)GET_MEMBER(_HANDLE_T, h, fileHandle));
|
||||||
|
|
||||||
|
char buffer[sizeof(TEST_STRING)+1] = {0};
|
||||||
|
LARGE_INTEGER offset = {0};
|
||||||
|
if(STATUS_SUCCESS != (stat = DO_SYSCALL(get_syscall_ID(NTREADFILE), *(PHANDLE)GET_MEMBER(_HANDLE_T, h, fileHandle),
|
||||||
|
NULL, NULL, NULL, iostatusblock, buffer, sizeof(buffer), &offset, NULL)))
|
||||||
|
{
|
||||||
|
printf("Reading failed: %X\t%X != %X\n", stat, sizeof(buffer), offset.LowPart);
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
buffer[*(PDWORD)GET_MEMBER(_IO_STATUS_BLOCK_T, uInformation, iostatusblock)] = 0;
|
||||||
|
printf("io stat: %X\tio inf: %X\tread: %X\n", *(PDWORD)GET_MEMBER(_IO_STATUS_BLOCK_T, Status, iostatusblock), *(PDWORD)GET_MEMBER(_IO_STATUS_BLOCK_T, uInformation, iostatusblock), offset.LowPart);
|
||||||
|
|
||||||
|
DO_SYSCALL(get_syscall_ID(NTCLOSE), *(PHANDLE)GET_MEMBER(_HANDLE_T, h, fileHandle));
|
||||||
|
FREE_STRUCTURE(_HANDLE_T, fileHandle);
|
||||||
|
|
||||||
|
printf("'%s' == '%s'\n", TEST_STRING, buffer);
|
||||||
|
return 0 == lstrcmpA(TEST_STRING, buffer);
|
||||||
|
|
||||||
|
}
|
||||||
409
wow64ext.h
Normal file
409
wow64ext.h
Normal file
@@ -0,0 +1,409 @@
|
|||||||
|
/**
|
||||||
|
*
|
||||||
|
* WOW64Ext Library
|
||||||
|
*
|
||||||
|
* Copyright (c) 2014 ReWolf
|
||||||
|
* http://blog.rewolf.pl/
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU Lesser General Public License as published
|
||||||
|
* by the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU Lesser General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU Lesser General Public License
|
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#ifndef STATUS_SUCCESS
|
||||||
|
# define STATUS_SUCCESS 0
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#pragma pack(push)
|
||||||
|
#pragma pack(1)
|
||||||
|
template <class T>
|
||||||
|
struct _LIST_ENTRY_T
|
||||||
|
{
|
||||||
|
T Flink;
|
||||||
|
T Blink;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _UNICODE_STRING_T
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
WORD Length;
|
||||||
|
WORD MaximumLength;
|
||||||
|
};
|
||||||
|
T dummy;
|
||||||
|
};
|
||||||
|
T Buffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _NT_TIB_T
|
||||||
|
{
|
||||||
|
T ExceptionList;
|
||||||
|
T StackBase;
|
||||||
|
T StackLimit;
|
||||||
|
T SubSystemTib;
|
||||||
|
T FiberData;
|
||||||
|
T ArbitraryUserPointer;
|
||||||
|
T Self;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _CLIENT_ID_T
|
||||||
|
{
|
||||||
|
T UniqueProcess;
|
||||||
|
T UniqueThread;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _TEB_T_
|
||||||
|
{
|
||||||
|
_NT_TIB_T<T> NtTib;
|
||||||
|
T EnvironmentPointer;
|
||||||
|
_CLIENT_ID_T<T> ClientId;
|
||||||
|
T ActiveRpcHandle;
|
||||||
|
T ThreadLocalStoragePointer;
|
||||||
|
T ProcessEnvironmentBlock;
|
||||||
|
DWORD LastErrorValue;
|
||||||
|
DWORD CountOfOwnedCriticalSections;
|
||||||
|
T CsrClientThread;
|
||||||
|
T Win32ThreadInfo;
|
||||||
|
DWORD User32Reserved[26];
|
||||||
|
//rest of the structure is not defined for now, as it is not needed
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _LDR_DATA_TABLE_ENTRY_T
|
||||||
|
{
|
||||||
|
_LIST_ENTRY_T<T> InLoadOrderLinks;
|
||||||
|
_LIST_ENTRY_T<T> InMemoryOrderLinks;
|
||||||
|
_LIST_ENTRY_T<T> InInitializationOrderLinks;
|
||||||
|
T DllBase;
|
||||||
|
T EntryPoint;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
DWORD SizeOfImage;
|
||||||
|
T dummy01;
|
||||||
|
};
|
||||||
|
_UNICODE_STRING_T<T> FullDllName;
|
||||||
|
_UNICODE_STRING_T<T> BaseDllName;
|
||||||
|
DWORD Flags;
|
||||||
|
WORD LoadCount;
|
||||||
|
WORD TlsIndex;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
_LIST_ENTRY_T<T> HashLinks;
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
T SectionPointer;
|
||||||
|
T CheckSum;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
union
|
||||||
|
{
|
||||||
|
T LoadedImports;
|
||||||
|
DWORD TimeDateStamp;
|
||||||
|
};
|
||||||
|
T EntryPointActivationContext;
|
||||||
|
T PatchInformation;
|
||||||
|
_LIST_ENTRY_T<T> ForwarderLinks;
|
||||||
|
_LIST_ENTRY_T<T> ServiceTagLinks;
|
||||||
|
_LIST_ENTRY_T<T> StaticLinks;
|
||||||
|
T ContextInformation;
|
||||||
|
T OriginalBase;
|
||||||
|
_LARGE_INTEGER LoadTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _PEB_LDR_DATA_T
|
||||||
|
{
|
||||||
|
DWORD Length;
|
||||||
|
DWORD Initialized;
|
||||||
|
T SsHandle;
|
||||||
|
_LIST_ENTRY_T<T> InLoadOrderModuleList;
|
||||||
|
_LIST_ENTRY_T<T> InMemoryOrderModuleList;
|
||||||
|
_LIST_ENTRY_T<T> InInitializationOrderModuleList;
|
||||||
|
T EntryInProgress;
|
||||||
|
DWORD ShutdownInProgress;
|
||||||
|
T ShutdownThreadId;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T, class NGF, int A>
|
||||||
|
struct _PEB_T
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
BYTE InheritedAddressSpace;
|
||||||
|
BYTE ReadImageFileExecOptions;
|
||||||
|
BYTE BeingDebugged;
|
||||||
|
BYTE BitField;
|
||||||
|
};
|
||||||
|
T dummy01;
|
||||||
|
};
|
||||||
|
T Mutant;
|
||||||
|
T ImageBaseAddress;
|
||||||
|
T Ldr;
|
||||||
|
T ProcessParameters;
|
||||||
|
T SubSystemData;
|
||||||
|
T ProcessHeap;
|
||||||
|
T FastPebLock;
|
||||||
|
T AtlThunkSListPtr;
|
||||||
|
T IFEOKey;
|
||||||
|
T CrossProcessFlags;
|
||||||
|
T UserSharedInfoPtr;
|
||||||
|
DWORD SystemReserved;
|
||||||
|
DWORD AtlThunkSListPtr32;
|
||||||
|
T ApiSetMap;
|
||||||
|
T TlsExpansionCounter;
|
||||||
|
T TlsBitmap;
|
||||||
|
DWORD TlsBitmapBits[2];
|
||||||
|
T ReadOnlySharedMemoryBase;
|
||||||
|
T HotpatchInformation;
|
||||||
|
T ReadOnlyStaticServerData;
|
||||||
|
T AnsiCodePageData;
|
||||||
|
T OemCodePageData;
|
||||||
|
T UnicodeCaseTableData;
|
||||||
|
DWORD NumberOfProcessors;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
DWORD NtGlobalFlag;
|
||||||
|
NGF dummy02;
|
||||||
|
};
|
||||||
|
LARGE_INTEGER CriticalSectionTimeout;
|
||||||
|
T HeapSegmentReserve;
|
||||||
|
T HeapSegmentCommit;
|
||||||
|
T HeapDeCommitTotalFreeThreshold;
|
||||||
|
T HeapDeCommitFreeBlockThreshold;
|
||||||
|
DWORD NumberOfHeaps;
|
||||||
|
DWORD MaximumNumberOfHeaps;
|
||||||
|
T ProcessHeaps;
|
||||||
|
T GdiSharedHandleTable;
|
||||||
|
T ProcessStarterHelper;
|
||||||
|
T GdiDCAttributeList;
|
||||||
|
T LoaderLock;
|
||||||
|
DWORD OSMajorVersion;
|
||||||
|
DWORD OSMinorVersion;
|
||||||
|
WORD OSBuildNumber;
|
||||||
|
WORD OSCSDVersion;
|
||||||
|
DWORD OSPlatformId;
|
||||||
|
DWORD ImageSubsystem;
|
||||||
|
DWORD ImageSubsystemMajorVersion;
|
||||||
|
T ImageSubsystemMinorVersion;
|
||||||
|
T ActiveProcessAffinityMask;
|
||||||
|
T GdiHandleBuffer[A];
|
||||||
|
T PostProcessInitRoutine;
|
||||||
|
T TlsExpansionBitmap;
|
||||||
|
DWORD TlsExpansionBitmapBits[32];
|
||||||
|
T SessionId;
|
||||||
|
ULARGE_INTEGER AppCompatFlags;
|
||||||
|
ULARGE_INTEGER AppCompatFlagsUser;
|
||||||
|
T pShimData;
|
||||||
|
T AppCompatInfo;
|
||||||
|
_UNICODE_STRING_T<T> CSDVersion;
|
||||||
|
T ActivationContextData;
|
||||||
|
T ProcessAssemblyStorageMap;
|
||||||
|
T SystemDefaultActivationContextData;
|
||||||
|
T SystemAssemblyStorageMap;
|
||||||
|
T MinimumStackCommit;
|
||||||
|
T FlsCallback;
|
||||||
|
_LIST_ENTRY_T<T> FlsListHead;
|
||||||
|
T FlsBitmap;
|
||||||
|
DWORD FlsBitmapBits[4];
|
||||||
|
T FlsHighIndex;
|
||||||
|
T WerRegistrationData;
|
||||||
|
T WerShipAssertPtr;
|
||||||
|
T pContextData;
|
||||||
|
T pImageHeaderHash;
|
||||||
|
T TracingFlags;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD> LDR_DATA_TABLE_ENTRY32;
|
||||||
|
typedef _LDR_DATA_TABLE_ENTRY_T<DWORD64> LDR_DATA_TABLE_ENTRY64;
|
||||||
|
|
||||||
|
typedef _TEB_T_<DWORD> TEB32;
|
||||||
|
typedef _TEB_T_<DWORD64> TEB64;
|
||||||
|
|
||||||
|
typedef _PEB_LDR_DATA_T<DWORD> PEB_LDR_DATA32;
|
||||||
|
typedef _PEB_LDR_DATA_T<DWORD64> PEB_LDR_DATA64;
|
||||||
|
|
||||||
|
typedef _PEB_T<DWORD, DWORD64, 34> PEB32;
|
||||||
|
//typedef _PEB_T<DWORD64, DWORD, 30> PEB64;
|
||||||
|
|
||||||
|
struct _XSAVE_FORMAT64
|
||||||
|
{
|
||||||
|
WORD ControlWord;
|
||||||
|
WORD StatusWord;
|
||||||
|
BYTE TagWord;
|
||||||
|
BYTE Reserved1;
|
||||||
|
WORD ErrorOpcode;
|
||||||
|
DWORD ErrorOffset;
|
||||||
|
WORD ErrorSelector;
|
||||||
|
WORD Reserved2;
|
||||||
|
DWORD DataOffset;
|
||||||
|
WORD DataSelector;
|
||||||
|
WORD Reserved3;
|
||||||
|
DWORD MxCsr;
|
||||||
|
DWORD MxCsr_Mask;
|
||||||
|
_M128A FloatRegisters[8];
|
||||||
|
_M128A XmmRegisters[16];
|
||||||
|
BYTE Reserved4[96];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct _CONTEXT64
|
||||||
|
{
|
||||||
|
DWORD64 P1Home;
|
||||||
|
DWORD64 P2Home;
|
||||||
|
DWORD64 P3Home;
|
||||||
|
DWORD64 P4Home;
|
||||||
|
DWORD64 P5Home;
|
||||||
|
DWORD64 P6Home;
|
||||||
|
DWORD ContextFlags;
|
||||||
|
DWORD MxCsr;
|
||||||
|
WORD SegCs;
|
||||||
|
WORD SegDs;
|
||||||
|
WORD SegEs;
|
||||||
|
WORD SegFs;
|
||||||
|
WORD SegGs;
|
||||||
|
WORD SegSs;
|
||||||
|
DWORD EFlags;
|
||||||
|
DWORD64 Dr0;
|
||||||
|
DWORD64 Dr1;
|
||||||
|
DWORD64 Dr2;
|
||||||
|
DWORD64 Dr3;
|
||||||
|
DWORD64 Dr6;
|
||||||
|
DWORD64 Dr7;
|
||||||
|
DWORD64 Rax;
|
||||||
|
DWORD64 Rcx;
|
||||||
|
DWORD64 Rdx;
|
||||||
|
DWORD64 Rbx;
|
||||||
|
DWORD64 Rsp;
|
||||||
|
DWORD64 Rbp;
|
||||||
|
DWORD64 Rsi;
|
||||||
|
DWORD64 Rdi;
|
||||||
|
DWORD64 R8;
|
||||||
|
DWORD64 R9;
|
||||||
|
DWORD64 R10;
|
||||||
|
DWORD64 R11;
|
||||||
|
DWORD64 R12;
|
||||||
|
DWORD64 R13;
|
||||||
|
DWORD64 R14;
|
||||||
|
DWORD64 R15;
|
||||||
|
DWORD64 Rip;
|
||||||
|
_XSAVE_FORMAT64 FltSave;
|
||||||
|
_M128A Header[2];
|
||||||
|
_M128A Legacy[8];
|
||||||
|
_M128A Xmm0;
|
||||||
|
_M128A Xmm1;
|
||||||
|
_M128A Xmm2;
|
||||||
|
_M128A Xmm3;
|
||||||
|
_M128A Xmm4;
|
||||||
|
_M128A Xmm5;
|
||||||
|
_M128A Xmm6;
|
||||||
|
_M128A Xmm7;
|
||||||
|
_M128A Xmm8;
|
||||||
|
_M128A Xmm9;
|
||||||
|
_M128A Xmm10;
|
||||||
|
_M128A Xmm11;
|
||||||
|
_M128A Xmm12;
|
||||||
|
_M128A Xmm13;
|
||||||
|
_M128A Xmm14;
|
||||||
|
_M128A Xmm15;
|
||||||
|
_M128A VectorRegister[26];
|
||||||
|
DWORD64 VectorControl;
|
||||||
|
DWORD64 DebugControl;
|
||||||
|
DWORD64 LastBranchToRip;
|
||||||
|
DWORD64 LastBranchFromRip;
|
||||||
|
DWORD64 LastExceptionToRip;
|
||||||
|
DWORD64 LastExceptionFromRip;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Below defines for .ContextFlags field are taken from WinNT.h
|
||||||
|
#ifndef CONTEXT_AMD64
|
||||||
|
#define CONTEXT_AMD64 0x100000
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#define CONTEXT64_CONTROL (CONTEXT_AMD64 | 0x1L)
|
||||||
|
#define CONTEXT64_INTEGER (CONTEXT_AMD64 | 0x2L)
|
||||||
|
#define CONTEXT64_SEGMENTS (CONTEXT_AMD64 | 0x4L)
|
||||||
|
#define CONTEXT64_FLOATING_POINT (CONTEXT_AMD64 | 0x8L)
|
||||||
|
#define CONTEXT64_DEBUG_REGISTERS (CONTEXT_AMD64 | 0x10L)
|
||||||
|
#define CONTEXT64_FULL (CONTEXT64_CONTROL | CONTEXT64_INTEGER | CONTEXT64_FLOATING_POINT)
|
||||||
|
#define CONTEXT64_ALL (CONTEXT64_CONTROL | CONTEXT64_INTEGER | CONTEXT64_SEGMENTS | CONTEXT64_FLOATING_POINT | CONTEXT64_DEBUG_REGISTERS)
|
||||||
|
#define CONTEXT64_XSTATE (CONTEXT_AMD64 | 0x20L)
|
||||||
|
|
||||||
|
// My changes
|
||||||
|
template <class T>
|
||||||
|
struct _OBJECT_ATTRIBUTES_T
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ULONG uLength;
|
||||||
|
T dummy;
|
||||||
|
};
|
||||||
|
T hRootDirectory;
|
||||||
|
T pObjectName;
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ULONG uAttributes;
|
||||||
|
T dummy2;
|
||||||
|
};
|
||||||
|
T pSecurityDescriptor;
|
||||||
|
T pSecurityQualityOfService;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class T>
|
||||||
|
struct _HANDLE_T
|
||||||
|
{
|
||||||
|
T h;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Extremly weird. sizeof(IO_STATUS_BLOCK) == 8 on x86 & x64.
|
||||||
|
However NtCreateFile doesn't seem to agree
|
||||||
|
|
||||||
|
UNICODE_STRING filename = {0};
|
||||||
|
RtlInitUnicodeString(&filename, L"\\??\\D:\\abc.txt");
|
||||||
|
|
||||||
|
OBJECT_ATTRIBUTES obja = {0};
|
||||||
|
IO_STATUS_BLOCK iostatusblock = {0};
|
||||||
|
InitializeObjectAttributes(&obja, &filename, OBJ_CASE_INSENSITIVE, NULL, NULL);
|
||||||
|
|
||||||
|
HANDLE h = INVALID_HANDLE_VALUE;
|
||||||
|
NTSTATUS stat = NtCreateFile(&h, FILE_READ_DATA | FILE_WRITE_DATA | SYNCHRONIZE,
|
||||||
|
&obja, &iostatusblock,
|
||||||
|
NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OVERWRITE_IF,
|
||||||
|
FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0);
|
||||||
|
|
||||||
|
Run in Debug mode and see Run-Time Check Failure #2 - Stack around the variable 'iostatusblock' was corrupted.
|
||||||
|
*/
|
||||||
|
template <class T>
|
||||||
|
struct _IO_STATUS_BLOCK_T
|
||||||
|
{
|
||||||
|
union
|
||||||
|
{
|
||||||
|
NTSTATUS Status;
|
||||||
|
T dummy;
|
||||||
|
};
|
||||||
|
union
|
||||||
|
{
|
||||||
|
ULONG uInformation;
|
||||||
|
T dummy;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
#pragma pack(pop)
|
||||||
Reference in New Issue
Block a user