Back to Blog

#10 Incidente Panóptico: A Linearidade XOR Quebra o Esquema de Impressão Digital de Posição

Code Auditing
13 de fevereiro de 2026
9 min read

Em 25 de agosto de 2025, com a assistência de Cantina e Seal911, a Panoptic conduziu uma operação de resgate white hat, protegendo aproximadamente $400K em fundos em risco [1]. A causa raiz foi uma falha na construção do s_positionsHash: o protocolo usava XOR para agregar hashes Keccak256 de IDs de posição em uma única impressão digital. Embora os hashes Keccak256 individuais permaneçam resistentes a colisões, a linearidade matemática do XOR torna a impressão digital composta insegura. Um atacante pode gerar um conjunto de IDs de posição forjados cujo hash agregado por XOR corresponde a qualquer impressão digital alvo, contornando a verificação de posição do protocolo e retirando colateral sem pagar a dívida.

Contexto

A Panoptic é um protocolo descentralizado de negociação de opções perpétuas construído na Ethereum que permite aos usuários negociar opções de venda e compra.

A função withdraw() possui um parâmetro de entrada positionList, que consiste em um conjunto de tokenIds. Cada tokenId representa uma posição. A função withdraw() verifica o status de dívida de cada posição com base no s_positionsHash e então recupera o colateral do usuário.

Para economizar gas, o protocolo não armazena cada posição (ou seja, tokenId) do usuário. Em vez disso, ele calcula uma impressão digital com base em todos os tokenIds do usuário e a registra em s_positionsHash. Os 8 bits mais significativos de cada s_positionsHash representam numLegs, enquanto os 248 bits inferiores representam o user position hash.

Para cada tokenId passado, o protocolo calcula seu hash, pega os 248 bits inferiores e atualiza o user position hash realizando um XOR bit a bit com os 248 bits inferiores do s_positionsHash atual.

Simultaneamente, countLegs é ajustado com base no intervalo numérico do tokenId: permanece inalterado quando o tokenId é menor que 2642^{64}, e aumenta em 1, 2 ou 3 quando nos intervalos (264,2112)(2^{64}, 2^{112}), (2112,2168)(2^{112}, 2^{168}) e (2168,2208)(2^{168}, 2^{208}) respectivamente. Isso é finalmente escrito nos 8 bits superiores do s_positionsHash para atualizar numLegs.

Análise da Vulnerabilidade

A causa raiz é uma falha no algoritmo do contrato para construir o s_positionsHash, especificamente o uso da operação XOR para agregar resultados de hash Keccak256. Embora uma única função hash permaneça segura, a linearidade matemática da operação XOR torna o algoritmo de impressão digital geral (ou seja, a soma XOR de hashes) inseguro [2].

A linearidade implica que um atacante não precisa quebrar a própria função hash (ou seja, reverter o tokenId a partir do hash). Em vez disso, ele pode empregar uma estratégia combinatória: gerando um grande número de tokenIds aleatórios, calculando seu Keccak256(tokenId) e, a partir desses valores de hash, selecionando um subconjunto específico de modo que a soma XOR desses hashes de tokenId corresponda exatamente à impressão digital alvo da vítima.

Isso permite que um atacante passe um conjunto de tokenIds forjados ao chamar withdraw() para passar na verificação de saúde e recuperar todo o colateral correspondente ao s_positionsHash.

Teoria

Suponha que a impressão digital de posição do usuário s_positionsHash tenha um user position hash de TT e numLegs de kk, com tokenIds do usuário {t1,t2,…,tn}\{t_1, t_2, \dots, t_n\}. Assim:

⨁i=1k[Hash(ti)(mod2248)]=T\bigoplus_{i=1}^{k} [\text{Hash}(t_i) \pmod{2^{248}}] = T

Aqui, cada valor de hash de 248 bits pode ser visto como um vetor de 248 dimensões.

Hash(ti)(mod2248)=[b0,b1,…,b247]T,where bi∈{0,1}\text{Hash}(t_i) \pmod{2^{248}} = [b_0, b_1, \dots, b_{247}]^T, where\ b_i \in \{0, 1\}

Portanto, TT reside em um espaço de 248 dimensões composto de 0s e 1s (F2248\mathbb{F}_2^{248}). Neste espaço, a operação XOR é equivalente à adição vetorial (Apêndice I).

O objetivo do atacante é encontrar nn vetores de 248 dimensões {v1,v2,…,vn}\{v_1, v_2, \dots, v_n\} dentre todos os vetores disponíveis, de modo que os 248 bits inferiores de sua soma XOR sejam iguais a TT. Assim, podemos formular o objetivo do atacante como um sistema de equações lineares:

x1v1+x2v2+⋯+xnvn=Tx_1 v_1 + x_2 v_2 + \dots + x_n v_n = T

Especificamente, não precisamos tentar construir TT diretamente. Em vez disso, selecionamos nn vetores de hash linearmente independentes {v1,v2,…,vn}\{v_1, v_2, \dots, v_n\} (onde nn é igual à dimensão 248) e os usamos como vetores coluna para construir uma matriz n×nn \times n AA:

A=[v1,v2,…,vn]A = [v_1, v_2, \dots, v_n]

O problema então se transforma em encontrar um vetor de coeficientes xx tal que:

A⋅x=TA \cdot x = T

Onde x=[x1,x2,…,xn]Tx = [x_1, x_2, \dots, x_n]^T, e xi∈{0,1}x_i \in \{0, 1\}.

De acordo com a teoria da álgebra linear, desde que a matriz AA seja de posto completo, ela abrange todo o espaço nn-dimensional. Isso significa que para qualquer alvo TT, o sistema de equações possui uma solução única. Após resolver xx, simplesmente retemos os vetores viv_i para os quais xi=1x_i=1; sua soma XOR é TT.

Exemplo

Para facilitar a compreensão, demonstramos esse processo de construção com um estudo de caso. Suponha que os vetores sejam tridimensionais, com cada dimensão consistindo apenas de 0 ou 1, e o valor de hash alvo seja 101, ou seja, T=[1,0,1]TT = [1, 0, 1]^T.

Em seguida, geramos aleatoriamente 3 valores de hash:

  • v1=[1,1,0]Tv_1 = [1, 1, 0]^T
  • v2=[0,1,0]Tv_2 = [0, 1, 0]^T
  • v3=[0,1,1]Tv_3 = [0, 1, 1]^T

Construímos a matriz AA usando esses três vetores como colunas e configuramos a equação Ax=TAx=T:

[100111001][x1x2x3]=[101]\begin{bmatrix} 1 & 0 & 0 \\ 1 & 1 & 1 \\ 0 & 0 & 1 \end{bmatrix} \begin{bmatrix} x_1 \\ x_2 \\ x_3 \end{bmatrix} = \begin{bmatrix} 1 \\ 0 \\ 1 \end{bmatrix}

Resolvendo via eliminação de Gauss, obtemos x=[1,0,1]Tx = [1, 0, 1]^T. Isso significa que precisamos selecionar v1v_1 e v3v_3 (correspondentes a x1=1,x3=1x_1=1, x_3=1), e sua soma XOR é exatamente igual ao alvo TT.

Selecionando n Vetores Linearmente Independentes

Como mencionado anteriormente, para resolver Ax=TAx=T, precisamos construir uma matriz AA de posto completo. Dado que estamos lidando com um espaço vetorial extremamente grande (m=2248m = 2^{248}), a questão central é: Como selecionamos rapidamente nn vetores linearmente independentes nesse vasto espaço?

Considere o método mais simples: gerar nn tokenIds aleatoriamente e selecionar seus resultados de hash como vetores.

Sobre F2\mathbb{F}_2, a probabilidade PP de que nn vetores nn-dimensionais selecionados aleatoriamente formem uma matriz de posto completo é:

P(n)=∏k=0n−1(1−2k2n)P(n) = \prod_{k=0}^{n-1} \left(1 - \frac{2^k}{2^n}\right)

Quando nn é grande (neste exemplo, n=248n=248), essa probabilidade converge para uma constante (Apêndice II):

lim⁡n→∞P(n)≈0.28879\lim_{n \to \infty} P(n) \approx 0.28879

Isso significa que cada tentativa aleatória tem aproximadamente 28,9% de probabilidade de sucesso. Em média, um atacante precisa de apenas cerca de 3,5 tentativas para encontrar um conjunto de vetores linearmente independentes. Portanto, o custo computacional é extremamente baixo, e o atacante pode rapidamente construir uma matriz AA que satisfaça as condições.

Para controlar o countLegs nos 8 bits superiores, precisamos apenas ajustar o intervalo dos valores de tokenId gerados aleatoriamente com base no countLegs alvo.

Por exemplo, se quisermos que o numLegs na impressão digital forjada seja 0, simplesmente garantimos que os nn tokenIds gerados aleatoriamente sejam todos menores que 2642^{64}. Como o incremento de leg para tokenIds nesse intervalo é 0, independentemente de quais vetores a solução xx da eliminação de Gauss selecionar, o numLegs acumulado final será inevitavelmente 0.

Análise do Ataque

O resgatador white hat iniciou múltiplas transações de resgate. Por simplicidade, a discussão a seguir é baseada em apenas uma dessas transações [3].

A lógica central consiste nas seguintes 5 etapas:

  1. Tomar emprestado 0,23e8 WBTC e 28e18 WETH via flash loan da Aave.
  2. Depositar 0,23e8 WBTC e 28e18 WETH no contrato poWBTC e no contrato 0x1f8d_poWETH.
  3. Chamar mintOptions() do contrato PanopticPool com um positionIdList normal para tomar fundos emprestados e abrir uma posição alavancada.
  4. Chamar withdraw(), passando os tokenIds forjados. Como essas posições forjadas têm um positionSize de 0, a função retorna tokenRequired como 0, o que significa que o colateral total exigido para todas as posições é erroneamente calculado como zero. Enquanto isso, o s_positionsHash gerado por esses tokenIds é exatamente o mesmo que o gerado na etapa 3, permitindo que o resgatador recupere todo o colateral sem pagar nenhuma dívida.
  5. Pagar o flash loan e executar a próxima rodada.

Resumo

Este incidente destaca duas falhas combinadas que juntas possibilitaram a retirada não autorizada de colateral.

  • XOR não é uma função de agregação segura para hashes. O Keccak256 é resistente a colisões, mas a linearidade do XOR significa que a soma XOR de múltiplos hashes não herda essa propriedade. Construir um conjunto de entradas cujos hashes XOR resultem em qualquer valor alvo se reduz a resolver um sistema de equações lineares sobre F2\mathbb{F}_2, o que é computacionalmente trivial. A composição de hashes requer operações que preservem a resistência a colisões, como concatenação seguida de re-hashing.
  • Validação ausente de IDs de posição. O protocolo não verificou se os positionIds passados correspondiam a posições de opção válidas. Valores abaixo de 2642^{64} carregam um countLegs de 0 e um positionSize de 0, o que significa que as posições forjadas não geram dívida. Isso permitiu que o atacante passasse na verificação de saúde com requisito de colateral zero enquanto correspondia à impressão digital alvo.

Referência

  1. https://x.com/Panoptic_xyz/status/1961187739866644524

  2. https://cseweb.ucsd.edu/~mihir/papers/inchash.pdf

  3. https://app.blocksec.com/explorer/tx/eth/0x67a45dfe5ff4b190058674d7c791bbdc48e889f319f937c24fa13a5f9093f088

Apêndice

Os dois apêndices a seguir fornecem uma explicação e prova matemática das afirmações no texto principal, a saber, que a operação XOR é equivalente à adição vetorial (Apêndice I) e a probabilidade de que uma matriz aleatória seja de posto completo (Apêndice II).

Apêndice I

No corpo finito F2={0,1}\mathbb{F}_2 = \{0, 1\}, a adição é definida como adição módulo 2:

0+0=00+1=11+0=11+1=0(mod2)\begin{aligned} 0 + 0 &= 0 \\ 0 + 1 &= 1 \\ 1 + 0 &= 1 \\ 1 + 1 &= 0 \pmod 2 \end{aligned}

Observa-se que isso é idêntico à operação lógica OR Exclusivo (XOR, símbolo ⊕\oplus).

Para o espaço vetorial nn-dimensional F2n\mathbb{F}_2^n (neste caso n=248n=248), a adição de dois vetores u=[u1,…,un]Tu = [u_1, \dots, u_n]^T e v=[v1,…,vn]Tv = [v_1, \dots, v_n]^T é definida como adição componente a componente módulo 2:

u+v=[u1+v1(mod2)⋮un+vn(mod2)]=[u1⊕v1⋮un⊕vn]=u⊕vu + v = \begin{bmatrix} u_1 + v_1 \pmod 2 \\ \vdots \\ u_n + v_n \pmod 2 \end{bmatrix} = \begin{bmatrix} u_1 \oplus v_1 \\ \vdots \\ u_n \oplus v_n \end{bmatrix} = u \oplus v

Portanto, no espaço vetorial F2n\mathbb{F}_2^{n}, a adição vetorial é equivalente à operação XOR bit a bit nos componentes do vetor.

Apêndice II

Para que a matriz seja de posto completo, esses nn vetores devem ser linearmente independentes.

O primeiro vetor v1v_1 pode ser qualquer vetor em F2n\mathbb{F}_2^n exceto o vetor zero, fornecendo 2n−12^n - 1 escolhas; o segundo vetor v2v_2 não deve residir no subespaço gerado por {v1}\{v_1\}, que contém 212^1 vetores, deixando 2n−22^n - 2 escolhas; seguindo essa lógica, o kk-ésimo vetor vkv_k não pode estar no subespaço gerado pelos k−1k-1 vetores anteriores, resultando em 2n−2k−12^n - 2^{k-1} escolhas possíveis.

O número total de tais matrizes (ou seja, a ordem de GL(n,F2)GL(n, \mathbb{F}_2)) é:

N=∏k=0n−1(2n−2k)=(2n−1)(2n−2)(2n−4)⋯(2n−2n−1)N = \prod_{k=0}^{n-1} (2^n - 2^k) = (2^n - 1)(2^n - 2)(2^n - 4)\cdots(2^n - 2^{n-1})

E o número total de todas as matrizes n×nn \times n possíveis é (2n)n=2n2(2^n)^n = 2^{n^2}.

Assim, a probabilidade PP é:

P=∏k=0n−1(2n−2k)2n2=∏k=0n−1(2n−2k2n)=∏k=0n−1(1−2k2n)P = \frac{\prod_{k=0}^{n-1} (2^n - 2^k)}{2^{n^2}} = \prod_{k=0}^{n-1} \left( \frac{2^n - 2^k}{2^n} \right) = \prod_{k=0}^{n-1} (1 - \frac{2^k}{2^n})

Fazendo j=n−kj = n - k, podemos reescrever essa expressão como:

P=∏j=1n(1−12j)P = \prod_{j=1}^{n} \left(1 - \frac{1}{2^j}\right)

Quando n→∞n \to \infty, esse produto converge para uma constante:

P≈0.288788P \approx 0.288788

Isso significa que para nn grande, a probabilidade de que uma matriz aleatória seja de posto completo é aproximadamente 28,9%.


Sobre a BlockSec

A BlockSec é um provedor completo de segurança blockchain e conformidade cripto. Desenvolvemos produtos e serviços que ajudam os clientes a realizar auditorias de código (incluindo contratos inteligentes, blockchain e carteiras), interceptar ataques em tempo real, analisar incidentes, rastrear fundos ilícitos e cumprir obrigações AML/CFT, ao longo de todo o ciclo de vida de protocolos e plataformas.

A BlockSec publicou múltiplos artigos de segurança blockchain em conferências de prestígio, relatou vários ataques zero-day em aplicações DeFi, bloqueou múltiplos hacks para resgatar mais de 20 milhões de dólares e protegeu bilhões em criptomoedas.

Sign up for the latest updates
Perdas de ~5,57 milhões USD: exploração de falhas no X100 Freezer e no NEM | Boletim semanal da BlockSec
Security Insights

Perdas de ~5,57 milhões USD: exploração de falhas no X100 Freezer e no NEM | Boletim semanal da BlockSec

Este relatório semanal aborda dois incidentes de segurança na Ethereum e na BNB Chain no período de 19/10/2026–25/10/2026. Ele examina uma tomada de controle da governança do X100 Freezer financiada por um empréstimo-relâmpago e uma falha na liquidação de impostos que afetou um pool de liquidez de um token NEM na BNB Chain. As perdas estimadas no escopo do relatório totalizam aproximadamente 5,57 milhões USD: cerca de 4,75 milhões USD da transação representativa do X100 e cerca de 820 mil USD do incidente no pool do NEM. O total exclui as extrações subsequentes do X100 relatadas separadamente.

Perdas de ~3,87 milhões USD: ataques à Autheo Network e à UNCX | Resumo semanal da BlockSec
Security Insights

Perdas de ~3,87 milhões USD: ataques à Autheo Network e à UNCX | Resumo semanal da BlockSec

Este relatório semanal aborda 3 incidentes de segurança no período de 12/10/2026–18/10/2026, com perdas totais estimadas em aproximadamente 3,87 milhões USD. A Autheo Network sofreu um comprometimento de chave privada com perdas de cerca de 1,76 milhões USD, a Qubic Wallet perdeu cerca de 1,30 milhões USD devido à geração de seeds fracas, e os contratos Smart Token Vesting V2 da UNCX Network sofreram um erro contábil de cerca de 807 mil USD nas redes Ethereum, BNB Chain, Base e Robinhood Chain. A análise em destaque da UNCX explica como um cálculo incorreto do total de cotas misturou parcelas de vesting criadas com diferentes proporções entre tokens e cotas, permitindo saques acima do devido de pools compartilhados.

~US$ 542 mil perdidos: ataque ao MakerDaoBot | Boletim semanal da BlockSec
Security Insights

~US$ 542 mil perdidos: ataque ao MakerDaoBot | Boletim semanal da BlockSec

O relatório cobre 1 incidente no Ethereum (05–11/10/2026), com perda estimada de US$ 542 mil. Um adaptador malicioso obteve acesso aos saldos MakerDAO via função desprotegida do MakerDaoBot, liquidou 4 leilões ETH-A e converteu 200 ETH-A em WETH, depois enviados como ETH ao atacante.

Best Security Auditor for Web3

Validate design, code, and business logic before launch. Aligned with the highest industry security standards.

BlockSec Audit