Skip to content

Instantly share code, notes, and snippets.

@Theldus
Last active February 16, 2022 01:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Theldus/82e85532a973891feea52b1c89f7769e to your computer and use it in GitHub Desktop.
Save Theldus/82e85532a973891feea52b1c89f7769e to your computer and use it in GitHub Desktop.
Encrypt files recursively using XOR and PRNG.
/*
* 2021 November, 17
* The author disclaims copyright to this source code, and it is
* release to the public domain.
*
* This code serves and has been published for study purposes
* only, I am not responsible for any damage caused by it.
*/
/* Enable nftw(). */
#define _XOPEN_SOURCE 700
#define _LARGEFILE64_SOURCE
#define _FILE_OFFSET_BITS 64
#include <dirent.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <inttypes.h>
#include <ctype.h>
#include <fcntl.h>
#include <limits.h>
#include <ftw.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <sys/stat.h>
#include <linux/fs.h>
/* Panic abort. */
#define panic(...) \
do {\
fprintf(stderr, __VA_ARGS__); \
exit(EXIT_FAILURE); \
} while (0)
/* 1TiB, every SEED_STEP MiB. */
#define SEED_CACHE_SIZE ((1024ULL << 30) >> SEED_STEP_LOG)
#define SEED_STEP (1 << SEED_STEP_LOG) /* 16 MiB. */
#define SEED_STEP_LOG (24)
/* xorshift128+ current seed. */
static uint64_t xsp_seed[2] = {0xdeadbeefc0ffeeee, 0xcaca0decadec0c0a};
/* seed cache. */
static struct seed { uint64_t s[2]; } *seed_cache;
struct fiemap_extent
{
/*
* Logical offset in bytes for the start of
* the extent from the beginning of the file.
*/
uint64_t fe_logical;
/*
* Physical offset in bytes for the start
* of the extent from the beginning of the disk.
*/
uint64_t fe_physical;
/* Length in bytes for this extent. */
uint64_t fe_length;
uint64_t fe_reserved64[2];
/* FIEMAP_EXTENT_* flags for this extent. */
uint32_t fe_flags;
uint32_t fe_reserved[3];
};
struct fiemap
{
/*
* Logical offset (inclusive) at
* which to start mapping (in).
*/
uint64_t fm_start;
/*
* Logical length of mapping which
* userspace wants (in).
*/
uint64_t fm_length;
/* FIEMAP_FLAG_* flags for request (in/out). */
uint32_t fm_flags;
/* Number of extents that were mapped (out). */
uint32_t fm_mapped_extents;
/* Size of fm_extents array (in). */
uint32_t fm_extent_count;
uint32_t fm_reserved;
/* Array of mapped extents (out). */
struct fiemap_extent fm_extents[0];
};
/*
* xorshift128+ algorithm, based on Lemire's blog post:
* https://lemire.me/blog/2017/09/08/the-xorshift128-random-
* number-generator-fails-bigcrush/
*
* Also:
* https://v8.dev/blog/math-random
*
* I know, I know... xorshift128+ should not be considered
* secure for crypto-stuff... but this is just a PoC anyway,
* and this is faster and safer than the traditional 'rand()'.
*/
static uint64_t next(void)
{
uint64_t s1 = xsp_seed[0];
uint64_t s0 = xsp_seed[1];
uint64_t result = s0 + s1;
xsp_seed[0] = s0;
s1 ^= s1 << 23; // a
xsp_seed[1] = s1 ^ s0 ^ (s1 >> 17) ^ (s0 >> 26); // b, c
return (result);
}
/*
* @brief Get first physical byte of the given
* file descriptor fd.
*
* @param fd File to be retrieved the phys position.
*
* @return Returns the physical position on success,
* 0 otherwise.
*/
static uint64_t get_first_phys_byte(int fd)
{
uint8_t buf[128] = {0}; /* Fiemap/extent buffer. */
int rc; /* Return code. */
struct fiemap *fiemap = (struct fiemap *)buf;
struct fiemap_extent *fm_ext = &fiemap->fm_extents[0];
int count = (sizeof(buf) - sizeof(*fiemap)) /
sizeof(struct fiemap_extent);
fiemap->fm_length = ~0ULL;
fiemap->fm_flags = 0;
fiemap->fm_extent_count = count;
rc = ioctl(fd, FS_IOC_FIEMAP, (unsigned long) fiemap);
if (rc < 0)
{
fprintf(stderr, "Unable to FIEMAP fd...\n");
return (0);
}
return (fm_ext[0].fe_physical);
}
/**
* @brief Generates the initial seed, which must be
* super-secret and never discovered =).
*
* The seed is actually nothing more than the UUID
* of the disk that runs the root partition ('/').
* Using this UUID is interesting as it is guaranteed
* to be unique per partition and linked to the hard
* drive. This makes the initial seed dependent on the
* user's current hardware, and without the need to
* generate public/private key files for this.
*
* It is worth noting that _the whole_ security of the
* algorithm is that the initial seed _never_ be
* discovered. Once a person has possession of the
* algorithm and knows that this code has been executed
* on the machine, it is simple to reverse the process
* and break the encryption.
*
* I know, I know... security by obscurity is not
* security... but that's just a PoC. It was just a way I
* thought of 'OTP' (since each file has its own seed)
* of infinite size and that it was safe as long as the
* initial seed was never discovered.
*/
static void init_seed(void)
{
DIR *d; /* Opened directory. */
FILE *f; /* /etc/mtab file. */
int idx; /* Seed index. */
int count; /* Half-byte counter. */
char *line; /* Line buffer. */
char *mount; /* Mounting point. */
char *device; /* Hard disk device. */
size_t len; /* Line lenght. */
ssize_t ret; /* Return code. */
int error; /* UUID found. */
char *uuid; /* UUID string. */
struct dirent *dir; /* Current dir entry. */
error = 1;
idx = 0;
len = 0;
line = NULL;
uuid = NULL;
count = 0;
/* Set initial seed, byte 0. */
seed_cache[0].s[0] = xsp_seed[0];
seed_cache[0].s[1] = xsp_seed[1];
/* Get system info and make seed. */
f = fopen("/etc/mtab", "r");
if (!f)
goto err1;
while ((ret = getline(&line, &len, f)) != -1)
{
device = line;
device = strtok(device, " ");
mount = strtok(NULL, " ");
/* Check if current mount is the root fs. */
if (!strcmp(mount, "/"))
{
device = strdup(device);
break;
}
device = NULL;
}
free(line);
if (!device)
goto err2;
/* Root fs found, lets discover its UUID. */
d = opendir("/dev/disk/by-uuid/");
if (!d)
goto err3;
while ((dir = readdir(d)) != NULL)
{
char path [PATH_MAX + 1] = {0};
char target[PATH_MAX + 1] = {0};
strcpy(path, "/dev/disk/by-uuid/");
strcat(path + 18, dir->d_name);
if (!realpath(path, target))
goto err4;
if (!strcmp(target, device))
{
uuid = dir->d_name;
break;
}
}
if (!uuid)
goto err4;
xsp_seed[0] = 0;
xsp_seed[1] = 0;
line = uuid;
for (; *line != '\0'; line++)
{
char c = tolower(*line);
if (!isalnum(c))
continue;
if (c >= '0' && c <= '9')
c -= '0';
else
c -= 87;
xsp_seed[idx] <<= 4;
xsp_seed[idx] |= c;
if (count >= 15)
{
idx++;
count = 0;
}
else
count++;
}
printf("seed: %16" PRIx64 "%16" PRIx64 "\n",
xsp_seed[0], xsp_seed[1]);
/* Set initial seed, byte 0. */
seed_cache[0].s[0] = xsp_seed[0];
seed_cache[0].s[1] = xsp_seed[1];
error = 0;
err4:
closedir(d);
err3:
free(device);
err2:
fclose(f);
err1:
if (error)
fprintf(stderr, "Unable to find seed, using generic\n");
}
/**
* @brief Call next() to generate a new random value until
* reach the physical disk from the first byte position.
*
* The reason behind this is to prevent the file from using
* a known seed, so the algorithm discovers the seed at runtime.
* Each call to next() assumes 1-byte random (yes, I'm ignoring
* the remaining 7 bytes the routine gives us).
*
* This routine also caches the results to optimize the
* performance of future invocations.
*
* @param target_byte How many next()s will be invoked.
*/
static void move_until_right_next(int64_t target_byte)
{
int64_t i; /* Seed cache index. */
int64_t idx; /* Loop index. */
int64_t target; /* Target byte. */
target = -1;
/* Cache miss?. */
i = target_byte >> SEED_STEP_LOG;
if (seed_cache[i].s[0] == 0 && seed_cache[i].s[1] == 0)
{
/* Cache miss, look for the nearest available index. */
for (idx = 0; idx < i; idx++)
if (seed_cache[idx].s[0] != 0 || seed_cache[idx].s[1] != 0)
target = idx;
}
else
target = i;
/* Set global seed to our recovered seed. */
xsp_seed[0] = seed_cache[target].s[0];
xsp_seed[1] = seed_cache[target].s[1];
/* Advance SEED_RND_BYTES per time. */
for (i = target*SEED_STEP; i < target_byte; i++)
{
/* Cache at every SEED_STEP. */
if (!(i & (SEED_STEP - 1)))
{
seed_cache[i >> SEED_STEP_LOG].s[0] = xsp_seed[0];
seed_cache[i >> SEED_STEP_LOG].s[1] = xsp_seed[1];
}
next();
}
}
/**
* @brief Xor a file, using its physical disk position
* as an index to generate a new seed from the start.
*
* @return Returns 0 if success, -1 otherwise.
*/
static int xor_file(const char *file)
{
int fd; /* Target file fd. */
off_t i; /* Loop index. */
struct stat sbuf; /* File stat buffer. */
unsigned char *buf; /* File content (mmap-ed). */
uint64_t first_byte; /* First physical disk byte. */
printf("Target file: %s\n", file);
fd = open(file, O_RDWR);
if (fd == -1)
{
fprintf(stderr, "Unable to open file (%s)\n", file);
return (1);
}
first_byte = get_first_phys_byte(fd);
/* mmap file. */
if (fstat(fd, &sbuf) == -1)
goto out1;
buf = mmap(NULL, sbuf.st_size, PROT_READ|PROT_WRITE,
MAP_SHARED, fd, 0);
if (buf == MAP_FAILED)
goto out1;
/* advance to our right-next(). */
printf(" Moving to right next for file: %s (%" PRId64 ")\n", file,
first_byte);
move_until_right_next((int64_t)first_byte);
/* xor file. */
printf(" Xoring file: %s (%" PRIx64 " - %" PRIx64")\n", file,
xsp_seed[0], xsp_seed[1]);
for (i = 0; i < sbuf.st_size; i++)
buf[i] ^= (next() >> 56);
munmap(buf, sbuf.st_size);
out1:
close(fd);
return (0);
}
/**
* @brief nftw() handler, xor one file per time
*
* @param filepath File path to be analyzed.
* @param info File path stat structure.
* @param typeflag File path type.
* @param pathinfo File path additional infos.
*
* @return Returns 0 if success, -1 otherwise.
*/
static int do_xor_file(const char *filepath, const struct stat *info,
const int typeflag, struct FTW *pathinfo)
{
((void)pathinfo);
/* Ignore everything that is not a file or is empty. */
if (typeflag != FTW_F || !info->st_size)
goto out;
xor_file(filepath);
out:
return (0);
}
/**
* @brief Alias for nftw() routine.
*
* @param path Path to traverse.
*
* @return Returns 0 on success, -1 otherwise.
*/
static int recurse_files(const char *path)
{
return (nftw(path, do_xor_file, 7, FTW_PHYS|FTW_MOUNT));
}
/* Main routine. */
int main(int argc, char **argv)
{
int i; /* Loop index. */
struct stat path_stat; /* Path stat. */
/* Emit warning before. */
printf("================ WARNING ================\n");
printf("Are you really sure you wanna execute it?\n"
"You have 5 seconds to cancel (Ctrl+C)\n");
sleep(5);
/*
* Reserve space for up to 1TiB hardisk, caching
* every SEED_STEP MiB.
*/
seed_cache = calloc(SEED_CACHE_SIZE, sizeof(*seed_cache));
if (!seed_cache)
panic("Unable to reserve room for our cache, aborting\n");
/* Creates and initialize seed from system info. */
init_seed();
/* Xor =). */
if (argc < 2)
recurse_files(".");
else
{
for (i = 1; i < argc; i++)
{
if (stat(argv[i], &path_stat) != 0)
panic("Error: cannot stat file: %s\n", argv[i]);
/* If regular file, xor-it. */
if (S_ISREG(path_stat.st_mode))
xor_file(argv[i]);
/* If path, recurse. */
else if (S_ISDIR(path_stat.st_mode))
recurse_files(argv[i]);
}
}
free(seed_cache);
return (0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment