Esse post é uma pesquisa que fiz na graduação como membro do Ganesh-ICMC. Muito do que fiz lá dentro acredito que pode ser útil para mais pessoas e eu queria transmitir esse conhecimento por meio de publicações nesse blog.
Os ataques de side-channel consistem em vazar dados a partir de características da implementação de algoritmos, como tempo gasto e consumo de energia. Caso essas características sejam infuenciadas pela entrada, a medida delas permitem inferir informações sobre essa entrada. Posto isso, para analisar os ataques é necessário pensar tanto no software quanto no hardware.
Na criptografia, a preocupação com side-channels é grande porque chaves secretas utilizadas nos algoritmos garantem a comunicação segura e não devem ser vazadas. Por conta disso, existem grandes conferências e revistas dedicadas a construção de dispositivos criptográficos seguros, como a Conference on Cryptographic Hardware e a Embedded Systems (CHES) e o Journal of Cryptographic Engineering, e muitas de teses de doutorado em side-channel são publicadas todos os anos [5].
Ataque baseado em som
Na competição CTF SpamAndHex de 2020, um dos desafios tinha um vídeo quebrado de uma impressora 3D escrevendo o texto da flag, o segredo buscado. O vídeo mostra a impressora escrevendo o texto “SAF”, mas em seguida ele congela e é possível apenas ouvir o barulho da impressora.
Porém, esse barulho indicava que tipo de movimento a impressora estava fazendo e isso era o suficiente para obter o texto que a impressora escrevia, SAF{AIRGAPPED2}
. Nosso writeup do desafio descreve o procedimento como um todo.
Embora esse problema seja apenas um desafio de CTF, o ataque acústico tem aplicações na prática. O primeiro ataque de side-channel registrado na história foi em 1965 e consistia em utilizar um microfone para identificar a posição de parte dos rotores de uma máquina criptográfica [2][5]. A partir do ruído, a inteligência britânica foi capaz de inferir a configuração da máquina e observar comunicações cifradas entre embaixadas do Egito.
Um exemplo mais moderno foi na harwear.io 2022, em que pesquisadores mostraram na sua apresentação a quebra da criptografia ECDSA de um laptop convencional utilizando um microfone.
Ataque baseado em tempo
Um exemplo de algoritmo vulnerável a um ataque de side-channel por tempo é uma verificação de senha com uma comparação caractere a caractere.
Nesse caso, senhas parcialmente corretas levam mais tempo do que senhas totalmente incorretas. Por exemplo, se a senha correta é 123456
e a tentativa é ABCDEF
, o algoritmo pode finalizar após a primeira comparação, de 1
com A
. Porém se a tentativa for 12345A
, o algoritmo precisa percorrer 6 caracteres para determinar que a senha é incorreta, quando ele compara 6
com A
.
O código Python a seguir mostra uma implementação dessa técnica:
def unsafe_pass_check(password, user_input): if len(password) != len(user_input): return False for i,c in enumerate(user_input): if c != password[i]: return False return True
Para verificar na prática se essa diferença de tempo é perceptível, temos o seguinte código que testa diferentes comprimentos de senha e mede o tempo da verificação de comprimento no início da função:
from time import time import matplotlib.pyplot as plt from statistics import mean, stdev NUM_TRIES=100 MAX_TIME = 1e-6 # threshold to eliminate outliers times_mean = [] times_stdev = [] sizes = [] for l in range(20): tries = [] for n_try in range(NUM_TRIES): guess = l*b"A" start = time() unsafe_pass_check(PASS, guess) end = time() duration = end-start if duration < MAX_TIME: tries.append(end-start) times_mean.append( mean(tries) ) times_stdev.append( stdev(tries) ) sizes.append(l) plt.errorbar(sizes, times_mean, yerr=times_stdev, linestyle="None", marker="o", capsize=5.0) plt.ylim(0, 2e-6) plt.show()
Daí observa-se os seguintes tempos gastos para diferentes comprimentos de senha:
Aqui observa-se que a senha de comprimento 6 leva mais tempo e esse é o comprimento correto. Essa diferença de tempo ocorre porque, uma vez verificado que o comprimento está certo, a função continua e verifica o primeiro caractere, mas caso contrário ela encerra imediatamente.
Com o comprimento em mãos, é possível chutar diferentes caracteres para o início da senha e medir seu tempo. O seguinte script faz esse trabalho:
from time import time import matplotlib.pyplot as plt from statistics import mean, stdev from string import ascii_letters, digits char_list = ascii_letters+digits LEN = 6 MAX_TIME = 1e-6 # threshold to eliminate outliers times_mean = [] times_stdev = [] chars = [] NUM_TRIES=2000 for c in char_list: tries = [] for n_try in range(NUM_TRIES): guess = c.encode() + (LEN-1)*b"A" start = time() unsafe_pass_check(PASS, guess) end = time() duration = end-start if duration < MAX_TIME: tries.append(duration) times_mean.append( mean(tries) ) times_stdev.append( stdev(tries) ) chars.append(c) plt.errorbar(chars, times_mean, yerr=times_stdev, linestyle="None", marker="o", capsize=5.0) plt.ylim(0, 2e-6) plt.show()
Com isso tem-se as seguintes medidas de tempo para senhas iniciadas por cada caractere alfanumérico:
Observa-se aqui que ter o caractere “G” gasta um tempo levemente maior do que os outros, sendo essa diferença porque quando o primeiro caractere está correto verifica-se se o segundo caractere também está, mas retorna-se imediatamente caso contrário.
Tendo o primeiro caractere em mãos, pode-se realizar o mesmo procedimento para determinar o segundo caractere, depois o terceiro e assim sucessivamente. O script a seguir vaza a senha toda:
from time import time from statistics import mean from string import ascii_letters, digits char_list = ascii_letters+digits LEN = 6 MAX_TIME = 2e-6 # threshold to eliminate outliers discovered = b"" NUM_TRIES=2000 for i in range(LEN-1): times_mean = [] chars = [] for c in char_list: tries = [] for n_try in range(NUM_TRIES): guess = discovered + c.encode() + (LEN-len(discovered)-1)*b"A" start = time() unsafe_pass_check(PASS, guess) end = time() duration = end-start if duration < MAX_TIME: tries.append(end-start) times_mean.append( mean(tries) ) chars.append(c) max_time_idx = max((v,i) for i,v in enumerate(times_mean))[1] print(char_list[max_time_idx]) discovered += char_list[max_time_idx].encode() for c in char_list: if unsafe_pass_check(PASS, discovered + c.encode()): discovered += c.encode() print(c, end="") break print("\n{isCorrect} {passwd}".format(isCorrect=(LEN==len(discovered)), passwd=discovered.decode()) )
Esse último código obtém a senha: G4nesh
. Vazamos um valor secreto!
A partir desse exemplo, é possível perceber que diferenças no tempo de execução, embora pequenas, podem ser o suficiente para vazar informações importantes. Eu não tenho conhecimento de nenhum ataque do mundo real com essa técnica, mas ela foi útil nessa solução do desafio “Ethernet from Above” do CTF Pwn2Win 2021.
Para se defender desses ataques de tempo, algoritmos criptográficos buscam gastar um tempo constante, mesmo que isso signifique levar mais tempo do que um método mais eficiente seria capaz de oferecer. Outra estratégia que minimiza o problema é a adição de atrasos aleatórios no processamento.
No caso da nossa função, podemos protegê-la removendo o desvio condicional através de um XOR caractere por caractere, seguido de um OR com um acumulador. Assim caso qualquer bit seja diferente, o XOR obtém um valor não nulo e esse bit é preservado no acumulador. Essa solução aparece a seguir:
def safe_pass_check(password, user_input): if len(password) != len(user_input): return False accumulator = 0x00 for i,c in enumerate(user_input): accumulator = accumulator | (c ^ password[i]) if accumulator: return False return True
Ataque baseado em potência – Princípios de eletrônica
Para entender ataques de potência, é importante visualizar como funciona o consumo de energia de um circuito digital típico.
Todo circuito digital, como os componentes de uma CPU, é construído a partir de portas lógicas, como NAND, NOR e NOT. No processo de desenvolvimento do chip, essas portas lógicas são mapeadas em circuitos eletrônicos e esse mapeamento usa uma família lógica, geralmente CMOS.
Por exemplo, na imagem abaixo aparece a eletrônica correspondente a uma porta lógica inversora:
Representação lógica do inversor e sua construção em CMOS
O inversor CMOS consiste em um transistor PMOS ligado com a tensão de alimentação VDD e um transistor NMOS ligado com o terra GND.
Aproximando transistores como chaves controladas, quando a tensão em A é 0V o PMOS está conduzindo e o NMOS em corte, portanto a saída Y está ligada com a fonte de VDD. Analogamente, se a tensão em A tiver valor VDD, o PMOS entra em corte e o NMOS passa a conduzir, ligando a saída em GND. Com isso tem-se o comportamento do inversor, que responde um nível lógico baixo com um nível lógico alto e vice-versa.
No caso acima, a saída do inversor está desconectada e os transistores não alimentam qualquer carga. Porém, na prática as portas lógicas quase sempre tem como entrada e saída outras portas lógicas, como o inversor do meio na figura a seguir:
A entrada de uma porta lógica são os gates de transistores MOSFET, que tem comportamento capacitivo. Mais precisamente, essa carga se comporta como um capacitor MOS. Sua estrutura é como mostra a figura a seguir:
Daí a carga de uma porta lógica pode ser vista como um capacitor. De acordo com a equação do capacitor, $i = C \frac{du}{dt}$, um capacitor conduz corrente apenas para variações de tensão, ou seja, os transistores só consomem energia durante transições de estado. Uma porta lógica CMOS consome zero de potência se sua saída for constante!
Nesse sentido, uma transição da saída de 0 para 1 se inicia com o capacitor em 0V e ele se carrega através da corrente pelo transistor PMOS até ter tensão VDD, e então não há mais consumo de corrente. Já na transição de 1 para 0, o capacitor na saída inicia carregado em VDD e se descarrega através do transistor NMOS.
O seguinte código para NGSPICE simula o circuito da figura com 3 inversores recebendo uma onda quadrada como entrada. Ele faz medidas da corrente que passa pelos transistores do inversor central e suas tensões de entrada e de saída.
CMOS Inverter * Loads transistor models .model N1 NMOS level=8 version=3.3.0 .model P1 PMOS level=8 version=3.3.0 * Inverter definition .subckt INV in out vdd vss M1 out in vdd vdd P1 M2 out in vss vss N1 .ends INV * Power source and input VDD vdd 0 5V VIN in 0 PULSE 0V 5V 400ns 100ns 100ns 400ns 1u * Inverters X1 in mid12 vdd 0 INV X2 mid12 mid23 xpow xgnd INV X3 mid23 out vdd 0 INV * Current measurement Vpow vdd xpow 0V Vgnd xgnd 0 0V * Simulation .tran 10ns 2.25u .end * Console * run * plot v(mid12)/1e5+300u v(mid23)/1e5+200u i(vpow)+100u i(vgnd)
Na figura observa-se que só há passagem de corrente quando está ocorrendo uma transição de estado. Essa corrente está diretamente ligada ao consumo de potência, porque $P = u \cdot i$. Quando a saída se torna nível lógico alto, o transistor PMOS carrega o capacitor com uma corrente vindo de VDD, e quando ela se torna baixo, o transistor NMOS descarrega o capacitor em GND.
Ataque baseado em radiação eletromagnética
Além de circuitos eletrônicos poderem apresentar diferentes consumos de potência na execução de um algoritmo, correntes no interior de um chip geram ondas eletromagnéticas e elas também podem vazar dados.
Utilizando antenas de campo próximo, como na figura a seguir, é possível medir a potência de radiação eletromagnética. O comportamento dessa radiação é semelhante ao de consumo potência do chip discutido anteriormente, e os ataques são análogos.
A diferença entre os ataques é que uma antena de campo próximo permite selecionar espacialmente a região atacada e com isso evitar ruído de outras regiões do chip [1]. Por exemplo, uma CPU com múltiplos núcleos tem o consumo de potência dos outros núcleos aparecendo como ruído, assim um ataque de potência tradicional se torna mais difícil.
Um caso interessante de ataque com radiação eletromagnética é o phreaking de Van Eck, que se baseia na radiação advinda dos harmônicos presentes no sinal digital de um cabo. O software TempestSDR traz uma implementação do ataque que intercepta o sinal do cabo de um monitor e mostra o conteúdo da tela. Uma demonstração do ataque aparece nesse vídeo:
Esse ataque envolvendo os cabos é também chamado de TEMPEST e teve importância para inteligência por ser útil, por exemplo, para espionar comunicações em telégrafos [4]. Sua descoberta tornou a blindagem eletrostática de cabos que transmitem informações sensíveis um tema importante.
Discussão
Com os ataques citados, observa-se que a execução de algoritmos pode apresentar diversos efeitos colaterais capazes de vazar informações. Devido a esses ataques é uma preocupação para a criptografia que o software e o hardware sejam protegidos e minimizem esses efeitos.
Esses ataques podem demandar acesso físico para conectar um osciloscópio medindo o consumo de potência ou uma antena, mas as vezes podem explorados puramente por software. As vulnerabilidades Spectre e Meltdown, mais discutidas em uma publicação anterior, são exemplos de uma característica de hardware explorável por software e afetam processadores modernos.
Outra vulnerabilidade curiosa em processadores modernos é a Hertzbleed. Ela explora o controle dinâmico da frequência e tensão de um processador moderno para fazer um side-channel de potência criar uma diferença no tempo de execução. Com isso ela trouxe um ataque capaz de vazar remotamente a chave do algoritmo criptográfico SIKE em bibliotecas da Cloudflare e da Microsoft.
Portanto, esses efeitos colaterais que parecem detalhes desimportantes do algoritmo ou do hardware tem efeitos reais na segurança e afetam mesmo dispositivos modernos. Nesse sentido tanto o software e o hardware devem ser analisados com cuidado em sistemas em que side-channels são uma ameaça.
Referências
[1] O’Flynn, C.; van Woudenberg, J. The Hardware Hacking Handbook: Breaking Embedded Security with Hardware Attacks; No Starch Press, 2021.
[2] F. Standaert, Introduction to Side-Channel Attacks, in: Secure Integrated Circuits and Systems, Springer, Boston, MA, 2010, pp. 27–42.
[3] Marinov, M. Remote Video Eavesdropping Using a Software-Defined Radio Platform. MS thesis, University of Cambridge 2014.
[4] N.S.A., TEMPEST: A Signal Problem
[5] Mayes, K.; Markantonakis, K. Smart Cards, Tokens, Security and Applications; Springer International Publishing, 2017.