<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Kleber Silva</title>
    <description>The latest articles on DEV Community by Kleber Silva (@kleber333).</description>
    <link>https://dev.to/kleber333</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4082222%2F2f6d3117-0c83-446e-844e-4c00e499afdf.png</url>
      <title>DEV Community: Kleber Silva</title>
      <link>https://dev.to/kleber333</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/kleber333"/>
    <language>en</language>
    <item>
      <title>Comprei um water cooler sem suporte para Linux. Então fiz engenharia reversa do software.</title>
      <dc:creator>Kleber Silva</dc:creator>
      <pubDate>Mon, 17 Aug 2026 20:50:09 +0000</pubDate>
      <link>https://dev.to/kleber333/comprei-um-water-cooler-sem-suporte-para-linux-entao-fiz-engenharia-reversa-do-software-4gli</link>
      <guid>https://dev.to/kleber333/comprei-um-water-cooler-sem-suporte-para-linux-entao-fiz-engenharia-reversa-do-software-4gli</guid>
      <description>&lt;p&gt;Quando comprei um &lt;strong&gt;Water Cooler Pichau Aqua 120X&lt;/strong&gt;, a ideia era simples: resfriar e usar o display LCD para mostrar a temperatura da CPU.&lt;/p&gt;

&lt;p&gt;O problema apareceu quando fui procurar o software.&lt;/p&gt;

&lt;p&gt;O fabricante disponibilizava uma aplicação para Windows, mas não existia suporte nativo para Linux. O hardware funcionava normalmente como dispositivo USB, mas o display não recebia nenhuma informação.&lt;/p&gt;

&lt;p&gt;Em outras palavras:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;O Linux enxergava o dispositivo. Eu só não sabia como conversar com ele.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A solução mais óbvia seria encontrar uma aplicação compatível ou uma biblioteca pronta. Como não encontrei algo que funcionasse com esse modelo específico, resolvi seguir outro caminho: descobrir como o software oficial fazia isso.&lt;/p&gt;

&lt;p&gt;Obs: Foi a minha primeira experiência prática de engenharia reversa.&lt;/p&gt;

&lt;p&gt;Este artigo é um relato técnico do processo: desde a identificação da interface usada pelo dispositivo até a descoberta do formato dos bytes enviados ao LCD e a criação de um script para atualizar a temperatura da CPU periodicamente.&lt;/p&gt;




&lt;h2&gt;
  
  
  O primeiro passo: descobrir como o dispositivo aparecia no Linux
&lt;/h2&gt;

&lt;p&gt;Antes de pensar em protocolo, eu precisava entender como o water cooler era exposto pelo sistema.&lt;/p&gt;

&lt;p&gt;O dispositivo apareceu como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/dev/ttyACM0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso já era uma pista importante.&lt;/p&gt;

&lt;p&gt;Em vez de precisar falar diretamente com USB através de uma biblioteca específica, aparentemente existia uma interface serial disponível no sistema.&lt;/p&gt;

&lt;p&gt;Fiz alguns testes básicos:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;ls&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; /dev/ttyACM0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e também:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;sudo &lt;/span&gt;fuser &lt;span class="nt"&gt;-v&lt;/span&gt; /dev/ttyACM0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A partir daí, a pergunta era:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Quais bytes o software oficial envia para &lt;code&gt;/dev/ttyACM0&lt;/code&gt;?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Procurando pistas no software oficial
&lt;/h2&gt;

&lt;p&gt;Como o fabricante disponibilizava um executável para Windows, comecei analisando o binário no &lt;strong&gt;Ghidra&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Eu nunca tinha feito engenharia reversa antes, então o processo foi bastante incremental.&lt;/p&gt;

&lt;p&gt;Primeiro procurei por strings relacionadas às informações que eu imaginava que o software poderia enviar ao dispositivo.&lt;/p&gt;

&lt;p&gt;Encontrei referências como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;set_send_cpu_temperature
set_send_cpu_frequency
set_send_cpu_utilization
set_send_cpu_fan_speed

set_send_gpu_frequency
set_send_gpu_utilization
set_send_gpu_fan_speed
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso indicava que o programa provavelmente conseguia enviar várias métricas para o display.&lt;/p&gt;

&lt;p&gt;Também encontrei referências ao &lt;code&gt;QSerialPort&lt;/code&gt;, parte do framework Qt usada para comunicação serial.&lt;/p&gt;

&lt;p&gt;A partir daí, comecei a seguir as referências no Ghidra.&lt;/p&gt;




&lt;h2&gt;
  
  
  Descobrindo como o software encontra o dispositivo
&lt;/h2&gt;

&lt;p&gt;Uma das funções encontradas fazia algo conceitualmente parecido com isso:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;QSerialPortInfo&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;availablePorts&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois percorria as portas disponíveis e comparava o Vendor ID e o Product ID do dispositivo.&lt;/p&gt;

&lt;p&gt;O software tentava duas combinações:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VID: 1f3a
PID: 0008
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e, caso não encontrasse essa:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;VID: 1a86
PID: 484a
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Depois de localizar o dispositivo, configurava a comunicação serial com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Baud rate: 1000000
Data bits: 8
Parity: none
Stop bits: 1
Flow control: none
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ou seja:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;1.000.000 baud
8N1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Essa foi uma descoberta importante.&lt;/p&gt;

&lt;p&gt;Eu já sabia que existia uma interface serial no Linux, e agora tinha descoberto a configuração usada pelo software oficial.&lt;/p&gt;

&lt;p&gt;Mas ainda faltava a parte mais importante:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Qual era o formato dos dados enviados?&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Seguindo o fluxo até a construção do pacote
&lt;/h2&gt;

&lt;p&gt;A função que inicializava o dispositivo era chamada por outra rotina maior.&lt;/p&gt;

&lt;p&gt;Seguindo as chamadas, encontrei uma função responsável por montar um &lt;code&gt;QByteArray&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Foi aqui que o protocolo começou a aparecer.&lt;/p&gt;

&lt;p&gt;A rotina fazia algo parecido com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;QByteArray&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;QByteArray&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sc"&gt;'t'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;QByteArray&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;flag&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;QByteArray&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;QByteArray&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value2_high&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;QByteArray&lt;/span&gt;&lt;span class="o"&gt;::&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value2_low&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Convertendo a sequência de &lt;code&gt;append()&lt;/code&gt; para um layout de bytes, chegamos a um pacote de &lt;strong&gt;23 bytes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A estrutura era:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Offset&lt;/th&gt;
&lt;th&gt;Tipo&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;byte fixo: &lt;code&gt;'t'&lt;/code&gt; (&lt;code&gt;0x74&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;flag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;campo de 8 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3–4&lt;/td&gt;
&lt;td&gt;campo de 16 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;campo de 8 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6–7&lt;/td&gt;
&lt;td&gt;campo de 16 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;campo de 8 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9–10&lt;/td&gt;
&lt;td&gt;campo de 16 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11–14&lt;/td&gt;
&lt;td&gt;campo de 32 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15–18&lt;/td&gt;
&lt;td&gt;campo de 32 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;19–20&lt;/td&gt;
&lt;td&gt;campo de 16 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;campo de 8 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;campo de 8 bits&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Os valores de múltiplos bytes eram montados manualmente usando shifts, o que mostrou que estavam sendo enviados em &lt;strong&gt;big-endian&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Por exemplo, um valor de 16 bits aparecia conceitualmente assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Já os valores de 32 bits seguiam:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Nesse momento já tínhamos o esqueleto completo do pacote.&lt;/p&gt;

&lt;p&gt;Mas havia mais uma transformação.&lt;/p&gt;




&lt;h2&gt;
  
  
  O XOR escondido no meio da função
&lt;/h2&gt;

&lt;p&gt;Depois de montar o &lt;code&gt;QByteArray&lt;/code&gt;, a função percorria os bytes do pacote e aplicava:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight cpp"&gt;&lt;code&gt;&lt;span class="n"&gt;byte&lt;/span&gt; &lt;span class="o"&gt;^=&lt;/span&gt; &lt;span class="mh"&gt;0x02&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Inicialmente o código parecia um pouco confuso porque o compilador havia otimizado parte do loop usando operações de 32 bits.&lt;/p&gt;

&lt;p&gt;Mas, reduzindo aquilo à lógica real, o comportamento era simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;byte 0 → não é alterado
byte 1 → não é alterado

byte 2 até byte 22 → XOR 0x02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Então o protocolo efetivamente era:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ 0x74 ][ flag ][ payload XOR 0x02 ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;onde o payload possui 21 bytes.&lt;/p&gt;

&lt;p&gt;Essa parte foi especialmente interessante porque agora era possível reproduzir um pacote sem sequer precisar saber o significado de todos os campos.&lt;/p&gt;

&lt;p&gt;Bastava:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Montar os 23 bytes.&lt;/li&gt;
&lt;li&gt;Manter os dois primeiros intactos.&lt;/li&gt;
&lt;li&gt;Aplicar XOR &lt;code&gt;0x02&lt;/code&gt; nos bytes restantes.&lt;/li&gt;
&lt;li&gt;Escrever o resultado na porta serial.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  O primeiro pacote enviado manualmente
&lt;/h2&gt;

&lt;p&gt;Com essa estrutura em mãos, fiz o teste mais simples possível.&lt;/p&gt;

&lt;p&gt;Se todos os campos do payload fossem zero &lt;strong&gt;antes do XOR&lt;/strong&gt;, eles se tornariam &lt;code&gt;0x02&lt;/code&gt; depois da transformação.&lt;/p&gt;

&lt;p&gt;Então o pacote seria:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;74 00 02 02 02 02 02 02 02 02 02
02 02 02 02 02 02 02 02 02 02 02 02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No Linux, enviei diretamente para o dispositivo usando:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\x74\x00\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /dev/ttyACM0 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado no display foi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;00
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Esse foi o momento em que ficou claro que a estrutura estava funcionando.&lt;/p&gt;

&lt;p&gt;O hardware estava respondendo ao pacote.&lt;/p&gt;

&lt;p&gt;Agora era necessário descobrir qual campo controlava o valor mostrado no LCD.&lt;/p&gt;




&lt;h2&gt;
  
  
  Testando os campos um por um
&lt;/h2&gt;

&lt;p&gt;Como eu sabia que o pacote aplicava XOR &lt;code&gt;0x02&lt;/code&gt;, não podia simplesmente colocar o valor decimal diretamente no byte enviado.&lt;/p&gt;

&lt;p&gt;Por exemplo, para mostrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;o valor decimal é:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50 = 0x32
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mas o protocolo aplica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x32 XOR 0x02 = 0x30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Portanto, o byte efetivamente transmitido deveria ser:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;0x30
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para testar o primeiro campo do payload, o pacote ficava:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;74 00 30 02 02 02 02 ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;ou, no shell:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'\x74\x00\x30\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02\x02'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  | &lt;span class="nb"&gt;sudo tee&lt;/span&gt; /dev/ttyACM0 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E funcionou.&lt;/p&gt;

&lt;p&gt;O display mostrou:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Isso confirmou que:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Offset 0 → 't'
Offset 1 → flag
Offset 2 → valor exibido
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;E o valor do offset 2 era enviado com:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;valor XOR 0x02
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No código original, esse byte vinha de um campo interno da estrutura:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;param_1 + 0x1c
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Eu ainda não precisava saber o nome original do campo.&lt;/p&gt;

&lt;p&gt;Experimentalmente, já tinha descoberto o que ele fazia.&lt;/p&gt;




&lt;h2&gt;
  
  
  Transformando o teste em um script
&lt;/h2&gt;

&lt;p&gt;Depois de confirmar que o byte controlava o display, criei um pequeno script de linha de comando.&lt;/p&gt;

&lt;p&gt;A ideia era simples:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;temperatura decimal
        ↓
XOR 0x02
        ↓
montar pacote
        ↓
escrever em /dev/ttyACM0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Uma versão simplificada:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;

&lt;span class="nv"&gt;PORT&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"/dev/ttyACM0"&lt;/span&gt;
&lt;span class="nv"&gt;TEMP&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$1&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Uso: &lt;/span&gt;&lt;span class="nv"&gt;$0&lt;/span&gt;&lt;span class="s2"&gt; &amp;lt;temperatura&amp;gt;"&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$TEMP&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^[0-9]+&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; TEMP &amp;lt; 0 &lt;span class="o"&gt;||&lt;/span&gt; TEMP &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 99 &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Temperatura deve estar entre 0 e 99."&lt;/span&gt;
    &lt;span class="nb"&gt;exit &lt;/span&gt;1
&lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nv"&gt;ENCODED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;$((&lt;/span&gt;TEMP &lt;span class="o"&gt;^&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="k"&gt;))&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; TEMP_BYTE &lt;span class="s1"&gt;'\\x%02x'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$ENCODED&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="nv"&gt;FRAME&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;74&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;00&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;TEMP_BYTE&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="c"&gt;# Os demais campos permanecem zerados antes do XOR,&lt;/span&gt;
&lt;span class="c"&gt;# portanto são enviados como 0x02.&lt;/span&gt;
FRAME+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02"&lt;/span&gt;
FRAME+&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02&lt;/span&gt;&lt;span class="se"&gt;\x&lt;/span&gt;&lt;span class="s2"&gt;02"&lt;/span&gt;

&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$FRAME&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$PORT&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assim:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./aqua120x 50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;fazia o display mostrar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;50
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Pegando a temperatura diretamente do kernel
&lt;/h2&gt;

&lt;p&gt;O próximo passo era parar de enviar valores manualmente.&lt;/p&gt;

&lt;p&gt;Eu queria mostrar a temperatura real da CPU.&lt;/p&gt;

&lt;p&gt;A primeira opção seria chamar:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;sensors
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e filtrar a saída.&lt;/p&gt;

&lt;p&gt;Mas isso seria executar um programa completo repetidamente apenas para ler um valor que o próprio kernel já disponibiliza.&lt;/p&gt;

&lt;p&gt;No Linux, sensores de hardware são expostos através do sysfs.&lt;/p&gt;

&lt;p&gt;No meu caso, o sensor &lt;code&gt;coretemp&lt;/code&gt; estava disponível em:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/sys/class/hwmon/hwmonX/
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O arquivo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;temp1_input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;retornava algo como:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;35000
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O valor está em milésimos de grau Celsius.&lt;/p&gt;

&lt;p&gt;Então:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;35000 / 1000 = 35°C
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para converter diretamente para inteiro:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print int($1/1000)}'&lt;/span&gt; /sys/class/hwmon/hwmonX/temp1_input
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;O resultado:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;35
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Agora bastava passar essa saída para o script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./aqua120x &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;awk&lt;/span&gt; &lt;span class="s1"&gt;'{print int($1/1000)}'&lt;/span&gt; /sys/class/hwmon/hwmonX/temp1_input&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Atualizando o display periodicamente
&lt;/h2&gt;

&lt;p&gt;Por fim, coloquei o comando para rodar periodicamente.&lt;/p&gt;

&lt;p&gt;Uma forma simples de fazer isso foi usando &lt;code&gt;watch&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;watch &lt;span class="nt"&gt;-n&lt;/span&gt; 1 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="s1"&gt;'./aqua120x "$(awk '&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;'{print int($1/1000)}'&lt;/span&gt;&lt;span class="se"&gt;\'&lt;/span&gt;&lt;span class="s1"&gt;' /sys/class/hwmon/hwmonX/temp1_input)"'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A cada segundo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;temperatura do kernel
        ↓
awk
        ↓
valor inteiro
        ↓
script
        ↓
XOR
        ↓
pacote de 23 bytes
        ↓
/dev/ttyACM0
        ↓
LCD do water cooler
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para testar a variação da temperatura, também usei:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;stress-ng &lt;span class="nt"&gt;--cpu&lt;/span&gt; 1 &lt;span class="nt"&gt;--cpu-load&lt;/span&gt; 50 &lt;span class="nt"&gt;--timeout&lt;/span&gt; 1m
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Enquanto o processador recebia uma carga leve, a temperatura variava e o display acompanhava.&lt;/p&gt;




&lt;h2&gt;
  
  
  O que eu aprendi com isso
&lt;/h2&gt;

&lt;p&gt;O resultado final foi relativamente simples: um pequeno script escrevendo bytes em uma porta serial.&lt;/p&gt;

&lt;p&gt;Mas chegar até ele foi a parte interessante.&lt;/p&gt;

&lt;p&gt;O processo envolveu:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identificar como o dispositivo era exposto no Linux.&lt;/li&gt;
&lt;li&gt;Analisar um executável Windows no Ghidra.&lt;/li&gt;
&lt;li&gt;Encontrar referências a &lt;code&gt;QSerialPort&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Descobrir como o software localizava o hardware através de VID/PID.&lt;/li&gt;
&lt;li&gt;Descobrir a configuração serial: &lt;code&gt;1000000 8N1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Seguir as chamadas até encontrar a função que montava o pacote.&lt;/li&gt;
&lt;li&gt;Reconstruir o layout de 23 bytes.&lt;/li&gt;
&lt;li&gt;Identificar a transformação &lt;code&gt;XOR 0x02&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Enviar pacotes manualmente.&lt;/li&gt;
&lt;li&gt;Alterar os campos individualmente.&lt;/li&gt;
&lt;li&gt;Descobrir experimentalmente qual byte controlava o valor exibido.&lt;/li&gt;
&lt;li&gt;Ler a temperatura diretamente do sysfs.&lt;/li&gt;
&lt;li&gt;Automatizar a atualização do LCD.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Tudo isso sem documentação do protocolo e sem código-fonte.&lt;/p&gt;

&lt;p&gt;O mais curioso é que, no começo, parecia que o dispositivo simplesmente "não tinha suporte para Linux".&lt;/p&gt;

&lt;p&gt;No fim, ele tinha uma interface serial perfeitamente utilizável.&lt;/p&gt;

&lt;p&gt;O que faltava era saber:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Qual sequência de bytes ele esperava receber.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Essa experiência também mudou um pouco a forma como eu vejo engenharia reversa.&lt;/p&gt;

&lt;p&gt;Eu imaginava algo muito distante, extremamente complexo e quase inacessível para quem nunca tinha feito isso.&lt;/p&gt;

&lt;p&gt;Na prática, pelo menos neste caso, o processo foi muito mais incremental:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;encontrar uma pista
      ↓
seguir uma função
      ↓
entender uma pequena parte
      ↓
testar no hardware
      ↓
usar o resultado como próxima pista
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No final, um water cooler sem aplicativo para Linux acabou virando um pequeno projeto de engenharia reversa.&lt;/p&gt;

&lt;p&gt;E agora o display mostra a temperatura da CPU em tempo real.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>linux</category>
    </item>
  </channel>
</rss>
