scanning a USB drive using a C program (fopen,fread,fwrite)
Categories:
Scanning USB Drives with C: A Deep Dive into fopen
, fread
, and fwrite

Learn how to programmatically access and scan raw data from USB drives using standard C library functions like fopen
, fread
, and fwrite
on Linux systems. This guide covers device identification, raw data access, and basic scanning principles.
Accessing raw data from a USB drive using C can be a powerful technique for various applications, from forensic analysis to custom data recovery tools or even simple integrity checks. This article will guide you through the process of identifying a USB device, opening it as a raw block device, reading its contents sector by sector, and optionally writing data back. We'll focus on Linux environments, where block devices are exposed as special files, making them accessible via standard file I/O functions.
Understanding USB Device Access on Linux
On Linux, physical storage devices, including USB drives, are represented as block devices in the /dev/
directory. These devices are typically named sdX
(e.g., sda
, sdb
, sdc
) for SCSI-like devices, which includes most USB drives. Partitions on these drives are further denoted as sdXN
(e.g., sdb1
, sdb2
). To perform a raw scan, you need to open the entire device (e.g., /dev/sdb
), not just a partition. This grants you direct access to the raw sectors of the drive, bypassing the filesystem layer.
flowchart TD A[User Request Scan] --> B{Identify USB Device Path} B --> C{Open Device with `fopen`} C --> D{Loop `fread` for Data Blocks} D --> E[Process Data Block] E --> F{Check for EOF or Error} F -- No --> D F -- Yes --> G[Close Device with `fclose`] G --> H[Scan Complete]
Workflow for scanning a USB drive using C file I/O functions.
fopen
, fread
, and fwrite
requires root privileges (sudo
) and carries significant risk. Incorrect writes can lead to data loss or render the device unusable. Always double-check the device path before proceeding!Identifying the USB Device
Before you can scan a USB drive, you need to know its device path. You can typically find this using commands like lsblk
, fdisk -l
, or dmesg
after plugging in the drive. For example, lsblk
will show a tree-like structure of block devices and their mount points. Look for a device that corresponds to your USB drive, usually one without a mount point or with a recognizable size.
lsblk
# Example output:
# NAME MAJ:MIN RM SIZE RO TYPE MOUNTPOINTS
# sda 8:0 0 238.5G 0 disk
# ├─sda1 8:1 0 512M 0 part /boot/efi
# └─sda2 8:2 0 238G 0 part /
# sdb 8:16 1 7.5G 0 disk
# └─sdb1 8:17 1 7.5G 0 part /media/user/USB_DRIVE
Using lsblk
to identify connected block devices. In this example, /dev/sdb
is likely the USB drive.
Opening the Device and Reading Data
Once you have the device path (e.g., /dev/sdb
), you can open it using fopen
. It's crucial to open it in binary read mode ("rb"
) for reading, or binary read/write mode ("rb+"
) if you intend to write. Reading is typically done in chunks (e.g., 512 bytes, which is a common sector size) using fread
within a loop until the end of the device is reached (indicated by feof
or fread
returning 0 bytes read).
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 512 // Common sector size
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <device_path>\n", argv[0]);
return 1;
}
const char *device_path = argv[1];
FILE *fp = NULL;
unsigned char buffer[BUFFER_SIZE];
size_t bytes_read;
long long total_bytes_read = 0;
// Open the device in binary read mode
fp = fopen(device_path, "rb");
if (fp == NULL) {
perror("Error opening device");
fprintf(stderr, "Ensure you have root privileges (sudo) and the path is correct.\n");
return 1;
}
printf("Scanning device: %s\n", device_path);
printf("Reading in %d-byte chunks...\n", BUFFER_SIZE);
// Read data in chunks until EOF
while ((bytes_read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {
// Process the buffer here. For example, print a dot for progress
// or perform a simple byte scan.
// For demonstration, we'll just print progress.
printf(".");
fflush(stdout); // Flush output to see progress immediately
// Example: Simple check for a specific byte pattern
// if (memcmp(buffer, "\x55\xAA", 2) == 0) { // Check for MBR signature
// printf("\nFound MBR signature at offset %lld\n", total_bytes_read);
// }
total_bytes_read += bytes_read;
// Optional: Break after reading a certain amount for testing
// if (total_bytes_read >= 1024 * 1024) { // Read 1MB
// break;
// }
}
if (ferror(fp)) {
perror("Error reading from device");
} else if (feof(fp)) {
printf("\nEnd of device reached. Total bytes read: %lld\n", total_bytes_read);
}
// Close the file pointer
fclose(fp);
printf("Device scan complete.\n");
return 0;
}
C program to open a raw device and read its contents in 512-byte chunks.
O_DIRECT
flag with open()
and read()
/write()
system calls instead of fopen
/fread
/fwrite
. This bypasses the kernel's page cache, which can be beneficial for raw device I/O.Writing Data to the Device (Use with Extreme Caution)
Writing data directly to a raw device is extremely dangerous and should only be done if you fully understand the implications. If you need to write, open the device with "rb+"
or "wb"
(though "wb"
will truncate the file, which is usually not desired for block devices). Use fwrite
to write your buffer to the device. You can use fseek
to move to specific offsets before writing.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 512
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <device_path> <offset_in_bytes>\n", argv[0]);
return 1;
}
const char *device_path = argv[1];
long long offset = atoll(argv[2]);
FILE *fp = NULL;
unsigned char data_to_write[BUFFER_SIZE];
size_t bytes_written;
// Fill buffer with some pattern (e.g., all 'A's)
memset(data_to_write, 'A', BUFFER_SIZE);
// Open the device in binary read/write mode
fp = fopen(device_path, "rb+");
if (fp == NULL) {
perror("Error opening device for writing");
fprintf(stderr, "Ensure you have root privileges (sudo) and the path is correct.\n");
return 1;
}
printf("Attempting to write %d bytes to %s at offset %lld\n", BUFFER_SIZE, device_path, offset);
// Seek to the desired offset
if (fseek(fp, offset, SEEK_SET) != 0) {
perror("Error seeking to offset");
fclose(fp);
return 1;
}
// Write the data
bytes_written = fwrite(data_to_write, 1, BUFFER_SIZE, fp);
if (bytes_written != BUFFER_SIZE) {
perror("Error writing to device");
fprintf(stderr, "Only %zu bytes written instead of %d.\n", bytes_written, BUFFER_SIZE);
fclose(fp);
return 1;
}
printf("Successfully wrote %zu bytes to %s at offset %lld.\n", bytes_written, device_path, offset);
// Close the file pointer
fclose(fp);
return 0;
}
C program to write a 512-byte block to a specific offset on a raw device.
Error Handling and EOF
Robust error handling is critical when dealing with low-level device I/O. Always check the return values of fopen
, fread
, fwrite
, and fseek
. fread
returns the number of items successfully read, which might be less than the requested amount if EOF is reached or an error occurs. You can use feof(fp)
to check specifically for end-of-file and ferror(fp)
to check for other I/O errors. perror()
is invaluable for printing system error messages associated with errno
.
By following these guidelines, you can effectively use standard C file I/O functions to interact with USB drives at a low level. Remember the importance of caution and thorough testing, especially when writing data, to avoid unintended consequences.