diff options
author | Guillermo Ramos | 2012-03-11 14:54:30 +0100 |
---|---|---|
committer | Guillermo Ramos | 2012-03-11 14:54:30 +0100 |
commit | 4c0eeddba45e354019cb6cd08ec398bdecfdc9ee (patch) | |
tree | 26be919e8ecf873b367996635a8d239f1dc208f8 | |
download | mjolnir-4c0eeddba45e354019cb6cd08ec398bdecfdc9ee.tar.gz |
First commit
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 21 | ||||
-rw-r--r-- | mjolnir.c | 341 |
3 files changed, 366 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ef56f59 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.o +mjolnir +tags +cscope.* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..57823d1 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +CC = clang +CFLAGS = -Wall -Wextra -std=c99 -pedantic -O3 +LDFLAGS = -lm +OBJECTS = mjolnir.o +TARGET = mjolnir + +release: $(TARGET) + +%.o: %.c + $(CC) -c $(CFLAGS) $< + +$(TARGET): $(OBJECTS) + +install: release + echo "Installing... (not implemented)" + +clean: + rm -f *.o *~ core tags $(TARGET) + +tags: + ctags * diff --git a/mjolnir.c b/mjolnir.c new file mode 100644 index 0000000..3ea12cb --- /dev/null +++ b/mjolnir.c @@ -0,0 +1,341 @@ +/* + * mjolnir - simple and portable shredder (secure file eraser) + * Copyright (c) 2010-2012 Guillermo Ramos GutiƩrrez <0xwille@gmail.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of copyright holders nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +#define _XOPEN_SOURCE 600 // SUSv3 - required by fsync(2) & sigaction(2) + +#include <dirent.h> +#include <errno.h> +#include <fcntl.h> +#include <limits.h> +#include <math.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/mman.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <time.h> +#include <unistd.h> + +#define BUFLEN 0x80000 // randbuf length (bytes) +#define LOOPS 4 // number of times the file is overwritten +#define BARLEN 40 // length of the progress bar + +// http://www.gnu.org/software/libc/manual/html_mono/libc.html#Limits-for-Files +#ifndef PATH_MAX +# define PATH_MAX FILENAME_MAX +#endif + + +static int interrupted = 0; +static int recursive = 0; + +static int shred(char *path); // recursive; must declare it here + + +/* All-in-one signal handler */ +static void sighandler(int sig) +{ + switch (sig) { + case SIGINT: + interrupted = 1; + break; + } +} + + +/* Unified error message format */ +static void err(char *path) +{ + fprintf(stderr, "[-] ERROR %d with file '%s': %s\n", + errno, path, strerror(errno)); +} + + +/* Print program usage and exit */ +static void usage(char *path, int retval) +{ + printf("Usage: %s [-hr] [FILE]...\n" + "Securely erase FILEs so that they can hardly be recovered\n" + "\nOPTIONS:\n-h\n\tshow this help\n-r\n-R\n" + "\trecursive mode (erase directories with all their content)\n", path); + exit(retval); +} + + +/* Print a progress bar showing how's the shredding going by the moment */ +static void show_progress(int jump) +{ + float jump_len = (float)BARLEN / LOOPS; + int i; + + printf("\r\t["); + + for (i = 0; i < round(jump * jump_len); i++) + putchar('#'); + for (i = 0; i < BARLEN - round(jump * jump_len); i++) + putchar('-'); + + switch (jump % 4) { + case 0: + printf("] | "); + break; + case 1: + printf("] / "); + break; + case 2: + printf("] - "); + break; + case 3: + printf("] \\ "); + } + + fflush(stdout); +} + + +/* Overwrites file with 1's, 0's, and random characters LOOPS times. */ +static int shred_file(char *path) +{ + struct sigaction sact, soact; // signal control structs + void *map; // mmap'ed file + char randbuf[BUFLEN]; // buffer with random data + unsigned tofill; // useful part of the randbuf + int remaining, offset; // controlling randbuf memcpy + size_t len; // file length + int fd; + unsigned i, j; + + if ((fd = open(path, O_RDWR)) == -1) + return -1; + if ((len = lseek(fd, 0, SEEK_END)) == 0) { + printf("[+] File '%s' has length 0, skipped\n", path); + return 0; + } + + // Let's zero these garbagish structs + memset(&sact, 0, sizeof(struct sigaction)); + memset(&soact, 0, sizeof(struct sigaction)); + + // Create memory map of target file + map = mmap(NULL, len, PROT_WRITE, MAP_SHARED, fd, 0); + if (map == MAP_FAILED) + return -1; + + // Set up the random buffer + tofill = BUFLEN < len ? BUFLEN : len; + for (i = 0; i < tofill; i++) + randbuf[i] = rand() % 255; + + // Set the new signal behavior and save the previous one for later restore + sact.sa_handler = sighandler; + sigaction(SIGINT, &sact, &soact); + + printf("[+] Shredding '%s'...\n", path); + show_progress(0); + + for (i = 0; i < LOOPS; i++) { + + // If received SIGINT during last loop, abort program + if (interrupted) { + sigaction(SIGINT, &soact, &sact); + printf("\tSIGINT caught, aborting...\n"); + munmap(map, len); + exit(0); + } + + // Switch between writing \x00, \xFF and copying random data + if (i % 3 == 0) { + memset(map, 0, len); + } else if (i % 3 == 1) { + memset(map, 255, len); + } else { + + /* + * With random data, we must be extra careful. Since the buffer + * and the file are nearly always going to have different sizes, we + * will write the buffer either several times or not even one. The + * remaining and offset variables are used to control what has + * already been overwritten so that we fill exactly the entire file + * without overflowing it. + */ + remaining = BUFLEN; + offset = 0; + + while (remaining == BUFLEN) { + if (((signed)len - offset) < remaining) + remaining = len-offset; + memcpy((char *)map + offset, randbuf, remaining); + offset += remaining; + } + + /* + * Only revert random buffer if it's not the last time the file is + * going to be filled with random data + */ + if (i < LOOPS-2) + for (j = 0; j < tofill; j++) + randbuf[j] = ~randbuf[j]; + } + + fsync(fd); // Apply the changes from map into the HDD + show_progress(i+1); // Update progress bar + } + + putchar('\n'); + munmap(map, len); + close(fd); + + return 0; +} + + +/* Return true (!= 0) if path is a file, false otherwise */ +static int is_dir(char *path) +{ + struct stat s; + lstat(path, &s); + return S_ISDIR(s.st_mode); +} + + +/* Rename path (file/dir) several times and unlink/rmdir it afterwards */ +static int rename_del(char *path) +{ + int pathlen = strlen(path); + char *old_path = path; + char new_path[pathlen]; + char *p = strrchr(path, '/'); + int file_offset = p ? (p - path + 1) : 0; + int retval = 0; + int i; + + printf("[+] Deleting %s...\n", path); + + if (is_dir(path)) { + return rmdir(path); + } else { + strncpy(new_path, old_path, pathlen+1); // pathlen+1: copy the '\0' too + + while ((pathlen-file_offset) > 1) { + + // random new file name (FN -> new_path[file_offset..pathlen-1]) + for (i = file_offset; i < pathlen; i++) + new_path[i] = (rand() % 26) + 'a'; + new_path[--pathlen] = '\0'; // reduce file length by 1 each time + + if ((retval = rename(old_path, new_path))) + return retval; + + strncpy(old_path, new_path, pathlen+1); // again, copy the '\0' + } + + return unlink(new_path); + } +} + + +/* Recursively shred all the files inside target dir */ +static int shred_dir(char *path) +{ + char *filename = malloc(PATH_MAX * sizeof(char)); + struct dirent *ent; + int retval = 0; + DIR *d; + + if ((d = opendir(path)) == NULL) + return -1; + + while ((ent = readdir(d)) != NULL) { + snprintf(filename, PATH_MAX, "%s/%s", path, ent->d_name); + if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) + retval |= shred(filename); + } + + free(filename); + + return retval|closedir(d); +} + + +/* Detect path's type and call the apropiate functions to shred it */ +static int shred(char *path) +{ + int retval = 0; + + if (access(path, F_OK) == -1) + err(path); + else if (is_dir(path)) { + if (recursive) { + if ((retval = shred_dir(path)) || (retval = rename_del(path))) + err(path); + } else { + printf("[*] Omitting directory '%s' (-r to shred recursively)\n", + path); + } + } else { + if ((retval = shred_file(path)) || (retval = rename_del(path))) + err(path); + } + + return retval; +} + + +int main(int argc, char **argv) +{ + int opt, i; + int retval = 0; + + if (argc < 2 || !strcmp(argv[1], "--help")) + usage(*argv, 0); + + while ((opt = getopt(argc, argv, "hrR")) != -1) + switch (opt) { + case 'h': + usage(*argv, 0); + break; + case 'r': + case 'R': + recursive = 1; + break; + case '?': + default: + usage(*argv, EINVAL); + } + + srand((unsigned)(time(NULL) * (time_t)&i)); // ASLR is our friend }:D + + for (i = optind; i < argc; i++) + retval |= shred(argv[i]); + + return retval; +} |