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