The Recover program is a forensic tool that recovers deleted JPEG images from a memory card. Digital cameras often format memory cards such that "deleted" photos are simply hidden from the index but remain on the card. This program scans the raw data block by block to identify JPEG signatures and reconstructs images.
Project Requirements
- Implement a program that recovers JPEGs from a forensic image
- Accept a single command-line argument (the forensic image)
- Open the memory card image file
- Read the file in 512-byte blocks
- Identify JPEG signatures in file headers
- Create new JPEG files for each found image
- Write consecutive blocks to the current JPEG file
- Generate unique filenames in sequence (000.jpg, 001.jpg, etc.)
- Properly close all files when finished
SD Card Structure
Memory cards are structured in 512-byte blocks. JPEG files are stored contiguously with specific headers:
found()
Checks if a 512-byte block starts with a valid JPEG signature.
File Creation
Creates new JPEG files with sequential naming (000.jpg, 001.jpg, etc.)
Block Writing
Writes 512-byte blocks to the current JPEG file until a new signature is found.
Step 1: Opening the Memory Card
The program starts by validating command-line arguments and opening the memory card file for reading.
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
// Function prototype
bool found(uint8_t header[]);
int main(int argc, char *argv[])
{
// Check for correct usage
if (argc != 2)
{
printf("Usage: ./recover FILE\n");
return 1;
}
// Open memory card file in binary mode
FILE *card = fopen(argv[1], "rb");
if (card == NULL)
{
printf("Card not opened\n");
return 2;
}
// Rest of the code...
Important Note
The file is opened in binary mode ("rb") to ensure no data transformation occurs during reading, which is critical for forensic recovery.
Step 2: Reading Blocks and Detecting JPEG Signatures
The program reads the memory card 512 bytes at a time and checks for the JPEG signature in each block.
// Buffer for reading 512-byte blocks
uint8_t buffer[512];
// Array to store the first 4 bytes (potential JPEG header)
uint8_t header[4];
// Output file pointer
FILE *output = NULL;
// Filename buffer (8 chars for 000.jpg\0)
char filename[8];
int file_count = 0;
// Read through the card block by block
while (fread(buffer, 1, 512, card) == 512)
{
// Extract the first 4 bytes
for (int i = 0; i < 4; i++)
{
header[i] = buffer[i];
}
// Check if we have a JPEG signature
if (found(header))
{
// Handle JPEG found...
}
// Continue writing if we're in the middle of a JPEG
else if (output != NULL)
{
fwrite(buffer, 1, 512, output);
}
}
JPEG Signature Structure
JPEG files start with a specific 4-byte sequence:
Byte Position | Value | Description |
---|---|---|
Byte 0 | 0xFF | Start of JPEG marker |
Byte 1 | 0xD8 | JPEG file start marker |
Byte 2 | 0xFF | Additional marker |
Byte 3 | 0xE0-0xEF | Specific JPEG format identifier |
Step 3: Creating JPEG Files
When a new JPEG signature is found, the program closes any open file and creates a new JPEG file.
if (found(header) == true)
{
// Close previous JPEG if open
if (output != NULL)
{
fclose(output);
}
// Create new filename (000.jpg, 001.jpg, etc.)
sprintf(filename, "%03i.jpg", file_count);
// Open new JPEG file in binary write mode
output = fopen(filename, "wb");
file_count++;
// Write the first block of the new JPEG
fwrite(buffer, 1, 512, output);
}
Filename Formatting
The sprintf
function formats filenames with 3-digit numbers padded with zeros (e.g., 000.jpg, 001.jpg). This ensures proper sorting and meets the project requirements.
Step 4: JPEG Signature Detection
The found()
function checks if a byte sequence matches the JPEG signature.
// Check if header matches JPEG signature
bool found(uint8_t header[])
{
// First 3 bytes must be 0xff, 0xd8, 0xff
uint8_t signature[] = {0xff, 0xd8, 0xff};
// Fourth byte must be in this range
uint8_t sign2[] = {
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5,
0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
0xec, 0xed, 0xee, 0xef
};
// Check first 3 bytes
for (int i = 0; i < 3; i++)
{
if (header[i] != signature[i])
{
return false;
}
}
// Check fourth byte
for (int i = 0; i < 16; i++)
{
if (header[3] == sign2[i])
{
return true;
}
}
return false;
}
Full Implementation
The complete JPEG recovery implementation with all functions and main logic:
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <stdint.h>
// Check if header matches JPEG signature
bool found(uint8_t header[]);
int main(int argc, char *argv[])
{
// Validate command line arguments
if (argc != 2)
{
printf("Usage: ./recover FILE\n");
return 1;
}
// Open memory card file
FILE *card = fopen(argv[1], "rb");
if (card == NULL)
{
printf("Card not opened\n");
return 2;
}
// Buffer for reading 512-byte blocks
uint8_t buffer[512];
// Array to store header bytes
uint8_t header[4];
// Output file pointer
FILE *output = NULL;
// Filename buffer
char filename[8];
int file_count = 0;
// Read through card block by block
while (fread(buffer, 1, 512, card) == 512)
{
// Extract first 4 bytes
for (int i = 0; i < 4; i++)
{
header[i] = buffer[i];
}
// Check for JPEG signature
if (found(header))
{
// Close previous file if open
if (output != NULL)
{
fclose(output);
}
// Create new filename
sprintf(filename, "%03i.jpg", file_count);
output = fopen(filename, "wb");
file_count++;
// Write the first block
fwrite(buffer, 1, 512, output);
}
// Continue writing if we're in a JPEG
else if (output != NULL)
{
fwrite(buffer, 1, 512, output);
}
}
// Close any remaining files
if (output != NULL)
{
fclose(output);
}
// Close card file
fclose(card);
return 0;
}
// Function to detect JPEG signature
bool found(uint8_t header[])
{
uint8_t signature[] = {0xff, 0xd8, 0xff};
uint8_t sign2[] = {
0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5,
0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xeb,
0xec, 0xed, 0xee, 0xef
};
// Check first 3 bytes
for (int i = 0; i < 3; i++)
{
if (header[i] != signature[i])
{
return false;
}
}
// Check fourth byte
for (int i = 0; i < 16; i++)
{
if (header[3] == sign2[i])
{
return true;
}
}
return false;
}
Key Considerations
- Binary Mode: Files are opened in binary mode ("rb"/"wb") to prevent data corruption
- JPEG Signatures: The detection function checks for the specific 4-byte sequence that identifies JPEG files
- Block Reading: The program reads the card in 512-byte blocks, which is the standard size for storage devices
- File Management: Properly closing files prevents resource leaks and ensures all data is written
- Sequential Naming: Files are named with 3-digit numbers for proper sorting and identification
Important Note
To use this program on a physical SD card, you must first create a raw image using tools like Win32 Disk Imager. The input file should be in .raw or .img format containing the exact binary data from the memory card.
Example Execution
Input: ./recover card.raw
Output:
- 000.jpg
- 001.jpg
- 002.jpg
- ...
Result: Recovered JPEG images from the memory card image