Okay, I’m going to explain some of the theory behind Privileged Instructions and Ring Levels. I came across this really interesting Stop 0x1E bugcheck this morning, and found that the exception which was caused was due to a privileged instruction being called at the wrong Ring Level. I will also in fact provide a link to the the thread, where I found such a error.
Firstly, let’s discuss briefly the concept of Ring Levels. There are four different Ring Levels, with the most outer ring being the least privileged and the inner most ring being the most privileged.
As your can see in the diagram above, essentially the ring levels are the User-Mode and Kernel Mode. Ring 0 and Ring 3 are usually only used on modern operating systems. Some instructions can only run at certain Ring Levels, which brings around the fact, that a privileged instruction being executed at a less privileged Ring Level which cause a exception. Gate Descriptors are used to switch between the privilege levels.
So, now you have a understanding of the privilege levels, lets take a look at the privileged instructions. I’ll provide a brief explanation of what these instructions are designed to do.
These are based upon the Intel architecture:
LGDT – Load Global Descriptor Table
LLDT – Load Local Descriptor Table
LTR – Load Task Register
LIDT – Load Interrupt Descriptor Table Register
MOV (to and from control registers only) – Control Register is used to change the CPU behavior, related to interrupt control, addressing mode, paging and coprocessor control.
MOV (to and from debug registers only) – Debugging programs, and use to set debugging controls.
LMSW – Load Machine Status Word
CLTS – Clear Task Switched
INVD – Invalidate Cache
WBINVD – Write Back and Invalidate Cache
INVLPG – Invalidate TLB Entry
HLT – Halt; halts the CPU and causing it to wait for the next interrupt.
RDMSR – Read From Model Specific Register
WRMSR – Write to Model Specific Register
RDPMC – Read Performance Monitoring Counters
RDTSC – Time Stamp Counter
STI (Set Interrupts) and CLI (Clear Interrupts) are a bit different, they can run at Ring 0 and Ring 3, however, this must be set in the IOPL (I/O Privilege Level). For example, if the IOPL was Ring 0, then the instruction can only be used at Ring 0 and not Ring 3. I’ll discuss the IOPL soon. Firstly, let’s get back to our bugcheck and our dump file.
By dumping the exception record and the using !error extension, we see the exception which happened and the function which caused it; this case it was nt!KiIdleLoop.
Now, this is where things begin to become more complex, we need to dump the registers for the trap frame of the exception and then use the r command to dump the segment registers, IOPL and EFLAGS.
Let’s just take apart a few interesting segments here: the instruction mov is being used to copy data from the rcx register into the cr8 (Control Register), which is used to prioritize certain interrupts. This instruction does have some relevance in why it’s being used, however, I don’t think this is the entire problem since something else happened earlier (will explain later).
Now, let’s explain IOPL more, the IOPL determines which code can access I/O ports. Ring 0 can always access all I/O ports, whereas, Ring 3 will need to be given permission and the privilege level to do so. The IOPL is set in the EFLAGS register.
There is a slight problem with this dump file too, a trap will interrupt and switch into Kernel-Mode, gaining the CPL (Current Privilege Level) from cs segment register, will point to the CPL being Ring 0, therefore it will look like the instruction was called at the correctly given IOPL.
I dissembled the function which was supposedly causing problems, and I believe the sti instruction may have been called from Ring 3 when the IOPL was set to Ring 0.