SayNoToWhiteBoys
SayNoToWhiteBoys

Reputation: 9

Examine environment variables on the stack

I know that environment variables are above the stack in memory, and I want to list them or at least be able to see them through examining the stack using gdb. First I take the address of the stack pointer but since the variables are placed above the stack, how do I know by how much I should increment the address?

(gdb) info register rsp
rsp            0x7fffffffdf48      0x7fffffffdf48
(gdb) x/32s $rsp + 0x(mmm..)

An example I saw while browsing the web was

x/32s $rsp + 0x240 // Why 0x240, exactly?

I think I misunderstood smth.

Upvotes: 1

Views: 1922

Answers (2)

Maxim Egorushkin
Maxim Egorushkin

Reputation: 136485

I know that environment variables are above the stack in memory,

It is Linux implementation detail that envp vector (along with argv) and its elements are stored in the stack memory of the main thread (only). System V Application Binary Interface AMD64 with LP64 and ILP32 Programming Models (see Figure 3.9: Initial Process Stack) requires that executable entry point _start gets %rsp which points to argc, followed by argv vector, followed by envp vector, all in the main thread stack. How much of the stack is consumed by _start (normally implemented by your language/compiler run-time) when it invokes main is an implementation detail which depends on the version and the vendor of the run-time, the ABI is only concerned with entry point _start.

An advanced defender can start other threads from the main thread and then terminate the main thread without terminating the process. This way, no thread stack contains the environment. It can also wipe its argv and envp and/or the elements the vectors point to, so that they become unavailable in the process address space. The latter, however, doesn't affect /proc/self/environ or /proc/self/cmdline maintained by the kernel.

The exact location is in envp argument to main. The C standard doesn't require anything beyond that, e.g. the thread stack is an implementation detail.

how do I know by how much I should increment the address?

That may depend on options the code was compiled with and the current stacktrace.

Ask x to display a larger array to see the environment variables:

x/2048s $rsp

Upvotes: 2

Employed Russian
Employed Russian

Reputation: 213829

Update:

I want to get the addresses of these variables in run-time with gdb.

That's trivial: step to the main level in GDB, print the value of the RBP register, and then use your understanding of stack layout (see below).

Example:

env -i FOO=ABC BAR=DEF HOME=/tmp gdb -q --args ./a.out foo bar baz
Reading symbols from ./a.out...
(gdb) start
Temporary breakpoint 1 at 0x11b8: file t.c, line 6.
Starting program: /tmp/a.out foo bar baz

Temporary breakpoint 1, main (argc=4, argv=0x7fffffffedb8) at t.c:6
6         printf("Argument vector:\n");

(gdb) p/x $rbp
$1 = 0x7fffffffecc0
(gdb) x/8gx $rbp
0x7fffffffecc0: 0x0000000000000000      0x00007ffff7df17fd
0x7fffffffecd0: 0x00007fffffffedb8      0x00000004f7fca000
0x7fffffffece0: 0x00005555555551a9      0x00007fffffffef79
0x7fffffffecf0: 0x00005555555552c0      0x9a3bd5580a4c103c

Above, you can see the &argv[0] == 0x00007fffffffedb8 as the 3rd word on the stack (the other two are the previous value of RBP and the return address). Use the first example code below to recover complete argv[] and envp[].


how do I know by how much I should increment the address?

If you are in main(), on Linux, you can know the exact layout of stack, and can examine argc, argv[] and envp[] using e.g. this code:

#include <stdio.h>

int main(int argc, char *argv[])
{
  int j;
  printf("Argument vector:\n");
  for (j = 0; ; j++) {
    if (argv[j] == NULL) break;
    printf("%2d: %p %s\n", j, argv[j], argv[j]);
  }
  printf("Environment vector:\n");
  for (j++; ; j++) {
    if (argv[j] == NULL) break;
    printf("%2d: %p %s\n", j, argv[j], argv[j]);
  }
  return 0;
}
$ gcc -g t.c && env -i FOO=ABC BAR=DEF HOME=/tmp ./a.out foo bar baz
Argument vector:
 0: 0x7ffd39e05fc2 ./a.out
 1: 0x7ffd39e05fca foo
 2: 0x7ffd39e05fce bar
 3: 0x7ffd39e05fd2 baz
Environment vector:
 5: 0x7ffd39e05fd6 FOO=ABC
 6: 0x7ffd39e05fde BAR=DEF
 7: 0x7ffd39e05fe6 HOME=/tmp

You could also leverage the fact that GLIBC actually passes envp[] to main() as the 3rd argument:

#include <stdio.h>

int main(int argc, char *argv[], char *envp[])
{
  printf("Environment vector:\n");
  for (int j= 0; ; j++) {
    if (envp[j] == NULL) break;
    printf("%2d: %p %s\n", j, envp[j], envp[j]);
  }
  return 0;
}
Environment vector:
 0: 0x7ffdef096fd6 FOO=ABC
 1: 0x7ffdef096fde BAR=DEF
 2: 0x7ffdef096fe6 HOME=/tmp

If you are not in main(), and can't have main() save &argv[0] in a global, then things get trickier.

You can parse /proc/self/maps to figure out stack boundaries, and examine the stack that way (see "stack set by kernel" section here).

Upvotes: 0

Related Questions