Home Security Learning Python for Forensics

Learning Python for Forensics

By Preston Miller , Chapin Bryce
books-svg-icon Book
Subscription FREE
eBook $51.99
Print + eBook $65.99
READ FOR FREE Free Trial for 7 days. $15.99 p/m after trial. Cancel Anytime! BUY NOW BUY NOW
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
READ FOR FREE Free Trial for 7 days. $15.99 p/m after trial. Cancel Anytime! BUY NOW BUY NOW
Subscription FREE
eBook $51.99
Print + eBook $65.99
What do you get with a Packt Subscription?
This book & 7000+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook + Subscription?
Download this book in EPUB and PDF formats
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with a Packt Subscription?
This book & 6500+ ebooks & video courses on 1000+ technologies
60+ curated reading lists for various learning paths
50+ new titles added every month on new and emerging tech
Early Access to eBooks as they are being written
Personalised content suggestions
Customised display settings for better reading experience
50+ new titles added every month on new and emerging tech
Playlists, Notes and Bookmarks to easily manage your learning
Mobile App with offline access
What do you get with eBook?
Download this book in EPUB and PDF formats
Access this title in our online reader
DRM FREE - Read whenever, wherever and however you want
Online reader with customised display settings for better reading experience
What do you get with video?
Download this video in MP4 format
Access this title in our online reader
DRM FREE - Watch whenever, wherever and however you want
Online reader with customised display settings for better learning experience
What do you get with Audiobook?
Download a zip folder consisting of audio files (in MP3 Format) along with supplementary PDF
  1. Free Chapter
    Now For Something Completely Different
About this book
This book will illustrate how and why you should learn Python to strengthen your analysis skills and efficiency as you creatively solve real-world problems through instruction-based tutorials. The tutorials use an interactive design, giving you experience of the development process so you gain a better understanding of what it means to be a forensic developer. Each chapter walks you through a forensic artifact and one or more methods to analyze the evidence. It also provides reasons why one method may be advantageous over another. We cover common digital forensics and incident response scenarios, with scripts that can be used to tackle case work in the field. Using built-in and community-sourced libraries, you will improve your problem solving skills with the addition of the Python scripting language. In addition, we provide resources for further exploration of each script so you can understand what further purposes Python can serve. With this knowledge, you can rapidly develop and deploy solutions to identify critical information and fine-tune your skill set as an examiner.
Publication date:
May 2016


Chapter 1. Now For Something Completely Different

This book presents Python as a necessary tool to optimize digital forensic analysis—it is written from an examiner's perspective. In the first two chapters, we will introduce the basics of Python in preparation for the remainder of the book where we will develop scripts to accomplish forensic tasks. While focusing on the use of the language as a tool, we will also explore the advantages of Python and how it allows many individuals in the field to create solutions for a number of complex forensic challenges. Similar to Monty Python, Python's namesake, the next 12 chapters aim to present "something completely different".

In this fast-paced field, a scripting language provides flexible problem solving in an automated fashion, thus giving the examiner additional time to investigate other artifacts that may not have been analyzed as thoroughly due to time constraints. Python may not always be the correct tool to complete the task at hand, but it is certainly a resource to develop rapid and accurate solutions. This chapter outlines the basics of Python from "Hello World" to fundamental scripting operations.

In this chapter, we will cover the following topics:

  • An introduction to Python and healthy development practices

  • Basic programming concepts

  • Manipulating and storing objects in Python

  • Creating simple conditionals, loops, and functions


When to use Python?

Python is a powerful forensic tool, but before deciding to develop a script it is important to consider the type of analysis required and the project timeline. In the following examples, we will outline situations when Python is invaluable and, conversely, when it is not worth the development effort. Though rapid development makes it easy to deploy a solution in a tough situation, Python is not always the best tool to implement it. If a tool exists that performs the task, and is available, it can be the preferred method for analysis.

Python is a preferred programming language for forensics due to its ease of use, library support, detailed documentation, and interoperability among operating systems. There are two main types of programming languages, those that are interpreted and those that are compiled. Compiling the code allows the programming language to be converted into a machine language. This lower level language is more efficient for the computer to interpret. Interpreted languages are not as fast as the compiled languages at run time and they do not require compilation, which can take some time. As Python is an interpreted language, we can make modifications to our code and quickly run and view the results. With a compiled language, we would have to wait for our code to re-compile before viewing the effect of our modifications. For this reason, Python may not run as quickly as a compiled language; however, it allows rapid prototyping.

An incident response case presents an excellent example of when to use Python in a case setting. For example, let us consider that a client calls, panicked, reporting a data breach and unsure of how many files were exfiltrated over the past 24 hours from their file server. Once on site, you are instructed to perform the fastest count of files accessed in the past 24 hours. This count, along with a list of compromised files, will determine the course of action.

Python fits this bill quite nicely. Armed with just a laptop, you can open a text editor and begin writing a code solution. Python can be built and designed without the need of a fancy editor or tool set. The build process of your script may look similar to this, with each step building upon the previous:

  1. Make the script read a single file's last accessed time stamp.

  2. Write a loop that steps through directories and subdirectories.

  3. Test each file to see if the timestamp is within the past 24 hours.

  4. If it has been accessed within 24 hours then create a list of affected files to display file paths and access times.

The preceding process would result in a script that recurses over the entire server and the output files found with a last accessed time in the past 24 hours for manual review. This script will be approximately 20 lines of code and might require ten minutes, or less, for an intermediate scripter to develop and validate (it is apparent that this will be more efficient than manually reviewing the timestamps on the filesystem).

Before deploying any developed code, it is imperative that you validate its capability first. As Python is not a compiled language, we can easily run the script after adding new lines of code to ensure that we haven't broken anything. This approach is known as test-then-code, a method commonly used in script development. Any software, regardless of who wrote it, should be scrutinized and evaluated to ensure accuracy and precision. Validation ensures that the code is operating properly. Although it is more time consuming, validated code provides reliable results capable of withstanding the courtroom, which is an important aspect in forensics.

A situation where Python might not be the best tool is for general case analysis. If you are handed a hard drive and asked to find evidence without additional insight, then a preexisting tool will be a better solution. Python is invaluable for targeted solutions, such as analyzing a given file type and creating a metadata report. Developing a custom all-in-one solution for a given filesystem requires too much time to create, when other tools, both paid and free, support such generic analysis.

Python is useful in pre-processing automation. If you find yourself repeating the same task for each evidence item, it may be worthwhile to develop a system that automates those steps. A great example of suites that perform such analysis is ManTech's Analysis and Triage System (MantaRay[1]), which leverages a series of tools to create general reports that can speed up analysis when there is no scope of what data may exist.

When considering whether to commit resources to develop Python scripts, either on the fly or for larger projects, it is important to consider what solutions already exist, the time available to create a solution, and the time saved through automation. Despite the best intentions, the development of solutions can go on for much longer than initially conceived without a strong design plan.

Development life cycle

The development life cycle involves at least five steps:

  • Identify

  • Plan

  • Program

  • Validate

  • Bugs

The first step is self-explanatory: before you develop, you must identify the problem that needs to be solved. Planning is perhaps the most crucial step in the development cycle.

Good planning will help you by decreasing the amount of code required and the number of bugs. Planning becomes even more vital during the learning process. A Forensic programmer must begin to answer the following questions: how will data be ingested, what Python data types are most appropriate, are third party libraries necessary, and how will the results be displayed to the examiner? In the beginning, just as we were writing a term paper, it is a good idea to write, or draw, an outline of your program. As you become more proficient in Python, planning will become a second nature, but initially it is recommended that you create an outline or write a pseudocode.

A pseudocode is an informal way of writing code before filling in the details with actual code. Pseudocode can represent the barebones of the program, such as defining pertinent variables and functions while describing how they will all fit together within the script's framework. Pseudocode for a function might look like the following example:

# open the database
# read from the database using the sqlite3 library – store in variable called records
    for record in records:
        # process database records here

After identifying and planning, the next three steps make up the largest part of the development cycle. Once your program is sufficiently planned, it is time to start writing the code! Once the code is written, break into your new program with as much test data as possible. Especially in forensics, it is critical to thoroughly test your code instead of relying on the results of one example. Without comprehensive debugging, the code can crash when it encounters something unexpected, or, even worse, it could provide the examiner with false information and lead them down the wrong path. After the code is tested, it is time to release it and prepare for bug reports. We are not just talking about insects! Despite a programmer's best efforts, there will always be bugs in the code. Bugs have a nasty way of multiplying even when you squash one, perpetually causing the programming cycle to begin repeatedly.


Getting started

Before we get started, it is necessary that you install Python on your machine. Please refer to Appendix A, Installing Python for instructions. Additionally, we recommend using an Integrated Development Environment, IDE, such as JetBrain's PyCharm. An IDE will highlight errors and offer suggestions that help streamline the development process and promote best practices when writing a code. If the installation of an IDE is not available, a simple text editor will work. We recommend an application such as Notepad++, Sublime Text, or Atom Text Editor. For those who are command line orientated, an editor such as Vim or Nano will work as well.

With Python installed, let's open the interactive prompt by typing python into your Command Prompt or terminal. We will begin by introducing some built-in functions to be used in troubleshooting. The first line of defense when confused by any object or function discussed in this book, or found in the wild, are the type(), dir(), and help() built-in functions. We realized that we have not yet introduced the common data types and so the following code might appear confusing. However, that is exactly the point of this exercise. During development, you will encounter data types you are unfamiliar with or what methods exist to interact with the object. These three functions help solve those issues. We will introduce the fundamental data types later in this chapter.

The type() function, when supplied with an object, will return its __name__ attribute, thus providing the type identifying information about the object. The dir() function, when supplied with a string representing the name of an object, will return its attributes showing all the available options of functions and parameters belonging to the object. The help() function can be used to display the specifics of these methods through its docstrings. Docstrings are nothing more than descriptions of a function that detail the inputs, outputs, and how to use the function.

Let's look at the str, or string, object as an example of these three functions. In the following example, passing a string of characters surrounded by single quotes to the type() function results in a type of str, or string. When we give examples where our typed input follows the >>> symbol, it indicates that you should type these statements in the Python interactive prompt. The Python interactive prompt can be accessed by typing python in the Command Prompt. Please refer to Appendix A, Installing Python if you receive an error while trying to access the interactive prompt:

>>> type('what am I?')
<type 'str'>

If we pass in an object to the dir() function, such as str, we can see its methods and attributes. Let's say that we then want to know what one of these functions, title(), does. We can use the help function to specify the object and its function as the input. The output of the help function tells us that no input is required, the output is a string object, and that the function capitalized the first character of every word. Let's use the title method on the 'what am I?' string:

>>> dir(str)
['__add__', '__class__', '__contains__', '__delattr__', '__doc__', '__eq__',
'swapcase', 'title', 'translate', 'upper', 'zfill']

>>> help(str.title)
S.title() -> string
Return a titlecased version of S, i.e. words start with uppercase characters, all remaining cased characters have lowercase.
>>> 'what am I?'.title()
'What Am I?'

Next, type number = 5; now we have created a variable, called number, that has a value of 5. Using type() on that object indicates that 5 is an int, or integer. Going through the same procedure as earlier, we can see a series of available attributes and functions for the integer object. With the help() function, we check what the __add__() function does for our number object. From the following output, we can see that this function is equivalent to using the + symbol on two values:

>>> number = 5
>>> type(number)
<type 'int'>

>>> dir(number)
>>> ['__abs__', '__add__', __and__', '__class__', '__cmp__', '__coerce__',
'denominator', 'imag', 'numerator', 'real']

>>> help(number.__add__)
x.__add__(y) <==> x+y

Let's compare the difference between the __add__() function and the + symbol to verify our assumption. Using both methods to add 3 to our number object results in a returned value of 8. Unfortunately, we've broken the best practice rule as illustrated in the following example:

>>> number.__add__(3)
>>> number + 3

Notice how some methods, such as __add__(), have double leading and trailing underscores. These are referred to as magic methods and they are the methods the Python interpreter calls and they should not be called by the programmer. These magic methods are instead called indirectly by the user. For example, the integer __add__() magic method is called when the + symbol is being used between two numbers. Following the preceding example, you should never run number.__add__(3) instead of number + 3.

Python, just like any other programming language, has a specific syntax. Compared to other common programming languages, Python is like the English language and can be read fairly easily in scripts. This feature has attracted many, including the forensics community, to use this language. Even though Python's language is easy to read, it is not to be underestimated as it is powerful and supports common programming paradigms.

Most programmers start with a simple "Hello World" script, a test that proves that they are able to execute code and print the famous message onto the console window. With Python, the code to print this statement is a single line written on the first line of a file, as shown in the following example:

001 print "Hello World!"

Please do not write the line number (001) in your script. Line numbers are for illustration purposes only and are helpful when we discuss larger code samples and need to reference a particular line. Save this line of code in a file called hello.py. To run this script we call Python and the name of the script. The message "Hello World!" should be displayed in your terminal.


Standard data types

With our first script complete, it is now time to understand the basic data types of Python. These data types are similar to those found in other programming languages, but are invoked with a simple syntax described in the following table and sections. For a full list of standard data types available in Python, visit the official documentation at http://docs.python.org/2/library/stdtypes.html.

Data type





str(), "Hello", 'Hello'


Unicode characters

unicode(), u'hello', "world".encode('utf-8')



int(), 1, 55


Decimal precision integers

float(), 1.0, .032


Boolean Values

bool(), True, False


List of elements

list(), [3, 'asd', True, 3]


Set of key:value pairs used to structure data

dict(), {'element': 'Mn', 'Atomic Number': 25, 'Atomic Mass': 54.938}


List of unique elements

set(), [3, 4, 'hello']


Organized list of elements

tuple(), (2, 'Hello World!', 55.6, ['element1'])


A file object

open('write_output.txt', 'w')

You will find that constructing most of our scripts can be accomplished using only the standard data types that Python offers. Before we take a look at one of the most common data types, strings, we will introduce comments.

Something that is always said, and can never be said enough, is to comment your code. In Python, comments are formed by a line beginning with the # symbol. When Python encounters this symbol, it skips the remainder of the line and proceeds to the next line. For comments that span multiple lines, we can use three single or double quotes to mark the beginning and end of the comments rather than using a single pound symbol for every line. The following are the examples of types of comments in a file called comments.py. When running this script, we should only see 10 printed to the console, as all comments are ignored.

001 # This is a comment
002 print 5 + 5 # This is an inline comment. Everything to the right of the # symbol does not get executed
003 """We can use three quotes to create
004 multi-line comments."""

Strings and Unicode

Strings is a data type that contains any character including alphanumeric characters, symbols, Unicode, and other codecs. With the vast amount of information that can be stored as a string, it is no surprise that they are one of the most common data types. Examples of areas where strings are found include reading arguments at the command line, user input, data from files, and outputting data. To begin with, let us look at how we can define a string in Python.

There are three ways to create a string: single quotes, double-quotes, or the built-in str() constructor method. Note, there is no difference between single and double quoted strings. Having multiple ways to create a string is advantageous, as it gives us the ability to differentiate between intentional quotes within a string. For example, in the 'I hate when people use "air-quotes"!' string, we use the single quotes to demarcate the beginning and end of the main string. The double quotes inside the string will not cause any issue with the Python interpreter. Let's verify with the type() function that both single and double quotes create the same type of object.

>>> type('Hello World!')
<type 'str'>
>>> type("Foo Bar 1234")
<type 'str'>

As we saw in the case of comments, a block string can be defined by three single or double quotes to create multi-line strings.

>>> """This is also a string"""
'This is also a string'
>>> '''it 
... can span 
... several lines'''
'it\ncan span\nseveral lines'

The \n character in the returned line signifies a line feed or a new line. The output in the interpreter displays these new line characters as \n, although when it's fed into a file or console, a new line is created. The \n is one of the most common escape characters in Python. Escape characters are denoted by a backslash followed by a specific character. Other common escape characters include \t for horizontal tabs, \r for carriage returns, \', \", and \\ for literal single quotes, double quotes, and backslashes among others. Literal characters allow us to use these characters without unintentionally using their special meaning in Python's context.

We can also use the add (+) or multiply (*) operators with strings. The add operator is used to concatenate strings together and the multiply operator will repeat the provided string values.

>>> 'Hello' + ' ' + 'World'
'Hello World'
>>> "Are we there yet? " * 3
'Are we there yet? Are we there yet? Are we there yet?'

Let's look at some common functions that we use with strings. We can remove characters from the start or end of a string using the strip() function. The strip() function requires the character that we want to remove as its input or will replace whitespace if we omit the argument. Similarly, the replace() function takes two inputs: the character to replace and what to replace it with.

# This will remove the colon (`:`) from the start and/or end of the line
>>> ':HelloWorld:'.strip(':') 

# This will remove the colon (`:`) from the line and place a space (` `) in it's place
 >>> 'Hello:World'.replace(':', ' ') 
Hello World

Using the in statement, we can check if a character or characters is in a string or not. We can also be more specific and check if a string startswith() or endswith() a specific character or characters (you know a language is easy to understand when you can create sensible sentences out of functions). These methods return True or False Boolean objects.

>>> 'a' in 'Chapter 2'
>>> 'Chapter 1'.startswith('Chapter')
>>> 'Chapter 1'.endswith('1')

We can quickly split a string into a list based on some delimiter. This can be helpful to quickly convert data separated by a delimiter into a list. For example, the CSV (comma separated values) data is separated by commas and can be split on that value.

>>> print "This string is really long! It should probably be on two lines.".split('!')
["This string is really long", " It should probably be on two lines."]

Strings can be used to capture Unicode or raw data by prepending either a u or r to the string prior to the opening quote.

>>> u'This is a unicode string'
u'This is a unicode string'
>>> r'This is a raw string, good to capture escape characters such as \ which can break strings'
r'This is a raw string, good to capture escape characters such as \ which can break strings'

Formatting parameters can be used on strings to manipulate and convert them depending on the provided values. With the .format() function, we can insert values into strings, pad numbers, and display patterns with simple formatting. This chapter will highlight a few examples of the .format() method; we will introduce its more complex features throughout this book. The .format() method replaces curly brackets with the provided values in order. This is the most basic operation for inserting values into a string dynamically.

>>> "{} {} {} {}".format("Formatted", "strings", "are", "easy!")
'Formatted strings are easy!'

Our second example displays some of the expressions that we can use to manipulate a string. Inside the curly brackets, we place a colon which indicates that we are going to specify a format for interpretation. We specify that at least 6 characters should be printed following this colon. If the supplied input is not 6 characters long, we prepend zeroes to the beginning of the input.

>>> "{:06d}".format(42)

Lastly, the d character specifies that the input will be a base 10 decimal. Our last example demonstrated how we can easily print a string of 20 equals signs by stating that our fill character is the equals symbol, followed by the caret (to center the symbols in the output), and the number of times to repeat the symbol. By providing this format string, we can quickly create visual separators in our outputs.

>>> "{:=^20}".format('')

Integers and floats

The integer is another valuable data type that is frequently used. An integer is any whole positive or negative number. The float data type is similar, but it allows us to use numbers requiring decimal level precision. With integers and floats we can use standard mathematical operations, such as: +, -, *, and /. These operations return slightly different results based on the object's type (for example, integer or float).

Integer uses whole numbers and rounding; for example, dividing two integers will result in another whole number integer. However, by using one float in the equation, even one that has the same value as the integer, will result in a float. For example, 3/2=1 and 3/2.0=1.5 in Python. The following are the examples of integer and float operations:

>>> type(1010)
<type 'int'>
>>> 127*66
>>> 66/10
>>> 10 * (10 - 8)

We can use ** to raise an integer by a power. For example, in the following section we raise 11 by the power of 2. In programming, it can be helpful to determine the numerator resulting from the division between two integers. For this, we use the modulo or the percent (%) symbol. With Python, negative numbers are those with a dash character (-) preceding the value. We can use the built-in abs() function to get the absolute value of any integer or float.

>>> 11**2
>>> 11 % 2 # 11 divided by 2 is 5.5 or 5 ½. 
>>> abs(-3)

A float is defined by any number with a decimal. Floats follow the same rules and operations as integers, with the exception of the division behavior described earlier:

>>> type(0.123)
<type 'float'>
>>> 1.23 * 5.23
>>> 27/8.0

Booleans and None

The integers 1 and 0 can also represent Boolean values in Python. These values are the Boolean True or False objects, respectively. To define a Boolean, we can use the bool() constructor statement. These data types are used extensively in program logic to evaluate statements for conditionals, as covered later in this chapter.

Another built-in data type is the null type, which is defined by the keyword None. When used, it represents an empty object, and when evaluated, it will return False. This is helpful when initializing a variable that may use several data types throughout the execution. By assigning a null value, the variable remains sanitized until reassigned:

>>> bool(0)
>>> bool(1)
>>> None

Structured data types

There are several data types that are more complex and allow us to create structures of raw data. These include lists, dictionaries, sets, and tuples. Most of these structures are comprised of previously mentioned data types. These structures are very useful in creating powerful units of values, thus allowing raw data to be stored in a manageable manner.


Lists are a series of ordered elements. A list supports any data type as an element and will maintain the order of data as they are appended to the list. Elements can be called by position or a loop can be used to step through each item. In Python, unlike other languages, printing a list takes one line. In languages such as Java or C++ it can take three or more lines to print a list. Lists in Python can be as long as needed and can expand or contract on the fly, another feature uncommon in other languages.

We can create lists using brackets with elements separated by a comma, or we can use the list() class constructor with any iterable object. List elements can be accessed by index, where 0 is the first element. To access an element by position, we place the desired index in brackets following the list object. Rather than knowing how long a list is (which can be accomplished with the len() function) we can use negative index numbers to access the last elements in a list.

>>> type(['element1', 2, 6.0, True, None, 234])
<type 'list'>
>>> list('element')
 ['e', 'l', 'e', 'm', 'e', 'n', 't']
>>> len([0,1,2,3,4,5,6])
>>> ['hello_world', 'foo bar'][0]
>>> ['hello_world', 'foo_bar'][-1]

We can add, remove, or check if a value is in a list using a couple of different functions. First, let's create a list of animals using brackets and assigning it to the variable my_list. Variables are aliases referring to Python objects. We will discuss variables in much greater detail later in this chapter. The append() method adds data to the end of the list which we can verify by printing said list afterwards. Alternatively, the insert() method allows us to specify an index when adding data to the list. For example, we can add the string "mouse" to the beginning, or the zeroth index, of our list.

>>> my_list = ['cat', 'dog']
>>> my_list.append('fish')
>>> print my_list
['cat', 'dog', 'fish']
>>> my_list.insert(0, 'mouse')
>>> print my_list
['mouse', 'cat', 'dog', 'fish']

The pop() and remove() functions delete data from a list either by index or by a specific object, respectively. If an index is not supplied with the pop() function, the last element in the list is popped. This returns the last element in the list to the interactive prompt. We can then print the list to verify that the last element was indeed popped. Note that the remove() function gets rid of the first instance of the supplied object in the list and does not return the item removed to the interactive prompt.

>>> your_list = [0, 1, 2]
>>> your_list.pop()
>>> print your_list
[0, 1]
>>> our_list = [3, 4, 5]
>>> our_list.pop(1) 
>>> print our_list
[3, 5]
>>> everyones_list = [1, 1, 2 ,3]
>>> everyones_list.remove(1)
>>> print everyones_list
[1, 2, 3]

We can use the in statement to check if some objects are in the list. The count() function tells us how many instances of an object are there in the list.

>>> 'cat' in ['mountain lion', 'ox', 'cat']
>>> ['fish', 920.5, 3, 5, 3].count(3)

If we want to access a subset of elements, we can use a list slice notation. Other objects, such as strings, also support this same slice notation to obtain a subset of data. Slice notation has the following format, where "a" is our list or string object:


In the preceding example, X represents the start of the slice, Y represents the end of the slice, and Z represents the step of the slice. Note that each segment is separated by colons and enclosed in square brackets. A negative step is a quick way to reverse the contents of an object that supports the slice notation. Each of these arguments is optional. In the first example, our slice returns the second element and up to, but not including, the fifth element in the list. Using just one of these slice elements returns a list containing everything from the second index forward or everything up to and including the fifth index.

>>> [0,1,2,3,4,5,6][2:5]
[2, 3, 4]
>>> [0,1,2,3,4,5,6][2:]
[2, 3, 4, 5, 6]
>>> [0,1,2,3,4,5,6][:5]
[0, 1, 2, 3, 4]

Using the third slice element, we can skip every other element or simply reverse the list with a negative one. We can use a combination of these slice elements to specify how to carve a subset of data from the list.

>>> [0,1,2,3,4,5,6][::2]
[0, 2, 4, 6]
>>> [0,1,2,3,4,5,6][::-1]
[6, 5, 4, 3, 2, 1, 0]


Dictionaries, otherwise known as dict, are another common Python data container. Unlike lists, this object does not add data in a linear fashion. Instead, data is stored as key and value pairs, where you can create and name keys to act as an index for stored values. It is important to note that dictionaries do not preserve the order in which items are added to them. They are used heavily in forensic scripting, as they allow us to store data in a manner that provides a known key to recall a value without needing to assign a lot of new variables. By storing data in dictionaries, it is possible to have one variable contain structured data.

We can define a dictionary using curly braces, where each key is a string and its corresponding value follows a colon. Additionally, we can use the dict() class constructor to instantiate dictionary objects. Calling a value from a dictionary is accomplished by specifying the key in brackets following the dictionary object. If we supply a key that does not exist, we will receive a KeyError (notice, we have assigned our dictionary to a variable, a). While we have not introduced variables at this point it is necessary here to highlight some of the functions specific to dictionaries.

>>> type({'Key Lime Pie': 1, 'Blueberry Pie': 2})
<type 'dict'>
>>> dict((['key_1', 'value_1'],['key_2', 'value_2']))
{'key_1': 'value_1', 'key_2': 'value_2'}
>>> a = {'key1': 123, 'key2': 456}
>>> a['key1']

We can add or modify the value of a pre-existing key in a dictionary by specifying a key and setting it equal to another object. We can remove objects using the pop() function, similar to the list pop() function to remove an item in a dictionary by specifying its key instead of an index:

>>> a['key3'] = 789
>>> print a
{'key3': 789, 'key2': 456, 'key1': 123}
>>> a.pop('key1')
>>> print a
{'key3': 789, 'key2': 456}

The keys() and values() functions return a list of keys and values present in the dictionary. We can use the items() function to return a list of tuples containing each key and value pair. These three functions are often used for conditionals and loops as shown:

>>> a.keys()
['key3', 'key2']
>>> a.values()
['789', '456']
>>> a.items()
[('key3', 789), ('key2', 456)]

Sets and tuples

Sets are similar to lists as they contain a list of elements, though they must be unique items. With this, the elements must be immutable, meaning that the value must remain constant. For this, sets are best used on integers, strings, Boolean, floats, and tuples as elements. Sets do not index the elements and therefore we cannot access the elements by their location in the set. Instead, we can access and remove elements through the use of the pop() method mentioned for the list method. Tuples are also similar to lists, though they are immutable. Built using parentheses in lieu of brackets, elements do not have to be unique and can be of any data type:

>>> type(set([1, 4, 'asd', True]))
<type 'set'>
>>> g = set(["element1", "element2"])
>>> print g
set(['element1', 'element2'])
>>> g.pop()
>>> print g

# Defining a tuple
>>> tuple('foo')
('f', 'o' , 'o')
>>> ('b', 'a', 'r')
('b', 'a', 'r')
# Calling an element from a tuple
>>> ('Chapter1', 22)[0]
>>> ('Foo', 'Bar')[-1]

Data type conversions

In some situations, the initial data type might not be the desired data type and needs to be changed while preserving its content. For example, when a user inputs arguments from the command line, the commands are commonly captured as strings and sometimes that user input needs to be, for example, an integer. We need to use the integer class constructor to convert that string object before processing the data. Imagine we have a simple script that returns the square of a user-supplied integer; we need to first convert the user-input to an integer prior to squaring the input. One of the most common ways to convert data types is to wrap the variable or string with the constructor method seen in the following example for each of the data types:

>>> int('123456') # The string 123456
123456 # Is now the integer 123456
>>> str(45) # The integer 45
'45' # Is now the string 45
>>> float('37.5') # The string 37.5
37.5 # Is now the float 37.5

Invalid conversions, for example, converting the letter 'a' to an integer will raise a ValueError. This error will state that the specified value cannot be converted to the desired type. In this case, we would want to use the built-in ord() method, which converts a character to its integer equivalent based on the ASCII value. In other scenarios, we might need to use other methods to convert data types. The following is a table of common built-in data type conversion methods that we can utilize for most scenarios.



str(), int(), float(), dict(), list(), set(), tuple()

Class constructor methods

hex(), oct()

Converts an integer to base 16 (hex) or base 8 (octal) representation

chr(), unichr()

Converts an integer to an ASCII or Unicode character


Converts a character to an integer



We often create file objects to read or write data from a file. File objects can be created using the built-in open() method. The open() function takes two arguments, the name of the file and the mode. These modes dictate how we can interact with the file object. The mode argument is optional and, if left blank, defaults to read only. The following table illustrates the different file modes available for use:

File mode



Opens the file for read-only mode (default).


Creates the file, or overwrites it if it exists, for writing.


Creates a file if it doesn't exist for writing. If the file does exist, the file pointer is placed at the end of the file to append writes to the file.

rb, wb, or ab

Opens the file for reading or writing in "binary" mode.

r+, rb+, w+, wb+, a+, or ab+

Opens the file for reading and writing in either standard or "binary" mode. If the file does not exist, the w or a mode creates the file.

Most often, we will use read and write in standard or binary mode. Let's take a look at a few examples and some of the common functions that we might use. For this section, we will create a text file called files.txt with the following contents:

This is a simple test for file manipulation.
We will often find ourselves interacting with file objects.
It pays to get comfortable with these objects.

Create this file using a text editor of your choice and save it in your current working directory. In the following example, we open a file object that exists, files.txt, and assign it to variable in_file. Since we do not supply a file mode, it is opened in read-only mode by default. We can use the read() method to read all lines as a continuous string. The readline() method can be used to read individual lines as a string. Alternatively, the readlines() method creates a string for each line and stores it in a list. These functions take an optional argument specifying the size of bytes to read.

Python keeps track of where we currently are in the file. To illustrate the examples described, we need to create a new in_file object after each operation. For example, if we did not do this and used the read() method and then tried any of the other read methods discussed we would receive an empty list. This is because the file pointer would be at the end of the file, as a result of the read() function:

>>> in_file = open('files.txt')
>>> print in_file.read()
This is a simple test for file manipulation.
We will often find ourselves interacting with file objects.
It pays to get comfortable with these objects.
>>> in_file = open('files.txt')
>>> print in_file.readline()
This is a simple test for file manipulation.
>>> in_file = open('files.txt')
>>> print in_file.readlines()
['This is a simple test for file manipulation.\n', 'We will often find ourselves interacting with file objects.\n', 'It pays to get comfortable with these objects.']

In a similar fashion, we can create or open and overwrite an existing file using the w file mode. We can use the write() function to write an individual string or the writelines() method to write any iterable object to the file. The writelines() function essentially calls the write() method for each element of the iterable object. For example, this is tantamount to calling write() on each element of a list.

>>> out_file = open('output.txt', 'w')
>>> out_file.write('Hello output!')
>>> data = ['falken', 124, 'joshua']
>>> out_file.writelines(data)

Python does a great job of closing connections to a file object automatically. However, best practice dictates that we should use the flush() and close() methods after we finish writing data to a file. The flush() method writes any data remaining in a buffer to the file and the close() function closes our connection to the file object.

>>> out_file.flush()
>>> out_file.close()


We can assign values to variables using the data types we just covered. By assigning values to variables we can refer to that value, which could be a large 100 element list, by its variable name. This not only saves the programmer from re-typing the value over and over again, but also helps enhance the readability of the code and allows us to change the values of a variable over time. Throughout the chapter, we have already assigned objects to variables using the "=" sign. Variable names can technically be anything, although we recommend the following guidelines:

  • Variable names should be short and descriptive of the stored content or purpose.

  • Begin with a letter or underscore.

  • Constant variables should be denoted by capitalized words.

  • Dynamic variables should be lowercase words separated by underscores.

  • Never be one of the following or any Python reserved name: input, output, tmp, temp, in, for, next, file, True, False, None, str, int, list, and so on.

  • Never include a space in a variable name. Python thinks two variables are being defined and will raise a syntax error. Use underscores to separate words.

Generally, programmers use memorable and descriptive names that indicate the data they hold. For example, in a script that prompts for the phone number of the user, the variable should be phone_number, which clearly indicates the purpose and contents of this variable. Another popular naming style is CamelCase where every word is capitalized. This naming convention is often used in conjunction with function and class names.

Variable assignment allows the value to be modified as the script runs. The general rule of thumb is to assign a value to a variable if it will be used again. Let's practice by creating variables and assigning them data types that we have just learned about. While this is simple, we recommend following along in the interactive prompt to get into the habit of assigning variables. In the following first example, we assign a string to a variable before printing the variable.

>>> hello_world = "Hello World!"
>>> print hello_world
Hello World!

The second example introduces some new operators. First, we assign the integer 5 to the variable our_number. Then we use the plus-gets (+=) as a built-in shorthand for our_number = our_number + 20. In addition to plus-gets, we have minus-gets (-=), multiply-gets (*=), and divide-gets (/=).

>>> our_number = 5
>>> our_number += 20
>>> print our_number

In the following code block we assign a series of variables before printing them. The data types used for our variables are string, integer, float, unicode, and Boolean, respectively.

>>> BOOK_TITLE = 'Learning Python for Forensics'
>>> edition = 1
>>> python_version = 2.7
>>> AUTHOR_NAMES = u'Preston Miller, Chapin Bryce'
>>> is_written_in_english = True
>>> print BOOK_TITLE
Learning Python for Forensics
>>> print AUTHOR_NAMES
Preston Miller, Chapin Bryce
>>> print edition
>>> print python_version
>>> print is_written_in_english

Notice the BOOK_TITLE and AUTHOR_NAMES variables. When a variable is static, or non-changing, throughout the execution of a script, it is referred to as a constant variable. Unlike other programming languages, there is not a built-in method for protecting constants from being overwritten, so we use naming conventions to assist in reminding us not to replace the value. While some variables, such as the edition of the book, language, or version of Python might change, the title and authors should be constants (we hope). If there is ever confusion when it comes to naming and styling conventions in Python try running the following statement in an interpreter.

>>>import this 

As we saw previously, we can use the split() method on a string to convert it into a list. We can also convert a list into a string using the join method. This method follows a string containing the desired common denominator and the list as its only argument. In the following example, we take a list containing two strings and join them into one string where the elements are separated by a comma.

>>> print ', '.join(["This string is really long", " It should probably be on two lines."])
This string is really long, It should probably be on two lines.

Understanding scripting flow logic

Flow control logic allows us to create dynamic logic by specifying different routes of program execution based upon a series of circumstances. In any script worth its salt, some manner of flow control needs to be introduced. For example, flow logic would be required to create a dynamic script that returns different results based on options selected by the user. Unaccounted for possibilities are a common cause for error, and are another reason to test your code thoroughly. Creating dynamic code is important in Forensics as we sometimes encounter fragments of data, such as within slack or unallocated space, whose incomplete nature may interfere or cause errors in our scripts. In Python, there are two basic sets of flow logic: conditionals and loops.

Flow operators are frequently accompanied with flow logic. These operators can be strung together to create more complicated logic. The table below represents a "truth table" and illustrates the value of various flow operators based on the "A" or "B" variable Boolean state.



A and B

A or B

not A

not B

























The logical AND and OR operators are the third and fourth columns in the table. Both A and B must be True for the AND operator to return True. Only one of the variables need to be True for the OR operator to be True. The not operator simply switches the Boolean value of the variable to its opposite (for example, True becomes False and vice versa).

Mastering conditionals and loops will take our scripts to another level. At its core, flow logic relies on only two values, True or False. As noted earlier, in Python these are represented by the Boolean True and False data types.


When a script hits a conditional, it's much like standing at a fork in the road. Depending on some factor, say a more promising horizon, you may decide to go East over West. Computer logic is less arbitrary: if something is true the script proceeds one way, if it is false then it will go another. These junctions are critical, if the program decides to go off the path we've developed for it, we'll be in serious trouble. There are three statements used to form a conditional block: if, elif, and else.

The conditional block refers to the conditional statements, their flow logic, and code. A conditional block starts with an if statement followed by flow logic, a colon, and indented line(s) of code. If the flow logic evaluates to True, then the indented code below the if statement will be executed. If it does not evaluate to True the Python Virtual Machine (PVM) will skip those lines of code and go to the next line on the same level of indentation as the if statement. This is usually a corresponding elif (else-if) or else statement.

Indentation is very important in Python. It is used to demarcate code to be executed within a conditional statement or loop. A standard of 4 spaces for indentation is used in this book, though you may encounter code that uses a 2 space indentation or uses tab characters. While all three of these practices are allowed in Python, 4 spaces are preferred and easier to read.

In a conditional block, once one of the statements evaluates to True, the code is executed and the PVM exits the block without evaluating the other statements. Please review Appendix B, Python Technical Details for a description on the Python Virtual Machine.

# Conditional Block Pseudocode
if [logic]:
    # Line(s) of indented code to execute if logic evaluates to True.
elif [logic]:
    # Line(s) of indented code to execute if the 'if' statement is false and this logic is True.
    # Line(s) of code to catch all other possibilities if the if and elif(s) statements are all False.

Until we define functions, we will stick to simple if statement examples.

>>> a = 5
>>> b = 22
>>> a > 0
>>> a > b
>>> if a > 0:
...     print str(a) + ' is greater than zero!'
5 is greater than zero!
>>> if a > b:
...     print str(a) + ' beats ' + str(b)

Notice how when the flow logic evaluates to True then the code indented below the if statement is executed. When it evaluates to False the code is skipped. Typically, when the if statement is False you will have a secondary statement, such as an elif or else to catch other possibilities, such as when "a" is less than or equal to "b". However, it is important to note that we can just use an if statement without any elif or else statements.

The difference between if and elif is subtle. We can only functionally notice a difference when we use multiple if statements. The elif statement allows for a second condition to be evaluated in the case that the first isn't successful. A second if statement will be evaluated regardless of the outcome of the first if statement.

The else statement does not require any flow logic and can be treated as a catch-all case for any remaining or unaccounted for case. This does not mean, however, errors will not occur when the code in the else statement is executed. Do not rely on else statements to handle errors.

Conditional statements can be made more comprehensive by using the logical and or or operators. These allow for more complex logic in a single conditional statement.

>>> a = 5
>>> b = 22
>>> if a > 4 and a < b:
...     print 'Both statements must be true to print this'
Both statements must be true to print this
>>> if a > 10 or a < b:
...     print 'One of these statements must be true to print this'
One of these statements must be true to print this

The following table can be helpful to understand how common operators work.





<, >

less than, greater than

8 < 3


<=, >=

less than equal or to, greater than or equal to

5 <= 5


==, !=

equal to, not equal to

2 != 3



switches Boolean value

not True



Loops provide another method of flow control and are suited to perform iterative tasks. A loop will repeat inclusive code until the provided condition is no longer True or an exit signal is provided. There are two kinds of loops: for and while. For most iterative tasks a for loop will be the best option to use.


For loops are the most common and, in most cases, the preferred method to perform a task over and over again. Imagine a factory line, for each object on the conveyor belt a for loop could be used to perform some task on it, such as placing a label on the object. In this manner, multiple for loops can come together in the form of an assembly line, processing each object, until they are ready to be presented to the user.

Much like the rest of Python, the for loop is very simple syntactically and yet powerful. In some languages a for loop needs to be initialized, have a counter of sorts, and a termination case. Python's for loop is much more dynamic and handles these tasks on its own. These loops contain indented code that is executed line by line. If the object being iterated over still has elements (for example, more items to process) at the end of the indented block, the PVM will position itself at the beginning of the loop and repeat the code again.

The for loop syntax will specify the object to iterate over and what to call each of the elements within the object. Note, the object must be iterable. For example, strings and lists are iterable, but an integer is not. In the example below, we can see how a for loop treats strings and lists and helps us iterate over each element in iterable objects.

>>> for character in 'Python':
...     print character
>>> cars = ['Volkswagon', 'Audi', 'BMW']
>>> for car in cars:
...     print car

There are additional, more advanced, ways to call a for loop. The enumerate() function can be used to start an index. This comes in handy when you need to keep track of the index of the current object. Indexes are incremented at the beginning of the loop. The first object has an index of 0, the second has an index of 1, and so on. The range() and xrange() functions can execute a loop a certain number of times and provide an index. The difference between range() and xrange() is somewhat subtle, though the xrange() function is quicker and more memory efficient than the range function.

>>> numbers = [5, 25, 35]
>>> for i, x in enumerate(numbers):
...     print 'Item', i, 'from the list is:', x
Item 0 from the list is: 5
Item 1 from the list is: 25
Item 2 from the list is: 35
>>> for x in xrange(0, 100):  # prints 0 to 100 (not shown below in an effort to save trees)
...     print x


While loops are not encountered as frequently in Python. A while loop executes as long as a statement is true. The simplest while loop would be a while True statement. This kind of loop would execute forever as the Boolean object True is always True and so the indented code would continually execute.

If you are not careful, you can inadvertently create an infinite loop, which will wreak havoc on your script's intended functionality. It is imperative to utilize conditionals to cover all your bases such as if, elif, and else statements. If you fail to do so, your script can enter an unaccounted situation and crash. This is not to say that while loops are not worth using. They are quite powerful and have their own place in Python.

>>> guess = 0
>>> answer = 42
>>> while True:
...     if guess == answer:
...             print 'You\'ve found the answer to this loop: ' + str(answer) + '.'
...             break
...     else:
...             print guess, 'is not the answer.'
...             guess += 1

The break, continue, and pass statements are used in conjunction with for and while loops to create more dynamic loops. The break escapes from the current loop, while the continue statement causes the PVM to begin executing code at the beginning of the loop, skipping any indented code after the continue. The pass statement literally does nothing and acts as a placeholder. If you're feeling brave or bored, or worse, both, remove the break statement from the previous example and note what happens.



Functions are the first step to creating more complex Python code. At a high level, they are containers of Python code that can be bundled together into a callable block. A simple model function requires a single input, performs an operation on the provided data, and returns a single output. However, this quickly becomes more complicated as functions can run without inputs or optional inputs or do not need to return an output at all.

Functions are an integral component of any programming language and have already been encountered many times in this chapter. For example, the append from list.append() is a function that requires input to add to a list. Once a function is created you can invoke it by its name and pass any required inputs.

When it comes to writing functions, more is better. It is much easier to handle and troubleshoot a bug in a program with many small functions than one big function. Smaller functions make your code more readable and make it easier to find troublesome logic. That being said, functions should contain code for a singular purpose like accessing a certain key in a registry file. There is no need to create functions for each line of code in your script.

The function syntax starts with a definition, def, followed by the name of the function, any inputs in parenthesis, and a colon. Following this format are indented lines of code that will run when the function is called. Optionally, a function may have a return statement to pass information back to the instance it was called from.

>>> def simpleFunction():
...     print 'I am a simple function'
>>> simpleFunction()
I am a simple function

In the preceding example, we've created a function named simpleFunction() that takes no inputs. This function does not return anything and instead prints a string. Let's now take a look at more complicated examples.

Our first function, square(), takes one input and squares it. As this function returns a value, we catch it by assigning it to a variable when invoking the function. This variable, squared_number, will be equal to the returned value of the function. While this is a very succinct function it is very easily broken if given the wrong input. Give the square function some other data type, such as a string, and you will receive a TypeError.

>>> def square(x):
...     return x**2
>>> squared_number = square(4)
>>> print squared_number

Our second function, even_or_odd, is slightly more advanced. This function first checks if it is passed an input that is of type integer. If not, it returns immediately, which causes the function to exit. If it is an integer, it performs some logic and prints if the integer is even or odd. Notice that when we try to give the function the string '5', not to be confused with the integer 5, it returns nothing, whereas in the square function, which lacks any input validation checks, this would have caused an error.

>>> def even_or_odd(value):
...     if isinstance(value, int):
...             if value % 2 == 0:
...                     print 'This number is even.'
...             else:
...                     print 'This number is odd.'
...     else:
...             return
>>> values = [1, 3, 4, 6, '5']
>>> for value in values:
...     even_or_odd(value)
This number is odd.
This number is odd.
This number is even.
This number is even.

Aspiring developers should get in the habit of writing functions. As always, functions should be well commented, to help explain their purpose. Functions will be used throughout the book, especially as we begin to develop our forensic scripts.



This chapter has covered a wide range of introductory content that provides a foundation to be built upon throughout the duration of this book; by the end you will become well-versed in Python development. These topics have been handpicked as the most important items to comprise a basic understanding of the language as we move forward. We have covered data types, what they are and when they are used, variable naming and the associated rules and guidelines, logic and operations to manipulate and make decisions based on values, and conditions and loops that provide a sequential organization for our scripts form the baseline of everything we develop.

Through these features alone we can create basic scripts. Python is a very powerful and complex language belying its simplistic syntax. At this point, you should feel comfortable with the Python interpreter if you have been re-typing the prompts (>>>) as presented in the code blocks. In the next chapter, we will explore more complex foundational items and continue expanding upon the knowledge established in this chapter prior to moving into real world examples.

About the Authors
  • Preston Miller

    Preston Miller is a consultant at an internationally recognized risk management firm. Preston holds an undergraduate degree from Vassar College and a master's degree in digital forensics from Marshall University. While at Marshall, Preston unanimously received the prestigious J. Edgar Hoover Foundation's scientific scholarship. Preston is a published author, recently of Python Digital Forensics Cookbook, which won the Forensic 4:cast Digital Forensics Book of the Year award in 2018. Preston is a member of the GIAC advisory board and holds multiple industry-recognized certifications in his field.

    Browse publications by this author
  • Chapin Bryce

    Chapin Bryce is a senior consultant at a global firm that is a leader in digital forensics and incident response investigations. After graduating from Champlain College, with a bachelor's degree in computer and digital forensics, Chapin dove into the field of digital forensics and incident response joining the GIAC advisory board and earning four GIAC certifications: GCIH, GCFE, GCFA, and GNFA. As a member of multiple ongoing research and development projects, he has authored several books and articles in professional and academic publications, including Python Digital Forensics Cookbook (2018 Digital Forensics Book of the Year, Forensic 4Cast), Learning Python for Forensics First Edition, and Digital Forensic Magazine.

    Browse publications by this author
Latest Reviews (5 reviews total)
Well written book that has helped me use Python for practical purposes.
I just start reading so could not comment on it
Learning Python for Forensics
Unlock this book and the full library FREE for 7 days
Start now