Wednesday, May 15, 2013

Finding Deleted JPEGs in Disk Dumps

If you've ever accidentally deleted items from your digital camera, or want to find them from a drive that has been lost, this script could be for you!

It works by looking through a disk image for a sequence of bytes that signify the beginning and end of a JPEG image.

Running It

First save the below script as ImageCarver.java

Then compile it:
javac ImageCarver.java
Afterwards, run it with the path to the disk image you want it to search:
java ImageCarver disk.img
It will report images it found, as well as the locations in the disk that it found them:
1.jpg 0xe3fa 0x10272
2.jpg 0xe000 0x20865
3.jpg 0x1000 0x2557e
4.jpg 0x41000 0x4437d
5.jpg 0x26000 0x654d4
6.jpg 0x25000 0x96816
7.jpg 0x86000 0x9eca0
8.jpg 0x6d000 0xdc614
9.jpg 0xe2000 0xe7ac9
10.jpg 0xa4000 0xeb28a
11.jpg 0xed000 0xfc9a9
12.jpg 0x136000 0x13b08e
13.jpg 0x1b5144 0x1b629e
14.jpg 0x1b5000 0x1b794a
15.jpg 0x1b67f0 0x1bea75
16.jpg 0x1c9000 0x1cfada
17.jpg 0x22d000 0x237824
18.jpg 0x281000 0x288928

The Script

import java.io.IOException;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.LinkedList;

/**
* Carves images from a file given from the command line.
*
* @author Joseph Lewis <joehms22@gmail.com>
*
*/
public class ImageCarver
{
byte[] STARTING_BYTES = new byte[]{(byte)0xFF, (byte)0xD8,(byte)0xFF};
byte[] ENDING_BYTES = new byte[]{(byte)0xFF,(byte)0xD9};
int imgno = 0;


/**
* Carves images out of a file.
* @param sbc - the byte channel to read from.
* @throws IOException - on a file error
*/
public ImageCarver(SeekableByteChannel sbc) throws IOException
{
// find all potential starting and ending points in the file.
LinkedList<Long> startingPoints = ringMatch(sbc, STARTING_BYTES);
LinkedList<Long> endingPoints = ringMatch(sbc, ENDING_BYTES);

// keep writing out we're out of images
while(startingPoints.size() > 0)
{
carveImage(sbc, startingPoints, endingPoints);
}
}

/**
* Carves an image out of the file.
* @param sbc - the channel to read from
* @param starts - the start positions of the image
* @param ends - the end positions of the image
* @throws IOException - on error
*/
public void carveImage(SeekableByteChannel sbc, LinkedList<Long> starts, LinkedList<Long> ends) throws IOException
{
// clear off no longer useful end points
while(starts.peek() > ends.peek())
{
ends.pop();
}

// If ends is empty, add the literal file end.
if(ends.size() == 0)
{
ends.push(sbc.size());
}

// get our starting position.
long sp = starts.pop();

// Check to see if there's an image inside of us.
if(starts.size() > 0 && starts.peek() < ends.peek())
{
carveImage(sbc, starts, ends);
}

// get our closest end point.
long endloc = ends.pop();

imgno++;
createImage(imgno + ".jpg", sbc, sp, endloc);
}

/**
* Extracts an image from the SeekableByteChannel.
* @param name - the image name
* @param src - the channel to read from
* @param begin - the offset to the beginning of the image
* @param end - the offset to the end of the image
* @throws IOException - on error
*/
public void createImage(String name, SeekableByteChannel src, long begin, long end) throws IOException
{
// alert the user about the image we're outputting
System.out.println(String.format("%s 0x%x 0x%x", name, begin, end));

// write out the given slice of bytes
try(OutputStream os = Files.newOutputStream(Paths.get(name), StandardOpenOption.CREATE))
{
src.position(begin);
ByteBuffer b = ByteBuffer.allocate((int) (end - begin));
src.read(b);
os.write(b.array());
}
}

/**
* Searches through a channel looking for a given array of bytes to match.
*
* @param sbc - the channel to read from
* @param toMatch - the bytes to match in the stream
* @return a list of all offsets where the given bytes are found
* @throws IOException - on file read error
*/
public LinkedList<Long> ringMatch(SeekableByteChannel sbc, byte[] toMatch) throws IOException
{
// holds all found vars
LinkedList<Long> finds = new LinkedList<Long>();

for(long i = 0; i < sbc.size() - toMatch.length; i++)
{
ByteBuffer b = ByteBuffer.allocate(toMatch.length);
sbc.position(i);
sbc.read(b);

boolean matches = true;
for(int j = 0; j < toMatch.length; j++)
{
if(b.array()[j] != toMatch[j])
{
matches = false;
break;
}
}

if(matches)
{
finds.add(i);
}

}
return finds;
}

public static void main(String[] args) throws Exception
{
// Make sure we have an arg.
if(args.length != 1)
{
System.out.println("Must supply exactly one argument.");
return;
}

Path p = Paths.get(args[0]);

try (SeekableByteChannel sbc = Files.newByteChannel(p))
{
new ImageCarver(sbc);
}
catch (IOException x)
{
System.out.println("Caught exception: " + x);
}
}
}

No comments:

Post a Comment