Discussione Ufficiale Programmiamo con Inforge | Bruteforce in C

Una Discussione Ufficiale punta a raccogliere tutte le informazioni su un argomento o un fatto di attualità, con costanti aggiornamenti da parte del creatore e dei partecipanti.

Not an engineer

Moderatore
31 Ottobre 2011
2,522
855
620
Ultima modifica da un moderatore:
main.png


Programmiamo con Inforge | Bruteforce

Ad oggi, sul web, si trovano moltissime guide sui vari linguaggi di programmazione e sulle loro molteplici applicazioni. Tuttavia, chi si approccia a queste risorse, non sempre riesce a mettere in pratica ciò che ha appreso. Al fine di limitare queste mancanze, nasce Programmiamo con Inforge.

In questa rubrica potrai scrivere codice per la risoluzione di alcuni problemi legati alla programmazione, mettendo in pratica quanto stai apprendendo dalla teoria oppure mostrando le tue abilità e competenze nel campo dell’informatica.

Nelle precedenti 5 edizioni di questa rubrica vi abbiamo messo alla prova con 5 esercitazioni in ordine di difficoltà:


  1. Esercitazione 01 in C | Livello base
  2. Esercitazione 02 in C | Livello base
  3. Esercitazione 03 in C | Livello intermedio
  4. Esercitazione 04 in C | Livello intermedio
  5. Esercitazione 05 in C | Livello difficile
In questo sesto capitolo vi proponiamo un progetto da sviluppare passo per passo insieme allo staff di Inforge. Il progetto sarà diviso in features: volta per volta vi chiederemo di aggiungere una nuova funzionalità così da guidarvi verso lo sviluppo di un progetto potenzialmente complesso. Avrete tre giorni per implementare ogni feature per tempo. Ogni iterazione si svolgerà nel modo seguente:

  • giorno 1: pubblichiamo l'obbiettivo da sviluppare per la feature corrente che, entro 48h, potrete risolvere in completa autonomia;
  • giorno 3: pubblichiamo un suggerimento per aiutarvi a implementare la feature e, in altre 24h, potete provare a risolvere la feature per tempo.
In qualunque momento durante questi 3 giorni siete liberi di postare la soluzione e di porre domande di qualsiasi tipo, per esempio: "che argomenti mi consigliate di ripassare, utili a risolvere risolvere questa feature?". Non è una challenge, non c'è nessun aspetto competitivo, è programmiamo con Inforge. Ogni utente manterrà il proprio codice e proporrà la propria soluzione, lo staff vi guiderà nello sviluppo. Siete più che invitati a postare soluzioni parziali, spiegare il vostro ragionamento e porre domande. Il progetto è didattico ma, si spera, di vostro interesse. Di volta in volta, alla fine del terzo giorno, pubblicheremo (con eventuali spiegazioni) la soluzione della feature corrente e, contemporaneamente, l'obiettivo della feature successiva, anch'essa da svilupparsi in tre giorni. Il ciclo si ripeterà fino al termine del progetto.

La difficoltà del progetto si basa sul numero di partecipanti delle 5 esercitazioni proposte in precedenza. Chi non le ha risolte è invitato provarci e a pubblicare la propria soluzione nei threads elencati in precedenza; in questo modo valuteremo se vale la pena proseguire verso progetti ed esercitazioni più complesse.

Le esercitazioni che hanno avuto maggior successo sono state quelle di livello base, ed è da lì che partiremo. Per come strutturato il progetto, esso è alla portata di tutti: richiede presenza più che conoscenza e, di fatto, potresti essere in grado di partecipare anche se non conosci il C. I più bravi riusciranno a risolvere ogni features nel giro di pochi minuti e i meno bravi (con un po' di buona volontà!) hanno il tempo di studiare gli argomenti necessari a risolverla. Piuttosto che rimanere bloccati, siete invitati a chiedere aiuto e a discuterne: non dovete aspettare i suggerimenti (o le soluzioni) prima di fare domande. Man mano che aggiungeremo features, il progetto diventerà via via più complesso e non vogliamo lasciare indietro nessuno.

Ovviamente siete liberi di partecipare anche a progetto già iniziato o concluso ma, per sfruttare al meglio questa esperienza didattica, vi consigliamo di partecipare (attivamente) nel periodo indicato. Vedrete che ogni feature è ragionevolmente breve da poter essere completata per tempo. Anche da principianti, con l'aiuto allo staff, sarete più che incentivati a fare domande senza fermarvi ad aspettare il suggerimento pre-programmato.

Chi non è pratico di C e chi è maggiormente interessato ad altri linguaggi di programmazione è comunque invitato a partecipare allo sviluppo. Sentitevi liberi di porre domande specifiche per il linguaggio di programmazione che state usando e di postare la vostra soluzione. Ciò nonostante, questo progetto è stato pensato per essere sviluppato in C e il livello di difficoltà potrebbe aumentare (se lo sviluppate in Assembly) o diminuire (se lo sviluppate in Python).

Se vi è possibile, utilizzate una distribuzione GNU/Linux. Il codice che andremo a scrivere sarà per lo più multipiattaforma, ma siamo più propensi a fornirvi supporto se utilizzate lo stesso sistema operativo utilizzato dagli organizzatori del progetto. Se utilizzate altro, dovrete essere sufficientemente autonomi ad includere e compilare eventuali librerie esterne.


Partiamo dalle basi del C

In questa guida puoi trovare i testi per studiare e approfondire il C: I migliori libri per imparare e approfondire il C
In questa discussione puoi trovare le risposte alle domande più frequenti su come scrivere codice in C: Frequently asked questions: da dove si parte?


Progetto: Bruteforce



Prima di iniziare

Il progetto inizierà il 29 Maggio. Cogliamo l'occasione per iniziare a raccogliere feedback di qualunque tipo. In qualunque momento potete darci consigli organizzativi di qualsiasi genere (e.g., "tre giorni sono pochi", "meglio 24h in autonomia e 48h con suggerimento", "il progetto è troppo facile/difficile")

Ringraziamenti


Un ringraziamento particolare a @St3ve per aver contribuito attivamente alla formulazione di questo progetto ;) Speriamo vivamente che sia di vostro gradimento!
 
DOWNLOAD
Banner pubblicitario per Bright Data su Inforge.net azienda di vendita Proxy, Data Collector e Content Unlocker

St3ve

Utente Platinum
12 Ottobre 2011
2,108
1,403
665
Pubblicata la prima feature, inclusa di mini-challenge (da risolvere utilizzando il programma che scriverete).

Ricordate che è un progetto alla portata di tutti. Se rimanete bloccati da qualche parte, potete chiedere aiuto e, in ogni caso, abbiamo prefissato un suggerimento.
 
DOWNLOAD
Banner pubblicitario per Bright Data su Inforge.net azienda di vendita Proxy, Data Collector e Content Unlocker

ddos attack

Utente Iron
18 Marzo 2021
44
12
18
Ultima modifica:
Come sempre io lo faccio in C#
C#:
using System;
using System.Text;
using System.Security.Cryptography;

namespace Md5
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var md5=MD5.Create())
            {
                foreach(char carattere in args[0])
                {
                    var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes($"{carattere}"));
                    var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
                    Console.WriteLine($"md5({carattere}) = {hash}");
                }
            }
        }
    }
}
 

JunkCoder

Moderatore
5 Giugno 2020
751
599
345
Bella challange! Pubblico la mia soluzione in C:

Il codice per MD5 l'ho semplicemente incollato da github.

C:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>

void md5(const uint8_t* initial_msg, size_t initial_len, uint8_t* digest);
void bin2hex(char* result, const unsigned char* old, const size_t oldlen);

void HashEachChar(const char* lpStr)
{
    uint8_t digest[16];
    char digestHex[33];
    int len = strlen(lpStr);
    for (int i = 0; i < len; i++)
    {
        const uint8_t* charPtr = (const uint8_t*)&lpStr[i];
        md5(charPtr, 1, digest);
        bin2hex(digestHex, digest, sizeof(digest));
        printf("md5(%c) = %s\n", lpStr[i], digestHex);
    }
}

void BruteforceChars(const char* match)
{
    uint8_t buff[1];
    uint8_t digest[16];
    char digestHex[33];
    for (int i = 0; i < 256; i++)
    {
        buff[0] = (uint8_t)i;
        md5(buff, 1, digest);
        bin2hex(digestHex, digest, sizeof(digest));
        if (strcmp(digestHex, match) == 0)
        {
            printf("Char with md5(%s) is: %c", match, (char)i);
            break;
        }
    }
}

int main(int argc, char** argv)
{
    // Hash char args
    HashEachChar(argv[1]);
    // Bruteforce ASCII
    BruteforceChars("b15835f133ff2e27c7cb28117bfae8f4");
    return 0;
}

Output:
Codice:
./challange abcdef

md5(a) = 0cc175b9c0f1b6a831c399e269772661
md5(b) = 92eb5ffee6ae2fec3ad71c777531578f
md5(c) = 4a8a08f09d37b73795649038408b5f33
md5(d) = 8277e0910d750195b448797616e091ad
md5(e) = e1671797c52e15f763380b45e841ec32
md5(f) = 8fa14cdd754f91cc6554c9e71929cce7
Char with md5(b15835f133ff2e27c7cb28117bfae8f4) is: "
 

ddos attack

Utente Iron
18 Marzo 2021
44
12
18
Ultima modifica:
Per trovare la password associata a b15835f133ff2e27c7cb28117bfae8f4 io faccio:
C#:
using System;
using System.Text;
using System.IO;
using System.Security.Cryptography;

namespace Md5
{
    class Program
    {
        static string GeneraMd5(string parola)
        {
            var md5 = MD5.Create();
            var hashBytes = md5.ComputeHash(Encoding.UTF8.GetBytes(parola));
            var hash = BitConverter.ToString(hashBytes).Replace("-", string.Empty);
            return hash.ToString();
        }
        static void Main(string[] args)
        {
            string hash = "b15835f133ff2e27c7cb28117bfae8f4", line=""; //hash da trovare
            using (var md5 = MD5.Create())
            {
                using (var stream=new StreamReader("rockyou.txt"))
                {
                    while((line=stream.ReadLine()) != null)
                    {
                        string hashtemporaneo = GeneraMd5(line);
                        if (hashtemporaneo==hash)
                        {
                            Console.WriteLine($"Ho trovato l'hash è: {hashtemporaneo}");
                            Environment.Exit(0);
                        }
                    }
                }
            }
        }
    }
}
Messaggio unito automaticamente:

La password è "
 
  • Mi piace
Reactions: Max Fridman
Supporta Inforge con una donazione
Banner pubblicitario per Bright Data su Inforge.net azienda di vendita Proxy, Data Collector e Content Unlocker

St3ve

Utente Platinum
12 Ottobre 2011
2,108
1,403
665
Ultima modifica:
Per chi non l'ha risolto, suggerimento:
Usando OpenSSL (dovete installare la libreria), l'md5 può essere calcolato in questo modo
C:
// https://stackoverflow.com/a/61333376
void md5(char *result, const char *data, int len) {
  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
  const EVP_MD *md = EVP_md5();
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len, i;
  EVP_DigestInit_ex(mdctx, md, NULL);
  EVP_DigestUpdate(mdctx, data, len);
  EVP_DigestFinal_ex(mdctx, md_value, &md_len);
  EVP_MD_CTX_free(mdctx);
  for (i = 0; i < md_len; i++) {
    snprintf(&(result[i * 2]), 16 * 2, "%02x", md_value[i]);
  }
}
e, se usate linux, il codice si compila così gcc -lcrypto -o bruteforce bruteforce.c.

Buone le soluzioni postate da @ddos attack e @JunkCoder. Visto che siamo in tema suggerimenti, vi consiglio di aggiungere anche un pezzo di codice per gestire il caso in cui l'utente non inserisce nessun carattere. È il vostro programma, quindi sta a a voi decidere cosa sia meglio fare. Personalmente, vi consiglio di scegliere uno di questi due comportamenti:
  • stampa un messaggio di errore che spiega all'utente come va utilizzato il programma (e.g., "ti sei dimenticato di inserire il charset");
  • imposta un valore di default ragionevole (e.g., tutti i possibili bytes, solo le lettere e i numeri, solo lettere numeri e simboli ma niente spazi, etc...).

Domani pubblico la mia soluzione e si passa alla seconda feature. Intanto, come vi è sembrata la prima feature? Troppo facile/difficile, tre giorni sono troppi/pochi, c'erano troppe/poche cose da fare, etc... Visto che siamo in pochi possiamo tranquillamente adeguarci alle vostre necessità e al vostro livello di competenza. Se volete cose più difficili, ditelo... altrimenti si può continuare così per un altro po' ;)

Idem per i curiosi che sono rimasti in silenzio. Se volete solo guardare non c'è nessun problema, ma se c'è un motivo particolare per cui non avete postato la vostra soluzione, fatecelo sapere!
 

St3ve

Utente Platinum
12 Ottobre 2011
2,108
1,403
665
Soluzione:
Come detto nel suggerimento, io ho scelto di utilizzare OpenSSL.
C:
// gcc -lcrypto -o bruteforce bruteforce.c
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>

void md5(char *result, const char *data, int len);

int main(int argc, char *argv[]) {
  if (argc <= 1) {
    fprintf(stderr, "Usage: %s charset\n", argv[0]);
    return 1;
  }

  const char *const charset = argv[1];
  const int length = strlen(charset);

  char digest[33];  // 32 caratteri hex di MD5 + '\0'
  for (int i = 0; i < length; i++) {
    md5(digest, &charset[i], 1);
    printf("md5(%c) = %s\n", charset[i], digest);
  }

  return 0;
}

// https://stackoverflow.com/a/61333376
void md5(char *result, const char *data, int len) {
  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
  const EVP_MD *md = EVP_md5();
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len, i;
  EVP_DigestInit_ex(mdctx, md, NULL);
  EVP_DigestUpdate(mdctx, data, len);
  EVP_DigestFinal_ex(mdctx, md_value, &md_len);
  EVP_MD_CTX_free(mdctx);
  for (i = 0; i < md_len; i++) {
    snprintf(&(result[i * 2]), 16 * 2, "%02x", md_value[i]);
  }
}

La mini-challange l'avete risolta a modo vostro, ma io mi aspettavo una soluzione più semplice: ./bruteforce abc...ABC...123...\"

Non c'era bisogno di trasformare il bruteforce in un dictionary attack, perché la challange era pensata per essere risolta con quel che abbiamo fatto finora: sapevamo che la password doveva essere di un solo carattere. Per il resto volevo semplicemente farvi notare che alcuni caratteri speciali, tra cui " (virgolette) e * (asterisco) devono essere preceduti da un escape (il simbolo \ di backslash) altrimenti la shell li interpreta a modo suo: " è l'inizio di una stringa e * (glob) viene usato per fare filename expansion (elenca i file nella nostra working directory).

Ho aggiunto l'obettivo per la seconda feature al post iniziale.
 

St3ve

Utente Platinum
12 Ottobre 2011
2,108
1,403
665
Per chi non l'ha risolto.
Nella prima feature abbiamo imparato a stampare l'md5 delle password di lunghezza 1. Per generare l'md5 di tutte le password di lunghezza 2 possiamo fare così:

C:
char string[3] = {0};       // 2 + '\0' per semplificare la printf
for (int b = 0; b < length; b++) {  // length è la lunghezza del charset (vedi soluzione precedente)
  string[1] = charset[b];
  for (int a = 0; a < length; a++) {
    string[0] = charset[a];
    md5(digest, string, 2);
    printf("md5(%s) = %s\n", string, digest);
  }
}

Ripeti anche per lunghezza 3 e lunghezza 4, poi combina le soluzioni. Scriverai un codice brutto, ma non ha importanza:
  1. meglio una soluzione pessima che lasciare il problema non risolto;
  2. stiamo facendo sviluppo incrementale e la feature attuale ci serve per affrontare più facilmente la successiva.

Se rimanete bloccati siete liberi di fare domande. Domani posto la soluzione e si passa alla feature 3.
 
DOWNLOAD
Banner pubblicitario per Bright Data su Inforge.net azienda di vendita Proxy, Data Collector e Content Unlocker

St3ve

Utente Platinum
12 Ottobre 2011
2,108
1,403
665
Ultima modifica:
Posto anche la soluzione.
Tutto molto semplice, basta ripetere lo stesso codice della feature 1 (o del suggerimento della feature 2) e innestare un nuovo ciclo for di volta in volta. Non è una soluzione elegante, ma non ha importanza. La soluzione pulita arriva con la feature 3, la feature 2 era decisamente alla portata di tutti.

C:
// gcc -lcrypto -o bruteforce bruteforce.c
#include <openssl/evp.h>
#include <stdio.h>
#include <string.h>

void md5(char *result, const char *data, int len);

int main(int argc, char *argv[]) {
  if (argc <= 1) {
    fprintf(stderr, "Usage: %s charset\n", argv[0]);
    return 1;
  }

  const char *const charset = argv[1];
  const int length = strlen(charset);

  char string[5] = {0};
  char digest[32];

  // 1 carattere
  for (int a = 0; a < length; a++) {
    string[0] = charset[a];
    md5(digest, string, 1);
    printf("md5(%s) = %s\n", string, digest);
  }

  // 2 caratteri
  for (int b = 0; b < length; b++) {
    string[1] = charset[b];
    for (int a = 0; a < length; a++) {
      string[0] = charset[a];
      md5(digest, string, 2);
      printf("md5(%s) = %s\n", string, digest);
    }
  }

  // 3 caratteri
  for (int c = 0; c < length; c++) {
    string[2] = charset[c];
    for (int b = 0; b < length; b++) {
      string[1] = charset[b];
      for (int a = 0; a < length; a++) {
        string[0] = charset[a];
        md5(digest, string, 3);
        printf("md5(%s) = %s\n", string, digest);
      }
    }
  }

  // 4 caratteri
  for (int d = 0; d < length; d++) {
    string[3] = charset[d];
    for (int c = 0; c < length; c++) {
      string[2] = charset[c];
      for (int b = 0; b < length; b++) {
        string[1] = charset[b];
        for (int a = 0; a < length; a++) {
          string[0] = charset[a];
          md5(digest, string, 4);
          printf("md5(%s) = %s\n", string, digest);
        }
      }
    }
  }

  return 0;
}

// https://stackoverflow.com/questions/7627723/how-to-create-a-md5-hash-of-a-string-in-c
void md5(char *result, const char *data, int len) {
  EVP_MD_CTX *mdctx = EVP_MD_CTX_new();
  const EVP_MD *md = EVP_md5();
  unsigned char md_value[EVP_MAX_MD_SIZE];
  unsigned int md_len, i;
  EVP_DigestInit_ex(mdctx, md, NULL);
  EVP_DigestUpdate(mdctx, data, len);
  EVP_DigestFinal_ex(mdctx, md_value, &md_len);
  EVP_MD_CTX_free(mdctx);
  for (i = 0; i < md_len; i++) {
    snprintf(&(result[i * 2]), 16 * 2, "%02x", md_value[i]);
  }
}

Aspetto a postare la soluzione della mini-challange. È molto facile da risolvere, quindi potete provarci anche semplicemente compilando ed eseguendo la soluzione che ho appena postato: dovete aggiungere il codice per controllare se l'hash è uguale a quello a cui siete interessati (lo rifaremo, in modo migliore, in una feature successiva), ma la versione più semplice è una modifica banale che i due partecipanti avevano già implementato nella feature 1.

Aggiungo l'obiettivo per la feature 3 nell'open post. Altri tre giorni e poi si passa alla successiva.
 

St3ve

Utente Platinum
12 Ottobre 2011
2,108
1,403
665
Suggerimento
Ogni ciclo for conta da 0 fino a length (escluso), dove length è la lunghezza del charset. Al posto di mantenere una variabile per ogni contatore possiamo utilizzare un array lungo N, dove N è la più lunga stringa che andremo a generare. Se length fosse 10 questo codice preso dalla soluzione precedente
C:
for (int d = 0; d < length; d++) {
  string[3] = charset[d];
  for (int c = 0; c < length; c++) {
    string[2] = charset[c];
    for (int b = 0; b < length; b++) {
      string[1] = charset[b];
      for (int a = 0; a < length; a++) {
        string[0] = charset[a];
        md5(digest, string, 4); // il 4 conta il numero di cicli innestati, è da parametrizzare
        printf("md5(%s) = %s\n", string, digest);
      }
    }
  }
}
non fa altro che che contare da 0000 a 9999 e
  1. utilizzare la cifra delle unità (i.e., prima posizione) per indicizzare charset da assegnare a string[0];
  2. utilizzare la cifra nelle decine (i.e., seconda posizione) per indicizzare charset da assegnare a string[1];
  3. utilizzare la cifra nelle decine (i.e., terza posizione) per indicizzare charset da assegnare a string[2];
  4. utilizzare la cifra nelle decine (i.e., quarta posizione) per indicizzare charset da assegnare a string[3].
Se length è un numero generico, stiamo contando da 000...000 (N zeri) al più grande numero di N cifre espresso in base length. Una volta che parametrizziamo anche il numero di cifre (4 nel codice postato), abbiamo quasi finito. L'ultima cosa che ci manca è aggiornare il contenuto della stringa: dobbiamo generalizzare lo string[_] = ...; presente nella soluzione precedente. Il modo più banale per farlo è quello di rigenerare l'intera stringa di volta in volta... ma, più efficientemente (se siamo capaci), possiamo ricalcolare solo la parte modificata.

Non è semplicissimo. È il primo e probabilmente sarà l'ultimo punto un po' difficile di questo progetto didattico. Il suggerimento aiuta, ma non lo rende banale come le features precedenti. Ciò nonostante, la soluzione sarà molto corta e sullo stesso stile delle soluzioni precedenti: le scadenze sono brevi (ogni 3 giorni) perché il codice che dovete scrivere non vi chiede più di 15-30 minuti di tempo, 1h se dovete pure studiare gli argomenti necessari a risolverlo.

Se qualcuno ci sta provando ed è rimasto bloccato, possiamo discuterne. Altre 24h e posto la soluzione e si passa alla feature 4.
 

St3ve

Utente Platinum
12 Ottobre 2011
2,108
1,403
665
Ultima modifica:
Soluzione
Il mio N si chiama MSGLEN e l'ho fissato a 1024, che mi sembra un numero ragionevolente grande. Al posto della string, ho utilizzato una combinazione di code con message: code è l'array che funge da contatore modulo lunghezza del charset e message è la sequenza di bytes che verrà hashata.

Per contare modulo lunghezza del charset ho scritto la funzione ricorsiva next, che non fa altro che una somma con riporto: quando il valore nella casella attuale raggiunge n (i.e., la lunghezza del charset), azzeralo e incrementa ricorsivamente la casella successiva. Ogni casella dell'array, vi ricordo, rappresenta una cifra valore di ogni cifra del numero modulo lunghezza del charset. Il numero è espresso da sinistra (meno significativa) a destra (più significativa), al contrario di come facciamo con carta e penna (e.g., al posto di scrivere mille come 1000, scrivo 0001 dove 1 è in code[3]).
C:
int next(int *code, int n, int length) {
  if (++(*code) == n) {
    *code = 0;
    return next(code + 1, n, length - 1);
  }
  return length;
}
Il parametro length indica la lunghezza dell'array code e il valore restituito da next è il complemento della quantità di celle modificate. Per esempio, con n=10 e length=4 e code={9981} (aka., milleottocentonovantanove), una chiamata a next genera code={0091} (aka., millenovecento) e restituisce 1 perché ho modificato 3 cifre, length=4 e 4-3=1. Quando la funzione restituisce zero, ho generato il più grande numero composto da length cifre in base n.

Per aggiornare la stringa da hashare, ho utilizzato la funzione refresh che dovrebbe essere abbastanza autoesplicativa: uso l'array code per indicizzare l'array charset, poi assegno il carattere/simbolo alla corrispondente componente di message.
C:
void refresh(char *message, const int *code, const char *charset, int length) {
  for (int i = 0; i < length; i++) message[i] = charset[code[i]];
}

Questo procedimento viene ripetuto finché next non restituisce zero. Inoltre, il valore restituito da next mi aiuta a capire quanti caratteri devo refreshare; ovvero, il parametro length da passare a refresh: MSGLEN - next(...) + 1. Se mi salvo il massimo tra questi valori, ho anche la lunghezza effettiva (il valore da passare a md5) di message.
C:
int invrem, length = 0;
while (invrem = next(code, symbols, MSGLEN)) {
  if (MSGLEN - invrem + 1 > length) length = MSGLEN - invrem + 1;
  refresh(message, code, charset, MSGLEN - invrem + 1);
  md5(digest, message, length);
  printf("md5(%s) = %s\n", message, digest);
}

Riguardo alle cifre (array code), dobbiamo avere l'accortezza di partire da -1 perché 000..000 è una sequenza valida.
C:
int code[MSGLEN];
memset(code, -1, sizeof(int) * MSGLEN);

E questo è tutto. Il codice completo lo posterò al termine della quarta e ultima feature, ma con quel che vi ho scritto finora potete quasi limitarvi a fare ctrl+c ctrl+v nel posto giusto. Come al solito, se c'è qualcosa di poco chiaro siete invitati a fare domande.

La quarta e ultima feature è un po' più corposa, ma non è niente di difficile. La aggiungo in questo momento all'open post.
Come al solito, l'appuntamento è tra due giorni per il suggerimento e tra tre giorni per la soluzione.
 
DOWNLOAD
Banner pubblicitario per Bright Data su Inforge.net azienda di vendita Proxy, Data Collector e Content Unlocker