1 /* 2 * The Sleuth Kit 3 * 4 * Contact: Brian Carrier [carrier <at> sleuthkit [dot] org] 5 * Copyright (c) 2010-2013 Basis Technology Corporation. All Rights 6 * reserved. 7 * 8 * This software is distributed under the Common Public License 1.0 9 */ 10 11 /** 12 * \file RegRipperModule.cpp 13 * Contains the implementation for the reg ripper reporting module. 14 * This module runs the RegRipper executable against the common set of 15 * Windows registry files (i.e., NTUSER, SYSTEM, SAM and SOFTWARE). 16 */ 17 18 // TSK Framework includes 19 #include "tsk/framework/utilities/TskModuleDev.h" 20 21 // Poco includes 22 #include "Poco/String.h" 23 #include "Poco/StringTokenizer.h" 24 #include "Poco/File.h" 25 #include "Poco/Process.h" 26 #include "Poco/PipeStream.h" 27 #include "Poco/FileStream.h" 28 #include "Poco/StreamCopier.h" 29 #include "Poco/Path.h" 30 #include "Poco/RegularExpression.h" 31 #include "Poco/Environment.h" 32 33 // C/C++ standard library includes 34 #include <string> 35 #include <sstream> 36 #include <cassert> 37 38 namespace 39 { 40 const char *MODULE_NAME = "RegRipper"; 41 const char *MODULE_DESCRIPTION = "Runs the RegRipper executable against the common set of Windows registry files (i.e., NTUSER, SYSTEM, SAM and SOFTWARE)"; 42 const char *MODULE_VERSION = "1.0.2"; 43 const uint64_t VOLUME_SHADOW_SNAPSHOT_FILE_PARENT_ID = 9223372036854775807; 44 45 std::string ripExePath; 46 std::string outputFolderPath; 47 std::vector<std::string> interpreterArgs; 48 std::string pluginPath; 49 50 enum RegistryHiveType 51 { 52 NTUSER, 53 SYSTEM, 54 SAM, 55 SOFTWARE 56 }; 57 58 /** 59 * Looks for an executable file in the PATH environment variable. 60 * If exeFilename is found, it is also tested to see if it's executable. 61 * @param exeFilename The filename of an executable file. 62 * @return Path found to the executable if it exists, otherwise an empty string 63 */ checkExeEnvPath(const std::string & exeFilename)64 static const std::string checkExeEnvPath(const std::string & exeFilename) 65 { 66 static const short unsigned int MAX_ENV_LEN = 4096; 67 68 std::string envPaths = Poco::Environment::get("PATH"); 69 70 // Don't waste time checking if env var is unreasonably large 71 if (envPaths.length() < MAX_ENV_LEN) 72 { 73 Poco::Path p; 74 if (Poco::Path::find(envPaths, exeFilename, p)) 75 { 76 std::string newExePath = p.toString(); 77 78 // Check if executable mode is set 79 Poco::File exeFile(newExePath); 80 if (exeFile.canExecute()) 81 { 82 return newExePath; 83 } 84 } 85 } 86 return std::string(); 87 } 88 89 /** 90 * Parse RegRipper output from a specific output file for matches on the valueName. The 91 * function will return all lines in the file that match the valueName followed by one 92 * of the potential RegRipper separators. This may not always find all lines if a plugin 93 * writer uses a new separator. 94 * @param regRipperFileName The full path to a regRipper output file. 95 * @param valueName The name of the value to search for. Will support regex matches that 96 * come before a separator. 97 * @return A vector of matching lines from the file. 98 */ getRegRipperValues(const std::string & regRipperFileName,const std::string & valueName)99 std::vector<std::string> getRegRipperValues(const std::string& regRipperFileName, const std::string& valueName) 100 { 101 Poco::FileInputStream inStream(regRipperFileName); 102 std::vector<std::string> results; 103 104 std::string line; 105 106 std::stringstream pattern; 107 pattern << valueName << "[\\s\\->=:]+"; 108 109 Poco::RegularExpression regex(pattern.str(), 0, true); 110 Poco::RegularExpression::Match match; 111 112 while (std::getline(inStream, line)) 113 { 114 int nummatches = regex.match(line, match, 0); 115 if (nummatches > 0) 116 { 117 results.push_back(line.substr(match.offset + match.length, line.size())); 118 } 119 } 120 121 inStream.close(); 122 return results; 123 } 124 125 /** 126 * Processes the RegRipper output from a SOFTWARE hive and creates blackboard 127 * entries for operating system details. 128 * @param pFile A pointer to the SOFTWARE file object. 129 * @param fileName The name of the RegRipper output file for the SOFTWARE hive. 130 */ getSoftwareInfo(TskFile * pFile,const std::string & fileName)131 void getSoftwareInfo(TskFile * pFile, const std::string& fileName) 132 { 133 std::vector<std::string> names = getRegRipperValues(fileName, "ProductName"); 134 135 TskBlackboardArtifact osart = pFile->createArtifact(TSK_OS_INFO); 136 for (size_t i = 0; i < names.size(); i++) 137 { 138 TskBlackboardAttribute attr(TSK_NAME, MODULE_NAME, "", names[i]); 139 osart.addAttribute(attr); 140 } 141 142 vector<std::string> versions = getRegRipperValues(fileName, "CSDVersion"); 143 for (size_t i = 0; i < versions.size(); i++) 144 { 145 TskBlackboardAttribute attr(TSK_VERSION, MODULE_NAME, "", versions[i]); 146 osart.addAttribute(attr); 147 } 148 } 149 150 /** 151 * Processes the RegRipper output from a SYSTEM hive and creates blackboard 152 * entries for operating system details. 153 * @param pFile A pointer to the SYSTEM file object. 154 * @param fileName The name of the RegRipper output file for the SYSTEM hive. 155 */ getSystemInfo(TskFile * pFile,const std::string & fileName)156 void getSystemInfo(TskFile * pFile, const std::string& fileName) 157 { 158 std::vector<std::string> names = getRegRipperValues(fileName, "ProcessorArchitecture"); 159 TskBlackboardArtifact osart = pFile->createArtifact(TSK_OS_INFO); 160 for (size_t i = 0; i < names.size(); i++) 161 { 162 if (names[i].compare("AMD64") == 0) 163 { 164 TskBlackboardAttribute attr(TSK_PROCESSOR_ARCHITECTURE, MODULE_NAME, "", "x86-64"); 165 osart.addAttribute(attr); 166 } 167 else 168 { 169 TskBlackboardAttribute attr(TSK_PROCESSOR_ARCHITECTURE, MODULE_NAME, "", names[i]); 170 osart.addAttribute(attr); 171 } 172 } 173 } 174 getFileNamesForHiveType(RegistryHiveType type,std::string & hiveFileName,std::string & pluginSetFileName)175 void getFileNamesForHiveType(RegistryHiveType type, std::string &hiveFileName, std::string &pluginSetFileName) 176 { 177 std::string funcName(MODULE_NAME + std::string("RegRipperModule::getFileNamesForHiveType")); 178 179 std::string pluginsPath; 180 pluginsPath = Poco::Path(ripExePath).parent().toString(); 181 pluginsPath.append("plugins"); 182 183 Poco::Path pluginSetFilePath; 184 switch (type) 185 { 186 case NTUSER: 187 hiveFileName = "NTUSER.DAT"; 188 if (!Poco::Path::find(pluginsPath, "ntuser-all", pluginSetFilePath) && 189 !Poco::Path::find(pluginsPath, "ntuser", pluginSetFilePath)) 190 { 191 throw TskException("failed to find either ntuser-all or ntuser plugin wrapper file"); 192 } 193 break; 194 195 case SYSTEM: 196 hiveFileName = "SYSTEM"; 197 if (!Poco::Path::find(pluginsPath, "system-all", pluginSetFilePath) && 198 !Poco::Path::find(pluginsPath, "system", pluginSetFilePath)) 199 { 200 throw TskException("failed to find either system-all or system plugin wrapper file"); 201 } 202 break; 203 204 case SOFTWARE: 205 hiveFileName = "SOFTWARE"; 206 if (!Poco::Path::find(pluginsPath, "software-all", pluginSetFilePath) && 207 !Poco::Path::find(pluginsPath, "software", pluginSetFilePath)) 208 { 209 throw TskException("failed to find either software-all or software plugin wrapper file"); 210 } 211 break; 212 213 case SAM: 214 hiveFileName = "SAM"; 215 if (!Poco::Path::find(pluginsPath, "sam-all", pluginSetFilePath) && 216 !Poco::Path::find(pluginsPath, "sam", pluginSetFilePath)) 217 { 218 throw TskException("failed to find either sam-all or sam plugin wrapper file"); 219 } 220 break; 221 222 default: 223 std::ostringstream msg; 224 msg << "unexpected RegistryHiveType value " << type << " in " << funcName; 225 assert(false && msg.str().c_str()); 226 throw TskException(msg.str()); 227 } 228 229 pluginSetFileName = pluginSetFilePath.getFileName(); 230 } 231 runRegRipper(RegistryHiveType type)232 void runRegRipper(RegistryHiveType type) 233 { 234 std::string funcName(MODULE_NAME + std::string("RegRipperModule::runRegRipper")); 235 236 // Get the hive name and plugin set file names. 237 std::string hiveFileName; 238 std::string pluginSetFileName; 239 getFileNamesForHiveType(type, hiveFileName, pluginSetFileName); 240 241 TskFileManager& fileManager = TskServices::Instance().getFileManager(); 242 243 // Get a list corresponding to the files 244 TskFileManager::AutoFilePtrList files(fileManager.findFilesByName(hiveFileName, TSK_FS_META_TYPE_REG)); 245 246 // Iterate over the files running RegRipper on each one. 247 for (TskFileManager::FilePtrList::iterator file = files.begin(); file != files.end(); ++file) 248 { 249 // Skip empty files 250 if ((*file)->getSize() == 0) 251 { 252 continue; 253 } 254 255 // Save the file content so that we can run RegRipper against it 256 fileManager.saveFile(*file); 257 258 // Create a file stream for the RegRipper output. 259 Poco::Path outputFilePath = Poco::Path::forDirectory(outputFolderPath); 260 std::ostringstream fileName; 261 if ((*file)->getParentFileId() == VOLUME_SHADOW_SNAPSHOT_FILE_PARENT_ID) 262 { 263 Poco::Path filePath((*file)->getFullPath()); 264 fileName << filePath.directory(0) << "_"; 265 } 266 fileName << (*file)->getName() << "_" << (*file)->getHash(TskImgDB::MD5) << "_" << (*file)->getId() << ".txt"; 267 outputFilePath.setFileName(fileName.str()); 268 269 // Log what's happening. 270 std::ostringstream msg; 271 msg << funcName << " : ripping " << (*file)->getName() << " to " << outputFilePath.toString(); 272 LOGINFO(msg.str()); 273 274 // Run RegRipper. 275 Poco::Process::Args cmdArgs; 276 277 // Insert interpreter arguments, if any 278 for (std::vector<std::string>::iterator it = interpreterArgs.begin(); it != interpreterArgs.end(); ++it) { 279 cmdArgs.push_back(*it); 280 } 281 282 cmdArgs.push_back("-f"); 283 cmdArgs.push_back(pluginSetFileName); 284 cmdArgs.push_back("-r"); 285 cmdArgs.push_back((*file)->getPath()); 286 Poco::Pipe outPipe; 287 Poco::ProcessHandle handle = Poco::Process::launch(ripExePath, cmdArgs, NULL, &outPipe, &outPipe); 288 289 // Capture the RegRipper output. 290 Poco::PipeInputStream istr(outPipe); 291 Poco::FileOutputStream ostr(outputFilePath.toString()); 292 while (istr) 293 { 294 Poco::StreamCopier::copyStream(istr, ostr); 295 } 296 ostr.close(); 297 298 if (Poco::Process::wait(handle) == 0) 299 { 300 // If Regripper runs without error, parse selected artifacts from the raw output and post them to the blackboard. 301 if (type == SOFTWARE) 302 { 303 getSoftwareInfo(*file, outputFilePath.toString()); 304 } 305 else if (type == SYSTEM) 306 { 307 getSystemInfo(*file, outputFilePath.toString()); 308 } 309 } 310 else 311 { 312 // If RegRipper fails on a particular file, log a warning and move on to the next file. 313 std::stringstream msg; 314 msg << funcName << " : RegRipper returned error code for " << (*file)->getName() << " (file id = " << (*file)->getId() << ")"; 315 LOGWARN(msg.str()); 316 } 317 } 318 } 319 parseOption(const std::string & option,std::string & arg)320 void parseOption(const std::string &option, std::string &arg) 321 { 322 if (!arg.empty()) 323 { 324 std::ostringstream msg; 325 msg << "module command line has multiple " << option << " options"; 326 throw TskException(msg.str()); 327 } 328 329 arg = option.substr(3); 330 if (arg.empty()) 331 { 332 std::ostringstream msg; 333 msg << "module command line missing argument for " << option << " option"; 334 throw TskException(msg.str()); 335 } 336 337 TskUtilities::stripQuotes(arg); 338 } 339 parseModuleCommandLine(const char * arguments)340 void parseModuleCommandLine(const char *arguments) 341 { 342 ripExePath.clear(); 343 outputFolderPath.clear(); 344 345 Poco::StringTokenizer tokenizer(std::string(arguments), ";"); 346 for (Poco::StringTokenizer::Iterator token = tokenizer.begin(); token != tokenizer.end(); ++token) 347 { 348 if ((*token).find("-e") == 0) 349 { 350 parseOption(*token, ripExePath); 351 } 352 else if ((*token).find("-o") == 0) 353 { 354 parseOption(*token, outputFolderPath); 355 } 356 else 357 { 358 std::ostringstream msg; 359 msg << "module command line " << *token << " option not recognized"; 360 throw TskException(msg.str()); 361 } 362 } 363 364 if (ripExePath.empty()) 365 { 366 Poco::Path defaultPath = Poco::Path::forDirectory(GetSystemProperty(TskSystemProperties::PROG_DIR)); 367 defaultPath.pushDirectory("RegRipper"); 368 defaultPath.setFileName("rip.exe"); 369 ripExePath = defaultPath.toString(); 370 } 371 else 372 { 373 // Check to see if we have been asked to run RegRipper through 374 // the perl interpreter. 375 std::string perl = "perl"; 376 377 if (ripExePath.substr(0, perl.size()) == perl) 378 { 379 /* We have been asked to run the perl interpreter format (e.g. "perl /foobar/rip.pl"). 380 Assumptions: 381 - The last token is the script path 382 - Any other script arguments are space delimited 383 - There are no nested quotes 384 */ 385 Poco::StringTokenizer tokenizer(ripExePath, " "); 386 if (tokenizer.count() > 1) 387 { 388 ripExePath = *tokenizer.begin(); // The interpreter exe path 389 std::string ripdotplPath = tokenizer[tokenizer.count()-1]; // RegRipper script path 390 391 // Our plugin path will be relative to where rip.pl lives 392 pluginPath = Poco::Path(ripdotplPath).parent().toString(); 393 394 // Get interpreter arguments, if any 395 Poco::StringTokenizer::Iterator it = tokenizer.begin(); 396 interpreterArgs = std::vector<std::string>(++it, tokenizer.end()); 397 } 398 399 } 400 else 401 { 402 // Not perl so the plugin path is relative to the RegRipper executable 403 pluginPath = Poco::Path(ripExePath).parent().toString(); 404 } 405 } 406 407 if (outputFolderPath.empty()) 408 { 409 std::string moduleOutDir = GetSystemProperty(TskSystemProperties::MODULE_OUT_DIR); 410 if (moduleOutDir.empty()) 411 { 412 throw TskException("output folder not specified in module command line and MODULE_OUT_DIR is system property not set"); 413 } 414 415 Poco::Path defaultPath(Poco::Path::forDirectory(moduleOutDir)); 416 defaultPath.pushDirectory(MODULE_NAME); 417 outputFolderPath = defaultPath.toString(); 418 } 419 } 420 } 421 422 extern "C" 423 { 424 /** 425 * Module identification function. 426 * 427 * @return The name of the module. 428 */ name()429 TSK_MODULE_EXPORT const char *name() 430 { 431 return MODULE_NAME; 432 } 433 434 /** 435 * Module identification function. 436 * 437 * @return A description of the module. 438 */ description()439 TSK_MODULE_EXPORT const char *description() 440 { 441 return MODULE_DESCRIPTION; 442 } 443 444 /** 445 * Module identification function. 446 * 447 * @return The version of the module. 448 */ version()449 TSK_MODULE_EXPORT const char *version() 450 { 451 return MODULE_VERSION; 452 } 453 454 /** 455 * Module initialization function. Receives a string of intialization arguments, 456 * typically read by the caller from a pipeline configuration file. 457 * Returns TskModule::OK or TskModule::FAIL. Returning TskModule::FAIL indicates 458 * the module is not in an operational state. 459 * 460 * @param args An optional semicolon separated list of arguments: 461 * -e Path to the RegRipper executable 462 * -o Directory in which to place RegRipper output 463 * @return TskModule::OK if initialization succeeded, otherwise TskModule::FAIL. 464 */ initialize(const char * arguments)465 TskModule::Status TSK_MODULE_EXPORT initialize(const char* arguments) 466 { 467 const std::string funcName(MODULE_NAME + std::string("::initialize")); 468 try 469 { 470 parseModuleCommandLine(arguments); 471 472 // Log the configuration of the module. 473 std::ostringstream msg; 474 msg << funcName << " : using RegRipper executable '" << ripExePath << "'"; 475 LOGINFO(msg.str()); 476 477 msg.str(""); 478 msg.clear(); 479 msg << funcName << " : writing output to '" + outputFolderPath << "'"; 480 LOGINFO(msg.str()); 481 482 // Verify the RegRipper executable path. 483 Poco::File ripExe(ripExePath); 484 if (!ripExe.exists() || !ripExe.canExecute()) 485 { 486 // Try to find it in a dir in the path environment variable 487 std::string newpath = checkExeEnvPath(ripExePath); 488 489 if (!newpath.empty()) 490 { 491 ripExePath = newpath; 492 } 493 else 494 { 495 std::ostringstream msg; 496 msg << "'" << ripExePath << "' does not exist or is not executable"; 497 throw TskException(msg.str()); 498 } 499 } 500 501 // Create the output folder. 502 Poco::File(outputFolderPath).createDirectories(); 503 504 return TskModule::OK; 505 } 506 catch (TskException &ex) 507 { 508 std::ostringstream msg; 509 msg << funcName << ": TskException : " << ex.message(); 510 LOGERROR(msg.str()); 511 return TskModule::FAIL; 512 } 513 catch (Poco::Exception &ex) 514 { 515 std::ostringstream msg; 516 msg << funcName << ": Poco::Exception : " << ex.displayText(); 517 LOGERROR(msg.str()); 518 return TskModule::FAIL; 519 } 520 catch (std::exception &ex) 521 { 522 std::ostringstream msg; 523 msg << funcName << ": std::exception : " << ex.what(); 524 LOGERROR(msg.str()); 525 return TskModule::FAIL; 526 } 527 catch (...) 528 { 529 LOGERROR(funcName + ": unrecognized exception"); 530 return TskModule::FAIL; 531 } 532 } 533 534 /** 535 * Module execution function. Returns TskModule::OK, TskModule::FAIL, or TskModule::STOP. 536 * Returning TskModule::FAIL indicates error performing its job. Returning TskModule::STOP 537 * is a request to terminate execution of the reporting pipeline. 538 * 539 * @returns TskModule::OK on success, TskModule::FAIL on error, or TskModule::STOP. 540 */ report()541 TskModule::Status TSK_MODULE_EXPORT report() 542 { 543 std::string funcName(MODULE_NAME + std::string("report")); 544 try 545 { 546 runRegRipper(NTUSER); 547 runRegRipper(SYSTEM); 548 runRegRipper(SAM); 549 runRegRipper(SOFTWARE); 550 551 return TskModule::OK; 552 } 553 catch (TskException &ex) 554 { 555 std::ostringstream msg; 556 msg << funcName << ": TskException : " << ex.message(); 557 LOGERROR(msg.str()); 558 return TskModule::FAIL; 559 } 560 catch (Poco::Exception &ex) 561 { 562 std::ostringstream msg; 563 msg << funcName << ": Poco::Exception : " << ex.displayText(); 564 LOGERROR(msg.str()); 565 return TskModule::FAIL; 566 } 567 catch (std::exception &ex) 568 { 569 std::ostringstream msg; 570 msg << funcName << ": std::exception : " << ex.what(); 571 LOGERROR(msg.str()); 572 return TskModule::FAIL; 573 } 574 catch (...) 575 { 576 LOGERROR(funcName + ": unrecognized exception"); 577 return TskModule::FAIL; 578 } 579 } 580 581 /** 582 * Module cleanup function. Deletes output directory if it is empty. 583 * 584 * @returns TskModule::OK on success and TskModule::FAIL on error. 585 */ finalize()586 TskModule::Status TSK_MODULE_EXPORT finalize() 587 { 588 std::string funcName(MODULE_NAME + std::string("report")); 589 try 590 { 591 #if !defined(_DEBUG) 592 593 Poco::File folder(outputFolderPath); 594 std::vector<std::string> files; 595 folder.list(files); 596 597 if (files.empty()) 598 { 599 folder.remove(false); 600 } 601 602 #endif 603 604 return TskModule::OK; 605 } 606 catch (TskException &ex) 607 { 608 std::ostringstream msg; 609 msg << funcName << ": TskException : " << ex.message(); 610 LOGERROR(msg.str()); 611 return TskModule::FAIL; 612 } 613 catch (Poco::Exception &ex) 614 { 615 std::ostringstream msg; 616 msg << funcName << ": Poco::Exception : " << ex.displayText(); 617 LOGERROR(msg.str()); 618 return TskModule::FAIL; 619 } 620 catch (std::exception &ex) 621 { 622 std::ostringstream msg; 623 msg << funcName << ": std::exception : " << ex.what(); 624 LOGERROR(msg.str()); 625 return TskModule::FAIL; 626 } 627 catch (...) 628 { 629 LOGERROR(funcName + ": unrecognized exception"); 630 return TskModule::FAIL; 631 } 632 } 633 } 634