Análise da Política de Segurança DragDrop do Sandbox do Internet Explorer

  1. A política DragDrop no sandbox do IE

Uma das formas estudadas de evasão do sandbox do Internet Explorer envolve o abuso de programas listados na chave ElevationPolicy. De maneira análoga, o IE mantém uma configuração chamada DragDrop, responsável por controlar se uma operação de arrastar-e-soltar pode cruzar a fronteira do sandbox:

HKLM\Software\Microsoft\Internet Explorer\Low Rights\DragDrop

Os valores possíveis dessa política são:

  • 0: a janela de destino não é um drop target válido — operação negada;
  • 1: a janela é um drop target válido, mas o conteúdo não pode ser copiado;
  • 2: o IE exibe um diálogo de confirmação para o usuário e, se confirmado, copia o conteúdo;
  • 3: a operação de arrastar-e-soltar é permitida silenciosamente.

Em uma instalação limpa do Windows 8.1, a chave DragDrop contém, por padrão, iexplore.exe, explorer.exe e notepad.exe, todos com valor 3.

  1. Comportamento do Explorer como destino do arrasto

Mesmo com a política configurada para 3, arrastar um arquivo do IE para uma janela do Explorer pode fazer com que o próprio Explorer exiba um prompt de confirmação. No entanto, essa confirmação não ocorre quando o destino é o painel lateral de árvore de diretórios do Explorer. Essa inconsistência pode ser explorada para transferir dados para processos de maior privilégio, desde que seja possível simular a operação de arrasto por código a partir do sandbox.

  1. Arrasto OLE sem movimento real do mouse

O mecanismo de arrastar-e-soltar do Windows é baseado em COM e define três interfaces principais:

  • IDropSource: representa a origem do arrasto;
  • IDropTarget: representa o destino do arrasto;
  • IDataObject: transporta os dados sendo arrastados.

A função ole32!DoDragDrop orquestra o processo. Internamente, ela cria uma instância de CDragOperation e, em um laço, atualiza o destino, processa mensagens e finaliza a operação. O núcleo da ação ocorre em ole32!PrivDragDrop. Ao invocar essas rotinas internas diretamente, é possível reproduzir o arrasto sem depender do posicionamento do cursor:

HRESULT SimularDrop(HWND hWndDest, IDataObject* pDataObject)
{
    GetPrivateClipboardWindow(CLIP_CREATEIFNOTTHERE);
    CreateSharedDragFormats(pDataObject);

    void* pMarshalBuffer = nullptr;
    HRESULT hr = GetMarshalledInterfaceBuffer(
        IID_IDataObject,
        pDataObject,
        &pMarshalBuffer);

    if (FAILED(hr))
        return hr;

    DWORD   dwEffect = 0;
    POINTL  pt       = { 0, 0 };
    void*   pDragCtx = nullptr;

    hr = PrivDragDrop(
        hWndDest,
        DRAGOP_ENTER,
        pMarshalBuffer,
        pDataObject,
        MK_LBUTTON,
        pt,
        &dwEffect,
        0,
        &pDragCtx);

    if (SUCCEEDED(hr))
    {
        hr = PrivDragDrop(
            hWndDest,
            DRAGOP_OVER,
            nullptr,
            nullptr,
            MK_LBUTTON,
            pt,
            &dwEffect,
            0,
            &pDragCtx);

        if (SUCCEEDED(hr))
        {
            HWND hClip = GetPrivateClipboardWindow(CLIP_QUERY);

            hr = PrivDragDrop(
                hWndDest,
                DRAGOP_DROP,
                pMarshalBuffer,
                pDataObject,
                0,
                pt,
                &dwEffect,
                hClip,
                &pDragCtx);
        }
    }

    return hr;
}

Essas funções não são exportadas; seus endereços são obtidos por meio de offsets fixos dentro do ole32.dll. O handle da janela de destino pode ser resolvido com FindWindow ou variações, e a origem pode construir um IDataObject contendo um formato CF_HDROP.

  1. Construindo um IDataObject para arquivo

Uma das maneiras mais simples de obter um IDataObject para um arquivo é utilizar as classes de suporte a OLE do MFC. O exemplo abaixo aloca um bloco HGLOBAL no formato DROPFILES e o associa a um COleDataSource:

IDataObject* CriarObjetoDeArquivo(const CString& caminho)
{
    COleDataSource* pFonte = new COleDataSource();
    IDataObject*    pObj   = nullptr;

    UINT cbTamanho = sizeof(DROPFILES) +
                     sizeof(TCHAR) * (caminho.GetLength() + 2);

    HGLOBAL hGlobal = GlobalAlloc(GHND | GMEM_SHARE, cbTamanho);
    if (!hGlobal)
        return nullptr;

    DROPFILES* pDrop = static_cast<DROPFILES*>(GlobalLock(hGlobal));
    if (pDrop)
    {
        pDrop->pFiles = sizeof(DROPFILES);

        TCHAR* pDest = reinterpret_cast<TCHAR*>(
            reinterpret_cast<LPBYTE>(pDrop) + sizeof(DROPFILES));

        lstrcpy(pDest, caminho);
        GlobalUnlock(hGlobal);

        FORMATETC fmt = {
            CF_HDROP,
            nullptr,
            DVASPECT_CONTENT,
            -1,
            TYMED_HGLOBAL
        };

        pFonte->CacheGlobalData(CF_HDROP, hGlobal, &fmt);
        pObj = static_cast<IDataObject*>(
            pFonte->GetInterface(&IID_IDataObject));
    }
    else
    {
        GlobalFree(hGlobal);
    }

    return pObj;
}
  1. Como o IE executa o arrasto dentro do sandbox

Quando o usuário arrasta algo dentro de uma aba do IE em modo protegido, a operação não é concluída no próprio processo da aba. Em vez disso, o processo filho envia uma mensagem ALPC para o processo principal do IE, que então executa a chamada real a DoDragDrop. A cadeia de chamadas se parece com:

Processo filho (sandbox):
  MSHTML!CDoc::DoDrag
    MSHTML!CDragDropManager::DoDrag
      combase!ObjectStubless
        ... ALPC ...

Processo principal do IE:
  ... recebe ALPC ...
    RPCRT4!Invoke
      IEFRAME!CShdocvwBroker::PerformDoDragDrop
        IEFRAME!CShdocvwBroker::PerformDoDragDropThreadProc
          ole32!DoDragDrop
  1. Restrições de segurança impostas pelo sandbox

Um código executando dentro do sandbox pode criar um IIEUserBroker e, a partir dele, obter uma referência a IShdocvwBroker, alcançando PerformDoDragDrop no processo principal. Isso permite chegar até DoDragDrop com parâmetros controlados:

typedef HRESULT (__stdcall *PFnCriarUserBroker)(IIEUserBroker**);

IIEUserBrokerPtr CriarBrokerUsuario()
{
    HMODULE hLib = LoadLibrary(L"iertutil.dll");
    if (!hLib)
        return nullptr;

    PFnCriarUserBroker fnCriar = reinterpret_cast<PFnCriarUserBroker>(
        GetProcAddress(hLib, reinterpret_cast<LPCSTR>(58)));

    if (!fnCriar)
        return nullptr;

    IIEUserBrokerPtr pBroker;
    return SUCCEEDED(fnCriar(&pBroker)) ? pBroker : nullptr;
}

IIEUserBrokerPtr pBroker = CriarBrokerUsuario();
IShdocvwBroker*  pVis     = nullptr;

pBroker->BrokerCreateKnownObject(
    CLSID_CShdocvwBroker,
    __uuidof(IShdocvwBroker),
    reinterpret_cast<IUnknown**>(&pVis));

pVis->PerformDoDragDrop(
    hWndDest,
    pDataObject,
    pDropSource,
    0,
    0,
    &dwOk,
    &lResultado);

Para simular o arrasto, restam duas abordagens possíveis:

  1. Invocar funções internas do ole32.dll no prcoesso principal: inviável, porque o código no sandbox só pode sair através da interface do broker, sem acesso direto ao espaço de endereçamento do processo principal.

  2. Alterar a posição do cursor: DoDragDrop utiliza a posição do mouse para determinar a janela de destino, mas o sandbox impede essa alteração.

  3. Bloqueio da movimentação do cursor e UIPI


Dentro do sandbox, chamadas como SendInput e SetCursorPos não funcionam. A SendInput é interceptada por um hook em IEShims.dll:

IEShims.dll!NS_InputQueueLowMIC::APIHook_SendInput
  IEFRAME!FrameUtilExports::PreSendInput
    ShimHelper::PreSendInput

É possível contornar o hook chamando diretamente user32!NtUserSendInput, obtida por offset. No entanto, a operação ainda falha silenciosamente por causa do UIPI (User Interface Privilege Isolation), mecanismo do kernel que valida o nível de integridade e o contexto de AppContainer entre processos. SetCursorPos sofre a mesma restrição.

A lógica de verificação no kernel, no Windows 8.1, pode ser resumida assim:

int VerificarAcessoPorNivelIntegridade(
    unsigned int ilOrigem,
    int          acOrigem,
    unsigned int ilDestino,
    int          acDestino)
{
    if (gbEnforceUIPI && ilOrigem < ilDestino)
        return 0;   // acesso negado

    if (gbEnforceUIPI && ilOrigem == ilDestino)
    {
        return acOrigem == acDestino ||
               acDestino == -1 ||
               acOrigem  == -1 ||
               SeIsParentOfChildAppContainer(
                   gSessionId,
                   acOrigem,
                   acDestino);
    }

    return 1;   // acesso permitido
}

Se o nível de integridade da origem for inferior ao do destino, a ação é negada. Quando os níveis são iguais e ambos executam dentro de um AppContainer, o kernel exige que o processo de origem seja pai do processo de destino. Essa verificação, realizada em win32k!CheckAccessForIntegrityLevel, impede que um processo de baixa integridade dentro do sandbox mova o cursor ou envie entrada para processos mais privilegiados, bloqueando efetivamente a simulação do arrasto.

Tags: Internet Explorer Sandbox DragDrop Policy UIPI OLE Drag and Drop ShdocvwBroker

Publicado em 10-26 19:09