Timestamp handling now 100% correct; device ATSE <model> no longer required
[cfe_generate_password.git] / cfe_generate_password.c
1 /*
2   Generate Broadcom CFE seeds and passwords for many popular modem/router devices
3
4   Copyright 2015 TJ <hacker@iam.tj>
5   Licenced on the terms of the GNU General Public Licence version 3
6
7   To build:
8
9     gcc -o cfe_gen_pass cfe_generate_password.c
10
11   Or:
12
13     make
14
15   To use:
16
17     ./cfe_gen_pass [options]
18
19   This tool can generate passwords for use with many devices that contain
20   Broadcom Common Firmware Environment (CFE) bootbase which has a debug mode
21   that is enabled using the "ATEN 1 XXXXXXXX" command, where XXXXXXXX is an
22   eight digit hexadecimal 'password'.
23
24   It is NOT necessary to have the device generate a 'seed' using "ATSE [MODEL-ID]"
25   because this tool can generate the seed from the device's first (base) MAC address.
26
27   When the device generates a seed it combines the number of seconds since 1970-01-01 00:00:00
28   with the router MAC address. Both are encoded in a single 6-byte hexadecimal
29
30   Each value is truncated to its 3 least significant bytes so, for example:
31
32   $ date +%F.%T; echo "obase=16;$(date +%s)" | bc
33   2016-03-26.23:06:32
34   56F715F8
35   # MAC Address: EC:43:F6:46:C0:80
36
37   becomes F715F8 concatenated with 46C080
38
39   CFE> ATSE DSL-2492GNAU-B1BC
40   F715F846C080   <<<< last 3 bytes of MAC address
41   ^^^^^^
42   seconds since 1970-01-01 00:00:00 (2016-03-26 23:06:32)
43
44   *NOTE: the default seed after power-up is 000000 so no time value needs to be specifed
45   if "ATSE <model-id-string>" has not been executed on the device.
46
47   Access to the device's console via a serial UART port, or a network telnet/ssh session,
48   is required to enter the password.
49
50   So, for a device with base MAC address (reported by the CFE during boot) E.g:
51
52     CFE version 1.0.38-112.118 for BCM963268 (32bit,SP,BE)
53     ...
54     Base MAC Address                  : ec:43:f6:46:c0:80
55     ...
56     *** Press any key to stop auto run (1 seconds) ***
57     CFE>
58
59   Using this tool do:
60
61     ./cfe_gen_pass -s ec:43:f6:46:c0:80 -p
62
63     MAC address: ec:43:f6:46:c0:80 Timestamp: 000000 Seed: 00000046c080 Password: 10f0a563
64
65   And on the device do:
66
67     CFE> ATEN 1 10f0a563
68     OK
69     *** command status = 0
70
71   The tool can accept a timestamp as 8 hexadecimal characters (useful for testing the algorithm):
72
73     ./cfe_gen_pass -t 0FF020 -s ec:43:f6:46:c0:80 -p
74
75     MAC address: ec:43:f6:46:c0:80 Timestamp: 0FF020 Seed: 0FF02046c080 Password: 110f65a3
76  */
77
78 #include <stdio.h>
79 #include <stdlib.h>
80 #include <stdarg.h>
81 #include <string.h>
82 #include <time.h>
83
84 static const float VERSION = 1.2f;
85 static const size_t TIMESTAMP_SIZE = 8;
86 static const size_t SEED_SIZE = 12;
87 static const size_t PASSWORD_SIZE = 8;
88 static const size_t MESSAGE_SIZE = 128;
89 static const size_t MAC_ADDR_SIZE = 17;
90 static const size_t DATESTRING_SIZE = 20;
91
92 static void
93 pr_usage()
94 {
95   fprintf(stderr, "%s\n",
96     "Usage:\n"
97     "  -v                   show version\n"
98     "  -s 00:01:02:03:04:05 create seed from MAC address\n"
99     "  -t [00000000]        seconds since 1970-01-01 (defaults to NOW) \n"
100     "  -p [SEED]            generate password (with optional seed)\n\n"
101     "  E.g. -s 01:02:03:04:05 \n"
102     "       -s 01:02:03:04:05 -p\n"
103     "       -p 000000030405\n"
104   );
105 }
106
107 static void
108 pr_error_exit(unsigned int usage, const char *error, ...)
109 {
110  va_list args;
111  char error_message[MESSAGE_SIZE + 1];
112
113  if (!error) return;
114
115  va_start(args, error);
116  (void) vsnprintf(error_message, MESSAGE_SIZE + 1, error, args);
117  va_end(args);
118  fprintf(stderr, "Error: %s\n", error_message);
119
120  if (usage) pr_usage();
121
122  exit(EXIT_FAILURE);
123 }
124
125 static const unsigned int passwords[8] = {
126   0x10F0A563,
127   0x887852B1,
128   0xC43C2958,
129   0x621E14AC,
130   0x310F0A56,
131   0x1887852B,
132   0x8C43C295,
133   0xC621E14A
134 };
135
136 static unsigned int
137 generate_seed(char *mac, char *timestamp, char *seed)
138 {
139   unsigned int result = 0;
140   if (mac && strlen(mac) == MAC_ADDR_SIZE) {
141     size_t i;
142     char *mac_ptr = mac + 9;
143     size_t ts_len = strlen(timestamp);
144     if (ts_len == TIMESTAMP_SIZE) {
145       for (i = 0; i < SEED_SIZE; ++i) {
146         if (i < 6) // first half of seed is the truncated timestamp
147           seed[i] = timestamp[i+2];
148         else { // second half is the truncated MAC address
149           if (*mac_ptr == ':' || *mac_ptr == '-')
150             ++mac_ptr;
151           seed[i] = *mac_ptr++;
152         }
153       }
154       result = 1;
155     } else
156       pr_error_exit(0, "Timestamp ('%s') should be %d hexadecimal characters e.g: 56F715F8", timestamp, TIMESTAMP_SIZE);
157   } else
158    pr_error_exit(0, "MAC-ADDR should be %d characters, e.g: 00:01:02:03:04:05", MAC_ADDR_SIZE);
159
160   return result;
161 }
162
163 static unsigned int
164 generate_pass(char *seed, char *password)
165 {
166   unsigned int result = 0;
167
168   if (seed && strlen(seed) == SEED_SIZE) {
169     unsigned int timestamp, byte, key, pass;
170     timestamp = byte = 0;
171     if(! sscanf(seed, "%06x", &timestamp))
172       pr_error_exit(1, "unable to parse seed's timestamp");
173     if (! sscanf(&seed[10], "%02x", &byte))
174       pr_error_exit(1, "unable to parse seed's MAC address");
175     key = byte & 0x07;
176     pass = (passwords[key] + timestamp) ^ timestamp;
177     snprintf(password, PASSWORD_SIZE + 1,  "%08x", pass);
178     result = 1;
179   } else
180     pr_error_exit(0, "Seed should be %d hex characters", SEED_SIZE);
181
182   return result;
183 }
184
185 int
186 main(int argc, char **argv, char **env)
187 {
188   int result = 0;
189
190   if (argc == 1) {
191     pr_usage();
192   }
193   else {
194     unsigned int arg;
195     char *MAC_ADDR = NULL;
196     char timestamp[TIMESTAMP_SIZE + 1];
197     char seed[SEED_SIZE + 1];
198     char password[PASSWORD_SIZE + 1];
199     char date_string[DATESTRING_SIZE + 1];
200     unsigned int opt_seed, opt_pass, opt_ts;
201     time_t ts = 0;
202     struct tm *t = NULL;
203     seed[0] = password[0] = timestamp[0] = 0;
204     seed[SEED_SIZE] = password[PASSWORD_SIZE] = 0;
205     opt_seed = opt_pass = opt_ts = 0;
206     strncpy(timestamp, "00000000", TIMESTAMP_SIZE + 1);
207
208     for (arg = 1; arg < (unsigned) argc; ++arg) {
209       size_t arg_len = strlen(argv[arg]);
210
211       if (argv[arg][0] == '-') {
212         switch (argv[arg][1]) {
213           case 's':
214             opt_seed = 1;
215             break;
216           case 'p':
217             opt_pass = 1;
218             break;
219           case 't':
220             opt_ts = 1;
221             break;
222           case 'v':
223             fprintf(stderr, "Version: %0.2f\n", VERSION);
224         }
225       } else if (opt_seed == 1) {
226         MAC_ADDR = argv[arg];
227         ++opt_seed;
228       } else if (opt_pass == 1 && opt_seed == 0) {
229         if (arg_len != SEED_SIZE)
230           pr_error_exit(1, "seed length must be %d characters", SEED_SIZE);
231
232         strncpy(seed, argv[arg], SEED_SIZE);
233         ++opt_pass;
234       } else if (opt_ts == 1) {
235         if (arg_len != TIMESTAMP_SIZE)
236           pr_error_exit(1, "timestamp length must be %d hexadecimal characters", TIMESTAMP_SIZE);
237
238         strncpy(timestamp, argv[arg], TIMESTAMP_SIZE);
239         ++opt_ts;
240       }
241     }
242     if (! opt_seed && ! opt_pass)
243       pr_usage();
244     else if (opt_seed && opt_seed != 2)
245       pr_error_exit(1, "seed requires MAC-ADDRESS");
246     else if (! opt_seed && opt_pass && opt_pass != 2)
247       pr_error_exit(1, "password on its own requires a pre-generated seed");
248     else if (opt_seed && opt_pass && opt_pass != 1)
249       pr_error_exit(1, "generating seed and password; cannot also accept pre-generated seed");
250     else if (opt_pass == 2 && opt_ts)
251       pr_error_exit(1, "seed already contains a timestamp; cannot over-ride it");
252     else if (opt_ts == 1 || opt_pass == 2) { // no timestamp provided; use NOW
253       ts = time(NULL);
254       if (ts)
255         snprintf(timestamp, TIMESTAMP_SIZE + 1, "%08lX", ts);
256     }
257
258     if (opt_pass == 2) { // try to figure out the correct date-time from the seed
259       // inherits the most significant 2 characters from the NOW time
260       strncpy(timestamp+2, seed, 6);
261       time_t tmp;
262       if (sscanf(timestamp, "%08lx", &tmp))
263         if (tmp > ts-3600 && tmp < ts+3600) // timestamps are so close they must be for the same date
264           ts = tmp;
265     }
266
267     if(opt_ts) { // ts needs to be valid to be converted to a time string
268       if(! sscanf(timestamp, "%08lx", &ts))
269         pr_error_exit(1, "converting timestamp string ('%s') to number", timestamp);
270     }
271     t = gmtime(&ts);
272     strftime(date_string, DATESTRING_SIZE, "%F %T", t);
273
274     if (opt_seed)
275       if (! generate_seed(MAC_ADDR, timestamp, seed))
276         pr_error_exit(1, "unable to generate seed; aborting");
277     if (opt_pass)
278       if (! generate_pass(seed, password))
279         pr_error_exit(0, "unable to generate password");
280
281     if (opt_seed || opt_pass)
282       printf("MAC address: %s Timestamp: %s (%s) Seed: %s Password: %s\n", MAC_ADDR, timestamp, date_string, seed, password);
283   }
284
285   return result;
286 }
287