1 /*
2  * The Sleuth Kit
3  *
4  * Contact: Brian Carrier [carrier <at> sleuthkit [dot] org]
5  * Copyright (c) 2010-2012 Basis Technology Corporation. All Rights
6  * reserved.
7  *
8  * This software is distributed under the Common Public License 1.0
9  */
10 
11 #include <string>
12 #include <sstream>
13 #include <string.h>
14 
15 #include "TskAutoImpl.h"
16 #include "tsk/framework/services/TskServices.h"
17 
18 #define TSK_SCHEMA_VER 1
19 
TSKAutoImpl()20 TSKAutoImpl::TSKAutoImpl() : m_db(TskServices::Instance().getImgDB()), m_numFilesSeen(0)
21 {
22     m_curFsId = 0;
23     m_curVsId = 0;
24     m_vsSeen = false;
25     m_lastUpdateMsg = 0;
26 
27     setVolFilterFlags((TSK_VS_PART_FLAG_ENUM)(TSK_VS_PART_FLAG_ALLOC | TSK_VS_PART_FLAG_UNALLOC));
28     setFileFilterFlags((TSK_FS_DIR_WALK_FLAG_ENUM)(TSK_FS_DIR_WALK_FLAG_ALLOC|TSK_FS_DIR_WALK_FLAG_UNALLOC));
29 
30     // add the version to the DB
31     m_db.addToolInfo("Sleuth Kit", tsk_version_get_str());
32 }
33 
~TSKAutoImpl()34 TSKAutoImpl::~TSKAutoImpl()
35 {
36 }
37 
openImage(TSK_IMG_INFO * a_img_info)38 uint8_t TSKAutoImpl::openImage(TSK_IMG_INFO *a_img_info)
39 {
40     m_curFsId = 0;
41     m_curVsId = 0;
42 
43     return TskAuto::openImageHandle(a_img_info);
44 }
45 
46 void
closeImage()47  TSKAutoImpl::closeImage()
48 {
49     TskAuto::closeImage();
50 }
51 
52 
53 /**
54  * Main method to call for this class after image has been opened as it takes care of the transactions.
55  */
extractFiles()56 uint8_t TSKAutoImpl::extractFiles()
57 {
58     m_db.begin();
59     uint8_t retval = findFilesInImg();
60     commitAndSchedule();
61     return retval;
62 }
63 
64 /**
65 * Scan the image for file systems creating allocated volumes for file systems found
66 * and unallocated volumes for areas in the image that do not contain file systems.
67 * Will initially look for file system in first sect_count sectors. If a file system
68 * is found then it will continue to process the remainder of the image for other
69 * file systems.
70 *
71 * @param sect_start Start looking for file systems starting at this sector.
72 * @param sect_count The initial number of sectors to scan for file systems.
73 * @return 0 on success, 1 on failure
74 */
scanImgForFs(const uint64_t sect_start,const uint64_t sect_count)75 uint8_t TSKAutoImpl::scanImgForFs(const uint64_t sect_start, const uint64_t sect_count)
76 {
77     if (m_img_info == NULL)
78     {
79         LOGERROR(L"TSKAutoImpl::scanImgForFs - Image not open.");
80         return 1;
81     }
82 
83     LOGINFO(L"TSKAutoImpl::scanImgForFs - Starting file system scan.");
84 
85     // Initialize current offset to our starting byte location.
86     TSK_OFF_T current_offset = sect_start * m_img_info->sector_size;
87 
88     TSK_OFF_T end_offset = current_offset + (sect_count * m_img_info->sector_size);
89 
90     // Last offset keeps track of byte location where we last saw file system
91     // data. It gets initialized to our starting location.
92     TSK_OFF_T last_offset = current_offset;
93 
94     while (current_offset < end_offset)
95     {
96         TSK_FS_INFO * fs_info;
97 
98         if ((fs_info = tsk_fs_open_img(m_img_info,
99                                        current_offset,
100                                        TSK_FS_TYPE_DETECT)) == NULL)
101         {
102             // We didn't find a file system so we move on to the next sector.
103             current_offset += m_img_info->sector_size;
104         }
105         else
106         {
107             // We found a file system so we will continue to search for file
108             // systems beyond the initial sectors.
109             end_offset = m_img_info->size;
110 
111             // If there is a gap between the location of this file system and
112             // where we last saw file system data, an unallocated volume entry
113             // needs to be created for the gap.
114             if (fs_info->offset > last_offset)
115             {
116                 createDummyVolume(last_offset / m_img_info->sector_size,
117                                   (fs_info->offset - last_offset) / m_img_info->sector_size,
118                                   "Dummy volume for carving purposes",
119                                   TSK_VS_PART_FLAG_UNALLOC);
120             }
121 
122             /* The call to findFilesInFs will take care of creating a
123              * dummy volume for the file system.*/
124             /* errors encountered during this phase will have been
125              * logged. */
126             findFilesInFs(fs_info);
127 
128             // Move the current offset past the file system we just found.
129             current_offset += ((fs_info->block_count + 1) * fs_info->block_size);
130 
131             // Update the last location we saw file system data.
132             last_offset = current_offset;
133 
134             tsk_fs_close(fs_info);
135         }
136     }
137 
138     // Finally, create a dummy unallocated volume for the area between the
139     // last offset and the end of the image.
140    if (last_offset < m_img_info->size)
141     {
142         createDummyVolume(last_offset / m_img_info->sector_size,
143             (m_img_info->size - last_offset) / m_img_info->sector_size,
144             "Dummy volume for carving purposes",
145             TSK_VS_PART_FLAG_UNALLOC);
146     }
147 
148     LOGINFO(L"TSKAutoImpl::scanImgForFs - File system scan complete.");
149 
150     return 0;
151 }
152 
filterVol(const TSK_VS_PART_INFO * a_vsPart)153 TSK_FILTER_ENUM TSKAutoImpl::filterVol(const TSK_VS_PART_INFO * a_vsPart)
154 {
155     // flag that this image has a volume system
156     m_vsSeen = true;
157     m_db.addVolumeInfo(a_vsPart);
158 
159     m_curVsId = a_vsPart->addr;
160 
161     std::wstringstream msg;
162     msg << L"TSKAutoImpl::filterVol - Discovered " << a_vsPart->desc
163         << L" partition (sectors " << a_vsPart->start << L"-"
164         << ((a_vsPart->start + a_vsPart->len) - 1) << L")";
165     LOGINFO(msg.str());
166 
167     // we only want to process the allocated volumes
168     if ((a_vsPart->flags & TSK_VS_PART_FLAG_ALLOC) == 0)
169         return TSK_FILTER_SKIP;
170 
171     return TSK_FILTER_CONT;
172 }
173 
174 
filterFs(TSK_FS_INFO * a_fsInfo)175 TSK_FILTER_ENUM TSKAutoImpl::filterFs(TSK_FS_INFO * a_fsInfo)
176 {
177     // add a volume entry if there is no file system
178     if (m_vsSeen == false)
179     {
180         TSK_DADDR_T start_sect = a_fsInfo->offset / a_fsInfo->img_info->sector_size;
181         TSK_DADDR_T end_sect = start_sect +
182             ((a_fsInfo->block_count * a_fsInfo->block_size) / a_fsInfo->img_info->sector_size);
183 
184         createDummyVolume(start_sect, (end_sect - start_sect) + 1,
185                           "Dummy volume for file system",
186                           TSK_VS_PART_FLAG_ALLOC);
187     }
188 
189     m_curFsId++;
190     m_db.addFsInfo(m_curVsId, m_curFsId, a_fsInfo);
191 
192     /* Process the root directory so that its contents are added to
193      * the DB.  We won't see it during the dir_walk. */
194     TSK_FS_FILE *fs_file = tsk_fs_file_open(a_fsInfo, NULL, "/");
195     if (fs_file != NULL)
196     {
197         processFile(fs_file, "\\");
198     }
199 
200     // make sure that flags are set to get all files -- we need this to
201     // find parent directory
202     setFileFilterFlags((TSK_FS_DIR_WALK_FLAG_ENUM)
203         (TSK_FS_DIR_WALK_FLAG_ALLOC | TSK_FS_DIR_WALK_FLAG_UNALLOC));
204 
205     std::wstringstream msg;
206     msg << L"TSKAutoImpl::filterFs - Discovered " << tsk_fs_type_toname(a_fsInfo->ftype)
207         << L" file system at offset " << a_fsInfo->offset << L" with Id : " << m_curFsId;
208     LOGINFO(msg.str());
209 
210     return TSK_FILTER_CONT;
211 }
212 
213 /* Insert the file data into the file table.
214  * @returns OK on success, COR on error because of the data (and we should keep on processing more files),
215  * and ERR because of system error (and we shoudl proabably stop processing)
216  */
insertFileData(TSK_FS_FILE * a_fsFile,const TSK_FS_ATTR * a_fsAttr,const char * a_path,uint64_t & fileId)217 TSK_RETVAL_ENUM TSKAutoImpl::insertFileData(TSK_FS_FILE * a_fsFile,
218     const TSK_FS_ATTR * a_fsAttr, const char * a_path, uint64_t & fileId)
219 {
220     int type = TSK_FS_ATTR_TYPE_NOT_FOUND;
221     int idx = 0;
222     fileId = 0;
223 
224     if (a_fsFile->name == NULL) {
225         LOGERROR(L"TSKAutoImpl::insertFileData name value is NULL");
226         return TSK_COR;
227     }
228 
229     size_t attr_len = 0;
230     if (a_fsAttr) {
231         type = a_fsAttr->type;
232         idx = a_fsAttr->id;
233         if (a_fsAttr->name)
234         {
235             if ((a_fsAttr->type != TSK_FS_ATTR_TYPE_NTFS_IDXROOT) ||
236                 (strcmp(a_fsAttr->name, "$I30") != 0))
237             {
238                 attr_len = strlen(a_fsAttr->name);
239             }
240         }
241     }
242 
243     // clean up special characters in name before we insert
244     size_t len = strlen(a_fsFile->name->name);
245     char *name;
246     size_t nlen = 2 * (len + attr_len);
247     if ((name = (char *) malloc(nlen + 1)) == NULL)
248     {
249         LOGERROR(L"Error allocating memory");
250         return TSK_ERR;
251     }
252     memset(name, 0, nlen+1);
253 
254     size_t j = 0;
255     for (size_t i = 0; i < len && j < nlen; i++)
256     {
257         // ' is special in SQLite
258         if (a_fsFile->name->name[i] == '\'')
259         {
260             name[j++] = '\'';
261             name[j++] = '\'';
262         }
263         else
264         {
265             name[j++] = a_fsFile->name->name[i];
266         }
267     }
268 
269     // Add the attribute name
270     if (attr_len > 0) {
271         name[j++] = ':';
272 
273         for (unsigned i = 0; i < attr_len && j < nlen; i++) {
274             // ' is special in SQLite
275             if (a_fsAttr->name[i] == '\'')
276             {
277                 name[j++] = '\'';
278                 name[j++] = '\'';
279             }
280             else
281             {
282                 name[j++] = a_fsAttr->name[i];
283             }
284         }
285     }
286 
287     int result = m_db.addFsFileInfo(m_curFsId, a_fsFile, name, type, idx, fileId, a_path);
288     free(name);
289 
290     // Message was already logged
291     if (result) {
292         return TSK_COR;
293     }
294 
295     Scheduler::task_struct task;
296     task.task = Scheduler::FileAnalysis;
297     task.id = fileId;
298     m_filesToSchedule.push(task);
299 
300     return TSK_OK;
301 }
302 
303 
304 /* Based on the error handling design, we only return OK or STOP.  All
305  * other errors have been handled, so we don't return ERROR to TSK. */
processFile(TSK_FS_FILE * a_fsFile,const char * a_path)306 TSK_RETVAL_ENUM TSKAutoImpl::processFile(TSK_FS_FILE * a_fsFile, const char * a_path)
307 {
308     // skip the . and .. dirs
309     if (isDotDir(a_fsFile) == 1)
310     {
311         return TSK_OK;
312     }
313 
314     TSK_RETVAL_ENUM retval;
315     // process the attributes if there are more than 1
316     if (tsk_fs_file_attr_getsize(a_fsFile) == 0)
317     {
318         uint64_t fileId;
319         // If COR is returned, then keep on going.
320         if (insertFileData(a_fsFile, NULL, a_path, fileId) == TSK_ERR) {
321             retval = TSK_STOP;
322         }
323         else {
324             m_numFilesSeen++;
325             retval = TSK_OK;
326         }
327     }
328     else
329     {
330         retval = processAttributes(a_fsFile, a_path);
331     }
332 
333     time_t timeNow = time(NULL);
334     if ((timeNow - m_lastUpdateMsg) > 3600)
335     {
336         m_lastUpdateMsg = timeNow;
337         std::wstringstream msg;
338         msg << L"TSKAutoImpl::processFile : Processed " << m_numFilesSeen << " files.";
339         LOGINFO(msg.str());
340     }
341 
342     if (m_filesToSchedule.size() > m_numOfFilesToQueue) {
343         commitAndSchedule();
344         m_db.begin();
345     }
346 
347     return retval;
348 }
349 
350 /**
351  * commits the open transaction and schedules the files that
352  * were queued up as being part of that transaction.
353  * Does not create a new transaction.
354  */
commitAndSchedule()355 void TSKAutoImpl::commitAndSchedule()
356 {
357     m_db.commit();
358 
359     while (m_filesToSchedule.size() > 0) {
360         Scheduler::task_struct &task = m_filesToSchedule.front();
361         if (TskServices::Instance().getScheduler().schedule(task)) {
362             LOGERROR(L"Error adding file for scheduling");
363         }
364         m_filesToSchedule.pop();
365     }
366 }
367 
handleError()368 uint8_t TSKAutoImpl::handleError()
369 {
370     const char * tskMsg = tsk_error_get();
371 
372     // @@@ Possibly test tsk_errno to determine how the message should be logged.
373     if (tskMsg != NULL)
374     {
375         std::wstringstream msg;
376         msg << L"TskAutoImpl::handleError " << tsk_error_get();
377 
378         LOGWARN(msg.str());
379     }
380     return 0;
381 }
382 
383 
384 
385 /* Based on the error handling design, we only return OK or STOP.  All
386  * other errors have been handled, so we don't return ERROR to TSK. */
processAttribute(TSK_FS_FILE * a_fsFile,const TSK_FS_ATTR * a_fsAttr,const char * a_path)387 TSK_RETVAL_ENUM TSKAutoImpl::processAttribute(TSK_FS_FILE * a_fsFile,
388     const TSK_FS_ATTR * a_fsAttr, const char * a_path)
389 {
390     uint64_t mFileId = 0;
391 
392     // add the file metadata for the default attribute type
393     if (isDefaultType(a_fsFile, a_fsAttr))
394     {
395         // if COR is returned, then keep on going.
396         if (insertFileData(a_fsAttr->fs_file, a_fsAttr, a_path, mFileId) == TSK_ERR)
397             return TSK_STOP;
398     }
399 
400     // add the block map, if the file is non-resident
401     if (isNonResident(a_fsAttr))
402     {
403         TSK_FS_ATTR_RUN *run;
404         int count = 0;
405         for (run = a_fsAttr->nrd.run; run != NULL; run = run->next)
406         {
407             // ignore sparse blocks
408             if (run->flags & TSK_FS_ATTR_RUN_FLAG_SPARSE)
409                 continue;
410 
411             if (m_db.addFsBlockInfo(m_curFsId, mFileId, count++, run->addr, run->len))
412             {
413                 // this error should have been logged.
414                 // we'll continue to try processing the file
415             }
416         }
417     }
418 
419     return TSK_OK;
420 }
421 
createDummyVolume(const TSK_DADDR_T sect_start,const TSK_DADDR_T sect_len,const char * desc,TSK_VS_PART_FLAG_ENUM flags)422 void TSKAutoImpl::createDummyVolume(const TSK_DADDR_T sect_start, const TSK_DADDR_T sect_len,
423                                     const char * desc, TSK_VS_PART_FLAG_ENUM flags)
424 {
425     m_curVsId++;
426 
427     TSK_VS_PART_INFO part;
428     part.addr = m_curVsId;
429     part.len = sect_len;
430     part.start = sect_start;
431     part.flags = flags;
432     part.desc = (char *)desc; // remove the cast when TSK_VS_PART_INFO.desc is const char *
433 
434     if (m_db.addVolumeInfo(&part))
435     {
436         LOGERROR(L"TSKAutoImpl::createDummyVolume - Error creating volume.");
437     }
438 }
439