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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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 */ 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(). 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. 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 */ 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 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 */ 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 */ 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