3

In the main function, program authors can choose to receive the char * array as a parameter to main that points to the environment variables

int main(int argc, char **argv, char **envp) {
...
}

The point being here is the envp pointer is wanted, but not from the perspective of the program author with the source. The question is, at runtime, how can one know the location of the environment variables, even if the binary being run is stripped?

Basically, I've figured out how to do this reliably on 32 and 64 bit linux for argv; I can know how many command arguments there are, where they are and what they are. And I can do this for envp on 32 bits, but not 64 bits. Does anybody know a technique to do this reliably for 64 bits at runtime in linux on stripped binaries?

  • Aren't they simply at the top stackframe? – Ditmar Wendt Jan 21 '15 at 5:50
  • Yeah, but what constitutes precisely where is, I think, affected by compiler and libc version, and other variables. If the software author uses int main(void), does the compiler choose to pass 0 to _init? What does it do about environment variables and command line parameters in that case? It's not consistent across even 32/64 bits because how many bytes an int is by default isn't really reliable. The question concerns how to make a tool to do this in a fully automated fashion by injecting a hook at _init. – Adam Miller Jan 21 '15 at 12:49
1

The global variable __environ holds a pointer to the start of the list. In my tests* it was exported by libm.so

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

// Slightly modded from http://linux.die.net/man/3/dlopen
static char*** _findEnviron()
{
    void *handle;
        char *error,
        ***ret = NULL; //! Pointer to a ** list

    handle = dlopen("libm.so", RTLD_LAZY);
        if (!handle) {
        fprintf(stderr, "%s\n", dlerror());
        return NULL;
        }

    dlerror();    /* Clear any existing error */

    ret = dlsym(handle, "__environ");

    if ((error = dlerror()) != NULL)  {
        fprintf(stderr, "%s\n", error);
        dlclose(handle);
        return NULL;
    }

    dlclose(handle);

    return ret;
}

void print(char** p)
{
    while(p && *p)
    {
        printf("%p --> %s\n", *p, *p);
        ++p;
    }
}

int main(int argc, char** argv, char** envp)
{
    char** pEnviron = NULL;

    printf("Find %p\n", envp);
    pEnviron = *_findEnviron();
    printf("Found %p\n", pEnviron);

    print(envp);
    printf("---\n");
    print(pEnviron);

    getc(stdin);
}
    1. Debian 2.6.32; ldd (Debian EGLIBC 2.13-38+deb7u8) 2.13
    2. CentOS7 3.10.0; ldd (GNU libc) 2.17
  • I'm running in the context of a dynamic binary instrumentation tool. I don't want source code that would work in the target. I have to explore the stack manually, otherwise I would just use argc, argv and envp. Thanks for your effort though. – Adam Miller May 21 '15 at 21:21
  • Can your tool run code inside that process? Or can your tool be instrumented in any way. – user45891 May 21 '15 at 21:24
  • It doesn't really make sense to use your code the way you think because the compiler doesn't have the information required for the source code that you've written in order to produce meaningful assembler in the context of any target. That's why the question is hard. – Adam Miller May 21 '15 at 21:28
  • Also, the context from which I'm speaking is using pintools, and pin uses basically two processes inside of one. The pintools have their own command line arguments and environment variables, which is likely the result this will produce. – Adam Miller May 21 '15 at 21:29
0

As it turns out-and while this doesn't exactly constitute a reliable, cross compiler, cross os, cross 32/64 bit solution, it works on my machine-they are on the stack, and if you navigate just past the location of argc on the stack you can see that the pointer to environment variables is argc*sizeof(uintptr_t) + argv, and argv is contiguous to argc naturally.

You can programmatically read the environment variables by continuously doing strcpy (I know it's crap api, but they are null terminated). Wait until you get a null byte back and no string at all-that signifies the end.

It's not the same quality of answer I might have hoped, but for now it suffices.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.