Analyzing Your Applications with Windows Application Verifier

 

Michael Howard
Secure Windows Initiative

December 3, 2003

Summary: Michael Howard provides a tutorial on how to use the Windows Application Verifier, which helps you build better applications by making common errors easily testable and discoverable. (5 printed pages)

Building secure applications requires many disciplines—better coding, better testing, fuzz-testing, good designs, code inspection, defensive mechanisms, checklists, and tools. Tools are very useful, but as you can see from the list, it is only one aspect of building secure applications. In my opinion, tools are used to enforce policy, not ship secure code. There is simply no replacement for human intellect, and teaming great designers, testers, and developers with great tools can help deliver more secure software.

In this column, based on a document written by the Microsoft Windows® Application Experience team, I will outline one such tool named the Windows Application Verifier, and explain how you can use it as part of the end-to-end development process.

The Windows Application Verifier

The Windows Application Verifier (or AppVerifier for short) is a test tool designed to help developers and testers create better applications. It was designed by the Windows Application Experience group and Windows Base team in an effort to make many common programmer errors easily testable and discoverable. The AppVerifier watches your application when it is running and monitors its behavior by intercepting certain API calls. Sitting between your application and the operating system, it has the ability to verify an application's behavior by checking the parameters passed to API functions, injecting erroneous inputs to test the application's ability to recover from errors, logging changes to the registry and file system, and doing other API-level sanity checks. The user can enable various tests like RegistryChecks, PageHeap, and FileChecks to check for appropriate use of the registry, the heap, and the file system, respectively. These tests log warnings and generate debugger stops to help developers track down incorrect or dangerous behavior in their applications.

Developers in the Windows group at Microsoft have used the Application Verifier internally to check for application stability, compatibility, and security errors as part of a better-code-quality regimen. The latest version of the Application Verifier contains new checks that uphold more stringent secure coding practices than previous versions of the tool. Over time, we will augment the list of security checks.

Obtaining the Application Verifier

The latest version of the Application Verifier is available at https://www.microsoft.com/windows/appcompatibility/appverifier.mspx. For general information on using the AppVerifier, see Testing Applications with AppVerifier. Version 2.5 is available for use on Windows 2000 SP3, Windows XP, and Windows Server 2003. Developers are urged to always use the SecurityChecks in the AppVerifier to test their applications before shipping them because end users have access to the AppVerifier and are able to find any security holes that it exposes.

How the Windows Application Verifier Enforces Security Coding Policy

During security testing for Windows Server 2003, the Windows Application Verifier team recommended that the following minimum tests be enabled to check applications for security compliance:

  • PageHeap
  • Handles
  • Locks
  • SecurityChecks

The first three tests check for the proper use of kernel resources and memory. The goal of the SecurityChecks test is to ensure that applications run safely in the Windows environment.

Many developers unwittingly write code that is susceptible to attack by malicious programs simply by setting security attributes incorrectly or by misusing common API functions. The SecurityChecks test is designed to catch and log situations that may allow unwanted code to be executed so that developers can make their code more secure.

The AppVerifier enforces security standards in the following ways:

  1. Warns the user about ACLs that apply questionable security controls.
  2. Checks that CreateProcess and other related APIs are used safely.
  3. Warns the user when using functions that are susceptible to buffer overrun.
  4. Checks for safe socket API settings.

Checking for Properly ACLed Resources

Checking for properly ACLed resources is the most important function of the SecurityChecks test. The most common way that developers make their code susceptible to attack is by improper use of Discretionary Access Control Lists (DACLs), which specify what groups can access a particular resource and what kind of access they have. A number of APIs that create resources take a DACL as one of their parameters. One of the simplest and most effective ways for the AppVerifier to confirm that resources have secure access permissions is by intercepting these API calls and checking that the DACL parameter is valid. The AppVerifier logs an error whenever it encounters a NULL DACL or any DACL, which gives unnecessary permissions to all users (for example, allows all users to change access permissions or to delete a resource).

Unlike a NULL Security Descriptor, which sets default security control for an object, a NULL DACL creates an object with no control, allowing any user to access and modify it. This also gives any user the ability to change the DACL for that object so that the creator of the object can no longer access it. In spite of the danger of creating NULL DACLs, the AppVerifier commonly encounters them when performing security passes on applications. Why do programmers persist in creating NULL DACLs in spite of this danger? Programmers often create NULL DACLs because they are either overwhelmed by the amount of work involved in creating a DACL or think that a NULL DACL provides sufficient security because using a NULL DACL does not cause the code to break.

In addition, the Windows Application Verifier considers it erroneous to allow any user who is not an administrator to change the access permissions or owner of a resource. It incorporates code that allows the AppVerifier to check the security descriptor of a securable object even when the open mode for the object does not return security information. This enables the AppVerifier to check DACLs in many more situations than it could before.

The AppVerifier also checks the access permissions whenever a file is copied or moved across volumes. Checking DACLs when a file is moved across volumes is necessary because a file's security attributes are not preserved when the file is moved to a different volume, which means that the file is not guaranteed to be protected unless the calling application explicitly checks the security descriptor of the file after moving it.

Later versions of the AppVerifier may also give users the option to log all changes to DACLs so they can easily sanity-check their access permission policies.

Checking for Proper Use of CreateProcess

Calls to the CreateProcess API function are subject to attack if the command line parameter is not specified correctly. The AppVerifier generates an error if CreateProcess (or other various related API functions, like CreateProcessAsUser, ShellExecute, or WinExec) is called with a NULL lpApplicationName parameter and an lpCommandLine parameter that contains unquoted spaces. For example, the AppVerifier does not allow the following call to CreateProcess:

   CreateProcess(NULL,
          "c:\program files\sample.exe –t –g c:\program files\sample\test",
          ...);

Using this command line, an application can inadvertently execute unwanted code if a malicious user installs their program to c:\program. The CreateProcess API executes the first white-space delimited executable in the command line if the lpApplicationName parameter is NULL. Malicious code execution can be easily avoided with CreateProcess by ensuring that any command line that contains spaces is enclosed in quotes if the application name is NULL. The following modification makes the CreateProcess call secure:

   CreateProcess(NULL,
          "\"c:\program files\sample.exe\" –t –g c:\program files\sample\test",
          ...);

Warning for Risk of Buffer Overrun

The PageHeap test can check for buffer overruns occurring in the heap, but has no mechanism in place to warn users when their applications are susceptible to buffer overrun. There are a number of user-input functions that make user programs susceptible to unintentional code execution (see Fix Those Buffer Overruns! for more information). It is not feasible to check for all of them because many applications still rely heavily on them. However, the AppVerifier does currently warn users when they use the gets function because developers are told frequently enough that they should replace all instances of it in their code with calls to fgets or ReadConsole in order to avoid buffer overruns.

Enforcing Safe Use of Socket APIs

The AppVerifier checks that applications that invoke socket functions use the SO_EXCLUSIVEADDRUSE flag. Doing so prevents unwanted hosts using SO_REUSEADDR from binding onto a port and hijacking the host. The following is an example of the kind of socket usage that the AppVerifier warns against:

//
// This sample demonstrates the incorrect way of binding
// a socket. Specifically, it excludes the use of the 
// SO_EXCLUSIVEADDRUSE flag. Without theis flag, multiple
// sockets can be bound to the same port.
//
#include <winsock2.h>

int
main(
    int   argc,
    char* argv[]
    )
{
    BOOL        bVal = TRUE;
    SOCKET      sock;
    sockaddr_in sin;

    //
    // External function to initialize the Winsock library.
    //
    if (!InitWinsock()) {
        return -1;
    }

    //
    // Create our socket using UDP.
    //
    sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

    if (sock == INVALID_SOCKET) {
        return -1;
    }

    //
    // External function to initialize the sockaddr_in structure.
    //
    if (!InitSockAddr(&sin, argv[1], 8391)) {
        closesocket(sock);
        return -1;
    }

    //
    // Here we make the mistake of not using the SO_EXCLUSIVEADDRUSE flag.
    //
    if (!setsockopt(sock,
                    SOL_SOCKET,
                    SO_REUSEADDR,
                    (char*)&bVal,
                    sizeof(bVal))) {
        closesocket(sock);
        return -1;
    }

    //
    // Bind the socket and continue the rest of our work.
    //
    if (bind(sock, (sockaddr*)&sin, sizeof(sockaddr_in) == SOCKET_ERROR)) {
        closesocket(sock);
        return -1;
    }

    //
    // Sending and receiving code not shown in the example.
    //
    // ...

    closesocket(sock);

    return 1;
}

In this example, failing to use the SO_EXCLUSIVEADDRUSE flag allows a malicious host using SO_REUSEADDR to bind to the socket and intercept messages that it should not be allowed to see.

Is the AppVerifier Enough?

Passing the SecurityChecks test in AppVerifier is merely a first step in making sure that your application has applied to security policy. The SecurityChecks test is general enough to apply to all applications, but accordingly may not check all of the conditions necessary to ensure safe execution of your particular application. For example, when flagging incorrect DACLs, the AppVerifier can only flag those that are never (or rarely ever) correct in any application. It is still up to the developer to decide exactly what level of control is needed for any particular object and create DACLs accordingly. Developers are always advised to give no more access than necessary to no more users than necessary, depending on the specific application and the needs of its users.

Future Plans

Currently, the SecurityChecks test only warns about a small number of exploitable behaviors in applications. Newer versions of the Windows Application Verifier will detect other vulnerabilities such as weak encryption keys, potential heap overruns, and issues that prevent an application from running for a non-administrator. Additional columns will focus on this new functionality. Stay Tuned!

Spot the Secure Flaw

A number of people found the security defect in my last column—the call to sprintf should use %.10s, not %10s.

Okay, now to this little gem. What's wrong with this code? It's a code sample I saw recently on outlining a safe way to write buffer overrun-free code.

void noOverflow(char *str)
{
char buffer[10];
strncpy(buffer,str,(sizeof(buffer)-1));
buffer[(sizeof(buffer)-1)]=0;
/* Avoiding buffer flow with the above two lines */
}

Michael Howard is a Senior Security Program Manager in the Secure Windows Initiative group at Microsoft and is the coauthor of Writing Secure Code, now in its second edition, and the main author of Designing Secure Web-based Applications for Windows 2000. His main focus in life is making sure people design, build, test, and document nothing short of a secure system. His favorite line is "One person's feature is another's exploit."