Hide Arguments to Program Without Source Code – Linux Process Management

argumentslinuxprocessps

I need to hide some sensitive arguments to a program I am running, but I don't have access to the source code. I am also running this on a shared server so I can't use something like hidepid because I don't have sudo privileges.

Here are some things I have tried:

  • export SECRET=[my arguments], followed by a call to ./program $SECRET, but this doesn't seem to help.

  • ./program `cat secret.txt` where secret.txt contains my arguments, but the almighty ps is able to sniff out my secrets.

Is there any other way to hide my arguments that doesn't involve admin intervention?

Best Answer

As explained here, Linux puts a program's arguments in the program's data space, and keeps a pointer to the start of this area. This is what is used by ps and so on to find and show the program arguments.

Since the data is in the program's space, it can manipulate it. Doing this without changing the program itself involves loading a shim with a main() function that will be called before the real main of the program. This shim can copy the real arguments to a new space, then overwrite the original arguments so that ps will just see nuls.

The following C code does this.

/* https://unix.stackexchange.com/a/403918/119298
 * capture calls to a routine and replace with your code
 * gcc -Wall -O2 -fpic -shared -ldl -o shim_main.so shim_main.c
 * LD_PRELOAD=/.../shim_main.so theprogram theargs...
 */
#define _GNU_SOURCE /* needed to get RTLD_NEXT defined in dlfcn.h */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>
#include <dlfcn.h>

typedef int (*pfi)(int, char **, char **);
static pfi real_main;

/* copy argv to new location */
char **copyargs(int argc, char** argv){
    char **newargv = malloc((argc+1)*sizeof(*argv));
    char *from,*to;
    int i,len;

    for(i = 0; i<argc; i++){
        from = argv[i];
        len = strlen(from)+1;
        to = malloc(len);
        memcpy(to,from,len);
        memset(from,'\0',len);    /* zap old argv space */
        newargv[i] = to;
        argv[i] = 0;
    }
    newargv[argc] = 0;
    return newargv;
}

static int mymain(int argc, char** argv, char** env) {
    fprintf(stderr, "main argc %d\n", argc);
    return real_main(argc, copyargs(argc,argv), env);
}

int __libc_start_main(pfi main, int argc,
                      char **ubp_av, void (*init) (void),
                      void (*fini)(void),
                      void (*rtld_fini)(void), void (*stack_end)){
    static int (*real___libc_start_main)() = NULL;

    if (!real___libc_start_main) {
        char *error;
        real___libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
        if ((error = dlerror()) != NULL) {
            fprintf(stderr, "%s\n", error);
            exit(1);
        }
    }
    real_main = main;
    return real___libc_start_main(mymain, argc, ubp_av, init, fini,
            rtld_fini, stack_end);
}

It is not possible to intervene on main(), but you can intervene on the standard C library function __libc_start_main, which goes on to call main. Compile this file shim_main.c as noted in the comment at the start, and run it as shown. I've left a printf in the code so you check that it is actually being called. For example, run

LD_PRELOAD=/tmp/shim_main.so /bin/sleep 100

then do a ps and you will see a blank command and args being shown.

There is still a small amount of time that the command args may be visible. To avoid this, you could, for example, change the shim to read your secret from a file and add it to the args passed to the program.

Related Question