pdfforensesegurancadocumentosfraudetamperlens

Como um PDF adulterado se revela: forense de estrutura, não de aparência

Douglas Haruo 12 min 24/08/2026

Vim de KYC/AML e compliance fiscal antes de construir o Tamperlens, e a intuição que carrego dessa vida é chata de repetir para quem está começando a olhar documento: abrir bem não é integridade. Um PDF que abre no leitor, mostra o carimbo certo, imprime bonito e devolve “200 OK” quando você faz o download não te diz nada sobre ter sido editado depois de assinado. A aparência é a camada que o adulterador controla. A estrutura é a que ele esquece.

Este post é sobre a estrutura — como um PDF é montado por dentro, onde a edição fica gravada mesmo quando a página parece intacta, e como raciocinar sobre isso. Não é um pitch. Se você sai daqui sabendo abrir um PDF num editor de bytes e entender o que está vendo, cumpri o que vim fazer.

As quatro partes de um PDF

Um PDF, no osso, tem quatro seções, nesta ordem no arquivo:

  1. Header — uma linha, %PDF-1.7 ou parecido.
  2. Body — a lista de objetos: páginas, fontes, imagens, o catálogo, os fluxos de conteúdo. Cada objeto é numerado (12 0 obj … endobj).
  3. Tabela de referências cruzadas (xref) — um índice que diz, para cada objeto, em que byte do arquivo ele começa. É o que permite ao leitor pular direto para a página 40 sem ler as 39 anteriores.
  4. Trailer — aponta para a raiz do documento e, crucialmente, diz onde a tabela xref começa, com a palavra-chave startxref seguida de um offset.

O leitor abre o arquivo, vai para o fim, lê o startxref, salta para a tabela xref, e a partir dela encontra todo o resto. Guarde isso: um PDF se lê de trás para frente. É essa decisão de design de 1993 que torna a adulteração visível.

O pecado original: a atualização incremental

Aqui está o detalhe que quase ninguém que não mexe com o formato conhece. Quando você edita um PDF e salva, o padrão não reescreve o arquivo. Ele anexa. Os bytes novos vão para o fim, seguidos de uma nova tabela xref que registra só o que mudou, seguida de um novo trailer com um startxref apontando para essa nova tabela.

E esse novo trailer carrega uma entrada /Prev: o offset da tabela xref anterior. Ou seja, cada revisão aponta para a que veio antes, formando uma cadeia encadeada para trás, até a original. O arquivo inteiro vira uma pilha de camadas de sedimento, e cada camada é uma vez que alguém abriu, mexeu e salvou.

Por que o padrão faz isso? Por dois motivos legítimos: permite salvar rápido (não precisa reescrever 200 páginas para trocar uma), e permite assinar sem invalidar assinaturas anteriores — o assinante seguinte anexa, não reescreve. É por isso que a atualização incremental existe, e é por isso que ela não é, por si só, prova de fraude. É prova de que o arquivo foi escrito mais de uma vez. O que estava na segunda vez é a próxima pergunta.

O Tamperlens tem uma família de sinais chamada incremental-updates que faz exatamente essa leitura: conta as revisões e, para cada uma que veio depois da primeira, lista quais objetos foram escritos e quais foram sobrescritos. Sobrescrever um objeto do tipo page, content ou image numa revisão posterior é a assinatura estrutural de “alguém mudou o que estava visível na página depois que o documento foi finalizado”. O objeto 12 existia; a revisão 3 escreveu um objeto 12 novo que a xref nova passa a apontar, e o antigo continua ali, enterrado, byte por byte, ainda legível para quem sabe onde procurar.

O primeiro jeito de errar: contar revisão demais

A parte honesta dessa história é que a contagem ingênua de revisões mente. Dois mecanismos perfeitamente normais escrevem duas seções de referência cruzada num único save:

  • Linearização (o “fast web view”, que deixa o PDF começar a renderizar antes de baixar inteiro) escreve uma segunda tabela xref por desenho.
  • Layout de referência híbrida — uma tabela clássica para leitores PDF 1.4 antigos, mais uma segunda que aponta para um /XRefStm — também escreve duas.

Se você conta seções de xref, um PDF recém-exportado do Word, salvo uma única vez, aparece como “duas revisões” e você grita fraude num arquivo virgem. O incremental-updates do Tamperlens trata cada um desses pares como um save — o parser reconhece o par de linearização e o par híbrido e os colapsa — e por isso a família fica calada num “fast web view” impecável e num Word, Excel ou PowerPoint comum sem assinatura. A contagem de revisões que ele reporta é deliberadamente menor que o número de startxref que você conta no arquivo bruto, e o próprio achado explica por quê, porque um analista que compara os dois números precisa saber a razão.

Isso é o que separa forense de teatro: um sinal que dispara no documento legítimo mais comum do mercado não é um sinal, é um gerador de falso positivo. Voltarei a esse ponto — ele é o assunto de outro post inteiro.

As causas benignas têm nome

Uma revisão extra genuína tem explicações inocentes, e o correto é nomeá-las, não escondê-las: assinatura, preenchimento de formulário, anotação. Cada uma anexa bytes legitimamente. A pergunta forense nunca é “tem mais de uma revisão?” — é “o que a revisão posterior tocou?”. Uma assinatura que anexa um objeto de assinatura é uma coisa. Uma “assinatura” que, na mesma anexação, sobrescreve o objeto de conteúdo da página 2 é outra completamente diferente, e a estrutura conta as duas com a mesma clareza.

/ByteRange: por que “assinado” não fecha a conversa

Agora a parte que mais gente entende errado, inclusive gente de compliance. Uma assinatura digital num PDF cobre um intervalo de bytes declarado, o /ByteRange. Ela não cobre magicamente “o documento”. Ela cobre os bytes que existiam quando foi aplicada.

E o que impede alguém de assinar, e depois anexar uma atualização incremental com bytes novos, além do fim do intervalo assinado? Nada, na estrutura do formato. A assinatura continua criptograficamente válida — ela cobre o que cobria — mas há bytes no arquivo que ela não cobre. O signature-coverage do Tamperlens compara o /ByteRange de cada assinatura contra o tamanho do arquivo e reporta exatamente essa diferença: quantos bytes ficaram além da cobertura, e onde a cobertura termina.

Repare no que essa família não faz: ela não verifica a criptografia da assinatura. Ela reporta cobertura. É uma distinção que importa, porque muita “verificação de assinatura” por aí responde à pergunta errada — confirma que a matemática da assinatura fecha e declara o documento válido, sem nunca perguntar se a assinatura cobre o arquivo inteiro ou só os primeiros dois terços dele.

O caso que prova que a banda de risco não é um oráculo

Deixe-me dar o exemplo mais desconfortável que temos, porque ele é a melhor aula sobre “abrir bem não é integridade” — e sobre humildade em medição.

Em 11 de agosto de 2026 rodamos o engine sobre 1.317 contratos públicos do PNCP (o portal de contratações do governo brasileiro). Resultado: 77% dos contratos assinados pontuam high — a banda de risco mais alta. E o gatilho não é adulteração. É uma segunda assinatura.

O mecanismo é lindo de tão simples. Um contrato tem duas partes que assinam. O primeiro assinante assina; seu /ByteRange cobre o arquivo até ali. O segundo assinante (a contra-assinatura) anexa os bytes dele — que caem, necessariamente, além do /ByteRange do primeiro. Do ponto de vista da estrutura, isso é literalmente idêntico a alguém ter alterado o documento depois da primeira assinatura: signature-coverage dispara (há bytes além da cobertura), incremental-updates dispara (há uma revisão posterior), id-inconsistency dispara (o identificador do trailer mudou). Três sinais acendem. E, ao mesmo tempo, a família que verifica a integridade das assinaturas reporta ambas intactas.

Cada afirmação é verdadeira sobre os bytes. E, mesmo assim, num contrato de duas partes — o documento legítimo mais comum nesse mercado — a banda high é ruidosa e pouco informativa. É por isso que não trato a pontuação como veredito. A pontuação é a soma dos sinais; o que cada sinal significa exige ler o achado, não o número. Num contrato de duas assinaturas, os sinais a ler são os de integridade e de permissões da assinatura, não a banda.

Publicar esse achado sobre a própria ferramenta — “olha o falso positivo que ela produz no documento mais comum do mercado” — é o oposto de marketing. É a única postura defensável para quem vende confiança.

Como a pontuação é montada (e por que ela é conservadora)

Já que citei a banda, vale abrir a caixa. O Tamperlens agrega os sinais numa nota de 0 a 100 com um contrato explícito:

  • Qualquer sinal de severidade high força a nota para pelo menos 70 — a banda high começa em 70.
  • E, ao contrário, a banda high é reservada para sinais high: sem nenhum deles, a nota é limitada a 69. banda === high é equivalente a “existe pelo menos um sinal que, sozinho, estabelece uma alteração”.
  • Vários sinais medium acumulam, mas com retornos decrescentes depois dos dois primeiros da mesma severidade — um monte de sinais fracos, sozinho, não alcança 100.
  • Bandas: abaixo de 30 é low, 30 a 69 é elevated, 70 ou mais é high.

O ponto de projeto aqui é a assimetria deliberada: fraqueza não vira força por acumulação. Você não consegue empilhar dez suspeitas fracas e produzir uma certeza. A certeza — a banda alta — é reservada para o tipo de achado que, isolado, já é uma alteração. Isso é uma escolha de risco, não de engenharia: prefiro que a nota alta signifique algo firme a que ela seja fácil de atingir.

Como raciocinar sobre um PDF, na prática

Se você for olhar um PDF suspeito à mão, o roteiro é este:

  1. Vá para o fim. Encontre o último startxref. Conte quantos startxref o arquivo tem — descontando os pares de linearização e híbrido, cada um vale um save de verdade.
  2. Ande a cadeia /Prev para trás. Cada trailer aponta para a xref anterior. Cada salto é uma vez que o arquivo foi escrito.
  3. Em cada revisão posterior à primeira, pergunte o que foi sobrescrito. Um objeto de page, content ou image reescrito é a pergunta que importa.
  4. Se há assinatura, compare o /ByteRange com o tamanho do arquivo. Sobrou byte além da cobertura? Descubra o que é. Contra-assinatura legítima e adulteração produzem a mesma sombra aqui — a diferença está no que os bytes anexados fazem, não na sua mera existência.
  5. Trate a nota como um resumo, nunca como um veredito. Leia os sinais.

Duas defesas de sanidade que o formato exige, porque o arquivo é escrito pelo adversário: a cadeia /Prev é limitada (o Tamperlens trunca em 64 saltos — documentos reais acumulam algumas revisões, não milhares), e a tabela xref é limitada a 250.000 entradas no total. Um arquivo de 50 KB pode declarar uma cadeia de /Prev que consome gigabytes de RAM de um parser ingênuo; essa é a primeira coisa que um parser forense precisa recusar, porque o documento hostil vai tentar.

O resumo em uma frase

Um adulterador controla o que a página mostra. Ele não controla — e quase nunca limpa — o rastro que a estrutura do PDF guarda de cada save: a cadeia de revisões, os objetos que ele sobrescreveu, os bytes que ele anexou além da assinatura. “Abre bem” e “200 OK” são afirmações sobre a superfície. Integridade é uma afirmação sobre a estrutura. São perguntas diferentes, e só a segunda importa quando o documento é dinheiro, contrato ou identidade.

Se você quer ver esse raciocínio virar JSON num arquivo real, é para isso que o tamperlens.com existe. Mas o raciocínio é seu para ficar — leve-o mesmo que nunca use a ferramenta.

Precisa de um projeto técnico sob medida?

Arquitetura, TypeScript, APIs e automação, do protótipo à produção. Quem responde o seu e-mail é quem escreve o código, e o prazo que eu prometo é o prazo que eu consigo cumprir.

Me manda uma mensagem →