Decompiling the vulnerable function for MS08-067

Oct 25, 2008

I spent a couple of hours tonight reversing the vulnerable code responsible for the MS08-067 vulnerability. This bug is pretty interesting, because it is in the same area of code as the MS06-040 buffer overflow, but it was completely missed by all security researchers and Microsoft. It's quite embarassing.

Here's the code of the vulnerable function on Windows XP SP3 and Vista SP1:

#include <wchar.h>

// This is the decompiled function sub_5B86A51B in netapi32.dll on XP SP3
// and sub_6EA11D4D on Vista SP1

int ms08_067(wchar_t* path)
{
    wchar_t* p;
    wchar_t* q;
    wchar_t* previous_slash = NULL;
    wchar_t* current_slash  = NULL;
    wchar_t  ch;

#ifdef VISTA
    int len = wcslen(path);
    wchar_t* end_of_path = path + len;
#endif

    // If the path starts with a server name, skip it

    if ((path[0] == L'\\' || path[0] == L'/') &&
        (path[1] == L'\\' || path[1] == L'/'))
    {
        p = path+2;

        while (*p != L'\\' && *p != L'/') {
            if (*p == L'\0')
                return 0;
            p++;
        }

        p++;

        // make path point after the server name

        path = p;

        // make sure the server name is followed by a single slash

        if (path[0] == L'\\' || path[0] == L'/')
            return 0;
    }

    if (path[0] == L'\0')   // return if the path is empty
        return 1;

    // Iterate through the path and canonicalize ..\ and .\

    p = path;

    while (1) {
        if (*p == L'\\') {
            // we have a slash

            if (current_slash == p-1)   // don't allow consequtive slashes
                return 0;

            // store the locations of the current and previous slashes

            previous_slash = current_slash;
            current_slash = p;
        }
        else if (*p == L'.' && (current_slash == p-1 || p == path)) {
            // we have \. or ^.

            if (p[1] == L'.' && (p[2] == L'\\' || p[2] == L'\0')) {
                // we have a \..\, \..$, ^..\ or ^..$ sequence

                if (previous_slash == NULL)
                    return 0;

                // example: aaa\bbb\..\ccc
                //             ^   ^  ^
                //             |   |  &p[2]
                //             |   |
                //             |   current_slash
                //             |
                //             previous_slash

                ch = p[2];

#ifdef VISTA
                if (previous_slash >= end_of_path)
                    return 0;

                wcscpy_s(previous_slash, (end_of_path-previous_slash)/2, p+2);
#else // XP
                wcscpy(previous_slash, &p[2]);
#endif

                if (ch == L'\0')
                    return 1;

                current_slash = previous_slash;
                p = previous_slash;

                // find the slash before p

                // BUG: if previous_slash points to the beginning of the
                // string, we'll go beyond the start of the buffer
                //
                // example string: \a\..\

                q = p-1;
                
                while (*q != L'\\' && q != path)
                    q--;

                if (*p == L'\\')
                    previous_slash = q;
                else
                    previous_slash = NULL;
            }
            else if (p[1] == L'\\') {
                // we have \.\ or ^.\ 

#ifdef VISTA
                if (current_slash != NULL) {
                    if (current_slash >= end_of_path)
                        return 0;
                    wcscpy_s(current_slash, (end_of_path-current_slash)/2, p+2);
                    goto end_of_loop;
                }
                else {  // current_slash == NULL
                    if (p >= end_of_path)
                        return 0;
                    wcscpy_s(p, (end_of_path-p)/2, p+2);
                    goto end_of_loop;
                }
#else // XP
                if (current_slash != NULL) {
                    wcscpy(current_slash, p+2);
                    goto end_of_loop;
                }
                else { // current_slash == NULL
                    wcscpy(p, p+2);
                    goto end_of_loop;
                }
#endif
            }
            else if (p[1] != L'\0') {
                // we have \. or ^. followed by some other char

                if (current_slash != NULL) {
                    p = current_slash;
                }
                *p = L'\0';
                return 1;
            }
        }

        p++;

end_of_loop:
        if (*p == L'\0')
            return 1;
    }
}

// Run this program to simulate the MS08-067 vulnerability

int main()
{
    return ms08_067(L"\\c\\..\\..\\AAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}