Na Shape, nos deparamos com muitos trechos suspeitos de JavaScript. Encontramos scripts que são injetados maliciosamente em páginas; eles podem ser enviados por um cliente em busca de orientação, ou nossas equipes de segurança podem encontrar na web um recurso que parece fazer referência específica a alguns aspectos do nosso serviço. Como parte da nossa rotina diária, mergulhamos de cabeça nos scripts para entender o que fazem e como funcionam. Geralmente, eles são minificados, muitas vezes ofuscados e sempre exigem vários níveis de modificação antes de estarem realmente prontos para uma análise aprofundada.
Até pouco tempo atrás, a maneira mais fácil de fazer essa análise era usar configurações armazenadas em cache localmente, que permitem a edição manual, ou proxies para reescrever o conteúdo em tempo real. A solução local é a mais conveniente, mas os sites nem sempre funcionam perfeitamente em outros ambientes, e isso muitas vezes leva as pessoas a passar horas solucionando problemas antes mesmo de conseguirem começar a trabalhar. Os proxies são extremamente flexíveis, mas geralmente são trabalhosos e pouco portáteis — cada pessoa tem sua própria configuração personalizada para o ambiente e algumas estão mais familiarizadas com um proxy do que com outro. Comecei a usar o Chrome e seu protocolo DevTools para interceptar solicitações e respostas à medida que ocorrem e modificá-las em tempo real. Essa abordagem funciona em qualquer plataforma que tenha o Chrome, evita uma série de problemas e se integra bem às ferramentas comuns de JavaScript. Neste artigo, mostrarei como interceptar e modificar JavaScript em tempo real usando o protocolo DevTools do Chrome.
Usaremos Node, mas grande parte do conteúdo pode ser adaptada para a linguagem de sua preferência, desde que você tenha acesso fácil aos hooks do DevTools.
Antes de mais nada, se você nunca explorou a automação do Chrome por meio de scripts, Eric Bidelman escreveu um excelente Guia de primeiros passos com o Chrome Headless. As dicas apresentadas nele se aplicam tanto ao Chrome Headless quanto ao Chrome com interface gráfica (com uma peculiaridade que abordarei na próxima seção).
Como iniciar o Chrome
Usaremos a biblioteca chrome-launcher do npm para facilitar esse processo.
chrome-launcher faz exatamente o que você imagina e permite usar, sem alterações, as mesmas opções de linha de comando que você já usa no terminal (uma ótima lista é mantida aqui). Usaremos as seguintes opções:
–window-size=1200,800
- Defina automaticamente o tamanho da janela para um valor razoável.
–auto-open-devtools-for-tabs
- Abra automaticamente as ferramentas de desenvolvedor, pois as usamos com frequência.
–user-data-dir=/tmp/chrome-testing
- Defina um diretório de dados do usuário fixo. (O ideal seria não precisarmos disso, mas o modo não headless no Mac OSX aparentemente não permite interceptar requisições sem essa flag. Se você souber de uma maneira melhor, fale comigo pelo Twitter!)

Tente executar seu script para verificar se você consegue abrir o Chrome. Você deverá ver algo parecido com isto:

Usando o protocolo Chrome DevTools
Isso também é chamado de “protocolo de depuração do Chrome”, e os dois termos parecem ser usados de forma intercambiável na documentação do Google. Primeiro, instale o pacote chrome-remote-interface via npm, que oferece métodos práticos para interagir com o protocolo do DevTools. Tenha a documentação do protocolo à mão caso queira se aprofundar no assunto.
Para usar o CDP, você precisa se conectar à porta do depurador e, como estamos usando a biblioteca chrome-launcher, ela pode ser acessada facilmente por meio de chrome.port.
Muitos dos domínios do protocolo precisam ser habilitados primeiro, e vamos começar pelo domínio Runtime para podermos nos conectar à API do console e encaminhar para a linha de comando todas as chamadas de console feitas no navegador.

Agora, ao executar seu script, você terá uma janela do Chrome totalmente funcional, que também exibirá todas as mensagens do console no seu terminal. Isso por si só já é incrível, especialmente para fins de teste!
Interceptando requisições
Primeiro, precisamos registrar o que queremos interceptar enviando uma lista de RequestPatterns para setRequestInterception. É possível interceptar tanto no estágio “Request” quanto no estágio “HeadersReceived” e, para realmente modificar uma resposta, precisamos aguardar “HeadersReceived”. O tipo de recurso corresponde aos tipos que normalmente aparecem no painel de rede das ferramentas de desenvolvedor.
Não se esqueça de habilitar o domínio Network como você fez acima com Runtime, adicionando Network.enable() ao mesmo array.

Registrar o manipulador de eventos é relativamente simples, e cada solicitação interceptada vem com um interceptionId que pode ser usado para consultar informações sobre a solicitação ou, posteriormente, permitir que ela prossiga. Aqui, estamos apenas intervindo e registrando no terminal cada solicitação que interceptamos.

Modificando requisições
Para modificar as requisições, precisaremos instalar algumas bibliotecas auxiliares para codificar e decodificar strings em base64. Há muitas bibliotecas disponíveis; fique à vontade para escolher a que preferir. Usaremos atob e btoa.
A API para lidar com respostas é um pouco complicada. Para processá-las, você precisa incluir toda a lógica de resposta na interceptação da solicitação (em vez de simplesmente interceptar uma resposta, por exemplo) e, em seguida, consultar o corpo usando o ID da interceptação. Isso ocorre porque o corpo pode não estar disponível no momento em que o manipulador é chamado, e essa abordagem permite aguardar explicitamente apenas o que você está procurando. O corpo também pode vir codificado em base64, portanto, é recomendável verificar isso e decodificá-lo antes de simplesmente repassá-lo.

Neste ponto, você pode fazer o que quiser com o JavaScript. Seu código coloca você no meio de uma resposta, permitindo tanto acessar todo o JavaScript solicitado quanto retornar a resposta modificada. Incrível! Vamos apenas ajustar o JS adicionando um console.log ao final dele, para que nosso terminal receba uma mensagem quando o código modificado for executado no navegador.
Não podemos simplesmente repassar apenas um corpo modificado, pois o conteúdo pode entrar em conflito com os cabeçalhos enviados com o recurso original. Como você está testando e fazendo ajustes ativamente, provavelmente é melhor começar pelo básico antes de se preocupar demais com outras informações de cabeçalho que precise transmitir. Se necessário, você pode acessar os cabeçalhos da resposta por meio de responseHeaders passado ao manipulador de eventos, mas, por enquanto, vamos apenas criar nosso próprio conjunto mínimo em um array para facilitar a manipulação e a edição posteriormente.

Para enviar a nova resposta, é necessário criar uma resposta HTTP completa, codificada em base64 (incluindo a linha de status HTTP), e enviá-la por meio de uma propriedade rawResponse no objeto passado para continueInterceptedRequest.

Agora, quando você executar seu script e navegar pela internet, verá algo parecido com o seguinte no terminal, à medida que seu script intercepta o JavaScript e também conforme o JavaScript modificado é executado no navegador e as chamadas a console.log() são propagadas pelo hook que criamos no início do tutorial.

O código completo e funcional do exemplo básico está aqui:

Próximos passos
Você pode começar formatando o código-fonte para facilitar a leitura, o que é sempre uma maneira útil de iniciar a engenharia reversa de algo. Sim, é claro que você pode fazer isso na maioria dos navegadores modernos, mas é melhor controlar pessoalmente cada etapa da modificação para manter a consistência entre diferentes navegadores e versões e conseguir ligar os pontos à medida que analisa o código-fonte. Quando estou investigando um código desconhecido e ofuscado, gosto de renomear variáveis e funções conforme começo a entender a finalidade delas. Modificar JavaScript com segurança não é nada trivial e daria um post à parte, mas, por enquanto, você pode usar algo como unminify para desfazer técnicas comuns de minificação e ofuscação.
Você pode instalar o unminify via npm e envolver o corpo do seu novo JavaScript em uma chamada a unminify para vê-lo em ação:

Vamos nos aprofundar nas transformações no próximo post. Se você tiver alguma dúvida, comentário ou outro truque interessante, entre em contato comigo pelo Twitter!
Sobre o autor
Publicações relacionadas no blog

Entrega e proteção de agentes de IA em fábricas de IA: F5 para a arquitetura de referência NVIDIA OpenShell para agentes de IA seguros
As soluções da F5 ampliam a Arquitetura de Referência NVIDIA OpenShell para agentes de IA seguros.
Como medir – e minimizar – o custo total de propriedade do seu Kubernetes
O Kubernetes pode ser baixado e executado gratuitamente, mas sua manutenção está longe de não ter custos. Assim como outras grandes plataformas de código aberto que são “gratuitas” apenas em um sentido simplista, o Kubernetes quase certamente exigirá investimentos para ser implantado e operado.
As-Code: a pilha de TI contínua
Infraestrutura, configuração, pipeline, operações. De repente, tudo é “como código”. Lori MacVittie busca esclarecer a terminologia e identificar os diferentes componentes de uma stack contínua de TI.
O estado da entrega de aplicações em 2017: automação, APIs e DevOps
Explore o relatório State of Application Delivery de 2017. Leia o blog retrospectivo da F5 sobre o início da ascensão das redes declarativas e dos pipelines de DevOps.
DevOps Summit: onde a rede foi convidada para a festa
No DevOps Summit NY, falou-se muito não apenas sobre DevOps, mas também sobre contêineres, IoT e microsserviços. As sessões se concentraram não só na mudança cultural necessária para crescer em escala com uma abordagem DevOps, mas também fizeram questão de incluir...
F5 e Nutanix convergem em infraestrutura no .NEXT
Aplicações empresariais como Microsoft SharePoint e Exchange, juntamente com a VDI, continuam sendo a espinha dorsal da sua empresa. A produtividade, embora não seja tão atraente quanto o lucro, é um fator essencial para o sucesso das operações empresariais atuais. Deixar de...