Archives

Archives / 2008 / September
  • Visual C++ in Short: Unblock downloaded applications

    Raymond Chen published an article earlier this week about ShellExecute hooks and it reminded me of a shell hook of sorts that still exists in Windows Vista despite Raymond’s very good argument that shell hooks are often used for evil and certainly can’t be used to add security. Granted this hook is not a ShellExecute hook in the literal sense but it amounts to the same thing.

    Since I’m long overdue for a Visual C++ in Short article I thought I’d talk about a security feature in Windows that isn’t, well, secure. I’ve previously written about User Account Control and cannot say enough about it being a major step forward in providing a more secure environment for normal users to work and surf the web (with Internet Explorer). But there is this little so called security feature that the Windows shell introduced prior to Windows Vista that somehow came along for the ride but never quite got the UAC makeover and that is the topic of this article.

    Many applications are distributed simply as an executable without an installer. You can for example go to live.sysinternals.com and get various great system tools by simply downloading the executable and running it. Similarly you can download the Window Clippings executable and run it without first going through an installer.

    Downloading an application and then running it often results in the following dialog box appearing before the application starts:



    There are so many things wrong with this dialog box that I don’t really know where to start. Perhaps the most glaring problem is that it is neither consistent nor integrated into the secure desktop prompt used by User Account Control. Granted, the UAC prompt is not used since the application does not need to be elevated but the inconsistency is still jarring for the user.

    A more serious problem is that, unlike UAC, this security warning is not coming from the Windows kernel but merely from the ShellExecute function called by Windows Explorer. A simple way to demonstrate this is by using the command prompt instead of Windows Explorer to run the downloaded application:



    Sure enough there’s no security warning and the application runs immediately.

    What about some code?

    Here’s the equivalent of what Windows Explorer does, hardcoded for this example:

    ::ShellExecute(0, 0, L"C:\\Data\\WindowClippings.exe", L"/options", 0, SW_SHOWNORMAL);

    And here’s the equivalent of what the command prompt does:

    WCHAR commandLine[] = L"C:\\Data\\WindowClippings.exe /options";
    ::CreateProcess(0, commandLine, 0, 0, FALSE, 0, 0, 0, &si, &pi);


    In fact you can even use ShellExecute’s big brother and tell it to forgo the security warning:

    SHELLEXECUTEINFO info = { sizeof(info) };
    info.fMask = SEE_MASK_FLAG_DDEWAIT | SEE_MASK_NOZONECHECKS;
    info.nShow = SW_SHOWNORMAL;
    info.lpFile = L"C:\\Data\\WindowClippings.exe";
    info.lpParameters = L"/options";

    ::ShellExecuteEx(&info);


    The magic flag in this case is SEE_MASK_NOZONECHECKS.

    It might seem that developers should just avoid ShellExecute(Ex) but that is far from the truth. It does add valuable features such as initiating the UAC prompt if needed, whereas CreateProcess would simply fail with ERROR_ELEVATION_REQUIRED.

    As the application developer you can’t control whether the user will run your application using the command prompt or by simply double clicking the executable. Arguably the latter is far more common. Short of providing an installer there isn’t much you can do to avoid this security warning. All is not lost however. Take another look at the security warning:



    Most users when confronted with obstacles like this one seem to just do whatever they can to overcome them as quickly as possible so they can perform the task they originally intended to perform. It is then not surprising that they completely overlook the little check box in the corner. All they need to do is clear this check box and Windows will no longer prompt them for this application. This check box only exists to annoy the user further. Think about it. Once the user has run the application a single time there’s no reason to prompt the user again in the name of security since a malicious application would have already had the opportunity to cause any intended harm. In fact the application could even unblock itself once it has been given an opportunity to execute some code!

    To understand how this is possible we need to take a look at how Windows Explorer knows to block an application from running directly. For starters, you can quickly determine whether an application is blocked by looking at its properties window:



    Notice the security warning at the bottom. Clicking the Unblock button has the same effect as clearing the check box in the security warning dialog box. When the application was first downloaded, the browser created an alternate NTFS data stream where it stored zone information. You can view this information using the command prompt:



    Unblocking the application simply involves removing this data stream. Although not foolproof, such as when the application does not have the file permission to delete the data stream, it works in most cases.

    Start by getting the full path to the executable. You can do this with the QueryFullProcessImageName function that was introduced with Windows Vista. If needed, you can fall back to the slightly less reliable GetModuleFileNameEx function for previous versions of Windows. Now simply append ":Zone.Identifier" to the path and call the DeleteFile function to get rid of it. Here is a complete example. The UnblockProcessImage function returns S_OK if the file was unblocked, S_FALSE if it wasn’t blocked to begin with, or another HRESULT describing what went wrong.

    HRESULT UnblockProcessImage()
    {
       static const WCHAR streamName[] = L":Zone.Identifier";

       WCHAR fileName[MAX_PATH + _countof(streamName)] = { 0 };
       DWORD length = MAX_PATH; // Deliberately not the actual size

    #if _WIN32_WINNT >= 0x0600

       // QueryFullProcessImageName updates the length parameter.
       if (!::QueryFullProcessImageName(::GetCurrentProcess(),
                                        0, // flags
                                        fileName,
                                        &length))
       {
           return HRESULT_FROM_WIN32(::GetLastError());
       }

    #else

       // GetModuleFileNameEx returns the length.
       length = ::GetModuleFileNameEx(::GetCurrentProcess(),
                                      0, // module
                                      fileName,
                                      length);

       if (0 == length)
       {
           return HRESULT_FROM_WIN32(::GetLastError());
       }

    #endif

       wcscpy_s(fileName + length, _countof(fileName) - length, streamName);

       if (!::DeleteFile(fileName))
       {
           const DWORD error = ::GetLastError();

           if (ERROR_FILE_NOT_FOUND == error)
           {
               return S_FALSE;
           }
           else
           {
               return HRESULT_FROM_WIN32(error);
           }
       }

       return S_OK;
    }

    Please understand that I’m not advising developers to do this. I’m merely demonstrating that it is possible. A better solution is to simply ship your product with an installer, which is exactly what the next version of Window Clippings will provide.

    Produce the highest quality screenshots! Use Window Clippings.

  • Window Clippings v3 Sneak Peek – New Options Window – Take 2

    I’ve updated the layout a bit to reduce the focus on the configurations. I felt that the configuration selection was too prominent in the earlier builds and I also wanted to make the window simpler and more approachable.



    Here you can see the options corresponding to the Image tab in version 2 of Window Clippings. There’s nothing really new here aside from the new color picker. I’ve never liked the built-in color picker that Windows provides so I decided to bake it into the options window even though it’s not a very necessary feature.