MSI ntiolib.sys/winio.sys local privilege escalation

So, it seems that not only ASUS drivers allows unprivileged reading and writing to physical memory. Just a few months ago I was looking at the drivers that are loaded on my machine, and I found small MSI driver called NTIOLib_X64.sys. Out of curiosity I’ve looked at it in IDA and it turned out that it has almost the same functionality as the ASMMAP/ASMMAP64 ASUS drivers. I’ve tried to contact MSI through various different channels, but I haven’t really get past their customer support, so I’m not sure if anyone from the development team is aware of this design flaw. After almost 4 months I decided to publish my findings here.

Brief description

NTIOLib.sys is installed with a few different MSI utilities that are part of the software package for MSI motherboards and graphic cards. WinIO.sys is completely different driver and is installed with Dragon Gaming Center application, which is part of the software package for MSI notebooks. Since both drivers expose physical memory access to the unprivileged users, I decided to put it into one report (I’ll describe the technical differences later). Actually when I was verifying list of affected software, I’ve found third driver that is doing exactly the same thing, just have a bit different interface and name (RTCore32.sys / RTCore64.sys).

Affected software:

  • NTIOLib.sys / NTIOLib_X64.sys
    • MSI FastBoot
    • MSI Command Center
    • MSI Live Update
    • MSI Gaming APP
    • MSI Super Charger
    • MSI Dragon Center
  • WinIO.sys / WinIO64.sys
    • MSI Dragon Gaming Center
    • MSI Dragon Center
  • RTCore32.sys / RTCore64.sys
    • MSI Afterburner

NTIOLib functionality exposed through IOCTLs:

  • read/write physical memory (using MmMapIoSpace)
  • read write MSR registers (using rdmsr/wrmsr opcodes)
  • read PMC register (using rdpmc opcode)
  • in/out port operations
  • HalGetBusDataByOffset / HalSetBusDataByOffset

WinIO functionality exposed through IOCTLs:

  • read/write physical memory (ZwMapViewOfSection of “\\Device\\PhysicalMemory”)
  • in/out port operations

RTCore functionality exposed through IOCTLs:

  • read/write physical memory (ZwMapViewOfSection of “\\Device\\PhysicalMemory”)
  • read write MSR registers (using rdmsr/wrmsr opcodes)
  • in/out port operations
  • HalGetBusDataByOffset / HalSetBusDataByOffset

It appears that RTCore driver is kind of hybrid between NTIOLib and WinIO. It’s also worth noting that WinIO driver is just compiled (and signed by MSI) version of the code that can be found here:

UPDATE: RTCore driver is part of RivaTuner software, so all OEM branded RivaTuner clones are vulnerable (

Some of the mentioned applications load vulnerable driver on demand, but some of them loads the driver with service startup and keeps it loaded for the whole time, thus exploitation is rather trivial. I haven’t thoroughly inspected all MSI applications, since it’s not really possible (different version of the software for different hardware, multiple installers etc), so it’s very probable that my list doesn’t cover all cases. Generally if someone owns any MSI hardware, it’s good to check if any of above drivers (or with similar name) is loaded, and if yes, just remove the application that installed it.

Disclosure timeline:

30.05.2016 sent e-mail notification to the addresses:,, (none of those is valid, but it was worth trying)
31.05.2016 – 03.06.2016 tried reporting through official support channel, without any luck, final reply:

Please don’t worry about it and the software files are secure.Anyway,we will send the information to relative department.Thanks!

03.06.2016 tried contact through a friend from security team of some super-secret big corporation – also without luck
26.09.2016 full disclosure

Technical details & PoC

After ASMMAP disclosure, I’ve read that the exploitation of this kind of vulnerability is rather easy:

This can be done by scanning for EPROCESS structures within memory and identifying one, then jumping through the linked list to find your target process and a known SYSTEM process (e.g. lsass), then duplicating the Token field across to elevate your process. This part isn’t really that novel or interesting, so I won’t go into it here.

Since I don’t have much experience in this area, I decided to try above method and see if the exploitation is really straightforward. I’ve started randomly poking with physical pages, just to see how it behaves. My first observation was, that the WinIO driver is a lot more stable than NTIOLib, it probably stems from the method that is used to expose physical memory to the user application (MmMapIoSpace vs ZwMapViewOfSection). NTIOLib tends to BSODs sometimes, especially if the accessed addresses are random (aligned to the 0x1000). My second observation was, that NTIOLib becomes quite stable if the memory is accessed sequentially (page by page). This is actually good, because EPROCESS search is sequential activity.

EPROCESS structures are allocated with Proc pool tag, this is the first indicator that EPROCESS search algorithm will look for. Each memory chunk starts with POOL_HEADER structure, followed by a few OBJECT_HEADER_xxx_INFO structures and finally by the OBJECT_HEADER. OBJECT_HEADER.Body is the actual EPROCESS. More details can be found in Uninformed Journal or in WRK (ObpAllocateObject, \wrk\base\ntos\ob\obcreate.c). On Windows 10 x64 (TH2, RS1) all those structures sums up to 0x80 bytes. To successfully execute local privilege escalation, I need to locate EPROCESS structure of 2 processes. One will be some system process and the second should be the process that privileges are supposed to be escalated. For system process I chose wininit.exe, and the escalated process will be the current process. Having names and PIDs of chosen processes, exploit can proceed to final EPROCESS verification (checks of UniqueProcessId and ImageFileName fields).

With above information it is possible to test initial exploit – it is very slow, so slow that I haven’t wait till it finish. The slowdown comes from accessing addresses that are reserved for hardware IO devices. Those reserved memory ranges will vary from one machine to another, so it’s required to find them out and skip during EPROCESS search. The easiest method to get those ranges is calling NtQuerySystemInformation with SuperfetchInformationClass (, however this call requires elevation, so it has no use in this case. Second place where this information can be obtained is WMI (CIMV2, Win32_DeviceMemoryAddress). This method is not as accurate as SuperfetchInformationClass, but I decided to use it in my PoC. Information returned on VMware test system were 100% accurate, and the slowdown disappeared, however I was still experiencing slowdown on my host machine. I come up with really simple and ugly solution: I’ve added hardcoded <0xF0000000-0xFFFFFFFF> region to the ranges returned from WMI. At this point PoC successfully runs on both VMware test machine (Win10 x64 TH2) and my host machine (Win10 x64 RS1):

  Whoami: secret\user
  Found wininit.exe PID: 000002D8
  Looking for wininit.exe EPROCESS...
  EPROCESS: wininit.exe, token: FFFF8A06105A006B, PID: 2D8
  Stealing token...
  Stolen token: FFFF8A06105A006B
  Looking for MsiExploit.exe EPROCESS...
  EPROCESS: MsiExploit.exe, token: FFFF8A0642E3B957, PID: CAA8
  Reusing token...
  Whoami: nt authority\system

Over-engineered version of PoC can be found on github (Visual Studio 2015 recommended):

It has hard-coded EPROCESS field offsets, so it only works on Win10 x64 TH2/RS1. PoC should work with any version of NTIOLib and WinIO drivers. I haven’t fully analyzed RTCore interface due to the fact, that I found it just today, so obviously it is not included in PoC.

If anyone has any suggestions, thoughts or just want to point out some mistakes, please leave a comment below.


  1. > So, it seems that not only ASUS drivers allows unprivileged reading and writing to physical memory

    That’s by far not only ASUS. Quite opposite, it applies to virtually any vendor providing own GPU control software. Do you realize that access to physical memory from user mode is the only way to directly access GPU registers space from any GPU-oriented software? So you can find similar IOCTLs in any driver from ASUS, GB, MSI, EVGA branded software as well as in a LOT of custom drivers from vendor independent applications like GPU-Z or HwInfo.


  2. Summarizing, any GPU software offering some functionality unexposed by AMD/NVIDIA drivers (e.g. access to VRM or SMC monitoring on AMD GPUs) comes with own driver providing low-level access to GPU registers and very similar IOCTLs will be located inside it. GPU registers are accessed via MMIO, so basic GPU access strategy for such type of software is based on reading GPU registers aperture BAR from PCI configuration registers, mapping it to application address range via IOCTLs you’ve discovered and working with GPU registers directly via this mapped range. So you’ll find similar functionality everywhere. Have no ideas why ASUS then MSI were sleeping so long instead of documenting that.


    1. The thing is, that they should limit this API. So the exposed interface would allow to modify mentioned GPU registers (and other stuff that they need) and not the full physical memory, ports, MSRs etc. With the current state of those drivers, they’re practically subverting the whole Windows security model.


      1. In ideal secure OS and ideal world – yep, definitively in should be limited. But sadly such user mode MMIO approach outlined above is really wide spread and de-facto for tools directly accessing hardware under Windows so “they” in your context apply to dozens of ISVs and IHVs and a lot of various applications installed on almost any PC. So it will be close to impossible to see every existing driver and application protected against such usage.
        At least I’ll protect RTCore from my side, MMIO IOCTLs will require specifying PCI device location / address pair and mapping will be performed only if the address is located within specified device MMIO aperture.


      2. RTCore32/64 memory mapping IOCTLs are now modified to allow memory mapping to be performed for hardware IO reserved address ranges only:
        – Valid PCI device location (bus, device, function) and BAR index must be passed to IOCTL when mapping a physical memory address
        – IOCTL handler read specified PCI device BAR from PCI configuration space and ensures that requested physical memory address range is located inside the device MMIO aperture, mapping is restricted if target address range is outside the aperture
        – Fixed VGA BIOS image address range (0xC0000-0xDFFFF) is also whitelisted for mapping


  3. Trying to update this for W7x64 for fun but it’s not working and I can’t figure out. I’m assuming its the PoolHeaderDelta. The remaining offsets are easy to find with windbg but am I missing something below?

    //* POOL_HEADER = 0x10
    //* OBJECT_HEADER = 0x38
    //* Total Size = 0xC8


    1. Some of those structures are optional, I haven’t really checked which of them. You can look at any EPROCESS in WinDbg and just find the address of “Proc” pool tag, and then you can calculate PoolHeaderDelta for Win7x64


  4. I tried to do something similar on x86, Windows 10 rs1703 but I cant seem to find all Proc’s, thus finding EPROCESS structures for both processes was unreliable. Was this reliable for x64? If so, do you know what that might be? I mapped 1.4 gig of \dev\PhysicalMemory from user mode (Had a bug that allowed me to smash the SecurityDescriptor DACL for the Section Object mapped in System).


  5. Could you please do a POC for windows 10 [Version 10.0.19042.1620] ?
    I don’t know how to retreive the constants you used…
    (It would be nicer to make another small script that gets the constants for the actual system and creates an include file dynamically.


Leave a Reply

Your email address will not be published. Required fields are marked *