Last active
February 16, 2022 01:42
-
-
Save Theldus/82e85532a973891feea52b1c89f7769e to your computer and use it in GitHub Desktop.
Encrypt files recursively using XOR and PRNG.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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