7

I am trapping for the execution of some old 16-bit applications that our internal folks should no longer be using. They are 1985 DOS apps, so trapping for them was easy... capture any process that launches under NTVDM.exe

Now, the problem is finding out which program NTVDM is actually running under the hood. Apparently there are a coupleof the 1985 programs that they SHOULD be allowed to run, so I need to see the actual EXE name that is hiding under NTVDM.

        WqlEventQuery query =
            new WqlEventQuery("__InstanceCreationEvent",
            new TimeSpan(0, 0, 1),
            "TargetInstance isa \"Win32_Process\"");

        ManagementEventWatcher watcher = new ManagementEventWatcher(query);

        watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);

        watcher.Start();


...


    static void watcher_EventArrived(object sender, EventArrivedEventArgs e)
    {
        ManagementBaseObject instance = (ManagementBaseObject)e.NewEvent["TargetInstance"];

        ProcessInfo PI = new ProcessInfo();
        PI.ProcessID = int.Parse(instance["ProcessID"].ToString());
        PI.ProcessName = instance["Name"].ToString();
        PI.ProcessPath = instance["ExecutablePath"].ToString();

        // Here's the part I need...
        PI.ActualEXE = ???;

        // ... do the magic on the PI class ...

        instance.Dispose();
    }

When I capture the instance information, I can get the command line, but the arguments are "-f -i10" ... There is no EXE name on the command line. Is there any other method/property I should be looking at to determine the EXE name of the 16-bit application that's actually running?

UPDATE: Let me refine the question: If I can find the NTVDM process, how can I -- programatically -- know the actual path to the EXE that is being executed underneath?

Thanks.

Jerry
  • 4,507
  • 9
  • 50
  • 79
  • What do you mean by "trapping for"? It sounds like you're polling to see what's running, when it might be better to block execution in the first place? – Hugh Allen Nov 09 '09 at 03:18
  • That's what I'm trying to do... kill apps that are running that shouldn't be running. But since it is a 16-bit application and it runs in a virtual environment, you can't just set up an AD policy. How else would you suggest to kill it? Post it as an answer and I'll gladly give the bounty to you. =) – Jerry Nov 09 '09 at 04:35
  • Done, although I'd still be interested to see an answer to your original question :) – Hugh Allen Nov 09 '09 at 05:08
  • I've done some tests with my approach on Windows Vista, and even after applying the patch (see link in my answer), it still doesn't work. It'll requires a call to MS I'm afraid. Also, MS's code does work on Windows XP: http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q182559&ID=KB;EN-US;Q182559 – Abel Nov 09 '09 at 18:54
  • What, out of curiosity, is wrong with my answer? Did it not work for you? – Hugh Allen Nov 09 '09 at 23:28
  • I see. I just read the FAQ on bounties. Abel's answer was probably accepted automatically. – Hugh Allen Nov 10 '09 at 01:00
  • @Hugh, sorry about that. Usually the reverse happens: bounties get neglected and no answer gets accepted (and none ever will, it's then disallowed). The bounty systems is kinda nasty and this is one of the nastier bits: the auto-accept. – Abel Nov 14 '09 at 23:19

3 Answers3

6

The trick is not to use VDMEnumProcessWOW (which gives the VDMs), but to use VDMEnumTasksWOW. The enumerator function that you pass to this function will be called for each 16 bit task in the specified VDM.

I haven't checked it myself, but according to the documentation, this library of CodeProject does exactly that, if you pass in the PROC16 enum value. It's C++, if you need help compiling that code and calling it from C#, let me know and I'll give you an example.

A program that uses this technique is Process Master, it comes with full source. I suggest you run it to find out whether it gives the info you need, and if so, you can apply this method to your own application (it doesn't run on Windows Vista or 7, it uses old VB5 code, apparently it's not compatible. It should run on XP).

If things with these functions do not go as planned, you may be on Vista and may need the hotfix described in this StackOverflow question, which points to downloading a hotfix, which is in turn described here:

"An application that uses the VDMEnumProcessWOW function to enumerate virtual DOS machines returns no output or incorrect output on a computer that is running a 32-bit version of Windows Vista"

Update: while this seems promising, I applied the patch, ran several versions of the code, including Microsoft's, and while they all work on XP, they fail silently (no error, or wrong return value) on Vista.


The "kinda" working code

Update: I experimented with (amongst others) with the following code, which compiles fine in C# (and can be written simpler, but I didn't want to run a marshal-mistake risk). When you add these functions, you can call Enum16BitProcesses, which will write the filenames of the EXE files of the 16 bit processes to the Console.

I can't run it on Vista 32 bit. But perhaps others can try and compile it, or find the error in the code. It would be nice to know whether it works on other systems:

public class YourEnumerateClass
{
    public static void Enum16BitProcesses()
    {
        // create a delegate for the callback function
        ProcessTasksExDelegate procTasksDlgt = 
             new ProcessTasksExDelegate(YourEnumerateClass.ProcessTasksEx);

        // this part is the easy way of getting NTVDM procs
        foreach (var ntvdm in Process.GetProcessesByName("ntvdm"))
        {
            Console.WriteLine("ntvdm id = {0}", ntvdm.Id);
            int apiRet = VDMEnumTaskWOWEx(ntvdm.Id, procTasksDlgt, IntPtr.Zero);
            Console.WriteLine("EnumTaskWOW returns {0}", apiRet);
        }
    
    }
    
    // declaration of API function callback
    public delegate bool ProcessTasksExDelegate(
        int ThreadId,
        IntPtr hMod16,
        IntPtr hTask16,
        IntPtr ptrModName,
        IntPtr ptrFileName,
        IntPtr UserDefined
        );
    
    // the actual function that fails on Vista so far
    [DllImport("VdmDbg.dll", SetLastError = false, CharSet = CharSet.Auto)]
    public static extern int VDMEnumTaskWOWEx(
        int processId, 
        ProcessTasksExDelegate TaskEnumProc, 
        IntPtr lparam);
    
    // the actual callback function, on Vista never gets called
    public static bool ProcessTasksEx(
        int ThreadId,
        IntPtr hMod16,
        IntPtr hTask16,
        IntPtr ptrModName,
        IntPtr ptrFileName,
        IntPtr UserDefined
        )
    {
        // using PtrToStringAnsi, based on Matt's comment, if it fails, try PtrToStringAuto
        string filename = Marshal.PtrToStringAnsi(ptrFileName);
        Console.WriteLine("Filename of WOW16 process: {0}", filename);
        return false;       // false continues enumeration
    }

}

Update: Intriguing read by the renown Matt Pietrek. Mind the sentence, somewhere near the end:

"For starters, MS-DOS-based programs seem to always run in separate NTVDM sessions. I was never able to get an MS-DOS-based program to run in the same session as a 16-bit Windows-based program. Nor was I able to get two independently started MS-DOS-based programs to run in the same NTVDM session. In fact, NTVDM sessions running MS-DOS programs don't show up in VDMEnumProcessWOW enumerations."

Seems that, to find out what processes are loaded, you'll need to write a hook into NTVDM or write a listener that monitors access to the file. When the application that tries to read a certain DOS file is NTVDM.exe, it's bingo. You may want to write a DLL that's only attached to NTVDM.exe, but now we're getting a bit ahead of ourselves. Long story short: this little ride into NTVDM has shown "possibilities" that appeared real hoaxes in the end.

There's one other way, but time is too short to create an example. You can poke around in the DOS memory segments and the EXE is usually loaded at the same segment. But I'm unsure if that eventually will lead to the same result and whether it's worth the effort.

Community
  • 1
  • 1
Abel
  • 56,041
  • 24
  • 146
  • 247
  • Reading those links I see no evidence that they answer the actual question. The VDM debug APIs only give info about win16 tasks - not DOS programs. – Hugh Allen Nov 09 '09 at 19:19
  • Both win16 tasks and 16 bit DOS programs are considered kinda equal by WOW. This line summarizes it *"In Windows NT, 16-bit applications run within a Virtual DOS Machine (VDM)"*, from here: http://support.microsoft.com/default.aspx?scid=KB;EN-US;Q182559&ID=KB;EN-US;Q182559 – Abel Nov 09 '09 at 19:33
  • Btw, I'm not trying to say that my answer is correct, I'm just saying that WOW also covers DOS programs. Here's a clear quote from Matt Pietrek, enough an authority to believe, I hope: *"In other words, programs for MS-DOS and 16-bit Windows are like children belonging to the adult NTVDM process. NTVDM is just another adult Win32 process, with essentially the same rights and privileges as any other Win32-based program. This architecture even has its own name, Windows On Windows (WOW)."*. In Column Under The Hood in MSJ: http://www.microsoft.com/msj/0898/hood0898.aspx – Abel Nov 09 '09 at 19:49
  • I tried this code but got gibberish for `filename` so I had to change `PtrToStringAuto` to `PtrToStringAnsi`. – Matt Lyons-Wood Dec 13 '13 at 02:36
2

This works for me:

  • Follow the instructions at Description of the Software Restriction Policies in Windows XP to open the local or domain policy editor.

  • Under Software Restriction Policies -> Additional Rules, right click and select New Hash Rule.

  • Browse to (for example) edit.com. Make sure Security Level is set to Disallowed. Click OK.

Now,

C:\>edit
The system cannot execute the specified program.

(I get the same results from command.com and cmd.exe -- under Win XP)

Hugh Allen
  • 6,509
  • 1
  • 34
  • 44
1

From this link about VDMDBG functions, you may be able to P/Invoke "VDMEnumProcessWOW()", then enumerate modules within the process using PSAPI.

Note Regarding 16-bit DOS Applications:

None of the VDMDBG functions work with 16-bit DOS applications. To enumerate DOS VDMs, you need to use another method. First, you could use VDMEnumProcessWOW() to make a list of all Win16 VDMs, and then enumerate all instances of NTVDM.exe using some other scheme (such as PSAPI). Any NTVDM.exe from the full enumeration that was not in the Win16 list is a DOS VDM. You can create and terminate 16-bit DOS applications with CreateProcess() and TerminateProcess().

Hope that helps...

Noah Heldman
  • 6,724
  • 3
  • 40
  • 40
  • Yeah. I've already seen this. It gives the same results (after much more work) as Process.GetProcessById(processID).Modules. While this does seem to be nearing the right track, it still doesn't answer the question at hand... how to get the EXE name. I'm beginning to think this is one area where I'm going to have to surrender. – Jerry Nov 03 '09 at 15:14
  • Ugh...sorry it wasn't more helpful. Best of luck! – Noah Heldman Nov 03 '09 at 22:39