Click here to Skip to main content
Click here to Skip to main content
Go to top

Navigating the PEB

, 19 May 2005
Rate this:
Please Sign up or sign in to vote.
Obtaining another process' command-line arguments.

Introduction

This article is a brief, and somewhat rehashed, introduction to the steps involved in obtaining the command-line arguments of a process other than the current process. The two primary functions involved are OpenProcess() and ReadProcessMemory(). Another function that is used, although not required, is NtQueryInformationProcess().

When I first looked at this problem, I thought that I could just run the GetCommandLine() function in the target process using CreateRemoteThread(). That turned out to be a bit too involved.

Getting the list of processes

There are several ways of getting the list of running processes. One is via the Process32First()/Process32Next() pair. The other is with EnumProcesses() followed by GetModuleFileNameEx() to get the path of the first module in the process which is usually the executable. For my example, I'll use the former.

HANDLE hProcessSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (INVALID_HANDLE_VALUE != hProcessSnapshot)
{
    PROCESSENTRY32 ProcessEntry = {0};
    ProcessEntry.dwSize = sizeof(PROCESSENTRY32);

    if (Process32First(hProcessSnapshot, &ProcessEntry) != FALSE)
    {
        do
        {
            // keep track of ProcessEntry.th32ProcessID here for later
        } while (Process32Next(hProcessSnapshot, &ProcessEntry) != FALSE);
    }

    CloseHandle(hProcessSnapshot);
}

Getting the PEB's starting address

Most of the literature that I read indicated that the starting address of the PEB was always located at memory address 0x7ffdf000. I did find one reference that indicated it to be a random address for Windows XP SP2. I did a very brief experiment on such a machine and found that the address was still located at 0x7ffdf000. That said, I went ahead and accounted for both by defaulting to 0x7ffdf000 but then possibly overriding that by calling NtQueryInformationProcess() like:

typedef LONG (WINAPI NTQIP)(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
NTQIP *lpfnNtQueryInformationProcess;

PROCESS_BASIC_INFORMATION pbi;
pbi.PebBaseAddress = (_PEB *) 0x7ffdf000;

DWORD dwSize;

HMODULE hLibrary = GetModuleHandle(_T("ntdll.dll"));
if (NULL != hLibrary)
{
    lpfnNtQueryInformationProcess = (NTQIP *) GetProcAddress(hLibrary, 
                                         "NtQueryInformationProcess");
    if (NULL != lpfnNtQueryInformationProcess)
        (*lpfnNtQueryInformationProcess)(hProcess, 
             ProcessBasicInformation, &pbi, sizeof(pbi), &dwSize);
}

I found that you could also use ZwQueryInformationProcess() as it has the same signature. With the starting address known, we can now read the PEB. One question that should have popped into your head is how we can read each process' PEB by specifying the same address. I'm going to hazard a guess and say that the magic of this lies in the depths of ReadProcessMemory(). Given that it takes a handle to the process and the address of the PEB, it must internally map that virtual address into a physical address before doing the actual reading.

Enabling the Debug access privilege

I learned some interesting and useful information from this project regarding privileges. While a particular privilege might be added to a user or group account, that does not necessarily mean that the privilege has been enabled. On my development machine, I am a member of the Administrators group, thus I have lots and lots of privileges. One of these is the Debug privilege (i.e., SeDebugPrivilege). Consequently I did not run into any access-related issues when trying to open a process or read a process' memory. It was not until Toby Opferman brought to my attention that the Debug privilege should be enabled.

To explore this further, I went to my other development machine and tried the sample project while logged in using the local Guest account. Sure enough, when trying to open several of the processes, I was presented with an error 5 (access denied). To remedy this, I simply added the local Group account to the Debug policy. At this point, the Debug privilege has been added to and enabled for the Guest account. I'm not sure what would disable this privilege under normal circumstances, but to account for that situation, I used the following:

HANDLE hToken;
TOKEN_PRIVILEGES tokenPriv;
LUID luidDebug;

if (OpenProcessToken(GetCurrentProcess(), 
    TOKEN_ADJUST_PRIVILEGES, &hToken) != FALSE)
{
    if (LookupPrivilegeValue(_T(""), SE_DEBUG_NAME, 
                              &luidDebug) != FALSE)
    {
        tokenPriv.PrivilegeCount           = 1;
        tokenPriv.Privileges[0].Luid       = luidDebug;
        tokenPriv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;

        AdjustTokenPrivileges(hToken, FALSE, 
                &tokenPriv, sizeof(tokenPriv), NULL, NULL);
    }
}

Navigating the PEB

This figure indicates that we must read the first 20 bytes of the PEB to get the address of the process' parameter information block. This is done with:

struct __PEB
{
    DWORD   dwFiller[4];
    DWORD   dwInfoBlockAddress;
} PEB;

HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | 
                              PROCESS_VM_READ, FALSE, dwProcessID);

ReadProcessMemory(hProcess, pbi.PebBaseAddress, &PEB, sizeof(PEB), &dwSize);

Although probably insignificant, I found that the address of the parameter information block was at address 0x20000 for all but some of the "special" system processes. This figure indicates that we must read the first 72 bytes of this block to get the address of the buffer that contains the command-line arguments. This is done with:

struct __INFOBLOCK
{
    DWORD   dwFiller[16];
    WORD    wLength;
    WORD    wMaxLength;
    DWORD   dwCmdLineAddress;
} Block;

ReadProcessMemory(hProcess, (LPVOID) PEB.dwInfoBlockAddress, 
                             &Block, sizeof(Block), &dwSize);

At this point, we know the address of the buffer containing the command-line arguments as well as the length of that buffer. Since the buffer is Unicode, we'll need to use a wide character type to hold the contents. We can allocate memory on the stack and set its size big enough (1K would probably suffice), or we can allocate memory on the heap by using the value contained in the length field preceding the buffer. I found that the difference between the two length fields was consistently 2 bytes. That would account for the '\0' character.

TCHAR *pszCmdLine = new TCHAR[Block.wMaxLength];

ReadProcessMemory(hProcess, (LPVOID) Block.dwCmdLineAddress, 
                  pszCmdLine, Block.wMaxLength, &dwSize);

Guess what? We now have the process' command-line arguments tucked nicely away in the pszCmdLine variable.

Don't forget to free up the memory when you are done with it.

Alternative approach

Almost halfway through this article, Christophe indicates that there are three ways of obtaining a process' command-line arguments. I dismissed the third option of capturing the output from Tlist because it just feels so cheesy. The second option seemed to be fairly straightforward but I am not familiar enough with all of the possible ramifications (e.g., authority) that could arise from injecting code into another's address space. Thus I opted to read through the PEB to get at the desired information.

I did find, however, that I could not access some of the processes running on my machine. Two errors that I frequently saw were ERROR_PARTIAL_COPY and ERROR_INVALID_PARAMETER. Getting access to and debugging a process is out of scope and not the intent of this article. I leave the task of accounting for those to the interested reader.

Enjoy!

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

Share

About the Author

DavidCrow
Software Developer (Senior) Pinnacle Business Systems
United States United States

The page you are looking for might have been removed, had its name changed, or is temporarily unavailable.
 
HTTP 404 - File not found
Internet Information Services

Comments and Discussions

 
AnswerOvercoming ERROR_PARTIAL_COPY Pinmemberi.Wahn5-May-10 6:28 
GeneralEnvironment variable of a process other than the current process Pinmembercsavie28-Jun-07 22:25 
QuestiongetModuleHandle? PinmemberMaliq Epstein21-Nov-06 4:20 
AnswerRe: getModuleHandle? PinmemberDavidCrow21-Nov-06 5:22 
QuestionAnyone would tell me? PinmemberJoshdai5-Mar-06 21:23 
AnswerRe: Anyone would tell me? PinmemberDavidCrow6-Mar-06 2:31 
GeneralRe: Anyone would tell me? PinmemberJoshdai6-Mar-06 4:18 
GeneralRe: Anyone would tell me? PinmemberDavidCrow6-Mar-06 5:51 
GeneralRe: Anyone would tell me? PinmemberRaymond Menard20-Mar-06 16:35 
GeneralRe: Anyone would tell me? PinmemberDavidCrow27-Mar-06 4:02 
GeneralThanks a lot..Great work..! PinmemberNagareshwar7-Sep-05 0:14 
GeneralPPEB_LDR_DATA ... PinmemberOrkblutt19-Jul-05 11:17 
GeneralRe: PPEB_LDR_DATA ... PinmemberDavidCrow25-Jul-05 8:56 
GeneralRe: PPEB_LDR_DATA ... PinmemberOrkblutt25-Jul-05 10:19 
Generalactual PEB structure Pinmemberakcom21-May-05 4:11 
GeneralRe: actual PEB structure PinmemberDavidCrow23-May-05 1:58 
GeneralNot bad Pinmemberrocky_pulley19-May-05 16:37 
GeneralRe: Not bad PinmemberAlexms24-May-05 10:43 
GeneralQuickView PinmemberToby Opferman19-May-05 13:29 
GeneralRe: QuickView PinmemberToby Opferman19-May-05 13:44 
GeneralRe: QuickView PinmemberDavidCrow20-May-05 4:52 
GeneralRe: QuickView PinmemberToby Opferman20-May-05 11:01 
GeneralRe: QuickView PinmemberDavidCrow23-May-05 2:10 
GeneralRe: QuickView PinmemberToby Opferman23-May-05 16:39 
GeneralRe: QuickView PinmemberToby Opferman23-May-05 16:44 
GeneralRe: QuickView PinmemberDavidCrow23-May-05 16:57 
GeneralRe: QuickView PinmemberDavidCrow23-May-05 16:55 
GeneralRe: QuickView PinmemberToby Opferman23-May-05 20:25 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web03 | 2.8.140922.1 | Last Updated 19 May 2005
Article Copyright 2005 by DavidCrow
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid