Search code examples
cfopenbinaryfiles

open a running binary file


I coding lite program in basic c who add (or sub) offset for each bytes in binary file passed in argument. This work for all other ELF, but when I'm trying to run :
$ ./a.out a.out 1 (add 0x01 to each bytes),
fopen() crash with the error message "Text file busy".
I checked with lsof, but is not opened on the file system.
I thought that when an executable is running, a image of file is loaded in RAM and the file is accessible.
If anyone know what about that, I'll take it ! Thank you for taking the time to read me !

Here is the code :

#include <stdlib.h>
#include <stdio.h>
#include <string.h>

void usage (char *pgr_name); // print usage and exit
void fatal(char *s); // print s, call perror("") and exit(-1)

// lite hexdump ([0xff] x16 | ...a...b...c...d )
void dump (const unsigned char *data_buffer, const unsigned int len);

int main(int argc, char **argv) {
    FILE *my, *crypted;
    void *content;
    unsigned char *ascii;
    char output[256] = "";
    int i, bytes_read, offset;

    if (argc < 3)
        usage(argv[0]);

    offset = atoi(argv[2]);
    if (offset < -255 || offset > 0xff) {
        printf("bad offset\n");
        usage(argv[0]);
    }

    printf("offset %d\n", offset);

    // open src
    if ((my = fopen(argv[1], "rb+")) == NULL)
        fatal("in opening argv[1]");

    // alloc memory for src
    if ((content = malloc (10000)) == NULL)
        fatal("in malloc");

    // read src
    bytes_read = fread(content, 1, 9999, my);
    printf("%d bytes read\n", bytes_read);

    // for reading easily
    ascii = (unsigned char *) content;

    dump(content, bytes_read);

    // apply offset on each bytes
    for (i=0; i<bytes_read; i++)
        ascii[i] = ascii[i] + offset;
    printf("\n\ntranslation complete\n\n");

    dump(content, bytes_read);

    strncpy(output, argv[1], 250);
    strcat(output, ".cry");

    // open dest
    if ((crypted = fopen(output, "wb+")) == NULL)
        fatal("in open crypted");

    // write src translated in dest
    bytes_read = fwrite(content, 1, bytes_read, crypted);
    printf("%d bytes written\n", bytes_read);

    // terminate pgrm
    fclose(crypted);
    fclose(my);
    free(content);

    return 0;
}

void fatal(char *s) {
    if (s) {
        fprintf(stderr, "[!] Fatal [!] : %s\n", s);
    }
    perror("");
    exit(-1);
}

void usage (char *pgr_name) {
    printf("Usage : %s <binary input> <offset [-255:255]>\n\n", pgr_name);
    exit(0);
}

void dump (const unsigned char *data_buffer, const unsigned int len) {
    unsigned char byte;
    unsigned int i, j;
    for (i=0; i<len; i++) {
        byte = data_buffer[i];
        printf("%02x ", data_buffer[i]);
        if (((i%16) == 15 ) || (i==len-1)) {
            for (j=0; j < 15-(i%16); j++) 
                printf("   ");
            printf("|  ");
            for (j=(i-(i%16)); j<=i; j++) {
                byte = data_buffer[j];
                if (byte > 31 && byte < 127)
                    printf("%c", byte);
                else
                    printf(".");
            }
            printf("\n");
        }
    }
}

Solution

  • Unix filesystems -- those used in Unix, BSDs, macOS, Linux, and so on -- rely on inodes. Several file names can refer to the same inode.

    If the filename is deleted, but the inode is still open in one or more processes, the inode has no file names that refer to it. You can still work with the open file normally -- extend it, for example -- but no other process can open it (unless you provide them with the open file descriptor somehow).

    When ELF binaries are executed, the underlying inode is locked by the kernel. Note: it is the inode that is locked, not the file name.

    This is because rather than "loading" the data into memory, most systems simply memory-map the data. In Linux, for example, this means that no matter how many copies of an executable or a dynamic library you have running, only one copy of the binary exists in RAM.

    In practice, this means that you cannot modify an ELF binary while it is being executed. However, you can rename or even delete the file, because it is the inode the filename refers to and not the filename that is locked by the kernel. (Of course, you can read ELF binaries just fine even if they are being executed; you just need to make sure you open them read-only, as opening them read-write will fail. This is because most Unix filesystems only check access permissions at open time.)

    What you can do, is create a new file; write the modified data; optionally copy the owner, group, mode, last access and last modification timestamps, and/or extended attributes (xattrs); and finally rename or hardlink the new file name over the old file name. (Renaming or hardlinking over an existing file just changes the inode the existing filename refers to, and therefore does not violate the inode locking.)

    This has the added benefit of the replacement (of the old binary) being atomic. Regardless of the moment at which other processes may open or execute the old binary, they will always only see either the old inode, or the new inode, never some sort of in-between version.