Prerequisites: Python 2.7 installed and a raw dump of a file system.
Arguments (Header and footer carving mode): arg1 = path to dump file.
Arguments (Header and max size carving mode): arg1 = path to dump file, arg2 = max size to carve in kb.

This Python 2.7 script can be used to carve deleted JPEG and GIF files from a physical raw dump of any file system.
The carving techniques used are header / footer carving and also header max size carving.
As long as the deleted files have not been overwritten or are fragmented,
they should be able to be carved out.

The header signatures to look for can be edited,
the same script can be used to find any kind of files that have a known signature.

JPEG header and footer carving example on dump file with deleted JPEG files

Carved JPEG files
(duplicates because of carving with both the header / footer method and header max size).
Notice that picture number 4 is broken because it had been fragmented

The script will re-create the carved files in the same directory where the script is located,
it will also create a log file with the console output in the same location.
The offset where the file begins,
offset where the file ends will also be displayed and number of bytes carved per file (size) is displayed in the output.

Code:

#!/usr/bin/env python
# This program carves JPEG and GIF images from the binary dump that is
# given as argument 1 to the program. Argument 2 is max size in kb
# to read when an image header is found.
# The carver works in HEADER/FOOTER mode if argument 2 is omitted
# and in HEADER/MAX size mode if argument 2 is given.
# Output from the program is created image files with the following
# naming convention: "name of dump file"_"counter_HF/HS".jpg/gif.
# HF alternatively HS tells us which mode the carver was running in.
# HF stands for HEADER/FOOTER and HS for HEADER/SIZE.
# A log file is also produced with the same contents as in the
# console output.
# The file is named as "name of dump file"_"HF/HS".log.
#
# Author:
# Kim Pettersson
#

import sys
import os
import time
from binascii import hexlify

# =======================================
# Read an image from the dump and save it
# =======================================
def saveImage(fdIn, fdLog, fileName, startAddress, numberOfbytes):
    data = 0

    try:
        # Seek from the beginning of the dump file and read binary data
        fdIn.seek(startAddress, 0)
        data = fdIn.read(numberOfbytes)

        # Create JPEG or GIF file
        fdOut = open(fileName, "wb")
        fdOut.write(data)
    except Exception, e:
        doLog(fdLog, "!!!!!! Failed to save " + fileName + " !!!!!!")

    fdOut.close()

# ==============================================
# Print a log message to the console and log file
# ==============================================
def doLog(fd, s):
    try:
        print(s)
        fd.write(s + "\n")
    except Exception, e:
        print("!!!!!! Failed to write message to log file !!!!!!")

# ==============================================
# Initialise file objects. Open binary input file
# in read mode and create a log file.
# ==============================================
def init(inputFile, maxImageSize):
    fdIn = 0
    fdLog = 0
    fileSize = 0
    logFile = inputFile + ("_HS.log" if (maxImageSize > 0) else "_HF.log")

    try:
        # Open image file in binary read mode
        fdIn = open(inputFile, mode='rb')

        # Create log file
        fdLog = open(logFile, mode='w')

        # Get size of dump file
        fileSize = os.path.getsize(argv1)
    except Exception, e:
        print("!!!!!! Failed to initialise file descriptors !!!!!!")
        fdIn = 0
        fdLog = 0
        fileSize = 0

    return {'fdIn': fdIn, 'fdLog' : fdLog, 'fileSize': fileSize, 'logFile' : logFile}

# =========
# Variables
# =========
jpegHeader = hexlify('\xff\xd8\xff\xe0')    # JPEG header to search for
jpegFooter = hexlify('\xff\xd9')            # JPEG footer to search for
gifHeader = hexlify('\x47\x49\x46\x38')     # GIF header to search for
gifFooter = hexlify('\x00\x3b')             # GIF footer to search for
isJpegHeader = 0                            # 1 if current found header is a JPEG header
isGifHeader = 0                             # 1 if current found header is a GIF header
isJpeg = 0                                  # 1 if current found image is a JPEG image in header/footer carving mode.
isGif = 0                                   # 1 if current found image is a GIF image in header/footer carving mode.
headerSize = 4                              # Size of jpegHeader/gifHeader
footerSize = 2                              # Size of jpegFooter/gifFooter
headerStart = 0                             # Holds offset to an image header in the file in header/footer carving mode
fileCounter = 0                             # Number of found image files
fileOffset = 0                              # Current offset in file
fileSize = 0                                # Size of dump file
sizeToRead = 0                              # Number of bytes to read from file (headerSize or footerSize).
buf = 0                                     # Holding data read from file
imageSize = 0                               # Size of current image if header/footer mode
maxImageSize = 0                            # Bytes to read from header start in header/max size mode.
fileName = ""                               # Name of image file to be created
oneKiloByte = 1024                          # Number of bytes in 1 kb
fdIn = 0                                    # File object for binary dump file
fdLog = 0                                   # File object for created log file
startTime = time.time()                     # For diagnostics usage


# ============
# Main program
# ============
try:
    argv1 = sys.argv[1]

    # Illegal number of arguments?
    if (len(sys.argv) < 2):
        raise ValueError("")

    # Max size mode?
    if (len(sys.argv) == 3):
        # Bytes to read in max size mode
        maxImageSize =  int(sys.argv[2]) * oneKiloByte

        # Check size. Less than 1 kb?
        if (maxImageSize < oneKiloByte):
            raise ValueError("")
except:
    print("""
            Usage: python DataCarve.py "dumpFile" [optional "kb to read in max size mode)"]
            Example args: 11-carve-fat.dd 500
            """)
    sys.exit(1)

# Open input file and create a log file with the same name as the
# input file and with the extension .log
initData = init(argv1, maxImageSize)

# Get return values
fdIn = initData['fdIn']
fdLog = initData['fdLog']
fileSize = initData['fileSize']

doLog(fdLog, "------------- JPEG/GIF header/footer and header/max size carver -------------\n")
doLog(fdLog, "Size of image file " + argv1 + ": " + str(fileSize / 1048576) + " MB")
doLog(fdLog, "Carving started in " + ("HEADER/MAX SIZE mode\n" if (maxImageSize > 0) else "HEADER/FOOTER mode\n"))
print(initData['logFile'] + " was created with the console output\n")

# Failed to open binary dump file?
if (fdIn == 0):
    sys.exit(1)

# Parse file contents
while (fileOffset < (fileSize - headerSize)):
    try:
        # Move file pointer to address to read from
        fdIn.seek(fileOffset)

        # Read header or footer size from file? Header size if header/footer mode and
        # we are looking for a header or if header/max size mode.
        sizeToRead = headerSize if ((headerStart == 0) or (maxImageSize > 0)) else footerSize

        # Read from file and convert to hex
        buf = hexlify(fdIn.read(sizeToRead))

        # Looking for a header?
        if (headerStart == 0):
            isJpegHeader = (buf == jpegHeader)
            isGifHeader = (buf == gifHeader)

            # A header?
            if (isJpegHeader or isGifHeader):
                # Running in max size mode?
                if (maxImageSize > 0):
                    # Enough bytes left in file to read max size?
                    if (fileOffset <= (fileSize - maxImageSize)):
                        fileCounter += 1
                        fileName = argv1 + '_' + str(fileCounter) + ("_HS.jpg" if (isJpegHeader) else "_HS.gif")
                        doLog(fdLog, fileName + " created: start: " + hex(fileOffset) + " size: " + str(maxImageSize))

                        # Save data from dump to an image file
                        saveImage(fdIn, fdLog, fileName, fileOffset, maxImageSize)

                        # Set new file offset
                        fileOffset += headerSize
                    else:
                        # Leave loop
                        break
                else:
                    # Header/footer mode
                    # Remember position in file for start of header
                    headerStart = fileOffset
                    doLog(fdLog, ("JPEG header \\xff\\xd8\\xff\\xe0" if (isJpegHeader) else "GIF header \\x47\\x49\\x46\\x38") + " found at offset " + hex(headerStart))

                    # Set offset to next byte to start reading from
                    fileOffset += headerSize
            else:
                # Set offset to next byte to start reading from
                fileOffset += 1
        else:
            # Header/footer mode. We have found a header and we are looking for a footer.
            isJpeg = (isJpegHeader and (buf == jpegFooter))
            isGif = (isGifHeader and (buf == gifFooter))

            # Found the end mark for a corresponding header?
            if (isJpeg or isGif):
                imageSize = fileOffset + footerSize - headerStart
                fileCounter += 1
                fileName = argv1 + '_' + str(fileCounter) + ("_HF.jpg" if (isJpeg) else "_HF.gif")
                doLog(fdLog, ("JPEG footer \\xff\\xd9" if (isJpeg) else "GIF footer \\x00\\x3b") + " found at offset " + hex(fileOffset))
                doLog(fdLog, fileName + " created: start: " + hex(headerStart) + " end: " + hex(fileOffset + 1) + " size: " + str(imageSize) + "\n")

                # Save data from dump to an image file
                saveImage (fdIn, fdLog, fileName, headerStart, imageSize)

                # Start looking for a header again
                fileOffset = headerStart + headerSize
                headerStart = 0
            else:
                # Not a footer. Try from next byte.
                fileOffset += 1
    except Exception, e:
        doLog(fdLog, e)
        sys.exit(1)

# Update console and log file with execution time
doLog(fdLog, "Execution time: " + str(round(time.time() - startTime, 1)) + " seconds\n")

# Close input file and log file
fdIn.close()
fdLog.close()