Reading another process’s environment

A reader of my Under The Hood columns writes:


I know the PID of a process, I need to get the environment strings of that process. My problem is, the environment string address of each process seems different. If I can get the environment string address of a process, I can use ReadProcessMemory to read the environment. The problem is, how can I get the environment string address if I know the process PID?


First, I need to point out that programs such as Process Explorer and TopToBottomNT have the ability to peek into any process and show the environment strings. There’s no use reinventing the wheel if somebody’s already done the hard work.


However, what about getting the environment from within your own code? As far as I’m aware, there are no supported APIs that let you do this. That said, if you’re willing to make some simplifying assumptions, it’s not too hard.  The assumptions:


  • You’re only running on an x86 machine
  • You only care about Windows NT/ Windows 2000 / Windows XP / Windows 2003 Server, and don’t care about Windows 95 and its descendents.
  • You can open a process handle for the PID in question

The trick is remarkably simple. Write a trivial program that calls GetEnvironmentStringsW, and step into it in the debugger’s assembly window mode. You’ll see code that looks like this:


mov  eax,fs:[0x18]              

mov  eax,dword ptr [eax+0x30]   

mov  eax,dword ptr [eax+0x10]   

mov  eax,dword ptr [eax+0x48]   



It’s relatively well known (including info from the Platform SDK’s WINTERNL.H file) that FS:[0x18] points to a thread’s Thread Environment Block (or TEB). Offset 0x30 within the TEB points to the Process Environment Block (or PEB.)


Knowing that the first two instructions of GetEnvironmentStringW put the address of the PEB into EAX, what can we infer? The third instruction grabs a DWORD value from offset 0x10 in the PEB. It must be a pointer to a structure of some kind because the fourth instruction dereferences that value (+ 0x48). What are these structures? It doesn’t really matter for our purposes, as you’ll soon see.


What good is it to know what GetEnvironmentStringsW does in your process? Consider that the code for GetEnvironmentStringsW will be the same in every process. Since the code is just a few short, simple instructions, you can effectively simulate the code in another process by using ReadProcessMemory. In other words, locate the PEB of the process you’re interested in, and then use ReadProcessMemory to emulate the actions of the third and fourth instructions.


How do you get the PEB of another process? Check out the WINTERNL.H file, which includes the NtQueryInformationProcess function and the PROCESS_BASIC_INFORMATION structure. I won’t post a code sample here that performs all the steps described here. Think of it as an exercise for the reader. J


Finally, bear in mind that the data structures and APIs described here could change in the future. Use them at your own risk, and litter your code with asserts and other sanity checks.


Embarrassing postscript: I had completely forgotten about WINTERNL.H, and only rediscovered it while doing a Google search for some relevant terms. Along the way, I learned that a bunch of my old, but still useful MSDN content doesn’t seem to be on the MSDN site anymore. I’ll have to see what I can do about rectifying that.

Comments (3)

  1. First, I would like to thank you Matt for your blog. I’m missing your columns in MSJ/MSDN :^)

    Coming back to environment variables, your reeader can take a look at:

    where it is explained (and detailed with source code) how to use CreateRemoteThread() in order to get this information in addition to the command line using the technics your are describing here (remotely reading the PEB)

  2. Fei Liu says:

    "Along the way, I learned that a bunch of my old, but still useful MSDN content doesn’t seem to be on the MSDN site anymore. I’ll have to see what I can do about rectifying that."

    Matt, what are you referring to here? I know some of the UTH articles before 1995 are no longer available online. Some of those are still pretty good.

Skip to main content