Ghostwire: Instrumentação em tempo de execução semelhante ao Frida para JavaScript em navegadores
Expert Network Defense Engineer
Resumo:
- Ghostwire é uma instrumentação em tempo de execução para JavaScript no navegador, na mesma categoria que o Frida. Ambos observam um limite de função ao vivo em vez de ler o código fonte e esperar. O alvo difere: o Frida instrumenta processos nativos e móveis, enquanto o Ghostwire instrumenta JavaScript dentro do Chrome.
- O transporte é o Protocolo DevTools do Chrome sobre um pipe do sistema operacional. O Ghostwire inicia o Chrome com
--remote-debugging-pipe, portanto, não há porta de depuração para se conectar e nenhuma dependência de terceiros para instalar no núcleo. - Os hooks são pontos de interrupção do depurador, não wrappers. O objeto de função ao vivo nunca é substituído, então
fn.toString()continua retornando a fonte original. - A parte interessante é o oráculo. Você captura pares reais
(entrada → saída), escreve uma reimplementação candidata e deixa o Ghostwire provar que está errada com um contraexemplo concreto. - Uma execução ao vivo rejeitou uma candidata plausível. Doze pares capturados verificaram a função correta 12/12; uma versão que faltava um único
+1coincidiu 0/13 e produziu a exata entrada que falhou. - Ghostwire é uma ferramenta local do Chrome hoje. Ele não se conecta a um endpoint CDP WebSocket remoto, portanto, atualmente não pode dirigir a sessão do Scraping Browser Sem Anotações.
- Use-o apenas onde você estiver autorizado. Comece com uma conta gratuita da Scrapeless para coleta de dados de navegador ordinária, que é um trabalho diferente da análise em tempo de execução.
Ler JavaScript ofuscado dá a você uma hipótese. Executá-lo dá a você um fato. Ghostwire existe para transformar o primeiro no segundo: ele observa o que uma função realmente retorna em tempo de execução, depois verifica sua reimplementação contra essa verdade fundamental e relata toda entrada onde você estava errado.
O Que É o Ghostwire
Ghostwire é uma biblioteca de instrumentação em Python para JavaScript executando dentro do Chrome. Ele se conecta através do Protocolo DevTools do Chrome, define hooks em objetos de função ao vivo, grava argumentos e valores de retorno, e compara uma implementação candidata com o comportamento capturado.
A versão usada aqui é 0.1.0 no commit do upstream a3fe4ef1e27804ab1e976e28dfa1801ee53aabf8. Ela requer Python 3.9 ou posterior e licença MIT, e o núcleo não possui dependências de terceiros porque o protocolo é executado sobre um pipe do sistema operacional em vez de uma biblioteca de cliente WebSocket.
Este guia usa uma página local sintética durante todo o tempo. A instrumentação em tempo de execução pertence à mesma categoria de autorização que qualquer outra ferramenta de segurança: analise sistemas que você possui ou tem permissão explícita para testar.
Ghostwire e Frida: Onde a Analogía se Mantém
Chamar o Ghostwire de "Frida para JavaScript do navegador" é uma abreviação útil, e é preciso quanto ao método em vez da implementação.
| Preocupação | Frida | Ghostwire 0.1.0 |
|---|---|---|
| Alvo principal | Processos nativos, aplicativos móveis, binários de desktop | JavaScript executando no Chrome |
| Mecanismo de anexo | Injeta um agente no processo alvo | Anexa uma sessão de depuração sobre CDP |
| Mecanismo de hook | Interceptor em endereços de função nativa | Debugger.setBreakpointOnFunctionCall em objetos de função ao vivo |
| Superfície de script | Agente JavaScript dentro do processo | Python conduzindo o protocolo de fora |
| Dependência central | Tempo de execução e bindings do Frida | Nenhuma; CDP sobre um pipe do sistema operacional |
API JavaScript do Frida executa um agente dentro do processo que está inspecionando. O Ghostwire nunca coloca código na página. Essa diferença é deliberada: um ponto de interrupção no objeto de função ao vivo deixa o objeto não modificado, então a própria verificação fn.toString() da página vê a fonte original.
Onde a analogia se mantém é no modelo de trabalho. Ambos substituem a leitura estática por observação ao vivo, e ambos são para análise autorizada em vez de coleta de dados de produção.
Pré-requisitos
- Python 3.9 ou posterior.
- Google Chrome ou Chromium instalado localmente.
- Um checkout do repositório Ghostwire em um commit fixo.
- Apenas alvos que você possui ou está autorizado a analisar.
Instalação
O núcleo não tem pacotes para instalar, então um checkout fixo é toda a configuração:
bash
git clone https://github.com/sofianeelhor/ghostwire.git
cd ghostwire
git checkout a3fe4ef1e27804ab1e976e28dfa1801ee53aabf8
Leia pyproject.toml antes de executar qualquer coisa. Ele declara version = "0.1.0", requires-python = ">=3.9", e uma lista dependencies vazia, com mcp>=1.0 apenas como um extra opcional para o servidor MCP.
O Alvo Sintético
Este fixture é servido a partir do localhost e calcula um valor deliberadamente simples. Ele não contém credenciais, lógica anti-bot e nenhum código de terceiros:
python
PAGE = b"""<!doctype html><html><body><script>
function transform(name, value) {
return btoa(name + ':' + (value * 7 + 1));
}
window.transform = transform;
let i = 0;
setInterval(function () {
window.lastResult = transform('sample' + i, 10 + i);
i += 1;
}, 120);
</script></body></html>"""
O temporizador é importante. Ghostwire captura chamadas à medida que elas acontecem, então um limite que é exercido repetidamente produz um corpus sem nenhuma interação.
Anexar e Vincular
ghostwire.attach() inicia o Chrome, liga as sondas padrão e retorna um Inspector:
python
import ghostwire
with ghostwire.attach("http://localhost:8000/", headless=True) as gw:
gw.wait(1.0)
gw.hook("window.transform", capture_returns=True, label="transform")
gw.wait(1.5)
corpus = gw.corpus("transform")
print("pares capturados:", len(corpus))
capture_returns=True registra o valor de retorno juntamente com os argumentos, o que torna o corpus utilizável como verdade absoluta. O label agrupa esses pares para verificação posterior.
Pronto para coletar dados de página comuns em vez de analisar o comportamento em tempo de execução? Abra uma conta gratuita no Scrapeless e use um navegador gerenciado para esse trabalho.
Verificar um Candidato Contra a Realidade
Você fornece uma reimplementação e o Ghostwire a executa contra o corpus capturado em uma página isolada que não pode ver a função real:
python
good = "(name,value)=>btoa(name+':'+(value*7+1))"
wrong = "(name,value)=>btoa(name+':'+(value*7))"
good_result = gw.verify("window.transform", good, label="transform")
wrong_result = gw.verify("window.transform", wrong, label="transform")
O segundo candidato é a falha realista: sintaticamente correto, semanticamente incorreto em uma unidade. Um modelo que lê código minificado produz esse tipo de erro regularmente e sobrevive a uma revisão casual porque a saída ainda parece uma base64 válida.
Script Executável Completo
O contêiner usado para verificação precisava de dois ajustes, ambos ambientais e não defeitos do Ghostwire: o Chrome requer --no-sandbox quando executado como root, e a primeira navegação HTTP é lenta o suficiente para exceder o prazo padrão de 20 segundos do CDP.
python
import base64
import http.server
import socketserver
import sys
import threading
from pathlib import Path
HERE = Path(__file__).resolve().parent
UPSTREAM = next(
p / "upstream" for p in (HERE, *HERE.parents)
if (p / "upstream" / "ghostwire").is_dir()
)
sys.path.insert(0, str(UPSTREAM))
from ghostwire import Browser, Engine, Tracer
from ghostwire.api import Inspector
from ghostwire.crypto import CryptoLogger
from ghostwire.dataflow import DataflowTracer
from ghostwire.objects import LiveObjects
from ghostwire.oracle import Oracle
from ghostwire.origin import OriginTracer
from ghostwire.probes import NetLog, ScriptWatcher
def attach_local(url, extra_flags):
"""Mesma ligação que ghostwire.attach(), com flags do Chrome seguras para o contêiner."""
browser = Browser(headless=True, extra_flags=extra_flags)
browser.cdp.default_timeout = 90.0
engine = Engine(browser=browser)
scripts, net, tracer = ScriptWatcher(), NetLog(), Tracer()
for probe in (scripts, net, tracer):
engine.add_probe(probe)
oracle = Oracle(engine, tracer)
engine.start()
engine.navigate(url)
return Inspector(
engine, scripts, net, tracer, oracle,
OriginTracer(engine), DataflowTracer(engine),
LiveObjects(engine), CryptoLogger(engine),
)
PAGE = b"""<!doctype html><html><body><script>
function transform(name, value) {
return btoa(name + ':' + (value * 7 + 1));
}
window.transform = transform;
let i = 0;
setInterval(function () {
window.lastResult = transform('sample' + i, 10 + i);
i += 1;
}, 120);
</script></body></html>"""
class Handler(http.server.BaseHTTPRequestHandler):
def do_GET(self):
self.send_response(200)
self.send_header("Content-Type", "text/html")
self.send_header("Content-Length", str(len(PAGE)))
self.end_headers()
self.wfile.write(PAGE)
def log_message(self, *args):
pass
class QuietServer(socketserver.TCPServer):
allow_reuse_address = True
def handle_error(self, request, client_address):
pass
server = QuietServer(("127.0.0.1", 0), Handler)
port = server.server_address[1]
threading.Thread(target=server.serve_forever, daemon=True).start()
gw = attach_local(
f"http://localhost:{port}/",
["--no-sandbox", "--disable-dev-shm-usage"],
)
try:
gw.wait(1.0)
gw.hook("window.transform", capture_returns=True, label="transform")
gw.wait(1.5)
corpus = gw.corpus("transform")
good = "(name,value)=>btoa(name+':'+(value*7+1))"
```pt
errado = "(nome, valor)=>btoa(nome+':'+(valor*7))"
bom_resultado = gw.verify("window.transform", bom, label="transform")
errado_resultado = gw.verify("window.transform", errado, label="transform")
fresco = [["alpha", 1], ["beta", 2], ["", 0]]
resultado_fresco = gw.verify("window.transform", bom, fresh_inputs=fresco)
discrepância = errado_resultado["mismatches"][0]
esperado_decodificado = base64.b64decode(discrepância["expected"]).decode()
obtido_decodificado = base64.b64decode(discrepância["got"]).decode()
assert len(corpus) >= 5
assert bom_resultado["verified"] is True
assert errado_resultado["verified"] is False
assert resultado_fresco["verified"] is True
print("versão do ghostwire: 0.1.0")
print("pares capturados:", len(corpus))
print("candidato correto:", bom_resultado["verified"],
"testado:", bom_resultado["tested"], "correspondido:", bom_resultado["matched"])
print("candidato errado:", errado_resultado["verified"],
"testado:", errado_resultado["tested"], "correspondido:", errado_resultado["matched"])
print("entrada de contraexemplo:", discrepância["input"])
print("contraexemplo esperado:", esperado_decodificado)
print("contraexemplo obtido:", obtido_decodificado)
print("entradas frescas verificadas:", resultado_fresco["verified"])
print("entradas frescas testadas:", resultado_fresco["tested"])
finally:
gw.close()
servidor.shutdown()
O que a Execução Retornou
text
versão do ghostwire: 0.1.0
pares capturados: 12
candidato correto: True testado: 12 correspondido: 12
candidato errado: False testado: 13 correspondido: 0
entrada de contraexemplo: ['sample8', 18]
contraexemplo esperado: sample8:127
contraexemplo obtido: sample8:126
entradas frescas verificadas: True
entradas frescas testadas: 3
O candidato errado combinou 0 de 13, e não alguns deles. Porque o +1 ausente desloca todos os resultados, o corpus rejeita em toda parte. Um erro mais sutil produziria uma correspondência parcial, que é exatamente o sinal de que um corpus é bom em expor.
O contraexemplo é concreto: a entrada ['sample8', 18] deveria produzir sample8:127 porque 18 * 7 + 1 = 127, e o candidato produziu sample8:126. Essa é uma diferença decodificada e utilizável, em vez de uma falha booleana.
A verificação com entradas frescas verificou 3 entradas sem corpus anterior. O Ghostwire chamou a função ao vivo para valores que nunca havia observado, cobrindo casos extremos que o temporizador nunca gerou, incluindo a string vazia.
Limites Honestros: Ghostwire e o Navegador de Scraping
Atualmente, o Ghostwire não pode conduzir uma sessão do Navegador de Scraping Sem Scraping, e a razão é arquitetônica em vez de um problema de configuração.
Browser.__init__ inicia um processo local do Chrome com --remote-debugging-pipe=JSON, duplica dois descritores de arquivo no filho e os entrega a PipeConnection. Cada mensagem de protocolo flui através desses descritores. O Navegador de Scraping expõe um endpoint WebSocket remoto em vez disso, e o Ghostwire não tem um transporte WebSocket para se comunicar com ele.
Engine(browser=...) aceita um objeto semelhante a um navegador, que é uma verdadeira costura de extensão — o próprio runner deste artigo o utiliza para passar flags do Chrome seguras para contêiner. Mas, fazer com que ele alcance um endpoint remoto exigiria um transporte WebSocket compatível com PipeConnection, além de mudanças no ciclo de vida, já que Browser.close() assume que possui um processo local que pode encerrar. Esse trabalho não existe upstream, então trate uma ponte como um esboço de design em vez de um caminho suportado.
A divisão prática é clara. Use o Ghostwire localmente para análise de tempo de execução autorizada. Use o Navegador de Scraping para coleta de dados em larga escala baseada em navegador, que é o que a visão geral do Protocolo Chrome DevTools cobre.
Solucionando Problemas
O Chrome sai imediatamente e o pipe quebra
Um BrokenPipeError de cdp.py significa que o Chrome morreu durante a inicialização. Executar como root em um contêiner é a causa usual; passe --no-sandbox e --disable-dev-shm-usage através de extra_flags.
CDPError: timeout: Page.navigate
A navegação excedeu o prazo de conexão. Aumente browser.cdp.default_timeout antes de criar o Engine. No contêiner usado aqui, a primeira navegação para 127.0.0.1 levou cerca de 15 segundos, enquanto localhost resolveu em cerca de 0.1 segundos, então apenas o nome do host mudou o resultado.
O hook não captura nada
A função não estava definida quando o hook foi configurado. Aguarde a inicialização da página e confirme que a expressão se resolve avaliando typeof window.yourFunction primeiro.
A verificação passa, mas o corpus é pequeno
Um punhado de pares prova muito pouco. Exercite mais a fronteira ou forneça fresh_inputs cobrindo arestas que o tráfego observado nunca alcançou.
Conclusão
A ligação de função é a metade fácil. A metade que muda a forma como você trabalha é o oráculo, que verifica sua reimplementação em relação ao comportamento registrado e devolve a entrada onde falhou. Nesta execução, essa entrada foi `['sample8', 18]`, e o candidato falhou porque ignorou um único `+1`.
Mantenha o escopo honesto. Ghostwire é um instrumento de pesquisa no Chrome local para sistemas que você está autorizado a analisar, e atualmente não se comunica com pontos de extremidade de navegador remoto.
[Comece com o plano gratuito Scrapeless](https://app.scrapeless.com/passport/login/?utm_source=website&utm_medium=blog&utm_campaign=scrapingbrowser&utm_term=ghostwire-browser-javascript-instrumentation) para coleta de dados de navegador gerenciada e revise [os preços do Scrapeless](https://www.scrapeless.com/pt/pricing?utm_source=website&utm_medium=blog&utm_campaign=scrapingbrowser&utm_term=ghostwire-browser-javascript-instrumentation) quando essa carga de trabalho crescer.
## FAQ
**Q: O Ghostwire é realmente o Frida para o navegador?**
Ele ocupa o mesmo papel — instrumentação de tempo de execução ao vivo em vez de leitura estática — mas as implementações diferem. O Frida injeta um agente em um processo nativo; o Ghostwire anexa uma sessão de depuração ao Chrome e nunca coloca código na página.
**Q: Por que o Ghostwire usa um pipe em vez de um WebSocket?**
`--remote-debugging-pipe` evita abrir uma porta de depuração que o JavaScript da página poderia descobrir e elimina a necessidade de uma dependência do WebSocket no núcleo.
**Q: A ligação de uma função muda o que a página vê?**
Não para as verificações de detecção habituais. As ligações são definidas com `Debugger.setBreakpointOnFunctionCall` no objeto ao vivo, de modo que o objeto não é envolto e `fn.toString()` ainda retorna a fonte original.
**Q: O que o oráculo de verificação realmente prova?**
Que seu candidato correspondeu à função real em cada entrada testada. É uma evidência limitada pela cobertura, não uma prova de equivalência, então um resultado positivo com 12 pares significa menos do que um com ampla cobertura de casos extremos.
**Q: O Ghostwire pode se conectar ao Scrapeless Scraping Browser?**
Não. O Ghostwire 0.1.0 lança e possui um Chrome local por meio de um pipe do SO, enquanto o Scraping Browser expõe um ponto de extremidade CDP WebSocket remoto. Conectá-los exige um transporte WebSocket e gerenciamento de ciclo de vida remoto que o upstream não fornece.
**Q: É seguro executar isso contra qualquer site?**
Não. A instrumentação em tempo de execução deve ser limitada a sistemas que você possui ou tem autorização por escrito para testar. Revise os termos do alvo e <a href="https://datatracker.ietf.org/doc/html/rfc9309" rel="nofollow"><strong>diretivas de robôs</strong></a>, e obtenha aconselhamento jurídico antes de analisar código de terceiros.
Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.



