Skip to content
KitploitKITPLOIT
StrumentiBlog
Invia
StrumentiBlog
Invia

Strumenti di Hacking, PenTest e Cybersecurity per il tuo Arsenale di Sicurezza!

Kitploit è una directory di strumenti di hacking, cybersecurity e pentesting. Scopri gli ultimi aggiornamenti dei progetti per trovare vulnerabilità, analizzare sistemi, automatizzare i test e rafforzare la tua sicurezza.

··Feed·Contatto·Privacy·© 2026 Kitploit

Directory degli strumenti

Categorie

Vedi tutte le categorie
Loading categories
Strumenti/GitHubGitHub/markoglasgow/gemma_crackme_tutorial
Reverse EngineeringDebuggerAnalisi di BinariMachine LearningApprendimento e FormazioneReverse Engineering Assistito dall'IA
GitHubmarkoglasgow/gemma_crackme_tutorial

gemma_crackme_tutorial

Tutorial passo-passo sull'utilizzo del modello AI locale Google Gemma 4 E4B per fare reverse engineering di un crackme Windows con Ghidra, inclusa la configurazione per l'inferenza locale e la rinomina automatica di funzioni/variabili.

Più Popolari

Vedi tutti →

Scopri gli strumenti più utilizzati dalla nostra community.

Esplora tutti gli strumenti

Sfoglia la nostra collezione di strumenti

Vedi tutti gli strumenti →
Condividi
Vedi Repository
3533 mesi faRevisionato da Kitploit

Usare il modello AI locale Gemma 4 E4B di Google per fare Reverse Engineering di un semplice Crackme

Stavo giocando con il nuovo modello locale open weights Gemma E4B rilasciato da Google, e con mia sorpresa ho riscontrato un grande successo nell'usarlo per scenari di reverse engineering offline locale. Volevo scrivere questo tutorial per diffondere la notizia che l'AI locale è ora abbastanza buona per molti compiti basilari di reversing, e che le cose probabilmente miglioreranno rapidamente d'ora in poi.

Reverse Engineering e AI

Una delle parti più noiose del fare reversing di un nuovo binario è all'inizio, quando non hai alcuna intuizione su quali siano le funzioni e variabili importanti. Ci sono molti trucchi che i reverser usano per iniziare, tra cui guardare i riferimenti alle stringhe, il binary diffing o il matching di funzioni simili.

L'AI è molto utile qui, e personalmente ho avuto grande successo nell'usare l'API OpenAI per annotare un binario o ripulire l'output del decompilatore. Tuttavia, ci sono diversi svantaggi nell'usare queste API:

  • Costo - La decompilazione e il disassemblaggio generano tonnellate di token. Le API addebitano $ per token, quindi binari più grandi possono richiedere parecchi $$$ da analizzare. Se hai a che fare con un target grande con molti binari che si aggiornano ogni settimana, questi costi possono salire rapidamente e sono proibitivi per i reverse engineer hobbisti.

  • Privacy - Quando usi un'API remota, l'host dell'AI ha visibilità su ciò che stai facendo. Questo è un punto di non ritorno in alcuni scenari professionali.

  • Controllo - Quando ti affidi a un'API remota, non hai controllo su quali modelli ti vengono serviti, né su qual è la qualità dei modelli. Se fai affidamento su di loro per cose critiche, questo può essere un problema quando diventano lenti o vanno giù quando ne hai bisogno, o quando la qualità del loro output si degrada al punto da diventare inutilizzabili.

Eseguire i propri modelli AI locali risolve alcuni di questi punti dolenti:

  • Costo - Può essere molto più economico eseguire un modello locale piuttosto che affidarsi a un servizio ospitato. Mentre il tuo modello locale è probabilmente più piccolo e più lento di uno di un buon fornitore, se è abbastanza buono e funziona in un tempo ragionevole, può avere senso risparmiare denaro eseguendo il proprio modello, specialmente se stai elaborando grandi quantità di dati per compiti semplici.

  • Privacy - Quando esegui il modello localmente, non ci sono chiamate di rete in corso e hai il controllo completo della tua privacy. Nessuno può vedere per cosa stai usando il modello sulla tua macchina.

  • Controllo - La bellezza di un modello open weights è che nessuno può portartelo via. OpenAI o Anthropic potrebbero un giorno rendere i loro modelli SotA non disponibili, sia attraverso aumenti di prezzo che rimuovendo esplicitamente le loro API. Ma con un modello open weights, hai il controllo del tuo destino, nel bene e nel male.

I modelli AI locali hanno i loro svantaggi però:

  • Dimensione - Più grande è il tuo modello, più è intelligente. Tuttavia, la maggior parte dei modelli grandi non può essere eseguita su hardware consumer. Per questo motivo, se stai eseguendo un modello locale, è probabile che tu stia eseguendo un modello 10x-100x più piccolo di un modello SotA (State-of-the-Art). Questa diminuzione di dimensioni porta direttamente a una diminuzione dell'intelligenza del modello, rendendoli inadatti a molti compiti che le persone danno per scontati nei modelli SotA come ChatGPT/Codex o Claude.

  • Velocità - I modelli locali probabilmente saranno più lenti sulla tua macchina rispetto all'uso di un'API AI. Di nuovo, questo è dovuto alle limitazioni dell'hardware consumer e ad alcuni trucchi che i fornitori di API possono fare che generalmente non sono disponibili per te.

  • Configurazione - Eseguire modelli AI locali è come cercare di far funzionare Linux su un portatile ricondizionato rispetto a entrare nell'Apple Store e comprare un nuovo Macbook Air. L'esperienza di Codex e Claude Code è l'esperienza Apple Store dell'AI. L'esperienza del modello AI locale è il tizio con il fedora in garage che sbatte su un computer traballante cercando di farlo funzionare. Come minimo, devi preoccuparti delle seguenti cose:

    • Acquisire l'hardware giusto
    • Driver GPU
    • Scegliere e configurare il server di inferenza giusto
    • Scegliere un modello che si adatti al tuo hardware, sia abbastanza veloce da essere pratico e sia sufficientemente intelligente per il compito da svolgere
    • Usare il template di chat corretto con il tuo modello
    • Esplorare le quantizzazioni del modello per trovare il giusto compromesso tra dimensioni/velocità e intelligenza del modello
    • Promptare correttamente il modello
    • Scegliere l'harness giusto, o crearne uno proprio se nessuno di quelli esistenti funziona.

Non è un percorso facile, e molte persone si arrendono e presumono che i modelli AI locali non siano all'altezza del compito, perché non hanno mai trovato la giusta combinazione di hardware/modello/impostazioni/prompting/harness per farli funzionare per il loro compito. Anche se in molti casi hanno ragione, spero che questo tutorial almeno faccia luce su quanto siano avanzati i modelli locali, su come possano aiutare nel reverse engineering e ispiri le persone a dare una possibilità all'AI locale.

Setup di Reverse Engineering

  • Il crackme è un crackme Windows estremamente semplice scaricato da Crackmes.one qui: https://crackmes.one/crackme/69e13f938afd9d6c48b488fd

(la password dell'archivio è crackmes.one). Ho ospitato un link alternativo qui nel repository nel caso il link originale non fosse più disponibile.

  • Ghidra 12.04 è usato per disassemblaggio e decompilazione. Dovrai installare OpenJDK 21 per usarlo: https://github.com/nationalsecurityagency/ghidra

  • Mentre lavoravo a questo tutorial, ho vibe-codato con Claude Code un plugin per Ghidra per rinominare funzioni e variabili con l'AI. Puoi scaricare il plugin da qui: https://github.com/markoglasgow/Ghidra_FastAIRenamer_Plugin

Per installarlo, basta spostare il file zip ghidra_12.0.4_PUBLIC_20260427_FastAIRenamerPlugin.zip in ${GHIDRA_HOME}\Extensions\Ghidra, quindi eseguire Ghidra lanciando ${GHIDRA_HOME}\ghidraRun.bat. Per attivare il plugin, nella schermata iniziale di Ghidra, dal menu in alto seleziona File -> Install Extensions, poi nel browser dei plugin spunta la casella accanto a FastAIRenamerPlugin, quindi clicca Ok. Ghidra ti chiederà di riavviarsi, quindi fallo subito.

Per configurare il plugin, la prossima volta che Ghidra si avvia, nel menu in alto vai su Tools -> Run Tool -> CodeBrowser. Ghidra dirà "New Extensions detected. Would you like to configure them?". Clicca sì, poi di nuovo spunta la casella accanto a FastAIRenamerPlugin, quindi clicca Ok. Quando si apre il CodeBrowser, nel menu in alto clicca Window -> Fast AI Renamer, quindi clicca il pulsante Config. Qui potrai configurare il tuo modello AI. Chiudi la finestra del plugin e la finestra vuota del CodeBrowser una volta terminato.

Nota: se hai problemi a caricare il plugin, potresti dover abilitare la modalità Developer in Ghidra (File -> Configure -> checkbox accanto a Developer)

Nota: puoi sempre verificare se il plugin è caricato andando nel CodeBrowser, cliccando File -> Configure -> Ghidra Core -> clicca il pulsante blu configure -> filtra per "FastAIRenamer" -> assicurati che la casella accanto al suo nome sia spuntata.

Nota: per disinstallare il plugin, prima apri CodeBrowser, File -> Configure -> Ghidra Core -> clicca il pulsante blu configure -> filtra per "FastAIRenamer" -> deseleziona -> ok. Chiudi CodeBrowser, poi nella finestra iniziale di Ghidra, File -> Install Extensions -> deseleziona "FastAIRenamer". Infine chiudi Ghidra ed elimina ghidra_12.0.4_PUBLIC_20260427_FastAIRenamerPlugin.zip da ${GHIDRA_HOME}\Extensions\Ghidra. Per assicurarti che l'estensione sia eliminata, la prossima volta che esegui Ghidra, nella finestra iniziale vai su Help -> Runtime Information -> Extension Points -> filtra per "FastAIRenamer" e assicurati che non appaia nulla. Phew.

Infine, assicurati di avere Visual Studio o un altro ambiente di sviluppo C++ configurato in modo da poter vibe codare una soluzione al crackme quando sarà il momento.

Setup dell'AI Locale

Le configurazioni dell'AI locale delle persone varieranno notevolmente a seconda dell'hardware e dei $$$ a disposizione. Per me:

  • Personalmente eseguo una Nvidia GTX 3080, con Cuda 13.2 installato. Puoi controllare la tua versione di Cuda eseguendo nvcc --version nel tuo Terminale Windows.

  • Sto eseguendo le quantizzazioni https://huggingface.co/bartowski/google_gemma-4-E4B-it-GGUF/blob/main/google_gemma-4-E4B-it-Q8_0.gguf di bartowski. Potrebbe essere una superstizione da parte mia, ma per compiti analitici cerco di eseguire con la minima quantizzazione possibile, ed evitarla del tutto se posso.

  • Uso llama.cpp come server di inferenza, con questa versione specifica: llama-b8893-bin-win-cuda-13.1-x64.

  • Eseguo llama.cpp con le seguenti impostazioni CLI:``` ..\llama-b8893-bin-win-cuda-13.1-x64\llama-server.exe ^ --port 8090 ^ --threads 12 ^ --n-gpu-layers 256 ^ --no-mmap ^ --model "google_gemma-4-E4B-it-Q8_0.gguf" ^ --ctx-size 32768 ^ --temp 1.0 ^ --top-k 64 ^ --top-p 0.95 ^ --offline

root@kitploit:~
Questo produce `75 token/sec`, il che è piuttosto veloce per un'IA locale.

## Nessun hardware? Nessun problema

Se non hai l'hardware disponibile o hai problemi con la configurazione, ma vuoi comunque seguire questo tutorial, puoi ottenere un account OpenRouter e usare una delle API gratuite:
https://openrouter.ai/models/?q=free

Google sta offrendo specificamente `Gemma 4 31B` e `Gemma 4 26B-A4B` gratuitamente in questo momento (solo per un periodo limitato):
https://openrouter.ai/google/gemma-4-31b-it:free
https://openrouter.ai/google/gemma-4-26b-a4b-it:free

... tieni presente che queste API gratuite sono lente, inaffidabili, e tutti i dati che invii verranno probabilmente registrati nei loro analytics interni e utilizzati per il loro prossimo addestramento. Tuttavia, ai fini di questo tutorial, queste API dovrebbero permetterti di seguire.

Per configurare il plugin di Ghidra per usare il tuo account OpenRouter, apri la configurazione del plugin e inserisci quanto segue:```
Base URL: https://openrouter.ai/api/
API Key: <your OpenRouter API Key>
Model Name: qwen/qwen3-235b-a22b-2507

Questo esempio eseguirà il plugin contro il modello qwen3 qui

Ispezione del Crackme

Decomprimi ed esegui crackmepls.exe, ti apparirà una schermata di login standard. Inserisci una password a caso e riceverai un messaggio 'Access Denied':``` User: marko Pass: 123 Access denied

root@kitploit:~
Avvia Ghidra eseguendo `ghidraRun.bat`. Nella barra degli strumenti seleziona `File -> New Project`, lascia selezionato `Non-Shared Project` e clicca `Next >>`, quindi seleziona una directory di progetto vuota e dai un nome al progetto. Poi clicca `Finish`. 

Successivamente, clicca su `File -> Import File`, quindi seleziona `crackmepls.exe` per aggiungerlo al progetto. Ghidra farà apparire alcuni dettagli sul file, informandoti che è un file `Portable Executable (PE)` per `x86:LE:64:default:windows`. Basta cliccare `OK` per accettare senza modificare nulla. Dopo un breve momento, appariranno ulteriori dettagli sul file, anche in questo caso clicca `OK` per accettare. Infine, fai doppio clic su `crackmepls.exe` nel progetto per aprire il Code Browser e iniziare il disassemblaggio. 

All'inizio, Ghidra mostrerà un messaggio che dice `crackmepls.exe has not been analyzed. Would you like to analyze it now?`. Clicca `Yes` e poi il pulsante `Analyze` nella finestra successiva, quindi attendi che Ghidra localizzi, disassembli e decompili tutte le funzioni nel binario. Potresti ricevere uno o due errori durante l'analisi relativi a file PDB non trovati, basta cliccare `Ok` e ignorarli. 

Una volta che Ghidra ha terminato l'analisi del file, dovresti vedere qualcosa come di seguito:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/2be69c6e539fa74e1ac31eec4304bcd7e8952ba96b91fab51add36ed6c1440ee.png "Program Entry")

Questo è il punto di ingresso standard di MSVC. Fai doppio clic su `FUN_14000200c` e scorri verso il basso. 

Dovresti vedere nella finestra del decompilatore riferimenti a `__p___argv`, `__p___argc`, e poi una chiamata di funzione `FUN_140001290` che li accetta come parametri. Questa è probabilmente la funzione `main()` del crackme, quindi fai doppio clic su di essa.

![alt text](https://assets.kitploit.com/production/public/readmes/13760/577b01a818727ee43010a24d03236157d22a69f94599f4117420436a641745ac.png "Finding Main")

Una volta nella funzione `FUN_140001290`, scorri un po' verso il basso nella finestra del decompilatore. Vedrai riferimenti alle stringhe `User:` e `Pass:`, insieme a riferimenti a `basic_istream` (flusso di input) e `basic_ostream` (flusso di output). 

![alt text](https://assets.kitploit.com/production/public/readmes/13760/dda6baaa2d284402bd8ce61c776810a0c154c6861e0012a56391fccbeb71a0c5.png "Print and inputs")

Queste stringhe corrispondono alle istruzioni di stampa e input che abbiamo visto quando abbiamo eseguito il crackme per la prima volta, quindi sappiamo di essere nella funzione `main()` del crackme.

A questo punto, inizierebbe il lavoro noioso in cui dobbiamo effettivamente sederci e rinominare ogni nome di variabile e chiamata di funzione in qualcosa di significativo, mentre analizziamo cosa sta facendo il binario per risolvere il crackme. I reverse engineer un tempo dovevano farlo a mano nel disassemblato, ma per fortuna la tecnologia e le tasse ci hanno dato questo pratico decompilatore che può essere scriptato per funzionare con l'IA.

Quindi invece di fare lavoro vero, rilassiamoci, spegniamo il cervello e lasciamo che la nostra IA locale faccia tutto il lavoro per noi.

Durante la configurazione per questo tutorial, dovresti aver installato un plugin di Ghidra per aiutare con la rinomina di funzioni e variabili. È il momento di usarlo. In Ghidra, nella barra degli strumenti in alto, clicca su `Window` e poi `Fast AI Renamer` per aprire il plugin. 

![alt text](https://assets.kitploit.com/production/public/readmes/13760/7511b88d33bb13e45a830295f42d1fceda2eb5897beffbec6ca5e6a3bd52f46e.png "Open Fast AI Renamer")

Dovresti vedere l'interfaccia del plugin, che è un insieme di pulsanti e un'area di testo. Inizia cliccando il pulsante "Config", poi assicurati di aver configurato tutto per comunicare correttamente con la tua IA locale. Ecco come appare sul mio computer:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/4807636497952b764fc6dad362d0defc2804df0d5ba353c6938e266864c676cc.png "Config")

Puoi chiudere la finestra di configurazione cliccando "Save", e poi clicca sul pulsante `Rename Variables`. Dovrebbe apparire una finestra di avanzamento con un drago, e potresti sentire il tuo computer iniziare a sforzarsi mentre lavora per eseguire il modello AI locale per rinominare tutte le variabili nella finestra del decompilatore:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/1f9e6ac902cce19f63319621adaabef504ad5a94fa84db2ec39782b74e2f18f6.png "Rename Variables")

Una volta che l'IA ha finito (ci vogliono circa 10-20 secondi sulla mia macchina), dovresti vedere una descrizione delle variabili che ha rinominato nell'area di testo del plugin, e le variabili stesse dovrebbero essere rinominato nella finestra del decompilatore:

![alt text](https://assets.kitploit.com/production/public/readmes/13760/3454e5e42d7a4d84dffffbc6343e9579d33b3241b42e0be06bdb068eb91ed1e3.png "Rename Results")

Tutti i modelli di IA, ma specialmente quelli locali piccoli, sono intrinsecamente inaffidabili, quindi potresti vedere qualche errore in quest'ultimo passaggio. Ne riparleremo più avanti. Nel frattempo, puoi sempre rieseguire l'IA cliccando nuovamente il pulsante `Rename Variables`, finché non ottieni risultati senza errori che ti sembrano soddisfacenti. 

## Risolvere il Crackme 

A questo punto, abbiamo un output del decompilatore ben annotato, in cui tutti i nomi delle variabili sono stati rinominati in qualcosa di significativo. Tradizionalmente, un reverse engineer potrebbe ora leggere questo codice e iniziare a abbozzare una soluzione per il Crackme. Tuttavia, ho pensato che sarebbe stato interessante provare a far risolvere il crackme all'IA locale. Ricorda, oggi non useremo il nostro cervello. 

Per prima cosa, ho fatto in modo che l'IA finisse di annotare l'intero binario per noi cliccando sul pulsante `Rename ALL Functions and Variables`. Questo serve ad assegnare nomi a tutte le chiamate di funzione utilizzate in questa funzione del crackme, il che pulisce ancora di più l'output del decompilatore. Sulla mia macchina, ci vogliono circa 15 minuti. 

Quando si utilizza `llama.cpp` con i parametri CLI che ho elencato in `Local AI Setup`, puoi aprire un'interfaccia Chat per il tuo modello locale navigando nel browser a [http://localhost:8090/](http://localhost:8090/)

Qui, ho inserito l'output del decompilatore della funzione del crackme (puoi ottenerlo cliccando sul pulsante "Decompile Function" nell'interfaccia del plugin, o semplicemente copiandolo dalla finestra del decompilatore sulla destra), e ho chiesto all'IA di scrivere una soluzione per noi:

======================================================================
======================================================================
======================================================================


Voglio che mi aiuti a scrivere una soluzione per un crackme. Di seguito è riportato l'elenco del decompilatore di una funzione di un crackme, in cui l'utente digita un nome utente e una password, e poi l'accesso viene concesso se ha fornito la password corretta. La password viene calcolata nella funzione.```


/* WARNING: Function: __security_check_cookie replaced with injection: security_check_cookie */
/* **Reasoning:**
   The function takes user input (a username/input and a password) via standard input. It processes
   the user input by calculating a complex, custom checksum/hash. It then compares the provided
   password input against a target buffer (likely a stored hash or secret). Finally, it determines
   and outputs whether "Access granted" or "Access denied," indicating the function serves as an
   authentication routine. */

undefined8 authenticate_user(undefined8 param_1,undefined8 param_2,undefined8 max_len)

{
  uint user_checksum;
  int iVar1;
  undefined8 ****temp_free_ptr;
  ulonglong user_input_byte_index;
  char *result_message;
  undefined1 *status_buffer;
  undefined8 ****data_buffer_ptr;
  ulonglong pass_input_length_1;
  bool access_granted;
  undefined8 uStack_d0;
  undefined1 status_buffer_small [8];
  undefined1 status_buffer_large [32];
  undefined8 ***allocated_ptrs [2];
  size_t compare_length;
  ulonglong ptr_metadata_size;
  undefined8 ***user_input_buffer;
  undefined8 uStack_78;
  ulonglong user_input_length;
  ulonglong user_input_length_param;
  undefined8 ***pass_input_buffer;
  undefined8 uStack_58;
  size_t pass_input_length;
  ulonglong pass_input_length_param;
  ulonglong checksum_seed;
  code *code_pointer;
  undefined8 ***pass_input_buffer_ptr;
  undefined8 ***user_input_buffer_ptr;
  ulonglong user_input_length_1;
  
  status_buffer = status_buffer_small;
  checksum_seed = MAGIC_VALUE_1 ^ (ulonglong)status_buffer_small;
  user_input_byte_index = 0;
  uStack_78 = 0;
  user_input_length = 0;
  user_input_length_param = 0xf;
  user_input_buffer = (undefined8 ****)0x0;
  uStack_58 = 0;
  pass_input_length = 0;
  pass_input_length_param = 0xf;
  pass_input_buffer = (undefined8 ****)0x0;
  formatted_string_output((basic_ostream<char,struct_std::char_traits<char>_> *)cout_exref,"User: ");
  extract_token_from_stream((basic_istream<char,struct_std::char_traits<char>_> *)cin_exref,
                (longlong *)&user_input_buffer,max_len);
  formatted_string_output((basic_ostream<char,struct_std::char_traits<char>_> *)cout_exref,"Pass: ");
  extract_token_from_stream((basic_istream<char,struct_std::char_traits<char>_> *)cin_exref,
                (longlong *)&pass_input_buffer,max_len);
  user_input_length_1 = user_input_length_param;
  user_input_buffer_ptr = user_input_buffer;
  user_checksum = 0;
  pass_input_length_1 = user_input_byte_index;
  if (user_input_length != 0) {
    do {
      temp_free_ptr = &user_input_buffer;
      if (0xf < user_input_length_param) {
        temp_free_ptr = (undefined8 ****)user_input_buffer;
      }
      user_checksum =
           ((int)user_input_byte_index + 1) *
           (int)*(char *)((longlong)temp_free_ptr + user_input_byte_index) +
           (int)pass_input_length_1;
      user_checksum = user_checksum * 8 ^ user_checksum;
      user_input_byte_index = user_input_byte_index + 1;
      pass_input_length_1 = (ulonglong)user_checksum;
    } while (user_input_byte_index < user_input_length);
  }
  int_to_string_dynamic(allocated_ptrs,user_checksum * 0x539 ^ 0x5a5a);
  pass_input_length_1 = pass_input_length_param;
  pass_input_buffer_ptr = pass_input_buffer;
  temp_free_ptr = &pass_input_buffer;
  if (0xf < pass_input_length_param) {
    temp_free_ptr = (undefined8 ****)pass_input_buffer;
  }
  data_buffer_ptr = allocated_ptrs;
  if (0xf < ptr_metadata_size) {
    data_buffer_ptr = (undefined8 ****)allocated_ptrs[0];
  }
  if (compare_length == pass_input_length) {
    if (compare_length == 0) {
      access_granted = true;
    }
    else {
      iVar1 = memcmp(data_buffer_ptr,temp_free_ptr,compare_length);
      access_granted = iVar1 == 0;
    }
  }
  else {
    access_granted = false;
  }
  if (0xf < ptr_metadata_size) {
    temp_free_ptr = (undefined8 ****)allocated_ptrs[0];
    status_buffer = status_buffer_small;
    if (0xfff < ptr_metadata_size + 1) {
      temp_free_ptr = (undefined8 ****)allocated_ptrs[0][-1];
      data_buffer_ptr =
           (undefined8 ****)((longlong)allocated_ptrs[0] + (-8 - (longlong)temp_free_ptr));
      status_buffer = status_buffer_small;
      if ((undefined8 ****)0x1f < data_buffer_ptr) {
        code_pointer = (code *)swi(0x29);
        (*code_pointer)(5);
        temp_free_ptr = data_buffer_ptr;
        status_buffer = status_buffer_large;
      }
    }
    *(undefined8 *)(status_buffer + -8) = 0x140001424;
    free(temp_free_ptr);
  }
  result_message = "Access granted\n";
  if (!access_granted) {
    result_message = "Access denied\n";
  }
  *(undefined8 *)(status_buffer + -8) = 0x140001443;
  formatted_string_output((basic_ostream<char,struct_std::char_traits<char>_> *)cout_exref,result_message);
  if (0xf < pass_input_length_1) {
    temp_free_ptr = (undefined8 ****)pass_input_buffer_ptr;
    if (0xfff < pass_input_length_1 + 1) {
      temp_free_ptr = (undefined8 ****)pass_input_buffer_ptr[-1];
      data_buffer_ptr =
           (undefined8 ****)((longlong)pass_input_buffer_ptr + (-8 - (longlong)temp_free_ptr));
      if ((undefined8 ****)0x1f < data_buffer_ptr) {
        code_pointer = (code *)swi(0x29);
        (*code_pointer)(5);
        status_buffer = status_buffer + 8;
        temp_free_ptr = data_buffer_ptr;
      }
    }
    *(undefined8 *)(status_buffer + -8) = 0x14000147d;
    free(temp_free_ptr);
  }
  if (0xf < user_input_length_1) {
    temp_free_ptr = (undefined8 ****)user_input_buffer_ptr;
    if (0xfff < user_input_length_1 + 1) {
      temp_free_ptr = (undefined8 ****)user_input_buffer_ptr[-1];
      data_buffer_ptr =
           (undefined8 ****)((longlong)user_input_buffer_ptr + (-8 - (longlong)temp_free_ptr));
      if ((undefined8 ****)0x1f < data_buffer_ptr) {
        code_pointer = (code *)swi(0x29);
        (*code_pointer)(5);
        status_buffer = status_buffer + 8;
        temp_free_ptr = data_buffer_ptr;
      }
    }
    *(undefined8 *)(status_buffer + -8) = 0x1400014b7;
    free(temp_free_ptr);
  }
  *(undefined8 *)(status_buffer + -8) = 0x1400014c9;
  return 0;
}


Leggere il listato del decompilatore e quindi completare il mio codice C++ qui sotto per calcolare la password corretta per un nome utente inserito:``` #include #include #include #include

int main() { std::string username; std::cout << "User: "; std::cin >> username;

root@kitploit:~
uint32_t password_value = 0;

std::cout << "Pass: " << password_value << "\n";
return 0;

}

root@kitploit:~
======================================================================
======================================================================
======================================================================

## La soluzione generata


La prima volta che ha generato una soluzione, era sbagliata. L'ho inclusa in [tut1.txt](https://github.com/markoglasgow/gemma_crackme_tutorial/blob/HEAD/tut1.txt) in questo repository. A prima vista la soluzione _sembra_ corretta, ma fallisce quando la proviamo. Le IA sono fondamentalmente inaffidabili, e questo ne è un perfetto esempio. 

Non importa, possiamo semplicemente aprire una nuova chat, copiare e incollare il prompt sopra, e provare a far generare all'IA una nuova soluzione. Nel mio caso, ho ottenuto una soluzione funzionante al secondo tentativo:```
#include <cstdio>
#include <cstdint>
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>

/**
 * @brief Simulates the custom checksum calculation defined in the crackme function.
 *
 * @param username The input string (username).
 * @return int The final calculated user_checksum.
 */
int calculate_username_checksum(const std::string& username) {
    // user_checksum is treated as an 'int' (32-bit signed integer) during the core calculation.
    int user_checksum = 0;

    // pass_input_length_1 acts as the state variable and is a ulonglong (64-bit).
    // It starts at 0, as pass_input_length_1 = user_input_byte_index (which is 0) initially.
    uint64_t state_pass_length_1 = 0;

    for (size_t i = 0; i < username.length(); ++i) {
        // i = user_input_byte_index

        // Step 1: Calculate the intermediate value T1.
        // The decompiler implies this calculation happens using standard int math 
        // (truncating/wrapping if intermediate values exceed 32 bits).
        // (int)user_input_byte_index + 1
        int index_term = (int)(i + 1);

        // (int)*(char *)((longlong)temp_free_ptr + user_input_byte_index)
        // This is just the ASCII value of the character.
        int char_term = (int)username[i];

        // (int)pass_input_length_1
        // The state is cast down to an int for this arithmetic operation.
        int state_term = (int)state_pass_length_1;

        // T1 = ((int)user_input_byte_index + 1) * (int)char + (int)state
        int intermediate_t1 = index_term * char_term + state_term;

        // Step 2: Apply the XOR/Multiplication hash function.
        // user_checksum = user_checksum * 8 ^ user_checksum;
        // Note: The original code structure suggests the update happens here, 
        // but since we are recalculating the hash step-by-step, we use the definition:
        // user_checksum = T1 * 8 ^ T1; 

        // Since the original code overwrites user_checksum completely with the hash 
        // of the current character, we use the T1 derived above.

        int current_checksum = intermediate_t1;
        current_checksum = current_checksum * 8 ^ current_checksum;

        // Update the final running checksum
        user_checksum = current_checksum;

        // Step 3: Update the state variable for the next iteration.
        state_pass_length_1 = (uint64_t)user_checksum;
    }

    return user_checksum;
}


int main()
{
    std::string username;
    std::cout << "User: ";
    std::cin >> username;

    // 1. Calculate the final checksum using the username
    int final_user_checksum = calculate_username_checksum(username);

    // 2. Calculate the final hash/secret value
    // FinalHashValue = user_checksum * 0x539 ^ 0x5a5a
    // Note: The arithmetic here is done on the final 32-bit 'int' checksum.
    uint32_t final_hash_value = (uint32_t)final_user_checksum * 0x539 ^ 0x5a5a;

    // 3. Convert the final hash value into a string (as implied by int_to_string_dynamic)
    // Since the password needs to match the memory contents (memcmp), it must be the string representation.
    std::stringstream ss;
    ss << final_hash_value;
    std::string required_password = ss.str();


    // We output the required password string.
    std::cout << "Pass: " << required_password << "\n";
    return 0;
}

Ho incluso la chat che ha generato la soluzione come solution_chat.html in questo repo. Includeva il flusso di ragionamento, che ho pensato fosse piuttosto interessante per un modello così piccolo eseguito in locale.

Comunque, possiamo compilare la soluzione con Visual Studio e poi eseguirla per generare una combinazione valida di nome utente/password. Inseriamo quindi nome utente e password nel crackme e verifichiamo di averlo risolto:

alt text

gg

Limitazioni, allucinazioni ed errori

Come abbiamo visto più volte durante questo tutorial, l'IA è intrinsecamente inaffidabile. Considera i due screenshot qui sotto:

alt text

alt text

Il primo screenshot proviene da prima nel tutorial, mentre il secondo screenshot è stato scattato allo stesso passaggio, ma con una diversa riesecuzione della ridenominazione tramite IA. Nota nella finestra del decompilatore come nello screenshot in alto il buffer di input della password sia etichettato &pass_input_buffer, mentre nello screenshot in basso lo stesso buffer è etichettato &username_buffer_1. Nello screenshot in basso, l'IA ci sta mentendo e chiamiamo queste menzogne "allucinazioni".

Tutta l'IA ti mentirà e allucinerà. Più piccolo è il modello, più frequenti sono le allucinazioni. Più grande è il modello, più sarai fortunato e meno allucinerà. Più paghi, più sei fortunato. Tuttavia, nessun modello di IA è immune alle allucinazioni, e non devi mai dimenticarlo mentre lavori con loro.

L'IA è più adatta per compiti in cui puoi verificare l'output, o in cui l'output ha una posta in gioco sufficientemente bassa da non richiedere una precisione perfetta. Nel caso del reverse engineering, l'IA può aiutarci a dare rapidamente un'occhiata a un binario sconosciuto e risparmiarci molto lavoro manuale noioso. Ma come abbiamo visto con la cattiva ridenominazione che ha fatto, può anche portarci fuori strada. Tratta sempre i risultati generati dall'IA con cautela e, se possibile, verificali sempre.

Il modo migliore per usare l'IA è verificarne automaticamente i risultati. Ad esempio, se fai scrivere codice all'IA, sarebbe bello avere uno script che testi l'output dell'IA rispetto a una suite di test scritta a mano. Nel caso dell'IA che genera una soluzione per il nostro crackme, avremmo potuto scrivere una suite di test che testi automaticamente ogni soluzione generata dall'IA contro il crackme, e poi accettare una soluzione che generasse password valide per migliaia di nomi utente diversi. Poi avremmo potuto eseguire l'IA in un ciclo finché non scriveva una soluzione che superava i nostri test.

Limitazioni dei modelli piccoli

Abbiamo risolto questo crackme con un modello relativamente piccolo (Gemma 4 E4B). Questo modello ha 8 miliardi di parametri, a differenza dei modelli SotA esistenti che hanno centinaia di miliardi se non trilioni di parametri. Un modello più piccolo non sarà in grado di ragionare bene, allucinerà più frequentemente e diventerà inutile più velocemente man mano che la sua finestra di contesto si riempie.

In un vero esercizio di reverse engineering, potremmo dover fare reverse engineering di funzioni più grandi della finestra di contesto del nostro modello piccolo, o con logica troppo contorta per la sua intelligenza limitata. Dobbiamo conoscere i limiti dei nostri modelli quando li usiamo, così da capire in anticipo dove e come ci deluderanno.

Man mano che l'hardware consumer avanza di pari passo con l'architettura dei modelli, credo che saremo in grado di eseguire modelli più grandi e migliori in locale, il che metterà compiti di reverse engineering ancora più avanzati alla portata dei nostri modelli locali.

Lavori futuri

Il plugin Ghidra "Fast AI Renamer" che ho scritto attualmente scrive un riepilogo per ogni funzione che analizza e ha la capacità di esportare in massa i listati C e asm del progetto su cui sta lavorando. Vorrei scrivere uno strumento che calcoli un embedding per ogni riepilogo di funzione e lo indicizzi in un database vettoriale o SQLite, in modo da poter interrogare il database per trovare funzioni in base a query in linguaggio naturale.

Il plugin Ghidra attualmente utilizza un flusso di lavoro semplice senza conversazioni multi-turno per la ridenominazione e si basa sull'analisi di un CSV dall'output dell'IA per svolgere il suo compito. Ho implementato le cose in questo modo perché non pensavo che un piccolo modello di IA sarebbe stato in grado di gestire le lunghe conversazioni e le multiple chiamate a strumenti inerenti a un flusso di lavoro agentico. Potrei sbagliarmi e dovrebbero essere esplorati modi alternativi di sollecitare l'IA. Tuttavia, per i piccoli modelli locali, penso sia meglio limitare il più possibile la dimensione della finestra di contesto.

Non sono sicuro di quanto il plugin Ghidra sia vulnerabile all'iniezione di prompt, e i professionisti della sicurezza dovrebbero diffidare dall'usarlo per analizzare malware o binari ostili. In generale, questo vale per tutti gli strumenti di reverse engineering basati sull'IA, che potrebbero avere server MCP localhost non autenticati o strumenti disponibili per funzioni sensibili che possono ottenere l'esecuzione di codice. Fai attenzione.

Dato che stiamo usando un modello generico piccolo per il reverse engineering, mi chiedo quanto le sue prestazioni possano essere migliorate ottimizzandolo su conversazioni distillate da un modello più grande e più intelligente?

Infine, la funzionalità di esportazione C/asm del plugin Ghidra funziona bene con Claude Code, e sono riuscito a usare Claude Code + Sonnet per scrivere una soluzione corretta per questo crackme al primo tentativo, semplicemente fornendogli la decompilazione annotata del crackme e chiedendogli di trovare una soluzione. Mi chiedo se esistano competenze che possiamo scrivere per Claude Code che lo rendano migliore nell'analizzare un binario attraverso i file di testo dell'output del decompilatore, senza farlo passare attraverso un server MCP verso Ghidra per eseguire l'analisi. Ad esempio, potremmo introdurre una competenza per Claude Code che gli permetta di ispezionare i byte nella memoria virtuale del binario, in modo che se vede alcuni dati referenziati dagli elenchi di disassemblaggio/decompilazione, possa vedere cosa contiene quella memoria senza dover passare attraverso Ghidra.

Ringraziamenti e riconoscimenti

Grazie a Kryptos per aver creato il Crackme e a Crackmes.one per averlo ospitato. Li apprezzo per aver raccolto il testimone dove crackmes.de ha lasciato (RIP).

Grazie a Google per aver rilasciato un modello locale sorprendente e ingegnoso.

Grazie alla NSA per aver usato i miei soldi delle tasse per creare un fantastico strumento di reverse engineering, e grazie a Ryan Kurtz per tutto il supporto che fornisce sul Github di Ghidra.

Scarica lo strumento