Bruteforce in C++

Hi @ all,
öfters wurde hier im Forum nach Bruteforce-Algorithmen gefragt. Hab' mal kurz spaßhalber einen Bruteforce-Algorithmus geschrieben, um zu zeigen wie's gemacht wird. Am Anfang des Programms könnt ihr zwischen einer iterativen und einer rekursiven Version wählen, wobei zu sagen ist, dass die Anzahl der Rekursionen der Anzahl der durchlaufenen Permutationen entspricht und somit nach dem vollgehauenen Stack das Programm abbricht. Der rekursive Algorithmus ist ohnehin eher spaßhalber, da er auch kleine Redundanzen enthält.

Anyway, hab's gepackt mit dem iterativen Algorithmus testhalber ins FTP-Account meiner MS-XBOX einzubrechen.

Der Algorithmus ist nur ein Beispiel, das noch gut erweitert werden kann. Wer möchte kann dem Algo. gern noch Sonderzeichen hinzufügen, oder ihn bei einer bestimmten Sequenz starten oder stoppen lassen.

Der iterative Algorithmus lässt sich mit nur drei zusätzlichen Zeilen dazu bewegen Zip-Archive, Rar-Archive und FTP-Accounts zu knacken (und VIELE andere Dinge), doch diesen illegalen Teil werd' ich nicht posten können und beschränke mich somit rein auf das Anzeigen der Permutationen.

Sourcecode sollte selbsterklärend sein, die Bruteforce-Algorithmen hab' ich in englisch kommentiert. Getestet hab' ich den Algorithmus unter Debian GNU/Linux.

Compile: g++ -o bruteforce bruteforce.cpp

Greetz
Hackse

PS: Anbei der Sourcecode:
Code:
// bruteforce.cpp --> iterative and recursive bruteforce algorithm

// ---------------------- (C) HACKSE 2006 ----------------------

#include <iostream>

using namespace std;

//***************************** GLOBAL MEMBERS ******************************//

const char SPACE = char(32);
const unsigned short MAX_LEN=30; // max. len. of bstring
char bstring[MAX_LEN];           // bruteforce string
unsigned short len=0;            // current length ob bstring
unsigned int recdepths=0;	 // depths of recursive algorithm

//****************************** PROTOTYPES *********************************//

// getting next bruteforce-ascii-symbol
char next(char);

// initialization for <bruteforce()>
void init();

// appends ascii-symbol to bstring and inc. len by 1
bool append();

// recursive calculation of next bruteforce string (causes STACK OVERFLOW !!!)
void bruteforce_recu(unsigned short i=(len-1));

// iterative version of bruteforce algorithm
void bruteforce_iter();

// sets whole bstring to next permutation (for <bruteforce_iter)
bool next_permutation();

//**************************** MAIN-FUNCTION ********************************//

int main (int argc, char *argv[]) {
  init();
  cout<<" *** BRUTEFORCE *** (c) Hackse 2006"<<endl;
  cout<<" **********************************"<<endl<<endl;
  char reply=0;
  do {
    cout<<"(i)terative or (r)ecursive?: "; 
    cin>>reply;
  } while (reply != 'i' && reply !='r');  
  if (reply=='i'){
    cout<<"Starting iterative version of bruteforce ..."<<endl;
    sleep(2);
    bruteforce_iter();
  } else {
      cout<<"Starting recursive version of bruteforce until ";
      cout<<"STACK-OVERFLOW! ..."<<endl;
      sleep(2);
      bruteforce_recu();
  }
  return 0;
};

//********************* FUNCTION IMPLEMENTATION *****************************//

char next(char s){
  switch (s) { 
    case SPACE :   return 'a';   break;
    case 'z'   :   return 'A';   break;
    case 'Z'   :   return '0';   break;
    case '9'   :   return SPACE; break;
    default    :   return  1+s;
  }
}

void init(){  
  for (int i=0; i<MAX_LEN; i++)
    (i==0)?bstring[i]=SPACE:bstring[i]=0;
  len=1;  
  recdepths=0;
};

bool append(){
  if (len>=MAX_LEN) 
    return false;
  bstring[len]=SPACE;
  len++;
  return true;
};

void bruteforce_recu(unsigned short i){
  cout<<bstring<<" --> recursion depths: " <<++recdepths<<endl;
  bstring[i]=next(bstring[i]);         // calc. next bstring
  if (bstring[i] == SPACE){            // carry?
    if (i==0) {                        // first pos.?
      append();                        // append element to bstring
      bruteforce_recu(len-1);          // recursive call on last index
   }
   else
        bruteforce_recu(i-1);          // recursive call on "prev." index
  }
  else
        bruteforce_recu(len-1);        // recursive call on last index
  recdepths--;
};

bool next_permutation(){  
  for (int i=len-1; i>=0; i--){
    bstring[i]=next(bstring[i]);       // set next bstring 
    if (bstring[i]!=SPACE)             // check for carry 
      return true;                     // exit if no carry
    else
      if (i==0)                        // on first char ...  
        if (append())                  // try to append char on carry-event
	  return true;                 // appending char was successful
	else
	  return false;                // length of bstring exceeds MAX_LEN
  }
}

void bruteforce_iter(){
  do  
    cout<<bstring<<endl;
  while (next_permutation());
}
 
cool

nice :-)

hast du mal getestet welcher ansatz fixer ist, rekusion oder iteration? würde mich mal interessieren :)
 
Hallo,
Aber wenn ich das richtig verstanden habe, ist BruteForce ein Programm zum Passwörter knacken...

Dustin,
es ist ein Demoprogramm, das auf die Schnelle runtergeschrieben wurde. Es ist weder performancetechnisch optimiert, noch parallelisiert, sondern soll in menschenlesbarer Form den Sachverhalt des Bruteforce möglichst einfach darstellen. Das mit dem FTP-Account ging lediglich, weil meine damalige Xbox im gleichen Netzsegment lag. Wie eben schon via PN beschrieben, kommst Du im Internet damit nicht weit, denn

a) Bruteforce dauert viel zu lange
b) Viele Accounts machen nach 3 bis 10 falschen Passworteingaben dicht, damit 14-jährige Schüler wie Du nicht versuchen alle möglichen Passworte durchzuprobieren. :) (nichts für ungut, Junge)
nice :-)

hast du mal getestet welcher ansatz fixer ist, rekusion oder iteration? würde mich mal interessieren
Man kann auch einfach den Autor fragen. :-)
Die Antwort ist simpel: Durch die vielen unnötigen Funktionsaufrufe sind beide Varianten gleichermaßen langsam und mit der rekursiven Version kommst Du, wie im initialen Posting beschrieben, nicht weit, da Dein Stack ganz sicher nicht groß genug ist um die Funkionsparameter NP-vollständiger Algorithmen wie Bruteforce dort abzulegen. :wink:
 
@Hackse,

könntest du mir erklären wie dein Programm genau funktioniert,ich bin nicht so der Profi in C++?Ich verstehe nicht,wie es neue Buchstaben hinzufügt?
 
Ein paar Kleinstoptimierungen für den iterativen Modus:
Ein Präfixinkrement (++n) ist in den meisten Fällen™ schneller als 1+n oder n++. Also wäres es praktisch in next(char s) im default case folgendes zu verwenden:
Code:
default    :   return  ++s;
Das Selbe ist in append() der Fall bei len++.

Der Operator ?: ist in auf den meisten Compilern nicht schneller als ein if-else Konstrukt, es sei denn du willst gemäß einer Bedingung eine Variable zuweisen. (Auch wenn das in init() wahrscheinlich nicht mal messbar sein wird.).

Die Funktion next_permutation kann als inline deklariert werden, sodass sie in die Bedingung der do...while Schleife eingefügt wird. Die Dateigröße wurde hier mit g++ 4.4.3 nicht größer.

Auf diesem System (Ubuntu 10.04 mit Core i7) werde ich 0,137 Sekunden schneller fertig, wenn ich vier statt 30 Zeichen durchprobiere. Mit dazugeschalteten Compileroptimierungen sinds 0,161 Sekunden Zeitersparnis. Nicht viel, aber ich würde mal spekulieren, dass bei >30 Zeichen das eventuell doch ein wenig zum Tragen käme. (BTW: Ich hoffe Haeckse hat nix dagegen, dass mit dem Programm etwas herumgespielt wird)


Code:
// ---------------------- (C) HACKSE 2006 ----------------------

#include <iostream>

using namespace std;

//***************************** GLOBAL MEMBERS ******************************//

const char SPACE = char(32);
const unsigned short MAX_LEN=4; // max. len. of bstring (verkürzt)
char bstring[MAX_LEN];           // bruteforce string
unsigned short len=0;            // current length ob bstring
unsigned int recdepths=0;     // depths of recursive algorithm

//****************************** PROTOTYPES *********************************//

// getting next bruteforce-ascii-symbol
char next(char);

// initialization for <bruteforce()>
void init();

// appends ascii-symbol to bstring and inc. len by 1
bool append();

// recursive calculation of next bruteforce string (causes STACK OVERFLOW !!!)
void bruteforce_recu(unsigned short i=(len-1));

// iterative version of bruteforce algorithm
void bruteforce_iter();

// sets whole bstring to next permutation (for <bruteforce_iter)
bool next_permutation();

//**************************** MAIN-FUNCTION ********************************//

int main (int argc, char *argv[]) {
  init();
  /*cout<<" *** BRUTEFORCE *** (c) Hackse 2006"<<endl;
  cout<<" **********************************"<<endl<<endl;
  char reply=0;
  do {
    cout<<"(i)terative or (r)ecursive?: "; 
    cin>>reply;
  } while (reply != 'i' && reply !='r');  
  if (reply=='i'){
    cout<<"Starting iterative version of bruteforce ..."<<endl;
    sleep(2);
    bruteforce_iter();
  } else {
      cout<<"Starting recursive version of bruteforce until ";
      cout<<"STACK-OVERFLOW! ..."<<endl;
      sleep(2);
      bruteforce_recu();
  }*/
  bruteforce_iter();
  return 0;
};

//********************* FUNCTION IMPLEMENTATION *****************************//

char next(char s){
  switch (s) { 
    case SPACE :   return 'a';   break;
    case 'z'   :   return 'A';   break;
    case 'Z'   :   return '0';   break;
    case '9'   :   return SPACE; break;
    default    :   return  ++s;
  }
}

void init(){  
  for (int i=0; i<MAX_LEN; i++)
    (i==0)?bstring[i]=SPACE:bstring[i]=0;
  len=1;  
  recdepths=0;
};

bool append(){
  if (len>=MAX_LEN) 
    return false;
  bstring[len]=SPACE;
  ++len;
  return true;
};

void bruteforce_recu(unsigned short i){
  cout<<bstring<<" --> recursion depths: " <<++recdepths<<endl;
  bstring[i]=next(bstring[i]);         // calc. next bstring
  if (bstring[i] == SPACE){            // carry?
    if (i==0) {                        // first pos.?
      append();                        // append element to bstring
      bruteforce_recu(len-1);          // recursive call on last index
   }
   else
        bruteforce_recu(i-1);          // recursive call on "prev." index
  }
  else
        bruteforce_recu(len-1);        // recursive call on last index
  recdepths--;
};

inline bool next_permutation(){  
  for (int i=len-1; i>=0; i--){
    bstring[i]=next(bstring[i]);       // set next bstring 
    if (bstring[i]!=SPACE)             // check for carry 
      return true;                     // exit if no carry
    else
      if (i==0)                        // on first char ...  
        if (append())                  // try to append char on carry-event
      return true;                 // appending char was successful
    else
      return false;                // length of bstring exceeds MAX_LEN
  }
}

void bruteforce_iter(){
  do  
    cout<<bstring<<endl;
  while (next_permutation());
}
 
bad_alloc: Ich hoffe, du kannst deinen Standpunkt erklären, denn ich stelle ihn ernsthaft infrage :rolleyes:
++n, n++ und 1+n sind drei verschiedene Sachen - darin werden wir uns einig sein. In der Funktion append ist es irrelevant, ob ich ++n oder n++ verwende, weil beides dasselbe bewirkt und (nicht zu vergessen) denselben Assemblercode erzeugt.
Noch mehr haben mich aber deine "Verbesserungen" in next() verwundert.
++s und 1+s bewirken in diesem Fall nicht dasselbe. 1+s liefert einfach die Summe aus 1+s und der Compiler rechnet die direkt im Rückgaberegister eax aus.
++s speichert den um 1 erhöhten Wert noch einmal in s (und verschiebt in dann nach al) - s ist aber eine lokale Variable und wird nach dem return nicht mehr gebraucht. D.h. du hast einen zusätzlichen (unnötigen) Speicherschreibzugriff eingebaut.
Code:
	incb	-4(%ebp)  // aus deinem Code
	movb	-4(%ebp), %al
Code:
	movb	-4(%ebp), %al  // aus dem Ursprungscode
	incl	%eax
Wenn du mir schlüssig erklären kannst, wieso ein byte-inkrement im Speicher schneller ist, als ein long-inkrement auf ein Register, bin ich aber auf deiner Seite. Sicher bin ich mir in dem Punkt wirklich nicht, aber ich war bisher der Überzeugung, ein Speicherzugriff ist immer langsamer als ein Registerzugriff.

Tut mir Leid, dass das so off-topic ist, aber das hat gerade mein Weltbild erschüttert :wink:
 
@bad_alloc

Das Programm dient lediglich zu Demozwecken und wurde ohne große Überlegungen einfach runtergeschrieben. Ziel und Zweck ist es nicht performant zu sein, sondern für Anfänger möglichst leserlich.

Für eine gute Optimierung müsste man die Vielzahl an Funktionsaufrufen minimieren, das Ganze (im Prinzip wie B-adische Zahlen) über mehrere Cores paralellisieren und, wenn dann richtig, z.B. CUDA für die GPU einbinden. Und wenn wir schon dabei sind, dann bitte direkt in Assembler und nicht, wie oben in C, das nicht als C++ bezeichnet werden darf, da in obigem Beispiel keine typischen C++-Klassen (wie die Klasse ¨string¨) verwendet werden um die Permutationen zu berechnen, sondern lediglich pointer of char.

Mich würde mal interessieren, ob der Performance-Unterschied zw. Ansi-C char-pointer und die C++-Klasse ¨string¨nennenswert ist. Hat jemand Lust die paar Zeilen kurz umzuschreiben oder soll ich es am WE schnell machen?
 
Ich hoffe, du kannst deinen Standpunkt erklären, denn ich stelle ihn ernsthaft infrage
Ich habe mir das nochmal genauer angesehen und muss eingestehen, dass die Inkrementsache Murks ist. Ich hab hier wohl voreilig eine Binsenweisheit von einem atmel32 auf einen normalen Computer übertragen. Auf dem Chip war es unter bestimmten Umständen wirklich schneller ein Präfixinkrement zu nutzen als eine Addition. Du hast natürlich völlig Recht in der Ansicht, dass Speicherzugriffe wesentlich langsamer sind als Registerzugriffe. Und schon wieder hat sich jemand an diesen Optimierungen die Finger verbrannt :(
 
Zurück
Oben