June 7th, 2023

Pulling sleight of hand tricks in a security vulnerability report, episode 2

A security vulnerability report came in that claimed to have “found a way for privileged accounts to force the system to crash, and for non-privileged accounts to force the termination of any process.” They claim that they were exploiting a vulnerability in Message­Box.

They included a proof of concept, which went something like this.

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;

public class Program
{
  [DllImport("kernel32.dll")]
  static extern IntPtr OpenProcess(int access, bool inherit, int id);

  [DllImport("kernel32.dll")]
  static extern IntPtr VirtualAllocEx(IntPtr process, IntPtr address,
   int size, uint type, uint protection);

  [DllImport("kernel32.dll")]
  static extern bool WriteProcessMemory(IntPtr process, IntPtr address,
    IntPtr source, int size, out uint written);

  [DllImport("kernel32.dll")]
  static extern IntPtr CreateRemoteThread(IntPtr process,
   IntPtr attributes, uint stackSize, IntPtr address,
   IntPtr parameter, uint flags, out uint threadId);

  [DllImport("user32.dll")]
  static extern int MessageBox(IntPtr window, string text, string caption, uint type);

  [DllImport("user32.dll")]
  public static extern int EnableMenuItem(IntPtr menu, uint id, uint enable);

  public static void Main(string[] args)
  {
    Func<IntPtr, uint, uint, int> MessageBox = EnableMenuItem;
    GCHandle gcHandle = GCHandle.Alloc(MessageBox);
    IntPtr inject = GCHandle.ToIntPtr(gcHandle);
    int size = MessageBox.ToString().Length;

    int id = Process.GetProcessesByName(args[0])[0].Id;

    IntPtr process = OpenProcess(0x1F0FFF, false, id);

    IntPtr memory = VirtualAllocEx(process, IntPtr.Zero, size, 0x00001000, 0x40);
    uint written;
    WriteProcessMemory(process, memory, inject, size, out written);

    uint threadId;
    CreateRemoteThread(process, IntPtr.Zero, 0, memory, IntPtr.Zero, 0, out threadId);

  }
}

The instructions for running the proof of concept were very simple:

As administrator:
attack.exe svchost

As normal user:
attack.exe notepad (or any other process name)

As is customary of low-quality reports, the finder provides no explanation of what they’re attacking or how the attack works. They just attach a program and say “Good luck figuring out what I did!”¹

I mean, I like puzzles. But this is not the place for puzzles.

Here’s what’s going on.

First, the code gets the native address and size of the Message­Box function.

Next, they take the program name from the command line and find the process with that ID. (If there’s more than one, then they take the first one.)

Once they have the process ID, they use Open­Process to get a handle.

With the process handle, they use Virtual­Alloc­Ex to allocate (MEM_COMMIT = 0x00001000) read-write-execute data (PAGE_EXECUTE_READ­WRITE = 0x40) in the victim process, and then copy the Message­Box function into the process.

Finally, they inject a thread to execute the injected code.

Is this a security vulnerability?

Notice the first parameter to Open­Process: It is 0x1F0FFF = PROCESS_ALL_ACCESS.² If you can get “all access” rights to a process, then you pwn the process, and it’s therefore not suprising that you can inject code into it to make it crash.

In fact, if your goal is to crash the process, you don’t need to do all this nonsense. PROCESS_ALL_ACCESS includes PROCESS_TERMINATE, so this entire program could be simplified to

public class Program
{
  public static void Main(string[] args)
  {
    System.Diagnostics.Process.
        GetProcessesByName(args[0])[0].Kill();
  }
}

or a C# 9 one-liner,

System.Diagnostics.Process.GetProcessesByName(args[0])[0].Kill();

or avoid having to write any code at all: Run Task Manager, find the svchost.exe or notepad.exe you want to terminate, and click “End Task”.

Oh, and did you see the sleight of hand?

The report first says that an administrator can terminate any process, and they picked svchost. But then when they said that a non-administrator can also terminate any process, and somehow they switch from svchost to a lowly notepad.

That’s because when they tried having a non-administrator attack svchost, it didn’t work.

Somehow conveniently forgot to mention that.

Remember, you’re a researcher, not a student turning in a homework assignment. If you find evidence that runs counter to your hypothesis, you need to take it into consideration, not hide it and hope that nobody notices.

Bonus chatter: There are plenty of other wrong things about this vulnerability report. I’ll leave them as Easter Eggs for you to discover.

¹ I suspect that one of the reasons they don’t explain what their code does, or how the code accomplishes what it claims to do, is that they don’t know themselves. They just wrote some code, gosh it acts funny, must be a security vulnerability, send it to Microsoft!

² Specifically, it is the value of PROCESS_ALL_ACCESS from Windows XP. The value was upgraded in Windows Vista to 0x001FFFFF to include the “limited information” access bits, but this finder is apparently working from a very old worksheet.

Topics
Other

Author

Raymond has been involved in the evolution of Windows for more than 30 years. In 2003, he began a Web site known as The Old New Thing which has grown in popularity far beyond his wildest imagination, a development which still gives him the heebie-jeebies. The Web site spawned a book, coincidentally also titled The Old New Thing (Addison Wesley 2007). He occasionally appears on the Windows Dev Docs Twitter account to tell stories which convey no useful information.

11 comments

Discussion is closed. Login to edit/delete existing comments.

  • Alexis Ryan · Edited

    Strongly Suspect they tried to convert someone else's C code to do code injection into C#, messed it up and crashed the target process and thought the problem was with Windows, not their code. The intent was obviously to create code that would cause another process to display a message box, but this is not the way to do it. Setting the start address to the memory buffer? You had better make sure the buffer...

    Read more
  • a b · Edited

    There is indeed a message box related method of crashing the system which does not need admin mode or shutdown privilege.
    <code>

    EndTask somehow bypasses the protected process mechanism, terminating csrss which normally can't be terminated even with debug privilege.
    You can change WTSSendMessageW to NtRaiseHardError, another way to ask csrss to create a message box.

    Read more
    • Daniel Roskams · Edited

      That crashes Windows 10 systems, but it doesn't have any effect on Windows 7, Vista, XP, or 2000. It was a bug introduced in Windows 8.

      The reason EndTask "bypasses" the protected process mechanism is because EndTask is implemented as an inter-process call to CSRSS which basically asks CSRSS to terminate the process that owns the specified window handle (the function within CSRSS which implements this is called _EndTask). So when you pass a HWND which...

      Read more
  • Daniel Roskams

    From all the vulnerability reports that Microsoft receives, about what percentage of them are this sort of rubbish that, despite being obviously amateur, still has to be given at least a cursory glance? I can imagine there being plenty of script kids that think they’ve found a great new Security Hole(TM) and want to try and cash in.

  • Simon Geard

    It’s worse than that… running as an administrator, they can force the entire system to reboot or shut down!! 😉

  • 🌊#️⃣

    If you can get “all access” rights to a process, then you pwn the process, and it’s therefore not suprising that you can inject code into it to make it crash.

    Think you meant own the process, but pwn works as well 😁

    • Peter Cooper Jr.

      I suspect that Raymond did, in fact, intentionally use the word “pwn”.

  • Dan Bugglin · Edited

    WOW.

    Here is what I am seeing:

    1. First, GCHandle.Allow simply causes the object passed into it to not be garbage collected. The returned value has nothing to do with the object itself (MSDN explicitly points this out: "Normal handles are opaque, which means that you cannot resolve the address of the object it contains through the handle."), so the IntPtr they get doesn't mean anything. .NET can move data around in memory so the GCHandle cannot...

    Read more
    • George Tokmaji

      And they’re also leaking the handle returned by CreateRemoteThread, not that it matters much since the target process crashes anyway.

    • chrisok

      Amen to all of that. :-)

      Cannot resist to add these things though.

      Func MessageBox = EnableMenuItem;

      This just defines a delegate to call the (via P/Invoke) EnableMenuItem Win32 function. The actual MessageBox Win32 P/Invoke declaration above is never used and could have been just left out of the code. So the whole "vulnerability" is even less about MessageBox as it was before (not that it has ever been ;-)).

      As you said, "size" is simply the length of...

      Read more
    • Raymond ChenMicrosoft employee Author

      “Only thing that confuses me is how the thread crashing crashes the whole application.” The thread crashes, the exception goes unhandled, and that crashes the process.