fork(1) download
  1. /*************************************************************
  2.  * crack.c *
  3.  * *
  4.  * CS50 OpenCourseWare. PSET 2 *
  5.  * *
  6.  * "crack" accepts a single command line argument: *
  7.  * *
  8.  * an encrypted password (DES algorithm). *
  9.  * *
  10.  * The program attempts to crack the password using first a *
  11.  * *
  12.  * dictionary attack and then if it fails a brute force *
  13.  * *
  14.  * attack. *
  15.  *************************************************************/
  16.  
  17. #define _XOPEN_SOURCE
  18.  
  19. #include <stdio.h>
  20. #include <stdlib.h>
  21. #include <string.h>
  22. #include <stdbool.h>
  23. #include <unistd.h>
  24. #include <math.h>
  25.  
  26. #define CORRECT_ARG_COUNT 2
  27. #define DES_ENCRYPTED_PASSWD_LEN 13
  28. #define WORDLIST "/usr/share/dict/words"
  29. #define BUFFER_SIZE 96
  30. #define SALT_LEN 2
  31. #define MAX_PASSWD_LEN 8
  32. #define NBR_OF_PRINTBL_ASCII_CHARS 95
  33. #define BASE NBR_OF_PRINTBL_ASCII_CHARS
  34. #define FIRST_PRINTBL_ASCII_CHAR 32
  35.  
  36. int check_cmdline_args(int argument_count, const char program_name[], const char ciphertext[]);
  37. int usage(const char program_name[]);
  38. int dictionary_attack(const char ciphertext[]);
  39. int try_potential_passwd(const char potential_passwd[], const char ciphertext[]);
  40. int brute_force_attack(const char ciphertext[]);
  41.  
  42. int
  43. main(int argc, char *argv[])
  44. {
  45. /* verify command line arguments number and ciphertex validity
  46. if incorrect exit the program */
  47. if (check_cmdline_args(argc, argv[0], argv[1]))
  48. exit(EXIT_FAILURE);
  49.  
  50. // if the dictionary attack is unsuccessful attempt a brute force attack
  51. if (dictionary_attack(argv[1]))
  52. brute_force_attack(argv[1]);
  53.  
  54. exit(EXIT_SUCCESS);
  55. }
  56.  
  57. /*
  58.  * Checks that the program received an appropriate
  59.  * number of command line arguments. Prints usage if
  60.  * it is not the case.
  61.  * if number of command line arguments is correct,
  62.  * checks that the ciphertext is a DES encrypted password.
  63.  */
  64. int
  65. check_cmdline_args(int argument_count, const char program_name[], const char ciphertext[])
  66. {
  67. // check the number of command line arguments
  68. if (argument_count != CORRECT_ARG_COUNT) {
  69. if (argument_count < CORRECT_ARG_COUNT)
  70. fprintf(stderr, "%s: missing argument.\n", program_name);
  71. else
  72. fprintf(stderr, "%s: too many arguments.\n", program_name);
  73.  
  74. usage(program_name);
  75. return 1;
  76.  
  77. } else {
  78. /* check the validity of the ciphertext.
  79. The ciphertext should be a thirteen-character string
  80. chosen from the set [a–zA–Z0–9./] */
  81.  
  82. int ciphertext_len = strlen(ciphertext);
  83.  
  84. if (ciphertext_len != DES_ENCRYPTED_PASSWD_LEN) {
  85. fprintf(stderr, "%s: Ciphertex is not a DES encrypted password\n", program_name);
  86. return 2;
  87. }
  88.  
  89. for (int i = 0; i < ciphertext_len; i++) {
  90. if ( (ciphertext[i] >= 'a' && ciphertext[i] <= 'z')
  91. || (ciphertext[i] >= 'A' && ciphertext[i] <= 'Z')
  92. || (ciphertext[i] >= '0' && ciphertext[i] <= '9')
  93. || ciphertext[i] == '/'
  94. || ciphertext[i] == '.' ) {
  95.  
  96. // do nothing
  97. ;
  98.  
  99. } else {
  100. fprintf(stderr, "%s: Ciphertext is not a DES encrypted password\n", program_name);
  101. return 3;
  102. }
  103. }
  104. }
  105.  
  106. return 0;
  107. }
  108.  
  109. /*
  110.  * Prints usage.
  111.  */
  112. int
  113. usage(const char program_name[])
  114. {
  115. // print to standard error (stderr).
  116. fprintf(stderr, "Usage: %s <CIPHERTEXT>\n", program_name);
  117. fprintf(stderr, "Decrypt DES encrypted password.\n");
  118.  
  119. return 0;
  120. }
  121.  
  122. /*
  123.  * Attempts to crack a password
  124.  * by trying all the words found in
  125.  * a given wordlist (dictionary).
  126.  */
  127. int
  128. dictionary_attack(const char ciphertext[])
  129. {
  130. // open the worldlist
  131. FILE *wordlist = fopen(WORDLIST, "r");
  132.  
  133. /* if the worldlist is not successfully opened print an error message and return 1
  134. otherwise initiate the dictionary attack */
  135. if (!wordlist) {
  136. fprintf(stderr, "%s could not be opened.\n", WORDLIST);
  137. return 1;
  138.  
  139. } else {
  140. // will be used to return the appropriate value
  141. bool cracked = false;
  142.  
  143. // will hold the dictionary's words
  144. char potential_passwd[BUFFER_SIZE] = {0};
  145.  
  146. // read the wordlist one line at the time (there is one word per line) until end of the list
  147. while (fgets(potential_passwd, sizeof (potential_passwd), wordlist) != NULL) {
  148. const unsigned int potential_passwd_len = strlen(potential_passwd);
  149.  
  150. // fgets also stores '\n' this last is consequently replaced by '\0' at the end of the string
  151. if (potential_passwd[potential_passwd_len - 1] == '\n')
  152. potential_passwd[potential_passwd_len - 1] = '\0';
  153.  
  154. // encrypt the potential password and compare it the ciphertext
  155. if (!try_potential_passwd(potential_passwd, ciphertext)) {
  156. cracked = true;
  157. printf("%s\n", potential_passwd);
  158. break;
  159. }
  160. }
  161.  
  162. // close the wordlist
  163. fclose(wordlist);
  164.  
  165. if (cracked)
  166. return 0;
  167. else
  168. return 2;
  169. }
  170. }
  171.  
  172. /*
  173.  * Enciphers a given password and compare it to
  174.  * an already encripted password.
  175.  * Returns 0 if the two match together or 2 if
  176.  * it is not the case.
  177.  */
  178. int
  179. try_potential_passwd(const char potential_passwd[], const char ciphertext[])
  180. {
  181. // will hold the salt
  182. char salt[SALT_LEN + 1] = {0};
  183.  
  184. // copy into the array salt the first two characters of the ciphertext (the salt)
  185. strncpy(salt, ciphertext, SALT_LEN);
  186.  
  187. // add the null terminating byte
  188. salt[SALT_LEN] = '\0';
  189.  
  190. /* the word stored in potential_passwd[] is encrypted and compared to
  191. the ciphertext passed as command line argument. If they match together,
  192. the word stored in potential_passwd[] is the cleartext version of the ciphertext */
  193.  
  194. char *unciphered_potential_passwd = crypt(potential_passwd, salt);
  195.  
  196. if (!unciphered_potential_passwd) {
  197. fprintf(stderr, "Could not encrypt %s\n", potential_passwd);
  198. return 1;
  199. }
  200.  
  201. if (strcmp(unciphered_potential_passwd, ciphertext))
  202. return 2;
  203. else
  204. return 0;
  205. }
  206.  
  207. /*
  208.  * Attempts to crack a password
  209.  * by trying all possible characters
  210.  * permutations (repetitions allowed).
  211.  * Thanks to CaZe on ##c (freenode)
  212.  */
  213. int
  214. brute_force_attack(const char ciphertext[])
  215. {
  216. //will be used to break out of the nested for loops
  217. bool cracked = false;
  218.  
  219. // will hold the generated potential passwords
  220. char potential_passwd[MAX_PASSWD_LEN + 1] = {0};
  221.  
  222. // will hold all the printable ascii characters
  223. char printbl_ascii_chars[NBR_OF_PRINTBL_ASCII_CHARS] = {0};
  224.  
  225. // populate printbl_ascii_chars[] with the printable ascii characters
  226. for (int i = 0, j = FIRST_PRINTBL_ASCII_CHAR; i < NBR_OF_PRINTBL_ASCII_CHARS; i++, j++)
  227. printbl_ascii_chars[i] = j;
  228.  
  229. /* To generate the characters permutations for a given password length,
  230. let's count from zero to BASE raised to the power passwd_len.
  231. when counting, instead of base 10, we use base 95 (the number of
  232. printable ascii characters).
  233. When counting, instead of using digits, we use printable ascii characters. */
  234.  
  235. for (int passwd_len = 1; passwd_len <= MAX_PASSWD_LEN; passwd_len++) {
  236. for (int count = 0; count < pow(BASE, passwd_len); count++) {
  237. for (int index = 0, power = passwd_len - 1; index < passwd_len; index++, power--) {
  238. potential_passwd[index] = printbl_ascii_chars[(int) (count / (pow(BASE, power))) % BASE];
  239. }
  240.  
  241. potential_passwd[passwd_len] = '\0';
  242.  
  243. if (!try_potential_passwd(potential_passwd, ciphertext)) {
  244. cracked = true;
  245. printf("%s\n", potential_passwd);
  246. break;
  247. }
  248. }
  249.  
  250. if (cracked)
  251. break;
  252. }
  253.  
  254. return 0;
  255. }
  256.  
Compilation error #stdin compilation error #stdout 0s 0KB
stdin
Standard input is empty
compilation info
prog.c: In function ‘check_cmdline_args’:
prog.c:89: error: ‘for’ loop initial declaration used outside C99 mode
prog.c: In function ‘brute_force_attack’:
prog.c:226: error: ‘for’ loop initial declaration used outside C99 mode
prog.c:235: error: ‘for’ loop initial declaration used outside C99 mode
prog.c:236: error: ‘for’ loop initial declaration used outside C99 mode
prog.c:237: error: ‘for’ loop initial declaration used outside C99 mode
stdout
Standard output is empty