1 /*
2 * File: sbtool.cpp
3 *
4 * Copyright (c) Freescale Semiconductor, Inc. All rights reserved.
5 * See included license file for license details.
6 */
7
8 #include "stdafx.h"
9 #include <iostream>
10 #include <fstream>
11 #include <sstream>
12 #include <stdlib.h>
13 #include <stdexcept>
14 #include <stdio.h>
15 #include "options.h"
16 #include "EncoreBootImage.h"
17 #include "smart_ptr.h"
18 #include "Logging.h"
19 #include "EncoreBootImageReader.h"
20 #include "format_string.h"
21
22 using namespace elftosb;
23
24 //! The tool's name.
25 const char k_toolName[] = "sbtool";
26
27 //! Current version number for the tool.
28 const char k_version[] = "1.1.4";
29
30 //! Copyright string.
31 const char k_copyright[] = "Copyright (c) 2006-2010 Freescale Semiconductor, Inc.\nAll rights reserved.";
32
33 //! Definition of command line options.
34 static const char * k_optionsDefinition[] = {
35 "?|help",
36 "v|version",
37 "k:key <file>",
38 "z|zero-key",
39 "x:extract",
40 "b|binary",
41 "d|debug",
42 "q|quiet",
43 "V|verbose",
44 NULL
45 };
46
47 //! Help string.
48 const char k_usageText[] = "\nOptions:\n\
49 -?/--help Show this help\n\
50 -v/--version Display tool version\n\
51 -k/--key <file> Add OTP key used for decryption\n\
52 -z/--zero-key Add default key of all zeroes\n\
53 -x/--extract <index> Extract section number <index>\n\
54 -b/--binary Extract section data as binary\n\
55 -d/--debug Enable debug output\n\
56 -q/--quiet Output only warnings and errors\n\
57 -V/--verbose Print extra detailed log information\n\n";
58
59 //! An array of strings.
60 typedef std::vector<std::string> string_vector_t;
61
62 // prototypes
63 int main(int argc, char* argv[], char* envp[]);
64
65 /*!
66 * \brief Class that encapsulates the sbtool interface.
67 *
68 * A single global logger instance is created during object construction. It is
69 * never freed because we need it up to the last possible minute, when an
70 * exception could be thrown.
71 */
72 class sbtool
73 {
74 protected:
75 int m_argc; //!< Number of command line arguments.
76 char ** m_argv; //!< String value for each command line argument.
77 StdoutLogger * m_logger; //!< Singleton logger instance.
78 string_vector_t m_keyFilePaths; //!< Paths to OTP key files.
79 string_vector_t m_positionalArgs; //!< Arguments coming after explicit options.
80 bool m_isVerbose; //!< Whether the verbose flag was turned on.
81 bool m_useDefaultKey; //!< Include a default (zero) crypto key.
82 bool m_doExtract; //!< True if extract mode is on.
83 unsigned m_sectionIndex; //!< Index of section to extract.
84 bool m_extractBinary; //!< True if extraction output is binary, false for hex.
85 smart_ptr<EncoreBootImageReader> m_reader; //!< Boot image reader object.
86
87 public:
88 /*!
89 * Constructor.
90 *
91 * Creates the singleton logger instance.
92 */
sbtool(int argc,char * argv[])93 sbtool(int argc, char * argv[])
94 : m_argc(argc),
95 m_argv(argv),
96 m_logger(0),
97 m_keyFilePaths(),
98 m_positionalArgs(),
99 m_isVerbose(false),
100 m_useDefaultKey(false),
101 m_doExtract(false),
102 m_sectionIndex(0),
103 m_extractBinary(false),
104 m_reader()
105 {
106 // create logger instance
107 m_logger = new StdoutLogger();
108 m_logger->setFilterLevel(Logger::INFO);
109 Log::setLogger(m_logger);
110 }
111
112 /*!
113 * Destructor.
114 */
~sbtool()115 ~sbtool()
116 {
117 }
118
119 /*!
120 * Reads the command line options passed into the constructor.
121 *
122 * This method can return a return code to its caller, which will cause the
123 * tool to exit immediately with that return code value. Normally, though, it
124 * will return -1 to signal that the tool should continue to execute and
125 * all options were processed successfully.
126 *
127 * The Options class is used to parse command line options. See
128 * #k_optionsDefinition for the list of options and #k_usageText for the
129 * descriptive help for each option.
130 *
131 * \retval -1 The options were processed successfully. Let the tool run normally.
132 * \return A zero or positive result is a return code value that should be
133 * returned from the tool as it exits immediately.
134 */
processOptions()135 int processOptions()
136 {
137 Options options(*m_argv, k_optionsDefinition);
138 OptArgvIter iter(--m_argc, ++m_argv);
139
140 // process command line options
141 int optchar;
142 const char * optarg;
143 while (optchar = options(iter, optarg))
144 {
145 switch (optchar)
146 {
147 case '?':
148 printUsage(options);
149 return 0;
150
151 case 'v':
152 printf("%s %s\n%s\n", k_toolName, k_version, k_copyright);
153 return 0;
154
155 case 'k':
156 m_keyFilePaths.push_back(optarg);
157 break;
158
159 case 'z':
160 m_useDefaultKey = true;
161 break;
162
163 case 'x':
164 m_doExtract = true;
165 m_sectionIndex = strtoul(optarg, NULL, 0);
166 break;
167
168 case 'b':
169 m_extractBinary = true;
170 Log::getLogger()->setFilterLevel(Logger::WARNING);
171 break;
172
173 case 'd':
174 Log::getLogger()->setFilterLevel(Logger::DEBUG);
175 break;
176
177 case 'q':
178 Log::getLogger()->setFilterLevel(Logger::WARNING);
179 break;
180
181 case 'V':
182 m_isVerbose = true;
183 break;
184
185 default:
186 Log::log(Logger::ERROR, "error: unrecognized option\n\n");
187 printUsage(options);
188 return 1;
189 }
190 }
191
192 // handle positional args
193 if (iter.index() < m_argc)
194 {
195 // Log::SetOutputLevel leveler(Logger::DEBUG);
196 // Log::log("positional args:\n");
197 int i;
198 for (i = iter.index(); i < m_argc; ++i)
199 {
200 // Log::log("%d: %s\n", i - iter.index(), m_argv[i]);
201 m_positionalArgs.push_back(m_argv[i]);
202 }
203 }
204
205 // all is well
206 return -1;
207 }
208
209 /*!
210 * Prints help for the tool.
211 */
printUsage(Options & options)212 void printUsage(Options & options)
213 {
214 options.usage(std::cout, "sb-file");
215 printf("%s", k_usageText);
216 }
217
218 /*!
219 * Core of the tool. Calls processOptions() to handle command line options
220 * before performing the real work the tool does.
221 */
run()222 int run()
223 {
224 try
225 {
226 // read command line options
227 int result;
228 if ((result = processOptions()) != -1)
229 {
230 return result;
231 }
232
233 // set verbose logging
234 setVerboseLogging();
235
236 // make sure a file was provided
237 if (m_positionalArgs.size() < 1)
238 {
239 throw std::runtime_error("no sb file path was provided");
240 }
241
242 // read the boot image
243 readBootImage();
244 }
245 catch (std::exception & e)
246 {
247 Log::log(Logger::ERROR, "error: %s\n", e.what());
248 return 1;
249 }
250 catch (...)
251 {
252 Log::log(Logger::ERROR, "error: unexpected exception\n");
253 return 1;
254 }
255
256 return 0;
257 }
258
259 /*!
260 * \brief Turns on verbose logging.
261 */
setVerboseLogging()262 void setVerboseLogging()
263 {
264 if (m_isVerbose)
265 {
266 // verbose only affects the INFO and DEBUG filter levels
267 // if the user has selected quiet mode, it overrides verbose
268 switch (Log::getLogger()->getFilterLevel())
269 {
270 case Logger::INFO:
271 Log::getLogger()->setFilterLevel(Logger::INFO2);
272 break;
273 case Logger::DEBUG:
274 Log::getLogger()->setFilterLevel(Logger::DEBUG2);
275 break;
276 }
277 }
278 }
279
280 /*!
281 * \brief Opens and reads the boot image identified on the command line.
282 * \pre At least one position argument must be present.
283 */
readBootImage()284 void readBootImage()
285 {
286 Log::SetOutputLevel infoLevel(Logger::INFO);
287
288 // open the sb file stream
289 std::ifstream sbStream(m_positionalArgs[0].c_str(), std::ios_base::binary | std::ios_base::in);
290 if (!sbStream.is_open())
291 {
292 throw std::runtime_error("failed to open input file");
293 }
294
295 // create the boot image reader
296 m_reader = new EncoreBootImageReader(sbStream);
297
298 // read image header
299 m_reader->readImageHeader();
300 const EncoreBootImage::boot_image_header_t & header = m_reader->getHeader();
301 if (header.m_majorVersion > 1)
302 {
303 throw std::runtime_error(format_string("boot image format version is too new (format version %d.%d)\n", header.m_majorVersion, header.m_minorVersion));
304 }
305 Log::log("---- Boot image header ----\n");
306 dumpImageHeader(header);
307
308 // compute SHA-1 over image header and test against the digest stored in the header
309 sha1_digest_t computedDigest;
310 m_reader->computeHeaderDigest(computedDigest);
311 if (compareDigests(computedDigest, m_reader->getHeader().m_digest))
312 {
313 Log::log("Header digest is correct.\n");
314 }
315 else
316 {
317 Log::log(Logger::WARNING, "warning: stored SHA-1 header digest does not match the actual header digest\n");
318 Log::log(Logger::WARNING, "\n---- Actual SHA-1 digest of image header ----\n");
319 logHexArray(Logger::WARNING, (uint8_t *)&computedDigest, sizeof(computedDigest));
320 }
321
322 // read the section table
323 m_reader->readSectionTable();
324 const EncoreBootImageReader::section_array_t & sectionTable = m_reader->getSections();
325 EncoreBootImageReader::section_array_t::const_iterator it = sectionTable.begin();
326 Log::log("\n---- Section table ----\n");
327 unsigned n = 0;
328 for (; it != sectionTable.end(); ++it, ++n)
329 {
330 const EncoreBootImage::section_header_t & sectionHeader = *it;
331 Log::log("Section %d:\n", n);
332 dumpSectionHeader(sectionHeader);
333 }
334
335 // read the key dictionary
336 // XXX need to support multiple keys, not just the first!
337 if (m_reader->isEncrypted())
338 {
339 Log::log("\n---- Key dictionary ----\n");
340 if (m_keyFilePaths.size() > 0 || m_useDefaultKey)
341 {
342 if (m_keyFilePaths.size() > 0)
343 {
344 std::string & keyPath = m_keyFilePaths[0];
345 std::ifstream keyStream(keyPath.c_str(), std::ios_base::binary | std::ios_base::in);
346 if (!keyStream.is_open())
347 {
348 Log::log(Logger::WARNING, "warning: unable to read key %s\n", keyPath.c_str());
349 }
350 AESKey<128> kek(keyStream);
351
352 // search for this key in the key dictionary
353 if (!m_reader->readKeyDictionary(kek))
354 {
355 throw std::runtime_error("the provided key is not valid for this encrypted boot image");
356 }
357
358 Log::log("\nKey %s was found in key dictionary.\n", keyPath.c_str());
359 }
360 else
361 {
362 // default key of zero, overriden if -k was used
363 AESKey<128> defaultKek;
364
365 // search for this key in the key dictionary
366 if (!m_reader->readKeyDictionary(defaultKek))
367 {
368 throw std::runtime_error("the default key is not valid for this encrypted boot image");
369 }
370
371 Log::log("\nDefault key was found in key dictionary.\n");
372 }
373
374 // print out the DEK
375 AESKey<128> dek = m_reader->getKey();
376 std::stringstream dekStringStream(std::ios_base::in | std::ios_base::out);
377 dek.writeToStream(dekStringStream);
378 std::string dekString = dekStringStream.str();
379 // Log::log("\nData encryption key: %s\n", dekString.c_str());
380 Log::log("\nData encryption key:\n");
381 logHexArray(Logger::INFO, (const uint8_t *)&dek.getKey(), sizeof(AESKey<128>::key_t));
382 }
383 else
384 {
385 throw std::runtime_error("the image is encrypted but no key was provided");
386 }
387 }
388
389 // read the SHA-1 digest over the entire image. this is done after
390 // reading the key dictionary because the digest is encrypted in
391 // encrypted boot images.
392 m_reader->readImageDigest();
393 const sha1_digest_t & embeddedDigest = m_reader->getDigest();
394 Log::log("\n---- SHA-1 digest of entire image ----\n");
395 logHexArray(Logger::INFO, (const uint8_t *)&embeddedDigest, sizeof(embeddedDigest));
396
397 // compute the digest over the entire image and compare
398 m_reader->computeImageDigest(computedDigest);
399 if (compareDigests(computedDigest, embeddedDigest))
400 {
401 Log::log("Image digest is correct.\n");
402 }
403 else
404 {
405 Log::log(Logger::WARNING, "warning: stored SHA-1 digest does not match the actual digest\n");
406 Log::log(Logger::WARNING, "\n---- Actual SHA-1 digest of entire image ----\n");
407 logHexArray(Logger::WARNING, (uint8_t *)&computedDigest, sizeof(computedDigest));
408 }
409
410 // read the boot tags
411 m_reader->readBootTags();
412 Log::log("\n---- Boot tags ----\n");
413 unsigned block = header.m_firstBootTagBlock;
414 const EncoreBootImageReader::boot_tag_array_t & tags = m_reader->getBootTags();
415 EncoreBootImageReader::boot_tag_array_t::const_iterator tagIt = tags.begin();
416 for (n = 0; tagIt != tags.end(); ++tagIt, ++n)
417 {
418 const EncoreBootImage::boot_command_t & command = *tagIt;
419 Log::log("%04u: @ block %06u | id=0x%08x | length=%06u | flags=0x%08x\n", n, block, command.m_address, command.m_count, command.m_data);
420
421 if (command.m_data & EncoreBootImage::ROM_SECTION_BOOTABLE)
422 {
423 Log::log(" 0x1 = ROM_SECTION_BOOTABLE\n");
424 }
425
426 if (command.m_data & EncoreBootImage::ROM_SECTION_CLEARTEXT)
427 {
428 Log::log(" 0x2 = ROM_SECTION_CLEARTEXT\n");
429 }
430
431 block += command.m_count + 1;
432 }
433
434 // now read all of the sections
435 Log::log(Logger::INFO2, "\n---- Sections ----\n");
436 for (n = 0; n < header.m_sectionCount; ++n)
437 {
438 EncoreBootImage::Section * section = m_reader->readSection(n);
439 section->debugPrint();
440
441 // Check if this is the section the user wants to extract.
442 if (m_doExtract && n == m_sectionIndex)
443 {
444 extractSection(section);
445 }
446 }
447 }
448
449 //! \brief Dumps the contents of a section to stdout.
450 //!
451 //! If #m_extractBinary is true then the contents are written as
452 //! raw binary to stdout. Otherwise the data is formatted using
453 //! logHexArray().
extractSection(EncoreBootImage::Section * section)454 void extractSection(EncoreBootImage::Section * section)
455 {
456 // Allocate buffer to hold section data.
457 unsigned blockCount = section->getBlockCount();
458 unsigned dataLength = sizeOfCipherBlocks(blockCount);
459 smart_array_ptr<uint8_t> buffer = new uint8_t[dataLength];
460 cipher_block_t * data = reinterpret_cast<cipher_block_t *>(buffer.get());
461
462 // Read section data into the buffer one block at a time.
463 unsigned offset;
464 for (offset = 0; offset < blockCount;)
465 {
466 unsigned blocksRead = section->getBlocks(offset, 1, data);
467 offset += blocksRead;
468 data += blocksRead;
469 }
470
471 // Print header.
472 Log::log(Logger::INFO, "\nSection %d contents:\n", m_sectionIndex);
473
474 // Now dump the extracted data to stdout.
475 if (m_extractBinary)
476 {
477 if (fwrite(buffer.get(), 1, dataLength, stdout) != dataLength)
478 {
479 throw std::runtime_error(format_string("failed to write data to stdout (%d)", ferror(stdout)));
480 }
481 }
482 else
483 {
484 // Use the warning log level so the data will be visible even in quiet mode.
485 logHexArray(Logger::WARNING, buffer, dataLength);
486 }
487 }
488
489 //! \brief Compares two SHA-1 digests and returns whether they are equal.
490 //! \retval true The two digests are equal.
491 //! \retval false The \a a and \a b digests are different from each other.
compareDigests(const sha1_digest_t & a,const sha1_digest_t & b)492 bool compareDigests(const sha1_digest_t & a, const sha1_digest_t & b)
493 {
494 return memcmp(a, b, sizeof(sha1_digest_t)) == 0;
495 }
496
497 /*
498 struct boot_image_header_t
499 {
500 union
501 {
502 sha1_digest_t m_digest; //!< SHA-1 digest of image header. Also used as the crypto IV.
503 struct
504 {
505 cipher_block_t m_iv; //!< The first four bytes of the digest form the initialization vector.
506 uint8_t m_extra[4]; //!< The leftover top four bytes of the SHA-1 digest.
507 };
508 };
509 uint8_t m_signature[4]; //!< 'STMP', see #ROM_IMAGE_HEADER_SIGNATURE.
510 uint16_t m_version; //!< Version of the boot image format, see #ROM_BOOT_IMAGE_VERSION.
511 uint16_t m_flags; //!< Flags or options associated with the entire image.
512 uint32_t m_imageBlocks; //!< Size of entire image in blocks.
513 uint32_t m_firstBootTagBlock; //!< Offset from start of file to the first boot tag, in blocks.
514 section_id_t m_firstBootableSectionID; //!< ID of section to start booting from.
515 uint16_t m_keyCount; //!< Number of entries in DEK dictionary.
516 uint16_t m_keyDictionaryBlock; //!< Starting block number for the key dictionary.
517 uint16_t m_headerBlocks; //!< Size of this header, including this size word, in blocks.
518 uint16_t m_sectionCount; //!< Number of section headers in this table.
519 uint16_t m_sectionHeaderSize; //!< Size in blocks of a section header.
520 uint8_t m_padding0[6]; //!< Padding to align #m_timestamp to long word.
521 uint64_t m_timestamp; //!< Timestamp when image was generated in microseconds since 1-1-2000.
522 version_t m_productVersion; //!< Product version.
523 version_t m_componentVersion; //!< Component version.
524 uint16_t m_driveTag;
525 uint8_t m_padding1[6]; //!< Padding to round up to next cipher block.
526 };
527 */
dumpImageHeader(const EncoreBootImage::boot_image_header_t & header)528 void dumpImageHeader(const EncoreBootImage::boot_image_header_t & header)
529 {
530 version_t vers;
531
532 Log::SetOutputLevel infoLevel(Logger::INFO);
533 Log::log("Signature 1: %c%c%c%c\n", header.m_signature[0], header.m_signature[1], header.m_signature[2], header.m_signature[3]);
534 Log::log("Signature 2: %c%c%c%c\n", header.m_signature2[0], header.m_signature2[1], header.m_signature2[2], header.m_signature2[3]);
535 Log::log("Format version: %d.%d\n", header.m_majorVersion, header.m_minorVersion);
536 Log::log("Flags: 0x%04x\n", header.m_flags);
537 Log::log("Image blocks: %u\n", header.m_imageBlocks);
538 Log::log("First boot tag block: %u\n", header.m_firstBootTagBlock);
539 Log::log("First boot section ID: 0x%08x\n", header.m_firstBootableSectionID);
540 Log::log("Key count: %u\n", header.m_keyCount);
541 Log::log("Key dictionary block: %u\n", header.m_keyDictionaryBlock);
542 Log::log("Header blocks: %u\n", header.m_headerBlocks);
543 Log::log("Section count: %u\n", header.m_sectionCount);
544 Log::log("Section header size: %u\n", header.m_sectionHeaderSize);
545 Log::log("Timestamp: %llu\n", header.m_timestamp);
546 vers = header.m_productVersion;
547 vers.fixByteOrder();
548 Log::log("Product version: %x.%x.%x\n", vers.m_major, vers.m_minor, vers.m_revision);
549 vers = header.m_componentVersion;
550 vers.fixByteOrder();
551 Log::log("Component version: %x.%x.%x\n", vers.m_major, vers.m_minor, vers.m_revision);
552 if (header.m_majorVersion == 1 && header.m_minorVersion >= 1)
553 {
554 Log::log("Drive tag: 0x%04x\n", header.m_driveTag);
555 }
556 Log::log("SHA-1 digest of header:\n");
557 logHexArray(Logger::INFO, (uint8_t *)&header.m_digest, sizeof(header.m_digest));
558 }
559
dumpSectionHeader(const EncoreBootImage::section_header_t & header)560 void dumpSectionHeader(const EncoreBootImage::section_header_t & header)
561 {
562 Log::SetOutputLevel infoLevel(Logger::INFO);
563 Log::log(" Identifier: 0x%x\n", header.m_tag);
564 Log::log(" Offset: %d block%s (%d bytes)\n", header.m_offset, header.m_offset!=1?"s":"", sizeOfCipherBlocks(header.m_offset));
565 Log::log(" Length: %d block%s (%d bytes)\n", header.m_length, header.m_length!=1?"s":"", sizeOfCipherBlocks(header.m_length));
566 Log::log(" Flags: 0x%08x\n", header.m_flags);
567
568 if (header.m_flags & EncoreBootImage::ROM_SECTION_BOOTABLE)
569 {
570 Log::log(" 0x1 = ROM_SECTION_BOOTABLE\n");
571 }
572
573 if (header.m_flags & EncoreBootImage::ROM_SECTION_CLEARTEXT)
574 {
575 Log::log(" 0x2 = ROM_SECTION_CLEARTEXT\n");
576 }
577 }
578
579 /*!
580 * \brief Log an array of bytes as hex.
581 */
logHexArray(Logger::log_level_t level,const uint8_t * bytes,unsigned count)582 void logHexArray(Logger::log_level_t level, const uint8_t * bytes, unsigned count)
583 {
584 Log::SetOutputLevel leveler(level);
585
586 unsigned i;
587 for (i = 0; i < count; ++i, ++bytes)
588 {
589 if ((i % 16 == 0) && (i < count - 1))
590 {
591 if (i != 0)
592 {
593 Log::log("\n");
594 }
595 Log::log(" 0x%08x: ", i);
596 }
597 Log::log("%02x ", *bytes & 0xff);
598 }
599
600 Log::log("\n");
601 }
602
603 };
604
605 /*!
606 * Main application entry point. Creates an sbtool instance and lets it take over.
607 */
main(int argc,char * argv[],char * envp[])608 int main(int argc, char* argv[], char* envp[])
609 {
610 try
611 {
612 return sbtool(argc, argv).run();
613 }
614 catch (...)
615 {
616 Log::log(Logger::ERROR, "error: unexpected exception\n");
617 return 1;
618 }
619
620 return 0;
621 }
622
623
624
625
626
627