I recently took SANS FOR508 with Rob Lee in Las Vegas. It was a great class and I highly recommend it to everyone interested in Digital Forensics. I’m new to forensics and learned so much from the class.
One of the topics covered is using the srch_strings command from the Sleuth Kit on a filesystem image to obtain not just the strings within the file, but also the byte offset of each string. This is done using the “-t d” option:
$ srch_strings -a -t d sda1.img 7208 vmlinuz-2.2.14-5.0 7336 System.map-2.2.14-5.0smp 7464 module-info-2.2.14-5.0 262176 lost+found 262196 kernel.h 262212 System.map-2.2.14-5.0 262244 module-info-2.2.14-5.0
Then, after obtaining the block size of the filesystem using fsstat, we figure out which block each of these strings is in. For example, this is an image of a filesystem with 1024 byte blocks, so divide each byte offset by 1024:
Block String 7 vmlinuz-2.2.14-5.0 7 System.map-2.2.14-5.0smp 7 module-info-2.2.14-5.0 256 lost+found 256 kernel.h 256 System.map-2.2.14-5.0 256 module-info-2.2.14-5.0
During class, I got tired of opening the calculator to figure out these blocks, so I came up with a little one liner to do everything at once:
$ strings -a -t d sda1.img | tee file | awk '{print $1"/1024"}' | bc | paste - file 7 7208 vmlinuz-2.2.14-5.0 7 7336 System.map-2.2.14-5.0smp 7 7464 module-info-2.2.14-5.0 256 262176 lost+found 256 262196 kernel.h 256 262212 System.map-2.2.14-5.0 256 262244 module-info-2.2.14-5.0
Eventually, I got tired of typing that out and turned it into a script after getting back home after class. I emailed Rob Lee about it and he put me in touch with Hal Pomeranz, who had been working on a similar script. Hal and I had some other ideas of where this could be taken, and that’s what eventually became srch_strings_wrap.
In a previous post, I gave an overview of the command line options and functionality, so now I’d just like to show some examples.
As I said in the overview, if you just supply the same command line options as you would to srch_strings, srch_strings_wrap will give the same output:
$ srch_strings_wrap -a -t d sda1.img 7208 vmlinuz-2.2.14-5.0 7336 System.map-2.2.14-5.0smp 7464 module-info-2.2.14-5.0 262176 lost+found 262196 kernel.h 262212 System.map-2.2.14-5.0 262244 module-info-2.2.14-5.0
If you know the blocksize you can specify with the -b option or use -d and it will be determined from fsstat.
$ srch_strings_wrap -a -t d -d sda1.img OR $ srch_strings_wrap -a -t d -b 1024 sda1.img FILENAME_NF NF Metadata A 7 40 7208 vmlinuz-2.2.14-5.0 FILENAME_NF NF Metadata A 7 168 7336 System.map-2.2.14-5.0smp FILENAME_NF NF Metadata A 7 296 7464 module-info-2.2.14-5.0 / A 2 A 256 32 262176 lost+found / A 2 A 256 52 262196 kernel.h / A 2 A 256 68 262212 System.map-2.2.14-5.0 / A 2 A 256 100 262244 module-info-2.2.14-5.0
There are a few different output options. To write STDOUT to a file, use “-w file”. To suppress STDOUT, use “-N”. To print a header line, use “-H”, which for the output above would be:
FILENAME I_STATUS INODE B_STATUS BLOCK B_OFFSET BYTE OFFSET STRING
The default delimiter is the tab character, but it can be changed with “-F delim” where delim is 1 or more characters to use. Alternatively, “-C” can be used to print in CSV format, which will put quotes around the string and escape any quotes within the string.
"/","A","2","A","256","32","262176","lost+found"
The default output takes the srch_strings output and prepends the additional columns. Another option is to use “-O” which will group all the hits within a single file or inode, if it was found, or the block if not.
$ srch_strings_wrap -a -t d -d -O sda1.img IMAGE: sda1.img, PARTITION: N/A, FILE: FILENAME_NF, INODE STATUS: NF, INODE: Metadata, BLOCK: 7 BLOCK_OFFSET STRING 40 vmlinuz-2.2.14-5.0 168 System.map-2.2.14-5.0smp 296 module-info-2.2.14-5.0 IMAGE: sda1.img, PARTITION: N/A, FILE: /, INODE STATUS: A, INODE: 2 FILE_OFFSET STRING 32 lost+found 52 kernel.h 68 System.map-2.2.14-5.0
All these commands are using the default “level” of 3 which tries to go all the way from the byte offset (level 0) to the block (level 1) to the inode (level 2) to the filename (level 3). The “-l #” option can be used to specify a custom level if going all the way to the filename layer is not needed. The output will be adjusted accordingly and the command should run faster at lower levels. Note that “level 0” is essentially the same as the basic srch_strings output. Here’s an example of only going to level 1:
srch_strings_wrap -a -t d -d -l 1 -H sda1.img B_STATUS BLOCK B_OFFSET BYTE OFFSET STRING A 7 40 7208 vmlinuz-2.2.14-5.0 A 7 168 7336 System.map-2.2.14-5.0smp A 7 296 7464 module-info-2.2.14-5.0 A 256 32 262176 lost+found A 256 52 262196 kernel.h
The -A option can be used to automatically carve any matches into a folder. The default folder is in the current directory and is called ssw_output, but it can be changed with the “-D path” option.
$ srch_strings_wrap -a -t d -d -A -N sda1.img $ ls ssw_output/sda1.img/00/\[root\]/module-info-2.2.14-5.0 module-info-2.2.14-5.0smp vmlinux-2.2.14-5.0 ROOTDIR vmlinux-2.2.14-5.0smp System.map-2.2.14-5.0 vmlinuz-2.2.14-5.0
The file named ROOTDIR is the root directory on the filesystem. The directory structure within ssw_output is “image_name/partition_number/” followed by [root] for allocated files, [unallocated] for unallocated files, and [filename unknown] when the filename couldn’t be found.
All of these examples so far have assumed that the image is a partition or filesystem image. This is the default, but a full disk image can be given as well. The mmls command is used to pick the partitions and their offsets. The partition number will be prepended in the output and “00” will be used if it’s just a filesystem image.
$ srch_strings_wrap -a -t d -d -A sda.img 00 FILENAME_NF NF Metadata A 7 40 7208 vmlinuz-2.2.14-5.0 00 FILENAME_NF NF Metadata A 7 168 7336 System.map-2.2.14-5.0smp 00 FILENAME_NF NF Metadata A 7 296 7464 module-info-2.2.14-5.0 00 / A 2 A 256 32 262176 lost+found 00 / A 2 A 256 52 262196 kernel.h
Autocarving the whole image may match on many files, so srch_strings_wrap accepts “-g string” or “-G file” where string is a grep regex or file contains a list of strings to pass to “grep -f”. Case insensitivity can be specified with “-i”. The grep commands can be used with or without the “-A” option.
$ /srch_strings_wrap -a -t d -d -g f.*le sda1.img | head /System.map-2.2.14-5.0 A 13 A 269 1003 276459 c0106e91 t mtrr_file_add /System.map-2.2.14-5.0 A 13 A 270 4 276484 c0106f03 t mtrr_file_del /System.map-2.2.14-5.0 A 13 A 279 123 285819 c0112e72 t copy_files /System.map-2.2.14-5.0 A 13 A 280 896 287616 c0116ff0 T exit_files
The last option is “-P” which, rather than an image, accepts the output of a previously run srch_strings command. This would be useful if you wanted to run srch_strings on an entire image just once, then wanted to run multiple different “grep” searches on those results. The precomputed file is cat’d in via the pipeline. The “-I image” is also required where image is the image file that srch_strings was run against:
$ srch_strings_wrap -a -t d sda1.img > sda1.asc $ cat sda1.asc | srch_strings_wrap -P -I sda1.img FILENAME_NF NF Metadata A 7 40 7208 vmlinuz-2.2.14-5.0 FILENAME_NF NF Metadata A 7 168 7336 System.map-2.2.14-5.0smp FILENAME_NF NF Metadata A 7 296 7464 module-info-2.2.14-5.0 / A 2 A 256 32 262176 lost+found / A 2 A 256 52 262196 kernel.h $ cat sda1.asc | srch_strings_wrap -P -I sda1.img -g f.*le > sda1.asc.f_le $ cat sda1.asc | srch_strings_wrap -P -I sda1.img -g kernel > sda1.asc.kernel
That covers all of the current functionality. I hope people find it useful. If anyone has any suggestions for improvement or finds any bugs, let me know at dave at this domain.
Thanks for sharing this. I’ve been using my own bash script to do something similar for a few years, but I like this better. Nice work.