December, 2012

  • The Old New Thing

    Why do some shortcuts not support editing the command line and other properties?

    • 27 Comments

    Ben L observed that some shortcuts do not permit the command line and other options to be edited. "Where is this feature controlled? Is there a way to override this mode?" This question is echoed by "Anonymous (Mosquito buzzing around)" (and don't think we don't know who you are), who in a huge laundry list of questions adds, "Why does the Game Explorer limit customizing command line, target, etc?"

    These questions are looking at the situation backwards. The issue is not "Why do these shortcuts block editing the command line?" The issue is "Why do some shortcuts allow editing the command line?"

    Recall that shortcuts are references to objects in the shell namespace. Shell namespace objects are abstract. Some of them refer to files, but others refer to non-file objects, like control panels, printers, and dial-up networking connectoids. And in the abstract, these objects support verbs like Open and Rename. But there is no requirement that a shell namespace object support "Run with command line argument".

    If you have a shortcut to an executable, then the LNK file handler says, "Okay, this is a special case. Executables support command line arguments, so I will run the executable with the command line arguments set by the IShell­Link::Set­Arguments method.

    Note that the shortcut target and arguments are separate properties. The LNK file property sheet hides this from you by calling IShell­Link::Get­Path and IShell­Link::Get­Arguments, then taking the two strings and combining them into a single Target field for display. When you save the changes, the LNK file property sheet takes the Target, figures out which part is the executable and which part is the arguments, and calls IShell­Link::Set­Path and IShell­Link::Set­Arguments on the two parts.

    In other words, the command line is all a ruse.

    This special action is performed only for executable targets, because those are the only things that accept arguments. If you create a shortcut to a control panel, you'll find that the Target is not editable. If you create a shortcut to a printer, you'll find that the Target is not editable. If you create a shortcut to a dial-up networking connectoid, you'll find that the Target is not editable. Having a non-editable command line is the normal case. The file system is the weirdo.

    Shortcuts to advertise applications and shortcuts to items in the Games folder are not shortcuts to executables. They are shortcuts into the shell namespace for various types of virtual data. An advertised application is a shell namespace object that represents "an installed application". It is not a pointer directly to the executable, but rather a reference to an entry in the MSI database, which in turn contains information about how to install the program, repair it, update it, and run it. The shell doesn't even know what the command line is. To launch an advertised shortcut, the shell asks the MSI database for the command line, and it then executes that command line that MSI returns. The value set by IShell­Link::Set­Arguments never enters the picture. Similarly, the entries in the Games Folder are not executables; they are entries in the games database.

    I can see how this can be confusing, because when you click on these shortcuts, a program runs, but these shortcuts are not shortcuts directly to programs. As a result, the code that takes a Target and Arguments and combines them into a command line does not get a chance to run.

  • The Old New Thing

    It rather involved being on the other side of this airtight hatchway: Writing to the application directory

    • 25 Comments

    We received a security vulnerability report that went roughly like this:

    There is a security vulnerability in the X component. It loads shell32.dll from the current directory, thereby making it vulnerable to a current directory attack. Here is a sample program that illustrates the problem. Copy a rogue shell32.dll into the current directory and run the program. Observe that the rogue shell32.dll is loaded instead of the system one.

    If you actually followed the instructions, what you saw depended on your definition of "run the program." Let's assume that the program has been placed in the directory C:\sample\sample.exe.

    1. Setting the current directory to the application directory.
      cd /d C:\sample
      copy \\rogue\server\shell32.dll
      c:\sample\sample.exe
      
      In this case, the attack succeeds.
    2. Setting the current directory to an unrelated directory.
      cd /d %USERPROFILE%
      copy \\rogue\server\shell32.dll
      c:\sample\sample.exe
      
      In this case, the attack fails.
    3. Running the application from Explorer.
      copy \\rogue\server\shell32.dll C:\sample
      double-click sample.exe in Explorer
      
      In this case, the attack succeeds.

    Let's look at case 3 first. In case 3, what is the current directory? When you launch a program from Explorer, the current directory is set to the directory of the thing you double-clicked. Therefore, case 3 is identical to case 1. That's one less case to have to study.

    We also see that the attack is not strictly a current directory attack, because the attack failed in case 2 even though a rogue shell32.dll was in the current directory.

    What we're actually seeing is an application directory attack.

    Recall that the application directory is searched ahead of the system directory. Therefore, you can override a file in the system directory by putting it in your application directory. This is part of the directory as a bundle principle. If you packaged a DLL with your application, then presumably that's the one you want, even if a future version of Windows decides to create a DLL of the same name.

    The vulnerability report sort of acknowledged that this was an application directory attack rather than a current directory attack when they explained why this is a serious problem:

    By placing a rogue copy of shell32.dll in the C:\Program Files\Microsoft Office\Office12 directory, an attacker can inject arbitrary code into all Office applications.

    If the attack were really a current directory attack, the attacker would have put a rogue copy of shell32.dll in the directory containing your Excel spreadsheet, not the directory containing EXCEL.EXE.

    And that's where you reach the airtight hatchway: Normal users do not have write permission into the C:\Program Files\Microsoft Office\Office12 directory. You need administrator privileges to create files there. And if you have administrator privileges, then you already pwn the machine. It's not really a vulnerability that you can do anything you want once you pwn the machine.

    Of course, this non-vulnerability does expose a security issue you need to bear in mind when you run your own programs: Your application's directory is its airtight hatchway. Make sure you control who you let in! If you leave your application directory world-writeable, then you've effectively left your airtight hatchway unlocked. This is one reason why the Microsoft Logo guidelines recommend (require?) that programs be installed into the Program Files directory: The default security descriptor for subdirectories of Program Files does not grant write permission to normal users. It's secure by default.

    There are many variations of this type of vulnerability report, and they nearly always are mischaracterized as a current directory attack. They usually go like this:

    There is a DLL planting vulnerability in LITWARE.EXE. Place a rogue DLL named SHELL32.DLL in the same directory as LITWARE.EXE. When LITWARE.EXE is run, the rogue DLL is loaded from the current directory, resulting in code injection.

    The person who submits the report has confused the application directory with the current directory, probably because they never considered that the two might be different.

    C:\> mkdir C:\test
    C:\> cd C:\test
    C:\test> copy \\trusted\server\LITWARE.EXE
    C:\test> copy \\rogue\server\SHELL32.DLL
    C:\test> LITWARE
    -- observe that the rogue DLL is loaded
    -- proof of current directory attack
    

    They never tried this:

    C:\> mkdir C:\test
    C:\> cd C:\test
    C:\test> copy \\trusted\server\LITWARE.EXE
    C:\> mkdir C:\test2
    C:\> cd C:\test2
    C:\test2> copy \\rogue\server\SHELL32.DLL
    C:\test2> ..\test\LITWARE
    -- observe that the rogue DLL is not loaded
    

    That second experiment shows that the attack is not a current directory attack at all. It's an application directory attack.

    Each time one of these reports comes in, we have to perform the same evaluation to confirm that it really is an application directory attack and not a current directory attack. (This means, among other things, repeating the test on every version of Windows, and every version of LitWare, and every combination of the two, just to make sure all the possibilities have been covered. The odds are strong that it will all turn into a false alarm, but who knows. Maybe there's something about the interaction between LitWare 5.2 SP2 and Windows XP SP3 that triggers a new code path that does indeed try to load shell32.dll from the current directory. And it's that specific combination of circumstances the person was trying to report, but did a bad job of expressing.)

  • The Old New Thing

    Replaying input is not the same as reprocessing it

    • 19 Comments

    Once upon a time, there was an application that received some input and said, "Okay, this input cancels my temporary state. I want to exit my temporary state, but I also want the input that took me out of the temporary state to go to whatever control would have received the input if I hadn't been in the temporary state in the first place." (For example, you might want the input that dismisses a pop-up window to be acted upon rather than eaten by the pop-up.) The application decided to solve this problem by regenerating the input message via Send­Input, so that it goes back into the input queue. The theory, is that when the message pump pulls the regenerated input out of the queue, the temporary state will not be present, and the message will be routed to the correct window.

    I raised concerns that this technique would create problems with input reordering and multiple-processing, but the customer decided to stick with their original design.

    Time passed, and I had forgotten about this application.

    Some months later, another question came in: "We find that when the system is under load, we sometimes get into a state where dismissing our temporary state results in the mouse button getting 'stuck' down. i.e., the user physically releases the mouse button, but we get spurious WM_LBUTTON­DOWN with no matching WM_LBUTTON­UP."

    The customer, it turns out, was the same one I had cautioned earlier about the dangers of replaying input.

    When you get input, that is your chance to process the input. If you decide you don't want to deal with the input right now and replay it via Send­Input, you create a few new problems:

    First, you've caused everybody else who is looking at input states to see a second copy of your replayed events. If it were a keyboard event you replayed, a keyboard hook (or any code which subclassed your window) would see a key go down twice. If there were any mouse hooks, they would see the button go down twice. This is particularly confusing because the mouse button doesn't autorepeat. How can it go Down two times in a row without an intervening Up?

    Second, if there is other input in your queue, you just rearranged input events. For example, suppose the input queue consists of the following events:

    WM_LBUTTON­DOWN
    WM_LBUTTON­UP

    You retrieve the first message (the button-down), resulting in the following input queue:

    WM_LBUTTON­DOWN
    WM_LBUTTON­UP

    For illustrative purposes, I crossed out the message that is no longer in the queue, so you can see where it used to be.

    Now you decide to replay that message via Send­Input. This appends the event to your queue, resulting in

    WM_LBUTTON­DOWN
    WM_LBUTTON­UP
    WM_LBUTTON­DOWN

    Your message pump runs, it processes the button-up event ("Huh? How did I get an Up without a Down?"), and then it processes the button-down event. There are no further events, so the mouse button is down and gets stuck that way.

    You can imagine what other sorts of bad things can happen if an event in the queue is, say, a press or release of the shift key. Oops, the user clicked the Delete button and then hit the shift key afterwards to type a capital letter A, but due to your input reordering, your code saw it as a Shift+Click on the Delete button, and the item was deleted without confirmation.

    When you get an input message, that is your chance to process it. If you decide that you want to hand the message off to somebody else, you have to do it during the processing of that message. If you try to process it at some other time, the input states may not be right.

  • The Old New Thing

    Why do I get notified for changes to HKEY_CLASSES_ROOT when nobody is writing to HKEY_CLASSES_ROOT?

    • 23 Comments

    A customer had a question about the Reg­Notify­Change­Key­Value function.

    We are using it to monitor the HKEY_CLASSES_ROOT tree as follows:

    RegNotifyChangeKeyValue(
        HKEY_CLASSES_ROOT,
        true, // monitor entire subtree
        REG_NOTIFY_CHANGE_NAME | REG_NOTIFY_CHANGE_LAST_SET,
        eventRegKeyChanged,
        true); // asynchronous mode
    

    If I understand the documentation correctly, this registers for notifications when subkeys are added, deleted, or when values are changed. However, it seems that my event becomes signaled at many other times, for example, when I switch folders in an Explorer window. I fired up Process Monitor and confirmed that nobody (not even Explorer) is writing to HKEY_CLASSES_ROOT.

    Why are we getting spurious notifications? Have we misunderstood what this function does?

    Everything is working as expected; it's just that your expectations are wrong.

    Recall that the HKEY_CLASSES_ROOT registry key is really a combined view of HKEY_LOCAL_MACHINE and HKEY_CURRENT_USER. Specifically, it is a combined view of HKEY_LOCAL_MACHINE\Software\Classes and HKEY_CURRENT_USER\Software\Classes. And HKEY_CURRENT_USER\Software\Classes is itself just an alias for HKEY_USERS\«SID»_Classes. Therefore, if you're going to look in Process Monitor, you need to be looking at all of those locations to see changes that eventually get reported as changes in HKEY_CLASSES_ROOT.

    In this particular case, Explorer was making changes to HKEY_USERS\«SID»_Classes\Local Settings, which shows up as HKEY_CLASSES_ROOT\Local Settings.

    Upon receiving this explanation, the customer understood what was going on, and also remarked that they were going to look to see if they could register their notification on a location that isn't quite so broad.

  • The Old New Thing

    The QuickCD PowerToy, a brief look back

    • 27 Comments

    One of the original Windows 95 PowerToys was a tool called QuickCD. Though that wasn't its original name.

    The original name of the QuickCD PowerToy was FlexiCD. You'd think that it was short for "Flexible CD Player", but you'd be wrong. FlexiCD was actually named after its author, whose name is Felix, but who uses the "Flexi" anagram as a whimsical nickname. We still called him Felix, but he would occasionally use the Flexi nickname to sign off an email message, or use it whenever he had to create a userid for a Web site (if Web sites which required user registration existed in 1994).

    You can still see remnants of FlexiCD in the documentation. The last sample INF file on this page was taken from the QuickCD installer.

  • The Old New Thing

    Have you found any TheDailyWTF-worthy code during the development of Windows 95?

    • 25 Comments

    Mott555 is interested in some sloppy/ugly code or strange workarounds or code comments during the development of Windows 95, like "anything TheDailyWTF-worthy."

    I discovered that opening a particular program churned the hard drive a lot when you opened it. I decided to hook up the debugger to see what the problem was. What I discovered was code that went roughly like this, in pseudo-code:

    int TryToCallFunctionX(a, b, c)
    {
      for each file in (SystemDirectory,
                        WindowsDirectory,
                        ProgramFilesDirectory(RecursiveSearch),
                        KitchenSink,
                        Uncle.GetKitchenSink)
      {
        hInstance = LoadLibrary(file);
        fn = GetProcAddress(hInstance, "FunctionX");
        if (fn != nullptr) {
            int result = fn(a,b,c);
            FreeLibrary(hInstance);
            return result;
        }
        fn = GetProcAddress(hInstance, "__imp__FunctionX");
        if (fn != nullptr) {
            int result = fn(a,b,c);
            FreeLibrary(hInstance);
            return result;
        }
        fn = GetProcAddress(hInstance, "FunctionX@12");
        if (fn != nullptr) {
            int result = fn(a,b,c);
            FreeLibrary(hInstance);
            return result;
        }
        fn = GetProcAddress(hInstance, "__imp__FunctionX@12");
        if (fn != nullptr) {
            int result = fn(a,b,c);
            FreeLibrary(hInstance);
            return result;
        }
        FreeLibrary(hInstance);
      }
      return 0;
    }
    

    The code enumerated every file in the system directory, Windows directory, Program Files directory, and possibly also the kitchen sink and their uncle's kitchen sink. It tries to load each one as a library, and sees if it has an export called FunctionX. For good measure, it also tries __imp__­FunctionX, FunctionX@12, and __imp__­FunctionX@12. If it finds any match, it calls the function.

    As it happens, every single call to Get­Proc­Address failed. The function they were trying to call was an internal function in the window manager that wasn't exported. I guess they figured, "Hm, I can't find it in user32. Maybe it moved to some other DLL," and went through every DLL they could think of.

    I called out this rather dubious programming technique, and word got back to the development team for that program. They came back and admitted, "Yeah, we were hoping to call that function, but couldn't find it, and the code you found is stuff we added during debugging. We have no intention of actually shipping that code."

    Well, yeah, but still, what possesses you to try such a crazy technique, even if only for debugging?

Page 3 of 3 (26 items) 123