1 /*
2 * This file is part of John the Ripper password cracker,
3 * Copyright (c) 2015 by Kai Zhao
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted.
7 *
8 * There's ABSOLUTELY NO WARRANTY, express or implied.
9 */
10
11 #include "os.h"
12
13 #include <sys/stat.h>
14
15 #if _MSC_VER || __MINGW32__ || __MINGW64__ || __CYGWIN__ || HAVE_WINDOWS_H
16 #include "win32_memmap.h"
17 #undef MEM_FREE
18 #if !defined(__CYGWIN__) && !defined(__MINGW64__)
19 #include "mmap-windows.c"
20 #endif /* __CYGWIN */
21 #endif /* _MSC_VER ... */
22
23 #if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
24 #include <io.h> /* mingW _mkdir */
25 #endif
26
27 #if defined(HAVE_MMAP)
28 #include <sys/mman.h>
29 #endif
30
31 #include "jumbo.h"
32 #include "misc.h" // error()
33 #include "config.h"
34 #include "john.h"
35 #include "params.h"
36 #include "signals.h"
37 #include "unicode.h"
38
39 #define _STR_VALUE(arg) #arg
40 #define STR_MACRO(n) _STR_VALUE(n)
41
42 #define CHAR_FROM -128
43 #define CHAR_TO 127
44
45 // old value from params.h
46 #define FUZZ_LINE_BUFFER_SIZE 0x30000
47 static char fuzz_hash[FUZZ_LINE_BUFFER_SIZE];
48 static char status_file_path[PATH_BUFFER_SIZE + 1];
49
50 struct FuzzDic {
51 struct FuzzDic *next;
52 char *value;
53 };
54
55 static struct FuzzDic *rfd;
56
57 static FILE *s_file; // Status file which is ./fuzz_status/'format->params.label'
58
59 extern int pristine_gecos;
60 extern int single_skip_login;
61
62 static char *file_pos, *file_end;
63
get_line()64 static char *get_line()
65 {
66 char *new_line, *line_start;
67
68 line_start = file_pos;
69 while (file_pos < file_end && *file_pos != '\n')
70 file_pos++;
71
72 if (file_pos == file_end)
73 return NULL;
74 file_pos++;
75
76 new_line = mem_alloc(file_pos - line_start);
77 strncpy(new_line, line_start, file_pos - line_start);
78 new_line[file_pos - line_start - 1] = 0;
79
80 return new_line;
81 }
82
fuzz_init_dictionary()83 static void fuzz_init_dictionary()
84 {
85 FILE *file;
86 char *line;
87 struct FuzzDic *last_fd, *pfd;
88 int64_t file_len = 0;
89 #ifdef HAVE_MMAP
90 char *mem_map;
91 #else
92 char *file_start;
93 #endif
94
95 if (!options.fuzz_dic)
96 return;
97
98 if (!(file = jtr_fopen(options.fuzz_dic, "r")))
99 pexit("fopen: %s", options.fuzz_dic);
100
101 jtr_fseek64(file, 0, SEEK_END);
102 if ((file_len = jtr_ftell64(file)) == -1)
103 pexit(STR_MACRO(jtr_ftell64));
104 jtr_fseek64(file, 0, SEEK_SET);
105 if (file_len == 0) {
106 if (john_main_process)
107 fprintf(stderr, "Error, dictionary file is empty\n");
108 error();
109 }
110
111 #ifdef HAVE_MMAP
112 mem_map = MAP_FAILED;
113 if (file_len < ((1LL)<<32))
114 mem_map = mmap(NULL, file_len, PROT_READ, MAP_SHARED,
115 fileno(file), 0);
116 if (mem_map == MAP_FAILED) {
117 mem_map = NULL;
118 fprintf(stderr, "fuzz: memory mapping failed (%s)\n",
119 strerror(errno));
120 error();
121 } else {
122 file_pos = mem_map;
123 file_end = mem_map + file_len;
124 }
125 #else
126 file_pos = file_start = mem_alloc(file_len);
127 file_end = file_start + file_len;
128 if (fread(file_pos, 1, (size_t)file_len, file) != file_len) {
129 if (ferror(file))
130 pexit("fread");
131 fprintf(stderr, "fread: Unexpected EOF\n");
132 error();
133 }
134 #endif
135
136 rfd = mem_alloc(sizeof(struct FuzzDic));
137 rfd->next = NULL;
138 last_fd = rfd;
139
140 while ((line = get_line()) != NULL) {
141 pfd = mem_alloc(sizeof(struct FuzzDic));
142 pfd->next = NULL;
143 pfd->value = line;
144 last_fd->next = pfd;
145 last_fd = pfd;
146 }
147
148 #ifdef HAVE_MMAP
149 if (mem_map)
150 munmap(mem_map, file_len);
151 #else
152 MEM_FREE(file_start);
153 #endif
154 file_pos = file_end = NULL;
155
156 if (ferror(file)) pexit("fgets");
157
158 if (fclose(file)) pexit("fclose");
159 }
160
161 // Replace chars with '9$*#'
replace_each_chars(char * ciphertext,int * is_replace_finish)162 static char * replace_each_chars(char *ciphertext, int *is_replace_finish)
163 {
164 static int replaced_chars_index = 0;
165 static int cipher_index = 0;
166 static char replaced_chars[5] = "\xFF" "9$*#";
167
168 while (replaced_chars_index < sizeof(replaced_chars)) {
169 if (ciphertext[cipher_index] != replaced_chars[replaced_chars_index]) {
170 fuzz_hash[cipher_index] = replaced_chars[replaced_chars_index];
171 replaced_chars_index++;
172 return fuzz_hash;
173 }
174 replaced_chars_index++;
175 }
176 if (replaced_chars_index == sizeof(replaced_chars)) {
177 replaced_chars_index = 0;
178 cipher_index++;
179 }
180 if (cipher_index >= strlen(ciphertext)) {
181 *is_replace_finish = 1;
182 cipher_index = 0;
183 replaced_chars_index = 0;
184 return NULL;
185 } else {
186 while (replaced_chars_index < sizeof(replaced_chars)) {
187 if (ciphertext[cipher_index] != replaced_chars[replaced_chars_index]) {
188 fuzz_hash[cipher_index] = replaced_chars[replaced_chars_index];
189 replaced_chars_index++;
190 return fuzz_hash;
191 }
192 replaced_chars_index++;
193 }
194 }
195 // It will never reach here
196 return NULL;
197 }
198
199 // Swap two adjacent chars
200 // e.g
201 // ABCDE -> BACDE, ACBDE, ABDCE, ABCED
swap_chars(char * origin_ctext,int * is_swap_finish)202 static char * swap_chars(char *origin_ctext, int *is_swap_finish)
203 {
204 static int cipher_index = 1;
205
206 while (cipher_index < strlen(fuzz_hash)) {
207 if (origin_ctext[cipher_index - 1] != origin_ctext[cipher_index]) {
208 fuzz_hash[cipher_index - 1] = origin_ctext[cipher_index];
209 fuzz_hash[cipher_index] = origin_ctext[cipher_index - 1];
210 cipher_index++;
211 return fuzz_hash;
212 }
213 cipher_index++;
214 }
215
216 cipher_index = 1;
217 *is_swap_finish = 1;
218 return NULL;
219 }
220
221 // Append times of the last char
222 // times: 1, 2, 6, 42, 1806
append_last_char(char * origin_ctext,int * is_append_finish)223 static char * append_last_char(char *origin_ctext, int *is_append_finish)
224 {
225 static int times = 1;
226 static int i = 0;
227 int origin_ctext_len = 0;
228 int append_len = 0;
229
230 origin_ctext_len = strlen(origin_ctext);
231
232 if (origin_ctext_len == 0 || i == 5) {
233 times = 1;
234 i = 0;
235 *is_append_finish = 1;
236 return NULL;
237 }
238
239 if (origin_ctext_len + times < FUZZ_LINE_BUFFER_SIZE)
240 append_len = times;
241 else
242 append_len = FUZZ_LINE_BUFFER_SIZE - origin_ctext_len - 1;
243
244 memset(fuzz_hash + origin_ctext_len, origin_ctext[origin_ctext_len - 1], append_len);
245 fuzz_hash[origin_ctext_len + append_len] = 0;
246
247 i++;
248 times *= times + 1;
249
250 return fuzz_hash;
251 }
252
253 // Change hash cases, such as "abcdef" -> "Abcdef"
change_case(char * origin_ctext,int * is_chgcase_finish)254 static char * change_case(char *origin_ctext, int *is_chgcase_finish)
255 {
256 char c;
257 char *pc;
258 static int flag = 2;
259 static int cipher_index = 0;
260
261 while (origin_ctext[cipher_index]) {
262 c = origin_ctext[cipher_index];
263 if ('a' <= c && 'z' >= c) {
264 fuzz_hash[cipher_index] = c - 'a' + 'A';
265 cipher_index++;
266 return fuzz_hash;
267 } else if ('A' <= c && 'Z' >= c) {
268 fuzz_hash[cipher_index] = c - 'A' + 'a';
269 cipher_index++;
270 return fuzz_hash;
271 }
272 cipher_index++;
273 }
274
275 if (flag == 2) {
276 // Change all to upper cases
277 pc = fuzz_hash;
278 while (*pc) {
279 if ('a' <= *pc && 'z' >= *pc)
280 *pc = *pc - 'a' + 'A';
281 pc++;
282 }
283
284 flag--;
285 return fuzz_hash;
286 } else if (flag == 1) {
287 // Change all to lower cases
288 pc = fuzz_hash;
289 while (*pc) {
290 if ('A' <= *pc && 'Z' >= *pc)
291 *pc = *pc - 'A' + 'a';
292 pc++;
293 }
294
295 flag--;
296 return fuzz_hash;
297 }
298
299 flag = 2;
300 cipher_index = 0;
301 *is_chgcase_finish = 1;
302 return NULL;
303 }
304
305 // Insert str before pos in origin_ctext, and copy the result
306 // to out
insert_str(char * origin_ctext,int pos,char * str,char * out)307 static void insert_str(char *origin_ctext, int pos, char *str, char *out)
308 {
309 const int origin_ctext_len = strlen(origin_ctext);
310 int str_len = strlen(str);
311
312 if (str_len + origin_ctext_len >= FUZZ_LINE_BUFFER_SIZE)
313 str_len = FUZZ_LINE_BUFFER_SIZE - origin_ctext_len - 1;
314
315 strncpy(out, origin_ctext, pos);
316 strncpy(out + pos, str, str_len);
317 strcpy(out + pos + str_len, origin_ctext + pos);
318 }
319
320 // Insert strings from dictionary before each char
insert_dic(char * origin_ctext,int * is_insertdic_finish)321 static char * insert_dic(char *origin_ctext, int *is_insertdic_finish)
322 {
323 static int flag = 0;
324 static struct FuzzDic *pfd = NULL;
325 static int index = 0;
326 static int flag_long = 0;
327
328 if (!options.fuzz_dic)
329 return NULL;
330
331 if (!flag) {
332 pfd = rfd->next;
333 flag = 1;
334 }
335
336 if (!pfd) {
337 flag = 0;
338 *is_insertdic_finish = 1;
339 return NULL;
340 }
341
342 if (100000 > strlen(origin_ctext)) {
343 // Insert strings before each char
344 insert_str(origin_ctext, index++, pfd->value, fuzz_hash);
345 if (index >= strlen(origin_ctext) + 1) {
346 index = 0;
347 pfd = pfd->next;
348 }
349 } else {
350 // Insert strings before and after these chars: ",.:#$*@"
351 while (index < strlen(origin_ctext)) {
352 switch (origin_ctext[index]) {
353 case ',':
354 case '.':
355 case ':':
356 case '#':
357 case '$':
358 case '*':
359 case '@':
360 if (!flag_long) {
361 insert_str(origin_ctext, index, pfd->value, fuzz_hash);
362 flag_long = 1;
363 } else {
364 insert_str(origin_ctext, index + 1, pfd->value, fuzz_hash);
365 flag_long = 0;
366 index++;
367 }
368 default:
369 index++;
370 break;
371 }
372 }
373 if (index >= strlen(origin_ctext)) {
374 index = 0;
375 pfd = pfd->next;
376 }
377 }
378
379 return fuzz_hash;
380 }
381
382 // Insert str before pos in origin_ctext, and copy the result
383 // to out
insert_char(char * origin_ctext,int pos,char c,int size,char * out)384 static void insert_char(char *origin_ctext, int pos, char c, int size, char *out)
385 {
386 const int origin_ctext_len = strlen(origin_ctext);
387
388 if (size + origin_ctext_len >= FUZZ_LINE_BUFFER_SIZE)
389 size = FUZZ_LINE_BUFFER_SIZE- origin_ctext_len - 1;
390
391 strncpy(out, origin_ctext, pos);
392 memset(out + pos, c, size);
393 strcpy(out + pos + size, origin_ctext + pos);
394 }
395
396 // Insert chars from -128 to 127
insert_chars(char * origin_ctext,int * is_insertchars_finish)397 static char * insert_chars(char *origin_ctext, int *is_insertchars_finish)
398 {
399 static int oc_index = 0;
400 static int c_index = CHAR_FROM;
401 static int flag_long = 0;
402 static int times[5] = { 1, 10, 100, 1000, 10000 };
403 static int times_index = 0;
404
405 //printf("%s:%d %s(oc='%s', times_index=%d, c_index=%d, oc_index=%d)\n",
406 // __FILE__, __LINE__, __FUNCTION__, origin_ctext,
407 // times_index, c_index, oc_index);
408
409 if (times_index > 4) {
410 times_index = 0;
411 c_index++;
412 if (c_index > CHAR_TO) {
413 c_index = CHAR_FROM;
414 oc_index++;
415 flag_long = 0;
416 if (oc_index > strlen(origin_ctext)) {
417 oc_index = 0;
418 *is_insertchars_finish = 1;
419 return NULL;
420 }
421 }
422 }
423
424 if (100000 > strlen(origin_ctext)) {
425 // Insert chars before each char
426 insert_char(origin_ctext, oc_index, (char)c_index, times[times_index++], fuzz_hash);
427 } else {
428 // Insert chars before and after these chars: ",.:#$*"
429 while (oc_index < strlen(origin_ctext)) {
430 switch (origin_ctext[oc_index]) {
431 case ',':
432 case '.':
433 case ':':
434 case '#':
435 case '$':
436 case '*':
437 if (!flag_long) {
438 insert_char(origin_ctext, oc_index, (char)c_index, times[times_index], fuzz_hash);
439 flag_long = 1;
440 } else {
441 insert_char(origin_ctext, oc_index + 1, (char)c_index, times[times_index], fuzz_hash);
442 times_index++;
443 flag_long = 0;
444 }
445 return fuzz_hash;
446 default:
447 oc_index++;
448 break;
449 }
450 }
451 oc_index = 0;
452 c_index = CHAR_FROM;
453 flag_long = 0;
454 times_index = 0;
455 return NULL;
456 }
457
458 return fuzz_hash;
459 }
460
get_next_fuzz_case(const char * label,char * ciphertext)461 static char * get_next_fuzz_case(const char *label, char *ciphertext)
462 {
463 static int is_replace_finish = 0; // is_replace_finish = 1 if all the replaced cases have been generated
464 static int is_swap_finish = 0; // is_swap_finish = 1 if all the swaped cases have been generated
465 static int is_append_finish = 0; // is_append_finish = 1 if all the appended cases have been generated
466 static int is_chgcase_finish = 0; // is_chgcase_finish = 1 if all the change cases have been generated
467 static int is_insertdic_finish = 0; // is_insertdic_finish = 1 if all the insert dictionary cases have been generated
468 static int is_insertchars_finish = 0; // is_insertchars_finish = 1 if all the chars from -128 to 127 cases have been generated
469 static const char *last_label = NULL;
470 static char *last_ciphertext = NULL;
471
472 if (strlen(ciphertext) > FUZZ_LINE_BUFFER_SIZE) {
473 fprintf(stderr, "ciphertext='%s' is bigger than the FUZZ_LINE_BUFFER_SIZE=%d\n",
474 ciphertext, FUZZ_LINE_BUFFER_SIZE);
475 error();
476 }
477 strcpy(fuzz_hash, ciphertext);
478
479 if (!last_label)
480 last_label = label;
481
482 if (!last_ciphertext)
483 last_ciphertext = ciphertext;
484
485 if (strcmp(label, last_label) != 0 || strcmp(ciphertext, last_ciphertext) != 0) {
486 is_replace_finish = 0;
487 is_swap_finish = 0;
488 is_append_finish = 0;
489 is_chgcase_finish = 0;
490 is_insertdic_finish = 0;
491 is_insertchars_finish = 0;
492 last_label = label;
493 last_ciphertext = ciphertext;
494 }
495
496 if (!is_replace_finish)
497 if (replace_each_chars(ciphertext, &is_replace_finish))
498 return fuzz_hash;
499
500 if (!is_swap_finish)
501 if (swap_chars(ciphertext, &is_swap_finish))
502 return fuzz_hash;
503
504 if (!is_append_finish)
505 if (append_last_char(ciphertext, &is_append_finish))
506 return fuzz_hash;
507
508 if (!is_chgcase_finish)
509 if (change_case(ciphertext, &is_chgcase_finish))
510 return fuzz_hash;
511
512 if (!is_insertdic_finish)
513 if (insert_dic(ciphertext, &is_insertdic_finish))
514 return fuzz_hash;
515
516 if (!is_insertchars_finish)
517 if (insert_chars(ciphertext, &is_insertchars_finish))
518 return fuzz_hash;
519
520 return NULL;
521 }
522
init_status(const char * format_label)523 static void init_status(const char *format_label)
524 {
525 sprintf(status_file_path, "%s", "fuzz_status");
526 #if defined(_MSC_VER) || defined(__MINGW32__) || defined(__MINGW64__)
527 if (_mkdir(status_file_path)) { // MingW
528 #else
529 if (mkdir(status_file_path, S_IRUSR | S_IWUSR | S_IXUSR)) {
530 #endif
531 if (errno != EEXIST) pexit("mkdir: %s", status_file_path);
532 } else
533 fprintf(stderr, "Created directory: %s\n", status_file_path);
534
535 sprintf(status_file_path, "%s/%s", "fuzz_status", format_label);
536 if (!(s_file = fopen(status_file_path, "w")))
537 pexit("fopen: %s", status_file_path);
538 }
539
540 static void save_index(const int index)
541 {
542 fprintf(s_file, "%d\n", index);
543 fflush(s_file);
544 }
545
546 static void fuzz_test(struct db_main *db, struct fmt_main *format)
547 {
548 int index;
549 char *ret, *line;
550 struct fmt_tests *current;
551
552 printf("Fuzzing: %s%s%s%s [%s]%s... ",
553 format->params.label,
554 format->params.format_name[0] ? ", " : "",
555 format->params.format_name,
556 format->params.benchmark_comment,
557 format->params.algorithm_name,
558 #ifndef BENCH_BUILD
559 (options.target_enc == UTF_8 &&
560 format->params.flags & FMT_UNICODE) ?
561 " in UTF-8 mode" : "");
562 #else
563 "");
564 #endif
565 fflush(stdout);
566
567
568 // validate that there are no NULL function pointers
569 if (format->methods.prepare == NULL) return;
570 if (format->methods.valid == NULL) return;
571 if (format->methods.split == NULL) return;
572 if (format->methods.init == NULL) return;
573
574 index = 0;
575 current = format->params.tests;
576
577 init_status(format->params.label);
578 db->format = format;
579
580 while (1) {
581 ret = get_next_fuzz_case(format->params.label, current->ciphertext);
582 save_index(index++);
583 line = fuzz_hash;
584 db->format = format;
585 ldr_load_pw_line(db, line);
586
587 if (!ret) {
588 if (!(++current)->ciphertext)
589 break;
590 }
591 }
592 if (fclose(s_file)) pexit("fclose");
593 remove(status_file_path);
594 printf(" Completed\n");
595 }
596
597 // Dump fuzzed hashes which index is between from and to, including from and excluding to
598 static void fuzz_dump(struct fmt_main *format, const int from, const int to)
599 {
600 int index;
601 char *ret;
602 struct fmt_tests *current;
603 char file_name[PATH_BUFFER_SIZE];
604 FILE *file;
605 size_t len = 0;
606
607 sprintf(file_name, "pwfile.%s", format->params.label);
608
609 printf("Generating %s for %s%s%s%s ... ",
610 file_name,
611 format->params.label,
612 format->params.format_name[0] ? ", " : "",
613 format->params.format_name,
614 format->params.benchmark_comment);
615 fflush(stdout);
616
617 if (!(file = fopen(file_name, "w")))
618 pexit("fopen: %s", file_name);
619
620 index = 0;
621 current = format->params.tests;
622
623 while (1) {
624 ret = get_next_fuzz_case(format->params.label, current->ciphertext);
625 if (index >= from) {
626 if (index == to)
627 break;
628 fprintf(file, "%s\n", fuzz_hash);
629 len += 1 + strlen(fuzz_hash);
630 }
631 index++;
632 if (!ret) {
633 if (!(++current)->ciphertext)
634 break;
635 }
636 }
637 printf(LLu" bytes\n", (unsigned long long) len);
638 if (fclose(file)) pexit("fclose");
639 }
640
641
642 int fuzz(struct db_main *db)
643 {
644 char *p;
645 int from, to;
646 unsigned int total;
647 struct fmt_main *format;
648
649 pristine_gecos = cfg_get_bool(SECTION_OPTIONS, NULL,
650 "PristineGecos", 0);
651 single_skip_login = cfg_get_bool(SECTION_OPTIONS, NULL,
652 "SingleSkipLogin", 0);
653
654 if (options.flags & FLG_FUZZ_DUMP_CHK) {
655 from = -1;
656 to = -1;
657
658 if (options.fuzz_dump) {
659 p = strtok(options.fuzz_dump, ",");
660 if (p) {
661 sscanf(p, "%d", &from);
662 p = strtok(NULL, ",");
663
664 if (p)
665 sscanf(p, "%d", &to);
666 }
667 }
668 if (from > to) {
669 fprintf(stderr, "--fuzz-dump from=%d is bigger than to=%d\n",
670 from, to);
671 error();
672 }
673 }
674
675 fuzz_init_dictionary();
676
677 total = 0;
678 if ((format = fmt_list))
679 do {
680 /* Silently skip formats for which we have no tests, unless forced */
681 if (!format->params.tests && format != fmt_list)
682 continue;
683
684 if (options.flags & FLG_FUZZ_DUMP_CHK)
685 fuzz_dump(format, from, to);
686 else
687 fuzz_test(db, format);
688
689 total++;
690 } while ((format = format->next) && !event_abort);
691
692 if (options.flags & FLG_FUZZ_DUMP_CHK)
693 printf("Generated pwfile.<format> for %u formats\n", total);
694 else
695 printf("All %u formats passed fuzzing test!\n", total);
696
697 return 0;
698 }
699